// Created on savesnippets.com · https://savesnippets.com/cUc8BMnTzea0qe type Listener = (payload: T) => void; export class TypedEmitter> { private listeners: { [K in keyof Events]?: Listener[] } = {}; on(event: K, fn: Listener): () => void { (this.listeners[event] ??= []).push(fn); return () => this.off(event, fn); } off(event: K, fn: Listener): void { this.listeners[event] = this.listeners[event]?.filter(l => l !== fn); } emit(event: K, payload: Events[K]): void { this.listeners[event]?.forEach(l => l(payload)); } } type AppEvents = { 'user:login': { id: number; name: string }; 'user:logout': { id: number }; }; const bus = new TypedEmitter(); bus.on('user:login', u => console.log(`hi ${u.name}`)); // u: { id; name } bus.emit('user:login', { id: 1, name: 'Alice' }); // ✓ // bus.emit('user:login', { id: 1 }); // ✗ missing 'name'