@dudousxd/nestjs-durable-core 0.85.4 → 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
@@ -24,6 +24,7 @@ var index_exports = {};
24
24
  __export(index_exports, {
25
25
  CHILD_EDGE_PREFIXES: () => CHILD_EDGE_PREFIXES,
26
26
  CONTROL_FLOW_SIGNAL: () => CONTROL_FLOW_SIGNAL,
27
+ CORE_ORIGIN: () => CORE_ORIGIN,
27
28
  CURRENT_PROTOCOL_VERSION: () => CURRENT_PROTOCOL_VERSION,
28
29
  CodecStateStore: () => CodecStateStore,
29
30
  ConcurrencyLimitError: () => ConcurrencyLimitError,
@@ -859,6 +860,386 @@ var BoundedRunner = class {
859
860
  };
860
861
  var LOCAL_RUN_CONCURRENCY = 256;
861
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
+
862
1243
  // src/execution-hooks.ts
863
1244
  var wrappers = [];
864
1245
  function useDurableExecution(wrapper) {
@@ -1188,6 +1569,32 @@ function completionError(c) {
1188
1569
  };
1189
1570
  }
1190
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");
1191
1598
 
1192
1599
  // src/concurrency.ts
1193
1600
  var DEFAULT_CONCURRENCY_STATUSES = [
@@ -1241,6 +1648,9 @@ async function assertConcurrency(store, workflow, quota) {
1241
1648
  }
1242
1649
  __name(assertConcurrency, "assertConcurrency");
1243
1650
 
1651
+ // src/origin.ts
1652
+ var CORE_ORIGIN = "@dudousxd/nestjs-durable-core";
1653
+
1244
1654
  // src/entities.ts
1245
1655
  var NUDGE = {
1246
1656
  nudge: true
@@ -1257,7 +1667,9 @@ var Entities = class {
1257
1667
  constructor(host, workflowHost) {
1258
1668
  this.host = host;
1259
1669
  this.workflowHost = workflowHost;
1260
- host.register(ENTITY_WORKFLOW, "1", (ctx, input) => this.run(ctx, input));
1670
+ host.register(ENTITY_WORKFLOW, "1", (ctx, input) => this.run(ctx, input), {
1671
+ origin: CORE_ORIGIN
1672
+ });
1261
1673
  }
1262
1674
  register(name, config) {
1263
1675
  this.configs.set(name, config);
@@ -1376,8 +1788,12 @@ var EventAccumulators = class {
1376
1788
  host;
1377
1789
  constructor(host) {
1378
1790
  this.host = host;
1379
- host.register("__evt_debounce", "1", (ctx, input) => this.debounce(ctx, input));
1380
- host.register("__evt_batch", "1", (ctx, input) => this.batch(ctx, input));
1791
+ host.register("__evt_debounce", "1", (ctx, input) => this.debounce(ctx, input), {
1792
+ origin: CORE_ORIGIN
1793
+ });
1794
+ host.register("__evt_batch", "1", (ctx, input) => this.batch(ctx, input), {
1795
+ origin: CORE_ORIGIN
1796
+ });
1381
1797
  }
1382
1798
  /** Route an `onEvent` payload into its target's debounce/batch accumulator. */
1383
1799
  route(target, config, payload) {
@@ -1445,150 +1861,6 @@ var EventAccumulators = class {
1445
1861
  }
1446
1862
  };
1447
1863
 
1448
- // src/events.ts
1449
- var enc = /* @__PURE__ */ __name((s) => Buffer.from(s, "utf8").toString("base64"), "enc");
1450
- var dec = /* @__PURE__ */ __name((s) => Buffer.from(s, "base64").toString("utf8"), "dec");
1451
- var EVENT_BUFFER_SCAN_LIMIT = 50;
1452
- var EVENT_TOKEN_PREFIX = "event:";
1453
- function eventPrefix(name) {
1454
- return `${EVENT_TOKEN_PREFIX}${enc(name)}:`;
1455
- }
1456
- __name(eventPrefix, "eventPrefix");
1457
- function isEventToken(token) {
1458
- return token.startsWith(EVENT_TOKEN_PREFIX);
1459
- }
1460
- __name(isEventToken, "isEventToken");
1461
- function eventToken(name, match, runId, seq) {
1462
- return `${eventPrefix(name)}${enc(JSON.stringify(match ?? {}))}:${runId}#${seq}`;
1463
- }
1464
- __name(eventToken, "eventToken");
1465
- function eventNameOf(token) {
1466
- try {
1467
- return dec(token.split(":")[1] ?? "");
1468
- } catch {
1469
- return token;
1470
- }
1471
- }
1472
- __name(eventNameOf, "eventNameOf");
1473
- function eventMatchOf(token) {
1474
- try {
1475
- return JSON.parse(dec(token.split(":")[2] ?? ""));
1476
- } catch {
1477
- return {};
1478
- }
1479
- }
1480
- __name(eventMatchOf, "eventMatchOf");
1481
- function eventMatches(payload, match) {
1482
- const keys = Object.keys(match);
1483
- if (keys.length === 0) return true;
1484
- if (!payload || typeof payload !== "object") return false;
1485
- const p = payload;
1486
- return keys.every((k) => JSON.stringify(p[k]) === JSON.stringify(match[k]));
1487
- }
1488
- __name(eventMatches, "eventMatches");
1489
-
1490
- // src/fan-join.ts
1491
- function allChildId(runId, firstSeq, index, wave) {
1492
- return `${allBase(runId, firstSeq, wave)}${index}`;
1493
- }
1494
- __name(allChildId, "allChildId");
1495
- function allGroup(firstSeq, size, mode) {
1496
- const group = `all:${firstSeq}:n${size}`;
1497
- return mode === "failFast" ? `${group}:failFast` : group;
1498
- }
1499
- __name(allGroup, "allGroup");
1500
- function stepFanGroup(firstSeq, size, mode) {
1501
- const group = `steps:${firstSeq}:${size}`;
1502
- return mode === "failFast" ? `${group}:failFast` : group;
1503
- }
1504
- __name(stepFanGroup, "stepFanGroup");
1505
- function parseStepFanGroup(group) {
1506
- const fan = parseFanGroup(group);
1507
- if (fan?.kind !== "steps" || fan.size === void 0) return void 0;
1508
- return {
1509
- firstSeq: fan.firstSeq,
1510
- size: fan.size
1511
- };
1512
- }
1513
- __name(parseStepFanGroup, "parseStepFanGroup");
1514
- function parseFanGroup(group) {
1515
- const m = group ? /^(all|steps):(\d+)(?::n?(\d+))?(:failFast)?$/.exec(group) : null;
1516
- if (!m) return void 0;
1517
- if (m[1] === "steps" && m[3] === void 0) return void 0;
1518
- return {
1519
- kind: m[1],
1520
- firstSeq: Number(m[2]),
1521
- size: m[3] === void 0 ? void 0 : Number(m[3]),
1522
- failFast: m[4] !== void 0
1523
- };
1524
- }
1525
- __name(parseFanGroup, "parseFanGroup");
1526
- function allWait(runId, firstSeq, wave) {
1527
- const base = allBase(runId, firstSeq, wave);
1528
- return {
1529
- waiterPrefix: `child:${base}`,
1530
- checkpointPrefix: `signal:child:${base}`
1531
- };
1532
- }
1533
- __name(allWait, "allWait");
1534
- function fanJoin(waits) {
1535
- return {
1536
- waits
1537
- };
1538
- }
1539
- __name(fanJoin, "fanJoin");
1540
- var FOLDED_PREFIX = "fan:";
1541
- function allFoldedName(runId, firstSeq, wave) {
1542
- return `${FOLDED_PREFIX}${allBase(runId, firstSeq, wave)}`;
1543
- }
1544
- __name(allFoldedName, "allFoldedName");
1545
- function foldedChildIds(checkpoint) {
1546
- const base = checkpoint.name.slice(FOLDED_PREFIX.length);
1547
- const { from, values } = checkpoint.output;
1548
- return values.map((_, j) => `${base}${from + j}`);
1549
- }
1550
- __name(foldedChildIds, "foldedChildIds");
1551
- function allBase(runId, firstSeq, wave) {
1552
- return wave === void 0 ? `${runId}.all.${firstSeq}.` : `${runId}.all.${firstSeq}.w${wave}.`;
1553
- }
1554
- __name(allBase, "allBase");
1555
- function allWaves(size, concurrency) {
1556
- if (concurrency === void 0 || concurrency >= size) return void 0;
1557
- if (!Number.isInteger(concurrency) || concurrency < 1) {
1558
- throw new Error(`ctx.all: concurrency must be a positive integer, got ${concurrency}`);
1559
- }
1560
- if (concurrency === 1) return {
1561
- waveSize: 1,
1562
- maxWaves: 1
1563
- };
1564
- return {
1565
- waveSize: Math.floor(concurrency / 2),
1566
- maxWaves: 2
1567
- };
1568
- }
1569
- __name(allWaves, "allWaves");
1570
- var ENGINE_CHILD_ID = /^(.+)\.(?:all\.\d+\.(?:w\d+\.)?\d+|child\.\d+|onError\.\d+)$/;
1571
- function parentRunIdOf(runId) {
1572
- return ENGINE_CHILD_ID.exec(runId)?.[1];
1573
- }
1574
- __name(parentRunIdOf, "parentRunIdOf");
1575
- var GROUP = /^all:(\d+)(?::n\d+)?(:failFast)?$/;
1576
- var REST = /^(?:w(\d+)\.)?\d+$/;
1577
- function fanOfWaiter(waiter) {
1578
- const group = waiter.parallelGroup ? GROUP.exec(waiter.parallelGroup) : null;
1579
- if (!group) return void 0;
1580
- const base = `child:${waiter.runId}.all.${group[1]}.`;
1581
- if (!waiter.token.startsWith(base)) return void 0;
1582
- const rest = REST.exec(waiter.token.slice(base.length));
1583
- if (!rest) return void 0;
1584
- const waiterPrefix = rest[1] === void 0 ? base : `${base}w${rest[1]}.`;
1585
- return {
1586
- waiterPrefix,
1587
- failFast: group[2] !== void 0
1588
- };
1589
- }
1590
- __name(fanOfWaiter, "fanOfWaiter");
1591
-
1592
1864
  // src/handshake/descriptor.ts
1593
1865
  var CURRENT_PROTOCOL_VERSION = 1;
1594
1866
  var LEGACY_V1_PROTOCOL = {
@@ -2272,33 +2544,9 @@ function planDispatch(requires, liveDescriptors, cp, token) {
2272
2544
  controlPlane: cp,
2273
2545
  workers
2274
2546
  }
2275
- };
2276
- }
2277
- __name(planDispatch, "planDispatch");
2278
-
2279
- // src/interfaces.ts
2280
- function checkpointNameKind(name) {
2281
- if (name.startsWith("signal:child:")) return "child";
2282
- if (name.startsWith("fan:")) return "folded";
2283
- if (name.startsWith("spawn:")) return "spawn";
2284
- return "step";
2285
- }
2286
- __name(checkpointNameKind, "checkpointNameKind");
2287
- function poolNamespace(namespace) {
2288
- return namespace === "" ? "default" : namespace;
2289
- }
2290
- __name(poolNamespace, "poolNamespace");
2291
- var TERMINAL_RUN_STATUSES = [
2292
- "completed",
2293
- "failed",
2294
- "cancelled",
2295
- "dead"
2296
- ];
2297
- function isNonEmptyRunScope(scope) {
2298
- if (!scope) return false;
2299
- 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
+ };
2300
2548
  }
2301
- __name(isNonEmptyRunScope, "isNonEmptyRunScope");
2549
+ __name(planDispatch, "planDispatch");
2302
2550
 
2303
2551
  // src/on-error.ts
2304
2552
  var UNROUTABLE_CODES = /* @__PURE__ */ new Set([
@@ -2474,122 +2722,6 @@ var RunDispatchBatcher = class {
2474
2722
  }
2475
2723
  };
2476
2724
 
2477
- // src/run-waiting.ts
2478
- var WEBHOOK_PREFIX = "wh:";
2479
- var CHILD_PREFIX = "child:";
2480
- var BREAKPOINT_PREFIX = "bp:";
2481
- var EVENT_PREFIX = EVENT_TOKEN_PREFIX;
2482
- function parseWebhookToken(token) {
2483
- if (!token.startsWith(WEBHOOK_PREFIX)) return null;
2484
- const rest = token.slice(WEBHOOK_PREFIX.length);
2485
- const at = rest.lastIndexOf(":");
2486
- if (at <= 0) return null;
2487
- const seqText = rest.slice(at + 1);
2488
- if (!/^\d+$/.test(seqText)) return null;
2489
- return {
2490
- runId: rest.slice(0, at),
2491
- seq: Number(seqText)
2492
- };
2493
- }
2494
- __name(parseWebhookToken, "parseWebhookToken");
2495
- function classifyWaiterToken(token) {
2496
- if (token.startsWith(WEBHOOK_PREFIX)) return {
2497
- on: "webhook",
2498
- name: token
2499
- };
2500
- if (token.startsWith(BREAKPOINT_PREFIX)) return {
2501
- on: "breakpoint",
2502
- name: "breakpoint"
2503
- };
2504
- if (token.startsWith(CHILD_PREFIX)) return {
2505
- on: "child",
2506
- name: token.slice(CHILD_PREFIX.length)
2507
- };
2508
- if (token.startsWith(EVENT_PREFIX)) return {
2509
- on: "signal",
2510
- name: eventNameOf(token)
2511
- };
2512
- return {
2513
- on: "signal",
2514
- name: token
2515
- };
2516
- }
2517
- __name(classifyWaiterToken, "classifyWaiterToken");
2518
- function resolveRunWaiting(run, waiterByRun) {
2519
- if (run.status !== "suspended") return void 0;
2520
- const waiter = waiterByRun.get(run.id);
2521
- return waiter ? classifyWaiterToken(waiter.token) : void 0;
2522
- }
2523
- __name(resolveRunWaiting, "resolveRunWaiting");
2524
- var RUN_ID_BATCH = 500;
2525
- async function listSignalWaitersOfRuns(store, runIds) {
2526
- const unique = [
2527
- ...new Set(runIds)
2528
- ];
2529
- if (unique.length === 0) return [];
2530
- if (!store.listSignalWaitersByRun) {
2531
- const ids = new Set(unique);
2532
- return (await store.listSignalWaiters("")).filter((w) => ids.has(w.runId));
2533
- }
2534
- const out = [];
2535
- for (let at = 0; at < unique.length; at += RUN_ID_BATCH) {
2536
- out.push(...await store.listSignalWaitersByRun(unique.slice(at, at + RUN_ID_BATCH)));
2537
- }
2538
- return out;
2539
- }
2540
- __name(listSignalWaitersOfRuns, "listSignalWaitersOfRuns");
2541
- function indexWaitersByRun(waiters) {
2542
- const byRun = /* @__PURE__ */ new Map();
2543
- for (const w of waiters) if (!byRun.has(w.runId)) byRun.set(w.runId, w);
2544
- return byRun;
2545
- }
2546
- __name(indexWaitersByRun, "indexWaitersByRun");
2547
- var GET_RUN_FALLBACK_PARALLEL = 50;
2548
- async function getRunsByIds(store, runIds) {
2549
- const { runs, unread, error: error2 } = await readRunsByIds(store, runIds);
2550
- if (unread.length > 0) throw error2;
2551
- return runs;
2552
- }
2553
- __name(getRunsByIds, "getRunsByIds");
2554
- async function readRunsByIds(store, runIds) {
2555
- const unique = [
2556
- ...new Set(runIds)
2557
- ];
2558
- const runs = /* @__PURE__ */ new Map();
2559
- const unread = [];
2560
- let error2;
2561
- const batch = store.getRuns ? RUN_ID_BATCH : GET_RUN_FALLBACK_PARALLEL;
2562
- for (let at = 0; at < unique.length; at += batch) {
2563
- const ids = unique.slice(at, at + batch);
2564
- if (store.getRuns) {
2565
- try {
2566
- for (const run of await store.getRuns(ids)) runs.set(run.id, run);
2567
- } catch (err) {
2568
- error2 ??= err;
2569
- unread.push(...ids);
2570
- }
2571
- continue;
2572
- }
2573
- const reads = await Promise.allSettled(ids.map((id) => store.getRun(id)));
2574
- reads.forEach((read, k) => {
2575
- if (read.status === "fulfilled") {
2576
- if (read.value) runs.set(read.value.id, read.value);
2577
- return;
2578
- }
2579
- error2 ??= read.reason;
2580
- unread.push(ids[k]);
2581
- });
2582
- }
2583
- return {
2584
- runs,
2585
- unread,
2586
- ...unread.length > 0 ? {
2587
- error: error2
2588
- } : {}
2589
- };
2590
- }
2591
- __name(readRunsByIds, "readRunsByIds");
2592
-
2593
2725
  // src/scheduler.ts
2594
2726
  var import_node_module = require("module");
2595
2727
 
@@ -4046,7 +4178,23 @@ function createWorkflowCtx(host, runId, compensations, workflow = "", searchAttr
4046
4178
  status: "running"
4047
4179
  }));
4048
4180
  }
4049
- 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
+ }
4050
4198
  throw new WorkflowSuspended(void 0, fanJoin([
4051
4199
  {
4052
4200
  waiterPrefix: `child:${id}`,
@@ -4067,164 +4215,174 @@ function createWorkflowCtx(host, runId, compensations, workflow = "", searchAttr
4067
4215
  const group = allGroup(firstSeq, inputs.length, mode);
4068
4216
  const waveOf = /* @__PURE__ */ __name((i) => waves ? Math.floor(i / waves.waveSize) : void 0, "waveOf");
4069
4217
  const id = /* @__PURE__ */ __name((i) => allChildId(runId, firstSeq, i, waveOf(i)), "id");
4070
- const { checkpoints: existing, folded } = await readFan(positions, [
4071
- allWait(runId, firstSeq).checkpointPrefix,
4072
- allFoldedName(runId, firstSeq)
4073
- ]);
4074
- const waveCount = waves ? Math.ceil(inputs.length / waves.waveSize) : 1;
4075
- const waveItems = /* @__PURE__ */ __name((k) => {
4076
- if (!waves) return inputs.map((_, i) => i);
4077
- const from = k * waves.waveSize;
4078
- return inputs.map((_, i) => i).slice(from, from + waves.waveSize);
4079
- }, "waveItems");
4080
- if (mode === "failFast") {
4081
- const failure = /* @__PURE__ */ __name((i2) => {
4082
- const cp = existing[i2];
4083
- const c2 = cp?.status === "completed" ? cp.output : null;
4084
- return c2 && c2.ok === false ? c2 : null;
4085
- }, "failure");
4086
- const first = /* @__PURE__ */ __name((cancelled) => inputs.findIndex((_, i2) => {
4087
- const c2 = failure(i2);
4088
- return c2 !== null && c2.code === "cancelled" === cancelled;
4089
- }), "first");
4090
- const i = first(false) !== -1 ? first(false) : first(true);
4091
- const c = i === -1 ? null : failure(i);
4092
- if (c) {
4093
- for (let j = 0; j < inputs.length; j += 1) {
4094
- if (j !== i && existing[j]?.status !== "completed") host.cancelChild?.(id(j));
4095
- }
4096
- throw new GatherError([
4097
- {
4098
- index: i,
4099
- id: id(i),
4100
- 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));
4101
4244
  }
4102
- ], inputs.length);
4103
- }
4104
- }
4105
- if (store.compactCheckpoints && inputs.length >= FOLD_MIN_FAN) {
4106
- for (let k = 0; k < waveCount; k += 1) {
4107
- const items = waveItems(k);
4108
- const first = items[0];
4109
- const last = items[items.length - 1];
4110
- if (folded[first]) continue;
4111
- const values = [];
4112
- for (const i of items) {
4113
- const c = existing[i]?.status === "completed" ? existing[i]?.output : void 0;
4114
- if (!c || typeof c !== "object" || c.ok !== true) break;
4115
- values.push(c.value);
4116
- }
4117
- if (values.length < items.length) continue;
4118
- const into = instantCheckpoint({
4119
- runId,
4120
- seq: positions[first],
4121
- name: allFoldedName(runId, firstSeq, waves ? k : void 0),
4122
- kind: "signal",
4123
- output: {
4124
- from: first,
4125
- values
4126
- },
4127
- parallelGroup: group
4128
- });
4129
- await store.compactCheckpoints(runId, positions[first], positions[last], into);
4130
- for (const i of items) replay?.delete(positions[i]);
4131
- replay?.set(into.seq, into);
4132
- }
4133
- }
4134
- const sent = /* @__PURE__ */ __name((k) => waveItems(k).some((i) => existing[i]), "sent");
4135
- const settled = /* @__PURE__ */ __name((k) => waveItems(k).every((i) => existing[i]?.status === "completed"), "settled");
4136
- const active = [];
4137
- let unsettled = 0;
4138
- for (let k = 0; k < waveCount; k += 1) {
4139
- if (settled(k)) continue;
4140
- if (sent(k) || unsettled < (waves?.maxWaves ?? 1)) {
4141
- active.push(k);
4142
- unsettled += 1;
4143
- }
4144
- }
4145
- if (active.length > 0) {
4146
- const toRegister = [];
4147
- const toEnsure = [];
4148
- for (const k of active) {
4149
- for (const i of waveItems(k)) {
4150
- const cp = existing[i];
4151
- if (cp?.status === "completed") continue;
4152
- (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);
4153
4252
  }
4154
4253
  }
4155
- for (let at = 0; at < toRegister.length; at += FAN_CHUNK) {
4156
- const chunk = toRegister.slice(at, at + FAN_CHUNK);
4157
- const entries = chunk.map((i) => ({
4158
- // Carry the fan `group` onto the waiter too: the child's terminal `signal:child:`
4159
- // checkpoint is (re)written by engine.signal when the child notifies the parent,
4160
- // OVERWRITING the running placeholder at the same seq — so without this the resolved
4161
- // checkpoint would lose the group and the dashboard would render the resolved fan as a
4162
- // sequential chain.
4163
- waiter: {
4164
- token: `child:${id(i)}`,
4165
- runId,
4166
- seq: positions[i],
4167
- parallelGroup: group
4168
- },
4169
- 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({
4170
4268
  runId,
4171
- seq: positions[i],
4172
- name: `signal:child:${id(i)}`,
4269
+ seq: positions[first],
4270
+ name: allFoldedName(runId, firstSeq, waves ? k : void 0),
4173
4271
  kind: "signal",
4174
- status: "running",
4272
+ output: {
4273
+ from: first,
4274
+ values
4275
+ },
4175
4276
  parallelGroup: group
4176
- })
4177
- }));
4178
- await registerChildWaits(entries);
4179
- for (const i of chunk) host.startChild(name, inputs[i], id(i));
4180
- }
4181
- if (toEnsure.length > 0) {
4182
- const { runs: started, unread } = await readRunsByIds(store, toEnsure.map((i) => id(i)));
4183
- const skip = new Set(unread);
4184
- for (const i of toEnsure) {
4185
- 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);
4186
4281
  }
4187
4282
  }
4188
- }
4189
- if (active.length > 0) {
4190
- throw new WorkflowSuspended(void 0, fanJoin(active.map((k) => allWait(runId, firstSeq, waves ? k : void 0))));
4191
- }
4192
- const outputs = [];
4193
- const results = [];
4194
- const failures = [];
4195
- for (let i = 0; i < inputs.length; i += 1) {
4196
- const c = existing[i]?.output;
4197
- if (c && typeof c === "object" && "ok" in c && c.ok === false) {
4198
- const { message, ...rest } = completionError({
4199
- ...c,
4200
- error: c.error ?? "unknown"
4201
- });
4202
- 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) => ({
4203
4340
  id: id(i),
4204
- error: message,
4205
- ...rest
4206
- };
4207
- failures.push({
4208
- index: i,
4209
- ...failure
4210
- });
4211
- results.push({
4212
- status: "rejected",
4213
- ...failure
4214
- });
4215
- outputs.push(void 0);
4216
- } else {
4217
- const value = c?.value;
4218
- results.push({
4219
- status: "fulfilled",
4220
- value
4221
- });
4222
- 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
+ }
4223
4381
  }
4382
+ if (settle) return results;
4383
+ if (failures.length > 0) throw new GatherError(failures, inputs.length);
4384
+ return outputs;
4224
4385
  }
4225
- if (settle) return results;
4226
- if (failures.length > 0) throw new GatherError(failures, inputs.length);
4227
- return outputs;
4228
4386
  }, "fan");
4229
4387
  const all = /* @__PURE__ */ __name((workflow2, inputs, opts) => fan(workflow2, inputs, opts, false), "all");
4230
4388
  const allSettled = /* @__PURE__ */ __name((workflow2, inputs, opts) => fan(workflow2, inputs, opts, true), "allSettled");
@@ -4310,6 +4468,39 @@ function createWorkflowCtx(host, runId, compensations, workflow = "", searchAttr
4310
4468
  }
4311
4469
  for (const e of entries) replay?.set(e.checkpoint.seq, e.checkpoint);
4312
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");
4313
4504
  const startChild = /* @__PURE__ */ __name(async (workflow2, input, options) => {
4314
4505
  const { childId, priority, version } = normalizeChildOptions(options);
4315
4506
  const current = pos.next();
@@ -4667,33 +4858,10 @@ var pendingStateKey = /* @__PURE__ */ __name((at) => String(new Date(at).getTime
4667
4858
  var freshPendingKey = /* @__PURE__ */ __name(() => `${Date.now()}-${globalThis.crypto.randomUUID().slice(0, 8)}`, "freshPendingKey");
4668
4859
  var wakeRequestToken = /* @__PURE__ */ __name((runId) => `durable:wake-after-turn:${runId}`, "wakeRequestToken");
4669
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;
4670
4864
  var DELIVER_CHUNK = 50;
4671
- function childCompletion(child) {
4672
- if (child.status === "completed") return {
4673
- ok: true,
4674
- value: child.output
4675
- };
4676
- if (child.status === "failed") return failedCompletion(child.error ?? {
4677
- message: "failed"
4678
- });
4679
- if (child.status === "dead") return failedCompletion(child.error ?? {
4680
- message: "dead"
4681
- });
4682
- if (child.status === "cancelled") return cancelledCompletion(child.error);
4683
- return void 0;
4684
- }
4685
- __name(childCompletion, "childCompletion");
4686
- var CHILD_CANCELLED = "cancelled";
4687
- function cancelledCompletion(error2) {
4688
- const base = error2 ?? {
4689
- message: "cancelled"
4690
- };
4691
- return failedCompletion({
4692
- ...base,
4693
- code: base.code ?? CHILD_CANCELLED
4694
- });
4695
- }
4696
- __name(cancelledCompletion, "cancelledCompletion");
4697
4865
  var RESULT_BATCH_MAX = 256;
4698
4866
  function shuffle(items) {
4699
4867
  for (let i = items.length - 1; i > 0; i -= 1) {
@@ -4909,6 +5077,10 @@ var WorkflowEngine = class {
4909
5077
  /** Runs already paged about, by id → the `updatedAt` of the paged episode (see {@link sweepStalled}). */
4910
5078
  notifiedStalled = /* @__PURE__ */ new Map();
4911
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;
4912
5084
  stalledAfterMs;
4913
5085
  draining = false;
4914
5086
  /** Wait already spent on this shutdown before `drain()` (see `beginDrain`). */
@@ -5514,6 +5686,50 @@ var WorkflowEngine = class {
5514
5686
  return () => this.deadListeners.delete(listener);
5515
5687
  }
5516
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
+ /**
5517
5733
  * Register a pager hook for STRANDED runs: an in-flight run untouched past `stalledAfterMs` that is
5518
5734
  * either suspended with NO wake timer (wake-forever: nothing will ever re-drive it) or parked on an
5519
5735
  * old pending remote step with no live lease (dispatched and, most likely, lost). The timer poller
@@ -7220,7 +7436,9 @@ var WorkflowEngine = class {
7220
7436
  await this.deliverSignal(waiter, completion);
7221
7437
  return;
7222
7438
  }
7223
- 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;
7224
7442
  await this.signal(token, completion);
7225
7443
  return;
7226
7444
  } catch (err) {
@@ -7234,24 +7452,6 @@ var WorkflowEngine = class {
7234
7452
  }
7235
7453
  }
7236
7454
  /**
7237
- * Whether the parent of the child behind `token` (`child:<id>`) already holds `completion` as that
7238
- * child's settled `signal:child:<id>` checkpoint: its own turn settled the child from the child's
7239
- * row, so this notification is a duplicate. A different completion (a fix-and-replay run's success
7240
- * for a child the parent saw fail) is not, and is buffered for the parent's retry. Only for a child
7241
- * whose id the engine minted (its parent is derivable from it).
7242
- */
7243
- async parentHoldsCompletion(token, completion) {
7244
- const childId = token.slice("child:".length);
7245
- const parentId = parentRunIdOf(childId);
7246
- if (!parentId) return false;
7247
- const name = `${CHILD_SIGNAL_PREFIX}${childId}`;
7248
- const checkpoints = this.store.listCheckpointsByNamePrefix ? await this.store.listCheckpointsByNamePrefix(parentId, [
7249
- name
7250
- ]) : await this.store.listCheckpoints(parentId);
7251
- const expected = JSON.stringify(completion);
7252
- return checkpoints.some((cp) => cp.name === name && cp.status === "completed" && JSON.stringify(cp.output) === expected);
7253
- }
7254
- /**
7255
7455
  * Deferred child start shared by the in-process ctx host and the remote `startChild` command.
7256
7456
  * Deferred (microtask) so a fast child can't reentrantly resume a still-suspending parent. A start
7257
7457
  * that THROWS (unregistered/unroutable workflow, input validation, singleton back-pressure, store
@@ -7266,15 +7466,22 @@ var WorkflowEngine = class {
7266
7466
  startChildDeferred(workflow, input, childId, opts) {
7267
7467
  this.localWork.submit(`start:${childId}`, async () => {
7268
7468
  if (this.draining) return;
7269
- await this.start(workflow, input, childId, opts).catch((err) => {
7469
+ const started = await this.start(workflow, input, childId, opts).catch((err) => {
7270
7470
  const message = err instanceof Error ? err.message : String(err);
7271
7471
  this.notifyParent(childId, {
7272
7472
  ok: false,
7273
7473
  error: `child workflow "${workflow}" failed to start: ${message}`
7274
7474
  });
7475
+ return void 0;
7275
7476
  });
7477
+ if (started) this.redeliverFinishedChild(childId, started);
7276
7478
  });
7277
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
+ }
7278
7485
  /**
7279
7486
  * Cancel a run (e.g. from the dashboard). Returns null if the run does not exist. Pass
7280
7487
  * `{ compensate: true }` to undo the saga first: the suspended run is resumed so its completed
@@ -8879,10 +9086,13 @@ ${run.workflowVersion}`;
8879
9086
  // dashboard renders the fan as one parallel group. Undefined for a lone (non-fan) child.
8880
9087
  parallelGroup: cmd.parallelGroup
8881
9088
  });
8882
- if (!await this.store.getRun(childId)) {
9089
+ const existingChild = await this.store.getRun(childId);
9090
+ if (!existingChild) {
8883
9091
  this.startChildDeferred(cmd.workflow, cmd.input, childId, {
8884
9092
  namespace: run.namespace
8885
9093
  });
9094
+ } else {
9095
+ this.redeliverFinishedChild(childId, existingChild);
8886
9096
  }
8887
9097
  } else {
8888
9098
  throw new Error(`remote workflow command '${cmd.kind}' is not supported yet`);
@@ -10893,6 +11103,10 @@ var CodecStateStore = class {
10893
11103
  compactCheckpoints;
10894
11104
  /** Decodes each run like {@link getRun}. Bound only when the inner store has it. */
10895
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;
10896
11110
  /** Forwarded verbatim — a waiter carries no payload. */
10897
11111
  listSignalWaitersByRun;
10898
11112
  /** Forwarded verbatim — a waiter carries no payload. */
@@ -10938,6 +11152,13 @@ var CodecStateStore = class {
10938
11152
  if (compact) this.compactCheckpoints = (runId, fromSeq, toSeq, into) => compact.call(inner, runId, fromSeq, toSeq, this.encCp(into));
10939
11153
  const getRuns = inner.getRuns;
10940
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);
10941
11162
  const byRun = inner.listSignalWaitersByRun;
10942
11163
  if (byRun) this.listSignalWaitersByRun = (runIds) => byRun.call(inner, runIds);
10943
11164
  const hasWaiter = inner.hasSignalWaiter;
@@ -11683,20 +11904,47 @@ var InMemoryStateStore = class {
11683
11904
  });
11684
11905
  }
11685
11906
  bufferedSignals = /* @__PURE__ */ new Map();
11907
+ bufferedSignalSeq = 0;
11686
11908
  async bufferSignal(token, payload) {
11687
11909
  const queue = this.bufferedSignals.get(token) ?? [];
11688
- queue.push(payload);
11910
+ queue.push({
11911
+ id: ++this.bufferedSignalSeq,
11912
+ payload
11913
+ });
11689
11914
  this.bufferedSignals.set(token, queue);
11690
11915
  }
11691
11916
  async takeBufferedSignal(token) {
11692
11917
  const queue = this.bufferedSignals.get(token);
11693
11918
  if (!queue || queue.length === 0) return null;
11694
- const payload = queue.shift();
11919
+ const head = queue.shift();
11695
11920
  if (queue.length === 0) this.bufferedSignals.delete(token);
11696
11921
  return {
11697
- payload
11922
+ payload: head.payload
11698
11923
  };
11699
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
+ }
11700
11948
  /** `id -> row`, so {@link removeBufferedEvent} can delete an exact row regardless of iteration
11701
11949
  * order — mirrors the SQL stores' PK-by-id delete. */
11702
11950
  bufferedEvents = /* @__PURE__ */ new Map();
@@ -12048,6 +12296,7 @@ var InMemoryRunQueue = class {
12048
12296
  0 && (module.exports = {
12049
12297
  CHILD_EDGE_PREFIXES,
12050
12298
  CONTROL_FLOW_SIGNAL,
12299
+ CORE_ORIGIN,
12051
12300
  CURRENT_PROTOCOL_VERSION,
12052
12301
  CodecStateStore,
12053
12302
  ConcurrencyLimitError,