TypeDrop

2026-03-20 Challenge

2026-03-20 Hard

Typed Workflow State Machine Executor

You're building the automation backbone for a CI/CD platform. Each pipeline is a finite state machine whose transitions are guarded by typed conditions, carry typed payloads, and emit strongly-typed side-effect events — all resolved through a `Result<T, E>` monad with exhaustive error handling and zero `any`.

Goals

  • Define `WorkflowState<K>` via intersection of a discriminant and a mapped payload lookup, and encode `AllowedTransitions` as a mapped type over `StateKind`.
  • Implement `validatePayload` using a `switch` on `StateKind` and `typeof` narrowing to convert `unknown` payloads into typed `StatePayloadMap[K]` values — no `any`, no assertions.
  • Implement `StateMachine` with a generic `transition` method that enforces the transition graph, runs variadic guards in sequence, validates the payload, updates state, and emits the correct `WorkflowEvent`.
  • Implement `aggregateRuns` in a single `reduce` pass that computes total count, per-final-state breakdown, average duration of succeeded runs, and a sorted unique list of failure reasons.
challenge.ts
// Core types and main class signature — no solution code included

type StateKind = 'idle' | 'queued' | 'running' | 'succeeded' | 'failed' | 'cancelled';

type WorkflowState<K extends StateKind> = { readonly kind: K } & StatePayloadMap[K];
type AnyWorkflowState = { [K in StateKind]: WorkflowState<K> }[StateKind];

type Guard<
  From extends StateKind,
  To extends AllowedTransitions[From][number]
> = (current: WorkflowState<From>, rawPayload: unknown) => Result<true, TransitionError>;

class StateMachine {
  constructor(
    workflowId: string,
    initial: WorkflowState<'idle'>,
    emit: EventEmitter
  ) { /* TODO */ }

  get state(): AnyWorkflowState { /* TODO */ }

  transition<To extends AllowedTransitions[StateKind][number]>(
    to: To,
    rawPayload: unknown,
    ...guards: ReadonlyArray<Guard<StateKind, To>>
  ): Result<WorkflowState<To>, TransitionError> { /* TODO */ }

  history(): ReadonlyArray<AnyWorkflowState> { /* TODO */ }
}
Hints (click to reveal)

Hints

  • For `validatePayload`, a `switch (kind)` gives you a narrowed `K` in each branch — then use `typeof` checks on individual fields and return `err({ kind: 'PayloadValidationError', ... })` on the first mismatch.
  • The `AllowedTransitions[From][number]` indexed access type is the key to constraining `To` in both `Guard` and `transition` — make sure `ALLOWED_TRANSITIONS` is typed as `AllowedTransitions` (not inferred) so the literal arrays stay narrow.
  • In `aggregateRuns`, accumulate a `Set<string>` for failure reasons inside the reduce, then convert to a sorted array only once at the end — and track `totalDuration` + `succeededCount` separately to compute the average without a second pass.

Or clone locally

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