@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,55 @@
|
|
|
1
|
+
import { type SagaAdminServiceOpts, type SagaStateRow } from "./types";
|
|
2
|
+
export interface GetSagaRequest {
|
|
3
|
+
sagaId: string;
|
|
4
|
+
}
|
|
5
|
+
export interface ListSagasRequest {
|
|
6
|
+
tenantId?: string;
|
|
7
|
+
status?: string;
|
|
8
|
+
type?: string;
|
|
9
|
+
limit?: number;
|
|
10
|
+
}
|
|
11
|
+
export interface WriteRpcRequest {
|
|
12
|
+
sagaId: string;
|
|
13
|
+
justification: string;
|
|
14
|
+
actorId?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface RetryStepRequest extends WriteRpcRequest {
|
|
17
|
+
stepName: string;
|
|
18
|
+
}
|
|
19
|
+
export interface SkipStepRequest extends WriteRpcRequest {
|
|
20
|
+
stepName: string;
|
|
21
|
+
}
|
|
22
|
+
export interface ForceCompensateRequest extends WriteRpcRequest {
|
|
23
|
+
fromStep?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface MarkManuallyCompensatedRequest extends WriteRpcRequest {
|
|
26
|
+
resolutionRef?: string;
|
|
27
|
+
}
|
|
28
|
+
export interface CancelSagaRequest extends WriteRpcRequest {
|
|
29
|
+
reason: string;
|
|
30
|
+
}
|
|
31
|
+
export interface SagaAdminServer {
|
|
32
|
+
GetSaga(req: GetSagaRequest): Promise<{
|
|
33
|
+
saga: SagaStateRow | null;
|
|
34
|
+
}>;
|
|
35
|
+
ListSagas(req: ListSagasRequest): Promise<{
|
|
36
|
+
sagas: SagaStateRow[];
|
|
37
|
+
}>;
|
|
38
|
+
RetryStep(req: RetryStepRequest): Promise<{
|
|
39
|
+
ok: true;
|
|
40
|
+
}>;
|
|
41
|
+
SkipStep(req: SkipStepRequest): Promise<{
|
|
42
|
+
ok: true;
|
|
43
|
+
}>;
|
|
44
|
+
ForceCompensate(req: ForceCompensateRequest): Promise<{
|
|
45
|
+
ok: true;
|
|
46
|
+
}>;
|
|
47
|
+
MarkManuallyCompensated(req: MarkManuallyCompensatedRequest): Promise<{
|
|
48
|
+
ok: true;
|
|
49
|
+
}>;
|
|
50
|
+
CancelSaga(req: CancelSagaRequest): Promise<{
|
|
51
|
+
ok: true;
|
|
52
|
+
}>;
|
|
53
|
+
}
|
|
54
|
+
export declare function createSagaAdminServer(opts: SagaAdminServiceOpts): SagaAdminServer;
|
|
55
|
+
//# sourceMappingURL=admin-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin-service.d.ts","sourceRoot":"","sources":["../src/admin-service.ts"],"names":[],"mappings":"AASA,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,YAAY,EAGlB,MAAM,SAAS,CAAC;AAEjB,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,gBAAiB,SAAQ,eAAe;IACvD,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,eAAgB,SAAQ,eAAe;IACtD,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,sBAAuB,SAAQ,eAAe;IAC7D,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,8BAA+B,SAAQ,eAAe;IACrE,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,iBAAkB,SAAQ,eAAe;IACxD,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,YAAY,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC;IACrE,SAAS,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC;QAAE,KAAK,EAAE,YAAY,EAAE,CAAA;KAAE,CAAC,CAAC;IACrE,SAAS,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,IAAI,CAAA;KAAE,CAAC,CAAC;IACxD,QAAQ,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,IAAI,CAAA;KAAE,CAAC,CAAC;IACtD,eAAe,CAAC,GAAG,EAAE,sBAAsB,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,IAAI,CAAA;KAAE,CAAC,CAAC;IACpE,uBAAuB,CACrB,GAAG,EAAE,8BAA8B,GAClC,OAAO,CAAC;QAAE,EAAE,EAAE,IAAI,CAAA;KAAE,CAAC,CAAC;IACzB,UAAU,CAAC,GAAG,EAAE,iBAAiB,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,IAAI,CAAA;KAAE,CAAC,CAAC;CAC3D;AAED,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,oBAAoB,GACzB,eAAe,CAsKjB"}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// SagaAdminService factory — § 5.10. v0.1.0 ships the JSON-RPC-style
|
|
2
|
+
// surface; the real gRPC proto + codegen + Pane wiring lands in
|
|
3
|
+
// saga-v0.1.x. Stubs for the 4 write RPCs throw NotImplementedError AFTER
|
|
4
|
+
// validating `justification` — that keeps the audit shape correct in the
|
|
5
|
+
// rejection path.
|
|
6
|
+
//
|
|
7
|
+
// Two read RPCs (`GetSaga`, `ListSagas`) are fully wired against the
|
|
8
|
+
// pluggable SagaStateStore.
|
|
9
|
+
import { JustificationRequired, MIN_JUSTIFICATION_CHARS, } from "./types";
|
|
10
|
+
export function createSagaAdminServer(opts) {
|
|
11
|
+
const { stateStore, audit } = opts;
|
|
12
|
+
const validateJustification = (justification) => {
|
|
13
|
+
if (typeof justification !== "string" ||
|
|
14
|
+
justification.length < MIN_JUSTIFICATION_CHARS) {
|
|
15
|
+
throw new JustificationRequired(typeof justification === "string" ? justification.length : 0);
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
const emitAdminAudit = async (action, sagaId, payload) => {
|
|
19
|
+
if (!audit)
|
|
20
|
+
return;
|
|
21
|
+
await audit.emit({
|
|
22
|
+
action,
|
|
23
|
+
target_kind: "saga",
|
|
24
|
+
target_id: sagaId,
|
|
25
|
+
payload,
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
return {
|
|
29
|
+
async GetSaga(req) {
|
|
30
|
+
await emitAdminAudit("saga_admin_get_saga", req.sagaId);
|
|
31
|
+
const saga = await stateStore.getSaga(req.sagaId);
|
|
32
|
+
return { saga };
|
|
33
|
+
},
|
|
34
|
+
async ListSagas(req) {
|
|
35
|
+
await emitAdminAudit("saga_admin_list_sagas", req.tenantId ?? "*");
|
|
36
|
+
// Per § 5.5 list() is optional on SagaStateStore — stores that omit it
|
|
37
|
+
// return []. Production-bound stores (Drizzle) MUST implement it.
|
|
38
|
+
const rows = stateStore.list
|
|
39
|
+
? await stateStore.list({
|
|
40
|
+
tenantId: req.tenantId,
|
|
41
|
+
status: req.status,
|
|
42
|
+
type: req.type,
|
|
43
|
+
})
|
|
44
|
+
: [];
|
|
45
|
+
const limit = req.limit ?? 100;
|
|
46
|
+
return { sagas: rows.slice(0, limit) };
|
|
47
|
+
},
|
|
48
|
+
async RetryStep(req) {
|
|
49
|
+
validateJustification(req.justification);
|
|
50
|
+
await emitAdminAudit("saga_admin_retry_step", req.sagaId, {
|
|
51
|
+
step: req.stepName,
|
|
52
|
+
justification: req.justification,
|
|
53
|
+
});
|
|
54
|
+
const now = new Date().toISOString();
|
|
55
|
+
// Reset failure state + transition back to running. The next
|
|
56
|
+
// saga-runner tick on this row picks up at current_step.
|
|
57
|
+
await stateStore.updateSagaStatus(req.sagaId, {
|
|
58
|
+
status: "active",
|
|
59
|
+
current_step: req.stepName,
|
|
60
|
+
failure_reason: null,
|
|
61
|
+
failure_step: null,
|
|
62
|
+
last_admin_action: "retry_step",
|
|
63
|
+
last_admin_actor_id: req.actorId ?? null,
|
|
64
|
+
last_admin_at: now,
|
|
65
|
+
});
|
|
66
|
+
return { ok: true };
|
|
67
|
+
},
|
|
68
|
+
async SkipStep(req) {
|
|
69
|
+
validateJustification(req.justification);
|
|
70
|
+
await emitAdminAudit("saga_admin_skip_step", req.sagaId, {
|
|
71
|
+
step: req.stepName,
|
|
72
|
+
justification: req.justification,
|
|
73
|
+
});
|
|
74
|
+
const now = new Date().toISOString();
|
|
75
|
+
// Record the skip + transition back to running. The skipped step
|
|
76
|
+
// is marked in step_outputs so the saga handler can branch on it.
|
|
77
|
+
await stateStore.appendStepOutput(req.sagaId, req.stepName, {
|
|
78
|
+
admin_skipped: true,
|
|
79
|
+
skipped_at: now,
|
|
80
|
+
actor_id: req.actorId ?? null,
|
|
81
|
+
justification: req.justification,
|
|
82
|
+
});
|
|
83
|
+
await stateStore.updateSagaStatus(req.sagaId, {
|
|
84
|
+
status: "active",
|
|
85
|
+
current_step: req.stepName,
|
|
86
|
+
failure_reason: null,
|
|
87
|
+
failure_step: null,
|
|
88
|
+
last_admin_action: "skip_step",
|
|
89
|
+
last_admin_actor_id: req.actorId ?? null,
|
|
90
|
+
last_admin_at: now,
|
|
91
|
+
});
|
|
92
|
+
return { ok: true };
|
|
93
|
+
},
|
|
94
|
+
async ForceCompensate(req) {
|
|
95
|
+
validateJustification(req.justification);
|
|
96
|
+
await emitAdminAudit("saga_admin_force_compensate", req.sagaId, {
|
|
97
|
+
from_step: req.fromStep,
|
|
98
|
+
justification: req.justification,
|
|
99
|
+
});
|
|
100
|
+
const now = new Date().toISOString();
|
|
101
|
+
// Transition to compensating. The reaper picks up paused/running →
|
|
102
|
+
// compensating rows and runs undo() handlers in reverse step order
|
|
103
|
+
// from current_step (defaulting to fromStep when provided).
|
|
104
|
+
await stateStore.updateSagaStatus(req.sagaId, {
|
|
105
|
+
status: "compensating",
|
|
106
|
+
current_step: req.fromStep ?? null,
|
|
107
|
+
last_admin_action: "force_compensate",
|
|
108
|
+
last_admin_actor_id: req.actorId ?? null,
|
|
109
|
+
last_admin_at: now,
|
|
110
|
+
});
|
|
111
|
+
return { ok: true };
|
|
112
|
+
},
|
|
113
|
+
async MarkManuallyCompensated(req) {
|
|
114
|
+
validateJustification(req.justification);
|
|
115
|
+
await emitAdminAudit("saga_admin_mark_manually_compensated", req.sagaId, {
|
|
116
|
+
resolution_ref: req.resolutionRef,
|
|
117
|
+
justification: req.justification,
|
|
118
|
+
});
|
|
119
|
+
const now = new Date().toISOString();
|
|
120
|
+
// Terminal: operator confirms out-of-band remediation. Records
|
|
121
|
+
// resolution_ref in the compensation_log for audit traceability.
|
|
122
|
+
await stateStore.appendCompensationLog(req.sagaId, {
|
|
123
|
+
step_name: "<manual>",
|
|
124
|
+
// "undone" is the closest outcome semantic: out-of-band remediation
|
|
125
|
+
// by an operator counts as the compensation having succeeded; the
|
|
126
|
+
// resolution_ref + admin action audit trail provide the manual-
|
|
127
|
+
// override traceability.
|
|
128
|
+
outcome: "undone",
|
|
129
|
+
ts: now,
|
|
130
|
+
error: req.resolutionRef
|
|
131
|
+
? `manual_resolution_ref=${req.resolutionRef}`
|
|
132
|
+
: "manual_resolution",
|
|
133
|
+
});
|
|
134
|
+
await stateStore.updateSagaStatus(req.sagaId, {
|
|
135
|
+
status: "compensated_manual",
|
|
136
|
+
completed_at: now,
|
|
137
|
+
last_admin_action: "mark_manually_compensated",
|
|
138
|
+
last_admin_actor_id: req.actorId ?? null,
|
|
139
|
+
last_admin_at: now,
|
|
140
|
+
});
|
|
141
|
+
return { ok: true };
|
|
142
|
+
},
|
|
143
|
+
async CancelSaga(req) {
|
|
144
|
+
validateJustification(req.justification);
|
|
145
|
+
await emitAdminAudit("saga_admin_cancel", req.sagaId, {
|
|
146
|
+
reason: req.reason,
|
|
147
|
+
justification: req.justification,
|
|
148
|
+
});
|
|
149
|
+
await stateStore.updateSagaStatus(req.sagaId, {
|
|
150
|
+
status: "cancelled",
|
|
151
|
+
cancel_reason: req.reason,
|
|
152
|
+
completed_at: new Date().toISOString(),
|
|
153
|
+
last_admin_action: "cancel",
|
|
154
|
+
last_admin_actor_id: req.actorId ?? null,
|
|
155
|
+
last_admin_at: new Date().toISOString(),
|
|
156
|
+
});
|
|
157
|
+
return { ok: true };
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=admin-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin-service.js","sourceRoot":"","sources":["../src/admin-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { SagaError, type StepOpts } from "./types";
|
|
2
|
+
/** Internal sentinel — thrown to unwind a saga at the async-step boundary
|
|
3
|
+
* without triggering compensation. `createSaga` catches and treats as
|
|
4
|
+
* "saga paused, return current handle". */
|
|
5
|
+
export declare class SagaPausedSentinel extends SagaError {
|
|
6
|
+
readonly sagaId: string;
|
|
7
|
+
readonly stepName: string;
|
|
8
|
+
constructor(sagaId: string, stepName: string);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Begin a Pattern 2 step. Called from `step()` when `opts.async === true`.
|
|
12
|
+
*
|
|
13
|
+
* Side effects:
|
|
14
|
+
* - saga_state.status → 'paused', current_step = name, next_resume_at = now + grace.
|
|
15
|
+
* - saga_outbox row written: { event_type: 'saga.step.<name>.begin', payload }
|
|
16
|
+
* - SagaPausedSentinel thrown — control exits the saga's `fn`.
|
|
17
|
+
*/
|
|
18
|
+
export declare function beginAsyncStep<O>(name: string, opts: StepOpts<O>): Promise<O>;
|
|
19
|
+
/**
|
|
20
|
+
* Mark an async (Pattern 2) step as completed. Consumer workers call this
|
|
21
|
+
* when the participant-side completion event arrives.
|
|
22
|
+
*
|
|
23
|
+
* - Persists the step output to saga_state.step_outputs.
|
|
24
|
+
* - Transitions saga_state.status='active' + clears next_resume_at.
|
|
25
|
+
* - Emits `saga_resumed` audit event.
|
|
26
|
+
*
|
|
27
|
+
* The caller is responsible for invoking `withSaga(sagaId, ...)` + the
|
|
28
|
+
* registered saga function afterwards (the lifecycle pattern from § 5.8).
|
|
29
|
+
*/
|
|
30
|
+
export declare function completeAsyncStep(args: {
|
|
31
|
+
sagaId: string;
|
|
32
|
+
stepName: string;
|
|
33
|
+
output: unknown;
|
|
34
|
+
}): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Read the persisted output of a previously-completed async step. Used by
|
|
37
|
+
* a re-entered saga function to retrieve the result inside its step body.
|
|
38
|
+
*/
|
|
39
|
+
export declare function getCompletedAsyncOutput(args: {
|
|
40
|
+
sagaId: string;
|
|
41
|
+
stepName: string;
|
|
42
|
+
}): Promise<unknown | null>;
|
|
43
|
+
//# sourceMappingURL=async-step.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"async-step.d.ts","sourceRoot":"","sources":["../src/async-step.ts"],"names":[],"mappings":"AAgCA,OAAO,EAAE,SAAS,EAAE,KAAK,QAAQ,EAAE,MAAM,SAAS,CAAC;AAKnD;;4CAE4C;AAC5C,qBAAa,kBAAmB,SAAQ,SAAS;IAC/C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;gBACd,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;CAQ7C;AAED;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAAC,CAAC,EACpC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,GAChB,OAAO,CAAC,CAAC,CAAC,CAuEZ;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE;IAC5C,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;CACjB,GAAG,OAAO,CAAC,IAAI,CAAC,CA+BhB;AAED;;;GAGG;AACH,wBAAsB,uBAAuB,CAAC,IAAI,EAAE;IAClD,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAK1B"}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
// Pattern 2 (async-via-outbox) step impl.
|
|
2
|
+
//
|
|
3
|
+
// Spec § 5.6 + comm doctrine § 4.5. When a step is invoked with
|
|
4
|
+
// `{ async: true }`, the library:
|
|
5
|
+
//
|
|
6
|
+
// 1. Pauses the saga (status='paused', next_resume_at=now+graceMs).
|
|
7
|
+
// 2. Writes a `saga.step.<step_name>.begin` event to saga_outbox in
|
|
8
|
+
// the same DB tx as the saga_state advance.
|
|
9
|
+
// 3. Throws `SagaPausedSentinel` which `createSaga` recognizes as a
|
|
10
|
+
// pause boundary — NOT a step failure. The saga exits cleanly with
|
|
11
|
+
// its handle reported as paused. The outer caller observes
|
|
12
|
+
// paused-state via `getSaga(id).status === 'paused'`.
|
|
13
|
+
//
|
|
14
|
+
// The completion path is driven by `completeAsyncStep(sagaId, stepName,
|
|
15
|
+
// result)`. This is the function a consumer worker (per-service) calls
|
|
16
|
+
// when the participant-side work finishes. It:
|
|
17
|
+
//
|
|
18
|
+
// 1. Persists the step output.
|
|
19
|
+
// 2. Transitions saga_state.status='active'.
|
|
20
|
+
// 3. Emits a `saga.step.<step_name>.completed` audit event.
|
|
21
|
+
// 4. The next `withSaga(sagaId, ...)` invocation can then re-enter the
|
|
22
|
+
// saga and call the same step name; the step short-circuits on
|
|
23
|
+
// idempotency-cached output OR returns the persisted output via
|
|
24
|
+
// `getCompletedAsyncOutput`.
|
|
25
|
+
//
|
|
26
|
+
// For v0.1.0 we keep the implementation deterministic + observable
|
|
27
|
+
// rather than fully transactional — Pattern 2's "same DB tx" mandate is
|
|
28
|
+
// the orchestrator-of-truth's concern; the library writes the outbox row
|
|
29
|
+
// in the same call but does not span participant boundaries.
|
|
30
|
+
import { requireSagaContext } from "./context";
|
|
31
|
+
import { requireSaga } from "./init";
|
|
32
|
+
import { SagaError } from "./types";
|
|
33
|
+
const ASYNC_OUTBOX_EVENT_PREFIX = "saga.step.";
|
|
34
|
+
const PAUSE_DEFAULT_GRACE_MS = 24 * 60 * 60 * 1000; // 24h, per § 5.8
|
|
35
|
+
/** Internal sentinel — thrown to unwind a saga at the async-step boundary
|
|
36
|
+
* without triggering compensation. `createSaga` catches and treats as
|
|
37
|
+
* "saga paused, return current handle". */
|
|
38
|
+
export class SagaPausedSentinel extends SagaError {
|
|
39
|
+
sagaId;
|
|
40
|
+
stepName;
|
|
41
|
+
constructor(sagaId, stepName) {
|
|
42
|
+
super(`saga_paused: ${sagaId} paused at step '${stepName}' awaiting Pattern 2 completion`);
|
|
43
|
+
this.name = "SagaPausedSentinel";
|
|
44
|
+
this.sagaId = sagaId;
|
|
45
|
+
this.stepName = stepName;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Begin a Pattern 2 step. Called from `step()` when `opts.async === true`.
|
|
50
|
+
*
|
|
51
|
+
* Side effects:
|
|
52
|
+
* - saga_state.status → 'paused', current_step = name, next_resume_at = now + grace.
|
|
53
|
+
* - saga_outbox row written: { event_type: 'saga.step.<name>.begin', payload }
|
|
54
|
+
* - SagaPausedSentinel thrown — control exits the saga's `fn`.
|
|
55
|
+
*/
|
|
56
|
+
export async function beginAsyncStep(name, opts) {
|
|
57
|
+
const cfg = requireSaga();
|
|
58
|
+
const sagaCtx = requireSagaContext("step(async)");
|
|
59
|
+
const row = sagaCtx.row;
|
|
60
|
+
// Compute the pause window. `opts` doesn't carry it for v0.1.0; we
|
|
61
|
+
// reuse the global default.
|
|
62
|
+
const nextResumeAt = new Date(Date.now() + PAUSE_DEFAULT_GRACE_MS).toISOString();
|
|
63
|
+
await cfg.stateStore.updateSagaStatus(row.id, {
|
|
64
|
+
status: "paused",
|
|
65
|
+
current_step: name,
|
|
66
|
+
next_resume_at: nextResumeAt,
|
|
67
|
+
});
|
|
68
|
+
row.status = "paused";
|
|
69
|
+
row.current_step = name;
|
|
70
|
+
row.next_resume_at = nextResumeAt;
|
|
71
|
+
// Idempotency-key payload mirrors step()'s deterministic serialization
|
|
72
|
+
// so a downstream completion event can correlate.
|
|
73
|
+
let serializedInput;
|
|
74
|
+
try {
|
|
75
|
+
const payload = {
|
|
76
|
+
name,
|
|
77
|
+
persist_fields: [...(opts.persistFields ?? [])].sort(),
|
|
78
|
+
};
|
|
79
|
+
if (opts.input !== undefined)
|
|
80
|
+
payload.input = opts.input;
|
|
81
|
+
serializedInput = JSON.stringify(payload, [
|
|
82
|
+
"input",
|
|
83
|
+
"name",
|
|
84
|
+
"persist_fields",
|
|
85
|
+
]);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
serializedInput = `${name}::nonserializable::${row.id}`;
|
|
89
|
+
}
|
|
90
|
+
// Write outbox row — same call as the state advance. Stores that don't
|
|
91
|
+
// implement outbox (in-memory) get a soft warning + no-op.
|
|
92
|
+
if (cfg.stateStore.writeOutboxBegin) {
|
|
93
|
+
await cfg.stateStore.writeOutboxBegin({
|
|
94
|
+
sagaId: row.id,
|
|
95
|
+
stepName: name,
|
|
96
|
+
eventType: `${ASYNC_OUTBOX_EVENT_PREFIX}${name}.begin`,
|
|
97
|
+
payload: {
|
|
98
|
+
saga_id: row.id,
|
|
99
|
+
step_name: name,
|
|
100
|
+
idempotency_serialized_input: serializedInput,
|
|
101
|
+
tenant_id: row.tenant_id,
|
|
102
|
+
},
|
|
103
|
+
tenantId: row.tenant_id,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
cfg.logger.warn("async-step: state store does not implement writeOutboxBegin; outbox event not persisted", { saga_id: row.id, step: name });
|
|
108
|
+
}
|
|
109
|
+
if (cfg.telemetryAudit) {
|
|
110
|
+
await cfg.telemetryAudit.emit({
|
|
111
|
+
action: "saga_paused",
|
|
112
|
+
target_kind: "saga",
|
|
113
|
+
target_id: row.id,
|
|
114
|
+
payload: { step: name, pattern: "2" },
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
throw new SagaPausedSentinel(row.id, name);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Mark an async (Pattern 2) step as completed. Consumer workers call this
|
|
121
|
+
* when the participant-side completion event arrives.
|
|
122
|
+
*
|
|
123
|
+
* - Persists the step output to saga_state.step_outputs.
|
|
124
|
+
* - Transitions saga_state.status='active' + clears next_resume_at.
|
|
125
|
+
* - Emits `saga_resumed` audit event.
|
|
126
|
+
*
|
|
127
|
+
* The caller is responsible for invoking `withSaga(sagaId, ...)` + the
|
|
128
|
+
* registered saga function afterwards (the lifecycle pattern from § 5.8).
|
|
129
|
+
*/
|
|
130
|
+
export async function completeAsyncStep(args) {
|
|
131
|
+
const cfg = requireSaga();
|
|
132
|
+
const row = await cfg.stateStore.getSaga(args.sagaId);
|
|
133
|
+
if (!row) {
|
|
134
|
+
throw new Error(`completeAsyncStep: saga ${args.sagaId} not found`);
|
|
135
|
+
}
|
|
136
|
+
if (row.status !== "paused") {
|
|
137
|
+
// Per comm doctrine § 4.5 step 11: drop on duplicate / late arrivals.
|
|
138
|
+
cfg.logger.warn("completeAsyncStep: saga is not paused; treating completion event as duplicate", { saga_id: args.sagaId, current_status: row.status, step: args.stepName });
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
await cfg.stateStore.appendStepOutput(args.sagaId, args.stepName, args.output);
|
|
142
|
+
await cfg.stateStore.updateSagaStatus(args.sagaId, {
|
|
143
|
+
status: "active",
|
|
144
|
+
next_resume_at: null,
|
|
145
|
+
});
|
|
146
|
+
if (cfg.telemetryAudit) {
|
|
147
|
+
await cfg.telemetryAudit.emit({
|
|
148
|
+
action: "saga_resumed",
|
|
149
|
+
target_kind: "saga",
|
|
150
|
+
target_id: args.sagaId,
|
|
151
|
+
payload: { step: args.stepName, pattern: "2" },
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Read the persisted output of a previously-completed async step. Used by
|
|
157
|
+
* a re-entered saga function to retrieve the result inside its step body.
|
|
158
|
+
*/
|
|
159
|
+
export async function getCompletedAsyncOutput(args) {
|
|
160
|
+
const cfg = requireSaga();
|
|
161
|
+
const row = await cfg.stateStore.getSaga(args.sagaId);
|
|
162
|
+
if (!row)
|
|
163
|
+
return null;
|
|
164
|
+
return row.step_outputs?.[args.stepName] ?? null;
|
|
165
|
+
}
|
|
166
|
+
//# sourceMappingURL=async-step.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"async-step.js","sourceRoot":"","sources":["../src/async-step.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type ResumeContext, type SagaHandle, type SagaStateRow } from "./types";
|
|
2
|
+
interface SagaContextState {
|
|
3
|
+
handle: SagaHandle;
|
|
4
|
+
row: SagaStateRow;
|
|
5
|
+
resume?: ResumeContext;
|
|
6
|
+
/** Steps that completed in this episode (push-on-complete). */
|
|
7
|
+
completedSteps: {
|
|
8
|
+
name: string;
|
|
9
|
+
output: unknown;
|
|
10
|
+
}[];
|
|
11
|
+
}
|
|
12
|
+
export declare function runInSagaContext<T>(state: SagaContextState, fn: () => Promise<T>): Promise<T>;
|
|
13
|
+
export declare function getSagaContext(): SagaContextState | undefined;
|
|
14
|
+
export declare function requireSagaContext(api: string): SagaContextState;
|
|
15
|
+
export declare function runWithResumeContext<T>(ctx: ResumeContext, fn: () => Promise<T>): Promise<T>;
|
|
16
|
+
export declare function getResumeContext(): ResumeContext | undefined;
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,UAAU,EACf,KAAK,YAAY,EAElB,MAAM,SAAS,CAAC;AAEjB,UAAU,gBAAgB;IACxB,MAAM,EAAE,UAAU,CAAC;IACnB,GAAG,EAAE,YAAY,CAAC;IAClB,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,+DAA+D;IAC/D,cAAc,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,OAAO,CAAA;KAAE,EAAE,CAAC;CACrD;AAID,wBAAgB,gBAAgB,CAAC,CAAC,EAChC,KAAK,EAAE,gBAAgB,EACvB,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACnB,OAAO,CAAC,CAAC,CAAC,CAEZ;AAED,wBAAgB,cAAc,IAAI,gBAAgB,GAAG,SAAS,CAE7D;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,CAMhE;AAMD,wBAAgB,oBAAoB,CAAC,CAAC,EACpC,GAAG,EAAE,aAAa,EAClB,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACnB,OAAO,CAAC,CAAC,CAAC,CAEZ;AAED,wBAAgB,gBAAgB,IAAI,aAAa,GAAG,SAAS,CAE5D"}
|
package/dist/context.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// ALS-based saga context — primes (sagaId, resumeContext, siblings) for
|
|
2
|
+
// `step`, `withSaga`, `emitSagaSignal`, etc.
|
|
3
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
4
|
+
import { NoActiveSaga, } from "./types";
|
|
5
|
+
const sagaStorage = new AsyncLocalStorage();
|
|
6
|
+
export function runInSagaContext(state, fn) {
|
|
7
|
+
return sagaStorage.run(state, fn);
|
|
8
|
+
}
|
|
9
|
+
export function getSagaContext() {
|
|
10
|
+
return sagaStorage.getStore();
|
|
11
|
+
}
|
|
12
|
+
export function requireSagaContext(api) {
|
|
13
|
+
const ctx = sagaStorage.getStore();
|
|
14
|
+
if (!ctx) {
|
|
15
|
+
throw new NoActiveSaga(api);
|
|
16
|
+
}
|
|
17
|
+
return ctx;
|
|
18
|
+
}
|
|
19
|
+
// Resume context priming — `withSaga()` sets this so the next createSaga
|
|
20
|
+
// detects "we're inside a resume episode" and reloads from the state store.
|
|
21
|
+
const resumeStorage = new AsyncLocalStorage();
|
|
22
|
+
export function runWithResumeContext(ctx, fn) {
|
|
23
|
+
return resumeStorage.run(ctx, fn);
|
|
24
|
+
}
|
|
25
|
+
export function getResumeContext() {
|
|
26
|
+
return resumeStorage.getStore();
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,6CAA6C;AAE7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAIL,YAAY,GACb,MAAM,SAAS,CAAC;AAUjB,MAAM,WAAW,GAAG,IAAI,iBAAiB,EAAoB,CAAC;AAE9D,MAAM,UAAU,gBAAgB,CAC9B,KAAuB,EACvB,EAAoB;IAEpB,OAAO,WAAW,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AACpC,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,OAAO,WAAW,CAAC,QAAQ,EAAE,CAAC;AAChC,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,GAAW;IAC5C,MAAM,GAAG,GAAG,WAAW,CAAC,QAAQ,EAAE,CAAC;IACnC,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,YAAY,CAAC,GAAG,CAAC,CAAC;IAC9B,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,yEAAyE;AACzE,4EAA4E;AAC5E,MAAM,aAAa,GAAG,IAAI,iBAAiB,EAAiB,CAAC;AAE7D,MAAM,UAAU,oBAAoB,CAClC,GAAkB,EAClB,EAAoB;IAEpB,OAAO,aAAa,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;AACpC,CAAC;AAED,MAAM,UAAU,gBAAgB;IAC9B,OAAO,aAAa,CAAC,QAAQ,EAAE,CAAC;AAClC,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { SagaCallableConfig } from "./types";
|
|
2
|
+
interface RegistryEntry {
|
|
3
|
+
className: string;
|
|
4
|
+
methodName: string;
|
|
5
|
+
config: SagaCallableConfig;
|
|
6
|
+
}
|
|
7
|
+
/** Symbol-keyed slot so consumers can read the tag off a method. */
|
|
8
|
+
export declare const SAGA_CALLABLE_METADATA: unique symbol;
|
|
9
|
+
export type SagaCallableMethod = {
|
|
10
|
+
[SAGA_CALLABLE_METADATA]: SagaCallableConfig;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Decorator factory — usable as `@sagaCallable({...})` (TC39 stage-3
|
|
14
|
+
* decorators) and also callable explicitly: `sagaCallable({...})(method)`.
|
|
15
|
+
*
|
|
16
|
+
* Legacy `experimentalDecorators` shape (3-arg) is supported so this works
|
|
17
|
+
* under bun's default tsconfig.
|
|
18
|
+
*/
|
|
19
|
+
export declare function sagaCallable(config: SagaCallableConfig): (target: unknown, propertyKeyOrContext?: unknown, descriptor?: PropertyDescriptor) => unknown;
|
|
20
|
+
export declare function readSagaCallableConfig(fn: unknown): SagaCallableConfig | undefined;
|
|
21
|
+
export declare function readSagaCallableRegistry(): readonly RegistryEntry[];
|
|
22
|
+
export declare function _resetDecoratorRegistryForTests(): void;
|
|
23
|
+
export {};
|
|
24
|
+
//# sourceMappingURL=decorator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decorator.d.ts","sourceRoot":"","sources":["../src/decorator.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAElD,UAAU,aAAa;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,kBAAkB,CAAC;CAC5B;AAID,oEAAoE;AACpE,eAAO,MAAM,sBAAsB,EAAE,OAAO,MAE3C,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,CAAC,sBAAsB,CAAC,EAAE,kBAAkB,CAAC;CAC9C,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,kBAAkB,IAEnD,QAAQ,OAAO,EACf,uBAAuB,OAAO,EAC9B,aAAa,kBAAkB,KAC9B,OAAO,CAgDX;AAWD,wBAAgB,sBAAsB,CACpC,EAAE,EAAE,OAAO,GACV,kBAAkB,GAAG,SAAS,CAKhC;AAED,wBAAgB,wBAAwB,IAAI,SAAS,aAAa,EAAE,CAEnE;AAED,wBAAgB,+BAA+B,IAAI,IAAI,CAEtD"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// @sagaCallable — D165 six-tag schema with runtime metadata + class registry.
|
|
2
|
+
//
|
|
3
|
+
// Spec § 5.7. v0.1.0 attaches the tag to the method's prototype-bound function
|
|
4
|
+
// and records (className, methodName, config) into a process-local registry
|
|
5
|
+
// the lint plugin + codegen will read at build time (`// TODO saga-v0.1.x:`
|
|
6
|
+
// for the build-time emitters).
|
|
7
|
+
const registry = [];
|
|
8
|
+
/** Symbol-keyed slot so consumers can read the tag off a method. */
|
|
9
|
+
export const SAGA_CALLABLE_METADATA = Symbol.for("@nodii/saga.sagaCallableMetadata");
|
|
10
|
+
/**
|
|
11
|
+
* Decorator factory — usable as `@sagaCallable({...})` (TC39 stage-3
|
|
12
|
+
* decorators) and also callable explicitly: `sagaCallable({...})(method)`.
|
|
13
|
+
*
|
|
14
|
+
* Legacy `experimentalDecorators` shape (3-arg) is supported so this works
|
|
15
|
+
* under bun's default tsconfig.
|
|
16
|
+
*/
|
|
17
|
+
export function sagaCallable(config) {
|
|
18
|
+
return function decorate(target, propertyKeyOrContext, descriptor) {
|
|
19
|
+
// Stage-3 decorator: (value, context) returning a replacement function.
|
|
20
|
+
if (typeof target === "function" &&
|
|
21
|
+
propertyKeyOrContext &&
|
|
22
|
+
typeof propertyKeyOrContext === "object" &&
|
|
23
|
+
"kind" in propertyKeyOrContext) {
|
|
24
|
+
const ctx = propertyKeyOrContext;
|
|
25
|
+
const fn = target;
|
|
26
|
+
attachMetadata(fn, config);
|
|
27
|
+
ctx.addInitializer?.(function () {
|
|
28
|
+
const className = this.constructor?.name ??
|
|
29
|
+
"<anonymous>";
|
|
30
|
+
registry.push({ className, methodName: ctx.name, config });
|
|
31
|
+
});
|
|
32
|
+
return fn;
|
|
33
|
+
}
|
|
34
|
+
// Legacy decorator: (target, propertyKey, descriptor).
|
|
35
|
+
if (typeof propertyKeyOrContext === "string" ||
|
|
36
|
+
typeof propertyKeyOrContext === "symbol") {
|
|
37
|
+
const className = target?.constructor?.name ??
|
|
38
|
+
target.name ??
|
|
39
|
+
"<anonymous>";
|
|
40
|
+
const methodName = String(propertyKeyOrContext);
|
|
41
|
+
if (descriptor && typeof descriptor.value === "function") {
|
|
42
|
+
attachMetadata(descriptor.value, config);
|
|
43
|
+
}
|
|
44
|
+
registry.push({ className, methodName, config });
|
|
45
|
+
return descriptor;
|
|
46
|
+
}
|
|
47
|
+
// Explicit call: sagaCallable({...})(fn).
|
|
48
|
+
if (typeof target === "function") {
|
|
49
|
+
attachMetadata(target, config);
|
|
50
|
+
return target;
|
|
51
|
+
}
|
|
52
|
+
return target;
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function attachMetadata(fn, config) {
|
|
56
|
+
fn[SAGA_CALLABLE_METADATA] = config;
|
|
57
|
+
}
|
|
58
|
+
export function readSagaCallableConfig(fn) {
|
|
59
|
+
if (typeof fn !== "function")
|
|
60
|
+
return undefined;
|
|
61
|
+
return fn[SAGA_CALLABLE_METADATA];
|
|
62
|
+
}
|
|
63
|
+
export function readSagaCallableRegistry() {
|
|
64
|
+
return [...registry];
|
|
65
|
+
}
|
|
66
|
+
export function _resetDecoratorRegistryForTests() {
|
|
67
|
+
registry.length = 0;
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=decorator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decorator.js","sourceRoot":"","sources":["../src/decorator.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,EAAE;AACF,+EAA+E;AAC/E,4EAA4E;AAC5E,4EAA4E;AAC5E,gCAAgC;AAUhC,MAAM,QAAQ,GAAoB,EAAE,CAAC;AAErC,oEAAoE;AACpE,MAAM,CAAC,MAAM,sBAAsB,GAAkB,MAAM,CAAC,GAAG,CAC7D,kCAAkC,CACnC,CAAC;AAMF;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,MAA0B;IACrD,OAAO,SAAS,QAAQ,CACtB,MAAe,EACf,oBAA8B,EAC9B,UAA+B;QAE/B,wEAAwE;QACxE,IACE,OAAO,MAAM,KAAK,UAAU;YAC5B,oBAAoB;YACpB,OAAO,oBAAoB,KAAK,QAAQ;YACxC,MAAM,IAAK,oBAA+B,EAC1C,CAAC;YACD,MAAM,GAAG,GAAG,oBAIX,CAAC;YACF,MAAM,EAAE,GAAG,MAAyC,CAAC;YACrD,cAAc,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;YAC3B,GAAG,CAAC,cAAc,EAAE,CAAC;gBACnB,MAAM,SAAS,GACZ,IAA4C,CAAC,WAAW,EAAE,IAAI;oBAC/D,aAAa,CAAC;gBAChB,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,UAAU,EAAE,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;YAC7D,CAAC,CAAC,CAAC;YACH,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,uDAAuD;QACvD,IACE,OAAO,oBAAoB,KAAK,QAAQ;YACxC,OAAO,oBAAoB,KAAK,QAAQ,EACxC,CAAC;YACD,MAAM,SAAS,GACZ,MAA8C,EAAE,WAAW,EAAE,IAAI;gBACjE,MAA4B,CAAC,IAAI;gBAClC,aAAa,CAAC;YAChB,MAAM,UAAU,GAAG,MAAM,CAAC,oBAAoB,CAAC,CAAC;YAChD,IAAI,UAAU,IAAI,OAAO,UAAU,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;gBACzD,cAAc,CAAC,UAAU,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;YAC3C,CAAC;YACD,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC,CAAC;YACjD,OAAO,UAAU,CAAC;QACpB,CAAC;QAED,0CAA0C;QAC1C,IAAI,OAAO,MAAM,KAAK,UAAU,EAAE,CAAC;YACjC,cAAc,CAAC,MAAyC,EAAE,MAAM,CAAC,CAAC;YAClE,OAAO,MAAM,CAAC;QAChB,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC;AACJ,CAAC;AAED,SAAS,cAAc,CACrB,EAAmC,EACnC,MAA0B;IAEzB,EAAkE,CACjE,sBAAsB,CACvB,GAAG,MAAM,CAAC;AACb,CAAC;AAED,MAAM,UAAU,sBAAsB,CACpC,EAAW;IAEX,IAAI,OAAO,EAAE,KAAK,UAAU;QAAE,OAAO,SAAS,CAAC;IAC/C,OAAQ,EAAwD,CAC9D,sBAAsB,CACvB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,wBAAwB;IACtC,OAAO,CAAC,GAAG,QAAQ,CAAC,CAAC;AACvB,CAAC;AAED,MAAM,UAAU,+BAA+B;IAC7C,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;AACtB,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { IdempotencyShim } from "./types";
|
|
2
|
+
export declare class NoopIdempotency implements IdempotencyShim {
|
|
3
|
+
wrapForSagaStep<O>(args: {
|
|
4
|
+
sagaId: string;
|
|
5
|
+
stepName: string;
|
|
6
|
+
serializedInput: string;
|
|
7
|
+
handler: () => Promise<O>;
|
|
8
|
+
}): Promise<O>;
|
|
9
|
+
}
|
|
10
|
+
export declare class InMemoryIdempotency implements IdempotencyShim {
|
|
11
|
+
private cache;
|
|
12
|
+
wrapForSagaStep<O>(args: {
|
|
13
|
+
sagaId: string;
|
|
14
|
+
stepName: string;
|
|
15
|
+
serializedInput: string;
|
|
16
|
+
handler: () => Promise<O>;
|
|
17
|
+
}): Promise<O>;
|
|
18
|
+
/** Test-only — used by the parity-fence fixtures to assert byte equality. */
|
|
19
|
+
static debugComputeKey(sagaId: string, stepName: string, serializedInput: string): string;
|
|
20
|
+
reset(): void;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=idempotency.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"idempotency.d.ts","sourceRoot":"","sources":["../src/idempotency.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C,qBAAa,eAAgB,YAAW,eAAe;IAC/C,eAAe,CAAC,CAAC,EAAE,IAAI,EAAE;QAC7B,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,eAAe,EAAE,MAAM,CAAC;QACxB,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,CAAC;KAC3B,GAAG,OAAO,CAAC,CAAC,CAAC;CAGf;AAED,qBAAa,mBAAoB,YAAW,eAAe;IACzD,OAAO,CAAC,KAAK,CAA8B;IAErC,eAAe,CAAC,CAAC,EAAE,IAAI,EAAE;QAC7B,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,eAAe,EAAE,MAAM,CAAC;QACxB,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,CAAC;KAC3B,GAAG,OAAO,CAAC,CAAC,CAAC;IAUd,6EAA6E;IAC7E,MAAM,CAAC,eAAe,CACpB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,eAAe,EAAE,MAAM,GACtB,MAAM;IAIT,KAAK,IAAI,IAAI;CAGd"}
|