@suaveplan/workflow - Workflow / pipeline orchestration: sequential + parallel + conditional steps with saga compensation, retry, timeout, lifecycle hooks, and OTel instrumentation
Workflow and pipeline orchestration: sequential and parallel step
execution, conditional branching with guard functions, retry strategies,
timeouts, saga-pattern compensation, lifecycle hooks, and OpenTelemetry
instrumentation. Type-safe via TypeScript generics, tree-shakeable, and
zero external runtime dependencies beyond @suaveplan/error,
@suaveplan/events, @suaveplan/types, @opentelemetry/api, tslib,
and zod.
Documentation tree
- compensator —
Compensatoris the explicit, registry-based rollback engine used by@suaveplan/workflow. The workflow engine and th…- compensator —
Compensatoris the explicit, registry-based rollback engine used by@suaveplan/workflow. The workflow engine and th…
- compensator —
- saga —
Sagais the dedicated, opinionated orchestrator for the textbook saga pattern: sequential business steps where every…- saga —
Sagais the dedicated, opinionated orchestrator for the textbook saga pattern: sequential business steps where every…
- saga —
- context —
WorkflowContextis the per-run state container that the workflow engine threads through every step. It owns the workf…- context —
WorkflowContextis the per-run state container that the workflow engine threads through every step. It owns the workf…
- context —
- workflow-builder —
WorkflowBuilderis the fluent author-facing API for assembling workflows. Most consumers use it to chain together ste…- workflow-builder —
WorkflowBuilderis the fluent author-facing API for assembling workflows. Most consumers use it to chain together ste…
- workflow-builder —
- workflow-engine —
WorkflowEngineis the runtime orchestrator at the centre of@suaveplan/workflow. It accepts a Zod-validated `Workfl…- transition-hooks —
TransitionHooksis the internal data-pipeline registry theWorkflowEngineuses to expose the `workflow:transitionin… - workflow-engine —
WorkflowEngineis the runtime orchestrator at the centre of@suaveplan/workflow. It accepts a Zod-validated `Workfl…
- transition-hooks —
- errors —
@suaveplan/workflowraises a single typed error class —WorkflowError— for every failure mode the engine can surfa…- workflow-error —
@suaveplan/workflowraises a single typed error class —WorkflowError— for every failure mode the engine can surfa…
- workflow-error —
- hook —
HookRegistryis the lifecycle-event dispatcher for@suaveplan/workflow. It is the seam between the engine's interna…- hook —
HookRegistryis the lifecycle-event dispatcher for@suaveplan/workflow. It is the seam between the engine's interna…
- hook —
- retry —
withRetryis the standalone, reusable retry executor that@suaveplan/workflowexposes alongside theStep.retryco…- retry —
withRetryis the standalone, reusable retry executor that@suaveplan/workflowexposes alongside theStep.retryco…
- retry —
- otel — This module is the only place in
@suaveplan/workflowthat talks to the OpenTelemetry API directly. Every other module…- instruments — This module is the only place in
@suaveplan/workflowthat talks to the OpenTelemetry API directly. Every other module…
- instruments — This module is the only place in
- conditional-step —
ConditionalStepis the branching primitive in@suaveplan/workflow. It evaluates aGuardFnagainst the context and…- conditional-step —
ConditionalStepis the branching primitive in@suaveplan/workflow. It evaluates aGuardFnagainst the context and…
- conditional-step —
- parallel-step —
ParallelStepgroups multipleStepinstances and runs them concurrently against a shared context. It is the only pla…- parallel-step —
ParallelStepgroups multipleStepinstances and runs them concurrently against a shared context. It is the only pla…
- parallel-step —
- step —
Stepis the atomic unit of execution in@suaveplan/workflow. Every sequential branch of a workflow ultimately runs…- step —
Stepis the atomic unit of execution in@suaveplan/workflow. Every sequential branch of a workflow ultimately runs…
- step —
- common — This module is the canonical home for every shared structural type in
@suaveplan/workflow. TheWorkflowEngine, buil… - workflow-hooks —
@suaveplan/workflowexposes two distinct hook surfaces. The first is the fire-and-forgetHookRegistry(`workflow:st… - utils — Pure helpers shared across every other module in
@suaveplan/workflow. Centralising them here keepsStep, the engine…- utils — Pure helpers shared across every other module in
@suaveplan/workflow. Centralising them here keepsStep, the engine…
- utils — Pure helpers shared across every other module in
Install
bun add @suaveplan/workflow
Quick Start
import { createWorkflow } from "@suaveplan/workflow";
interface OrderData {
orderId: string;
items: string[];
total: number;
}
const result = await createWorkflow<OrderData>("process-order")
.addStep("validate", (ctx) => {
if (ctx.items.length === 0) throw new Error("No items");
})
.build()
.run({
initialData: { orderId: "ord-1", items: ["widget"], total: 29.99 },
});
Modules
- Errors —
WORKFLOW_CODESandWorkflowError(STEP_FAILED,TIMEOUT,CANCELLED,COMPENSATION_FAILED,GUARD_REJECTED,RETRY_EXHAUSTED). - OTel instruments — counters + histogram.
- Types and transition hooks.
- Utils —
generateId,delay,withTimeout,calculateRetryDelay,now,defaultClock. - Step, Parallel step, Conditional step.
- Compensator and Saga.
- Hook registry and Retry middleware.
- Workflow context.
- Workflow builder.
- Workflow engine and Transition hooks.
Error Handling Strategies
builder.onError("stop"); // halt immediately on first failure
builder.onError("compensate"); // run reverse-order compensations, then stop
builder.onError("continue"); // log + skip the failure, run the rest
Retry Configuration
import { RetryPresets } from "@suaveplan/workflow";
builder
.addStep("flaky-api", async (ctx) => callExternalApi(ctx))
.withRetry(RetryPresets.standard());
builder.addStep("custom", (ctx) => callApi(ctx)).withRetry({
maxAttempts: 5,
strategy: "exponential",
delayMs: 500,
maxDelayMs: 10_000,
backoffMultiplier: 2,
});
Lifecycle Hooks
const engine = createWorkflowEngine(config);
engine.hooks.on("workflow:start", (p) =>
console.log(`Workflow ${p.workflowId} started`),
);
engine.hooks.on("step:complete", (p) =>
console.log(`Step ${p.stepId} completed`),
);
engine.hooks.on("step:fail", (p) =>
console.error(`Step ${p.stepId} failed:`, p.error),
);
Saga Pattern
import { createSaga, createStep } from "@suaveplan/workflow";
const saga = createSaga({ id: "booking", name: "Booking Saga" });
saga
.addStep(createStep({
id: "reserve",
name: "Reserve Seats",
execute: (ctx) => reserveSeats(ctx),
compensate: (ctx) => releaseSeats(ctx),
}))
.addStep(createStep({
id: "charge",
name: "Charge Payment",
execute: (ctx) => chargeCard(ctx),
compensate: (ctx) => refundCard(ctx),
}));
const result = await saga.execute(context);
// If "charge" fails, "reserve" is automatically compensated.
License
MIT