TypeDrop
2026-07-24 Challenge
2026-07-24
Hard
Typed Event Emitter with Wildcard Subscriptions & Replay
You're building the real-time notification backbone for a collaborative document editor. Components subscribe to strongly-typed events (e.g. `"cursor:moved"`, `"doc:saved"`, `"user:joined"`), and late-joining subscribers can replay the last N events they missed — all with zero type-unsafe escape hatches.
Goals
- Define `AnyEvent<TMap>` as a distributive mapped type that produces a discriminated union `{ event: K; payload: TMap[K] }` for every key `K` in `TMap`.
- Implement `createEmitter<TMap>()` with internal per-event handler sets and a history store — all typed without `any`.
- Implement `.replay(event, n, handler)` so it immediately delivers the last `n` historical payloads oldest-first, then keeps the handler subscribed for future emissions.
- Ensure `.history(event)` returns `ReadonlyArray<TMap[K]>` for the specific key `K` — never a wider union type.
challenge.ts
// Core types you must define and implement:
export type BaseEventMap = Record<string, unknown>;
// TODO: make this a distributive mapped type — a discriminated union
// where each member is { event: K; payload: TMap[K] } for every K in TMap.
export type AnyEvent<TMap extends BaseEventMap> = never; // ← your work here
export type Unsubscribe = () => void;
export type EventHandler<TPayload> = (payload: TPayload) => void;
export type AnyEventHandler<TMap extends BaseEventMap> = (event: AnyEvent<TMap>) => void;
export interface Emitter<TMap extends BaseEventMap> {
on<K extends keyof TMap>(event: K, handler: EventHandler<TMap[K]>): Unsubscribe;
once<K extends keyof TMap>(event: K, handler: EventHandler<TMap[K]>): Unsubscribe;
onAny(handler: AnyEventHandler<TMap>): Unsubscribe;
emit<K extends keyof TMap>(event: K, payload: TMap[K]): void;
replay<K extends keyof TMap>(event: K, n: number, handler: EventHandler<TMap[K]>): Unsubscribe;
history<K extends keyof TMap>(event: K): ReadonlyArray<TMap[K]>;
off<K extends keyof TMap>(event: K, handler: EventHandler<TMap[K]>): void;
}
export function createEmitter<TMap extends BaseEventMap>(): Emitter<TMap> {
throw new Error("TODO: implement createEmitter");
}
Hints (click to reveal)
Hints
- For `AnyEvent<TMap>`, try a mapped type that distributes over `keyof TMap`: `{ [K in keyof TMap]: { event: K; payload: TMap[K] } }[keyof TMap]`.
- Store handlers in a `Map<keyof TMap, Set<EventHandler<TMap[keyof TMap]>>>` — you'll need a small type cast-free helper to retrieve and widen per-key, or store them as `Map<K, Set<...>>` inside a generic helper function.
- For `.replay`, slice `history(event).slice(-n)` to get the last N entries, iterate oldest-first, then call `.on` and return its unsubscribe handle.
Useful resources
Or clone locally
git clone -b challenge/2026-07-24 https://github.com/niltonheck/typedrop.git