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.

Or clone locally

git clone -b challenge/2026-05-19 https://github.com/niltonheck/typedrop.git