TypeDrop
2026-03-09 Challenge
2026-03-09
Hard
Typed State Machine Executor with Transition Guards
You're building the order-lifecycle engine for a fulfilment platform. Each order moves through a strict set of states (e.g. `pending → confirmed → shipped → delivered`), and every transition must pass a typed guard before it fires. The engine must enforce exhaustive state/event coverage at the type level, accumulate a typed audit log, and surface a discriminated `Result` for every attempted transition — with zero `any`.
Goals
- Define a branded `OrderId` type and a safe factory function using a `unique symbol` brand.
- Implement the `StateMachine` class so it correctly resolves transitions, runs typed guards in order, and appends discriminated `AuditEntry` records to an internal log.
- Implement `replayToState` so it drives the machine through a sequence of events and returns a fully typed `ReplaySummary` with success/failure breakdowns.
- Implement `buildTransitionMap` as a generic dual-constraint helper that preserves literal target types at the call site without any type assertions.
challenge.ts
// Core types at a glance
type OrderState = "pending" | "confirmed" | "processing" | "shipped" | "delivered" | "cancelled";
type OrderEvent = "CONFIRM" | "START_PROCESSING" | "SHIP" | "DELIVER" | "CANCEL";
type Guard = (ctx: OrderContext) => true | string; // true = allow, string = deny reason
interface TransitionDef<S extends OrderState> {
readonly target: Exclude<OrderState, S>; // can't transition to yourself
readonly guards?: readonly Guard[];
}
type TransitionMap = {
readonly [S in OrderState]?: {
readonly [E in OrderEvent]?: TransitionDef<S>;
};
};
// Main class signature
class StateMachine {
constructor(map: TransitionMap);
transition(ctx: OrderContext, currentState: OrderState, event: OrderEvent): TransitionResult;
getAuditLog(): readonly AuditEntry[];
getAuditLogFor(orderId: OrderId): readonly AuditEntry[];
}
Hints (click to reveal)
Hints
- The `Exclude<OrderState, S>` on `TransitionDef.target` is enforced at the type level — lean on it to see how the generic `S` flows through the mapped type.
- For `buildTransitionMap`, a single `return map` is the entire implementation — the heavy lifting is in the dual generic constraint `T extends TransitionMap` combined with the parameter type `T & TransitionMap`.
- When narrowing `TransitionResult` in `replayToState`, use the `ok` discriminant to update your running state only on success, keeping the final state stable after a failure.
Useful resources
Or clone locally
git clone -b challenge/2026-03-09 https://github.com/niltonheck/typedrop.git