TypeDrop

2026-06-14 Challenge

2026-06-14 Hard

Typed Workflow Orchestrator with Conditional Branching

You're building the workflow engine for a business-process automation platform. Raw workflow definitions arrive as `unknown` from a configuration API; your orchestrator must validate them, execute each step through a typed middleware pipeline, conditionally branch based on strongly-typed step outcomes, and produce a fully-typed execution trace — with zero `any`.

Goals

  • Implement `validateWorkflowDefinition` to safely narrow `unknown` → `WorkflowDefinition` using manual type guards, returning a typed `Result` on the first failure found.
  • Implement `createOrchestrator` with a full step-execution loop that handles transform (with optional timeout via `Promise.race`), branch (with wildcard fallback), and merge steps, wiring middleware as an ordered async chain.
  • Implement `extractStepIds` using a single `reduce` call to build a `Record<WorkflowStep["type"], string[]>` derived entirely from the union — no manual object literal types.
  • Implement `narrowError` as a generic exhaustive dispatcher over `WorkflowError` kinds using a mapped `Extract` handler record — TypeScript must reject any call with a missing handler.
challenge.ts
// Key types and main function signatures

type Result<T, E extends WorkflowError> =
  | { ok: true; value: T }
  | { ok: false; error: E };

type WorkflowStep = TransformStep | BranchStep | MergeStep;
// TransformStep: { type:"transform"; id:string; transformer:string; timeoutMs?:number }
// BranchStep:    { type:"branch";    id:string; branches: Record<string,string> }
// MergeStep:     { type:"merge";     id:string; strategy:"first"|"concat"|"sum" }

type TransformerFn<TIn, TOut> = (
  input: TIn,
  ctx: StepContext
) => Promise<Result<{ value: TOut; outcome: string }, WorkflowError>>;

// ── Validate raw unknown → typed definition ──────────────────
function validateWorkflowDefinition(
  raw: unknown
): Result<WorkflowDefinition, WorkflowError> { /* TODO */ }

// ── Build a reusable orchestrator instance ───────────────────
function createOrchestrator<
  R extends Record<string, TransformerFn<unknown, unknown>>
>(
  registry: StepRegistry<R>,
  middleware?: StepMiddleware[]
): { run: (definition: WorkflowDefinition, initialInput: unknown) => Promise<ExecutionReport> }
{ /* TODO */ }

// ── Exhaustive error dispatcher ──────────────────────────────
function narrowError<E extends WorkflowError>(
  error: E,
  handlers: { [K in E["kind"]]: (e: Extract<E, { kind: K }>) => string }
): string { /* TODO */ }
Hints (click to reveal)

Hints

  • For `validateWorkflowDefinition`, write a small `isRecord(v: unknown): v is Record<string, unknown>` guard first — it composes cleanly into every nested check without needing `as`.
  • For the middleware chain in `createOrchestrator`, build the composed `next` function by `reduceRight`-ing over the middleware array so the first-registered middleware becomes the outermost wrapper.
  • For `narrowError`, the handler lookup is just `handlers[error.kind](error as never)` — but try to avoid `as never`; instead cast through the mapped type using the discriminant key to satisfy TypeScript's narrowing.

Or clone locally

git clone -b challenge/2026-06-14 https://github.com/niltonheck/typedrop.git