@fluidgrids/workflows 2026.1027.1 → 2026.1028.2
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/README.md +144 -13
- package/dist/activation-BBh1p82z.d.cts +57 -0
- package/dist/activation-DnhG7SmF.d.ts +57 -0
- package/dist/assemble-BLqQ8lSu.d.cts +39 -0
- package/dist/assemble-DWgaXHd-.d.ts +39 -0
- package/dist/chunk-2J6TF7YI.cjs +86 -0
- package/dist/chunk-4LYSEANA.js +199 -0
- package/dist/chunk-623FVQSN.cjs +251 -0
- package/dist/{chunk-W32BSO3M.js → chunk-6FSHT5IL.js} +2 -2
- package/dist/{chunk-E3APHA6O.cjs → chunk-7AAZGVKD.js} +1 -64
- package/dist/chunk-ABFZWYHT.js +612 -0
- package/dist/{chunk-6U446SKY.js → chunk-BD7ZPG53.js} +2 -2
- package/dist/chunk-BO6OVILK.js +79 -0
- package/dist/chunk-C5OLWZ42.js +245 -0
- package/dist/{chunk-OSPTJCDI.cjs → chunk-CPDK3K5N.cjs} +6 -6
- package/dist/chunk-DHTLALBL.cjs +80 -0
- package/dist/chunk-DJYMDLV5.js +227 -0
- package/dist/{chunk-IHKXLG6I.js → chunk-E2VK7J2C.js} +1 -1
- package/dist/{chunk-3M34AUIY.cjs → chunk-FEW3JBYA.cjs} +3 -3
- package/dist/chunk-FMSW2HDF.cjs +267 -0
- package/dist/chunk-FR7KYWRO.cjs +41 -0
- package/dist/{chunk-7YR7DI7B.cjs → chunk-GBQ33SIZ.cjs} +44 -44
- package/dist/{chunk-6AIAC2FP.cjs → chunk-GVTZMA5C.cjs} +4 -4
- package/dist/{chunk-EEAIM35V.js → chunk-HWJZVOG7.js} +64 -23
- package/dist/chunk-I5LR3D2K.js +869 -0
- package/dist/chunk-IISBRUWJ.js +13 -0
- package/dist/chunk-JD2VQIDZ.cjs +15 -0
- package/dist/{chunk-UB2BHU2M.js → chunk-JZ4TOOLN.js} +1 -1
- package/dist/chunk-L24YIZY7.cjs +207 -0
- package/dist/chunk-LNUQE6D6.js +39 -0
- package/dist/chunk-M4BBAERY.cjs +1635 -0
- package/dist/chunk-N23SKIG4.js +73 -0
- package/dist/{chunk-TWOYB4Y7.cjs → chunk-NEIUQASE.cjs} +13 -13
- package/dist/chunk-P6TA5EZT.cjs +634 -0
- package/dist/chunk-PMCM2OH4.js +46 -0
- package/dist/chunk-PZCVF76Q.cjs +868 -0
- package/dist/{chunk-6BEW4QD6.js → chunk-QAN5MZEM.js} +1 -1
- package/dist/{chunk-CSYBNN3V.cjs → chunk-QKZL3ONM.cjs} +2 -2
- package/dist/chunk-QQ6ULXHI.cjs +885 -0
- package/dist/chunk-SAENRNBJ.cjs +52 -0
- package/dist/{chunk-TQF7MYEN.cjs → chunk-SAOBPYYJ.cjs} +134 -96
- package/dist/{chunk-XBIID5DY.js → chunk-SMCANK3F.js} +5 -4
- package/dist/{chunk-UWQWD7BP.cjs → chunk-SX2PNKKT.cjs} +18 -17
- package/dist/{chunk-UJNHTS6D.js → chunk-TBDXTV22.cjs} +8 -45
- package/dist/chunk-UK3WM7YZ.cjs +75 -0
- package/dist/chunk-VOKMAAJP.js +848 -0
- package/dist/chunk-VRHHBPT6.js +1622 -0
- package/dist/{chunk-5AW745OG.js → chunk-XHXIDVEC.js} +1 -1
- package/dist/chunk-XKJA7KX3.js +72 -0
- package/dist/chunk-Z2Y65YOY.cjs +7 -0
- package/dist/codegen/index.cjs +9 -7
- package/dist/codegen/index.d.cts +2 -2
- package/dist/codegen/index.d.ts +2 -2
- package/dist/codegen/index.js +6 -5
- package/dist/file-BOoHZgoQ.d.cts +1557 -0
- package/dist/file-yuPCFU5b.d.ts +1557 -0
- package/dist/history/index.cjs +287 -0
- package/dist/history/index.d.cts +157 -0
- package/dist/history/index.d.ts +157 -0
- package/dist/history/index.js +5 -0
- package/dist/index.cjs +134 -36
- package/dist/index.d.cts +137 -9
- package/dist/index.d.ts +137 -9
- package/dist/index.js +8 -3
- package/dist/ir/index.cjs +152 -158
- package/dist/ir/index.d.cts +14 -4
- package/dist/ir/index.d.ts +14 -4
- package/dist/ir/index.js +41 -50
- package/dist/lower-BzmUz0sq.d.cts +129 -0
- package/dist/lower-_-iT4HYs.d.ts +129 -0
- package/dist/lower-types-DQNBfWQn.d.cts +37 -0
- package/dist/lower-types-DQNBfWQn.d.ts +37 -0
- package/dist/{manifest-Duf-gHdO.d.cts → manifest-0OLw0i7k.d.cts} +1 -1
- package/dist/{manifest-Bp9ZJlav.d.ts → manifest-C8N6HDYl.d.ts} +1 -1
- package/dist/node-evaluator-4MSDKWOG.cjs +22 -0
- package/dist/node-evaluator-SX4LEYQC.js +12 -0
- package/dist/quickjs/index.cjs +236 -12
- package/dist/quickjs/index.d.cts +46 -5
- package/dist/quickjs/index.d.ts +46 -5
- package/dist/quickjs/index.js +230 -9
- package/dist/roundtrip/index.cjs +333 -131
- package/dist/roundtrip/index.d.cts +39 -7
- package/dist/roundtrip/index.d.ts +39 -7
- package/dist/roundtrip/index.js +185 -13
- package/dist/runtime/index.cjs +90 -0
- package/dist/runtime/index.d.cts +297 -0
- package/dist/runtime/index.d.ts +297 -0
- package/dist/runtime/index.js +8 -0
- package/dist/testing/index.cjs +1057 -0
- package/dist/testing/index.d.cts +305 -0
- package/dist/testing/index.d.ts +305 -0
- package/dist/testing/index.js +1037 -0
- package/dist/{types-Dr2Fn1Ax.d.ts → types-BGoPzgcl.d.cts} +1 -1
- package/dist/{types-Dr2Fn1Ax.d.cts → types-BGoPzgcl.d.ts} +1 -1
- package/dist/types-CpBBjaGX.d.cts +380 -0
- package/dist/types-DV-HX_Gt.d.ts +380 -0
- package/dist/{assemble-CrpUyd_3.d.ts → workflow-Bpr3vnA_.d.ts} +40 -160
- package/dist/workflow-C2a05WUJ.d.ts +444 -0
- package/dist/{assemble-DEf3znGD.d.cts → workflow-FXUkpIBc.d.cts} +40 -160
- package/dist/workflow-J-yYixny.d.cts +444 -0
- package/fixtures/README.md +128 -0
- package/fixtures/histories/activity-failure-fails-run.history.json +163 -0
- package/fixtures/histories/activity-retry-then-success.history.json +147 -0
- package/fixtures/histories/cancel-activity-abandon.history.json +126 -0
- package/fixtures/histories/cancel-activity-compensate.history.json +307 -0
- package/fixtures/histories/cancel-concurrent-non-cancellable.history.json +234 -0
- package/fixtures/histories/cancel-concurrent-scope-timeout.history.json +184 -0
- package/fixtures/histories/cancel-condition-after-cancel.history.json +200 -0
- package/fixtures/histories/cancel-during-timer.history.json +123 -0
- package/fixtures/histories/cancel-saga-compensations.history.json +325 -0
- package/fixtures/histories/cancel-scope-timeout.history.json +148 -0
- package/fixtures/histories/child-abandon-outlives-parent.history.json +191 -0
- package/fixtures/histories/child-cancel-default.history.json +234 -0
- package/fixtures/histories/child-cancel-request.history.json +206 -0
- package/fixtures/histories/child-durable-child-side.history.json +124 -0
- package/fixtures/histories/child-durable-result.history.json +163 -0
- package/fixtures/histories/child-failure-caught.history.json +166 -0
- package/fixtures/histories/child-graph-by-key.history.json +138 -0
- package/fixtures/histories/child-terminated-by-parent-close.history.json +85 -0
- package/fixtures/histories/continue-as-new-first-run.history.json +146 -0
- package/fixtures/histories/continue-as-new-history-limit.history.json +489 -0
- package/fixtures/histories/continue-as-new-last-run.history.json +147 -0
- package/fixtures/histories/continue-as-new-suggested.history.json +352 -0
- package/fixtures/histories/details-and-context.history.json +176 -0
- package/fixtures/histories/deterministic-values.history.json +128 -0
- package/fixtures/histories/engine-builtins.history.json +172 -0
- package/fixtures/histories/gather-activities.history.json +291 -0
- package/fixtures/histories/gather-all-settled.history.json +219 -0
- package/fixtures/histories/gather-mixed.history.json +281 -0
- package/fixtures/histories/input-during-running-task.history.json +111 -0
- package/fixtures/histories/nondeterminism-reordered.history.json +218 -0
- package/fixtures/histories/patched-deprecated.history.json +271 -0
- package/fixtures/histories/patched-new-run.history.json +270 -0
- package/fixtures/histories/patched-old-history.history.json +190 -0
- package/fixtures/histories/race-activity-wins.history.json +152 -0
- package/fixtures/histories/race-signal-beats-child.history.json +161 -0
- package/fixtures/histories/race-timeout-wins.history.json +118 -0
- package/fixtures/histories/side-effect-recorded.history.json +153 -0
- package/fixtures/histories/signal-buffered-at-start.history.json +157 -0
- package/fixtures/histories/signal-default-handler.history.json +187 -0
- package/fixtures/histories/signal-handler.history.json +191 -0
- package/fixtures/histories/signal-parent-child-side.history.json +132 -0
- package/fixtures/histories/signal-parent.history.json +202 -0
- package/fixtures/histories/signal-run-delivered.history.json +128 -0
- package/fixtures/histories/signal-run-undelivered.history.json +134 -0
- package/fixtures/histories/signal-wait.history.json +111 -0
- package/fixtures/histories/task-failure-code-error.history.json +137 -0
- package/fixtures/histories/task-failure-then-transient-attempt.history.json +140 -0
- package/fixtures/histories/timer-condition-met.history.json +201 -0
- package/fixtures/histories/timer-condition-timeout.history.json +118 -0
- package/fixtures/histories/timer-sleep-once.history.json +118 -0
- package/fixtures/histories/timer-sleep-sequence.history.json +210 -0
- package/fixtures/histories/update-accepted-completed.history.json +149 -0
- package/fixtures/histories/update-with-start-rejected-validation.history.json +91 -0
- package/package.json +44 -5
- package/schema/history-file.schema.json +2544 -0
- package/schema/ir-v2.schema.json +16 -0
- package/schema/run-event.schema.json +2374 -0
- package/schema/workflow-command.schema.json +1095 -0
- package/schema/workflow-task.schema.json +3560 -0
- package/dist/chunk-MBBKGD7K.js +0 -440
- package/dist/chunk-Y7NWR3WH.cjs +0 -450
- package/dist/lower-CXggJTIo.d.ts +0 -67
- package/dist/lower-DAzvsJCo.d.cts +0 -67
- package/dist/node-evaluator-CH3XCJND.cjs +0 -17
- package/dist/node-evaluator-TULXA3WE.js +0 -8
|
@@ -0,0 +1,1057 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkJD2VQIDZ_cjs = require('../chunk-JD2VQIDZ.cjs');
|
|
4
|
+
var chunkSAOBPYYJ_cjs = require('../chunk-SAOBPYYJ.cjs');
|
|
5
|
+
var chunkGBQ33SIZ_cjs = require('../chunk-GBQ33SIZ.cjs');
|
|
6
|
+
require('../chunk-G4UEPUGO.cjs');
|
|
7
|
+
require('../chunk-GVTZMA5C.cjs');
|
|
8
|
+
require('../chunk-QKZL3ONM.cjs');
|
|
9
|
+
require('../chunk-TBDXTV22.cjs');
|
|
10
|
+
var chunkPZCVF76Q_cjs = require('../chunk-PZCVF76Q.cjs');
|
|
11
|
+
var chunkSAENRNBJ_cjs = require('../chunk-SAENRNBJ.cjs');
|
|
12
|
+
var chunkM4BBAERY_cjs = require('../chunk-M4BBAERY.cjs');
|
|
13
|
+
var chunkFMSW2HDF_cjs = require('../chunk-FMSW2HDF.cjs');
|
|
14
|
+
require('../chunk-FR7KYWRO.cjs');
|
|
15
|
+
var chunkL24YIZY7_cjs = require('../chunk-L24YIZY7.cjs');
|
|
16
|
+
var chunkDHTLALBL_cjs = require('../chunk-DHTLALBL.cjs');
|
|
17
|
+
var chunkP6TA5EZT_cjs = require('../chunk-P6TA5EZT.cjs');
|
|
18
|
+
var chunk623FVQSN_cjs = require('../chunk-623FVQSN.cjs');
|
|
19
|
+
require('../chunk-2J6TF7YI.cjs');
|
|
20
|
+
require('../chunk-Z2Y65YOY.cjs');
|
|
21
|
+
var vm = require('vm');
|
|
22
|
+
|
|
23
|
+
// src/testing/host.ts
|
|
24
|
+
function drainJobs() {
|
|
25
|
+
return new Promise((resolve) => setImmediate(resolve));
|
|
26
|
+
}
|
|
27
|
+
function registryOf(workflows) {
|
|
28
|
+
return chunkM4BBAERY_cjs.buildWorkflowRegistry([["in-process", { ...workflows }]]);
|
|
29
|
+
}
|
|
30
|
+
async function hostLoop(abi, requestJson) {
|
|
31
|
+
abi.start(requestJson);
|
|
32
|
+
await drainJobs();
|
|
33
|
+
while (abi.step() === "more") await drainJobs();
|
|
34
|
+
await drainJobs();
|
|
35
|
+
return abi.complete();
|
|
36
|
+
}
|
|
37
|
+
async function runWorkflowTaskInProcess(registry, request) {
|
|
38
|
+
const abi = chunkM4BBAERY_cjs.createWorkflowRuntime(registry);
|
|
39
|
+
const json = await hostLoop(abi, JSON.stringify(request));
|
|
40
|
+
return JSON.parse(json);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// src/testing/environment.ts
|
|
44
|
+
var WorkflowFailedError = class extends Error {
|
|
45
|
+
constructor(runId, failure) {
|
|
46
|
+
super(`Run ${runId} failed: ${failure.message}`);
|
|
47
|
+
this.runId = runId;
|
|
48
|
+
this.failure = failure;
|
|
49
|
+
this.name = "WorkflowFailedError";
|
|
50
|
+
Object.defineProperty(this, "cause", { value: chunkFMSW2HDF_cjs.failureToError(failure), enumerable: false });
|
|
51
|
+
}
|
|
52
|
+
runId;
|
|
53
|
+
failure;
|
|
54
|
+
};
|
|
55
|
+
var WorkflowTaskFailedError = class extends Error {
|
|
56
|
+
constructor(runId, result) {
|
|
57
|
+
super(
|
|
58
|
+
`A workflow task of run ${runId} failed (${result.failure?.cause ?? "INTERNAL"}): ${result.failure?.message ?? ""}`
|
|
59
|
+
);
|
|
60
|
+
this.runId = runId;
|
|
61
|
+
this.result = result;
|
|
62
|
+
this.name = "WorkflowTaskFailedError";
|
|
63
|
+
}
|
|
64
|
+
runId;
|
|
65
|
+
result;
|
|
66
|
+
};
|
|
67
|
+
var RunBlockedError = class extends Error {
|
|
68
|
+
constructor(runId) {
|
|
69
|
+
super(`Run ${runId} is waiting (for a signal, a timer without time skipping, or a child)`);
|
|
70
|
+
this.runId = runId;
|
|
71
|
+
this.name = "RunBlockedError";
|
|
72
|
+
}
|
|
73
|
+
runId;
|
|
74
|
+
};
|
|
75
|
+
var TestRunHandle = class {
|
|
76
|
+
constructor(env, runId) {
|
|
77
|
+
this.env = env;
|
|
78
|
+
this.runId = runId;
|
|
79
|
+
}
|
|
80
|
+
env;
|
|
81
|
+
runId;
|
|
82
|
+
/** The run's history so far. */
|
|
83
|
+
history() {
|
|
84
|
+
return this.env.historyOf(this.runId);
|
|
85
|
+
}
|
|
86
|
+
/** The history as a history file. */
|
|
87
|
+
historyFile(extra = {}) {
|
|
88
|
+
return this.env.historyFileOf(this.runId, extra);
|
|
89
|
+
}
|
|
90
|
+
/** Deliver a signal and run until idle (without skipping time). */
|
|
91
|
+
async signal(signal, payload) {
|
|
92
|
+
this.env.deliverSignal(this.runId, chunkFMSW2HDF_cjs.signalName(signal), payload);
|
|
93
|
+
await this.env.runUntilIdle(false);
|
|
94
|
+
}
|
|
95
|
+
/** Request cancellation and run until idle (without skipping time). */
|
|
96
|
+
async cancel(reason) {
|
|
97
|
+
this.env.requestCancel(this.runId, reason);
|
|
98
|
+
await this.env.runUntilIdle(false);
|
|
99
|
+
}
|
|
100
|
+
/** Terminate the run without running code. */
|
|
101
|
+
async terminate(reason = "terminated") {
|
|
102
|
+
this.env.terminate(this.runId, reason);
|
|
103
|
+
await this.env.runUntilIdle(false);
|
|
104
|
+
}
|
|
105
|
+
/** Run until this run (or the last run of its continue-as-new chain) ends; its output. */
|
|
106
|
+
async result() {
|
|
107
|
+
return this.env.resultOf(this.runId);
|
|
108
|
+
}
|
|
109
|
+
/** The projected state of the run. */
|
|
110
|
+
state() {
|
|
111
|
+
return this.env.stateOf(this.runId);
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
function toDate(value) {
|
|
115
|
+
if (value === void 0) return Date.parse("2026-01-01T00:00:00.000Z");
|
|
116
|
+
if (value instanceof Date) return value.getTime();
|
|
117
|
+
return typeof value === "number" ? value : Date.parse(value);
|
|
118
|
+
}
|
|
119
|
+
function toJsonValue(value) {
|
|
120
|
+
if (value === void 0) return null;
|
|
121
|
+
const text = JSON.stringify(value);
|
|
122
|
+
return text === void 0 ? null : JSON.parse(text);
|
|
123
|
+
}
|
|
124
|
+
var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
|
|
125
|
+
clock;
|
|
126
|
+
timeSkipping;
|
|
127
|
+
enabled;
|
|
128
|
+
workspaceId;
|
|
129
|
+
runs = /* @__PURE__ */ new Map();
|
|
130
|
+
runOrder = [];
|
|
131
|
+
mocks = /* @__PURE__ */ new Map();
|
|
132
|
+
childMocks = /* @__PURE__ */ new Map();
|
|
133
|
+
workflows;
|
|
134
|
+
registry;
|
|
135
|
+
attempts = [];
|
|
136
|
+
timers = [];
|
|
137
|
+
mockedChildren = [];
|
|
138
|
+
runCounter = 0;
|
|
139
|
+
executor;
|
|
140
|
+
workflowKeys;
|
|
141
|
+
constructor(options) {
|
|
142
|
+
this.clock = toDate(options.startTime);
|
|
143
|
+
this.timeSkipping = options.timeSkipping !== false;
|
|
144
|
+
this.enabled = options.enabled ?? "parity";
|
|
145
|
+
this.workspaceId = options.workspaceId ?? "test-workspace";
|
|
146
|
+
this.workflows = { ...options.workflows ?? {} };
|
|
147
|
+
this.registry = registryOf(this.workflows);
|
|
148
|
+
this.executor = options.taskExecutor;
|
|
149
|
+
this.workflowKeys = { ...options.workflowKeys ?? {} };
|
|
150
|
+
}
|
|
151
|
+
/** Create an environment. */
|
|
152
|
+
static async create(options = {}) {
|
|
153
|
+
return new _TestWorkflowEnvironment(options);
|
|
154
|
+
}
|
|
155
|
+
/** The fake clock, in epoch milliseconds. */
|
|
156
|
+
now() {
|
|
157
|
+
return this.clock;
|
|
158
|
+
}
|
|
159
|
+
/** Mock a node operation (by node type, and operation when given). */
|
|
160
|
+
mock(operation, impl, operationName) {
|
|
161
|
+
const nodeType = typeof operation === "string" ? operation : operation.nodeType;
|
|
162
|
+
const op = operationName ?? (typeof operation === "string" ? void 0 : operation.operation);
|
|
163
|
+
this.mocks.set(
|
|
164
|
+
op === void 0 ? nodeType : `${nodeType}#${op}`,
|
|
165
|
+
impl
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
/** Mock a child workflow started by key (graph or durable). */
|
|
169
|
+
mockChild(workflowKey, impl) {
|
|
170
|
+
this.childMocks.set(workflowKey, impl);
|
|
171
|
+
}
|
|
172
|
+
/** Start a run of a tier-2 function (by declaration, or by export name with a task executor). */
|
|
173
|
+
async start(definition, options = {}) {
|
|
174
|
+
const workflowFn = typeof definition === "string" ? definition : (
|
|
175
|
+
// A declared workflow is stored by its declaration; its input type is the caller's.
|
|
176
|
+
this.ensureRegistered(definition)
|
|
177
|
+
);
|
|
178
|
+
const runId = this.createRun(
|
|
179
|
+
workflowFn,
|
|
180
|
+
toJsonValue(options.input),
|
|
181
|
+
{ ...options.info, ...options.runId !== void 0 ? { runId: options.runId } : {} },
|
|
182
|
+
void 0
|
|
183
|
+
);
|
|
184
|
+
for (const signal of options.signals ?? [])
|
|
185
|
+
this.deliverSignal(runId, signal.name, signal.payload);
|
|
186
|
+
await this.runUntilIdle(false);
|
|
187
|
+
return new TestRunHandle(this, runId);
|
|
188
|
+
}
|
|
189
|
+
/** Advance the clock by `duration`, firing timers and running work as it comes due. */
|
|
190
|
+
async sleep(duration) {
|
|
191
|
+
const until = this.clock + chunkFMSW2HDF_cjs.toMs(duration, "sleep duration");
|
|
192
|
+
await this.runUntilIdle(false);
|
|
193
|
+
for (; ; ) {
|
|
194
|
+
const next = this.nextDue();
|
|
195
|
+
if (next === void 0 || next > until) break;
|
|
196
|
+
this.clock = Math.max(this.clock, next);
|
|
197
|
+
this.fireDue();
|
|
198
|
+
await this.runUntilIdle(false);
|
|
199
|
+
}
|
|
200
|
+
this.clock = until;
|
|
201
|
+
this.fireDue();
|
|
202
|
+
await this.runUntilIdle(false);
|
|
203
|
+
}
|
|
204
|
+
/** Every run, in start order. */
|
|
205
|
+
runIds() {
|
|
206
|
+
return [...this.runOrder];
|
|
207
|
+
}
|
|
208
|
+
historyOf(runId) {
|
|
209
|
+
return this.run(runId).history.map((event) => ({ ...event, payload: { ...event.payload } }));
|
|
210
|
+
}
|
|
211
|
+
historyFileOf(runId, extra = {}) {
|
|
212
|
+
const run = this.run(runId);
|
|
213
|
+
return chunkPZCVF76Q_cjs.createHistoryFile(run.workflowFn, this.historyOf(runId), {
|
|
214
|
+
info: { ...run.info },
|
|
215
|
+
...extra
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
/** The failed workflow task that stopped a run, if one did. */
|
|
219
|
+
taskFailureOf(runId) {
|
|
220
|
+
return this.run(runId).taskFailure;
|
|
221
|
+
}
|
|
222
|
+
stateOf(runId) {
|
|
223
|
+
return this.run(runId).state;
|
|
224
|
+
}
|
|
225
|
+
// ---- runs -------------------------------------------------------------------
|
|
226
|
+
ensureRegistered(definition) {
|
|
227
|
+
for (const [name2, value] of Object.entries(this.workflows))
|
|
228
|
+
if (value === definition) return name2;
|
|
229
|
+
const name = Object.hasOwn(this.workflows, definition.name) ? `${definition.name}#${this.runCounter}` : definition.name;
|
|
230
|
+
this.workflows[name] = definition;
|
|
231
|
+
this.registry = registryOf(this.workflows);
|
|
232
|
+
return name;
|
|
233
|
+
}
|
|
234
|
+
run(runId) {
|
|
235
|
+
const run = this.runs.get(runId);
|
|
236
|
+
if (!run) throw new Error(`Unknown run ${runId}`);
|
|
237
|
+
return run;
|
|
238
|
+
}
|
|
239
|
+
createRun(workflowFn, input, info, parent, extraStarted = {}) {
|
|
240
|
+
this.runCounter += 1;
|
|
241
|
+
const runId = info.runId ?? `run-${this.runCounter}`;
|
|
242
|
+
const snapshot = {
|
|
243
|
+
workspaceId: this.workspaceId,
|
|
244
|
+
taskQueue: "managed",
|
|
245
|
+
attempt: 1,
|
|
246
|
+
startedAt: new Date(this.clock).toISOString(),
|
|
247
|
+
runStartedAt: new Date(this.clock).toISOString(),
|
|
248
|
+
...info,
|
|
249
|
+
runId,
|
|
250
|
+
...parent ? { parentRunId: parent.runId } : {}
|
|
251
|
+
};
|
|
252
|
+
const run = {
|
|
253
|
+
runId,
|
|
254
|
+
workflowFn,
|
|
255
|
+
history: [],
|
|
256
|
+
state: chunkP6TA5EZT_cjs.initialRunState(),
|
|
257
|
+
taskCounter: 0,
|
|
258
|
+
info: snapshot,
|
|
259
|
+
children: /* @__PURE__ */ new Map(),
|
|
260
|
+
...parent ? { parent } : {}
|
|
261
|
+
};
|
|
262
|
+
this.runs.set(runId, run);
|
|
263
|
+
this.runOrder.push(runId);
|
|
264
|
+
this.append(run, {
|
|
265
|
+
type: "WORKFLOW_STARTED",
|
|
266
|
+
// The service writes the seed of ctx.random()/ctx.uuid() into the start
|
|
267
|
+
// event so the history replays on its own; it uses the run id.
|
|
268
|
+
payload: { workflowFn, input, taskQueue: "managed", randomnessSeed: runId, ...extraStarted }
|
|
269
|
+
});
|
|
270
|
+
return runId;
|
|
271
|
+
}
|
|
272
|
+
append(run, draft) {
|
|
273
|
+
const event = {
|
|
274
|
+
seq: run.history.length + 1,
|
|
275
|
+
type: draft.type,
|
|
276
|
+
payload: draft.payload,
|
|
277
|
+
createdAt: new Date(this.clock).toISOString(),
|
|
278
|
+
...draft.taskId !== void 0 ? { taskId: draft.taskId } : {}
|
|
279
|
+
};
|
|
280
|
+
chunkP6TA5EZT_cjs.applyEvent(run.state, event);
|
|
281
|
+
run.history.push(event);
|
|
282
|
+
if (chunk623FVQSN_cjs.isInputEvent(event) && run.state.status === "RUNNING" && run.state.task.state === "NONE") {
|
|
283
|
+
run.taskCounter += 1;
|
|
284
|
+
const taskId = `${run.runId}-task-${run.taskCounter}`;
|
|
285
|
+
const scheduled = {
|
|
286
|
+
seq: run.history.length + 1,
|
|
287
|
+
type: "WORKFLOW_TASK_SCHEDULED",
|
|
288
|
+
payload: { taskId },
|
|
289
|
+
createdAt: event.createdAt
|
|
290
|
+
};
|
|
291
|
+
chunkP6TA5EZT_cjs.applyEvent(run.state, scheduled);
|
|
292
|
+
run.history.push(scheduled);
|
|
293
|
+
}
|
|
294
|
+
return event;
|
|
295
|
+
}
|
|
296
|
+
deliverSignal(runId, name, payload) {
|
|
297
|
+
const run = this.runs.get(runId);
|
|
298
|
+
if (!run || run.state.status !== "RUNNING") return false;
|
|
299
|
+
this.append(run, {
|
|
300
|
+
type: "SIGNAL_RECEIVED",
|
|
301
|
+
payload: {
|
|
302
|
+
name,
|
|
303
|
+
payload: toJsonValue(payload),
|
|
304
|
+
receivedAt: new Date(this.clock).toISOString()
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
return true;
|
|
308
|
+
}
|
|
309
|
+
requestCancel(runId, reason) {
|
|
310
|
+
const run = this.runs.get(runId);
|
|
311
|
+
if (!run || run.state.status !== "RUNNING") return false;
|
|
312
|
+
if (run.state.cancelRequested) return true;
|
|
313
|
+
this.append(run, {
|
|
314
|
+
type: "WORKFLOW_CANCEL_REQUESTED",
|
|
315
|
+
payload: reason !== void 0 ? { reason } : {}
|
|
316
|
+
});
|
|
317
|
+
return true;
|
|
318
|
+
}
|
|
319
|
+
terminate(runId, reason) {
|
|
320
|
+
const run = this.runs.get(runId);
|
|
321
|
+
if (!run || run.state.status !== "RUNNING") return;
|
|
322
|
+
this.append(run, { type: "WORKFLOW_TERMINATED", payload: { reason } });
|
|
323
|
+
this.onClosed(run);
|
|
324
|
+
}
|
|
325
|
+
// ---- the loop ---------------------------------------------------------------
|
|
326
|
+
/** Run every runnable task and activity; skip time when allowed and idle. */
|
|
327
|
+
async runUntilIdle(skipTime) {
|
|
328
|
+
for (let guard = 0; guard < 1e5; guard++) {
|
|
329
|
+
const task = this.runOrder.map((id) => this.runs.get(id)).find(
|
|
330
|
+
(run) => run !== void 0 && !run.taskFailure && run.state.status === "RUNNING" && run.state.task.state === "SCHEDULED"
|
|
331
|
+
);
|
|
332
|
+
if (task) {
|
|
333
|
+
await this.runTask(task);
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
const attempt = this.attempts.find((entry) => entry.dueAt <= this.clock);
|
|
337
|
+
if (attempt) {
|
|
338
|
+
this.attempts = this.attempts.filter((entry) => entry !== attempt);
|
|
339
|
+
await this.runAttempt(attempt);
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
const mocked = this.mockedChildren.shift();
|
|
343
|
+
if (mocked) {
|
|
344
|
+
await this.runMockedChild(mocked);
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
if (this.fireDue()) continue;
|
|
348
|
+
if (!skipTime) return;
|
|
349
|
+
const next = this.nextDue();
|
|
350
|
+
if (next === void 0) return;
|
|
351
|
+
this.clock = Math.max(this.clock, next);
|
|
352
|
+
}
|
|
353
|
+
throw new Error("The test environment did not settle after 100000 steps");
|
|
354
|
+
}
|
|
355
|
+
nextDue() {
|
|
356
|
+
const due = [
|
|
357
|
+
...this.timers.map((timer) => timer.fireAt),
|
|
358
|
+
...this.attempts.map((entry) => entry.dueAt)
|
|
359
|
+
];
|
|
360
|
+
return due.length === 0 ? void 0 : Math.min(...due);
|
|
361
|
+
}
|
|
362
|
+
fireDue() {
|
|
363
|
+
const due = this.timers.filter((timer) => timer.fireAt <= this.clock).sort((a, b) => a.fireAt - b.fireAt);
|
|
364
|
+
if (due.length === 0) return false;
|
|
365
|
+
this.timers = this.timers.filter((timer) => !due.includes(timer));
|
|
366
|
+
for (const timer of due) {
|
|
367
|
+
const run = this.runs.get(timer.runId);
|
|
368
|
+
if (!run || run.state.status !== "RUNNING" || run.state.timers[timer.timerId]?.status !== "PENDING")
|
|
369
|
+
continue;
|
|
370
|
+
this.append(run, { type: "TIMER_FIRED", payload: { timerId: timer.timerId } });
|
|
371
|
+
}
|
|
372
|
+
return true;
|
|
373
|
+
}
|
|
374
|
+
async runTask(run) {
|
|
375
|
+
const taskId = run.state.task.taskId ?? `${run.runId}-task-${run.taskCounter}`;
|
|
376
|
+
this.append(run, { type: "WORKFLOW_TASK_STARTED", payload: { taskId, attempt: 1 } });
|
|
377
|
+
const request = {
|
|
378
|
+
protocol: chunk623FVQSN_cjs.HISTORY_PROTOCOL,
|
|
379
|
+
kind: "task",
|
|
380
|
+
workflowFn: run.workflowFn,
|
|
381
|
+
info: run.info,
|
|
382
|
+
history: run.history,
|
|
383
|
+
nowMs: this.clock
|
|
384
|
+
};
|
|
385
|
+
const result = this.executor ? await this.executor(request) : await runWorkflowTaskInProcess(this.registry, request);
|
|
386
|
+
const fail = (cause, message) => {
|
|
387
|
+
this.append(run, {
|
|
388
|
+
type: "WORKFLOW_TASK_FAILED",
|
|
389
|
+
payload: { taskId, attempt: 1, cause, message }
|
|
390
|
+
});
|
|
391
|
+
run.taskFailure = { ...result, outcome: "failed", commands: [], failure: { cause, message } };
|
|
392
|
+
};
|
|
393
|
+
if (result.outcome !== "commands") {
|
|
394
|
+
fail(result.failure?.cause ?? "INTERNAL", result.failure?.message ?? "The task failed");
|
|
395
|
+
run.taskFailure = result;
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
const checked = chunkPZCVF76Q_cjs.validateCommands(result.commands, { enabled: this.enabled });
|
|
399
|
+
if (!checked.ok) {
|
|
400
|
+
fail(checked.cause, checked.issues.map((issue) => issue.message).join("; "));
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
const drafts = chunkP6TA5EZT_cjs.eventsForCommands(run.state, checked.commands, {
|
|
404
|
+
taskId,
|
|
405
|
+
nowMs: this.clock,
|
|
406
|
+
enabled: this.enabled
|
|
407
|
+
});
|
|
408
|
+
if (!drafts.ok) {
|
|
409
|
+
fail(drafts.cause, drafts.issues.map((issue) => issue.message).join("; "));
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
this.append(run, { type: "WORKFLOW_TASK_COMPLETED", taskId, payload: { taskId } });
|
|
413
|
+
for (const draft of drafts.events) this.append(run, draft);
|
|
414
|
+
for (const command of checked.commands)
|
|
415
|
+
if (run.state.status === "RUNNING" || chunk623FVQSN_cjs.TERMINAL_COMMANDS.has(command.type))
|
|
416
|
+
this.afterCommand(run, command);
|
|
417
|
+
}
|
|
418
|
+
afterCommand(run, command) {
|
|
419
|
+
switch (command.type) {
|
|
420
|
+
case "SCHEDULE_ACTIVITY": {
|
|
421
|
+
const attempt = {
|
|
422
|
+
runId: run.runId,
|
|
423
|
+
activityId: command.activityId,
|
|
424
|
+
attempt: 1,
|
|
425
|
+
dueAt: this.clock,
|
|
426
|
+
scheduledAt: this.clock,
|
|
427
|
+
args: command.args,
|
|
428
|
+
nodeType: command.nodeType,
|
|
429
|
+
operation: command.operation,
|
|
430
|
+
retry: command.retry,
|
|
431
|
+
cancelRequested: false,
|
|
432
|
+
...command.connection !== void 0 ? { connection: command.connection } : {},
|
|
433
|
+
...command.scheduleToCloseTimeout !== void 0 ? { scheduleToClose: command.scheduleToCloseTimeout } : {}
|
|
434
|
+
};
|
|
435
|
+
this.attempts.push(attempt);
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
case "REQUEST_CANCEL_ACTIVITY": {
|
|
439
|
+
const pending = this.attempts.find(
|
|
440
|
+
(entry) => entry.runId === run.runId && entry.activityId === command.activityId
|
|
441
|
+
);
|
|
442
|
+
if (pending) {
|
|
443
|
+
this.attempts = this.attempts.filter((entry) => entry !== pending);
|
|
444
|
+
this.append(run, {
|
|
445
|
+
type: "ACTIVITY_CANCELLED",
|
|
446
|
+
payload: { activityId: command.activityId }
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
case "START_TIMER":
|
|
452
|
+
this.timers.push({
|
|
453
|
+
runId: run.runId,
|
|
454
|
+
timerId: command.timerId,
|
|
455
|
+
fireAt: this.clock + command.durationMs
|
|
456
|
+
});
|
|
457
|
+
return;
|
|
458
|
+
case "CANCEL_TIMER":
|
|
459
|
+
this.timers = this.timers.filter(
|
|
460
|
+
(timer) => !(timer.runId === run.runId && timer.timerId === command.timerId)
|
|
461
|
+
);
|
|
462
|
+
return;
|
|
463
|
+
case "START_CHILD_RUN":
|
|
464
|
+
this.startChild(run, command);
|
|
465
|
+
return;
|
|
466
|
+
case "SIGNAL_RUN": {
|
|
467
|
+
const target = this.resolveTarget(run, command.target);
|
|
468
|
+
const ok = target !== void 0 && this.deliverSignal(target, command.name, command.payload);
|
|
469
|
+
this.append(run, {
|
|
470
|
+
type: "EXTERNAL_RUN_SIGNALED",
|
|
471
|
+
payload: {
|
|
472
|
+
externalId: command.externalId,
|
|
473
|
+
resolved: true,
|
|
474
|
+
ok,
|
|
475
|
+
...target !== void 0 ? { targetRunId: target } : {},
|
|
476
|
+
...ok ? {} : {
|
|
477
|
+
failure: {
|
|
478
|
+
errorType: "VALIDATION",
|
|
479
|
+
message: "The target run is not open",
|
|
480
|
+
type: "ServerFailure"
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
});
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
case "REQUEST_CANCEL_EXTERNAL_RUN": {
|
|
488
|
+
const target = this.resolveTarget(run, command.target);
|
|
489
|
+
const ok = target !== void 0 && this.requestCancel(target);
|
|
490
|
+
this.append(run, {
|
|
491
|
+
type: "EXTERNAL_RUN_CANCEL_RESOLVED",
|
|
492
|
+
payload: {
|
|
493
|
+
externalId: command.externalId,
|
|
494
|
+
ok,
|
|
495
|
+
...target !== void 0 ? { targetRunId: target } : {},
|
|
496
|
+
...ok ? {} : {
|
|
497
|
+
failure: {
|
|
498
|
+
errorType: "VALIDATION",
|
|
499
|
+
message: "The target run is not open",
|
|
500
|
+
type: "ServerFailure"
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
});
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
case "CONTINUE_AS_NEW": {
|
|
508
|
+
const next = this.createRun(
|
|
509
|
+
run.workflowFn,
|
|
510
|
+
command.input,
|
|
511
|
+
{
|
|
512
|
+
...run.info,
|
|
513
|
+
runId: void 0,
|
|
514
|
+
continuedFromRunId: run.runId,
|
|
515
|
+
firstRunId: run.info.firstRunId ?? run.runId
|
|
516
|
+
},
|
|
517
|
+
run.parent
|
|
518
|
+
);
|
|
519
|
+
run.continuedTo = next;
|
|
520
|
+
this.onClosed(run);
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
case "COMPLETE_WORKFLOW":
|
|
524
|
+
case "FAIL_WORKFLOW":
|
|
525
|
+
case "CANCEL_WORKFLOW":
|
|
526
|
+
this.onClosed(run);
|
|
527
|
+
return;
|
|
528
|
+
default:
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
resolveTarget(run, target) {
|
|
533
|
+
if ("runId" in target) return target.runId;
|
|
534
|
+
if ("parent" in target) return run.parent?.runId ?? run.info.parentRunId ?? void 0;
|
|
535
|
+
if ("childId" in target) return run.children.get(target.childId);
|
|
536
|
+
return void 0;
|
|
537
|
+
}
|
|
538
|
+
startChild(run, command) {
|
|
539
|
+
const byFn = command.workflowFn !== void 0 && (this.executor !== void 0 || this.registry.byName.has(command.workflowFn));
|
|
540
|
+
const keyed = command.workflowKey === void 0 ? void 0 : this.workflowKeys[command.workflowKey] ?? Object.entries(this.workflows).find(
|
|
541
|
+
([, definition]) => definition.name === command.workflowKey
|
|
542
|
+
)?.[0];
|
|
543
|
+
const mock = command.workflowKey !== void 0 ? this.childMocks.get(command.workflowKey) : void 0;
|
|
544
|
+
if (mock) {
|
|
545
|
+
run.children.set(command.childId, void 0);
|
|
546
|
+
this.mockedChildren.push({
|
|
547
|
+
parentRunId: run.runId,
|
|
548
|
+
childId: command.childId,
|
|
549
|
+
input: command.input,
|
|
550
|
+
key: command.workflowKey ?? ""
|
|
551
|
+
});
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
const workflowFn = byFn ? command.workflowFn : keyed;
|
|
555
|
+
if (workflowFn === void 0) {
|
|
556
|
+
this.append(run, {
|
|
557
|
+
type: "CHILD_RUN_FAILED",
|
|
558
|
+
payload: {
|
|
559
|
+
childId: command.childId,
|
|
560
|
+
failure: {
|
|
561
|
+
errorType: "VALIDATION",
|
|
562
|
+
message: `No workflow "${command.workflowFn ?? command.workflowKey ?? ""}" in the test environment (register it or mockChild it)`,
|
|
563
|
+
type: "ServerFailure"
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
});
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
const childRunId = this.createRun(
|
|
570
|
+
workflowFn,
|
|
571
|
+
command.input,
|
|
572
|
+
{ rootRunId: run.info.rootRunId ?? run.runId },
|
|
573
|
+
{ runId: run.runId, childId: command.childId },
|
|
574
|
+
{
|
|
575
|
+
parentRunId: run.runId,
|
|
576
|
+
parentClosePolicy: command.options.parentClosePolicy
|
|
577
|
+
}
|
|
578
|
+
);
|
|
579
|
+
run.children.set(command.childId, childRunId);
|
|
580
|
+
this.append(run, {
|
|
581
|
+
type: "CHILD_RUN_STARTED",
|
|
582
|
+
payload: { childId: command.childId, childRunId, executionKind: "DURABLE_FN" }
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
async runMockedChild(entry) {
|
|
586
|
+
const parent = this.runs.get(entry.parentRunId);
|
|
587
|
+
const mock = this.childMocks.get(entry.key);
|
|
588
|
+
if (!parent || !mock || parent.state.status !== "RUNNING") return;
|
|
589
|
+
const childRunId = `${entry.parentRunId}-${entry.childId}`;
|
|
590
|
+
this.append(parent, {
|
|
591
|
+
type: "CHILD_RUN_STARTED",
|
|
592
|
+
payload: { childId: entry.childId, childRunId, executionKind: "GRAPH" }
|
|
593
|
+
});
|
|
594
|
+
try {
|
|
595
|
+
const result = toJsonValue(
|
|
596
|
+
await mock(entry.input, { runId: entry.parentRunId, childId: entry.childId })
|
|
597
|
+
);
|
|
598
|
+
if (parent.state.status === "RUNNING")
|
|
599
|
+
this.append(parent, {
|
|
600
|
+
type: "CHILD_RUN_COMPLETED",
|
|
601
|
+
payload: { childId: entry.childId, childRunId, result }
|
|
602
|
+
});
|
|
603
|
+
} catch (error) {
|
|
604
|
+
if (parent.state.status === "RUNNING")
|
|
605
|
+
this.append(parent, {
|
|
606
|
+
type: "CHILD_RUN_FAILED",
|
|
607
|
+
payload: {
|
|
608
|
+
childId: entry.childId,
|
|
609
|
+
childRunId,
|
|
610
|
+
failure: toJsonValue(chunkFMSW2HDF_cjs.errorToFailure(error, "engine"))
|
|
611
|
+
}
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
async runAttempt(attempt) {
|
|
616
|
+
const run = this.runs.get(attempt.runId);
|
|
617
|
+
if (!run || run.state.status !== "RUNNING") return;
|
|
618
|
+
const activity = run.state.activities[attempt.activityId];
|
|
619
|
+
if (!activity || !chunkP6TA5EZT_cjs.isActivityOpen(activity)) return;
|
|
620
|
+
if (attempt.scheduleToClose !== void 0 && this.clock - attempt.scheduledAt > attempt.scheduleToClose) {
|
|
621
|
+
this.append(run, {
|
|
622
|
+
type: "ACTIVITY_TIMED_OUT",
|
|
623
|
+
payload: {
|
|
624
|
+
activityId: attempt.activityId,
|
|
625
|
+
attempt: Math.max(1, attempt.attempt - 1),
|
|
626
|
+
timeoutType: "SCHEDULE_TO_CLOSE"
|
|
627
|
+
}
|
|
628
|
+
});
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
const mock = this.mocks.get(`${attempt.nodeType}#${attempt.operation}`) ?? this.mocks.get(attempt.nodeType);
|
|
632
|
+
let failure;
|
|
633
|
+
let result = null;
|
|
634
|
+
try {
|
|
635
|
+
if (!mock)
|
|
636
|
+
throw new chunkFMSW2HDF_cjs.ApplicationError(
|
|
637
|
+
`No mock for ${attempt.nodeType} ${attempt.operation} (use env.mock)`,
|
|
638
|
+
{
|
|
639
|
+
errorType: "VALIDATION",
|
|
640
|
+
nonRetryable: true,
|
|
641
|
+
type: "UnmockedActivity"
|
|
642
|
+
}
|
|
643
|
+
);
|
|
644
|
+
result = toJsonValue(
|
|
645
|
+
await mock(attempt.args, {
|
|
646
|
+
runId: attempt.runId,
|
|
647
|
+
activityId: attempt.activityId,
|
|
648
|
+
attempt: attempt.attempt,
|
|
649
|
+
nodeType: attempt.nodeType,
|
|
650
|
+
operation: attempt.operation,
|
|
651
|
+
...attempt.connection !== void 0 ? { connection: attempt.connection } : {}
|
|
652
|
+
})
|
|
653
|
+
);
|
|
654
|
+
} catch (error) {
|
|
655
|
+
failure = chunkFMSW2HDF_cjs.errorToFailure(error, `node:${attempt.nodeType}`);
|
|
656
|
+
delete failure.stackTrace;
|
|
657
|
+
}
|
|
658
|
+
if (run.state.status !== "RUNNING" || !chunkP6TA5EZT_cjs.isActivityOpen(run.state.activities[attempt.activityId] ?? activity))
|
|
659
|
+
return;
|
|
660
|
+
const started = () => {
|
|
661
|
+
this.append(run, {
|
|
662
|
+
type: "ACTIVITY_STARTED",
|
|
663
|
+
payload: { activityId: attempt.activityId, attempt: attempt.attempt }
|
|
664
|
+
});
|
|
665
|
+
};
|
|
666
|
+
if (!failure) {
|
|
667
|
+
started();
|
|
668
|
+
this.append(run, {
|
|
669
|
+
type: "ACTIVITY_COMPLETED",
|
|
670
|
+
payload: { activityId: attempt.activityId, attempt: attempt.attempt, result }
|
|
671
|
+
});
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
if (failure.timeoutType !== void 0 || failure.type === "TimeoutFailure") {
|
|
675
|
+
const decision2 = chunkP6TA5EZT_cjs.computeRetryDelay(attempt.retry, attempt.attempt, failure);
|
|
676
|
+
if (!decision2.retry) {
|
|
677
|
+
started();
|
|
678
|
+
this.append(run, {
|
|
679
|
+
type: "ACTIVITY_TIMED_OUT",
|
|
680
|
+
payload: {
|
|
681
|
+
activityId: attempt.activityId,
|
|
682
|
+
attempt: attempt.attempt,
|
|
683
|
+
timeoutType: failure.timeoutType ?? "START_TO_CLOSE",
|
|
684
|
+
failure: toJsonValue(failure)
|
|
685
|
+
}
|
|
686
|
+
});
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
this.attempts.push({
|
|
690
|
+
...attempt,
|
|
691
|
+
attempt: attempt.attempt + 1,
|
|
692
|
+
dueAt: this.clock + decision2.delayMs
|
|
693
|
+
});
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
const decision = chunkP6TA5EZT_cjs.computeRetryDelay(attempt.retry, attempt.attempt, failure);
|
|
697
|
+
if (decision.retry) {
|
|
698
|
+
this.attempts.push({
|
|
699
|
+
...attempt,
|
|
700
|
+
attempt: attempt.attempt + 1,
|
|
701
|
+
dueAt: this.clock + decision.delayMs
|
|
702
|
+
});
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
started();
|
|
706
|
+
this.append(run, {
|
|
707
|
+
type: "ACTIVITY_FAILED",
|
|
708
|
+
payload: {
|
|
709
|
+
activityId: attempt.activityId,
|
|
710
|
+
attempt: attempt.attempt,
|
|
711
|
+
failure: toJsonValue(failure)
|
|
712
|
+
}
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
onClosed(run) {
|
|
716
|
+
this.timers = this.timers.filter((timer) => timer.runId !== run.runId);
|
|
717
|
+
this.attempts = this.attempts.filter((entry) => entry.runId !== run.runId);
|
|
718
|
+
for (const [childId2, childRunId] of run.children) {
|
|
719
|
+
const child = run.state.children[childId2];
|
|
720
|
+
if (!child || !chunkP6TA5EZT_cjs.isChildOpen(child) || childRunId === void 0) continue;
|
|
721
|
+
if (child.parentClosePolicy === "TERMINATE") this.terminate(childRunId, "parent closed");
|
|
722
|
+
else if (child.parentClosePolicy === "REQUEST_CANCEL")
|
|
723
|
+
this.requestCancel(childRunId, "parent closed");
|
|
724
|
+
}
|
|
725
|
+
if (!run.parent) return;
|
|
726
|
+
const parent = this.runs.get(run.parent.runId);
|
|
727
|
+
if (!parent || parent.state.status !== "RUNNING") return;
|
|
728
|
+
const childId = run.parent.childId;
|
|
729
|
+
if (!parent.state.children[childId] || !chunkP6TA5EZT_cjs.isChildOpen(parent.state.children[childId])) return;
|
|
730
|
+
const last = run.history[run.history.length - 1];
|
|
731
|
+
if (!last) return;
|
|
732
|
+
if (run.continuedTo !== void 0) {
|
|
733
|
+
const next = this.runs.get(run.continuedTo);
|
|
734
|
+
if (next) {
|
|
735
|
+
next.parent = run.parent;
|
|
736
|
+
parent.children.set(childId, next.runId);
|
|
737
|
+
}
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
const base = { childId, childRunId: run.runId };
|
|
741
|
+
switch (last.type) {
|
|
742
|
+
case "WORKFLOW_COMPLETED":
|
|
743
|
+
this.append(parent, {
|
|
744
|
+
type: "CHILD_RUN_COMPLETED",
|
|
745
|
+
payload: { ...base, result: last.payload["output"] ?? null }
|
|
746
|
+
});
|
|
747
|
+
return;
|
|
748
|
+
case "WORKFLOW_FAILED":
|
|
749
|
+
this.append(parent, {
|
|
750
|
+
type: "CHILD_RUN_FAILED",
|
|
751
|
+
payload: {
|
|
752
|
+
...base,
|
|
753
|
+
failure: last.payload["failure"] ?? { errorType: "UNKNOWN", message: "failed" }
|
|
754
|
+
}
|
|
755
|
+
});
|
|
756
|
+
return;
|
|
757
|
+
case "WORKFLOW_CANCELLED":
|
|
758
|
+
this.append(parent, { type: "CHILD_RUN_CANCELLED", payload: base });
|
|
759
|
+
return;
|
|
760
|
+
case "WORKFLOW_TERMINATED":
|
|
761
|
+
this.append(parent, {
|
|
762
|
+
type: "CHILD_RUN_TERMINATED",
|
|
763
|
+
payload: {
|
|
764
|
+
...base,
|
|
765
|
+
reason: typeof last.payload["reason"] === "string" ? last.payload["reason"] : "terminated"
|
|
766
|
+
}
|
|
767
|
+
});
|
|
768
|
+
return;
|
|
769
|
+
default:
|
|
770
|
+
this.append(parent, {
|
|
771
|
+
type: "CHILD_RUN_TIMED_OUT",
|
|
772
|
+
payload: { ...base, timeoutType: "WORKFLOW_RUN" }
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
async resultOf(runId) {
|
|
777
|
+
await this.runUntilIdle(this.timeSkipping);
|
|
778
|
+
let run = this.run(runId);
|
|
779
|
+
while (run.continuedTo !== void 0) run = this.run(run.continuedTo);
|
|
780
|
+
if (run.taskFailure) throw new WorkflowTaskFailedError(run.runId, run.taskFailure);
|
|
781
|
+
const last = run.history[run.history.length - 1];
|
|
782
|
+
switch (run.state.status) {
|
|
783
|
+
case "COMPLETED":
|
|
784
|
+
return run.state.output ?? null;
|
|
785
|
+
case "FAILED":
|
|
786
|
+
throw new WorkflowFailedError(
|
|
787
|
+
run.runId,
|
|
788
|
+
run.state.failure ?? { errorType: "UNKNOWN", message: "failed" }
|
|
789
|
+
);
|
|
790
|
+
case "CANCELLED":
|
|
791
|
+
throw new chunkFMSW2HDF_cjs.CancelledFailure(`Run ${run.runId} was cancelled`);
|
|
792
|
+
case "TERMINATED":
|
|
793
|
+
throw new WorkflowFailedError(run.runId, {
|
|
794
|
+
errorType: "PERMANENT",
|
|
795
|
+
message: typeof last?.payload["reason"] === "string" ? last.payload["reason"] : "terminated",
|
|
796
|
+
type: "TerminatedFailure"
|
|
797
|
+
});
|
|
798
|
+
default:
|
|
799
|
+
throw new RunBlockedError(run.runId);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
/** Every tier-2 function registered, by export name. */
|
|
803
|
+
registered() {
|
|
804
|
+
return { ...this.workflows };
|
|
805
|
+
}
|
|
806
|
+
};
|
|
807
|
+
|
|
808
|
+
// src/testing/replay.ts
|
|
809
|
+
var ReplayError = class extends Error {
|
|
810
|
+
constructor(result) {
|
|
811
|
+
super(
|
|
812
|
+
`Replay failed (${result.failure?.cause ?? "INTERNAL"}): ${result.failure?.message ?? ""}`
|
|
813
|
+
);
|
|
814
|
+
this.result = result;
|
|
815
|
+
this.name = "ReplayError";
|
|
816
|
+
}
|
|
817
|
+
result;
|
|
818
|
+
};
|
|
819
|
+
function replayRequest(workflowFn, history, info = {}, nowMs) {
|
|
820
|
+
const last = history[history.length - 1];
|
|
821
|
+
return {
|
|
822
|
+
protocol: chunk623FVQSN_cjs.HISTORY_PROTOCOL,
|
|
823
|
+
kind: "verify",
|
|
824
|
+
workflowFn,
|
|
825
|
+
info: { runId: "replay", workspaceId: "replay", ...info },
|
|
826
|
+
history,
|
|
827
|
+
nowMs: nowMs ?? (last ? Date.parse(last.createdAt) : 0)
|
|
828
|
+
};
|
|
829
|
+
}
|
|
830
|
+
function eventsOf(input) {
|
|
831
|
+
if (Array.isArray(input)) return { events: input };
|
|
832
|
+
const file = typeof input === "string" ? chunkPZCVF76Q_cjs.parseHistoryFile(input) : input;
|
|
833
|
+
return { events: file.events, file };
|
|
834
|
+
}
|
|
835
|
+
async function replay(workflows, input, options = {}) {
|
|
836
|
+
const { events, file } = eventsOf(input);
|
|
837
|
+
const started = events[0]?.type === "WORKFLOW_STARTED" ? events[0].payload["workflowFn"] : void 0;
|
|
838
|
+
const recordedFn = options.workflowFn ?? file?.workflowFn ?? (typeof started === "string" ? started : void 0);
|
|
839
|
+
const map = chunkDHTLALBL_cjs.isDurableWorkflow(workflows) ? { [recordedFn ?? workflows.name]: workflows } : { ...workflows };
|
|
840
|
+
const workflowFn = recordedFn ?? Object.keys(map)[0] ?? "";
|
|
841
|
+
const result = await runWorkflowTaskInProcess(
|
|
842
|
+
registryOf(map),
|
|
843
|
+
replayRequest(
|
|
844
|
+
workflowFn,
|
|
845
|
+
events,
|
|
846
|
+
{ ...file?.info, ...options.info },
|
|
847
|
+
options.nowMs ?? file?.nowMs
|
|
848
|
+
)
|
|
849
|
+
);
|
|
850
|
+
if (result.outcome === "failed") {
|
|
851
|
+
if (result.failure?.cause === "NON_DETERMINISM")
|
|
852
|
+
throw new chunkFMSW2HDF_cjs.DeterminismViolation(result.failure.message);
|
|
853
|
+
throw new ReplayError(result);
|
|
854
|
+
}
|
|
855
|
+
return result;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
// src/testing/assert.ts
|
|
859
|
+
var HistoryAssertionError = class extends Error {
|
|
860
|
+
constructor(message) {
|
|
861
|
+
super(message);
|
|
862
|
+
this.name = "HistoryAssertionError";
|
|
863
|
+
}
|
|
864
|
+
};
|
|
865
|
+
function contains(actual, expected) {
|
|
866
|
+
if (chunkSAENRNBJ_cjs.isRecord(expected)) {
|
|
867
|
+
if (!chunkSAENRNBJ_cjs.isRecord(actual)) return false;
|
|
868
|
+
return Object.entries(expected).every(([key, value]) => contains(actual[key], value));
|
|
869
|
+
}
|
|
870
|
+
if (Array.isArray(expected)) {
|
|
871
|
+
return Array.isArray(actual) && actual.length === expected.length && expected.every((item, index) => contains(actual[index], item));
|
|
872
|
+
}
|
|
873
|
+
return Object.is(actual, expected);
|
|
874
|
+
}
|
|
875
|
+
function assertHistory(history, expected, options = {}) {
|
|
876
|
+
const ignored = new Set(options.ignore ?? []);
|
|
877
|
+
const events = (Array.isArray(history) ? history : history.history()).filter((event) => !ignored.has(event.type));
|
|
878
|
+
let position = 0;
|
|
879
|
+
for (const [index, want] of expected.entries()) {
|
|
880
|
+
let found = -1;
|
|
881
|
+
for (let cursor = position; cursor < events.length; cursor++) {
|
|
882
|
+
const event = events[cursor];
|
|
883
|
+
if (event && event.type === want.type && (want.payload === void 0 || contains(event.payload, want.payload))) {
|
|
884
|
+
found = cursor;
|
|
885
|
+
break;
|
|
886
|
+
}
|
|
887
|
+
if (options.exact) break;
|
|
888
|
+
}
|
|
889
|
+
if (found < 0)
|
|
890
|
+
throw new HistoryAssertionError(
|
|
891
|
+
`Expected event ${index + 1} (${want.type}${want.payload ? ` ${JSON.stringify(want.payload)}` : ""}) after seq ${events[position - 1]?.seq ?? 0}; the history has ${events.slice(position).map((event) => event.type).join(", ") || "nothing more"}`
|
|
892
|
+
);
|
|
893
|
+
position = found + 1;
|
|
894
|
+
}
|
|
895
|
+
if (options.exact && position !== events.length)
|
|
896
|
+
throw new HistoryAssertionError(
|
|
897
|
+
`The history has ${events.length - position} more events than expected`
|
|
898
|
+
);
|
|
899
|
+
}
|
|
900
|
+
function definitionOf(value) {
|
|
901
|
+
return chunkGBQ33SIZ_cjs.normalizeDefinition(chunkSAOBPYYJ_cjs.isTier1Workflow(value) ? chunkSAOBPYYJ_cjs.toIR(value) : value);
|
|
902
|
+
}
|
|
903
|
+
function expectIR(value) {
|
|
904
|
+
const definition = definitionOf(value);
|
|
905
|
+
const hash = chunkGBQ33SIZ_cjs.irHash(definition);
|
|
906
|
+
return {
|
|
907
|
+
definition,
|
|
908
|
+
irHash: hash,
|
|
909
|
+
toEqualIR(other) {
|
|
910
|
+
const otherHash = chunkGBQ33SIZ_cjs.irHash(definitionOf(other));
|
|
911
|
+
if (otherHash !== hash)
|
|
912
|
+
throw new HistoryAssertionError(`The IR differs: ${hash} versus ${otherHash}`);
|
|
913
|
+
}
|
|
914
|
+
};
|
|
915
|
+
}
|
|
916
|
+
function jsonCopy(value) {
|
|
917
|
+
return JSON.parse(JSON.stringify(value ?? null));
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
// src/testing/simulate.ts
|
|
921
|
+
function isHandleResult(value) {
|
|
922
|
+
return chunkSAENRNBJ_cjs.isRecord(value) && Object.hasOwn(value, "output") && Object.keys(value).every((key) => key === "output" || key === "handle");
|
|
923
|
+
}
|
|
924
|
+
async function simulate(definition, options = {}) {
|
|
925
|
+
const nodes = definition.data.nodes;
|
|
926
|
+
const edges = definition.data.edges;
|
|
927
|
+
const byId = new Map(nodes.map((node) => [node.id, node]));
|
|
928
|
+
const incoming = new Set(edges.map((edge) => edge.target));
|
|
929
|
+
const maxVisits = options.maxVisits ?? 1;
|
|
930
|
+
const visits = /* @__PURE__ */ new Map();
|
|
931
|
+
const steps = [];
|
|
932
|
+
const outputs = {};
|
|
933
|
+
const queue = nodes.filter((node) => !incoming.has(node.id)).map((node) => ({ nodeId: node.id, input: options.trigger ?? {} }));
|
|
934
|
+
const mocks = options.mocks ?? {};
|
|
935
|
+
let env;
|
|
936
|
+
for (let guard = 0; queue.length > 0 && guard < 1e4; guard++) {
|
|
937
|
+
const next = queue.shift();
|
|
938
|
+
if (!next) break;
|
|
939
|
+
const node = byId.get(next.nodeId);
|
|
940
|
+
if (!node) continue;
|
|
941
|
+
const count = visits.get(node.id) ?? 0;
|
|
942
|
+
if (count >= maxVisits) continue;
|
|
943
|
+
visits.set(node.id, count + 1);
|
|
944
|
+
const mock = mocks[node.id] ?? mocks[node.type];
|
|
945
|
+
let output = next.input;
|
|
946
|
+
let handle;
|
|
947
|
+
if (mock) {
|
|
948
|
+
const produced = await mock(next.input, node);
|
|
949
|
+
if (isHandleResult(produced)) {
|
|
950
|
+
output = produced.output;
|
|
951
|
+
handle = produced.handle;
|
|
952
|
+
} else output = produced;
|
|
953
|
+
} else if (node.type === chunkGBQ33SIZ_cjs.CODE_WORKFLOW_NODE_TYPE) {
|
|
954
|
+
const state = node.args?.state;
|
|
955
|
+
const workflowFn = chunkSAENRNBJ_cjs.isRecord(state) && typeof state["workflowFn"] === "string" ? state["workflowFn"] : "";
|
|
956
|
+
const fn = options.workflows?.[workflowFn];
|
|
957
|
+
if (!fn)
|
|
958
|
+
throw new Error(
|
|
959
|
+
`simulate: code-workflow node "${node.id}" runs "${workflowFn}"; pass it in workflows or mock the node`
|
|
960
|
+
);
|
|
961
|
+
env ??= await TestWorkflowEnvironment.create({ workflows: options.workflows ?? {} });
|
|
962
|
+
const handleRun = await env.start(fn, { input: next.input });
|
|
963
|
+
const result = jsonCopy(await handleRun.result());
|
|
964
|
+
output = result !== null && typeof result === "object" && !Array.isArray(result) ? result : { result };
|
|
965
|
+
}
|
|
966
|
+
outputs[node.id] = output;
|
|
967
|
+
steps.push({
|
|
968
|
+
nodeId: node.id,
|
|
969
|
+
type: node.type,
|
|
970
|
+
input: next.input,
|
|
971
|
+
output,
|
|
972
|
+
...handle !== void 0 ? { handle } : {}
|
|
973
|
+
});
|
|
974
|
+
for (const edge of edges) {
|
|
975
|
+
if (edge.source !== node.id) continue;
|
|
976
|
+
if (handle !== void 0 && edge.sourceHandle !== void 0 && edge.sourceHandle !== handle)
|
|
977
|
+
continue;
|
|
978
|
+
queue.push({ nodeId: edge.target, input: output });
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
return { steps, outputs };
|
|
982
|
+
}
|
|
983
|
+
async function bundleToScript(bundle, esbuild) {
|
|
984
|
+
const api = esbuild ?? await import('esbuild');
|
|
985
|
+
const result = await api.transform(bundle, {
|
|
986
|
+
format: "iife",
|
|
987
|
+
globalName: "__fluidgridsBundle",
|
|
988
|
+
target: "es2023",
|
|
989
|
+
platform: "neutral",
|
|
990
|
+
charset: "utf8",
|
|
991
|
+
legalComments: "none",
|
|
992
|
+
logLevel: "silent"
|
|
993
|
+
});
|
|
994
|
+
return result.code;
|
|
995
|
+
}
|
|
996
|
+
function settle() {
|
|
997
|
+
return new Promise((resolve) => setImmediate(resolve));
|
|
998
|
+
}
|
|
999
|
+
async function runWorkflowTaskInVm(script, request, options = {}) {
|
|
1000
|
+
const timeout = options.timeoutMs ?? 1e4;
|
|
1001
|
+
const context = vm.createContext(/* @__PURE__ */ Object.create(null), {
|
|
1002
|
+
codeGeneration: { strings: false, wasm: false },
|
|
1003
|
+
microtaskMode: "afterEvaluate",
|
|
1004
|
+
name: "fluidgrids-workflow-task"
|
|
1005
|
+
});
|
|
1006
|
+
const run = (source, filename) => new vm.Script(source, { filename }).runInContext(context, { timeout });
|
|
1007
|
+
try {
|
|
1008
|
+
run(chunkL24YIZY7_cjs.TASK_PRELUDE, "fluidgrids-task-prelude.js");
|
|
1009
|
+
run(script, "workflow-bundle.js");
|
|
1010
|
+
run(
|
|
1011
|
+
`globalThis.__fluidgridsRuntime.start(${JSON.stringify(JSON.stringify(request))})`,
|
|
1012
|
+
"start.js"
|
|
1013
|
+
);
|
|
1014
|
+
await settle();
|
|
1015
|
+
const deadline = Date.now() + timeout;
|
|
1016
|
+
for (; ; ) {
|
|
1017
|
+
const step = run("globalThis.__fluidgridsRuntime.step()", "step.js");
|
|
1018
|
+
await settle();
|
|
1019
|
+
if (step !== "more") break;
|
|
1020
|
+
if (Date.now() > deadline)
|
|
1021
|
+
return chunkJD2VQIDZ_cjs.failedTaskResult(
|
|
1022
|
+
"TASK_TIMEOUT",
|
|
1023
|
+
`The workflow task ran past its ${timeout} ms budget`
|
|
1024
|
+
);
|
|
1025
|
+
}
|
|
1026
|
+
const json = run("globalThis.__fluidgridsRuntime.complete()", "complete.js");
|
|
1027
|
+
if (typeof json !== "string")
|
|
1028
|
+
return chunkJD2VQIDZ_cjs.failedTaskResult("INTERNAL", "The runtime returned no result");
|
|
1029
|
+
return JSON.parse(json);
|
|
1030
|
+
} catch (error) {
|
|
1031
|
+
const fields = typeof error === "object" && error !== null ? error : {};
|
|
1032
|
+
const message = typeof fields.message === "string" ? fields.message : String(error);
|
|
1033
|
+
const name = typeof fields.name === "string" ? fields.name : "";
|
|
1034
|
+
if (/timed out/i.test(message)) return chunkJD2VQIDZ_cjs.failedTaskResult("TASK_TIMEOUT", message);
|
|
1035
|
+
if (name === "DeterminismViolation") return chunkJD2VQIDZ_cjs.failedTaskResult("NON_DETERMINISM", message);
|
|
1036
|
+
return chunkJD2VQIDZ_cjs.failedTaskResult("WORKFLOW_CODE_ERROR", `${name || "Error"}: ${message}`);
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
exports.HistoryAssertionError = HistoryAssertionError;
|
|
1041
|
+
exports.ReplayError = ReplayError;
|
|
1042
|
+
exports.RunBlockedError = RunBlockedError;
|
|
1043
|
+
exports.TestRunHandle = TestRunHandle;
|
|
1044
|
+
exports.TestWorkflowEnvironment = TestWorkflowEnvironment;
|
|
1045
|
+
exports.WorkflowFailedError = WorkflowFailedError;
|
|
1046
|
+
exports.WorkflowTaskFailedError = WorkflowTaskFailedError;
|
|
1047
|
+
exports.assertHistory = assertHistory;
|
|
1048
|
+
exports.bundleToScript = bundleToScript;
|
|
1049
|
+
exports.drainJobs = drainJobs;
|
|
1050
|
+
exports.expectIR = expectIR;
|
|
1051
|
+
exports.jsonCopy = jsonCopy;
|
|
1052
|
+
exports.registryOf = registryOf;
|
|
1053
|
+
exports.replay = replay;
|
|
1054
|
+
exports.replayRequest = replayRequest;
|
|
1055
|
+
exports.runWorkflowTaskInProcess = runWorkflowTaskInProcess;
|
|
1056
|
+
exports.runWorkflowTaskInVm = runWorkflowTaskInVm;
|
|
1057
|
+
exports.simulate = simulate;
|