@dudousxd/nestjs-durable-core 0.78.0 → 0.79.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1063 -78
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +396 -21
- package/dist/index.d.ts +396 -21
- package/dist/index.js +1040 -78
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -265,10 +265,16 @@ var FatalError = class extends Error {
|
|
|
265
265
|
__name(this, "FatalError");
|
|
266
266
|
}
|
|
267
267
|
code;
|
|
268
|
-
|
|
268
|
+
/** Structured, JSON-serializable detail (e.g. validation issues). It travels with the failure to
|
|
269
|
+
* the run's error, a parent's `allSettled` item and an onError handler's input. */
|
|
270
|
+
details;
|
|
271
|
+
/** Read by the step result builders, so a dispatched step's retry policy honours it too. */
|
|
272
|
+
retryable = false;
|
|
273
|
+
constructor(message, code, details) {
|
|
269
274
|
super(message);
|
|
270
275
|
this.name = "FatalError";
|
|
271
276
|
this.code = code;
|
|
277
|
+
this.details = details;
|
|
272
278
|
}
|
|
273
279
|
};
|
|
274
280
|
var ConcurrencyLimitError = class extends Error {
|
|
@@ -346,6 +352,8 @@ var NonDeterminismError = class extends Error {
|
|
|
346
352
|
static {
|
|
347
353
|
__name(this, "NonDeterminismError");
|
|
348
354
|
}
|
|
355
|
+
/** Marks the failure as a broken deploy, which an `onError` route never takes. */
|
|
356
|
+
code = "non_determinism";
|
|
349
357
|
runId;
|
|
350
358
|
seq;
|
|
351
359
|
constructor(runId, seq, expected, recorded) {
|
|
@@ -757,6 +765,9 @@ async function runStepHandler(task, handler) {
|
|
|
757
765
|
...typeof e?.code === "string" ? {
|
|
758
766
|
code: e.code
|
|
759
767
|
} : {},
|
|
768
|
+
...e?.details !== void 0 ? {
|
|
769
|
+
details: e.details
|
|
770
|
+
} : {},
|
|
760
771
|
...typeof e?.retryable === "boolean" ? {
|
|
761
772
|
retryable: e.retryable
|
|
762
773
|
} : {}
|
|
@@ -833,11 +844,36 @@ __name(isFailedCompletion, "isFailedCompletion");
|
|
|
833
844
|
function unwrapCompletion(payload, label) {
|
|
834
845
|
const c = payload;
|
|
835
846
|
if (c && typeof c === "object" && "ok" in c && c.ok === false) {
|
|
836
|
-
throw new FatalError(`${label} failed: ${c.error}
|
|
847
|
+
throw new FatalError(`${label} failed: ${c.error}`, c.code, c.details);
|
|
837
848
|
}
|
|
838
849
|
return c?.value;
|
|
839
850
|
}
|
|
840
851
|
__name(unwrapCompletion, "unwrapCompletion");
|
|
852
|
+
function failedCompletion(error2) {
|
|
853
|
+
return {
|
|
854
|
+
ok: false,
|
|
855
|
+
error: error2.message,
|
|
856
|
+
...error2.code !== void 0 ? {
|
|
857
|
+
code: error2.code
|
|
858
|
+
} : {},
|
|
859
|
+
...error2.details !== void 0 ? {
|
|
860
|
+
details: error2.details
|
|
861
|
+
} : {}
|
|
862
|
+
};
|
|
863
|
+
}
|
|
864
|
+
__name(failedCompletion, "failedCompletion");
|
|
865
|
+
function completionError(c) {
|
|
866
|
+
return {
|
|
867
|
+
message: c.error,
|
|
868
|
+
...c.code !== void 0 ? {
|
|
869
|
+
code: c.code
|
|
870
|
+
} : {},
|
|
871
|
+
...c.details !== void 0 ? {
|
|
872
|
+
details: c.details
|
|
873
|
+
} : {}
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
__name(completionError, "completionError");
|
|
841
877
|
|
|
842
878
|
// src/concurrency.ts
|
|
843
879
|
var DEFAULT_CONCURRENCY_STATUSES = [
|
|
@@ -1158,7 +1194,7 @@ function allWaves(size, concurrency) {
|
|
|
1158
1194
|
};
|
|
1159
1195
|
}
|
|
1160
1196
|
__name(allWaves, "allWaves");
|
|
1161
|
-
var ENGINE_CHILD_ID = /^(.+)\.(?:all\.\d+\.(?:w\d+\.)?\d+|child\.\d+)$/;
|
|
1197
|
+
var ENGINE_CHILD_ID = /^(.+)\.(?:all\.\d+\.(?:w\d+\.)?\d+|child\.\d+|onError\.\d+)$/;
|
|
1162
1198
|
function parentRunIdOf(runId) {
|
|
1163
1199
|
return ENGINE_CHILD_ID.exec(runId)?.[1];
|
|
1164
1200
|
}
|
|
@@ -1243,7 +1279,11 @@ function canonicalizeRegistrations(regs) {
|
|
|
1243
1279
|
requires: r.requires ? [
|
|
1244
1280
|
...new Set(r.requires)
|
|
1245
1281
|
].sort() : null,
|
|
1246
|
-
origin: r.origin ?? null
|
|
1282
|
+
origin: r.origin ?? null,
|
|
1283
|
+
// Conditional, so a registration that states no routes keeps the hash it had before routes existed.
|
|
1284
|
+
...r.onError !== void 0 ? {
|
|
1285
|
+
onError: canonicalRoutes(r.onError)
|
|
1286
|
+
} : {}
|
|
1247
1287
|
}));
|
|
1248
1288
|
const byJson = /* @__PURE__ */ new Map();
|
|
1249
1289
|
for (const entry of canonical) byJson.set(JSON.stringify(entry), entry);
|
|
@@ -1252,6 +1292,20 @@ function canonicalizeRegistrations(regs) {
|
|
|
1252
1292
|
].sort().map((k) => byJson.get(k));
|
|
1253
1293
|
}
|
|
1254
1294
|
__name(canonicalizeRegistrations, "canonicalizeRegistrations");
|
|
1295
|
+
function canonicalRoutes(routes) {
|
|
1296
|
+
const byJson = /* @__PURE__ */ new Map();
|
|
1297
|
+
for (const r of routes) {
|
|
1298
|
+
const entry = {
|
|
1299
|
+
code: r.code ?? null,
|
|
1300
|
+
workflow: r.workflow
|
|
1301
|
+
};
|
|
1302
|
+
byJson.set(JSON.stringify(entry), entry);
|
|
1303
|
+
}
|
|
1304
|
+
return [
|
|
1305
|
+
...byJson.keys()
|
|
1306
|
+
].sort().map((k) => byJson.get(k));
|
|
1307
|
+
}
|
|
1308
|
+
__name(canonicalRoutes, "canonicalRoutes");
|
|
1255
1309
|
function canonicalizeForHash(d) {
|
|
1256
1310
|
const set = /* @__PURE__ */ __name((xs) => [
|
|
1257
1311
|
...new Set(xs)
|
|
@@ -1286,13 +1340,13 @@ function fnv1a64Hex(input) {
|
|
|
1286
1340
|
const OFFSET = 0xcbf29ce484222325n;
|
|
1287
1341
|
const PRIME = 0x100000001b3n;
|
|
1288
1342
|
const MASK = 0xffffffffffffffffn;
|
|
1289
|
-
let
|
|
1343
|
+
let hash2 = OFFSET;
|
|
1290
1344
|
for (let i = 0; i < input.length; i += 1) {
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1345
|
+
hash2 ^= BigInt(input.charCodeAt(i) & 255);
|
|
1346
|
+
hash2 ^= BigInt(input.charCodeAt(i) >> 8 & 255);
|
|
1347
|
+
hash2 = hash2 * PRIME & MASK;
|
|
1294
1348
|
}
|
|
1295
|
-
return
|
|
1349
|
+
return hash2.toString(16).padStart(16, "0");
|
|
1296
1350
|
}
|
|
1297
1351
|
__name(fnv1a64Hex, "fnv1a64Hex");
|
|
1298
1352
|
function descriptorHash(descriptor) {
|
|
@@ -1408,6 +1462,20 @@ function aggregateAnnouncements(descriptors) {
|
|
|
1408
1462
|
const requires = [
|
|
1409
1463
|
...requiresByJson.values()
|
|
1410
1464
|
].sort((x, y) => x.join(",").localeCompare(y.join(",")));
|
|
1465
|
+
const routesByJson = /* @__PURE__ */ new Map();
|
|
1466
|
+
for (const a of announcements) {
|
|
1467
|
+
if (a.onError === void 0) continue;
|
|
1468
|
+
const canonical = canonicalRoutes(a.onError).map((r) => r.code === null ? {
|
|
1469
|
+
workflow: r.workflow
|
|
1470
|
+
} : {
|
|
1471
|
+
code: r.code,
|
|
1472
|
+
workflow: r.workflow
|
|
1473
|
+
});
|
|
1474
|
+
routesByJson.set(JSON.stringify(canonical), canonical);
|
|
1475
|
+
}
|
|
1476
|
+
const onError = [
|
|
1477
|
+
...routesByJson.keys()
|
|
1478
|
+
].sort().map((k) => routesByJson.get(k) ?? []);
|
|
1411
1479
|
const disagreements = [];
|
|
1412
1480
|
if (groups.length > 1) disagreements.push({
|
|
1413
1481
|
axis: "group",
|
|
@@ -1423,6 +1491,14 @@ function aggregateAnnouncements(descriptors) {
|
|
|
1423
1491
|
values: requires.map((r) => r.join(","))
|
|
1424
1492
|
});
|
|
1425
1493
|
}
|
|
1494
|
+
if (onError.length > 1) {
|
|
1495
|
+
disagreements.push({
|
|
1496
|
+
axis: "onError",
|
|
1497
|
+
values: [
|
|
1498
|
+
...routesByJson.keys()
|
|
1499
|
+
].sort()
|
|
1500
|
+
});
|
|
1501
|
+
}
|
|
1426
1502
|
const first = announcements[0];
|
|
1427
1503
|
if (!first) continue;
|
|
1428
1504
|
out.push({
|
|
@@ -1435,6 +1511,7 @@ function aggregateAnnouncements(descriptors) {
|
|
|
1435
1511
|
groups,
|
|
1436
1512
|
origins,
|
|
1437
1513
|
requires,
|
|
1514
|
+
onError,
|
|
1438
1515
|
runtimes: [
|
|
1439
1516
|
...new Set(announcements.map((a) => a.runtime))
|
|
1440
1517
|
].sort(),
|
|
@@ -1468,6 +1545,7 @@ function observedAnnouncements(heartbeats, covered) {
|
|
|
1468
1545
|
],
|
|
1469
1546
|
origins: [],
|
|
1470
1547
|
requires: [],
|
|
1548
|
+
onError: [],
|
|
1471
1549
|
runtimes: [],
|
|
1472
1550
|
instances: [
|
|
1473
1551
|
...instances
|
|
@@ -1826,6 +1904,13 @@ function planDispatch(requires, liveDescriptors, cp, token) {
|
|
|
1826
1904
|
__name(planDispatch, "planDispatch");
|
|
1827
1905
|
|
|
1828
1906
|
// src/interfaces.ts
|
|
1907
|
+
function checkpointNameKind(name) {
|
|
1908
|
+
if (name.startsWith("signal:child:")) return "child";
|
|
1909
|
+
if (name.startsWith("fan:")) return "folded";
|
|
1910
|
+
if (name.startsWith("spawn:")) return "spawn";
|
|
1911
|
+
return "step";
|
|
1912
|
+
}
|
|
1913
|
+
__name(checkpointNameKind, "checkpointNameKind");
|
|
1829
1914
|
var TERMINAL_RUN_STATUSES = [
|
|
1830
1915
|
"completed",
|
|
1831
1916
|
"failed",
|
|
@@ -1838,6 +1923,71 @@ function isNonEmptyRunScope(scope) {
|
|
|
1838
1923
|
}
|
|
1839
1924
|
__name(isNonEmptyRunScope, "isNonEmptyRunScope");
|
|
1840
1925
|
|
|
1926
|
+
// src/on-error.ts
|
|
1927
|
+
var UNROUTABLE_CODES = /* @__PURE__ */ new Set([
|
|
1928
|
+
"non_determinism",
|
|
1929
|
+
"nested_workflow_call"
|
|
1930
|
+
]);
|
|
1931
|
+
var HANDOFF_SEQ_BASE = 1e9;
|
|
1932
|
+
var onErrorRunId = /* @__PURE__ */ __name((runId, attempt) => `${runId}.onError.${attempt}`, "onErrorRunId");
|
|
1933
|
+
var handoffName = /* @__PURE__ */ __name((handlerId) => `signal:child:${handlerId}`, "handoffName");
|
|
1934
|
+
var retiredHandoffName = /* @__PURE__ */ __name((handlerId) => `onError:retried:${handlerId}`, "retiredHandoffName");
|
|
1935
|
+
function handoffHandlerId(runId, cp) {
|
|
1936
|
+
const prefix = `signal:child:${runId}.onError.`;
|
|
1937
|
+
return cp.seq >= HANDOFF_SEQ_BASE && cp.name.startsWith(prefix) ? cp.name.slice("signal:child:".length) : void 0;
|
|
1938
|
+
}
|
|
1939
|
+
__name(handoffHandlerId, "handoffHandlerId");
|
|
1940
|
+
function liveHandoff(runId, checkpoints) {
|
|
1941
|
+
let live;
|
|
1942
|
+
for (const cp of checkpoints) {
|
|
1943
|
+
if (handoffHandlerId(runId, cp) !== void 0 && (!live || cp.seq > live.seq)) live = cp;
|
|
1944
|
+
}
|
|
1945
|
+
return live;
|
|
1946
|
+
}
|
|
1947
|
+
__name(liveHandoff, "liveHandoff");
|
|
1948
|
+
function routeFor(routes, error2) {
|
|
1949
|
+
if (!routes?.length || error2.code && UNROUTABLE_CODES.has(error2.code)) return void 0;
|
|
1950
|
+
return (error2.code ? routes.find((r) => r.code === error2.code) : void 0) ?? routes.find((r) => r.code === void 0);
|
|
1951
|
+
}
|
|
1952
|
+
__name(routeFor, "routeFor");
|
|
1953
|
+
function assertRoutes(workflow, routes) {
|
|
1954
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1955
|
+
for (const r of routes) {
|
|
1956
|
+
if (seen.has(r.code)) {
|
|
1957
|
+
throw new Error(r.code === void 0 ? `workflow ${workflow}: more than one onError route without a code` : `workflow ${workflow}: more than one onError route for code "${r.code}"`);
|
|
1958
|
+
}
|
|
1959
|
+
seen.add(r.code);
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
__name(assertRoutes, "assertRoutes");
|
|
1963
|
+
function codeOf(err) {
|
|
1964
|
+
const e = err;
|
|
1965
|
+
return [
|
|
1966
|
+
e?.code,
|
|
1967
|
+
e?.stepError?.code,
|
|
1968
|
+
e?.error?.code
|
|
1969
|
+
].find((c) => typeof c === "string");
|
|
1970
|
+
}
|
|
1971
|
+
__name(codeOf, "codeOf");
|
|
1972
|
+
function detailsOf(err) {
|
|
1973
|
+
const e = err;
|
|
1974
|
+
return e?.details ?? e?.stepError?.details ?? e?.error?.details;
|
|
1975
|
+
}
|
|
1976
|
+
__name(detailsOf, "detailsOf");
|
|
1977
|
+
function codeAndDetails(err) {
|
|
1978
|
+
const code = codeOf(err);
|
|
1979
|
+
const details = detailsOf(err);
|
|
1980
|
+
return {
|
|
1981
|
+
...code !== void 0 ? {
|
|
1982
|
+
code
|
|
1983
|
+
} : {},
|
|
1984
|
+
...details !== void 0 ? {
|
|
1985
|
+
details
|
|
1986
|
+
} : {}
|
|
1987
|
+
};
|
|
1988
|
+
}
|
|
1989
|
+
__name(codeAndDetails, "codeAndDetails");
|
|
1990
|
+
|
|
1841
1991
|
// src/tenant-group.ts
|
|
1842
1992
|
function tenantGroup(baseGroup, tenant) {
|
|
1843
1993
|
return tenant !== void 0 && tenant !== "" && tenant !== "default" ? `${baseGroup}@${tenant}` : baseGroup;
|
|
@@ -2897,7 +3047,8 @@ function createWorkflowCtx(host, runId, compensations, workflow = "", searchAttr
|
|
|
2897
3047
|
kind: "local",
|
|
2898
3048
|
error: {
|
|
2899
3049
|
message: err instanceof Error ? err.message : String(err),
|
|
2900
|
-
stack: err instanceof Error ? err.stack : void 0
|
|
3050
|
+
stack: err instanceof Error ? err.stack : void 0,
|
|
3051
|
+
...codeAndDetails(err)
|
|
2901
3052
|
},
|
|
2902
3053
|
events,
|
|
2903
3054
|
attempts: attempt,
|
|
@@ -3153,12 +3304,12 @@ function createWorkflowCtx(host, runId, compensations, workflow = "", searchAttr
|
|
|
3153
3304
|
}
|
|
3154
3305
|
]));
|
|
3155
3306
|
}, "child");
|
|
3156
|
-
const
|
|
3307
|
+
const fan = /* @__PURE__ */ __name(async (workflow2, inputs, opts, settle) => {
|
|
3157
3308
|
if (inputs.length === 0) return [];
|
|
3158
3309
|
if (stepNameOf(workflow2) !== void 0) {
|
|
3159
|
-
return allSteps(workflow2, inputs, opts);
|
|
3310
|
+
return allSteps(workflow2, inputs, opts, settle);
|
|
3160
3311
|
}
|
|
3161
|
-
const mode = opts?.mode ?? "waitAll";
|
|
3312
|
+
const mode = settle ? "waitAll" : opts?.mode ?? "waitAll";
|
|
3162
3313
|
const name = workflowName(workflow2);
|
|
3163
3314
|
const waves = allWaves(inputs.length, opts?.concurrency);
|
|
3164
3315
|
const positions = inputs.map(() => pos.next());
|
|
@@ -3280,24 +3431,45 @@ function createWorkflowCtx(host, runId, compensations, workflow = "", searchAttr
|
|
|
3280
3431
|
throw new WorkflowSuspended(void 0, fanJoin(active.map((k) => allWait(runId, firstSeq, waves ? k : void 0))));
|
|
3281
3432
|
}
|
|
3282
3433
|
const outputs = [];
|
|
3434
|
+
const results = [];
|
|
3283
3435
|
const failures = [];
|
|
3284
3436
|
for (let i = 0; i < inputs.length; i += 1) {
|
|
3285
3437
|
const c = existing[i]?.output;
|
|
3286
3438
|
if (c && typeof c === "object" && "ok" in c && c.ok === false) {
|
|
3439
|
+
const { message, ...rest } = completionError({
|
|
3440
|
+
...c,
|
|
3441
|
+
error: c.error ?? "unknown"
|
|
3442
|
+
});
|
|
3443
|
+
const failure = {
|
|
3444
|
+
id: id(i),
|
|
3445
|
+
error: message,
|
|
3446
|
+
...rest
|
|
3447
|
+
};
|
|
3287
3448
|
failures.push({
|
|
3288
3449
|
index: i,
|
|
3289
|
-
|
|
3290
|
-
|
|
3450
|
+
...failure
|
|
3451
|
+
});
|
|
3452
|
+
results.push({
|
|
3453
|
+
status: "rejected",
|
|
3454
|
+
...failure
|
|
3291
3455
|
});
|
|
3292
3456
|
outputs.push(void 0);
|
|
3293
3457
|
} else {
|
|
3294
|
-
|
|
3458
|
+
const value = c?.value;
|
|
3459
|
+
results.push({
|
|
3460
|
+
status: "fulfilled",
|
|
3461
|
+
value
|
|
3462
|
+
});
|
|
3463
|
+
outputs.push(value);
|
|
3295
3464
|
}
|
|
3296
3465
|
}
|
|
3466
|
+
if (settle) return results;
|
|
3297
3467
|
if (failures.length > 0) throw new GatherError(failures, inputs.length);
|
|
3298
3468
|
return outputs;
|
|
3299
|
-
}, "
|
|
3300
|
-
const
|
|
3469
|
+
}, "fan");
|
|
3470
|
+
const all = /* @__PURE__ */ __name((workflow2, inputs, opts) => fan(workflow2, inputs, opts, false), "all");
|
|
3471
|
+
const allSettled = /* @__PURE__ */ __name((workflow2, inputs, opts) => fan(workflow2, inputs, opts, true), "allSettled");
|
|
3472
|
+
const allSteps = /* @__PURE__ */ __name(async (ref, inputs, opts, settle) => {
|
|
3301
3473
|
if (opts?.queue !== void 0) {
|
|
3302
3474
|
throw new Error("ctx.all: a flow-control queue is not supported on a step fan");
|
|
3303
3475
|
}
|
|
@@ -3308,7 +3480,8 @@ function createWorkflowCtx(host, runId, compensations, workflow = "", searchAttr
|
|
|
3308
3480
|
};
|
|
3309
3481
|
const positions = inputs.map(() => pos.next());
|
|
3310
3482
|
return host.callRemoteAll(runId, positions, def, inputs, {
|
|
3311
|
-
mode: opts?.mode ?? "waitAll",
|
|
3483
|
+
mode: settle ? "waitAll" : opts?.mode ?? "waitAll",
|
|
3484
|
+
settle,
|
|
3312
3485
|
concurrency: opts?.concurrency,
|
|
3313
3486
|
transport: opts?.transport,
|
|
3314
3487
|
admission: {
|
|
@@ -3626,6 +3799,7 @@ function createWorkflowCtx(host, runId, compensations, workflow = "", searchAttr
|
|
|
3626
3799
|
task: guard("task", task),
|
|
3627
3800
|
child: guard("child", child),
|
|
3628
3801
|
all: guard("all", all),
|
|
3802
|
+
allSettled: guard("allSettled", allSettled),
|
|
3629
3803
|
startChild: guard("startChild", startChild),
|
|
3630
3804
|
breakpoint: guard("breakpoint", breakpoint),
|
|
3631
3805
|
webhook: guardedWebhook,
|
|
@@ -3651,9 +3825,11 @@ function mergeTags(staticTags, runTags) {
|
|
|
3651
3825
|
__name(mergeTags, "mergeTags");
|
|
3652
3826
|
var BREAKPOINT = "breakpoint";
|
|
3653
3827
|
var isBreakpoint = /* @__PURE__ */ __name((cp) => cp.status === "pending" && cp.name.startsWith(BREAKPOINT), "isBreakpoint");
|
|
3828
|
+
var ANNOUNCED_ROUTES_TTL_MS = 5e3;
|
|
3654
3829
|
var VERSION_UNDECLARED_TAG = "version:undeclared";
|
|
3655
3830
|
var versionKey = /* @__PURE__ */ __name((name, version) => `${name}@${version}`, "versionKey");
|
|
3656
3831
|
var STEP_FAN_CHUNK = 200;
|
|
3832
|
+
var RESULT_BATCH_MAX = 256;
|
|
3657
3833
|
function shuffle(items) {
|
|
3658
3834
|
for (let i = items.length - 1; i > 0; i -= 1) {
|
|
3659
3835
|
const j = Math.floor(Math.random() * (i + 1));
|
|
@@ -3761,6 +3937,8 @@ var WorkflowEngine = class {
|
|
|
3761
3937
|
updateValidators = /* @__PURE__ */ new Map();
|
|
3762
3938
|
/** Runs being cancelled WITH saga compensation — see `cancel({ compensate: true })`. */
|
|
3763
3939
|
cancelRequested = /* @__PURE__ */ new Set();
|
|
3940
|
+
/** {@link announcedRoutes} reads, by group + workflow + version. */
|
|
3941
|
+
announcedRoutesCache = /* @__PURE__ */ new Map();
|
|
3764
3942
|
/**
|
|
3765
3943
|
* Seqs of the dispatched remote steps a turn suspended on, by run, while that turn executes on
|
|
3766
3944
|
* THIS instance — so the turn can re-check them once its lease is released (see
|
|
@@ -3782,6 +3960,8 @@ var WorkflowEngine = class {
|
|
|
3782
3960
|
stepFansFiredHere = /* @__PURE__ */ new Map();
|
|
3783
3961
|
/** The tail of each run's queue of results being handled on this instance — see {@link serializedPerRun}. */
|
|
3784
3962
|
resultChains = /* @__PURE__ */ new Map();
|
|
3963
|
+
/** Each run's results waiting to be settled on this instance — see {@link completeRemoteResult}. */
|
|
3964
|
+
resultQueues = /* @__PURE__ */ new Map();
|
|
3785
3965
|
/** Flow-control admission backend for remote steps (see {@link registerQueue}). */
|
|
3786
3966
|
admission;
|
|
3787
3967
|
/** Runs on THIS instance blocked on admission, by queue — woken early on a freed-slot signal. */
|
|
@@ -3988,6 +4168,7 @@ var WorkflowEngine = class {
|
|
|
3988
4168
|
name,
|
|
3989
4169
|
version,
|
|
3990
4170
|
fn,
|
|
4171
|
+
onError: this.checkedRoutes(name, opts?.onError),
|
|
3991
4172
|
tags: opts?.tags,
|
|
3992
4173
|
singleton: opts?.singleton,
|
|
3993
4174
|
concurrency: opts?.concurrency,
|
|
@@ -4057,6 +4238,7 @@ var WorkflowEngine = class {
|
|
|
4057
4238
|
executionTimeoutMs: opts.executionTimeout != null ? parseDuration(opts.executionTimeout) : void 0,
|
|
4058
4239
|
validateInput: opts.validateInput,
|
|
4059
4240
|
requires: opts.requires,
|
|
4241
|
+
onError: this.checkedRoutes(name, opts.onError),
|
|
4060
4242
|
remote: {
|
|
4061
4243
|
group: opts.group,
|
|
4062
4244
|
executor: opts.executor
|
|
@@ -4834,6 +5016,24 @@ var WorkflowEngine = class {
|
|
|
4834
5016
|
if (resurrecting) {
|
|
4835
5017
|
const isFailureCompletion = /* @__PURE__ */ __name((v) => typeof v === "object" && v !== null && v.ok === false, "isFailureCompletion");
|
|
4836
5018
|
for (const cp of await this.store.listCheckpoints(runId)) {
|
|
5019
|
+
const handlerId = handoffHandlerId(runId, cp);
|
|
5020
|
+
if (handlerId !== void 0) {
|
|
5021
|
+
await this.store.takeSignalWaiter(`child:${handlerId}`);
|
|
5022
|
+
await this.store.saveCheckpoint({
|
|
5023
|
+
...cp,
|
|
5024
|
+
name: retiredHandoffName(handlerId),
|
|
5025
|
+
status: "completed"
|
|
5026
|
+
});
|
|
5027
|
+
const tags = run.tags?.filter((t) => !t.startsWith("onError:"));
|
|
5028
|
+
if (tags && tags.length !== run.tags?.length) {
|
|
5029
|
+
await this.store.updateRun(runId, {
|
|
5030
|
+
tags,
|
|
5031
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
5032
|
+
});
|
|
5033
|
+
run.tags = tags;
|
|
5034
|
+
}
|
|
5035
|
+
continue;
|
|
5036
|
+
}
|
|
4837
5037
|
if (cp.status === "failed") {
|
|
4838
5038
|
await this.store.saveCheckpoint({
|
|
4839
5039
|
...cp,
|
|
@@ -4978,13 +5178,13 @@ var WorkflowEngine = class {
|
|
|
4978
5178
|
* `start` when a call site needs the outcome — `await start(...); const r = await waitForRun(id)`.
|
|
4979
5179
|
*/
|
|
4980
5180
|
waitForRun(runId, opts) {
|
|
4981
|
-
const
|
|
5181
|
+
const TERMINAL2 = /* @__PURE__ */ new Set([
|
|
4982
5182
|
"completed",
|
|
4983
5183
|
"failed",
|
|
4984
5184
|
"cancelled",
|
|
4985
5185
|
"dead"
|
|
4986
5186
|
]);
|
|
4987
|
-
const isSettled = /* @__PURE__ */ __name((run) => opts?.until === "terminal" ?
|
|
5187
|
+
const isSettled = /* @__PURE__ */ __name((run) => opts?.until === "terminal" ? TERMINAL2.has(run.status) : run.status !== "pending" && run.status !== "running" && run.status !== "cancelling" && !(run.status === "suspended" && run.awaitingDecisionTaskId !== void 0), "isSettled");
|
|
4988
5188
|
const toResult = /* @__PURE__ */ __name((run) => ({
|
|
4989
5189
|
runId,
|
|
4990
5190
|
status: run.status,
|
|
@@ -5239,10 +5439,7 @@ var WorkflowEngine = class {
|
|
|
5239
5439
|
async redriveSettledStepFans(runId, fans) {
|
|
5240
5440
|
for (const fan of fans) {
|
|
5241
5441
|
if (this.stepFansFiredHere.has(`${runId}:${fan.from}`)) continue;
|
|
5242
|
-
|
|
5243
|
-
"pending"
|
|
5244
|
-
]) : (await this.store.listCheckpoints(runId)).filter((c) => c.seq >= fan.from && c.seq <= fan.to && c.status === "pending").length;
|
|
5245
|
-
if (pending > 0) continue;
|
|
5442
|
+
if (await this.stepFanPending(runId, fan.from, fan.to) > 0) continue;
|
|
5246
5443
|
const nowMs = this.clock();
|
|
5247
5444
|
if (!await this.store.tryLockRun(runId, this.instanceId, nowMs + this.leaseMs, nowMs)) {
|
|
5248
5445
|
return;
|
|
@@ -5952,6 +6149,209 @@ var WorkflowEngine = class {
|
|
|
5952
6149
|
* us back here (a remote step finished, a timer fired) goes through `resume` like any TS workflow.
|
|
5953
6150
|
*/
|
|
5954
6151
|
/**
|
|
6152
|
+
* The registered onError routes whose handler workflow nothing can run: not registered on this
|
|
6153
|
+
* engine, and no live worker group serves it. Meant for a boot-time warning: a typo in a handler's
|
|
6154
|
+
* name would otherwise surface only when the first run fails.
|
|
6155
|
+
*/
|
|
6156
|
+
async unservedOnErrorRoutes() {
|
|
6157
|
+
const unserved = [];
|
|
6158
|
+
let groups;
|
|
6159
|
+
for (const registered of this.workflows.values()) {
|
|
6160
|
+
for (const route of registered.onError ?? []) {
|
|
6161
|
+
if (this.latest.has(route.workflow)) continue;
|
|
6162
|
+
groups ??= await this.pool.listWorkerGroups().catch(() => []);
|
|
6163
|
+
const token = sanitizeQueueToken(route.workflow);
|
|
6164
|
+
if (groups.some((g) => g === token || g.startsWith(`${token}@`))) continue;
|
|
6165
|
+
unserved.push({
|
|
6166
|
+
workflow: registered.name,
|
|
6167
|
+
route
|
|
6168
|
+
});
|
|
6169
|
+
}
|
|
6170
|
+
}
|
|
6171
|
+
return unserved;
|
|
6172
|
+
}
|
|
6173
|
+
/** Validate a workflow's {@link OnErrorRoute}s at registration. */
|
|
6174
|
+
checkedRoutes(name, routes) {
|
|
6175
|
+
if (!routes?.length) return void 0;
|
|
6176
|
+
assertRoutes(name, routes);
|
|
6177
|
+
return routes;
|
|
6178
|
+
}
|
|
6179
|
+
/**
|
|
6180
|
+
* Route a failed run to its {@link OnErrorRoute} handler, read from the workflow's local registration
|
|
6181
|
+
* or, for one served by convention, from what its live workers announce. Starts the handler run and
|
|
6182
|
+
* waits for it the way a parent waits for a `ctx.child`: a `child:<handlerId>` waiter plus a
|
|
6183
|
+
* `running` placeholder checkpoint carrying the handler's name and input, so the handler's terminal
|
|
6184
|
+
* state wakes the run (buffered and reconciled like any child). From then on every execution of the
|
|
6185
|
+
* run only follows the handler ({@link followHandoff}); the body never runs again. Undefined when no
|
|
6186
|
+
* route matches: the run fails as usual.
|
|
6187
|
+
*
|
|
6188
|
+
* The placeholder sits past every seq the body used and is left out of {@link remoteHistory}.
|
|
6189
|
+
*/
|
|
6190
|
+
async routeFailure(run, error2) {
|
|
6191
|
+
const registered = this.workflows.get(versionKey(run.workflow, run.workflowVersion));
|
|
6192
|
+
const routes = registered ? registered.onError : await this.announcedRoutes(run, error2);
|
|
6193
|
+
const route = routeFor(routes, error2);
|
|
6194
|
+
if (!route) return void 0;
|
|
6195
|
+
const checkpoints = await this.handoffCheckpoints(run.id);
|
|
6196
|
+
const live = liveHandoff(run.id, checkpoints);
|
|
6197
|
+
if (live) return this.followHandoff(run, live);
|
|
6198
|
+
const attempt = checkpoints.length;
|
|
6199
|
+
const seq = HANDOFF_SEQ_BASE + attempt;
|
|
6200
|
+
const handlerId = onErrorRunId(run.id, attempt);
|
|
6201
|
+
const handler = {
|
|
6202
|
+
workflow: route.workflow,
|
|
6203
|
+
input: {
|
|
6204
|
+
runId: run.id,
|
|
6205
|
+
workflow: run.workflow,
|
|
6206
|
+
input: run.input,
|
|
6207
|
+
error: error2
|
|
6208
|
+
}
|
|
6209
|
+
};
|
|
6210
|
+
await this.store.putSignalWaiter({
|
|
6211
|
+
token: `child:${handlerId}`,
|
|
6212
|
+
runId: run.id,
|
|
6213
|
+
seq
|
|
6214
|
+
});
|
|
6215
|
+
await this.store.saveCheckpoint({
|
|
6216
|
+
...instantCheckpoint({
|
|
6217
|
+
runId: run.id,
|
|
6218
|
+
seq,
|
|
6219
|
+
name: handoffName(handlerId),
|
|
6220
|
+
kind: "signal",
|
|
6221
|
+
status: "running"
|
|
6222
|
+
}),
|
|
6223
|
+
input: handler
|
|
6224
|
+
});
|
|
6225
|
+
const tag = `onError:${route.code ?? "*"}`;
|
|
6226
|
+
if (!run.tags?.includes(tag)) {
|
|
6227
|
+
const tags = [
|
|
6228
|
+
...run.tags ?? [],
|
|
6229
|
+
tag
|
|
6230
|
+
];
|
|
6231
|
+
await this.store.updateRun(run.id, {
|
|
6232
|
+
tags,
|
|
6233
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
6234
|
+
});
|
|
6235
|
+
run.tags = tags;
|
|
6236
|
+
}
|
|
6237
|
+
this.emit({
|
|
6238
|
+
type: "run.routed",
|
|
6239
|
+
runId: run.id,
|
|
6240
|
+
workflow: run.workflow,
|
|
6241
|
+
namespace: run.namespace,
|
|
6242
|
+
name: route.workflow,
|
|
6243
|
+
error: error2
|
|
6244
|
+
});
|
|
6245
|
+
this.startChildDeferred(handler.workflow, handler.input, handlerId, {
|
|
6246
|
+
namespace: run.namespace
|
|
6247
|
+
});
|
|
6248
|
+
return {
|
|
6249
|
+
kind: "suspended"
|
|
6250
|
+
};
|
|
6251
|
+
}
|
|
6252
|
+
/** A run's handoff checkpoints, live and retired: the only ones at {@link HANDOFF_SEQ_BASE} and up. */
|
|
6253
|
+
async handoffCheckpoints(runId) {
|
|
6254
|
+
const handoffs = await this.store.listCheckpointsByNamePrefix?.(runId, [
|
|
6255
|
+
`signal:child:${runId}.onError.`,
|
|
6256
|
+
`onError:retried:${runId}.onError.`
|
|
6257
|
+
]);
|
|
6258
|
+
return (handoffs ?? await this.store.listCheckpoints(runId)).filter((cp) => cp.seq >= HANDOFF_SEQ_BASE);
|
|
6259
|
+
}
|
|
6260
|
+
/**
|
|
6261
|
+
* Where a run that handed its failure off stands: the handler's outcome once it settled (from the
|
|
6262
|
+
* wake's checkpoint, or off the handler run when the wake never arrived), else still waiting. Starts
|
|
6263
|
+
* the handler if the turn that handed off crashed before its deferred start ran.
|
|
6264
|
+
*/
|
|
6265
|
+
async followHandoff(run, handoff) {
|
|
6266
|
+
if (handoff.status === "completed") {
|
|
6267
|
+
const outcome = handoff.output;
|
|
6268
|
+
return outcome.ok ? {
|
|
6269
|
+
kind: "completed",
|
|
6270
|
+
output: outcome.value
|
|
6271
|
+
} : {
|
|
6272
|
+
kind: "failed",
|
|
6273
|
+
error: completionError(outcome)
|
|
6274
|
+
};
|
|
6275
|
+
}
|
|
6276
|
+
const handlerId = handoffHandlerId(run.id, handoff);
|
|
6277
|
+
const handlerRun = await this.store.getRun(handlerId);
|
|
6278
|
+
if (handlerRun?.status === "completed") return {
|
|
6279
|
+
kind: "completed",
|
|
6280
|
+
output: handlerRun.output
|
|
6281
|
+
};
|
|
6282
|
+
if (handlerRun && TERMINAL_RUN_STATUSES.includes(handlerRun.status)) {
|
|
6283
|
+
return {
|
|
6284
|
+
kind: "failed",
|
|
6285
|
+
error: handlerRun.error ?? {
|
|
6286
|
+
message: `onError handler ${handlerId} ${handlerRun.status}`
|
|
6287
|
+
}
|
|
6288
|
+
};
|
|
6289
|
+
}
|
|
6290
|
+
if (!handlerRun) {
|
|
6291
|
+
const handler = handoff.input;
|
|
6292
|
+
this.startChildDeferred(handler.workflow, handler.input, handlerId, {
|
|
6293
|
+
namespace: run.namespace
|
|
6294
|
+
});
|
|
6295
|
+
}
|
|
6296
|
+
return {
|
|
6297
|
+
kind: "suspended"
|
|
6298
|
+
};
|
|
6299
|
+
}
|
|
6300
|
+
/**
|
|
6301
|
+
* The onError routes the live workers announce for a workflow the engine serves by convention: the
|
|
6302
|
+
* one set they agree on, or none when they state none or cannot be read. Workers that announce
|
|
6303
|
+
* different sets (a rollout in progress) route nothing, and the run's failure is reported as a
|
|
6304
|
+
* `run.unrouted` event. Announcers of another version of the workflow are ignored. The read is
|
|
6305
|
+
* cached for {@link ANNOUNCED_ROUTES_TTL_MS}, so a burst of failures costs one descriptor read.
|
|
6306
|
+
*/
|
|
6307
|
+
async announcedRoutes(run, error2) {
|
|
6308
|
+
const group = tenantGroup(sanitizeQueueToken(run.workflow), run.namespace);
|
|
6309
|
+
const key = `${group}
|
|
6310
|
+
${run.workflow}
|
|
6311
|
+
${run.workflowVersion}`;
|
|
6312
|
+
const now = this.clock();
|
|
6313
|
+
let cached = this.announcedRoutesCache.get(key);
|
|
6314
|
+
if (!cached || now - cached.at > ANNOUNCED_ROUTES_TTL_MS) {
|
|
6315
|
+
cached = {
|
|
6316
|
+
at: now,
|
|
6317
|
+
sets: await this.readAnnouncedRoutes(run, group)
|
|
6318
|
+
};
|
|
6319
|
+
this.announcedRoutesCache.set(key, cached);
|
|
6320
|
+
}
|
|
6321
|
+
if (cached.sets.length > 1) {
|
|
6322
|
+
this.emit({
|
|
6323
|
+
type: "run.unrouted",
|
|
6324
|
+
runId: run.id,
|
|
6325
|
+
workflow: run.workflow,
|
|
6326
|
+
namespace: run.namespace,
|
|
6327
|
+
error: error2,
|
|
6328
|
+
reason: `live workers announce ${cached.sets.length} different onError route sets`
|
|
6329
|
+
});
|
|
6330
|
+
return void 0;
|
|
6331
|
+
}
|
|
6332
|
+
return cached.sets[0];
|
|
6333
|
+
}
|
|
6334
|
+
/** Every distinct onError route set the live workers of `group` announce for the run's workflow. */
|
|
6335
|
+
async readAnnouncedRoutes(run, group) {
|
|
6336
|
+
const transport = await this.pool.transportWithLiveGroup(group);
|
|
6337
|
+
if (!transport?.readWorkerDescriptors) return [];
|
|
6338
|
+
let descriptors;
|
|
6339
|
+
try {
|
|
6340
|
+
descriptors = await transport.readWorkerDescriptors(group);
|
|
6341
|
+
} catch {
|
|
6342
|
+
return [];
|
|
6343
|
+
}
|
|
6344
|
+
const sets = /* @__PURE__ */ new Map();
|
|
6345
|
+
for (const announced of aggregateAnnouncements(descriptors)) {
|
|
6346
|
+
if (announced.name !== run.workflow) continue;
|
|
6347
|
+
if (announced.version !== void 0 && announced.version !== run.workflowVersion) continue;
|
|
6348
|
+
for (const routes of announced.onError) sets.set(JSON.stringify(routes), routes);
|
|
6349
|
+
}
|
|
6350
|
+
return [
|
|
6351
|
+
...sets.values()
|
|
6352
|
+
];
|
|
6353
|
+
}
|
|
6354
|
+
/**
|
|
5955
6355
|
* Apply a terminal/suspended transition once: persist the new run status, emit the matching
|
|
5956
6356
|
* lifecycle event, and (on completion/failure) wake a waiting parent. Both the TS and remote
|
|
5957
6357
|
* executors funnel their completed/failed/suspended outcomes through here so a status update can
|
|
@@ -5985,9 +6385,12 @@ var WorkflowEngine = class {
|
|
|
5985
6385
|
};
|
|
5986
6386
|
}
|
|
5987
6387
|
if (outcome.kind === "failed") {
|
|
6388
|
+
const handed = await this.routeFailure(run, outcome.error);
|
|
6389
|
+
if (handed && handed.kind !== "failed") return this.settleRun(run, handed);
|
|
6390
|
+
const error2 = handed?.error ?? outcome.error;
|
|
5988
6391
|
await this.store.updateRun(run.id, {
|
|
5989
6392
|
status: "failed",
|
|
5990
|
-
error:
|
|
6393
|
+
error: error2,
|
|
5991
6394
|
updatedAt
|
|
5992
6395
|
});
|
|
5993
6396
|
this.emit({
|
|
@@ -5995,16 +6398,13 @@ var WorkflowEngine = class {
|
|
|
5995
6398
|
runId: run.id,
|
|
5996
6399
|
workflow: run.workflow,
|
|
5997
6400
|
namespace: run.namespace,
|
|
5998
|
-
error:
|
|
5999
|
-
});
|
|
6000
|
-
void this.notifyParent(run.id, {
|
|
6001
|
-
ok: false,
|
|
6002
|
-
error: outcome.error.message
|
|
6401
|
+
error: error2
|
|
6003
6402
|
});
|
|
6403
|
+
void this.notifyParent(run.id, failedCompletion(error2));
|
|
6004
6404
|
return {
|
|
6005
6405
|
runId: run.id,
|
|
6006
6406
|
status: "failed",
|
|
6007
|
-
error:
|
|
6407
|
+
error: error2
|
|
6008
6408
|
};
|
|
6009
6409
|
}
|
|
6010
6410
|
if (outcome.kind === "blocked") return this.parkBlocked(run, outcome.blocked);
|
|
@@ -6093,7 +6493,14 @@ var WorkflowEngine = class {
|
|
|
6093
6493
|
}
|
|
6094
6494
|
const wfPlan = await this.planRoute(tenantGroup(sanitizeQueueToken(remote.group), void 0), registered.requires);
|
|
6095
6495
|
if (wfPlan?.status === "blocked") return this.parkBlocked(run, wfPlan);
|
|
6096
|
-
const
|
|
6496
|
+
const checkpoints = await this.store.listCheckpoints(run.id);
|
|
6497
|
+
const handoff = liveHandoff(run.id, checkpoints);
|
|
6498
|
+
if (handoff) {
|
|
6499
|
+
const result = await this.settleRun(run, await this.followHandoff(run, handoff));
|
|
6500
|
+
await this.store.releaseRunLock(run.id);
|
|
6501
|
+
return result;
|
|
6502
|
+
}
|
|
6503
|
+
const history = await this.remoteHistory(run.id, checkpoints);
|
|
6097
6504
|
if (remote.executor.dispatch) {
|
|
6098
6505
|
const taskId = `${run.id}:wf:${globalThis.crypto.randomUUID().slice(0, 8)}`;
|
|
6099
6506
|
const silenceMs = this.remoteAdvanceSilenceMs;
|
|
@@ -6417,8 +6824,8 @@ var WorkflowEngine = class {
|
|
|
6417
6824
|
}
|
|
6418
6825
|
}
|
|
6419
6826
|
/** The run's resolved durable ops as replay inputs: completed/failed steps + elapsed timers. */
|
|
6420
|
-
async remoteHistory(runId) {
|
|
6421
|
-
const checkpoints = await this.store.listCheckpoints(runId);
|
|
6827
|
+
async remoteHistory(runId, listed) {
|
|
6828
|
+
const checkpoints = listed ?? await this.store.listCheckpoints(runId);
|
|
6422
6829
|
const kindOf = {
|
|
6423
6830
|
remote: "call",
|
|
6424
6831
|
local: "step",
|
|
@@ -6428,15 +6835,14 @@ var WorkflowEngine = class {
|
|
|
6428
6835
|
const events = [];
|
|
6429
6836
|
for (const cp of checkpoints) {
|
|
6430
6837
|
if (cp.status === "completed" || cp.status === "failed") {
|
|
6838
|
+
if (cp.seq >= HANDOFF_SEQ_BASE) continue;
|
|
6431
6839
|
if (cp.kind === "signal" && cp.name.startsWith("signal:child:")) {
|
|
6432
6840
|
const completion = cp.output;
|
|
6433
6841
|
events.push({
|
|
6434
6842
|
seq: cp.seq,
|
|
6435
6843
|
kind: "child",
|
|
6436
6844
|
output: completion?.ok ? completion.value : void 0,
|
|
6437
|
-
error: completion && completion.ok === false ?
|
|
6438
|
-
message: completion.error
|
|
6439
|
-
} : void 0
|
|
6845
|
+
error: completion && completion.ok === false ? completionError(completion) : void 0
|
|
6440
6846
|
});
|
|
6441
6847
|
continue;
|
|
6442
6848
|
}
|
|
@@ -6717,6 +7123,10 @@ var WorkflowEngine = class {
|
|
|
6717
7123
|
this.awaitedStepFans.set(run.id, awaitedFans);
|
|
6718
7124
|
const ctx = createWorkflowCtx(this.ctxHostFor(replay, run.namespace), run.id, compensations, run.workflow, registered?.searchAttributesSchema);
|
|
6719
7125
|
try {
|
|
7126
|
+
const handoff = liveHandoff(run.id, snapshot);
|
|
7127
|
+
if (handoff && !this.cancelRequested.has(run.id)) {
|
|
7128
|
+
return await this.settleRun(run, await this.followHandoff(run, handoff));
|
|
7129
|
+
}
|
|
6720
7130
|
const output = await runInWorkflowCtx(ctx, () => fn(ctx, run.input));
|
|
6721
7131
|
if (this.cancelRequested.has(run.id)) {
|
|
6722
7132
|
return await this.compensateAndCancel(run, compensations, replay);
|
|
@@ -6779,7 +7189,8 @@ var WorkflowEngine = class {
|
|
|
6779
7189
|
}
|
|
6780
7190
|
const error2 = {
|
|
6781
7191
|
message: err instanceof Error ? err.message : String(err),
|
|
6782
|
-
stack: err instanceof Error ? err.stack : void 0
|
|
7192
|
+
stack: err instanceof Error ? err.stack : void 0,
|
|
7193
|
+
...codeAndDetails(err)
|
|
6783
7194
|
};
|
|
6784
7195
|
const pending = await this.unwindCompensations(run, compensations, replay);
|
|
6785
7196
|
if (pending) return await this.settleRun(run, {
|
|
@@ -7415,7 +7826,8 @@ var WorkflowEngine = class {
|
|
|
7415
7826
|
const failure = {
|
|
7416
7827
|
index: i,
|
|
7417
7828
|
id: stepId(runId, seq),
|
|
7418
|
-
error: err.message
|
|
7829
|
+
error: err.message,
|
|
7830
|
+
...codeAndDetails(err)
|
|
7419
7831
|
};
|
|
7420
7832
|
if (opts.mode === "failFast") throw new GatherError([
|
|
7421
7833
|
failure
|
|
@@ -7437,6 +7849,24 @@ var WorkflowEngine = class {
|
|
|
7437
7849
|
});
|
|
7438
7850
|
throw new WorkflowSuspended(wakeAt);
|
|
7439
7851
|
}
|
|
7852
|
+
if (opts.settle) {
|
|
7853
|
+
const failed = new Map(failures.map((f) => [
|
|
7854
|
+
f.index,
|
|
7855
|
+
f
|
|
7856
|
+
]));
|
|
7857
|
+
return Array.from(outputs, (value, i) => {
|
|
7858
|
+
const f = failed.get(i);
|
|
7859
|
+
if (!f) return {
|
|
7860
|
+
status: "fulfilled",
|
|
7861
|
+
value
|
|
7862
|
+
};
|
|
7863
|
+
const { index: _index, ...rejected } = f;
|
|
7864
|
+
return {
|
|
7865
|
+
status: "rejected",
|
|
7866
|
+
...rejected
|
|
7867
|
+
};
|
|
7868
|
+
});
|
|
7869
|
+
}
|
|
7440
7870
|
if (failures.length > 0) throw new GatherError(failures, inputs.length);
|
|
7441
7871
|
return outputs;
|
|
7442
7872
|
}
|
|
@@ -7508,6 +7938,25 @@ var WorkflowEngine = class {
|
|
|
7508
7938
|
await this.store.saveCheckpoint(checkpoint);
|
|
7509
7939
|
return true;
|
|
7510
7940
|
}
|
|
7941
|
+
/**
|
|
7942
|
+
* {@link savePendingCheckpoint} for checkpoints at distinct seqs of one run: one
|
|
7943
|
+
* {@link StateStore.saveCheckpointsIfPending} call when the store has it, else one write each.
|
|
7944
|
+
* Never rejects as a whole: a write that failed is reported as that entry's error, and the batch
|
|
7945
|
+
* call rejecting means it wrote nothing, so the entries are then written one by one.
|
|
7946
|
+
*/
|
|
7947
|
+
async savePendingCheckpoints(checkpoints) {
|
|
7948
|
+
if (checkpoints.length > 1 && this.store.saveCheckpointsIfPending) {
|
|
7949
|
+
try {
|
|
7950
|
+
const wrote = await this.store.saveCheckpointsIfPending(checkpoints);
|
|
7951
|
+
return checkpoints.map((_, i) => wrote[i] === true);
|
|
7952
|
+
} catch {
|
|
7953
|
+
}
|
|
7954
|
+
}
|
|
7955
|
+
const settled = await Promise.allSettled(checkpoints.map((cp) => this.savePendingCheckpoint(cp)));
|
|
7956
|
+
return settled.map((s) => s.status === "fulfilled" ? s.value : {
|
|
7957
|
+
error: s.reason
|
|
7958
|
+
});
|
|
7959
|
+
}
|
|
7511
7960
|
/** Enqueue a remote step task to its worker group via the transport pool. Shared by the initial
|
|
7512
7961
|
* dispatch, the opt-in self-heal re-dispatch (see `remoteRedispatchMs`), and the explicit
|
|
7513
7962
|
* {@link redispatchPending}. Carries the current trace/context so a re-dispatch is traceable. */
|
|
@@ -7546,57 +7995,161 @@ var WorkflowEngine = class {
|
|
|
7546
7995
|
* result (the dispatching one may be gone), so the run is crash/scale-safe. Resolves whether the
|
|
7547
7996
|
* run should now be resumed: not for a duplicate or late delivery (the checkpoint isn't `pending`),
|
|
7548
7997
|
* a run already finished, or a step fan whose barrier is still closed.
|
|
7998
|
+
*
|
|
7999
|
+
* Results of one run are settled in batches: the first starts a batch, and the ones that arrive
|
|
8000
|
+
* while it is being written form the next. Under load a wide fan's results are written by one
|
|
8001
|
+
* {@link StateStore.saveCheckpointsIfPending} call per batch, and the fan's barrier is asked once
|
|
8002
|
+
* per batch, not once per result. A result resolves only once its own write has committed, so the
|
|
8003
|
+
* transport acknowledges nothing that is not durable yet.
|
|
8004
|
+
*/
|
|
8005
|
+
completeRemoteResult(result) {
|
|
8006
|
+
return new Promise((resolve, reject) => {
|
|
8007
|
+
const entry = {
|
|
8008
|
+
result,
|
|
8009
|
+
resolve,
|
|
8010
|
+
reject
|
|
8011
|
+
};
|
|
8012
|
+
const queued = this.resultQueues.get(result.runId);
|
|
8013
|
+
if (queued) {
|
|
8014
|
+
queued.push(entry);
|
|
8015
|
+
return;
|
|
8016
|
+
}
|
|
8017
|
+
const queue = [
|
|
8018
|
+
entry
|
|
8019
|
+
];
|
|
8020
|
+
this.resultQueues.set(result.runId, queue);
|
|
8021
|
+
void this.drainResultQueue(result.runId, queue);
|
|
8022
|
+
});
|
|
8023
|
+
}
|
|
8024
|
+
/** Settle `queue` (a run's queued results) batch by batch until it is empty, then retire it. */
|
|
8025
|
+
async drainResultQueue(runId, queue) {
|
|
8026
|
+
await Promise.resolve();
|
|
8027
|
+
while (queue.length > 0) {
|
|
8028
|
+
const batch = queue.splice(0, RESULT_BATCH_MAX);
|
|
8029
|
+
let outcomes;
|
|
8030
|
+
try {
|
|
8031
|
+
outcomes = await this.completeRemoteResults(runId, batch.map((q) => q.result));
|
|
8032
|
+
} catch (err) {
|
|
8033
|
+
outcomes = batch.map(() => ({
|
|
8034
|
+
error: err
|
|
8035
|
+
}));
|
|
8036
|
+
}
|
|
8037
|
+
batch.forEach((q, i) => {
|
|
8038
|
+
const outcome = outcomes[i] ?? false;
|
|
8039
|
+
if (typeof outcome === "boolean") q.resolve(outcome);
|
|
8040
|
+
else q.reject(outcome.error);
|
|
8041
|
+
});
|
|
8042
|
+
}
|
|
8043
|
+
this.resultQueues.delete(runId);
|
|
8044
|
+
}
|
|
8045
|
+
/**
|
|
8046
|
+
* Settle a batch of one run's step results. At most one of them resolves `true` (resume the run):
|
|
8047
|
+
* that resume replays every write of the batch, so a second would only replay it again. A result
|
|
8048
|
+
* whose read or write failed resolves its error, and the transport redelivers just that one.
|
|
7549
8049
|
*/
|
|
7550
|
-
async
|
|
7551
|
-
const
|
|
7552
|
-
|
|
7553
|
-
|
|
7554
|
-
|
|
7555
|
-
|
|
8050
|
+
async completeRemoteResults(runId, results) {
|
|
8051
|
+
const outcomes = results.map(() => false);
|
|
8052
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8053
|
+
const live = results.flatMap((r, i) => {
|
|
8054
|
+
if (seen.has(r.seq)) return [];
|
|
8055
|
+
seen.add(r.seq);
|
|
8056
|
+
return [
|
|
8057
|
+
i
|
|
8058
|
+
];
|
|
8059
|
+
});
|
|
8060
|
+
const reads = await Promise.allSettled(live.map((i) => this.store.getCheckpoint(runId, results[i].seq)));
|
|
8061
|
+
const pending = [];
|
|
8062
|
+
reads.forEach((read, k) => {
|
|
8063
|
+
const index = live[k];
|
|
8064
|
+
if (read.status === "rejected") outcomes[index] = {
|
|
8065
|
+
error: read.reason
|
|
8066
|
+
};
|
|
8067
|
+
else if (read.value?.status === "pending") pending.push({
|
|
8068
|
+
index,
|
|
8069
|
+
cp: read.value
|
|
8070
|
+
});
|
|
8071
|
+
});
|
|
8072
|
+
if (pending.length === 0) return outcomes;
|
|
8073
|
+
for (const { cp } of pending) void this.releaseQueueSlot(cp.stepId).catch(() => void 0);
|
|
8074
|
+
const run = await this.store.getRun(runId);
|
|
8075
|
+
if (run && (run.status === "cancelled" || run.status === "completed")) return outcomes;
|
|
7556
8076
|
const finishedAt = /* @__PURE__ */ new Date();
|
|
7557
|
-
const
|
|
7558
|
-
|
|
7559
|
-
|
|
7560
|
-
|
|
7561
|
-
|
|
7562
|
-
|
|
7563
|
-
|
|
7564
|
-
|
|
7565
|
-
|
|
8077
|
+
const writes = pending.map(({ index, cp }) => {
|
|
8078
|
+
const result = results[index];
|
|
8079
|
+
return {
|
|
8080
|
+
...cp,
|
|
8081
|
+
status: result.status,
|
|
8082
|
+
output: result.status === "completed" ? result.output : cp.output,
|
|
8083
|
+
error: result.error,
|
|
8084
|
+
events: result.events ?? cp.events,
|
|
8085
|
+
startedAt: result.startedAt ? new Date(result.startedAt) : cp.startedAt,
|
|
8086
|
+
finishedAt
|
|
8087
|
+
};
|
|
7566
8088
|
});
|
|
7567
|
-
|
|
7568
|
-
|
|
7569
|
-
|
|
7570
|
-
|
|
7571
|
-
|
|
7572
|
-
|
|
7573
|
-
|
|
7574
|
-
|
|
7575
|
-
|
|
7576
|
-
|
|
7577
|
-
queueMs: startedAt.getTime() - cp.enqueuedAt.getTime(),
|
|
7578
|
-
durationMs: finishedAt.getTime() - startedAt.getTime()
|
|
8089
|
+
const claims = await this.savePendingCheckpoints(writes);
|
|
8090
|
+
const settled = [];
|
|
8091
|
+
claims.forEach((claim, k) => {
|
|
8092
|
+
const { index, cp } = pending[k];
|
|
8093
|
+
if (claim === true) settled.push({
|
|
8094
|
+
index,
|
|
8095
|
+
cp,
|
|
8096
|
+
result: results[index]
|
|
8097
|
+
});
|
|
8098
|
+
else if (claim !== false) outcomes[index] = claim;
|
|
7579
8099
|
});
|
|
7580
|
-
|
|
8100
|
+
for (const { cp, result } of settled) {
|
|
8101
|
+
this.noteSettledHere(`${runId}:${result.seq}`);
|
|
8102
|
+
const startedAt = result.startedAt ? new Date(result.startedAt) : cp.startedAt;
|
|
8103
|
+
this.emit({
|
|
8104
|
+
type: result.status === "completed" ? "step.completed" : "step.failed",
|
|
8105
|
+
runId,
|
|
8106
|
+
seq: result.seq,
|
|
8107
|
+
name: cp.name,
|
|
8108
|
+
kind: cp.kind,
|
|
8109
|
+
output: result.output,
|
|
8110
|
+
error: result.error,
|
|
8111
|
+
queueMs: startedAt.getTime() - cp.enqueuedAt.getTime(),
|
|
8112
|
+
durationMs: finishedAt.getTime() - startedAt.getTime()
|
|
8113
|
+
});
|
|
8114
|
+
}
|
|
8115
|
+
const barriers = /* @__PURE__ */ new Map();
|
|
8116
|
+
let carrier;
|
|
8117
|
+
for (const { index, cp, result } of settled) {
|
|
8118
|
+
if (!await this.stepFanStillRunning(cp, result, barriers)) carrier = index;
|
|
8119
|
+
}
|
|
8120
|
+
if (carrier !== void 0) outcomes[carrier] = true;
|
|
8121
|
+
return outcomes;
|
|
7581
8122
|
}
|
|
7582
8123
|
/**
|
|
7583
8124
|
* The join barrier of a step fan: a result for one of its steps resumes the parent only once no
|
|
7584
8125
|
* step of the fan is still `pending` (a settled wave), or on a failure (failFast surfaces it, and a
|
|
7585
8126
|
* retry needs the parent to schedule it). Without it every result would replay the whole parent,
|
|
7586
|
-
* which makes a fan of N steps cost O(N²) checkpoint reads.
|
|
8127
|
+
* which makes a fan of N steps cost O(N²) checkpoint reads. `barriers` holds the answer per fan
|
|
8128
|
+
* for the rest of a batch. A barrier that cannot be read counts as open: a spare resume replays and
|
|
8129
|
+
* re-suspends, a lost one strands the run.
|
|
7587
8130
|
*/
|
|
7588
|
-
async stepFanStillRunning(cp, result) {
|
|
8131
|
+
async stepFanStillRunning(cp, result, barriers) {
|
|
7589
8132
|
const fan = parseStepFanGroup(cp.parallelGroup);
|
|
7590
8133
|
if (!fan) return false;
|
|
7591
8134
|
if (result.status === "failed") return false;
|
|
7592
|
-
|
|
7593
|
-
|
|
7594
|
-
|
|
7595
|
-
|
|
7596
|
-
|
|
8135
|
+
let closed = barriers.get(fan.firstSeq);
|
|
8136
|
+
if (!closed) {
|
|
8137
|
+
closed = this.stepFanPending(result.runId, fan.firstSeq, fan.firstSeq + fan.size - 1).then((pending) => pending > 0, () => false);
|
|
8138
|
+
barriers.set(fan.firstSeq, closed);
|
|
8139
|
+
}
|
|
8140
|
+
if (await closed) return true;
|
|
7597
8141
|
this.noteRecent(this.stepFansFiredHere, `${result.runId}:${fan.firstSeq}`);
|
|
7598
8142
|
return false;
|
|
7599
8143
|
}
|
|
8144
|
+
/** How many steps of a step fan's seq range are still `pending`. */
|
|
8145
|
+
async stepFanPending(runId, from, to) {
|
|
8146
|
+
if (this.store.countCheckpoints) {
|
|
8147
|
+
return this.store.countCheckpoints(runId, from, to, [
|
|
8148
|
+
"pending"
|
|
8149
|
+
]);
|
|
8150
|
+
}
|
|
8151
|
+
return (await this.store.listCheckpoints(runId)).filter((c) => c.seq >= from && c.seq <= to && c.status === "pending").length;
|
|
8152
|
+
}
|
|
7600
8153
|
/**
|
|
7601
8154
|
* Apply a remote workflow-turn {@link WorkflowDecision} durably — runs on WHATEVER instance receives
|
|
7602
8155
|
* it over the broker (the dispatching one may be gone, or a point-to-point broker handed it to a
|
|
@@ -8043,14 +8596,339 @@ function summarizeTimeline(timeline, over) {
|
|
|
8043
8596
|
__name(summarizeTimeline, "summarizeTimeline");
|
|
8044
8597
|
function pageFan(timeline, group, query) {
|
|
8045
8598
|
const matching = timeline.filter((cp) => cp.parallelGroup === group && (!query.failedOnly || isFailedMember(cp)));
|
|
8046
|
-
const
|
|
8047
|
-
const limit = query.limit !== void 0 && Number.isFinite(query.limit) ? Math.min(FAN_PAGE_MAX, Math.max(1, Math.floor(query.limit))) : 100;
|
|
8599
|
+
const { offset, limit } = fanPageBounds(query);
|
|
8048
8600
|
return {
|
|
8049
8601
|
items: matching.slice(offset, offset + limit),
|
|
8050
8602
|
total: matching.length
|
|
8051
8603
|
};
|
|
8052
8604
|
}
|
|
8053
8605
|
__name(pageFan, "pageFan");
|
|
8606
|
+
function fanPageBounds(query) {
|
|
8607
|
+
const offset = query.offset !== void 0 && Number.isFinite(query.offset) ? Math.max(0, Math.floor(query.offset)) : 0;
|
|
8608
|
+
const limit = query.limit !== void 0 && Number.isFinite(query.limit) ? Math.min(FAN_PAGE_MAX, Math.max(1, Math.floor(query.limit))) : 100;
|
|
8609
|
+
return {
|
|
8610
|
+
offset,
|
|
8611
|
+
limit
|
|
8612
|
+
};
|
|
8613
|
+
}
|
|
8614
|
+
__name(fanPageBounds, "fanPageBounds");
|
|
8615
|
+
|
|
8616
|
+
// src/run-detail-store.ts
|
|
8617
|
+
function aggregatesCheckpoints(store) {
|
|
8618
|
+
return store.checkpointStats !== void 0 && store.queryCheckpoints !== void 0;
|
|
8619
|
+
}
|
|
8620
|
+
__name(aggregatesCheckpoints, "aggregatesCheckpoints");
|
|
8621
|
+
function checkpointStatsOf(checkpoints, opts) {
|
|
8622
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
8623
|
+
for (const cp of checkpoints) {
|
|
8624
|
+
const group = cp.parallelGroup ?? null;
|
|
8625
|
+
if (opts?.group !== void 0 && group !== opts.group) continue;
|
|
8626
|
+
const nameKind = checkpointNameKind(cp.name);
|
|
8627
|
+
const key = JSON.stringify([
|
|
8628
|
+
group,
|
|
8629
|
+
cp.status,
|
|
8630
|
+
nameKind
|
|
8631
|
+
]);
|
|
8632
|
+
const enqueuedAt = cp.enqueuedAt ?? cp.startedAt;
|
|
8633
|
+
const bucket = buckets.get(key);
|
|
8634
|
+
if (!bucket) {
|
|
8635
|
+
buckets.set(key, {
|
|
8636
|
+
group,
|
|
8637
|
+
status: cp.status,
|
|
8638
|
+
nameKind,
|
|
8639
|
+
count: 1,
|
|
8640
|
+
minSeq: cp.seq,
|
|
8641
|
+
maxSeq: cp.seq,
|
|
8642
|
+
minName: cp.name,
|
|
8643
|
+
maxName: cp.name,
|
|
8644
|
+
minEnqueuedAt: enqueuedAt,
|
|
8645
|
+
minStartedAt: cp.startedAt,
|
|
8646
|
+
maxStartedAt: cp.startedAt,
|
|
8647
|
+
maxFinishedAt: cp.finishedAt,
|
|
8648
|
+
sumAttempts: cp.attempts
|
|
8649
|
+
});
|
|
8650
|
+
continue;
|
|
8651
|
+
}
|
|
8652
|
+
bucket.count += 1;
|
|
8653
|
+
bucket.minSeq = Math.min(bucket.minSeq, cp.seq);
|
|
8654
|
+
bucket.maxSeq = Math.max(bucket.maxSeq, cp.seq);
|
|
8655
|
+
if (cp.name < bucket.minName) bucket.minName = cp.name;
|
|
8656
|
+
if (cp.name > bucket.maxName) bucket.maxName = cp.name;
|
|
8657
|
+
if (enqueuedAt < bucket.minEnqueuedAt) bucket.minEnqueuedAt = enqueuedAt;
|
|
8658
|
+
if (cp.startedAt < bucket.minStartedAt) bucket.minStartedAt = cp.startedAt;
|
|
8659
|
+
if (cp.startedAt > bucket.maxStartedAt) bucket.maxStartedAt = cp.startedAt;
|
|
8660
|
+
if (cp.finishedAt > bucket.maxFinishedAt) bucket.maxFinishedAt = cp.finishedAt;
|
|
8661
|
+
bucket.sumAttempts += cp.attempts;
|
|
8662
|
+
}
|
|
8663
|
+
return [
|
|
8664
|
+
...buckets.values()
|
|
8665
|
+
];
|
|
8666
|
+
}
|
|
8667
|
+
__name(checkpointStatsOf, "checkpointStatsOf");
|
|
8668
|
+
function queryCheckpointsOf(checkpoints, query) {
|
|
8669
|
+
const excluded = new Set(query.excludeGroups ?? []);
|
|
8670
|
+
const matching = checkpoints.filter((cp) => (query.group === void 0 || cp.parallelGroup === query.group) && (cp.parallelGroup === void 0 || !excluded.has(cp.parallelGroup)) && (query.statuses === void 0 || query.statuses.includes(cp.status)) && (query.namePrefixes === void 0 || query.namePrefixes.some((prefix) => cp.name.startsWith(prefix)))).sort((a, b) => a.seq - b.seq);
|
|
8671
|
+
const offset = query.offset ?? 0;
|
|
8672
|
+
return matching.slice(offset, query.limit === void 0 ? void 0 : offset + query.limit);
|
|
8673
|
+
}
|
|
8674
|
+
__name(queryCheckpointsOf, "queryCheckpointsOf");
|
|
8675
|
+
var TERMINAL = /* @__PURE__ */ __name((status) => status !== "pending" && status !== "running", "TERMINAL");
|
|
8676
|
+
function isWide(group, rows, over) {
|
|
8677
|
+
return rows > over || (parseFanGroup(group)?.size ?? 0) > over;
|
|
8678
|
+
}
|
|
8679
|
+
__name(isWide, "isWide");
|
|
8680
|
+
function mergeBySeq(a, b) {
|
|
8681
|
+
return [
|
|
8682
|
+
...a,
|
|
8683
|
+
...b
|
|
8684
|
+
].sort((x, y) => x.seq - y.seq);
|
|
8685
|
+
}
|
|
8686
|
+
__name(mergeBySeq, "mergeBySeq");
|
|
8687
|
+
async function failedChildren(store, runId, group, buckets) {
|
|
8688
|
+
if (!buckets.some((b) => b.nameKind === "child" && b.status === "completed")) {
|
|
8689
|
+
return {
|
|
8690
|
+
completed: [],
|
|
8691
|
+
failed: []
|
|
8692
|
+
};
|
|
8693
|
+
}
|
|
8694
|
+
const completed = await store.queryCheckpoints(runId, {
|
|
8695
|
+
group,
|
|
8696
|
+
statuses: [
|
|
8697
|
+
"completed"
|
|
8698
|
+
],
|
|
8699
|
+
namePrefixes: [
|
|
8700
|
+
"signal:child:"
|
|
8701
|
+
]
|
|
8702
|
+
});
|
|
8703
|
+
return {
|
|
8704
|
+
completed,
|
|
8705
|
+
failed: completed.filter(isFailedMember)
|
|
8706
|
+
};
|
|
8707
|
+
}
|
|
8708
|
+
__name(failedChildren, "failedChildren");
|
|
8709
|
+
async function summarizeFanFromStats(store, runId, group, buckets, folded) {
|
|
8710
|
+
const counts = {};
|
|
8711
|
+
const add = /* @__PURE__ */ __name((status, n) => {
|
|
8712
|
+
counts[status] = (counts[status] ?? 0) + n;
|
|
8713
|
+
}, "add");
|
|
8714
|
+
let size = 0;
|
|
8715
|
+
let startedAt;
|
|
8716
|
+
let finishedAt;
|
|
8717
|
+
let firstSeq = Number.POSITIVE_INFINITY;
|
|
8718
|
+
let lastSeq = Number.NEGATIVE_INFINITY;
|
|
8719
|
+
let minName;
|
|
8720
|
+
let maxName;
|
|
8721
|
+
let childRuns = false;
|
|
8722
|
+
for (const b of buckets) {
|
|
8723
|
+
firstSeq = Math.min(firstSeq, b.minSeq);
|
|
8724
|
+
lastSeq = Math.max(lastSeq, b.maxSeq);
|
|
8725
|
+
if (minName === void 0 || b.minName < minName) minName = b.minName;
|
|
8726
|
+
if (maxName === void 0 || b.maxName > maxName) maxName = b.maxName;
|
|
8727
|
+
if (b.nameKind === "child" || b.nameKind === "folded") childRuns = true;
|
|
8728
|
+
if (TERMINAL(b.status)) {
|
|
8729
|
+
if (!startedAt || b.minStartedAt < startedAt) startedAt = b.minStartedAt;
|
|
8730
|
+
if (!finishedAt || b.maxFinishedAt > finishedAt) finishedAt = b.maxFinishedAt;
|
|
8731
|
+
} else if (!startedAt || b.minEnqueuedAt < startedAt) {
|
|
8732
|
+
startedAt = b.minEnqueuedAt;
|
|
8733
|
+
}
|
|
8734
|
+
if (b.nameKind === "folded") continue;
|
|
8735
|
+
if (b.nameKind === "child" && b.status === "completed") continue;
|
|
8736
|
+
add(b.status, b.count);
|
|
8737
|
+
size += b.count;
|
|
8738
|
+
}
|
|
8739
|
+
for (const cp of folded) {
|
|
8740
|
+
const n = foldedCount(cp) ?? 1;
|
|
8741
|
+
add(isFailedMember(cp) ? "failed" : cp.status, n);
|
|
8742
|
+
size += n;
|
|
8743
|
+
}
|
|
8744
|
+
const children = await failedChildren(store, runId, group, buckets);
|
|
8745
|
+
const failedChildSet = new Set(children.failed);
|
|
8746
|
+
for (const cp of children.completed) add(failedChildSet.has(cp) ? "failed" : "completed", 1);
|
|
8747
|
+
size += children.completed.length;
|
|
8748
|
+
const failedStatusRows = buckets.some((b) => b.status === "failed") ? await store.queryCheckpoints(runId, {
|
|
8749
|
+
group,
|
|
8750
|
+
statuses: [
|
|
8751
|
+
"failed"
|
|
8752
|
+
],
|
|
8753
|
+
limit: FAN_SUMMARY_FAILURES
|
|
8754
|
+
}) : [];
|
|
8755
|
+
const failures = mergeBySeq(failedStatusRows, children.failed).slice(0, FAN_SUMMARY_FAILURES);
|
|
8756
|
+
const parsed = parseFanGroup(group);
|
|
8757
|
+
const total = parsed?.size;
|
|
8758
|
+
return {
|
|
8759
|
+
group,
|
|
8760
|
+
label: childRuns ? "child runs" : minName === maxName ? minName ?? "" : "steps",
|
|
8761
|
+
firstSeq,
|
|
8762
|
+
lastSeq,
|
|
8763
|
+
size,
|
|
8764
|
+
total,
|
|
8765
|
+
notStarted: total === void 0 ? void 0 : Math.max(0, total - size),
|
|
8766
|
+
failFast: parsed?.failFast ?? false,
|
|
8767
|
+
counts,
|
|
8768
|
+
startedAt,
|
|
8769
|
+
finishedAt,
|
|
8770
|
+
failures
|
|
8771
|
+
};
|
|
8772
|
+
}
|
|
8773
|
+
__name(summarizeFanFromStats, "summarizeFanFromStats");
|
|
8774
|
+
async function firstChildIds(store, runId) {
|
|
8775
|
+
const ids = [];
|
|
8776
|
+
for (let offset = 0; ids.length < SUMMARY_CHILD_LIMIT; offset += SUMMARY_CHILD_LIMIT) {
|
|
8777
|
+
const page = await store.queryCheckpoints(runId, {
|
|
8778
|
+
namePrefixes: CHILD_EDGE_PREFIXES,
|
|
8779
|
+
offset,
|
|
8780
|
+
limit: SUMMARY_CHILD_LIMIT
|
|
8781
|
+
});
|
|
8782
|
+
ids.push(...childIdsOf(page));
|
|
8783
|
+
if (page.length < SUMMARY_CHILD_LIMIT) break;
|
|
8784
|
+
}
|
|
8785
|
+
return ids.slice(0, SUMMARY_CHILD_LIMIT);
|
|
8786
|
+
}
|
|
8787
|
+
__name(firstChildIds, "firstChildIds");
|
|
8788
|
+
async function summarizeRunFromStore(store, runId, over, stats) {
|
|
8789
|
+
const all = stats ?? await store.checkpointStats(runId);
|
|
8790
|
+
const byGroup = /* @__PURE__ */ new Map();
|
|
8791
|
+
let edgeRows = 0;
|
|
8792
|
+
let hasFolded = false;
|
|
8793
|
+
for (const b of all) {
|
|
8794
|
+
if (b.nameKind === "child" || b.nameKind === "spawn") edgeRows += b.count;
|
|
8795
|
+
if (b.nameKind === "folded") hasFolded = true;
|
|
8796
|
+
if (b.group === null) continue;
|
|
8797
|
+
const buckets = byGroup.get(b.group);
|
|
8798
|
+
if (buckets) buckets.push(b);
|
|
8799
|
+
else byGroup.set(b.group, [
|
|
8800
|
+
b
|
|
8801
|
+
]);
|
|
8802
|
+
}
|
|
8803
|
+
const wide = [
|
|
8804
|
+
...byGroup
|
|
8805
|
+
].filter(([group, buckets]) => isWide(group, buckets.reduce((n, b) => n + b.count, 0), over));
|
|
8806
|
+
const [timeline, folded, children] = await Promise.all([
|
|
8807
|
+
store.queryCheckpoints(runId, {
|
|
8808
|
+
excludeGroups: wide.map(([group]) => group)
|
|
8809
|
+
}),
|
|
8810
|
+
hasFolded ? store.queryCheckpoints(runId, {
|
|
8811
|
+
namePrefixes: [
|
|
8812
|
+
FOLDED_PREFIX
|
|
8813
|
+
]
|
|
8814
|
+
}) : [],
|
|
8815
|
+
edgeRows > 0 || hasFolded ? firstChildIds(store, runId) : []
|
|
8816
|
+
]);
|
|
8817
|
+
const fans = await Promise.all(wide.map(([group, buckets]) => summarizeFanFromStats(store, runId, group, buckets, folded.filter((cp) => cp.parallelGroup === group))));
|
|
8818
|
+
const childCount = folded.reduce((n, cp) => n + childIdsOf([
|
|
8819
|
+
cp
|
|
8820
|
+
]).length, edgeRows);
|
|
8821
|
+
return {
|
|
8822
|
+
timeline,
|
|
8823
|
+
fans: fans.sort((a, b) => a.firstSeq - b.firstSeq),
|
|
8824
|
+
children,
|
|
8825
|
+
childCount
|
|
8826
|
+
};
|
|
8827
|
+
}
|
|
8828
|
+
__name(summarizeRunFromStore, "summarizeRunFromStore");
|
|
8829
|
+
async function pageFanFromStore(store, runId, group, query, stats) {
|
|
8830
|
+
const buckets = stats ?? await store.checkpointStats(runId, {
|
|
8831
|
+
group
|
|
8832
|
+
});
|
|
8833
|
+
const { offset, limit } = fanPageBounds(query);
|
|
8834
|
+
if (!query.failedOnly) {
|
|
8835
|
+
const total2 = buckets.reduce((n, b) => n + b.count, 0);
|
|
8836
|
+
const items = total2 > offset ? await store.queryCheckpoints(runId, {
|
|
8837
|
+
group,
|
|
8838
|
+
offset,
|
|
8839
|
+
limit
|
|
8840
|
+
}) : [];
|
|
8841
|
+
return {
|
|
8842
|
+
items,
|
|
8843
|
+
total: total2
|
|
8844
|
+
};
|
|
8845
|
+
}
|
|
8846
|
+
const failedStatus = buckets.filter((b) => b.status === "failed").reduce((n, b) => n + b.count, 0);
|
|
8847
|
+
const children = await failedChildren(store, runId, group, buckets);
|
|
8848
|
+
const total = failedStatus + children.failed.length;
|
|
8849
|
+
if (total <= offset) return {
|
|
8850
|
+
items: [],
|
|
8851
|
+
total
|
|
8852
|
+
};
|
|
8853
|
+
if (children.failed.length === 0) {
|
|
8854
|
+
return {
|
|
8855
|
+
items: await store.queryCheckpoints(runId, {
|
|
8856
|
+
group,
|
|
8857
|
+
statuses: [
|
|
8858
|
+
"failed"
|
|
8859
|
+
],
|
|
8860
|
+
offset,
|
|
8861
|
+
limit
|
|
8862
|
+
}),
|
|
8863
|
+
total
|
|
8864
|
+
};
|
|
8865
|
+
}
|
|
8866
|
+
const failedRows = await store.queryCheckpoints(runId, {
|
|
8867
|
+
group,
|
|
8868
|
+
statuses: [
|
|
8869
|
+
"failed"
|
|
8870
|
+
],
|
|
8871
|
+
limit: offset + limit
|
|
8872
|
+
});
|
|
8873
|
+
return {
|
|
8874
|
+
items: mergeBySeq(failedRows, children.failed).slice(offset, offset + limit),
|
|
8875
|
+
total
|
|
8876
|
+
};
|
|
8877
|
+
}
|
|
8878
|
+
__name(pageFanFromStore, "pageFanFromStore");
|
|
8879
|
+
function hash(text) {
|
|
8880
|
+
let h1 = 3735928559;
|
|
8881
|
+
let h2 = 1103547991;
|
|
8882
|
+
for (let i = 0; i < text.length; i++) {
|
|
8883
|
+
const ch = text.charCodeAt(i);
|
|
8884
|
+
h1 = Math.imul(h1 ^ ch, 2654435761);
|
|
8885
|
+
h2 = Math.imul(h2 ^ ch, 1597334677);
|
|
8886
|
+
}
|
|
8887
|
+
h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507) ^ Math.imul(h2 ^ h2 >>> 13, 3266489909);
|
|
8888
|
+
h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507) ^ Math.imul(h1 ^ h1 >>> 13, 3266489909);
|
|
8889
|
+
return (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(16);
|
|
8890
|
+
}
|
|
8891
|
+
__name(hash, "hash");
|
|
8892
|
+
function statsFingerprint(stats) {
|
|
8893
|
+
return stats.map((b) => [
|
|
8894
|
+
b.group ?? "",
|
|
8895
|
+
b.status,
|
|
8896
|
+
b.nameKind,
|
|
8897
|
+
b.count,
|
|
8898
|
+
b.minSeq,
|
|
8899
|
+
b.maxSeq,
|
|
8900
|
+
b.minName,
|
|
8901
|
+
b.maxName,
|
|
8902
|
+
b.minEnqueuedAt.getTime(),
|
|
8903
|
+
b.minStartedAt.getTime(),
|
|
8904
|
+
b.maxStartedAt.getTime(),
|
|
8905
|
+
b.maxFinishedAt.getTime(),
|
|
8906
|
+
b.sumAttempts
|
|
8907
|
+
].join("")).sort().join("");
|
|
8908
|
+
}
|
|
8909
|
+
__name(statsFingerprint, "statsFingerprint");
|
|
8910
|
+
function runDetailEtag(run, stats, over) {
|
|
8911
|
+
return `"rd-${hash(`${over}${JSON.stringify(run)}${statsFingerprint(stats)}`)}"`;
|
|
8912
|
+
}
|
|
8913
|
+
__name(runDetailEtag, "runDetailEtag");
|
|
8914
|
+
function fanPageEtag(group, stats, query) {
|
|
8915
|
+
const { offset, limit } = fanPageBounds(query);
|
|
8916
|
+
const key = [
|
|
8917
|
+
group,
|
|
8918
|
+
offset,
|
|
8919
|
+
limit,
|
|
8920
|
+
query.failedOnly === true
|
|
8921
|
+
].join("");
|
|
8922
|
+
return `"fp-${hash(`${key}${statsFingerprint(stats)}`)}"`;
|
|
8923
|
+
}
|
|
8924
|
+
__name(fanPageEtag, "fanPageEtag");
|
|
8925
|
+
function etagMatches(ifNoneMatch, etag) {
|
|
8926
|
+
if (!ifNoneMatch) return false;
|
|
8927
|
+
const bare = /* @__PURE__ */ __name((tag) => tag.trim().replace(/^W\//, ""), "bare");
|
|
8928
|
+
const target = bare(etag);
|
|
8929
|
+
return ifNoneMatch.split(",").some((tag) => tag.trim() === "*" || bare(tag) === target);
|
|
8930
|
+
}
|
|
8931
|
+
__name(etagMatches, "etagMatches");
|
|
8054
8932
|
|
|
8055
8933
|
// src/codec-state-store.ts
|
|
8056
8934
|
var CodecStateStore = class {
|
|
@@ -8080,11 +8958,19 @@ var CodecStateStore = class {
|
|
|
8080
8958
|
/** Encodes like {@link saveCheckpoint}. Bound only when the inner store has it, so the engine's
|
|
8081
8959
|
* plain-save fallback still kicks in through the wrapper. */
|
|
8082
8960
|
saveCheckpointIfPending;
|
|
8961
|
+
/** Encodes each checkpoint like {@link saveCheckpoint}. Bound only when the inner store has it. */
|
|
8962
|
+
saveCheckpointsIfPending;
|
|
8083
8963
|
/** Forwarded verbatim — a count reads no payload. Bound only when the inner store has it. */
|
|
8084
8964
|
countCheckpoints;
|
|
8085
8965
|
/** Forwarded verbatim — retention selects by status/updatedAt/scope, never by payload. Bound only
|
|
8086
8966
|
* when the inner store prunes, so the retention poller still sees (and warns about) its absence. */
|
|
8087
8967
|
pruneTerminalRuns;
|
|
8968
|
+
/** Forwarded verbatim — the buckets aggregate names, statuses and timestamps, never a payload.
|
|
8969
|
+
* Bound only when the inner store aggregates, so the run detail's fallback still applies. */
|
|
8970
|
+
checkpointStats;
|
|
8971
|
+
/** Filters on unencoded columns, then decodes the rows like {@link listCheckpoints}. Bound only
|
|
8972
|
+
* when the inner store has it. */
|
|
8973
|
+
queryCheckpoints;
|
|
8088
8974
|
/** Forwarded verbatim — a count reads no payload. Bound only when the inner store counts, so the
|
|
8089
8975
|
* engine's facet/listing fallback still applies through the wrapper. */
|
|
8090
8976
|
countRuns;
|
|
@@ -8111,8 +8997,14 @@ var CodecStateStore = class {
|
|
|
8111
8997
|
if (deleteRuns) this.deleteRuns = (ids) => deleteRuns.call(inner, ids);
|
|
8112
8998
|
const saveIfPending = inner.saveCheckpointIfPending;
|
|
8113
8999
|
if (saveIfPending) this.saveCheckpointIfPending = (cp) => saveIfPending.call(inner, this.encCp(cp));
|
|
9000
|
+
const saveManyIfPending = inner.saveCheckpointsIfPending;
|
|
9001
|
+
if (saveManyIfPending) this.saveCheckpointsIfPending = (cps) => saveManyIfPending.call(inner, cps.map((cp) => this.encCp(cp)));
|
|
8114
9002
|
const countCps = inner.countCheckpoints;
|
|
8115
9003
|
if (countCps) this.countCheckpoints = (runId, from, to, statuses) => countCps.call(inner, runId, from, to, statuses);
|
|
9004
|
+
const stats = inner.checkpointStats;
|
|
9005
|
+
if (stats) this.checkpointStats = (runId, opts) => stats.call(inner, runId, opts);
|
|
9006
|
+
const queryCps = inner.queryCheckpoints;
|
|
9007
|
+
if (queryCps) this.queryCheckpoints = async (runId, query) => (await queryCps.call(inner, runId, query)).map((c) => this.decCp(c));
|
|
8116
9008
|
const prune = inner.pruneTerminalRuns;
|
|
8117
9009
|
if (prune) this.pruneTerminalRuns = (policy, now, limit) => prune.call(inner, policy, now, limit);
|
|
8118
9010
|
const count = inner.countRuns;
|
|
@@ -8320,8 +9212,11 @@ function collectMetrics(engine) {
|
|
|
8320
9212
|
started: 0,
|
|
8321
9213
|
completed: 0,
|
|
8322
9214
|
failed: 0,
|
|
8323
|
-
suspended: 0
|
|
9215
|
+
suspended: 0,
|
|
9216
|
+
routed: 0,
|
|
9217
|
+
unrouted: 0
|
|
8324
9218
|
},
|
|
9219
|
+
routedByCode: {},
|
|
8325
9220
|
steps: {
|
|
8326
9221
|
started: 0,
|
|
8327
9222
|
completed: 0,
|
|
@@ -8363,6 +9258,15 @@ function collectMetrics(engine) {
|
|
|
8363
9258
|
case "run.suspended":
|
|
8364
9259
|
m.runs.suspended += 1;
|
|
8365
9260
|
break;
|
|
9261
|
+
case "run.routed": {
|
|
9262
|
+
m.runs.routed += 1;
|
|
9263
|
+
const code = e.error?.code ?? "*";
|
|
9264
|
+
m.routedByCode[code] = (m.routedByCode[code] ?? 0) + 1;
|
|
9265
|
+
break;
|
|
9266
|
+
}
|
|
9267
|
+
case "run.unrouted":
|
|
9268
|
+
m.runs.unrouted += 1;
|
|
9269
|
+
break;
|
|
8366
9270
|
case "step.started":
|
|
8367
9271
|
m.steps.started += 1;
|
|
8368
9272
|
break;
|
|
@@ -8389,6 +9293,8 @@ function collectMetrics(engine) {
|
|
|
8389
9293
|
for (const [k, v] of Object.entries(m.steps)) lines.push(`durable_steps_total{event="${k}"} ${v}`);
|
|
8390
9294
|
lines.push("# TYPE durable_runs_by_workflow_total counter");
|
|
8391
9295
|
for (const [name, v] of Object.entries(m.byWorkflow)) for (const [event, n] of Object.entries(v)) lines.push(`durable_runs_by_workflow_total{workflow="${name}",event="${event}"} ${n}`);
|
|
9296
|
+
lines.push("# TYPE durable_runs_routed_total counter");
|
|
9297
|
+
for (const [code, n] of Object.entries(m.routedByCode)) lines.push(`durable_runs_routed_total{code="${code}"} ${n}`);
|
|
8392
9298
|
lines.push("# TYPE durable_step_duration_ms_sum counter");
|
|
8393
9299
|
lines.push(`durable_step_duration_ms_sum ${m.stepDuration.sumMs}`);
|
|
8394
9300
|
lines.push("# TYPE durable_step_duration_count counter");
|
|
@@ -8559,6 +9465,11 @@ var InMemoryStateStore = class {
|
|
|
8559
9465
|
* so this lets the benchmark contrast "fetch N, scan in JS" against "fetch the k that match".
|
|
8560
9466
|
*/
|
|
8561
9467
|
lastCheckpointRowsRead = 0;
|
|
9468
|
+
/**
|
|
9469
|
+
* Test/observability hook: checkpoint rows materialized since construction, across every
|
|
9470
|
+
* checkpoint read that returns rows — what a run-detail benchmark compares between paths.
|
|
9471
|
+
*/
|
|
9472
|
+
checkpointRowsRead = 0;
|
|
8562
9473
|
key(runId, seq) {
|
|
8563
9474
|
return `${runId}:${seq}`;
|
|
8564
9475
|
}
|
|
@@ -8684,6 +9595,16 @@ var InMemoryStateStore = class {
|
|
|
8684
9595
|
});
|
|
8685
9596
|
return true;
|
|
8686
9597
|
}
|
|
9598
|
+
async saveCheckpointsIfPending(checkpoints) {
|
|
9599
|
+
return checkpoints.map((checkpoint) => {
|
|
9600
|
+
const key = this.key(checkpoint.runId, checkpoint.seq);
|
|
9601
|
+
if (this.checkpoints.get(key)?.status !== "pending") return false;
|
|
9602
|
+
this.checkpoints.set(key, {
|
|
9603
|
+
...checkpoint
|
|
9604
|
+
});
|
|
9605
|
+
return true;
|
|
9606
|
+
});
|
|
9607
|
+
}
|
|
8687
9608
|
async listIncompleteRuns(namespace) {
|
|
8688
9609
|
return [
|
|
8689
9610
|
...this.runs.values()
|
|
@@ -8963,6 +9884,23 @@ var InMemoryStateStore = class {
|
|
|
8963
9884
|
...cp
|
|
8964
9885
|
}));
|
|
8965
9886
|
this.lastCheckpointRowsRead = rows.length;
|
|
9887
|
+
this.checkpointRowsRead += rows.length;
|
|
9888
|
+
return rows;
|
|
9889
|
+
}
|
|
9890
|
+
runCheckpoints(runId) {
|
|
9891
|
+
return [
|
|
9892
|
+
...this.checkpoints.values()
|
|
9893
|
+
].filter((cp) => cp.runId === runId);
|
|
9894
|
+
}
|
|
9895
|
+
async checkpointStats(runId, opts) {
|
|
9896
|
+
return checkpointStatsOf(this.runCheckpoints(runId), opts);
|
|
9897
|
+
}
|
|
9898
|
+
async queryCheckpoints(runId, query) {
|
|
9899
|
+
const rows = queryCheckpointsOf(this.runCheckpoints(runId), query).map((cp) => ({
|
|
9900
|
+
...cp
|
|
9901
|
+
}));
|
|
9902
|
+
this.lastCheckpointRowsRead = rows.length;
|
|
9903
|
+
this.checkpointRowsRead += rows.length;
|
|
8966
9904
|
return rows;
|
|
8967
9905
|
}
|
|
8968
9906
|
async getLatestCheckpointByName(runId, name) {
|
|
@@ -8990,6 +9928,7 @@ var InMemoryStateStore = class {
|
|
|
8990
9928
|
...cp
|
|
8991
9929
|
}));
|
|
8992
9930
|
this.lastCheckpointRowsRead = rows.length;
|
|
9931
|
+
this.checkpointRowsRead += rows.length;
|
|
8993
9932
|
return rows;
|
|
8994
9933
|
}
|
|
8995
9934
|
};
|
|
@@ -9040,6 +9979,7 @@ var InMemoryTransport = class {
|
|
|
9040
9979
|
}
|
|
9041
9980
|
};
|
|
9042
9981
|
export {
|
|
9982
|
+
CHILD_EDGE_PREFIXES,
|
|
9043
9983
|
CONTROL_FLOW_SIGNAL,
|
|
9044
9984
|
CURRENT_PROTOCOL_VERSION,
|
|
9045
9985
|
CodecStateStore,
|
|
@@ -9060,6 +10000,7 @@ export {
|
|
|
9060
10000
|
FAN_SUMMARY_FAILURES,
|
|
9061
10001
|
FatalError,
|
|
9062
10002
|
GatherError,
|
|
10003
|
+
HANDOFF_SEQ_BASE,
|
|
9063
10004
|
InMemoryAdmissionBackend,
|
|
9064
10005
|
InMemoryStateStore,
|
|
9065
10006
|
InMemoryTransport,
|
|
@@ -9094,9 +10035,11 @@ export {
|
|
|
9094
10035
|
WorkflowEngine,
|
|
9095
10036
|
WorkflowSuspended,
|
|
9096
10037
|
aggregateAnnouncements,
|
|
10038
|
+
aggregatesCheckpoints,
|
|
9097
10039
|
announcementsOf,
|
|
9098
10040
|
applyAttributeQuery,
|
|
9099
10041
|
assertConcurrency,
|
|
10042
|
+
assertRoutes,
|
|
9100
10043
|
attributeColumnFor,
|
|
9101
10044
|
attributeOperand,
|
|
9102
10045
|
attributePredicateOperands,
|
|
@@ -9106,10 +10049,14 @@ export {
|
|
|
9106
10049
|
buildWorkflowDirectory,
|
|
9107
10050
|
canRoute,
|
|
9108
10051
|
capableWorkers,
|
|
10052
|
+
checkpointNameKind,
|
|
10053
|
+
checkpointStatsOf,
|
|
9109
10054
|
childIdsOf,
|
|
9110
10055
|
classifyWaiterToken,
|
|
9111
10056
|
clearDurableExecutionWrappers,
|
|
9112
10057
|
clearTerminalSingletonAdmission,
|
|
10058
|
+
codeAndDetails,
|
|
10059
|
+
codeOf,
|
|
9113
10060
|
collectMetrics,
|
|
9114
10061
|
concurrencyTag,
|
|
9115
10062
|
controlPlaneDescriptor,
|
|
@@ -9122,13 +10069,20 @@ export {
|
|
|
9122
10069
|
debug,
|
|
9123
10070
|
describeWorker,
|
|
9124
10071
|
descriptorHash,
|
|
10072
|
+
detailsOf,
|
|
9125
10073
|
error,
|
|
10074
|
+
etagMatches,
|
|
9126
10075
|
eventMatchOf,
|
|
9127
10076
|
eventMatches,
|
|
9128
10077
|
eventNameOf,
|
|
9129
10078
|
eventPrefix,
|
|
9130
10079
|
eventToken,
|
|
9131
10080
|
facetOrigin,
|
|
10081
|
+
fanPageBounds,
|
|
10082
|
+
fanPageEtag,
|
|
10083
|
+
foldedCount,
|
|
10084
|
+
handoffHandlerId,
|
|
10085
|
+
handoffName,
|
|
9132
10086
|
hasDurableExecutionWrappers,
|
|
9133
10087
|
heartbeatStatus,
|
|
9134
10088
|
indexWaitersByRun,
|
|
@@ -9140,6 +10094,7 @@ export {
|
|
|
9140
10094
|
isNonEmptyRunScope,
|
|
9141
10095
|
isWorkflowControlFlowSignal,
|
|
9142
10096
|
listSignalWaitersOfRuns,
|
|
10097
|
+
liveHandoff,
|
|
9143
10098
|
log,
|
|
9144
10099
|
matchesAttributes,
|
|
9145
10100
|
mergeRunFacetRows,
|
|
@@ -9150,17 +10105,23 @@ export {
|
|
|
9150
10105
|
normalizeAttributeRows,
|
|
9151
10106
|
normalizeDescriptor,
|
|
9152
10107
|
observedAnnouncements,
|
|
10108
|
+
onErrorRunId,
|
|
9153
10109
|
pageFan,
|
|
10110
|
+
pageFanFromStore,
|
|
9154
10111
|
parentRunIdOf,
|
|
9155
10112
|
parseDuration,
|
|
9156
10113
|
partitionOfToken,
|
|
9157
10114
|
planDispatch,
|
|
9158
10115
|
prevCronFireMs,
|
|
10116
|
+
queryCheckpointsOf,
|
|
9159
10117
|
readSearchAttributes,
|
|
9160
10118
|
requiredCapabilities,
|
|
9161
10119
|
resolveConcurrency,
|
|
9162
10120
|
resolveRouting,
|
|
9163
10121
|
resolveRunWaiting,
|
|
10122
|
+
retiredHandoffName,
|
|
10123
|
+
routeFor,
|
|
10124
|
+
runDetailEtag,
|
|
9164
10125
|
runDurableExecution,
|
|
9165
10126
|
runInStepLogger,
|
|
9166
10127
|
runInStepScope,
|
|
@@ -9181,6 +10142,7 @@ export {
|
|
|
9181
10142
|
subEvent,
|
|
9182
10143
|
subProcess,
|
|
9183
10144
|
summarizeFan,
|
|
10145
|
+
summarizeRunFromStore,
|
|
9184
10146
|
summarizeTimeline,
|
|
9185
10147
|
tenantGroup,
|
|
9186
10148
|
useDurableExecution,
|