Skip to content

Instantly share code, notes, and snippets.

@gund
Last active August 14, 2019 16:34
Show Gist options
  • Save gund/0ceb9c047d52e8755afbf89fe67878ba to your computer and use it in GitHub Desktop.
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
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' }));
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://'),
}));
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 });
}
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,
),
);
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