TypeDrop

2026-08-22 Challenge

2026-08-22 Easy

Typed Event Emitter with Discriminated Union Payloads

You're building the real-time notification layer for a project management app. UI components subscribe to named events (task assigned, comment posted, status changed), and every listener must receive a payload that is already narrowed to the correct shape — no casting, no guessing.

Goals

  • Define `AppEvent` as a discriminated union and derive `EventMap` automatically using a distributive mapped type — no hand-written object literal.
  • Type `Listener<K>` so that each callback receives a payload already narrowed to the specific event shape, enforced by the generic parameter.
  • Implement `on`, `off`, and `emit` with bounded generics so the compiler rejects mismatched event-name / payload pairs at call sites.
  • Implement `listenerCount` and ensure `off` correctly removes only the first matching listener reference.
challenge.ts
// Key types you must define:

type AppEvent =
  | TaskAssignedEvent
  | CommentPostedEvent
  | StatusChangedEvent;

// EventMap maps each discriminant string → its full event shape:
// { "task:assigned": TaskAssignedEvent; "comment:posted": ...; ... }
type EventMap = { [E in AppEvent as E["type"]]: E };

type Listener<K extends keyof EventMap> = (event: EventMap[K]) => void;

// Main class you must implement:
class TypedEventEmitter {
  on<K extends keyof EventMap>(event: K, listener: Listener<K>): void;
  off<K extends keyof EventMap>(event: K, listener: Listener<K>): void;
  emit<K extends keyof EventMap>(event: EventMap[K]): void;
  listenerCount(event: keyof EventMap): number;
}
Hints (click to reveal)

Hints

  • For `EventMap`, look into key-remapping in mapped types: `{ [E in AppEvent as E["type"]]: E }` — this lets you pivot a union into a lookup map.
  • The `emit` method receives a full event object (which already has `.type`); use `event.type` as the key into `_listeners` — you may need a small type assertion inside the private implementation body only.
  • For `_listeners`, a `Map<keyof EventMap, Listener<keyof EventMap>[]>` is a pragmatic internal type — the public API generics enforce correctness at call sites.

Or clone locally

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