TypeDrop
2026-04-19 Challenge
2026-04-19
Hard
Typed Workflow Orchestrator
You're building the workflow execution engine for a low-code automation platform. Raw workflow definitions arrive as unknown JSON from a user-facing editor; your orchestrator must validate them, compile each step into a strongly-typed execution graph, run steps with concurrency limits and retry logic, and emit a discriminated-union result per step — with zero `any`.
Goals
- Implement `validateStep` and `validateWorkflow` to narrow `unknown` JSON into the `WorkflowStep` discriminated union and `Workflow` type using the `Result<T,E>` pattern — with zero `as` casts in your logic.
- Implement `topoSort` using Kahn's algorithm to produce a dependency-respecting execution order and return `Err` on cycle detection.
- Implement `orchestrate` to execute steps concurrently (up to `config.concurrency`), skip steps whose dependencies failed, handle `ConditionStep` branch skipping, and retry failing steps with exponential back-off.
- Implement `runWorkflow` as the public entry point that composes validation and orchestration, returning a well-formed `ExecutionReport` even when validation fails.
challenge.ts
// Key types at a glance
type WorkflowStep =
| HttpStep // { kind: "http"; url, method, body?, dependsOn }
| TransformStep // { kind: "transform"; expression, dependsOn }
| ConditionStep // { kind: "condition"; predicate, thenStepId, elseStepId, dependsOn }
| DelayStep; // { kind: "delay"; ms, dependsOn }
// Mapped type: each step kind → a strongly-typed async handler
type StepHandlerMap = {
[K in WorkflowStep["kind"]]: (
step: Extract<WorkflowStep, { kind: K }>,
ctx: ExecutionContext
) => Promise<unknown>;
};
// Result monad used throughout
type Result<T, E> = { ok: true; value: T } | { ok: false; error: E };
// Main entry point
async function runWorkflow(
rawWorkflow: unknown, // validated at runtime
handlers: StepHandlerMap,
config: ExecutorConfig // { concurrency, maxRetries, retryBaseMs }
): Promise<ExecutionReport>
Hints (click to reveal)
Hints
- For `StepHandlerMap`, write `[K in WorkflowStep["kind"]]` and use `Extract<WorkflowStep, { kind: K }>` to get the exact step type for each handler — this is the core mapped-type trick.
- In `orchestrate`, maintain a `Set<StepId>` of completed/failed steps and a queue; on each tick, dispatch all steps whose `dependsOn` are fully resolved, then `await Promise.race` to stay within the concurrency limit.
- For retry back-off, a simple `await new Promise(r => setTimeout(r, config.retryBaseMs * 2 ** attempt))` inside a loop is all you need — track `attempts` to populate `StepFailure`.
Useful resources
Or clone locally
git clone -b challenge/2026-04-19 https://github.com/niltonheck/typedrop.git