@fabricorg/platform-temporal 4.0.0 → 4.1.1
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/CHANGELOG.md +41 -0
- package/README.md +15 -0
- package/dist/index.cjs +113 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +128 -1
- package/dist/index.d.ts +128 -1
- package/dist/index.js +107 -0
- package/dist/index.js.map +1 -1
- package/package.json +10 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,46 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 4.1.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 1b81b20: Publish with real build provenance, and name the repository each attestation points at. `pnpm publish` accepts `--provenance` and drops it, so every release until now was unattested while the release document claimed otherwise. The pipeline now packs with pnpm and uploads the tarball with `npm publish --provenance`, and every package declares `repository`, `homepage`, and `bugs`.
|
|
8
|
+
|
|
9
|
+
## 4.1.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- 293561d: Close adapter and saga correctness gaps: deliver actual deadline and AbortSignal context to adapters, require the governed Host to classify thrown adapter failures before retrying, reject contradictory successful ambiguous outcomes, reserve parent lifecycle identity prefixes, and provide a durable Platform Host parent lifecycle adapter tested through public store seams.
|
|
14
|
+
- 4e585a4: Close final production-maturity correctness gaps: expired leases can no longer be renewed,
|
|
15
|
+
saga parent transitions are serialized, atomic, terminal-safe, and retry-idempotent by lifecycle identity, hydrated
|
|
16
|
+
projections must meet requested checkpoints, and SDUI releases accept components only from
|
|
17
|
+
resolved document packs. The release gate now verifies every package and public subpath in
|
|
18
|
+
isolated ESM, CommonJS, and TypeScript consumers.
|
|
19
|
+
- fb14d67: Add versioned PostgreSQL migrations and live PostgreSQL 16 certification. Fence and renew invocation
|
|
20
|
+
and outbox leases, bound publisher calls, reject stale finalization, and add deterministic Temporal
|
|
21
|
+
saga workflow binding with explicit parent lifecycle activities.
|
|
22
|
+
- eefd1eb: Complete the saga parent lifecycle: add durable progress, approval-parking, and cancellation activity
|
|
23
|
+
contracts alongside the existing completion and failure seams. Add stable parent and child causation
|
|
24
|
+
identity helpers and stable saga signal names. Cover duplicate dispatch, cancellation, approval,
|
|
25
|
+
compensation failure, and worker rollover through public Temporal seams.
|
|
26
|
+
|
|
27
|
+
### Additional release notes
|
|
28
|
+
|
|
29
|
+
### Minor Changes
|
|
30
|
+
|
|
31
|
+
- Add an instance-scoped saga workflow registry, stable Temporal dispatcher, and explicit durable
|
|
32
|
+
parent completion and compensation-failure activity seams.
|
|
33
|
+
|
|
34
|
+
- Complete the saga parent lifecycle: add durable progress, approval-parking, and cancellation
|
|
35
|
+
activity contracts alongside the existing completion and failure seams. Add stable parent and
|
|
36
|
+
child causation identity helpers (`sagaParentActivityIdentity`, `sagaChildCausation`) and stable
|
|
37
|
+
saga signal names (`SAGA_SIGNALS`). Cover duplicate dispatch, cancellation, approval, compensation
|
|
38
|
+
failure, and worker rollover through public Temporal seams.
|
|
39
|
+
- Reserve the `parent:` identity prefix in `assertDistinctSteps` so a forward step cannot collide
|
|
40
|
+
with a parent lifecycle activity identity.
|
|
41
|
+
- Attach stable occurrence-specific lifecycle identities in `createSagaParentActivities` so
|
|
42
|
+
activity retries map to one durable parent transition.
|
|
43
|
+
|
|
3
44
|
## 4.0.0
|
|
4
45
|
|
|
5
46
|
### Major Changes
|
package/README.md
CHANGED
|
@@ -3,3 +3,18 @@
|
|
|
3
3
|
Host-side Temporal binding for governed Platform actions. Activities invoke the public governed Host boundary with stable workflow/step idempotency, carry workflow evidence, and map saga/approval messages to signals. Temporal supplies durable orchestration; Platform invocation and event records remain canonical audit evidence. This package does not call handlers or make Platform a workflow authority.
|
|
4
4
|
|
|
5
5
|
`temporalWorkflowStartClient()` adapts the official `@temporalio/client` `WorkflowClient`; all direct Temporal packages in an application must remain on the family-supported version line.
|
|
6
|
+
|
|
7
|
+
`SagaWorkflowRegistry` maps saga action IDs to workflow types and task queues without process-global
|
|
8
|
+
registration. `createTemporalSagaDispatcher()` starts the parent under the Host's stable workflow ID
|
|
9
|
+
with Temporal's use-existing conflict policy. `createSagaParentActivities()` exposes explicit durable
|
|
10
|
+
progress, approval-parking, cancellation, completion, and compensation-failure callbacks; workflows
|
|
11
|
+
must finalize the canonical parent invocation through those activities rather than treating Temporal
|
|
12
|
+
history as the audit ledger.
|
|
13
|
+
|
|
14
|
+
`createSagaParentActivities()` attaches a distinct stable `lifecycleId` to each progress step,
|
|
15
|
+
approval gate, cancellation, completion, and failure. `sagaParentActivityIdentity()` accepts an
|
|
16
|
+
optional occurrence discriminator for callers constructing those identities directly, while
|
|
17
|
+
`sagaChildCausation()` produces stable child causation. A durable lifecycle adapter uses these
|
|
18
|
+
identities to make a retried or replayed activity idempotent. `SAGA_SIGNALS` declares
|
|
19
|
+
the stable signal names (`saga.proceed`, `saga.cancel`, `saga.approval`) a workflow may register
|
|
20
|
+
handlers for.
|
package/dist/index.cjs
CHANGED
|
@@ -20,12 +20,18 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
SAGA_SIGNALS: () => SAGA_SIGNALS,
|
|
23
24
|
SagaCompensationError: () => SagaCompensationError,
|
|
25
|
+
SagaWorkflowRegistry: () => SagaWorkflowRegistry,
|
|
24
26
|
assertWorkerVersionEvidence: () => assertWorkerVersionEvidence,
|
|
25
27
|
compensationInvocationIdentity: () => compensationInvocationIdentity,
|
|
26
28
|
createGovernedActionActivities: () => createGovernedActionActivities,
|
|
29
|
+
createSagaParentActivities: () => createSagaParentActivities,
|
|
27
30
|
createSagaSignalDispatcher: () => createSagaSignalDispatcher,
|
|
31
|
+
createTemporalSagaDispatcher: () => createTemporalSagaDispatcher,
|
|
28
32
|
runCompensatedSaga: () => runCompensatedSaga,
|
|
33
|
+
sagaChildCausation: () => sagaChildCausation,
|
|
34
|
+
sagaParentActivityIdentity: () => sagaParentActivityIdentity,
|
|
29
35
|
startGovernedWorkflow: () => startGovernedWorkflow,
|
|
30
36
|
temporalInvocationIdentity: () => temporalInvocationIdentity,
|
|
31
37
|
temporalWorkflowStartClient: () => temporalWorkflowStartClient
|
|
@@ -58,6 +64,106 @@ function createSagaSignalDispatcher(client) {
|
|
|
58
64
|
function startGovernedWorkflow(client, input) {
|
|
59
65
|
return client.start({ ...input, idConflictPolicy: "use_existing" });
|
|
60
66
|
}
|
|
67
|
+
var SagaWorkflowRegistry = class {
|
|
68
|
+
bindings = /* @__PURE__ */ new Map();
|
|
69
|
+
constructor(bindings = []) {
|
|
70
|
+
for (const binding of bindings) this.register(binding);
|
|
71
|
+
}
|
|
72
|
+
register(binding) {
|
|
73
|
+
if (this.bindings.has(binding.actionId)) {
|
|
74
|
+
throw new Error(`Saga workflow binding already registered: ${binding.actionId}`);
|
|
75
|
+
}
|
|
76
|
+
if (!binding.workflowType.trim() || !binding.taskQueue.trim()) {
|
|
77
|
+
throw new Error(`Saga workflow binding ${binding.actionId} requires workflowType and taskQueue`);
|
|
78
|
+
}
|
|
79
|
+
this.bindings.set(binding.actionId, { ...binding });
|
|
80
|
+
}
|
|
81
|
+
require(actionId) {
|
|
82
|
+
const binding = this.bindings.get(actionId);
|
|
83
|
+
if (!binding) throw new Error(`No saga workflow binding registered for ${actionId}`);
|
|
84
|
+
return { ...binding };
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
function createTemporalSagaDispatcher(client, registry) {
|
|
88
|
+
return {
|
|
89
|
+
dispatch: async (input) => {
|
|
90
|
+
const binding = registry.require(input.actionId);
|
|
91
|
+
const handle = await startGovernedWorkflow(client, {
|
|
92
|
+
workflowType: binding.workflowType,
|
|
93
|
+
workflowId: input.workflowId,
|
|
94
|
+
taskQueue: binding.taskQueue,
|
|
95
|
+
args: [{
|
|
96
|
+
parentInvocationId: input.actionInvocationId,
|
|
97
|
+
actionId: input.actionId,
|
|
98
|
+
tenantId: input.tenantId,
|
|
99
|
+
spaceId: input.spaceId
|
|
100
|
+
}]
|
|
101
|
+
});
|
|
102
|
+
const runId = runIdFromHandle(handle);
|
|
103
|
+
return { workflowId: input.workflowId, ...runId ? { runId } : {} };
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function runIdFromHandle(handle) {
|
|
108
|
+
if (!handle || typeof handle !== "object") return void 0;
|
|
109
|
+
const candidate = handle.firstExecutionRunId;
|
|
110
|
+
return typeof candidate === "string" && candidate ? candidate : void 0;
|
|
111
|
+
}
|
|
112
|
+
function createSagaParentActivities(lifecycle) {
|
|
113
|
+
return {
|
|
114
|
+
recordSagaProgress: (input) => lifecycle.recordProgress({
|
|
115
|
+
...input,
|
|
116
|
+
lifecycleId: input.lifecycleId ?? sagaParentActivityIdentity(
|
|
117
|
+
input.parentInvocationId,
|
|
118
|
+
"progress",
|
|
119
|
+
`${input.progress.stepId}:${input.progress.index}`
|
|
120
|
+
).idempotencyKey
|
|
121
|
+
}),
|
|
122
|
+
parkSagaApproval: (input) => lifecycle.parkApproval({
|
|
123
|
+
...input,
|
|
124
|
+
lifecycleId: input.lifecycleId ?? sagaParentActivityIdentity(
|
|
125
|
+
input.parentInvocationId,
|
|
126
|
+
"approval",
|
|
127
|
+
input.approvalId
|
|
128
|
+
).idempotencyKey
|
|
129
|
+
}),
|
|
130
|
+
cancelSagaParent: (input) => lifecycle.cancel({
|
|
131
|
+
...input,
|
|
132
|
+
lifecycleId: input.lifecycleId ?? sagaParentActivityIdentity(
|
|
133
|
+
input.parentInvocationId,
|
|
134
|
+
"cancel"
|
|
135
|
+
).idempotencyKey
|
|
136
|
+
}),
|
|
137
|
+
completeSagaParent: (input) => lifecycle.complete({
|
|
138
|
+
...input,
|
|
139
|
+
lifecycleId: input.lifecycleId ?? sagaParentActivityIdentity(
|
|
140
|
+
input.parentInvocationId,
|
|
141
|
+
"complete"
|
|
142
|
+
).idempotencyKey
|
|
143
|
+
}),
|
|
144
|
+
failSagaParent: (input) => lifecycle.fail({
|
|
145
|
+
...input,
|
|
146
|
+
lifecycleId: input.lifecycleId ?? sagaParentActivityIdentity(
|
|
147
|
+
input.parentInvocationId,
|
|
148
|
+
"fail"
|
|
149
|
+
).idempotencyKey
|
|
150
|
+
})
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
function sagaParentActivityIdentity(workflowId, activity, discriminator) {
|
|
154
|
+
return temporalInvocationIdentity({
|
|
155
|
+
workflowId,
|
|
156
|
+
stepId: `parent:${activity}${discriminator ? `:${discriminator}` : ""}`
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
function sagaChildCausation(workflowId, parentInvocationId, stepId) {
|
|
160
|
+
return { ...temporalInvocationIdentity({ workflowId, stepId }), causationId: parentInvocationId };
|
|
161
|
+
}
|
|
162
|
+
var SAGA_SIGNALS = {
|
|
163
|
+
proceed: "saga.proceed",
|
|
164
|
+
cancel: "saga.cancel",
|
|
165
|
+
approval: "saga.approval"
|
|
166
|
+
};
|
|
61
167
|
function temporalWorkflowStartClient(client) {
|
|
62
168
|
return {
|
|
63
169
|
start: ({ workflowType, workflowId, taskQueue, args }) => client.start(workflowType, { workflowId, taskQueue, args, workflowIdConflictPolicy: "USE_EXISTING" })
|
|
@@ -90,6 +196,7 @@ function assertDistinctSteps(steps) {
|
|
|
90
196
|
for (const step of steps) {
|
|
91
197
|
if (!step.stepId.trim()) throw new Error("Every saga step requires a stepId.");
|
|
92
198
|
if (step.stepId.startsWith("compensate:")) throw new Error(`Saga step "${step.stepId}" must not use the reserved "compensate:" prefix.`);
|
|
199
|
+
if (step.stepId.startsWith("parent:")) throw new Error(`Saga step "${step.stepId}" must not use the reserved "parent:" prefix.`);
|
|
93
200
|
if (seen.has(step.stepId)) throw new Error(`Saga declares duplicate step "${step.stepId}"; step identity must be unique within a workflow.`);
|
|
94
201
|
seen.add(step.stepId);
|
|
95
202
|
}
|
|
@@ -164,12 +271,18 @@ async function runCompensatedSaga(activity, input) {
|
|
|
164
271
|
}
|
|
165
272
|
// Annotate the CommonJS export names for ESM import in node:
|
|
166
273
|
0 && (module.exports = {
|
|
274
|
+
SAGA_SIGNALS,
|
|
167
275
|
SagaCompensationError,
|
|
276
|
+
SagaWorkflowRegistry,
|
|
168
277
|
assertWorkerVersionEvidence,
|
|
169
278
|
compensationInvocationIdentity,
|
|
170
279
|
createGovernedActionActivities,
|
|
280
|
+
createSagaParentActivities,
|
|
171
281
|
createSagaSignalDispatcher,
|
|
282
|
+
createTemporalSagaDispatcher,
|
|
172
283
|
runCompensatedSaga,
|
|
284
|
+
sagaChildCausation,
|
|
285
|
+
sagaParentActivityIdentity,
|
|
173
286
|
startGovernedWorkflow,
|
|
174
287
|
temporalInvocationIdentity,
|
|
175
288
|
temporalWorkflowStartClient
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../index.ts"],"sourcesContent":["import type { ActorType } from \"@fabricorg/platform\";\nimport type { WorkflowClient } from \"@temporalio/client\";\n\n/** Serializable input passed from deterministic workflows to a Platform activity. */\nexport interface GovernedActionActivityInput {\n\tactionId: string;\n\tparameters: Record<string, unknown>;\n\ttenantId: string;\n\tspaceId: string;\n\tactorId: string;\n\tactorType: ActorType;\n\tworkflowId: string;\n\trunId: string;\n\tstepId: string;\n\tparentInvocationId?: string;\n}\n\nexport interface GovernedActionInvoker {\n\tinvoke(input: {\n\t\tactionId: string; parameters: Record<string, unknown>;\n\t\ttenantId: string; spaceId: string; actorId: string; actorType: ActorType;\n\t\tidempotencyKey: string; correlationId: string; causationId?: string;\n\t\texecutionEvidence: { temporalWorkflowId: string; temporalRunId: string; temporalStepId: string };\n\t}): Promise<unknown>;\n}\n\nexport function temporalInvocationIdentity(input: Pick<GovernedActionActivityInput, \"workflowId\" | \"stepId\">) {\n\treturn { idempotencyKey: `temporal:${input.workflowId}:${input.stepId}`, correlationId: `temporal:${input.workflowId}` };\n}\n\n/** Activity implementation. It always enters through the governed Host; handlers are never called directly. */\nexport function createGovernedActionActivities(invoker: GovernedActionInvoker) {\n\treturn {\n\t\tinvokeGovernedAction: async (input: GovernedActionActivityInput) => {\n\t\t\tconst identity = temporalInvocationIdentity(input);\n\t\t\treturn invoker.invoke({\n\t\t\t\tactionId: input.actionId, parameters: input.parameters,\n\t\t\t\ttenantId: input.tenantId, spaceId: input.spaceId,\n\t\t\t\tactorId: input.actorId, actorType: input.actorType,\n\t\t\t\t...identity,\n\t\t\t\t...(input.parentInvocationId ? { causationId: input.parentInvocationId } : {}),\n\t\t\t\texecutionEvidence: { temporalWorkflowId: input.workflowId, temporalRunId: input.runId, temporalStepId: input.stepId },\n\t\t\t});\n\t\t},\n\t};\n}\n\nexport interface SagaSignalEnvelope<T = unknown> { sagaId: string; signal: string; payload: T; actorId: string; receivedAt: string }\nexport interface TemporalSignalClient { signal(workflowId: string, signalName: string, payload: unknown): Promise<void> }\nexport function createSagaSignalDispatcher(client: TemporalSignalClient) {\n\treturn (workflowId: string, envelope: SagaSignalEnvelope) => client.signal(workflowId, envelope.signal, envelope);\n}\n\nexport interface WorkflowStartClient<THandle = unknown> {\n\tstart(input: { workflowType: string; workflowId: string; taskQueue: string; args: unknown[]; idConflictPolicy: \"use_existing\" }): Promise<THandle>;\n}\nexport function startGovernedWorkflow<THandle>(client: WorkflowStartClient<THandle>, input: { workflowType: string; workflowId: string; taskQueue: string; args: unknown[] }) {\n\treturn client.start({ ...input, idConflictPolicy: \"use_existing\" });\n}\n\n/** Adapts the official Temporal TypeScript `WorkflowClient` to the narrow testable binding seam. */\nexport function temporalWorkflowStartClient(client: WorkflowClient): WorkflowStartClient {\n\treturn {\n\t\tstart: ({ workflowType, workflowId, taskQueue, args }) =>\n\t\t\tclient.start(workflowType, { workflowId, taskQueue, args, workflowIdConflictPolicy: \"USE_EXISTING\" }),\n\t};\n}\n\nexport interface WorkerVersionEvidence { buildId: string; deploymentName: string; rampPercent?: number }\nexport function assertWorkerVersionEvidence(value: WorkerVersionEvidence): void {\n\tif (!value.buildId.trim() || !value.deploymentName.trim()) throw new Error(\"Temporal worker buildId and deploymentName are required for rollover evidence.\");\n}\n\n// ── Declared compensation ───────────────────────────────────────────────────\n\nexport interface CompensatedStep {\n\t/** Workflow-local step identity. Must match the compensation declared in the module manifest. */\n\tstepId: string;\n\tactionId: string;\n\tparameters: Record<string, unknown>;\n\t/**\n\t * Governed action that reverses this step's effect. Omit for a step with\n\t * nothing to undo; a step whose effect is externally visible should declare one.\n\t */\n\tcompensateWith?: { actionId: string; parameters?: Record<string, unknown> };\n}\n\nexport interface CompensatedSagaInput {\n\tworkflowId: string;\n\trunId: string;\n\tsteps: CompensatedStep[];\n\ttenantId: string;\n\tspaceId: string;\n\tactorId: string;\n\tactorType: ActorType;\n\tparentInvocationId?: string;\n\t/** When supplied, every step's reversal must match what the manifest declares. */\n\tworkflow?: DeclaredWorkflow;\n}\n\nexport interface CompensationRecord {\n\tstepId: string;\n\tactionId: string;\n\t/** Present when the reversal itself failed; the saga surfaces it rather than swallowing it. */\n\terror?: string;\n}\n\nexport interface CompensatedSagaResult {\n\tcompleted: string[];\n\tcompensated: CompensationRecord[];\n\tfailedStep?: { stepId: string; error: string };\n}\n\n/** Reversals are governed invocations too, so they carry their own stable identity. */\nexport function compensationInvocationIdentity(input: { workflowId: string; stepId: string }) {\n\treturn temporalInvocationIdentity({ workflowId: input.workflowId, stepId: `compensate:${input.stepId}` });\n}\n\nexport class SagaCompensationError extends Error {\n\toverride readonly name = \"SagaCompensationError\";\n\tconstructor(readonly result: CompensatedSagaResult) {\n\t\tsuper(`Saga step \"${result.failedStep?.stepId}\" failed: ${result.failedStep?.error}`);\n\t}\n}\n\ntype GovernedActivity = ReturnType<typeof createGovernedActionActivities>[\"invokeGovernedAction\"];\n\n/**\n * The governed Host resolves for outcomes that are not success — a blocked policy, a\n * failed validation, a reconciliation, an approval park. Treating a resolved promise\n * as a completed step would silently skip the unwind, so the status is inspected.\n */\nconst SUCCESSFUL_STATUS = \"completed\";\n\nfunction stepFailure(result: unknown, label: string): string | undefined {\n\tif (!result || typeof result !== \"object\") return undefined;\n\tconst status = (result as { status?: unknown }).status;\n\tif (typeof status !== \"string\" || status === SUCCESSFUL_STATUS) return undefined;\n\tconst error = (result as { error?: unknown }).error;\n\treturn `${label} resolved with status \"${status}\"${typeof error === \"string\" && error ? `: ${error}` : \"\"}`;\n}\n\nfunction assertDistinctSteps(steps: CompensatedStep[]): void {\n\tconst seen = new Set<string>();\n\tfor (const step of steps) {\n\t\tif (!step.stepId.trim()) throw new Error(\"Every saga step requires a stepId.\");\n\t\t// A forward step called \"compensate:x\" would share an identity with the reversal of \"x\".\n\t\tif (step.stepId.startsWith(\"compensate:\")) throw new Error(`Saga step \"${step.stepId}\" must not use the reserved \"compensate:\" prefix.`);\n\t\tif (seen.has(step.stepId)) throw new Error(`Saga declares duplicate step \"${step.stepId}\"; step identity must be unique within a workflow.`);\n\t\tseen.add(step.stepId);\n\t}\n}\n\n/** The manifest declaration this saga is expected to honour, when one is supplied. */\nexport interface DeclaredWorkflow {\n\tname: string;\n\tcompensations?: Array<{ step: string; actionId: string }>;\n}\n\nfunction assertMatchesDeclaration(steps: CompensatedStep[], workflow: DeclaredWorkflow): void {\n\tconst declared = new Map((workflow.compensations ?? []).map((entry) => [entry.step, entry.actionId]));\n\tfor (const step of steps) {\n\t\tconst expected = declared.get(step.stepId);\n\t\tif (expected === undefined) {\n\t\t\tif (step.compensateWith) throw new Error(`Workflow \"${workflow.name}\" step \"${step.stepId}\" supplies a reversal the manifest does not declare.`);\n\t\t\tcontinue;\n\t\t}\n\t\tif (!step.compensateWith) throw new Error(`Workflow \"${workflow.name}\" step \"${step.stepId}\" omits the reversal \"${expected}\" its manifest declares.`);\n\t\tif (step.compensateWith.actionId !== expected) {\n\t\t\tthrow new Error(`Workflow \"${workflow.name}\" step \"${step.stepId}\" reverses with \"${step.compensateWith.actionId}\" but its manifest declares \"${expected}\".`);\n\t\t}\n\t}\n\tfor (const [step, actionId] of declared) {\n\t\tif (!steps.some((candidate) => candidate.stepId === step)) {\n\t\t\tthrow new Error(`Workflow \"${workflow.name}\" declares a reversal \"${actionId}\" for step \"${step}\", which this saga never runs.`);\n\t\t}\n\t}\n}\n\n/**\n * Runs steps in order, accumulating reversals as each succeeds. On failure the\n * accumulated reversals run newest-first, so an unwind never leaves an orphan.\n * Every forward step and every reversal enters through the governed Host with a\n * deterministic idempotency key, so a retried or replayed unwind is not double-applied.\n */\nexport async function runCompensatedSaga(\n\tactivity: GovernedActivity,\n\tinput: CompensatedSagaInput,\n): Promise<CompensatedSagaResult> {\n\tconst scope = {\n\t\ttenantId: input.tenantId,\n\t\tspaceId: input.spaceId,\n\t\tactorId: input.actorId,\n\t\tactorType: input.actorType,\n\t\tworkflowId: input.workflowId,\n\t\trunId: input.runId,\n\t\t...(input.parentInvocationId ? { parentInvocationId: input.parentInvocationId } : {}),\n\t};\n\tassertDistinctSteps(input.steps);\n\tif (input.workflow) assertMatchesDeclaration(input.steps, input.workflow);\n\n\tconst completed: string[] = [];\n\tconst reversals: CompensatedStep[] = [];\n\n\tconst unwind = async (failedStep: { stepId: string; error: string }): Promise<never> => {\n\t\tconst compensated: CompensationRecord[] = [];\n\t\t// Newest-first: the last effect applied is the first one withdrawn.\n\t\tfor (const applied of [...reversals].reverse()) {\n\t\t\tconst reversal = applied.compensateWith!;\n\t\t\tconst record: CompensationRecord = { stepId: applied.stepId, actionId: reversal.actionId };\n\t\t\ttry {\n\t\t\t\tconst result = await activity({\n\t\t\t\t\t...scope,\n\t\t\t\t\tactionId: reversal.actionId,\n\t\t\t\t\tparameters: reversal.parameters ?? applied.parameters,\n\t\t\t\t\tstepId: `compensate:${applied.stepId}`,\n\t\t\t\t});\n\t\t\t\tconst refused = stepFailure(result, `reversal \"${reversal.actionId}\"`);\n\t\t\t\tif (refused) record.error = refused;\n\t\t\t} catch (compensationError) {\n\t\t\t\trecord.error = compensationError instanceof Error ? compensationError.message : String(compensationError);\n\t\t\t}\n\t\t\tcompensated.push(record);\n\t\t}\n\t\tthrow new SagaCompensationError({ completed, compensated, failedStep });\n\t};\n\n\tfor (const step of input.steps) {\n\t\tlet result: unknown;\n\t\ttry {\n\t\t\tresult = await activity({ ...scope, actionId: step.actionId, parameters: step.parameters, stepId: step.stepId });\n\t\t} catch (error) {\n\t\t\treturn await unwind({ stepId: step.stepId, error: error instanceof Error ? error.message : String(error) });\n\t\t}\n\t\tconst refused = stepFailure(result, `step \"${step.stepId}\"`);\n\t\tif (refused) return await unwind({ stepId: step.stepId, error: refused });\n\t\tcompleted.push(step.stepId);\n\t\tif (step.compensateWith) reversals.push(step);\n\t}\n\n\treturn { completed, compensated: [] };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../index.ts"],"sourcesContent":["import type { ActorType } from \"@fabricorg/platform\";\nimport type { WorkflowClient } from \"@temporalio/client\";\n\n/** Serializable input passed from deterministic workflows to a Platform activity. */\nexport interface GovernedActionActivityInput {\n\tactionId: string;\n\tparameters: Record<string, unknown>;\n\ttenantId: string;\n\tspaceId: string;\n\tactorId: string;\n\tactorType: ActorType;\n\tworkflowId: string;\n\trunId: string;\n\tstepId: string;\n\tparentInvocationId?: string;\n}\n\nexport interface GovernedActionInvoker {\n\tinvoke(input: {\n\t\tactionId: string; parameters: Record<string, unknown>;\n\t\ttenantId: string; spaceId: string; actorId: string; actorType: ActorType;\n\t\tidempotencyKey: string; correlationId: string; causationId?: string;\n\t\texecutionEvidence: { temporalWorkflowId: string; temporalRunId: string; temporalStepId: string };\n\t}): Promise<unknown>;\n}\n\nexport function temporalInvocationIdentity(input: Pick<GovernedActionActivityInput, \"workflowId\" | \"stepId\">) {\n\treturn { idempotencyKey: `temporal:${input.workflowId}:${input.stepId}`, correlationId: `temporal:${input.workflowId}` };\n}\n\n/** Activity implementation. It always enters through the governed Host; handlers are never called directly. */\nexport function createGovernedActionActivities(invoker: GovernedActionInvoker) {\n\treturn {\n\t\tinvokeGovernedAction: async (input: GovernedActionActivityInput) => {\n\t\t\tconst identity = temporalInvocationIdentity(input);\n\t\t\treturn invoker.invoke({\n\t\t\t\tactionId: input.actionId, parameters: input.parameters,\n\t\t\t\ttenantId: input.tenantId, spaceId: input.spaceId,\n\t\t\t\tactorId: input.actorId, actorType: input.actorType,\n\t\t\t\t...identity,\n\t\t\t\t...(input.parentInvocationId ? { causationId: input.parentInvocationId } : {}),\n\t\t\t\texecutionEvidence: { temporalWorkflowId: input.workflowId, temporalRunId: input.runId, temporalStepId: input.stepId },\n\t\t\t});\n\t\t},\n\t};\n}\n\nexport interface SagaSignalEnvelope<T = unknown> { sagaId: string; signal: string; payload: T; actorId: string; receivedAt: string }\nexport interface TemporalSignalClient { signal(workflowId: string, signalName: string, payload: unknown): Promise<void> }\nexport function createSagaSignalDispatcher(client: TemporalSignalClient) {\n\treturn (workflowId: string, envelope: SagaSignalEnvelope) => client.signal(workflowId, envelope.signal, envelope);\n}\n\nexport interface WorkflowStartClient<THandle = unknown> {\n\tstart(input: { workflowType: string; workflowId: string; taskQueue: string; args: unknown[]; idConflictPolicy: \"use_existing\" }): Promise<THandle>;\n}\nexport function startGovernedWorkflow<THandle>(client: WorkflowStartClient<THandle>, input: { workflowType: string; workflowId: string; taskQueue: string; args: unknown[] }) {\n\treturn client.start({ ...input, idConflictPolicy: \"use_existing\" });\n}\n\nexport interface SagaWorkflowBinding {\n\tactionId: `${string}.${string}`;\n\tworkflowType: string;\n\ttaskQueue: string;\n}\n\n/** Instance-scoped registry; registration order never changes resolution. */\nexport class SagaWorkflowRegistry {\n\tprivate readonly bindings = new Map<string, SagaWorkflowBinding>();\n\n\tconstructor(bindings: readonly SagaWorkflowBinding[] = []) {\n\t\tfor (const binding of bindings) this.register(binding);\n\t}\n\n\tregister(binding: SagaWorkflowBinding): void {\n\t\tif (this.bindings.has(binding.actionId)) {\n\t\t\tthrow new Error(`Saga workflow binding already registered: ${binding.actionId}`);\n\t\t}\n\t\tif (!binding.workflowType.trim() || !binding.taskQueue.trim()) {\n\t\t\tthrow new Error(`Saga workflow binding ${binding.actionId} requires workflowType and taskQueue`);\n\t\t}\n\t\tthis.bindings.set(binding.actionId, { ...binding });\n\t}\n\n\trequire(actionId: string): SagaWorkflowBinding {\n\t\tconst binding = this.bindings.get(actionId);\n\t\tif (!binding) throw new Error(`No saga workflow binding registered for ${actionId}`);\n\t\treturn { ...binding };\n\t}\n}\n\nexport interface TemporalSagaDispatchInput {\n\tactionInvocationId: string;\n\tactionId: `${string}.${string}`;\n\ttenantId: string;\n\tspaceId: string;\n\tworkflowId: string;\n}\n\n/** Starts the registered parent workflow with an invocation-only durable identity. */\nexport function createTemporalSagaDispatcher<THandle>(\n\tclient: WorkflowStartClient<THandle>,\n\tregistry: SagaWorkflowRegistry,\n) {\n\treturn {\n\t\tdispatch: async (input: TemporalSagaDispatchInput) => {\n\t\t\tconst binding = registry.require(input.actionId);\n\t\t\tconst handle = await startGovernedWorkflow(client, {\n\t\t\t\tworkflowType: binding.workflowType,\n\t\t\t\tworkflowId: input.workflowId,\n\t\t\t\ttaskQueue: binding.taskQueue,\n\t\t\t\targs: [{\n\t\t\t\t\tparentInvocationId: input.actionInvocationId,\n\t\t\t\t\tactionId: input.actionId,\n\t\t\t\t\ttenantId: input.tenantId,\n\t\t\t\t\tspaceId: input.spaceId,\n\t\t\t\t}],\n\t\t\t});\n\t\t\tconst runId = runIdFromHandle(handle);\n\t\t\treturn { workflowId: input.workflowId, ...(runId ? { runId } : {}) };\n\t\t},\n\t};\n}\n\nfunction runIdFromHandle(handle: unknown): string | undefined {\n\tif (!handle || typeof handle !== \"object\") return undefined;\n\tconst candidate = (handle as { firstExecutionRunId?: unknown }).firstExecutionRunId;\n\treturn typeof candidate === \"string\" && candidate ? candidate : undefined;\n}\n\n/** Durable record of a single step completing within a saga parent. */\nexport interface SagaParentProgressRecord {\n\t/** The step that just completed. */\n\tstepId: string;\n\t/** Zero-based position of this step in the declared sequence. */\n\tindex: number;\n\t/** Total steps declared for this saga. */\n\ttotal: number;\n\t/** ISO-8601 timestamp the progress was recorded. */\n\tcompletedAt: string;\n}\n\nexport interface SagaParentProgressInput {\n\tparentInvocationId: string;\n\ttenantId: string;\n\tspaceId: string;\n\t/** Stable identity shared by every retry of this lifecycle transition. */\n\tlifecycleId?: string;\n\tprogress: SagaParentProgressRecord;\n}\n\nexport interface SagaParentApprovalInput {\n\tparentInvocationId: string;\n\ttenantId: string;\n\tspaceId: string;\n\t/** Stable identity shared by every retry of this lifecycle transition. */\n\tlifecycleId?: string;\n\t/** The step at which the saga paused for human approval. */\n\tstepId: string;\n\t/** Stable identifier for the approval gate, so a later signal can resume it. */\n\tapprovalId: string;\n\treason: string;\n}\n\nexport interface SagaParentCancellationInput {\n\tparentInvocationId: string;\n\ttenantId: string;\n\tspaceId: string;\n\t/** Stable identity shared by every retry of this lifecycle transition. */\n\tlifecycleId?: string;\n\treason: string;\n\t/** Actor requesting the cancellation, for audit attribution. */\n\tactorId: string;\n}\n\nexport interface SagaParentLifecycle {\n\t/** Record durable progress as each step completes. */\n\trecordProgress(input: SagaParentProgressInput): Promise<void>;\n\t/** Park the parent for human approval at a specific step. */\n\tparkApproval(input: SagaParentApprovalInput): Promise<void>;\n\t/** Cancel the parent saga with an attributable reason. */\n\tcancel(input: SagaParentCancellationInput): Promise<void>;\n\t/** Complete the parent saga with its final result. */\n\tcomplete(input: { parentInvocationId: string; tenantId: string; spaceId: string; lifecycleId?: string; result: Record<string, unknown> }): Promise<void>;\n\t/** Fail the parent saga, surfacing any compensation records. */\n\tfail(input: { parentInvocationId: string; tenantId: string; spaceId: string; lifecycleId?: string; error: string; compensation: CompensationRecord[] }): Promise<void>;\n}\n\n/** Parent lifecycle remains explicit durable activities, never workflow-local state. */\nexport function createSagaParentActivities(lifecycle: SagaParentLifecycle) {\n\treturn {\n\t\trecordSagaProgress: (input: SagaParentProgressInput) => lifecycle.recordProgress({\n\t\t\t...input,\n\t\t\tlifecycleId: input.lifecycleId ?? sagaParentActivityIdentity(\n\t\t\t\tinput.parentInvocationId,\n\t\t\t\t\"progress\",\n\t\t\t\t`${input.progress.stepId}:${input.progress.index}`,\n\t\t\t).idempotencyKey,\n\t\t}),\n\t\tparkSagaApproval: (input: SagaParentApprovalInput) => lifecycle.parkApproval({\n\t\t\t...input,\n\t\t\tlifecycleId: input.lifecycleId ?? sagaParentActivityIdentity(\n\t\t\t\tinput.parentInvocationId,\n\t\t\t\t\"approval\",\n\t\t\t\tinput.approvalId,\n\t\t\t).idempotencyKey,\n\t\t}),\n\t\tcancelSagaParent: (input: SagaParentCancellationInput) => lifecycle.cancel({\n\t\t\t...input,\n\t\t\tlifecycleId: input.lifecycleId ?? sagaParentActivityIdentity(\n\t\t\t\tinput.parentInvocationId,\n\t\t\t\t\"cancel\",\n\t\t\t).idempotencyKey,\n\t\t}),\n\t\tcompleteSagaParent: (input: Parameters<SagaParentLifecycle[\"complete\"]>[0]) =>\n\t\t\tlifecycle.complete({\n\t\t\t\t...input,\n\t\t\t\tlifecycleId: input.lifecycleId ?? sagaParentActivityIdentity(\n\t\t\t\t\tinput.parentInvocationId,\n\t\t\t\t\t\"complete\",\n\t\t\t\t).idempotencyKey,\n\t\t\t}),\n\t\tfailSagaParent: (input: Parameters<SagaParentLifecycle[\"fail\"]>[0]) => lifecycle.fail({\n\t\t\t...input,\n\t\t\tlifecycleId: input.lifecycleId ?? sagaParentActivityIdentity(\n\t\t\t\tinput.parentInvocationId,\n\t\t\t\t\"fail\",\n\t\t\t).idempotencyKey,\n\t\t}),\n\t};\n}\n\n// ── Stable parent and child causation identities ───────────────────────────\n\n/**\n * Stable identity for a saga parent's own lifecycle activity (progress,\n * cancellation, approval parking, completion, failure). The activity stepId\n * is namespaced under `parent:` so it can never collide with a forward step or\n * a `compensate:` reversal.\n */\nexport function sagaParentActivityIdentity(\n\tworkflowId: string,\n\tactivity: SagaParentActivityKind,\n\tdiscriminator?: string,\n) {\n\treturn temporalInvocationIdentity({\n\t\tworkflowId,\n\t\tstepId: `parent:${activity}${discriminator ? `:${discriminator}` : \"\"}`,\n\t});\n}\n\nexport type SagaParentActivityKind = \"progress\" | \"approval\" | \"cancel\" | \"complete\" | \"fail\";\n\n/**\n * Stable causation identity linking a child step to its parent invocation.\n * The parent invocation ID becomes the causation ID; the step keeps its own\n * deterministic idempotency key and shares the workflow correlation ID.\n */\nexport function sagaChildCausation(workflowId: string, parentInvocationId: string, stepId: string) {\n\treturn { ...temporalInvocationIdentity({ workflowId, stepId }), causationId: parentInvocationId };\n}\n\n// ── Saga signal names ───────────────────────────────────────────────────────\n\n/** Stable signal names a saga workflow may register handlers for. */\nexport const SAGA_SIGNALS = {\n\tproceed: \"saga.proceed\",\n\tcancel: \"saga.cancel\",\n\tapproval: \"saga.approval\",\n} as const;\n\n/** Adapts the official Temporal TypeScript `WorkflowClient` to the narrow testable binding seam. */\nexport function temporalWorkflowStartClient(client: WorkflowClient): WorkflowStartClient {\n\treturn {\n\t\tstart: ({ workflowType, workflowId, taskQueue, args }) =>\n\t\t\tclient.start(workflowType, { workflowId, taskQueue, args, workflowIdConflictPolicy: \"USE_EXISTING\" }),\n\t};\n}\n\nexport interface WorkerVersionEvidence { buildId: string; deploymentName: string; rampPercent?: number }\nexport function assertWorkerVersionEvidence(value: WorkerVersionEvidence): void {\n\tif (!value.buildId.trim() || !value.deploymentName.trim()) throw new Error(\"Temporal worker buildId and deploymentName are required for rollover evidence.\");\n}\n\n// ── Declared compensation ───────────────────────────────────────────────────\n\nexport interface CompensatedStep {\n\t/** Workflow-local step identity. Must match the compensation declared in the module manifest. */\n\tstepId: string;\n\tactionId: string;\n\tparameters: Record<string, unknown>;\n\t/**\n\t * Governed action that reverses this step's effect. Omit for a step with\n\t * nothing to undo; a step whose effect is externally visible should declare one.\n\t */\n\tcompensateWith?: { actionId: string; parameters?: Record<string, unknown> };\n}\n\nexport interface CompensatedSagaInput {\n\tworkflowId: string;\n\trunId: string;\n\tsteps: CompensatedStep[];\n\ttenantId: string;\n\tspaceId: string;\n\tactorId: string;\n\tactorType: ActorType;\n\tparentInvocationId?: string;\n\t/** When supplied, every step's reversal must match what the manifest declares. */\n\tworkflow?: DeclaredWorkflow;\n}\n\nexport interface CompensationRecord {\n\tstepId: string;\n\tactionId: string;\n\t/** Present when the reversal itself failed; the saga surfaces it rather than swallowing it. */\n\terror?: string;\n}\n\nexport interface CompensatedSagaResult {\n\tcompleted: string[];\n\tcompensated: CompensationRecord[];\n\tfailedStep?: { stepId: string; error: string };\n}\n\n/** Reversals are governed invocations too, so they carry their own stable identity. */\nexport function compensationInvocationIdentity(input: { workflowId: string; stepId: string }) {\n\treturn temporalInvocationIdentity({ workflowId: input.workflowId, stepId: `compensate:${input.stepId}` });\n}\n\nexport class SagaCompensationError extends Error {\n\toverride readonly name = \"SagaCompensationError\";\n\tconstructor(readonly result: CompensatedSagaResult) {\n\t\tsuper(`Saga step \"${result.failedStep?.stepId}\" failed: ${result.failedStep?.error}`);\n\t}\n}\n\ntype GovernedActivity = ReturnType<typeof createGovernedActionActivities>[\"invokeGovernedAction\"];\n\n/**\n * The governed Host resolves for outcomes that are not success — a blocked policy, a\n * failed validation, a reconciliation, an approval park. Treating a resolved promise\n * as a completed step would silently skip the unwind, so the status is inspected.\n */\nconst SUCCESSFUL_STATUS = \"completed\";\n\nfunction stepFailure(result: unknown, label: string): string | undefined {\n\tif (!result || typeof result !== \"object\") return undefined;\n\tconst status = (result as { status?: unknown }).status;\n\tif (typeof status !== \"string\" || status === SUCCESSFUL_STATUS) return undefined;\n\tconst error = (result as { error?: unknown }).error;\n\treturn `${label} resolved with status \"${status}\"${typeof error === \"string\" && error ? `: ${error}` : \"\"}`;\n}\n\nfunction assertDistinctSteps(steps: CompensatedStep[]): void {\n\tconst seen = new Set<string>();\n\tfor (const step of steps) {\n\t\tif (!step.stepId.trim()) throw new Error(\"Every saga step requires a stepId.\");\n\t\t// A forward step called \"compensate:x\" would share an identity with the reversal of \"x\".\n\t\tif (step.stepId.startsWith(\"compensate:\")) throw new Error(`Saga step \"${step.stepId}\" must not use the reserved \"compensate:\" prefix.`);\n\t\t// A forward step called \"parent:x\" would share an identity with a parent lifecycle activity.\n\t\tif (step.stepId.startsWith(\"parent:\")) throw new Error(`Saga step \"${step.stepId}\" must not use the reserved \"parent:\" prefix.`);\n\t\tif (seen.has(step.stepId)) throw new Error(`Saga declares duplicate step \"${step.stepId}\"; step identity must be unique within a workflow.`);\n\t\tseen.add(step.stepId);\n\t}\n}\n\n/** The manifest declaration this saga is expected to honour, when one is supplied. */\nexport interface DeclaredWorkflow {\n\tname: string;\n\tcompensations?: Array<{ step: string; actionId: string }>;\n}\n\nfunction assertMatchesDeclaration(steps: CompensatedStep[], workflow: DeclaredWorkflow): void {\n\tconst declared = new Map((workflow.compensations ?? []).map((entry) => [entry.step, entry.actionId]));\n\tfor (const step of steps) {\n\t\tconst expected = declared.get(step.stepId);\n\t\tif (expected === undefined) {\n\t\t\tif (step.compensateWith) throw new Error(`Workflow \"${workflow.name}\" step \"${step.stepId}\" supplies a reversal the manifest does not declare.`);\n\t\t\tcontinue;\n\t\t}\n\t\tif (!step.compensateWith) throw new Error(`Workflow \"${workflow.name}\" step \"${step.stepId}\" omits the reversal \"${expected}\" its manifest declares.`);\n\t\tif (step.compensateWith.actionId !== expected) {\n\t\t\tthrow new Error(`Workflow \"${workflow.name}\" step \"${step.stepId}\" reverses with \"${step.compensateWith.actionId}\" but its manifest declares \"${expected}\".`);\n\t\t}\n\t}\n\tfor (const [step, actionId] of declared) {\n\t\tif (!steps.some((candidate) => candidate.stepId === step)) {\n\t\t\tthrow new Error(`Workflow \"${workflow.name}\" declares a reversal \"${actionId}\" for step \"${step}\", which this saga never runs.`);\n\t\t}\n\t}\n}\n\n/**\n * Runs steps in order, accumulating reversals as each succeeds. On failure the\n * accumulated reversals run newest-first, so an unwind never leaves an orphan.\n * Every forward step and every reversal enters through the governed Host with a\n * deterministic idempotency key, so a retried or replayed unwind is not double-applied.\n */\nexport async function runCompensatedSaga(\n\tactivity: GovernedActivity,\n\tinput: CompensatedSagaInput,\n): Promise<CompensatedSagaResult> {\n\tconst scope = {\n\t\ttenantId: input.tenantId,\n\t\tspaceId: input.spaceId,\n\t\tactorId: input.actorId,\n\t\tactorType: input.actorType,\n\t\tworkflowId: input.workflowId,\n\t\trunId: input.runId,\n\t\t...(input.parentInvocationId ? { parentInvocationId: input.parentInvocationId } : {}),\n\t};\n\tassertDistinctSteps(input.steps);\n\tif (input.workflow) assertMatchesDeclaration(input.steps, input.workflow);\n\n\tconst completed: string[] = [];\n\tconst reversals: CompensatedStep[] = [];\n\n\tconst unwind = async (failedStep: { stepId: string; error: string }): Promise<never> => {\n\t\tconst compensated: CompensationRecord[] = [];\n\t\t// Newest-first: the last effect applied is the first one withdrawn.\n\t\tfor (const applied of [...reversals].reverse()) {\n\t\t\tconst reversal = applied.compensateWith!;\n\t\t\tconst record: CompensationRecord = { stepId: applied.stepId, actionId: reversal.actionId };\n\t\t\ttry {\n\t\t\t\tconst result = await activity({\n\t\t\t\t\t...scope,\n\t\t\t\t\tactionId: reversal.actionId,\n\t\t\t\t\tparameters: reversal.parameters ?? applied.parameters,\n\t\t\t\t\tstepId: `compensate:${applied.stepId}`,\n\t\t\t\t});\n\t\t\t\tconst refused = stepFailure(result, `reversal \"${reversal.actionId}\"`);\n\t\t\t\tif (refused) record.error = refused;\n\t\t\t} catch (compensationError) {\n\t\t\t\trecord.error = compensationError instanceof Error ? compensationError.message : String(compensationError);\n\t\t\t}\n\t\t\tcompensated.push(record);\n\t\t}\n\t\tthrow new SagaCompensationError({ completed, compensated, failedStep });\n\t};\n\n\tfor (const step of input.steps) {\n\t\tlet result: unknown;\n\t\ttry {\n\t\t\tresult = await activity({ ...scope, actionId: step.actionId, parameters: step.parameters, stepId: step.stepId });\n\t\t} catch (error) {\n\t\t\treturn await unwind({ stepId: step.stepId, error: error instanceof Error ? error.message : String(error) });\n\t\t}\n\t\tconst refused = stepFailure(result, `step \"${step.stepId}\"`);\n\t\tif (refused) return await unwind({ stepId: step.stepId, error: refused });\n\t\tcompleted.push(step.stepId);\n\t\tif (step.compensateWith) reversals.push(step);\n\t}\n\n\treturn { completed, compensated: [] };\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
CHANGED
|
@@ -66,6 +66,133 @@ declare function startGovernedWorkflow<THandle>(client: WorkflowStartClient<THan
|
|
|
66
66
|
taskQueue: string;
|
|
67
67
|
args: unknown[];
|
|
68
68
|
}): Promise<THandle>;
|
|
69
|
+
interface SagaWorkflowBinding {
|
|
70
|
+
actionId: `${string}.${string}`;
|
|
71
|
+
workflowType: string;
|
|
72
|
+
taskQueue: string;
|
|
73
|
+
}
|
|
74
|
+
/** Instance-scoped registry; registration order never changes resolution. */
|
|
75
|
+
declare class SagaWorkflowRegistry {
|
|
76
|
+
private readonly bindings;
|
|
77
|
+
constructor(bindings?: readonly SagaWorkflowBinding[]);
|
|
78
|
+
register(binding: SagaWorkflowBinding): void;
|
|
79
|
+
require(actionId: string): SagaWorkflowBinding;
|
|
80
|
+
}
|
|
81
|
+
interface TemporalSagaDispatchInput {
|
|
82
|
+
actionInvocationId: string;
|
|
83
|
+
actionId: `${string}.${string}`;
|
|
84
|
+
tenantId: string;
|
|
85
|
+
spaceId: string;
|
|
86
|
+
workflowId: string;
|
|
87
|
+
}
|
|
88
|
+
/** Starts the registered parent workflow with an invocation-only durable identity. */
|
|
89
|
+
declare function createTemporalSagaDispatcher<THandle>(client: WorkflowStartClient<THandle>, registry: SagaWorkflowRegistry): {
|
|
90
|
+
dispatch: (input: TemporalSagaDispatchInput) => Promise<{
|
|
91
|
+
runId?: string | undefined;
|
|
92
|
+
workflowId: string;
|
|
93
|
+
}>;
|
|
94
|
+
};
|
|
95
|
+
/** Durable record of a single step completing within a saga parent. */
|
|
96
|
+
interface SagaParentProgressRecord {
|
|
97
|
+
/** The step that just completed. */
|
|
98
|
+
stepId: string;
|
|
99
|
+
/** Zero-based position of this step in the declared sequence. */
|
|
100
|
+
index: number;
|
|
101
|
+
/** Total steps declared for this saga. */
|
|
102
|
+
total: number;
|
|
103
|
+
/** ISO-8601 timestamp the progress was recorded. */
|
|
104
|
+
completedAt: string;
|
|
105
|
+
}
|
|
106
|
+
interface SagaParentProgressInput {
|
|
107
|
+
parentInvocationId: string;
|
|
108
|
+
tenantId: string;
|
|
109
|
+
spaceId: string;
|
|
110
|
+
/** Stable identity shared by every retry of this lifecycle transition. */
|
|
111
|
+
lifecycleId?: string;
|
|
112
|
+
progress: SagaParentProgressRecord;
|
|
113
|
+
}
|
|
114
|
+
interface SagaParentApprovalInput {
|
|
115
|
+
parentInvocationId: string;
|
|
116
|
+
tenantId: string;
|
|
117
|
+
spaceId: string;
|
|
118
|
+
/** Stable identity shared by every retry of this lifecycle transition. */
|
|
119
|
+
lifecycleId?: string;
|
|
120
|
+
/** The step at which the saga paused for human approval. */
|
|
121
|
+
stepId: string;
|
|
122
|
+
/** Stable identifier for the approval gate, so a later signal can resume it. */
|
|
123
|
+
approvalId: string;
|
|
124
|
+
reason: string;
|
|
125
|
+
}
|
|
126
|
+
interface SagaParentCancellationInput {
|
|
127
|
+
parentInvocationId: string;
|
|
128
|
+
tenantId: string;
|
|
129
|
+
spaceId: string;
|
|
130
|
+
/** Stable identity shared by every retry of this lifecycle transition. */
|
|
131
|
+
lifecycleId?: string;
|
|
132
|
+
reason: string;
|
|
133
|
+
/** Actor requesting the cancellation, for audit attribution. */
|
|
134
|
+
actorId: string;
|
|
135
|
+
}
|
|
136
|
+
interface SagaParentLifecycle {
|
|
137
|
+
/** Record durable progress as each step completes. */
|
|
138
|
+
recordProgress(input: SagaParentProgressInput): Promise<void>;
|
|
139
|
+
/** Park the parent for human approval at a specific step. */
|
|
140
|
+
parkApproval(input: SagaParentApprovalInput): Promise<void>;
|
|
141
|
+
/** Cancel the parent saga with an attributable reason. */
|
|
142
|
+
cancel(input: SagaParentCancellationInput): Promise<void>;
|
|
143
|
+
/** Complete the parent saga with its final result. */
|
|
144
|
+
complete(input: {
|
|
145
|
+
parentInvocationId: string;
|
|
146
|
+
tenantId: string;
|
|
147
|
+
spaceId: string;
|
|
148
|
+
lifecycleId?: string;
|
|
149
|
+
result: Record<string, unknown>;
|
|
150
|
+
}): Promise<void>;
|
|
151
|
+
/** Fail the parent saga, surfacing any compensation records. */
|
|
152
|
+
fail(input: {
|
|
153
|
+
parentInvocationId: string;
|
|
154
|
+
tenantId: string;
|
|
155
|
+
spaceId: string;
|
|
156
|
+
lifecycleId?: string;
|
|
157
|
+
error: string;
|
|
158
|
+
compensation: CompensationRecord[];
|
|
159
|
+
}): Promise<void>;
|
|
160
|
+
}
|
|
161
|
+
/** Parent lifecycle remains explicit durable activities, never workflow-local state. */
|
|
162
|
+
declare function createSagaParentActivities(lifecycle: SagaParentLifecycle): {
|
|
163
|
+
recordSagaProgress: (input: SagaParentProgressInput) => Promise<void>;
|
|
164
|
+
parkSagaApproval: (input: SagaParentApprovalInput) => Promise<void>;
|
|
165
|
+
cancelSagaParent: (input: SagaParentCancellationInput) => Promise<void>;
|
|
166
|
+
completeSagaParent: (input: Parameters<SagaParentLifecycle["complete"]>[0]) => Promise<void>;
|
|
167
|
+
failSagaParent: (input: Parameters<SagaParentLifecycle["fail"]>[0]) => Promise<void>;
|
|
168
|
+
};
|
|
169
|
+
/**
|
|
170
|
+
* Stable identity for a saga parent's own lifecycle activity (progress,
|
|
171
|
+
* cancellation, approval parking, completion, failure). The activity stepId
|
|
172
|
+
* is namespaced under `parent:` so it can never collide with a forward step or
|
|
173
|
+
* a `compensate:` reversal.
|
|
174
|
+
*/
|
|
175
|
+
declare function sagaParentActivityIdentity(workflowId: string, activity: SagaParentActivityKind, discriminator?: string): {
|
|
176
|
+
idempotencyKey: string;
|
|
177
|
+
correlationId: string;
|
|
178
|
+
};
|
|
179
|
+
type SagaParentActivityKind = "progress" | "approval" | "cancel" | "complete" | "fail";
|
|
180
|
+
/**
|
|
181
|
+
* Stable causation identity linking a child step to its parent invocation.
|
|
182
|
+
* The parent invocation ID becomes the causation ID; the step keeps its own
|
|
183
|
+
* deterministic idempotency key and shares the workflow correlation ID.
|
|
184
|
+
*/
|
|
185
|
+
declare function sagaChildCausation(workflowId: string, parentInvocationId: string, stepId: string): {
|
|
186
|
+
causationId: string;
|
|
187
|
+
idempotencyKey: string;
|
|
188
|
+
correlationId: string;
|
|
189
|
+
};
|
|
190
|
+
/** Stable signal names a saga workflow may register handlers for. */
|
|
191
|
+
declare const SAGA_SIGNALS: {
|
|
192
|
+
readonly proceed: "saga.proceed";
|
|
193
|
+
readonly cancel: "saga.cancel";
|
|
194
|
+
readonly approval: "saga.approval";
|
|
195
|
+
};
|
|
69
196
|
/** Adapts the official Temporal TypeScript `WorkflowClient` to the narrow testable binding seam. */
|
|
70
197
|
declare function temporalWorkflowStartClient(client: WorkflowClient): WorkflowStartClient;
|
|
71
198
|
interface WorkerVersionEvidence {
|
|
@@ -144,4 +271,4 @@ interface DeclaredWorkflow {
|
|
|
144
271
|
*/
|
|
145
272
|
declare function runCompensatedSaga(activity: GovernedActivity, input: CompensatedSagaInput): Promise<CompensatedSagaResult>;
|
|
146
273
|
|
|
147
|
-
export { type CompensatedSagaInput, type CompensatedSagaResult, type CompensatedStep, type CompensationRecord, type DeclaredWorkflow, type GovernedActionActivityInput, type GovernedActionInvoker, SagaCompensationError, type SagaSignalEnvelope, type TemporalSignalClient, type WorkerVersionEvidence, type WorkflowStartClient, assertWorkerVersionEvidence, compensationInvocationIdentity, createGovernedActionActivities, createSagaSignalDispatcher, runCompensatedSaga, startGovernedWorkflow, temporalInvocationIdentity, temporalWorkflowStartClient };
|
|
274
|
+
export { type CompensatedSagaInput, type CompensatedSagaResult, type CompensatedStep, type CompensationRecord, type DeclaredWorkflow, type GovernedActionActivityInput, type GovernedActionInvoker, SAGA_SIGNALS, SagaCompensationError, type SagaParentActivityKind, type SagaParentApprovalInput, type SagaParentCancellationInput, type SagaParentLifecycle, type SagaParentProgressInput, type SagaParentProgressRecord, type SagaSignalEnvelope, type SagaWorkflowBinding, SagaWorkflowRegistry, type TemporalSagaDispatchInput, type TemporalSignalClient, type WorkerVersionEvidence, type WorkflowStartClient, assertWorkerVersionEvidence, compensationInvocationIdentity, createGovernedActionActivities, createSagaParentActivities, createSagaSignalDispatcher, createTemporalSagaDispatcher, runCompensatedSaga, sagaChildCausation, sagaParentActivityIdentity, startGovernedWorkflow, temporalInvocationIdentity, temporalWorkflowStartClient };
|
package/dist/index.d.ts
CHANGED
|
@@ -66,6 +66,133 @@ declare function startGovernedWorkflow<THandle>(client: WorkflowStartClient<THan
|
|
|
66
66
|
taskQueue: string;
|
|
67
67
|
args: unknown[];
|
|
68
68
|
}): Promise<THandle>;
|
|
69
|
+
interface SagaWorkflowBinding {
|
|
70
|
+
actionId: `${string}.${string}`;
|
|
71
|
+
workflowType: string;
|
|
72
|
+
taskQueue: string;
|
|
73
|
+
}
|
|
74
|
+
/** Instance-scoped registry; registration order never changes resolution. */
|
|
75
|
+
declare class SagaWorkflowRegistry {
|
|
76
|
+
private readonly bindings;
|
|
77
|
+
constructor(bindings?: readonly SagaWorkflowBinding[]);
|
|
78
|
+
register(binding: SagaWorkflowBinding): void;
|
|
79
|
+
require(actionId: string): SagaWorkflowBinding;
|
|
80
|
+
}
|
|
81
|
+
interface TemporalSagaDispatchInput {
|
|
82
|
+
actionInvocationId: string;
|
|
83
|
+
actionId: `${string}.${string}`;
|
|
84
|
+
tenantId: string;
|
|
85
|
+
spaceId: string;
|
|
86
|
+
workflowId: string;
|
|
87
|
+
}
|
|
88
|
+
/** Starts the registered parent workflow with an invocation-only durable identity. */
|
|
89
|
+
declare function createTemporalSagaDispatcher<THandle>(client: WorkflowStartClient<THandle>, registry: SagaWorkflowRegistry): {
|
|
90
|
+
dispatch: (input: TemporalSagaDispatchInput) => Promise<{
|
|
91
|
+
runId?: string | undefined;
|
|
92
|
+
workflowId: string;
|
|
93
|
+
}>;
|
|
94
|
+
};
|
|
95
|
+
/** Durable record of a single step completing within a saga parent. */
|
|
96
|
+
interface SagaParentProgressRecord {
|
|
97
|
+
/** The step that just completed. */
|
|
98
|
+
stepId: string;
|
|
99
|
+
/** Zero-based position of this step in the declared sequence. */
|
|
100
|
+
index: number;
|
|
101
|
+
/** Total steps declared for this saga. */
|
|
102
|
+
total: number;
|
|
103
|
+
/** ISO-8601 timestamp the progress was recorded. */
|
|
104
|
+
completedAt: string;
|
|
105
|
+
}
|
|
106
|
+
interface SagaParentProgressInput {
|
|
107
|
+
parentInvocationId: string;
|
|
108
|
+
tenantId: string;
|
|
109
|
+
spaceId: string;
|
|
110
|
+
/** Stable identity shared by every retry of this lifecycle transition. */
|
|
111
|
+
lifecycleId?: string;
|
|
112
|
+
progress: SagaParentProgressRecord;
|
|
113
|
+
}
|
|
114
|
+
interface SagaParentApprovalInput {
|
|
115
|
+
parentInvocationId: string;
|
|
116
|
+
tenantId: string;
|
|
117
|
+
spaceId: string;
|
|
118
|
+
/** Stable identity shared by every retry of this lifecycle transition. */
|
|
119
|
+
lifecycleId?: string;
|
|
120
|
+
/** The step at which the saga paused for human approval. */
|
|
121
|
+
stepId: string;
|
|
122
|
+
/** Stable identifier for the approval gate, so a later signal can resume it. */
|
|
123
|
+
approvalId: string;
|
|
124
|
+
reason: string;
|
|
125
|
+
}
|
|
126
|
+
interface SagaParentCancellationInput {
|
|
127
|
+
parentInvocationId: string;
|
|
128
|
+
tenantId: string;
|
|
129
|
+
spaceId: string;
|
|
130
|
+
/** Stable identity shared by every retry of this lifecycle transition. */
|
|
131
|
+
lifecycleId?: string;
|
|
132
|
+
reason: string;
|
|
133
|
+
/** Actor requesting the cancellation, for audit attribution. */
|
|
134
|
+
actorId: string;
|
|
135
|
+
}
|
|
136
|
+
interface SagaParentLifecycle {
|
|
137
|
+
/** Record durable progress as each step completes. */
|
|
138
|
+
recordProgress(input: SagaParentProgressInput): Promise<void>;
|
|
139
|
+
/** Park the parent for human approval at a specific step. */
|
|
140
|
+
parkApproval(input: SagaParentApprovalInput): Promise<void>;
|
|
141
|
+
/** Cancel the parent saga with an attributable reason. */
|
|
142
|
+
cancel(input: SagaParentCancellationInput): Promise<void>;
|
|
143
|
+
/** Complete the parent saga with its final result. */
|
|
144
|
+
complete(input: {
|
|
145
|
+
parentInvocationId: string;
|
|
146
|
+
tenantId: string;
|
|
147
|
+
spaceId: string;
|
|
148
|
+
lifecycleId?: string;
|
|
149
|
+
result: Record<string, unknown>;
|
|
150
|
+
}): Promise<void>;
|
|
151
|
+
/** Fail the parent saga, surfacing any compensation records. */
|
|
152
|
+
fail(input: {
|
|
153
|
+
parentInvocationId: string;
|
|
154
|
+
tenantId: string;
|
|
155
|
+
spaceId: string;
|
|
156
|
+
lifecycleId?: string;
|
|
157
|
+
error: string;
|
|
158
|
+
compensation: CompensationRecord[];
|
|
159
|
+
}): Promise<void>;
|
|
160
|
+
}
|
|
161
|
+
/** Parent lifecycle remains explicit durable activities, never workflow-local state. */
|
|
162
|
+
declare function createSagaParentActivities(lifecycle: SagaParentLifecycle): {
|
|
163
|
+
recordSagaProgress: (input: SagaParentProgressInput) => Promise<void>;
|
|
164
|
+
parkSagaApproval: (input: SagaParentApprovalInput) => Promise<void>;
|
|
165
|
+
cancelSagaParent: (input: SagaParentCancellationInput) => Promise<void>;
|
|
166
|
+
completeSagaParent: (input: Parameters<SagaParentLifecycle["complete"]>[0]) => Promise<void>;
|
|
167
|
+
failSagaParent: (input: Parameters<SagaParentLifecycle["fail"]>[0]) => Promise<void>;
|
|
168
|
+
};
|
|
169
|
+
/**
|
|
170
|
+
* Stable identity for a saga parent's own lifecycle activity (progress,
|
|
171
|
+
* cancellation, approval parking, completion, failure). The activity stepId
|
|
172
|
+
* is namespaced under `parent:` so it can never collide with a forward step or
|
|
173
|
+
* a `compensate:` reversal.
|
|
174
|
+
*/
|
|
175
|
+
declare function sagaParentActivityIdentity(workflowId: string, activity: SagaParentActivityKind, discriminator?: string): {
|
|
176
|
+
idempotencyKey: string;
|
|
177
|
+
correlationId: string;
|
|
178
|
+
};
|
|
179
|
+
type SagaParentActivityKind = "progress" | "approval" | "cancel" | "complete" | "fail";
|
|
180
|
+
/**
|
|
181
|
+
* Stable causation identity linking a child step to its parent invocation.
|
|
182
|
+
* The parent invocation ID becomes the causation ID; the step keeps its own
|
|
183
|
+
* deterministic idempotency key and shares the workflow correlation ID.
|
|
184
|
+
*/
|
|
185
|
+
declare function sagaChildCausation(workflowId: string, parentInvocationId: string, stepId: string): {
|
|
186
|
+
causationId: string;
|
|
187
|
+
idempotencyKey: string;
|
|
188
|
+
correlationId: string;
|
|
189
|
+
};
|
|
190
|
+
/** Stable signal names a saga workflow may register handlers for. */
|
|
191
|
+
declare const SAGA_SIGNALS: {
|
|
192
|
+
readonly proceed: "saga.proceed";
|
|
193
|
+
readonly cancel: "saga.cancel";
|
|
194
|
+
readonly approval: "saga.approval";
|
|
195
|
+
};
|
|
69
196
|
/** Adapts the official Temporal TypeScript `WorkflowClient` to the narrow testable binding seam. */
|
|
70
197
|
declare function temporalWorkflowStartClient(client: WorkflowClient): WorkflowStartClient;
|
|
71
198
|
interface WorkerVersionEvidence {
|
|
@@ -144,4 +271,4 @@ interface DeclaredWorkflow {
|
|
|
144
271
|
*/
|
|
145
272
|
declare function runCompensatedSaga(activity: GovernedActivity, input: CompensatedSagaInput): Promise<CompensatedSagaResult>;
|
|
146
273
|
|
|
147
|
-
export { type CompensatedSagaInput, type CompensatedSagaResult, type CompensatedStep, type CompensationRecord, type DeclaredWorkflow, type GovernedActionActivityInput, type GovernedActionInvoker, SagaCompensationError, type SagaSignalEnvelope, type TemporalSignalClient, type WorkerVersionEvidence, type WorkflowStartClient, assertWorkerVersionEvidence, compensationInvocationIdentity, createGovernedActionActivities, createSagaSignalDispatcher, runCompensatedSaga, startGovernedWorkflow, temporalInvocationIdentity, temporalWorkflowStartClient };
|
|
274
|
+
export { type CompensatedSagaInput, type CompensatedSagaResult, type CompensatedStep, type CompensationRecord, type DeclaredWorkflow, type GovernedActionActivityInput, type GovernedActionInvoker, SAGA_SIGNALS, SagaCompensationError, type SagaParentActivityKind, type SagaParentApprovalInput, type SagaParentCancellationInput, type SagaParentLifecycle, type SagaParentProgressInput, type SagaParentProgressRecord, type SagaSignalEnvelope, type SagaWorkflowBinding, SagaWorkflowRegistry, type TemporalSagaDispatchInput, type TemporalSignalClient, type WorkerVersionEvidence, type WorkflowStartClient, assertWorkerVersionEvidence, compensationInvocationIdentity, createGovernedActionActivities, createSagaParentActivities, createSagaSignalDispatcher, createTemporalSagaDispatcher, runCompensatedSaga, sagaChildCausation, sagaParentActivityIdentity, startGovernedWorkflow, temporalInvocationIdentity, temporalWorkflowStartClient };
|
package/dist/index.js
CHANGED
|
@@ -26,6 +26,106 @@ function createSagaSignalDispatcher(client) {
|
|
|
26
26
|
function startGovernedWorkflow(client, input) {
|
|
27
27
|
return client.start({ ...input, idConflictPolicy: "use_existing" });
|
|
28
28
|
}
|
|
29
|
+
var SagaWorkflowRegistry = class {
|
|
30
|
+
bindings = /* @__PURE__ */ new Map();
|
|
31
|
+
constructor(bindings = []) {
|
|
32
|
+
for (const binding of bindings) this.register(binding);
|
|
33
|
+
}
|
|
34
|
+
register(binding) {
|
|
35
|
+
if (this.bindings.has(binding.actionId)) {
|
|
36
|
+
throw new Error(`Saga workflow binding already registered: ${binding.actionId}`);
|
|
37
|
+
}
|
|
38
|
+
if (!binding.workflowType.trim() || !binding.taskQueue.trim()) {
|
|
39
|
+
throw new Error(`Saga workflow binding ${binding.actionId} requires workflowType and taskQueue`);
|
|
40
|
+
}
|
|
41
|
+
this.bindings.set(binding.actionId, { ...binding });
|
|
42
|
+
}
|
|
43
|
+
require(actionId) {
|
|
44
|
+
const binding = this.bindings.get(actionId);
|
|
45
|
+
if (!binding) throw new Error(`No saga workflow binding registered for ${actionId}`);
|
|
46
|
+
return { ...binding };
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
function createTemporalSagaDispatcher(client, registry) {
|
|
50
|
+
return {
|
|
51
|
+
dispatch: async (input) => {
|
|
52
|
+
const binding = registry.require(input.actionId);
|
|
53
|
+
const handle = await startGovernedWorkflow(client, {
|
|
54
|
+
workflowType: binding.workflowType,
|
|
55
|
+
workflowId: input.workflowId,
|
|
56
|
+
taskQueue: binding.taskQueue,
|
|
57
|
+
args: [{
|
|
58
|
+
parentInvocationId: input.actionInvocationId,
|
|
59
|
+
actionId: input.actionId,
|
|
60
|
+
tenantId: input.tenantId,
|
|
61
|
+
spaceId: input.spaceId
|
|
62
|
+
}]
|
|
63
|
+
});
|
|
64
|
+
const runId = runIdFromHandle(handle);
|
|
65
|
+
return { workflowId: input.workflowId, ...runId ? { runId } : {} };
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function runIdFromHandle(handle) {
|
|
70
|
+
if (!handle || typeof handle !== "object") return void 0;
|
|
71
|
+
const candidate = handle.firstExecutionRunId;
|
|
72
|
+
return typeof candidate === "string" && candidate ? candidate : void 0;
|
|
73
|
+
}
|
|
74
|
+
function createSagaParentActivities(lifecycle) {
|
|
75
|
+
return {
|
|
76
|
+
recordSagaProgress: (input) => lifecycle.recordProgress({
|
|
77
|
+
...input,
|
|
78
|
+
lifecycleId: input.lifecycleId ?? sagaParentActivityIdentity(
|
|
79
|
+
input.parentInvocationId,
|
|
80
|
+
"progress",
|
|
81
|
+
`${input.progress.stepId}:${input.progress.index}`
|
|
82
|
+
).idempotencyKey
|
|
83
|
+
}),
|
|
84
|
+
parkSagaApproval: (input) => lifecycle.parkApproval({
|
|
85
|
+
...input,
|
|
86
|
+
lifecycleId: input.lifecycleId ?? sagaParentActivityIdentity(
|
|
87
|
+
input.parentInvocationId,
|
|
88
|
+
"approval",
|
|
89
|
+
input.approvalId
|
|
90
|
+
).idempotencyKey
|
|
91
|
+
}),
|
|
92
|
+
cancelSagaParent: (input) => lifecycle.cancel({
|
|
93
|
+
...input,
|
|
94
|
+
lifecycleId: input.lifecycleId ?? sagaParentActivityIdentity(
|
|
95
|
+
input.parentInvocationId,
|
|
96
|
+
"cancel"
|
|
97
|
+
).idempotencyKey
|
|
98
|
+
}),
|
|
99
|
+
completeSagaParent: (input) => lifecycle.complete({
|
|
100
|
+
...input,
|
|
101
|
+
lifecycleId: input.lifecycleId ?? sagaParentActivityIdentity(
|
|
102
|
+
input.parentInvocationId,
|
|
103
|
+
"complete"
|
|
104
|
+
).idempotencyKey
|
|
105
|
+
}),
|
|
106
|
+
failSagaParent: (input) => lifecycle.fail({
|
|
107
|
+
...input,
|
|
108
|
+
lifecycleId: input.lifecycleId ?? sagaParentActivityIdentity(
|
|
109
|
+
input.parentInvocationId,
|
|
110
|
+
"fail"
|
|
111
|
+
).idempotencyKey
|
|
112
|
+
})
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function sagaParentActivityIdentity(workflowId, activity, discriminator) {
|
|
116
|
+
return temporalInvocationIdentity({
|
|
117
|
+
workflowId,
|
|
118
|
+
stepId: `parent:${activity}${discriminator ? `:${discriminator}` : ""}`
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
function sagaChildCausation(workflowId, parentInvocationId, stepId) {
|
|
122
|
+
return { ...temporalInvocationIdentity({ workflowId, stepId }), causationId: parentInvocationId };
|
|
123
|
+
}
|
|
124
|
+
var SAGA_SIGNALS = {
|
|
125
|
+
proceed: "saga.proceed",
|
|
126
|
+
cancel: "saga.cancel",
|
|
127
|
+
approval: "saga.approval"
|
|
128
|
+
};
|
|
29
129
|
function temporalWorkflowStartClient(client) {
|
|
30
130
|
return {
|
|
31
131
|
start: ({ workflowType, workflowId, taskQueue, args }) => client.start(workflowType, { workflowId, taskQueue, args, workflowIdConflictPolicy: "USE_EXISTING" })
|
|
@@ -58,6 +158,7 @@ function assertDistinctSteps(steps) {
|
|
|
58
158
|
for (const step of steps) {
|
|
59
159
|
if (!step.stepId.trim()) throw new Error("Every saga step requires a stepId.");
|
|
60
160
|
if (step.stepId.startsWith("compensate:")) throw new Error(`Saga step "${step.stepId}" must not use the reserved "compensate:" prefix.`);
|
|
161
|
+
if (step.stepId.startsWith("parent:")) throw new Error(`Saga step "${step.stepId}" must not use the reserved "parent:" prefix.`);
|
|
61
162
|
if (seen.has(step.stepId)) throw new Error(`Saga declares duplicate step "${step.stepId}"; step identity must be unique within a workflow.`);
|
|
62
163
|
seen.add(step.stepId);
|
|
63
164
|
}
|
|
@@ -131,12 +232,18 @@ async function runCompensatedSaga(activity, input) {
|
|
|
131
232
|
return { completed, compensated: [] };
|
|
132
233
|
}
|
|
133
234
|
export {
|
|
235
|
+
SAGA_SIGNALS,
|
|
134
236
|
SagaCompensationError,
|
|
237
|
+
SagaWorkflowRegistry,
|
|
135
238
|
assertWorkerVersionEvidence,
|
|
136
239
|
compensationInvocationIdentity,
|
|
137
240
|
createGovernedActionActivities,
|
|
241
|
+
createSagaParentActivities,
|
|
138
242
|
createSagaSignalDispatcher,
|
|
243
|
+
createTemporalSagaDispatcher,
|
|
139
244
|
runCompensatedSaga,
|
|
245
|
+
sagaChildCausation,
|
|
246
|
+
sagaParentActivityIdentity,
|
|
140
247
|
startGovernedWorkflow,
|
|
141
248
|
temporalInvocationIdentity,
|
|
142
249
|
temporalWorkflowStartClient
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../index.ts"],"sourcesContent":["import type { ActorType } from \"@fabricorg/platform\";\nimport type { WorkflowClient } from \"@temporalio/client\";\n\n/** Serializable input passed from deterministic workflows to a Platform activity. */\nexport interface GovernedActionActivityInput {\n\tactionId: string;\n\tparameters: Record<string, unknown>;\n\ttenantId: string;\n\tspaceId: string;\n\tactorId: string;\n\tactorType: ActorType;\n\tworkflowId: string;\n\trunId: string;\n\tstepId: string;\n\tparentInvocationId?: string;\n}\n\nexport interface GovernedActionInvoker {\n\tinvoke(input: {\n\t\tactionId: string; parameters: Record<string, unknown>;\n\t\ttenantId: string; spaceId: string; actorId: string; actorType: ActorType;\n\t\tidempotencyKey: string; correlationId: string; causationId?: string;\n\t\texecutionEvidence: { temporalWorkflowId: string; temporalRunId: string; temporalStepId: string };\n\t}): Promise<unknown>;\n}\n\nexport function temporalInvocationIdentity(input: Pick<GovernedActionActivityInput, \"workflowId\" | \"stepId\">) {\n\treturn { idempotencyKey: `temporal:${input.workflowId}:${input.stepId}`, correlationId: `temporal:${input.workflowId}` };\n}\n\n/** Activity implementation. It always enters through the governed Host; handlers are never called directly. */\nexport function createGovernedActionActivities(invoker: GovernedActionInvoker) {\n\treturn {\n\t\tinvokeGovernedAction: async (input: GovernedActionActivityInput) => {\n\t\t\tconst identity = temporalInvocationIdentity(input);\n\t\t\treturn invoker.invoke({\n\t\t\t\tactionId: input.actionId, parameters: input.parameters,\n\t\t\t\ttenantId: input.tenantId, spaceId: input.spaceId,\n\t\t\t\tactorId: input.actorId, actorType: input.actorType,\n\t\t\t\t...identity,\n\t\t\t\t...(input.parentInvocationId ? { causationId: input.parentInvocationId } : {}),\n\t\t\t\texecutionEvidence: { temporalWorkflowId: input.workflowId, temporalRunId: input.runId, temporalStepId: input.stepId },\n\t\t\t});\n\t\t},\n\t};\n}\n\nexport interface SagaSignalEnvelope<T = unknown> { sagaId: string; signal: string; payload: T; actorId: string; receivedAt: string }\nexport interface TemporalSignalClient { signal(workflowId: string, signalName: string, payload: unknown): Promise<void> }\nexport function createSagaSignalDispatcher(client: TemporalSignalClient) {\n\treturn (workflowId: string, envelope: SagaSignalEnvelope) => client.signal(workflowId, envelope.signal, envelope);\n}\n\nexport interface WorkflowStartClient<THandle = unknown> {\n\tstart(input: { workflowType: string; workflowId: string; taskQueue: string; args: unknown[]; idConflictPolicy: \"use_existing\" }): Promise<THandle>;\n}\nexport function startGovernedWorkflow<THandle>(client: WorkflowStartClient<THandle>, input: { workflowType: string; workflowId: string; taskQueue: string; args: unknown[] }) {\n\treturn client.start({ ...input, idConflictPolicy: \"use_existing\" });\n}\n\n/** Adapts the official Temporal TypeScript `WorkflowClient` to the narrow testable binding seam. */\nexport function temporalWorkflowStartClient(client: WorkflowClient): WorkflowStartClient {\n\treturn {\n\t\tstart: ({ workflowType, workflowId, taskQueue, args }) =>\n\t\t\tclient.start(workflowType, { workflowId, taskQueue, args, workflowIdConflictPolicy: \"USE_EXISTING\" }),\n\t};\n}\n\nexport interface WorkerVersionEvidence { buildId: string; deploymentName: string; rampPercent?: number }\nexport function assertWorkerVersionEvidence(value: WorkerVersionEvidence): void {\n\tif (!value.buildId.trim() || !value.deploymentName.trim()) throw new Error(\"Temporal worker buildId and deploymentName are required for rollover evidence.\");\n}\n\n// ── Declared compensation ───────────────────────────────────────────────────\n\nexport interface CompensatedStep {\n\t/** Workflow-local step identity. Must match the compensation declared in the module manifest. */\n\tstepId: string;\n\tactionId: string;\n\tparameters: Record<string, unknown>;\n\t/**\n\t * Governed action that reverses this step's effect. Omit for a step with\n\t * nothing to undo; a step whose effect is externally visible should declare one.\n\t */\n\tcompensateWith?: { actionId: string; parameters?: Record<string, unknown> };\n}\n\nexport interface CompensatedSagaInput {\n\tworkflowId: string;\n\trunId: string;\n\tsteps: CompensatedStep[];\n\ttenantId: string;\n\tspaceId: string;\n\tactorId: string;\n\tactorType: ActorType;\n\tparentInvocationId?: string;\n\t/** When supplied, every step's reversal must match what the manifest declares. */\n\tworkflow?: DeclaredWorkflow;\n}\n\nexport interface CompensationRecord {\n\tstepId: string;\n\tactionId: string;\n\t/** Present when the reversal itself failed; the saga surfaces it rather than swallowing it. */\n\terror?: string;\n}\n\nexport interface CompensatedSagaResult {\n\tcompleted: string[];\n\tcompensated: CompensationRecord[];\n\tfailedStep?: { stepId: string; error: string };\n}\n\n/** Reversals are governed invocations too, so they carry their own stable identity. */\nexport function compensationInvocationIdentity(input: { workflowId: string; stepId: string }) {\n\treturn temporalInvocationIdentity({ workflowId: input.workflowId, stepId: `compensate:${input.stepId}` });\n}\n\nexport class SagaCompensationError extends Error {\n\toverride readonly name = \"SagaCompensationError\";\n\tconstructor(readonly result: CompensatedSagaResult) {\n\t\tsuper(`Saga step \"${result.failedStep?.stepId}\" failed: ${result.failedStep?.error}`);\n\t}\n}\n\ntype GovernedActivity = ReturnType<typeof createGovernedActionActivities>[\"invokeGovernedAction\"];\n\n/**\n * The governed Host resolves for outcomes that are not success — a blocked policy, a\n * failed validation, a reconciliation, an approval park. Treating a resolved promise\n * as a completed step would silently skip the unwind, so the status is inspected.\n */\nconst SUCCESSFUL_STATUS = \"completed\";\n\nfunction stepFailure(result: unknown, label: string): string | undefined {\n\tif (!result || typeof result !== \"object\") return undefined;\n\tconst status = (result as { status?: unknown }).status;\n\tif (typeof status !== \"string\" || status === SUCCESSFUL_STATUS) return undefined;\n\tconst error = (result as { error?: unknown }).error;\n\treturn `${label} resolved with status \"${status}\"${typeof error === \"string\" && error ? `: ${error}` : \"\"}`;\n}\n\nfunction assertDistinctSteps(steps: CompensatedStep[]): void {\n\tconst seen = new Set<string>();\n\tfor (const step of steps) {\n\t\tif (!step.stepId.trim()) throw new Error(\"Every saga step requires a stepId.\");\n\t\t// A forward step called \"compensate:x\" would share an identity with the reversal of \"x\".\n\t\tif (step.stepId.startsWith(\"compensate:\")) throw new Error(`Saga step \"${step.stepId}\" must not use the reserved \"compensate:\" prefix.`);\n\t\tif (seen.has(step.stepId)) throw new Error(`Saga declares duplicate step \"${step.stepId}\"; step identity must be unique within a workflow.`);\n\t\tseen.add(step.stepId);\n\t}\n}\n\n/** The manifest declaration this saga is expected to honour, when one is supplied. */\nexport interface DeclaredWorkflow {\n\tname: string;\n\tcompensations?: Array<{ step: string; actionId: string }>;\n}\n\nfunction assertMatchesDeclaration(steps: CompensatedStep[], workflow: DeclaredWorkflow): void {\n\tconst declared = new Map((workflow.compensations ?? []).map((entry) => [entry.step, entry.actionId]));\n\tfor (const step of steps) {\n\t\tconst expected = declared.get(step.stepId);\n\t\tif (expected === undefined) {\n\t\t\tif (step.compensateWith) throw new Error(`Workflow \"${workflow.name}\" step \"${step.stepId}\" supplies a reversal the manifest does not declare.`);\n\t\t\tcontinue;\n\t\t}\n\t\tif (!step.compensateWith) throw new Error(`Workflow \"${workflow.name}\" step \"${step.stepId}\" omits the reversal \"${expected}\" its manifest declares.`);\n\t\tif (step.compensateWith.actionId !== expected) {\n\t\t\tthrow new Error(`Workflow \"${workflow.name}\" step \"${step.stepId}\" reverses with \"${step.compensateWith.actionId}\" but its manifest declares \"${expected}\".`);\n\t\t}\n\t}\n\tfor (const [step, actionId] of declared) {\n\t\tif (!steps.some((candidate) => candidate.stepId === step)) {\n\t\t\tthrow new Error(`Workflow \"${workflow.name}\" declares a reversal \"${actionId}\" for step \"${step}\", which this saga never runs.`);\n\t\t}\n\t}\n}\n\n/**\n * Runs steps in order, accumulating reversals as each succeeds. On failure the\n * accumulated reversals run newest-first, so an unwind never leaves an orphan.\n * Every forward step and every reversal enters through the governed Host with a\n * deterministic idempotency key, so a retried or replayed unwind is not double-applied.\n */\nexport async function runCompensatedSaga(\n\tactivity: GovernedActivity,\n\tinput: CompensatedSagaInput,\n): Promise<CompensatedSagaResult> {\n\tconst scope = {\n\t\ttenantId: input.tenantId,\n\t\tspaceId: input.spaceId,\n\t\tactorId: input.actorId,\n\t\tactorType: input.actorType,\n\t\tworkflowId: input.workflowId,\n\t\trunId: input.runId,\n\t\t...(input.parentInvocationId ? { parentInvocationId: input.parentInvocationId } : {}),\n\t};\n\tassertDistinctSteps(input.steps);\n\tif (input.workflow) assertMatchesDeclaration(input.steps, input.workflow);\n\n\tconst completed: string[] = [];\n\tconst reversals: CompensatedStep[] = [];\n\n\tconst unwind = async (failedStep: { stepId: string; error: string }): Promise<never> => {\n\t\tconst compensated: CompensationRecord[] = [];\n\t\t// Newest-first: the last effect applied is the first one withdrawn.\n\t\tfor (const applied of [...reversals].reverse()) {\n\t\t\tconst reversal = applied.compensateWith!;\n\t\t\tconst record: CompensationRecord = { stepId: applied.stepId, actionId: reversal.actionId };\n\t\t\ttry {\n\t\t\t\tconst result = await activity({\n\t\t\t\t\t...scope,\n\t\t\t\t\tactionId: reversal.actionId,\n\t\t\t\t\tparameters: reversal.parameters ?? applied.parameters,\n\t\t\t\t\tstepId: `compensate:${applied.stepId}`,\n\t\t\t\t});\n\t\t\t\tconst refused = stepFailure(result, `reversal \"${reversal.actionId}\"`);\n\t\t\t\tif (refused) record.error = refused;\n\t\t\t} catch (compensationError) {\n\t\t\t\trecord.error = compensationError instanceof Error ? compensationError.message : String(compensationError);\n\t\t\t}\n\t\t\tcompensated.push(record);\n\t\t}\n\t\tthrow new SagaCompensationError({ completed, compensated, failedStep });\n\t};\n\n\tfor (const step of input.steps) {\n\t\tlet result: unknown;\n\t\ttry {\n\t\t\tresult = await activity({ ...scope, actionId: step.actionId, parameters: step.parameters, stepId: step.stepId });\n\t\t} catch (error) {\n\t\t\treturn await unwind({ stepId: step.stepId, error: error instanceof Error ? error.message : String(error) });\n\t\t}\n\t\tconst refused = stepFailure(result, `step \"${step.stepId}\"`);\n\t\tif (refused) return await unwind({ stepId: step.stepId, error: refused });\n\t\tcompleted.push(step.stepId);\n\t\tif (step.compensateWith) reversals.push(step);\n\t}\n\n\treturn { completed, compensated: [] };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../index.ts"],"sourcesContent":["import type { ActorType } from \"@fabricorg/platform\";\nimport type { WorkflowClient } from \"@temporalio/client\";\n\n/** Serializable input passed from deterministic workflows to a Platform activity. */\nexport interface GovernedActionActivityInput {\n\tactionId: string;\n\tparameters: Record<string, unknown>;\n\ttenantId: string;\n\tspaceId: string;\n\tactorId: string;\n\tactorType: ActorType;\n\tworkflowId: string;\n\trunId: string;\n\tstepId: string;\n\tparentInvocationId?: string;\n}\n\nexport interface GovernedActionInvoker {\n\tinvoke(input: {\n\t\tactionId: string; parameters: Record<string, unknown>;\n\t\ttenantId: string; spaceId: string; actorId: string; actorType: ActorType;\n\t\tidempotencyKey: string; correlationId: string; causationId?: string;\n\t\texecutionEvidence: { temporalWorkflowId: string; temporalRunId: string; temporalStepId: string };\n\t}): Promise<unknown>;\n}\n\nexport function temporalInvocationIdentity(input: Pick<GovernedActionActivityInput, \"workflowId\" | \"stepId\">) {\n\treturn { idempotencyKey: `temporal:${input.workflowId}:${input.stepId}`, correlationId: `temporal:${input.workflowId}` };\n}\n\n/** Activity implementation. It always enters through the governed Host; handlers are never called directly. */\nexport function createGovernedActionActivities(invoker: GovernedActionInvoker) {\n\treturn {\n\t\tinvokeGovernedAction: async (input: GovernedActionActivityInput) => {\n\t\t\tconst identity = temporalInvocationIdentity(input);\n\t\t\treturn invoker.invoke({\n\t\t\t\tactionId: input.actionId, parameters: input.parameters,\n\t\t\t\ttenantId: input.tenantId, spaceId: input.spaceId,\n\t\t\t\tactorId: input.actorId, actorType: input.actorType,\n\t\t\t\t...identity,\n\t\t\t\t...(input.parentInvocationId ? { causationId: input.parentInvocationId } : {}),\n\t\t\t\texecutionEvidence: { temporalWorkflowId: input.workflowId, temporalRunId: input.runId, temporalStepId: input.stepId },\n\t\t\t});\n\t\t},\n\t};\n}\n\nexport interface SagaSignalEnvelope<T = unknown> { sagaId: string; signal: string; payload: T; actorId: string; receivedAt: string }\nexport interface TemporalSignalClient { signal(workflowId: string, signalName: string, payload: unknown): Promise<void> }\nexport function createSagaSignalDispatcher(client: TemporalSignalClient) {\n\treturn (workflowId: string, envelope: SagaSignalEnvelope) => client.signal(workflowId, envelope.signal, envelope);\n}\n\nexport interface WorkflowStartClient<THandle = unknown> {\n\tstart(input: { workflowType: string; workflowId: string; taskQueue: string; args: unknown[]; idConflictPolicy: \"use_existing\" }): Promise<THandle>;\n}\nexport function startGovernedWorkflow<THandle>(client: WorkflowStartClient<THandle>, input: { workflowType: string; workflowId: string; taskQueue: string; args: unknown[] }) {\n\treturn client.start({ ...input, idConflictPolicy: \"use_existing\" });\n}\n\nexport interface SagaWorkflowBinding {\n\tactionId: `${string}.${string}`;\n\tworkflowType: string;\n\ttaskQueue: string;\n}\n\n/** Instance-scoped registry; registration order never changes resolution. */\nexport class SagaWorkflowRegistry {\n\tprivate readonly bindings = new Map<string, SagaWorkflowBinding>();\n\n\tconstructor(bindings: readonly SagaWorkflowBinding[] = []) {\n\t\tfor (const binding of bindings) this.register(binding);\n\t}\n\n\tregister(binding: SagaWorkflowBinding): void {\n\t\tif (this.bindings.has(binding.actionId)) {\n\t\t\tthrow new Error(`Saga workflow binding already registered: ${binding.actionId}`);\n\t\t}\n\t\tif (!binding.workflowType.trim() || !binding.taskQueue.trim()) {\n\t\t\tthrow new Error(`Saga workflow binding ${binding.actionId} requires workflowType and taskQueue`);\n\t\t}\n\t\tthis.bindings.set(binding.actionId, { ...binding });\n\t}\n\n\trequire(actionId: string): SagaWorkflowBinding {\n\t\tconst binding = this.bindings.get(actionId);\n\t\tif (!binding) throw new Error(`No saga workflow binding registered for ${actionId}`);\n\t\treturn { ...binding };\n\t}\n}\n\nexport interface TemporalSagaDispatchInput {\n\tactionInvocationId: string;\n\tactionId: `${string}.${string}`;\n\ttenantId: string;\n\tspaceId: string;\n\tworkflowId: string;\n}\n\n/** Starts the registered parent workflow with an invocation-only durable identity. */\nexport function createTemporalSagaDispatcher<THandle>(\n\tclient: WorkflowStartClient<THandle>,\n\tregistry: SagaWorkflowRegistry,\n) {\n\treturn {\n\t\tdispatch: async (input: TemporalSagaDispatchInput) => {\n\t\t\tconst binding = registry.require(input.actionId);\n\t\t\tconst handle = await startGovernedWorkflow(client, {\n\t\t\t\tworkflowType: binding.workflowType,\n\t\t\t\tworkflowId: input.workflowId,\n\t\t\t\ttaskQueue: binding.taskQueue,\n\t\t\t\targs: [{\n\t\t\t\t\tparentInvocationId: input.actionInvocationId,\n\t\t\t\t\tactionId: input.actionId,\n\t\t\t\t\ttenantId: input.tenantId,\n\t\t\t\t\tspaceId: input.spaceId,\n\t\t\t\t}],\n\t\t\t});\n\t\t\tconst runId = runIdFromHandle(handle);\n\t\t\treturn { workflowId: input.workflowId, ...(runId ? { runId } : {}) };\n\t\t},\n\t};\n}\n\nfunction runIdFromHandle(handle: unknown): string | undefined {\n\tif (!handle || typeof handle !== \"object\") return undefined;\n\tconst candidate = (handle as { firstExecutionRunId?: unknown }).firstExecutionRunId;\n\treturn typeof candidate === \"string\" && candidate ? candidate : undefined;\n}\n\n/** Durable record of a single step completing within a saga parent. */\nexport interface SagaParentProgressRecord {\n\t/** The step that just completed. */\n\tstepId: string;\n\t/** Zero-based position of this step in the declared sequence. */\n\tindex: number;\n\t/** Total steps declared for this saga. */\n\ttotal: number;\n\t/** ISO-8601 timestamp the progress was recorded. */\n\tcompletedAt: string;\n}\n\nexport interface SagaParentProgressInput {\n\tparentInvocationId: string;\n\ttenantId: string;\n\tspaceId: string;\n\t/** Stable identity shared by every retry of this lifecycle transition. */\n\tlifecycleId?: string;\n\tprogress: SagaParentProgressRecord;\n}\n\nexport interface SagaParentApprovalInput {\n\tparentInvocationId: string;\n\ttenantId: string;\n\tspaceId: string;\n\t/** Stable identity shared by every retry of this lifecycle transition. */\n\tlifecycleId?: string;\n\t/** The step at which the saga paused for human approval. */\n\tstepId: string;\n\t/** Stable identifier for the approval gate, so a later signal can resume it. */\n\tapprovalId: string;\n\treason: string;\n}\n\nexport interface SagaParentCancellationInput {\n\tparentInvocationId: string;\n\ttenantId: string;\n\tspaceId: string;\n\t/** Stable identity shared by every retry of this lifecycle transition. */\n\tlifecycleId?: string;\n\treason: string;\n\t/** Actor requesting the cancellation, for audit attribution. */\n\tactorId: string;\n}\n\nexport interface SagaParentLifecycle {\n\t/** Record durable progress as each step completes. */\n\trecordProgress(input: SagaParentProgressInput): Promise<void>;\n\t/** Park the parent for human approval at a specific step. */\n\tparkApproval(input: SagaParentApprovalInput): Promise<void>;\n\t/** Cancel the parent saga with an attributable reason. */\n\tcancel(input: SagaParentCancellationInput): Promise<void>;\n\t/** Complete the parent saga with its final result. */\n\tcomplete(input: { parentInvocationId: string; tenantId: string; spaceId: string; lifecycleId?: string; result: Record<string, unknown> }): Promise<void>;\n\t/** Fail the parent saga, surfacing any compensation records. */\n\tfail(input: { parentInvocationId: string; tenantId: string; spaceId: string; lifecycleId?: string; error: string; compensation: CompensationRecord[] }): Promise<void>;\n}\n\n/** Parent lifecycle remains explicit durable activities, never workflow-local state. */\nexport function createSagaParentActivities(lifecycle: SagaParentLifecycle) {\n\treturn {\n\t\trecordSagaProgress: (input: SagaParentProgressInput) => lifecycle.recordProgress({\n\t\t\t...input,\n\t\t\tlifecycleId: input.lifecycleId ?? sagaParentActivityIdentity(\n\t\t\t\tinput.parentInvocationId,\n\t\t\t\t\"progress\",\n\t\t\t\t`${input.progress.stepId}:${input.progress.index}`,\n\t\t\t).idempotencyKey,\n\t\t}),\n\t\tparkSagaApproval: (input: SagaParentApprovalInput) => lifecycle.parkApproval({\n\t\t\t...input,\n\t\t\tlifecycleId: input.lifecycleId ?? sagaParentActivityIdentity(\n\t\t\t\tinput.parentInvocationId,\n\t\t\t\t\"approval\",\n\t\t\t\tinput.approvalId,\n\t\t\t).idempotencyKey,\n\t\t}),\n\t\tcancelSagaParent: (input: SagaParentCancellationInput) => lifecycle.cancel({\n\t\t\t...input,\n\t\t\tlifecycleId: input.lifecycleId ?? sagaParentActivityIdentity(\n\t\t\t\tinput.parentInvocationId,\n\t\t\t\t\"cancel\",\n\t\t\t).idempotencyKey,\n\t\t}),\n\t\tcompleteSagaParent: (input: Parameters<SagaParentLifecycle[\"complete\"]>[0]) =>\n\t\t\tlifecycle.complete({\n\t\t\t\t...input,\n\t\t\t\tlifecycleId: input.lifecycleId ?? sagaParentActivityIdentity(\n\t\t\t\t\tinput.parentInvocationId,\n\t\t\t\t\t\"complete\",\n\t\t\t\t).idempotencyKey,\n\t\t\t}),\n\t\tfailSagaParent: (input: Parameters<SagaParentLifecycle[\"fail\"]>[0]) => lifecycle.fail({\n\t\t\t...input,\n\t\t\tlifecycleId: input.lifecycleId ?? sagaParentActivityIdentity(\n\t\t\t\tinput.parentInvocationId,\n\t\t\t\t\"fail\",\n\t\t\t).idempotencyKey,\n\t\t}),\n\t};\n}\n\n// ── Stable parent and child causation identities ───────────────────────────\n\n/**\n * Stable identity for a saga parent's own lifecycle activity (progress,\n * cancellation, approval parking, completion, failure). The activity stepId\n * is namespaced under `parent:` so it can never collide with a forward step or\n * a `compensate:` reversal.\n */\nexport function sagaParentActivityIdentity(\n\tworkflowId: string,\n\tactivity: SagaParentActivityKind,\n\tdiscriminator?: string,\n) {\n\treturn temporalInvocationIdentity({\n\t\tworkflowId,\n\t\tstepId: `parent:${activity}${discriminator ? `:${discriminator}` : \"\"}`,\n\t});\n}\n\nexport type SagaParentActivityKind = \"progress\" | \"approval\" | \"cancel\" | \"complete\" | \"fail\";\n\n/**\n * Stable causation identity linking a child step to its parent invocation.\n * The parent invocation ID becomes the causation ID; the step keeps its own\n * deterministic idempotency key and shares the workflow correlation ID.\n */\nexport function sagaChildCausation(workflowId: string, parentInvocationId: string, stepId: string) {\n\treturn { ...temporalInvocationIdentity({ workflowId, stepId }), causationId: parentInvocationId };\n}\n\n// ── Saga signal names ───────────────────────────────────────────────────────\n\n/** Stable signal names a saga workflow may register handlers for. */\nexport const SAGA_SIGNALS = {\n\tproceed: \"saga.proceed\",\n\tcancel: \"saga.cancel\",\n\tapproval: \"saga.approval\",\n} as const;\n\n/** Adapts the official Temporal TypeScript `WorkflowClient` to the narrow testable binding seam. */\nexport function temporalWorkflowStartClient(client: WorkflowClient): WorkflowStartClient {\n\treturn {\n\t\tstart: ({ workflowType, workflowId, taskQueue, args }) =>\n\t\t\tclient.start(workflowType, { workflowId, taskQueue, args, workflowIdConflictPolicy: \"USE_EXISTING\" }),\n\t};\n}\n\nexport interface WorkerVersionEvidence { buildId: string; deploymentName: string; rampPercent?: number }\nexport function assertWorkerVersionEvidence(value: WorkerVersionEvidence): void {\n\tif (!value.buildId.trim() || !value.deploymentName.trim()) throw new Error(\"Temporal worker buildId and deploymentName are required for rollover evidence.\");\n}\n\n// ── Declared compensation ───────────────────────────────────────────────────\n\nexport interface CompensatedStep {\n\t/** Workflow-local step identity. Must match the compensation declared in the module manifest. */\n\tstepId: string;\n\tactionId: string;\n\tparameters: Record<string, unknown>;\n\t/**\n\t * Governed action that reverses this step's effect. Omit for a step with\n\t * nothing to undo; a step whose effect is externally visible should declare one.\n\t */\n\tcompensateWith?: { actionId: string; parameters?: Record<string, unknown> };\n}\n\nexport interface CompensatedSagaInput {\n\tworkflowId: string;\n\trunId: string;\n\tsteps: CompensatedStep[];\n\ttenantId: string;\n\tspaceId: string;\n\tactorId: string;\n\tactorType: ActorType;\n\tparentInvocationId?: string;\n\t/** When supplied, every step's reversal must match what the manifest declares. */\n\tworkflow?: DeclaredWorkflow;\n}\n\nexport interface CompensationRecord {\n\tstepId: string;\n\tactionId: string;\n\t/** Present when the reversal itself failed; the saga surfaces it rather than swallowing it. */\n\terror?: string;\n}\n\nexport interface CompensatedSagaResult {\n\tcompleted: string[];\n\tcompensated: CompensationRecord[];\n\tfailedStep?: { stepId: string; error: string };\n}\n\n/** Reversals are governed invocations too, so they carry their own stable identity. */\nexport function compensationInvocationIdentity(input: { workflowId: string; stepId: string }) {\n\treturn temporalInvocationIdentity({ workflowId: input.workflowId, stepId: `compensate:${input.stepId}` });\n}\n\nexport class SagaCompensationError extends Error {\n\toverride readonly name = \"SagaCompensationError\";\n\tconstructor(readonly result: CompensatedSagaResult) {\n\t\tsuper(`Saga step \"${result.failedStep?.stepId}\" failed: ${result.failedStep?.error}`);\n\t}\n}\n\ntype GovernedActivity = ReturnType<typeof createGovernedActionActivities>[\"invokeGovernedAction\"];\n\n/**\n * The governed Host resolves for outcomes that are not success — a blocked policy, a\n * failed validation, a reconciliation, an approval park. Treating a resolved promise\n * as a completed step would silently skip the unwind, so the status is inspected.\n */\nconst SUCCESSFUL_STATUS = \"completed\";\n\nfunction stepFailure(result: unknown, label: string): string | undefined {\n\tif (!result || typeof result !== \"object\") return undefined;\n\tconst status = (result as { status?: unknown }).status;\n\tif (typeof status !== \"string\" || status === SUCCESSFUL_STATUS) return undefined;\n\tconst error = (result as { error?: unknown }).error;\n\treturn `${label} resolved with status \"${status}\"${typeof error === \"string\" && error ? `: ${error}` : \"\"}`;\n}\n\nfunction assertDistinctSteps(steps: CompensatedStep[]): void {\n\tconst seen = new Set<string>();\n\tfor (const step of steps) {\n\t\tif (!step.stepId.trim()) throw new Error(\"Every saga step requires a stepId.\");\n\t\t// A forward step called \"compensate:x\" would share an identity with the reversal of \"x\".\n\t\tif (step.stepId.startsWith(\"compensate:\")) throw new Error(`Saga step \"${step.stepId}\" must not use the reserved \"compensate:\" prefix.`);\n\t\t// A forward step called \"parent:x\" would share an identity with a parent lifecycle activity.\n\t\tif (step.stepId.startsWith(\"parent:\")) throw new Error(`Saga step \"${step.stepId}\" must not use the reserved \"parent:\" prefix.`);\n\t\tif (seen.has(step.stepId)) throw new Error(`Saga declares duplicate step \"${step.stepId}\"; step identity must be unique within a workflow.`);\n\t\tseen.add(step.stepId);\n\t}\n}\n\n/** The manifest declaration this saga is expected to honour, when one is supplied. */\nexport interface DeclaredWorkflow {\n\tname: string;\n\tcompensations?: Array<{ step: string; actionId: string }>;\n}\n\nfunction assertMatchesDeclaration(steps: CompensatedStep[], workflow: DeclaredWorkflow): void {\n\tconst declared = new Map((workflow.compensations ?? []).map((entry) => [entry.step, entry.actionId]));\n\tfor (const step of steps) {\n\t\tconst expected = declared.get(step.stepId);\n\t\tif (expected === undefined) {\n\t\t\tif (step.compensateWith) throw new Error(`Workflow \"${workflow.name}\" step \"${step.stepId}\" supplies a reversal the manifest does not declare.`);\n\t\t\tcontinue;\n\t\t}\n\t\tif (!step.compensateWith) throw new Error(`Workflow \"${workflow.name}\" step \"${step.stepId}\" omits the reversal \"${expected}\" its manifest declares.`);\n\t\tif (step.compensateWith.actionId !== expected) {\n\t\t\tthrow new Error(`Workflow \"${workflow.name}\" step \"${step.stepId}\" reverses with \"${step.compensateWith.actionId}\" but its manifest declares \"${expected}\".`);\n\t\t}\n\t}\n\tfor (const [step, actionId] of declared) {\n\t\tif (!steps.some((candidate) => candidate.stepId === step)) {\n\t\t\tthrow new Error(`Workflow \"${workflow.name}\" declares a reversal \"${actionId}\" for step \"${step}\", which this saga never runs.`);\n\t\t}\n\t}\n}\n\n/**\n * Runs steps in order, accumulating reversals as each succeeds. On failure the\n * accumulated reversals run newest-first, so an unwind never leaves an orphan.\n * Every forward step and every reversal enters through the governed Host with a\n * deterministic idempotency key, so a retried or replayed unwind is not double-applied.\n */\nexport async function runCompensatedSaga(\n\tactivity: GovernedActivity,\n\tinput: CompensatedSagaInput,\n): Promise<CompensatedSagaResult> {\n\tconst scope = {\n\t\ttenantId: input.tenantId,\n\t\tspaceId: input.spaceId,\n\t\tactorId: input.actorId,\n\t\tactorType: input.actorType,\n\t\tworkflowId: input.workflowId,\n\t\trunId: input.runId,\n\t\t...(input.parentInvocationId ? { parentInvocationId: input.parentInvocationId } : {}),\n\t};\n\tassertDistinctSteps(input.steps);\n\tif (input.workflow) assertMatchesDeclaration(input.steps, input.workflow);\n\n\tconst completed: string[] = [];\n\tconst reversals: CompensatedStep[] = [];\n\n\tconst unwind = async (failedStep: { stepId: string; error: string }): Promise<never> => {\n\t\tconst compensated: CompensationRecord[] = [];\n\t\t// Newest-first: the last effect applied is the first one withdrawn.\n\t\tfor (const applied of [...reversals].reverse()) {\n\t\t\tconst reversal = applied.compensateWith!;\n\t\t\tconst record: CompensationRecord = { stepId: applied.stepId, actionId: reversal.actionId };\n\t\t\ttry {\n\t\t\t\tconst result = await activity({\n\t\t\t\t\t...scope,\n\t\t\t\t\tactionId: reversal.actionId,\n\t\t\t\t\tparameters: reversal.parameters ?? applied.parameters,\n\t\t\t\t\tstepId: `compensate:${applied.stepId}`,\n\t\t\t\t});\n\t\t\t\tconst refused = stepFailure(result, `reversal \"${reversal.actionId}\"`);\n\t\t\t\tif (refused) record.error = refused;\n\t\t\t} catch (compensationError) {\n\t\t\t\trecord.error = compensationError instanceof Error ? compensationError.message : String(compensationError);\n\t\t\t}\n\t\t\tcompensated.push(record);\n\t\t}\n\t\tthrow new SagaCompensationError({ completed, compensated, failedStep });\n\t};\n\n\tfor (const step of input.steps) {\n\t\tlet result: unknown;\n\t\ttry {\n\t\t\tresult = await activity({ ...scope, actionId: step.actionId, parameters: step.parameters, stepId: step.stepId });\n\t\t} catch (error) {\n\t\t\treturn await unwind({ stepId: step.stepId, error: error instanceof Error ? error.message : String(error) });\n\t\t}\n\t\tconst refused = stepFailure(result, `step \"${step.stepId}\"`);\n\t\tif (refused) return await unwind({ stepId: step.stepId, error: refused });\n\t\tcompleted.push(step.stepId);\n\t\tif (step.compensateWith) reversals.push(step);\n\t}\n\n\treturn { completed, compensated: [] };\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fabricorg/platform-temporal",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.1.1",
|
|
4
4
|
"description": "Temporal binding contracts for governed Fabric Platform actions and sagas.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"tsup": "^8.5.0",
|
|
38
38
|
"typescript": "^5.7.3",
|
|
39
39
|
"vitest": "^4.1.5",
|
|
40
|
-
"@fabricorg/platform
|
|
41
|
-
"@fabricorg/platform": "
|
|
40
|
+
"@fabricorg/platform": "1.5.1",
|
|
41
|
+
"@fabricorg/platform-host": "7.2.1"
|
|
42
42
|
},
|
|
43
43
|
"engines": {
|
|
44
44
|
"node": ">=20"
|
|
@@ -47,6 +47,13 @@
|
|
|
47
47
|
"access": "public"
|
|
48
48
|
},
|
|
49
49
|
"license": "MIT",
|
|
50
|
+
"repository": {
|
|
51
|
+
"type": "git",
|
|
52
|
+
"url": "git+https://github.com/Fabric-Pro/fabric-platform.git",
|
|
53
|
+
"directory": "packages/platform-temporal"
|
|
54
|
+
},
|
|
55
|
+
"homepage": "https://github.com/Fabric-Pro/fabric-platform/tree/main/packages/platform-temporal#readme",
|
|
56
|
+
"bugs": "https://github.com/Fabric-Pro/fabric-platform/issues",
|
|
50
57
|
"peerDependenciesMeta": {
|
|
51
58
|
"@temporalio/client": {
|
|
52
59
|
"optional": true
|