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.
Useful resources
Or clone locally
git clone -b challenge/2026-06-11 https://github.com/niltonheck/typedrop.git