Initial Commit
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
# `orchestrator/compensate-runner`
|
||||
|
||||
## Purpose
|
||||
|
||||
`runFullCompensation(...)` is the orchestrator's per-instance
|
||||
compensation driver. It bridges the generic `compensation` module
|
||||
(which only knows about `CompensableStep` + retry loop) with the
|
||||
saga-specific concerns: looking up each completed step's registered
|
||||
compensate handler, transitioning the step through `compensating →
|
||||
compensated`, recording the compensation counter + span, and emitting
|
||||
`saga.step.compensated`. The result of the walk is returned as a
|
||||
structured outcome so the orchestrator can settle the instance state.
|
||||
|
||||
This module is split out from `orchestrator.ts` to keep the main
|
||||
file under the ~250-line soft limit and to isolate the compensation
|
||||
concerns from the forward step-dispatch loop.
|
||||
|
||||
## Features
|
||||
|
||||
- Walks completed step rows in reverse via the generic
|
||||
`runCompensation` helper.
|
||||
- Per-step: opens a `saga.compensate` span, transitions to
|
||||
`compensating`, invokes the registered compensate handler under a
|
||||
fresh `RequestContext`, increments
|
||||
`saga.compensations_total`, transitions to `compensated`, emits
|
||||
`saga.step.compensated`.
|
||||
- Honours per-saga `maxCompensationAttempts` (defaults to 5 via the
|
||||
`compensation` module).
|
||||
- On terminal failure: transitions the failed step row back to
|
||||
`failed` so the persistence layer reflects the broken state.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
Internal helper — typically only consumed by `createSagaOrchestrator`:
|
||||
|
||||
```ts
|
||||
import { runFullCompensation } from "@suaveplan/api-saga/orchestrator/compensate-runner";
|
||||
|
||||
const result = await runFullCompensation({
|
||||
instance,
|
||||
maxCompensationAttempts: 5,
|
||||
principal,
|
||||
persistence,
|
||||
registry,
|
||||
emitter,
|
||||
instruments,
|
||||
requestContextFactory,
|
||||
nowMs: () => Date.now(),
|
||||
});
|
||||
|
||||
if (result.outcome === "compensated") {
|
||||
persistence.transitionInstance(instance.id, "compensated");
|
||||
}
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
A consumer that wants to drive compensation manually (e.g. an
|
||||
operator-triggered "rollback this saga" admin command) can call
|
||||
this directly without going through `start()` or `resume()`. The
|
||||
caller is responsible for transitioning the instance into the
|
||||
matching terminal state afterwards:
|
||||
|
||||
```ts
|
||||
import { runFullCompensation } from "@suaveplan/api-saga";
|
||||
|
||||
const result = await runFullCompensation({
|
||||
instance: persistence.requireInstance(id),
|
||||
maxCompensationAttempts: 3,
|
||||
principal: null,
|
||||
persistence,
|
||||
registry,
|
||||
emitter,
|
||||
instruments,
|
||||
requestContextFactory,
|
||||
nowMs: () => Date.now(),
|
||||
});
|
||||
|
||||
persistence.transitionInstance(
|
||||
id,
|
||||
result.outcome === "compensated"
|
||||
? "compensated"
|
||||
: "compensation_failed"
|
||||
);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `runFullCompensation(args): Promise<CompensationFlowOutcome>`.
|
||||
- `args`:
|
||||
- `instance: SagaInstanceRow`,
|
||||
- `maxCompensationAttempts: number`,
|
||||
- `principal: Principal | null`,
|
||||
- `persistence, registry, emitter, instruments,
|
||||
requestContextFactory, nowMs` — injected dependencies.
|
||||
- `CompensationFlowOutcome`:
|
||||
- `{ outcome: "compensated" }`,
|
||||
- `{ outcome: "compensation_failed", failedStep: string }`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The compensation walk only considers step rows whose persisted
|
||||
state is `completed`. Steps in `pending`, `in_flight`, or `failed`
|
||||
have either not run or already failed; running compensation against
|
||||
them would either invoke a compensate handler with no matching
|
||||
output or repeat work the runner already declared lost.
|
||||
|
||||
`updateStepPayload` is invoked AFTER the compensate handler so the
|
||||
persistence layer records the final attempt count rather than the
|
||||
initial one. This matters for the operator-facing dashboards that
|
||||
read the row to display "compensation took N attempts".
|
||||
Reference in New Issue
Block a user