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).

Or clone locally

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