@dudousxd/nestjs-durable-core 0.85.5 → 0.85.6

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 CHANGED
@@ -860,6 +860,386 @@ var BoundedRunner = class {
860
860
  };
861
861
  var LOCAL_RUN_CONCURRENCY = 256;
862
862
 
863
+ // src/fan-join.ts
864
+ function allChildId(runId, firstSeq, index, wave) {
865
+ return `${allBase(runId, firstSeq, wave)}${index}`;
866
+ }
867
+ __name(allChildId, "allChildId");
868
+ function allGroup(firstSeq, size, mode) {
869
+ const group = `all:${firstSeq}:n${size}`;
870
+ return mode === "failFast" ? `${group}:failFast` : group;
871
+ }
872
+ __name(allGroup, "allGroup");
873
+ function stepFanGroup(firstSeq, size, mode) {
874
+ const group = `steps:${firstSeq}:${size}`;
875
+ return mode === "failFast" ? `${group}:failFast` : group;
876
+ }
877
+ __name(stepFanGroup, "stepFanGroup");
878
+ function parseStepFanGroup(group) {
879
+ const fan = parseFanGroup(group);
880
+ if (fan?.kind !== "steps" || fan.size === void 0) return void 0;
881
+ return {
882
+ firstSeq: fan.firstSeq,
883
+ size: fan.size
884
+ };
885
+ }
886
+ __name(parseStepFanGroup, "parseStepFanGroup");
887
+ function parseFanGroup(group) {
888
+ const m = group ? /^(all|steps):(\d+)(?::n?(\d+))?(:failFast)?$/.exec(group) : null;
889
+ if (!m) return void 0;
890
+ if (m[1] === "steps" && m[3] === void 0) return void 0;
891
+ return {
892
+ kind: m[1],
893
+ firstSeq: Number(m[2]),
894
+ size: m[3] === void 0 ? void 0 : Number(m[3]),
895
+ failFast: m[4] !== void 0
896
+ };
897
+ }
898
+ __name(parseFanGroup, "parseFanGroup");
899
+ function allWait(runId, firstSeq, wave) {
900
+ const base = allBase(runId, firstSeq, wave);
901
+ return {
902
+ waiterPrefix: `child:${base}`,
903
+ checkpointPrefix: `signal:child:${base}`
904
+ };
905
+ }
906
+ __name(allWait, "allWait");
907
+ function fanJoin(waits) {
908
+ return {
909
+ waits
910
+ };
911
+ }
912
+ __name(fanJoin, "fanJoin");
913
+ var FOLDED_PREFIX = "fan:";
914
+ function allFoldedName(runId, firstSeq, wave) {
915
+ return `${FOLDED_PREFIX}${allBase(runId, firstSeq, wave)}`;
916
+ }
917
+ __name(allFoldedName, "allFoldedName");
918
+ function foldedChildIds(checkpoint) {
919
+ const base = checkpoint.name.slice(FOLDED_PREFIX.length);
920
+ const { from, values } = checkpoint.output;
921
+ return values.map((_, j) => `${base}${from + j}`);
922
+ }
923
+ __name(foldedChildIds, "foldedChildIds");
924
+ function allBase(runId, firstSeq, wave) {
925
+ return wave === void 0 ? `${runId}.all.${firstSeq}.` : `${runId}.all.${firstSeq}.w${wave}.`;
926
+ }
927
+ __name(allBase, "allBase");
928
+ function allWaves(size, concurrency) {
929
+ if (concurrency === void 0 || concurrency >= size) return void 0;
930
+ if (!Number.isInteger(concurrency) || concurrency < 1) {
931
+ throw new Error(`ctx.all: concurrency must be a positive integer, got ${concurrency}`);
932
+ }
933
+ if (concurrency === 1) return {
934
+ waveSize: 1,
935
+ maxWaves: 1
936
+ };
937
+ return {
938
+ waveSize: Math.floor(concurrency / 2),
939
+ maxWaves: 2
940
+ };
941
+ }
942
+ __name(allWaves, "allWaves");
943
+ var ENGINE_CHILD_ID = /^(.+)\.(?:all\.\d+\.(?:w\d+\.)?\d+|child\.\d+|onError\.\d+)$/;
944
+ function parentRunIdOf(runId) {
945
+ return ENGINE_CHILD_ID.exec(runId)?.[1];
946
+ }
947
+ __name(parentRunIdOf, "parentRunIdOf");
948
+ var GROUP = /^all:(\d+)(?::n\d+)?(:failFast)?$/;
949
+ var REST = /^(?:w(\d+)\.)?\d+$/;
950
+ function fanOfWaiter(waiter) {
951
+ const group = waiter.parallelGroup ? GROUP.exec(waiter.parallelGroup) : null;
952
+ if (!group) return void 0;
953
+ const base = `child:${waiter.runId}.all.${group[1]}.`;
954
+ if (!waiter.token.startsWith(base)) return void 0;
955
+ const rest = REST.exec(waiter.token.slice(base.length));
956
+ if (!rest) return void 0;
957
+ const waiterPrefix = rest[1] === void 0 ? base : `${base}w${rest[1]}.`;
958
+ return {
959
+ waiterPrefix,
960
+ failFast: group[2] !== void 0
961
+ };
962
+ }
963
+ __name(fanOfWaiter, "fanOfWaiter");
964
+
965
+ // src/interfaces.ts
966
+ function checkpointNameKind(name) {
967
+ if (name.startsWith("signal:child:")) return "child";
968
+ if (name.startsWith("fan:")) return "folded";
969
+ if (name.startsWith("spawn:")) return "spawn";
970
+ return "step";
971
+ }
972
+ __name(checkpointNameKind, "checkpointNameKind");
973
+ function poolNamespace(namespace) {
974
+ return namespace === "" ? "default" : namespace;
975
+ }
976
+ __name(poolNamespace, "poolNamespace");
977
+ var TERMINAL_RUN_STATUSES = [
978
+ "completed",
979
+ "failed",
980
+ "cancelled",
981
+ "dead"
982
+ ];
983
+ function isNonEmptyRunScope(scope) {
984
+ if (!scope) return false;
985
+ return scope.workflow !== void 0 || scope.workflows !== void 0 || scope.tag !== void 0 || scope.tags !== void 0 || scope.namespace !== void 0 || scope.namespaces !== void 0 || scope.attributes !== void 0 && scope.attributes.length > 0;
986
+ }
987
+ __name(isNonEmptyRunScope, "isNonEmptyRunScope");
988
+
989
+ // src/events.ts
990
+ var enc = /* @__PURE__ */ __name((s) => Buffer.from(s, "utf8").toString("base64"), "enc");
991
+ var dec = /* @__PURE__ */ __name((s) => Buffer.from(s, "base64").toString("utf8"), "dec");
992
+ var EVENT_BUFFER_SCAN_LIMIT = 50;
993
+ var EVENT_TOKEN_PREFIX = "event:";
994
+ function eventPrefix(name) {
995
+ return `${EVENT_TOKEN_PREFIX}${enc(name)}:`;
996
+ }
997
+ __name(eventPrefix, "eventPrefix");
998
+ function isEventToken(token) {
999
+ return token.startsWith(EVENT_TOKEN_PREFIX);
1000
+ }
1001
+ __name(isEventToken, "isEventToken");
1002
+ function eventToken(name, match, runId, seq) {
1003
+ return `${eventPrefix(name)}${enc(JSON.stringify(match ?? {}))}:${runId}#${seq}`;
1004
+ }
1005
+ __name(eventToken, "eventToken");
1006
+ function eventNameOf(token) {
1007
+ try {
1008
+ return dec(token.split(":")[1] ?? "");
1009
+ } catch {
1010
+ return token;
1011
+ }
1012
+ }
1013
+ __name(eventNameOf, "eventNameOf");
1014
+ function eventMatchOf(token) {
1015
+ try {
1016
+ return JSON.parse(dec(token.split(":")[2] ?? ""));
1017
+ } catch {
1018
+ return {};
1019
+ }
1020
+ }
1021
+ __name(eventMatchOf, "eventMatchOf");
1022
+ function eventMatches(payload, match) {
1023
+ const keys = Object.keys(match);
1024
+ if (keys.length === 0) return true;
1025
+ if (!payload || typeof payload !== "object") return false;
1026
+ const p = payload;
1027
+ return keys.every((k) => JSON.stringify(p[k]) === JSON.stringify(match[k]));
1028
+ }
1029
+ __name(eventMatches, "eventMatches");
1030
+
1031
+ // src/run-waiting.ts
1032
+ var WEBHOOK_PREFIX = "wh:";
1033
+ var CHILD_PREFIX = "child:";
1034
+ var BREAKPOINT_PREFIX = "bp:";
1035
+ var EVENT_PREFIX = EVENT_TOKEN_PREFIX;
1036
+ function parseWebhookToken(token) {
1037
+ if (!token.startsWith(WEBHOOK_PREFIX)) return null;
1038
+ const rest = token.slice(WEBHOOK_PREFIX.length);
1039
+ const at = rest.lastIndexOf(":");
1040
+ if (at <= 0) return null;
1041
+ const seqText = rest.slice(at + 1);
1042
+ if (!/^\d+$/.test(seqText)) return null;
1043
+ return {
1044
+ runId: rest.slice(0, at),
1045
+ seq: Number(seqText)
1046
+ };
1047
+ }
1048
+ __name(parseWebhookToken, "parseWebhookToken");
1049
+ function classifyWaiterToken(token) {
1050
+ if (token.startsWith(WEBHOOK_PREFIX)) return {
1051
+ on: "webhook",
1052
+ name: token
1053
+ };
1054
+ if (token.startsWith(BREAKPOINT_PREFIX)) return {
1055
+ on: "breakpoint",
1056
+ name: "breakpoint"
1057
+ };
1058
+ if (token.startsWith(CHILD_PREFIX)) return {
1059
+ on: "child",
1060
+ name: token.slice(CHILD_PREFIX.length)
1061
+ };
1062
+ if (token.startsWith(EVENT_PREFIX)) return {
1063
+ on: "signal",
1064
+ name: eventNameOf(token)
1065
+ };
1066
+ return {
1067
+ on: "signal",
1068
+ name: token
1069
+ };
1070
+ }
1071
+ __name(classifyWaiterToken, "classifyWaiterToken");
1072
+ function resolveRunWaiting(run, waiterByRun) {
1073
+ if (run.status !== "suspended") return void 0;
1074
+ const waiter = waiterByRun.get(run.id);
1075
+ return waiter ? classifyWaiterToken(waiter.token) : void 0;
1076
+ }
1077
+ __name(resolveRunWaiting, "resolveRunWaiting");
1078
+ var RUN_ID_BATCH = 500;
1079
+ async function listSignalWaitersOfRuns(store, runIds) {
1080
+ const unique = [
1081
+ ...new Set(runIds)
1082
+ ];
1083
+ if (unique.length === 0) return [];
1084
+ if (!store.listSignalWaitersByRun) {
1085
+ const ids = new Set(unique);
1086
+ return (await store.listSignalWaiters("")).filter((w) => ids.has(w.runId));
1087
+ }
1088
+ const out = [];
1089
+ for (let at = 0; at < unique.length; at += RUN_ID_BATCH) {
1090
+ out.push(...await store.listSignalWaitersByRun(unique.slice(at, at + RUN_ID_BATCH)));
1091
+ }
1092
+ return out;
1093
+ }
1094
+ __name(listSignalWaitersOfRuns, "listSignalWaitersOfRuns");
1095
+ function indexWaitersByRun(waiters) {
1096
+ const byRun = /* @__PURE__ */ new Map();
1097
+ for (const w of waiters) if (!byRun.has(w.runId)) byRun.set(w.runId, w);
1098
+ return byRun;
1099
+ }
1100
+ __name(indexWaitersByRun, "indexWaitersByRun");
1101
+ var GET_RUN_FALLBACK_PARALLEL = 50;
1102
+ async function getRunsByIds(store, runIds) {
1103
+ const { runs, unread, error: error2 } = await readRunsByIds(store, runIds);
1104
+ if (unread.length > 0) throw error2;
1105
+ return runs;
1106
+ }
1107
+ __name(getRunsByIds, "getRunsByIds");
1108
+ async function readRunsByIds(store, runIds) {
1109
+ const unique = [
1110
+ ...new Set(runIds)
1111
+ ];
1112
+ const runs = /* @__PURE__ */ new Map();
1113
+ const unread = [];
1114
+ let error2;
1115
+ const batch = store.getRuns ? RUN_ID_BATCH : GET_RUN_FALLBACK_PARALLEL;
1116
+ for (let at = 0; at < unique.length; at += batch) {
1117
+ const ids = unique.slice(at, at + batch);
1118
+ if (store.getRuns) {
1119
+ try {
1120
+ for (const run of await store.getRuns(ids)) runs.set(run.id, run);
1121
+ } catch (err) {
1122
+ error2 ??= err;
1123
+ unread.push(...ids);
1124
+ }
1125
+ continue;
1126
+ }
1127
+ const reads = await Promise.allSettled(ids.map((id) => store.getRun(id)));
1128
+ reads.forEach((read, k) => {
1129
+ if (read.status === "fulfilled") {
1130
+ if (read.value) runs.set(read.value.id, read.value);
1131
+ return;
1132
+ }
1133
+ error2 ??= read.reason;
1134
+ unread.push(ids[k]);
1135
+ });
1136
+ }
1137
+ return {
1138
+ runs,
1139
+ unread,
1140
+ ...unread.length > 0 ? {
1141
+ error: error2
1142
+ } : {}
1143
+ };
1144
+ }
1145
+ __name(readRunsByIds, "readRunsByIds");
1146
+
1147
+ // src/buffered-child-signals.ts
1148
+ var CHILD_TOKEN_PREFIX = "child:";
1149
+ var CHILD_SIGNAL_NAME_PREFIX = "signal:child:";
1150
+ var RETRYABLE = /* @__PURE__ */ new Set([
1151
+ "failed",
1152
+ "dead"
1153
+ ]);
1154
+ var FAN_CHILD = /^(.+\.all\.\d+\.(?:w\d+\.)?)(\d+)$/;
1155
+ function canonicalJson(value) {
1156
+ const plain = JSON.stringify(value);
1157
+ return plain === void 0 ? "null" : sortedJson(JSON.parse(plain));
1158
+ }
1159
+ __name(canonicalJson, "canonicalJson");
1160
+ function sortedJson(value) {
1161
+ if (Array.isArray(value)) return `[${value.map(sortedJson).join(",")}]`;
1162
+ if (value !== null && typeof value === "object") {
1163
+ const entries = Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
1164
+ return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${sortedJson(v)}`).join(",")}}`;
1165
+ }
1166
+ return JSON.stringify(value);
1167
+ }
1168
+ __name(sortedJson, "sortedJson");
1169
+ function settledNames(childId) {
1170
+ const signal = `${CHILD_SIGNAL_NAME_PREFIX}${childId}`;
1171
+ const fan = FAN_CHILD.exec(childId);
1172
+ if (!fan) return {
1173
+ signal
1174
+ };
1175
+ return {
1176
+ signal,
1177
+ fold: {
1178
+ name: `${FOLDED_PREFIX}${fan[1]}`,
1179
+ index: Number(fan[2])
1180
+ }
1181
+ };
1182
+ }
1183
+ __name(settledNames, "settledNames");
1184
+ function parentHoldsChild(checkpoints, childId, completion) {
1185
+ const { signal, fold } = settledNames(childId);
1186
+ const expected = canonicalJson(completion);
1187
+ return checkpoints.some((cp) => {
1188
+ if (cp.status !== "completed") return false;
1189
+ if (cp.name === signal) return canonicalJson(cp.output) === expected;
1190
+ if (fold && cp.name === fold.name) {
1191
+ const { from, values } = cp.output ?? {};
1192
+ return typeof from === "number" && Array.isArray(values) && fold.index >= from && fold.index < from + values.length;
1193
+ }
1194
+ return false;
1195
+ });
1196
+ }
1197
+ __name(parentHoldsChild, "parentHoldsChild");
1198
+ async function parentCheckpoints(store, parentId, childId) {
1199
+ const { signal, fold } = settledNames(childId);
1200
+ const prefixes = fold ? [
1201
+ signal,
1202
+ fold.name
1203
+ ] : [
1204
+ signal
1205
+ ];
1206
+ return store.listCheckpointsByNamePrefix ? store.listCheckpointsByNamePrefix(parentId, prefixes) : store.listCheckpoints(parentId);
1207
+ }
1208
+ __name(parentCheckpoints, "parentCheckpoints");
1209
+ async function childCompletionNeedsBuffer(store, childId, completion, parent) {
1210
+ const parentId = parentRunIdOf(childId);
1211
+ if (!parentId) return false;
1212
+ const run = parent === void 0 ? await store.getRun(parentId) : parent;
1213
+ if (!run) return false;
1214
+ if (TERMINAL_RUN_STATUSES.includes(run.status) && !RETRYABLE.has(run.status)) return false;
1215
+ return !parentHoldsChild(await parentCheckpoints(store, parentId, childId), childId, completion);
1216
+ }
1217
+ __name(childCompletionNeedsBuffer, "childCompletionNeedsBuffer");
1218
+ async function unneededChildSignals(store, rows) {
1219
+ const parentIds = rows.flatMap((row) => {
1220
+ const parentId = parentRunIdOf(row.token.slice(CHILD_TOKEN_PREFIX.length));
1221
+ return parentId ? [
1222
+ parentId
1223
+ ] : [];
1224
+ });
1225
+ const { runs, unread } = await readRunsByIds(store, parentIds);
1226
+ const unreadable = new Set(unread);
1227
+ const doomed = [];
1228
+ for (const row of rows) {
1229
+ if (!row.token.startsWith(CHILD_TOKEN_PREFIX)) continue;
1230
+ const childId = row.token.slice(CHILD_TOKEN_PREFIX.length);
1231
+ const parentId = parentRunIdOf(childId);
1232
+ if (parentId && unreadable.has(parentId)) continue;
1233
+ const parent = parentId ? runs.get(parentId) ?? null : null;
1234
+ try {
1235
+ if (!await childCompletionNeedsBuffer(store, childId, row.payload, parent)) doomed.push(row);
1236
+ } catch {
1237
+ }
1238
+ }
1239
+ return doomed;
1240
+ }
1241
+ __name(unneededChildSignals, "unneededChildSignals");
1242
+
863
1243
  // src/execution-hooks.ts
864
1244
  var wrappers = [];
865
1245
  function useDurableExecution(wrapper) {
@@ -1189,6 +1569,32 @@ function completionError(c) {
1189
1569
  };
1190
1570
  }
1191
1571
  __name(completionError, "completionError");
1572
+ function childCompletion(child) {
1573
+ if (child.status === "completed") return {
1574
+ ok: true,
1575
+ value: child.output
1576
+ };
1577
+ if (child.status === "failed") return failedCompletion(child.error ?? {
1578
+ message: "failed"
1579
+ });
1580
+ if (child.status === "dead") return failedCompletion(child.error ?? {
1581
+ message: "dead"
1582
+ });
1583
+ if (child.status === "cancelled") return cancelledCompletion(child.error);
1584
+ return void 0;
1585
+ }
1586
+ __name(childCompletion, "childCompletion");
1587
+ var CHILD_CANCELLED = "cancelled";
1588
+ function cancelledCompletion(error2) {
1589
+ const base = error2 ?? {
1590
+ message: "cancelled"
1591
+ };
1592
+ return failedCompletion({
1593
+ ...base,
1594
+ code: base.code ?? CHILD_CANCELLED
1595
+ });
1596
+ }
1597
+ __name(cancelledCompletion, "cancelledCompletion");
1192
1598
 
1193
1599
  // src/concurrency.ts
1194
1600
  var DEFAULT_CONCURRENCY_STATUSES = [
@@ -1455,150 +1861,6 @@ var EventAccumulators = class {
1455
1861
  }
1456
1862
  };
1457
1863
 
1458
- // src/events.ts
1459
- var enc = /* @__PURE__ */ __name((s) => Buffer.from(s, "utf8").toString("base64"), "enc");
1460
- var dec = /* @__PURE__ */ __name((s) => Buffer.from(s, "base64").toString("utf8"), "dec");
1461
- var EVENT_BUFFER_SCAN_LIMIT = 50;
1462
- var EVENT_TOKEN_PREFIX = "event:";
1463
- function eventPrefix(name) {
1464
- return `${EVENT_TOKEN_PREFIX}${enc(name)}:`;
1465
- }
1466
- __name(eventPrefix, "eventPrefix");
1467
- function isEventToken(token) {
1468
- return token.startsWith(EVENT_TOKEN_PREFIX);
1469
- }
1470
- __name(isEventToken, "isEventToken");
1471
- function eventToken(name, match, runId, seq) {
1472
- return `${eventPrefix(name)}${enc(JSON.stringify(match ?? {}))}:${runId}#${seq}`;
1473
- }
1474
- __name(eventToken, "eventToken");
1475
- function eventNameOf(token) {
1476
- try {
1477
- return dec(token.split(":")[1] ?? "");
1478
- } catch {
1479
- return token;
1480
- }
1481
- }
1482
- __name(eventNameOf, "eventNameOf");
1483
- function eventMatchOf(token) {
1484
- try {
1485
- return JSON.parse(dec(token.split(":")[2] ?? ""));
1486
- } catch {
1487
- return {};
1488
- }
1489
- }
1490
- __name(eventMatchOf, "eventMatchOf");
1491
- function eventMatches(payload, match) {
1492
- const keys = Object.keys(match);
1493
- if (keys.length === 0) return true;
1494
- if (!payload || typeof payload !== "object") return false;
1495
- const p = payload;
1496
- return keys.every((k) => JSON.stringify(p[k]) === JSON.stringify(match[k]));
1497
- }
1498
- __name(eventMatches, "eventMatches");
1499
-
1500
- // src/fan-join.ts
1501
- function allChildId(runId, firstSeq, index, wave) {
1502
- return `${allBase(runId, firstSeq, wave)}${index}`;
1503
- }
1504
- __name(allChildId, "allChildId");
1505
- function allGroup(firstSeq, size, mode) {
1506
- const group = `all:${firstSeq}:n${size}`;
1507
- return mode === "failFast" ? `${group}:failFast` : group;
1508
- }
1509
- __name(allGroup, "allGroup");
1510
- function stepFanGroup(firstSeq, size, mode) {
1511
- const group = `steps:${firstSeq}:${size}`;
1512
- return mode === "failFast" ? `${group}:failFast` : group;
1513
- }
1514
- __name(stepFanGroup, "stepFanGroup");
1515
- function parseStepFanGroup(group) {
1516
- const fan = parseFanGroup(group);
1517
- if (fan?.kind !== "steps" || fan.size === void 0) return void 0;
1518
- return {
1519
- firstSeq: fan.firstSeq,
1520
- size: fan.size
1521
- };
1522
- }
1523
- __name(parseStepFanGroup, "parseStepFanGroup");
1524
- function parseFanGroup(group) {
1525
- const m = group ? /^(all|steps):(\d+)(?::n?(\d+))?(:failFast)?$/.exec(group) : null;
1526
- if (!m) return void 0;
1527
- if (m[1] === "steps" && m[3] === void 0) return void 0;
1528
- return {
1529
- kind: m[1],
1530
- firstSeq: Number(m[2]),
1531
- size: m[3] === void 0 ? void 0 : Number(m[3]),
1532
- failFast: m[4] !== void 0
1533
- };
1534
- }
1535
- __name(parseFanGroup, "parseFanGroup");
1536
- function allWait(runId, firstSeq, wave) {
1537
- const base = allBase(runId, firstSeq, wave);
1538
- return {
1539
- waiterPrefix: `child:${base}`,
1540
- checkpointPrefix: `signal:child:${base}`
1541
- };
1542
- }
1543
- __name(allWait, "allWait");
1544
- function fanJoin(waits) {
1545
- return {
1546
- waits
1547
- };
1548
- }
1549
- __name(fanJoin, "fanJoin");
1550
- var FOLDED_PREFIX = "fan:";
1551
- function allFoldedName(runId, firstSeq, wave) {
1552
- return `${FOLDED_PREFIX}${allBase(runId, firstSeq, wave)}`;
1553
- }
1554
- __name(allFoldedName, "allFoldedName");
1555
- function foldedChildIds(checkpoint) {
1556
- const base = checkpoint.name.slice(FOLDED_PREFIX.length);
1557
- const { from, values } = checkpoint.output;
1558
- return values.map((_, j) => `${base}${from + j}`);
1559
- }
1560
- __name(foldedChildIds, "foldedChildIds");
1561
- function allBase(runId, firstSeq, wave) {
1562
- return wave === void 0 ? `${runId}.all.${firstSeq}.` : `${runId}.all.${firstSeq}.w${wave}.`;
1563
- }
1564
- __name(allBase, "allBase");
1565
- function allWaves(size, concurrency) {
1566
- if (concurrency === void 0 || concurrency >= size) return void 0;
1567
- if (!Number.isInteger(concurrency) || concurrency < 1) {
1568
- throw new Error(`ctx.all: concurrency must be a positive integer, got ${concurrency}`);
1569
- }
1570
- if (concurrency === 1) return {
1571
- waveSize: 1,
1572
- maxWaves: 1
1573
- };
1574
- return {
1575
- waveSize: Math.floor(concurrency / 2),
1576
- maxWaves: 2
1577
- };
1578
- }
1579
- __name(allWaves, "allWaves");
1580
- var ENGINE_CHILD_ID = /^(.+)\.(?:all\.\d+\.(?:w\d+\.)?\d+|child\.\d+|onError\.\d+)$/;
1581
- function parentRunIdOf(runId) {
1582
- return ENGINE_CHILD_ID.exec(runId)?.[1];
1583
- }
1584
- __name(parentRunIdOf, "parentRunIdOf");
1585
- var GROUP = /^all:(\d+)(?::n\d+)?(:failFast)?$/;
1586
- var REST = /^(?:w(\d+)\.)?\d+$/;
1587
- function fanOfWaiter(waiter) {
1588
- const group = waiter.parallelGroup ? GROUP.exec(waiter.parallelGroup) : null;
1589
- if (!group) return void 0;
1590
- const base = `child:${waiter.runId}.all.${group[1]}.`;
1591
- if (!waiter.token.startsWith(base)) return void 0;
1592
- const rest = REST.exec(waiter.token.slice(base.length));
1593
- if (!rest) return void 0;
1594
- const waiterPrefix = rest[1] === void 0 ? base : `${base}w${rest[1]}.`;
1595
- return {
1596
- waiterPrefix,
1597
- failFast: group[2] !== void 0
1598
- };
1599
- }
1600
- __name(fanOfWaiter, "fanOfWaiter");
1601
-
1602
1864
  // src/handshake/descriptor.ts
1603
1865
  var CURRENT_PROTOCOL_VERSION = 1;
1604
1866
  var LEGACY_V1_PROTOCOL = {
@@ -2282,33 +2544,9 @@ function planDispatch(requires, liveDescriptors, cp, token) {
2282
2544
  controlPlane: cp,
2283
2545
  workers
2284
2546
  }
2285
- };
2286
- }
2287
- __name(planDispatch, "planDispatch");
2288
-
2289
- // src/interfaces.ts
2290
- function checkpointNameKind(name) {
2291
- if (name.startsWith("signal:child:")) return "child";
2292
- if (name.startsWith("fan:")) return "folded";
2293
- if (name.startsWith("spawn:")) return "spawn";
2294
- return "step";
2295
- }
2296
- __name(checkpointNameKind, "checkpointNameKind");
2297
- function poolNamespace(namespace) {
2298
- return namespace === "" ? "default" : namespace;
2299
- }
2300
- __name(poolNamespace, "poolNamespace");
2301
- var TERMINAL_RUN_STATUSES = [
2302
- "completed",
2303
- "failed",
2304
- "cancelled",
2305
- "dead"
2306
- ];
2307
- function isNonEmptyRunScope(scope) {
2308
- if (!scope) return false;
2309
- return scope.workflow !== void 0 || scope.workflows !== void 0 || scope.tag !== void 0 || scope.tags !== void 0 || scope.namespace !== void 0 || scope.namespaces !== void 0 || scope.attributes !== void 0 && scope.attributes.length > 0;
2547
+ };
2310
2548
  }
2311
- __name(isNonEmptyRunScope, "isNonEmptyRunScope");
2549
+ __name(planDispatch, "planDispatch");
2312
2550
 
2313
2551
  // src/on-error.ts
2314
2552
  var UNROUTABLE_CODES = /* @__PURE__ */ new Set([
@@ -2484,122 +2722,6 @@ var RunDispatchBatcher = class {
2484
2722
  }
2485
2723
  };
2486
2724
 
2487
- // src/run-waiting.ts
2488
- var WEBHOOK_PREFIX = "wh:";
2489
- var CHILD_PREFIX = "child:";
2490
- var BREAKPOINT_PREFIX = "bp:";
2491
- var EVENT_PREFIX = EVENT_TOKEN_PREFIX;
2492
- function parseWebhookToken(token) {
2493
- if (!token.startsWith(WEBHOOK_PREFIX)) return null;
2494
- const rest = token.slice(WEBHOOK_PREFIX.length);
2495
- const at = rest.lastIndexOf(":");
2496
- if (at <= 0) return null;
2497
- const seqText = rest.slice(at + 1);
2498
- if (!/^\d+$/.test(seqText)) return null;
2499
- return {
2500
- runId: rest.slice(0, at),
2501
- seq: Number(seqText)
2502
- };
2503
- }
2504
- __name(parseWebhookToken, "parseWebhookToken");
2505
- function classifyWaiterToken(token) {
2506
- if (token.startsWith(WEBHOOK_PREFIX)) return {
2507
- on: "webhook",
2508
- name: token
2509
- };
2510
- if (token.startsWith(BREAKPOINT_PREFIX)) return {
2511
- on: "breakpoint",
2512
- name: "breakpoint"
2513
- };
2514
- if (token.startsWith(CHILD_PREFIX)) return {
2515
- on: "child",
2516
- name: token.slice(CHILD_PREFIX.length)
2517
- };
2518
- if (token.startsWith(EVENT_PREFIX)) return {
2519
- on: "signal",
2520
- name: eventNameOf(token)
2521
- };
2522
- return {
2523
- on: "signal",
2524
- name: token
2525
- };
2526
- }
2527
- __name(classifyWaiterToken, "classifyWaiterToken");
2528
- function resolveRunWaiting(run, waiterByRun) {
2529
- if (run.status !== "suspended") return void 0;
2530
- const waiter = waiterByRun.get(run.id);
2531
- return waiter ? classifyWaiterToken(waiter.token) : void 0;
2532
- }
2533
- __name(resolveRunWaiting, "resolveRunWaiting");
2534
- var RUN_ID_BATCH = 500;
2535
- async function listSignalWaitersOfRuns(store, runIds) {
2536
- const unique = [
2537
- ...new Set(runIds)
2538
- ];
2539
- if (unique.length === 0) return [];
2540
- if (!store.listSignalWaitersByRun) {
2541
- const ids = new Set(unique);
2542
- return (await store.listSignalWaiters("")).filter((w) => ids.has(w.runId));
2543
- }
2544
- const out = [];
2545
- for (let at = 0; at < unique.length; at += RUN_ID_BATCH) {
2546
- out.push(...await store.listSignalWaitersByRun(unique.slice(at, at + RUN_ID_BATCH)));
2547
- }
2548
- return out;
2549
- }
2550
- __name(listSignalWaitersOfRuns, "listSignalWaitersOfRuns");
2551
- function indexWaitersByRun(waiters) {
2552
- const byRun = /* @__PURE__ */ new Map();
2553
- for (const w of waiters) if (!byRun.has(w.runId)) byRun.set(w.runId, w);
2554
- return byRun;
2555
- }
2556
- __name(indexWaitersByRun, "indexWaitersByRun");
2557
- var GET_RUN_FALLBACK_PARALLEL = 50;
2558
- async function getRunsByIds(store, runIds) {
2559
- const { runs, unread, error: error2 } = await readRunsByIds(store, runIds);
2560
- if (unread.length > 0) throw error2;
2561
- return runs;
2562
- }
2563
- __name(getRunsByIds, "getRunsByIds");
2564
- async function readRunsByIds(store, runIds) {
2565
- const unique = [
2566
- ...new Set(runIds)
2567
- ];
2568
- const runs = /* @__PURE__ */ new Map();
2569
- const unread = [];
2570
- let error2;
2571
- const batch = store.getRuns ? RUN_ID_BATCH : GET_RUN_FALLBACK_PARALLEL;
2572
- for (let at = 0; at < unique.length; at += batch) {
2573
- const ids = unique.slice(at, at + batch);
2574
- if (store.getRuns) {
2575
- try {
2576
- for (const run of await store.getRuns(ids)) runs.set(run.id, run);
2577
- } catch (err) {
2578
- error2 ??= err;
2579
- unread.push(...ids);
2580
- }
2581
- continue;
2582
- }
2583
- const reads = await Promise.allSettled(ids.map((id) => store.getRun(id)));
2584
- reads.forEach((read, k) => {
2585
- if (read.status === "fulfilled") {
2586
- if (read.value) runs.set(read.value.id, read.value);
2587
- return;
2588
- }
2589
- error2 ??= read.reason;
2590
- unread.push(ids[k]);
2591
- });
2592
- }
2593
- return {
2594
- runs,
2595
- unread,
2596
- ...unread.length > 0 ? {
2597
- error: error2
2598
- } : {}
2599
- };
2600
- }
2601
- __name(readRunsByIds, "readRunsByIds");
2602
-
2603
2725
  // src/scheduler.ts
2604
2726
  var import_node_module = require("module");
2605
2727
 
@@ -4056,7 +4178,23 @@ function createWorkflowCtx(host, runId, compensations, workflow = "", searchAttr
4056
4178
  status: "running"
4057
4179
  }));
4058
4180
  }
4059
- if (!await store.getRun(id)) host.startChild(workflowName(workflow2), input, id, priority, version);
4181
+ const childRun = await store.getRun(id);
4182
+ if (!childRun) {
4183
+ host.startChild(workflowName(workflow2), input, id, priority, version);
4184
+ } else {
4185
+ const done = (await settleFinishedChildren([
4186
+ {
4187
+ id,
4188
+ seq: current
4189
+ }
4190
+ ], /* @__PURE__ */ new Map([
4191
+ [
4192
+ id,
4193
+ childRun
4194
+ ]
4195
+ ]))).get(id);
4196
+ if (done) return unwrapCompletion(done, `child "${id}"`);
4197
+ }
4060
4198
  throw new WorkflowSuspended(void 0, fanJoin([
4061
4199
  {
4062
4200
  waiterPrefix: `child:${id}`,
@@ -4077,164 +4215,174 @@ function createWorkflowCtx(host, runId, compensations, workflow = "", searchAttr
4077
4215
  const group = allGroup(firstSeq, inputs.length, mode);
4078
4216
  const waveOf = /* @__PURE__ */ __name((i) => waves ? Math.floor(i / waves.waveSize) : void 0, "waveOf");
4079
4217
  const id = /* @__PURE__ */ __name((i) => allChildId(runId, firstSeq, i, waveOf(i)), "id");
4080
- const { checkpoints: existing, folded } = await readFan(positions, [
4081
- allWait(runId, firstSeq).checkpointPrefix,
4082
- allFoldedName(runId, firstSeq)
4083
- ]);
4084
- const waveCount = waves ? Math.ceil(inputs.length / waves.waveSize) : 1;
4085
- const waveItems = /* @__PURE__ */ __name((k) => {
4086
- if (!waves) return inputs.map((_, i) => i);
4087
- const from = k * waves.waveSize;
4088
- return inputs.map((_, i) => i).slice(from, from + waves.waveSize);
4089
- }, "waveItems");
4090
- if (mode === "failFast") {
4091
- const failure = /* @__PURE__ */ __name((i2) => {
4092
- const cp = existing[i2];
4093
- const c2 = cp?.status === "completed" ? cp.output : null;
4094
- return c2 && c2.ok === false ? c2 : null;
4095
- }, "failure");
4096
- const first = /* @__PURE__ */ __name((cancelled) => inputs.findIndex((_, i2) => {
4097
- const c2 = failure(i2);
4098
- return c2 !== null && c2.code === "cancelled" === cancelled;
4099
- }), "first");
4100
- const i = first(false) !== -1 ? first(false) : first(true);
4101
- const c = i === -1 ? null : failure(i);
4102
- if (c) {
4103
- for (let j = 0; j < inputs.length; j += 1) {
4104
- if (j !== i && existing[j]?.status !== "completed") host.cancelChild?.(id(j));
4105
- }
4106
- throw new GatherError([
4107
- {
4108
- index: i,
4109
- id: id(i),
4110
- error: c.error ?? "unknown"
4218
+ for (; ; ) {
4219
+ const { checkpoints: existing, folded } = await readFan(positions, [
4220
+ allWait(runId, firstSeq).checkpointPrefix,
4221
+ allFoldedName(runId, firstSeq)
4222
+ ]);
4223
+ const waveCount = waves ? Math.ceil(inputs.length / waves.waveSize) : 1;
4224
+ const waveItems = /* @__PURE__ */ __name((k) => {
4225
+ if (!waves) return inputs.map((_, i) => i);
4226
+ const from = k * waves.waveSize;
4227
+ return inputs.map((_, i) => i).slice(from, from + waves.waveSize);
4228
+ }, "waveItems");
4229
+ if (mode === "failFast") {
4230
+ const failure = /* @__PURE__ */ __name((i2) => {
4231
+ const cp = existing[i2];
4232
+ const c2 = cp?.status === "completed" ? cp.output : null;
4233
+ return c2 && c2.ok === false ? c2 : null;
4234
+ }, "failure");
4235
+ const first = /* @__PURE__ */ __name((cancelled) => inputs.findIndex((_, i2) => {
4236
+ const c2 = failure(i2);
4237
+ return c2 !== null && c2.code === "cancelled" === cancelled;
4238
+ }), "first");
4239
+ const i = first(false) !== -1 ? first(false) : first(true);
4240
+ const c = i === -1 ? null : failure(i);
4241
+ if (c) {
4242
+ for (let j = 0; j < inputs.length; j += 1) {
4243
+ if (j !== i && existing[j]?.status !== "completed") host.cancelChild?.(id(j));
4111
4244
  }
4112
- ], inputs.length);
4113
- }
4114
- }
4115
- if (store.compactCheckpoints && inputs.length >= FOLD_MIN_FAN) {
4116
- for (let k = 0; k < waveCount; k += 1) {
4117
- const items = waveItems(k);
4118
- const first = items[0];
4119
- const last = items[items.length - 1];
4120
- if (folded[first]) continue;
4121
- const values = [];
4122
- for (const i of items) {
4123
- const c = existing[i]?.status === "completed" ? existing[i]?.output : void 0;
4124
- if (!c || typeof c !== "object" || c.ok !== true) break;
4125
- values.push(c.value);
4126
- }
4127
- if (values.length < items.length) continue;
4128
- const into = instantCheckpoint({
4129
- runId,
4130
- seq: positions[first],
4131
- name: allFoldedName(runId, firstSeq, waves ? k : void 0),
4132
- kind: "signal",
4133
- output: {
4134
- from: first,
4135
- values
4136
- },
4137
- parallelGroup: group
4138
- });
4139
- await store.compactCheckpoints(runId, positions[first], positions[last], into);
4140
- for (const i of items) replay?.delete(positions[i]);
4141
- replay?.set(into.seq, into);
4142
- }
4143
- }
4144
- const sent = /* @__PURE__ */ __name((k) => waveItems(k).some((i) => existing[i]), "sent");
4145
- const settled = /* @__PURE__ */ __name((k) => waveItems(k).every((i) => existing[i]?.status === "completed"), "settled");
4146
- const active = [];
4147
- let unsettled = 0;
4148
- for (let k = 0; k < waveCount; k += 1) {
4149
- if (settled(k)) continue;
4150
- if (sent(k) || unsettled < (waves?.maxWaves ?? 1)) {
4151
- active.push(k);
4152
- unsettled += 1;
4153
- }
4154
- }
4155
- if (active.length > 0) {
4156
- const toRegister = [];
4157
- const toEnsure = [];
4158
- for (const k of active) {
4159
- for (const i of waveItems(k)) {
4160
- const cp = existing[i];
4161
- if (cp?.status === "completed") continue;
4162
- (cp ? toEnsure : toRegister).push(i);
4245
+ throw new GatherError([
4246
+ {
4247
+ index: i,
4248
+ id: id(i),
4249
+ error: c.error ?? "unknown"
4250
+ }
4251
+ ], inputs.length);
4163
4252
  }
4164
4253
  }
4165
- for (let at = 0; at < toRegister.length; at += FAN_CHUNK) {
4166
- const chunk = toRegister.slice(at, at + FAN_CHUNK);
4167
- const entries = chunk.map((i) => ({
4168
- // Carry the fan `group` onto the waiter too: the child's terminal `signal:child:`
4169
- // checkpoint is (re)written by engine.signal when the child notifies the parent,
4170
- // OVERWRITING the running placeholder at the same seq — so without this the resolved
4171
- // checkpoint would lose the group and the dashboard would render the resolved fan as a
4172
- // sequential chain.
4173
- waiter: {
4174
- token: `child:${id(i)}`,
4175
- runId,
4176
- seq: positions[i],
4177
- parallelGroup: group
4178
- },
4179
- checkpoint: instantCheckpoint({
4254
+ if (store.compactCheckpoints && inputs.length >= FOLD_MIN_FAN) {
4255
+ for (let k = 0; k < waveCount; k += 1) {
4256
+ const items = waveItems(k);
4257
+ const first = items[0];
4258
+ const last = items[items.length - 1];
4259
+ if (folded[first]) continue;
4260
+ const values = [];
4261
+ for (const i of items) {
4262
+ const c = existing[i]?.status === "completed" ? existing[i]?.output : void 0;
4263
+ if (!c || typeof c !== "object" || c.ok !== true) break;
4264
+ values.push(c.value);
4265
+ }
4266
+ if (values.length < items.length) continue;
4267
+ const into = instantCheckpoint({
4180
4268
  runId,
4181
- seq: positions[i],
4182
- name: `signal:child:${id(i)}`,
4269
+ seq: positions[first],
4270
+ name: allFoldedName(runId, firstSeq, waves ? k : void 0),
4183
4271
  kind: "signal",
4184
- status: "running",
4272
+ output: {
4273
+ from: first,
4274
+ values
4275
+ },
4185
4276
  parallelGroup: group
4186
- })
4187
- }));
4188
- await registerChildWaits(entries);
4189
- for (const i of chunk) host.startChild(name, inputs[i], id(i));
4190
- }
4191
- if (toEnsure.length > 0) {
4192
- const { runs: started, unread } = await readRunsByIds(store, toEnsure.map((i) => id(i)));
4193
- const skip = new Set(unread);
4194
- for (const i of toEnsure) {
4195
- if (!started.has(id(i)) && !skip.has(id(i))) host.startChild(name, inputs[i], id(i));
4277
+ });
4278
+ await store.compactCheckpoints(runId, positions[first], positions[last], into);
4279
+ for (const i of items) replay?.delete(positions[i]);
4280
+ replay?.set(into.seq, into);
4196
4281
  }
4197
4282
  }
4198
- }
4199
- if (active.length > 0) {
4200
- throw new WorkflowSuspended(void 0, fanJoin(active.map((k) => allWait(runId, firstSeq, waves ? k : void 0))));
4201
- }
4202
- const outputs = [];
4203
- const results = [];
4204
- const failures = [];
4205
- for (let i = 0; i < inputs.length; i += 1) {
4206
- const c = existing[i]?.output;
4207
- if (c && typeof c === "object" && "ok" in c && c.ok === false) {
4208
- const { message, ...rest } = completionError({
4209
- ...c,
4210
- error: c.error ?? "unknown"
4211
- });
4212
- const failure = {
4283
+ const sent = /* @__PURE__ */ __name((k) => waveItems(k).some((i) => existing[i]), "sent");
4284
+ const settled = /* @__PURE__ */ __name((k) => waveItems(k).every((i) => existing[i]?.status === "completed"), "settled");
4285
+ const active = [];
4286
+ let unsettled = 0;
4287
+ for (let k = 0; k < waveCount; k += 1) {
4288
+ if (settled(k)) continue;
4289
+ if (sent(k) || unsettled < (waves?.maxWaves ?? 1)) {
4290
+ active.push(k);
4291
+ unsettled += 1;
4292
+ }
4293
+ }
4294
+ let known;
4295
+ if (active.length > 0) {
4296
+ const toRegister = [];
4297
+ const toEnsure = [];
4298
+ for (const k of active) {
4299
+ for (const i of waveItems(k)) {
4300
+ const cp = existing[i];
4301
+ if (cp?.status === "completed") continue;
4302
+ (cp ? toEnsure : toRegister).push(i);
4303
+ }
4304
+ }
4305
+ for (let at = 0; at < toRegister.length; at += FAN_CHUNK) {
4306
+ const chunk = toRegister.slice(at, at + FAN_CHUNK);
4307
+ const entries = chunk.map((i) => ({
4308
+ // Carry the fan `group` onto the waiter too: the child's terminal `signal:child:`
4309
+ // checkpoint is (re)written by engine.signal when the child notifies the parent,
4310
+ // OVERWRITING the running placeholder at the same seq — so without this the resolved
4311
+ // checkpoint would lose the group and the dashboard would render the resolved fan as a
4312
+ // sequential chain.
4313
+ waiter: {
4314
+ token: `child:${id(i)}`,
4315
+ runId,
4316
+ seq: positions[i],
4317
+ parallelGroup: group
4318
+ },
4319
+ checkpoint: instantCheckpoint({
4320
+ runId,
4321
+ seq: positions[i],
4322
+ name: `signal:child:${id(i)}`,
4323
+ kind: "signal",
4324
+ status: "running",
4325
+ parallelGroup: group
4326
+ })
4327
+ }));
4328
+ await registerChildWaits(entries);
4329
+ for (const i of chunk) host.startChild(name, inputs[i], id(i));
4330
+ }
4331
+ if (toEnsure.length > 0) {
4332
+ const { runs: started, unread } = await readRunsByIds(store, toEnsure.map((i) => id(i)));
4333
+ const skip = new Set(unread);
4334
+ for (const i of toEnsure) {
4335
+ if (!started.has(id(i)) && !skip.has(id(i))) host.startChild(name, inputs[i], id(i));
4336
+ }
4337
+ known = started;
4338
+ }
4339
+ const now2 = await settleFinishedChildren(toEnsure.map((i) => ({
4213
4340
  id: id(i),
4214
- error: message,
4215
- ...rest
4216
- };
4217
- failures.push({
4218
- index: i,
4219
- ...failure
4220
- });
4221
- results.push({
4222
- status: "rejected",
4223
- ...failure
4224
- });
4225
- outputs.push(void 0);
4226
- } else {
4227
- const value = c?.value;
4228
- results.push({
4229
- status: "fulfilled",
4230
- value
4231
- });
4232
- outputs.push(value);
4341
+ seq: positions[i],
4342
+ parallelGroup: group
4343
+ })), known);
4344
+ if (now2.size > 0) continue;
4345
+ }
4346
+ if (active.length > 0) {
4347
+ throw new WorkflowSuspended(void 0, fanJoin(active.map((k) => allWait(runId, firstSeq, waves ? k : void 0))));
4348
+ }
4349
+ const outputs = [];
4350
+ const results = [];
4351
+ const failures = [];
4352
+ for (let i = 0; i < inputs.length; i += 1) {
4353
+ const c = existing[i]?.output;
4354
+ if (c && typeof c === "object" && "ok" in c && c.ok === false) {
4355
+ const { message, ...rest } = completionError({
4356
+ ...c,
4357
+ error: c.error ?? "unknown"
4358
+ });
4359
+ const failure = {
4360
+ id: id(i),
4361
+ error: message,
4362
+ ...rest
4363
+ };
4364
+ failures.push({
4365
+ index: i,
4366
+ ...failure
4367
+ });
4368
+ results.push({
4369
+ status: "rejected",
4370
+ ...failure
4371
+ });
4372
+ outputs.push(void 0);
4373
+ } else {
4374
+ const value = c?.value;
4375
+ results.push({
4376
+ status: "fulfilled",
4377
+ value
4378
+ });
4379
+ outputs.push(value);
4380
+ }
4233
4381
  }
4382
+ if (settle) return results;
4383
+ if (failures.length > 0) throw new GatherError(failures, inputs.length);
4384
+ return outputs;
4234
4385
  }
4235
- if (settle) return results;
4236
- if (failures.length > 0) throw new GatherError(failures, inputs.length);
4237
- return outputs;
4238
4386
  }, "fan");
4239
4387
  const all = /* @__PURE__ */ __name((workflow2, inputs, opts) => fan(workflow2, inputs, opts, false), "all");
4240
4388
  const allSettled = /* @__PURE__ */ __name((workflow2, inputs, opts) => fan(workflow2, inputs, opts, true), "allSettled");
@@ -4320,6 +4468,39 @@ function createWorkflowCtx(host, runId, compensations, workflow = "", searchAttr
4320
4468
  }
4321
4469
  for (const e of entries) replay?.set(e.checkpoint.seq, e.checkpoint);
4322
4470
  }, "registerChildWaits");
4471
+ const settleFinishedChildren = /* @__PURE__ */ __name(async (awaited, known) => {
4472
+ const settled = /* @__PURE__ */ new Map();
4473
+ if (awaited.length === 0) return settled;
4474
+ const runs = new Map(known ?? []);
4475
+ const toRead = awaited.filter((a) => !runs.has(a.id)).map((a) => a.id);
4476
+ if (toRead.length > 0) {
4477
+ for (const [id, run] of (await readRunsByIds(store, toRead)).runs) runs.set(id, run);
4478
+ }
4479
+ for (const a of awaited) {
4480
+ const child2 = runs.get(a.id);
4481
+ const completion = child2 ? childCompletion(child2) : void 0;
4482
+ if (!completion) continue;
4483
+ const token = `child:${a.id}`;
4484
+ const taken = await store.takeSignalWaiter(token);
4485
+ if (!taken) continue;
4486
+ if (taken.runId !== runId || taken.seq !== a.seq) {
4487
+ await store.putSignalWaiter(taken);
4488
+ continue;
4489
+ }
4490
+ await writeCheckpoint(instantCheckpoint({
4491
+ runId,
4492
+ seq: a.seq,
4493
+ name: `signal:child:${a.id}`,
4494
+ kind: "signal",
4495
+ output: completion,
4496
+ ...a.parallelGroup !== void 0 ? {
4497
+ parallelGroup: a.parallelGroup
4498
+ } : {}
4499
+ }));
4500
+ settled.set(a.id, completion);
4501
+ }
4502
+ return settled;
4503
+ }, "settleFinishedChildren");
4323
4504
  const startChild = /* @__PURE__ */ __name(async (workflow2, input, options) => {
4324
4505
  const { childId, priority, version } = normalizeChildOptions(options);
4325
4506
  const current = pos.next();
@@ -4677,33 +4858,10 @@ var pendingStateKey = /* @__PURE__ */ __name((at) => String(new Date(at).getTime
4677
4858
  var freshPendingKey = /* @__PURE__ */ __name(() => `${Date.now()}-${globalThis.crypto.randomUUID().slice(0, 8)}`, "freshPendingKey");
4678
4859
  var wakeRequestToken = /* @__PURE__ */ __name((runId) => `durable:wake-after-turn:${runId}`, "wakeRequestToken");
4679
4860
  var CHILD_SIGNAL_PREFIX = "signal:child:";
4861
+ var BUFFERED_SWEEP_PAGE = 500;
4862
+ var BUFFERED_SWEEP_PAGES_PER_CALL = 4;
4863
+ var BUFFERED_SWEEP_REST_MS = 10 * 6e4;
4680
4864
  var DELIVER_CHUNK = 50;
4681
- function childCompletion(child) {
4682
- if (child.status === "completed") return {
4683
- ok: true,
4684
- value: child.output
4685
- };
4686
- if (child.status === "failed") return failedCompletion(child.error ?? {
4687
- message: "failed"
4688
- });
4689
- if (child.status === "dead") return failedCompletion(child.error ?? {
4690
- message: "dead"
4691
- });
4692
- if (child.status === "cancelled") return cancelledCompletion(child.error);
4693
- return void 0;
4694
- }
4695
- __name(childCompletion, "childCompletion");
4696
- var CHILD_CANCELLED = "cancelled";
4697
- function cancelledCompletion(error2) {
4698
- const base = error2 ?? {
4699
- message: "cancelled"
4700
- };
4701
- return failedCompletion({
4702
- ...base,
4703
- code: base.code ?? CHILD_CANCELLED
4704
- });
4705
- }
4706
- __name(cancelledCompletion, "cancelledCompletion");
4707
4865
  var RESULT_BATCH_MAX = 256;
4708
4866
  function shuffle(items) {
4709
4867
  for (let i = items.length - 1; i > 0; i -= 1) {
@@ -4919,6 +5077,10 @@ var WorkflowEngine = class {
4919
5077
  /** Runs already paged about, by id → the `updatedAt` of the paged episode (see {@link sweepStalled}). */
4920
5078
  notifiedStalled = /* @__PURE__ */ new Map();
4921
5079
  lastStalledSweepAt = Number.NEGATIVE_INFINITY;
5080
+ /** {@link sweepBufferedSignals}: the id the pass in progress resumes after, `undefined` between passes. */
5081
+ bufferedSweepCursor;
5082
+ bufferedSweepActive = false;
5083
+ lastBufferedSweepEndedAt = Number.NEGATIVE_INFINITY;
4922
5084
  stalledAfterMs;
4923
5085
  draining = false;
4924
5086
  /** Wait already spent on this shutdown before `drain()` (see `beginDrain`). */
@@ -5524,6 +5686,50 @@ var WorkflowEngine = class {
5524
5686
  return () => this.deadListeners.delete(listener);
5525
5687
  }
5526
5688
  /**
5689
+ * Delete buffered child completions no parent will ever consume (see
5690
+ * `childCompletionNeedsBuffer`): those of root runs, of children whose parent already settled them
5691
+ * or is gone, or terminal for good. Such rows were written by earlier versions, which buffered every
5692
+ * completion that found no waiter, and nothing else ever removes them.
5693
+ *
5694
+ * Walks `durable_buffered_signals` by primary key, {@link BUFFERED_SWEEP_PAGE} rows at a time and
5695
+ * at most {@link BUFFERED_SWEEP_PAGES_PER_CALL} pages per call, so a large backlog drains over a
5696
+ * few timer ticks without a long query or a big delete. A pass that reaches the end of the table
5697
+ * rests {@link BUFFERED_SWEEP_REST_MS} before the next one. Call it every tick (the NestJS timer
5698
+ * poller does). Skipped while draining, on a namespaced engine (a buffered signal carries no
5699
+ * namespace, and a scoped read would report another tenant's parent as gone), and on a store
5700
+ * without `listBufferedSignals`/`deleteBufferedSignals`. Returns how many rows it deleted.
5701
+ */
5702
+ async sweepBufferedSignals(now = this.clock()) {
5703
+ const { listBufferedSignals, deleteBufferedSignals } = this.store;
5704
+ if (!listBufferedSignals || !deleteBufferedSignals) return 0;
5705
+ if (this.draining || this.namespace !== void 0 || this.bufferedSweepActive) return 0;
5706
+ if (this.bufferedSweepCursor === void 0 && now - this.lastBufferedSweepEndedAt < BUFFERED_SWEEP_REST_MS) {
5707
+ return 0;
5708
+ }
5709
+ this.bufferedSweepActive = true;
5710
+ let deleted = 0;
5711
+ try {
5712
+ for (let page = 0; page < BUFFERED_SWEEP_PAGES_PER_CALL; page++) {
5713
+ const rows = await listBufferedSignals.call(this.store, CHILD_TOKEN_PREFIX, this.bufferedSweepCursor, BUFFERED_SWEEP_PAGE);
5714
+ const doomed = await unneededChildSignals(this.store, rows);
5715
+ if (doomed.length > 0) {
5716
+ await deleteBufferedSignals.call(this.store, doomed.map((row) => row.id));
5717
+ deleted += doomed.length;
5718
+ }
5719
+ const last = rows.at(-1);
5720
+ if (rows.length < BUFFERED_SWEEP_PAGE || !last) {
5721
+ this.bufferedSweepCursor = void 0;
5722
+ this.lastBufferedSweepEndedAt = now;
5723
+ break;
5724
+ }
5725
+ this.bufferedSweepCursor = last.id;
5726
+ }
5727
+ } finally {
5728
+ this.bufferedSweepActive = false;
5729
+ }
5730
+ return deleted;
5731
+ }
5732
+ /**
5527
5733
  * Register a pager hook for STRANDED runs: an in-flight run untouched past `stalledAfterMs` that is
5528
5734
  * either suspended with NO wake timer (wake-forever: nothing will ever re-drive it) or parked on an
5529
5735
  * old pending remote step with no live lease (dispatched and, most likely, lost). The timer poller
@@ -7230,7 +7436,9 @@ var WorkflowEngine = class {
7230
7436
  await this.deliverSignal(waiter, completion);
7231
7437
  return;
7232
7438
  }
7233
- if (cancelled || await this.parentHoldsCompletion(token, completion)) return;
7439
+ if (cancelled) return;
7440
+ const childId = token.slice(CHILD_TOKEN_PREFIX.length);
7441
+ if (!await childCompletionNeedsBuffer(this.store, childId, completion)) return;
7234
7442
  await this.signal(token, completion);
7235
7443
  return;
7236
7444
  } catch (err) {
@@ -7244,24 +7452,6 @@ var WorkflowEngine = class {
7244
7452
  }
7245
7453
  }
7246
7454
  /**
7247
- * Whether the parent of the child behind `token` (`child:<id>`) already holds `completion` as that
7248
- * child's settled `signal:child:<id>` checkpoint: its own turn settled the child from the child's
7249
- * row, so this notification is a duplicate. A different completion (a fix-and-replay run's success
7250
- * for a child the parent saw fail) is not, and is buffered for the parent's retry. Only for a child
7251
- * whose id the engine minted (its parent is derivable from it).
7252
- */
7253
- async parentHoldsCompletion(token, completion) {
7254
- const childId = token.slice("child:".length);
7255
- const parentId = parentRunIdOf(childId);
7256
- if (!parentId) return false;
7257
- const name = `${CHILD_SIGNAL_PREFIX}${childId}`;
7258
- const checkpoints = this.store.listCheckpointsByNamePrefix ? await this.store.listCheckpointsByNamePrefix(parentId, [
7259
- name
7260
- ]) : await this.store.listCheckpoints(parentId);
7261
- const expected = JSON.stringify(completion);
7262
- return checkpoints.some((cp) => cp.name === name && cp.status === "completed" && JSON.stringify(cp.output) === expected);
7263
- }
7264
- /**
7265
7455
  * Deferred child start shared by the in-process ctx host and the remote `startChild` command.
7266
7456
  * Deferred (microtask) so a fast child can't reentrantly resume a still-suspending parent. A start
7267
7457
  * that THROWS (unregistered/unroutable workflow, input validation, singleton back-pressure, store
@@ -7276,15 +7466,22 @@ var WorkflowEngine = class {
7276
7466
  startChildDeferred(workflow, input, childId, opts) {
7277
7467
  this.localWork.submit(`start:${childId}`, async () => {
7278
7468
  if (this.draining) return;
7279
- await this.start(workflow, input, childId, opts).catch((err) => {
7469
+ const started = await this.start(workflow, input, childId, opts).catch((err) => {
7280
7470
  const message = err instanceof Error ? err.message : String(err);
7281
7471
  this.notifyParent(childId, {
7282
7472
  ok: false,
7283
7473
  error: `child workflow "${workflow}" failed to start: ${message}`
7284
7474
  });
7475
+ return void 0;
7285
7476
  });
7477
+ if (started) this.redeliverFinishedChild(childId, started);
7286
7478
  });
7287
7479
  }
7480
+ /** {@link startChildDeferred}: re-deliver the completion of a child `start` found finished. */
7481
+ redeliverFinishedChild(childId, run) {
7482
+ const completion = childCompletion(run);
7483
+ if (completion) this.inBackground(this.deliverToParent(`child:${childId}`, completion));
7484
+ }
7288
7485
  /**
7289
7486
  * Cancel a run (e.g. from the dashboard). Returns null if the run does not exist. Pass
7290
7487
  * `{ compensate: true }` to undo the saga first: the suspended run is resumed so its completed
@@ -8889,10 +9086,13 @@ ${run.workflowVersion}`;
8889
9086
  // dashboard renders the fan as one parallel group. Undefined for a lone (non-fan) child.
8890
9087
  parallelGroup: cmd.parallelGroup
8891
9088
  });
8892
- if (!await this.store.getRun(childId)) {
9089
+ const existingChild = await this.store.getRun(childId);
9090
+ if (!existingChild) {
8893
9091
  this.startChildDeferred(cmd.workflow, cmd.input, childId, {
8894
9092
  namespace: run.namespace
8895
9093
  });
9094
+ } else {
9095
+ this.redeliverFinishedChild(childId, existingChild);
8896
9096
  }
8897
9097
  } else {
8898
9098
  throw new Error(`remote workflow command '${cmd.kind}' is not supported yet`);
@@ -10903,6 +11103,10 @@ var CodecStateStore = class {
10903
11103
  compactCheckpoints;
10904
11104
  /** Decodes each run like {@link getRun}. Bound only when the inner store has it. */
10905
11105
  getRuns;
11106
+ /** Decodes each payload like {@link takeBufferedSignal}. Bound only when the inner store has it. */
11107
+ listBufferedSignals;
11108
+ /** Forwarded verbatim — deletes by row id. */
11109
+ deleteBufferedSignals;
10906
11110
  /** Forwarded verbatim — a waiter carries no payload. */
10907
11111
  listSignalWaitersByRun;
10908
11112
  /** Forwarded verbatim — a waiter carries no payload. */
@@ -10948,6 +11152,13 @@ var CodecStateStore = class {
10948
11152
  if (compact) this.compactCheckpoints = (runId, fromSeq, toSeq, into) => compact.call(inner, runId, fromSeq, toSeq, this.encCp(into));
10949
11153
  const getRuns = inner.getRuns;
10950
11154
  if (getRuns) this.getRuns = async (runIds) => (await getRuns.call(inner, runIds)).map((r) => this.decRun(r));
11155
+ const listBuffered = inner.listBufferedSignals;
11156
+ if (listBuffered) this.listBufferedSignals = async (tokenPrefix, afterId, limit) => (await listBuffered.call(inner, tokenPrefix, afterId, limit)).map((row) => ({
11157
+ ...row,
11158
+ payload: this.dec(row.payload)
11159
+ }));
11160
+ const deleteBuffered = inner.deleteBufferedSignals;
11161
+ if (deleteBuffered) this.deleteBufferedSignals = (ids) => deleteBuffered.call(inner, ids);
10951
11162
  const byRun = inner.listSignalWaitersByRun;
10952
11163
  if (byRun) this.listSignalWaitersByRun = (runIds) => byRun.call(inner, runIds);
10953
11164
  const hasWaiter = inner.hasSignalWaiter;
@@ -11693,20 +11904,47 @@ var InMemoryStateStore = class {
11693
11904
  });
11694
11905
  }
11695
11906
  bufferedSignals = /* @__PURE__ */ new Map();
11907
+ bufferedSignalSeq = 0;
11696
11908
  async bufferSignal(token, payload) {
11697
11909
  const queue = this.bufferedSignals.get(token) ?? [];
11698
- queue.push(payload);
11910
+ queue.push({
11911
+ id: ++this.bufferedSignalSeq,
11912
+ payload
11913
+ });
11699
11914
  this.bufferedSignals.set(token, queue);
11700
11915
  }
11701
11916
  async takeBufferedSignal(token) {
11702
11917
  const queue = this.bufferedSignals.get(token);
11703
11918
  if (!queue || queue.length === 0) return null;
11704
- const payload = queue.shift();
11919
+ const head = queue.shift();
11705
11920
  if (queue.length === 0) this.bufferedSignals.delete(token);
11706
11921
  return {
11707
- payload
11922
+ payload: head.payload
11708
11923
  };
11709
11924
  }
11925
+ async listBufferedSignals(tokenPrefix, afterId, limit) {
11926
+ const after = afterId === void 0 ? 0 : Number(afterId);
11927
+ const rows = [];
11928
+ for (const [token, queue] of this.bufferedSignals) {
11929
+ if (!token.startsWith(tokenPrefix)) continue;
11930
+ for (const { id, payload } of queue) {
11931
+ if (id > after) rows.push({
11932
+ id: String(id),
11933
+ token,
11934
+ payload
11935
+ });
11936
+ }
11937
+ }
11938
+ return rows.sort((a, b) => Number(a.id) - Number(b.id)).slice(0, limit);
11939
+ }
11940
+ async deleteBufferedSignals(ids) {
11941
+ const doomed = new Set(ids.map(Number));
11942
+ for (const [token, queue] of this.bufferedSignals) {
11943
+ const kept = queue.filter((row) => !doomed.has(row.id));
11944
+ if (kept.length === 0) this.bufferedSignals.delete(token);
11945
+ else this.bufferedSignals.set(token, kept);
11946
+ }
11947
+ }
11710
11948
  /** `id -> row`, so {@link removeBufferedEvent} can delete an exact row regardless of iteration
11711
11949
  * order — mirrors the SQL stores' PK-by-id delete. */
11712
11950
  bufferedEvents = /* @__PURE__ */ new Map();