@dudousxd/nestjs-durable-core 0.80.1 → 0.82.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 +683 -123
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +357 -21
- package/dist/index.d.ts +357 -21
- package/dist/index.js +672 -123
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -30,12 +30,14 @@ __export(index_exports, {
|
|
|
30
30
|
ContinueAsNew: () => ContinueAsNew,
|
|
31
31
|
DEFAULT_CONCURRENCY_STATUSES: () => DEFAULT_CONCURRENCY_STATUSES,
|
|
32
32
|
DEFAULT_PARTITION: () => DEFAULT_PARTITION,
|
|
33
|
+
DEFAULT_STALLED_AFTER_MS: () => DEFAULT_STALLED_AFTER_MS,
|
|
33
34
|
DURABLE_OPTIONS: () => DURABLE_OPTIONS,
|
|
34
35
|
DURABLE_OPTIONS_CANONICAL: () => DURABLE_OPTIONS_CANONICAL,
|
|
35
36
|
DURABLE_STEP_CONFIG: () => DURABLE_STEP_CONFIG,
|
|
36
37
|
DURABLE_STEP_NAME: () => DURABLE_STEP_NAME,
|
|
37
38
|
DurableWorkflow: () => DurableWorkflow,
|
|
38
39
|
ENGINE_MINTED_TAG_PREFIXES: () => ENGINE_MINTED_TAG_PREFIXES,
|
|
40
|
+
ENTITY_WORKFLOW: () => ENTITY_WORKFLOW,
|
|
39
41
|
EVENT_BUFFER_SCAN_LIMIT: () => EVENT_BUFFER_SCAN_LIMIT,
|
|
40
42
|
EVENT_TOKEN_PREFIX: () => EVENT_TOKEN_PREFIX,
|
|
41
43
|
Entities: () => Entities,
|
|
@@ -52,6 +54,7 @@ __export(index_exports, {
|
|
|
52
54
|
NestedWorkflowCallError: () => NestedWorkflowCallError,
|
|
53
55
|
NonDeterminismError: () => NonDeterminismError,
|
|
54
56
|
QueueController: () => QueueController,
|
|
57
|
+
RUN_HISTORY_FIXTURE_VERSION: () => RUN_HISTORY_FIXTURE_VERSION,
|
|
55
58
|
RUN_VALUE_FACET_LIMIT: () => RUN_VALUE_FACET_LIMIT,
|
|
56
59
|
RUN_VALUE_FACET_SCAN: () => RUN_VALUE_FACET_SCAN,
|
|
57
60
|
RemoteStepError: () => RemoteStepError,
|
|
@@ -61,6 +64,8 @@ __export(index_exports, {
|
|
|
61
64
|
RunGateway: () => RunGateway,
|
|
62
65
|
SINGLETON_ACTIVE_STATUSES: () => SINGLETON_ACTIVE_STATUSES,
|
|
63
66
|
SINGLETON_ADMITTED_TAG: () => SINGLETON_ADMITTED_TAG,
|
|
67
|
+
SINGLETON_RESERVED_TAGS: () => SINGLETON_RESERVED_TAGS,
|
|
68
|
+
SINGLETON_TRACKED_TAG: () => SINGLETON_TRACKED_TAG,
|
|
64
69
|
STATE_STORE: () => STATE_STORE,
|
|
65
70
|
STATE_STORE_CANONICAL: () => STATE_STORE_CANONICAL,
|
|
66
71
|
SUMMARY_CHILD_LIMIT: () => SUMMARY_CHILD_LIMIT,
|
|
@@ -123,6 +128,7 @@ __export(index_exports, {
|
|
|
123
128
|
facetOrigin: () => facetOrigin,
|
|
124
129
|
fanPageBounds: () => fanPageBounds,
|
|
125
130
|
fanPageEtag: () => fanPageEtag,
|
|
131
|
+
findStalledRuns: () => findStalledRuns,
|
|
126
132
|
foldedCount: () => foldedCount,
|
|
127
133
|
handoffHandlerId: () => handoffHandlerId,
|
|
128
134
|
handoffName: () => handoffName,
|
|
@@ -136,6 +142,7 @@ __export(index_exports, {
|
|
|
136
142
|
isLegacyDescriptor: () => isLegacyDescriptor,
|
|
137
143
|
isNonEmptyRunScope: () => isNonEmptyRunScope,
|
|
138
144
|
isWorkflowControlFlowSignal: () => isWorkflowControlFlowSignal,
|
|
145
|
+
legacySingletonHolders: () => legacySingletonHolders,
|
|
139
146
|
listSignalWaitersOfRuns: () => listSignalWaitersOfRuns,
|
|
140
147
|
liveHandoff: () => liveHandoff,
|
|
141
148
|
log: () => log,
|
|
@@ -153,8 +160,10 @@ __export(index_exports, {
|
|
|
153
160
|
pageFanFromStore: () => pageFanFromStore,
|
|
154
161
|
parentRunIdOf: () => parentRunIdOf,
|
|
155
162
|
parseDuration: () => parseDuration,
|
|
163
|
+
parseWebhookToken: () => parseWebhookToken,
|
|
156
164
|
partitionOfToken: () => partitionOfToken,
|
|
157
165
|
planDispatch: () => planDispatch,
|
|
166
|
+
poolNamespace: () => poolNamespace,
|
|
158
167
|
prevCronFireMs: () => prevCronFireMs,
|
|
159
168
|
queryCheckpointsOf: () => queryCheckpointsOf,
|
|
160
169
|
readSearchAttributes: () => readSearchAttributes,
|
|
@@ -190,6 +199,8 @@ __export(index_exports, {
|
|
|
190
199
|
tenantGroup: () => tenantGroup,
|
|
191
200
|
useDurableExecution: () => useDurableExecution,
|
|
192
201
|
warn: () => warn,
|
|
202
|
+
withSingletonTracked: () => withSingletonTracked,
|
|
203
|
+
withoutSingletonMarkers: () => withoutSingletonMarkers,
|
|
193
204
|
workflowName: () => workflowName
|
|
194
205
|
});
|
|
195
206
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -1121,6 +1132,9 @@ async function assertConcurrency(store, workflow, quota) {
|
|
|
1121
1132
|
__name(assertConcurrency, "assertConcurrency");
|
|
1122
1133
|
|
|
1123
1134
|
// src/entities.ts
|
|
1135
|
+
var NUDGE = {
|
|
1136
|
+
nudge: true
|
|
1137
|
+
};
|
|
1124
1138
|
var ENTITY_WORKFLOW = "__entity";
|
|
1125
1139
|
var entityRunId = /* @__PURE__ */ __name((name, key) => `entity:${name}:${key}`, "entityRunId");
|
|
1126
1140
|
var Entities = class {
|
|
@@ -1128,9 +1142,11 @@ var Entities = class {
|
|
|
1128
1142
|
__name(this, "Entities");
|
|
1129
1143
|
}
|
|
1130
1144
|
host;
|
|
1145
|
+
workflowHost;
|
|
1131
1146
|
configs = /* @__PURE__ */ new Map();
|
|
1132
|
-
constructor(host) {
|
|
1147
|
+
constructor(host, workflowHost) {
|
|
1133
1148
|
this.host = host;
|
|
1149
|
+
this.workflowHost = workflowHost;
|
|
1134
1150
|
host.register(ENTITY_WORKFLOW, "1", (ctx, input) => this.run(ctx, input));
|
|
1135
1151
|
}
|
|
1136
1152
|
register(name, config) {
|
|
@@ -1152,6 +1168,52 @@ var Entities = class {
|
|
|
1152
1168
|
}
|
|
1153
1169
|
});
|
|
1154
1170
|
}
|
|
1171
|
+
/**
|
|
1172
|
+
* The durable half of a send from INSIDE a workflow (`ctx.callEntity` / `ctx.signalEntity`): write
|
|
1173
|
+
* the op to the entity's signal buffer and return once it is stored, so the caller can checkpoint
|
|
1174
|
+
* after it. The op carries `id`, the caller's stable `<runId>:<seq>`, so if the caller dies between
|
|
1175
|
+
* this write and its checkpoint and sends again on replay, the entity skips the second copy. The
|
|
1176
|
+
* entity's run is also ensured here, awaited (`start` only enqueues, it never runs the body
|
|
1177
|
+
* inline), so an op sent on first contact can't be stranded in a buffer no run will ever read if
|
|
1178
|
+
* the process dies right after. The wake-up ({@link kick}) is separate and may be lost: a busy
|
|
1179
|
+
* entity reads the buffer at its next wait, and a parked one is paired with it by the engine's
|
|
1180
|
+
* `reconcileMs` sweep. Returns `false` on a host without a workflow host.
|
|
1181
|
+
*/
|
|
1182
|
+
async enqueue(name, key, op, arg, id, reply) {
|
|
1183
|
+
if (!this.workflowHost) return false;
|
|
1184
|
+
const message = {
|
|
1185
|
+
op,
|
|
1186
|
+
arg,
|
|
1187
|
+
id,
|
|
1188
|
+
...reply ? {
|
|
1189
|
+
reply
|
|
1190
|
+
} : {}
|
|
1191
|
+
};
|
|
1192
|
+
const runId = entityRunId(name, key);
|
|
1193
|
+
await this.workflowHost.bufferSignal(runId, message);
|
|
1194
|
+
await this.workflowHost.ensureStarted(ENTITY_WORKFLOW, {
|
|
1195
|
+
name,
|
|
1196
|
+
key
|
|
1197
|
+
}, runId);
|
|
1198
|
+
return true;
|
|
1199
|
+
}
|
|
1200
|
+
/**
|
|
1201
|
+
* Make the entity act on what {@link enqueue} buffered: wake its run if it is parked on its wait
|
|
1202
|
+
* (the run itself was ensured by `enqueue`; ensured again here, idempotently, for a caller replay
|
|
1203
|
+
* that only re-kicks). A run that is busy needs nothing: its next wait reads the buffer before
|
|
1204
|
+
* suspending. The wake carries a no-op `nudge`, never the op itself, so the op stays in
|
|
1205
|
+
* the buffer in arrival order and a lost or repeated kick costs at most one empty iteration.
|
|
1206
|
+
*/
|
|
1207
|
+
async kick(name, key) {
|
|
1208
|
+
const host = this.workflowHost;
|
|
1209
|
+
if (!host) return;
|
|
1210
|
+
const runId = entityRunId(name, key);
|
|
1211
|
+
await host.ensureStarted(ENTITY_WORKFLOW, {
|
|
1212
|
+
name,
|
|
1213
|
+
key
|
|
1214
|
+
}, runId);
|
|
1215
|
+
await host.wakeWaiter(runId, NUDGE);
|
|
1216
|
+
}
|
|
1155
1217
|
async signal(name, key, op, arg) {
|
|
1156
1218
|
await this.dispatch(name, key, op, arg);
|
|
1157
1219
|
}
|
|
@@ -1165,11 +1227,18 @@ var Entities = class {
|
|
|
1165
1227
|
if (!config) throw new FatalError(`entity "${name}" is not registered`);
|
|
1166
1228
|
const token = entityRunId(name, key);
|
|
1167
1229
|
let state = config.initialState();
|
|
1230
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1168
1231
|
for (let i = 0; ; i += 1) {
|
|
1169
1232
|
const msg = await ctx.waitForSignal(token);
|
|
1233
|
+
if (msg.nudge) continue;
|
|
1234
|
+
if (msg.id !== void 0) {
|
|
1235
|
+
if (seen.has(msg.id)) continue;
|
|
1236
|
+
seen.add(msg.id);
|
|
1237
|
+
}
|
|
1238
|
+
const op = msg.op;
|
|
1170
1239
|
const out = await ctx.localStep(`op:${i}`, async () => {
|
|
1171
|
-
const handler = config.handlers[
|
|
1172
|
-
if (!handler) throw new FatalError(`entity "${name}" has no handler for op "${
|
|
1240
|
+
const handler = config.handlers[op];
|
|
1241
|
+
if (!handler) throw new FatalError(`entity "${name}" has no handler for op "${op}"`);
|
|
1173
1242
|
const result = await handler(state, msg.arg);
|
|
1174
1243
|
return {
|
|
1175
1244
|
result,
|
|
@@ -1179,8 +1248,9 @@ var Entities = class {
|
|
|
1179
1248
|
state = out.state;
|
|
1180
1249
|
await ctx.setEvent("state", state);
|
|
1181
1250
|
if (msg.reply) {
|
|
1251
|
+
const reply = msg.reply;
|
|
1182
1252
|
await ctx.localStep(`reply:${i}`, async () => {
|
|
1183
|
-
await this.host.signal(
|
|
1253
|
+
await this.host.signal(reply, out.result);
|
|
1184
1254
|
});
|
|
1185
1255
|
}
|
|
1186
1256
|
}
|
|
@@ -2104,6 +2174,10 @@ function checkpointNameKind(name) {
|
|
|
2104
2174
|
return "step";
|
|
2105
2175
|
}
|
|
2106
2176
|
__name(checkpointNameKind, "checkpointNameKind");
|
|
2177
|
+
function poolNamespace(namespace) {
|
|
2178
|
+
return namespace === "" ? "default" : namespace;
|
|
2179
|
+
}
|
|
2180
|
+
__name(poolNamespace, "poolNamespace");
|
|
2107
2181
|
var TERMINAL_RUN_STATUSES = [
|
|
2108
2182
|
"completed",
|
|
2109
2183
|
"failed",
|
|
@@ -2235,6 +2309,19 @@ var WEBHOOK_PREFIX = "wh:";
|
|
|
2235
2309
|
var CHILD_PREFIX = "child:";
|
|
2236
2310
|
var BREAKPOINT_PREFIX = "bp:";
|
|
2237
2311
|
var EVENT_PREFIX = EVENT_TOKEN_PREFIX;
|
|
2312
|
+
function parseWebhookToken(token) {
|
|
2313
|
+
if (!token.startsWith(WEBHOOK_PREFIX)) return null;
|
|
2314
|
+
const rest = token.slice(WEBHOOK_PREFIX.length);
|
|
2315
|
+
const at = rest.lastIndexOf(":");
|
|
2316
|
+
if (at <= 0) return null;
|
|
2317
|
+
const seqText = rest.slice(at + 1);
|
|
2318
|
+
if (!/^\d+$/.test(seqText)) return null;
|
|
2319
|
+
return {
|
|
2320
|
+
runId: rest.slice(0, at),
|
|
2321
|
+
seq: Number(seqText)
|
|
2322
|
+
};
|
|
2323
|
+
}
|
|
2324
|
+
__name(parseWebhookToken, "parseWebhookToken");
|
|
2238
2325
|
function classifyWaiterToken(token) {
|
|
2239
2326
|
if (token.startsWith(WEBHOOK_PREFIX)) return {
|
|
2240
2327
|
on: "webhook",
|
|
@@ -2772,6 +2859,11 @@ __name(errorMessage, "errorMessage");
|
|
|
2772
2859
|
|
|
2773
2860
|
// src/singleton-admission.ts
|
|
2774
2861
|
var SINGLETON_ADMITTED_TAG = "durable:singleton:admitted";
|
|
2862
|
+
var SINGLETON_TRACKED_TAG = "durable:singleton:tracked";
|
|
2863
|
+
var SINGLETON_RESERVED_TAGS = [
|
|
2864
|
+
SINGLETON_ADMITTED_TAG,
|
|
2865
|
+
SINGLETON_TRACKED_TAG
|
|
2866
|
+
];
|
|
2775
2867
|
var SINGLETON_ACTIVE_STATUSES = [
|
|
2776
2868
|
"pending",
|
|
2777
2869
|
"running",
|
|
@@ -2779,6 +2871,33 @@ var SINGLETON_ACTIVE_STATUSES = [
|
|
|
2779
2871
|
"blocked",
|
|
2780
2872
|
"cancelling"
|
|
2781
2873
|
];
|
|
2874
|
+
var LEGACY_SCAN_STATUSES = [
|
|
2875
|
+
"running",
|
|
2876
|
+
"suspended",
|
|
2877
|
+
"cancelling"
|
|
2878
|
+
];
|
|
2879
|
+
var byAdmissionOrder = /* @__PURE__ */ __name((a, b) => a.createdAt.getTime() - b.createdAt.getTime() || a.id.localeCompare(b.id), "byAdmissionOrder");
|
|
2880
|
+
function withoutSingletonMarkers(tags) {
|
|
2881
|
+
return tags?.filter((tag) => !SINGLETON_RESERVED_TAGS.includes(tag));
|
|
2882
|
+
}
|
|
2883
|
+
__name(withoutSingletonMarkers, "withoutSingletonMarkers");
|
|
2884
|
+
function withSingletonTracked(tags) {
|
|
2885
|
+
const current = tags ?? [];
|
|
2886
|
+
if (current.includes(SINGLETON_ADMITTED_TAG) || current.includes(SINGLETON_TRACKED_TAG)) {
|
|
2887
|
+
return current;
|
|
2888
|
+
}
|
|
2889
|
+
return [
|
|
2890
|
+
...current,
|
|
2891
|
+
SINGLETON_TRACKED_TAG
|
|
2892
|
+
];
|
|
2893
|
+
}
|
|
2894
|
+
__name(withSingletonTracked, "withSingletonTracked");
|
|
2895
|
+
function legacySingletonHolders(active, runId, limit) {
|
|
2896
|
+
const scanned = active.filter((run) => LEGACY_SCAN_STATUSES.includes(run.status)).sort(byAdmissionOrder);
|
|
2897
|
+
const window = new Set(scanned.slice(0, limit).map((run) => run.id));
|
|
2898
|
+
return scanned.filter((run) => run.id !== runId && !run.tags?.includes(SINGLETON_ADMITTED_TAG) && (!run.tags?.includes(SINGLETON_TRACKED_TAG) || window.has(run.id)));
|
|
2899
|
+
}
|
|
2900
|
+
__name(legacySingletonHolders, "legacySingletonHolders");
|
|
2782
2901
|
function singletonAdmissionTags(runs, runId, tag, workflow, limit) {
|
|
2783
2902
|
if (!Number.isInteger(limit) || limit < 1) throw new Error("singleton limit must be a positive integer");
|
|
2784
2903
|
const active = runs.filter((run) => run.workflow === workflow && SINGLETON_ACTIVE_STATUSES.includes(run.status) && run.tags?.includes(tag));
|
|
@@ -2787,12 +2906,13 @@ function singletonAdmissionTags(runs, runId, tag, workflow, limit) {
|
|
|
2787
2906
|
if (candidate.tags?.includes(SINGLETON_ADMITTED_TAG)) return candidate.tags;
|
|
2788
2907
|
if (candidate.status === "cancelling") return null;
|
|
2789
2908
|
const holders = active.filter((run) => run.tags?.includes(SINGLETON_ADMITTED_TAG));
|
|
2790
|
-
const
|
|
2909
|
+
const legacy = new Set(legacySingletonHolders(active, runId, limit).map((run) => run.id));
|
|
2910
|
+
const remaining = limit - holders.length - legacy.size;
|
|
2791
2911
|
if (remaining <= 0) return null;
|
|
2792
|
-
const waiters = active.filter((run) => !run.tags?.includes(SINGLETON_ADMITTED_TAG)
|
|
2912
|
+
const waiters = active.filter((run) => !run.tags?.includes(SINGLETON_ADMITTED_TAG) && !legacy.has(run.id)).sort(byAdmissionOrder);
|
|
2793
2913
|
if (!waiters.slice(0, remaining).some((run) => run.id === runId)) return null;
|
|
2794
2914
|
return [
|
|
2795
|
-
...candidate.tags ?? [],
|
|
2915
|
+
...(candidate.tags ?? []).filter((t) => t !== SINGLETON_TRACKED_TAG),
|
|
2796
2916
|
SINGLETON_ADMITTED_TAG
|
|
2797
2917
|
];
|
|
2798
2918
|
}
|
|
@@ -2802,7 +2922,7 @@ async function clearTerminalSingletonAdmission(runId, patch, getRun) {
|
|
|
2802
2922
|
const tags = "tags" in patch ? patch.tags : (await getRun(runId))?.tags;
|
|
2803
2923
|
return {
|
|
2804
2924
|
...patch,
|
|
2805
|
-
tags: tags
|
|
2925
|
+
tags: withoutSingletonMarkers(tags)
|
|
2806
2926
|
};
|
|
2807
2927
|
}
|
|
2808
2928
|
__name(clearTerminalSingletonAdmission, "clearTerminalSingletonAdmission");
|
|
@@ -2813,6 +2933,7 @@ function singletonAdmissionDenialPatch(runs, runId, tag, workflow, retryWakeAt)
|
|
|
2813
2933
|
return {
|
|
2814
2934
|
status: "suspended",
|
|
2815
2935
|
wakeAt: retryWakeAt,
|
|
2936
|
+
tags: withSingletonTracked(candidate.tags),
|
|
2816
2937
|
updatedAt: /* @__PURE__ */ new Date()
|
|
2817
2938
|
};
|
|
2818
2939
|
}
|
|
@@ -2865,7 +2986,7 @@ var SingletonGate = class {
|
|
|
2865
2986
|
const admitted = await this.deps.store.tryAdmitSingleton(run.id, this.tag(cfg, run.input), run.workflow, cfg.limit ?? 1, this.retryWakeAt());
|
|
2866
2987
|
if (admitted && !run.tags?.includes(SINGLETON_ADMITTED_TAG)) {
|
|
2867
2988
|
run.tags = [
|
|
2868
|
-
...run.tags ?? [],
|
|
2989
|
+
...(run.tags ?? []).filter((tag) => tag !== SINGLETON_TRACKED_TAG),
|
|
2869
2990
|
SINGLETON_ADMITTED_TAG
|
|
2870
2991
|
];
|
|
2871
2992
|
}
|
|
@@ -2899,6 +3020,66 @@ var SingletonGate = class {
|
|
|
2899
3020
|
}
|
|
2900
3021
|
};
|
|
2901
3022
|
|
|
3023
|
+
// src/stalled-runs.ts
|
|
3024
|
+
var DEFAULT_STALLED_AFTER_MS = 15 * 6e4;
|
|
3025
|
+
async function findStalledRuns(store, options = {}) {
|
|
3026
|
+
const now = options.now ?? Date.now();
|
|
3027
|
+
const thresholdMs = options.thresholdMs ?? DEFAULT_STALLED_AFTER_MS;
|
|
3028
|
+
const scan = Math.max(1, options.scan ?? 1e3);
|
|
3029
|
+
const pageSize = Math.min(200, scan);
|
|
3030
|
+
const stalled = [];
|
|
3031
|
+
for (let offset = 0; offset < scan; offset += pageSize) {
|
|
3032
|
+
const limit = Math.min(pageSize, scan - offset);
|
|
3033
|
+
const page = await store.listRuns({
|
|
3034
|
+
statuses: [
|
|
3035
|
+
"running",
|
|
3036
|
+
"suspended"
|
|
3037
|
+
],
|
|
3038
|
+
...options.namespace !== void 0 ? {
|
|
3039
|
+
namespace: options.namespace
|
|
3040
|
+
} : {},
|
|
3041
|
+
limit,
|
|
3042
|
+
offset
|
|
3043
|
+
});
|
|
3044
|
+
for (const run of page) {
|
|
3045
|
+
const ageMs = now - run.updatedAt.getTime();
|
|
3046
|
+
if (ageMs < thresholdMs) continue;
|
|
3047
|
+
const info2 = await stalledInfo(store, run, now, thresholdMs, ageMs);
|
|
3048
|
+
if (info2) stalled.push(info2);
|
|
3049
|
+
}
|
|
3050
|
+
if (page.length < limit) break;
|
|
3051
|
+
}
|
|
3052
|
+
return stalled;
|
|
3053
|
+
}
|
|
3054
|
+
__name(findStalledRuns, "findStalledRuns");
|
|
3055
|
+
async function stalledInfo(store, run, now, thresholdMs, ageMs) {
|
|
3056
|
+
let stalePending = null;
|
|
3057
|
+
for (const cp of await store.listCheckpoints(run.id)) {
|
|
3058
|
+
if (cp.kind !== "remote" || cp.status !== "pending") continue;
|
|
3059
|
+
const stepAgeMs = now - cp.enqueuedAt.getTime();
|
|
3060
|
+
if (stepAgeMs < thresholdMs) continue;
|
|
3061
|
+
if (cp.wakeAt != null && cp.wakeAt > now) continue;
|
|
3062
|
+
if (!stalePending || stepAgeMs > stalePending.ageMs) {
|
|
3063
|
+
stalePending = {
|
|
3064
|
+
seq: cp.seq,
|
|
3065
|
+
name: cp.name,
|
|
3066
|
+
attempts: cp.attempts,
|
|
3067
|
+
ageMs: stepAgeMs,
|
|
3068
|
+
leaseUntil: cp.wakeAt ?? null
|
|
3069
|
+
};
|
|
3070
|
+
}
|
|
3071
|
+
}
|
|
3072
|
+
const wakeForever = run.status === "suspended" && run.wakeAt == null;
|
|
3073
|
+
if (!wakeForever && !stalePending) return null;
|
|
3074
|
+
return {
|
|
3075
|
+
run,
|
|
3076
|
+
ageMs,
|
|
3077
|
+
wakeForever,
|
|
3078
|
+
stalePending
|
|
3079
|
+
};
|
|
3080
|
+
}
|
|
3081
|
+
__name(stalledInfo, "stalledInfo");
|
|
3082
|
+
|
|
2902
3083
|
// src/transport-pool.ts
|
|
2903
3084
|
var TransportPool = class {
|
|
2904
3085
|
static {
|
|
@@ -3760,30 +3941,82 @@ function createWorkflowCtx(host, runId, compensations, workflow = "", searchAttr
|
|
|
3760
3941
|
}));
|
|
3761
3942
|
return id;
|
|
3762
3943
|
}, "startChild");
|
|
3944
|
+
const sendEntityOp = /* @__PURE__ */ __name(async (name, key, op, arg, seq, reply) => await host.enqueueEntityOp?.(name, key, op, arg, `${runId}:${seq}`, reply) ?? false, "sendEntityOp");
|
|
3763
3945
|
const callEntity = /* @__PURE__ */ __name(async (name, key, op, arg) => {
|
|
3764
3946
|
const current = pos.next();
|
|
3765
|
-
|
|
3947
|
+
let existing = await readCheckpoint(current);
|
|
3766
3948
|
if (existing && existing.status === "completed") return existing.output;
|
|
3767
3949
|
const reply = `entityreply:${runId}:${current}`;
|
|
3950
|
+
const intentName = `signal:${reply}`;
|
|
3951
|
+
if (existing && existing.name !== intentName) {
|
|
3952
|
+
throw new NonDeterminismError(runId, current, intentName, existing.name);
|
|
3953
|
+
}
|
|
3954
|
+
if (existing && replay?.has(current)) {
|
|
3955
|
+
existing = await store.getCheckpoint(runId, current) ?? existing;
|
|
3956
|
+
if (existing.status === "completed") return existing.output;
|
|
3957
|
+
}
|
|
3958
|
+
const markSent = /* @__PURE__ */ __name(() => store.saveCheckpointIfPending?.(instantCheckpoint({
|
|
3959
|
+
runId,
|
|
3960
|
+
seq: current,
|
|
3961
|
+
name: intentName,
|
|
3962
|
+
kind: "signal",
|
|
3963
|
+
status: "pending",
|
|
3964
|
+
output: {
|
|
3965
|
+
sent: true
|
|
3966
|
+
}
|
|
3967
|
+
})) ?? Promise.resolve(false), "markSent");
|
|
3968
|
+
if (existing) {
|
|
3969
|
+
const buffered = await consumeBuffered(reply, current);
|
|
3970
|
+
if (buffered) {
|
|
3971
|
+
await store.removeSignalWaiter({
|
|
3972
|
+
token: reply,
|
|
3973
|
+
runId,
|
|
3974
|
+
seq: current
|
|
3975
|
+
});
|
|
3976
|
+
return buffered.value;
|
|
3977
|
+
}
|
|
3978
|
+
if (existing.output?.sent !== true) {
|
|
3979
|
+
if (await sendEntityOp(name, key, op, arg, current, reply)) await markSent();
|
|
3980
|
+
}
|
|
3981
|
+
host.kickEntity?.(name, key);
|
|
3982
|
+
throw new WorkflowSuspended();
|
|
3983
|
+
}
|
|
3768
3984
|
await store.putSignalWaiter({
|
|
3769
3985
|
token: reply,
|
|
3770
3986
|
runId,
|
|
3771
3987
|
seq: current
|
|
3772
3988
|
});
|
|
3773
|
-
host.
|
|
3989
|
+
if (!host.enqueueEntityOp) {
|
|
3990
|
+
host.signalEntity?.(name, key, op, arg, reply);
|
|
3991
|
+
throw new WorkflowSuspended();
|
|
3992
|
+
}
|
|
3993
|
+
await writeCheckpoint(instantCheckpoint({
|
|
3994
|
+
runId,
|
|
3995
|
+
seq: current,
|
|
3996
|
+
name: intentName,
|
|
3997
|
+
kind: "signal",
|
|
3998
|
+
status: "pending",
|
|
3999
|
+
output: {
|
|
4000
|
+
sent: false
|
|
4001
|
+
}
|
|
4002
|
+
}));
|
|
4003
|
+
if (await sendEntityOp(name, key, op, arg, current, reply)) await markSent();
|
|
4004
|
+
host.kickEntity?.(name, key);
|
|
3774
4005
|
throw new WorkflowSuspended();
|
|
3775
4006
|
}, "callEntity");
|
|
3776
4007
|
const signalEntity = /* @__PURE__ */ __name(async (name, key, op, arg) => {
|
|
3777
4008
|
const current = pos.next();
|
|
3778
4009
|
const existing = await readCheckpoint(current);
|
|
3779
4010
|
if (existing && existing.status === "completed") return;
|
|
3780
|
-
|
|
4011
|
+
const durable = await sendEntityOp(name, key, op, arg, current);
|
|
4012
|
+
if (!durable) host.signalEntity?.(name, key, op, arg);
|
|
3781
4013
|
await writeCheckpoint(instantCheckpoint({
|
|
3782
4014
|
runId,
|
|
3783
4015
|
seq: current,
|
|
3784
4016
|
name: `entitysig:${name}:${key}`,
|
|
3785
4017
|
kind: "local"
|
|
3786
4018
|
}));
|
|
4019
|
+
if (durable) host.kickEntity?.(name, key);
|
|
3787
4020
|
}, "signalEntity");
|
|
3788
4021
|
const breakpoint = /* @__PURE__ */ __name(async (label) => {
|
|
3789
4022
|
const current = pos.next();
|
|
@@ -3851,17 +4084,15 @@ function createWorkflowCtx(host, runId, compensations, workflow = "", searchAttr
|
|
|
3851
4084
|
const deadlineSeq = timeoutMs == null ? -1 : pos.next();
|
|
3852
4085
|
const existing = await readCheckpoint(current);
|
|
3853
4086
|
if (existing && existing.status === "completed") return existing.output;
|
|
3854
|
-
|
|
3855
|
-
|
|
4087
|
+
const buffered = await consumeBuffered(token, current);
|
|
4088
|
+
if (buffered) return buffered.value;
|
|
4089
|
+
const deadline = timeoutMs == null ? void 0 : await stampDeadline(deadlineSeq, token, timeoutMs);
|
|
4090
|
+
if (deadline !== void 0 && host.clock() >= deadline) {
|
|
4091
|
+
await store.removeSignalWaiter({
|
|
3856
4092
|
token,
|
|
3857
4093
|
runId,
|
|
3858
4094
|
seq: current
|
|
3859
|
-
});
|
|
3860
|
-
throw new WorkflowSuspended();
|
|
3861
|
-
}
|
|
3862
|
-
const deadline = await stampDeadline(deadlineSeq, token, timeoutMs);
|
|
3863
|
-
if (host.clock() >= deadline) {
|
|
3864
|
-
await store.takeSignalWaiter(token).catch(() => void 0);
|
|
4095
|
+
}).catch(() => void 0);
|
|
3865
4096
|
throw new SignalTimeoutError(token, timeoutMs);
|
|
3866
4097
|
}
|
|
3867
4098
|
await store.putSignalWaiter({
|
|
@@ -3869,6 +4100,15 @@ function createWorkflowCtx(host, runId, compensations, workflow = "", searchAttr
|
|
|
3869
4100
|
runId,
|
|
3870
4101
|
seq: current
|
|
3871
4102
|
});
|
|
4103
|
+
const lateBuffered = await consumeBuffered(token, current);
|
|
4104
|
+
if (lateBuffered) {
|
|
4105
|
+
await store.removeSignalWaiter({
|
|
4106
|
+
token,
|
|
4107
|
+
runId,
|
|
4108
|
+
seq: current
|
|
4109
|
+
});
|
|
4110
|
+
return lateBuffered.value;
|
|
4111
|
+
}
|
|
3872
4112
|
throw new WorkflowSuspended(deadline);
|
|
3873
4113
|
}, "wait");
|
|
3874
4114
|
return {
|
|
@@ -4060,6 +4300,12 @@ var SETTLE_ELIGIBLE_STATUSES = [
|
|
|
4060
4300
|
"failed",
|
|
4061
4301
|
"cancelling"
|
|
4062
4302
|
];
|
|
4303
|
+
var TIMEOUT_SWEPT_STATUSES = [
|
|
4304
|
+
"pending",
|
|
4305
|
+
"running",
|
|
4306
|
+
"suspended",
|
|
4307
|
+
"blocked"
|
|
4308
|
+
];
|
|
4063
4309
|
var SUSPEND_ELIGIBLE_STATUSES = SETTLE_ELIGIBLE_STATUSES.filter((status) => status !== "cancelling");
|
|
4064
4310
|
var RECOVERY_BATCH = 500;
|
|
4065
4311
|
var NamespaceMismatch = class NamespaceMismatch2 extends Error {
|
|
@@ -4082,6 +4328,12 @@ var WorkflowEngine = class {
|
|
|
4082
4328
|
instanceId;
|
|
4083
4329
|
namespace;
|
|
4084
4330
|
leaseMs;
|
|
4331
|
+
/**
|
|
4332
|
+
* Leases held by this `instanceId` that expire strictly before this epoch ms were taken by a
|
|
4333
|
+
* PREVIOUS process with the same id (every lease this one takes or renews expires at
|
|
4334
|
+
* `clock() + leaseMs`, so at or after it). See {@link orphanedRuns}.
|
|
4335
|
+
*/
|
|
4336
|
+
ownLeaseCutoffMs;
|
|
4085
4337
|
maxRecoveryAttempts;
|
|
4086
4338
|
remoteAdvanceSilenceMs;
|
|
4087
4339
|
reconcileMs;
|
|
@@ -4185,6 +4437,18 @@ var WorkflowEngine = class {
|
|
|
4185
4437
|
stepQueue = /* @__PURE__ */ new Map();
|
|
4186
4438
|
/** Executions currently in flight, so a graceful shutdown can wait for them to settle. */
|
|
4187
4439
|
inflight = /* @__PURE__ */ new Set();
|
|
4440
|
+
/**
|
|
4441
|
+
* The run lease of each execution in flight on THIS instance, by run id — renewed while it runs
|
|
4442
|
+
* and checked by every settle (the zombie fence, see {@link ExecutionLease}). Also what `drain()`
|
|
4443
|
+
* releases when its budget runs out.
|
|
4444
|
+
*/
|
|
4445
|
+
executionLeases = /* @__PURE__ */ new Map();
|
|
4446
|
+
/** Pager hooks for stranded runs (see {@link onStalled}). */
|
|
4447
|
+
stalledListeners = /* @__PURE__ */ new Set();
|
|
4448
|
+
/** Runs already paged about, by id → the `updatedAt` of the paged episode (see {@link sweepStalled}). */
|
|
4449
|
+
notifiedStalled = /* @__PURE__ */ new Map();
|
|
4450
|
+
lastStalledSweepAt = Number.NEGATIVE_INFINITY;
|
|
4451
|
+
stalledAfterMs;
|
|
4188
4452
|
draining = false;
|
|
4189
4453
|
/** Wait already spent on this shutdown before `drain()` (see `beginDrain`). */
|
|
4190
4454
|
drainSpentMs = 0;
|
|
@@ -4203,11 +4467,14 @@ var WorkflowEngine = class {
|
|
|
4203
4467
|
this.admission = deps.admission ?? new InMemoryAdmissionBackend(this.clock);
|
|
4204
4468
|
this.admission.onFreed?.((queue) => this.wakeQueueWaiters(queue));
|
|
4205
4469
|
this.instanceId = deps.instanceId ?? globalThis.crypto.randomUUID();
|
|
4206
|
-
this.namespace = deps.namespace;
|
|
4470
|
+
this.namespace = poolNamespace(deps.namespace);
|
|
4207
4471
|
this.leaseMs = deps.leaseMs ?? 3e4;
|
|
4472
|
+
this.ownLeaseCutoffMs = this.clock() + this.leaseMs;
|
|
4208
4473
|
this.maxRecoveryAttempts = deps.maxRecoveryAttempts;
|
|
4209
4474
|
this.remoteAdvanceSilenceMs = deps.remoteAdvanceSilenceMs;
|
|
4210
4475
|
this.reconcileMs = deps.reconcileMs === void 0 ? 3e5 : deps.reconcileMs || void 0;
|
|
4476
|
+
const stalledAfter = deps.stalledAfterMs;
|
|
4477
|
+
this.stalledAfterMs = stalledAfter !== void 0 && Number.isFinite(stalledAfter) ? Math.max(6e4, stalledAfter) : DEFAULT_STALLED_AFTER_MS;
|
|
4211
4478
|
const batch = deps.timerBatchSize;
|
|
4212
4479
|
this.timerBatchSize = batch !== void 0 && Number.isFinite(batch) && batch >= 1 ? Math.floor(batch) : 500;
|
|
4213
4480
|
this.eventBufferTtlMs = deps.eventBufferTtlMs || void 0;
|
|
@@ -4324,7 +4591,11 @@ var WorkflowEngine = class {
|
|
|
4324
4591
|
}
|
|
4325
4592
|
});
|
|
4326
4593
|
this.accumulators = new EventAccumulators(this);
|
|
4327
|
-
this.entities = new Entities(this
|
|
4594
|
+
this.entities = new Entities(this, {
|
|
4595
|
+
bufferSignal: /* @__PURE__ */ __name((token, payload) => this.store.bufferSignal(token, payload), "bufferSignal"),
|
|
4596
|
+
wakeWaiter: /* @__PURE__ */ __name((token, payload) => this.wakeSignalWaiter(token, payload), "wakeWaiter"),
|
|
4597
|
+
ensureStarted: /* @__PURE__ */ __name((workflow, input, runId) => this.start(workflow, input, runId), "ensureStarted")
|
|
4598
|
+
});
|
|
4328
4599
|
}
|
|
4329
4600
|
/**
|
|
4330
4601
|
* Register a **durable entity** (a virtual object): a keyed actor whose `handlers` run **serialized
|
|
@@ -4547,6 +4818,64 @@ var WorkflowEngine = class {
|
|
|
4547
4818
|
this.deadListeners.add(listener);
|
|
4548
4819
|
return () => this.deadListeners.delete(listener);
|
|
4549
4820
|
}
|
|
4821
|
+
/**
|
|
4822
|
+
* Register a pager hook for STRANDED runs: an in-flight run untouched past `stalledAfterMs` that is
|
|
4823
|
+
* either suspended with NO wake timer (wake-forever: nothing will ever re-drive it) or parked on an
|
|
4824
|
+
* old pending remote step with no live lease (dispatched and, most likely, lost). The timer poller
|
|
4825
|
+
* sweeps for these ({@link sweepStalled}, throttled to one pass a minute) and notifies each episode
|
|
4826
|
+
* ONCE per instance: a run is paged again only after it makes progress and strands again. Wire your
|
|
4827
|
+
* alerting here like {@link onDead}. A listener that throws never breaks the sweep. Returns an
|
|
4828
|
+
* unsubscribe function.
|
|
4829
|
+
*/
|
|
4830
|
+
onStalled(listener) {
|
|
4831
|
+
this.stalledListeners.add(listener);
|
|
4832
|
+
return () => this.stalledListeners.delete(listener);
|
|
4833
|
+
}
|
|
4834
|
+
/**
|
|
4835
|
+
* Detect stranded in-flight runs and notify the {@link onStalled} listeners. Self-throttled to one
|
|
4836
|
+
* pass a minute, skipped while draining, and a no-op without listeners — call it every tick (the
|
|
4837
|
+
* NestJS timer poller does). Scoped to this engine's `namespace` (an operator engine with no
|
|
4838
|
+
* namespace sweeps every namespace). Returns the runs notified THIS pass.
|
|
4839
|
+
*/
|
|
4840
|
+
async sweepStalled(now = this.clock()) {
|
|
4841
|
+
if (this.stalledListeners.size === 0 || this.draining) return [];
|
|
4842
|
+
if (now - this.lastStalledSweepAt < 6e4) return [];
|
|
4843
|
+
this.lastStalledSweepAt = now;
|
|
4844
|
+
const notified = [];
|
|
4845
|
+
for (const info2 of await this.listStalledRuns({
|
|
4846
|
+
now
|
|
4847
|
+
})) {
|
|
4848
|
+
const episode = info2.run.updatedAt.getTime();
|
|
4849
|
+
if (this.notifiedStalled.get(info2.run.id) === episode) continue;
|
|
4850
|
+
this.notifiedStalled.set(info2.run.id, episode);
|
|
4851
|
+
if (this.notifiedStalled.size > 1e3) this.notifiedStalled.clear();
|
|
4852
|
+
for (const listener of this.stalledListeners) {
|
|
4853
|
+
try {
|
|
4854
|
+
listener(info2);
|
|
4855
|
+
} catch {
|
|
4856
|
+
}
|
|
4857
|
+
}
|
|
4858
|
+
notified.push(info2);
|
|
4859
|
+
}
|
|
4860
|
+
return notified;
|
|
4861
|
+
}
|
|
4862
|
+
/**
|
|
4863
|
+
* The stranded runs right now, without notifying anyone: the query behind {@link sweepStalled} and
|
|
4864
|
+
* the CLI's `durable inspect --stale`. A `running`/`suspended` run qualifies when it has been
|
|
4865
|
+
* untouched for `thresholdMs` (default `stalledAfterMs`) AND either it is suspended with no wake
|
|
4866
|
+
* timer, or its oldest pending remote step is older than the threshold with no lease in the future
|
|
4867
|
+
* (a step with a `timeoutMs`, a heartbeat or `remoteRedispatchMs` keeps a lease; a long
|
|
4868
|
+
* `ctx.sleep` has a future `wakeAt`; neither is stranded). Scans up to `scan` candidates (default
|
|
4869
|
+
* 1000), newest first, in this engine's namespace.
|
|
4870
|
+
*/
|
|
4871
|
+
async listStalledRuns(options = {}) {
|
|
4872
|
+
return findStalledRuns(this.store, {
|
|
4873
|
+
now: options.now ?? this.clock(),
|
|
4874
|
+
thresholdMs: options.thresholdMs ?? this.stalledAfterMs,
|
|
4875
|
+
scan: options.scan,
|
|
4876
|
+
namespace: this.namespace
|
|
4877
|
+
});
|
|
4878
|
+
}
|
|
4550
4879
|
notifyDead(run) {
|
|
4551
4880
|
for (const fn of this.deadListeners) {
|
|
4552
4881
|
try {
|
|
@@ -4600,7 +4929,7 @@ var WorkflowEngine = class {
|
|
|
4600
4929
|
// tenant-suffixed group the run will dispatch to — an operator starting a tenant's run via the
|
|
4601
4930
|
// `onStartRun` wire (opts.namespace = tenant) must find the live `<workflow>@<tenant>` group,
|
|
4602
4931
|
// not the bare group. Without this the start-run entry path can't reach a suffixed-only worker.
|
|
4603
|
-
namespace: opts?.namespace ?? this.namespace,
|
|
4932
|
+
namespace: poolNamespace(opts?.namespace) ?? this.namespace,
|
|
4604
4933
|
input,
|
|
4605
4934
|
createdAt: /* @__PURE__ */ new Date(),
|
|
4606
4935
|
updatedAt: /* @__PURE__ */ new Date()
|
|
@@ -4637,7 +4966,7 @@ var WorkflowEngine = class {
|
|
|
4637
4966
|
VERSION_UNDECLARED_TAG
|
|
4638
4967
|
] : []
|
|
4639
4968
|
];
|
|
4640
|
-
const tags = mergeTags(registered.tags, stampedTags)?.filter((tag) => tag
|
|
4969
|
+
const tags = mergeTags(registered.tags, stampedTags)?.filter((tag) => !SINGLETON_RESERVED_TAGS.includes(tag));
|
|
4641
4970
|
if (registered.singleton) {
|
|
4642
4971
|
await this.singletons.assertCapacity(name, registered.singleton, input);
|
|
4643
4972
|
}
|
|
@@ -4651,7 +4980,7 @@ var WorkflowEngine = class {
|
|
|
4651
4980
|
...delayed ? {
|
|
4652
4981
|
wakeAt: startAtMs
|
|
4653
4982
|
} : {},
|
|
4654
|
-
namespace: opts?.namespace ?? this.namespace,
|
|
4983
|
+
namespace: poolNamespace(opts?.namespace) ?? this.namespace,
|
|
4655
4984
|
// DERIVED from the registration, exactly like `namespace` is derived from this engine — there is
|
|
4656
4985
|
// deliberately no `opts.origin`, so a caller cannot label its run as another package's work.
|
|
4657
4986
|
// `undefined` when the registration carried none (remote/convention/synthesized child, or a
|
|
@@ -4705,6 +5034,11 @@ var WorkflowEngine = class {
|
|
|
4705
5034
|
getRun(runId) {
|
|
4706
5035
|
return this.store.getRun(runId);
|
|
4707
5036
|
}
|
|
5037
|
+
/** A run's full checkpoint timeline, ordered by seq. A thin pass-through to the store (it is what
|
|
5038
|
+
* the testing kit's `captureHistory` reads, next to {@link getRun}). */
|
|
5039
|
+
listCheckpoints(runId) {
|
|
5040
|
+
return this.store.listCheckpoints(runId);
|
|
5041
|
+
}
|
|
4708
5042
|
/**
|
|
4709
5043
|
* Re-execute a run's body (replaying its checkpoints). This is the EXPLICIT entry point: besides
|
|
4710
5044
|
* driving a live run, it re-drives a `failed` run — the manual retry (prefer {@link requeue}, which
|
|
@@ -4790,6 +5124,8 @@ var WorkflowEngine = class {
|
|
|
4790
5124
|
* diagnosis.
|
|
4791
5125
|
*/
|
|
4792
5126
|
async releaseLeaseBestEffort(runId) {
|
|
5127
|
+
if (this.isFenced(runId)) return;
|
|
5128
|
+
this.handOffLease(runId);
|
|
4793
5129
|
try {
|
|
4794
5130
|
await this.store.releaseRunLock(runId);
|
|
4795
5131
|
} catch (err) {
|
|
@@ -4833,12 +5169,12 @@ var WorkflowEngine = class {
|
|
|
4833
5169
|
error: run.error
|
|
4834
5170
|
};
|
|
4835
5171
|
}
|
|
4836
|
-
if (this.namespace !== void 0 && run.namespace !== void 0 && run.namespace !== this.namespace) {
|
|
5172
|
+
if (this.namespace !== void 0 && run.namespace !== void 0 && poolNamespace(run.namespace) !== this.namespace) {
|
|
4837
5173
|
await this.store.releaseRunLock(runId);
|
|
4838
5174
|
throw new NamespaceMismatch();
|
|
4839
5175
|
}
|
|
4840
|
-
if (run.status === "failed" && run.tags?.includes(SINGLETON_ADMITTED_TAG)) {
|
|
4841
|
-
run.tags = run.tags
|
|
5176
|
+
if (run.status === "failed" && (run.tags?.includes(SINGLETON_ADMITTED_TAG) || run.tags?.includes(SINGLETON_TRACKED_TAG))) {
|
|
5177
|
+
run.tags = withoutSingletonMarkers(run.tags);
|
|
4842
5178
|
await this.store.updateRun(run.id, {
|
|
4843
5179
|
tags: run.tags
|
|
4844
5180
|
});
|
|
@@ -5039,17 +5375,47 @@ var WorkflowEngine = class {
|
|
|
5039
5375
|
rearm();
|
|
5040
5376
|
});
|
|
5041
5377
|
this.drainRearms.add(rearm);
|
|
5378
|
+
let settled = false;
|
|
5042
5379
|
try {
|
|
5043
5380
|
await Promise.race([
|
|
5044
5381
|
Promise.allSettled([
|
|
5045
5382
|
...this.inflight
|
|
5046
|
-
])
|
|
5383
|
+
]).then(() => {
|
|
5384
|
+
settled = true;
|
|
5385
|
+
}),
|
|
5047
5386
|
timer
|
|
5048
5387
|
]);
|
|
5049
5388
|
} finally {
|
|
5050
5389
|
this.drainRearms.delete(rearm);
|
|
5051
5390
|
clearTimeout(timeout);
|
|
5052
5391
|
}
|
|
5392
|
+
if (!settled) await this.releaseInflightLeases();
|
|
5393
|
+
}
|
|
5394
|
+
/**
|
|
5395
|
+
* The drain budget ran out with executions still in flight: release their recovery leases so
|
|
5396
|
+
* another instance re-drives those runs on its next recovery pass, instead of waiting out
|
|
5397
|
+
* `leaseMs` after this process exits. Each still-running turn is FENCED first, so whatever it
|
|
5398
|
+
* computes after this point is never written over the new owner's work (see settleRun). Its
|
|
5399
|
+
* completed steps are checkpointed, so the new owner replays them; a step still executing here
|
|
5400
|
+
* may run again there, exactly as after a crash. The release is owner-scoped where the store
|
|
5401
|
+
* supports it (only a lease this instance still holds is cleared). Best effort: never throws.
|
|
5402
|
+
*/
|
|
5403
|
+
async releaseInflightLeases() {
|
|
5404
|
+
const released = [];
|
|
5405
|
+
for (const [runId, lease] of [
|
|
5406
|
+
...this.executionLeases
|
|
5407
|
+
]) {
|
|
5408
|
+
if (lease.lost || lease.handedOff || lease.done) continue;
|
|
5409
|
+
lease.lost = true;
|
|
5410
|
+
try {
|
|
5411
|
+
await this.store.releaseRunLock(runId, this.instanceId);
|
|
5412
|
+
released.push(runId);
|
|
5413
|
+
} catch {
|
|
5414
|
+
}
|
|
5415
|
+
}
|
|
5416
|
+
if (released.length > 0) {
|
|
5417
|
+
console.warn(`[nestjs-durable] drain timed out with ${released.length} run(s) still executing; released their leases so another instance recovers them now: ${released.join(", ")}`);
|
|
5418
|
+
}
|
|
5053
5419
|
}
|
|
5054
5420
|
/**
|
|
5055
5421
|
* Cancel in-flight runs that have outlived their workflow's `executionTimeout`. Call it from the
|
|
@@ -5057,6 +5423,15 @@ var WorkflowEngine = class {
|
|
|
5057
5423
|
* `execution_timeout` error (terminal, so a late step result can't resurrect it), AND its children
|
|
5058
5424
|
* are cascaded exactly as an explicit {@link cancel} cascades them.
|
|
5059
5425
|
*
|
|
5426
|
+
* WHICH RUNS. Every run not yet finished, counted from `createdAt`: `pending` and `blocked` as well
|
|
5427
|
+
* as `running` and `suspended`. A run nobody picks up (no worker on its queue) or nobody can run (no
|
|
5428
|
+
* worker with its capabilities) is as stuck as one that hangs mid-body, and the cap is the backstop
|
|
5429
|
+
* for exactly that — leaving them out meant a run that never started lived forever while one that
|
|
5430
|
+
* started and hung was cut off. Cancelling one a worker is picking up at that moment is safe: the
|
|
5431
|
+
* worker's settle only writes over a live status ({@link SETTLE_ELIGIBLE_STATUSES}), so it can't
|
|
5432
|
+
* bring the run back. `cancelling` is left alone on purpose — a compensating cancel's saga undo is
|
|
5433
|
+
* running, it ends `cancelled` by itself, and cutting it short would skip the undo.
|
|
5434
|
+
*
|
|
5060
5435
|
* WHY THE TERMINAL WRITE IS DIRECT, not a call to {@link cancel}. Two reasons, both still standing:
|
|
5061
5436
|
* the run must land with the `execution_timeout` error `cancel` knows nothing about (`cancel` writes
|
|
5062
5437
|
* a plain `cancelled`, and the code is what a dashboard/alert distinguishes a timeout by), and this
|
|
@@ -5069,8 +5444,8 @@ var WorkflowEngine = class {
|
|
|
5069
5444
|
* children kept running with the parent gone and nothing pointing at them — a silent orphan you
|
|
5070
5445
|
* could only find by reading the runs table by hand. {@link cancelChildren} is the SAME recursive
|
|
5071
5446
|
* walk `cancel` uses, so children of children go too, and it inherits that path's two guards:
|
|
5072
|
-
* `cancel` returns early on a run that is already
|
|
5073
|
-
*
|
|
5447
|
+
* `cancel` returns early on a run that is already terminal (a child that beat the sweep to the
|
|
5448
|
+
* finish is never clobbered, and a re-cancel is a no-op), and it writes the
|
|
5074
5449
|
* parent's terminal status BEFORE recursing, so a cyclic parent→child graph terminates on the
|
|
5075
5450
|
* second visit instead of looping. Two pollers sweeping the same run concurrently therefore write
|
|
5076
5451
|
* the same terminal state twice and cascade into already-cancelled children — idempotent, not
|
|
@@ -5086,18 +5461,11 @@ var WorkflowEngine = class {
|
|
|
5086
5461
|
if (this.draining) return;
|
|
5087
5462
|
if (reg.executionTimeoutMs == null) continue;
|
|
5088
5463
|
const deadline = now - reg.executionTimeoutMs;
|
|
5089
|
-
const inflight =
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
}),
|
|
5095
|
-
...await this.store.listRuns({
|
|
5096
|
-
workflow: reg.name,
|
|
5097
|
-
status: "suspended",
|
|
5098
|
-
namespace: this.namespace
|
|
5099
|
-
})
|
|
5100
|
-
];
|
|
5464
|
+
const inflight = await this.store.listRuns({
|
|
5465
|
+
workflow: reg.name,
|
|
5466
|
+
statuses: TIMEOUT_SWEPT_STATUSES,
|
|
5467
|
+
namespace: this.namespace
|
|
5468
|
+
});
|
|
5101
5469
|
for (const run of inflight) {
|
|
5102
5470
|
if (this.draining) return;
|
|
5103
5471
|
if (run.createdAt.getTime() > deadline) continue;
|
|
@@ -5130,48 +5498,103 @@ var WorkflowEngine = class {
|
|
|
5130
5498
|
const results = [];
|
|
5131
5499
|
for (const listed of await this.orphanedRuns(nowMs)) {
|
|
5132
5500
|
if (this.draining) break;
|
|
5133
|
-
const
|
|
5134
|
-
if (!
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
|
|
5149
|
-
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
|
|
5153
|
-
|
|
5154
|
-
|
|
5155
|
-
|
|
5156
|
-
|
|
5157
|
-
|
|
5158
|
-
|
|
5159
|
-
|
|
5160
|
-
|
|
5161
|
-
|
|
5162
|
-
|
|
5501
|
+
const outcome = this.isOwnLiveLease(listed, nowMs) ? await this.reclaimOwnLease(listed, nowMs) : await this.recoverOrphan(listed, nowMs);
|
|
5502
|
+
if (!outcome) continue;
|
|
5503
|
+
if (outcome.dispatch) await this.runDispatcher.dispatch(outcome.result.runId);
|
|
5504
|
+
results.push(outcome.result);
|
|
5505
|
+
}
|
|
5506
|
+
return results;
|
|
5507
|
+
}
|
|
5508
|
+
/** A run whose still-live lease is held by this very `instanceId` (see {@link reclaimOwnLease}). */
|
|
5509
|
+
isOwnLiveLease(run, nowMs) {
|
|
5510
|
+
return run.lockedBy === this.instanceId && (run.lockedUntil ?? 0) > nowMs;
|
|
5511
|
+
}
|
|
5512
|
+
/**
|
|
5513
|
+
* Recover a run whose lease is still live but held by THIS `instanceId` and predates this process
|
|
5514
|
+
* (listed by {@link orphanedRuns}): the previous process with this stable id died without a
|
|
5515
|
+
* graceful shutdown (OOM, SIGKILL), so nothing is renewing that lease and waiting for it to expire
|
|
5516
|
+
* only delays the run. The lease is taken back with `renewRunLock` (a compare-and-set on the
|
|
5517
|
+
* owner; `tryLockRun` refuses a live lease), so the lease alone cannot fence a live execution in
|
|
5518
|
+
* THIS process from its own recovery: the in-process run gate does. A run with a
|
|
5519
|
+
* gate is executing (or about to) here and is skipped; otherwise a gate is held while the run is
|
|
5520
|
+
* recovered, so a resume landing meanwhile queues behind the recovery instead of running beside it.
|
|
5521
|
+
* The gate is released before the re-dispatch, which may resume the run on this instance.
|
|
5522
|
+
*/
|
|
5523
|
+
async reclaimOwnLease(listed, nowMs) {
|
|
5524
|
+
if (this.runGates.has(listed.id)) return void 0;
|
|
5525
|
+
let release;
|
|
5526
|
+
const tail = new Promise((resolve) => {
|
|
5527
|
+
release = resolve;
|
|
5528
|
+
});
|
|
5529
|
+
const gate = {
|
|
5530
|
+
tail
|
|
5531
|
+
};
|
|
5532
|
+
this.runGates.set(listed.id, gate);
|
|
5533
|
+
this.closeGateAfter(listed.id, gate, tail);
|
|
5534
|
+
let outcome;
|
|
5535
|
+
try {
|
|
5536
|
+
const fresh = await this.store.getRun(listed.id);
|
|
5537
|
+
const liveHere = fresh?.lockedBy === this.instanceId && (fresh.lockedUntil ?? 0) > nowMs && (fresh.lockedUntil ?? 0) >= this.ownLeaseCutoffMs;
|
|
5538
|
+
if (fresh && !liveHere) outcome = await this.recoverOrphan(listed, nowMs, true);
|
|
5539
|
+
} finally {
|
|
5540
|
+
release(outcome?.result ?? {
|
|
5541
|
+
runId: listed.id,
|
|
5542
|
+
status: listed.status
|
|
5543
|
+
});
|
|
5544
|
+
}
|
|
5545
|
+
return outcome;
|
|
5546
|
+
}
|
|
5547
|
+
/**
|
|
5548
|
+
* One run of a {@link recoverIncomplete} pass: take its lease and decide on it. Resolves the
|
|
5549
|
+
* pass's result for it and whether it must be re-dispatched, or `undefined` when it was not ours.
|
|
5550
|
+
* `ownLease`: the run's live lease is this instance id's own, left by a previous process (see
|
|
5551
|
+
* {@link reclaimOwnLease}), so it is taken back by owner rather than acquired as a free one.
|
|
5552
|
+
*/
|
|
5553
|
+
async recoverOrphan(listed, nowMs, ownLease = false) {
|
|
5554
|
+
const acquired = ownLease ? await this.store.renewRunLock(listed.id, this.instanceId, nowMs + this.leaseMs) : await this.store.tryLockRun(listed.id, this.instanceId, nowMs + this.leaseMs, nowMs);
|
|
5555
|
+
if (!acquired) return void 0;
|
|
5556
|
+
const run = await this.store.getRun(listed.id);
|
|
5557
|
+
if (!run || run.status !== "running" && run.status !== "cancelling") {
|
|
5558
|
+
await this.store.releaseRunLock(listed.id);
|
|
5559
|
+
return void 0;
|
|
5560
|
+
}
|
|
5561
|
+
if (run.status === "running") {
|
|
5562
|
+
const dispatched = await this.inFlightRemoteStep(run.id);
|
|
5563
|
+
if (dispatched) {
|
|
5163
5564
|
await this.store.updateRun(run.id, {
|
|
5164
|
-
status: "
|
|
5565
|
+
status: "suspended",
|
|
5566
|
+
wakeAt: this.reconcileWakeAt(dispatched.wakeAt ?? void 0),
|
|
5165
5567
|
updatedAt: /* @__PURE__ */ new Date()
|
|
5166
5568
|
});
|
|
5569
|
+
await this.store.releaseRunLock(run.id);
|
|
5570
|
+
return {
|
|
5571
|
+
result: {
|
|
5572
|
+
runId: run.id,
|
|
5573
|
+
status: "suspended"
|
|
5574
|
+
},
|
|
5575
|
+
dispatch: false
|
|
5576
|
+
};
|
|
5167
5577
|
}
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
|
|
5171
|
-
|
|
5578
|
+
}
|
|
5579
|
+
const settled = await this.countRecovery(run);
|
|
5580
|
+
if (settled) return {
|
|
5581
|
+
result: settled,
|
|
5582
|
+
dispatch: false
|
|
5583
|
+
};
|
|
5584
|
+
await this.store.releaseRunLock(run.id);
|
|
5585
|
+
if (run.status !== "cancelling") {
|
|
5586
|
+
await this.store.updateRun(run.id, {
|
|
5587
|
+
status: "pending",
|
|
5588
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
5172
5589
|
});
|
|
5173
5590
|
}
|
|
5174
|
-
return
|
|
5591
|
+
return {
|
|
5592
|
+
result: {
|
|
5593
|
+
runId: run.id,
|
|
5594
|
+
status: run.status === "cancelling" ? "cancelling" : "pending"
|
|
5595
|
+
},
|
|
5596
|
+
dispatch: true
|
|
5597
|
+
};
|
|
5175
5598
|
}
|
|
5176
5599
|
/**
|
|
5177
5600
|
* The runs {@link recoverIncomplete} should try to reclaim: incomplete runs whose lease is free or
|
|
@@ -5181,12 +5604,20 @@ var WorkflowEngine = class {
|
|
|
5181
5604
|
* ({@link StateStore.listOrphanedRuns}, bounded by {@link RECOVERY_BATCH}); else the full list is
|
|
5182
5605
|
* filtered here, which still spares the probes. Shuffled, like the due-timer pass, so instances
|
|
5183
5606
|
* recovering at once spread over the batch instead of contending for the same leases.
|
|
5607
|
+
*
|
|
5608
|
+
* Also listed: runs still leased by THIS `instanceId` with a lease taken before this process
|
|
5609
|
+
* booted ({@link ownLeaseCutoffMs}). With a stable `instanceId` (pinned to the pod identity), that
|
|
5610
|
+
* is the previous process, killed without a graceful shutdown; see {@link reclaimOwnLease}.
|
|
5184
5611
|
*/
|
|
5185
5612
|
async orphanedRuns(nowMs) {
|
|
5613
|
+
const ownedBy = {
|
|
5614
|
+
owner: this.instanceId,
|
|
5615
|
+
lockedBefore: this.ownLeaseCutoffMs
|
|
5616
|
+
};
|
|
5186
5617
|
if (this.store.listOrphanedRuns) {
|
|
5187
|
-
return shuffle(await this.store.listOrphanedRuns(nowMs, RECOVERY_BATCH, this.namespace));
|
|
5618
|
+
return shuffle(await this.store.listOrphanedRuns(nowMs, RECOVERY_BATCH, this.namespace, ownedBy));
|
|
5188
5619
|
}
|
|
5189
|
-
return shuffle((await this.store.listIncompleteRuns(this.namespace)).filter((run) => run.lockedUntil == null || run.lockedUntil <= nowMs).sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime()).slice(0, RECOVERY_BATCH));
|
|
5620
|
+
return shuffle((await this.store.listIncompleteRuns(this.namespace)).filter((run) => run.lockedUntil == null || run.lockedUntil <= nowMs || run.lockedBy === ownedBy.owner && run.lockedUntil < ownedBy.lockedBefore).sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime()).slice(0, RECOVERY_BATCH));
|
|
5190
5621
|
}
|
|
5191
5622
|
/**
|
|
5192
5623
|
* The remote step this run dispatched and is still awaiting a result for, if any — the signature
|
|
@@ -5229,6 +5660,7 @@ var WorkflowEngine = class {
|
|
|
5229
5660
|
error: error2,
|
|
5230
5661
|
recoveryAttempts: attempts
|
|
5231
5662
|
});
|
|
5663
|
+
this.notifyParent(run.id, failedCompletion(error2));
|
|
5232
5664
|
return {
|
|
5233
5665
|
runId: run.id,
|
|
5234
5666
|
status: "dead",
|
|
@@ -5435,7 +5867,7 @@ var WorkflowEngine = class {
|
|
|
5435
5867
|
error: void 0,
|
|
5436
5868
|
...resurrecting ? {
|
|
5437
5869
|
recoveryAttempts: 0,
|
|
5438
|
-
tags: run.tags
|
|
5870
|
+
tags: withoutSingletonMarkers(run.tags)
|
|
5439
5871
|
} : {},
|
|
5440
5872
|
updatedAt: /* @__PURE__ */ new Date()
|
|
5441
5873
|
});
|
|
@@ -5847,6 +6279,16 @@ var WorkflowEngine = class {
|
|
|
5847
6279
|
return this.deliverSignal(lateWaiter, reclaimed.payload);
|
|
5848
6280
|
}
|
|
5849
6281
|
/**
|
|
6282
|
+
* Wake the run parked on `token` with `payload`, if there is one; otherwise do nothing — unlike
|
|
6283
|
+
* {@link signal}, it never buffers. For a wake-up whose real payload is ALREADY buffered on the
|
|
6284
|
+
* token (an entity op, see `Entities.kick`): a run that isn't parked reads that buffer at its next
|
|
6285
|
+
* wait, so there is nothing to keep.
|
|
6286
|
+
*/
|
|
6287
|
+
async wakeSignalWaiter(token, payload) {
|
|
6288
|
+
const waiter = await this.store.takeSignalWaiter(token);
|
|
6289
|
+
return waiter ? this.deliverSignal(waiter, payload) : null;
|
|
6290
|
+
}
|
|
6291
|
+
/**
|
|
5850
6292
|
* Ensure a run exists for `runId`, then deliver a signal to it — atomically race-free thanks to
|
|
5851
6293
|
* signal buffering: if the run is new (or busy / not yet waiting), the signal is buffered and
|
|
5852
6294
|
* consumed when it reaches `waitForSignal(token)`. The canonical **durable-entity / accumulator**
|
|
@@ -6442,9 +6884,31 @@ var WorkflowEngine = class {
|
|
|
6442
6884
|
});
|
|
6443
6885
|
}
|
|
6444
6886
|
/** Admission persists the wait atomically; never overwrite a cancellation after it returns. */
|
|
6887
|
+
/**
|
|
6888
|
+
* A run's first execution: `pending` -> `running`. A singleton run not yet admitted is stamped
|
|
6889
|
+
* {@link SINGLETON_TRACKED_TAG} in the same write, BEFORE it competes for a slot, so a run this
|
|
6890
|
+
* replica is about to admit or deny is never mistaken for a holder admitted by a replica older
|
|
6891
|
+
* than 0.73 (see `legacySingletonHolders`).
|
|
6892
|
+
*/
|
|
6893
|
+
async markStarted(run, singleton) {
|
|
6894
|
+
const tags = singleton ? withSingletonTracked(run.tags) : run.tags;
|
|
6895
|
+
const retag = tags !== run.tags;
|
|
6896
|
+
await this.store.updateRun(run.id, {
|
|
6897
|
+
status: "running",
|
|
6898
|
+
...retag ? {
|
|
6899
|
+
tags
|
|
6900
|
+
} : {},
|
|
6901
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
6902
|
+
});
|
|
6903
|
+
run.status = "running";
|
|
6904
|
+
if (retag) run.tags = tags;
|
|
6905
|
+
}
|
|
6445
6906
|
async singletonWaitResult(run) {
|
|
6446
6907
|
const current = await this.store.getRun(run.id);
|
|
6447
|
-
|
|
6908
|
+
if (!this.isFenced(run.id)) {
|
|
6909
|
+
this.handOffLease(run.id);
|
|
6910
|
+
await this.store.releaseRunLock(run.id);
|
|
6911
|
+
}
|
|
6448
6912
|
if (!current) throw new Error(`run ${run.id} was deleted during singleton admission`);
|
|
6449
6913
|
if (current.status === "suspended") {
|
|
6450
6914
|
this.emit({
|
|
@@ -6472,10 +6936,7 @@ var WorkflowEngine = class {
|
|
|
6472
6936
|
};
|
|
6473
6937
|
}
|
|
6474
6938
|
}
|
|
6475
|
-
const
|
|
6476
|
-
void this.store.renewRunLock(run.id, this.instanceId, this.clock() + this.leaseMs).catch(() => void 0);
|
|
6477
|
-
}, Math.max(50, Math.floor(this.leaseMs / 2)));
|
|
6478
|
-
renew.unref?.();
|
|
6939
|
+
const held = this.holdExecutionLease(run.id);
|
|
6479
6940
|
try {
|
|
6480
6941
|
const result = registered?.remote ? await this.runRemoteExecution(run, registered) : await this.runExecution(run, fn, registered);
|
|
6481
6942
|
if (registered?.singleton && (result.status === "completed" || result.status === "failed" || result.status === "cancelled" || result.status === "dead")) {
|
|
@@ -6483,10 +6944,59 @@ var WorkflowEngine = class {
|
|
|
6483
6944
|
}
|
|
6484
6945
|
return result;
|
|
6485
6946
|
} finally {
|
|
6486
|
-
|
|
6947
|
+
held.stop();
|
|
6487
6948
|
}
|
|
6488
6949
|
}
|
|
6489
6950
|
/**
|
|
6951
|
+
* Renew `runId`'s lease for as long as this execution holds it, and register it in
|
|
6952
|
+
* {@link executionLeases} so settles can check the zombie fence and `drain()` can release it.
|
|
6953
|
+
*
|
|
6954
|
+
* A renewal that returns `false` means the lease is gone: this turn stalled past `leaseMs` and
|
|
6955
|
+
* another instance reclaimed the run, so this executor is now a zombie. It is FENCED (`lost`): its
|
|
6956
|
+
* settles degrade to an echo of the store's state instead of clobbering the new owner's writes, and
|
|
6957
|
+
* its own lease release is skipped (it would free the new owner's lease). A renewal that THROWS (a
|
|
6958
|
+
* transient store error) is not evidence of a takeover, so it stays swallowed and the next
|
|
6959
|
+
* interval retries. A lease this execution released on purpose (`handedOff`) is not renewed, and a
|
|
6960
|
+
* renewal resolving after the execution ended (`done`) fences nothing.
|
|
6961
|
+
*/
|
|
6962
|
+
holdExecutionLease(runId) {
|
|
6963
|
+
const lease = {
|
|
6964
|
+
lost: false,
|
|
6965
|
+
handedOff: false,
|
|
6966
|
+
done: false
|
|
6967
|
+
};
|
|
6968
|
+
this.executionLeases.set(runId, lease);
|
|
6969
|
+
const renew = setInterval(() => {
|
|
6970
|
+
if (lease.handedOff || lease.lost || lease.done) return;
|
|
6971
|
+
void this.store.renewRunLock(runId, this.instanceId, this.clock() + this.leaseMs).then((renewed) => {
|
|
6972
|
+
if (renewed || lease.handedOff || lease.lost || lease.done) return;
|
|
6973
|
+
lease.lost = true;
|
|
6974
|
+
console.warn(`[nestjs-durable] lost the lease of run ${runId} while executing it (another instance took it over after it expired); this instance will not write the run's outcome`);
|
|
6975
|
+
}).catch(() => void 0);
|
|
6976
|
+
}, Math.max(50, Math.floor(this.leaseMs / 2)));
|
|
6977
|
+
renew.unref?.();
|
|
6978
|
+
return {
|
|
6979
|
+
lease,
|
|
6980
|
+
stop: /* @__PURE__ */ __name(() => {
|
|
6981
|
+
clearInterval(renew);
|
|
6982
|
+
lease.done = true;
|
|
6983
|
+
if (this.executionLeases.get(runId) === lease) this.executionLeases.delete(runId);
|
|
6984
|
+
}, "stop")
|
|
6985
|
+
};
|
|
6986
|
+
}
|
|
6987
|
+
/** Whether `runId`'s execution on this instance lost its lease (see {@link holdExecutionLease}). */
|
|
6988
|
+
isFenced(runId) {
|
|
6989
|
+
return this.executionLeases.get(runId)?.lost === true;
|
|
6990
|
+
}
|
|
6991
|
+
/**
|
|
6992
|
+
* Mark `runId`'s lease as given away on purpose by its own execution (released mid-turn), so a
|
|
6993
|
+
* renewal that now fails is not read as a takeover.
|
|
6994
|
+
*/
|
|
6995
|
+
handOffLease(runId) {
|
|
6996
|
+
const lease = this.executionLeases.get(runId);
|
|
6997
|
+
if (lease) lease.handedOff = true;
|
|
6998
|
+
}
|
|
6999
|
+
/**
|
|
6490
7000
|
* Advance a remote (cross-SDK) workflow one turn: hand its history to the executor (which dispatches
|
|
6491
7001
|
* a workflow task to the worker and awaits its replay) and apply the decision. Mirrors
|
|
6492
7002
|
* {@link runExecution}'s settle/suspend; the lease is held by {@link execute}. The result that lands
|
|
@@ -6703,6 +7213,7 @@ ${run.workflowVersion}`;
|
|
|
6703
7213
|
* keeps its own lease handling (the TS executor's `finally`, the remote path's caller).
|
|
6704
7214
|
*/
|
|
6705
7215
|
async settleRun(run, outcome) {
|
|
7216
|
+
if (this.isFenced(run.id)) return this.echoCurrentStatus(run.id);
|
|
6706
7217
|
const updatedAt = /* @__PURE__ */ new Date();
|
|
6707
7218
|
if (outcome.kind === "completed") {
|
|
6708
7219
|
const applied2 = await (this.store.updateRunIf ?? this.updateRunIfFallback).call(this.store, run.id, SETTLE_ELIGIBLE_STATUSES, {
|
|
@@ -6783,6 +7294,7 @@ ${run.workflowVersion}`;
|
|
|
6783
7294
|
}
|
|
6784
7295
|
async runRemoteExecution(run, registered) {
|
|
6785
7296
|
if (run.status === "cancelling") {
|
|
7297
|
+
if (this.isFenced(run.id)) return this.echoCurrentStatus(run.id);
|
|
6786
7298
|
const error2 = {
|
|
6787
7299
|
message: "cancelled"
|
|
6788
7300
|
};
|
|
@@ -6791,6 +7303,7 @@ ${run.workflowVersion}`;
|
|
|
6791
7303
|
error: error2,
|
|
6792
7304
|
updatedAt: /* @__PURE__ */ new Date()
|
|
6793
7305
|
});
|
|
7306
|
+
if (this.isFenced(run.id)) return this.echoCurrentStatus(run.id);
|
|
6794
7307
|
this.emit({
|
|
6795
7308
|
type: "run.failed",
|
|
6796
7309
|
runId: run.id,
|
|
@@ -6815,11 +7328,7 @@ ${run.workflowVersion}`;
|
|
|
6815
7328
|
}
|
|
6816
7329
|
const remote = registered.remote;
|
|
6817
7330
|
if (run.status === "pending") {
|
|
6818
|
-
await this.
|
|
6819
|
-
status: "running",
|
|
6820
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
6821
|
-
});
|
|
6822
|
-
run.status = "running";
|
|
7331
|
+
await this.markStarted(run, registered?.singleton !== void 0);
|
|
6823
7332
|
this.emit({
|
|
6824
7333
|
type: "run.started",
|
|
6825
7334
|
runId: run.id,
|
|
@@ -6841,6 +7350,7 @@ ${run.workflowVersion}`;
|
|
|
6841
7350
|
}
|
|
6842
7351
|
const history = await this.remoteHistory(run.id, checkpoints);
|
|
6843
7352
|
if (remote.executor.dispatch) {
|
|
7353
|
+
if (this.isFenced(run.id)) return this.echoCurrentStatus(run.id);
|
|
6844
7354
|
const taskId = `${run.id}:wf:${globalThis.crypto.randomUUID().slice(0, 8)}`;
|
|
6845
7355
|
const silenceMs = this.remoteAdvanceSilenceMs;
|
|
6846
7356
|
const wakeAt = silenceMs != null ? this.clock() + silenceMs : void 0;
|
|
@@ -6856,6 +7366,7 @@ ${run.workflowVersion}`;
|
|
|
6856
7366
|
workflow: run.workflow,
|
|
6857
7367
|
namespace: run.namespace
|
|
6858
7368
|
});
|
|
7369
|
+
this.handOffLease(run.id);
|
|
6859
7370
|
await this.store.releaseRunLock(run.id);
|
|
6860
7371
|
await remote.executor.dispatch(run, history, taskId);
|
|
6861
7372
|
return {
|
|
@@ -6884,6 +7395,7 @@ ${run.workflowVersion}`;
|
|
|
6884
7395
|
message: err instanceof Error ? err.message : String(err),
|
|
6885
7396
|
stack: err instanceof Error ? err.stack : void 0
|
|
6886
7397
|
};
|
|
7398
|
+
if (this.isFenced(run.id)) return this.echoCurrentStatus(run.id);
|
|
6887
7399
|
await this.store.updateRun(run.id, {
|
|
6888
7400
|
status: "failed",
|
|
6889
7401
|
error: error2,
|
|
@@ -6912,6 +7424,7 @@ ${run.workflowVersion}`;
|
|
|
6912
7424
|
* — the caller has already ensured this is the decision for the currently-awaited turn.
|
|
6913
7425
|
*/
|
|
6914
7426
|
async applyDecision(run, decision) {
|
|
7427
|
+
if (this.isFenced(run.id)) return this.echoCurrentStatus(run.id);
|
|
6915
7428
|
if (decision.status === "completed") {
|
|
6916
7429
|
await this.applyCommands(run, decision.commands);
|
|
6917
7430
|
return this.settleRun(run, {
|
|
@@ -7381,6 +7894,7 @@ ${run.workflowVersion}`;
|
|
|
7381
7894
|
* cancel flag stays too, re-derived from the status on any later re-drive regardless.
|
|
7382
7895
|
*/
|
|
7383
7896
|
async compensateAndCancel(run, compensations, replay) {
|
|
7897
|
+
if (this.isFenced(run.id)) return this.echoCurrentStatus(run.id);
|
|
7384
7898
|
const pending = await this.unwindCompensations(run, compensations, replay);
|
|
7385
7899
|
if (pending) return {
|
|
7386
7900
|
runId: run.id,
|
|
@@ -7435,11 +7949,7 @@ ${run.workflowVersion}`;
|
|
|
7435
7949
|
async runExecutionTurn(run, fn, registered) {
|
|
7436
7950
|
if (run.status === "cancelling") this.cancelRequested.add(run.id);
|
|
7437
7951
|
if (run.status === "pending") {
|
|
7438
|
-
await this.
|
|
7439
|
-
status: "running",
|
|
7440
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
7441
|
-
});
|
|
7442
|
-
run.status = "running";
|
|
7952
|
+
await this.markStarted(run, registered?.singleton !== void 0);
|
|
7443
7953
|
this.emit({
|
|
7444
7954
|
type: "run.started",
|
|
7445
7955
|
runId: run.id,
|
|
@@ -7476,6 +7986,7 @@ ${run.workflowVersion}`;
|
|
|
7476
7986
|
});
|
|
7477
7987
|
} catch (err) {
|
|
7478
7988
|
if (err instanceof ContinueAsNew) {
|
|
7989
|
+
if (this.isFenced(run.id)) return await this.echoCurrentStatus(run.id);
|
|
7479
7990
|
if (this.cancelRequested.has(run.id)) {
|
|
7480
7991
|
return await this.compensateAndCancel(run, compensations, replay);
|
|
7481
7992
|
}
|
|
@@ -7815,6 +8326,17 @@ ${run.workflowVersion}`;
|
|
|
7815
8326
|
signalEntity: /* @__PURE__ */ __name((name, key, op, arg, reply) => {
|
|
7816
8327
|
queueMicrotask(() => void this.entities.dispatch(name, key, op, arg, reply).catch(() => void 0));
|
|
7817
8328
|
}, "signalEntity"),
|
|
8329
|
+
// The durable half of a workflow-side entity send: awaited inside the turn (a store write
|
|
8330
|
+
// only, never an inline entity turn), so the ctx can checkpoint after it. See Entities.enqueue.
|
|
8331
|
+
enqueueEntityOp: /* @__PURE__ */ __name((name, key, op, arg, id, reply) => this.entities.enqueue(name, key, op, arg, id, reply), "enqueueEntityOp"),
|
|
8332
|
+
// Start/wake the entity for what enqueueEntityOp buffered — deferred like signalEntity above,
|
|
8333
|
+
// so it never re-enters a still-executing turn. A failed wake loses nothing: the op stays
|
|
8334
|
+
// buffered for the entity's next wait (and recovery's buffered-entity wake).
|
|
8335
|
+
kickEntity: /* @__PURE__ */ __name((name, key) => {
|
|
8336
|
+
queueMicrotask(() => void this.entities.kick(name, key).catch((err) => {
|
|
8337
|
+
console.warn(`[nestjs-durable] waking entity "${name}" key "${key}" failed (its op stays buffered for the next wake):`, err);
|
|
8338
|
+
}));
|
|
8339
|
+
}, "kickEntity"),
|
|
7818
8340
|
interceptStep: /* @__PURE__ */ __name((invocation, body) => this.interceptStep(invocation, body), "interceptStep")
|
|
7819
8341
|
};
|
|
7820
8342
|
}
|
|
@@ -7916,6 +8438,7 @@ ${run.workflowVersion}`;
|
|
|
7916
8438
|
* own `finally` also releases (the run-execution path) is safe.
|
|
7917
8439
|
*/
|
|
7918
8440
|
async parkBlocked(run, blocked) {
|
|
8441
|
+
if (this.isFenced(run.id)) return this.echoCurrentStatus(run.id);
|
|
7919
8442
|
const error2 = {
|
|
7920
8443
|
message: blocked.reason,
|
|
7921
8444
|
code: blocked.code,
|
|
@@ -7956,7 +8479,7 @@ ${run.workflowVersion}`;
|
|
|
7956
8479
|
"blocked"
|
|
7957
8480
|
]
|
|
7958
8481
|
});
|
|
7959
|
-
return blocked.filter((r) => (this.namespace === void 0 || r.namespace === void 0 || r.namespace === this.namespace) && r.wakeAt !== void 0 && r.wakeAt <= nowMs);
|
|
8482
|
+
return blocked.filter((r) => (this.namespace === void 0 || r.namespace === void 0 || poolNamespace(r.namespace) === this.namespace) && r.wakeAt !== void 0 && r.wakeAt <= nowMs);
|
|
7960
8483
|
}
|
|
7961
8484
|
async callRemote(runId, seq, step, input, queue, transport, replay, admission, routing) {
|
|
7962
8485
|
const group = stepGroup(step, routing?.namespace);
|
|
@@ -8495,17 +9018,14 @@ ${run.workflowVersion}`;
|
|
|
8495
9018
|
const run = await this.store.getRun(decision.runId);
|
|
8496
9019
|
if (!run || run.awaitingDecisionTaskId !== decision.taskId) return;
|
|
8497
9020
|
if (run.status === "cancelled" || run.status === "completed" || run.status === "dead") return;
|
|
8498
|
-
if (this.namespace !== void 0 && run.namespace !== void 0 && run.namespace !== this.namespace) {
|
|
9021
|
+
if (this.namespace !== void 0 && run.namespace !== void 0 && poolNamespace(run.namespace) !== this.namespace) {
|
|
8499
9022
|
return;
|
|
8500
9023
|
}
|
|
8501
9024
|
const nowMs = this.clock();
|
|
8502
9025
|
if (!await this.store.tryLockRun(decision.runId, this.instanceId, nowMs + this.leaseMs, nowMs)) {
|
|
8503
9026
|
return;
|
|
8504
9027
|
}
|
|
8505
|
-
const
|
|
8506
|
-
void this.store.renewRunLock(decision.runId, this.instanceId, this.clock() + this.leaseMs).catch(() => void 0);
|
|
8507
|
-
}, Math.max(50, Math.floor(this.leaseMs / 2)));
|
|
8508
|
-
renew.unref?.();
|
|
9028
|
+
const held = this.holdExecutionLease(decision.runId);
|
|
8509
9029
|
try {
|
|
8510
9030
|
const fresh = await this.store.getRun(decision.runId);
|
|
8511
9031
|
if (!fresh || fresh.awaitingDecisionTaskId !== decision.taskId) return;
|
|
@@ -8520,8 +9040,9 @@ ${run.workflowVersion}`;
|
|
|
8520
9040
|
fresh.awaitingDecisionTaskId = void 0;
|
|
8521
9041
|
await this.applyDecision(fresh, decision);
|
|
8522
9042
|
} finally {
|
|
8523
|
-
|
|
8524
|
-
|
|
9043
|
+
const fenced = held.lease.lost;
|
|
9044
|
+
held.stop();
|
|
9045
|
+
if (!fenced) await this.store.releaseRunLock(decision.runId);
|
|
8525
9046
|
}
|
|
8526
9047
|
}
|
|
8527
9048
|
/** Earliest clock time a suspended turn's deadline is worth LOOKING at again (`runId` → clock ms) —
|
|
@@ -8717,6 +9238,9 @@ function childIdsOf(checkpoints) {
|
|
|
8717
9238
|
}
|
|
8718
9239
|
__name(childIdsOf, "childIdsOf");
|
|
8719
9240
|
|
|
9241
|
+
// src/run-history.ts
|
|
9242
|
+
var RUN_HISTORY_FIXTURE_VERSION = 1;
|
|
9243
|
+
|
|
8720
9244
|
// src/run-facets.ts
|
|
8721
9245
|
function facetOrigin(origin) {
|
|
8722
9246
|
return origin?.trim() ? origin : null;
|
|
@@ -9270,13 +9794,14 @@ var CodecStateStore = class {
|
|
|
9270
9794
|
}
|
|
9271
9795
|
inner;
|
|
9272
9796
|
codec;
|
|
9797
|
+
extended;
|
|
9273
9798
|
/** Atomic claims touch only searchable metadata and bypass payload encoding. */
|
|
9274
9799
|
tryAdmitSingleton;
|
|
9275
9800
|
/**
|
|
9276
9801
|
* Forwarded verbatim — a facet cell counts `status`/`origin`, neither of which the codec touches
|
|
9277
|
-
* (it encodes payloads: input/output/error). Bound as a
|
|
9278
|
-
* actually aggregates, so `store.runFacets?.()` still
|
|
9279
|
-
* of resolving to a silently wrong empty result.
|
|
9802
|
+
* (it encodes payloads: input/output, plus events/error under `extended` coverage). Bound as a
|
|
9803
|
+
* PROPERTY, and only when the inner store actually aggregates, so `store.runFacets?.()` still
|
|
9804
|
+
* reports absence through the wrapper instead of resolving to a silently wrong empty result.
|
|
9280
9805
|
*/
|
|
9281
9806
|
runFacets;
|
|
9282
9807
|
/**
|
|
@@ -9328,14 +9853,15 @@ var CodecStateStore = class {
|
|
|
9328
9853
|
updateSchedule;
|
|
9329
9854
|
deleteSchedule;
|
|
9330
9855
|
listSchedules;
|
|
9331
|
-
constructor(inner, codec) {
|
|
9856
|
+
constructor(inner, codec, options = {}) {
|
|
9332
9857
|
this.inner = inner;
|
|
9333
9858
|
this.codec = codec;
|
|
9859
|
+
this.extended = options.coverage === "extended";
|
|
9334
9860
|
if (inner.tryAdmitSingleton) this.tryAdmitSingleton = inner.tryAdmitSingleton.bind(inner);
|
|
9335
9861
|
const deleteRuns = inner.deleteRuns;
|
|
9336
9862
|
if (deleteRuns) this.deleteRuns = (ids) => deleteRuns.call(inner, ids);
|
|
9337
9863
|
const orphans = inner.listOrphanedRuns;
|
|
9338
|
-
if (orphans) this.listOrphanedRuns = async (nowMs, limit, namespace) => (await orphans.call(inner, nowMs, limit, namespace)).map((r) => this.decRun(r));
|
|
9864
|
+
if (orphans) this.listOrphanedRuns = async (nowMs, limit, namespace, ownedBy) => (await orphans.call(inner, nowMs, limit, namespace, ownedBy)).map((r) => this.decRun(r));
|
|
9339
9865
|
const updateIf = inner.updateRunIf;
|
|
9340
9866
|
if (updateIf) this.updateRunIf = (runId, expected, patch) => updateIf.call(inner, runId, expected, this.encRunPatch(patch));
|
|
9341
9867
|
const saveIfPending = inner.saveCheckpointIfPending;
|
|
@@ -9388,32 +9914,52 @@ var CodecStateStore = class {
|
|
|
9388
9914
|
return v === void 0 ? void 0 : this.codec.decode(v);
|
|
9389
9915
|
}
|
|
9390
9916
|
encRun(run) {
|
|
9391
|
-
|
|
9917
|
+
const next = {
|
|
9392
9918
|
...run,
|
|
9393
9919
|
input: this.enc(run.input),
|
|
9394
9920
|
output: this.enc(run.output)
|
|
9395
9921
|
};
|
|
9922
|
+
if (this.extended && run.error !== void 0) {
|
|
9923
|
+
next.error = this.enc(run.error);
|
|
9924
|
+
}
|
|
9925
|
+
return next;
|
|
9396
9926
|
}
|
|
9397
9927
|
decRun(run) {
|
|
9398
|
-
|
|
9928
|
+
if (!run) return run;
|
|
9929
|
+
const next = {
|
|
9399
9930
|
...run,
|
|
9400
9931
|
input: this.dec(run.input),
|
|
9401
9932
|
output: this.dec(run.output)
|
|
9402
9933
|
};
|
|
9934
|
+
if (this.extended && run.error !== void 0) {
|
|
9935
|
+
next.error = this.dec(run.error);
|
|
9936
|
+
}
|
|
9937
|
+
return next;
|
|
9403
9938
|
}
|
|
9404
9939
|
encCp(cp) {
|
|
9405
|
-
|
|
9940
|
+
const next = {
|
|
9406
9941
|
...cp,
|
|
9407
9942
|
input: this.enc(cp.input),
|
|
9408
9943
|
output: this.enc(cp.output)
|
|
9409
9944
|
};
|
|
9945
|
+
if (this.extended) {
|
|
9946
|
+
if (cp.events !== void 0) next.events = this.enc(cp.events);
|
|
9947
|
+
if (cp.error !== void 0) next.error = this.enc(cp.error);
|
|
9948
|
+
}
|
|
9949
|
+
return next;
|
|
9410
9950
|
}
|
|
9411
9951
|
decCp(cp) {
|
|
9412
|
-
|
|
9952
|
+
if (!cp) return cp;
|
|
9953
|
+
const next = {
|
|
9413
9954
|
...cp,
|
|
9414
9955
|
input: this.dec(cp.input),
|
|
9415
9956
|
output: this.dec(cp.output)
|
|
9416
9957
|
};
|
|
9958
|
+
if (this.extended) {
|
|
9959
|
+
if (cp.events !== void 0) next.events = this.dec(cp.events);
|
|
9960
|
+
if (cp.error !== void 0) next.error = this.dec(cp.error);
|
|
9961
|
+
}
|
|
9962
|
+
return next;
|
|
9417
9963
|
}
|
|
9418
9964
|
encSpec(spec) {
|
|
9419
9965
|
return "input" in spec ? {
|
|
@@ -9452,6 +9998,9 @@ var CodecStateStore = class {
|
|
|
9452
9998
|
};
|
|
9453
9999
|
if ("input" in patch) next.input = this.enc(patch.input);
|
|
9454
10000
|
if ("output" in patch) next.output = this.enc(patch.output);
|
|
10001
|
+
if (this.extended && patch.error !== void 0) {
|
|
10002
|
+
next.error = this.enc(patch.error);
|
|
10003
|
+
}
|
|
9455
10004
|
return next;
|
|
9456
10005
|
}
|
|
9457
10006
|
async getRun(runId) {
|
|
@@ -9487,8 +10036,8 @@ var CodecStateStore = class {
|
|
|
9487
10036
|
tryLockRun(runId, owner, leaseUntilMs, nowMs) {
|
|
9488
10037
|
return this.inner.tryLockRun(runId, owner, leaseUntilMs, nowMs);
|
|
9489
10038
|
}
|
|
9490
|
-
releaseRunLock(runId) {
|
|
9491
|
-
return this.inner.releaseRunLock(runId);
|
|
10039
|
+
releaseRunLock(runId, owner) {
|
|
10040
|
+
return owner === void 0 ? this.inner.releaseRunLock(runId) : this.inner.releaseRunLock(runId, owner);
|
|
9492
10041
|
}
|
|
9493
10042
|
renewRunLock(runId, owner, leaseUntilMs) {
|
|
9494
10043
|
return this.inner.renewRunLock(runId, owner, leaseUntilMs);
|
|
@@ -9856,7 +10405,7 @@ var InMemoryStateStore = class {
|
|
|
9856
10405
|
async createRun(run) {
|
|
9857
10406
|
this.runs.set(run.id, {
|
|
9858
10407
|
...run,
|
|
9859
|
-
namespace: run.namespace
|
|
10408
|
+
namespace: run.namespace || "default"
|
|
9860
10409
|
});
|
|
9861
10410
|
this.reindexAttributes(run.id, run.searchAttributes);
|
|
9862
10411
|
}
|
|
@@ -9871,7 +10420,7 @@ var InMemoryStateStore = class {
|
|
|
9871
10420
|
applyRunPatch(runId, inputPatch) {
|
|
9872
10421
|
const patch = inputPatch.status && TERMINAL_RUN_STATUSES.includes(inputPatch.status) ? {
|
|
9873
10422
|
...inputPatch,
|
|
9874
|
-
tags: ("tags" in inputPatch ? inputPatch.tags : this.runs.get(runId)?.tags)
|
|
10423
|
+
tags: withoutSingletonMarkers("tags" in inputPatch ? inputPatch.tags : this.runs.get(runId)?.tags)
|
|
9875
10424
|
} : inputPatch;
|
|
9876
10425
|
const existing = this.runs.get(runId);
|
|
9877
10426
|
if (!existing) throw new Error(`run ${runId} not found`);
|
|
@@ -9959,10 +10508,10 @@ var InMemoryStateStore = class {
|
|
|
9959
10508
|
return true;
|
|
9960
10509
|
});
|
|
9961
10510
|
}
|
|
9962
|
-
async listOrphanedRuns(nowMs, limit, namespace) {
|
|
10511
|
+
async listOrphanedRuns(nowMs, limit, namespace, ownedBy) {
|
|
9963
10512
|
return [
|
|
9964
10513
|
...this.runs.values()
|
|
9965
|
-
].filter((r) => (r.status === "running" || r.status === "cancelling") && (r.lockedUntil == null || r.lockedUntil <= nowMs) && (namespace === void 0 || r.namespace === namespace)).sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime()).slice(0, Math.max(0, limit)).map((r) => ({
|
|
10514
|
+
].filter((r) => (r.status === "running" || r.status === "cancelling") && (r.lockedUntil == null || r.lockedUntil <= nowMs || ownedBy !== void 0 && r.lockedBy === ownedBy.owner && r.lockedUntil < ownedBy.lockedBefore) && (namespace === void 0 || r.namespace === namespace)).sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime()).slice(0, Math.max(0, limit)).map((r) => ({
|
|
9966
10515
|
...r
|
|
9967
10516
|
}));
|
|
9968
10517
|
}
|
|
@@ -9996,9 +10545,9 @@ var InMemoryStateStore = class {
|
|
|
9996
10545
|
run.lockedUntil = leaseUntilMs;
|
|
9997
10546
|
return true;
|
|
9998
10547
|
}
|
|
9999
|
-
async releaseRunLock(runId) {
|
|
10548
|
+
async releaseRunLock(runId, owner) {
|
|
10000
10549
|
const run = this.runs.get(runId);
|
|
10001
|
-
if (run) {
|
|
10550
|
+
if (run && (owner === void 0 || run.lockedBy === owner)) {
|
|
10002
10551
|
run.lockedBy = void 0;
|
|
10003
10552
|
run.lockedUntil = void 0;
|
|
10004
10553
|
}
|
|
@@ -10349,12 +10898,14 @@ var InMemoryTransport = class {
|
|
|
10349
10898
|
ContinueAsNew,
|
|
10350
10899
|
DEFAULT_CONCURRENCY_STATUSES,
|
|
10351
10900
|
DEFAULT_PARTITION,
|
|
10901
|
+
DEFAULT_STALLED_AFTER_MS,
|
|
10352
10902
|
DURABLE_OPTIONS,
|
|
10353
10903
|
DURABLE_OPTIONS_CANONICAL,
|
|
10354
10904
|
DURABLE_STEP_CONFIG,
|
|
10355
10905
|
DURABLE_STEP_NAME,
|
|
10356
10906
|
DurableWorkflow,
|
|
10357
10907
|
ENGINE_MINTED_TAG_PREFIXES,
|
|
10908
|
+
ENTITY_WORKFLOW,
|
|
10358
10909
|
EVENT_BUFFER_SCAN_LIMIT,
|
|
10359
10910
|
EVENT_TOKEN_PREFIX,
|
|
10360
10911
|
Entities,
|
|
@@ -10371,6 +10922,7 @@ var InMemoryTransport = class {
|
|
|
10371
10922
|
NestedWorkflowCallError,
|
|
10372
10923
|
NonDeterminismError,
|
|
10373
10924
|
QueueController,
|
|
10925
|
+
RUN_HISTORY_FIXTURE_VERSION,
|
|
10374
10926
|
RUN_VALUE_FACET_LIMIT,
|
|
10375
10927
|
RUN_VALUE_FACET_SCAN,
|
|
10376
10928
|
RemoteStepError,
|
|
@@ -10380,6 +10932,8 @@ var InMemoryTransport = class {
|
|
|
10380
10932
|
RunGateway,
|
|
10381
10933
|
SINGLETON_ACTIVE_STATUSES,
|
|
10382
10934
|
SINGLETON_ADMITTED_TAG,
|
|
10935
|
+
SINGLETON_RESERVED_TAGS,
|
|
10936
|
+
SINGLETON_TRACKED_TAG,
|
|
10383
10937
|
STATE_STORE,
|
|
10384
10938
|
STATE_STORE_CANONICAL,
|
|
10385
10939
|
SUMMARY_CHILD_LIMIT,
|
|
@@ -10442,6 +10996,7 @@ var InMemoryTransport = class {
|
|
|
10442
10996
|
facetOrigin,
|
|
10443
10997
|
fanPageBounds,
|
|
10444
10998
|
fanPageEtag,
|
|
10999
|
+
findStalledRuns,
|
|
10445
11000
|
foldedCount,
|
|
10446
11001
|
handoffHandlerId,
|
|
10447
11002
|
handoffName,
|
|
@@ -10455,6 +11010,7 @@ var InMemoryTransport = class {
|
|
|
10455
11010
|
isLegacyDescriptor,
|
|
10456
11011
|
isNonEmptyRunScope,
|
|
10457
11012
|
isWorkflowControlFlowSignal,
|
|
11013
|
+
legacySingletonHolders,
|
|
10458
11014
|
listSignalWaitersOfRuns,
|
|
10459
11015
|
liveHandoff,
|
|
10460
11016
|
log,
|
|
@@ -10472,8 +11028,10 @@ var InMemoryTransport = class {
|
|
|
10472
11028
|
pageFanFromStore,
|
|
10473
11029
|
parentRunIdOf,
|
|
10474
11030
|
parseDuration,
|
|
11031
|
+
parseWebhookToken,
|
|
10475
11032
|
partitionOfToken,
|
|
10476
11033
|
planDispatch,
|
|
11034
|
+
poolNamespace,
|
|
10477
11035
|
prevCronFireMs,
|
|
10478
11036
|
queryCheckpointsOf,
|
|
10479
11037
|
readSearchAttributes,
|
|
@@ -10509,6 +11067,8 @@ var InMemoryTransport = class {
|
|
|
10509
11067
|
tenantGroup,
|
|
10510
11068
|
useDurableExecution,
|
|
10511
11069
|
warn,
|
|
11070
|
+
withSingletonTracked,
|
|
11071
|
+
withoutSingletonMarkers,
|
|
10512
11072
|
workflowName
|
|
10513
11073
|
});
|
|
10514
11074
|
//# sourceMappingURL=index.cjs.map
|