TypeDrop

2026-05-20 Challenge

2026-05-20 Medium

Typed Event Bus with Subscriber Registry

You're building the internal messaging backbone for a collaborative document editor. UI components publish strongly-typed domain events; other components subscribe to specific event kinds and must receive exactly the right payload shape — with zero `any`.

Goals

  • Implement `EventByKind<K>` using `Extract` to resolve the exact union member for a given `kind` literal.
  • Build `SubscriberRegistry` as a mapped type over `EventKind` so each key holds a correctly-typed `Set<Subscriber<K>>`.
  • Implement `EventBus.subscribe`, `publish`, and `subscriberCount` so events are delivered only to subscribers of the matching kind, with correct payload types and working unsubscribe.
  • Implement `filterEvents` to narrow an `AppEvent[]` to `EventByKind<K>[]` without any type assertions.
challenge.ts
/** All possible event kinds */
export type EventKind = AppEvent["kind"];

/** Resolve a union member by its `kind` literal */
export type EventByKind<K extends EventKind> = Extract<AppEvent, { kind: K }>;

/** A subscriber callback typed to the exact event shape */
export type Subscriber<K extends EventKind> = (event: EventByKind<K>) => void;

/** Maps every EventKind to its set of active subscribers */
export type SubscriberRegistry = {
  [K in EventKind]: Set<Subscriber<K>>;
};

export class EventBus {
  private registry: SubscriberRegistry;
  subscribe<K extends EventKind>(kind: K, callback: Subscriber<K>): () => void;
  publish(event: AppEvent): void;
  subscriberCount<K extends EventKind>(kind: K): number;
}

export function filterEvents<K extends EventKind>(
  events: AppEvent[],
  kind: K
): EventByKind<K>[];
Hints (click to reveal)

Hints

  • For `publish`, a small private generic helper method `dispatch<K extends EventKind>(kind: K, event: EventByKind<K>)` lets TypeScript verify the Set/callback relationship without casting.
  • A type predicate `(e: AppEvent): e is EventByKind<K> => e.kind === kind` is the cleanest way to implement `filterEvents` with no assertions.
  • When building the registry in the constructor, `Object.fromEntries` or `reduce` both work — cast only the final result to `SubscriberRegistry`, not the individual Sets.

Or clone locally

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