TypeDrop
2026-10-03 Challenge
2026-10-03
Easy
Typed Notification Router
You're building the notification dispatch layer for a team productivity app. The system receives raw notification events from various channels (email, push, SMS), routes each one to the correct typed handler, and produces a structured delivery summary — all enforced by the compiler with no unsafe casts.
Goals
- Define a discriminated union `NotificationEvent` with three channel-specific variants (EmailEvent, PushEvent, SmsEvent).
- Build `ChannelEventMap` and `HandlerMap` mapped types so each channel key is paired with a handler that receives only that channel's event shape.
- Implement `createRouter` so it narrows on `event.channel` and delegates to the correct typed handler, returning a `DeliveryResult`.
- Implement `summarise` using a single `Array.prototype.reduce` call to produce totals, per-status counts, and per-channel groupings.
challenge.ts
// Key types & main function signatures
type Channel = "email" | "push" | "sms";
type EmailEvent = { channel: "email"; to: string; subject: string; body: string };
type PushEvent = { channel: "push"; deviceToken: string; title: string; payload: Record<string, string> };
type SmsEvent = { channel: "sms"; phoneNumber: string; message: string };
type NotificationEvent = EmailEvent | PushEvent | SmsEvent;
// Maps each Channel key → its specific event type
type ChannelEventMap = { /* TODO */ };
// Each handler receives the *exact* event type for its channel
type HandlerMap = { /* TODO */ };
// Registers handlers and returns a type-safe router
function createRouter(handlers: HandlerMap): {
route(event: NotificationEvent): DeliveryResult;
};
// Single-pass reduce over results → structured summary
function summarise(results: DeliveryResult[]): DeliverySummary;
Hints (click to reveal)
Hints
- A mapped type over `Channel` with a conditional type on the value side (e.g. `K extends 'email' ? EmailEvent : ...`) is exactly what `ChannelEventMap` needs.
- Inside `createRouter`, a `switch (event.channel)` block gives TypeScript enough information to narrow `event` to the exact variant — no casts needed.
- For `summarise`, initialise the accumulator with all three `DeliveryStatus` keys set to 0 so `counts` is always a complete `Record<DeliveryStatus, number>`.
Useful resources
Or clone locally
git clone -b challenge/2026-10-03 https://github.com/niltonheck/typedrop.git