TypeDrop
A new TypeScript challenge every day. Sharpen your types.
TypeDrop delivers a fresh TypeScript challenge every day, generated by AI. Pick a challenge, open it in StackBlitz (preferred) or CodeSandbox (or clone it locally), and make the tests pass. No accounts, no setup — just you and the type system.
2026-04-05
Medium
Typed Event Stream Aggregator
You're building the real-time analytics engine for a live-streaming platform. Raw events arrive as unknown JSON blobs from multiple sources; your engine must validate them, fan out processing concurrently with a limit, and aggregate per-stream statistics into a fully typed report — with zero `any`.
Goals
- Declare `Brand<T, B>` and apply it to `EventId`, `StreamId`, and `TimestampMs` — then use those branded types throughout `StreamEvent` and `StreamStats`.
- Implement `validateEvent` using a `Result<T, E>` discriminated union and user-defined type guards to narrow every `unknown` field without `as` or `any`.
- Implement `aggregateEvents` to fan out `EventProcessor` calls concurrently up to `concurrencyLimit`, then aggregate per-stream `StreamStats` (counts, totals, firstSeen/lastSeen) into an `AggregationReport`.
- Implement `lookupStream` to convert a plain `string` to a branded `StreamId` via a type guard and return `Result<StreamStats, string>` — no non-null assertions.
challenge.ts
// Core types & main function signature
type Brand<T, B extends string> = T & { readonly __brand: B };
type EventId = Brand<string, "EventId">;
type StreamId = Brand<string, "StreamId">;
type TimestampMs = Brand<number, "TimestampMs">;
type EventKind = "view" | "like" | "comment" | "share" | "donation";
interface StreamEvent {
id: EventId;
streamId: StreamId;
kind: EventKind;
value: number;
timestamp: TimestampMs;
}
type Result<T, E> =
| { ok: true; value: T }
| { ok: false; error: E };
type EventProcessor = (event: StreamEvent) => Promise<void>;
// --- implement this ---
async function aggregateEvents(
rawEvents: RawEvent[],
processor: EventProcessor,
concurrencyLimit?: number,
): Promise<AggregationReport> { /* TODO */ }
Hints (click to reveal)
Hints
- A type guard of the form `function isEventId(x: string): x is EventId` lets you refine a plain string into a branded type without `as` — apply the same pattern for `StreamId` and `TimestampMs`.
- For `Result<T, E>`, a discriminated union on a boolean `ok` field (`{ ok: true; value: T } | { ok: false; error: E }`) gives you exhaustive narrowing with a simple `if (result.ok)` check.
- To respect `concurrencyLimit` without a library, slice the validated events array into chunks of that size and `await Promise.all(chunk.map(processor))` — iterate chunk by chunk sequentially.
Useful resources
Or clone locally
git clone -b challenge/2026-04-05 https://github.com/niltonheck/typedrop.git