TypeDrop

2026-04-27 Challenge

2026-04-27 Easy

Typed Notification Router

You're building the notification dispatch layer for a productivity app. Raw notification payloads arrive as `unknown` from a webhook endpoint; your router must validate them, fan them out to strongly-typed per-channel handlers, and collect a discriminated-union delivery report per notification — with zero `any`.

Goals

  • Define the `Notification` discriminated union with channel-specific fields for email, sms, and push variants.
  • Implement `parseNotification` to narrow `unknown` → `Notification` using type guards, throwing `ValidationError` on invalid input.
  • Define the `DeliveryReport` discriminated union and implement `routeNotification` with an exhaustive switch that the compiler enforces.
  • Implement `processAll` to parse and route a batch, converting parse failures into `{ status: 'failed', channel: 'unknown' }` reports.
challenge.ts
// Key types & main function signatures

export type EmailNotification = {
  channel: "email";
  to: string;
  subject: string;
  body: string;
};
export type SmsNotification  = { channel: "sms";  to: string; message: string };
export type PushNotification = { channel: "push"; deviceToken: string; title: string; body: string };

export type Notification = EmailNotification | SmsNotification | PushNotification;

// status:"delivered" carries sentAt; status:"failed" carries reason
export type DeliveryReport = never; // TODO — replace with discriminated union

export function parseNotification(raw: unknown): Notification { … }
export function routeNotification(n: Notification): DeliveryReport { … }
export function processAll(raws: unknown[]): DeliveryReport[] { … }
Hints (click to reveal)

Hints

  • A helper like `isRecord(v: unknown): v is Record<string, unknown>` makes nested property checks safe without `as`.
  • For an exhaustive switch, add a `default` branch that passes the value to a `(x: never) => never` helper — the compiler will error if any variant is unhandled.
  • Use `Notification["channel"] | "unknown"` as the type for the `channel` field in `DeliveryReport` to cover the parse-failure fallback.

Or clone locally

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