@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 +736 -487
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +61 -11
- package/dist/index.d.ts +61 -11
- package/dist/index.js +735 -487
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -646,6 +646,386 @@ var BoundedRunner = class {
|
|
|
646
646
|
};
|
|
647
647
|
var LOCAL_RUN_CONCURRENCY = 256;
|
|
648
648
|
|
|
649
|
+
// src/fan-join.ts
|
|
650
|
+
function allChildId(runId, firstSeq, index, wave) {
|
|
651
|
+
return `${allBase(runId, firstSeq, wave)}${index}`;
|
|
652
|
+
}
|
|
653
|
+
__name(allChildId, "allChildId");
|
|
654
|
+
function allGroup(firstSeq, size, mode) {
|
|
655
|
+
const group = `all:${firstSeq}:n${size}`;
|
|
656
|
+
return mode === "failFast" ? `${group}:failFast` : group;
|
|
657
|
+
}
|
|
658
|
+
__name(allGroup, "allGroup");
|
|
659
|
+
function stepFanGroup(firstSeq, size, mode) {
|
|
660
|
+
const group = `steps:${firstSeq}:${size}`;
|
|
661
|
+
return mode === "failFast" ? `${group}:failFast` : group;
|
|
662
|
+
}
|
|
663
|
+
__name(stepFanGroup, "stepFanGroup");
|
|
664
|
+
function parseStepFanGroup(group) {
|
|
665
|
+
const fan = parseFanGroup(group);
|
|
666
|
+
if (fan?.kind !== "steps" || fan.size === void 0) return void 0;
|
|
667
|
+
return {
|
|
668
|
+
firstSeq: fan.firstSeq,
|
|
669
|
+
size: fan.size
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
__name(parseStepFanGroup, "parseStepFanGroup");
|
|
673
|
+
function parseFanGroup(group) {
|
|
674
|
+
const m = group ? /^(all|steps):(\d+)(?::n?(\d+))?(:failFast)?$/.exec(group) : null;
|
|
675
|
+
if (!m) return void 0;
|
|
676
|
+
if (m[1] === "steps" && m[3] === void 0) return void 0;
|
|
677
|
+
return {
|
|
678
|
+
kind: m[1],
|
|
679
|
+
firstSeq: Number(m[2]),
|
|
680
|
+
size: m[3] === void 0 ? void 0 : Number(m[3]),
|
|
681
|
+
failFast: m[4] !== void 0
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
__name(parseFanGroup, "parseFanGroup");
|
|
685
|
+
function allWait(runId, firstSeq, wave) {
|
|
686
|
+
const base = allBase(runId, firstSeq, wave);
|
|
687
|
+
return {
|
|
688
|
+
waiterPrefix: `child:${base}`,
|
|
689
|
+
checkpointPrefix: `signal:child:${base}`
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
__name(allWait, "allWait");
|
|
693
|
+
function fanJoin(waits) {
|
|
694
|
+
return {
|
|
695
|
+
waits
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
__name(fanJoin, "fanJoin");
|
|
699
|
+
var FOLDED_PREFIX = "fan:";
|
|
700
|
+
function allFoldedName(runId, firstSeq, wave) {
|
|
701
|
+
return `${FOLDED_PREFIX}${allBase(runId, firstSeq, wave)}`;
|
|
702
|
+
}
|
|
703
|
+
__name(allFoldedName, "allFoldedName");
|
|
704
|
+
function foldedChildIds(checkpoint) {
|
|
705
|
+
const base = checkpoint.name.slice(FOLDED_PREFIX.length);
|
|
706
|
+
const { from, values } = checkpoint.output;
|
|
707
|
+
return values.map((_, j) => `${base}${from + j}`);
|
|
708
|
+
}
|
|
709
|
+
__name(foldedChildIds, "foldedChildIds");
|
|
710
|
+
function allBase(runId, firstSeq, wave) {
|
|
711
|
+
return wave === void 0 ? `${runId}.all.${firstSeq}.` : `${runId}.all.${firstSeq}.w${wave}.`;
|
|
712
|
+
}
|
|
713
|
+
__name(allBase, "allBase");
|
|
714
|
+
function allWaves(size, concurrency) {
|
|
715
|
+
if (concurrency === void 0 || concurrency >= size) return void 0;
|
|
716
|
+
if (!Number.isInteger(concurrency) || concurrency < 1) {
|
|
717
|
+
throw new Error(`ctx.all: concurrency must be a positive integer, got ${concurrency}`);
|
|
718
|
+
}
|
|
719
|
+
if (concurrency === 1) return {
|
|
720
|
+
waveSize: 1,
|
|
721
|
+
maxWaves: 1
|
|
722
|
+
};
|
|
723
|
+
return {
|
|
724
|
+
waveSize: Math.floor(concurrency / 2),
|
|
725
|
+
maxWaves: 2
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
__name(allWaves, "allWaves");
|
|
729
|
+
var ENGINE_CHILD_ID = /^(.+)\.(?:all\.\d+\.(?:w\d+\.)?\d+|child\.\d+|onError\.\d+)$/;
|
|
730
|
+
function parentRunIdOf(runId) {
|
|
731
|
+
return ENGINE_CHILD_ID.exec(runId)?.[1];
|
|
732
|
+
}
|
|
733
|
+
__name(parentRunIdOf, "parentRunIdOf");
|
|
734
|
+
var GROUP = /^all:(\d+)(?::n\d+)?(:failFast)?$/;
|
|
735
|
+
var REST = /^(?:w(\d+)\.)?\d+$/;
|
|
736
|
+
function fanOfWaiter(waiter) {
|
|
737
|
+
const group = waiter.parallelGroup ? GROUP.exec(waiter.parallelGroup) : null;
|
|
738
|
+
if (!group) return void 0;
|
|
739
|
+
const base = `child:${waiter.runId}.all.${group[1]}.`;
|
|
740
|
+
if (!waiter.token.startsWith(base)) return void 0;
|
|
741
|
+
const rest = REST.exec(waiter.token.slice(base.length));
|
|
742
|
+
if (!rest) return void 0;
|
|
743
|
+
const waiterPrefix = rest[1] === void 0 ? base : `${base}w${rest[1]}.`;
|
|
744
|
+
return {
|
|
745
|
+
waiterPrefix,
|
|
746
|
+
failFast: group[2] !== void 0
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
__name(fanOfWaiter, "fanOfWaiter");
|
|
750
|
+
|
|
751
|
+
// src/interfaces.ts
|
|
752
|
+
function checkpointNameKind(name) {
|
|
753
|
+
if (name.startsWith("signal:child:")) return "child";
|
|
754
|
+
if (name.startsWith("fan:")) return "folded";
|
|
755
|
+
if (name.startsWith("spawn:")) return "spawn";
|
|
756
|
+
return "step";
|
|
757
|
+
}
|
|
758
|
+
__name(checkpointNameKind, "checkpointNameKind");
|
|
759
|
+
function poolNamespace(namespace) {
|
|
760
|
+
return namespace === "" ? "default" : namespace;
|
|
761
|
+
}
|
|
762
|
+
__name(poolNamespace, "poolNamespace");
|
|
763
|
+
var TERMINAL_RUN_STATUSES = [
|
|
764
|
+
"completed",
|
|
765
|
+
"failed",
|
|
766
|
+
"cancelled",
|
|
767
|
+
"dead"
|
|
768
|
+
];
|
|
769
|
+
function isNonEmptyRunScope(scope) {
|
|
770
|
+
if (!scope) return false;
|
|
771
|
+
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;
|
|
772
|
+
}
|
|
773
|
+
__name(isNonEmptyRunScope, "isNonEmptyRunScope");
|
|
774
|
+
|
|
775
|
+
// src/events.ts
|
|
776
|
+
var enc = /* @__PURE__ */ __name((s) => Buffer.from(s, "utf8").toString("base64"), "enc");
|
|
777
|
+
var dec = /* @__PURE__ */ __name((s) => Buffer.from(s, "base64").toString("utf8"), "dec");
|
|
778
|
+
var EVENT_BUFFER_SCAN_LIMIT = 50;
|
|
779
|
+
var EVENT_TOKEN_PREFIX = "event:";
|
|
780
|
+
function eventPrefix(name) {
|
|
781
|
+
return `${EVENT_TOKEN_PREFIX}${enc(name)}:`;
|
|
782
|
+
}
|
|
783
|
+
__name(eventPrefix, "eventPrefix");
|
|
784
|
+
function isEventToken(token) {
|
|
785
|
+
return token.startsWith(EVENT_TOKEN_PREFIX);
|
|
786
|
+
}
|
|
787
|
+
__name(isEventToken, "isEventToken");
|
|
788
|
+
function eventToken(name, match, runId, seq) {
|
|
789
|
+
return `${eventPrefix(name)}${enc(JSON.stringify(match ?? {}))}:${runId}#${seq}`;
|
|
790
|
+
}
|
|
791
|
+
__name(eventToken, "eventToken");
|
|
792
|
+
function eventNameOf(token) {
|
|
793
|
+
try {
|
|
794
|
+
return dec(token.split(":")[1] ?? "");
|
|
795
|
+
} catch {
|
|
796
|
+
return token;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
__name(eventNameOf, "eventNameOf");
|
|
800
|
+
function eventMatchOf(token) {
|
|
801
|
+
try {
|
|
802
|
+
return JSON.parse(dec(token.split(":")[2] ?? ""));
|
|
803
|
+
} catch {
|
|
804
|
+
return {};
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
__name(eventMatchOf, "eventMatchOf");
|
|
808
|
+
function eventMatches(payload, match) {
|
|
809
|
+
const keys = Object.keys(match);
|
|
810
|
+
if (keys.length === 0) return true;
|
|
811
|
+
if (!payload || typeof payload !== "object") return false;
|
|
812
|
+
const p = payload;
|
|
813
|
+
return keys.every((k) => JSON.stringify(p[k]) === JSON.stringify(match[k]));
|
|
814
|
+
}
|
|
815
|
+
__name(eventMatches, "eventMatches");
|
|
816
|
+
|
|
817
|
+
// src/run-waiting.ts
|
|
818
|
+
var WEBHOOK_PREFIX = "wh:";
|
|
819
|
+
var CHILD_PREFIX = "child:";
|
|
820
|
+
var BREAKPOINT_PREFIX = "bp:";
|
|
821
|
+
var EVENT_PREFIX = EVENT_TOKEN_PREFIX;
|
|
822
|
+
function parseWebhookToken(token) {
|
|
823
|
+
if (!token.startsWith(WEBHOOK_PREFIX)) return null;
|
|
824
|
+
const rest = token.slice(WEBHOOK_PREFIX.length);
|
|
825
|
+
const at = rest.lastIndexOf(":");
|
|
826
|
+
if (at <= 0) return null;
|
|
827
|
+
const seqText = rest.slice(at + 1);
|
|
828
|
+
if (!/^\d+$/.test(seqText)) return null;
|
|
829
|
+
return {
|
|
830
|
+
runId: rest.slice(0, at),
|
|
831
|
+
seq: Number(seqText)
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
__name(parseWebhookToken, "parseWebhookToken");
|
|
835
|
+
function classifyWaiterToken(token) {
|
|
836
|
+
if (token.startsWith(WEBHOOK_PREFIX)) return {
|
|
837
|
+
on: "webhook",
|
|
838
|
+
name: token
|
|
839
|
+
};
|
|
840
|
+
if (token.startsWith(BREAKPOINT_PREFIX)) return {
|
|
841
|
+
on: "breakpoint",
|
|
842
|
+
name: "breakpoint"
|
|
843
|
+
};
|
|
844
|
+
if (token.startsWith(CHILD_PREFIX)) return {
|
|
845
|
+
on: "child",
|
|
846
|
+
name: token.slice(CHILD_PREFIX.length)
|
|
847
|
+
};
|
|
848
|
+
if (token.startsWith(EVENT_PREFIX)) return {
|
|
849
|
+
on: "signal",
|
|
850
|
+
name: eventNameOf(token)
|
|
851
|
+
};
|
|
852
|
+
return {
|
|
853
|
+
on: "signal",
|
|
854
|
+
name: token
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
__name(classifyWaiterToken, "classifyWaiterToken");
|
|
858
|
+
function resolveRunWaiting(run, waiterByRun) {
|
|
859
|
+
if (run.status !== "suspended") return void 0;
|
|
860
|
+
const waiter = waiterByRun.get(run.id);
|
|
861
|
+
return waiter ? classifyWaiterToken(waiter.token) : void 0;
|
|
862
|
+
}
|
|
863
|
+
__name(resolveRunWaiting, "resolveRunWaiting");
|
|
864
|
+
var RUN_ID_BATCH = 500;
|
|
865
|
+
async function listSignalWaitersOfRuns(store, runIds) {
|
|
866
|
+
const unique = [
|
|
867
|
+
...new Set(runIds)
|
|
868
|
+
];
|
|
869
|
+
if (unique.length === 0) return [];
|
|
870
|
+
if (!store.listSignalWaitersByRun) {
|
|
871
|
+
const ids = new Set(unique);
|
|
872
|
+
return (await store.listSignalWaiters("")).filter((w) => ids.has(w.runId));
|
|
873
|
+
}
|
|
874
|
+
const out = [];
|
|
875
|
+
for (let at = 0; at < unique.length; at += RUN_ID_BATCH) {
|
|
876
|
+
out.push(...await store.listSignalWaitersByRun(unique.slice(at, at + RUN_ID_BATCH)));
|
|
877
|
+
}
|
|
878
|
+
return out;
|
|
879
|
+
}
|
|
880
|
+
__name(listSignalWaitersOfRuns, "listSignalWaitersOfRuns");
|
|
881
|
+
function indexWaitersByRun(waiters) {
|
|
882
|
+
const byRun = /* @__PURE__ */ new Map();
|
|
883
|
+
for (const w of waiters) if (!byRun.has(w.runId)) byRun.set(w.runId, w);
|
|
884
|
+
return byRun;
|
|
885
|
+
}
|
|
886
|
+
__name(indexWaitersByRun, "indexWaitersByRun");
|
|
887
|
+
var GET_RUN_FALLBACK_PARALLEL = 50;
|
|
888
|
+
async function getRunsByIds(store, runIds) {
|
|
889
|
+
const { runs, unread, error: error2 } = await readRunsByIds(store, runIds);
|
|
890
|
+
if (unread.length > 0) throw error2;
|
|
891
|
+
return runs;
|
|
892
|
+
}
|
|
893
|
+
__name(getRunsByIds, "getRunsByIds");
|
|
894
|
+
async function readRunsByIds(store, runIds) {
|
|
895
|
+
const unique = [
|
|
896
|
+
...new Set(runIds)
|
|
897
|
+
];
|
|
898
|
+
const runs = /* @__PURE__ */ new Map();
|
|
899
|
+
const unread = [];
|
|
900
|
+
let error2;
|
|
901
|
+
const batch = store.getRuns ? RUN_ID_BATCH : GET_RUN_FALLBACK_PARALLEL;
|
|
902
|
+
for (let at = 0; at < unique.length; at += batch) {
|
|
903
|
+
const ids = unique.slice(at, at + batch);
|
|
904
|
+
if (store.getRuns) {
|
|
905
|
+
try {
|
|
906
|
+
for (const run of await store.getRuns(ids)) runs.set(run.id, run);
|
|
907
|
+
} catch (err) {
|
|
908
|
+
error2 ??= err;
|
|
909
|
+
unread.push(...ids);
|
|
910
|
+
}
|
|
911
|
+
continue;
|
|
912
|
+
}
|
|
913
|
+
const reads = await Promise.allSettled(ids.map((id) => store.getRun(id)));
|
|
914
|
+
reads.forEach((read, k) => {
|
|
915
|
+
if (read.status === "fulfilled") {
|
|
916
|
+
if (read.value) runs.set(read.value.id, read.value);
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
error2 ??= read.reason;
|
|
920
|
+
unread.push(ids[k]);
|
|
921
|
+
});
|
|
922
|
+
}
|
|
923
|
+
return {
|
|
924
|
+
runs,
|
|
925
|
+
unread,
|
|
926
|
+
...unread.length > 0 ? {
|
|
927
|
+
error: error2
|
|
928
|
+
} : {}
|
|
929
|
+
};
|
|
930
|
+
}
|
|
931
|
+
__name(readRunsByIds, "readRunsByIds");
|
|
932
|
+
|
|
933
|
+
// src/buffered-child-signals.ts
|
|
934
|
+
var CHILD_TOKEN_PREFIX = "child:";
|
|
935
|
+
var CHILD_SIGNAL_NAME_PREFIX = "signal:child:";
|
|
936
|
+
var RETRYABLE = /* @__PURE__ */ new Set([
|
|
937
|
+
"failed",
|
|
938
|
+
"dead"
|
|
939
|
+
]);
|
|
940
|
+
var FAN_CHILD = /^(.+\.all\.\d+\.(?:w\d+\.)?)(\d+)$/;
|
|
941
|
+
function canonicalJson(value) {
|
|
942
|
+
const plain = JSON.stringify(value);
|
|
943
|
+
return plain === void 0 ? "null" : sortedJson(JSON.parse(plain));
|
|
944
|
+
}
|
|
945
|
+
__name(canonicalJson, "canonicalJson");
|
|
946
|
+
function sortedJson(value) {
|
|
947
|
+
if (Array.isArray(value)) return `[${value.map(sortedJson).join(",")}]`;
|
|
948
|
+
if (value !== null && typeof value === "object") {
|
|
949
|
+
const entries = Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
950
|
+
return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${sortedJson(v)}`).join(",")}}`;
|
|
951
|
+
}
|
|
952
|
+
return JSON.stringify(value);
|
|
953
|
+
}
|
|
954
|
+
__name(sortedJson, "sortedJson");
|
|
955
|
+
function settledNames(childId) {
|
|
956
|
+
const signal = `${CHILD_SIGNAL_NAME_PREFIX}${childId}`;
|
|
957
|
+
const fan = FAN_CHILD.exec(childId);
|
|
958
|
+
if (!fan) return {
|
|
959
|
+
signal
|
|
960
|
+
};
|
|
961
|
+
return {
|
|
962
|
+
signal,
|
|
963
|
+
fold: {
|
|
964
|
+
name: `${FOLDED_PREFIX}${fan[1]}`,
|
|
965
|
+
index: Number(fan[2])
|
|
966
|
+
}
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
__name(settledNames, "settledNames");
|
|
970
|
+
function parentHoldsChild(checkpoints, childId, completion) {
|
|
971
|
+
const { signal, fold } = settledNames(childId);
|
|
972
|
+
const expected = canonicalJson(completion);
|
|
973
|
+
return checkpoints.some((cp) => {
|
|
974
|
+
if (cp.status !== "completed") return false;
|
|
975
|
+
if (cp.name === signal) return canonicalJson(cp.output) === expected;
|
|
976
|
+
if (fold && cp.name === fold.name) {
|
|
977
|
+
const { from, values } = cp.output ?? {};
|
|
978
|
+
return typeof from === "number" && Array.isArray(values) && fold.index >= from && fold.index < from + values.length;
|
|
979
|
+
}
|
|
980
|
+
return false;
|
|
981
|
+
});
|
|
982
|
+
}
|
|
983
|
+
__name(parentHoldsChild, "parentHoldsChild");
|
|
984
|
+
async function parentCheckpoints(store, parentId, childId) {
|
|
985
|
+
const { signal, fold } = settledNames(childId);
|
|
986
|
+
const prefixes = fold ? [
|
|
987
|
+
signal,
|
|
988
|
+
fold.name
|
|
989
|
+
] : [
|
|
990
|
+
signal
|
|
991
|
+
];
|
|
992
|
+
return store.listCheckpointsByNamePrefix ? store.listCheckpointsByNamePrefix(parentId, prefixes) : store.listCheckpoints(parentId);
|
|
993
|
+
}
|
|
994
|
+
__name(parentCheckpoints, "parentCheckpoints");
|
|
995
|
+
async function childCompletionNeedsBuffer(store, childId, completion, parent) {
|
|
996
|
+
const parentId = parentRunIdOf(childId);
|
|
997
|
+
if (!parentId) return false;
|
|
998
|
+
const run = parent === void 0 ? await store.getRun(parentId) : parent;
|
|
999
|
+
if (!run) return false;
|
|
1000
|
+
if (TERMINAL_RUN_STATUSES.includes(run.status) && !RETRYABLE.has(run.status)) return false;
|
|
1001
|
+
return !parentHoldsChild(await parentCheckpoints(store, parentId, childId), childId, completion);
|
|
1002
|
+
}
|
|
1003
|
+
__name(childCompletionNeedsBuffer, "childCompletionNeedsBuffer");
|
|
1004
|
+
async function unneededChildSignals(store, rows) {
|
|
1005
|
+
const parentIds = rows.flatMap((row) => {
|
|
1006
|
+
const parentId = parentRunIdOf(row.token.slice(CHILD_TOKEN_PREFIX.length));
|
|
1007
|
+
return parentId ? [
|
|
1008
|
+
parentId
|
|
1009
|
+
] : [];
|
|
1010
|
+
});
|
|
1011
|
+
const { runs, unread } = await readRunsByIds(store, parentIds);
|
|
1012
|
+
const unreadable = new Set(unread);
|
|
1013
|
+
const doomed = [];
|
|
1014
|
+
for (const row of rows) {
|
|
1015
|
+
if (!row.token.startsWith(CHILD_TOKEN_PREFIX)) continue;
|
|
1016
|
+
const childId = row.token.slice(CHILD_TOKEN_PREFIX.length);
|
|
1017
|
+
const parentId = parentRunIdOf(childId);
|
|
1018
|
+
if (parentId && unreadable.has(parentId)) continue;
|
|
1019
|
+
const parent = parentId ? runs.get(parentId) ?? null : null;
|
|
1020
|
+
try {
|
|
1021
|
+
if (!await childCompletionNeedsBuffer(store, childId, row.payload, parent)) doomed.push(row);
|
|
1022
|
+
} catch {
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
return doomed;
|
|
1026
|
+
}
|
|
1027
|
+
__name(unneededChildSignals, "unneededChildSignals");
|
|
1028
|
+
|
|
649
1029
|
// src/execution-hooks.ts
|
|
650
1030
|
var wrappers = [];
|
|
651
1031
|
function useDurableExecution(wrapper) {
|
|
@@ -975,6 +1355,32 @@ function completionError(c) {
|
|
|
975
1355
|
};
|
|
976
1356
|
}
|
|
977
1357
|
__name(completionError, "completionError");
|
|
1358
|
+
function childCompletion(child) {
|
|
1359
|
+
if (child.status === "completed") return {
|
|
1360
|
+
ok: true,
|
|
1361
|
+
value: child.output
|
|
1362
|
+
};
|
|
1363
|
+
if (child.status === "failed") return failedCompletion(child.error ?? {
|
|
1364
|
+
message: "failed"
|
|
1365
|
+
});
|
|
1366
|
+
if (child.status === "dead") return failedCompletion(child.error ?? {
|
|
1367
|
+
message: "dead"
|
|
1368
|
+
});
|
|
1369
|
+
if (child.status === "cancelled") return cancelledCompletion(child.error);
|
|
1370
|
+
return void 0;
|
|
1371
|
+
}
|
|
1372
|
+
__name(childCompletion, "childCompletion");
|
|
1373
|
+
var CHILD_CANCELLED = "cancelled";
|
|
1374
|
+
function cancelledCompletion(error2) {
|
|
1375
|
+
const base = error2 ?? {
|
|
1376
|
+
message: "cancelled"
|
|
1377
|
+
};
|
|
1378
|
+
return failedCompletion({
|
|
1379
|
+
...base,
|
|
1380
|
+
code: base.code ?? CHILD_CANCELLED
|
|
1381
|
+
});
|
|
1382
|
+
}
|
|
1383
|
+
__name(cancelledCompletion, "cancelledCompletion");
|
|
978
1384
|
|
|
979
1385
|
// src/concurrency.ts
|
|
980
1386
|
var DEFAULT_CONCURRENCY_STATUSES = [
|
|
@@ -1028,6 +1434,9 @@ async function assertConcurrency(store, workflow, quota) {
|
|
|
1028
1434
|
}
|
|
1029
1435
|
__name(assertConcurrency, "assertConcurrency");
|
|
1030
1436
|
|
|
1437
|
+
// src/origin.ts
|
|
1438
|
+
var CORE_ORIGIN = "@dudousxd/nestjs-durable-core";
|
|
1439
|
+
|
|
1031
1440
|
// src/entities.ts
|
|
1032
1441
|
var NUDGE = {
|
|
1033
1442
|
nudge: true
|
|
@@ -1044,7 +1453,9 @@ var Entities = class {
|
|
|
1044
1453
|
constructor(host, workflowHost) {
|
|
1045
1454
|
this.host = host;
|
|
1046
1455
|
this.workflowHost = workflowHost;
|
|
1047
|
-
host.register(ENTITY_WORKFLOW, "1", (ctx, input) => this.run(ctx, input)
|
|
1456
|
+
host.register(ENTITY_WORKFLOW, "1", (ctx, input) => this.run(ctx, input), {
|
|
1457
|
+
origin: CORE_ORIGIN
|
|
1458
|
+
});
|
|
1048
1459
|
}
|
|
1049
1460
|
register(name, config) {
|
|
1050
1461
|
this.configs.set(name, config);
|
|
@@ -1163,8 +1574,12 @@ var EventAccumulators = class {
|
|
|
1163
1574
|
host;
|
|
1164
1575
|
constructor(host) {
|
|
1165
1576
|
this.host = host;
|
|
1166
|
-
host.register("__evt_debounce", "1", (ctx, input) => this.debounce(ctx, input)
|
|
1167
|
-
|
|
1577
|
+
host.register("__evt_debounce", "1", (ctx, input) => this.debounce(ctx, input), {
|
|
1578
|
+
origin: CORE_ORIGIN
|
|
1579
|
+
});
|
|
1580
|
+
host.register("__evt_batch", "1", (ctx, input) => this.batch(ctx, input), {
|
|
1581
|
+
origin: CORE_ORIGIN
|
|
1582
|
+
});
|
|
1168
1583
|
}
|
|
1169
1584
|
/** Route an `onEvent` payload into its target's debounce/batch accumulator. */
|
|
1170
1585
|
route(target, config, payload) {
|
|
@@ -1232,150 +1647,6 @@ var EventAccumulators = class {
|
|
|
1232
1647
|
}
|
|
1233
1648
|
};
|
|
1234
1649
|
|
|
1235
|
-
// src/events.ts
|
|
1236
|
-
var enc = /* @__PURE__ */ __name((s) => Buffer.from(s, "utf8").toString("base64"), "enc");
|
|
1237
|
-
var dec = /* @__PURE__ */ __name((s) => Buffer.from(s, "base64").toString("utf8"), "dec");
|
|
1238
|
-
var EVENT_BUFFER_SCAN_LIMIT = 50;
|
|
1239
|
-
var EVENT_TOKEN_PREFIX = "event:";
|
|
1240
|
-
function eventPrefix(name) {
|
|
1241
|
-
return `${EVENT_TOKEN_PREFIX}${enc(name)}:`;
|
|
1242
|
-
}
|
|
1243
|
-
__name(eventPrefix, "eventPrefix");
|
|
1244
|
-
function isEventToken(token) {
|
|
1245
|
-
return token.startsWith(EVENT_TOKEN_PREFIX);
|
|
1246
|
-
}
|
|
1247
|
-
__name(isEventToken, "isEventToken");
|
|
1248
|
-
function eventToken(name, match, runId, seq) {
|
|
1249
|
-
return `${eventPrefix(name)}${enc(JSON.stringify(match ?? {}))}:${runId}#${seq}`;
|
|
1250
|
-
}
|
|
1251
|
-
__name(eventToken, "eventToken");
|
|
1252
|
-
function eventNameOf(token) {
|
|
1253
|
-
try {
|
|
1254
|
-
return dec(token.split(":")[1] ?? "");
|
|
1255
|
-
} catch {
|
|
1256
|
-
return token;
|
|
1257
|
-
}
|
|
1258
|
-
}
|
|
1259
|
-
__name(eventNameOf, "eventNameOf");
|
|
1260
|
-
function eventMatchOf(token) {
|
|
1261
|
-
try {
|
|
1262
|
-
return JSON.parse(dec(token.split(":")[2] ?? ""));
|
|
1263
|
-
} catch {
|
|
1264
|
-
return {};
|
|
1265
|
-
}
|
|
1266
|
-
}
|
|
1267
|
-
__name(eventMatchOf, "eventMatchOf");
|
|
1268
|
-
function eventMatches(payload, match) {
|
|
1269
|
-
const keys = Object.keys(match);
|
|
1270
|
-
if (keys.length === 0) return true;
|
|
1271
|
-
if (!payload || typeof payload !== "object") return false;
|
|
1272
|
-
const p = payload;
|
|
1273
|
-
return keys.every((k) => JSON.stringify(p[k]) === JSON.stringify(match[k]));
|
|
1274
|
-
}
|
|
1275
|
-
__name(eventMatches, "eventMatches");
|
|
1276
|
-
|
|
1277
|
-
// src/fan-join.ts
|
|
1278
|
-
function allChildId(runId, firstSeq, index, wave) {
|
|
1279
|
-
return `${allBase(runId, firstSeq, wave)}${index}`;
|
|
1280
|
-
}
|
|
1281
|
-
__name(allChildId, "allChildId");
|
|
1282
|
-
function allGroup(firstSeq, size, mode) {
|
|
1283
|
-
const group = `all:${firstSeq}:n${size}`;
|
|
1284
|
-
return mode === "failFast" ? `${group}:failFast` : group;
|
|
1285
|
-
}
|
|
1286
|
-
__name(allGroup, "allGroup");
|
|
1287
|
-
function stepFanGroup(firstSeq, size, mode) {
|
|
1288
|
-
const group = `steps:${firstSeq}:${size}`;
|
|
1289
|
-
return mode === "failFast" ? `${group}:failFast` : group;
|
|
1290
|
-
}
|
|
1291
|
-
__name(stepFanGroup, "stepFanGroup");
|
|
1292
|
-
function parseStepFanGroup(group) {
|
|
1293
|
-
const fan = parseFanGroup(group);
|
|
1294
|
-
if (fan?.kind !== "steps" || fan.size === void 0) return void 0;
|
|
1295
|
-
return {
|
|
1296
|
-
firstSeq: fan.firstSeq,
|
|
1297
|
-
size: fan.size
|
|
1298
|
-
};
|
|
1299
|
-
}
|
|
1300
|
-
__name(parseStepFanGroup, "parseStepFanGroup");
|
|
1301
|
-
function parseFanGroup(group) {
|
|
1302
|
-
const m = group ? /^(all|steps):(\d+)(?::n?(\d+))?(:failFast)?$/.exec(group) : null;
|
|
1303
|
-
if (!m) return void 0;
|
|
1304
|
-
if (m[1] === "steps" && m[3] === void 0) return void 0;
|
|
1305
|
-
return {
|
|
1306
|
-
kind: m[1],
|
|
1307
|
-
firstSeq: Number(m[2]),
|
|
1308
|
-
size: m[3] === void 0 ? void 0 : Number(m[3]),
|
|
1309
|
-
failFast: m[4] !== void 0
|
|
1310
|
-
};
|
|
1311
|
-
}
|
|
1312
|
-
__name(parseFanGroup, "parseFanGroup");
|
|
1313
|
-
function allWait(runId, firstSeq, wave) {
|
|
1314
|
-
const base = allBase(runId, firstSeq, wave);
|
|
1315
|
-
return {
|
|
1316
|
-
waiterPrefix: `child:${base}`,
|
|
1317
|
-
checkpointPrefix: `signal:child:${base}`
|
|
1318
|
-
};
|
|
1319
|
-
}
|
|
1320
|
-
__name(allWait, "allWait");
|
|
1321
|
-
function fanJoin(waits) {
|
|
1322
|
-
return {
|
|
1323
|
-
waits
|
|
1324
|
-
};
|
|
1325
|
-
}
|
|
1326
|
-
__name(fanJoin, "fanJoin");
|
|
1327
|
-
var FOLDED_PREFIX = "fan:";
|
|
1328
|
-
function allFoldedName(runId, firstSeq, wave) {
|
|
1329
|
-
return `${FOLDED_PREFIX}${allBase(runId, firstSeq, wave)}`;
|
|
1330
|
-
}
|
|
1331
|
-
__name(allFoldedName, "allFoldedName");
|
|
1332
|
-
function foldedChildIds(checkpoint) {
|
|
1333
|
-
const base = checkpoint.name.slice(FOLDED_PREFIX.length);
|
|
1334
|
-
const { from, values } = checkpoint.output;
|
|
1335
|
-
return values.map((_, j) => `${base}${from + j}`);
|
|
1336
|
-
}
|
|
1337
|
-
__name(foldedChildIds, "foldedChildIds");
|
|
1338
|
-
function allBase(runId, firstSeq, wave) {
|
|
1339
|
-
return wave === void 0 ? `${runId}.all.${firstSeq}.` : `${runId}.all.${firstSeq}.w${wave}.`;
|
|
1340
|
-
}
|
|
1341
|
-
__name(allBase, "allBase");
|
|
1342
|
-
function allWaves(size, concurrency) {
|
|
1343
|
-
if (concurrency === void 0 || concurrency >= size) return void 0;
|
|
1344
|
-
if (!Number.isInteger(concurrency) || concurrency < 1) {
|
|
1345
|
-
throw new Error(`ctx.all: concurrency must be a positive integer, got ${concurrency}`);
|
|
1346
|
-
}
|
|
1347
|
-
if (concurrency === 1) return {
|
|
1348
|
-
waveSize: 1,
|
|
1349
|
-
maxWaves: 1
|
|
1350
|
-
};
|
|
1351
|
-
return {
|
|
1352
|
-
waveSize: Math.floor(concurrency / 2),
|
|
1353
|
-
maxWaves: 2
|
|
1354
|
-
};
|
|
1355
|
-
}
|
|
1356
|
-
__name(allWaves, "allWaves");
|
|
1357
|
-
var ENGINE_CHILD_ID = /^(.+)\.(?:all\.\d+\.(?:w\d+\.)?\d+|child\.\d+|onError\.\d+)$/;
|
|
1358
|
-
function parentRunIdOf(runId) {
|
|
1359
|
-
return ENGINE_CHILD_ID.exec(runId)?.[1];
|
|
1360
|
-
}
|
|
1361
|
-
__name(parentRunIdOf, "parentRunIdOf");
|
|
1362
|
-
var GROUP = /^all:(\d+)(?::n\d+)?(:failFast)?$/;
|
|
1363
|
-
var REST = /^(?:w(\d+)\.)?\d+$/;
|
|
1364
|
-
function fanOfWaiter(waiter) {
|
|
1365
|
-
const group = waiter.parallelGroup ? GROUP.exec(waiter.parallelGroup) : null;
|
|
1366
|
-
if (!group) return void 0;
|
|
1367
|
-
const base = `child:${waiter.runId}.all.${group[1]}.`;
|
|
1368
|
-
if (!waiter.token.startsWith(base)) return void 0;
|
|
1369
|
-
const rest = REST.exec(waiter.token.slice(base.length));
|
|
1370
|
-
if (!rest) return void 0;
|
|
1371
|
-
const waiterPrefix = rest[1] === void 0 ? base : `${base}w${rest[1]}.`;
|
|
1372
|
-
return {
|
|
1373
|
-
waiterPrefix,
|
|
1374
|
-
failFast: group[2] !== void 0
|
|
1375
|
-
};
|
|
1376
|
-
}
|
|
1377
|
-
__name(fanOfWaiter, "fanOfWaiter");
|
|
1378
|
-
|
|
1379
1650
|
// src/handshake/descriptor.ts
|
|
1380
1651
|
var CURRENT_PROTOCOL_VERSION = 1;
|
|
1381
1652
|
var LEGACY_V1_PROTOCOL = {
|
|
@@ -2059,33 +2330,9 @@ function planDispatch(requires, liveDescriptors, cp, token) {
|
|
|
2059
2330
|
controlPlane: cp,
|
|
2060
2331
|
workers
|
|
2061
2332
|
}
|
|
2062
|
-
};
|
|
2063
|
-
}
|
|
2064
|
-
__name(planDispatch, "planDispatch");
|
|
2065
|
-
|
|
2066
|
-
// src/interfaces.ts
|
|
2067
|
-
function checkpointNameKind(name) {
|
|
2068
|
-
if (name.startsWith("signal:child:")) return "child";
|
|
2069
|
-
if (name.startsWith("fan:")) return "folded";
|
|
2070
|
-
if (name.startsWith("spawn:")) return "spawn";
|
|
2071
|
-
return "step";
|
|
2072
|
-
}
|
|
2073
|
-
__name(checkpointNameKind, "checkpointNameKind");
|
|
2074
|
-
function poolNamespace(namespace) {
|
|
2075
|
-
return namespace === "" ? "default" : namespace;
|
|
2076
|
-
}
|
|
2077
|
-
__name(poolNamespace, "poolNamespace");
|
|
2078
|
-
var TERMINAL_RUN_STATUSES = [
|
|
2079
|
-
"completed",
|
|
2080
|
-
"failed",
|
|
2081
|
-
"cancelled",
|
|
2082
|
-
"dead"
|
|
2083
|
-
];
|
|
2084
|
-
function isNonEmptyRunScope(scope) {
|
|
2085
|
-
if (!scope) return false;
|
|
2086
|
-
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;
|
|
2333
|
+
};
|
|
2087
2334
|
}
|
|
2088
|
-
__name(
|
|
2335
|
+
__name(planDispatch, "planDispatch");
|
|
2089
2336
|
|
|
2090
2337
|
// src/on-error.ts
|
|
2091
2338
|
var UNROUTABLE_CODES = /* @__PURE__ */ new Set([
|
|
@@ -2261,122 +2508,6 @@ var RunDispatchBatcher = class {
|
|
|
2261
2508
|
}
|
|
2262
2509
|
};
|
|
2263
2510
|
|
|
2264
|
-
// src/run-waiting.ts
|
|
2265
|
-
var WEBHOOK_PREFIX = "wh:";
|
|
2266
|
-
var CHILD_PREFIX = "child:";
|
|
2267
|
-
var BREAKPOINT_PREFIX = "bp:";
|
|
2268
|
-
var EVENT_PREFIX = EVENT_TOKEN_PREFIX;
|
|
2269
|
-
function parseWebhookToken(token) {
|
|
2270
|
-
if (!token.startsWith(WEBHOOK_PREFIX)) return null;
|
|
2271
|
-
const rest = token.slice(WEBHOOK_PREFIX.length);
|
|
2272
|
-
const at = rest.lastIndexOf(":");
|
|
2273
|
-
if (at <= 0) return null;
|
|
2274
|
-
const seqText = rest.slice(at + 1);
|
|
2275
|
-
if (!/^\d+$/.test(seqText)) return null;
|
|
2276
|
-
return {
|
|
2277
|
-
runId: rest.slice(0, at),
|
|
2278
|
-
seq: Number(seqText)
|
|
2279
|
-
};
|
|
2280
|
-
}
|
|
2281
|
-
__name(parseWebhookToken, "parseWebhookToken");
|
|
2282
|
-
function classifyWaiterToken(token) {
|
|
2283
|
-
if (token.startsWith(WEBHOOK_PREFIX)) return {
|
|
2284
|
-
on: "webhook",
|
|
2285
|
-
name: token
|
|
2286
|
-
};
|
|
2287
|
-
if (token.startsWith(BREAKPOINT_PREFIX)) return {
|
|
2288
|
-
on: "breakpoint",
|
|
2289
|
-
name: "breakpoint"
|
|
2290
|
-
};
|
|
2291
|
-
if (token.startsWith(CHILD_PREFIX)) return {
|
|
2292
|
-
on: "child",
|
|
2293
|
-
name: token.slice(CHILD_PREFIX.length)
|
|
2294
|
-
};
|
|
2295
|
-
if (token.startsWith(EVENT_PREFIX)) return {
|
|
2296
|
-
on: "signal",
|
|
2297
|
-
name: eventNameOf(token)
|
|
2298
|
-
};
|
|
2299
|
-
return {
|
|
2300
|
-
on: "signal",
|
|
2301
|
-
name: token
|
|
2302
|
-
};
|
|
2303
|
-
}
|
|
2304
|
-
__name(classifyWaiterToken, "classifyWaiterToken");
|
|
2305
|
-
function resolveRunWaiting(run, waiterByRun) {
|
|
2306
|
-
if (run.status !== "suspended") return void 0;
|
|
2307
|
-
const waiter = waiterByRun.get(run.id);
|
|
2308
|
-
return waiter ? classifyWaiterToken(waiter.token) : void 0;
|
|
2309
|
-
}
|
|
2310
|
-
__name(resolveRunWaiting, "resolveRunWaiting");
|
|
2311
|
-
var RUN_ID_BATCH = 500;
|
|
2312
|
-
async function listSignalWaitersOfRuns(store, runIds) {
|
|
2313
|
-
const unique = [
|
|
2314
|
-
...new Set(runIds)
|
|
2315
|
-
];
|
|
2316
|
-
if (unique.length === 0) return [];
|
|
2317
|
-
if (!store.listSignalWaitersByRun) {
|
|
2318
|
-
const ids = new Set(unique);
|
|
2319
|
-
return (await store.listSignalWaiters("")).filter((w) => ids.has(w.runId));
|
|
2320
|
-
}
|
|
2321
|
-
const out = [];
|
|
2322
|
-
for (let at = 0; at < unique.length; at += RUN_ID_BATCH) {
|
|
2323
|
-
out.push(...await store.listSignalWaitersByRun(unique.slice(at, at + RUN_ID_BATCH)));
|
|
2324
|
-
}
|
|
2325
|
-
return out;
|
|
2326
|
-
}
|
|
2327
|
-
__name(listSignalWaitersOfRuns, "listSignalWaitersOfRuns");
|
|
2328
|
-
function indexWaitersByRun(waiters) {
|
|
2329
|
-
const byRun = /* @__PURE__ */ new Map();
|
|
2330
|
-
for (const w of waiters) if (!byRun.has(w.runId)) byRun.set(w.runId, w);
|
|
2331
|
-
return byRun;
|
|
2332
|
-
}
|
|
2333
|
-
__name(indexWaitersByRun, "indexWaitersByRun");
|
|
2334
|
-
var GET_RUN_FALLBACK_PARALLEL = 50;
|
|
2335
|
-
async function getRunsByIds(store, runIds) {
|
|
2336
|
-
const { runs, unread, error: error2 } = await readRunsByIds(store, runIds);
|
|
2337
|
-
if (unread.length > 0) throw error2;
|
|
2338
|
-
return runs;
|
|
2339
|
-
}
|
|
2340
|
-
__name(getRunsByIds, "getRunsByIds");
|
|
2341
|
-
async function readRunsByIds(store, runIds) {
|
|
2342
|
-
const unique = [
|
|
2343
|
-
...new Set(runIds)
|
|
2344
|
-
];
|
|
2345
|
-
const runs = /* @__PURE__ */ new Map();
|
|
2346
|
-
const unread = [];
|
|
2347
|
-
let error2;
|
|
2348
|
-
const batch = store.getRuns ? RUN_ID_BATCH : GET_RUN_FALLBACK_PARALLEL;
|
|
2349
|
-
for (let at = 0; at < unique.length; at += batch) {
|
|
2350
|
-
const ids = unique.slice(at, at + batch);
|
|
2351
|
-
if (store.getRuns) {
|
|
2352
|
-
try {
|
|
2353
|
-
for (const run of await store.getRuns(ids)) runs.set(run.id, run);
|
|
2354
|
-
} catch (err) {
|
|
2355
|
-
error2 ??= err;
|
|
2356
|
-
unread.push(...ids);
|
|
2357
|
-
}
|
|
2358
|
-
continue;
|
|
2359
|
-
}
|
|
2360
|
-
const reads = await Promise.allSettled(ids.map((id) => store.getRun(id)));
|
|
2361
|
-
reads.forEach((read, k) => {
|
|
2362
|
-
if (read.status === "fulfilled") {
|
|
2363
|
-
if (read.value) runs.set(read.value.id, read.value);
|
|
2364
|
-
return;
|
|
2365
|
-
}
|
|
2366
|
-
error2 ??= read.reason;
|
|
2367
|
-
unread.push(ids[k]);
|
|
2368
|
-
});
|
|
2369
|
-
}
|
|
2370
|
-
return {
|
|
2371
|
-
runs,
|
|
2372
|
-
unread,
|
|
2373
|
-
...unread.length > 0 ? {
|
|
2374
|
-
error: error2
|
|
2375
|
-
} : {}
|
|
2376
|
-
};
|
|
2377
|
-
}
|
|
2378
|
-
__name(readRunsByIds, "readRunsByIds");
|
|
2379
|
-
|
|
2380
2511
|
// src/scheduler.ts
|
|
2381
2512
|
import { createRequire } from "node:module";
|
|
2382
2513
|
|
|
@@ -3833,7 +3964,23 @@ function createWorkflowCtx(host, runId, compensations, workflow = "", searchAttr
|
|
|
3833
3964
|
status: "running"
|
|
3834
3965
|
}));
|
|
3835
3966
|
}
|
|
3836
|
-
|
|
3967
|
+
const childRun = await store.getRun(id);
|
|
3968
|
+
if (!childRun) {
|
|
3969
|
+
host.startChild(workflowName(workflow2), input, id, priority, version);
|
|
3970
|
+
} else {
|
|
3971
|
+
const done = (await settleFinishedChildren([
|
|
3972
|
+
{
|
|
3973
|
+
id,
|
|
3974
|
+
seq: current
|
|
3975
|
+
}
|
|
3976
|
+
], /* @__PURE__ */ new Map([
|
|
3977
|
+
[
|
|
3978
|
+
id,
|
|
3979
|
+
childRun
|
|
3980
|
+
]
|
|
3981
|
+
]))).get(id);
|
|
3982
|
+
if (done) return unwrapCompletion(done, `child "${id}"`);
|
|
3983
|
+
}
|
|
3837
3984
|
throw new WorkflowSuspended(void 0, fanJoin([
|
|
3838
3985
|
{
|
|
3839
3986
|
waiterPrefix: `child:${id}`,
|
|
@@ -3854,164 +4001,174 @@ function createWorkflowCtx(host, runId, compensations, workflow = "", searchAttr
|
|
|
3854
4001
|
const group = allGroup(firstSeq, inputs.length, mode);
|
|
3855
4002
|
const waveOf = /* @__PURE__ */ __name((i) => waves ? Math.floor(i / waves.waveSize) : void 0, "waveOf");
|
|
3856
4003
|
const id = /* @__PURE__ */ __name((i) => allChildId(runId, firstSeq, i, waveOf(i)), "id");
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
const
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
const
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
throw new GatherError([
|
|
3884
|
-
{
|
|
3885
|
-
index: i,
|
|
3886
|
-
id: id(i),
|
|
3887
|
-
error: c.error ?? "unknown"
|
|
4004
|
+
for (; ; ) {
|
|
4005
|
+
const { checkpoints: existing, folded } = await readFan(positions, [
|
|
4006
|
+
allWait(runId, firstSeq).checkpointPrefix,
|
|
4007
|
+
allFoldedName(runId, firstSeq)
|
|
4008
|
+
]);
|
|
4009
|
+
const waveCount = waves ? Math.ceil(inputs.length / waves.waveSize) : 1;
|
|
4010
|
+
const waveItems = /* @__PURE__ */ __name((k) => {
|
|
4011
|
+
if (!waves) return inputs.map((_, i) => i);
|
|
4012
|
+
const from = k * waves.waveSize;
|
|
4013
|
+
return inputs.map((_, i) => i).slice(from, from + waves.waveSize);
|
|
4014
|
+
}, "waveItems");
|
|
4015
|
+
if (mode === "failFast") {
|
|
4016
|
+
const failure = /* @__PURE__ */ __name((i2) => {
|
|
4017
|
+
const cp = existing[i2];
|
|
4018
|
+
const c2 = cp?.status === "completed" ? cp.output : null;
|
|
4019
|
+
return c2 && c2.ok === false ? c2 : null;
|
|
4020
|
+
}, "failure");
|
|
4021
|
+
const first = /* @__PURE__ */ __name((cancelled) => inputs.findIndex((_, i2) => {
|
|
4022
|
+
const c2 = failure(i2);
|
|
4023
|
+
return c2 !== null && c2.code === "cancelled" === cancelled;
|
|
4024
|
+
}), "first");
|
|
4025
|
+
const i = first(false) !== -1 ? first(false) : first(true);
|
|
4026
|
+
const c = i === -1 ? null : failure(i);
|
|
4027
|
+
if (c) {
|
|
4028
|
+
for (let j = 0; j < inputs.length; j += 1) {
|
|
4029
|
+
if (j !== i && existing[j]?.status !== "completed") host.cancelChild?.(id(j));
|
|
3888
4030
|
}
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
const last = items[items.length - 1];
|
|
3897
|
-
if (folded[first]) continue;
|
|
3898
|
-
const values = [];
|
|
3899
|
-
for (const i of items) {
|
|
3900
|
-
const c = existing[i]?.status === "completed" ? existing[i]?.output : void 0;
|
|
3901
|
-
if (!c || typeof c !== "object" || c.ok !== true) break;
|
|
3902
|
-
values.push(c.value);
|
|
3903
|
-
}
|
|
3904
|
-
if (values.length < items.length) continue;
|
|
3905
|
-
const into = instantCheckpoint({
|
|
3906
|
-
runId,
|
|
3907
|
-
seq: positions[first],
|
|
3908
|
-
name: allFoldedName(runId, firstSeq, waves ? k : void 0),
|
|
3909
|
-
kind: "signal",
|
|
3910
|
-
output: {
|
|
3911
|
-
from: first,
|
|
3912
|
-
values
|
|
3913
|
-
},
|
|
3914
|
-
parallelGroup: group
|
|
3915
|
-
});
|
|
3916
|
-
await store.compactCheckpoints(runId, positions[first], positions[last], into);
|
|
3917
|
-
for (const i of items) replay?.delete(positions[i]);
|
|
3918
|
-
replay?.set(into.seq, into);
|
|
3919
|
-
}
|
|
3920
|
-
}
|
|
3921
|
-
const sent = /* @__PURE__ */ __name((k) => waveItems(k).some((i) => existing[i]), "sent");
|
|
3922
|
-
const settled = /* @__PURE__ */ __name((k) => waveItems(k).every((i) => existing[i]?.status === "completed"), "settled");
|
|
3923
|
-
const active = [];
|
|
3924
|
-
let unsettled = 0;
|
|
3925
|
-
for (let k = 0; k < waveCount; k += 1) {
|
|
3926
|
-
if (settled(k)) continue;
|
|
3927
|
-
if (sent(k) || unsettled < (waves?.maxWaves ?? 1)) {
|
|
3928
|
-
active.push(k);
|
|
3929
|
-
unsettled += 1;
|
|
3930
|
-
}
|
|
3931
|
-
}
|
|
3932
|
-
if (active.length > 0) {
|
|
3933
|
-
const toRegister = [];
|
|
3934
|
-
const toEnsure = [];
|
|
3935
|
-
for (const k of active) {
|
|
3936
|
-
for (const i of waveItems(k)) {
|
|
3937
|
-
const cp = existing[i];
|
|
3938
|
-
if (cp?.status === "completed") continue;
|
|
3939
|
-
(cp ? toEnsure : toRegister).push(i);
|
|
4031
|
+
throw new GatherError([
|
|
4032
|
+
{
|
|
4033
|
+
index: i,
|
|
4034
|
+
id: id(i),
|
|
4035
|
+
error: c.error ?? "unknown"
|
|
4036
|
+
}
|
|
4037
|
+
], inputs.length);
|
|
3940
4038
|
}
|
|
3941
4039
|
}
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
checkpoint: instantCheckpoint({
|
|
4040
|
+
if (store.compactCheckpoints && inputs.length >= FOLD_MIN_FAN) {
|
|
4041
|
+
for (let k = 0; k < waveCount; k += 1) {
|
|
4042
|
+
const items = waveItems(k);
|
|
4043
|
+
const first = items[0];
|
|
4044
|
+
const last = items[items.length - 1];
|
|
4045
|
+
if (folded[first]) continue;
|
|
4046
|
+
const values = [];
|
|
4047
|
+
for (const i of items) {
|
|
4048
|
+
const c = existing[i]?.status === "completed" ? existing[i]?.output : void 0;
|
|
4049
|
+
if (!c || typeof c !== "object" || c.ok !== true) break;
|
|
4050
|
+
values.push(c.value);
|
|
4051
|
+
}
|
|
4052
|
+
if (values.length < items.length) continue;
|
|
4053
|
+
const into = instantCheckpoint({
|
|
3957
4054
|
runId,
|
|
3958
|
-
seq: positions[
|
|
3959
|
-
name:
|
|
4055
|
+
seq: positions[first],
|
|
4056
|
+
name: allFoldedName(runId, firstSeq, waves ? k : void 0),
|
|
3960
4057
|
kind: "signal",
|
|
3961
|
-
|
|
4058
|
+
output: {
|
|
4059
|
+
from: first,
|
|
4060
|
+
values
|
|
4061
|
+
},
|
|
3962
4062
|
parallelGroup: group
|
|
3963
|
-
})
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
}
|
|
3968
|
-
if (toEnsure.length > 0) {
|
|
3969
|
-
const { runs: started, unread } = await readRunsByIds(store, toEnsure.map((i) => id(i)));
|
|
3970
|
-
const skip = new Set(unread);
|
|
3971
|
-
for (const i of toEnsure) {
|
|
3972
|
-
if (!started.has(id(i)) && !skip.has(id(i))) host.startChild(name, inputs[i], id(i));
|
|
4063
|
+
});
|
|
4064
|
+
await store.compactCheckpoints(runId, positions[first], positions[last], into);
|
|
4065
|
+
for (const i of items) replay?.delete(positions[i]);
|
|
4066
|
+
replay?.set(into.seq, into);
|
|
3973
4067
|
}
|
|
3974
4068
|
}
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
const
|
|
4069
|
+
const sent = /* @__PURE__ */ __name((k) => waveItems(k).some((i) => existing[i]), "sent");
|
|
4070
|
+
const settled = /* @__PURE__ */ __name((k) => waveItems(k).every((i) => existing[i]?.status === "completed"), "settled");
|
|
4071
|
+
const active = [];
|
|
4072
|
+
let unsettled = 0;
|
|
4073
|
+
for (let k = 0; k < waveCount; k += 1) {
|
|
4074
|
+
if (settled(k)) continue;
|
|
4075
|
+
if (sent(k) || unsettled < (waves?.maxWaves ?? 1)) {
|
|
4076
|
+
active.push(k);
|
|
4077
|
+
unsettled += 1;
|
|
4078
|
+
}
|
|
4079
|
+
}
|
|
4080
|
+
let known;
|
|
4081
|
+
if (active.length > 0) {
|
|
4082
|
+
const toRegister = [];
|
|
4083
|
+
const toEnsure = [];
|
|
4084
|
+
for (const k of active) {
|
|
4085
|
+
for (const i of waveItems(k)) {
|
|
4086
|
+
const cp = existing[i];
|
|
4087
|
+
if (cp?.status === "completed") continue;
|
|
4088
|
+
(cp ? toEnsure : toRegister).push(i);
|
|
4089
|
+
}
|
|
4090
|
+
}
|
|
4091
|
+
for (let at = 0; at < toRegister.length; at += FAN_CHUNK) {
|
|
4092
|
+
const chunk = toRegister.slice(at, at + FAN_CHUNK);
|
|
4093
|
+
const entries = chunk.map((i) => ({
|
|
4094
|
+
// Carry the fan `group` onto the waiter too: the child's terminal `signal:child:`
|
|
4095
|
+
// checkpoint is (re)written by engine.signal when the child notifies the parent,
|
|
4096
|
+
// OVERWRITING the running placeholder at the same seq — so without this the resolved
|
|
4097
|
+
// checkpoint would lose the group and the dashboard would render the resolved fan as a
|
|
4098
|
+
// sequential chain.
|
|
4099
|
+
waiter: {
|
|
4100
|
+
token: `child:${id(i)}`,
|
|
4101
|
+
runId,
|
|
4102
|
+
seq: positions[i],
|
|
4103
|
+
parallelGroup: group
|
|
4104
|
+
},
|
|
4105
|
+
checkpoint: instantCheckpoint({
|
|
4106
|
+
runId,
|
|
4107
|
+
seq: positions[i],
|
|
4108
|
+
name: `signal:child:${id(i)}`,
|
|
4109
|
+
kind: "signal",
|
|
4110
|
+
status: "running",
|
|
4111
|
+
parallelGroup: group
|
|
4112
|
+
})
|
|
4113
|
+
}));
|
|
4114
|
+
await registerChildWaits(entries);
|
|
4115
|
+
for (const i of chunk) host.startChild(name, inputs[i], id(i));
|
|
4116
|
+
}
|
|
4117
|
+
if (toEnsure.length > 0) {
|
|
4118
|
+
const { runs: started, unread } = await readRunsByIds(store, toEnsure.map((i) => id(i)));
|
|
4119
|
+
const skip = new Set(unread);
|
|
4120
|
+
for (const i of toEnsure) {
|
|
4121
|
+
if (!started.has(id(i)) && !skip.has(id(i))) host.startChild(name, inputs[i], id(i));
|
|
4122
|
+
}
|
|
4123
|
+
known = started;
|
|
4124
|
+
}
|
|
4125
|
+
const now2 = await settleFinishedChildren(toEnsure.map((i) => ({
|
|
3990
4126
|
id: id(i),
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
};
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4127
|
+
seq: positions[i],
|
|
4128
|
+
parallelGroup: group
|
|
4129
|
+
})), known);
|
|
4130
|
+
if (now2.size > 0) continue;
|
|
4131
|
+
}
|
|
4132
|
+
if (active.length > 0) {
|
|
4133
|
+
throw new WorkflowSuspended(void 0, fanJoin(active.map((k) => allWait(runId, firstSeq, waves ? k : void 0))));
|
|
4134
|
+
}
|
|
4135
|
+
const outputs = [];
|
|
4136
|
+
const results = [];
|
|
4137
|
+
const failures = [];
|
|
4138
|
+
for (let i = 0; i < inputs.length; i += 1) {
|
|
4139
|
+
const c = existing[i]?.output;
|
|
4140
|
+
if (c && typeof c === "object" && "ok" in c && c.ok === false) {
|
|
4141
|
+
const { message, ...rest } = completionError({
|
|
4142
|
+
...c,
|
|
4143
|
+
error: c.error ?? "unknown"
|
|
4144
|
+
});
|
|
4145
|
+
const failure = {
|
|
4146
|
+
id: id(i),
|
|
4147
|
+
error: message,
|
|
4148
|
+
...rest
|
|
4149
|
+
};
|
|
4150
|
+
failures.push({
|
|
4151
|
+
index: i,
|
|
4152
|
+
...failure
|
|
4153
|
+
});
|
|
4154
|
+
results.push({
|
|
4155
|
+
status: "rejected",
|
|
4156
|
+
...failure
|
|
4157
|
+
});
|
|
4158
|
+
outputs.push(void 0);
|
|
4159
|
+
} else {
|
|
4160
|
+
const value = c?.value;
|
|
4161
|
+
results.push({
|
|
4162
|
+
status: "fulfilled",
|
|
4163
|
+
value
|
|
4164
|
+
});
|
|
4165
|
+
outputs.push(value);
|
|
4166
|
+
}
|
|
4010
4167
|
}
|
|
4168
|
+
if (settle) return results;
|
|
4169
|
+
if (failures.length > 0) throw new GatherError(failures, inputs.length);
|
|
4170
|
+
return outputs;
|
|
4011
4171
|
}
|
|
4012
|
-
if (settle) return results;
|
|
4013
|
-
if (failures.length > 0) throw new GatherError(failures, inputs.length);
|
|
4014
|
-
return outputs;
|
|
4015
4172
|
}, "fan");
|
|
4016
4173
|
const all = /* @__PURE__ */ __name((workflow2, inputs, opts) => fan(workflow2, inputs, opts, false), "all");
|
|
4017
4174
|
const allSettled = /* @__PURE__ */ __name((workflow2, inputs, opts) => fan(workflow2, inputs, opts, true), "allSettled");
|
|
@@ -4097,6 +4254,39 @@ function createWorkflowCtx(host, runId, compensations, workflow = "", searchAttr
|
|
|
4097
4254
|
}
|
|
4098
4255
|
for (const e of entries) replay?.set(e.checkpoint.seq, e.checkpoint);
|
|
4099
4256
|
}, "registerChildWaits");
|
|
4257
|
+
const settleFinishedChildren = /* @__PURE__ */ __name(async (awaited, known) => {
|
|
4258
|
+
const settled = /* @__PURE__ */ new Map();
|
|
4259
|
+
if (awaited.length === 0) return settled;
|
|
4260
|
+
const runs = new Map(known ?? []);
|
|
4261
|
+
const toRead = awaited.filter((a) => !runs.has(a.id)).map((a) => a.id);
|
|
4262
|
+
if (toRead.length > 0) {
|
|
4263
|
+
for (const [id, run] of (await readRunsByIds(store, toRead)).runs) runs.set(id, run);
|
|
4264
|
+
}
|
|
4265
|
+
for (const a of awaited) {
|
|
4266
|
+
const child2 = runs.get(a.id);
|
|
4267
|
+
const completion = child2 ? childCompletion(child2) : void 0;
|
|
4268
|
+
if (!completion) continue;
|
|
4269
|
+
const token = `child:${a.id}`;
|
|
4270
|
+
const taken = await store.takeSignalWaiter(token);
|
|
4271
|
+
if (!taken) continue;
|
|
4272
|
+
if (taken.runId !== runId || taken.seq !== a.seq) {
|
|
4273
|
+
await store.putSignalWaiter(taken);
|
|
4274
|
+
continue;
|
|
4275
|
+
}
|
|
4276
|
+
await writeCheckpoint(instantCheckpoint({
|
|
4277
|
+
runId,
|
|
4278
|
+
seq: a.seq,
|
|
4279
|
+
name: `signal:child:${a.id}`,
|
|
4280
|
+
kind: "signal",
|
|
4281
|
+
output: completion,
|
|
4282
|
+
...a.parallelGroup !== void 0 ? {
|
|
4283
|
+
parallelGroup: a.parallelGroup
|
|
4284
|
+
} : {}
|
|
4285
|
+
}));
|
|
4286
|
+
settled.set(a.id, completion);
|
|
4287
|
+
}
|
|
4288
|
+
return settled;
|
|
4289
|
+
}, "settleFinishedChildren");
|
|
4100
4290
|
const startChild = /* @__PURE__ */ __name(async (workflow2, input, options) => {
|
|
4101
4291
|
const { childId, priority, version } = normalizeChildOptions(options);
|
|
4102
4292
|
const current = pos.next();
|
|
@@ -4454,33 +4644,10 @@ var pendingStateKey = /* @__PURE__ */ __name((at) => String(new Date(at).getTime
|
|
|
4454
4644
|
var freshPendingKey = /* @__PURE__ */ __name(() => `${Date.now()}-${globalThis.crypto.randomUUID().slice(0, 8)}`, "freshPendingKey");
|
|
4455
4645
|
var wakeRequestToken = /* @__PURE__ */ __name((runId) => `durable:wake-after-turn:${runId}`, "wakeRequestToken");
|
|
4456
4646
|
var CHILD_SIGNAL_PREFIX = "signal:child:";
|
|
4647
|
+
var BUFFERED_SWEEP_PAGE = 500;
|
|
4648
|
+
var BUFFERED_SWEEP_PAGES_PER_CALL = 4;
|
|
4649
|
+
var BUFFERED_SWEEP_REST_MS = 10 * 6e4;
|
|
4457
4650
|
var DELIVER_CHUNK = 50;
|
|
4458
|
-
function childCompletion(child) {
|
|
4459
|
-
if (child.status === "completed") return {
|
|
4460
|
-
ok: true,
|
|
4461
|
-
value: child.output
|
|
4462
|
-
};
|
|
4463
|
-
if (child.status === "failed") return failedCompletion(child.error ?? {
|
|
4464
|
-
message: "failed"
|
|
4465
|
-
});
|
|
4466
|
-
if (child.status === "dead") return failedCompletion(child.error ?? {
|
|
4467
|
-
message: "dead"
|
|
4468
|
-
});
|
|
4469
|
-
if (child.status === "cancelled") return cancelledCompletion(child.error);
|
|
4470
|
-
return void 0;
|
|
4471
|
-
}
|
|
4472
|
-
__name(childCompletion, "childCompletion");
|
|
4473
|
-
var CHILD_CANCELLED = "cancelled";
|
|
4474
|
-
function cancelledCompletion(error2) {
|
|
4475
|
-
const base = error2 ?? {
|
|
4476
|
-
message: "cancelled"
|
|
4477
|
-
};
|
|
4478
|
-
return failedCompletion({
|
|
4479
|
-
...base,
|
|
4480
|
-
code: base.code ?? CHILD_CANCELLED
|
|
4481
|
-
});
|
|
4482
|
-
}
|
|
4483
|
-
__name(cancelledCompletion, "cancelledCompletion");
|
|
4484
4651
|
var RESULT_BATCH_MAX = 256;
|
|
4485
4652
|
function shuffle(items) {
|
|
4486
4653
|
for (let i = items.length - 1; i > 0; i -= 1) {
|
|
@@ -4696,6 +4863,10 @@ var WorkflowEngine = class {
|
|
|
4696
4863
|
/** Runs already paged about, by id → the `updatedAt` of the paged episode (see {@link sweepStalled}). */
|
|
4697
4864
|
notifiedStalled = /* @__PURE__ */ new Map();
|
|
4698
4865
|
lastStalledSweepAt = Number.NEGATIVE_INFINITY;
|
|
4866
|
+
/** {@link sweepBufferedSignals}: the id the pass in progress resumes after, `undefined` between passes. */
|
|
4867
|
+
bufferedSweepCursor;
|
|
4868
|
+
bufferedSweepActive = false;
|
|
4869
|
+
lastBufferedSweepEndedAt = Number.NEGATIVE_INFINITY;
|
|
4699
4870
|
stalledAfterMs;
|
|
4700
4871
|
draining = false;
|
|
4701
4872
|
/** Wait already spent on this shutdown before `drain()` (see `beginDrain`). */
|
|
@@ -5301,6 +5472,50 @@ var WorkflowEngine = class {
|
|
|
5301
5472
|
return () => this.deadListeners.delete(listener);
|
|
5302
5473
|
}
|
|
5303
5474
|
/**
|
|
5475
|
+
* Delete buffered child completions no parent will ever consume (see
|
|
5476
|
+
* `childCompletionNeedsBuffer`): those of root runs, of children whose parent already settled them
|
|
5477
|
+
* or is gone, or terminal for good. Such rows were written by earlier versions, which buffered every
|
|
5478
|
+
* completion that found no waiter, and nothing else ever removes them.
|
|
5479
|
+
*
|
|
5480
|
+
* Walks `durable_buffered_signals` by primary key, {@link BUFFERED_SWEEP_PAGE} rows at a time and
|
|
5481
|
+
* at most {@link BUFFERED_SWEEP_PAGES_PER_CALL} pages per call, so a large backlog drains over a
|
|
5482
|
+
* few timer ticks without a long query or a big delete. A pass that reaches the end of the table
|
|
5483
|
+
* rests {@link BUFFERED_SWEEP_REST_MS} before the next one. Call it every tick (the NestJS timer
|
|
5484
|
+
* poller does). Skipped while draining, on a namespaced engine (a buffered signal carries no
|
|
5485
|
+
* namespace, and a scoped read would report another tenant's parent as gone), and on a store
|
|
5486
|
+
* without `listBufferedSignals`/`deleteBufferedSignals`. Returns how many rows it deleted.
|
|
5487
|
+
*/
|
|
5488
|
+
async sweepBufferedSignals(now = this.clock()) {
|
|
5489
|
+
const { listBufferedSignals, deleteBufferedSignals } = this.store;
|
|
5490
|
+
if (!listBufferedSignals || !deleteBufferedSignals) return 0;
|
|
5491
|
+
if (this.draining || this.namespace !== void 0 || this.bufferedSweepActive) return 0;
|
|
5492
|
+
if (this.bufferedSweepCursor === void 0 && now - this.lastBufferedSweepEndedAt < BUFFERED_SWEEP_REST_MS) {
|
|
5493
|
+
return 0;
|
|
5494
|
+
}
|
|
5495
|
+
this.bufferedSweepActive = true;
|
|
5496
|
+
let deleted = 0;
|
|
5497
|
+
try {
|
|
5498
|
+
for (let page = 0; page < BUFFERED_SWEEP_PAGES_PER_CALL; page++) {
|
|
5499
|
+
const rows = await listBufferedSignals.call(this.store, CHILD_TOKEN_PREFIX, this.bufferedSweepCursor, BUFFERED_SWEEP_PAGE);
|
|
5500
|
+
const doomed = await unneededChildSignals(this.store, rows);
|
|
5501
|
+
if (doomed.length > 0) {
|
|
5502
|
+
await deleteBufferedSignals.call(this.store, doomed.map((row) => row.id));
|
|
5503
|
+
deleted += doomed.length;
|
|
5504
|
+
}
|
|
5505
|
+
const last = rows.at(-1);
|
|
5506
|
+
if (rows.length < BUFFERED_SWEEP_PAGE || !last) {
|
|
5507
|
+
this.bufferedSweepCursor = void 0;
|
|
5508
|
+
this.lastBufferedSweepEndedAt = now;
|
|
5509
|
+
break;
|
|
5510
|
+
}
|
|
5511
|
+
this.bufferedSweepCursor = last.id;
|
|
5512
|
+
}
|
|
5513
|
+
} finally {
|
|
5514
|
+
this.bufferedSweepActive = false;
|
|
5515
|
+
}
|
|
5516
|
+
return deleted;
|
|
5517
|
+
}
|
|
5518
|
+
/**
|
|
5304
5519
|
* Register a pager hook for STRANDED runs: an in-flight run untouched past `stalledAfterMs` that is
|
|
5305
5520
|
* either suspended with NO wake timer (wake-forever: nothing will ever re-drive it) or parked on an
|
|
5306
5521
|
* old pending remote step with no live lease (dispatched and, most likely, lost). The timer poller
|
|
@@ -7007,7 +7222,9 @@ var WorkflowEngine = class {
|
|
|
7007
7222
|
await this.deliverSignal(waiter, completion);
|
|
7008
7223
|
return;
|
|
7009
7224
|
}
|
|
7010
|
-
if (cancelled
|
|
7225
|
+
if (cancelled) return;
|
|
7226
|
+
const childId = token.slice(CHILD_TOKEN_PREFIX.length);
|
|
7227
|
+
if (!await childCompletionNeedsBuffer(this.store, childId, completion)) return;
|
|
7011
7228
|
await this.signal(token, completion);
|
|
7012
7229
|
return;
|
|
7013
7230
|
} catch (err) {
|
|
@@ -7021,24 +7238,6 @@ var WorkflowEngine = class {
|
|
|
7021
7238
|
}
|
|
7022
7239
|
}
|
|
7023
7240
|
/**
|
|
7024
|
-
* Whether the parent of the child behind `token` (`child:<id>`) already holds `completion` as that
|
|
7025
|
-
* child's settled `signal:child:<id>` checkpoint: its own turn settled the child from the child's
|
|
7026
|
-
* row, so this notification is a duplicate. A different completion (a fix-and-replay run's success
|
|
7027
|
-
* for a child the parent saw fail) is not, and is buffered for the parent's retry. Only for a child
|
|
7028
|
-
* whose id the engine minted (its parent is derivable from it).
|
|
7029
|
-
*/
|
|
7030
|
-
async parentHoldsCompletion(token, completion) {
|
|
7031
|
-
const childId = token.slice("child:".length);
|
|
7032
|
-
const parentId = parentRunIdOf(childId);
|
|
7033
|
-
if (!parentId) return false;
|
|
7034
|
-
const name = `${CHILD_SIGNAL_PREFIX}${childId}`;
|
|
7035
|
-
const checkpoints = this.store.listCheckpointsByNamePrefix ? await this.store.listCheckpointsByNamePrefix(parentId, [
|
|
7036
|
-
name
|
|
7037
|
-
]) : await this.store.listCheckpoints(parentId);
|
|
7038
|
-
const expected = JSON.stringify(completion);
|
|
7039
|
-
return checkpoints.some((cp) => cp.name === name && cp.status === "completed" && JSON.stringify(cp.output) === expected);
|
|
7040
|
-
}
|
|
7041
|
-
/**
|
|
7042
7241
|
* Deferred child start shared by the in-process ctx host and the remote `startChild` command.
|
|
7043
7242
|
* Deferred (microtask) so a fast child can't reentrantly resume a still-suspending parent. A start
|
|
7044
7243
|
* that THROWS (unregistered/unroutable workflow, input validation, singleton back-pressure, store
|
|
@@ -7053,15 +7252,22 @@ var WorkflowEngine = class {
|
|
|
7053
7252
|
startChildDeferred(workflow, input, childId, opts) {
|
|
7054
7253
|
this.localWork.submit(`start:${childId}`, async () => {
|
|
7055
7254
|
if (this.draining) return;
|
|
7056
|
-
await this.start(workflow, input, childId, opts).catch((err) => {
|
|
7255
|
+
const started = await this.start(workflow, input, childId, opts).catch((err) => {
|
|
7057
7256
|
const message = err instanceof Error ? err.message : String(err);
|
|
7058
7257
|
this.notifyParent(childId, {
|
|
7059
7258
|
ok: false,
|
|
7060
7259
|
error: `child workflow "${workflow}" failed to start: ${message}`
|
|
7061
7260
|
});
|
|
7261
|
+
return void 0;
|
|
7062
7262
|
});
|
|
7263
|
+
if (started) this.redeliverFinishedChild(childId, started);
|
|
7063
7264
|
});
|
|
7064
7265
|
}
|
|
7266
|
+
/** {@link startChildDeferred}: re-deliver the completion of a child `start` found finished. */
|
|
7267
|
+
redeliverFinishedChild(childId, run) {
|
|
7268
|
+
const completion = childCompletion(run);
|
|
7269
|
+
if (completion) this.inBackground(this.deliverToParent(`child:${childId}`, completion));
|
|
7270
|
+
}
|
|
7065
7271
|
/**
|
|
7066
7272
|
* Cancel a run (e.g. from the dashboard). Returns null if the run does not exist. Pass
|
|
7067
7273
|
* `{ compensate: true }` to undo the saga first: the suspended run is resumed so its completed
|
|
@@ -8666,10 +8872,13 @@ ${run.workflowVersion}`;
|
|
|
8666
8872
|
// dashboard renders the fan as one parallel group. Undefined for a lone (non-fan) child.
|
|
8667
8873
|
parallelGroup: cmd.parallelGroup
|
|
8668
8874
|
});
|
|
8669
|
-
|
|
8875
|
+
const existingChild = await this.store.getRun(childId);
|
|
8876
|
+
if (!existingChild) {
|
|
8670
8877
|
this.startChildDeferred(cmd.workflow, cmd.input, childId, {
|
|
8671
8878
|
namespace: run.namespace
|
|
8672
8879
|
});
|
|
8880
|
+
} else {
|
|
8881
|
+
this.redeliverFinishedChild(childId, existingChild);
|
|
8673
8882
|
}
|
|
8674
8883
|
} else {
|
|
8675
8884
|
throw new Error(`remote workflow command '${cmd.kind}' is not supported yet`);
|
|
@@ -10680,6 +10889,10 @@ var CodecStateStore = class {
|
|
|
10680
10889
|
compactCheckpoints;
|
|
10681
10890
|
/** Decodes each run like {@link getRun}. Bound only when the inner store has it. */
|
|
10682
10891
|
getRuns;
|
|
10892
|
+
/** Decodes each payload like {@link takeBufferedSignal}. Bound only when the inner store has it. */
|
|
10893
|
+
listBufferedSignals;
|
|
10894
|
+
/** Forwarded verbatim — deletes by row id. */
|
|
10895
|
+
deleteBufferedSignals;
|
|
10683
10896
|
/** Forwarded verbatim — a waiter carries no payload. */
|
|
10684
10897
|
listSignalWaitersByRun;
|
|
10685
10898
|
/** Forwarded verbatim — a waiter carries no payload. */
|
|
@@ -10725,6 +10938,13 @@ var CodecStateStore = class {
|
|
|
10725
10938
|
if (compact) this.compactCheckpoints = (runId, fromSeq, toSeq, into) => compact.call(inner, runId, fromSeq, toSeq, this.encCp(into));
|
|
10726
10939
|
const getRuns = inner.getRuns;
|
|
10727
10940
|
if (getRuns) this.getRuns = async (runIds) => (await getRuns.call(inner, runIds)).map((r) => this.decRun(r));
|
|
10941
|
+
const listBuffered = inner.listBufferedSignals;
|
|
10942
|
+
if (listBuffered) this.listBufferedSignals = async (tokenPrefix, afterId, limit) => (await listBuffered.call(inner, tokenPrefix, afterId, limit)).map((row) => ({
|
|
10943
|
+
...row,
|
|
10944
|
+
payload: this.dec(row.payload)
|
|
10945
|
+
}));
|
|
10946
|
+
const deleteBuffered = inner.deleteBufferedSignals;
|
|
10947
|
+
if (deleteBuffered) this.deleteBufferedSignals = (ids) => deleteBuffered.call(inner, ids);
|
|
10728
10948
|
const byRun = inner.listSignalWaitersByRun;
|
|
10729
10949
|
if (byRun) this.listSignalWaitersByRun = (runIds) => byRun.call(inner, runIds);
|
|
10730
10950
|
const hasWaiter = inner.hasSignalWaiter;
|
|
@@ -11470,20 +11690,47 @@ var InMemoryStateStore = class {
|
|
|
11470
11690
|
});
|
|
11471
11691
|
}
|
|
11472
11692
|
bufferedSignals = /* @__PURE__ */ new Map();
|
|
11693
|
+
bufferedSignalSeq = 0;
|
|
11473
11694
|
async bufferSignal(token, payload) {
|
|
11474
11695
|
const queue = this.bufferedSignals.get(token) ?? [];
|
|
11475
|
-
queue.push(
|
|
11696
|
+
queue.push({
|
|
11697
|
+
id: ++this.bufferedSignalSeq,
|
|
11698
|
+
payload
|
|
11699
|
+
});
|
|
11476
11700
|
this.bufferedSignals.set(token, queue);
|
|
11477
11701
|
}
|
|
11478
11702
|
async takeBufferedSignal(token) {
|
|
11479
11703
|
const queue = this.bufferedSignals.get(token);
|
|
11480
11704
|
if (!queue || queue.length === 0) return null;
|
|
11481
|
-
const
|
|
11705
|
+
const head = queue.shift();
|
|
11482
11706
|
if (queue.length === 0) this.bufferedSignals.delete(token);
|
|
11483
11707
|
return {
|
|
11484
|
-
payload
|
|
11708
|
+
payload: head.payload
|
|
11485
11709
|
};
|
|
11486
11710
|
}
|
|
11711
|
+
async listBufferedSignals(tokenPrefix, afterId, limit) {
|
|
11712
|
+
const after = afterId === void 0 ? 0 : Number(afterId);
|
|
11713
|
+
const rows = [];
|
|
11714
|
+
for (const [token, queue] of this.bufferedSignals) {
|
|
11715
|
+
if (!token.startsWith(tokenPrefix)) continue;
|
|
11716
|
+
for (const { id, payload } of queue) {
|
|
11717
|
+
if (id > after) rows.push({
|
|
11718
|
+
id: String(id),
|
|
11719
|
+
token,
|
|
11720
|
+
payload
|
|
11721
|
+
});
|
|
11722
|
+
}
|
|
11723
|
+
}
|
|
11724
|
+
return rows.sort((a, b) => Number(a.id) - Number(b.id)).slice(0, limit);
|
|
11725
|
+
}
|
|
11726
|
+
async deleteBufferedSignals(ids) {
|
|
11727
|
+
const doomed = new Set(ids.map(Number));
|
|
11728
|
+
for (const [token, queue] of this.bufferedSignals) {
|
|
11729
|
+
const kept = queue.filter((row) => !doomed.has(row.id));
|
|
11730
|
+
if (kept.length === 0) this.bufferedSignals.delete(token);
|
|
11731
|
+
else this.bufferedSignals.set(token, kept);
|
|
11732
|
+
}
|
|
11733
|
+
}
|
|
11487
11734
|
/** `id -> row`, so {@link removeBufferedEvent} can delete an exact row regardless of iteration
|
|
11488
11735
|
* order — mirrors the SQL stores' PK-by-id delete. */
|
|
11489
11736
|
bufferedEvents = /* @__PURE__ */ new Map();
|
|
@@ -11834,6 +12081,7 @@ var InMemoryRunQueue = class {
|
|
|
11834
12081
|
export {
|
|
11835
12082
|
CHILD_EDGE_PREFIXES,
|
|
11836
12083
|
CONTROL_FLOW_SIGNAL,
|
|
12084
|
+
CORE_ORIGIN,
|
|
11837
12085
|
CURRENT_PROTOCOL_VERSION,
|
|
11838
12086
|
CodecStateStore,
|
|
11839
12087
|
ConcurrencyLimitError,
|