typescript / expert
Snippet
Type-Safe Event Listener Tuple Invariance Enforcement
This snippet demonstrates how to leverage mapped type index access, generic constraint bounds, and variadic tuple types to create a strictly typed event emitter. Event payloads are declared as tuples, ensuring that handler function signatures and `emit` arguments are checked at compile-time without type assertions.
snippet.ts
typescript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
type EventMap = {userLogin: [userId: string, timestamp: number];dataSync: [status: 'pending' | 'complete', payload: ReadonlyArray<number>];};type EventListener<T extends unknown[]> = (...args: T) => void | Promise<void>;class TypedEventEmitter<E extends Record<string, unknown[]>> {private listeners: { [K in keyof E]?: Array<EventListener<E[K]>> } = {};on<K extends keyof E>(event: K, listener: EventListener<E[K]>): void {(this.listeners[event] ??= []).push(listener);}async emit<K extends keyof E>(event: K, ...args: E[K]): Promise<void> {const handlers = this.listeners[event] ?? [];await Promise.all(handlers.map((fn) => fn(...args)));}}
Breakdown
1
type EventMap = { ... };
Defines a central type map associating event names with tuple types representing parameter lists.
2
type EventListener<T extends unknown[]> = (...args: T) => void | Promise<void>;
Uses variadic tuple types to map generic tuple types directly into strongly typed function parameters.
3
private listeners: { [K in keyof E]?: Array<EventListener<E[K]>> } = {};
Creates a mapped type dictionary holding arrays of typed listeners indexed by event keys.
4
on<K extends keyof E>(event: K, listener: EventListener<E[K]>): void
Constrains event registration to valid keys of E and enforces exact listener parameter signature matching.
5
async emit<K extends keyof E>(event: K, ...args: E[K]): Promise<void>
Uses rest parameter tuple expansion to enforce compile-time verification of payload arguments when emitting events.