@nodii/saga 0.0.1 → 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
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
// PostgresSagaStateStore — real, production-bound saga_state store.
|
|
2
|
+
//
|
|
3
|
+
// Spec § 5.5 — 21-column schema mirrored verbatim. The store uses the
|
|
4
|
+
// `postgres` (porsager/postgres) client because Drizzle and the other
|
|
5
|
+
// nodii-libs ORM choices have not yet stabilized — the SQL is hand-rolled
|
|
6
|
+
// so the schema is byte-identical to the migration shipped at
|
|
7
|
+
// `src/migrations/001-saga-state.sql`.
|
|
8
|
+
//
|
|
9
|
+
// Wire shape:
|
|
10
|
+
// - tenant_id, parent_saga_id, parent_relationship: persisted as TEXT
|
|
11
|
+
// to allow non-UUID test tenants. Production callers pass UUIDs.
|
|
12
|
+
// - step_outputs + children + compensation_log: jsonb columns.
|
|
13
|
+
// - next_resume_at: the reaper queries this; we keep it nullable.
|
|
14
|
+
// - All reads/writes go through the configured `nodii_services` pool
|
|
15
|
+
// (or any postgres.Sql) — RLS is not applied to saga_state per
|
|
16
|
+
// audit-only doctrine § 5.5.
|
|
17
|
+
//
|
|
18
|
+
// 16KB cap on step_outputs entries (§ 5.5 three-level resolution): we
|
|
19
|
+
// enforce on `appendStepOutput` — entries exceeding the cap are stored
|
|
20
|
+
// with `{ _truncated: true, original_bytes: N }` so the saga log stays
|
|
21
|
+
// queryable. Truncation counter would normally fire a metric; we keep
|
|
22
|
+
// the hook explicit on the store interface so tests can observe.
|
|
23
|
+
const STEP_OUTPUT_CAP_BYTES = 16 * 1024; // § 5.5 cap-and-truncate
|
|
24
|
+
export class PostgresSagaStateStore {
|
|
25
|
+
sql;
|
|
26
|
+
table;
|
|
27
|
+
onTruncate;
|
|
28
|
+
constructor(opts) {
|
|
29
|
+
this.sql = opts.sql;
|
|
30
|
+
this.table = opts.tableName ?? "saga_state";
|
|
31
|
+
this.onTruncate = opts.onTruncate;
|
|
32
|
+
}
|
|
33
|
+
async createSaga(row) {
|
|
34
|
+
// NOTE on jsonb params: porsager/postgres auto-stringifies JS objects/
|
|
35
|
+
// arrays/null when bound to jsonb-typed slots, so we pass the raw JS
|
|
36
|
+
// value (NOT JSON.stringify(...)). Passing a pre-stringified string
|
|
37
|
+
// sends it as TEXT and the `::jsonb` cast then encodes the literal
|
|
38
|
+
// string as a JSON scalar — see the in-tree debug commit notes.
|
|
39
|
+
const stmt = `INSERT INTO ${this.table}
|
|
40
|
+
(id, tenant_id, type, status, current_step, step_outputs, episode,
|
|
41
|
+
trigger_trace_id, trigger_span_id, trigger_request_id,
|
|
42
|
+
parent_saga_id, parent_relationship, children,
|
|
43
|
+
started_at, completed_at, next_resume_at, failure_reason, failure_step,
|
|
44
|
+
compensation_log, last_admin_action, last_admin_actor_id, last_admin_at,
|
|
45
|
+
input, cancel_reason)
|
|
46
|
+
VALUES ($1,$2,$3,$4,$5,$6::jsonb,$7,$8,$9,$10,$11,$12,$13::jsonb,
|
|
47
|
+
$14::timestamptz,$15::timestamptz,$16::timestamptz,$17,$18,$19::jsonb,$20,$21,$22::timestamptz,$23::jsonb,$24)`;
|
|
48
|
+
await this.sql.unsafe(stmt, [
|
|
49
|
+
row.id,
|
|
50
|
+
row.tenant_id,
|
|
51
|
+
row.type,
|
|
52
|
+
row.status,
|
|
53
|
+
row.current_step,
|
|
54
|
+
row.step_outputs ?? {},
|
|
55
|
+
row.episode,
|
|
56
|
+
row.trigger_trace_id,
|
|
57
|
+
row.trigger_span_id,
|
|
58
|
+
row.trigger_request_id,
|
|
59
|
+
row.parent_saga_id,
|
|
60
|
+
row.parent_relationship,
|
|
61
|
+
row.children ?? [],
|
|
62
|
+
row.started_at,
|
|
63
|
+
row.completed_at,
|
|
64
|
+
row.next_resume_at ?? null,
|
|
65
|
+
row.failure_reason,
|
|
66
|
+
row.failure_step,
|
|
67
|
+
row.compensation_log ?? [],
|
|
68
|
+
row.last_admin_action,
|
|
69
|
+
row.last_admin_actor_id,
|
|
70
|
+
row.last_admin_at,
|
|
71
|
+
row.input ?? null,
|
|
72
|
+
row.cancel_reason,
|
|
73
|
+
]);
|
|
74
|
+
}
|
|
75
|
+
async getSaga(sagaId) {
|
|
76
|
+
const stmt = `SELECT id, tenant_id, type, status, current_step, step_outputs,
|
|
77
|
+
episode, trigger_trace_id, trigger_span_id, trigger_request_id,
|
|
78
|
+
parent_saga_id, parent_relationship, children,
|
|
79
|
+
to_char(started_at, 'YYYY-MM-DD"T"HH24:MI:SS.US"Z"') AS started_at,
|
|
80
|
+
to_char(completed_at, 'YYYY-MM-DD"T"HH24:MI:SS.US"Z"') AS completed_at,
|
|
81
|
+
to_char(next_resume_at, 'YYYY-MM-DD"T"HH24:MI:SS.US"Z"') AS next_resume_at,
|
|
82
|
+
failure_reason, failure_step, compensation_log,
|
|
83
|
+
last_admin_action, last_admin_actor_id,
|
|
84
|
+
to_char(last_admin_at, 'YYYY-MM-DD"T"HH24:MI:SS.US"Z"') AS last_admin_at,
|
|
85
|
+
input, cancel_reason
|
|
86
|
+
FROM ${this.table} WHERE id = $1 LIMIT 1`;
|
|
87
|
+
const rows = (await this.sql.unsafe(stmt, [sagaId]));
|
|
88
|
+
if (!rows.length)
|
|
89
|
+
return null;
|
|
90
|
+
const r = rows[0];
|
|
91
|
+
if (!r)
|
|
92
|
+
return null;
|
|
93
|
+
return rawToRow(r);
|
|
94
|
+
}
|
|
95
|
+
async updateSagaStatus(sagaId, update) {
|
|
96
|
+
const sets = [];
|
|
97
|
+
const args = [];
|
|
98
|
+
let i = 1;
|
|
99
|
+
const push = (col, cast, value) => {
|
|
100
|
+
sets.push(`${col} = $${i}${cast}`);
|
|
101
|
+
args.push(value);
|
|
102
|
+
i += 1;
|
|
103
|
+
};
|
|
104
|
+
if (update.status !== undefined)
|
|
105
|
+
push("status", "", update.status);
|
|
106
|
+
if (update.current_step !== undefined)
|
|
107
|
+
push("current_step", "", update.current_step);
|
|
108
|
+
if (update.completed_at !== undefined)
|
|
109
|
+
push("completed_at", "::timestamptz", update.completed_at);
|
|
110
|
+
if (update.next_resume_at !== undefined)
|
|
111
|
+
push("next_resume_at", "::timestamptz", update.next_resume_at);
|
|
112
|
+
if (update.failure_reason !== undefined)
|
|
113
|
+
push("failure_reason", "", update.failure_reason);
|
|
114
|
+
if (update.failure_step !== undefined)
|
|
115
|
+
push("failure_step", "", update.failure_step);
|
|
116
|
+
if (update.last_admin_action !== undefined)
|
|
117
|
+
push("last_admin_action", "", update.last_admin_action);
|
|
118
|
+
if (update.last_admin_actor_id !== undefined)
|
|
119
|
+
push("last_admin_actor_id", "", update.last_admin_actor_id);
|
|
120
|
+
if (update.last_admin_at !== undefined)
|
|
121
|
+
push("last_admin_at", "::timestamptz", update.last_admin_at);
|
|
122
|
+
if (update.cancel_reason !== undefined)
|
|
123
|
+
push("cancel_reason", "", update.cancel_reason);
|
|
124
|
+
if (!sets.length)
|
|
125
|
+
return;
|
|
126
|
+
args.push(sagaId);
|
|
127
|
+
const stmt = `UPDATE ${this.table} SET ${sets.join(", ")} WHERE id = $${i}`;
|
|
128
|
+
await this.sql.unsafe(stmt, args);
|
|
129
|
+
}
|
|
130
|
+
async appendStepOutput(sagaId, stepName, output) {
|
|
131
|
+
// 16KB cap per § 5.5 — measured against the JSON-serialized size,
|
|
132
|
+
// but the actual value sent to postgres is the JS object (porsager/
|
|
133
|
+
// postgres handles the jsonb encoding itself).
|
|
134
|
+
let toStore = output ?? null;
|
|
135
|
+
try {
|
|
136
|
+
const serialized = JSON.stringify(toStore);
|
|
137
|
+
if (serialized.length > STEP_OUTPUT_CAP_BYTES) {
|
|
138
|
+
this.onTruncate?.({
|
|
139
|
+
sagaId,
|
|
140
|
+
stepName,
|
|
141
|
+
originalBytes: serialized.length,
|
|
142
|
+
});
|
|
143
|
+
toStore = { _truncated: true, original_bytes: serialized.length };
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
toStore = { _truncated: true, reason: "non_serializable_output" };
|
|
148
|
+
}
|
|
149
|
+
const stmt = `UPDATE ${this.table}
|
|
150
|
+
SET step_outputs = jsonb_set(
|
|
151
|
+
COALESCE(step_outputs, '{}'::jsonb),
|
|
152
|
+
ARRAY[$2::text],
|
|
153
|
+
$3::jsonb,
|
|
154
|
+
true
|
|
155
|
+
)
|
|
156
|
+
WHERE id = $1`;
|
|
157
|
+
await this.sql.unsafe(stmt, [sagaId, stepName, toStore]);
|
|
158
|
+
}
|
|
159
|
+
async appendCompensationLog(sagaId, entry) {
|
|
160
|
+
// Pass entry as a JS object; postgres.js handles the jsonb encoding.
|
|
161
|
+
const stmt = `UPDATE ${this.table}
|
|
162
|
+
SET compensation_log = COALESCE(compensation_log, '[]'::jsonb) || jsonb_build_array($2::jsonb)
|
|
163
|
+
WHERE id = $1`;
|
|
164
|
+
await this.sql.unsafe(stmt, [sagaId, entry]);
|
|
165
|
+
}
|
|
166
|
+
async list(filter) {
|
|
167
|
+
const args = [];
|
|
168
|
+
const wheres = [];
|
|
169
|
+
let i = 1;
|
|
170
|
+
if (filter?.tenantId !== undefined) {
|
|
171
|
+
wheres.push(`tenant_id = $${i}`);
|
|
172
|
+
args.push(filter.tenantId);
|
|
173
|
+
i += 1;
|
|
174
|
+
}
|
|
175
|
+
if (filter?.status !== undefined) {
|
|
176
|
+
wheres.push(`status = $${i}`);
|
|
177
|
+
args.push(filter.status);
|
|
178
|
+
i += 1;
|
|
179
|
+
}
|
|
180
|
+
if (filter?.type !== undefined) {
|
|
181
|
+
wheres.push(`type = $${i}`);
|
|
182
|
+
args.push(filter.type);
|
|
183
|
+
i += 1;
|
|
184
|
+
}
|
|
185
|
+
const where = wheres.length ? ` WHERE ${wheres.join(" AND ")}` : "";
|
|
186
|
+
const stmt = `SELECT id, tenant_id, type, status, current_step, step_outputs,
|
|
187
|
+
episode, trigger_trace_id, trigger_span_id, trigger_request_id,
|
|
188
|
+
parent_saga_id, parent_relationship, children,
|
|
189
|
+
to_char(started_at, 'YYYY-MM-DD"T"HH24:MI:SS.US"Z"') AS started_at,
|
|
190
|
+
to_char(completed_at, 'YYYY-MM-DD"T"HH24:MI:SS.US"Z"') AS completed_at,
|
|
191
|
+
to_char(next_resume_at, 'YYYY-MM-DD"T"HH24:MI:SS.US"Z"') AS next_resume_at,
|
|
192
|
+
failure_reason, failure_step, compensation_log,
|
|
193
|
+
last_admin_action, last_admin_actor_id,
|
|
194
|
+
to_char(last_admin_at, 'YYYY-MM-DD"T"HH24:MI:SS.US"Z"') AS last_admin_at,
|
|
195
|
+
input, cancel_reason
|
|
196
|
+
FROM ${this.table}${where} ORDER BY started_at DESC`;
|
|
197
|
+
const rows = (await this.sql.unsafe(stmt, args));
|
|
198
|
+
return rows.map(rawToRow);
|
|
199
|
+
}
|
|
200
|
+
/** Reaper-side scan — used by `startSagaReaper`. */
|
|
201
|
+
async listStalePaused(cutoffIso) {
|
|
202
|
+
const stmt = `SELECT id, tenant_id, type, status, current_step, step_outputs,
|
|
203
|
+
episode, trigger_trace_id, trigger_span_id, trigger_request_id,
|
|
204
|
+
parent_saga_id, parent_relationship, children,
|
|
205
|
+
to_char(started_at, 'YYYY-MM-DD"T"HH24:MI:SS.US"Z"') AS started_at,
|
|
206
|
+
to_char(completed_at, 'YYYY-MM-DD"T"HH24:MI:SS.US"Z"') AS completed_at,
|
|
207
|
+
to_char(next_resume_at, 'YYYY-MM-DD"T"HH24:MI:SS.US"Z"') AS next_resume_at,
|
|
208
|
+
failure_reason, failure_step, compensation_log,
|
|
209
|
+
last_admin_action, last_admin_actor_id,
|
|
210
|
+
to_char(last_admin_at, 'YYYY-MM-DD"T"HH24:MI:SS.US"Z"') AS last_admin_at,
|
|
211
|
+
input, cancel_reason
|
|
212
|
+
FROM ${this.table}
|
|
213
|
+
WHERE status = 'paused' AND next_resume_at IS NOT NULL AND next_resume_at < $1::timestamptz`;
|
|
214
|
+
const rows = (await this.sql.unsafe(stmt, [cutoffIso]));
|
|
215
|
+
return rows.map(rawToRow);
|
|
216
|
+
}
|
|
217
|
+
/** Outbox helpers — see `async-step.ts`. */
|
|
218
|
+
async writeOutboxBegin(args) {
|
|
219
|
+
const stmt = `INSERT INTO saga_outbox (id, saga_id, step_name, event_type, payload, tenant_id, created_at)
|
|
220
|
+
VALUES (gen_random_uuid(), $1, $2, $3, $4::jsonb, $5, now())`;
|
|
221
|
+
await this.sql.unsafe(stmt, [
|
|
222
|
+
args.sagaId,
|
|
223
|
+
args.stepName,
|
|
224
|
+
args.eventType,
|
|
225
|
+
args.payload ?? null,
|
|
226
|
+
args.tenantId,
|
|
227
|
+
]);
|
|
228
|
+
}
|
|
229
|
+
async readOutboxForSaga(sagaId) {
|
|
230
|
+
const stmt = `SELECT step_name, event_type, payload,
|
|
231
|
+
to_char(created_at, 'YYYY-MM-DD"T"HH24:MI:SS.US"Z"') AS created_at
|
|
232
|
+
FROM saga_outbox WHERE saga_id = $1 ORDER BY created_at ASC`;
|
|
233
|
+
const rows = (await this.sql.unsafe(stmt, [sagaId]));
|
|
234
|
+
return rows.map((r) => ({
|
|
235
|
+
stepName: r.step_name,
|
|
236
|
+
eventType: r.event_type,
|
|
237
|
+
payload: r.payload,
|
|
238
|
+
createdAt: r.created_at,
|
|
239
|
+
}));
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
function rawToRow(r) {
|
|
243
|
+
return {
|
|
244
|
+
id: r.id,
|
|
245
|
+
tenant_id: r.tenant_id,
|
|
246
|
+
type: r.type,
|
|
247
|
+
status: r.status,
|
|
248
|
+
current_step: r.current_step,
|
|
249
|
+
step_outputs: r.step_outputs ?? {},
|
|
250
|
+
episode: r.episode,
|
|
251
|
+
trigger_trace_id: r.trigger_trace_id,
|
|
252
|
+
trigger_span_id: r.trigger_span_id,
|
|
253
|
+
trigger_request_id: r.trigger_request_id,
|
|
254
|
+
parent_saga_id: r.parent_saga_id,
|
|
255
|
+
parent_relationship: r.parent_relationship === "compensation_of" ? "compensation_of" : "child",
|
|
256
|
+
children: r.children ?? [],
|
|
257
|
+
started_at: r.started_at,
|
|
258
|
+
completed_at: r.completed_at,
|
|
259
|
+
next_resume_at: r.next_resume_at,
|
|
260
|
+
failure_reason: r.failure_reason,
|
|
261
|
+
failure_step: r.failure_step,
|
|
262
|
+
compensation_log: r.compensation_log ?? [],
|
|
263
|
+
last_admin_action: r.last_admin_action,
|
|
264
|
+
last_admin_actor_id: r.last_admin_actor_id,
|
|
265
|
+
last_admin_at: r.last_admin_at,
|
|
266
|
+
input: r.input,
|
|
267
|
+
cancel_reason: r.cancel_reason,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
//# sourceMappingURL=postgres.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"postgres.js","sourceRoot":"","sources":["../../src/state-store/postgres.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { SagaSignalBus } from "../types";
|
|
2
|
+
export declare class NoopSignalBus implements SagaSignalBus {
|
|
3
|
+
emit(): Promise<void>;
|
|
4
|
+
consume(): Promise<unknown | null>;
|
|
5
|
+
list(): Promise<{
|
|
6
|
+
sagaId: string;
|
|
7
|
+
eventType: string;
|
|
8
|
+
payload: unknown;
|
|
9
|
+
}[]>;
|
|
10
|
+
close(): Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
export declare class InMemorySignalBus implements SagaSignalBus {
|
|
13
|
+
private buffer;
|
|
14
|
+
emit(args: {
|
|
15
|
+
sagaId: string;
|
|
16
|
+
eventType: string;
|
|
17
|
+
payload: unknown;
|
|
18
|
+
scope?: "siblings" | "global";
|
|
19
|
+
to?: string;
|
|
20
|
+
}): Promise<void>;
|
|
21
|
+
consume(args: {
|
|
22
|
+
sagaId: string;
|
|
23
|
+
eventType: string;
|
|
24
|
+
from?: string;
|
|
25
|
+
predicate?: (payload: unknown) => boolean;
|
|
26
|
+
}): Promise<unknown | null>;
|
|
27
|
+
list(args: {
|
|
28
|
+
sagaId: string;
|
|
29
|
+
eventType?: string;
|
|
30
|
+
}): Promise<{
|
|
31
|
+
sagaId: string;
|
|
32
|
+
eventType: string;
|
|
33
|
+
payload: unknown;
|
|
34
|
+
}[]>;
|
|
35
|
+
close(): Promise<void>;
|
|
36
|
+
reset(): void;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=in-memory-signal-bus.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"in-memory-signal-bus.d.ts","sourceRoot":"","sources":["../../src/test-doubles/in-memory-signal-bus.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE9C,qBAAa,aAAc,YAAW,aAAa;IAC3C,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAGrB,OAAO,IAAI,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IAGlC,IAAI,IAAI,OAAO,CACnB;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,EAAE,CAC1D;IAGK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAG7B;AAUD,qBAAa,iBAAkB,YAAW,aAAa;IACrD,OAAO,CAAC,MAAM,CAAwB;IAEhC,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;IAUX,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;IAarB,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;IAchE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAI5B,KAAK,IAAI,IAAI;CAGd"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// NoopSignalBus + InMemorySignalBus — TEST DOUBLES. Production uses
|
|
2
|
+
// `RedisSignalBus` (Redis Streams). These are kept only for unit tests
|
|
3
|
+
// + parity-fence fixtures.
|
|
4
|
+
export class NoopSignalBus {
|
|
5
|
+
async emit() {
|
|
6
|
+
/* no-op */
|
|
7
|
+
}
|
|
8
|
+
async consume() {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
async list() {
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
14
|
+
async close() {
|
|
15
|
+
/* no-op */
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export class InMemorySignalBus {
|
|
19
|
+
buffer = [];
|
|
20
|
+
async emit(args) {
|
|
21
|
+
this.buffer.push({
|
|
22
|
+
sagaId: args.sagaId,
|
|
23
|
+
eventType: args.eventType,
|
|
24
|
+
payload: args.payload,
|
|
25
|
+
scope: args.scope ?? "siblings",
|
|
26
|
+
to: args.to,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
async consume(args) {
|
|
30
|
+
const idx = this.buffer.findIndex((sig) => {
|
|
31
|
+
if (sig.eventType !== args.eventType)
|
|
32
|
+
return false;
|
|
33
|
+
if (sig.to && sig.to !== args.sagaId)
|
|
34
|
+
return false;
|
|
35
|
+
if (args.from && sig.sagaId !== args.from)
|
|
36
|
+
return false;
|
|
37
|
+
if (args.predicate && !args.predicate(sig.payload))
|
|
38
|
+
return false;
|
|
39
|
+
return true;
|
|
40
|
+
});
|
|
41
|
+
if (idx === -1)
|
|
42
|
+
return null;
|
|
43
|
+
const signal = this.buffer.splice(idx, 1)[0];
|
|
44
|
+
return signal ? signal.payload : null;
|
|
45
|
+
}
|
|
46
|
+
async list(args) {
|
|
47
|
+
return this.buffer
|
|
48
|
+
.filter((sig) => {
|
|
49
|
+
if (sig.to && sig.to !== args.sagaId)
|
|
50
|
+
return false;
|
|
51
|
+
if (args.eventType && sig.eventType !== args.eventType)
|
|
52
|
+
return false;
|
|
53
|
+
return true;
|
|
54
|
+
})
|
|
55
|
+
.map((sig) => ({
|
|
56
|
+
sagaId: sig.sagaId,
|
|
57
|
+
eventType: sig.eventType,
|
|
58
|
+
payload: sig.payload,
|
|
59
|
+
}));
|
|
60
|
+
}
|
|
61
|
+
async close() {
|
|
62
|
+
/* no-op */
|
|
63
|
+
}
|
|
64
|
+
reset() {
|
|
65
|
+
this.buffer = [];
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=in-memory-signal-bus.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"in-memory-signal-bus.js","sourceRoot":"","sources":["../../src/test-doubles/in-memory-signal-bus.ts"],"names":[],"mappings":"AAAA,oEAAoE;AACpE,uEAAuE;AACvE,2BAA2B;AAI3B,MAAM,OAAO,aAAa;IACxB,KAAK,CAAC,IAAI;QACR,WAAW;IACb,CAAC;IACD,KAAK,CAAC,OAAO;QACX,OAAO,IAAI,CAAC;IACd,CAAC;IACD,KAAK,CAAC,IAAI;QAGR,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,KAAK,CAAC,KAAK;QACT,WAAW;IACb,CAAC;CACF;AAUD,MAAM,OAAO,iBAAiB;IACpB,MAAM,GAAqB,EAAE,CAAC;IAEtC,KAAK,CAAC,IAAI,CAAC,IAMV;QACC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;YACf,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,UAAU;YAC/B,EAAE,EAAE,IAAI,CAAC,EAAE;SACZ,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,IAKb;QACC,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,GAAG,EAAE,EAAE;YACxC,IAAI,GAAG,CAAC,SAAS,KAAK,IAAI,CAAC,SAAS;gBAAE,OAAO,KAAK,CAAC;YACnD,IAAI,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,EAAE,KAAK,IAAI,CAAC,MAAM;gBAAE,OAAO,KAAK,CAAC;YACnD,IAAI,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,MAAM,KAAK,IAAI,CAAC,IAAI;gBAAE,OAAO,KAAK,CAAC;YACxD,IAAI,IAAI,CAAC,SAAS,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC;gBAAE,OAAO,KAAK,CAAC;YACjE,OAAO,IAAI,CAAC;QACd,CAAC,CAAC,CAAC;QACH,IAAI,GAAG,KAAK,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC;QAC5B,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC7C,OAAO,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;IACxC,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,IAGV;QACC,OAAO,IAAI,CAAC,MAAM;aACf,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE;YACd,IAAI,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,EAAE,KAAK,IAAI,CAAC,MAAM;gBAAE,OAAO,KAAK,CAAC;YACnD,IAAI,IAAI,CAAC,SAAS,IAAI,GAAG,CAAC,SAAS,KAAK,IAAI,CAAC,SAAS;gBAAE,OAAO,KAAK,CAAC;YACrE,OAAO,IAAI,CAAC;QACd,CAAC,CAAC;aACD,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;YACb,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,SAAS,EAAE,GAAG,CAAC,SAAS;YACxB,OAAO,EAAE,GAAG,CAAC,OAAO;SACrB,CAAC,CAAC,CAAC;IACR,CAAC;IAED,KAAK,CAAC,KAAK;QACT,WAAW;IACb,CAAC;IAED,KAAK;QACH,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC;IACnB,CAAC;CACF"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { CompensationLogEntry, ListSagasFilter, SagaStateRow, SagaStateStore } from "../types";
|
|
2
|
+
export declare class InMemorySagaStateStore implements SagaStateStore {
|
|
3
|
+
private rows;
|
|
4
|
+
private outbox;
|
|
5
|
+
createSaga(row: SagaStateRow): Promise<void>;
|
|
6
|
+
getSaga(sagaId: string): Promise<SagaStateRow | null>;
|
|
7
|
+
updateSagaStatus(sagaId: string, update: Partial<Pick<SagaStateRow, "status" | "current_step" | "completed_at" | "failure_reason" | "failure_step" | "last_admin_action" | "last_admin_actor_id" | "last_admin_at" | "cancel_reason" | "next_resume_at">>): Promise<void>;
|
|
8
|
+
appendStepOutput(sagaId: string, stepName: string, output: unknown): Promise<void>;
|
|
9
|
+
appendCompensationLog(sagaId: string, entry: CompensationLogEntry): Promise<void>;
|
|
10
|
+
list(filter?: ListSagasFilter): Promise<SagaStateRow[]>;
|
|
11
|
+
listStalePaused(cutoffIso: string): Promise<SagaStateRow[]>;
|
|
12
|
+
writeOutboxBegin(args: {
|
|
13
|
+
sagaId: string;
|
|
14
|
+
stepName: string;
|
|
15
|
+
eventType: string;
|
|
16
|
+
payload: unknown;
|
|
17
|
+
tenantId: string | null;
|
|
18
|
+
}): Promise<void>;
|
|
19
|
+
readOutboxForSaga(sagaId: string): Promise<{
|
|
20
|
+
stepName: string;
|
|
21
|
+
eventType: string;
|
|
22
|
+
payload: unknown;
|
|
23
|
+
createdAt: string;
|
|
24
|
+
}[]>;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=in-memory-state-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"in-memory-state-store.d.ts","sourceRoot":"","sources":["../../src/test-doubles/in-memory-state-store.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,oBAAoB,EACpB,eAAe,EACf,YAAY,EACZ,cAAc,EACf,MAAM,UAAU,CAAC;AAUlB,qBAAa,sBAAuB,YAAW,cAAc;IAC3D,OAAO,CAAC,IAAI,CAAmC;IAC/C,OAAO,CAAC,MAAM,CAAqB;IAE7B,UAAU,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAO5C,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAKrD,gBAAgB,CACpB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,OAAO,CACb,IAAI,CACF,YAAY,EACV,QAAQ,GACR,cAAc,GACd,cAAc,GACd,gBAAgB,GAChB,cAAc,GACd,mBAAmB,GACnB,qBAAqB,GACrB,eAAe,GACf,eAAe,GACf,gBAAgB,CACnB,CACF,GACA,OAAO,CAAC,IAAI,CAAC;IAMV,gBAAgB,CACpB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,OAAO,GACd,OAAO,CAAC,IAAI,CAAC;IAMV,qBAAqB,CACzB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,oBAAoB,GAC1B,OAAO,CAAC,IAAI,CAAC;IAMV,IAAI,CAAC,MAAM,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAsBvD,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAU3D,gBAAgB,CAAC,IAAI,EAAE;QAC3B,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,EAAE,OAAO,CAAC;QACjB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;KACzB,GAAG,OAAO,CAAC,IAAI,CAAC;IAUX,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAC9C;QACE,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,EAAE,OAAO,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;KACnB,EAAE,CACJ;CAUF"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// InMemorySagaStateStore — TEST DOUBLE. Production uses
|
|
2
|
+
// `PostgresSagaStateStore`. This implementation is kept only for unit
|
|
3
|
+
// tests + parity-fence fixtures; the public `initSaga()` no longer wires
|
|
4
|
+
// it as a default.
|
|
5
|
+
export class InMemorySagaStateStore {
|
|
6
|
+
rows = new Map();
|
|
7
|
+
outbox = [];
|
|
8
|
+
async createSaga(row) {
|
|
9
|
+
if (this.rows.has(row.id)) {
|
|
10
|
+
throw new Error(`saga ${row.id} already exists in state store`);
|
|
11
|
+
}
|
|
12
|
+
this.rows.set(row.id, structuredClone(row));
|
|
13
|
+
}
|
|
14
|
+
async getSaga(sagaId) {
|
|
15
|
+
const row = this.rows.get(sagaId);
|
|
16
|
+
return row ? structuredClone(row) : null;
|
|
17
|
+
}
|
|
18
|
+
async updateSagaStatus(sagaId, update) {
|
|
19
|
+
const row = this.rows.get(sagaId);
|
|
20
|
+
if (!row)
|
|
21
|
+
throw new Error(`saga ${sagaId} not found`);
|
|
22
|
+
Object.assign(row, update);
|
|
23
|
+
}
|
|
24
|
+
async appendStepOutput(sagaId, stepName, output) {
|
|
25
|
+
const row = this.rows.get(sagaId);
|
|
26
|
+
if (!row)
|
|
27
|
+
throw new Error(`saga ${sagaId} not found`);
|
|
28
|
+
row.step_outputs[stepName] = output;
|
|
29
|
+
}
|
|
30
|
+
async appendCompensationLog(sagaId, entry) {
|
|
31
|
+
const row = this.rows.get(sagaId);
|
|
32
|
+
if (!row)
|
|
33
|
+
throw new Error(`saga ${sagaId} not found`);
|
|
34
|
+
row.compensation_log.push({ ...entry });
|
|
35
|
+
}
|
|
36
|
+
async list(filter) {
|
|
37
|
+
let snapshot = Array.from(this.rows.values()).map((r) => structuredClone(r));
|
|
38
|
+
if (filter) {
|
|
39
|
+
snapshot = snapshot.filter((r) => {
|
|
40
|
+
if (filter.tenantId !== undefined && r.tenant_id !== filter.tenantId) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
if (filter.status !== undefined && r.status !== filter.status) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
if (filter.type !== undefined && r.type !== filter.type) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
return true;
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
return snapshot;
|
|
53
|
+
}
|
|
54
|
+
// Reaper-side scan: rows whose next_resume_at < cutoff and status = 'paused'.
|
|
55
|
+
async listStalePaused(cutoffIso) {
|
|
56
|
+
const out = [];
|
|
57
|
+
for (const r of this.rows.values()) {
|
|
58
|
+
if (r.status !== "paused")
|
|
59
|
+
continue;
|
|
60
|
+
if (!r.next_resume_at)
|
|
61
|
+
continue;
|
|
62
|
+
if (r.next_resume_at < cutoffIso)
|
|
63
|
+
out.push(structuredClone(r));
|
|
64
|
+
}
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
async writeOutboxBegin(args) {
|
|
68
|
+
this.outbox.push({
|
|
69
|
+
sagaId: args.sagaId,
|
|
70
|
+
stepName: args.stepName,
|
|
71
|
+
eventType: args.eventType,
|
|
72
|
+
payload: structuredClone(args.payload),
|
|
73
|
+
createdAt: new Date().toISOString(),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
async readOutboxForSaga(sagaId) {
|
|
77
|
+
return this.outbox
|
|
78
|
+
.filter((e) => e.sagaId === sagaId)
|
|
79
|
+
.map((e) => ({
|
|
80
|
+
stepName: e.stepName,
|
|
81
|
+
eventType: e.eventType,
|
|
82
|
+
payload: structuredClone(e.payload),
|
|
83
|
+
createdAt: e.createdAt,
|
|
84
|
+
}));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=in-memory-state-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"in-memory-state-store.js","sourceRoot":"","sources":["../../src/test-doubles/in-memory-state-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/test-doubles/index.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AACjE,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAK1E,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Test-double surface for @nodii/saga.
|
|
2
|
+
//
|
|
3
|
+
// Importing from `@nodii/saga/test-doubles` is the only sanctioned way
|
|
4
|
+
// to use the in-memory state store + Noop signal bus. They are NOT
|
|
5
|
+
// re-exported through the main `@nodii/saga` barrel — production code
|
|
6
|
+
// must use `PostgresSagaStateStore` + `RedisSignalBus` (the defaults
|
|
7
|
+
// when `db` + `redis` + `serviceId` are passed to `initSaga`).
|
|
8
|
+
export { InMemorySagaStateStore } from "./in-memory-state-store";
|
|
9
|
+
export { NoopSignalBus, InMemorySignalBus } from "./in-memory-signal-bus";
|
|
10
|
+
// Also expose the deterministic InMemoryIdempotency + NoopIdempotency
|
|
11
|
+
// so tests can wire a complete config without pulling from the (eventual)
|
|
12
|
+
// `@nodii/idempotency` package.
|
|
13
|
+
export { InMemoryIdempotency, NoopIdempotency } from "../idempotency";
|
|
14
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/test-doubles/index.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,EAAE;AACF,uEAAuE;AACvE,mEAAmE;AACnE,sEAAsE;AACtE,qEAAqE;AACrE,+DAA+D;AAE/D,OAAO,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AACjE,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAE1E,sEAAsE;AACtE,0EAA0E;AAC1E,gCAAgC;AAChC,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC"}
|