I am attempting to write a TypeScript wrapper around an existing JavaScript system that posts and sends events, for the sake of this question, the underlying JavaScript system cannot be changed.
The underlying event system is untyped, and essentially has publish
and subscribe
events, like so.
publish(eventName: string, ...args: any[])
subscribe(eventName: string, callback: Function)
Without typing, you would subscribe or publish to an event like so.
subscribe("myEvent", (myNumber: number, myString: string) => {
//...
};
publish('myEvent', 1, 'hello');
I am attempting to write a type safe wrapper around this, which currently looks like so.
type MyEvent = {
myNumber: number;
myString: string;
};
type EventDefs = {
"myEvent": MyEvent
};
type Event = keyof EventDefs;
function typedSubscribe<T extends Events>(
eventName: T,
callback: (payload: EventDefs[T]) => void
): void {
subscribe(eventName, (...args: unknown[]) => {
callback(args);
});
}
However obviously at this point unknown[]
or any[]
cannot be infered to EventDefs[T]
by the compiler.
On the point of subscribe
it is safe to assume the arguments are correct.
How do I narrow the unknown[]
type of ...args
to EventDefs[T]
? For example, when subscribing to myEvent
, the event type would be MyEvent
.
Tricky part is to convert record type to tuple of its fields' types. I used this answer, slightly modified. Note that in some cases this transformation is ambiguous and faulty (see mentioned question).
Unfortunately, there are still problems with optional properties/parameters.