TypeDrop
2026-07-10 Challenge
2026-07-10
Medium
Typed Event Aggregator with Time-Window Bucketing
You're building the analytics ingestion layer for a real-time monitoring dashboard. Raw events arrive as `unknown` from a webhook stream; your aggregator must validate them into strongly-typed `MonitoringEvent` records, bucket them into fixed time windows, and produce a fully-typed `WindowSummary` per bucket — with zero `any`.
Goals
- Implement `parseEvent` to validate `unknown` payloads and narrow them into the correct `MonitoringEvent` discriminated-union branch, returning `null` for any malformed input.
- Implement `getBucketKey` to compute the ISO-8601 window-start string for a given timestamp and window duration.
- Implement `aggregateEvents` to validate, bucket, and aggregate events into per-window `WindowSummary` objects with correct category counts, severity counts, metric stats, and deduplicated sources.
- Implement the generic `groupBy<T, K extends string>` helper so its return type preserves the caller's specific key type K.
challenge.ts
export type EventCategory = "error" | "warning" | "info" | "metric";
export type Severity = "low" | "medium" | "high" | "critical";
export interface AlertEvent extends BaseEvent { category: "error" | "warning"; severity: Severity; message: string; }
export interface InfoEvent extends BaseEvent { category: "info"; description: string; }
export interface MetricEvent extends BaseEvent { category: "metric"; metricName: string; value: number; }
export type MonitoringEvent = AlertEvent | InfoEvent | MetricEvent;
export type BucketKey = string;
export type CategoryCounts = Record<EventCategory, number>;
export type SeverityCounts = Record<Severity, number>;
export interface WindowSummary {
windowStart: BucketKey;
totalEvents: number;
categoryCounts: CategoryCounts;
severityCounts: SeverityCounts;
metricStats: Record<string, MetricStats>;
sources: string[];
}
export function parseEvent(raw: unknown): MonitoringEvent | null { /* TODO */ }
export function getBucketKey(timestampMs: number, windowMs: number): BucketKey { /* TODO */ }
export function aggregateEvents(rawEvents: unknown[], windowMs: number): AggregationResult { /* TODO */ }
export function groupBy<T, K extends string>(items: T[], keyFn: (item: T) => K): Map<K, T[]> { /* TODO */ }
Hints (click to reveal)
Hints
- In `parseEvent`, switch on `category` after confirming it's a valid `EventCategory` — TypeScript will narrow the type inside each `case` branch if you structure the checks correctly.
- For `CategoryCounts` and `SeverityCounts`, initialise every key explicitly (all four literals) so the `Record<…, number>` contract is satisfied from the start.
- The `groupBy` generic constraint `K extends string` lets TypeScript infer K from the return type of `keyFn` at the call site — you don't need to annotate it manually.
Useful resources
Or clone locally
git clone -b challenge/2026-07-10 https://github.com/niltonheck/typedrop.git