7.2 KiB
Enterprise migration: @suaveplan/workflow
Package: @suaveplan/workflow
Status: PROPOSED
Type: new-package (migration from upstream)
Date: 2026-05-24
Design: ./design.md (deferred — covered inline)
Spec delta: ./specs/workflow/spec.md
Why
Upstream ../suaveplan/packages/universal/workflow/ ships a tree-shakeable, zero-runtime-dep workflow / pipeline orchestration core with sequential + parallel step execution, conditional branching, saga compensation, retry strategies, timeouts, lifecycle hooks, and a fluent builder. It is consumed by higher-level packages (rules-engine, form-builder, multi-step business processes) and must land on genesis before any of those can migrate. The 43-file source has 100% test parity in upstream and we preserve that surface verbatim while bolting on the standard genesis additions: stable error codes via defineCodes, Zod schemas at the public boundary, OTel instruments, injectable Clock for deterministic timing.
What changes
New package @suaveplan/workflow at packages/foundation/flow/workflow/ (sibling to fsm, job-scheduler, streams). The public surface mirrors upstream exactly:
- ADDED:
createWorkflow<TData>(name, id?)andWorkflowBuilder<TData>— fluent builder withaddStep,on,onError,withTimeout,buildConfig,build,run. - ADDED:
StepBuilder<TData>—withCompensation,withGuard,withRetry,withTimeout,build. - ADDED:
WorkflowEngine<TData>andcreateWorkflowEngine— the orchestrator withrun,cancel,hooksaccessor. - ADDED:
Step<TCtx, TResult>andcreateStep— base unit with guard / retry / timeout / compensate. - ADDED:
ParallelStep<TCtx>/createParallelStepwithfailFast+maxConcurrency. - ADDED:
ConditionalStep<TCtx>/createConditionalStepwithonTrue/onFalsebranches. - ADDED:
Saga<TCtx>/createSaga— sequential steps with automatic reverse-order compensation. - ADDED:
Compensator<TCtx>/createCompensator— explicit compensation registry +compensateAll/compensateSteps. - ADDED:
HookRegistry<TCtx>/createHookRegistry+buildStepPayload— lifecycle-event dispatch. - ADDED:
withRetry,createRetryWrapper,resolveRetryConfig,RetryPresets— middleware-style retry. - ADDED:
WorkflowContext<TData>/createContext+ContextSnapshot<TData>. - ADDED: utilities —
generateId,delay,withTimeout,calculateRetryDelay,now. - ADDED: constants —
DEFAULT_*retry / timeout values. - ADDED: types —
WorkflowStatus,StepStatus,StepExecutor,CompensationFn,GuardFn,RetryStrategy,RetryConfig,StepResult,StepDefinition,WorkflowResult,HookEvent,HookPayload,HookListener,WorkflowConfig, plusWorkflowTransitionData/WorkflowTransitionHookMap/WorkflowTransitioningHandlerfor the data-pipeline hook. - ADDED:
WORKFLOW_CODES+WorkflowErrorvia@suaveplan/error'sdefineCodes— codesWORKFLOW.STEP_FAILED,WORKFLOW.TIMEOUT,WORKFLOW.CANCELLED,WORKFLOW.COMPENSATION_FAILED,WORKFLOW.GUARD_REJECTED,WORKFLOW.RETRY_EXHAUSTED.
Genesis-only additions on top of upstream:
WorkflowErrorextendsSuaveplanError<WorkflowCode>(upstream throws plainError).- Zod-validated
WorkflowConfigschema atbuildConfig()boundary. - OTel counter
workflow.step.executed_total{outcome,step_id}, counterworkflow.step.compensated_total{outcome,step_id}, histogramworkflow.step.duration_ms{step_id}— emitted fromStep.runandCompensator.compensateAll. - Injectable
Clock(defaults toDate.now) onStep,WorkflowEngine,Saga,Compensator,HookRegistry— makesnow()-driven timing deterministic in tests.
Parity Checklist
| Capability | Upstream | Migrated | Notes |
|---|---|---|---|
Fluent builder (createWorkflow, WorkflowBuilder, StepBuilder) |
✅ | ✅ | |
WorkflowEngine.run with onError: stop/compensate/continue |
✅ | ✅ | |
WorkflowEngine.cancel mid-run |
✅ | ✅ | |
Step with guard / retry / timeout / compensate |
✅ | ✅ | |
ParallelStep with failFast + maxConcurrency batching |
✅ | ✅ | |
ConditionalStep with onTrue / onFalse + skip when no false branch |
✅ | ✅ | |
Saga orchestrator with reverse-order compensation |
✅ | ✅ | |
Compensator with compensateAll + compensateSteps + continueOnError |
✅ | ✅ | |
HookRegistry (on, once, off, emit, listenerCount) |
✅ | ✅ | |
buildStepPayload helper |
✅ | ✅ | |
withRetry, createRetryWrapper, resolveRetryConfig, RetryPresets |
✅ | ✅ | |
WorkflowContext (updateData, setData, addStepResult, setMeta/getMeta, snapshot, reset) |
✅ | ✅ | |
calculateRetryDelay fixed/linear/exponential |
✅ | ✅ | |
generateId, delay, withTimeout, now utilities |
✅ | ✅ | |
Constants (DEFAULT_*) |
✅ | ✅ | |
Transition data-pipeline hook (workflow:transitioning) |
✅ | ✅ | |
| Subpath exports per feature module | ✅ | ✅ | |
Plain Error throws |
✅ | ⬆️ | Replaced by WorkflowError + stable codes per genesis rule. |
| Zod schema validation at config boundary | 🚫 | ⬆️ | Genesis addition — WorkflowConfig is Zod-validated in buildConfig. |
| OTel counters + histogram | 🚫 | ⬆️ | Genesis addition — step execution + compensation instrumented. |
Injectable Clock |
🚫 | ⬆️ | Genesis addition — defaults to Date.now, takes Clock from @suaveplan/types. |
Out of scope
- Durable replay / persistence — upstream does not ship this; out of scope for the first cut and would be a separate sibling package (
workflow-store) per the genesis modular pattern. - Distributed execution — upstream is single-process; genesis preserves that boundary.
Source mapping
This change derives from ../suaveplan/packages/universal/workflow/src/ (43 files). One genesis feature dir per upstream feature dir; file names preserved.
| Requirement | Source path |
|---|---|
| Step run / compensate / guard | src/steps/step/step.ts |
| Parallel step | src/steps/parallel-step/parallel-step.ts |
| Conditional step | src/steps/conditional-step/conditional-step.ts |
| Compensator | src/compensation/compensator/compensator.ts |
| Saga | src/compensation/saga/saga.ts |
| Workflow context | src/core/context/context.ts |
| Workflow builder | src/core/workflow-builder/workflow-builder.ts |
| Workflow engine | src/core/workflow-engine/workflow-engine.ts |
| Hook registry | src/middleware/hook/hook.ts |
| Retry middleware | src/middleware/retry/retry.ts |
| Utils + constants | src/utils/utils.ts, src/constants/index.ts |
| Common + hook types | src/types/common.ts, src/types/workflow-hooks.ts |
Verification approach
Co-located .test.ts per feature directory under src/. Every requirement in specs/workflow/spec.md has a **Verified by:** line pointing at a concrete it(...) title in one of these files. 100% coverage gate via bunx turbo test:coverage --filter=@suaveplan/workflow.
Impact
Affected dependents
No genesis-side dependents yet (bunx turbo ls --filter=...@suaveplan/workflow returns empty — additive only). The package is a prerequisite for future migrations of rules-engine and form-builder.