TypeDrop
2026-09-29 Challenge
2026-09-29
Medium
Typed State Machine Engine
You're building the workflow engine for an e-commerce order management system. Each order moves through a strict lifecycle — from placement to fulfillment — and the compiler must enforce that only valid transitions are allowed, that transition guards have access to the right context shape, and that side-effect actions are fully typed per transition.
Goals
- Define the `guard` and `action` properties on the `Transition` interface with correct generic signatures.
- Define `MachineSnapshot<S>` (with a `can()` method) and the `TransitionResult` discriminated union.
- Implement `StateMachine` — constructor, `snapshot()`, `send()`, and `patchContext()` — enforcing guard checks and running actions.
- Implement `createOrderMachine` with all ten pre-configured transitions, guards, and actions wired up correctly.
challenge.ts
// Key types & main class — enough to understand the challenge
type OrderState = "pending" | "payment_processing" | "paid"
| "preparing" | "shipped" | "delivered"
| "cancelled" | "refunded";
type OrderEvent = "SUBMIT_PAYMENT" | "PAYMENT_SUCCESS" | "PAYMENT_FAILED"
| "START_PREPARING" | "SHIP" | "DELIVER" | "CANCEL" | "REFUND";
interface Transition<S extends OrderState, E extends OrderEvent, C extends OrderContext> {
from: S;
event: E;
to: OrderState;
guard?: (context: C) => boolean; // ← you define this
action?: (from: S, to: OrderState, context: C) => void; // ← and this
}
// MachineSnapshot<S> and TransitionResult are also yours to define (TODOs 3 & 4)
class StateMachine {
snapshot(): MachineSnapshot<OrderState> { /* TODO */ }
send(event: OrderEvent): TransitionResult { /* TODO */ }
patchContext(patch: Partial<OrderContext>): void { /* TODO */ }
}
function createOrderMachine(initialContext: OrderContext): StateMachine { /* TODO */ }
Hints (click to reveal)
Hints
- For `MachineSnapshot`, the `can()` method must both match the transition's `from` state AND evaluate its `guard` — a missing guard means the event is allowed.
- The `TransitionResult` discriminated union works best with a literal `ok` field — TypeScript will narrow it for you in if/else branches.
- In `createOrderMachine`, the `action` for PAYMENT_SUCCESS/PAYMENT_FAILED should mutate the context object in place (since `patchContext` is also available on the machine, you may call it from the action via a closure).
Useful resources
Or clone locally
git clone -b challenge/2026-09-29 https://github.com/niltonheck/typedrop.git