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.
Useful resources
Or clone locally
git clone -b challenge/2026-03-20 https://github.com/niltonheck/typedrop.git