TypeDrop

2026-07-19 Challenge

2026-07-19 Hard

Typed Hierarchical State Machine

You're building the order-lifecycle engine for an e-commerce platform. Orders flow through a strict set of states (e.g. `Pending → Confirmed → Shipped → Delivered`, with `Cancelled` reachable from several states), and each transition may carry a typed payload. Your task is to implement a strongly-typed hierarchical state machine where invalid transitions are rejected at the type level, transition guards receive the correct context, and every state's entry/exit hooks are fully typed — with zero `any`.

Goals

  • Implement the `StateMachine` class so that `send()` correctly finds, guards, and applies transitions while firing typed entry/exit hooks.
  • Build `canSend()` to report reachability from the current state without evaluating guards.
  • Implement `history()` returning an immutable snapshot-safe log of every successful transition.
  • Implement `createOrderMachine()` wiring all five legal order transitions — including the guard that blocks `CANCEL` when `refundEligible` is false.
challenge.ts
// Key types — read these before implementing

type OrderState = "Pending" | "Confirmed" | "Shipped" | "Delivered" | "Cancelled";

interface OrderEventMap {
  CONFIRM: { paymentRef: string; totalCents: number };
  SHIP:    { trackingCode: string; carrier: string };
  DELIVER: { signedBy: string; deliveredAt: Date };
  CANCEL:  { reason: string; refundEligible: boolean };
}

// Discriminated union built from the map
type OrderEvent = {
  [K in keyof OrderEventMap]: { name: K; payload: OrderEventMap[K] };
}[keyof OrderEventMap];

type TransitionResult<To extends OrderState> =
  | { ok: true;  newState: To }
  | { ok: false; reason: "NO_TRANSITION" | "GUARD_REJECTED" };

// ── Main surface you must implement ──────────────────────────────
class StateMachine {
  constructor(
    orderId: string,
    initial: OrderState,
    transitions: readonly TransitionDef<OrderState, OrderState, keyof OrderEventMap>[],
    hooks?: StateHooks,
  ) { /* TODO */ }

  getState(): OrderState                                          { /* TODO */ }
  send(event: OrderEvent): TransitionResult<OrderState>          { /* TODO */ }
  canSend(eventName: keyof OrderEventMap): boolean               { /* TODO */ }
  history(): readonly { state: OrderState; event: OrderEvent }[] { /* TODO */ }
}

function createOrderMachine(orderId: string, hooks?: StateHooks): StateMachine {
  /* TODO — wire up all 5 legal transitions */
}
Hints (click to reveal)

Hints

  • The `OrderEvent` discriminated union is already built for you via a mapped-type index access — use `event.name` as the discriminant inside `send()` to narrow `event.payload` to the right shape automatically.
  • To keep `history()` snapshot-safe, store entries in a private array and return a *copy* (e.g. `[...this._history]`) so callers can't mutate your internal log.
  • In `TransitionDef`, `from` can be a single state or a `readonly` array — write a small helper that normalises it to an array before checking membership, so your `send()` and `canSend()` logic stays clean.

Or clone locally

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