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.

Or clone locally

git clone -b challenge/2026-04-12 https://github.com/niltonheck/typedrop.git