FUNCTIONS / @SPFN/WORKFLOW — PIPELINE ORCHESTRATION ON JOBS

Work,
in steps.

@spfn/workflow chains Jobs from @spfn/core into pipelines — sequential with .pipe(), parallel with .parallel(), results type-inferred between steps. Every execution is persisted to PostgreSQL, so a failed run can resume from the failure point or roll back through each step's compensate function.

Read the reference Get started $ pnpm add @spfn/workflow
01
The premise

Steps don't know
each other.

A workflow is a pipeline of Jobs, and each Job stays an independent unit of work — unaware of the steps around it, reusable across workflows. A new process is a new combination of existing steps, not new code. Data never flows implicitly: every step gets an explicit mapper over ctx.input and ctx.results, with previous outputs type-inferred, and only registered workflows can be executed.

job a
job b
job c
FIG 01 — INDEPENDENT JOBS, CHAINED BY EXPLICIT MAPPERS · CTX.RESULTS IS TYPE-INFERRED
02
Core technique

A failed run is a row,
not a restart.

The engine writes every execution to spfn_workflow.executions and every step to spfn_workflow.step_executions — status, output, error, timestamps. Because the database records exactly which steps completed, failure has two precise answers instead of one blunt one. With .resumable(true), retry skips the completed steps and restarts at the failure point. With .rollback(true), the engine runs the compensate function of each completed step in reverse order — and keeps going even if one compensation fails. Outputs past the 1MB threshold move to external storage, leaving a $ref in the row.

step 1 ✓
step 2 ✓
step 3 ✗
compensate, in reverse
FIG 02 — RESUME SKIPS COMPLETED STEPS · ROLLBACK RUNS IN REVERSE · ONE FAILED COMPENSATION DOESN'T STOP THE REST
03
What's inside

One builder, one engine,
the whole lifecycle.

Chain the jobs.