Type-safe event emitter
An event emitter whose event names and payloads are checked by the compiler through a generic map type.
- TypeScript
- GreenSnippets
- MIT
- Executed with Node.js 22 (type stripping)
type Handler<T> = (payload: T) => void;
class Emitter<Events extends Record<string, unknown>> {
private handlers: { [K in keyof Events]?: Set<Handler<Events[K]>> } = {};
on<K extends keyof Events>(event: K, handler: Handler<Events[K]>): () => void {
(this.handlers[event] ??= new Set()).add(handler);
return () => this.handlers[event]?.delete(handler);
}
emit<K extends keyof Events>(event: K, payload: Events[K]): void {
for (const handler of [...(this.handlers[event] ?? [])]) handler(payload);
}
}Example / expected output
const bus = new Emitter<{ login: { userId: string }; logout: undefined }>();
bus.on("login", ({ userId }) => console.log(userId));
bus.emit("login", { userId: "42" });How it works
Describe your events once as a map from name to payload type. on and emit are generic over the key, so a wrong event name or payload is a compile error and handlers get the exact payload type.
Related snippets
- TypeScript Tested
Typed Object.keys and Object.entries
Helpers that keep key types instead of widening to string, for objects you know the shape of.
- TypeScript Tested
Generic groupBy with key inference
Group an array into a Record by a property or function while keeping the item type.
- TypeScript Tested
Debounce with preserved argument types
A debounce function whose returned function keeps the parameter types and exposes cancel() and flush().
- JavaScript Tested
A tiny event emitter
Publish and subscribe to named events in about 15 lines, with an unsubscribe function returned from on().

