@dudousxd/nestjs-durable-core 0.63.0 → 0.65.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 +362 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +492 -127
- package/dist/index.d.ts +492 -127
- package/dist/index.js +355 -14
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -60,6 +60,8 @@ __export(index_exports, {
|
|
|
60
60
|
WorkflowBlocked: () => WorkflowBlocked,
|
|
61
61
|
WorkflowEngine: () => WorkflowEngine,
|
|
62
62
|
WorkflowSuspended: () => WorkflowSuspended,
|
|
63
|
+
aggregateAnnouncements: () => aggregateAnnouncements,
|
|
64
|
+
announcementsOf: () => announcementsOf,
|
|
63
65
|
applyAttributeQuery: () => applyAttributeQuery,
|
|
64
66
|
attributeColumnFor: () => attributeColumnFor,
|
|
65
67
|
attributeOperand: () => attributeOperand,
|
|
@@ -68,12 +70,14 @@ __export(index_exports, {
|
|
|
68
70
|
canRoute: () => canRoute,
|
|
69
71
|
capableWorkers: () => capableWorkers,
|
|
70
72
|
classifyWaiterToken: () => classifyWaiterToken,
|
|
73
|
+
clearDurableExecutionWrappers: () => clearDurableExecutionWrappers,
|
|
71
74
|
collectMetrics: () => collectMetrics,
|
|
72
75
|
controlPlaneDescriptor: () => controlPlaneDescriptor,
|
|
73
76
|
createStepLogger: () => createStepLogger,
|
|
74
77
|
currentStep: () => currentStep,
|
|
75
78
|
currentWorkflowCtx: () => currentWorkflowCtx,
|
|
76
79
|
debug: () => debug,
|
|
80
|
+
describeWorker: () => describeWorker,
|
|
77
81
|
descriptorHash: () => descriptorHash,
|
|
78
82
|
error: () => error,
|
|
79
83
|
eventMatchOf: () => eventMatchOf,
|
|
@@ -81,6 +85,7 @@ __export(index_exports, {
|
|
|
81
85
|
eventNameOf: () => eventNameOf,
|
|
82
86
|
eventPrefix: () => eventPrefix,
|
|
83
87
|
eventToken: () => eventToken,
|
|
88
|
+
hasDurableExecutionWrappers: () => hasDurableExecutionWrappers,
|
|
84
89
|
heartbeatStatus: () => heartbeatStatus,
|
|
85
90
|
indexWaitersByRun: () => indexWaitersByRun,
|
|
86
91
|
info: () => info,
|
|
@@ -100,6 +105,7 @@ __export(index_exports, {
|
|
|
100
105
|
requiredCapabilities: () => requiredCapabilities,
|
|
101
106
|
resolveRouting: () => resolveRouting,
|
|
102
107
|
resolveRunWaiting: () => resolveRunWaiting,
|
|
108
|
+
runDurableExecution: () => runDurableExecution,
|
|
103
109
|
runInStepLogger: () => runInStepLogger,
|
|
104
110
|
runInWorkflowCtx: () => runInWorkflowCtx,
|
|
105
111
|
runSchedules: () => runSchedules,
|
|
@@ -114,6 +120,7 @@ __export(index_exports, {
|
|
|
114
120
|
subEvent: () => subEvent,
|
|
115
121
|
subProcess: () => subProcess,
|
|
116
122
|
tenantGroup: () => tenantGroup,
|
|
123
|
+
useDurableExecution: () => useDurableExecution,
|
|
117
124
|
warn: () => warn,
|
|
118
125
|
workflowName: () => workflowName
|
|
119
126
|
});
|
|
@@ -599,6 +606,57 @@ function backoffDelay(attempt, options) {
|
|
|
599
606
|
}
|
|
600
607
|
__name(backoffDelay, "backoffDelay");
|
|
601
608
|
|
|
609
|
+
// src/execution-hooks.ts
|
|
610
|
+
var wrappers = [];
|
|
611
|
+
function useDurableExecution(wrapper) {
|
|
612
|
+
wrappers.push(wrapper);
|
|
613
|
+
return () => {
|
|
614
|
+
const i = wrappers.indexOf(wrapper);
|
|
615
|
+
if (i >= 0) wrappers.splice(i, 1);
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
__name(useDurableExecution, "useDurableExecution");
|
|
619
|
+
function clearDurableExecutionWrappers() {
|
|
620
|
+
wrappers.length = 0;
|
|
621
|
+
}
|
|
622
|
+
__name(clearDurableExecutionWrappers, "clearDurableExecutionWrappers");
|
|
623
|
+
function hasDurableExecutionWrappers() {
|
|
624
|
+
return wrappers.length > 0;
|
|
625
|
+
}
|
|
626
|
+
__name(hasDurableExecutionWrappers, "hasDurableExecutionWrappers");
|
|
627
|
+
async function runDurableExecution(execution, body) {
|
|
628
|
+
if (wrappers.length === 0) return body();
|
|
629
|
+
let outcome;
|
|
630
|
+
const inner = /* @__PURE__ */ __name(async () => {
|
|
631
|
+
try {
|
|
632
|
+
const value = await body();
|
|
633
|
+
outcome = {
|
|
634
|
+
ok: true,
|
|
635
|
+
value
|
|
636
|
+
};
|
|
637
|
+
return value;
|
|
638
|
+
} catch (error2) {
|
|
639
|
+
outcome = {
|
|
640
|
+
ok: false,
|
|
641
|
+
error: error2
|
|
642
|
+
};
|
|
643
|
+
throw error2;
|
|
644
|
+
}
|
|
645
|
+
}, "inner");
|
|
646
|
+
const chain = wrappers.reduceRight((next, wrapper) => () => wrapper(execution, next), inner);
|
|
647
|
+
try {
|
|
648
|
+
await chain();
|
|
649
|
+
} catch (error2) {
|
|
650
|
+
if (outcome === void 0) throw error2;
|
|
651
|
+
}
|
|
652
|
+
if (outcome === void 0) {
|
|
653
|
+
throw new Error("a durable execution wrapper returned without calling next() \u2014 the unit of work never ran");
|
|
654
|
+
}
|
|
655
|
+
if (outcome.ok) return outcome.value;
|
|
656
|
+
throw outcome.error;
|
|
657
|
+
}
|
|
658
|
+
__name(runDurableExecution, "runDurableExecution");
|
|
659
|
+
|
|
602
660
|
// src/step-logger.ts
|
|
603
661
|
function withDuration(start, now, data) {
|
|
604
662
|
if (data && typeof data === "object" && "durationMs" in data) {
|
|
@@ -740,7 +798,14 @@ async function runStepHandler(task, handler) {
|
|
|
740
798
|
} : result, "withEvents");
|
|
741
799
|
try {
|
|
742
800
|
const logger = createStepLogger(events, Date.now);
|
|
743
|
-
const output = await
|
|
801
|
+
const output = await runDurableExecution({
|
|
802
|
+
unit: "step",
|
|
803
|
+
runId: task.runId,
|
|
804
|
+
workflow: void 0,
|
|
805
|
+
name: task.name,
|
|
806
|
+
seq: task.seq,
|
|
807
|
+
attempt: task.attempt
|
|
808
|
+
}, async () => runInStepLogger(logger, () => handler(task.input, logger)));
|
|
744
809
|
return withEvents({
|
|
745
810
|
...base,
|
|
746
811
|
status: "completed",
|
|
@@ -1041,6 +1106,9 @@ function normalizeDescriptor(raw) {
|
|
|
1041
1106
|
],
|
|
1042
1107
|
workflows: raw.workflows ?? [],
|
|
1043
1108
|
steps: raw.steps ?? [],
|
|
1109
|
+
...raw.registrations !== void 0 ? {
|
|
1110
|
+
registrations: raw.registrations
|
|
1111
|
+
} : {},
|
|
1044
1112
|
...raw.partition !== void 0 ? {
|
|
1045
1113
|
partition: raw.partition
|
|
1046
1114
|
} : {},
|
|
@@ -1051,6 +1119,23 @@ function normalizeDescriptor(raw) {
|
|
|
1051
1119
|
};
|
|
1052
1120
|
}
|
|
1053
1121
|
__name(normalizeDescriptor, "normalizeDescriptor");
|
|
1122
|
+
function canonicalizeRegistrations(regs) {
|
|
1123
|
+
const canonical = regs.map((r) => ({
|
|
1124
|
+
name: r.name,
|
|
1125
|
+
version: r.version ?? null,
|
|
1126
|
+
group: r.group ?? null,
|
|
1127
|
+
requires: r.requires ? [
|
|
1128
|
+
...new Set(r.requires)
|
|
1129
|
+
].sort() : null,
|
|
1130
|
+
origin: r.origin ?? null
|
|
1131
|
+
}));
|
|
1132
|
+
const byJson = /* @__PURE__ */ new Map();
|
|
1133
|
+
for (const entry of canonical) byJson.set(JSON.stringify(entry), entry);
|
|
1134
|
+
return [
|
|
1135
|
+
...byJson.keys()
|
|
1136
|
+
].sort().map((k) => byJson.get(k));
|
|
1137
|
+
}
|
|
1138
|
+
__name(canonicalizeRegistrations, "canonicalizeRegistrations");
|
|
1054
1139
|
function canonicalizeForHash(d) {
|
|
1055
1140
|
const set = /* @__PURE__ */ __name((xs) => [
|
|
1056
1141
|
...new Set(xs)
|
|
@@ -1072,6 +1157,9 @@ function canonicalizeForHash(d) {
|
|
|
1072
1157
|
capabilities: set(d.capabilities),
|
|
1073
1158
|
workflows: set(d.workflows),
|
|
1074
1159
|
steps: set(d.steps),
|
|
1160
|
+
...d.registrations !== void 0 ? {
|
|
1161
|
+
registrations: canonicalizeRegistrations(d.registrations)
|
|
1162
|
+
} : {},
|
|
1075
1163
|
partition: d.partition ?? null,
|
|
1076
1164
|
namespace: d.namespace ?? null,
|
|
1077
1165
|
startedAt: d.startedAt
|
|
@@ -1104,6 +1192,144 @@ function heartbeatStatus(descriptor, opts = {}) {
|
|
|
1104
1192
|
};
|
|
1105
1193
|
}
|
|
1106
1194
|
__name(heartbeatStatus, "heartbeatStatus");
|
|
1195
|
+
function describeWorker(opts) {
|
|
1196
|
+
const version = CURRENT_PROTOCOL_VERSION;
|
|
1197
|
+
const registrations = [
|
|
1198
|
+
...opts.registrations ?? []
|
|
1199
|
+
].sort((a, b) => `${a.name}@${a.version ?? ""}`.localeCompare(`${b.name}@${b.version ?? ""}`));
|
|
1200
|
+
return {
|
|
1201
|
+
instanceId: opts.instanceId,
|
|
1202
|
+
runtime: opts.runtime,
|
|
1203
|
+
sdk: {
|
|
1204
|
+
name: opts.sdk.name,
|
|
1205
|
+
version: opts.sdk.version
|
|
1206
|
+
},
|
|
1207
|
+
protocol: {
|
|
1208
|
+
version,
|
|
1209
|
+
range: [
|
|
1210
|
+
version,
|
|
1211
|
+
version
|
|
1212
|
+
]
|
|
1213
|
+
},
|
|
1214
|
+
capabilities: [
|
|
1215
|
+
...LEGACY_V1_CAPABILITIES
|
|
1216
|
+
],
|
|
1217
|
+
workflows: [
|
|
1218
|
+
...opts.workflows ?? []
|
|
1219
|
+
].sort(),
|
|
1220
|
+
steps: [
|
|
1221
|
+
...opts.steps
|
|
1222
|
+
].sort(),
|
|
1223
|
+
...registrations.length > 0 ? {
|
|
1224
|
+
registrations
|
|
1225
|
+
} : {},
|
|
1226
|
+
...opts.partition ? {
|
|
1227
|
+
partition: opts.partition
|
|
1228
|
+
} : {},
|
|
1229
|
+
...opts.namespace && opts.namespace !== "default" ? {
|
|
1230
|
+
namespace: opts.namespace
|
|
1231
|
+
} : {},
|
|
1232
|
+
startedAt: opts.startedAt
|
|
1233
|
+
};
|
|
1234
|
+
}
|
|
1235
|
+
__name(describeWorker, "describeWorker");
|
|
1236
|
+
|
|
1237
|
+
// src/handshake/announced.ts
|
|
1238
|
+
function announcementsOf(raw) {
|
|
1239
|
+
const d = normalizeDescriptor(raw);
|
|
1240
|
+
const identity = {
|
|
1241
|
+
instanceId: d.instanceId,
|
|
1242
|
+
runtime: d.runtime
|
|
1243
|
+
};
|
|
1244
|
+
const announcements = (d.registrations ?? []).map((reg) => ({
|
|
1245
|
+
...reg,
|
|
1246
|
+
...identity
|
|
1247
|
+
}));
|
|
1248
|
+
const covered = new Set(announcements.map((a) => a.name));
|
|
1249
|
+
for (const name of d.workflows) {
|
|
1250
|
+
if (!covered.has(name)) announcements.push({
|
|
1251
|
+
name,
|
|
1252
|
+
...identity
|
|
1253
|
+
});
|
|
1254
|
+
}
|
|
1255
|
+
return announcements;
|
|
1256
|
+
}
|
|
1257
|
+
__name(announcementsOf, "announcementsOf");
|
|
1258
|
+
function announcementKey(a) {
|
|
1259
|
+
return a.version === void 0 ? a.name : `${a.name}@${a.version}`;
|
|
1260
|
+
}
|
|
1261
|
+
__name(announcementKey, "announcementKey");
|
|
1262
|
+
function stated(values) {
|
|
1263
|
+
return [
|
|
1264
|
+
...new Set(values.filter((v) => v !== void 0))
|
|
1265
|
+
].sort();
|
|
1266
|
+
}
|
|
1267
|
+
__name(stated, "stated");
|
|
1268
|
+
function aggregateAnnouncements(descriptors) {
|
|
1269
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
1270
|
+
for (const descriptor of descriptors) {
|
|
1271
|
+
for (const announcement of announcementsOf(descriptor)) {
|
|
1272
|
+
const key = announcementKey(announcement);
|
|
1273
|
+
const bucket = byKey.get(key);
|
|
1274
|
+
if (bucket) bucket.push(announcement);
|
|
1275
|
+
else byKey.set(key, [
|
|
1276
|
+
announcement
|
|
1277
|
+
]);
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
const out = [];
|
|
1281
|
+
for (const [key, announcements] of byKey) {
|
|
1282
|
+
const groups = stated(announcements.map((a) => a.group));
|
|
1283
|
+
const origins = stated(announcements.map((a) => a.origin));
|
|
1284
|
+
const requiresByJson = /* @__PURE__ */ new Map();
|
|
1285
|
+
for (const a of announcements) {
|
|
1286
|
+
if (a.requires === void 0) continue;
|
|
1287
|
+
const canonical = [
|
|
1288
|
+
...new Set(a.requires)
|
|
1289
|
+
].sort();
|
|
1290
|
+
requiresByJson.set(JSON.stringify(canonical), canonical);
|
|
1291
|
+
}
|
|
1292
|
+
const requires = [
|
|
1293
|
+
...requiresByJson.values()
|
|
1294
|
+
].sort((x, y) => x.join(",").localeCompare(y.join(",")));
|
|
1295
|
+
const disagreements = [];
|
|
1296
|
+
if (groups.length > 1) disagreements.push({
|
|
1297
|
+
axis: "group",
|
|
1298
|
+
values: groups
|
|
1299
|
+
});
|
|
1300
|
+
if (origins.length > 1) disagreements.push({
|
|
1301
|
+
axis: "origin",
|
|
1302
|
+
values: origins
|
|
1303
|
+
});
|
|
1304
|
+
if (requires.length > 1) {
|
|
1305
|
+
disagreements.push({
|
|
1306
|
+
axis: "requires",
|
|
1307
|
+
values: requires.map((r) => r.join(","))
|
|
1308
|
+
});
|
|
1309
|
+
}
|
|
1310
|
+
const first = announcements[0];
|
|
1311
|
+
if (!first) continue;
|
|
1312
|
+
out.push({
|
|
1313
|
+
key,
|
|
1314
|
+
name: first.name,
|
|
1315
|
+
...first.version !== void 0 ? {
|
|
1316
|
+
version: first.version
|
|
1317
|
+
} : {},
|
|
1318
|
+
groups,
|
|
1319
|
+
origins,
|
|
1320
|
+
requires,
|
|
1321
|
+
runtimes: [
|
|
1322
|
+
...new Set(announcements.map((a) => a.runtime))
|
|
1323
|
+
].sort(),
|
|
1324
|
+
instances: [
|
|
1325
|
+
...new Set(announcements.map((a) => a.instanceId))
|
|
1326
|
+
].sort(),
|
|
1327
|
+
disagreements
|
|
1328
|
+
});
|
|
1329
|
+
}
|
|
1330
|
+
return out.sort((a, b) => a.key.localeCompare(b.key));
|
|
1331
|
+
}
|
|
1332
|
+
__name(aggregateAnnouncements, "aggregateAnnouncements");
|
|
1107
1333
|
|
|
1108
1334
|
// src/handshake/negotiate.ts
|
|
1109
1335
|
var rangesOverlap = /* @__PURE__ */ __name((a, b) => Math.max(a[0], b[0]) <= Math.min(a[1], b[1]), "rangesOverlap");
|
|
@@ -1631,6 +1857,30 @@ var TransportPool = class {
|
|
|
1631
1857
|
}
|
|
1632
1858
|
return descriptors;
|
|
1633
1859
|
}
|
|
1860
|
+
/**
|
|
1861
|
+
* Every live descriptor in the deployment, merged across every transport that can report them and
|
|
1862
|
+
* de-duplicated by `instanceId` — one worker publishes the same descriptor under each token it
|
|
1863
|
+
* consumes, and it must count once. Returns `[]` when no transport implements
|
|
1864
|
+
* {@link Transport.readAllWorkerDescriptors} (a pure in-process pool advertises nothing).
|
|
1865
|
+
*
|
|
1866
|
+
* First writer wins on a duplicate id: the copies are the same bytes by construction (one process,
|
|
1867
|
+
* one descriptor, published N times), so there is nothing to choose between. Two DIFFERENT
|
|
1868
|
+
* processes sharing an `instanceId` would collapse into one here — that is a misconfiguration the
|
|
1869
|
+
* fleet already suffers from (they also share heartbeat and descriptor keys, so they overwrite each
|
|
1870
|
+
* other's advertisement), not something this read can repair.
|
|
1871
|
+
*/
|
|
1872
|
+
async readAllWorkerDescriptors() {
|
|
1873
|
+
const byInstance = /* @__PURE__ */ new Map();
|
|
1874
|
+
for (const { transport } of this.transports) {
|
|
1875
|
+
if (!transport.readAllWorkerDescriptors) continue;
|
|
1876
|
+
for (const descriptor of await transport.readAllWorkerDescriptors()) {
|
|
1877
|
+
if (!byInstance.has(descriptor.instanceId)) byInstance.set(descriptor.instanceId, descriptor);
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
return [
|
|
1881
|
+
...byInstance.values()
|
|
1882
|
+
];
|
|
1883
|
+
}
|
|
1634
1884
|
/** Distinct worker groups with a live heartbeat, merged across every transport that can report it. */
|
|
1635
1885
|
async listWorkerGroups() {
|
|
1636
1886
|
const groups = /* @__PURE__ */ new Set();
|
|
@@ -2031,7 +2281,7 @@ function createWorkflowCtx(host, runId, compensations, workflow = "", searchAttr
|
|
|
2031
2281
|
return unwrapCompletion(await waitForSignal(`task:${runId}:${name}`), `task "${name}"`);
|
|
2032
2282
|
}, "task");
|
|
2033
2283
|
const child = /* @__PURE__ */ __name(async (workflow2, input, options) => {
|
|
2034
|
-
const { childId, priority } = normalizeChildOptions(options);
|
|
2284
|
+
const { childId, priority, version } = normalizeChildOptions(options);
|
|
2035
2285
|
const current = pos.next();
|
|
2036
2286
|
const id = childId ?? `${runId}.child.${current}`;
|
|
2037
2287
|
const existing = await readCheckpoint(current);
|
|
@@ -2059,7 +2309,7 @@ function createWorkflowCtx(host, runId, compensations, workflow = "", searchAttr
|
|
|
2059
2309
|
}));
|
|
2060
2310
|
return unwrapCompletion(buffered.payload, `child "${id}"`);
|
|
2061
2311
|
}
|
|
2062
|
-
if (!await store.getRun(id)) host.startChild(workflowName(workflow2), input, id, priority);
|
|
2312
|
+
if (!await store.getRun(id)) host.startChild(workflowName(workflow2), input, id, priority, version);
|
|
2063
2313
|
if (!existing) {
|
|
2064
2314
|
await writeCheckpoint(instantCheckpoint({
|
|
2065
2315
|
runId,
|
|
@@ -2142,12 +2392,12 @@ function createWorkflowCtx(host, runId, compensations, workflow = "", searchAttr
|
|
|
2142
2392
|
return outputs;
|
|
2143
2393
|
}, "all");
|
|
2144
2394
|
const startChild = /* @__PURE__ */ __name(async (workflow2, input, options) => {
|
|
2145
|
-
const { childId, priority } = normalizeChildOptions(options);
|
|
2395
|
+
const { childId, priority, version } = normalizeChildOptions(options);
|
|
2146
2396
|
const current = pos.next();
|
|
2147
2397
|
const id = childId ?? `${runId}.child.${current}`;
|
|
2148
2398
|
const existing = await readCheckpoint(current);
|
|
2149
2399
|
if (existing && existing.status === "completed") return existing.output;
|
|
2150
|
-
if (!await store.getRun(id)) host.startChild(workflowName(workflow2), input, id, priority);
|
|
2400
|
+
if (!await store.getRun(id)) host.startChild(workflowName(workflow2), input, id, priority, version);
|
|
2151
2401
|
await writeCheckpoint(instantCheckpoint({
|
|
2152
2402
|
runId,
|
|
2153
2403
|
seq: current,
|
|
@@ -2707,6 +2957,15 @@ var WorkflowEngine = class {
|
|
|
2707
2957
|
const reg = this.workflows.get(versionKey(name, version));
|
|
2708
2958
|
return reg?.hasBody ? reg.fn : void 0;
|
|
2709
2959
|
}
|
|
2960
|
+
/** The registered versions of `name`, for the pinned-start error — "2 is not registered" is a
|
|
2961
|
+
* guess-the-typo error without them, and the answer (which versions this deploy actually carries)
|
|
2962
|
+
* is right here. `none` when the name is unknown entirely. */
|
|
2963
|
+
registeredVersions(name) {
|
|
2964
|
+
const versions = [
|
|
2965
|
+
...this.workflows.values()
|
|
2966
|
+
].filter((w) => w.name === name).map((w) => w.version);
|
|
2967
|
+
return versions.length ? versions.join(", ") : "none";
|
|
2968
|
+
}
|
|
2710
2969
|
/**
|
|
2711
2970
|
* Register a workflow whose body runs in another SDK (e.g. Python). The engine owns the run exactly
|
|
2712
2971
|
* as for a TS workflow — it persists checkpoints, recovers, runs timers — but advances it by handing
|
|
@@ -2856,12 +3115,13 @@ var WorkflowEngine = class {
|
|
|
2856
3115
|
}
|
|
2857
3116
|
async start(workflow, input, runId, opts) {
|
|
2858
3117
|
const name = workflowName(workflow);
|
|
2859
|
-
|
|
2860
|
-
|
|
3118
|
+
const pinned = opts?.version;
|
|
3119
|
+
let registered = pinned === void 0 ? this.latest.get(name) : this.workflows.get(versionKey(name, pinned));
|
|
3120
|
+
if (!registered && pinned === void 0) {
|
|
2861
3121
|
const ancestor = await this.findRemoteAncestor(runId);
|
|
2862
3122
|
if (ancestor) registered = this.synthesizeRemoteChild(name, ancestor.version, ancestor);
|
|
2863
3123
|
}
|
|
2864
|
-
if (!registered) {
|
|
3124
|
+
if (!registered && pinned === void 0) {
|
|
2865
3125
|
const conventionRun = {
|
|
2866
3126
|
id: runId,
|
|
2867
3127
|
workflow: name,
|
|
@@ -2878,7 +3138,9 @@ var WorkflowEngine = class {
|
|
|
2878
3138
|
};
|
|
2879
3139
|
registered = await this.resolveRemoteByConvention(conventionRun);
|
|
2880
3140
|
}
|
|
2881
|
-
if (!registered)
|
|
3141
|
+
if (!registered) {
|
|
3142
|
+
throw new Error(pinned === void 0 ? `workflow ${name} is not registered` : `workflow ${versionKey(name, pinned)} is not registered \u2014 a pinned start never falls back to the newest version (registered: ${this.registeredVersions(name)})`);
|
|
3143
|
+
}
|
|
2882
3144
|
await registered.validateInput?.(input);
|
|
2883
3145
|
const prior = await this.store.getRun(runId);
|
|
2884
3146
|
if (prior) {
|
|
@@ -3085,7 +3347,32 @@ var WorkflowEngine = class {
|
|
|
3085
3347
|
/**
|
|
3086
3348
|
* Cancel in-flight runs that have outlived their workflow's `executionTimeout`. Call it from the
|
|
3087
3349
|
* timer poller alongside {@link resumeDueTimers}. A timed-out run is moved to `cancelled` with an
|
|
3088
|
-
* `execution_timeout` error (terminal, so a late step result can't resurrect it)
|
|
3350
|
+
* `execution_timeout` error (terminal, so a late step result can't resurrect it), AND its children
|
|
3351
|
+
* are cascaded exactly as an explicit {@link cancel} cascades them.
|
|
3352
|
+
*
|
|
3353
|
+
* WHY THE TERMINAL WRITE IS DIRECT, not a call to {@link cancel}. Two reasons, both still standing:
|
|
3354
|
+
* the run must land with the `execution_timeout` error `cancel` knows nothing about (`cancel` writes
|
|
3355
|
+
* a plain `cancelled`, and the code is what a dashboard/alert distinguishes a timeout by), and this
|
|
3356
|
+
* is a SCAN — it walks every in-flight run of every timed workflow on every poller tick, so it must
|
|
3357
|
+
* not pay `cancel`'s per-run re-read and singleton/control-plane work on runs it isn't cancelling.
|
|
3358
|
+
* The cascade below is charged only to the runs that ACTUALLY timed out, which are rare by
|
|
3359
|
+
* construction; the scan itself is unchanged.
|
|
3360
|
+
*
|
|
3361
|
+
* The subtree, though, has no reason to differ, and before this it did: a timed-out parent's
|
|
3362
|
+
* children kept running with the parent gone and nothing pointing at them — a silent orphan you
|
|
3363
|
+
* could only find by reading the runs table by hand. {@link cancelChildren} is the SAME recursive
|
|
3364
|
+
* walk `cancel` uses, so children of children go too, and it inherits that path's two guards:
|
|
3365
|
+
* `cancel` returns early on a run that is already `completed`/`cancelled`/`dead` (a child that beat
|
|
3366
|
+
* the sweep to the finish is never clobbered, and a re-cancel is a no-op), and it writes the
|
|
3367
|
+
* parent's terminal status BEFORE recursing, so a cyclic parent→child graph terminates on the
|
|
3368
|
+
* second visit instead of looping. Two pollers sweeping the same run concurrently therefore write
|
|
3369
|
+
* the same terminal state twice and cascade into already-cancelled children — idempotent, not
|
|
3370
|
+
* corrupting. The one window it does NOT close is the pre-existing check-then-write race an
|
|
3371
|
+
* explicit `cancel` has always had (a child that completes between `cancel`'s read and its write);
|
|
3372
|
+
* closing that needs a compare-and-set in the store contract, not a change here.
|
|
3373
|
+
*
|
|
3374
|
+
* Cascade failures are swallowed per subtree for the same reason `cancelChildren` swallows them per
|
|
3375
|
+
* child: one unreachable child must not abort the sweep and leave every LATER timed-out run alive.
|
|
3089
3376
|
*/
|
|
3090
3377
|
async sweepTimeouts(now = this.clock()) {
|
|
3091
3378
|
for (const reg of new Set(this.latest.values())) {
|
|
@@ -3121,6 +3408,7 @@ var WorkflowEngine = class {
|
|
|
3121
3408
|
namespace: run.namespace,
|
|
3122
3409
|
error: error2
|
|
3123
3410
|
});
|
|
3411
|
+
await this.cancelChildren(run.id).catch(() => void 0);
|
|
3124
3412
|
}
|
|
3125
3413
|
}
|
|
3126
3414
|
}
|
|
@@ -3849,6 +4137,10 @@ var WorkflowEngine = class {
|
|
|
3849
4137
|
* `child:<id>` waiter) and fire-and-forget (`ctx.startChild`, found via its `spawn:<id>`
|
|
3850
4138
|
* checkpoint). Recursive, so a whole subtree is cancelled; the terminal guard in `cancel` stops it
|
|
3851
4139
|
* at finished / already-cancelled runs (no loops, no re-cancel).
|
|
4140
|
+
*
|
|
4141
|
+
* Two callers, deliberately the same walk: an explicit {@link cancel}, and {@link sweepTimeouts}
|
|
4142
|
+
* when a parent hits its `executionTimeout` — a timed-out parent's subtree dies the same way an
|
|
4143
|
+
* explicitly cancelled one's does, or the children are orphans nobody is holding a reference to.
|
|
3852
4144
|
*/
|
|
3853
4145
|
async cancelChildren(parentRunId, opts) {
|
|
3854
4146
|
for (const id of await this.getRunChildren(parentRunId)) {
|
|
@@ -3928,6 +4220,29 @@ var WorkflowEngine = class {
|
|
|
3928
4220
|
return out;
|
|
3929
4221
|
}
|
|
3930
4222
|
/**
|
|
4223
|
+
* What workflows the LIVE fleet announces it can execute — the deployment-wide registry a picker
|
|
4224
|
+
* ("call this existing workflow") needs, and the one question this engine's own registry cannot
|
|
4225
|
+
* answer. {@link workflowBody} answers only for this process, and a missing body is ambiguous: it
|
|
4226
|
+
* means "not registered here", but equally "registered via {@link registerRemote} against another
|
|
4227
|
+
* SDK" or "a group resolved by convention against a worker". An inferred answer would therefore
|
|
4228
|
+
* differ per replica. This one does not: every pod folds the same published announcements.
|
|
4229
|
+
*
|
|
4230
|
+
* Scoped like every other poll surface — an engine WITH a `namespace` sees only workers in it, and
|
|
4231
|
+
* an operator (namespace unset) sees the whole deployment ("ver tudo = ausência de namespace"). A
|
|
4232
|
+
* descriptor that states no namespace is `default`, matching how a single-tenant fleet publishes.
|
|
4233
|
+
*
|
|
4234
|
+
* Cost: one advertisement scan per CALL, on demand. Nothing is added to the poll loop and nothing
|
|
4235
|
+
* is retained — no pod holds a table of the fleet's registrations. Empty when no transport can
|
|
4236
|
+
* introspect the advertisement keyspace (a pure in-process transport advertises nothing).
|
|
4237
|
+
*
|
|
4238
|
+
* Workflows only, and deliberately: see the "steps are out of scope" note in `handshake/announced`.
|
|
4239
|
+
*/
|
|
4240
|
+
async announcedWorkflows() {
|
|
4241
|
+
const descriptors = await this.pool.readAllWorkerDescriptors();
|
|
4242
|
+
const mine = this.namespace === void 0 ? descriptors : descriptors.filter((d) => (d.namespace ?? "default") === this.namespace);
|
|
4243
|
+
return aggregateAnnouncements(mine);
|
|
4244
|
+
}
|
|
4245
|
+
/**
|
|
3931
4246
|
* The routing groups of every in-flight run's still-`pending` remote step — the queues where work is
|
|
3932
4247
|
* dispatched and awaiting a worker. Folded into {@link workerHealth} so a step waiting on a group
|
|
3933
4248
|
* that is NEITHER a registration NOR heartbeating (the exact shape of a run stuck on an offline
|
|
@@ -4642,8 +4957,31 @@ var WorkflowEngine = class {
|
|
|
4642
4957
|
error: error2
|
|
4643
4958
|
};
|
|
4644
4959
|
}
|
|
4645
|
-
/**
|
|
4646
|
-
|
|
4960
|
+
/**
|
|
4961
|
+
* The run body, lease held + renewed by {@link execute}, wrapped in the registered
|
|
4962
|
+
* {@link runDurableExecution} wrappers so an observability scope covers ONE WHOLE TURN.
|
|
4963
|
+
*
|
|
4964
|
+
* The wrap goes here — around the entire turn — rather than tightly around the `fn(ctx, input)`
|
|
4965
|
+
* call, because the store writes that bracket the body (the `pending` → `running` transition, the
|
|
4966
|
+
* step checkpoints, the terminal `settleRun` update) are exactly the queries an operator wants
|
|
4967
|
+
* correlated to the turn that issued them. Narrowing the wrap to the body would leave them
|
|
4968
|
+
* uncorrelated for the sake of a purity that buys nothing.
|
|
4969
|
+
*
|
|
4970
|
+
* Only the TS path is wrapped. {@link runRemoteExecution} does not execute a body at all — it
|
|
4971
|
+
* dispatches a workflow task and awaits the worker's decision — so a scope opened there would
|
|
4972
|
+
* describe a dispatch, not an execution, and would mislead anyone reading the trace.
|
|
4973
|
+
*/
|
|
4974
|
+
runExecution(run, fn, registered) {
|
|
4975
|
+
return runDurableExecution({
|
|
4976
|
+
unit: "workflow",
|
|
4977
|
+
runId: run.id,
|
|
4978
|
+
workflow: run.workflow,
|
|
4979
|
+
name: void 0,
|
|
4980
|
+
seq: void 0,
|
|
4981
|
+
attempt: void 0
|
|
4982
|
+
}, () => this.runExecutionTurn(run, fn, registered));
|
|
4983
|
+
}
|
|
4984
|
+
async runExecutionTurn(run, fn, registered) {
|
|
4647
4985
|
if (run.status === "cancelling") this.cancelRequested.add(run.id);
|
|
4648
4986
|
if (run.status === "pending") {
|
|
4649
4987
|
await this.store.updateRun(run.id, {
|
|
@@ -4983,10 +5321,13 @@ var WorkflowEngine = class {
|
|
|
4983
5321
|
// namespace) is the one executing the parent. Otherwise an operator recovery-resuming a
|
|
4984
5322
|
// `davi-local` pipeline would stamp its `processing` child `default` and it would leak off the
|
|
4985
5323
|
// tenant's worker pool onto the shared/dev workers.
|
|
4986
|
-
startChild: /* @__PURE__ */ __name((workflow, input, id, priority) => {
|
|
5324
|
+
startChild: /* @__PURE__ */ __name((workflow, input, id, priority, version) => {
|
|
4987
5325
|
this.startChildDeferred(workflow, input, id, {
|
|
4988
5326
|
priority,
|
|
4989
|
-
namespace: parentNamespace
|
|
5327
|
+
namespace: parentNamespace,
|
|
5328
|
+
// Absent unless the body pinned one (`ctx.child(ref, input, { version })`) — an omitted
|
|
5329
|
+
// pin must stay omitted, so the child keeps resolving `latest` exactly as before.
|
|
5330
|
+
version
|
|
4990
5331
|
});
|
|
4991
5332
|
}, "startChild"),
|
|
4992
5333
|
// Deferred for the same reentrancy reason as `startChild` above. `cancel()` is already
|
|
@@ -6395,6 +6736,8 @@ var InMemoryTransport = class {
|
|
|
6395
6736
|
WorkflowBlocked,
|
|
6396
6737
|
WorkflowEngine,
|
|
6397
6738
|
WorkflowSuspended,
|
|
6739
|
+
aggregateAnnouncements,
|
|
6740
|
+
announcementsOf,
|
|
6398
6741
|
applyAttributeQuery,
|
|
6399
6742
|
attributeColumnFor,
|
|
6400
6743
|
attributeOperand,
|
|
@@ -6403,12 +6746,14 @@ var InMemoryTransport = class {
|
|
|
6403
6746
|
canRoute,
|
|
6404
6747
|
capableWorkers,
|
|
6405
6748
|
classifyWaiterToken,
|
|
6749
|
+
clearDurableExecutionWrappers,
|
|
6406
6750
|
collectMetrics,
|
|
6407
6751
|
controlPlaneDescriptor,
|
|
6408
6752
|
createStepLogger,
|
|
6409
6753
|
currentStep,
|
|
6410
6754
|
currentWorkflowCtx,
|
|
6411
6755
|
debug,
|
|
6756
|
+
describeWorker,
|
|
6412
6757
|
descriptorHash,
|
|
6413
6758
|
error,
|
|
6414
6759
|
eventMatchOf,
|
|
@@ -6416,6 +6761,7 @@ var InMemoryTransport = class {
|
|
|
6416
6761
|
eventNameOf,
|
|
6417
6762
|
eventPrefix,
|
|
6418
6763
|
eventToken,
|
|
6764
|
+
hasDurableExecutionWrappers,
|
|
6419
6765
|
heartbeatStatus,
|
|
6420
6766
|
indexWaitersByRun,
|
|
6421
6767
|
info,
|
|
@@ -6435,6 +6781,7 @@ var InMemoryTransport = class {
|
|
|
6435
6781
|
requiredCapabilities,
|
|
6436
6782
|
resolveRouting,
|
|
6437
6783
|
resolveRunWaiting,
|
|
6784
|
+
runDurableExecution,
|
|
6438
6785
|
runInStepLogger,
|
|
6439
6786
|
runInWorkflowCtx,
|
|
6440
6787
|
runSchedules,
|
|
@@ -6449,6 +6796,7 @@ var InMemoryTransport = class {
|
|
|
6449
6796
|
subEvent,
|
|
6450
6797
|
subProcess,
|
|
6451
6798
|
tenantGroup,
|
|
6799
|
+
useDurableExecution,
|
|
6452
6800
|
warn,
|
|
6453
6801
|
workflowName
|
|
6454
6802
|
});
|