@nodii/saga 0.0.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin-service.d.ts +55 -0
- package/dist/admin-service.d.ts.map +1 -0
- package/dist/admin-service.js +161 -0
- package/dist/admin-service.js.map +1 -0
- package/dist/async-step.d.ts +43 -0
- package/dist/async-step.d.ts.map +1 -0
- package/dist/async-step.js +166 -0
- package/dist/async-step.js.map +1 -0
- package/dist/context.d.ts +18 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +28 -0
- package/dist/context.js.map +1 -0
- package/dist/decorator.d.ts +24 -0
- package/dist/decorator.d.ts.map +1 -0
- package/dist/decorator.js +69 -0
- package/dist/decorator.js.map +1 -0
- package/dist/idempotency.d.ts +22 -0
- package/dist/idempotency.d.ts.map +1 -0
- package/dist/idempotency.js +44 -0
- package/dist/idempotency.js.map +1 -0
- package/dist/index.d.ts +20 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +34 -4
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +45 -0
- package/dist/init.d.ts.map +1 -0
- package/dist/init.js +86 -0
- package/dist/init.js.map +1 -0
- package/dist/interceptor.d.ts +5 -0
- package/dist/interceptor.d.ts.map +1 -0
- package/dist/interceptor.js +34 -0
- package/dist/interceptor.js.map +1 -0
- package/dist/migrations/index.d.ts +19 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/index.js +106 -0
- package/dist/migrations/index.js.map +1 -0
- package/dist/reaper.d.ts +27 -0
- package/dist/reaper.d.ts.map +1 -0
- package/dist/reaper.js +79 -0
- package/dist/reaper.js.map +1 -0
- package/dist/registry.d.ts +7 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +23 -0
- package/dist/registry.js.map +1 -0
- package/dist/saga.d.ts +48 -0
- package/dist/saga.d.ts.map +1 -0
- package/dist/saga.js +384 -0
- package/dist/saga.js.map +1 -0
- package/dist/signal-bus/index.d.ts +2 -0
- package/dist/signal-bus/index.d.ts.map +1 -0
- package/dist/signal-bus/index.js +6 -0
- package/dist/signal-bus/index.js.map +1 -0
- package/dist/signal-bus/redis-stream.d.ts +43 -0
- package/dist/signal-bus/redis-stream.d.ts.map +1 -0
- package/dist/signal-bus/redis-stream.js +189 -0
- package/dist/signal-bus/redis-stream.js.map +1 -0
- package/dist/signals.d.ts +29 -0
- package/dist/signals.d.ts.map +1 -0
- package/dist/signals.js +113 -0
- package/dist/signals.js.map +1 -0
- package/dist/state-store/index.d.ts +2 -0
- package/dist/state-store/index.d.ts.map +1 -0
- package/dist/state-store/index.js +6 -0
- package/dist/state-store/index.js.map +1 -0
- package/dist/state-store/postgres.d.ts +47 -0
- package/dist/state-store/postgres.d.ts.map +1 -0
- package/dist/state-store/postgres.js +270 -0
- package/dist/state-store/postgres.js.map +1 -0
- package/dist/test-doubles/in-memory-signal-bus.d.ts +38 -0
- package/dist/test-doubles/in-memory-signal-bus.d.ts.map +1 -0
- package/dist/test-doubles/in-memory-signal-bus.js +68 -0
- package/dist/test-doubles/in-memory-signal-bus.js.map +1 -0
- package/dist/test-doubles/in-memory-state-store.d.ts +26 -0
- package/dist/test-doubles/in-memory-state-store.d.ts.map +1 -0
- package/dist/test-doubles/in-memory-state-store.js +87 -0
- package/dist/test-doubles/in-memory-state-store.js.map +1 -0
- package/dist/test-doubles/index.d.ts +4 -0
- package/dist/test-doubles/index.d.ts.map +1 -0
- package/dist/test-doubles/index.js +14 -0
- package/dist/test-doubles/index.js.map +1 -0
- package/dist/types.d.ts +260 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +103 -0
- package/dist/types.js.map +1 -0
- package/dist/uuid.d.ts +3 -0
- package/dist/uuid.d.ts.map +1 -0
- package/dist/uuid.js +62 -0
- package/dist/uuid.js.map +1 -0
- package/package.json +33 -7
- package/src/migrations/001-saga-state.sql +79 -0
package/dist/saga.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { type CreateSagaOpts, type SagaHandle, type StepOpts } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* § 5.2 + § 5.8 — createSaga.
|
|
4
|
+
*
|
|
5
|
+
* Resume detection: if a `ResumeContext` is primed on the ALS (set by
|
|
6
|
+
* `withSaga`), the function reloads the saga from the state store and
|
|
7
|
+
* passes the persisted `step_outputs` map so completed steps short-circuit
|
|
8
|
+
* to their cached output via the idempotency layer.
|
|
9
|
+
*/
|
|
10
|
+
export declare function createSaga<O>(opts: CreateSagaOpts, fn: () => Promise<O>): Promise<O>;
|
|
11
|
+
/**
|
|
12
|
+
* § 5.8 — withSaga primes a ResumeContext + an inner createSaga reloads.
|
|
13
|
+
*
|
|
14
|
+
* v0.1.0 surface: `withSaga(handle, fn)` runs `fn` in the saga's ALS scope
|
|
15
|
+
* with `resume.isResume = true`. The expected usage is for the inner
|
|
16
|
+
* function to call `createSaga` with the same type — that's how the registry
|
|
17
|
+
* gets re-entered.
|
|
18
|
+
*/
|
|
19
|
+
export declare function withSaga<O>(sagaId: string, fn: () => Promise<O>): Promise<O>;
|
|
20
|
+
/**
|
|
21
|
+
* § 5.2 — step.
|
|
22
|
+
*
|
|
23
|
+
* Pipeline:
|
|
24
|
+
* 1. update saga_state.current_step = name
|
|
25
|
+
* 2. run handler via idempotency.wrapForSagaStep
|
|
26
|
+
* 3. on success: persist output, push undo record onto saga's undo stack
|
|
27
|
+
* 4. on failure: throw SagaStepFailed; createSaga catches + runs compensation
|
|
28
|
+
*
|
|
29
|
+
* `mode: 'best-effort'` per-step (or via the undo object form) makes
|
|
30
|
+
* compensation lenient — a thrown undo records `compensated_with_errors`
|
|
31
|
+
* instead of `compensation_failed`.
|
|
32
|
+
*/
|
|
33
|
+
export declare function step<O>(name: string, opts: StepOpts<O>): Promise<O>;
|
|
34
|
+
/**
|
|
35
|
+
* § 5.2 — stepParallel. Fans out concurrently; collects results into an array.
|
|
36
|
+
*
|
|
37
|
+
* Compensation semantics match `step` — undos are pushed onto the stack in
|
|
38
|
+
* the order they completed, so reverse-order traversal is preserved.
|
|
39
|
+
*/
|
|
40
|
+
export declare function stepParallel<O>(steps: {
|
|
41
|
+
name: string;
|
|
42
|
+
opts: StepOpts<O>;
|
|
43
|
+
}[]): Promise<O[]>;
|
|
44
|
+
/** Public cancel entry-point (also reachable via handle.cancel). */
|
|
45
|
+
export declare function cancel(sagaId: string, reason: string): Promise<void>;
|
|
46
|
+
/** Read-only accessor for the active saga handle in scope. */
|
|
47
|
+
export declare function currentSaga(): SagaHandle | null;
|
|
48
|
+
//# sourceMappingURL=saga.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"saga.d.ts","sourceRoot":"","sources":["../src/saga.ts"],"names":[],"mappings":"AAoBA,OAAO,EAEL,KAAK,cAAc,EAEnB,KAAK,UAAU,EAEf,KAAK,QAAQ,EAGd,MAAM,SAAS,CAAC;AAmFjB;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAAC,CAAC,EAChC,IAAI,EAAE,cAAc,EACpB,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACnB,OAAO,CAAC,CAAC,CAAC,CA8EZ;AAED;;;;;;;GAOG;AACH,wBAAsB,QAAQ,CAAC,CAAC,EAC9B,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACnB,OAAO,CAAC,CAAC,CAAC,CAGZ;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAgGzE;AAeD;;;;;GAKG;AACH,wBAAsB,YAAY,CAAC,CAAC,EAClC,KAAK,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAA;CAAE,EAAE,GAC3C,OAAO,CAAC,CAAC,EAAE,CAAC,CAEd;AAuFD,oEAAoE;AACpE,wBAAsB,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE1E;AAED,8DAA8D;AAC9D,wBAAgB,WAAW,IAAI,UAAU,GAAG,IAAI,CAE/C"}
|
package/dist/saga.js
ADDED
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
// createSaga / withSaga / step / stepParallel / saga.cancel.
|
|
2
|
+
//
|
|
3
|
+
// Spec §§ 5.2, 5.3, 5.8. v0.1.0 ships the deterministic core: state-store
|
|
4
|
+
// CRUD, ALS-primed handle, idempotency-wrapped step execution, reverse-order
|
|
5
|
+
// undo on failure (strict-mode halt vs best-effort `compensated_with_errors`),
|
|
6
|
+
// `cancel()` → status='cancelled' + D33 audit event.
|
|
7
|
+
//
|
|
8
|
+
// Resume detection (§ 5.8) is wired but the reaper / grace-period orchestrator
|
|
9
|
+
// is deferred (`// TODO saga-v0.1.x:`); same for cross-saga compensation as
|
|
10
|
+
// library-spawned child saga (we record entries on `compensation_log` instead).
|
|
11
|
+
import { getResumeContext, getSagaContext, requireSagaContext, runInSagaContext, runWithResumeContext, } from "./context";
|
|
12
|
+
import { requireSaga } from "./init";
|
|
13
|
+
import { uuidv7 } from "./uuid";
|
|
14
|
+
import { SagaCompensationFailed, SagaStepFailed, } from "./types";
|
|
15
|
+
import { beginAsyncStep, SagaPausedSentinel } from "./async-step";
|
|
16
|
+
const nowIso = () => new Date().toISOString();
|
|
17
|
+
const TERMINAL_STATUSES = new Set([
|
|
18
|
+
"completed",
|
|
19
|
+
"compensated",
|
|
20
|
+
"compensation_failed",
|
|
21
|
+
"compensated_with_errors",
|
|
22
|
+
"compensated_manual",
|
|
23
|
+
"technical_failure",
|
|
24
|
+
"cancelled",
|
|
25
|
+
]);
|
|
26
|
+
// We track per-saga undo stacks on the context so resume + cancel paths
|
|
27
|
+
// don't lose them. Keyed by saga.id to support nested withSaga calls.
|
|
28
|
+
const undoStacks = new Map();
|
|
29
|
+
function pushUndo(sagaId, record) {
|
|
30
|
+
const stack = undoStacks.get(sagaId) ?? [];
|
|
31
|
+
stack.push(record);
|
|
32
|
+
undoStacks.set(sagaId, stack);
|
|
33
|
+
}
|
|
34
|
+
function takeUndoStack(sagaId) {
|
|
35
|
+
const stack = undoStacks.get(sagaId) ?? [];
|
|
36
|
+
undoStacks.delete(sagaId);
|
|
37
|
+
return stack;
|
|
38
|
+
}
|
|
39
|
+
function newSagaRow(opts) {
|
|
40
|
+
const id = opts.sagaId ?? uuidv7();
|
|
41
|
+
return {
|
|
42
|
+
id,
|
|
43
|
+
tenant_id: opts.tenantId ?? null,
|
|
44
|
+
type: opts.type,
|
|
45
|
+
status: "active",
|
|
46
|
+
current_step: null,
|
|
47
|
+
step_outputs: {},
|
|
48
|
+
episode: 1,
|
|
49
|
+
trigger_trace_id: opts.triggerTraceId ?? id,
|
|
50
|
+
trigger_span_id: opts.triggerSpanId ?? id,
|
|
51
|
+
trigger_request_id: opts.triggerRequestId ?? id,
|
|
52
|
+
parent_saga_id: opts.parent?.sagaId ?? null,
|
|
53
|
+
parent_relationship: opts.parentRelationship ?? "child",
|
|
54
|
+
children: [],
|
|
55
|
+
started_at: nowIso(),
|
|
56
|
+
completed_at: null,
|
|
57
|
+
next_resume_at: null,
|
|
58
|
+
failure_reason: null,
|
|
59
|
+
failure_step: null,
|
|
60
|
+
compensation_log: [],
|
|
61
|
+
last_admin_action: null,
|
|
62
|
+
last_admin_actor_id: null,
|
|
63
|
+
last_admin_at: null,
|
|
64
|
+
input: opts.input,
|
|
65
|
+
cancel_reason: null,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
async function emitAuditEvent(action, sagaId, payload) {
|
|
69
|
+
const cfg = requireSaga();
|
|
70
|
+
if (!cfg.telemetryAudit)
|
|
71
|
+
return;
|
|
72
|
+
await cfg.telemetryAudit.emit({
|
|
73
|
+
action,
|
|
74
|
+
target_kind: "saga",
|
|
75
|
+
target_id: sagaId,
|
|
76
|
+
payload,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* § 5.2 + § 5.8 — createSaga.
|
|
81
|
+
*
|
|
82
|
+
* Resume detection: if a `ResumeContext` is primed on the ALS (set by
|
|
83
|
+
* `withSaga`), the function reloads the saga from the state store and
|
|
84
|
+
* passes the persisted `step_outputs` map so completed steps short-circuit
|
|
85
|
+
* to their cached output via the idempotency layer.
|
|
86
|
+
*/
|
|
87
|
+
export async function createSaga(opts, fn) {
|
|
88
|
+
const cfg = requireSaga();
|
|
89
|
+
const resume = getResumeContext();
|
|
90
|
+
let row;
|
|
91
|
+
if (resume?.isResume) {
|
|
92
|
+
const existing = await cfg.stateStore.getSaga(resume.sagaId);
|
|
93
|
+
if (!existing) {
|
|
94
|
+
// Treat as a fresh saga — defensive: resume context referenced a row
|
|
95
|
+
// the store no longer has.
|
|
96
|
+
row = newSagaRow({ ...opts, sagaId: resume.sagaId });
|
|
97
|
+
await cfg.stateStore.createSaga(row);
|
|
98
|
+
await emitAuditEvent("saga_started", row.id, { type: row.type });
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
row = existing;
|
|
102
|
+
await cfg.stateStore.updateSagaStatus(row.id, { status: "active" });
|
|
103
|
+
row.status = "active";
|
|
104
|
+
await emitAuditEvent("saga_resumed", row.id, { episode: row.episode });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
row = newSagaRow(opts);
|
|
109
|
+
await cfg.stateStore.createSaga(row);
|
|
110
|
+
await emitAuditEvent("saga_started", row.id, { type: row.type });
|
|
111
|
+
}
|
|
112
|
+
const handle = {
|
|
113
|
+
id: row.id,
|
|
114
|
+
type: row.type,
|
|
115
|
+
tenantId: row.tenant_id,
|
|
116
|
+
async cancel(reason) {
|
|
117
|
+
await cancelSaga(row.id, reason);
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
try {
|
|
121
|
+
const result = await runInSagaContext({ handle, row, resume, completedSteps: [] }, fn);
|
|
122
|
+
// If the saga was cancelled (or otherwise terminalized) mid-flight,
|
|
123
|
+
// do NOT overwrite to "completed". Re-read once to settle.
|
|
124
|
+
const latest = await cfg.stateStore.getSaga(row.id);
|
|
125
|
+
if (latest &&
|
|
126
|
+
!TERMINAL_STATUSES.has(latest.status) &&
|
|
127
|
+
latest.status !== "paused") {
|
|
128
|
+
await cfg.stateStore.updateSagaStatus(row.id, {
|
|
129
|
+
status: "completed",
|
|
130
|
+
completed_at: nowIso(),
|
|
131
|
+
});
|
|
132
|
+
await emitAuditEvent("saga_completed", row.id);
|
|
133
|
+
}
|
|
134
|
+
undoStacks.delete(row.id);
|
|
135
|
+
return result;
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
// Pattern 2 sentinel — saga paused at an async step boundary. NOT a
|
|
139
|
+
// failure; the saga remains 'paused' and a downstream completion-event
|
|
140
|
+
// resumes it via withSaga(). Return the saga handle's id as the
|
|
141
|
+
// result placeholder (sentinel.sagaId).
|
|
142
|
+
if (err instanceof SagaPausedSentinel) {
|
|
143
|
+
// Don't touch status — beginAsyncStep already set it.
|
|
144
|
+
// Don't clear the undo stack — a future resume might need it.
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
// step() throws SagaStepFailed when its do-fn fails; we run compensation
|
|
148
|
+
// here so the user-facing API stays "createSaga rejects if any step
|
|
149
|
+
// fails after compensation runs". v0.1.0 compensation walks the per-saga
|
|
150
|
+
// undoStack in reverse.
|
|
151
|
+
if (err instanceof SagaStepFailed) {
|
|
152
|
+
await runCompensation(row.id, err.stepName);
|
|
153
|
+
throw err;
|
|
154
|
+
}
|
|
155
|
+
// Non-step errors (programming errors, etc.) propagate without changing
|
|
156
|
+
// status — the caller decides what to do.
|
|
157
|
+
throw err;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* § 5.8 — withSaga primes a ResumeContext + an inner createSaga reloads.
|
|
162
|
+
*
|
|
163
|
+
* v0.1.0 surface: `withSaga(handle, fn)` runs `fn` in the saga's ALS scope
|
|
164
|
+
* with `resume.isResume = true`. The expected usage is for the inner
|
|
165
|
+
* function to call `createSaga` with the same type — that's how the registry
|
|
166
|
+
* gets re-entered.
|
|
167
|
+
*/
|
|
168
|
+
export async function withSaga(sagaId, fn) {
|
|
169
|
+
const ctx = { isResume: true, sagaId };
|
|
170
|
+
return runWithResumeContext(ctx, fn);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* § 5.2 — step.
|
|
174
|
+
*
|
|
175
|
+
* Pipeline:
|
|
176
|
+
* 1. update saga_state.current_step = name
|
|
177
|
+
* 2. run handler via idempotency.wrapForSagaStep
|
|
178
|
+
* 3. on success: persist output, push undo record onto saga's undo stack
|
|
179
|
+
* 4. on failure: throw SagaStepFailed; createSaga catches + runs compensation
|
|
180
|
+
*
|
|
181
|
+
* `mode: 'best-effort'` per-step (or via the undo object form) makes
|
|
182
|
+
* compensation lenient — a thrown undo records `compensated_with_errors`
|
|
183
|
+
* instead of `compensation_failed`.
|
|
184
|
+
*/
|
|
185
|
+
export async function step(name, opts) {
|
|
186
|
+
if (opts.async === true) {
|
|
187
|
+
// Pattern 2: beginAsyncStep persists `paused` + writes the outbox row
|
|
188
|
+
// and throws SagaPausedSentinel — control unwinds back through
|
|
189
|
+
// createSaga which catches the sentinel and returns the saga handle.
|
|
190
|
+
return beginAsyncStep(name, opts);
|
|
191
|
+
}
|
|
192
|
+
const cfg = requireSaga();
|
|
193
|
+
const sagaCtx = requireSagaContext("step");
|
|
194
|
+
const row = sagaCtx.row;
|
|
195
|
+
await cfg.stateStore.updateSagaStatus(row.id, { current_step: name });
|
|
196
|
+
row.current_step = name;
|
|
197
|
+
// Idempotency-key payload — narrow + alphabetically-keyed JSON to be
|
|
198
|
+
// byte-equal with the Go (stepKeyPayload struct) and Python
|
|
199
|
+
// (json.dumps(..., sort_keys=True)) ports. Spec § 5.4: the cache key
|
|
200
|
+
// is sha256(saga_id + step_name + serialized_input) and parity-fence
|
|
201
|
+
// requires the three languages produce the same hash.
|
|
202
|
+
let serializedInput;
|
|
203
|
+
try {
|
|
204
|
+
const payload = {
|
|
205
|
+
name,
|
|
206
|
+
persist_fields: [...(opts.persistFields ?? [])].sort(),
|
|
207
|
+
};
|
|
208
|
+
if (opts.input !== undefined)
|
|
209
|
+
payload.input = opts.input;
|
|
210
|
+
// Stable key order: input < name < persist_fields (alphabetical).
|
|
211
|
+
serializedInput = JSON.stringify(payload, [
|
|
212
|
+
"input",
|
|
213
|
+
"name",
|
|
214
|
+
"persist_fields",
|
|
215
|
+
]);
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
serializedInput = `${name}::nonserializable::${row.id}`;
|
|
219
|
+
}
|
|
220
|
+
const handler = opts.do;
|
|
221
|
+
let result;
|
|
222
|
+
try {
|
|
223
|
+
result = await cfg.idempotency.wrapForSagaStep({
|
|
224
|
+
sagaId: row.id,
|
|
225
|
+
stepName: name,
|
|
226
|
+
serializedInput: opts.idempotencyKey ?? serializedInput,
|
|
227
|
+
handler,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
catch (err) {
|
|
231
|
+
// Surface the store-write failure alongside the step failure —
|
|
232
|
+
// matches Go's errors.Join + Python's logger.warn-then-raise.
|
|
233
|
+
// Silently swallowing it would lose visibility into transient
|
|
234
|
+
// production-store errors.
|
|
235
|
+
try {
|
|
236
|
+
await cfg.stateStore.updateSagaStatus(row.id, {
|
|
237
|
+
failure_reason: err instanceof Error ? err.message : String(err),
|
|
238
|
+
failure_step: name,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
catch (updErr) {
|
|
242
|
+
cfg.logger?.warn?.("updateSagaStatus failed while recording step failure", {
|
|
243
|
+
saga_id: row.id,
|
|
244
|
+
step: name,
|
|
245
|
+
error: updErr instanceof Error ? updErr.message : String(updErr),
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
throw new SagaStepFailed(name, err);
|
|
249
|
+
}
|
|
250
|
+
// Persist the output + push the undo record (when undo provided).
|
|
251
|
+
await cfg.stateStore.appendStepOutput(row.id, name, result);
|
|
252
|
+
row.step_outputs[name] = result;
|
|
253
|
+
sagaCtx.completedSteps.push({ name, output: result });
|
|
254
|
+
if (opts.undo) {
|
|
255
|
+
const { fn: undoFn, mode } = normalizeUndo(opts.undo, opts.mode);
|
|
256
|
+
pushUndo(row.id, {
|
|
257
|
+
stepName: name,
|
|
258
|
+
output: result,
|
|
259
|
+
fn: undoFn,
|
|
260
|
+
mode,
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
// Even when there's no undo, record an entry so compensation walks can
|
|
265
|
+
// log "skipped_no_undo" — keeps the compensation_log shape consistent.
|
|
266
|
+
pushUndo(row.id, {
|
|
267
|
+
stepName: name,
|
|
268
|
+
output: result,
|
|
269
|
+
fn: async () => {
|
|
270
|
+
/* no-op; logged as skipped_no_undo */
|
|
271
|
+
},
|
|
272
|
+
mode: opts.mode === "best-effort" ? "best-effort" : "strict",
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
return result;
|
|
276
|
+
}
|
|
277
|
+
function normalizeUndo(undo, stepMode) {
|
|
278
|
+
if (typeof undo === "function") {
|
|
279
|
+
return {
|
|
280
|
+
fn: undo,
|
|
281
|
+
mode: stepMode === "best-effort" ? "best-effort" : "strict",
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
return { fn: undo.fn, mode: undo.mode };
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* § 5.2 — stepParallel. Fans out concurrently; collects results into an array.
|
|
288
|
+
*
|
|
289
|
+
* Compensation semantics match `step` — undos are pushed onto the stack in
|
|
290
|
+
* the order they completed, so reverse-order traversal is preserved.
|
|
291
|
+
*/
|
|
292
|
+
export async function stepParallel(steps) {
|
|
293
|
+
return Promise.all(steps.map((s) => step(s.name, s.opts)));
|
|
294
|
+
}
|
|
295
|
+
async function runCompensation(sagaId, failedStep) {
|
|
296
|
+
const cfg = requireSaga();
|
|
297
|
+
await cfg.stateStore.updateSagaStatus(sagaId, {
|
|
298
|
+
status: "compensating",
|
|
299
|
+
failure_step: failedStep,
|
|
300
|
+
});
|
|
301
|
+
await emitAuditEvent("saga_compensation_started", sagaId, {
|
|
302
|
+
failed_step: failedStep,
|
|
303
|
+
});
|
|
304
|
+
const stack = takeUndoStack(sagaId).reverse();
|
|
305
|
+
let hadLenientError = false;
|
|
306
|
+
for (const entry of stack) {
|
|
307
|
+
if (entry.stepName === failedStep) {
|
|
308
|
+
// The failing step never produced a checkpoint we trust, so we skip
|
|
309
|
+
// its undo. compensation_log records skipped_no_undo for visibility.
|
|
310
|
+
const skipEntry = {
|
|
311
|
+
step_name: entry.stepName,
|
|
312
|
+
outcome: "skipped_no_undo",
|
|
313
|
+
ts: nowIso(),
|
|
314
|
+
};
|
|
315
|
+
await cfg.stateStore.appendCompensationLog(sagaId, skipEntry);
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
try {
|
|
319
|
+
await entry.fn(entry.output);
|
|
320
|
+
const okEntry = {
|
|
321
|
+
step_name: entry.stepName,
|
|
322
|
+
outcome: "undone",
|
|
323
|
+
ts: nowIso(),
|
|
324
|
+
};
|
|
325
|
+
await cfg.stateStore.appendCompensationLog(sagaId, okEntry);
|
|
326
|
+
}
|
|
327
|
+
catch (undoErr) {
|
|
328
|
+
const failEntry = {
|
|
329
|
+
step_name: entry.stepName,
|
|
330
|
+
outcome: "undo_failed",
|
|
331
|
+
error: undoErr instanceof Error ? undoErr.message : String(undoErr),
|
|
332
|
+
ts: nowIso(),
|
|
333
|
+
};
|
|
334
|
+
await cfg.stateStore.appendCompensationLog(sagaId, failEntry);
|
|
335
|
+
if (entry.mode === "strict") {
|
|
336
|
+
await cfg.stateStore.updateSagaStatus(sagaId, {
|
|
337
|
+
status: "compensation_failed",
|
|
338
|
+
});
|
|
339
|
+
await emitAuditEvent("saga_compensation_failed", sagaId, {
|
|
340
|
+
step: entry.stepName,
|
|
341
|
+
error: failEntry.error,
|
|
342
|
+
});
|
|
343
|
+
throw new SagaCompensationFailed(entry.stepName, undoErr);
|
|
344
|
+
}
|
|
345
|
+
hadLenientError = true;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
const terminal = hadLenientError ? "compensated_with_errors" : "compensated";
|
|
349
|
+
await cfg.stateStore.updateSagaStatus(sagaId, { status: terminal });
|
|
350
|
+
if (hadLenientError) {
|
|
351
|
+
await emitAuditEvent("saga_compensation_completed", sagaId, {
|
|
352
|
+
outcome: "compensated_with_errors",
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
else {
|
|
356
|
+
await emitAuditEvent("saga_compensation_completed", sagaId);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
async function cancelSaga(sagaId, reason) {
|
|
360
|
+
const cfg = requireSaga();
|
|
361
|
+
await cfg.stateStore.updateSagaStatus(sagaId, {
|
|
362
|
+
status: "cancelled",
|
|
363
|
+
cancel_reason: reason,
|
|
364
|
+
completed_at: nowIso(),
|
|
365
|
+
});
|
|
366
|
+
if (cfg.telemetryAudit) {
|
|
367
|
+
await cfg.telemetryAudit.emit({
|
|
368
|
+
action: "saga_admin_cancel",
|
|
369
|
+
target_kind: "saga",
|
|
370
|
+
target_id: sagaId,
|
|
371
|
+
payload: { reason },
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
undoStacks.delete(sagaId);
|
|
375
|
+
}
|
|
376
|
+
/** Public cancel entry-point (also reachable via handle.cancel). */
|
|
377
|
+
export async function cancel(sagaId, reason) {
|
|
378
|
+
await cancelSaga(sagaId, reason);
|
|
379
|
+
}
|
|
380
|
+
/** Read-only accessor for the active saga handle in scope. */
|
|
381
|
+
export function currentSaga() {
|
|
382
|
+
return getSagaContext()?.handle ?? null;
|
|
383
|
+
}
|
|
384
|
+
//# sourceMappingURL=saga.js.map
|
package/dist/saga.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"saga.js","sourceRoot":"","sources":["../src/saga.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/signal-bus/index.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,cAAc,EAAE,KAAK,kBAAkB,EAAE,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/signal-bus/index.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,EAAE;AACF,sEAAsE;AACtE,6CAA6C;AAE7C,OAAO,EAAE,cAAc,EAA2B,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Redis } from "ioredis";
|
|
2
|
+
import type { SagaSignalBus } from "../types";
|
|
3
|
+
export interface RedisSignalBusOpts {
|
|
4
|
+
redis: Redis;
|
|
5
|
+
serviceId: string;
|
|
6
|
+
/** Override the stream name; defaults to nodii:<serviceId>:saga:signals. */
|
|
7
|
+
streamName?: string;
|
|
8
|
+
/** XREADGROUP block window in ms (default 50). */
|
|
9
|
+
blockMs?: number;
|
|
10
|
+
}
|
|
11
|
+
export declare class RedisSignalBus implements SagaSignalBus {
|
|
12
|
+
private readonly redis;
|
|
13
|
+
private readonly stream;
|
|
14
|
+
private readonly blockMs;
|
|
15
|
+
private readonly groupsEnsured;
|
|
16
|
+
constructor(opts: RedisSignalBusOpts);
|
|
17
|
+
emit(args: {
|
|
18
|
+
sagaId: string;
|
|
19
|
+
eventType: string;
|
|
20
|
+
payload: unknown;
|
|
21
|
+
scope?: "siblings" | "global";
|
|
22
|
+
to?: string;
|
|
23
|
+
}): Promise<void>;
|
|
24
|
+
consume(args: {
|
|
25
|
+
sagaId: string;
|
|
26
|
+
eventType: string;
|
|
27
|
+
from?: string;
|
|
28
|
+
predicate?: (payload: unknown) => boolean;
|
|
29
|
+
}): Promise<unknown | null>;
|
|
30
|
+
list(args: {
|
|
31
|
+
sagaId: string;
|
|
32
|
+
eventType?: string;
|
|
33
|
+
}): Promise<{
|
|
34
|
+
sagaId: string;
|
|
35
|
+
eventType: string;
|
|
36
|
+
payload: unknown;
|
|
37
|
+
}[]>;
|
|
38
|
+
/** Test helper — drop the stream + reset ensure cache. */
|
|
39
|
+
resetForTests(): Promise<void>;
|
|
40
|
+
close(): Promise<void>;
|
|
41
|
+
private ensureGroup;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=redis-stream.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redis-stream.d.ts","sourceRoot":"","sources":["../../src/signal-bus/redis-stream.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE9C,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,KAAK,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,4EAA4E;IAC5E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kDAAkD;IAClD,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAWD,qBAAa,cAAe,YAAW,aAAa;IAClD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;IAC9B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IAIjC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAqB;gBAEvC,IAAI,EAAE,kBAAkB;IAM9B,IAAI,CAAC,IAAI,EAAE;QACf,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,EAAE,OAAO,CAAC;QACjB,KAAK,CAAC,EAAE,UAAU,GAAG,QAAQ,CAAC;QAC9B,EAAE,CAAC,EAAE,MAAM,CAAC;KACb,GAAG,OAAO,CAAC,IAAI,CAAC;IAiBX,OAAO,CAAC,IAAI,EAAE;QAClB,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,EAAE,MAAM,CAAC;QAClB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC;KAC3C,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IAwDrB,IAAI,CAAC,IAAI,EAAE;QACf,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,EAAE,CAAC;IAyBtE,0DAA0D;IACpD,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAS9B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAId,WAAW;CAa1B"}
|