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.

Or clone locally

git clone -b challenge/2026-04-26 https://github.com/niltonheck/typedrop.git