TypeDrop

2026-06-21 Challenge

2026-06-21 Medium

Typed Notification Dispatcher with Retry & Channel Routing

You're building the notification layer for a user-facing SaaS app. Raw notification payloads arrive as `unknown` from an internal queue; your dispatcher must validate them, route them to the correct typed channel handler (email, SMS, push), fan out deliveries concurrently, and return a strongly-typed delivery report — with zero `any`.

Goals

  • Define a three-variant discriminated union `NotificationPayload` and a `ChannelRegistry` mapped type that pairs each channel key with its exact handler type using `Extract`.
  • Implement `parsePayload` to narrow `unknown` to `NotificationPayload` at runtime using type guards — no `as` assertions.
  • Implement `dispatchOne` to route a payload to its correct handler and retry on failure up to `maxRetries` additional times, always resolving to a `DeliveryStatus`.
  • Implement `dispatchAll` to fan out all dispatches concurrently with `Promise.allSettled`, then aggregate results into a fully-typed `DeliveryReport` with per-channel summaries.
challenge.ts
// Core types & main function signatures

type EmailPayload = { channel: "email"; id: string; userId: string; to: string; subject: string; body: string };
type SmsPayload   = { channel: "sms";   id: string; userId: string; to: string; text: string };
type PushPayload  = { channel: "push";  id: string; userId: string; deviceToken: string; title: string; body: string };

type NotificationPayload = EmailPayload | SmsPayload | PushPayload;

type ChannelHandler<T extends NotificationPayload> = (payload: T) => Promise<void>;

// ChannelRegistry maps each channel key → the handler for that exact variant
type ChannelRegistry = { [K in NotificationPayload["channel"]]: ChannelHandler<Extract<NotificationPayload, { channel: K }>> };

// Main entry point
async function dispatchAll(
  rawPayloads: unknown[],
  registry: ChannelRegistry,
  maxRetries: number,
): Promise<DeliveryReport> { /* TODO */ }
Hints (click to reveal)

Hints

  • For `ChannelRegistry`, a mapped type `{ [K in NotificationPayload["channel"]]: ChannelHandler<Extract<NotificationPayload, { channel: K }>> }` lets the compiler verify each handler receives the right payload variant.
  • In `parsePayload`, narrow step-by-step: first check `typeof raw === 'object' && raw !== null`, then check the `channel` field is a string, then branch on its value — the compiler will track the type in each branch.
  • In `dispatchOne`, a `for` loop from `0` to `maxRetries` (inclusive) lets you attempt the call and `break` on success, capturing the last error for the `failed` status.

Or clone locally

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