@dudousxd/nestjs-durable-core 0.73.0 → 0.75.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +473 -79
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +155 -12
- package/dist/index.d.ts +155 -12
- package/dist/index.js +471 -79
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -389,12 +389,15 @@ var WorkflowSuspended = class extends Error {
|
|
|
389
389
|
}
|
|
390
390
|
/** Epoch ms to auto-resume (durable sleep), or undefined when waiting on an external signal. */
|
|
391
391
|
wakeAt;
|
|
392
|
+
/** Set when awaited children are what the run is waiting on — see {@link FanJoin}. */
|
|
393
|
+
fan;
|
|
392
394
|
/** Marks this as a control-flow signal — see {@link isWorkflowControlFlowSignal}. */
|
|
393
395
|
[CONTROL_FLOW_SIGNAL] = true;
|
|
394
|
-
constructor(wakeAt) {
|
|
396
|
+
constructor(wakeAt, fan) {
|
|
395
397
|
super("workflow suspended");
|
|
396
398
|
this.name = "WorkflowSuspended";
|
|
397
399
|
this.wakeAt = wakeAt;
|
|
400
|
+
this.fan = fan;
|
|
398
401
|
}
|
|
399
402
|
};
|
|
400
403
|
var ContinueAsNew = class extends Error {
|
|
@@ -821,6 +824,21 @@ function instantCheckpoint(p) {
|
|
|
821
824
|
}
|
|
822
825
|
__name(instantCheckpoint, "instantCheckpoint");
|
|
823
826
|
|
|
827
|
+
// src/completion.ts
|
|
828
|
+
function isFailedCompletion(payload) {
|
|
829
|
+
const c = payload;
|
|
830
|
+
return !!c && typeof c === "object" && "ok" in c && c.ok === false;
|
|
831
|
+
}
|
|
832
|
+
__name(isFailedCompletion, "isFailedCompletion");
|
|
833
|
+
function unwrapCompletion(payload, label) {
|
|
834
|
+
const c = payload;
|
|
835
|
+
if (c && typeof c === "object" && "ok" in c && c.ok === false) {
|
|
836
|
+
throw new FatalError(`${label} failed: ${c.error}`);
|
|
837
|
+
}
|
|
838
|
+
return c?.value;
|
|
839
|
+
}
|
|
840
|
+
__name(unwrapCompletion, "unwrapCompletion");
|
|
841
|
+
|
|
824
842
|
// src/concurrency.ts
|
|
825
843
|
var DEFAULT_CONCURRENCY_STATUSES = [
|
|
826
844
|
"pending",
|
|
@@ -1060,6 +1078,81 @@ function eventMatches(payload, match) {
|
|
|
1060
1078
|
}
|
|
1061
1079
|
__name(eventMatches, "eventMatches");
|
|
1062
1080
|
|
|
1081
|
+
// src/fan-join.ts
|
|
1082
|
+
function allChildId(runId, firstSeq, index, wave) {
|
|
1083
|
+
return `${allBase(runId, firstSeq, wave)}${index}`;
|
|
1084
|
+
}
|
|
1085
|
+
__name(allChildId, "allChildId");
|
|
1086
|
+
function allGroup(firstSeq, mode) {
|
|
1087
|
+
return mode === "failFast" ? `all:${firstSeq}:failFast` : `all:${firstSeq}`;
|
|
1088
|
+
}
|
|
1089
|
+
__name(allGroup, "allGroup");
|
|
1090
|
+
function allWait(runId, firstSeq, wave) {
|
|
1091
|
+
const base = allBase(runId, firstSeq, wave);
|
|
1092
|
+
return {
|
|
1093
|
+
waiterPrefix: `child:${base}`,
|
|
1094
|
+
checkpointPrefix: `signal:child:${base}`
|
|
1095
|
+
};
|
|
1096
|
+
}
|
|
1097
|
+
__name(allWait, "allWait");
|
|
1098
|
+
function fanJoin(waits) {
|
|
1099
|
+
return {
|
|
1100
|
+
waits
|
|
1101
|
+
};
|
|
1102
|
+
}
|
|
1103
|
+
__name(fanJoin, "fanJoin");
|
|
1104
|
+
var FOLDED_PREFIX = "fan:";
|
|
1105
|
+
function allFoldedName(runId, firstSeq, wave) {
|
|
1106
|
+
return `${FOLDED_PREFIX}${allBase(runId, firstSeq, wave)}`;
|
|
1107
|
+
}
|
|
1108
|
+
__name(allFoldedName, "allFoldedName");
|
|
1109
|
+
function foldedChildIds(checkpoint) {
|
|
1110
|
+
const base = checkpoint.name.slice(FOLDED_PREFIX.length);
|
|
1111
|
+
const { from, values } = checkpoint.output;
|
|
1112
|
+
return values.map((_, j) => `${base}${from + j}`);
|
|
1113
|
+
}
|
|
1114
|
+
__name(foldedChildIds, "foldedChildIds");
|
|
1115
|
+
function allBase(runId, firstSeq, wave) {
|
|
1116
|
+
return wave === void 0 ? `${runId}.all.${firstSeq}.` : `${runId}.all.${firstSeq}.w${wave}.`;
|
|
1117
|
+
}
|
|
1118
|
+
__name(allBase, "allBase");
|
|
1119
|
+
function allWaves(size, concurrency) {
|
|
1120
|
+
if (concurrency === void 0 || concurrency >= size) return void 0;
|
|
1121
|
+
if (!Number.isInteger(concurrency) || concurrency < 1) {
|
|
1122
|
+
throw new Error(`ctx.all: concurrency must be a positive integer, got ${concurrency}`);
|
|
1123
|
+
}
|
|
1124
|
+
if (concurrency === 1) return {
|
|
1125
|
+
waveSize: 1,
|
|
1126
|
+
maxWaves: 1
|
|
1127
|
+
};
|
|
1128
|
+
return {
|
|
1129
|
+
waveSize: Math.floor(concurrency / 2),
|
|
1130
|
+
maxWaves: 2
|
|
1131
|
+
};
|
|
1132
|
+
}
|
|
1133
|
+
__name(allWaves, "allWaves");
|
|
1134
|
+
var ENGINE_CHILD_ID = /^(.+)\.(?:all\.\d+\.(?:w\d+\.)?\d+|child\.\d+)$/;
|
|
1135
|
+
function parentRunIdOf(runId) {
|
|
1136
|
+
return ENGINE_CHILD_ID.exec(runId)?.[1];
|
|
1137
|
+
}
|
|
1138
|
+
__name(parentRunIdOf, "parentRunIdOf");
|
|
1139
|
+
var GROUP = /^all:(\d+)(:failFast)?$/;
|
|
1140
|
+
var REST = /^(?:w(\d+)\.)?\d+$/;
|
|
1141
|
+
function fanOfWaiter(waiter) {
|
|
1142
|
+
const group = waiter.parallelGroup ? GROUP.exec(waiter.parallelGroup) : null;
|
|
1143
|
+
if (!group) return void 0;
|
|
1144
|
+
const base = `child:${waiter.runId}.all.${group[1]}.`;
|
|
1145
|
+
if (!waiter.token.startsWith(base)) return void 0;
|
|
1146
|
+
const rest = REST.exec(waiter.token.slice(base.length));
|
|
1147
|
+
if (!rest) return void 0;
|
|
1148
|
+
const waiterPrefix = rest[1] === void 0 ? base : `${base}w${rest[1]}.`;
|
|
1149
|
+
return {
|
|
1150
|
+
waiterPrefix,
|
|
1151
|
+
failFast: group[2] !== void 0
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
__name(fanOfWaiter, "fanOfWaiter");
|
|
1155
|
+
|
|
1063
1156
|
// src/handshake/descriptor.ts
|
|
1064
1157
|
var CURRENT_PROTOCOL_VERSION = 1;
|
|
1065
1158
|
var LEGACY_V1_PROTOCOL = {
|
|
@@ -1801,6 +1894,23 @@ function resolveRunWaiting(run, waiterByRun) {
|
|
|
1801
1894
|
return waiter ? classifyWaiterToken(waiter.token) : void 0;
|
|
1802
1895
|
}
|
|
1803
1896
|
__name(resolveRunWaiting, "resolveRunWaiting");
|
|
1897
|
+
var RUN_ID_BATCH = 500;
|
|
1898
|
+
async function listSignalWaitersOfRuns(store, runIds) {
|
|
1899
|
+
const unique = [
|
|
1900
|
+
...new Set(runIds)
|
|
1901
|
+
];
|
|
1902
|
+
if (unique.length === 0) return [];
|
|
1903
|
+
if (!store.listSignalWaitersByRun) {
|
|
1904
|
+
const ids = new Set(unique);
|
|
1905
|
+
return (await store.listSignalWaiters("")).filter((w) => ids.has(w.runId));
|
|
1906
|
+
}
|
|
1907
|
+
const out = [];
|
|
1908
|
+
for (let at = 0; at < unique.length; at += RUN_ID_BATCH) {
|
|
1909
|
+
out.push(...await store.listSignalWaitersByRun(unique.slice(at, at + RUN_ID_BATCH)));
|
|
1910
|
+
}
|
|
1911
|
+
return out;
|
|
1912
|
+
}
|
|
1913
|
+
__name(listSignalWaitersOfRuns, "listSignalWaitersOfRuns");
|
|
1804
1914
|
function indexWaitersByRun(waiters) {
|
|
1805
1915
|
const byRun = /* @__PURE__ */ new Map();
|
|
1806
1916
|
for (const w of waiters) if (!byRun.has(w.runId)) byRun.set(w.runId, w);
|
|
@@ -2077,12 +2187,14 @@ var ScheduleClient = class {
|
|
|
2077
2187
|
const store = this.requireStore();
|
|
2078
2188
|
const started = [];
|
|
2079
2189
|
for (let round = 0; round < 50; round++) {
|
|
2190
|
+
if (this.host.isDraining?.()) break;
|
|
2080
2191
|
const due = await store.listSchedules({
|
|
2081
2192
|
dueBy: nowMs,
|
|
2082
2193
|
namespace: this.options.namespace,
|
|
2083
2194
|
limit: TICK_BATCH
|
|
2084
2195
|
});
|
|
2085
2196
|
for (const record of due) {
|
|
2197
|
+
if (this.host.isDraining?.()) return started;
|
|
2086
2198
|
const runId = await this.fire(record, nowMs);
|
|
2087
2199
|
if (runId) started.push(runId);
|
|
2088
2200
|
}
|
|
@@ -2611,16 +2723,6 @@ var TransportPool = class {
|
|
|
2611
2723
|
}
|
|
2612
2724
|
};
|
|
2613
2725
|
|
|
2614
|
-
// src/completion.ts
|
|
2615
|
-
function unwrapCompletion(payload, label) {
|
|
2616
|
-
const c = payload;
|
|
2617
|
-
if (c && typeof c === "object" && "ok" in c && c.ok === false) {
|
|
2618
|
-
throw new FatalError(`${label} failed: ${c.error}`);
|
|
2619
|
-
}
|
|
2620
|
-
return c?.value;
|
|
2621
|
-
}
|
|
2622
|
-
__name(unwrapCompletion, "unwrapCompletion");
|
|
2623
|
-
|
|
2624
2726
|
// src/step-name-symbol.ts
|
|
2625
2727
|
var DURABLE_STEP_NAME = Symbol.for("durable.step.name");
|
|
2626
2728
|
var DURABLE_STEP_CONFIG = Symbol.for("durable.step.config");
|
|
@@ -2642,6 +2744,8 @@ function stepConfigOf(ref) {
|
|
|
2642
2744
|
__name(stepConfigOf, "stepConfigOf");
|
|
2643
2745
|
|
|
2644
2746
|
// src/workflow-ctx.ts
|
|
2747
|
+
var FAN_CHUNK = 500;
|
|
2748
|
+
var FOLD_MIN_FAN = 100;
|
|
2645
2749
|
function normalizeChildOptions(options) {
|
|
2646
2750
|
return typeof options === "string" ? {
|
|
2647
2751
|
childId: options
|
|
@@ -2984,7 +3088,7 @@ function createWorkflowCtx(host, runId, compensations, workflow = "", searchAttr
|
|
|
2984
3088
|
if (existing && existing.status === "completed") {
|
|
2985
3089
|
return unwrapCompletion(existing.output, `child "${id}"`);
|
|
2986
3090
|
}
|
|
2987
|
-
await store.putSignalWaiter({
|
|
3091
|
+
if (!existing) await store.putSignalWaiter({
|
|
2988
3092
|
token: `child:${id}`,
|
|
2989
3093
|
runId,
|
|
2990
3094
|
seq: current
|
|
@@ -3015,16 +3119,33 @@ function createWorkflowCtx(host, runId, compensations, workflow = "", searchAttr
|
|
|
3015
3119
|
status: "running"
|
|
3016
3120
|
}));
|
|
3017
3121
|
}
|
|
3018
|
-
throw new WorkflowSuspended(
|
|
3122
|
+
throw new WorkflowSuspended(void 0, fanJoin([
|
|
3123
|
+
{
|
|
3124
|
+
waiterPrefix: `child:${id}`,
|
|
3125
|
+
checkpointPrefix: `signal:child:${id}`
|
|
3126
|
+
}
|
|
3127
|
+
]));
|
|
3019
3128
|
}, "child");
|
|
3020
3129
|
const all = /* @__PURE__ */ __name(async (workflow2, inputs, opts) => {
|
|
3021
3130
|
if (inputs.length === 0) return [];
|
|
3022
3131
|
const mode = opts?.mode ?? "waitAll";
|
|
3023
3132
|
const name = workflowName(workflow2);
|
|
3133
|
+
const waves = allWaves(inputs.length, opts?.concurrency);
|
|
3024
3134
|
const positions = inputs.map(() => pos.next());
|
|
3025
|
-
const
|
|
3026
|
-
const
|
|
3027
|
-
const
|
|
3135
|
+
const firstSeq = positions[0];
|
|
3136
|
+
const group = allGroup(firstSeq, mode);
|
|
3137
|
+
const waveOf = /* @__PURE__ */ __name((i) => waves ? Math.floor(i / waves.waveSize) : void 0, "waveOf");
|
|
3138
|
+
const id = /* @__PURE__ */ __name((i) => allChildId(runId, firstSeq, i, waveOf(i)), "id");
|
|
3139
|
+
const { checkpoints: existing, folded } = await readFan(positions, [
|
|
3140
|
+
allWait(runId, firstSeq).checkpointPrefix,
|
|
3141
|
+
allFoldedName(runId, firstSeq)
|
|
3142
|
+
]);
|
|
3143
|
+
const waveCount = waves ? Math.ceil(inputs.length / waves.waveSize) : 1;
|
|
3144
|
+
const waveItems = /* @__PURE__ */ __name((k) => {
|
|
3145
|
+
if (!waves) return inputs.map((_, i) => i);
|
|
3146
|
+
const from = k * waves.waveSize;
|
|
3147
|
+
return inputs.map((_, i) => i).slice(from, from + waves.waveSize);
|
|
3148
|
+
}, "waveItems");
|
|
3028
3149
|
if (mode === "failFast") {
|
|
3029
3150
|
for (let i = 0; i < inputs.length; i += 1) {
|
|
3030
3151
|
const cp = existing[i];
|
|
@@ -3043,32 +3164,91 @@ function createWorkflowCtx(host, runId, compensations, workflow = "", searchAttr
|
|
|
3043
3164
|
}
|
|
3044
3165
|
}
|
|
3045
3166
|
}
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
if (!cp) {
|
|
3061
|
-
await writeCheckpoint(instantCheckpoint({
|
|
3167
|
+
if (store.compactCheckpoints && inputs.length >= FOLD_MIN_FAN) {
|
|
3168
|
+
for (let k = 0; k < waveCount; k += 1) {
|
|
3169
|
+
const items = waveItems(k);
|
|
3170
|
+
const first = items[0];
|
|
3171
|
+
const last = items[items.length - 1];
|
|
3172
|
+
if (folded[first]) continue;
|
|
3173
|
+
const values = [];
|
|
3174
|
+
for (const i of items) {
|
|
3175
|
+
const c = existing[i]?.status === "completed" ? existing[i]?.output : void 0;
|
|
3176
|
+
if (!c || typeof c !== "object" || c.ok !== true) break;
|
|
3177
|
+
values.push(c.value);
|
|
3178
|
+
}
|
|
3179
|
+
if (values.length < items.length) continue;
|
|
3180
|
+
const into = instantCheckpoint({
|
|
3062
3181
|
runId,
|
|
3063
|
-
seq,
|
|
3064
|
-
name:
|
|
3182
|
+
seq: positions[first],
|
|
3183
|
+
name: allFoldedName(runId, firstSeq, waves ? k : void 0),
|
|
3065
3184
|
kind: "signal",
|
|
3066
|
-
|
|
3185
|
+
output: {
|
|
3186
|
+
from: first,
|
|
3187
|
+
values
|
|
3188
|
+
},
|
|
3067
3189
|
parallelGroup: group
|
|
3190
|
+
});
|
|
3191
|
+
await store.compactCheckpoints(runId, positions[first], positions[last], into);
|
|
3192
|
+
for (const i of items) replay?.delete(positions[i]);
|
|
3193
|
+
replay?.set(into.seq, into);
|
|
3194
|
+
}
|
|
3195
|
+
}
|
|
3196
|
+
const sent = /* @__PURE__ */ __name((k) => waveItems(k).some((i) => existing[i]), "sent");
|
|
3197
|
+
const settled = /* @__PURE__ */ __name((k) => waveItems(k).every((i) => existing[i]?.status === "completed"), "settled");
|
|
3198
|
+
const active = [];
|
|
3199
|
+
let unsettled = 0;
|
|
3200
|
+
for (let k = 0; k < waveCount; k += 1) {
|
|
3201
|
+
if (settled(k)) continue;
|
|
3202
|
+
if (sent(k) || unsettled < (waves?.maxWaves ?? 1)) {
|
|
3203
|
+
active.push(k);
|
|
3204
|
+
unsettled += 1;
|
|
3205
|
+
}
|
|
3206
|
+
}
|
|
3207
|
+
if (active.length > 0) {
|
|
3208
|
+
const toRegister = [];
|
|
3209
|
+
const toEnsure = [];
|
|
3210
|
+
for (const k of active) {
|
|
3211
|
+
for (const i of waveItems(k)) {
|
|
3212
|
+
const cp = existing[i];
|
|
3213
|
+
if (cp?.status === "completed") continue;
|
|
3214
|
+
(cp ? toEnsure : toRegister).push(i);
|
|
3215
|
+
}
|
|
3216
|
+
}
|
|
3217
|
+
for (let at = 0; at < toRegister.length; at += FAN_CHUNK) {
|
|
3218
|
+
const chunk = toRegister.slice(at, at + FAN_CHUNK);
|
|
3219
|
+
const entries = chunk.map((i) => ({
|
|
3220
|
+
// Carry the fan `group` onto the waiter too: the child's terminal `signal:child:`
|
|
3221
|
+
// checkpoint is (re)written by engine.signal when the child notifies the parent,
|
|
3222
|
+
// OVERWRITING the running placeholder at the same seq — so without this the resolved
|
|
3223
|
+
// checkpoint would lose the group and the dashboard would render the resolved fan as a
|
|
3224
|
+
// sequential chain.
|
|
3225
|
+
waiter: {
|
|
3226
|
+
token: `child:${id(i)}`,
|
|
3227
|
+
runId,
|
|
3228
|
+
seq: positions[i],
|
|
3229
|
+
parallelGroup: group
|
|
3230
|
+
},
|
|
3231
|
+
checkpoint: instantCheckpoint({
|
|
3232
|
+
runId,
|
|
3233
|
+
seq: positions[i],
|
|
3234
|
+
name: `signal:child:${id(i)}`,
|
|
3235
|
+
kind: "signal",
|
|
3236
|
+
status: "running",
|
|
3237
|
+
parallelGroup: group
|
|
3238
|
+
})
|
|
3239
|
+
}));
|
|
3240
|
+
await registerChildWaits(entries);
|
|
3241
|
+
for (const i of chunk) host.startChild(name, inputs[i], id(i));
|
|
3242
|
+
}
|
|
3243
|
+
for (let at = 0; at < toEnsure.length; at += FAN_CHUNK) {
|
|
3244
|
+
await Promise.all(toEnsure.slice(at, at + FAN_CHUNK).map(async (i) => {
|
|
3245
|
+
if (!await store.getRun(id(i))) host.startChild(name, inputs[i], id(i));
|
|
3068
3246
|
}));
|
|
3069
3247
|
}
|
|
3070
3248
|
}
|
|
3071
|
-
if (
|
|
3249
|
+
if (active.length > 0) {
|
|
3250
|
+
throw new WorkflowSuspended(void 0, fanJoin(active.map((k) => allWait(runId, firstSeq, waves ? k : void 0))));
|
|
3251
|
+
}
|
|
3072
3252
|
const outputs = [];
|
|
3073
3253
|
const failures = [];
|
|
3074
3254
|
for (let i = 0; i < inputs.length; i += 1) {
|
|
@@ -3087,6 +3267,67 @@ function createWorkflowCtx(host, runId, compensations, workflow = "", searchAttr
|
|
|
3087
3267
|
if (failures.length > 0) throw new GatherError(failures, inputs.length);
|
|
3088
3268
|
return outputs;
|
|
3089
3269
|
}, "all");
|
|
3270
|
+
const readFan = /* @__PURE__ */ __name(async (positions, prefixes) => {
|
|
3271
|
+
const checkpoints = positions.map((seq) => replay?.get(seq) ?? null);
|
|
3272
|
+
const folded = positions.map(() => false);
|
|
3273
|
+
const expand = /* @__PURE__ */ __name(() => {
|
|
3274
|
+
for (let i = 0; i < checkpoints.length; i += 1) {
|
|
3275
|
+
const cp = checkpoints[i];
|
|
3276
|
+
if (!cp?.name.startsWith(FOLDED_PREFIX) || folded[i]) continue;
|
|
3277
|
+
const fold = cp.output;
|
|
3278
|
+
if (!fold || typeof fold.from !== "number" || !Array.isArray(fold.values)) {
|
|
3279
|
+
throw new Error(`ctx.all: folded checkpoint "${cp.name}" at seq ${cp.seq} is malformed`);
|
|
3280
|
+
}
|
|
3281
|
+
const { from, values } = fold;
|
|
3282
|
+
values.forEach((value, j) => {
|
|
3283
|
+
checkpoints[from + j] = {
|
|
3284
|
+
...cp,
|
|
3285
|
+
seq: positions[from + j],
|
|
3286
|
+
output: {
|
|
3287
|
+
ok: true,
|
|
3288
|
+
value
|
|
3289
|
+
}
|
|
3290
|
+
};
|
|
3291
|
+
folded[from + j] = true;
|
|
3292
|
+
});
|
|
3293
|
+
}
|
|
3294
|
+
}, "expand");
|
|
3295
|
+
expand();
|
|
3296
|
+
const missing = positions.filter((_, i) => !checkpoints[i]);
|
|
3297
|
+
if (missing.length === 0) return {
|
|
3298
|
+
checkpoints,
|
|
3299
|
+
folded
|
|
3300
|
+
};
|
|
3301
|
+
if (missing.length > 1 && store.listCheckpointsByNamePrefix) {
|
|
3302
|
+
const bySeq = /* @__PURE__ */ new Map();
|
|
3303
|
+
for (const cp of await store.listCheckpointsByNamePrefix(runId, prefixes)) {
|
|
3304
|
+
bySeq.set(cp.seq, cp);
|
|
3305
|
+
}
|
|
3306
|
+
positions.forEach((seq, i) => {
|
|
3307
|
+
checkpoints[i] ??= bySeq.get(seq) ?? null;
|
|
3308
|
+
});
|
|
3309
|
+
} else {
|
|
3310
|
+
await Promise.all(positions.map(async (seq, i) => {
|
|
3311
|
+
checkpoints[i] ??= await readCheckpoint(seq);
|
|
3312
|
+
}));
|
|
3313
|
+
}
|
|
3314
|
+
expand();
|
|
3315
|
+
return {
|
|
3316
|
+
checkpoints,
|
|
3317
|
+
folded
|
|
3318
|
+
};
|
|
3319
|
+
}, "readFan");
|
|
3320
|
+
const registerChildWaits = /* @__PURE__ */ __name(async (entries) => {
|
|
3321
|
+
if (store.registerChildWaits) {
|
|
3322
|
+
await store.registerChildWaits(entries);
|
|
3323
|
+
} else {
|
|
3324
|
+
await Promise.all(entries.map(async (e) => {
|
|
3325
|
+
await store.putSignalWaiter(e.waiter);
|
|
3326
|
+
await store.saveCheckpoint(e.checkpoint);
|
|
3327
|
+
}));
|
|
3328
|
+
}
|
|
3329
|
+
for (const e of entries) replay?.set(e.checkpoint.seq, e.checkpoint);
|
|
3330
|
+
}, "registerChildWaits");
|
|
3090
3331
|
const startChild = /* @__PURE__ */ __name(async (workflow2, input, options) => {
|
|
3091
3332
|
const { childId, priority, version } = normalizeChildOptions(options);
|
|
3092
3333
|
const current = pos.next();
|
|
@@ -3466,6 +3707,8 @@ var WorkflowEngine = class {
|
|
|
3466
3707
|
/** Executions currently in flight, so a graceful shutdown can wait for them to settle. */
|
|
3467
3708
|
inflight = /* @__PURE__ */ new Set();
|
|
3468
3709
|
draining = false;
|
|
3710
|
+
/** Wait already spent on this shutdown before `drain()` (see `beginDrain`). */
|
|
3711
|
+
drainSpentMs = 0;
|
|
3469
3712
|
constructor(deps) {
|
|
3470
3713
|
this.store = deps.store;
|
|
3471
3714
|
this.pool = new TransportPool(deps.transports ?? (deps.transport ? [
|
|
@@ -3514,7 +3757,8 @@ var WorkflowEngine = class {
|
|
|
3514
3757
|
});
|
|
3515
3758
|
this.schedules = new ScheduleClient(this.store, {
|
|
3516
3759
|
start: /* @__PURE__ */ __name((workflow, input, runId, opts) => this.start(workflow, input, runId, opts), "start"),
|
|
3517
|
-
getRun: /* @__PURE__ */ __name((runId) => this.store.getRun(runId), "getRun")
|
|
3760
|
+
getRun: /* @__PURE__ */ __name((runId) => this.store.getRun(runId), "getRun"),
|
|
3761
|
+
isDraining: /* @__PURE__ */ __name(() => this.draining, "isDraining")
|
|
3518
3762
|
}, {
|
|
3519
3763
|
namespace: this.namespace,
|
|
3520
3764
|
clock: this.clock
|
|
@@ -4126,10 +4370,8 @@ var WorkflowEngine = class {
|
|
|
4126
4370
|
*/
|
|
4127
4371
|
async findParentRunId(childRunId) {
|
|
4128
4372
|
const token = `child:${childRunId}`;
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
}
|
|
4132
|
-
return void 0;
|
|
4373
|
+
const waiters = await this.store.listSignalWaiters(token);
|
|
4374
|
+
return waiters.find((waiter) => waiter.token === token)?.runId;
|
|
4133
4375
|
}
|
|
4134
4376
|
/** Track an in-flight execution so {@link drain} can wait for it. */
|
|
4135
4377
|
track(p) {
|
|
@@ -4141,15 +4383,34 @@ var WorkflowEngine = class {
|
|
|
4141
4383
|
return p;
|
|
4142
4384
|
}
|
|
4143
4385
|
/**
|
|
4386
|
+
* Stop picking up new work without waiting: recovery, timer, schedule and timeout sweeps (and
|
|
4387
|
+
* their per-run lock loops) start nothing once this is called, and {@link runOne} returns null. A
|
|
4388
|
+
* poller calls it on its own shutdown so an in-flight sweep stops locking runs before it is awaited.
|
|
4389
|
+
*
|
|
4390
|
+
* `spentMs` is wait time ALREADY spent on this shutdown (the poller awaiting its in-flight sweep).
|
|
4391
|
+
* It is deducted from the budget of a later {@link drain}, so that wait plus the drain stay within
|
|
4392
|
+
* one `shutdownTimeoutMs`. Only time actually spent waiting is charged: time that merely passes
|
|
4393
|
+
* between this call and `drain()` (other hooks, the HTTP server closing) never is. Calls add up.
|
|
4394
|
+
*/
|
|
4395
|
+
beginDrain(spentMs = 0) {
|
|
4396
|
+
this.draining = true;
|
|
4397
|
+
this.drainSpentMs += Math.max(0, spentMs);
|
|
4398
|
+
}
|
|
4399
|
+
/**
|
|
4144
4400
|
* Graceful shutdown: stop picking up new runs (recovery/timer become no-ops) and wait for
|
|
4145
4401
|
* in-flight executions to settle, up to `timeoutMs`. Call from your app's shutdown hook so a
|
|
4146
4402
|
* deploy hands off cleanly instead of leaving runs to the lease timeout.
|
|
4403
|
+
*
|
|
4404
|
+
* `timeoutMs` is the total budget for waiting on in-flight runs; any wait already reported through
|
|
4405
|
+
* {@link beginDrain} (`spentMs`) is deducted, so a shutdown that already waited on the timer poll
|
|
4406
|
+
* for part of it waits only for the remainder. With nothing reported, the full `timeoutMs` applies.
|
|
4147
4407
|
*/
|
|
4148
4408
|
async drain(timeoutMs = 1e4) {
|
|
4149
|
-
this.
|
|
4409
|
+
this.beginDrain();
|
|
4410
|
+
const budget = Math.max(0, timeoutMs - this.drainSpentMs);
|
|
4150
4411
|
if (this.inflight.size === 0) return;
|
|
4151
4412
|
const timer = new Promise((resolve) => {
|
|
4152
|
-
const t = setTimeout(resolve,
|
|
4413
|
+
const t = setTimeout(resolve, budget);
|
|
4153
4414
|
t.unref?.();
|
|
4154
4415
|
});
|
|
4155
4416
|
await Promise.race([
|
|
@@ -4191,6 +4452,7 @@ var WorkflowEngine = class {
|
|
|
4191
4452
|
*/
|
|
4192
4453
|
async sweepTimeouts(now = this.clock()) {
|
|
4193
4454
|
for (const reg of new Set(this.latest.values())) {
|
|
4455
|
+
if (this.draining) return;
|
|
4194
4456
|
if (reg.executionTimeoutMs == null) continue;
|
|
4195
4457
|
const deadline = now - reg.executionTimeoutMs;
|
|
4196
4458
|
const inflight = [
|
|
@@ -4206,6 +4468,7 @@ var WorkflowEngine = class {
|
|
|
4206
4468
|
})
|
|
4207
4469
|
];
|
|
4208
4470
|
for (const run of inflight) {
|
|
4471
|
+
if (this.draining) return;
|
|
4209
4472
|
if (run.createdAt.getTime() > deadline) continue;
|
|
4210
4473
|
const error2 = {
|
|
4211
4474
|
message: "execution timeout",
|
|
@@ -4235,6 +4498,7 @@ var WorkflowEngine = class {
|
|
|
4235
4498
|
if (this.draining) return [];
|
|
4236
4499
|
const results = [];
|
|
4237
4500
|
for (const listed of await this.store.listIncompleteRuns(this.namespace)) {
|
|
4501
|
+
if (this.draining) break;
|
|
4238
4502
|
const acquired = await this.store.tryLockRun(listed.id, this.instanceId, nowMs + this.leaseMs, nowMs);
|
|
4239
4503
|
if (!acquired) continue;
|
|
4240
4504
|
const run = await this.store.getRun(listed.id);
|
|
@@ -4350,7 +4614,10 @@ var WorkflowEngine = class {
|
|
|
4350
4614
|
const due = await this.store.listDueTimers(nowMs, this.namespace);
|
|
4351
4615
|
const blocked = await this.dueBlockedRuns(nowMs);
|
|
4352
4616
|
if (due.length === 0 && blocked.length === 0) return [];
|
|
4353
|
-
const waiterByRun = indexWaitersByRun(await this.store
|
|
4617
|
+
const waiterByRun = indexWaitersByRun(await listSignalWaitersOfRuns(this.store, [
|
|
4618
|
+
...due,
|
|
4619
|
+
...blocked
|
|
4620
|
+
].map((run) => run.id)));
|
|
4354
4621
|
return this.resumeLeased([
|
|
4355
4622
|
...due,
|
|
4356
4623
|
...blocked
|
|
@@ -4460,11 +4727,6 @@ var WorkflowEngine = class {
|
|
|
4460
4727
|
} : {}
|
|
4461
4728
|
});
|
|
4462
4729
|
} else if (cp.kind === "signal" && cp.name.startsWith("signal:child:") && cp.status === "completed" && isFailureCompletion(cp.output)) {
|
|
4463
|
-
await this.store.saveCheckpoint({
|
|
4464
|
-
...cp,
|
|
4465
|
-
status: "running",
|
|
4466
|
-
output: void 0
|
|
4467
|
-
});
|
|
4468
4730
|
const childId = cp.name.slice("signal:child:".length);
|
|
4469
4731
|
const parked = [];
|
|
4470
4732
|
for (; ; ) {
|
|
@@ -4472,8 +4734,28 @@ var WorkflowEngine = class {
|
|
|
4472
4734
|
if (!buffered) break;
|
|
4473
4735
|
parked.push(buffered.payload);
|
|
4474
4736
|
}
|
|
4737
|
+
const successAt = parked.findIndex((payload) => typeof payload === "object" && payload !== null && payload.ok === true);
|
|
4738
|
+
const success = successAt === -1 ? void 0 : parked.splice(successAt, 1)[0];
|
|
4475
4739
|
for (const payload of parked) await this.store.bufferSignal(`child:${childId}`, payload);
|
|
4476
|
-
const bufferedSuccess =
|
|
4740
|
+
const bufferedSuccess = success !== void 0;
|
|
4741
|
+
if (bufferedSuccess) {
|
|
4742
|
+
await this.store.saveCheckpoint({
|
|
4743
|
+
...cp,
|
|
4744
|
+
output: success
|
|
4745
|
+
});
|
|
4746
|
+
} else {
|
|
4747
|
+
await this.store.putSignalWaiter({
|
|
4748
|
+
token: `child:${childId}`,
|
|
4749
|
+
runId,
|
|
4750
|
+
seq: cp.seq,
|
|
4751
|
+
parallelGroup: cp.parallelGroup
|
|
4752
|
+
});
|
|
4753
|
+
await this.store.saveCheckpoint({
|
|
4754
|
+
...cp,
|
|
4755
|
+
status: "running",
|
|
4756
|
+
output: void 0
|
|
4757
|
+
});
|
|
4758
|
+
}
|
|
4477
4759
|
if (!bufferedSuccess) {
|
|
4478
4760
|
const child = await this.store.getRun(childId);
|
|
4479
4761
|
if (child && (child.status === "failed" || child.status === "dead")) {
|
|
@@ -4652,8 +4934,13 @@ var WorkflowEngine = class {
|
|
|
4652
4934
|
if (this.draining) return [];
|
|
4653
4935
|
const results = [];
|
|
4654
4936
|
for (const run of runs) {
|
|
4937
|
+
if (this.draining) break;
|
|
4655
4938
|
const acquired = await this.store.tryLockRun(run.id, this.instanceId, nowMs + this.leaseMs, nowMs);
|
|
4656
4939
|
if (!acquired) continue;
|
|
4940
|
+
if (this.draining) {
|
|
4941
|
+
await this.store.releaseRunLock(run.id);
|
|
4942
|
+
break;
|
|
4943
|
+
}
|
|
4657
4944
|
try {
|
|
4658
4945
|
const settled = onLocked ? await onLocked(run) : void 0;
|
|
4659
4946
|
results.push(settled ?? await this.resumeImplicit(run.id));
|
|
@@ -4753,12 +5040,62 @@ var WorkflowEngine = class {
|
|
|
4753
5040
|
// for an ordinary (non-fan) signal/child await.
|
|
4754
5041
|
parallelGroup: waiter.parallelGroup
|
|
4755
5042
|
}));
|
|
5043
|
+
if (await this.fanStillWaiting(waiter, payload)) {
|
|
5044
|
+
return {
|
|
5045
|
+
runId: waiter.runId,
|
|
5046
|
+
status: "suspended"
|
|
5047
|
+
};
|
|
5048
|
+
}
|
|
4756
5049
|
return this.resumeImplicit(waiter.runId).catch((err) => {
|
|
4757
5050
|
if (err instanceof NamespaceMismatch) return null;
|
|
4758
5051
|
throw err;
|
|
4759
5052
|
});
|
|
4760
5053
|
}
|
|
4761
5054
|
/**
|
|
5055
|
+
* The `ctx.all` join barrier: true when `waiter` was one child of a fan (or of one wave of it)
|
|
5056
|
+
* whose siblings are still out, so waking the parent now would only replay the fan and re-suspend.
|
|
5057
|
+
* Skipping that wake is what keeps a fan of N children at O(N) work instead of N replays of N items.
|
|
5058
|
+
*
|
|
5059
|
+
* Safe because the LAST sibling to settle always wakes the parent: each child consumes its own
|
|
5060
|
+
* waiter before it asks, so whichever asks last sees none left. A failed child of a failFast fan
|
|
5061
|
+
* always wakes it. Anything that is not a `ctx.all` waiter, or a store that cannot answer the
|
|
5062
|
+
* question cheaply, wakes the parent as before.
|
|
5063
|
+
*/
|
|
5064
|
+
async fanStillWaiting(waiter, payload) {
|
|
5065
|
+
if (!this.store.hasSignalWaiter) return false;
|
|
5066
|
+
const fan = fanOfWaiter(waiter);
|
|
5067
|
+
if (!fan) return false;
|
|
5068
|
+
if (fan.failFast && isFailedCompletion(payload)) return false;
|
|
5069
|
+
return this.store.hasSignalWaiter(waiter.runId, fan.waiterPrefix);
|
|
5070
|
+
}
|
|
5071
|
+
/**
|
|
5072
|
+
* Re-drive a run that just suspended on awaited children (a `ctx.all` fan or wave, a `ctx.child`)
|
|
5073
|
+
* which, by now, have settled.
|
|
5074
|
+
*
|
|
5075
|
+
* {@link execute} drops a wake that finds the run's lease held. So if a child settles while the
|
|
5076
|
+
* parent is still executing (the turn that dispatched it, or one woken by an earlier child), that
|
|
5077
|
+
* wake is spent, and the turn suspends on a snapshot that predates the child. With the `ctx.all`
|
|
5078
|
+
* barrier that wake was the only one the fan would get. Nothing would wake the run until the
|
|
5079
|
+
* `reconcileMs` sweep, minutes later.
|
|
5080
|
+
*
|
|
5081
|
+
* Called after the turn released its lease, so any child that settles from here on wakes the run
|
|
5082
|
+
* itself. Only re-drives for a group with no waiter left whose checkpoints are all settled, which
|
|
5083
|
+
* the next turn is then guaranteed to see — so it cannot loop.
|
|
5084
|
+
*/
|
|
5085
|
+
async redriveSettledChildren(runId, fan) {
|
|
5086
|
+
if (!this.store.hasSignalWaiter) return;
|
|
5087
|
+
for (const wait of fan.waits) {
|
|
5088
|
+
if (await this.store.hasSignalWaiter(runId, wait.waiterPrefix)) continue;
|
|
5089
|
+
const checkpoints = this.store.listCheckpointsByNamePrefix ? await this.store.listCheckpointsByNamePrefix(runId, [
|
|
5090
|
+
wait.checkpointPrefix
|
|
5091
|
+
]) : (await this.store.listCheckpoints(runId)).filter((cp) => cp.name.startsWith(wait.checkpointPrefix));
|
|
5092
|
+
if (checkpoints.length > 0 && checkpoints.every((cp) => cp.status === "completed")) {
|
|
5093
|
+
await this.resumeImplicit(runId);
|
|
5094
|
+
return;
|
|
5095
|
+
}
|
|
5096
|
+
}
|
|
5097
|
+
}
|
|
5098
|
+
/**
|
|
4762
5099
|
* Deliver an external signal to the run waiting on `token`, resume it with `payload`, and return
|
|
4763
5100
|
* the run result — or null if no run is (or ends up) waiting for it.
|
|
4764
5101
|
*
|
|
@@ -5051,23 +5388,11 @@ var WorkflowEngine = class {
|
|
|
5051
5388
|
}
|
|
5052
5389
|
/** The given runs plus every descendant, children before parents. One waiter scan for the batch. */
|
|
5053
5390
|
async collectSubtrees(rootIds) {
|
|
5054
|
-
const
|
|
5055
|
-
for (const w of await this.store.listSignalWaiters("child:")) {
|
|
5056
|
-
const list = waitersByParent.get(w.runId) ?? [];
|
|
5057
|
-
list.push(w.token.slice("child:".length));
|
|
5058
|
-
waitersByParent.set(w.runId, list);
|
|
5059
|
-
}
|
|
5060
|
-
const prefixes = [
|
|
5061
|
-
"signal:child:",
|
|
5062
|
-
"spawn:"
|
|
5063
|
-
];
|
|
5391
|
+
const prefixes = CHILD_EDGE_PREFIXES;
|
|
5064
5392
|
const childrenOf = /* @__PURE__ */ __name(async (parent) => {
|
|
5065
|
-
const ids = new Set(
|
|
5393
|
+
const ids = new Set(await this.awaitedChildIds(parent));
|
|
5066
5394
|
const cps = this.store.listCheckpointsByNamePrefix ? await this.store.listCheckpointsByNamePrefix(parent, prefixes) : (await this.store.listCheckpoints(parent)).filter((cp) => prefixes.some((p) => cp.name.startsWith(p)));
|
|
5067
|
-
for (const
|
|
5068
|
-
if (cp.name.startsWith("signal:child:")) ids.add(cp.name.slice("signal:child:".length));
|
|
5069
|
-
if (cp.name.startsWith("spawn:") && typeof cp.output === "string") ids.add(cp.output);
|
|
5070
|
-
}
|
|
5395
|
+
for (const id of childIdsOf(cps)) ids.add(id);
|
|
5071
5396
|
return [
|
|
5072
5397
|
...ids
|
|
5073
5398
|
];
|
|
@@ -5117,22 +5442,20 @@ var WorkflowEngine = class {
|
|
|
5117
5442
|
* so a completed parent (or a completed awaited child) would otherwise drop out of the tree. The
|
|
5118
5443
|
* `signal:child:<id>` checkpoint (the placeholder written when the parent first awaits the child,
|
|
5119
5444
|
* overwritten as completed/failed when it settles) persists across completion, so we read both and
|
|
5120
|
-
* dedupe — the edge stays stable for a finished run, not just a live one.
|
|
5445
|
+
* dedupe — the edge stays stable for a finished run, not just a live one. A settled `ctx.all`
|
|
5446
|
+
* group folded into one `fan:` checkpoint stands for every child in it.
|
|
5121
5447
|
*/
|
|
5448
|
+
/** The children `parentRunId` is awaiting right now, read off its own live `child:` waiters. */
|
|
5449
|
+
async awaitedChildIds(parentRunId) {
|
|
5450
|
+
return (await listSignalWaitersOfRuns(this.store, [
|
|
5451
|
+
parentRunId
|
|
5452
|
+
])).filter((w) => w.token.startsWith("child:")).map((w) => w.token.slice("child:".length));
|
|
5453
|
+
}
|
|
5122
5454
|
async getRunChildren(parentRunId) {
|
|
5123
|
-
const childIds =
|
|
5124
|
-
|
|
5125
|
-
if (w.runId === parentRunId) childIds.add(w.token.slice("child:".length));
|
|
5126
|
-
}
|
|
5127
|
-
const prefixes = [
|
|
5128
|
-
"signal:child:",
|
|
5129
|
-
"spawn:"
|
|
5130
|
-
];
|
|
5455
|
+
const childIds = new Set(await this.awaitedChildIds(parentRunId));
|
|
5456
|
+
const prefixes = CHILD_EDGE_PREFIXES;
|
|
5131
5457
|
const childCheckpoints = this.store.listCheckpointsByNamePrefix ? await this.store.listCheckpointsByNamePrefix(parentRunId, prefixes) : (await this.store.listCheckpoints(parentRunId)).filter((cp) => prefixes.some((p) => cp.name.startsWith(p)));
|
|
5132
|
-
for (const
|
|
5133
|
-
if (cp.name.startsWith("signal:child:")) childIds.add(cp.name.slice("signal:child:".length));
|
|
5134
|
-
if (cp.name.startsWith("spawn:") && typeof cp.output === "string") childIds.add(cp.output);
|
|
5135
|
-
}
|
|
5458
|
+
for (const id of childIdsOf(childCheckpoints)) childIds.add(id);
|
|
5136
5459
|
return [
|
|
5137
5460
|
...childIds
|
|
5138
5461
|
];
|
|
@@ -6212,6 +6535,7 @@ var WorkflowEngine = class {
|
|
|
6212
6535
|
const snapshot = await this.store.listCheckpoints(run.id);
|
|
6213
6536
|
const replay = /* @__PURE__ */ new Map();
|
|
6214
6537
|
for (const cp of snapshot) replay.set(cp.seq, cp);
|
|
6538
|
+
let suspendedOnFan;
|
|
6215
6539
|
const ctx = createWorkflowCtx(this.ctxHostFor(replay, run.namespace), run.id, compensations, run.workflow, registered?.searchAttributesSchema);
|
|
6216
6540
|
try {
|
|
6217
6541
|
const output = await runInWorkflowCtx(ctx, () => fn(ctx, run.input));
|
|
@@ -6263,10 +6587,12 @@ var WorkflowEngine = class {
|
|
|
6263
6587
|
if (this.cancelRequested.has(run.id)) {
|
|
6264
6588
|
return await this.compensateAndCancel(run, compensations, replay);
|
|
6265
6589
|
}
|
|
6266
|
-
|
|
6590
|
+
const suspended = await this.settleRun(run, {
|
|
6267
6591
|
kind: "suspended",
|
|
6268
6592
|
wakeAt: err.wakeAt
|
|
6269
6593
|
});
|
|
6594
|
+
suspendedOnFan = err.fan;
|
|
6595
|
+
return suspended;
|
|
6270
6596
|
}
|
|
6271
6597
|
if (this.cancelRequested.has(run.id)) {
|
|
6272
6598
|
return await this.compensateAndCancel(run, compensations, replay);
|
|
@@ -6286,6 +6612,9 @@ var WorkflowEngine = class {
|
|
|
6286
6612
|
});
|
|
6287
6613
|
} finally {
|
|
6288
6614
|
await this.store.releaseRunLock(run.id);
|
|
6615
|
+
if (suspendedOnFan) {
|
|
6616
|
+
void this.redriveSettledChildren(run.id, suspendedOnFan).catch(() => void 0);
|
|
6617
|
+
}
|
|
6289
6618
|
}
|
|
6290
6619
|
}
|
|
6291
6620
|
/**
|
|
@@ -7100,6 +7429,21 @@ var RemoteStepError = class extends Error {
|
|
|
7100
7429
|
this.stepError = stepError;
|
|
7101
7430
|
}
|
|
7102
7431
|
};
|
|
7432
|
+
var CHILD_EDGE_PREFIXES = [
|
|
7433
|
+
"signal:child:",
|
|
7434
|
+
FOLDED_PREFIX,
|
|
7435
|
+
"spawn:"
|
|
7436
|
+
];
|
|
7437
|
+
function childIdsOf(checkpoints) {
|
|
7438
|
+
const ids = [];
|
|
7439
|
+
for (const cp of checkpoints) {
|
|
7440
|
+
if (cp.name.startsWith("signal:child:")) ids.push(cp.name.slice("signal:child:".length));
|
|
7441
|
+
if (cp.name.startsWith(FOLDED_PREFIX)) ids.push(...foldedChildIds(cp));
|
|
7442
|
+
if (cp.name.startsWith("spawn:") && typeof cp.output === "string") ids.push(cp.output);
|
|
7443
|
+
}
|
|
7444
|
+
return ids;
|
|
7445
|
+
}
|
|
7446
|
+
__name(childIdsOf, "childIdsOf");
|
|
7103
7447
|
|
|
7104
7448
|
// src/run-facets.ts
|
|
7105
7449
|
function facetOrigin(origin) {
|
|
@@ -7249,6 +7593,14 @@ var CodecStateStore = class {
|
|
|
7249
7593
|
/** Forwarded verbatim — a count reads no payload. Bound only when the inner store counts, so the
|
|
7250
7594
|
* engine's facet/listing fallback still applies through the wrapper. */
|
|
7251
7595
|
countRuns;
|
|
7596
|
+
/** The placeholder checkpoints are encoded like any other checkpoint. */
|
|
7597
|
+
registerChildWaits;
|
|
7598
|
+
/** The folded checkpoint carries the children's outputs, so it is encoded. */
|
|
7599
|
+
compactCheckpoints;
|
|
7600
|
+
/** Forwarded verbatim — a waiter carries no payload. */
|
|
7601
|
+
listSignalWaitersByRun;
|
|
7602
|
+
/** Forwarded verbatim — a waiter carries no payload. */
|
|
7603
|
+
hasSignalWaiter;
|
|
7252
7604
|
/** Schedules persist their run `input` inside `spec` — encoded like any other payload. Bound only
|
|
7253
7605
|
* when the inner store persists schedules, so `engine.schedules` reports absence faithfully. */
|
|
7254
7606
|
saveSchedule;
|
|
@@ -7266,6 +7618,17 @@ var CodecStateStore = class {
|
|
|
7266
7618
|
if (prune) this.pruneTerminalRuns = (policy, now, limit) => prune.call(inner, policy, now, limit);
|
|
7267
7619
|
const count = inner.countRuns;
|
|
7268
7620
|
if (count) this.countRuns = (query) => count.call(inner, query);
|
|
7621
|
+
const registerChildWaits = inner.registerChildWaits;
|
|
7622
|
+
if (registerChildWaits) this.registerChildWaits = (entries) => registerChildWaits.call(inner, entries.map((e) => ({
|
|
7623
|
+
waiter: e.waiter,
|
|
7624
|
+
checkpoint: this.encCp(e.checkpoint)
|
|
7625
|
+
})));
|
|
7626
|
+
const compact = inner.compactCheckpoints;
|
|
7627
|
+
if (compact) this.compactCheckpoints = (runId, fromSeq, toSeq, into) => compact.call(inner, runId, fromSeq, toSeq, this.encCp(into));
|
|
7628
|
+
const byRun = inner.listSignalWaitersByRun;
|
|
7629
|
+
if (byRun) this.listSignalWaitersByRun = (runIds) => byRun.call(inner, runIds);
|
|
7630
|
+
const hasWaiter = inner.hasSignalWaiter;
|
|
7631
|
+
if (hasWaiter) this.hasSignalWaiter = (runId, tokenPrefix) => hasWaiter.call(inner, runId, tokenPrefix);
|
|
7269
7632
|
const { saveSchedule, getSchedule, updateSchedule, deleteSchedule, listSchedules } = inner;
|
|
7270
7633
|
if (saveSchedule && getSchedule && updateSchedule && deleteSchedule && listSchedules) {
|
|
7271
7634
|
this.saveSchedule = (r) => saveSchedule.call(inner, this.encSchedule(r));
|
|
@@ -7876,6 +8239,33 @@ var InMemoryStateStore = class {
|
|
|
7876
8239
|
...w
|
|
7877
8240
|
}));
|
|
7878
8241
|
}
|
|
8242
|
+
async registerChildWaits(entries) {
|
|
8243
|
+
for (const { waiter } of entries) this.signalWaiters.set(waiter.token, {
|
|
8244
|
+
...waiter
|
|
8245
|
+
});
|
|
8246
|
+
for (const { checkpoint } of entries) await this.saveCheckpoint(checkpoint);
|
|
8247
|
+
}
|
|
8248
|
+
async compactCheckpoints(runId, fromSeq, toSeq, into) {
|
|
8249
|
+
for (let seq = fromSeq; seq <= toSeq; seq += 1) this.checkpoints.delete(this.key(runId, seq));
|
|
8250
|
+
this.checkpoints.set(this.key(runId, into.seq), {
|
|
8251
|
+
...into,
|
|
8252
|
+
runId
|
|
8253
|
+
});
|
|
8254
|
+
}
|
|
8255
|
+
async listSignalWaitersByRun(runIds) {
|
|
8256
|
+
const ids = new Set(runIds);
|
|
8257
|
+
return [
|
|
8258
|
+
...this.signalWaiters.values()
|
|
8259
|
+
].filter((w) => ids.has(w.runId)).map((w) => ({
|
|
8260
|
+
...w
|
|
8261
|
+
}));
|
|
8262
|
+
}
|
|
8263
|
+
async hasSignalWaiter(runId, tokenPrefix) {
|
|
8264
|
+
for (const w of this.signalWaiters.values()) {
|
|
8265
|
+
if (w.runId === runId && w.token.startsWith(tokenPrefix)) return true;
|
|
8266
|
+
}
|
|
8267
|
+
return false;
|
|
8268
|
+
}
|
|
7879
8269
|
async removeSignalWaiter(waiter) {
|
|
7880
8270
|
const current = this.signalWaiters.get(waiter.token);
|
|
7881
8271
|
if (current && current.runId === waiter.runId && current.seq === waiter.seq) {
|
|
@@ -8230,6 +8620,7 @@ export {
|
|
|
8230
8620
|
isLegacyDescriptor,
|
|
8231
8621
|
isNonEmptyRunScope,
|
|
8232
8622
|
isWorkflowControlFlowSignal,
|
|
8623
|
+
listSignalWaitersOfRuns,
|
|
8233
8624
|
log,
|
|
8234
8625
|
matchesAttributes,
|
|
8235
8626
|
mergeRunFacetRows,
|
|
@@ -8240,6 +8631,7 @@ export {
|
|
|
8240
8631
|
normalizeAttributeRows,
|
|
8241
8632
|
normalizeDescriptor,
|
|
8242
8633
|
observedAnnouncements,
|
|
8634
|
+
parentRunIdOf,
|
|
8243
8635
|
parseDuration,
|
|
8244
8636
|
partitionOfToken,
|
|
8245
8637
|
planDispatch,
|