@fabricorg/platform-temporal 4.0.0 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,40 @@
1
1
  # Changelog
2
2
 
3
+ ## 4.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 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.
8
+ - 4e585a4: Close final production-maturity correctness gaps: expired leases can no longer be renewed,
9
+ saga parent transitions are serialized, atomic, terminal-safe, and retry-idempotent by lifecycle identity, hydrated
10
+ projections must meet requested checkpoints, and SDUI releases accept components only from
11
+ resolved document packs. The release gate now verifies every package and public subpath in
12
+ isolated ESM, CommonJS, and TypeScript consumers.
13
+ - fb14d67: Add versioned PostgreSQL migrations and live PostgreSQL 16 certification. Fence and renew invocation
14
+ and outbox leases, bound publisher calls, reject stale finalization, and add deterministic Temporal
15
+ saga workflow binding with explicit parent lifecycle activities.
16
+ - eefd1eb: Complete the saga parent lifecycle: add durable progress, approval-parking, and cancellation activity
17
+ contracts alongside the existing completion and failure seams. Add stable parent and child causation
18
+ identity helpers and stable saga signal names. Cover duplicate dispatch, cancellation, approval,
19
+ compensation failure, and worker rollover through public Temporal seams.
20
+
21
+ ### Additional release notes
22
+
23
+ ### Minor Changes
24
+
25
+ - Add an instance-scoped saga workflow registry, stable Temporal dispatcher, and explicit durable
26
+ parent completion and compensation-failure activity seams.
27
+
28
+ - Complete the saga parent lifecycle: add durable progress, approval-parking, and cancellation
29
+ activity contracts alongside the existing completion and failure seams. Add stable parent and
30
+ child causation identity helpers (`sagaParentActivityIdentity`, `sagaChildCausation`) and stable
31
+ saga signal names (`SAGA_SIGNALS`). Cover duplicate dispatch, cancellation, approval, compensation
32
+ failure, and worker rollover through public Temporal seams.
33
+ - Reserve the `parent:` identity prefix in `assertDistinctSteps` so a forward step cannot collide
34
+ with a parent lifecycle activity identity.
35
+ - Attach stable occurrence-specific lifecycle identities in `createSagaParentActivities` so
36
+ activity retries map to one durable parent transition.
37
+
3
38
  ## 4.0.0
4
39
 
5
40
  ### 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
@@ -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.0.0",
3
+ "version": "4.1.0",
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-host": "5.0.0",
41
- "@fabricorg/platform": "1.1.0"
40
+ "@fabricorg/platform": "1.2.0",
41
+ "@fabricorg/platform-host": "6.0.0"
42
42
  },
43
43
  "engines": {
44
44
  "node": ">=20"