I'm trying to create a typescript enforcer over EventEmitter. My goal is to create a emitter with addListener, removeListener and emit methods that will break on compiler time if I pass a invalid event or even if I pass a function that doesn't match with event signature (args and return).
For this to be possible, I'll need a Map interface (some interface with a list of functions) an example of this can be seen on type native GlobalEventHandlersEventMap in:
lib.dom.d.ts
//...
interface GlobalEventHandlersEventMap {
"abort": UIEvent;
"animationcancel": AnimationEvent;
"animationend": AnimationEvent;
"animationiteration": AnimationEvent;
//...
So far I got the first two methods right:
import { EventEmitter } from "events";
// A simple mapping interface
interface SomeMap {
greeting: (message: string) => void;
}
// This generalization of function is necessary to track
// the list of arguments and result types.
interface GFunc<TArgs extends any[] = any[], TResult = any> {
(...args: TArgs): TResult;
}
// This is a advanced type to mask the EventEmitter class
interface AdvEventEmitter<T extends { [K in keyof T]: GFunc }> {
addListener<K extends keyof T>(event: K, listener: T[K]): void;
removeListener<K extends keyof T>(event: K, listener: T[K]): void;
}
const emitter: AdvEventEmitter<SomeMap> = new EventEmitter();
emitter.addListener('greeting', msg => {
console.log(msg);
});
In the code above, AdvEventEmitter interface is capable to enforce constraint on the first parameter:
emitter.addListener('something_else', () => {});
Msg: Argument of type '"something_else"' is not assignable to parameter of type '"greeting"'.
And even enforce types and quantity of arguments in the second parameter:
emitter.addListener('greeting', (m1, m2) => {
console.log(m1, m2);
});
Msg: Argument of type '(m1: any, m2: any) => void' is not assignable to parameter of
type '(message: string) => void'.
Great.
Now the problem is the emit method.
I'm trying something like this:
interface AdvEventEmitter<T extends { [K in keyof T]: GFunc }> {
// ...
emit<K extends keyof T, TArgs extends any[] = any[]>(event: K, ...args: TArgs): void;
}
the arg event is validated correctly (as expected), but the args just a generic list of any. I'm don't know how to connect TArgs constraints with K constraints.
Is there a way to enforce this constrain?