type EventMap = {
login: { userId: string; timestamp: Date };
logout: { userId: string };
purchase: { productId: string; amount: number };
};
class TypedEventEmitter<T extends Record<string, any>> {
private listeners = new Map<keyof T, Set<(data: any) => void>>();
on<K extends keyof T>(event: K, listener: (data: T[K]) => void): void {
if (!this.listeners.has(event)) {
this.listeners.set(event, new Set());
}
this.listeners.get(event)!.add(listener);
}
emit<K extends keyof T>(event: K, data: T[K]): void {
this.listeners.get(event)?.forEach((listener) => listener(data));
}
off<K extends keyof T>(event: K, listener: (data: T[K]) => void): void {
this.listeners.get(event)?.delete(listener);
}
}
const emitter = new TypedEventEmitter<EventMap>();
// Fully type-safe!
emitter.on('login', (data) => {
console.log(data.userId); // string
console.log(data.timestamp); // Date
});
emitter.emit('login', { userId: '123', timestamp: new Date() });
// emitter.emit('login', { userId: 123 }); // ❌ Error