TypeDrop
2026-04-12 Challenge
2026-04-12
Medium
Typed Notification Dispatcher
You're building the notification delivery layer for a team collaboration app. Raw notification payloads arrive from a message broker as unknown blobs; your dispatcher must validate them, route them through a registry of typed channel handlers, and return a fully typed delivery report — with zero `any`.
Goals
- Define `NotificationPayload` as a discriminated union and `ChannelHandlerMap` as a mapped type that pairs each `ChannelKind` with its exact payload variant.
- Implement `validatePayload` to narrow an `unknown` blob to a `NotificationPayload` using type guards — no `as` or `any`.
- Implement `dispatchAll` to concurrently dispatch all valid payloads via `Promise.allSettled`, capturing failures without short-circuiting, and return a typed `DispatchReport`.
- Implement `createHandlerRegistry` to merge partial handler overrides with a default 'skipped' fallback, returning a complete `ChannelHandlerMap`.
challenge.ts
// Core types + main function signature preview
export type ChannelKind = "email" | "sms" | "push" | "webhook";
// Each channel has its own payload shape (discriminated by `channel`)
export type NotificationPayload =
| EmailPayload | SmsPayload | PushPayload | WebhookPayload;
// Mapped type: each ChannelKind → handler for the *matching* payload
export type ChannelHandlerMap = {
[K in ChannelKind]: (payload: Extract<NotificationPayload, { channel: K }>) => Promise<DeliveryResult>;
};
export interface DispatchReport {
total: number; sent: number; failed: number; skipped: number;
results: DeliveryResult[];
}
// Validate unknown blob → typed payload or null
export function validatePayload(raw: unknown): NotificationPayload | null { /* TODO */ return null; }
// Concurrently dispatch all payloads, aggregate into a DispatchReport
export async function dispatchAll(
rawPayloads: unknown[],
handlers: ChannelHandlerMap
): Promise<DispatchReport> { /* TODO */ return { total:0, sent:0, failed:0, skipped:0, results:[] }; }
Hints (click to reveal)
Hints
- For `ChannelHandlerMap`, try `{ [K in ChannelKind]: (p: Extract<NotificationPayload, { channel: K }>) => Promise<DeliveryResult> }` — `Extract` pulls the exact union member for each key.
- In `validatePayload`, start with `if (typeof raw !== 'object' || raw === null) return null`, then use `'to' in raw` and `typeof (raw as Record<string, unknown>).to === 'string'` style checks to drill down safely.
- `Promise.allSettled` never rejects — check each result's `.status` field (`'fulfilled'` vs `'rejected'`) to build your `DeliveryResult` without a try/catch around the whole batch.
Useful resources
Or clone locally
git clone -b challenge/2026-04-12 https://github.com/niltonheck/typedrop.git