@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 +722 -484
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +46 -9
- package/dist/index.d.ts +46 -9
- package/dist/index.js +722 -484
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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(
|
|
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
|
-
|
|
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
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
const
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
const
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
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
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
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
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
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[
|
|
4182
|
-
name:
|
|
4269
|
+
seq: positions[first],
|
|
4270
|
+
name: allFoldedName(runId, firstSeq, waves ? k : void 0),
|
|
4183
4271
|
kind: "signal",
|
|
4184
|
-
|
|
4272
|
+
output: {
|
|
4273
|
+
from: first,
|
|
4274
|
+
values
|
|
4275
|
+
},
|
|
4185
4276
|
parallelGroup: group
|
|
4186
|
-
})
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
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
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
const
|
|
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
|
-
|
|
4215
|
-
|
|
4216
|
-
};
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
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
|
|
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();
|