8.8 KiB
@suaveplan/api-saga - Saga orchestrator for the Suaveplan API plugin system. Drives manifest-declared multi-plugin workflows in declared order with reverse-order compensation on failure, per-step idempotency keyed by {sagaId, stepName}, persisted state across host restarts via the host's ledger DB (suaveplan_saga_instances + suaveplan_saga_steps), capability-gated cross-tenant dispatch, typed event-bus emission of every step + instance transition, and an in-memory MeterProvider-friendly OTel surface (saga.instance span, saga.step span, saga.compensate span, saga.instances_total counter, saga.step_duration_ms histogram, saga.compensations_total counter, saga.in_flight gauge). Exposes a SagaParticipantRegistry the host populates from plugin manifests' sagas.owns[] and sagas.participatesIn[] declarations, refuses install on duplicate owners or unknown participant wiring, and ships runSagaParticipantConformance on the ./testing subpath so every step + compensate handler authored against @suaveplan/api-types' SagaParticipant contract is pinned to the same invariants.
Saga orchestrator for the Suaveplan API plugin system. Drives
manifest-declared multi-plugin workflows in declared order with
reverse-order compensation on failure, per-step idempotency keyed by
{sagaId, stepName}, persisted state across host restarts via the
host's ledger DB, a capability-gated cross-tenant guard, typed
event-bus emission for every step + instance transition, and an
OpenTelemetry instrument surface covering saga.instances_total,
saga.step_duration_ms, saga.compensations_total, and saga.in_flight.
API plugin system — see the umbrella change
the design document and the spec at
api-saga/spec.md.
Documentation tree
- compensation — On step failure the saga orchestrator walks completed steps in reverse order and invokes each step's compensation handl…
- compensation — On step failure the saga orchestrator walks completed steps in reverse order and invokes each step's compensation handl…
- error-codes — Stable registry for every failure raised by
@suaveplan/api-saga. The saga orchestrator surfaces ten distinct fault mo…- error-codes — Stable registry for every failure raised by
@suaveplan/api-saga. The saga orchestrator surfaces ten distinct fault mo…
- error-codes — Stable registry for every failure raised by
- events — The saga orchestrator publishes every step + instance transition onto the host's
EventBus<SagaEventMap>. Sibling plug…- events — The saga orchestrator publishes every step + instance transition onto the host's
EventBus<SagaEventMap>. Sibling plug…
- events — The saga orchestrator publishes every step + instance transition onto the host's
- idempotency — Per design doc §9.1 the saga orchestrator MUST wrap every step invocation in an idempotency guard keyed by `{sagaId, st…
- idempotency — Per design doc §9.1 the saga orchestrator MUST wrap every step invocation in an idempotency guard keyed by `{sagaId, st…
- instruments — The saga orchestrator emits an OTel surface matching design doc §9.1: three spans (
saga.instance,saga.step, `saga.…- instruments — The saga orchestrator emits an OTel surface matching design doc §9.1: three spans (
saga.instance,saga.step, `saga.…
- instruments — The saga orchestrator emits an OTel surface matching design doc §9.1: three spans (
- orchestrator —
createSagaOrchestrator(deps)is the top-level factory plugin authors and host integrators consume. It binds together…- compensate-runner —
runFullCompensation(...)is the orchestrator's per-instance compensation driver. It bridges the generic `compensation… - orchestrator —
createSagaOrchestrator(deps)is the top-level factory plugin authors and host integrators consume. It binds together… - step-runner —
runStep(...)is the orchestrator's per-step engine. It bundles every cross-cutting concern that fires around an actio…
- compensate-runner —
- persistence —
SagaPersistenceis the sole path through which the orchestrator mutates the host's saga tables (`suaveplan_saga_insta…- persistence —
SagaPersistenceis the sole path through which the orchestrator mutates the host's saga tables (`suaveplan_saga_insta…
- persistence —
- recovery — When a host restarts, the saga orchestrator's in-memory state goes with it — but the saga itself is durable because eve…
- recovery — When a host restarts, the saga orchestrator's in-memory state goes with it — but the saga itself is durable because eve…
- registry — The participant registry is the host-side compendium of every plugin that owns a saga and every plugin that participate…
- participant-registry — The participant registry is the host-side compendium of every plugin that owns a saga and every plugin that participate…
- state — The saga lifecycle has two coupled finite-state machines: the instance state (overall saga health) and the per-step sta…
- state-machine — The saga lifecycle has two coupled finite-state machines: the instance state (overall saga health) and the per-step sta…
- tenant — Design doc §9.1 (final paragraph) requires that a saga step whose target tenant differs from the saga initiator's tenan…
- cross-tenant — Design doc §9.1 (final paragraph) requires that a saga step whose target tenant differs from the saga initiator's tenan…
- testing —
runSagaParticipantConformance(factory)is the canonical contract suite every saga step + compensate handler in the mo…- testing —
runSagaParticipantConformance(factory)is the canonical contract suite every saga step + compensate handler in the mo…
- testing —
Modules
- orchestrator —
createSagaOrchestrator(deps)factory +SagaOrchestratorpublic surface (start,resume,cancel,listInstances,getInstance). - orchestrator/step-runner — per-step engine.
- orchestrator/compensate-runner — reverse-order compensation driver.
- registry/participant-registry — owner vs participant registration; validates manifest wiring.
- persistence — state-machine-guarded CRUD against
suaveplan_saga_instances+suaveplan_saga_steps. - recovery — boot-time replay of non-terminal instances.
- idempotency —
@suaveplan/idempotencywrapper keyed by{sagaId, stepName}with manual opt-out. - events — typed
SagaEventMapand Zod schemas for every emitted payload. - compensation — generic reverse-walk retry runner.
- tenant/cross-tenant —
saga.cross-tenantcapability gate. - state/state-machine — instance + step state machines.
- instruments — OTel surface.
- error-codes — 10
SAGA.*codes viaSagaError. - testing —
runSagaParticipantConformancepublished on the./testingsubpath.
Owner vs participant
A plugin owns a saga when its manifest declares sagas.owns[] —
the entry pins the saga name and the ordered list of {plugin, action, compensate} steps. The owner is solely responsible for
choosing the step sequence.
A plugin participates in a saga when its manifest declares
sagas.participatesIn: ["owner-plugin/saga-name"] AND it exports
named actions[name] + compensations[name] handlers. The
orchestrator dispatches step events into the participant's handlers
via the SagaParticipantRegistry.
Two plugins MAY NOT both own the same saga name — the registry
refuses the second registration with SAGA.DUPLICATE_OWNER. A
participant whose actions[name] is missing fails at install with
SAGA.UNKNOWN_PARTICIPANT, before any saga can be started.
Status
0.1.0 — bottom-up rebuild stage. Will graduate to 1.0.0 per the
foundation umbrella's Stage 6 acceptance gate (a multi-step saga in api-plugin-example runs end-to-end including a compensation path).