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.

Or clone locally

git clone -b challenge/2026-07-24 https://github.com/niltonheck/typedrop.git