TypeDrop
2026-10-18 Challenge
2026-10-18
Hard
Typed Event Emitter State Machine
You're building the session-lifecycle engine for a real-time collaboration tool. User sessions move through a strict set of states (Idle → Connecting → Active → Suspended → Closed), and every legal transition must fire a strongly-typed event — the compiler must guarantee that only valid transitions are dispatched, that each event's payload matches its transition, and that listeners receive exactly the right payload type for the event they subscribed to.
Goals
- Define `SessionEventMap`, `Transition`, `LegalTransitions`, `DispatchFor`, and `Listener` so that all type relationships are enforced at compile time with no `any` or type assertions.
- Implement `SessionMachine` so that `dispatch()` validates the current state against legal transitions at runtime, updates state on success, notifies all subscribed listeners with the correctly-typed payload, and returns `false` without changing state on illegal transitions.
- Implement `on()` so it registers a listener for a specific event and returns a working unsubscribe function that prevents future invocations of that listener.
- Implement the standalone `once` helper so it subscribes a listener that automatically unsubscribes itself after its first invocation, with full generic typing.
challenge.ts
// Key types and main class signature
export interface SessionEventMap {
"session:connecting": { sessionId: string; url: string };
"session:active": { sessionId: string; userId: string; joinedAt: Date };
"session:suspended": { sessionId: string; reason: string; suspendedAt: Date };
"session:resumed": { sessionId: string; resumedAt: Date };
"session:closed": { sessionId: string; code: number; message: string };
}
export type Transition<
From extends SessionState,
To extends SessionState,
Event extends keyof SessionEventMap
> = { from: From; to: To; event: Event };
export type Listener<E extends keyof SessionEventMap> =
(payload: SessionEventMap[E]) => void;
export class SessionMachine {
on<E extends keyof SessionEventMap>(
event: E,
listener: Listener<E>
): () => void { /* ... */ }
dispatch<E extends keyof SessionEventMap>(
event: E,
payload: SessionEventMap[E]
): boolean { /* ... */ }
getState(): SessionState { /* ... */ }
}
Hints (click to reveal)
Hints
- Use `Extract<LegalTransition, { event: E }>` inside `DispatchFor<E>` to filter the union of all legal transitions down to only those relevant for a given event — this gives you both the valid `from` states and the `to` state for free.
- Store listeners in a `Map<keyof SessionEventMap, Set<Listener<keyof SessionEventMap>>>` — but be careful: when calling listeners, you'll need to narrow the key to `E` inside `dispatch` so TypeScript can connect the payload type to the listener signature.
- For `once`, capture the unsubscribe function returned by `on` inside a wrapper listener, call it immediately after invoking the original listener — no external state needed.
Useful resources
Or clone locally
git clone -b challenge/2026-10-18 https://github.com/niltonheck/typedrop.git