TypeDrop
2026-02-28 Challenge
2026-02-28
Medium
Typed Event Aggregator with Windowed Rollups
You're building the analytics backbone of a real-time dashboard for a SaaS platform. Raw telemetry events (page views, clicks, errors, purchases) stream in continuously, and the dashboard needs per-event-type rollups aggregated over fixed time windows — all with zero `any` and full type safety on every event shape and its aggregated form.
Goals
- Define `TelemetryEvent` as a discriminated union and derive `EventKind` and `ExtractEvent<K>` from it using utility/conditional types — no hand-written string literals.
- Build `RollupMap` as a mapped type over `EventKind` that associates each event kind with its rollup shape.
- Implement the generic `Reducer<K>` interface and all four concrete reducers (`pageViewReducer`, `clickReducer`, `errorReducer`, `purchaseReducer`) with correct types.
- Implement `windowKey` and `aggregateEvents` so that events are bucketed by time window and folded through the correct reducer, returning a fully-typed `Map<string, RollupMap[K]>`.
challenge.ts
// Core event union & kind extraction
type TelemetryEvent = PageViewEvent | ClickEvent | ErrorEvent | PurchaseEvent;
type EventKind = TelemetryEvent["kind"]; // "pageView" | "click" | "error" | "purchase"
// Map each kind to its rollup shape
type RollupMap = { [K in EventKind]: /* matching rollup type */ never };
// Generic reducer interface
interface Reducer<K extends EventKind> {
reduce(acc: RollupMap[K] | undefined, event: ExtractEvent<K>): RollupMap[K];
}
// Main aggregation function — groups events into time windows and folds them
function aggregateEvents<K extends EventKind>(
events: TelemetryEvent[],
kind: K,
reducer: Reducer<K>,
windowMs?: number // default 60_000 ms
): Map<string, RollupMap[K]>
Hints (click to reveal)
Hints
- For `ExtractEvent<K>`, `Extract<TelemetryEvent, { kind: K }>` is the most concise approach — it leverages distributive conditional types built into `Extract`.
- Inside `aggregateEvents`, after filtering with `event.kind === kind`, TypeScript may still need a type assertion-free cast — try a type predicate helper `(e): e is ExtractEvent<K> => e.kind === kind` to keep things clean.
- For `RollupMap`, write `[K in EventKind]: ...` and use a conditional type (or explicit mapping) to select the right rollup type for each key — this is the one place a conditional type inside a mapped type really shines.
Useful resources
Or clone locally
git clone -b challenge/2026-02-28 https://github.com/niltonheck/typedrop.git