I have been using a strongly typed event emitter interface in Typescript for a while, but now I need it to support subclasses that add own events to it. At some point Typescript fails to be aware of the base class events.
Here is the code in a condensed version (Playground Link):
type UnionToIntersection<U> =
(U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
type AddParameters<ListenersT, EventT> =
ListenersT extends (...args: infer ArgsT) => void
? (event: EventT, ...args: ArgsT) => Promise<boolean>
: never;
type EmitSignatures<ListenersT> =
{ [EventT in keyof ListenersT]: AddParameters<ListenersT[EventT], EventT> };
type EmitAll<ListenersT> = UnionToIntersection<EmitSignatures<ListenersT>[keyof ListenersT]>
type OnSignatures<ListenersT, ReturnT> =
{ [EventT in keyof ListenersT]: (event: EventT, listener: ListenersT[EventT]) => ReturnT };
type OnAll<ListenersT, ReturnT> =
UnionToIntersection<OnSignatures<ListenersT, ReturnT>[keyof ListenersT]>;
type EventEmitter<ListenersT> = EmitterInterface<ListenersT>;
export interface EmitterInterface<ListenersT>
{
emit: EmitAll<ListenersT>;
on: OnAll<ListenersT, this>;
}
/////////////////////////////////////////////////////////////////////////////////////////////
interface VehicleEvents
{
accelerate(acceleration: number): void;
brake(deceleration: number): void;
}
interface BusEvents extends VehicleEvents
{
doorStateChange(front: boolean, middle: boolean, rear: boolean): void
}
interface Vehicle<E extends VehicleEvents> extends EventEmitter<E>
{
onSig: OnSignatures<E, this>;
onSigs: OnSignatures<E, this>[keyof E];
}
class Vehicle<E extends VehicleEvents>
{
public constructor()
{ this.on('brake', () => this.flashBrakeLights()); } // supposed to work?
public flashBrakeLights(): void {}
public hitTheGas(strength: number): void
{ this.emit('accelerate', strength * 42); } // supposed to work?
public test(): void
{
this.onSig.accelerate;
this.onSig.brake;
this.onSigs('accelerate', (a) => undefined); // error I don't understand
this.onSigs('brake', (d) => undefined); // error I don't understand
this.onSigs('foo', () => undefined); // supposed to error
}
}
interface Bus extends EventEmitter<BusEvents> {}
class Bus extends Vehicle<BusEvents>
{
public doorState: [boolean, boolean, boolean] = [false, false, false];
public constructor()
{
super();
this.on('accelerate', () => {
this.door(0, false);
this.door(1, false);
this.door(2, false);
});
}
public door(index: number, state: boolean): void
{
this.doorState[index] = state;
this.emit('doorStateChange', ...this.doorState);
}
}
export const bus = new Bus();
The E
type is declared as extension of VehicleEvents
, which should be enough to let Typescript know there are the accelerate
and brake
events, shouldn't it?
Any explanations for why this doesn't work? Any ideas for how to fix this or to achieve what I need in another way?