Last active
August 14, 2019 16:34
-
-
Save gund/0ceb9c047d52e8755afbf89fe67878ba to your computer and use it in GitHub Desktop.
Example of extensible type definitions in TypeScript leveraging interface and enum merging feature. REPL: https://repl.it/@gund/Extensible-type-definitions-in-TypeScript
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { registerMeta } from './event'; | |
enum EventKind { | |
Open = 'open', | |
} | |
interface EventMetaRegistry { | |
[EventKind.Open]: EventOpenMeta; | |
} | |
interface EventOpenMeta { | |
kind: EventKind.Open; | |
documentId: string; | |
} | |
registerMeta(EventKind.Open, () => ({ documentId: '123' })); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { registerMeta } from './event'; | |
enum EventKind { | |
Other = 'other', | |
} | |
interface EventMetaRegistry { | |
[EventKind.Other]: EventOtherMeta; | |
} | |
interface EventOtherMeta { | |
kind: EventKind.Other; | |
otherStuff: boolean; | |
} | |
registerMeta(EventKind.Other, event => ({ | |
otherStuff: event.url.startsWith('https://'), | |
})); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
interface Event { | |
url: string; | |
} | |
interface EventWithMeta extends Event { | |
meta: EventMeta; | |
} | |
enum EventKind {} | |
interface EventMetaRegistry {} | |
interface EventMetaBase<K extends EventKind> { | |
kind: K; | |
} | |
type EventMeta = EventMetaRegistry extends { [k in keyof EventMetaRegistry]: infer T } | |
? T | |
: never; | |
type MetaWithoutKind<T extends EventMeta> = Pick<T, Exclude<keyof T, 'kind'>>; | |
type InferEventMetaBy<K extends EventKind> = Exclude< | |
EventMeta, | |
EventMetaBase<Exclude<EventKind, K>> | |
>; | |
const metaProcessors: ({ | |
kind: EventKind; | |
metaFn: (event: Event) => MetaWithoutKind<any>; | |
})[] = []; | |
export function registerMeta<K extends EventKind, M extends EventMeta = InferEventMetaBy<K>>( | |
kind: K, | |
metaFn: (event: Event) => MetaWithoutKind<M>, | |
) { | |
metaProcessors.push({ kind, metaFn }); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const events: Event[] = []; // Some array of events | |
// Now all events have meta data | |
const eventsWithMeta = events.map(event => | |
metaProcessors.reduce( | |
(e, { kind, metaFn }) => ({ | |
...e, | |
meta: { | |
kind, | |
...e.meta, | |
...metaFn(event), | |
}, | |
}), | |
event as EventWithMeta, | |
), | |
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const evt: EventWithMeta = {} as any; // Some processed event with meta data | |
// Complete typesafety here | |
if (evt.meta.kind === EventKind.Open) { | |
console.log(evt.meta.documentId); | |
} | |
// And here | |
if (evt.meta.kind === EventKind.Other) { | |
console.log(evt.meta.otherStuff); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment