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).
Useful resources
Or clone locally
git clone -b challenge/2026-03-03 https://github.com/niltonheck/typedrop.git