TypeDrop

2026-03-03 Challenge

2026-03-03 Medium

Typed Workflow State Machine

You're building the order-processing engine for a fulfilment platform. Each order moves through a strict lifecycle — from placement to delivery or cancellation — and only certain transitions are legal at any given state. The challenge is encoding that lifecycle entirely in the type system so that illegal transitions are caught at compile time, not at runtime.

Goals

  • Define `TransitionMap` as a mapped type and derive `LegalTransition<S>` from it using indexed access so illegal states resolve to `never`.
  • Model `OrderEvent` as a discriminated union where event-specific fields (trackingCode, reason) only appear on the relevant member.
  • Implement `transition` with function overloads, throwing a `TypeError` on any illegal state transition.
  • Implement `getValidNextEvents` to return only the event-type strings that are currently legal for the order's state.
challenge.ts
// Key types — fill in the TODOs to make this compile
export type OrderState =
  | "pending" | "confirmed" | "picking"
  | "shipped" | "delivered" | "cancelled";

export type TransitionMap = {
  [S in OrderState]: ReadonlyArray<OrderState>; // narrow each value!
};

export type LegalTransition<S extends OrderState> = TODO; // indexed into TransitionMap

export type OrderEvent =
  | { type: "CONFIRM";       orderId: string; timestamp: number }
  | { type: "START_PICKING"; orderId: string; timestamp: number }
  | { type: "SHIP";          orderId: string; timestamp: number; trackingCode: string }
  | { type: "DELIVER";       orderId: string; timestamp: number }
  | { type: "CANCEL";        orderId: string; timestamp: number; reason: string };

// Main functions you must implement:
export function buildOrder(id: string, createdAt: number): Order { … }
export function transition(order: Order, event: OrderEvent): Order { … }
export function getValidNextEvents(order: Order): ReadonlyArray<OrderEvent["type"]> { … }
Hints (click to reveal)

Hints

  • For `LegalTransition<S>`, index into `TransitionMap` with `S` and then use `[number]` to convert the readonly array type into a union.
  • Inside `transition`, narrow the event with a `switch (event.type)` — TypeScript will automatically narrow the event shape in each branch, letting you access `trackingCode` or `reason` safely.
  • The provided `STATE_LEGAL_EVENTS` constant is keyed by `OrderState` and valued by `ReadonlyArray<OrderEvent["type"]>` — lean on it inside both `transition` (for validation) and `getValidNextEvents` (for the return value).

Or clone locally

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