TypeDrop

2026-06-11 Challenge

2026-06-11 Medium

Typed Real-Time Sensor Stream Aggregator

You're building the ingestion layer for an IoT monitoring platform. Raw sensor readings arrive as `unknown` from a WebSocket feed; your engine must validate them, group them by device and metric type via a strongly-typed pipeline, and produce a per-device aggregated summary — with zero `any`.

Goals

  • Define a discriminated union `SensorReading` with three `kind` variants and a mapped `DeviceSummary` type over `SensorKind`.
  • Implement `parseSensorReading` to safely validate `unknown` input and return a typed `Result<SensorReading, ParseError>` — no throws, no `any`.
  • Implement `aggregateReadings` to group readings by `deviceId` and compute `MetricStats` (count, min, max, avg) for each metric kind present.
  • Implement `processRawFeed` to parse a raw feed, collect errors, aggregate valid readings, and return a fully typed `FeedReport`.
challenge.ts
// Core domain types & main function signatures

export type SensorKind = "temperature" | "humidity" | "pressure";

export type SensorReading =
  | { kind: "temperature"; deviceId: string; timestamp: number; value: number }
  | { kind: "humidity";    deviceId: string; timestamp: number; value: number }
  | { kind: "pressure";   deviceId: string; timestamp: number; value: number };

export type MetricStats = { count: number; min: number; max: number; avg: number };

export type DeviceSummary = {
  deviceId: string;
  firstSeen: number;
  lastSeen: number;
} & Partial<{ [K in SensorKind]: MetricStats }>;

export type DeviceAggregation = Record<string, DeviceSummary>;

export type FeedReport = {
  aggregation: DeviceAggregation;
  errors: ParseError[];
  totalReceived: number;
  totalValid: number;
};

// Validate unknown → Result<SensorReading, ParseError>
declare function parseSensorReading(raw: unknown): Result<SensorReading, ParseError>;

// Group + compute per-device stats
declare function aggregateReadings(readings: SensorReading[]): DeviceAggregation;

// Parse + aggregate an entire raw feed
declare function processRawFeed(rawItems: unknown[]): FeedReport;
Hints (click to reveal)

Hints

  • Use `[K in SensorKind]: MetricStats` inside a `Partial<{...}>` intersection to make per-kind stats optional on `DeviceSummary`.
  • In `parseSensorReading`, check `typeof` and array `.includes()` to narrow `unknown` step-by-step before constructing the typed value — a type predicate helper can keep it clean.
  • A single `reduce` pass over `SensorReading[]` can build the entire `DeviceAggregation` by initialising a device entry on first encounter and updating stats in-place.

Or clone locally

git clone -b challenge/2026-06-11 https://github.com/niltonheck/typedrop.git