I am trying to implement an extensible EventEmitter
pattern in TypeScript.
This is the way it is supposed to be used:
class DataEmitter<T, _EE extends Dict<any[]>> extends EventEmitter<DefaultMerge<_EE, { data: [T] }>> {
// ...
}
So any class extending DataEmitter
might pass another object type defining new events it is capable of emitting, and so on.
This is how I tried to define it:
type Listener<T extends any[]> = (...args: T) => void
type Dict<V> = {
[name: string]: V
}
type DefaultMerge<A, B> = A & B
type EventEmitter_EE<_EE extends Dict<any[]> = {}> = DefaultMerge<
_EE,
{
listen: [{ event: 'listen' | keyof _EE }]
}
>
interface EE<_EE extends Dict<any[]> = {}> {
addListener<K extends keyof _EE>(
event: K,
listener: Listener<_EE[K]>
): void
emit<K extends keyof _EE>(event: K, ...args: _EE[K]): void
}
class EventEmitter<_EE extends Dict<any[]> = {}>
implements EE<EventEmitter_EE<_EE>>
{
private __listeners: {
[K in keyof EventEmitter_EE<_EE>]?: Listener<EventEmitter_EE<_EE>[K]>[]
} = {}
addListener<K extends keyof EventEmitter_EE<_EE>>(
event: K,
listener: Listener<EventEmitter_EE<_EE>[K]>
): void {
this.__listeners[event] = this.__listeners[event] || []
this.__listeners[event].push(listener)
// >> TypeScript Error << \\
this.emit('listen', { event })
}
emit<K extends keyof EventEmitter_EE<_EE>>(
event: K,
...args: EventEmitter_EE<_EE>[K]
): void {
const listeners = [...(this.__listeners[event] || [])]
for (const listener of listeners) {
listener.call(this, ...args)
}
}
}
Note the EventEmitter
class comes with a default event called "listen"
.
However, TypeScript complains about the referred line with the following error message:
Argument of type '[{ event: K; }]' is not assignable to parameter of type
'_EE["listen"] & [{ event: "listen" | keyof _EE; }]'.
Type '[{ event: K; }]' is not assignable to type '_EE["listen"]'.
Ideally, it should understand that "listen"
is a default event of EventEmitter
and infer its type to be "listen" | keyof _EE
as defined.
My guess is that DefaultMerge<A, B>
is not well defined; it should return an object type that will "first look in B, then, if not defined, look at A".
How do I get rid of this error?