TypeDrop

2026-08-08 Challenge

2026-08-08 Easy

Typed Event Emitter with Discriminated Payloads

You're building the notification layer for a real-time collaboration tool. Components fire strongly-typed domain events (user joined, document edited, cursor moved) and listeners must only receive the exact payload shape for the event they subscribed to — no casting, no `any`.

Goals

  • Define an `EventMap` interface that binds three event name literals to their distinct payload shapes.
  • Implement the `Listener<E, K>` generic type alias so callbacks are constrained to exactly `E[K]`.
  • Build the `TypedEmitter<E>` class with `on`, `off`, `emit`, and `once` methods that preserve full payload types.
  • Export a `createRoomEmitter` factory with an explicit `TypedEmitter<EventMap>` return type annotation.
challenge.ts
// Key types you'll define and implement:

export interface EventMap {
  "user:joined": { userId: string; roomId: string; timestamp: number };
  "doc:edited":  { docId: string; delta: string; authorId: string };
  "cursor:moved": { userId: string; x: number; y: number };
}

export type Listener<E extends EventMap, K extends keyof E> = /* TODO */ never;

export class TypedEmitter<E extends EventMap> {
  on<K extends keyof E>(event: K, listener: Listener<E, K>): void { /* TODO */ }
  off<K extends keyof E>(event: K, listener: Listener<E, K>): void { /* TODO */ }
  emit<K extends keyof E>(event: K, payload: E[K]): void { /* TODO */ }
  once<K extends keyof E>(event: K, listener: Listener<E, K>): void { /* TODO */ }
}

export function createRoomEmitter(): TypedEmitter<EventMap> { /* TODO */ }
Hints (click to reveal)

Hints

  • An indexed access type like `E[K]` is the key to connecting an event name to its payload — use it in both the `Listener` alias and the `emit` signature.
  • For internal storage, a `Map<keyof E, Array<Listener<E, keyof E>>>` works, but you may need a small cast-free trick: store each bucket as `Set<Function>` keyed by the event string — think about why `Map<K, Set<Listener<E,K>>>` is tricky with a single Map and how to work around it.
  • `once` can be implemented entirely in terms of `on` and `off` — wrap the user's listener in a new closure that calls `off` on itself before invoking the original.

Or clone locally

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