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