@dudousxd/nestjs-durable-core 0.45.0 → 0.46.1
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 +216 -64
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +366 -73
- package/dist/index.d.ts +366 -73
- package/dist/index.js +210 -63
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -26,6 +26,8 @@ __export(index_exports, {
|
|
|
26
26
|
ContinueAsNew: () => ContinueAsNew,
|
|
27
27
|
DURABLE_OPTIONS: () => DURABLE_OPTIONS,
|
|
28
28
|
DURABLE_OPTIONS_CANONICAL: () => DURABLE_OPTIONS_CANONICAL,
|
|
29
|
+
DURABLE_STEP_CONFIG: () => DURABLE_STEP_CONFIG,
|
|
30
|
+
DURABLE_STEP_NAME: () => DURABLE_STEP_NAME,
|
|
29
31
|
Entities: () => Entities,
|
|
30
32
|
EventAccumulators: () => EventAccumulators,
|
|
31
33
|
FatalError: () => FatalError,
|
|
@@ -63,12 +65,15 @@ __export(index_exports, {
|
|
|
63
65
|
normalizeAttributeRows: () => normalizeAttributeRows,
|
|
64
66
|
parseDuration: () => parseDuration,
|
|
65
67
|
prevCronFireMs: () => prevCronFireMs,
|
|
66
|
-
remoteStep: () => remoteStep,
|
|
67
68
|
runSchedules: () => runSchedules,
|
|
68
69
|
runStepHandler: () => runStepHandler,
|
|
70
|
+
sanitizeQueueToken: () => sanitizeQueueToken,
|
|
69
71
|
scheduledRunId: () => scheduledRunId,
|
|
70
72
|
sqlComparator: () => sqlComparator,
|
|
73
|
+
stepConfigOf: () => stepConfigOf,
|
|
71
74
|
stepId: () => stepId,
|
|
75
|
+
stepNameOf: () => stepNameOf,
|
|
76
|
+
tenantGroup: () => tenantGroup,
|
|
72
77
|
workflowName: () => workflowName
|
|
73
78
|
});
|
|
74
79
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -646,7 +651,7 @@ var Entities = class {
|
|
|
646
651
|
let state = config.initialState();
|
|
647
652
|
for (let i = 0; ; i += 1) {
|
|
648
653
|
const msg = await ctx.waitForSignal(token);
|
|
649
|
-
const out = await ctx.
|
|
654
|
+
const out = await ctx.localStep(`op:${i}`, async () => {
|
|
650
655
|
const handler = config.handlers[msg.op];
|
|
651
656
|
if (!handler) throw new FatalError(`entity "${name}" has no handler for op "${msg.op}"`);
|
|
652
657
|
const result = await handler(state, msg.arg);
|
|
@@ -658,7 +663,7 @@ var Entities = class {
|
|
|
658
663
|
state = out.state;
|
|
659
664
|
await ctx.setEvent("state", state);
|
|
660
665
|
if (msg.reply) {
|
|
661
|
-
await ctx.
|
|
666
|
+
await ctx.localStep(`reply:${i}`, async () => {
|
|
662
667
|
await this.host.signal(msg.reply, out.result);
|
|
663
668
|
});
|
|
664
669
|
}
|
|
@@ -772,16 +777,28 @@ function eventMatches(payload, match) {
|
|
|
772
777
|
}
|
|
773
778
|
__name(eventMatches, "eventMatches");
|
|
774
779
|
|
|
780
|
+
// src/tenant-group.ts
|
|
781
|
+
function tenantGroup(baseGroup, tenant) {
|
|
782
|
+
return tenant !== void 0 && tenant !== "" && tenant !== "default" ? `${baseGroup}@${tenant}` : baseGroup;
|
|
783
|
+
}
|
|
784
|
+
__name(tenantGroup, "tenantGroup");
|
|
785
|
+
function sanitizeQueueToken(name) {
|
|
786
|
+
return name.replace(/:/g, "-");
|
|
787
|
+
}
|
|
788
|
+
__name(sanitizeQueueToken, "sanitizeQueueToken");
|
|
789
|
+
|
|
775
790
|
// src/remote-workflow-executor.ts
|
|
776
791
|
var RemoteWorkflowExecutor = class {
|
|
777
792
|
static {
|
|
778
793
|
__name(this, "RemoteWorkflowExecutor");
|
|
779
794
|
}
|
|
780
795
|
transport;
|
|
781
|
-
|
|
782
|
-
|
|
796
|
+
name;
|
|
797
|
+
partition;
|
|
798
|
+
constructor(transport, name, partition, _opts = {}) {
|
|
783
799
|
this.transport = transport;
|
|
784
|
-
this.
|
|
800
|
+
this.name = name;
|
|
801
|
+
this.partition = partition;
|
|
785
802
|
}
|
|
786
803
|
/**
|
|
787
804
|
* Enqueue one workflow turn under the engine-supplied `taskId`. Does NOT await the decision — the
|
|
@@ -801,7 +818,7 @@ var RemoteWorkflowExecutor = class {
|
|
|
801
818
|
...pendingSignals ? {
|
|
802
819
|
pendingSignals
|
|
803
820
|
} : {},
|
|
804
|
-
group: this.
|
|
821
|
+
group: tenantGroup(sanitizeQueueToken(this.name), this.partition),
|
|
805
822
|
priority: run.priority,
|
|
806
823
|
attempt: 1
|
|
807
824
|
};
|
|
@@ -973,6 +990,16 @@ var TransportPool = class {
|
|
|
973
990
|
liveWorkers: reports.flatMap((r) => r.liveWorkers)
|
|
974
991
|
};
|
|
975
992
|
}
|
|
993
|
+
/**
|
|
994
|
+
* Register a control-plane `start-run` consumer on every transport that carries the channel. Only
|
|
995
|
+
* control-plane-side transports implement {@link Transport.onStartRun}; a transport without it is
|
|
996
|
+
* skipped, so a plain worker/in-process pool is a no-op. Mirrors {@link bind}'s per-transport wiring.
|
|
997
|
+
*/
|
|
998
|
+
onStartRun(handler) {
|
|
999
|
+
for (const { transport } of this.transports) {
|
|
1000
|
+
transport.onStartRun?.(handler);
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
976
1003
|
/** Distinct worker groups with a live heartbeat, merged across every transport that can report it. */
|
|
977
1004
|
async listWorkerGroups() {
|
|
978
1005
|
const groups = /* @__PURE__ */ new Set();
|
|
@@ -1018,6 +1045,26 @@ function unwrapCompletion(payload, label) {
|
|
|
1018
1045
|
}
|
|
1019
1046
|
__name(unwrapCompletion, "unwrapCompletion");
|
|
1020
1047
|
|
|
1048
|
+
// src/step-name-symbol.ts
|
|
1049
|
+
var DURABLE_STEP_NAME = Symbol.for("durable.step.name");
|
|
1050
|
+
var DURABLE_STEP_CONFIG = Symbol.for("durable.step.config");
|
|
1051
|
+
function isStepConfig(value) {
|
|
1052
|
+
return typeof value === "object" && value !== null;
|
|
1053
|
+
}
|
|
1054
|
+
__name(isStepConfig, "isStepConfig");
|
|
1055
|
+
function stepNameOf(ref) {
|
|
1056
|
+
if (typeof ref !== "function") return void 0;
|
|
1057
|
+
const stamped = ref[DURABLE_STEP_NAME];
|
|
1058
|
+
return typeof stamped === "string" ? stamped : void 0;
|
|
1059
|
+
}
|
|
1060
|
+
__name(stepNameOf, "stepNameOf");
|
|
1061
|
+
function stepConfigOf(ref) {
|
|
1062
|
+
if (typeof ref !== "function") return void 0;
|
|
1063
|
+
const stamped = ref[DURABLE_STEP_CONFIG];
|
|
1064
|
+
return isStepConfig(stamped) ? stamped : void 0;
|
|
1065
|
+
}
|
|
1066
|
+
__name(stepConfigOf, "stepConfigOf");
|
|
1067
|
+
|
|
1021
1068
|
// src/workflow-ref.ts
|
|
1022
1069
|
var WORKFLOW_NAME_KEY = Symbol.for("nestjs-durable:workflow-name");
|
|
1023
1070
|
function workflowName(ref) {
|
|
@@ -1037,6 +1084,23 @@ function normalizeChildOptions(options) {
|
|
|
1037
1084
|
} : options ?? {};
|
|
1038
1085
|
}
|
|
1039
1086
|
__name(normalizeChildOptions, "normalizeChildOptions");
|
|
1087
|
+
function resolveStepPolicy(config, opts) {
|
|
1088
|
+
const policy = {};
|
|
1089
|
+
const retries = opts?.retries ?? config?.retries;
|
|
1090
|
+
if (retries !== void 0) policy.retries = retries;
|
|
1091
|
+
const backoff = opts?.backoff ?? config?.backoff;
|
|
1092
|
+
if (backoff !== void 0) policy.backoff = backoff;
|
|
1093
|
+
const backoffMs = opts?.backoffMs ?? config?.backoffMs;
|
|
1094
|
+
if (backoffMs !== void 0) policy.backoffMs = backoffMs;
|
|
1095
|
+
const backoffMaxMs = opts?.backoffMaxMs ?? config?.backoffMaxMs;
|
|
1096
|
+
if (backoffMaxMs !== void 0) policy.backoffMaxMs = backoffMaxMs;
|
|
1097
|
+
const jitter = opts?.jitter ?? config?.jitter;
|
|
1098
|
+
if (jitter !== void 0) policy.jitter = jitter;
|
|
1099
|
+
const timeoutMs = opts?.timeoutMs ?? config?.timeoutMs;
|
|
1100
|
+
if (timeoutMs !== void 0) policy.timeoutMs = timeoutMs;
|
|
1101
|
+
return policy;
|
|
1102
|
+
}
|
|
1103
|
+
__name(resolveStepPolicy, "resolveStepPolicy");
|
|
1040
1104
|
var Position = class Position2 {
|
|
1041
1105
|
static {
|
|
1042
1106
|
__name(this, "Position");
|
|
@@ -1062,7 +1126,7 @@ function createWorkflowCtx(host, runId, compensations, workflow = "") {
|
|
|
1062
1126
|
await store.saveCheckpoint(cp);
|
|
1063
1127
|
replay?.set(cp.seq, cp);
|
|
1064
1128
|
}, "writeCheckpoint");
|
|
1065
|
-
const
|
|
1129
|
+
const localStep = /* @__PURE__ */ __name(async (name, fn, options) => {
|
|
1066
1130
|
const current = pos.next();
|
|
1067
1131
|
const existing = await readCheckpoint(current);
|
|
1068
1132
|
if (existing && existing.name !== name) {
|
|
@@ -1136,7 +1200,7 @@ function createWorkflowCtx(host, runId, compensations, workflow = "") {
|
|
|
1136
1200
|
if (wait > 0) await new Promise((resolve) => setTimeout(resolve, wait));
|
|
1137
1201
|
}
|
|
1138
1202
|
}
|
|
1139
|
-
}, "
|
|
1203
|
+
}, "localStep");
|
|
1140
1204
|
const transaction = /* @__PURE__ */ __name(async (name, fn) => {
|
|
1141
1205
|
if (!store.transaction) {
|
|
1142
1206
|
throw new Error("ctx.transaction needs a store that supports transactions (the SQL adapters do). Use ctx.step for non-transactional work.");
|
|
@@ -1283,7 +1347,7 @@ function createWorkflowCtx(host, runId, compensations, workflow = "") {
|
|
|
1283
1347
|
throw new WorkflowSuspended(deadline);
|
|
1284
1348
|
}, "waitForEvent");
|
|
1285
1349
|
const task = /* @__PURE__ */ __name(async (name, dispatch, options) => {
|
|
1286
|
-
await
|
|
1350
|
+
await localStep(`task:dispatch:${name}`, dispatch, options);
|
|
1287
1351
|
return unwrapCompletion(await waitForSignal(`task:${runId}:${name}`), `task "${name}"`);
|
|
1288
1352
|
}, "task");
|
|
1289
1353
|
const child = /* @__PURE__ */ __name(async (workflow2, input, options) => {
|
|
@@ -1496,9 +1560,8 @@ function createWorkflowCtx(host, runId, compensations, workflow = "") {
|
|
|
1496
1560
|
wait
|
|
1497
1561
|
};
|
|
1498
1562
|
}, "webhook");
|
|
1499
|
-
const
|
|
1500
|
-
const
|
|
1501
|
-
const uuid = /* @__PURE__ */ __name(() => step("uuid", async () => globalThis.crypto.randomUUID()), "uuid");
|
|
1563
|
+
const sideEffect = /* @__PURE__ */ __name((fn) => localStep("sideEffect", async () => fn()), "sideEffect");
|
|
1564
|
+
const now = /* @__PURE__ */ __name(() => localStep("now", async () => host.clock()), "now");
|
|
1502
1565
|
const upsertSearchAttributes = /* @__PURE__ */ __name(async (attrs) => {
|
|
1503
1566
|
const current = pos.next();
|
|
1504
1567
|
const existing = await readCheckpoint(current);
|
|
@@ -1517,9 +1580,26 @@ function createWorkflowCtx(host, runId, compensations, workflow = "") {
|
|
|
1517
1580
|
output: attrs
|
|
1518
1581
|
}));
|
|
1519
1582
|
}, "upsertSearchAttributes");
|
|
1583
|
+
function step(handlerOrName, input, opts) {
|
|
1584
|
+
const name = typeof handlerOrName === "string" ? handlerOrName : stepNameOf(handlerOrName);
|
|
1585
|
+
if (name === void 0) {
|
|
1586
|
+
throw new Error("ctx.step: handler is not a @Step reference (no stamped step name) \u2014 pass a @Step-decorated method or a step name string");
|
|
1587
|
+
}
|
|
1588
|
+
const config = typeof handlerOrName === "string" ? void 0 : stepConfigOf(handlerOrName);
|
|
1589
|
+
const def = {
|
|
1590
|
+
name,
|
|
1591
|
+
...resolveStepPolicy(config, opts)
|
|
1592
|
+
};
|
|
1593
|
+
return host.callRemote(runId, pos.next(), def, input, opts?.queue, opts?.transport, {
|
|
1594
|
+
priority: opts?.priority,
|
|
1595
|
+
fairnessKey: opts?.fairnessKey
|
|
1596
|
+
});
|
|
1597
|
+
}
|
|
1598
|
+
__name(step, "step");
|
|
1520
1599
|
return {
|
|
1521
1600
|
runId,
|
|
1522
1601
|
step,
|
|
1602
|
+
localStep,
|
|
1523
1603
|
upsertSearchAttributes,
|
|
1524
1604
|
transaction,
|
|
1525
1605
|
callEntity,
|
|
@@ -1539,12 +1619,7 @@ function createWorkflowCtx(host, runId, compensations, workflow = "") {
|
|
|
1539
1619
|
onUpdate,
|
|
1540
1620
|
patched,
|
|
1541
1621
|
now,
|
|
1542
|
-
|
|
1543
|
-
uuid,
|
|
1544
|
-
call: /* @__PURE__ */ __name((remote, input, opts) => host.callRemote(runId, pos.next(), remote, input, opts?.queue, opts?.transport, {
|
|
1545
|
-
priority: opts?.priority,
|
|
1546
|
-
fairnessKey: opts?.fairnessKey
|
|
1547
|
-
}), "call")
|
|
1622
|
+
sideEffect
|
|
1548
1623
|
};
|
|
1549
1624
|
}
|
|
1550
1625
|
__name(createWorkflowCtx, "createWorkflowCtx");
|
|
@@ -1662,8 +1737,10 @@ var WorkflowEngine = class {
|
|
|
1662
1737
|
this.admission = deps.admission ?? new InMemoryAdmissionBackend(this.clock);
|
|
1663
1738
|
this.admission.onFreed?.((queue) => this.wakeQueueWaiters(queue));
|
|
1664
1739
|
this.instanceId = deps.instanceId ?? globalThis.crypto.randomUUID();
|
|
1665
|
-
this.namespace = deps.namespace
|
|
1666
|
-
|
|
1740
|
+
this.namespace = deps.namespace;
|
|
1741
|
+
if (this.namespace !== void 0) {
|
|
1742
|
+
this.pool.useNamespace(this.namespace);
|
|
1743
|
+
}
|
|
1667
1744
|
this.leaseMs = deps.leaseMs ?? 3e4;
|
|
1668
1745
|
this.maxRecoveryAttempts = deps.maxRecoveryAttempts;
|
|
1669
1746
|
this.remoteAdvanceSilenceMs = deps.remoteAdvanceSilenceMs;
|
|
@@ -1728,6 +1805,13 @@ var WorkflowEngine = class {
|
|
|
1728
1805
|
await this.completeRemoteDecision(decision);
|
|
1729
1806
|
}
|
|
1730
1807
|
);
|
|
1808
|
+
this.pool.onStartRun(async (message) => {
|
|
1809
|
+
await this.start(message.workflow, message.input, message.runId ?? globalThis.crypto.randomUUID(), {
|
|
1810
|
+
namespace: message.tenant,
|
|
1811
|
+
tags: message.tags,
|
|
1812
|
+
searchAttributes: message.searchAttributes
|
|
1813
|
+
});
|
|
1814
|
+
});
|
|
1731
1815
|
this.controlPlane?.onControl((msg) => {
|
|
1732
1816
|
if (msg.from === this.instanceId) return;
|
|
1733
1817
|
if (msg.kind === "event") {
|
|
@@ -1892,7 +1976,7 @@ var WorkflowEngine = class {
|
|
|
1892
1976
|
});
|
|
1893
1977
|
}
|
|
1894
1978
|
/**
|
|
1895
|
-
* Register a flow-control queue referenced by `ctx.
|
|
1979
|
+
* Register a flow-control queue referenced by `ctx.step(step, input, { queue })`. Caps concurrent
|
|
1896
1980
|
* in-flight steps and/or the admission rate; blocked calls re-suspend and retry, so the limit is
|
|
1897
1981
|
* durable. Per engine instance (see {@link QueueConfig}). Registering the same name replaces it.
|
|
1898
1982
|
*/
|
|
@@ -1995,6 +2079,23 @@ var WorkflowEngine = class {
|
|
|
1995
2079
|
const ancestor = await this.findRemoteAncestor(runId);
|
|
1996
2080
|
if (ancestor) registered = this.synthesizeRemoteChild(name, ancestor.version, ancestor);
|
|
1997
2081
|
}
|
|
2082
|
+
if (!registered) {
|
|
2083
|
+
const conventionRun = {
|
|
2084
|
+
id: runId,
|
|
2085
|
+
workflow: name,
|
|
2086
|
+
workflowVersion: "1",
|
|
2087
|
+
status: "pending",
|
|
2088
|
+
// Stamp the namespace the REAL run will carry (below) so convention routing resolves the same
|
|
2089
|
+
// tenant-suffixed group the run will dispatch to — an operator starting a tenant's run via the
|
|
2090
|
+
// `onStartRun` wire (opts.namespace = tenant) must find the live `<workflow>@<tenant>` group,
|
|
2091
|
+
// not the bare group. Without this the start-run entry path can't reach a suffixed-only worker.
|
|
2092
|
+
namespace: opts?.namespace ?? this.namespace,
|
|
2093
|
+
input,
|
|
2094
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
2095
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
2096
|
+
};
|
|
2097
|
+
registered = await this.resolveRemoteByConvention(conventionRun);
|
|
2098
|
+
}
|
|
1998
2099
|
if (!registered) throw new Error(`workflow ${name} is not registered`);
|
|
1999
2100
|
await registered.validateInput?.(input);
|
|
2000
2101
|
const prior = await this.store.getRun(runId);
|
|
@@ -2019,7 +2120,7 @@ var WorkflowEngine = class {
|
|
|
2019
2120
|
workflow: name,
|
|
2020
2121
|
workflowVersion: registered.version,
|
|
2021
2122
|
status: "pending",
|
|
2022
|
-
namespace: this.namespace,
|
|
2123
|
+
namespace: opts?.namespace ?? this.namespace,
|
|
2023
2124
|
input,
|
|
2024
2125
|
tags,
|
|
2025
2126
|
searchAttributes: opts?.searchAttributes,
|
|
@@ -2056,11 +2157,11 @@ var WorkflowEngine = class {
|
|
|
2056
2157
|
error: run.error
|
|
2057
2158
|
};
|
|
2058
2159
|
}
|
|
2059
|
-
if (run.namespace !== void 0 && run.namespace !== this.namespace) {
|
|
2160
|
+
if (this.namespace !== void 0 && run.namespace !== void 0 && run.namespace !== this.namespace) {
|
|
2060
2161
|
await this.store.releaseRunLock(runId);
|
|
2061
2162
|
throw new NamespaceMismatch();
|
|
2062
2163
|
}
|
|
2063
|
-
const registered = this.workflows.get(versionKey(run.workflow, run.workflowVersion)) ?? await this.findInheritedRegistration(run);
|
|
2164
|
+
const registered = this.workflows.get(versionKey(run.workflow, run.workflowVersion)) ?? await this.findInheritedRegistration(run) ?? await this.resolveRemoteByConvention(run);
|
|
2064
2165
|
if (!registered) {
|
|
2065
2166
|
throw new Error(`workflow ${run.workflow}@${run.workflowVersion} is not registered \u2014 keep the prior version deployed so in-flight runs can drain (skew protection)`);
|
|
2066
2167
|
}
|
|
@@ -2106,6 +2207,34 @@ var WorkflowEngine = class {
|
|
|
2106
2207
|
};
|
|
2107
2208
|
}
|
|
2108
2209
|
/**
|
|
2210
|
+
* Attempt to route an unregistered workflow to the live worker group of the same name: if the
|
|
2211
|
+
* transport reports a live heartbeat for group `run.workflow`, synthesize a throwaway
|
|
2212
|
+
* {@link RegisteredWorkflow} that dispatches to that group over the primary transport. Returns
|
|
2213
|
+
* `undefined` when the group is not live. Always attempted — there is no opt-in flag; an explicit
|
|
2214
|
+
* `register`/`registerRemote` still takes precedence (this only runs when nothing is registered).
|
|
2215
|
+
*
|
|
2216
|
+
* Recomputed per call (never memoized into `this.workflows`) so it stays correct if group
|
|
2217
|
+
* liveness changes between calls, exactly as {@link synthesizeRemoteChild} does.
|
|
2218
|
+
*/
|
|
2219
|
+
async resolveRemoteByConvention(run) {
|
|
2220
|
+
const liveGroups = await this.pool.listWorkerGroups();
|
|
2221
|
+
const group = tenantGroup(sanitizeQueueToken(run.workflow), run.namespace);
|
|
2222
|
+
if (!liveGroups.includes(group)) return void 0;
|
|
2223
|
+
const executor = new RemoteWorkflowExecutor(this.pool.primary, run.workflow, run.namespace);
|
|
2224
|
+
return {
|
|
2225
|
+
name: run.workflow,
|
|
2226
|
+
version: run.workflowVersion,
|
|
2227
|
+
fn: /* @__PURE__ */ __name(() => {
|
|
2228
|
+
throw new Error(`remote workflow ${run.workflow} has no local body`);
|
|
2229
|
+
}, "fn"),
|
|
2230
|
+
hasBody: false,
|
|
2231
|
+
remote: {
|
|
2232
|
+
group,
|
|
2233
|
+
executor
|
|
2234
|
+
}
|
|
2235
|
+
};
|
|
2236
|
+
}
|
|
2237
|
+
/**
|
|
2109
2238
|
* Walk the parent chain of `childRunId` to the nearest ancestor registered as REMOTE, returning its
|
|
2110
2239
|
* registration (or `undefined` if there is none). The parent of an awaited child is found via its
|
|
2111
2240
|
* live `child:<childId>` signal waiter — present for as long as the parent is suspended awaiting the
|
|
@@ -2202,6 +2331,7 @@ var WorkflowEngine = class {
|
|
|
2202
2331
|
type: "run.failed",
|
|
2203
2332
|
runId: run.id,
|
|
2204
2333
|
workflow: run.workflow,
|
|
2334
|
+
namespace: run.namespace,
|
|
2205
2335
|
error
|
|
2206
2336
|
});
|
|
2207
2337
|
}
|
|
@@ -2259,6 +2389,7 @@ var WorkflowEngine = class {
|
|
|
2259
2389
|
type: "run.failed",
|
|
2260
2390
|
runId: run.id,
|
|
2261
2391
|
workflow: run.workflow,
|
|
2392
|
+
namespace: run.namespace,
|
|
2262
2393
|
error
|
|
2263
2394
|
});
|
|
2264
2395
|
this.notifyDead({
|
|
@@ -2333,7 +2464,8 @@ var WorkflowEngine = class {
|
|
|
2333
2464
|
if (!run) return null;
|
|
2334
2465
|
const id = newRunId ?? `${runId}~retry~${globalThis.crypto.randomUUID().slice(0, 8)}`;
|
|
2335
2466
|
await this.start(run.workflow, input, id, {
|
|
2336
|
-
tags: run.tags
|
|
2467
|
+
tags: run.tags,
|
|
2468
|
+
namespace: run.namespace
|
|
2337
2469
|
});
|
|
2338
2470
|
return {
|
|
2339
2471
|
runId: id
|
|
@@ -2567,6 +2699,7 @@ var WorkflowEngine = class {
|
|
|
2567
2699
|
type: "run.failed",
|
|
2568
2700
|
runId,
|
|
2569
2701
|
workflow: run.workflow,
|
|
2702
|
+
namespace: run.namespace,
|
|
2570
2703
|
error
|
|
2571
2704
|
});
|
|
2572
2705
|
await this.cancelChildren(runId, opts);
|
|
@@ -2843,7 +2976,7 @@ var WorkflowEngine = class {
|
|
|
2843
2976
|
});
|
|
2844
2977
|
}
|
|
2845
2978
|
async execute(run, fn) {
|
|
2846
|
-
const registered = this.workflows.get(versionKey(run.workflow, run.workflowVersion)) ?? await this.findInheritedRegistration(run);
|
|
2979
|
+
const registered = this.workflows.get(versionKey(run.workflow, run.workflowVersion)) ?? await this.findInheritedRegistration(run) ?? await this.resolveRemoteByConvention(run);
|
|
2847
2980
|
const lockNow = this.clock();
|
|
2848
2981
|
if (run.lockedBy !== this.instanceId || (run.lockedUntil ?? 0) <= lockNow) {
|
|
2849
2982
|
if (!await this.store.tryLockRun(run.id, this.instanceId, lockNow + this.leaseMs, lockNow)) {
|
|
@@ -2893,6 +3026,7 @@ var WorkflowEngine = class {
|
|
|
2893
3026
|
type: "run.completed",
|
|
2894
3027
|
runId: run.id,
|
|
2895
3028
|
workflow: run.workflow,
|
|
3029
|
+
namespace: run.namespace,
|
|
2896
3030
|
output: outcome.output
|
|
2897
3031
|
});
|
|
2898
3032
|
void this.notifyParent(run.id, {
|
|
@@ -2915,6 +3049,7 @@ var WorkflowEngine = class {
|
|
|
2915
3049
|
type: "run.failed",
|
|
2916
3050
|
runId: run.id,
|
|
2917
3051
|
workflow: run.workflow,
|
|
3052
|
+
namespace: run.namespace,
|
|
2918
3053
|
error: outcome.error
|
|
2919
3054
|
});
|
|
2920
3055
|
void this.notifyParent(run.id, {
|
|
@@ -2935,7 +3070,8 @@ var WorkflowEngine = class {
|
|
|
2935
3070
|
this.emit({
|
|
2936
3071
|
type: "run.suspended",
|
|
2937
3072
|
runId: run.id,
|
|
2938
|
-
workflow: run.workflow
|
|
3073
|
+
workflow: run.workflow,
|
|
3074
|
+
namespace: run.namespace
|
|
2939
3075
|
});
|
|
2940
3076
|
return {
|
|
2941
3077
|
runId: run.id,
|
|
@@ -2956,6 +3092,7 @@ var WorkflowEngine = class {
|
|
|
2956
3092
|
type: "run.failed",
|
|
2957
3093
|
runId: run.id,
|
|
2958
3094
|
workflow: run.workflow,
|
|
3095
|
+
namespace: run.namespace,
|
|
2959
3096
|
error
|
|
2960
3097
|
});
|
|
2961
3098
|
this.notifyCancelled(run.id);
|
|
@@ -2983,7 +3120,8 @@ var WorkflowEngine = class {
|
|
|
2983
3120
|
this.emit({
|
|
2984
3121
|
type: "run.started",
|
|
2985
3122
|
runId: run.id,
|
|
2986
|
-
workflow: run.workflow
|
|
3123
|
+
workflow: run.workflow,
|
|
3124
|
+
namespace: run.namespace
|
|
2987
3125
|
});
|
|
2988
3126
|
}
|
|
2989
3127
|
if (registered.singleton && !await this.singletons.admit(run, registered.singleton)) {
|
|
@@ -2996,7 +3134,8 @@ var WorkflowEngine = class {
|
|
|
2996
3134
|
this.emit({
|
|
2997
3135
|
type: "run.suspended",
|
|
2998
3136
|
runId: run.id,
|
|
2999
|
-
workflow: run.workflow
|
|
3137
|
+
workflow: run.workflow,
|
|
3138
|
+
namespace: run.namespace
|
|
3000
3139
|
});
|
|
3001
3140
|
await this.store.releaseRunLock(run.id);
|
|
3002
3141
|
return {
|
|
@@ -3018,7 +3157,8 @@ var WorkflowEngine = class {
|
|
|
3018
3157
|
this.emit({
|
|
3019
3158
|
type: "run.suspended",
|
|
3020
3159
|
runId: run.id,
|
|
3021
|
-
workflow: run.workflow
|
|
3160
|
+
workflow: run.workflow,
|
|
3161
|
+
namespace: run.namespace
|
|
3022
3162
|
});
|
|
3023
3163
|
await this.store.releaseRunLock(run.id);
|
|
3024
3164
|
await remote.executor.dispatch(run, history, taskId);
|
|
@@ -3057,6 +3197,7 @@ var WorkflowEngine = class {
|
|
|
3057
3197
|
type: "run.failed",
|
|
3058
3198
|
runId: run.id,
|
|
3059
3199
|
workflow: run.workflow,
|
|
3200
|
+
namespace: run.namespace,
|
|
3060
3201
|
error
|
|
3061
3202
|
});
|
|
3062
3203
|
return {
|
|
@@ -3207,13 +3348,16 @@ var WorkflowEngine = class {
|
|
|
3207
3348
|
status: "pending",
|
|
3208
3349
|
input: cmd.input,
|
|
3209
3350
|
attempts: 1,
|
|
3210
|
-
|
|
3351
|
+
// `cmd.group` is the routing declaration the worker's replay emitted (cross-SDK, or a
|
|
3352
|
+
// group-served TS body) — treated as an isolation partition on top of the step's own
|
|
3353
|
+
// name, mirroring the StepDef formula below (identical expression everywhere).
|
|
3354
|
+
workerGroup: tenantGroup(sanitizeQueueToken(cmd.name), cmd.group),
|
|
3211
3355
|
enqueuedAt: at,
|
|
3212
3356
|
startedAt: at,
|
|
3213
3357
|
finishedAt: at,
|
|
3214
3358
|
// A `ctx.gather_calls([...])` fan-out stamps every dispatched `call` in the fan with the
|
|
3215
|
-
// same group, so the dashboard renders the
|
|
3216
|
-
// the gathered `recordStep`/`startChild` tags). Undefined for a lone sequential `ctx.
|
|
3359
|
+
// same group, so the dashboard renders the dispatched steps as one parallel fan (parity
|
|
3360
|
+
// with the gathered `recordStep`/`startChild` tags). Undefined for a lone sequential `ctx.step`.
|
|
3217
3361
|
parallelGroup: cmd.parallelGroup
|
|
3218
3362
|
}));
|
|
3219
3363
|
await this.pool.dispatch({
|
|
@@ -3221,7 +3365,7 @@ var WorkflowEngine = class {
|
|
|
3221
3365
|
seq: cmd.seq,
|
|
3222
3366
|
name: cmd.name,
|
|
3223
3367
|
stepId: id,
|
|
3224
|
-
group: cmd.group,
|
|
3368
|
+
group: tenantGroup(sanitizeQueueToken(cmd.name), cmd.group),
|
|
3225
3369
|
input: cmd.input,
|
|
3226
3370
|
traceparent: this.traceparent?.(),
|
|
3227
3371
|
context: this.context?.(),
|
|
@@ -3280,7 +3424,9 @@ var WorkflowEngine = class {
|
|
|
3280
3424
|
parallelGroup: cmd.parallelGroup
|
|
3281
3425
|
});
|
|
3282
3426
|
if (!await this.store.getRun(childId)) {
|
|
3283
|
-
queueMicrotask(() => void this.start(cmd.workflow, cmd.input, childId
|
|
3427
|
+
queueMicrotask(() => void this.start(cmd.workflow, cmd.input, childId, {
|
|
3428
|
+
namespace: run.namespace
|
|
3429
|
+
}).catch(() => void 0));
|
|
3284
3430
|
}
|
|
3285
3431
|
} else {
|
|
3286
3432
|
throw new Error(`remote workflow command '${cmd.kind}' is not supported yet`);
|
|
@@ -3312,6 +3458,7 @@ var WorkflowEngine = class {
|
|
|
3312
3458
|
type: "run.failed",
|
|
3313
3459
|
runId: run.id,
|
|
3314
3460
|
workflow: run.workflow,
|
|
3461
|
+
namespace: run.namespace,
|
|
3315
3462
|
error
|
|
3316
3463
|
});
|
|
3317
3464
|
return {
|
|
@@ -3332,7 +3479,8 @@ var WorkflowEngine = class {
|
|
|
3332
3479
|
this.emit({
|
|
3333
3480
|
type: "run.started",
|
|
3334
3481
|
runId: run.id,
|
|
3335
|
-
workflow: run.workflow
|
|
3482
|
+
workflow: run.workflow,
|
|
3483
|
+
namespace: run.namespace
|
|
3336
3484
|
});
|
|
3337
3485
|
}
|
|
3338
3486
|
if (run.status !== "cancelling" && registered?.singleton && !await this.singletons.admit(run, registered.singleton)) {
|
|
@@ -3345,7 +3493,8 @@ var WorkflowEngine = class {
|
|
|
3345
3493
|
this.emit({
|
|
3346
3494
|
type: "run.suspended",
|
|
3347
3495
|
runId: run.id,
|
|
3348
|
-
workflow: run.workflow
|
|
3496
|
+
workflow: run.workflow,
|
|
3497
|
+
namespace: run.namespace
|
|
3349
3498
|
});
|
|
3350
3499
|
await this.store.releaseRunLock(run.id);
|
|
3351
3500
|
return {
|
|
@@ -3357,7 +3506,7 @@ var WorkflowEngine = class {
|
|
|
3357
3506
|
const snapshot = await this.store.listCheckpoints(run.id);
|
|
3358
3507
|
const replay = /* @__PURE__ */ new Map();
|
|
3359
3508
|
for (const cp of snapshot) replay.set(cp.seq, cp);
|
|
3360
|
-
const ctx = createWorkflowCtx(this.ctxHostFor(replay), run.id, compensations, run.workflow);
|
|
3509
|
+
const ctx = createWorkflowCtx(this.ctxHostFor(replay, run.namespace), run.id, compensations, run.workflow);
|
|
3361
3510
|
try {
|
|
3362
3511
|
const output = await fn(ctx, run.input);
|
|
3363
3512
|
if (this.cancelRequested.has(run.id)) return this.compensateAndCancel(run, compensations);
|
|
@@ -3377,7 +3526,8 @@ var WorkflowEngine = class {
|
|
|
3377
3526
|
this.emit({
|
|
3378
3527
|
type: "run.completed",
|
|
3379
3528
|
runId: run.id,
|
|
3380
|
-
workflow: run.workflow
|
|
3529
|
+
workflow: run.workflow,
|
|
3530
|
+
namespace: run.namespace
|
|
3381
3531
|
});
|
|
3382
3532
|
void this.notifyParent(run.id, {
|
|
3383
3533
|
ok: true,
|
|
@@ -3455,7 +3605,7 @@ var WorkflowEngine = class {
|
|
|
3455
3605
|
/** The seam handed to {@link createWorkflowCtx}: the authoring API reaches durability + lifecycle
|
|
3456
3606
|
* (checkpointing, dispatch, child start) through this, so the ctx primitives live in their own
|
|
3457
3607
|
* module and the engine stays the orchestrator. */
|
|
3458
|
-
ctxHostFor(replay) {
|
|
3608
|
+
ctxHostFor(replay, parentNamespace) {
|
|
3459
3609
|
return {
|
|
3460
3610
|
store: this.store,
|
|
3461
3611
|
replay,
|
|
@@ -3465,10 +3615,16 @@ var WorkflowEngine = class {
|
|
|
3465
3615
|
completeStep: /* @__PURE__ */ __name((s) => this.completeStep(s), "completeStep"),
|
|
3466
3616
|
failStep: /* @__PURE__ */ __name((s) => this.failStep(s), "failStep"),
|
|
3467
3617
|
callRemote: /* @__PURE__ */ __name((runId, seq, step, input, queue, transport, admission) => this.callRemote(runId, seq, step, input, queue, transport, replay, admission), "callRemote"),
|
|
3468
|
-
// Defer so a fast child can't reentrantly resume a still-running parent.
|
|
3618
|
+
// Defer so a fast child can't reentrantly resume a still-running parent. Stamp the child with
|
|
3619
|
+
// the PARENT run's namespace (not this engine's) so a child belongs to the tenant/partition of
|
|
3620
|
+
// the run it was spawned from — even when an operator (namespace: undefined, drives every
|
|
3621
|
+
// namespace) is the one executing the parent. Otherwise an operator recovery-resuming a
|
|
3622
|
+
// `davi-local` pipeline would stamp its `processing` child `default` and it would leak off the
|
|
3623
|
+
// tenant's worker pool onto the shared/dev workers.
|
|
3469
3624
|
startChild: /* @__PURE__ */ __name((workflow, input, id, priority) => {
|
|
3470
3625
|
queueMicrotask(() => void this.start(workflow, input, id, {
|
|
3471
|
-
priority
|
|
3626
|
+
priority,
|
|
3627
|
+
namespace: parentNamespace
|
|
3472
3628
|
}).catch(() => void 0));
|
|
3473
3629
|
}, "startChild"),
|
|
3474
3630
|
// Shallow-merge into the run's searchAttributes (the ctx primitive makes this exactly-once).
|
|
@@ -3596,7 +3752,6 @@ var WorkflowEngine = class {
|
|
|
3596
3752
|
this.queueWaiters.get(queue)?.delete(runId);
|
|
3597
3753
|
this.stepQueue.set(id, queue);
|
|
3598
3754
|
}
|
|
3599
|
-
const validInput = step.input.parse(input);
|
|
3600
3755
|
const enqueuedAt = /* @__PURE__ */ new Date();
|
|
3601
3756
|
await this.store.saveCheckpoint({
|
|
3602
3757
|
runId,
|
|
@@ -3605,9 +3760,9 @@ var WorkflowEngine = class {
|
|
|
3605
3760
|
kind: "remote",
|
|
3606
3761
|
stepId: id,
|
|
3607
3762
|
status: "pending",
|
|
3608
|
-
input
|
|
3763
|
+
input,
|
|
3609
3764
|
attempts: attempt,
|
|
3610
|
-
workerGroup: step.
|
|
3765
|
+
workerGroup: tenantGroup(sanitizeQueueToken(step.name), step.partition),
|
|
3611
3766
|
enqueuedAt,
|
|
3612
3767
|
startedAt: enqueuedAt,
|
|
3613
3768
|
finishedAt: enqueuedAt
|
|
@@ -3617,8 +3772,8 @@ var WorkflowEngine = class {
|
|
|
3617
3772
|
seq,
|
|
3618
3773
|
name: step.name,
|
|
3619
3774
|
stepId: id,
|
|
3620
|
-
group: step.
|
|
3621
|
-
input
|
|
3775
|
+
group: tenantGroup(sanitizeQueueToken(step.name), step.partition),
|
|
3776
|
+
input,
|
|
3622
3777
|
traceparent: this.traceparent?.(),
|
|
3623
3778
|
context: this.context?.(),
|
|
3624
3779
|
priority: admission?.priority,
|
|
@@ -3683,7 +3838,9 @@ var WorkflowEngine = class {
|
|
|
3683
3838
|
const run = await this.store.getRun(decision.runId);
|
|
3684
3839
|
if (!run || run.awaitingDecisionTaskId !== decision.taskId) return;
|
|
3685
3840
|
if (run.status === "cancelled" || run.status === "completed" || run.status === "dead") return;
|
|
3686
|
-
if (run.namespace !== void 0 && run.namespace !== this.namespace)
|
|
3841
|
+
if (this.namespace !== void 0 && run.namespace !== void 0 && run.namespace !== this.namespace) {
|
|
3842
|
+
return;
|
|
3843
|
+
}
|
|
3687
3844
|
const nowMs = this.clock();
|
|
3688
3845
|
if (!await this.store.tryLockRun(decision.runId, this.instanceId, nowMs + this.leaseMs, nowMs)) {
|
|
3689
3846
|
return;
|
|
@@ -3749,7 +3906,7 @@ var WorkflowEngine = class {
|
|
|
3749
3906
|
/** In-memory await path for a remote step with a liveness `timeoutMs` (re-dispatch on timeout). */
|
|
3750
3907
|
async callRemoteInMemory(runId, seq, step, input, transport) {
|
|
3751
3908
|
if (this.pool.size === 0) throw new Error("remote steps require a Transport");
|
|
3752
|
-
const validInput = step.input.parse(input);
|
|
3909
|
+
const validInput = step.input ? step.input.parse(input) : input;
|
|
3753
3910
|
const id = stepId(runId, seq);
|
|
3754
3911
|
const enqueuedAt = /* @__PURE__ */ new Date();
|
|
3755
3912
|
this.emit({
|
|
@@ -3772,7 +3929,7 @@ var WorkflowEngine = class {
|
|
|
3772
3929
|
seq,
|
|
3773
3930
|
name: step.name,
|
|
3774
3931
|
stepId: id,
|
|
3775
|
-
group: step.
|
|
3932
|
+
group: tenantGroup(sanitizeQueueToken(step.name), step.partition),
|
|
3776
3933
|
input: validInput,
|
|
3777
3934
|
traceparent: this.traceparent?.(),
|
|
3778
3935
|
context: this.context?.(),
|
|
@@ -3780,7 +3937,7 @@ var WorkflowEngine = class {
|
|
|
3780
3937
|
}, transport);
|
|
3781
3938
|
try {
|
|
3782
3939
|
const resolution = step.timeoutMs ? await this.awaitWithHeartbeat(id, resultPromise, step.timeoutMs) : await resultPromise;
|
|
3783
|
-
const output = step.output.parse(resolution.output);
|
|
3940
|
+
const output = step.output ? step.output.parse(resolution.output) : resolution.output;
|
|
3784
3941
|
const startedAt = resolution.startedAt ? new Date(resolution.startedAt) : enqueuedAt;
|
|
3785
3942
|
await this.completeStep({
|
|
3786
3943
|
runId,
|
|
@@ -3791,7 +3948,7 @@ var WorkflowEngine = class {
|
|
|
3791
3948
|
output,
|
|
3792
3949
|
events: resolution.events,
|
|
3793
3950
|
attempts: attempt,
|
|
3794
|
-
workerGroup: step.
|
|
3951
|
+
workerGroup: tenantGroup(sanitizeQueueToken(step.name), step.partition),
|
|
3795
3952
|
enqueuedAt,
|
|
3796
3953
|
startedAt
|
|
3797
3954
|
});
|
|
@@ -3867,16 +4024,6 @@ var TERMINAL_RUN_STATUSES = [
|
|
|
3867
4024
|
"dead"
|
|
3868
4025
|
];
|
|
3869
4026
|
|
|
3870
|
-
// src/remote-step-factory.ts
|
|
3871
|
-
function remoteStep(config) {
|
|
3872
|
-
return {
|
|
3873
|
-
...config,
|
|
3874
|
-
group: config.group ?? config.name.split(".")[0] ?? config.name,
|
|
3875
|
-
__remote: true
|
|
3876
|
-
};
|
|
3877
|
-
}
|
|
3878
|
-
__name(remoteStep, "remoteStep");
|
|
3879
|
-
|
|
3880
4027
|
// src/codec-state-store.ts
|
|
3881
4028
|
var CodecStateStore = class {
|
|
3882
4029
|
static {
|
|
@@ -4601,6 +4748,8 @@ var InMemoryTransport = class {
|
|
|
4601
4748
|
ContinueAsNew,
|
|
4602
4749
|
DURABLE_OPTIONS,
|
|
4603
4750
|
DURABLE_OPTIONS_CANONICAL,
|
|
4751
|
+
DURABLE_STEP_CONFIG,
|
|
4752
|
+
DURABLE_STEP_NAME,
|
|
4604
4753
|
Entities,
|
|
4605
4754
|
EventAccumulators,
|
|
4606
4755
|
FatalError,
|
|
@@ -4638,12 +4787,15 @@ var InMemoryTransport = class {
|
|
|
4638
4787
|
normalizeAttributeRows,
|
|
4639
4788
|
parseDuration,
|
|
4640
4789
|
prevCronFireMs,
|
|
4641
|
-
remoteStep,
|
|
4642
4790
|
runSchedules,
|
|
4643
4791
|
runStepHandler,
|
|
4792
|
+
sanitizeQueueToken,
|
|
4644
4793
|
scheduledRunId,
|
|
4645
4794
|
sqlComparator,
|
|
4795
|
+
stepConfigOf,
|
|
4646
4796
|
stepId,
|
|
4797
|
+
stepNameOf,
|
|
4798
|
+
tenantGroup,
|
|
4647
4799
|
workflowName
|
|
4648
4800
|
});
|
|
4649
4801
|
//# sourceMappingURL=index.cjs.map
|