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