HardPro challengeJavaScriptTypeScript

TypedEmitter<EventMap>

TypeScriptTypesPatterns

A standard EventEmitter accepts string events and any payloads. A TypedEmitter uses a generic EventMap to enforce correct types per event:

type AppEvents = {
  'user:login': { userId: string; at: Date };
  'challenge:solved': { challengeId: string; score: number };
  'error': Error;
};

const emitter = new TypedEmitter<AppEvents>();

// ✅ TypeScript enforces the correct payload type:
emitter.on('user:login', ({ userId, at }) => { ... });
emitter.emit('user:login', { userId: 'u_1', at: new Date() });

// ❌ TypeScript errors:
emitter.on('unknown-event', () => {});              // not in AppEvents
emitter.emit('user:login', { message: 'hello' }); // wrong payload shape

The type key: K extends keyof TMap

on<K extends keyof TMap>(event: K, handler: (payload: TMap[K]) => void): this
//   ↑ binds K to a valid event name
//                                   ↑ payload type is derived from the event name

Implement

MethodBehavior
on(event, handler)Subscribe (chainable)
off(event, handler)Unsubscribe (chainable)
emit(event, payload)Call all handlers
once(event, handler)Subscribe, auto-unsubscribe after first call

Sample tests

Test #1on then emit — handler receives payload
Input: [[["on","click","h1"],["emit","click",{"x":1,"y":2}]]]
Output: [{"event":"click","payload":{"x":1,"y":2}}]
Test #2emit fires handler each time
Input: [[["on","data","h1"],["emit","data",42],["emit","data",99]]]
Output: [{"event":"data","payload":42},{"event":"data","payload":99}]
Test #3off unsubscribes the handler
Input: [[["on","msg","h1"],["off","msg","h1"],["emit","msg","hello"]]]
Output: []
Test #4once only fires on first emit
Input: [[["once","ping","h1"],["emit","ping",1],["emit","ping",2]]]
Output: [{"event":"ping","payload":1}]
Test #5emit with no handlers is a no-op
Input: [[["emit","no-listeners","anything"]]]
Output: []