@fluidgrids/workflows 2026.1029.1 → 2026.1030.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +112 -4
- package/dist/{activation-CESehfYi.d.cts → activation-DqUfpHzk.d.cts} +1 -1
- package/dist/{activation-BnVQMv9y.d.ts → activation-xpx1oWuo.d.ts} +1 -1
- package/dist/{chunk-MJ5TGYVG.js → chunk-2D3MYIIF.js} +969 -153
- package/dist/{chunk-2B37XAFQ.cjs → chunk-3U2NU7YC.cjs} +24 -11
- package/dist/{chunk-NEIUQASE.cjs → chunk-4QKB4ARC.cjs} +3 -3
- package/dist/{chunk-GBQ33SIZ.cjs → chunk-54FLQNUM.cjs} +12 -1
- package/dist/{chunk-2DDULZFM.js → chunk-5HIVIV53.js} +86 -75
- package/dist/chunk-5MFVJM76.js +1222 -0
- package/dist/{chunk-EWYADREB.cjs → chunk-672KMMBK.cjs} +2 -2
- package/dist/{chunk-KXJDRQ6G.js → chunk-6A2FGSUS.js} +22 -2
- package/dist/{chunk-OIX4O6UR.js → chunk-6OLN6BMB.js} +16 -3
- package/dist/{chunk-6FSHT5IL.js → chunk-7EZIANC7.js} +1 -1
- package/dist/chunk-A5SXCA62.cjs +93 -0
- package/dist/chunk-AC6ZEXVR.js +85 -0
- package/dist/{chunk-FWBRQWRX.js → chunk-AE5Q6ZAJ.js} +167 -10
- package/dist/{chunk-7ILGCUI6.js → chunk-C4F5KJTI.js} +142 -3
- package/dist/{chunk-WXXNCHUY.js → chunk-G5H32GIG.js} +1 -1
- package/dist/{chunk-OMY3CKUZ.cjs → chunk-GEQDGUGB.cjs} +1042 -226
- package/dist/{chunk-25BKWR2O.js → chunk-HCM7ZC7Q.js} +1 -1
- package/dist/{chunk-I47EENR4.cjs → chunk-HMZUGSMU.cjs} +673 -156
- package/dist/{chunk-NXFBVDOI.cjs → chunk-IQPXHQQQ.cjs} +8 -4
- package/dist/{chunk-RUQIMTN5.cjs → chunk-ISOIA7NV.cjs} +178 -15
- package/dist/chunk-JSC75O5C.cjs +167 -0
- package/dist/{chunk-BD7ZPG53.js → chunk-NWQ5WRLE.js} +10 -2
- package/dist/chunk-ODZ2IAR2.cjs +1247 -0
- package/dist/{chunk-FQSJI5NV.js → chunk-SR7TR5KX.js} +6 -2
- package/dist/{chunk-OKX3FYIM.cjs → chunk-TCTE7DX5.cjs} +3 -3
- package/dist/{chunk-3FIDHONL.cjs → chunk-TYF56RGB.cjs} +99 -80
- package/dist/{chunk-J3PYFGKA.cjs → chunk-VA4OFR72.cjs} +162 -4
- package/dist/chunk-VU5S5A7V.js +150 -0
- package/dist/{chunk-A36CCOWS.js → chunk-YEHR4NNE.js} +633 -128
- package/dist/{chunk-ELL2RBML.cjs → chunk-ZCGKGDCL.cjs} +23 -1
- package/dist/codegen/index.d.cts +2 -2
- package/dist/codegen/index.d.ts +2 -2
- package/dist/{collect-BTHjQaSp.d.ts → collect-6NIEufHd.d.ts} +20 -3
- package/dist/{collect-CSyOg5XC.d.cts → collect-CcTCKqx6.d.cts} +20 -3
- package/dist/{file-cgL9l9l8.d.cts → file-BxYpwjRQ.d.cts} +250 -15
- package/dist/{file-olQudcx5.d.ts → file-CFdPQJI9.d.ts} +250 -15
- package/dist/history/index.cjs +283 -106
- package/dist/history/index.d.cts +549 -10
- package/dist/history/index.d.ts +549 -10
- package/dist/history/index.js +5 -4
- package/dist/index.cjs +146 -53
- package/dist/index.d.cts +38 -14
- package/dist/index.d.ts +38 -14
- package/dist/index.js +6 -5
- package/dist/interceptors-CNcr2dlk.d.ts +159 -0
- package/dist/interceptors-CqkNZVfI.d.cts +159 -0
- package/dist/ir/index.cjs +339 -36
- package/dist/ir/index.d.cts +244 -8
- package/dist/ir/index.d.ts +244 -8
- package/dist/ir/index.js +306 -21
- package/dist/{lower-3S6PsqjJ.d.cts → lower-BtRfIW2A.d.cts} +23 -9
- package/dist/{lower-DmZpQ8hJ.d.ts → lower-CpsMFOFX.d.ts} +23 -9
- package/dist/{manifest-BJjrQt77.d.ts → manifest-D-I_twys.d.ts} +1 -1
- package/dist/{manifest-gRI2Q5Fj.d.cts → manifest-DahBQVM8.d.cts} +1 -1
- package/dist/node-evaluator-DGQDPSPI.cjs +24 -0
- package/dist/node-evaluator-LEEVD7H7.js +14 -0
- package/dist/payload-fields-BVRQn2yH.d.cts +122 -0
- package/dist/payload-fields-oOBWxDiG.d.ts +122 -0
- package/dist/quickjs/index.cjs +27 -27
- package/dist/quickjs/index.d.cts +6 -6
- package/dist/quickjs/index.d.ts +6 -6
- package/dist/quickjs/index.js +9 -9
- package/dist/roundtrip/index.cjs +72 -66
- package/dist/roundtrip/index.d.cts +25 -11
- package/dist/roundtrip/index.d.ts +25 -11
- package/dist/roundtrip/index.js +13 -11
- package/dist/runtime/index.cjs +19 -19
- package/dist/runtime/index.d.cts +139 -12
- package/dist/runtime/index.d.ts +139 -12
- package/dist/runtime/index.js +6 -6
- package/dist/testing/index.cjs +688 -121
- package/dist/testing/index.d.cts +174 -10
- package/dist/testing/index.d.ts +174 -10
- package/dist/testing/index.js +646 -82
- package/dist/{types-CnIOinoz.d.ts → types-Bv2i4cBX.d.cts} +126 -23
- package/dist/{types-CQKOhPTa.d.cts → types-DGx-b9eq.d.ts} +126 -23
- package/dist/{types-Bb2wRnJ5.d.cts → types-V7jEEUaL.d.cts} +1 -1
- package/dist/{types-Bb2wRnJ5.d.ts → types-V7jEEUaL.d.ts} +1 -1
- package/dist/{workflow-8uRU9rhL.d.ts → workflow-B18OeF7g.d.ts} +2 -2
- package/dist/{workflow-DWTPHFbt.d.ts → workflow-BGnv8SS6.d.ts} +261 -14
- package/dist/{workflow-CZOXUlQ5.d.cts → workflow-BJPFv3cp.d.cts} +2 -2
- package/dist/{workflow-O6e64ZcG.d.cts → workflow-DS32Gtws.d.cts} +261 -14
- package/fixtures/histories/activity-options-updated.history.json +159 -0
- package/fixtures/histories/activity-paused-unpaused.history.json +160 -0
- package/fixtures/histories/codec-opaque.history.json +229 -0
- package/fixtures/histories/dynamic-workflow.history.json +150 -0
- package/fixtures/histories/interceptor-headers.history.json +339 -0
- package/fixtures/histories/query-custom.history.json +213 -0
- package/fixtures/histories/query-default-handler.history.json +120 -0
- package/fixtures/histories/reset-forked-reapplied-signals.history.json +239 -0
- package/fixtures/histories/reset-reapplied-update-admitted.history.json +228 -0
- package/fixtures/histories/search-attributes-upserted.history.json +177 -0
- package/fixtures/histories/update-accepted-completed.history.json +45 -30
- package/fixtures/histories/update-default-handler.history.json +246 -0
- package/fixtures/histories/update-handler-awaits-activity.history.json +196 -0
- package/fixtures/histories/update-not-handled.history.json +138 -0
- package/fixtures/histories/update-rejected-no-event.history.json +190 -0
- package/fixtures/histories/update-with-start-admitted.history.json +150 -0
- package/fixtures/histories/user-activity-cancel.history.json +149 -0
- package/fixtures/histories/user-activity-retry.history.json +143 -0
- package/fixtures/histories/user-activity.history.json +149 -0
- package/package.json +1 -1
- package/schema/bridge-v2.schema.json +1062 -0
- package/schema/history-file.schema.json +255 -81
- package/schema/ir-v2.schema.json +246 -1
- package/schema/payload-fields.json +162 -0
- package/schema/run-event.schema.json +239 -80
- package/schema/workflow-command.schema.json +210 -84
- package/schema/workflow-task.schema.json +532 -167
- package/dist/chunk-6QPOJZT6.cjs +0 -1180
- package/dist/chunk-A2OHX3UQ.js +0 -1156
- package/dist/chunk-PWIUEWC3.js +0 -81
- package/dist/chunk-YHBVMOMK.cjs +0 -87
- package/dist/node-evaluator-SOADN23V.js +0 -14
- package/dist/node-evaluator-YGDEXK4U.cjs +0 -24
package/dist/testing/index.js
CHANGED
|
@@ -1,29 +1,32 @@
|
|
|
1
|
-
import { failedTaskResult } from '../chunk-
|
|
2
|
-
import { isTier1Workflow, toIR } from '../chunk-
|
|
3
|
-
import { irHash, CODE_WORKFLOW_NODE_TYPE, normalizeDefinition } from '../chunk-
|
|
1
|
+
import { failedTaskResult } from '../chunk-G5H32GIG.js';
|
|
2
|
+
import { isTier1Workflow, toIR } from '../chunk-SR7TR5KX.js';
|
|
3
|
+
import { irHash, CODE_WORKFLOW_NODE_TYPE, normalizeDefinition } from '../chunk-NWQ5WRLE.js';
|
|
4
4
|
import '../chunk-5LUIDMF4.js';
|
|
5
5
|
import '../chunk-JZ4TOOLN.js';
|
|
6
6
|
import '../chunk-E2VK7J2C.js';
|
|
7
7
|
import '../chunk-7AAZGVKD.js';
|
|
8
|
-
import { createHistoryFile, validateCommands, parseHistoryFile } from '../chunk-
|
|
8
|
+
import { createHistoryFile, validateCommands, parseHistoryFile } from '../chunk-YEHR4NNE.js';
|
|
9
|
+
import '../chunk-VU5S5A7V.js';
|
|
9
10
|
import { isRecord } from '../chunk-PMCM2OH4.js';
|
|
10
|
-
import { buildWorkflowRegistry, createWorkflowRuntime } from '../chunk-
|
|
11
|
-
import { failureToError, signalName, ApplicationError, errorToFailure, CancelledFailure, DeterminismViolation } from '../chunk-
|
|
11
|
+
import { buildWorkflowRegistry, createWorkflowRuntime } from '../chunk-2D3MYIIF.js';
|
|
12
|
+
import { failureToError, signalName, ApplicationError, errorToFailure, CancelledFailure, DeterminismViolation } from '../chunk-6A2FGSUS.js';
|
|
12
13
|
import '../chunk-M272AHEX.js';
|
|
13
14
|
import { TASK_PRELUDE } from '../chunk-4LYSEANA.js';
|
|
14
|
-
import { runRetryPolicyOf, toMs, isDurableWorkflow } from '../chunk-
|
|
15
|
+
import { isActivityImplementation, isActivityDefinition, ACTIVITY_IMPL_MARKER, ACTIVITY_MARKER, runRetryPolicyOf, toMs, findActivityImplementation, executeActivity, isDurableWorkflow } from '../chunk-C4F5KJTI.js';
|
|
15
16
|
import '../chunk-LNUQE6D6.js';
|
|
16
|
-
import {
|
|
17
|
-
import
|
|
18
|
-
import { computeRetryDelay, computeRunRetryDelay } from '../chunk-
|
|
17
|
+
import { COMMAND_FEATURES, codeFeaturesEnabledBy, applyEvent, projectRunState, isActivityOpen, hasUnprocessedInput, initialRunState, eventsForCommands, continueAsNewRetryShape, isChildOpen } from '../chunk-AE5Q6ZAJ.js';
|
|
18
|
+
import '../chunk-AC6ZEXVR.js';
|
|
19
|
+
import { RUNTIME_RELEASE, HISTORY_PROTOCOL, isCommandEvent, TERMINAL_EVENTS, isInputEvent, TERMINAL_COMMANDS, computeRetryDelay, computeRunRetryDelay } from '../chunk-5HIVIV53.js';
|
|
19
20
|
import { createContext, Script } from 'vm';
|
|
20
21
|
|
|
21
22
|
// src/testing/host.ts
|
|
22
23
|
function drainJobs() {
|
|
23
24
|
return new Promise((resolve) => setImmediate(resolve));
|
|
24
25
|
}
|
|
25
|
-
function registryOf(workflows) {
|
|
26
|
-
return buildWorkflowRegistry([
|
|
26
|
+
function registryOf(workflows, interceptors) {
|
|
27
|
+
return buildWorkflowRegistry([
|
|
28
|
+
["in-process", { ...workflows, ...interceptors !== void 0 ? { interceptors } : {} }]
|
|
29
|
+
]);
|
|
27
30
|
}
|
|
28
31
|
async function hostLoop(abi, requestJson) {
|
|
29
32
|
abi.start(requestJson);
|
|
@@ -39,6 +42,32 @@ async function runWorkflowTaskInProcess(registry, request) {
|
|
|
39
42
|
}
|
|
40
43
|
|
|
41
44
|
// src/testing/environment.ts
|
|
45
|
+
var QueryRejectedError = class extends Error {
|
|
46
|
+
constructor(runId, queryName, message) {
|
|
47
|
+
super(message);
|
|
48
|
+
this.runId = runId;
|
|
49
|
+
this.queryName = queryName;
|
|
50
|
+
this.name = "QueryRejectedError";
|
|
51
|
+
}
|
|
52
|
+
runId;
|
|
53
|
+
queryName;
|
|
54
|
+
};
|
|
55
|
+
var QueryFailedError = class extends Error {
|
|
56
|
+
constructor(runId, queryName, message) {
|
|
57
|
+
super(message);
|
|
58
|
+
this.runId = runId;
|
|
59
|
+
this.queryName = queryName;
|
|
60
|
+
this.name = "QueryFailedError";
|
|
61
|
+
}
|
|
62
|
+
runId;
|
|
63
|
+
queryName;
|
|
64
|
+
};
|
|
65
|
+
var UpdateRefusedError = class extends Error {
|
|
66
|
+
constructor(message) {
|
|
67
|
+
super(message);
|
|
68
|
+
this.name = "UpdateRefusedError";
|
|
69
|
+
}
|
|
70
|
+
};
|
|
42
71
|
var WorkflowAlreadyStartedError = class extends Error {
|
|
43
72
|
constructor(workflowKey, workflowId, existingRunId) {
|
|
44
73
|
super(
|
|
@@ -96,7 +125,7 @@ var RunBlockedError = class extends Error {
|
|
|
96
125
|
}
|
|
97
126
|
runId;
|
|
98
127
|
};
|
|
99
|
-
var TestRunHandle = class {
|
|
128
|
+
var TestRunHandle = class _TestRunHandle {
|
|
100
129
|
constructor(env, runId) {
|
|
101
130
|
this.env = env;
|
|
102
131
|
this.runId = runId;
|
|
@@ -126,6 +155,22 @@ var TestRunHandle = class {
|
|
|
126
155
|
this.env.terminate(this.runId, reason);
|
|
127
156
|
await this.env.runUntilIdle(false);
|
|
128
157
|
}
|
|
158
|
+
/** Send an update and run until idle (Phase C). */
|
|
159
|
+
update(update, args, options = {}) {
|
|
160
|
+
return this.env.update(this.runId, update, args, options);
|
|
161
|
+
}
|
|
162
|
+
/** Answer a query from the run's history (Phase C custom queries and the built-ins). */
|
|
163
|
+
query(query, args) {
|
|
164
|
+
return this.env.query(this.runId, query, args);
|
|
165
|
+
}
|
|
166
|
+
/** Reset the run (11 §2.7): a new run forked at a completed task; this one is terminated. */
|
|
167
|
+
async reset(options = {}) {
|
|
168
|
+
return new _TestRunHandle(this.env, await this.env.reset(this.runId, options));
|
|
169
|
+
}
|
|
170
|
+
/** Sink calls the run's tasks made (Phase C). */
|
|
171
|
+
sinkCalls() {
|
|
172
|
+
return this.env.sinkCallsOf(this.runId);
|
|
173
|
+
}
|
|
129
174
|
/** Run until this run (or the last run of its continue-as-new chain) ends; its output. */
|
|
130
175
|
async result() {
|
|
131
176
|
return this.env.resultOf(this.runId);
|
|
@@ -149,6 +194,14 @@ var FAILED_STATUSES = /* @__PURE__ */ new Set([
|
|
|
149
194
|
"TERMINATED",
|
|
150
195
|
"TIMED_OUT"
|
|
151
196
|
]);
|
|
197
|
+
function admittedPayload(update) {
|
|
198
|
+
return {
|
|
199
|
+
updateId: update.updateId,
|
|
200
|
+
name: update.name,
|
|
201
|
+
args: update.args,
|
|
202
|
+
...update.headers !== void 0 ? { headers: { ...update.headers } } : {}
|
|
203
|
+
};
|
|
204
|
+
}
|
|
152
205
|
function toJsonValue(value) {
|
|
153
206
|
if (value === void 0) return null;
|
|
154
207
|
const text = JSON.stringify(value);
|
|
@@ -158,6 +211,8 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
|
|
|
158
211
|
clock;
|
|
159
212
|
timeSkipping;
|
|
160
213
|
enabled;
|
|
214
|
+
/** The bundle features the gate's flags enable, sent with every task (`request.features`). */
|
|
215
|
+
codeFeatures;
|
|
161
216
|
failOnUnfinishedHandlers;
|
|
162
217
|
historyRelease;
|
|
163
218
|
dues = [];
|
|
@@ -173,19 +228,65 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
|
|
|
173
228
|
timers = [];
|
|
174
229
|
mockedChildren = [];
|
|
175
230
|
runCounter = 0;
|
|
231
|
+
updateCounter = 0;
|
|
176
232
|
executor;
|
|
177
233
|
workflowKeys;
|
|
234
|
+
interceptors;
|
|
235
|
+
activityImpls;
|
|
236
|
+
activityInterceptors;
|
|
237
|
+
connections;
|
|
238
|
+
sinks;
|
|
239
|
+
/** Attempts that handed their completion to an outside system (`completeAsync`), by token. */
|
|
240
|
+
asyncAttempts = /* @__PURE__ */ new Map();
|
|
178
241
|
constructor(options) {
|
|
179
242
|
this.clock = toDate(options.startTime);
|
|
180
243
|
this.timeSkipping = options.timeSkipping !== false;
|
|
181
|
-
this.enabled = options.enabled ??
|
|
244
|
+
this.enabled = options.enabled ?? new Set(COMMAND_FEATURES);
|
|
245
|
+
this.codeFeatures = codeFeaturesEnabledBy(this.enabled);
|
|
182
246
|
this.failOnUnfinishedHandlers = options.failOnUnfinishedHandlers !== false;
|
|
183
247
|
this.historyRelease = options.historyRelease ?? RUNTIME_RELEASE;
|
|
184
248
|
this.workspaceId = options.workspaceId ?? "test-workspace";
|
|
185
249
|
this.workflows = { ...options.workflows ?? {} };
|
|
186
|
-
this.
|
|
250
|
+
this.interceptors = options.interceptors;
|
|
251
|
+
this.registry = registryOf(this.workflows, this.interceptors);
|
|
187
252
|
this.executor = options.taskExecutor;
|
|
188
253
|
this.workflowKeys = { ...options.workflowKeys ?? {} };
|
|
254
|
+
this.activityImpls = [...options.activities ?? []];
|
|
255
|
+
for (const impl of this.activityImpls)
|
|
256
|
+
if (!isActivityImplementation(impl))
|
|
257
|
+
throw new TypeError("activities must be implementActivity() or defineActivity() values");
|
|
258
|
+
this.activityInterceptors = [...options.activityInterceptors ?? []];
|
|
259
|
+
this.connections = { ...options.connections ?? {} };
|
|
260
|
+
this.sinks = { ...options.sinks ?? {} };
|
|
261
|
+
}
|
|
262
|
+
/** Register a user-activity implementation (Phase C). A later one replaces an earlier of the same name. */
|
|
263
|
+
registerActivity(implementation) {
|
|
264
|
+
if (!isActivityImplementation(implementation))
|
|
265
|
+
throw new TypeError(
|
|
266
|
+
"registerActivity needs an implementActivity() or defineActivity() value"
|
|
267
|
+
);
|
|
268
|
+
const index = this.activityImpls.findIndex(
|
|
269
|
+
(impl) => impl.name === implementation.name && impl.dynamic === implementation.dynamic
|
|
270
|
+
);
|
|
271
|
+
if (index >= 0) this.activityImpls.splice(index, 1);
|
|
272
|
+
this.activityImpls.push(implementation);
|
|
273
|
+
}
|
|
274
|
+
/** Mock a user activity by name or declaration (Phase C). */
|
|
275
|
+
mockActivity(activity, fn) {
|
|
276
|
+
const name = typeof activity === "string" ? activity : activity.name;
|
|
277
|
+
if (typeof activity !== "string" && !isActivityDefinition(activity))
|
|
278
|
+
throw new TypeError("mockActivity needs an activity name or a declareActivity() value");
|
|
279
|
+
this.registerActivity(
|
|
280
|
+
Object.freeze({
|
|
281
|
+
__fluidgrids: ACTIVITY_MARKER,
|
|
282
|
+
name,
|
|
283
|
+
options: typeof activity === "string" ? {} : activity.options,
|
|
284
|
+
implementation: ACTIVITY_IMPL_MARKER,
|
|
285
|
+
// The mock receives the activity's JSON args, typed by the declaration.
|
|
286
|
+
fn: (ctx, args) => fn(args, ctx),
|
|
287
|
+
dynamic: false
|
|
288
|
+
})
|
|
289
|
+
);
|
|
189
290
|
}
|
|
190
291
|
/** Create an environment. */
|
|
191
292
|
static async create(options = {}) {
|
|
@@ -217,6 +318,10 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
|
|
|
217
318
|
if (nodeType === void 0) this.held.clear();
|
|
218
319
|
else this.held.delete(nodeType);
|
|
219
320
|
}
|
|
321
|
+
/** The hold key of an attempt: its node type, or the task queue of a user activity. */
|
|
322
|
+
holdKeyOf(entry) {
|
|
323
|
+
return entry.kind === "user" ? entry.taskQueue ?? "" : entry.nodeType;
|
|
324
|
+
}
|
|
220
325
|
isHeld(nodeType) {
|
|
221
326
|
return this.held.has("*") || this.held.has(nodeType);
|
|
222
327
|
}
|
|
@@ -232,11 +337,17 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
|
|
|
232
337
|
);
|
|
233
338
|
const declared = this.workflows[workflowFn]?.options ?? {};
|
|
234
339
|
const workflowKey = options.workflowKey ?? (typeof definition === "string" ? this.workflows[workflowFn]?.name ?? workflowFn : definition.name);
|
|
340
|
+
if (options.update !== void 0 && options.startDelay !== void 0)
|
|
341
|
+
throw new UpdateRefusedError(
|
|
342
|
+
"Update-with-start cannot be combined with a start delay (11 \xA72.6)"
|
|
343
|
+
);
|
|
235
344
|
if (options.workflowId !== void 0) {
|
|
236
345
|
const existing = this.useExisting(workflowKey, options);
|
|
237
346
|
if (existing !== void 0) {
|
|
238
347
|
for (const signal of options.signals ?? [])
|
|
239
348
|
this.deliverSignal(existing, signal.name, signal.payload);
|
|
349
|
+
if (options.update !== void 0)
|
|
350
|
+
this.addUpdate(this.run(existing), options.update, "PENDING");
|
|
240
351
|
await this.runUntilIdle(false);
|
|
241
352
|
return new TestRunHandle(this, existing);
|
|
242
353
|
}
|
|
@@ -261,7 +372,11 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
|
|
|
261
372
|
...options.summary !== void 0 ? { summary: options.summary } : {},
|
|
262
373
|
...options.details !== void 0 ? { details: options.details } : {},
|
|
263
374
|
...options.memo !== void 0 ? { memo: toJsonValue(options.memo) } : {},
|
|
264
|
-
...(options.versioningBehavior ?? declared.versioningBehavior) !== void 0 ? { versioningBehavior: options.versioningBehavior ?? declared.versioningBehavior } : {}
|
|
375
|
+
...(options.versioningBehavior ?? declared.versioningBehavior) !== void 0 ? { versioningBehavior: options.versioningBehavior ?? declared.versioningBehavior } : {},
|
|
376
|
+
...options.headers !== void 0 ? { headers: { ...options.headers } } : {},
|
|
377
|
+
...options.searchAttributes !== void 0 ? { searchAttributes: toJsonValue(options.searchAttributes) } : {},
|
|
378
|
+
...options.fairnessKey !== void 0 ? { fairnessKey: options.fairnessKey } : {},
|
|
379
|
+
...options.fairnessWeight !== void 0 ? { fairnessWeight: options.fairnessWeight } : {}
|
|
265
380
|
};
|
|
266
381
|
const runId = this.createRun(
|
|
267
382
|
workflowFn,
|
|
@@ -269,13 +384,245 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
|
|
|
269
384
|
{ ...options.info, ...options.runId !== void 0 ? { runId: options.runId } : {} },
|
|
270
385
|
void 0,
|
|
271
386
|
{},
|
|
272
|
-
config
|
|
387
|
+
config,
|
|
388
|
+
options.update !== void 0 ? [this.newUpdate(options.update, "ADMITTED")] : []
|
|
273
389
|
);
|
|
274
390
|
for (const signal of options.signals ?? [])
|
|
275
391
|
this.deliverSignal(runId, signal.name, signal.payload);
|
|
276
392
|
await this.runUntilIdle(false);
|
|
277
393
|
return new TestRunHandle(this, runId);
|
|
278
394
|
}
|
|
395
|
+
/**
|
|
396
|
+
* Update-with-start (11 §2.6): start the run (or, with
|
|
397
|
+
* `idConflictPolicy: USE_EXISTING`, reach the open one) and deliver the
|
|
398
|
+
* update before any other task runs.
|
|
399
|
+
*/
|
|
400
|
+
async startWithUpdate(definition, options) {
|
|
401
|
+
const updateId = options.update.updateId ?? this.nextUpdateId();
|
|
402
|
+
const handle = await this.start(definition, {
|
|
403
|
+
...options,
|
|
404
|
+
update: { ...options.update, updateId }
|
|
405
|
+
});
|
|
406
|
+
return { handle, update: this.updateResultOf(handle.runId, updateId) };
|
|
407
|
+
}
|
|
408
|
+
nextUpdateId() {
|
|
409
|
+
this.updateCounter += 1;
|
|
410
|
+
return `u${this.updateCounter}`;
|
|
411
|
+
}
|
|
412
|
+
/** A new update row (not attached to a run yet). */
|
|
413
|
+
newUpdate(update, status) {
|
|
414
|
+
return {
|
|
415
|
+
updateId: update.updateId ?? this.nextUpdateId(),
|
|
416
|
+
name: update.name,
|
|
417
|
+
args: toJsonValue(update.args),
|
|
418
|
+
...update.headers !== void 0 ? { headers: { ...update.headers } } : {},
|
|
419
|
+
status
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
/** Record an update on a run: pending (delivered by the next task) or admitted into history. */
|
|
423
|
+
addUpdate(run, update, status) {
|
|
424
|
+
const existing = update.updateId !== void 0 ? run.updates.get(update.updateId) : void 0;
|
|
425
|
+
if (existing) return existing;
|
|
426
|
+
const entry = this.newUpdate(update, status);
|
|
427
|
+
run.updates.set(entry.updateId, entry);
|
|
428
|
+
if (status === "ADMITTED")
|
|
429
|
+
this.append(run, { type: "UPDATE_ADMITTED", payload: admittedPayload(entry) });
|
|
430
|
+
else {
|
|
431
|
+
if (run.delayedUntil !== void 0) this.endDelay(run);
|
|
432
|
+
else this.scheduleTask(run);
|
|
433
|
+
}
|
|
434
|
+
return entry;
|
|
435
|
+
}
|
|
436
|
+
/** Schedule a workflow task for a run that has none (an update needs no input event). */
|
|
437
|
+
scheduleTask(run) {
|
|
438
|
+
if (run.state.status !== "RUNNING" || run.state.task.state !== "NONE") return;
|
|
439
|
+
if (run.delayedUntil !== void 0) return;
|
|
440
|
+
run.taskCounter += 1;
|
|
441
|
+
const taskId = `${run.runId}-task-${run.taskCounter}`;
|
|
442
|
+
const scheduled = {
|
|
443
|
+
seq: run.history.length + 1,
|
|
444
|
+
type: "WORKFLOW_TASK_SCHEDULED",
|
|
445
|
+
payload: { taskId },
|
|
446
|
+
createdAt: new Date(this.clock).toISOString()
|
|
447
|
+
};
|
|
448
|
+
applyEvent(run.state, scheduled);
|
|
449
|
+
run.history.push(scheduled);
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Send an update to a run and run until idle (Phase C, 05 §6). The result
|
|
453
|
+
* is `REJECTED` (the validator refused it or no handler took it; nothing is
|
|
454
|
+
* written to history), `COMPLETED`, or `ACCEPTED` while its handler still
|
|
455
|
+
* waits (for a timer without time skipping, or with `waitFor: ACCEPTED`).
|
|
456
|
+
*/
|
|
457
|
+
async update(runId, update, args, options = {}) {
|
|
458
|
+
const run = this.run(runId);
|
|
459
|
+
const name = typeof update === "string" ? update : update.name;
|
|
460
|
+
if (options.updateId !== void 0 && run.updates.has(options.updateId))
|
|
461
|
+
return this.updateResultOf(runId, options.updateId);
|
|
462
|
+
if (run.state.status !== "RUNNING")
|
|
463
|
+
throw new UpdateRefusedError(`Run ${runId} is ${run.state.status}; it takes no updates`);
|
|
464
|
+
const entry = this.addUpdate(
|
|
465
|
+
run,
|
|
466
|
+
{
|
|
467
|
+
name,
|
|
468
|
+
args,
|
|
469
|
+
...options.updateId !== void 0 ? { updateId: options.updateId } : {},
|
|
470
|
+
...options.headers !== void 0 ? { headers: options.headers } : {}
|
|
471
|
+
},
|
|
472
|
+
"PENDING"
|
|
473
|
+
);
|
|
474
|
+
await this.runUntilIdle(false);
|
|
475
|
+
return this.updateResultOf(runId, entry.updateId);
|
|
476
|
+
}
|
|
477
|
+
/** The current result of an update. */
|
|
478
|
+
updateResultOf(runId, updateId) {
|
|
479
|
+
const entry = this.run(runId).updates.get(updateId);
|
|
480
|
+
if (!entry) throw new Error(`Run ${runId} has no update ${updateId}`);
|
|
481
|
+
return {
|
|
482
|
+
updateId,
|
|
483
|
+
status: entry.status === "ADMITTED" ? "PENDING" : entry.status,
|
|
484
|
+
...entry.result !== void 0 ? { result: entry.result } : {},
|
|
485
|
+
...entry.failure !== void 0 ? { failure: entry.failure } : {}
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
/** Answer a query from the run's history (a replay-only `query` task). */
|
|
489
|
+
async query(runId, query, args) {
|
|
490
|
+
const run = this.run(runId);
|
|
491
|
+
const name = typeof query === "string" ? query : query.name;
|
|
492
|
+
const request = {
|
|
493
|
+
protocol: HISTORY_PROTOCOL,
|
|
494
|
+
kind: "query",
|
|
495
|
+
workflowFn: run.workflowFn,
|
|
496
|
+
info: run.info,
|
|
497
|
+
history: run.history,
|
|
498
|
+
nowMs: this.clock,
|
|
499
|
+
queries: [{ queryId: "q1", name, args: toJsonValue(args) }]
|
|
500
|
+
};
|
|
501
|
+
const result = await this.execute(request);
|
|
502
|
+
const answer = result.queryResults?.[0];
|
|
503
|
+
if (answer?.rejected === true)
|
|
504
|
+
throw new QueryRejectedError(runId, name, answer.error ?? `No handler for query ${name}`);
|
|
505
|
+
if (answer === void 0 || answer.error !== void 0)
|
|
506
|
+
throw new QueryFailedError(
|
|
507
|
+
runId,
|
|
508
|
+
name,
|
|
509
|
+
answer?.error ?? result.failure?.message ?? "The query was not answered"
|
|
510
|
+
);
|
|
511
|
+
return answer.value ?? null;
|
|
512
|
+
}
|
|
513
|
+
/** Sink calls the run's tasks made. */
|
|
514
|
+
sinkCallsOf(runId) {
|
|
515
|
+
return this.run(runId).sinkCalls.map((call) => ({ ...call, args: [...call.args] }));
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Reset a run (11 §2.7): a new run whose history is the original's up to
|
|
519
|
+
* and including the chosen completed workflow task (with its commands),
|
|
520
|
+
* then `WORKFLOW_RESET`, then the signals (and, as `UPDATE_ADMITTED`, the
|
|
521
|
+
* accepted updates) received after that point. The original is terminated
|
|
522
|
+
* with reason `RESET`; children it started before the point follow the new
|
|
523
|
+
* run, the others get their parent-close policy. Returns the new run id.
|
|
524
|
+
*/
|
|
525
|
+
async reset(runId, options = {}) {
|
|
526
|
+
const original = this.run(runId);
|
|
527
|
+
const history = original.history;
|
|
528
|
+
const points = [];
|
|
529
|
+
for (const event of history) {
|
|
530
|
+
if (event.type !== "WORKFLOW_TASK_COMPLETED") continue;
|
|
531
|
+
const taskId = typeof event.payload["taskId"] === "string" ? event.payload["taskId"] : "";
|
|
532
|
+
let end = event.seq;
|
|
533
|
+
let closes = false;
|
|
534
|
+
while (end < history.length) {
|
|
535
|
+
const next = history[end];
|
|
536
|
+
if (!next || !isCommandEvent(next) || next.taskId !== taskId) break;
|
|
537
|
+
if (TERMINAL_EVENTS.has(next.type)) closes = true;
|
|
538
|
+
end += 1;
|
|
539
|
+
}
|
|
540
|
+
if (!closes) points.push({ seq: event.seq, cut: end });
|
|
541
|
+
}
|
|
542
|
+
const point = options.toSeq !== void 0 ? [...points].reverse().find((entry) => entry.seq <= (options.toSeq ?? 0)) : options.to === "FIRST_TASK" ? points[0] : points[points.length - 1];
|
|
543
|
+
if (!point) throw new Error(`Run ${runId} has no completed workflow task to reset to`);
|
|
544
|
+
const cut = point.cut;
|
|
545
|
+
const copied = history.slice(0, cut).map((event) => ({ ...event, payload: { ...event.payload } }));
|
|
546
|
+
const later = history.slice(cut);
|
|
547
|
+
const reapplySignals = options.reapplySignals !== false;
|
|
548
|
+
const reapplyUpdates = options.reapplyUpdates !== false;
|
|
549
|
+
const keptChildren = new Set(
|
|
550
|
+
copied.filter((event) => event.type === "CHILD_RUN_INITIATED").map((event) => String(event.payload["childId"]))
|
|
551
|
+
);
|
|
552
|
+
this.runCounter += 1;
|
|
553
|
+
const newRunId = `run-${this.runCounter}`;
|
|
554
|
+
if (original.state.status === "RUNNING") {
|
|
555
|
+
original.resetTo = newRunId;
|
|
556
|
+
this.append(original, { type: "WORKFLOW_TERMINATED", payload: { reason: "RESET" } });
|
|
557
|
+
this.onClosed(original, keptChildren);
|
|
558
|
+
} else original.resetTo = newRunId;
|
|
559
|
+
const fork = {
|
|
560
|
+
runId: newRunId,
|
|
561
|
+
workflowFn: original.workflowFn,
|
|
562
|
+
history: copied,
|
|
563
|
+
state: projectRunState(copied),
|
|
564
|
+
taskCounter: copied.filter((event) => event.type === "WORKFLOW_TASK_SCHEDULED").length,
|
|
565
|
+
info: { ...original.info, runId: newRunId },
|
|
566
|
+
children: new Map([...original.children].filter(([childId]) => keptChildren.has(childId))),
|
|
567
|
+
config: { ...original.config },
|
|
568
|
+
...original.parent ? { parent: original.parent } : {},
|
|
569
|
+
updates: /* @__PURE__ */ new Map(),
|
|
570
|
+
sinkCalls: []
|
|
571
|
+
};
|
|
572
|
+
this.runs.set(newRunId, fork);
|
|
573
|
+
this.runOrder.push(newRunId);
|
|
574
|
+
for (const [childId, childRunId] of fork.children) {
|
|
575
|
+
const child = childRunId !== void 0 ? this.runs.get(childRunId) : void 0;
|
|
576
|
+
if (child) child.parent = { runId: newRunId, childId };
|
|
577
|
+
}
|
|
578
|
+
this.append(fork, {
|
|
579
|
+
type: "WORKFLOW_RESET",
|
|
580
|
+
payload: {
|
|
581
|
+
fromRunId: runId,
|
|
582
|
+
toSeq: cut,
|
|
583
|
+
reapplySignals,
|
|
584
|
+
reapplyUpdates,
|
|
585
|
+
...options.reason !== void 0 ? { reason: options.reason } : {}
|
|
586
|
+
}
|
|
587
|
+
});
|
|
588
|
+
for (const [activityId, activity] of Object.entries(fork.state.activities)) {
|
|
589
|
+
if (!isActivityOpen(activity)) continue;
|
|
590
|
+
const scheduled = copied[activity.scheduledSeq - 1];
|
|
591
|
+
if (scheduled?.type !== "ACTIVITY_SCHEDULED") continue;
|
|
592
|
+
this.afterCommand(fork, {
|
|
593
|
+
// The run-event schema checked the payload; it is the command that scheduled it.
|
|
594
|
+
...scheduled.payload,
|
|
595
|
+
type: "SCHEDULE_ACTIVITY",
|
|
596
|
+
activityId
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
for (const [timerId, timer] of Object.entries(fork.state.timers))
|
|
600
|
+
if (timer.status === "PENDING")
|
|
601
|
+
this.timers.push({ runId: newRunId, timerId, fireAt: Date.parse(timer.fireAt) });
|
|
602
|
+
for (const event of later) {
|
|
603
|
+
if (event.type === "SIGNAL_RECEIVED" && reapplySignals)
|
|
604
|
+
this.append(fork, { type: "SIGNAL_RECEIVED", payload: { ...event.payload } });
|
|
605
|
+
if (reapplyUpdates && (event.type === "UPDATE_ACCEPTED" || event.type === "UPDATE_ADMITTED") && !fork.updates.has(String(event.payload["updateId"])) && fork.state.updates[String(event.payload["updateId"])] === void 0) {
|
|
606
|
+
const headers = event.payload["headers"];
|
|
607
|
+
this.addUpdate(
|
|
608
|
+
fork,
|
|
609
|
+
{
|
|
610
|
+
updateId: String(event.payload["updateId"]),
|
|
611
|
+
name: String(event.payload["name"]),
|
|
612
|
+
args: event.payload["args"] ?? null,
|
|
613
|
+
...typeof headers === "object" && headers !== null && !Array.isArray(headers) ? (
|
|
614
|
+
// The run-event schema checked the headers map.
|
|
615
|
+
{ headers }
|
|
616
|
+
) : {}
|
|
617
|
+
},
|
|
618
|
+
"ADMITTED"
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
if (hasUnprocessedInput(fork.history)) this.scheduleTask(fork);
|
|
623
|
+
await this.runUntilIdle(false);
|
|
624
|
+
return newRunId;
|
|
625
|
+
}
|
|
279
626
|
durationConfig(value, key) {
|
|
280
627
|
const ms = optionalDuration(value, key);
|
|
281
628
|
if (ms === void 0) return {};
|
|
@@ -351,7 +698,7 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
|
|
|
351
698
|
if (value === definition) return name2;
|
|
352
699
|
const name = Object.hasOwn(this.workflows, definition.name) ? `${definition.name}#${this.runCounter}` : definition.name;
|
|
353
700
|
this.workflows[name] = definition;
|
|
354
|
-
this.registry = registryOf(this.workflows);
|
|
701
|
+
this.registry = registryOf(this.workflows, this.interceptors);
|
|
355
702
|
return name;
|
|
356
703
|
}
|
|
357
704
|
run(runId) {
|
|
@@ -359,7 +706,7 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
|
|
|
359
706
|
if (!run) throw new Error(`Unknown run ${runId}`);
|
|
360
707
|
return run;
|
|
361
708
|
}
|
|
362
|
-
createRun(workflowFn, input, info, parent, extraStarted = {}, config = { workflowKey: workflowFn, runAttempt: 1 }) {
|
|
709
|
+
createRun(workflowFn, input, info, parent, extraStarted = {}, config = { workflowKey: workflowFn, runAttempt: 1 }, admitted = []) {
|
|
363
710
|
this.runCounter += 1;
|
|
364
711
|
const runId = info.runId ?? `run-${this.runCounter}`;
|
|
365
712
|
const snapshot = {
|
|
@@ -381,7 +728,9 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
|
|
|
381
728
|
info: snapshot,
|
|
382
729
|
children: /* @__PURE__ */ new Map(),
|
|
383
730
|
config,
|
|
384
|
-
...parent ? { parent } : {}
|
|
731
|
+
...parent ? { parent } : {},
|
|
732
|
+
updates: /* @__PURE__ */ new Map(),
|
|
733
|
+
sinkCalls: []
|
|
385
734
|
};
|
|
386
735
|
const firstTaskDelay = config.startDelayMs ?? config.firstTaskDelayMs;
|
|
387
736
|
if (firstTaskDelay !== void 0 && firstTaskDelay > 0) {
|
|
@@ -394,19 +743,30 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
|
|
|
394
743
|
this.dues.push({ runId, kind: "executionTimeout", at: config.executionDeadline });
|
|
395
744
|
this.runs.set(runId, run);
|
|
396
745
|
this.runOrder.push(runId);
|
|
397
|
-
this.append(
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
746
|
+
this.append(
|
|
747
|
+
run,
|
|
748
|
+
{
|
|
749
|
+
type: "WORKFLOW_STARTED",
|
|
750
|
+
// The service writes the seed of ctx.random()/ctx.uuid() into the start
|
|
751
|
+
// event so the history replays on its own; it uses the run id.
|
|
752
|
+
payload: {
|
|
753
|
+
workflowFn,
|
|
754
|
+
input,
|
|
755
|
+
taskQueue: config.taskQueue ?? "managed",
|
|
756
|
+
randomnessSeed: runId,
|
|
757
|
+
...extraStarted,
|
|
758
|
+
...this.startedFields(config)
|
|
759
|
+
}
|
|
760
|
+
},
|
|
761
|
+
admitted.length === 0
|
|
762
|
+
);
|
|
763
|
+
if (admitted.length > 0) {
|
|
764
|
+
for (const update of admitted) {
|
|
765
|
+
run.updates.set(update.updateId, update);
|
|
766
|
+
this.append(run, { type: "UPDATE_ADMITTED", payload: admittedPayload(update) }, false);
|
|
408
767
|
}
|
|
409
|
-
|
|
768
|
+
this.scheduleTask(run);
|
|
769
|
+
}
|
|
410
770
|
return runId;
|
|
411
771
|
}
|
|
412
772
|
/** The R2 fields of `WORKFLOW_STARTED`, each written only when it is set. */
|
|
@@ -434,9 +794,13 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
|
|
|
434
794
|
if (config.lastCompletionResult !== void 0 && this.historyRelease >= 2)
|
|
435
795
|
out["lastCompletionResult"] = config.lastCompletionResult;
|
|
436
796
|
if (config.lastFailure !== void 0) out["lastFailure"] = toJsonValue(config.lastFailure);
|
|
797
|
+
if (config.headers !== void 0) out["headers"] = { ...config.headers };
|
|
798
|
+
if (config.searchAttributes !== void 0) out["searchAttributes"] = config.searchAttributes;
|
|
799
|
+
if (config.fairnessKey !== void 0) out["fairnessKey"] = config.fairnessKey;
|
|
800
|
+
if (config.fairnessWeight !== void 0) out["fairnessWeight"] = config.fairnessWeight;
|
|
437
801
|
return out;
|
|
438
802
|
}
|
|
439
|
-
append(run, draft) {
|
|
803
|
+
append(run, draft, schedule = true) {
|
|
440
804
|
const event = {
|
|
441
805
|
seq: run.history.length + 1,
|
|
442
806
|
type: draft.type,
|
|
@@ -447,7 +811,7 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
|
|
|
447
811
|
applyEvent(run.state, event);
|
|
448
812
|
run.history.push(event);
|
|
449
813
|
if (run.delayedUntil !== void 0 && event.type === "SIGNAL_RECEIVED") this.endDelay(run);
|
|
450
|
-
if (run.delayedUntil === void 0 && isInputEvent(event) && run.state.status === "RUNNING" && run.state.task.state === "NONE") {
|
|
814
|
+
if (schedule && run.delayedUntil === void 0 && isInputEvent(event) && run.state.status === "RUNNING" && run.state.task.state === "NONE") {
|
|
451
815
|
run.taskCounter += 1;
|
|
452
816
|
const taskId = `${run.runId}-task-${run.taskCounter}`;
|
|
453
817
|
const scheduled = {
|
|
@@ -465,19 +829,9 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
|
|
|
465
829
|
endDelay(run) {
|
|
466
830
|
run.delayedUntil = void 0;
|
|
467
831
|
this.dues = this.dues.filter((due) => !(due.runId === run.runId && due.kind === "start"));
|
|
468
|
-
|
|
469
|
-
run.taskCounter += 1;
|
|
470
|
-
const taskId = `${run.runId}-task-${run.taskCounter}`;
|
|
471
|
-
const scheduled = {
|
|
472
|
-
seq: run.history.length + 1,
|
|
473
|
-
type: "WORKFLOW_TASK_SCHEDULED",
|
|
474
|
-
payload: { taskId },
|
|
475
|
-
createdAt: new Date(this.clock).toISOString()
|
|
476
|
-
};
|
|
477
|
-
applyEvent(run.state, scheduled);
|
|
478
|
-
run.history.push(scheduled);
|
|
832
|
+
this.scheduleTask(run);
|
|
479
833
|
}
|
|
480
|
-
deliverSignal(runId, name, payload) {
|
|
834
|
+
deliverSignal(runId, name, payload, headers) {
|
|
481
835
|
const run = this.runs.get(runId);
|
|
482
836
|
if (!run || run.state.status !== "RUNNING") return false;
|
|
483
837
|
this.append(run, {
|
|
@@ -485,7 +839,8 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
|
|
|
485
839
|
payload: {
|
|
486
840
|
name,
|
|
487
841
|
payload: toJsonValue(payload),
|
|
488
|
-
receivedAt: new Date(this.clock).toISOString()
|
|
842
|
+
receivedAt: new Date(this.clock).toISOString(),
|
|
843
|
+
...headers !== void 0 ? { headers: { ...headers } } : {}
|
|
489
844
|
}
|
|
490
845
|
});
|
|
491
846
|
return true;
|
|
@@ -523,7 +878,7 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
|
|
|
523
878
|
continue;
|
|
524
879
|
}
|
|
525
880
|
const attempt = this.attempts.find(
|
|
526
|
-
(entry) => entry.dueAt <= this.clock && !this.isHeld(entry
|
|
881
|
+
(entry) => entry.dueAt <= this.clock && !this.isHeld(this.holdKeyOf(entry))
|
|
527
882
|
);
|
|
528
883
|
if (attempt) {
|
|
529
884
|
this.attempts = this.attempts.filter((entry) => entry !== attempt);
|
|
@@ -545,12 +900,12 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
|
|
|
545
900
|
}
|
|
546
901
|
/** When a held attempt's schedule-to-start timeout expires, if it has one. */
|
|
547
902
|
heldDeadline(entry) {
|
|
548
|
-
return this.isHeld(entry
|
|
903
|
+
return this.isHeld(this.holdKeyOf(entry)) && entry.scheduleToStart !== void 0 ? entry.dueAt + entry.scheduleToStart : void 0;
|
|
549
904
|
}
|
|
550
905
|
nextDue() {
|
|
551
906
|
const due = [...this.timers.map((timer) => timer.fireAt)];
|
|
552
907
|
for (const entry of this.attempts) {
|
|
553
|
-
if (!this.isHeld(entry
|
|
908
|
+
if (!this.isHeld(this.holdKeyOf(entry))) due.push(entry.dueAt);
|
|
554
909
|
const deadline = this.heldDeadline(entry);
|
|
555
910
|
if (deadline !== void 0) due.push(deadline);
|
|
556
911
|
}
|
|
@@ -627,13 +982,25 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
|
|
|
627
982
|
async runTask(run) {
|
|
628
983
|
const taskId = run.state.task.taskId ?? `${run.runId}-task-${run.taskCounter}`;
|
|
629
984
|
this.append(run, { type: "WORKFLOW_TASK_STARTED", payload: { taskId, attempt: 1 } });
|
|
985
|
+
const delivered = [...run.updates.values()].filter(
|
|
986
|
+
(update) => update.status === "PENDING" || update.status === "ADMITTED"
|
|
987
|
+
);
|
|
988
|
+
const updates = delivered.map((update) => ({
|
|
989
|
+
updateId: update.updateId,
|
|
990
|
+
name: update.name,
|
|
991
|
+
args: update.args,
|
|
992
|
+
...update.headers !== void 0 ? { headers: { ...update.headers } } : {},
|
|
993
|
+
...update.status === "ADMITTED" ? { admitted: true } : {}
|
|
994
|
+
}));
|
|
630
995
|
const request = {
|
|
631
996
|
protocol: HISTORY_PROTOCOL,
|
|
632
997
|
kind: "task",
|
|
633
998
|
workflowFn: run.workflowFn,
|
|
634
999
|
info: run.info,
|
|
635
1000
|
history: run.history,
|
|
636
|
-
nowMs: this.clock
|
|
1001
|
+
nowMs: this.clock,
|
|
1002
|
+
...updates.length > 0 ? { updates } : {},
|
|
1003
|
+
...this.codeFeatures.length > 0 ? { features: [...this.codeFeatures] } : {}
|
|
637
1004
|
};
|
|
638
1005
|
const result = await this.executeWithLocalActivities(run, request);
|
|
639
1006
|
const fail = (cause, message) => {
|
|
@@ -648,7 +1015,11 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
|
|
|
648
1015
|
run.taskFailure = result;
|
|
649
1016
|
return;
|
|
650
1017
|
}
|
|
651
|
-
const
|
|
1018
|
+
const relief = {
|
|
1019
|
+
grantedLocalActivityIds: [],
|
|
1020
|
+
deliveredUpdateIds: delivered.map((update) => update.updateId)
|
|
1021
|
+
};
|
|
1022
|
+
const checked = validateCommands(result.commands, { enabled: this.enabled, relief });
|
|
652
1023
|
if (!checked.ok) {
|
|
653
1024
|
fail(checked.cause, checked.issues.map((issue) => issue.message).join("; "));
|
|
654
1025
|
return;
|
|
@@ -656,7 +1027,18 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
|
|
|
656
1027
|
const drafts = eventsForCommands(run.state, checked.commands, {
|
|
657
1028
|
taskId,
|
|
658
1029
|
nowMs: this.clock,
|
|
659
|
-
enabled: this.enabled
|
|
1030
|
+
enabled: this.enabled,
|
|
1031
|
+
relief,
|
|
1032
|
+
updates: Object.fromEntries(
|
|
1033
|
+
delivered.map((update) => [
|
|
1034
|
+
update.updateId,
|
|
1035
|
+
{
|
|
1036
|
+
name: update.name,
|
|
1037
|
+
args: update.args,
|
|
1038
|
+
...update.headers !== void 0 ? { headers: update.headers } : {}
|
|
1039
|
+
}
|
|
1040
|
+
])
|
|
1041
|
+
)
|
|
660
1042
|
});
|
|
661
1043
|
if (!drafts.ok) {
|
|
662
1044
|
fail(drafts.cause, drafts.issues.map((issue) => issue.message).join("; "));
|
|
@@ -664,12 +1046,55 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
|
|
|
664
1046
|
}
|
|
665
1047
|
if (result.unfinishedHandlers !== void 0 && result.unfinishedHandlers.length > 0)
|
|
666
1048
|
run.unfinishedHandlers = result.unfinishedHandlers;
|
|
1049
|
+
this.recordUpdateReplies(run, checked.commands, delivered);
|
|
1050
|
+
this.runSinks(run, result.sinkCalls ?? []);
|
|
667
1051
|
this.append(run, { type: "WORKFLOW_TASK_COMPLETED", taskId, payload: { taskId } });
|
|
668
1052
|
for (const draft of drafts.events) this.append(run, draft);
|
|
669
1053
|
for (const command of checked.commands)
|
|
670
1054
|
if (run.state.status === "RUNNING" || TERMINAL_COMMANDS.has(command.type))
|
|
671
1055
|
this.afterCommand(run, command);
|
|
672
1056
|
}
|
|
1057
|
+
/** What a task's update replies mean for the run's update rows. */
|
|
1058
|
+
recordUpdateReplies(run, commands, delivered) {
|
|
1059
|
+
const answered = /* @__PURE__ */ new Set();
|
|
1060
|
+
for (const command of commands) {
|
|
1061
|
+
if (command.type !== "ACCEPT_UPDATE" && command.type !== "REJECT_UPDATE" && command.type !== "COMPLETE_UPDATE")
|
|
1062
|
+
continue;
|
|
1063
|
+
const entry = run.updates.get(command.updateId);
|
|
1064
|
+
if (!entry) continue;
|
|
1065
|
+
answered.add(command.updateId);
|
|
1066
|
+
if (command.type === "ACCEPT_UPDATE") entry.status = "ACCEPTED";
|
|
1067
|
+
else if (command.type === "REJECT_UPDATE") {
|
|
1068
|
+
entry.status = "REJECTED";
|
|
1069
|
+
entry.failure = command.failure;
|
|
1070
|
+
} else {
|
|
1071
|
+
entry.status = "COMPLETED";
|
|
1072
|
+
if (command.failure !== void 0) entry.failure = command.failure;
|
|
1073
|
+
else entry.result = command.result ?? null;
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
for (const update of delivered)
|
|
1077
|
+
if (!answered.has(update.updateId) && update.status === "PENDING") {
|
|
1078
|
+
update.status = "REJECTED";
|
|
1079
|
+
update.failure = {
|
|
1080
|
+
errorType: "VALIDATION",
|
|
1081
|
+
type: "UpdateNotHandled",
|
|
1082
|
+
message: `The workflow task did not answer update "${update.name}"`
|
|
1083
|
+
};
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
/** Keep a task's sink calls and call the configured sinks with them. */
|
|
1087
|
+
runSinks(run, calls) {
|
|
1088
|
+
for (const call of calls) {
|
|
1089
|
+
run.sinkCalls.push(call);
|
|
1090
|
+
const fn = this.sinks[call.sink]?.[call.fn];
|
|
1091
|
+
if (fn === void 0) continue;
|
|
1092
|
+
try {
|
|
1093
|
+
fn(...call.args);
|
|
1094
|
+
} catch {
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
673
1098
|
execute(request) {
|
|
674
1099
|
return this.executor ? this.executor(request) : runWorkflowTaskInProcess(this.registry, request);
|
|
675
1100
|
}
|
|
@@ -728,6 +1153,7 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
|
|
|
728
1153
|
afterCommand(run, command) {
|
|
729
1154
|
switch (command.type) {
|
|
730
1155
|
case "SCHEDULE_ACTIVITY": {
|
|
1156
|
+
const user = command.kind === "user";
|
|
731
1157
|
const attempt = {
|
|
732
1158
|
runId: run.runId,
|
|
733
1159
|
activityId: command.activityId,
|
|
@@ -735,11 +1161,14 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
|
|
|
735
1161
|
dueAt: this.clock,
|
|
736
1162
|
scheduledAt: this.clock,
|
|
737
1163
|
args: command.args,
|
|
738
|
-
|
|
739
|
-
|
|
1164
|
+
kind: command.kind,
|
|
1165
|
+
nodeType: user ? "" : command.nodeType,
|
|
1166
|
+
operation: user ? command.activityName : command.operation,
|
|
740
1167
|
retry: command.retry,
|
|
741
1168
|
cancelRequested: false,
|
|
742
|
-
|
|
1169
|
+
...!user && command.connection !== void 0 ? { connection: command.connection } : {},
|
|
1170
|
+
...user ? { taskQueue: command.taskQueue } : {},
|
|
1171
|
+
...command.headers !== void 0 ? { headers: { ...command.headers } } : {},
|
|
743
1172
|
...command.scheduleToCloseTimeout !== void 0 ? { scheduleToClose: command.scheduleToCloseTimeout } : {},
|
|
744
1173
|
...command.scheduleToStartTimeout !== void 0 ? { scheduleToStart: command.scheduleToStartTimeout } : {}
|
|
745
1174
|
};
|
|
@@ -1020,40 +1449,151 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
|
|
|
1020
1449
|
});
|
|
1021
1450
|
return;
|
|
1022
1451
|
}
|
|
1023
|
-
const mock = this.mocks.get(`${attempt.nodeType}#${attempt.operation}`) ?? this.mocks.get(attempt.nodeType);
|
|
1024
1452
|
let failure;
|
|
1025
1453
|
let result = null;
|
|
1026
1454
|
let heartbeatDetails = attempt.heartbeatDetails;
|
|
1455
|
+
let asyncToken;
|
|
1027
1456
|
try {
|
|
1028
|
-
if (
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
nonRetryable: true,
|
|
1034
|
-
|
|
1457
|
+
if (attempt.kind === "user") {
|
|
1458
|
+
const impl = findActivityImplementation(this.activityImpls, attempt.operation);
|
|
1459
|
+
if (!impl)
|
|
1460
|
+
throw new ApplicationError(
|
|
1461
|
+
`No implementation of activity ${attempt.operation} (use env.registerActivity or env.mockActivity)`,
|
|
1462
|
+
{ errorType: "VALIDATION", nonRetryable: true, type: "ActivityNotRegistered" }
|
|
1463
|
+
);
|
|
1464
|
+
const ctx = this.activityContext(
|
|
1465
|
+
attempt,
|
|
1466
|
+
(details) => {
|
|
1467
|
+
heartbeatDetails = details;
|
|
1468
|
+
},
|
|
1469
|
+
(token) => {
|
|
1470
|
+
asyncToken = token;
|
|
1035
1471
|
}
|
|
1036
1472
|
);
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1473
|
+
result = toJsonValue(
|
|
1474
|
+
await executeActivity(impl, ctx, attempt.args, this.activityInterceptors)
|
|
1475
|
+
);
|
|
1476
|
+
} else {
|
|
1477
|
+
const mock = this.mocks.get(`${attempt.nodeType}#${attempt.operation}`) ?? this.mocks.get(attempt.nodeType);
|
|
1478
|
+
if (!mock)
|
|
1479
|
+
throw new ApplicationError(
|
|
1480
|
+
`No mock for ${attempt.nodeType} ${attempt.operation} (use env.mock)`,
|
|
1481
|
+
{
|
|
1482
|
+
errorType: "VALIDATION",
|
|
1483
|
+
nonRetryable: true,
|
|
1484
|
+
type: "UnmockedActivity"
|
|
1485
|
+
}
|
|
1486
|
+
);
|
|
1487
|
+
result = toJsonValue(
|
|
1488
|
+
await mock(attempt.args, {
|
|
1489
|
+
runId: attempt.runId,
|
|
1490
|
+
activityId: attempt.activityId,
|
|
1491
|
+
attempt: attempt.attempt,
|
|
1492
|
+
nodeType: attempt.nodeType,
|
|
1493
|
+
operation: attempt.operation,
|
|
1494
|
+
...attempt.connection !== void 0 ? { connection: attempt.connection } : {},
|
|
1495
|
+
...attempt.heartbeatDetails !== void 0 ? { heartbeatDetails: attempt.heartbeatDetails } : {},
|
|
1496
|
+
heartbeat: (details) => {
|
|
1497
|
+
heartbeatDetails = toJsonValue(details);
|
|
1498
|
+
}
|
|
1499
|
+
})
|
|
1500
|
+
);
|
|
1501
|
+
}
|
|
1051
1502
|
} catch (error) {
|
|
1052
|
-
failure = errorToFailure(
|
|
1503
|
+
failure = errorToFailure(
|
|
1504
|
+
error,
|
|
1505
|
+
attempt.kind === "user" ? "sdk-ts" : `node:${attempt.nodeType}`
|
|
1506
|
+
);
|
|
1053
1507
|
delete failure.stackTrace;
|
|
1054
1508
|
}
|
|
1055
1509
|
if (run.state.status !== "RUNNING" || !isActivityOpen(run.state.activities[attempt.activityId] ?? activity))
|
|
1056
1510
|
return;
|
|
1511
|
+
if (asyncToken !== void 0 && failure === void 0) {
|
|
1512
|
+
this.asyncAttempts.set(asyncToken, {
|
|
1513
|
+
...attempt,
|
|
1514
|
+
...heartbeatDetails !== void 0 ? { heartbeatDetails } : {}
|
|
1515
|
+
});
|
|
1516
|
+
return;
|
|
1517
|
+
}
|
|
1518
|
+
this.finishAttempt(run, attempt, failure, result, heartbeatDetails);
|
|
1519
|
+
}
|
|
1520
|
+
/** The `ActivityContext` of one user-activity attempt. */
|
|
1521
|
+
activityContext(attempt, onHeartbeat, onAsync) {
|
|
1522
|
+
const run = this.runs.get(attempt.runId);
|
|
1523
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
1524
|
+
const signal = {
|
|
1525
|
+
get aborted() {
|
|
1526
|
+
return attempt.cancelRequested;
|
|
1527
|
+
},
|
|
1528
|
+
addEventListener: (_type, listener) => {
|
|
1529
|
+
listeners.add(listener);
|
|
1530
|
+
},
|
|
1531
|
+
removeEventListener: (_type, listener) => {
|
|
1532
|
+
listeners.delete(listener);
|
|
1533
|
+
}
|
|
1534
|
+
};
|
|
1535
|
+
return {
|
|
1536
|
+
info: {
|
|
1537
|
+
runId: attempt.runId,
|
|
1538
|
+
workspaceId: this.workspaceId,
|
|
1539
|
+
...run?.config.workflowKey !== void 0 ? { workflowKey: run.config.workflowKey } : {},
|
|
1540
|
+
activityId: attempt.activityId,
|
|
1541
|
+
activityName: attempt.operation,
|
|
1542
|
+
taskQueue: attempt.taskQueue ?? "managed",
|
|
1543
|
+
attempt: attempt.attempt,
|
|
1544
|
+
...attempt.heartbeatDetails !== void 0 ? { heartbeatDetails: attempt.heartbeatDetails } : {},
|
|
1545
|
+
headers: { ...attempt.headers ?? {} }
|
|
1546
|
+
},
|
|
1547
|
+
heartbeat: (details) => {
|
|
1548
|
+
onHeartbeat(toJsonValue(details));
|
|
1549
|
+
return Promise.resolve({ cancelRequested: attempt.cancelRequested, paused: false });
|
|
1550
|
+
},
|
|
1551
|
+
cancellationSignal: signal,
|
|
1552
|
+
completeAsync: () => {
|
|
1553
|
+
const token = `test-task-token:${attempt.runId}:${attempt.activityId}:${attempt.attempt}`;
|
|
1554
|
+
onAsync(token);
|
|
1555
|
+
return Promise.resolve(token);
|
|
1556
|
+
},
|
|
1557
|
+
connection: (name) => {
|
|
1558
|
+
const found = this.connections[name];
|
|
1559
|
+
if (found === void 0)
|
|
1560
|
+
return Promise.reject(
|
|
1561
|
+
new ApplicationError(`No connection "${name}" in the test environment`, {
|
|
1562
|
+
errorType: "AUTHORIZATION",
|
|
1563
|
+
nonRetryable: true,
|
|
1564
|
+
type: "ConnectionNotReleased"
|
|
1565
|
+
})
|
|
1566
|
+
);
|
|
1567
|
+
return Promise.resolve({ ...found });
|
|
1568
|
+
}
|
|
1569
|
+
};
|
|
1570
|
+
}
|
|
1571
|
+
/** Complete an activity that called `completeAsync()` (`completeActivityAsync`). */
|
|
1572
|
+
async completeActivityAsync(token, result) {
|
|
1573
|
+
const attempt = this.takeAsync(token);
|
|
1574
|
+
const run = this.run(attempt.runId);
|
|
1575
|
+
this.finishAttempt(run, attempt, void 0, toJsonValue(result), attempt.heartbeatDetails);
|
|
1576
|
+
await this.runUntilIdle(false);
|
|
1577
|
+
}
|
|
1578
|
+
/** Fail an activity that called `completeAsync()` (`failActivityAsync`); the retry policy applies. */
|
|
1579
|
+
async failActivityAsync(token, error) {
|
|
1580
|
+
const attempt = this.takeAsync(token);
|
|
1581
|
+
const run = this.run(attempt.runId);
|
|
1582
|
+
const failure = errorToFailure(error, "sdk-ts");
|
|
1583
|
+
delete failure.stackTrace;
|
|
1584
|
+
this.finishAttempt(run, attempt, failure, null, attempt.heartbeatDetails);
|
|
1585
|
+
await this.runUntilIdle(false);
|
|
1586
|
+
}
|
|
1587
|
+
takeAsync(token) {
|
|
1588
|
+
const attempt = this.asyncAttempts.get(token);
|
|
1589
|
+
if (!attempt) throw new Error(`Unknown or used activity task token ${token}`);
|
|
1590
|
+
this.asyncAttempts.delete(token);
|
|
1591
|
+
return attempt;
|
|
1592
|
+
}
|
|
1593
|
+
/** Record an attempt's outcome: completion, a final failure or timeout, or the next attempt. */
|
|
1594
|
+
finishAttempt(run, attempt, failure, result, heartbeatDetails) {
|
|
1595
|
+
const open = run.state.activities[attempt.activityId];
|
|
1596
|
+
if (run.state.status !== "RUNNING" || !open || !isActivityOpen(open)) return;
|
|
1057
1597
|
const started = () => {
|
|
1058
1598
|
this.append(run, {
|
|
1059
1599
|
type: "ACTIVITY_STARTED",
|
|
@@ -1131,18 +1671,37 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
|
|
|
1131
1671
|
if (decision.delayMs > 0) config.firstTaskDelayMs = decision.delayMs;
|
|
1132
1672
|
this.startSuccessor(run, run.state.input ?? null, config);
|
|
1133
1673
|
}
|
|
1134
|
-
|
|
1674
|
+
/**
|
|
1675
|
+
* A run closed: drop its pending work, retry it, apply the parent-close
|
|
1676
|
+
* policy to its children (except `keptChildren`, which follow a reset
|
|
1677
|
+
* fork), settle its open updates and notify its parent.
|
|
1678
|
+
*/
|
|
1679
|
+
onClosed(run, keptChildren = /* @__PURE__ */ new Set()) {
|
|
1135
1680
|
this.timers = this.timers.filter((timer) => timer.runId !== run.runId);
|
|
1136
1681
|
this.attempts = this.attempts.filter((entry) => entry.runId !== run.runId);
|
|
1137
1682
|
this.dues = this.dues.filter((entry) => entry.runId !== run.runId);
|
|
1683
|
+
for (const [token, entry] of this.asyncAttempts)
|
|
1684
|
+
if (entry.runId === run.runId) this.asyncAttempts.delete(token);
|
|
1138
1685
|
this.retryRun(run);
|
|
1139
1686
|
for (const [childId2, childRunId] of run.children) {
|
|
1687
|
+
if (keptChildren.has(childId2)) continue;
|
|
1140
1688
|
const child = run.state.children[childId2];
|
|
1141
1689
|
if (!child || !isChildOpen(child) || childRunId === void 0) continue;
|
|
1142
1690
|
if (child.parentClosePolicy === "TERMINATE") this.terminate(childRunId, "parent closed");
|
|
1143
1691
|
else if (child.parentClosePolicy === "REQUEST_CANCEL")
|
|
1144
1692
|
this.requestCancel(childRunId, "parent closed");
|
|
1145
1693
|
}
|
|
1694
|
+
for (const update of run.updates.values()) {
|
|
1695
|
+
if (update.status !== "PENDING" && update.status !== "ACCEPTED" && update.status !== "ADMITTED")
|
|
1696
|
+
continue;
|
|
1697
|
+
const accepted = update.status === "ACCEPTED";
|
|
1698
|
+
update.status = accepted ? "COMPLETED" : "REJECTED";
|
|
1699
|
+
update.failure = {
|
|
1700
|
+
errorType: accepted ? "PERMANENT" : "VALIDATION",
|
|
1701
|
+
type: "RunClosed",
|
|
1702
|
+
message: `The run closed before update "${update.name}" ${accepted ? "completed" : "was accepted"}`
|
|
1703
|
+
};
|
|
1704
|
+
}
|
|
1146
1705
|
if (!run.parent) return;
|
|
1147
1706
|
const parent = this.runs.get(run.parent.runId);
|
|
1148
1707
|
if (!parent || parent.state.status !== "RUNNING") return;
|
|
@@ -1150,6 +1709,10 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
|
|
|
1150
1709
|
if (!parent.state.children[childId] || !isChildOpen(parent.state.children[childId])) return;
|
|
1151
1710
|
const last = run.history[run.history.length - 1];
|
|
1152
1711
|
if (!last) return;
|
|
1712
|
+
if (run.resetTo !== void 0) {
|
|
1713
|
+
parent.children.set(childId, run.resetTo);
|
|
1714
|
+
return;
|
|
1715
|
+
}
|
|
1153
1716
|
if (run.continuedTo !== void 0) {
|
|
1154
1717
|
const next = this.runs.get(run.continuedTo);
|
|
1155
1718
|
if (next) {
|
|
@@ -1200,7 +1763,8 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
|
|
|
1200
1763
|
async resultOf(runId) {
|
|
1201
1764
|
await this.runUntilIdle(this.timeSkipping);
|
|
1202
1765
|
let run = this.run(runId);
|
|
1203
|
-
while (run.continuedTo !== void 0
|
|
1766
|
+
while (run.continuedTo !== void 0 || run.resetTo !== void 0)
|
|
1767
|
+
run = this.run(run.continuedTo ?? run.resetTo ?? run.runId);
|
|
1204
1768
|
if (run.taskFailure) throw new WorkflowTaskFailedError(run.runId, run.taskFailure);
|
|
1205
1769
|
if (this.failOnUnfinishedHandlers && run.unfinishedHandlers !== void 0)
|
|
1206
1770
|
throw new UnfinishedHandlersError(run.runId, run.unfinishedHandlers);
|
|
@@ -1472,4 +2036,4 @@ async function runWorkflowTaskInVm(script, request, options = {}) {
|
|
|
1472
2036
|
}
|
|
1473
2037
|
}
|
|
1474
2038
|
|
|
1475
|
-
export { HistoryAssertionError, ReplayError, RunBlockedError, TestRunHandle, TestWorkflowEnvironment, UnfinishedHandlersError, WorkflowAlreadyStartedError, WorkflowFailedError, WorkflowTaskFailedError, assertHistory, bundleToScript, drainJobs, expectIR, jsonCopy, registryOf, replay, replayRequest, runWorkflowTaskInProcess, runWorkflowTaskInVm, simulate };
|
|
2039
|
+
export { HistoryAssertionError, QueryFailedError, QueryRejectedError, ReplayError, RunBlockedError, TestRunHandle, TestWorkflowEnvironment, UnfinishedHandlersError, UpdateRefusedError, WorkflowAlreadyStartedError, WorkflowFailedError, WorkflowTaskFailedError, assertHistory, bundleToScript, drainJobs, expectIR, jsonCopy, registryOf, replay, replayRequest, runWorkflowTaskInProcess, runWorkflowTaskInVm, simulate };
|