@fabricorg/platform-temporal 3.0.0 → 4.0.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 +29 -0
- package/dist/index.cjs +102 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +70 -1
- package/dist/index.d.ts +70 -1
- package/dist/index.js +99 -0
- package/dist/index.js.map +1 -1
- package/package.json +8 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 4.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- c504d0a: Make durability a declared, diffable contract.
|
|
8
|
+
|
|
9
|
+
`CapabilityMetadata` gains `workflows`: each declares its task queue, the event that starts it, the
|
|
10
|
+
durable deadlines it carries (resolved from configuration keys, never literals), and which governed
|
|
11
|
+
action reverses each compensatable step. The field is additive, so `MODULE_MANIFEST_VERSION` stays
|
|
12
|
+
at 2.
|
|
13
|
+
|
|
14
|
+
`diffModuleManifests` classifies this plane instead of reporting one blanket capability change:
|
|
15
|
+
adding a workflow, timer or reversal is compatible; moving a task queue, changing a trigger, dropping
|
|
16
|
+
a deadline or repointing a reversal is potentially breaking; removing a workflow or reversal is
|
|
17
|
+
breaking.
|
|
18
|
+
|
|
19
|
+
The compiler validates serialized workflow declarations, which never pass through a registry, and
|
|
20
|
+
rejects an action declaring `completion: "immediate"` while `kind: "saga"` — a manifest contradiction
|
|
21
|
+
that now fails the build rather than a running host.
|
|
22
|
+
|
|
23
|
+
`platform-temporal` executes declared reversals. `runCompensatedSaga` unwinds newest-first, routes
|
|
24
|
+
every reversal through the governed Host under a distinct deterministic
|
|
25
|
+
`compensationInvocationIdentity`, and surfaces a reversal that itself failed on
|
|
26
|
+
`SagaCompensationError` rather than swallowing it. A step that _resolves_ with a non-successful
|
|
27
|
+
status — blocked, failed, parked for approval, sent to reconciliation — now triggers the unwind
|
|
28
|
+
instead of being recorded as completed. Step identities must be unique and may not use the reserved
|
|
29
|
+
`compensate:` prefix, and when a `workflow` declaration is supplied every reversal is checked against
|
|
30
|
+
it.
|
|
31
|
+
|
|
3
32
|
## 3.0.0
|
|
4
33
|
|
|
5
34
|
### Patch Changes
|
package/dist/index.cjs
CHANGED
|
@@ -20,9 +20,12 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
SagaCompensationError: () => SagaCompensationError,
|
|
23
24
|
assertWorkerVersionEvidence: () => assertWorkerVersionEvidence,
|
|
25
|
+
compensationInvocationIdentity: () => compensationInvocationIdentity,
|
|
24
26
|
createGovernedActionActivities: () => createGovernedActionActivities,
|
|
25
27
|
createSagaSignalDispatcher: () => createSagaSignalDispatcher,
|
|
28
|
+
runCompensatedSaga: () => runCompensatedSaga,
|
|
26
29
|
startGovernedWorkflow: () => startGovernedWorkflow,
|
|
27
30
|
temporalInvocationIdentity: () => temporalInvocationIdentity,
|
|
28
31
|
temporalWorkflowStartClient: () => temporalWorkflowStartClient
|
|
@@ -63,11 +66,110 @@ function temporalWorkflowStartClient(client) {
|
|
|
63
66
|
function assertWorkerVersionEvidence(value) {
|
|
64
67
|
if (!value.buildId.trim() || !value.deploymentName.trim()) throw new Error("Temporal worker buildId and deploymentName are required for rollover evidence.");
|
|
65
68
|
}
|
|
69
|
+
function compensationInvocationIdentity(input) {
|
|
70
|
+
return temporalInvocationIdentity({ workflowId: input.workflowId, stepId: `compensate:${input.stepId}` });
|
|
71
|
+
}
|
|
72
|
+
var SagaCompensationError = class extends Error {
|
|
73
|
+
constructor(result) {
|
|
74
|
+
super(`Saga step "${result.failedStep?.stepId}" failed: ${result.failedStep?.error}`);
|
|
75
|
+
this.result = result;
|
|
76
|
+
}
|
|
77
|
+
result;
|
|
78
|
+
name = "SagaCompensationError";
|
|
79
|
+
};
|
|
80
|
+
var SUCCESSFUL_STATUS = "completed";
|
|
81
|
+
function stepFailure(result, label) {
|
|
82
|
+
if (!result || typeof result !== "object") return void 0;
|
|
83
|
+
const status = result.status;
|
|
84
|
+
if (typeof status !== "string" || status === SUCCESSFUL_STATUS) return void 0;
|
|
85
|
+
const error = result.error;
|
|
86
|
+
return `${label} resolved with status "${status}"${typeof error === "string" && error ? `: ${error}` : ""}`;
|
|
87
|
+
}
|
|
88
|
+
function assertDistinctSteps(steps) {
|
|
89
|
+
const seen = /* @__PURE__ */ new Set();
|
|
90
|
+
for (const step of steps) {
|
|
91
|
+
if (!step.stepId.trim()) throw new Error("Every saga step requires a stepId.");
|
|
92
|
+
if (step.stepId.startsWith("compensate:")) throw new Error(`Saga step "${step.stepId}" must not use the reserved "compensate:" prefix.`);
|
|
93
|
+
if (seen.has(step.stepId)) throw new Error(`Saga declares duplicate step "${step.stepId}"; step identity must be unique within a workflow.`);
|
|
94
|
+
seen.add(step.stepId);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function assertMatchesDeclaration(steps, workflow) {
|
|
98
|
+
const declared = new Map((workflow.compensations ?? []).map((entry) => [entry.step, entry.actionId]));
|
|
99
|
+
for (const step of steps) {
|
|
100
|
+
const expected = declared.get(step.stepId);
|
|
101
|
+
if (expected === void 0) {
|
|
102
|
+
if (step.compensateWith) throw new Error(`Workflow "${workflow.name}" step "${step.stepId}" supplies a reversal the manifest does not declare.`);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (!step.compensateWith) throw new Error(`Workflow "${workflow.name}" step "${step.stepId}" omits the reversal "${expected}" its manifest declares.`);
|
|
106
|
+
if (step.compensateWith.actionId !== expected) {
|
|
107
|
+
throw new Error(`Workflow "${workflow.name}" step "${step.stepId}" reverses with "${step.compensateWith.actionId}" but its manifest declares "${expected}".`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
for (const [step, actionId] of declared) {
|
|
111
|
+
if (!steps.some((candidate) => candidate.stepId === step)) {
|
|
112
|
+
throw new Error(`Workflow "${workflow.name}" declares a reversal "${actionId}" for step "${step}", which this saga never runs.`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
async function runCompensatedSaga(activity, input) {
|
|
117
|
+
const scope = {
|
|
118
|
+
tenantId: input.tenantId,
|
|
119
|
+
spaceId: input.spaceId,
|
|
120
|
+
actorId: input.actorId,
|
|
121
|
+
actorType: input.actorType,
|
|
122
|
+
workflowId: input.workflowId,
|
|
123
|
+
runId: input.runId,
|
|
124
|
+
...input.parentInvocationId ? { parentInvocationId: input.parentInvocationId } : {}
|
|
125
|
+
};
|
|
126
|
+
assertDistinctSteps(input.steps);
|
|
127
|
+
if (input.workflow) assertMatchesDeclaration(input.steps, input.workflow);
|
|
128
|
+
const completed = [];
|
|
129
|
+
const reversals = [];
|
|
130
|
+
const unwind = async (failedStep) => {
|
|
131
|
+
const compensated = [];
|
|
132
|
+
for (const applied of [...reversals].reverse()) {
|
|
133
|
+
const reversal = applied.compensateWith;
|
|
134
|
+
const record = { stepId: applied.stepId, actionId: reversal.actionId };
|
|
135
|
+
try {
|
|
136
|
+
const result = await activity({
|
|
137
|
+
...scope,
|
|
138
|
+
actionId: reversal.actionId,
|
|
139
|
+
parameters: reversal.parameters ?? applied.parameters,
|
|
140
|
+
stepId: `compensate:${applied.stepId}`
|
|
141
|
+
});
|
|
142
|
+
const refused = stepFailure(result, `reversal "${reversal.actionId}"`);
|
|
143
|
+
if (refused) record.error = refused;
|
|
144
|
+
} catch (compensationError) {
|
|
145
|
+
record.error = compensationError instanceof Error ? compensationError.message : String(compensationError);
|
|
146
|
+
}
|
|
147
|
+
compensated.push(record);
|
|
148
|
+
}
|
|
149
|
+
throw new SagaCompensationError({ completed, compensated, failedStep });
|
|
150
|
+
};
|
|
151
|
+
for (const step of input.steps) {
|
|
152
|
+
let result;
|
|
153
|
+
try {
|
|
154
|
+
result = await activity({ ...scope, actionId: step.actionId, parameters: step.parameters, stepId: step.stepId });
|
|
155
|
+
} catch (error) {
|
|
156
|
+
return await unwind({ stepId: step.stepId, error: error instanceof Error ? error.message : String(error) });
|
|
157
|
+
}
|
|
158
|
+
const refused = stepFailure(result, `step "${step.stepId}"`);
|
|
159
|
+
if (refused) return await unwind({ stepId: step.stepId, error: refused });
|
|
160
|
+
completed.push(step.stepId);
|
|
161
|
+
if (step.compensateWith) reversals.push(step);
|
|
162
|
+
}
|
|
163
|
+
return { completed, compensated: [] };
|
|
164
|
+
}
|
|
66
165
|
// Annotate the CommonJS export names for ESM import in node:
|
|
67
166
|
0 && (module.exports = {
|
|
167
|
+
SagaCompensationError,
|
|
68
168
|
assertWorkerVersionEvidence,
|
|
169
|
+
compensationInvocationIdentity,
|
|
69
170
|
createGovernedActionActivities,
|
|
70
171
|
createSagaSignalDispatcher,
|
|
172
|
+
runCompensatedSaga,
|
|
71
173
|
startGovernedWorkflow,
|
|
72
174
|
temporalInvocationIdentity,
|
|
73
175
|
temporalWorkflowStartClient
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../index.ts"],"sourcesContent":["import type { ActorType } from \"@fabricorg/platform\";\nimport type { WorkflowClient } from \"@temporalio/client\";\n\n/** Serializable input passed from deterministic workflows to a Platform activity. */\nexport interface GovernedActionActivityInput {\n\tactionId: string;\n\tparameters: Record<string, unknown>;\n\ttenantId: string;\n\tspaceId: string;\n\tactorId: string;\n\tactorType: ActorType;\n\tworkflowId: string;\n\trunId: string;\n\tstepId: string;\n\tparentInvocationId?: string;\n}\n\nexport interface GovernedActionInvoker {\n\tinvoke(input: {\n\t\tactionId: string; parameters: Record<string, unknown>;\n\t\ttenantId: string; spaceId: string; actorId: string; actorType: ActorType;\n\t\tidempotencyKey: string; correlationId: string; causationId?: string;\n\t\texecutionEvidence: { temporalWorkflowId: string; temporalRunId: string; temporalStepId: string };\n\t}): Promise<unknown>;\n}\n\nexport function temporalInvocationIdentity(input: Pick<GovernedActionActivityInput, \"workflowId\" | \"stepId\">) {\n\treturn { idempotencyKey: `temporal:${input.workflowId}:${input.stepId}`, correlationId: `temporal:${input.workflowId}` };\n}\n\n/** Activity implementation. It always enters through the governed Host; handlers are never called directly. */\nexport function createGovernedActionActivities(invoker: GovernedActionInvoker) {\n\treturn {\n\t\tinvokeGovernedAction: async (input: GovernedActionActivityInput) => {\n\t\t\tconst identity = temporalInvocationIdentity(input);\n\t\t\treturn invoker.invoke({\n\t\t\t\tactionId: input.actionId, parameters: input.parameters,\n\t\t\t\ttenantId: input.tenantId, spaceId: input.spaceId,\n\t\t\t\tactorId: input.actorId, actorType: input.actorType,\n\t\t\t\t...identity,\n\t\t\t\t...(input.parentInvocationId ? { causationId: input.parentInvocationId } : {}),\n\t\t\t\texecutionEvidence: { temporalWorkflowId: input.workflowId, temporalRunId: input.runId, temporalStepId: input.stepId },\n\t\t\t});\n\t\t},\n\t};\n}\n\nexport interface SagaSignalEnvelope<T = unknown> { sagaId: string; signal: string; payload: T; actorId: string; receivedAt: string }\nexport interface TemporalSignalClient { signal(workflowId: string, signalName: string, payload: unknown): Promise<void> }\nexport function createSagaSignalDispatcher(client: TemporalSignalClient) {\n\treturn (workflowId: string, envelope: SagaSignalEnvelope) => client.signal(workflowId, envelope.signal, envelope);\n}\n\nexport interface WorkflowStartClient<THandle = unknown> {\n\tstart(input: { workflowType: string; workflowId: string; taskQueue: string; args: unknown[]; idConflictPolicy: \"use_existing\" }): Promise<THandle>;\n}\nexport function startGovernedWorkflow<THandle>(client: WorkflowStartClient<THandle>, input: { workflowType: string; workflowId: string; taskQueue: string; args: unknown[] }) {\n\treturn client.start({ ...input, idConflictPolicy: \"use_existing\" });\n}\n\n/** Adapts the official Temporal TypeScript `WorkflowClient` to the narrow testable binding seam. */\nexport function temporalWorkflowStartClient(client: WorkflowClient): WorkflowStartClient {\n\treturn {\n\t\tstart: ({ workflowType, workflowId, taskQueue, args }) =>\n\t\t\tclient.start(workflowType, { workflowId, taskQueue, args, workflowIdConflictPolicy: \"USE_EXISTING\" }),\n\t};\n}\n\nexport interface WorkerVersionEvidence { buildId: string; deploymentName: string; rampPercent?: number }\nexport function assertWorkerVersionEvidence(value: WorkerVersionEvidence): void {\n\tif (!value.buildId.trim() || !value.deploymentName.trim()) throw new Error(\"Temporal worker buildId and deploymentName are required for rollover evidence.\");\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA0BO,SAAS,2BAA2B,OAAmE;AAC7G,SAAO,EAAE,gBAAgB,YAAY,MAAM,UAAU,IAAI,MAAM,MAAM,IAAI,eAAe,YAAY,MAAM,UAAU,GAAG;AACxH;AAGO,SAAS,+BAA+B,SAAgC;AAC9E,SAAO;AAAA,IACN,sBAAsB,OAAO,UAAuC;AACnE,YAAM,WAAW,2BAA2B,KAAK;AACjD,aAAO,QAAQ,OAAO;AAAA,QACrB,UAAU,MAAM;AAAA,QAAU,YAAY,MAAM;AAAA,QAC5C,UAAU,MAAM;AAAA,QAAU,SAAS,MAAM;AAAA,QACzC,SAAS,MAAM;AAAA,QAAS,WAAW,MAAM;AAAA,QACzC,GAAG;AAAA,QACH,GAAI,MAAM,qBAAqB,EAAE,aAAa,MAAM,mBAAmB,IAAI,CAAC;AAAA,QAC5E,mBAAmB,EAAE,oBAAoB,MAAM,YAAY,eAAe,MAAM,OAAO,gBAAgB,MAAM,OAAO;AAAA,MACrH,CAAC;AAAA,IACF;AAAA,EACD;AACD;AAIO,SAAS,2BAA2B,QAA8B;AACxE,SAAO,CAAC,YAAoB,aAAiC,OAAO,OAAO,YAAY,SAAS,QAAQ,QAAQ;AACjH;AAKO,SAAS,sBAA+B,QAAsC,OAAyF;AAC7K,SAAO,OAAO,MAAM,EAAE,GAAG,OAAO,kBAAkB,eAAe,CAAC;AACnE;AAGO,SAAS,4BAA4B,QAA6C;AACxF,SAAO;AAAA,IACN,OAAO,CAAC,EAAE,cAAc,YAAY,WAAW,KAAK,MACnD,OAAO,MAAM,cAAc,EAAE,YAAY,WAAW,MAAM,0BAA0B,eAAe,CAAC;AAAA,EACtG;AACD;AAGO,SAAS,4BAA4B,OAAoC;AAC/E,MAAI,CAAC,MAAM,QAAQ,KAAK,KAAK,CAAC,MAAM,eAAe,KAAK,EAAG,OAAM,IAAI,MAAM,gFAAgF;AAC5J;","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\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":[]}
|
package/dist/index.d.cts
CHANGED
|
@@ -74,5 +74,74 @@ interface WorkerVersionEvidence {
|
|
|
74
74
|
rampPercent?: number;
|
|
75
75
|
}
|
|
76
76
|
declare function assertWorkerVersionEvidence(value: WorkerVersionEvidence): void;
|
|
77
|
+
interface CompensatedStep {
|
|
78
|
+
/** Workflow-local step identity. Must match the compensation declared in the module manifest. */
|
|
79
|
+
stepId: string;
|
|
80
|
+
actionId: string;
|
|
81
|
+
parameters: Record<string, unknown>;
|
|
82
|
+
/**
|
|
83
|
+
* Governed action that reverses this step's effect. Omit for a step with
|
|
84
|
+
* nothing to undo; a step whose effect is externally visible should declare one.
|
|
85
|
+
*/
|
|
86
|
+
compensateWith?: {
|
|
87
|
+
actionId: string;
|
|
88
|
+
parameters?: Record<string, unknown>;
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
interface CompensatedSagaInput {
|
|
92
|
+
workflowId: string;
|
|
93
|
+
runId: string;
|
|
94
|
+
steps: CompensatedStep[];
|
|
95
|
+
tenantId: string;
|
|
96
|
+
spaceId: string;
|
|
97
|
+
actorId: string;
|
|
98
|
+
actorType: ActorType;
|
|
99
|
+
parentInvocationId?: string;
|
|
100
|
+
/** When supplied, every step's reversal must match what the manifest declares. */
|
|
101
|
+
workflow?: DeclaredWorkflow;
|
|
102
|
+
}
|
|
103
|
+
interface CompensationRecord {
|
|
104
|
+
stepId: string;
|
|
105
|
+
actionId: string;
|
|
106
|
+
/** Present when the reversal itself failed; the saga surfaces it rather than swallowing it. */
|
|
107
|
+
error?: string;
|
|
108
|
+
}
|
|
109
|
+
interface CompensatedSagaResult {
|
|
110
|
+
completed: string[];
|
|
111
|
+
compensated: CompensationRecord[];
|
|
112
|
+
failedStep?: {
|
|
113
|
+
stepId: string;
|
|
114
|
+
error: string;
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/** Reversals are governed invocations too, so they carry their own stable identity. */
|
|
118
|
+
declare function compensationInvocationIdentity(input: {
|
|
119
|
+
workflowId: string;
|
|
120
|
+
stepId: string;
|
|
121
|
+
}): {
|
|
122
|
+
idempotencyKey: string;
|
|
123
|
+
correlationId: string;
|
|
124
|
+
};
|
|
125
|
+
declare class SagaCompensationError extends Error {
|
|
126
|
+
readonly result: CompensatedSagaResult;
|
|
127
|
+
readonly name = "SagaCompensationError";
|
|
128
|
+
constructor(result: CompensatedSagaResult);
|
|
129
|
+
}
|
|
130
|
+
type GovernedActivity = ReturnType<typeof createGovernedActionActivities>["invokeGovernedAction"];
|
|
131
|
+
/** The manifest declaration this saga is expected to honour, when one is supplied. */
|
|
132
|
+
interface DeclaredWorkflow {
|
|
133
|
+
name: string;
|
|
134
|
+
compensations?: Array<{
|
|
135
|
+
step: string;
|
|
136
|
+
actionId: string;
|
|
137
|
+
}>;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Runs steps in order, accumulating reversals as each succeeds. On failure the
|
|
141
|
+
* accumulated reversals run newest-first, so an unwind never leaves an orphan.
|
|
142
|
+
* Every forward step and every reversal enters through the governed Host with a
|
|
143
|
+
* deterministic idempotency key, so a retried or replayed unwind is not double-applied.
|
|
144
|
+
*/
|
|
145
|
+
declare function runCompensatedSaga(activity: GovernedActivity, input: CompensatedSagaInput): Promise<CompensatedSagaResult>;
|
|
77
146
|
|
|
78
|
-
export { type GovernedActionActivityInput, type GovernedActionInvoker, type SagaSignalEnvelope, type TemporalSignalClient, type WorkerVersionEvidence, type WorkflowStartClient, assertWorkerVersionEvidence, createGovernedActionActivities, createSagaSignalDispatcher, startGovernedWorkflow, temporalInvocationIdentity, temporalWorkflowStartClient };
|
|
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 };
|
package/dist/index.d.ts
CHANGED
|
@@ -74,5 +74,74 @@ interface WorkerVersionEvidence {
|
|
|
74
74
|
rampPercent?: number;
|
|
75
75
|
}
|
|
76
76
|
declare function assertWorkerVersionEvidence(value: WorkerVersionEvidence): void;
|
|
77
|
+
interface CompensatedStep {
|
|
78
|
+
/** Workflow-local step identity. Must match the compensation declared in the module manifest. */
|
|
79
|
+
stepId: string;
|
|
80
|
+
actionId: string;
|
|
81
|
+
parameters: Record<string, unknown>;
|
|
82
|
+
/**
|
|
83
|
+
* Governed action that reverses this step's effect. Omit for a step with
|
|
84
|
+
* nothing to undo; a step whose effect is externally visible should declare one.
|
|
85
|
+
*/
|
|
86
|
+
compensateWith?: {
|
|
87
|
+
actionId: string;
|
|
88
|
+
parameters?: Record<string, unknown>;
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
interface CompensatedSagaInput {
|
|
92
|
+
workflowId: string;
|
|
93
|
+
runId: string;
|
|
94
|
+
steps: CompensatedStep[];
|
|
95
|
+
tenantId: string;
|
|
96
|
+
spaceId: string;
|
|
97
|
+
actorId: string;
|
|
98
|
+
actorType: ActorType;
|
|
99
|
+
parentInvocationId?: string;
|
|
100
|
+
/** When supplied, every step's reversal must match what the manifest declares. */
|
|
101
|
+
workflow?: DeclaredWorkflow;
|
|
102
|
+
}
|
|
103
|
+
interface CompensationRecord {
|
|
104
|
+
stepId: string;
|
|
105
|
+
actionId: string;
|
|
106
|
+
/** Present when the reversal itself failed; the saga surfaces it rather than swallowing it. */
|
|
107
|
+
error?: string;
|
|
108
|
+
}
|
|
109
|
+
interface CompensatedSagaResult {
|
|
110
|
+
completed: string[];
|
|
111
|
+
compensated: CompensationRecord[];
|
|
112
|
+
failedStep?: {
|
|
113
|
+
stepId: string;
|
|
114
|
+
error: string;
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/** Reversals are governed invocations too, so they carry their own stable identity. */
|
|
118
|
+
declare function compensationInvocationIdentity(input: {
|
|
119
|
+
workflowId: string;
|
|
120
|
+
stepId: string;
|
|
121
|
+
}): {
|
|
122
|
+
idempotencyKey: string;
|
|
123
|
+
correlationId: string;
|
|
124
|
+
};
|
|
125
|
+
declare class SagaCompensationError extends Error {
|
|
126
|
+
readonly result: CompensatedSagaResult;
|
|
127
|
+
readonly name = "SagaCompensationError";
|
|
128
|
+
constructor(result: CompensatedSagaResult);
|
|
129
|
+
}
|
|
130
|
+
type GovernedActivity = ReturnType<typeof createGovernedActionActivities>["invokeGovernedAction"];
|
|
131
|
+
/** The manifest declaration this saga is expected to honour, when one is supplied. */
|
|
132
|
+
interface DeclaredWorkflow {
|
|
133
|
+
name: string;
|
|
134
|
+
compensations?: Array<{
|
|
135
|
+
step: string;
|
|
136
|
+
actionId: string;
|
|
137
|
+
}>;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Runs steps in order, accumulating reversals as each succeeds. On failure the
|
|
141
|
+
* accumulated reversals run newest-first, so an unwind never leaves an orphan.
|
|
142
|
+
* Every forward step and every reversal enters through the governed Host with a
|
|
143
|
+
* deterministic idempotency key, so a retried or replayed unwind is not double-applied.
|
|
144
|
+
*/
|
|
145
|
+
declare function runCompensatedSaga(activity: GovernedActivity, input: CompensatedSagaInput): Promise<CompensatedSagaResult>;
|
|
77
146
|
|
|
78
|
-
export { type GovernedActionActivityInput, type GovernedActionInvoker, type SagaSignalEnvelope, type TemporalSignalClient, type WorkerVersionEvidence, type WorkflowStartClient, assertWorkerVersionEvidence, createGovernedActionActivities, createSagaSignalDispatcher, startGovernedWorkflow, temporalInvocationIdentity, temporalWorkflowStartClient };
|
|
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 };
|
package/dist/index.js
CHANGED
|
@@ -34,10 +34,109 @@ function temporalWorkflowStartClient(client) {
|
|
|
34
34
|
function assertWorkerVersionEvidence(value) {
|
|
35
35
|
if (!value.buildId.trim() || !value.deploymentName.trim()) throw new Error("Temporal worker buildId and deploymentName are required for rollover evidence.");
|
|
36
36
|
}
|
|
37
|
+
function compensationInvocationIdentity(input) {
|
|
38
|
+
return temporalInvocationIdentity({ workflowId: input.workflowId, stepId: `compensate:${input.stepId}` });
|
|
39
|
+
}
|
|
40
|
+
var SagaCompensationError = class extends Error {
|
|
41
|
+
constructor(result) {
|
|
42
|
+
super(`Saga step "${result.failedStep?.stepId}" failed: ${result.failedStep?.error}`);
|
|
43
|
+
this.result = result;
|
|
44
|
+
}
|
|
45
|
+
result;
|
|
46
|
+
name = "SagaCompensationError";
|
|
47
|
+
};
|
|
48
|
+
var SUCCESSFUL_STATUS = "completed";
|
|
49
|
+
function stepFailure(result, label) {
|
|
50
|
+
if (!result || typeof result !== "object") return void 0;
|
|
51
|
+
const status = result.status;
|
|
52
|
+
if (typeof status !== "string" || status === SUCCESSFUL_STATUS) return void 0;
|
|
53
|
+
const error = result.error;
|
|
54
|
+
return `${label} resolved with status "${status}"${typeof error === "string" && error ? `: ${error}` : ""}`;
|
|
55
|
+
}
|
|
56
|
+
function assertDistinctSteps(steps) {
|
|
57
|
+
const seen = /* @__PURE__ */ new Set();
|
|
58
|
+
for (const step of steps) {
|
|
59
|
+
if (!step.stepId.trim()) throw new Error("Every saga step requires a stepId.");
|
|
60
|
+
if (step.stepId.startsWith("compensate:")) throw new Error(`Saga step "${step.stepId}" must not use the reserved "compensate:" prefix.`);
|
|
61
|
+
if (seen.has(step.stepId)) throw new Error(`Saga declares duplicate step "${step.stepId}"; step identity must be unique within a workflow.`);
|
|
62
|
+
seen.add(step.stepId);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function assertMatchesDeclaration(steps, workflow) {
|
|
66
|
+
const declared = new Map((workflow.compensations ?? []).map((entry) => [entry.step, entry.actionId]));
|
|
67
|
+
for (const step of steps) {
|
|
68
|
+
const expected = declared.get(step.stepId);
|
|
69
|
+
if (expected === void 0) {
|
|
70
|
+
if (step.compensateWith) throw new Error(`Workflow "${workflow.name}" step "${step.stepId}" supplies a reversal the manifest does not declare.`);
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (!step.compensateWith) throw new Error(`Workflow "${workflow.name}" step "${step.stepId}" omits the reversal "${expected}" its manifest declares.`);
|
|
74
|
+
if (step.compensateWith.actionId !== expected) {
|
|
75
|
+
throw new Error(`Workflow "${workflow.name}" step "${step.stepId}" reverses with "${step.compensateWith.actionId}" but its manifest declares "${expected}".`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
for (const [step, actionId] of declared) {
|
|
79
|
+
if (!steps.some((candidate) => candidate.stepId === step)) {
|
|
80
|
+
throw new Error(`Workflow "${workflow.name}" declares a reversal "${actionId}" for step "${step}", which this saga never runs.`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async function runCompensatedSaga(activity, input) {
|
|
85
|
+
const scope = {
|
|
86
|
+
tenantId: input.tenantId,
|
|
87
|
+
spaceId: input.spaceId,
|
|
88
|
+
actorId: input.actorId,
|
|
89
|
+
actorType: input.actorType,
|
|
90
|
+
workflowId: input.workflowId,
|
|
91
|
+
runId: input.runId,
|
|
92
|
+
...input.parentInvocationId ? { parentInvocationId: input.parentInvocationId } : {}
|
|
93
|
+
};
|
|
94
|
+
assertDistinctSteps(input.steps);
|
|
95
|
+
if (input.workflow) assertMatchesDeclaration(input.steps, input.workflow);
|
|
96
|
+
const completed = [];
|
|
97
|
+
const reversals = [];
|
|
98
|
+
const unwind = async (failedStep) => {
|
|
99
|
+
const compensated = [];
|
|
100
|
+
for (const applied of [...reversals].reverse()) {
|
|
101
|
+
const reversal = applied.compensateWith;
|
|
102
|
+
const record = { stepId: applied.stepId, actionId: reversal.actionId };
|
|
103
|
+
try {
|
|
104
|
+
const result = await activity({
|
|
105
|
+
...scope,
|
|
106
|
+
actionId: reversal.actionId,
|
|
107
|
+
parameters: reversal.parameters ?? applied.parameters,
|
|
108
|
+
stepId: `compensate:${applied.stepId}`
|
|
109
|
+
});
|
|
110
|
+
const refused = stepFailure(result, `reversal "${reversal.actionId}"`);
|
|
111
|
+
if (refused) record.error = refused;
|
|
112
|
+
} catch (compensationError) {
|
|
113
|
+
record.error = compensationError instanceof Error ? compensationError.message : String(compensationError);
|
|
114
|
+
}
|
|
115
|
+
compensated.push(record);
|
|
116
|
+
}
|
|
117
|
+
throw new SagaCompensationError({ completed, compensated, failedStep });
|
|
118
|
+
};
|
|
119
|
+
for (const step of input.steps) {
|
|
120
|
+
let result;
|
|
121
|
+
try {
|
|
122
|
+
result = await activity({ ...scope, actionId: step.actionId, parameters: step.parameters, stepId: step.stepId });
|
|
123
|
+
} catch (error) {
|
|
124
|
+
return await unwind({ stepId: step.stepId, error: error instanceof Error ? error.message : String(error) });
|
|
125
|
+
}
|
|
126
|
+
const refused = stepFailure(result, `step "${step.stepId}"`);
|
|
127
|
+
if (refused) return await unwind({ stepId: step.stepId, error: refused });
|
|
128
|
+
completed.push(step.stepId);
|
|
129
|
+
if (step.compensateWith) reversals.push(step);
|
|
130
|
+
}
|
|
131
|
+
return { completed, compensated: [] };
|
|
132
|
+
}
|
|
37
133
|
export {
|
|
134
|
+
SagaCompensationError,
|
|
38
135
|
assertWorkerVersionEvidence,
|
|
136
|
+
compensationInvocationIdentity,
|
|
39
137
|
createGovernedActionActivities,
|
|
40
138
|
createSagaSignalDispatcher,
|
|
139
|
+
runCompensatedSaga,
|
|
41
140
|
startGovernedWorkflow,
|
|
42
141
|
temporalInvocationIdentity,
|
|
43
142
|
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"],"mappings":";AA0BO,SAAS,2BAA2B,OAAmE;AAC7G,SAAO,EAAE,gBAAgB,YAAY,MAAM,UAAU,IAAI,MAAM,MAAM,IAAI,eAAe,YAAY,MAAM,UAAU,GAAG;AACxH;AAGO,SAAS,+BAA+B,SAAgC;AAC9E,SAAO;AAAA,IACN,sBAAsB,OAAO,UAAuC;AACnE,YAAM,WAAW,2BAA2B,KAAK;AACjD,aAAO,QAAQ,OAAO;AAAA,QACrB,UAAU,MAAM;AAAA,QAAU,YAAY,MAAM;AAAA,QAC5C,UAAU,MAAM;AAAA,QAAU,SAAS,MAAM;AAAA,QACzC,SAAS,MAAM;AAAA,QAAS,WAAW,MAAM;AAAA,QACzC,GAAG;AAAA,QACH,GAAI,MAAM,qBAAqB,EAAE,aAAa,MAAM,mBAAmB,IAAI,CAAC;AAAA,QAC5E,mBAAmB,EAAE,oBAAoB,MAAM,YAAY,eAAe,MAAM,OAAO,gBAAgB,MAAM,OAAO;AAAA,MACrH,CAAC;AAAA,IACF;AAAA,EACD;AACD;AAIO,SAAS,2BAA2B,QAA8B;AACxE,SAAO,CAAC,YAAoB,aAAiC,OAAO,OAAO,YAAY,SAAS,QAAQ,QAAQ;AACjH;AAKO,SAAS,sBAA+B,QAAsC,OAAyF;AAC7K,SAAO,OAAO,MAAM,EAAE,GAAG,OAAO,kBAAkB,eAAe,CAAC;AACnE;AAGO,SAAS,4BAA4B,QAA6C;AACxF,SAAO;AAAA,IACN,OAAO,CAAC,EAAE,cAAc,YAAY,WAAW,KAAK,MACnD,OAAO,MAAM,cAAc,EAAE,YAAY,WAAW,MAAM,0BAA0B,eAAe,CAAC;AAAA,EACtG;AACD;AAGO,SAAS,4BAA4B,OAAoC;AAC/E,MAAI,CAAC,MAAM,QAAQ,KAAK,KAAK,CAAC,MAAM,eAAe,KAAK,EAAG,OAAM,IAAI,MAAM,gFAAgF;AAC5J;","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\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":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fabricorg/platform-temporal",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "Temporal binding contracts for governed Fabric Platform actions and sagas.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -26,16 +26,19 @@
|
|
|
26
26
|
],
|
|
27
27
|
"peerDependencies": {
|
|
28
28
|
"@fabricorg/platform": "^1.0.0",
|
|
29
|
-
"@temporalio/client": "^1.
|
|
29
|
+
"@temporalio/client": "^1.23.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
|
-
"@temporalio/client": "^1.
|
|
32
|
+
"@temporalio/client": "^1.23.0",
|
|
33
|
+
"@temporalio/testing": "^1.23.0",
|
|
34
|
+
"@temporalio/worker": "^1.23.0",
|
|
35
|
+
"@temporalio/workflow": "^1.23.0",
|
|
33
36
|
"@types/node": "^22.10.0",
|
|
34
37
|
"tsup": "^8.5.0",
|
|
35
38
|
"typescript": "^5.7.3",
|
|
36
39
|
"vitest": "^4.1.5",
|
|
37
|
-
"@fabricorg/platform-host": "
|
|
38
|
-
"@fabricorg/platform": "1.
|
|
40
|
+
"@fabricorg/platform-host": "5.0.0",
|
|
41
|
+
"@fabricorg/platform": "1.1.0"
|
|
39
42
|
},
|
|
40
43
|
"engines": {
|
|
41
44
|
"node": ">=20"
|