TypeDrop
2026-04-26 Challenge
2026-04-26
Hard
Typed Real-Time Metrics Aggregator
You're building the telemetry ingestion pipeline for a distributed observability platform. Raw metric events arrive as `unknown` from multiple instrumentation agents; your engine must validate them, route each event to a strongly-typed aggregator strategy, perform single-pass windowed aggregation with concurrency-safe flushing, and emit a discriminated-union report per metric series — with zero `any`.
Goals
- Define `ValidatedEvent<K>` using `Extract` and field remapping to replace `name`, `agentId`, and `timestampMs` with their branded counterparts while preserving all other payload fields.
- Implement `validateEvent` to safely narrow `unknown` → `RawMetricEvent` → `ValidatedEvent<K>` with full kind-specific payload checks and no `as`/`any` (except the single provided brand helper).
- Implement all four `AggregatorStrategy` classes (`CounterStrategy`, `GaugeStrategy`, `HistogramStrategy`, `SummaryStrategy`) with correct aggregation logic and return types constrained by `Extract<AggregationResult, { kind: K }>`.
- Implement `MetricsAggregator` to route validated events to per-series strategy instances, concurrently flush all series via `Promise.all`, clear state after flushing, and emit discriminated `FlushReport` results including error capture.
challenge.ts
// Core types & main engine signature — real-world telemetry pipeline
type Brand<T, B extends string> = T & { readonly [__brand]: B };
declare const __brand: unique symbol;
export type MetricName = Brand<string, "MetricName">;
export type AgentId = Brand<string, "AgentId">;
export type WindowMs = Brand<number, "WindowMs">;
export type SeriesKey = `${MetricName}::${AgentId}`;
export type RawMetricEvent =
| { kind: "counter"; name: string; agentId: string; value: number; timestampMs: number }
| { kind: "gauge"; name: string; agentId: string; value: number; timestampMs: number }
| { kind: "histogram"; name: string; agentId: string; buckets: number[]; timestampMs: number }
| { kind: "summary"; name: string; agentId: string; quantiles: [number, number][]; timestampMs: number };
// ValidatedEvent<K> — branded version of the matching RawMetricEvent member
export type ValidatedEvent<K extends RawMetricEvent["kind"]> = /* TODO */;
// AggregatorStrategy — typed per metric kind
export interface AggregatorStrategy<K extends RawMetricEvent["kind"]> {
ingest(event: ValidatedEvent<K>): void;
flush(windowMs: WindowMs): Promise<Extract<AggregationResult, { kind: K }> | null>;
}
// Main engine
export class MetricsAggregator {
constructor(registry: StrategyRegistry, windowMs: WindowMs) { /* TODO */ }
ingest(raw: unknown): ValidationResult { /* TODO */ }
flushAll(): Promise<FlushReport[]> { /* TODO */ }
activeSeries(): SeriesKey[] { /* TODO */ }
}
Hints (click to reveal)
Hints
- For `ValidatedEvent<K>`, start with `Extract<RawMetricEvent, { kind: K }>` then use the `Omit` + intersection pattern to swap out `name`, `agentId`, and `timestampMs` for their branded types.
- For the `StrategyRegistry` mapped type, iterate over `RawMetricEvent["kind"]` with `{ [K in RawMetricEvent["kind"]]: () => AggregatorStrategy<K> }` — this gives you a factory per kind with the K correctly bound.
- In `flushAll`, wrap each `strategy.flush(windowMs)` call in a `.then(...).catch(...)` chain before passing to `Promise.all` so individual failures become `{ status: "error" }` reports rather than rejecting the whole batch.
Useful resources
Or clone locally
git clone -b challenge/2026-04-26 https://github.com/niltonheck/typedrop.git