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.
Useful resources
Or clone locally
git clone -b challenge/2026-05-20 https://github.com/niltonheck/typedrop.git