TypeDrop

2026-03-28 Challenge

2026-03-28 Hard

Typed Event-Sourced State Machine

You're building the order-lifecycle engine for a commerce platform. Orders move through a strict set of states via typed events — your state machine must enforce legal transitions at the type level, fold an event log into the current state, and surface a fully typed `Result<T, E>` for every operation with zero `any`.

Goals

  • Define fully typed discriminated unions for `OrderState`, `OrderEvent`, and `MachineError` with the correct structural fields per variant.
  • Implement `applyEvent` that validates transitions against `LEGAL_TRANSITIONS` and returns a typed `Result<OrderState, MachineError>` with no `any`.
  • Implement `foldEvents` to sequentially replay an event log, short-circuiting on the first error and returning the final folded state.
  • Implement `assertState<K>` using the conditional type `StateByKind<K>` to narrow a `Result<OrderState, MachineError>` to a specific state variant without type assertions.
challenge.ts
// Key types and main function signatures

type OrderState =
  | { kind: "Pending" }
  | { kind: "Confirmed"; confirmedAt: Date }
  | { kind: "Shipped";   confirmedAt: Date; shippedAt: Date; trackingCode: string }
  | { kind: "Delivered"; confirmedAt: Date; shippedAt: Date; trackingCode: string; deliveredAt: Date }
  | { kind: "Cancelled"; cancelledAt: Date; reason: string };

type OrderEvent =
  | { id: EventId; occurredAt: Date; type: "OrderPlaced" }
  | { id: EventId; occurredAt: Date; type: "OrderConfirmed" }
  | { id: EventId; occurredAt: Date; type: "OrderShipped";   trackingCode: string }
  | { id: EventId; occurredAt: Date; type: "OrderDelivered" }
  | { id: EventId; occurredAt: Date; type: "OrderCancelled"; reason: string };

type Result<T, E> = { kind: "Ok"; value: T } | { kind: "Err"; error: E };

// Applies a single event to the current state, enforcing legal transitions
function applyEvent(
  currentState: OrderState | null,
  event: OrderEvent
): Result<OrderState, MachineError> { /* TODO */ }

// Replays an ordered event log and returns the final state or first error
function foldEvents(
  events: ReadonlyArray<OrderEvent>
): Result<OrderState, MachineError> { /* TODO */ }

// Narrows a Result to a specific state variant by its `kind` discriminant
function assertState<K extends OrderState["kind"]>(
  result: Result<OrderState, MachineError>,
  kind: K
): StateByKind<K> | null { /* TODO */ }
Hints (click to reveal)

Hints

  • For `StateByKind<K>`, use `Extract<OrderState, { kind: K }>` — it resolves to exactly the union member whose `kind` matches K.
  • In `applyEvent`, a `switch` on `event.type` with exhaustive case handling lets TypeScript narrow both the event shape and the expected prior state simultaneously.
  • The `satisfies` operator on `LEGAL_TRANSITIONS` lets you keep the precise literal types of each array while still enforcing the `LegalTransitions` contract.

Or clone locally

git clone -b challenge/2026-03-28 https://github.com/niltonheck/typedrop.git