@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.
Files changed (90) hide show
  1. package/dist/admin-service.d.ts +55 -0
  2. package/dist/admin-service.d.ts.map +1 -0
  3. package/dist/admin-service.js +161 -0
  4. package/dist/admin-service.js.map +1 -0
  5. package/dist/async-step.d.ts +43 -0
  6. package/dist/async-step.d.ts.map +1 -0
  7. package/dist/async-step.js +166 -0
  8. package/dist/async-step.js.map +1 -0
  9. package/dist/context.d.ts +18 -0
  10. package/dist/context.d.ts.map +1 -0
  11. package/dist/context.js +28 -0
  12. package/dist/context.js.map +1 -0
  13. package/dist/decorator.d.ts +24 -0
  14. package/dist/decorator.d.ts.map +1 -0
  15. package/dist/decorator.js +69 -0
  16. package/dist/decorator.js.map +1 -0
  17. package/dist/idempotency.d.ts +22 -0
  18. package/dist/idempotency.d.ts.map +1 -0
  19. package/dist/idempotency.js +44 -0
  20. package/dist/idempotency.js.map +1 -0
  21. package/dist/index.d.ts +20 -1
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +34 -4
  24. package/dist/index.js.map +1 -1
  25. package/dist/init.d.ts +45 -0
  26. package/dist/init.d.ts.map +1 -0
  27. package/dist/init.js +86 -0
  28. package/dist/init.js.map +1 -0
  29. package/dist/interceptor.d.ts +5 -0
  30. package/dist/interceptor.d.ts.map +1 -0
  31. package/dist/interceptor.js +34 -0
  32. package/dist/interceptor.js.map +1 -0
  33. package/dist/migrations/index.d.ts +19 -0
  34. package/dist/migrations/index.d.ts.map +1 -0
  35. package/dist/migrations/index.js +106 -0
  36. package/dist/migrations/index.js.map +1 -0
  37. package/dist/reaper.d.ts +27 -0
  38. package/dist/reaper.d.ts.map +1 -0
  39. package/dist/reaper.js +79 -0
  40. package/dist/reaper.js.map +1 -0
  41. package/dist/registry.d.ts +7 -0
  42. package/dist/registry.d.ts.map +1 -0
  43. package/dist/registry.js +23 -0
  44. package/dist/registry.js.map +1 -0
  45. package/dist/saga.d.ts +48 -0
  46. package/dist/saga.d.ts.map +1 -0
  47. package/dist/saga.js +384 -0
  48. package/dist/saga.js.map +1 -0
  49. package/dist/signal-bus/index.d.ts +2 -0
  50. package/dist/signal-bus/index.d.ts.map +1 -0
  51. package/dist/signal-bus/index.js +6 -0
  52. package/dist/signal-bus/index.js.map +1 -0
  53. package/dist/signal-bus/redis-stream.d.ts +43 -0
  54. package/dist/signal-bus/redis-stream.d.ts.map +1 -0
  55. package/dist/signal-bus/redis-stream.js +189 -0
  56. package/dist/signal-bus/redis-stream.js.map +1 -0
  57. package/dist/signals.d.ts +29 -0
  58. package/dist/signals.d.ts.map +1 -0
  59. package/dist/signals.js +113 -0
  60. package/dist/signals.js.map +1 -0
  61. package/dist/state-store/index.d.ts +2 -0
  62. package/dist/state-store/index.d.ts.map +1 -0
  63. package/dist/state-store/index.js +6 -0
  64. package/dist/state-store/index.js.map +1 -0
  65. package/dist/state-store/postgres.d.ts +47 -0
  66. package/dist/state-store/postgres.d.ts.map +1 -0
  67. package/dist/state-store/postgres.js +270 -0
  68. package/dist/state-store/postgres.js.map +1 -0
  69. package/dist/test-doubles/in-memory-signal-bus.d.ts +38 -0
  70. package/dist/test-doubles/in-memory-signal-bus.d.ts.map +1 -0
  71. package/dist/test-doubles/in-memory-signal-bus.js +68 -0
  72. package/dist/test-doubles/in-memory-signal-bus.js.map +1 -0
  73. package/dist/test-doubles/in-memory-state-store.d.ts +26 -0
  74. package/dist/test-doubles/in-memory-state-store.d.ts.map +1 -0
  75. package/dist/test-doubles/in-memory-state-store.js +87 -0
  76. package/dist/test-doubles/in-memory-state-store.js.map +1 -0
  77. package/dist/test-doubles/index.d.ts +4 -0
  78. package/dist/test-doubles/index.d.ts.map +1 -0
  79. package/dist/test-doubles/index.js +14 -0
  80. package/dist/test-doubles/index.js.map +1 -0
  81. package/dist/types.d.ts +260 -0
  82. package/dist/types.d.ts.map +1 -0
  83. package/dist/types.js +103 -0
  84. package/dist/types.js.map +1 -0
  85. package/dist/uuid.d.ts +3 -0
  86. package/dist/uuid.d.ts.map +1 -0
  87. package/dist/uuid.js +62 -0
  88. package/dist/uuid.js.map +1 -0
  89. package/package.json +33 -7
  90. 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,4 @@
1
+ export { InMemorySagaStateStore } from "./in-memory-state-store";
2
+ export { NoopSignalBus, InMemorySignalBus } from "./in-memory-signal-bus";
3
+ export { InMemoryIdempotency, NoopIdempotency } from "../idempotency";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -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"}