TypeDrop
2026-05-19 Challenge
2026-05-19
Hard
Typed Workflow State Machine with Retry & Cancellation
You're building the job-execution engine for a distributed task platform. Jobs arrive as `unknown` from a queue API; your engine must validate them, drive each job through a strict discriminated-union state machine, execute steps with typed retry logic and AbortController cancellation, and return a strongly-typed execution report — with zero `any`.
Goals
- Define all branded types, discriminated unions (StepState, JobState, ValidationError, ExecutionError), and the conditional type StepSummary<S> correctly under strict mode.
- Implement validateStep and validateJob to parse unknown input, collect all errors without short-circuiting, and return Result<T, ValidationError[]>.
- Implement createStepExecutor, runStep (with retry logic and AbortSignal cancellation), and runJob (sequential execution with abort-on-failure) with fully typed state transitions.
- Implement buildJobReport using exhaustive narrowing over StepState to produce StepReport<StepState>[], and wire everything together in processJobFromQueue.
challenge.ts
// Key types and main function signatures
export type JobId = string & { readonly __brand: "JobId" };
export type StepId = string & { readonly __brand: "StepId" };
// Discriminated union — StepState
export type StepState =
| { status: "pending" }
| { status: "running"; attempt: number; startedAt: number }
| { status: "succeeded"; attempt: number; startedAt: number; endedAt: number; durationMs: number }
| { status: "failed"; attempts: number; lastError: string }
| { status: "cancelled"; attempt: number; reason: string };
// Conditional type — maps each StepState variant to its summary shape
export type StepSummary<S extends StepState> =
S["status"] extends "succeeded" ? { outcome: "success"; durationMs: number } :
S["status"] extends "failed" ? { outcome: "failure"; attempts: number } :
S["status"] extends "cancelled" ? { outcome: "cancelled"; reason: string } :
{ outcome: "pending" };
export type Result<T, E> =
| { ok: true; value: T }
| { ok: false; error: E };
// Full pipeline entry point
export declare function processJobFromQueue(
raw: unknown
): Promise<Result<JobReport, string>>;
Hints (click to reveal)
Hints
- For StepSummary, use a conditional type `S["status"] extends "succeeded" ? ... : ...` chain — TypeScript can narrow the status discriminant inside each branch.
- In runStep, detect an AbortError by checking `error instanceof Error && error.message.startsWith("AbortError")` — then return a Cancelled ExecutionError without retrying.
- Use `satisfies` on your ValidationError and ExecutionError literal objects to catch misspelled `kind` discriminants at the call site without widening the type.
Useful resources
Or clone locally
git clone -b challenge/2026-05-19 https://github.com/niltonheck/typedrop.git