@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.js
CHANGED
|
@@ -574,7 +574,7 @@ var Entities = class {
|
|
|
574
574
|
let state = config.initialState();
|
|
575
575
|
for (let i = 0; ; i += 1) {
|
|
576
576
|
const msg = await ctx.waitForSignal(token);
|
|
577
|
-
const out = await ctx.
|
|
577
|
+
const out = await ctx.localStep(`op:${i}`, async () => {
|
|
578
578
|
const handler = config.handlers[msg.op];
|
|
579
579
|
if (!handler) throw new FatalError(`entity "${name}" has no handler for op "${msg.op}"`);
|
|
580
580
|
const result = await handler(state, msg.arg);
|
|
@@ -586,7 +586,7 @@ var Entities = class {
|
|
|
586
586
|
state = out.state;
|
|
587
587
|
await ctx.setEvent("state", state);
|
|
588
588
|
if (msg.reply) {
|
|
589
|
-
await ctx.
|
|
589
|
+
await ctx.localStep(`reply:${i}`, async () => {
|
|
590
590
|
await this.host.signal(msg.reply, out.result);
|
|
591
591
|
});
|
|
592
592
|
}
|
|
@@ -700,16 +700,28 @@ function eventMatches(payload, match) {
|
|
|
700
700
|
}
|
|
701
701
|
__name(eventMatches, "eventMatches");
|
|
702
702
|
|
|
703
|
+
// src/tenant-group.ts
|
|
704
|
+
function tenantGroup(baseGroup, tenant) {
|
|
705
|
+
return tenant !== void 0 && tenant !== "" && tenant !== "default" ? `${baseGroup}@${tenant}` : baseGroup;
|
|
706
|
+
}
|
|
707
|
+
__name(tenantGroup, "tenantGroup");
|
|
708
|
+
function sanitizeQueueToken(name) {
|
|
709
|
+
return name.replace(/:/g, "-");
|
|
710
|
+
}
|
|
711
|
+
__name(sanitizeQueueToken, "sanitizeQueueToken");
|
|
712
|
+
|
|
703
713
|
// src/remote-workflow-executor.ts
|
|
704
714
|
var RemoteWorkflowExecutor = class {
|
|
705
715
|
static {
|
|
706
716
|
__name(this, "RemoteWorkflowExecutor");
|
|
707
717
|
}
|
|
708
718
|
transport;
|
|
709
|
-
|
|
710
|
-
|
|
719
|
+
name;
|
|
720
|
+
partition;
|
|
721
|
+
constructor(transport, name, partition, _opts = {}) {
|
|
711
722
|
this.transport = transport;
|
|
712
|
-
this.
|
|
723
|
+
this.name = name;
|
|
724
|
+
this.partition = partition;
|
|
713
725
|
}
|
|
714
726
|
/**
|
|
715
727
|
* Enqueue one workflow turn under the engine-supplied `taskId`. Does NOT await the decision — the
|
|
@@ -729,7 +741,7 @@ var RemoteWorkflowExecutor = class {
|
|
|
729
741
|
...pendingSignals ? {
|
|
730
742
|
pendingSignals
|
|
731
743
|
} : {},
|
|
732
|
-
group: this.
|
|
744
|
+
group: tenantGroup(sanitizeQueueToken(this.name), this.partition),
|
|
733
745
|
priority: run.priority,
|
|
734
746
|
attempt: 1
|
|
735
747
|
};
|
|
@@ -901,6 +913,16 @@ var TransportPool = class {
|
|
|
901
913
|
liveWorkers: reports.flatMap((r) => r.liveWorkers)
|
|
902
914
|
};
|
|
903
915
|
}
|
|
916
|
+
/**
|
|
917
|
+
* Register a control-plane `start-run` consumer on every transport that carries the channel. Only
|
|
918
|
+
* control-plane-side transports implement {@link Transport.onStartRun}; a transport without it is
|
|
919
|
+
* skipped, so a plain worker/in-process pool is a no-op. Mirrors {@link bind}'s per-transport wiring.
|
|
920
|
+
*/
|
|
921
|
+
onStartRun(handler) {
|
|
922
|
+
for (const { transport } of this.transports) {
|
|
923
|
+
transport.onStartRun?.(handler);
|
|
924
|
+
}
|
|
925
|
+
}
|
|
904
926
|
/** Distinct worker groups with a live heartbeat, merged across every transport that can report it. */
|
|
905
927
|
async listWorkerGroups() {
|
|
906
928
|
const groups = /* @__PURE__ */ new Set();
|
|
@@ -946,6 +968,26 @@ function unwrapCompletion(payload, label) {
|
|
|
946
968
|
}
|
|
947
969
|
__name(unwrapCompletion, "unwrapCompletion");
|
|
948
970
|
|
|
971
|
+
// src/step-name-symbol.ts
|
|
972
|
+
var DURABLE_STEP_NAME = Symbol.for("durable.step.name");
|
|
973
|
+
var DURABLE_STEP_CONFIG = Symbol.for("durable.step.config");
|
|
974
|
+
function isStepConfig(value) {
|
|
975
|
+
return typeof value === "object" && value !== null;
|
|
976
|
+
}
|
|
977
|
+
__name(isStepConfig, "isStepConfig");
|
|
978
|
+
function stepNameOf(ref) {
|
|
979
|
+
if (typeof ref !== "function") return void 0;
|
|
980
|
+
const stamped = ref[DURABLE_STEP_NAME];
|
|
981
|
+
return typeof stamped === "string" ? stamped : void 0;
|
|
982
|
+
}
|
|
983
|
+
__name(stepNameOf, "stepNameOf");
|
|
984
|
+
function stepConfigOf(ref) {
|
|
985
|
+
if (typeof ref !== "function") return void 0;
|
|
986
|
+
const stamped = ref[DURABLE_STEP_CONFIG];
|
|
987
|
+
return isStepConfig(stamped) ? stamped : void 0;
|
|
988
|
+
}
|
|
989
|
+
__name(stepConfigOf, "stepConfigOf");
|
|
990
|
+
|
|
949
991
|
// src/workflow-ref.ts
|
|
950
992
|
var WORKFLOW_NAME_KEY = Symbol.for("nestjs-durable:workflow-name");
|
|
951
993
|
function workflowName(ref) {
|
|
@@ -965,6 +1007,23 @@ function normalizeChildOptions(options) {
|
|
|
965
1007
|
} : options ?? {};
|
|
966
1008
|
}
|
|
967
1009
|
__name(normalizeChildOptions, "normalizeChildOptions");
|
|
1010
|
+
function resolveStepPolicy(config, opts) {
|
|
1011
|
+
const policy = {};
|
|
1012
|
+
const retries = opts?.retries ?? config?.retries;
|
|
1013
|
+
if (retries !== void 0) policy.retries = retries;
|
|
1014
|
+
const backoff = opts?.backoff ?? config?.backoff;
|
|
1015
|
+
if (backoff !== void 0) policy.backoff = backoff;
|
|
1016
|
+
const backoffMs = opts?.backoffMs ?? config?.backoffMs;
|
|
1017
|
+
if (backoffMs !== void 0) policy.backoffMs = backoffMs;
|
|
1018
|
+
const backoffMaxMs = opts?.backoffMaxMs ?? config?.backoffMaxMs;
|
|
1019
|
+
if (backoffMaxMs !== void 0) policy.backoffMaxMs = backoffMaxMs;
|
|
1020
|
+
const jitter = opts?.jitter ?? config?.jitter;
|
|
1021
|
+
if (jitter !== void 0) policy.jitter = jitter;
|
|
1022
|
+
const timeoutMs = opts?.timeoutMs ?? config?.timeoutMs;
|
|
1023
|
+
if (timeoutMs !== void 0) policy.timeoutMs = timeoutMs;
|
|
1024
|
+
return policy;
|
|
1025
|
+
}
|
|
1026
|
+
__name(resolveStepPolicy, "resolveStepPolicy");
|
|
968
1027
|
var Position = class Position2 {
|
|
969
1028
|
static {
|
|
970
1029
|
__name(this, "Position");
|
|
@@ -990,7 +1049,7 @@ function createWorkflowCtx(host, runId, compensations, workflow = "") {
|
|
|
990
1049
|
await store.saveCheckpoint(cp);
|
|
991
1050
|
replay?.set(cp.seq, cp);
|
|
992
1051
|
}, "writeCheckpoint");
|
|
993
|
-
const
|
|
1052
|
+
const localStep = /* @__PURE__ */ __name(async (name, fn, options) => {
|
|
994
1053
|
const current = pos.next();
|
|
995
1054
|
const existing = await readCheckpoint(current);
|
|
996
1055
|
if (existing && existing.name !== name) {
|
|
@@ -1064,7 +1123,7 @@ function createWorkflowCtx(host, runId, compensations, workflow = "") {
|
|
|
1064
1123
|
if (wait > 0) await new Promise((resolve) => setTimeout(resolve, wait));
|
|
1065
1124
|
}
|
|
1066
1125
|
}
|
|
1067
|
-
}, "
|
|
1126
|
+
}, "localStep");
|
|
1068
1127
|
const transaction = /* @__PURE__ */ __name(async (name, fn) => {
|
|
1069
1128
|
if (!store.transaction) {
|
|
1070
1129
|
throw new Error("ctx.transaction needs a store that supports transactions (the SQL adapters do). Use ctx.step for non-transactional work.");
|
|
@@ -1211,7 +1270,7 @@ function createWorkflowCtx(host, runId, compensations, workflow = "") {
|
|
|
1211
1270
|
throw new WorkflowSuspended(deadline);
|
|
1212
1271
|
}, "waitForEvent");
|
|
1213
1272
|
const task = /* @__PURE__ */ __name(async (name, dispatch, options) => {
|
|
1214
|
-
await
|
|
1273
|
+
await localStep(`task:dispatch:${name}`, dispatch, options);
|
|
1215
1274
|
return unwrapCompletion(await waitForSignal(`task:${runId}:${name}`), `task "${name}"`);
|
|
1216
1275
|
}, "task");
|
|
1217
1276
|
const child = /* @__PURE__ */ __name(async (workflow2, input, options) => {
|
|
@@ -1424,9 +1483,8 @@ function createWorkflowCtx(host, runId, compensations, workflow = "") {
|
|
|
1424
1483
|
wait
|
|
1425
1484
|
};
|
|
1426
1485
|
}, "webhook");
|
|
1427
|
-
const
|
|
1428
|
-
const
|
|
1429
|
-
const uuid = /* @__PURE__ */ __name(() => step("uuid", async () => globalThis.crypto.randomUUID()), "uuid");
|
|
1486
|
+
const sideEffect = /* @__PURE__ */ __name((fn) => localStep("sideEffect", async () => fn()), "sideEffect");
|
|
1487
|
+
const now = /* @__PURE__ */ __name(() => localStep("now", async () => host.clock()), "now");
|
|
1430
1488
|
const upsertSearchAttributes = /* @__PURE__ */ __name(async (attrs) => {
|
|
1431
1489
|
const current = pos.next();
|
|
1432
1490
|
const existing = await readCheckpoint(current);
|
|
@@ -1445,9 +1503,26 @@ function createWorkflowCtx(host, runId, compensations, workflow = "") {
|
|
|
1445
1503
|
output: attrs
|
|
1446
1504
|
}));
|
|
1447
1505
|
}, "upsertSearchAttributes");
|
|
1506
|
+
function step(handlerOrName, input, opts) {
|
|
1507
|
+
const name = typeof handlerOrName === "string" ? handlerOrName : stepNameOf(handlerOrName);
|
|
1508
|
+
if (name === void 0) {
|
|
1509
|
+
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");
|
|
1510
|
+
}
|
|
1511
|
+
const config = typeof handlerOrName === "string" ? void 0 : stepConfigOf(handlerOrName);
|
|
1512
|
+
const def = {
|
|
1513
|
+
name,
|
|
1514
|
+
...resolveStepPolicy(config, opts)
|
|
1515
|
+
};
|
|
1516
|
+
return host.callRemote(runId, pos.next(), def, input, opts?.queue, opts?.transport, {
|
|
1517
|
+
priority: opts?.priority,
|
|
1518
|
+
fairnessKey: opts?.fairnessKey
|
|
1519
|
+
});
|
|
1520
|
+
}
|
|
1521
|
+
__name(step, "step");
|
|
1448
1522
|
return {
|
|
1449
1523
|
runId,
|
|
1450
1524
|
step,
|
|
1525
|
+
localStep,
|
|
1451
1526
|
upsertSearchAttributes,
|
|
1452
1527
|
transaction,
|
|
1453
1528
|
callEntity,
|
|
@@ -1467,12 +1542,7 @@ function createWorkflowCtx(host, runId, compensations, workflow = "") {
|
|
|
1467
1542
|
onUpdate,
|
|
1468
1543
|
patched,
|
|
1469
1544
|
now,
|
|
1470
|
-
|
|
1471
|
-
uuid,
|
|
1472
|
-
call: /* @__PURE__ */ __name((remote, input, opts) => host.callRemote(runId, pos.next(), remote, input, opts?.queue, opts?.transport, {
|
|
1473
|
-
priority: opts?.priority,
|
|
1474
|
-
fairnessKey: opts?.fairnessKey
|
|
1475
|
-
}), "call")
|
|
1545
|
+
sideEffect
|
|
1476
1546
|
};
|
|
1477
1547
|
}
|
|
1478
1548
|
__name(createWorkflowCtx, "createWorkflowCtx");
|
|
@@ -1590,8 +1660,10 @@ var WorkflowEngine = class {
|
|
|
1590
1660
|
this.admission = deps.admission ?? new InMemoryAdmissionBackend(this.clock);
|
|
1591
1661
|
this.admission.onFreed?.((queue) => this.wakeQueueWaiters(queue));
|
|
1592
1662
|
this.instanceId = deps.instanceId ?? globalThis.crypto.randomUUID();
|
|
1593
|
-
this.namespace = deps.namespace
|
|
1594
|
-
|
|
1663
|
+
this.namespace = deps.namespace;
|
|
1664
|
+
if (this.namespace !== void 0) {
|
|
1665
|
+
this.pool.useNamespace(this.namespace);
|
|
1666
|
+
}
|
|
1595
1667
|
this.leaseMs = deps.leaseMs ?? 3e4;
|
|
1596
1668
|
this.maxRecoveryAttempts = deps.maxRecoveryAttempts;
|
|
1597
1669
|
this.remoteAdvanceSilenceMs = deps.remoteAdvanceSilenceMs;
|
|
@@ -1656,6 +1728,13 @@ var WorkflowEngine = class {
|
|
|
1656
1728
|
await this.completeRemoteDecision(decision);
|
|
1657
1729
|
}
|
|
1658
1730
|
);
|
|
1731
|
+
this.pool.onStartRun(async (message) => {
|
|
1732
|
+
await this.start(message.workflow, message.input, message.runId ?? globalThis.crypto.randomUUID(), {
|
|
1733
|
+
namespace: message.tenant,
|
|
1734
|
+
tags: message.tags,
|
|
1735
|
+
searchAttributes: message.searchAttributes
|
|
1736
|
+
});
|
|
1737
|
+
});
|
|
1659
1738
|
this.controlPlane?.onControl((msg) => {
|
|
1660
1739
|
if (msg.from === this.instanceId) return;
|
|
1661
1740
|
if (msg.kind === "event") {
|
|
@@ -1820,7 +1899,7 @@ var WorkflowEngine = class {
|
|
|
1820
1899
|
});
|
|
1821
1900
|
}
|
|
1822
1901
|
/**
|
|
1823
|
-
* Register a flow-control queue referenced by `ctx.
|
|
1902
|
+
* Register a flow-control queue referenced by `ctx.step(step, input, { queue })`. Caps concurrent
|
|
1824
1903
|
* in-flight steps and/or the admission rate; blocked calls re-suspend and retry, so the limit is
|
|
1825
1904
|
* durable. Per engine instance (see {@link QueueConfig}). Registering the same name replaces it.
|
|
1826
1905
|
*/
|
|
@@ -1923,6 +2002,23 @@ var WorkflowEngine = class {
|
|
|
1923
2002
|
const ancestor = await this.findRemoteAncestor(runId);
|
|
1924
2003
|
if (ancestor) registered = this.synthesizeRemoteChild(name, ancestor.version, ancestor);
|
|
1925
2004
|
}
|
|
2005
|
+
if (!registered) {
|
|
2006
|
+
const conventionRun = {
|
|
2007
|
+
id: runId,
|
|
2008
|
+
workflow: name,
|
|
2009
|
+
workflowVersion: "1",
|
|
2010
|
+
status: "pending",
|
|
2011
|
+
// Stamp the namespace the REAL run will carry (below) so convention routing resolves the same
|
|
2012
|
+
// tenant-suffixed group the run will dispatch to — an operator starting a tenant's run via the
|
|
2013
|
+
// `onStartRun` wire (opts.namespace = tenant) must find the live `<workflow>@<tenant>` group,
|
|
2014
|
+
// not the bare group. Without this the start-run entry path can't reach a suffixed-only worker.
|
|
2015
|
+
namespace: opts?.namespace ?? this.namespace,
|
|
2016
|
+
input,
|
|
2017
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
2018
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
2019
|
+
};
|
|
2020
|
+
registered = await this.resolveRemoteByConvention(conventionRun);
|
|
2021
|
+
}
|
|
1926
2022
|
if (!registered) throw new Error(`workflow ${name} is not registered`);
|
|
1927
2023
|
await registered.validateInput?.(input);
|
|
1928
2024
|
const prior = await this.store.getRun(runId);
|
|
@@ -1947,7 +2043,7 @@ var WorkflowEngine = class {
|
|
|
1947
2043
|
workflow: name,
|
|
1948
2044
|
workflowVersion: registered.version,
|
|
1949
2045
|
status: "pending",
|
|
1950
|
-
namespace: this.namespace,
|
|
2046
|
+
namespace: opts?.namespace ?? this.namespace,
|
|
1951
2047
|
input,
|
|
1952
2048
|
tags,
|
|
1953
2049
|
searchAttributes: opts?.searchAttributes,
|
|
@@ -1984,11 +2080,11 @@ var WorkflowEngine = class {
|
|
|
1984
2080
|
error: run.error
|
|
1985
2081
|
};
|
|
1986
2082
|
}
|
|
1987
|
-
if (run.namespace !== void 0 && run.namespace !== this.namespace) {
|
|
2083
|
+
if (this.namespace !== void 0 && run.namespace !== void 0 && run.namespace !== this.namespace) {
|
|
1988
2084
|
await this.store.releaseRunLock(runId);
|
|
1989
2085
|
throw new NamespaceMismatch();
|
|
1990
2086
|
}
|
|
1991
|
-
const registered = this.workflows.get(versionKey(run.workflow, run.workflowVersion)) ?? await this.findInheritedRegistration(run);
|
|
2087
|
+
const registered = this.workflows.get(versionKey(run.workflow, run.workflowVersion)) ?? await this.findInheritedRegistration(run) ?? await this.resolveRemoteByConvention(run);
|
|
1992
2088
|
if (!registered) {
|
|
1993
2089
|
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)`);
|
|
1994
2090
|
}
|
|
@@ -2034,6 +2130,34 @@ var WorkflowEngine = class {
|
|
|
2034
2130
|
};
|
|
2035
2131
|
}
|
|
2036
2132
|
/**
|
|
2133
|
+
* Attempt to route an unregistered workflow to the live worker group of the same name: if the
|
|
2134
|
+
* transport reports a live heartbeat for group `run.workflow`, synthesize a throwaway
|
|
2135
|
+
* {@link RegisteredWorkflow} that dispatches to that group over the primary transport. Returns
|
|
2136
|
+
* `undefined` when the group is not live. Always attempted — there is no opt-in flag; an explicit
|
|
2137
|
+
* `register`/`registerRemote` still takes precedence (this only runs when nothing is registered).
|
|
2138
|
+
*
|
|
2139
|
+
* Recomputed per call (never memoized into `this.workflows`) so it stays correct if group
|
|
2140
|
+
* liveness changes between calls, exactly as {@link synthesizeRemoteChild} does.
|
|
2141
|
+
*/
|
|
2142
|
+
async resolveRemoteByConvention(run) {
|
|
2143
|
+
const liveGroups = await this.pool.listWorkerGroups();
|
|
2144
|
+
const group = tenantGroup(sanitizeQueueToken(run.workflow), run.namespace);
|
|
2145
|
+
if (!liveGroups.includes(group)) return void 0;
|
|
2146
|
+
const executor = new RemoteWorkflowExecutor(this.pool.primary, run.workflow, run.namespace);
|
|
2147
|
+
return {
|
|
2148
|
+
name: run.workflow,
|
|
2149
|
+
version: run.workflowVersion,
|
|
2150
|
+
fn: /* @__PURE__ */ __name(() => {
|
|
2151
|
+
throw new Error(`remote workflow ${run.workflow} has no local body`);
|
|
2152
|
+
}, "fn"),
|
|
2153
|
+
hasBody: false,
|
|
2154
|
+
remote: {
|
|
2155
|
+
group,
|
|
2156
|
+
executor
|
|
2157
|
+
}
|
|
2158
|
+
};
|
|
2159
|
+
}
|
|
2160
|
+
/**
|
|
2037
2161
|
* Walk the parent chain of `childRunId` to the nearest ancestor registered as REMOTE, returning its
|
|
2038
2162
|
* registration (or `undefined` if there is none). The parent of an awaited child is found via its
|
|
2039
2163
|
* live `child:<childId>` signal waiter — present for as long as the parent is suspended awaiting the
|
|
@@ -2130,6 +2254,7 @@ var WorkflowEngine = class {
|
|
|
2130
2254
|
type: "run.failed",
|
|
2131
2255
|
runId: run.id,
|
|
2132
2256
|
workflow: run.workflow,
|
|
2257
|
+
namespace: run.namespace,
|
|
2133
2258
|
error
|
|
2134
2259
|
});
|
|
2135
2260
|
}
|
|
@@ -2187,6 +2312,7 @@ var WorkflowEngine = class {
|
|
|
2187
2312
|
type: "run.failed",
|
|
2188
2313
|
runId: run.id,
|
|
2189
2314
|
workflow: run.workflow,
|
|
2315
|
+
namespace: run.namespace,
|
|
2190
2316
|
error
|
|
2191
2317
|
});
|
|
2192
2318
|
this.notifyDead({
|
|
@@ -2261,7 +2387,8 @@ var WorkflowEngine = class {
|
|
|
2261
2387
|
if (!run) return null;
|
|
2262
2388
|
const id = newRunId ?? `${runId}~retry~${globalThis.crypto.randomUUID().slice(0, 8)}`;
|
|
2263
2389
|
await this.start(run.workflow, input, id, {
|
|
2264
|
-
tags: run.tags
|
|
2390
|
+
tags: run.tags,
|
|
2391
|
+
namespace: run.namespace
|
|
2265
2392
|
});
|
|
2266
2393
|
return {
|
|
2267
2394
|
runId: id
|
|
@@ -2495,6 +2622,7 @@ var WorkflowEngine = class {
|
|
|
2495
2622
|
type: "run.failed",
|
|
2496
2623
|
runId,
|
|
2497
2624
|
workflow: run.workflow,
|
|
2625
|
+
namespace: run.namespace,
|
|
2498
2626
|
error
|
|
2499
2627
|
});
|
|
2500
2628
|
await this.cancelChildren(runId, opts);
|
|
@@ -2771,7 +2899,7 @@ var WorkflowEngine = class {
|
|
|
2771
2899
|
});
|
|
2772
2900
|
}
|
|
2773
2901
|
async execute(run, fn) {
|
|
2774
|
-
const registered = this.workflows.get(versionKey(run.workflow, run.workflowVersion)) ?? await this.findInheritedRegistration(run);
|
|
2902
|
+
const registered = this.workflows.get(versionKey(run.workflow, run.workflowVersion)) ?? await this.findInheritedRegistration(run) ?? await this.resolveRemoteByConvention(run);
|
|
2775
2903
|
const lockNow = this.clock();
|
|
2776
2904
|
if (run.lockedBy !== this.instanceId || (run.lockedUntil ?? 0) <= lockNow) {
|
|
2777
2905
|
if (!await this.store.tryLockRun(run.id, this.instanceId, lockNow + this.leaseMs, lockNow)) {
|
|
@@ -2821,6 +2949,7 @@ var WorkflowEngine = class {
|
|
|
2821
2949
|
type: "run.completed",
|
|
2822
2950
|
runId: run.id,
|
|
2823
2951
|
workflow: run.workflow,
|
|
2952
|
+
namespace: run.namespace,
|
|
2824
2953
|
output: outcome.output
|
|
2825
2954
|
});
|
|
2826
2955
|
void this.notifyParent(run.id, {
|
|
@@ -2843,6 +2972,7 @@ var WorkflowEngine = class {
|
|
|
2843
2972
|
type: "run.failed",
|
|
2844
2973
|
runId: run.id,
|
|
2845
2974
|
workflow: run.workflow,
|
|
2975
|
+
namespace: run.namespace,
|
|
2846
2976
|
error: outcome.error
|
|
2847
2977
|
});
|
|
2848
2978
|
void this.notifyParent(run.id, {
|
|
@@ -2863,7 +2993,8 @@ var WorkflowEngine = class {
|
|
|
2863
2993
|
this.emit({
|
|
2864
2994
|
type: "run.suspended",
|
|
2865
2995
|
runId: run.id,
|
|
2866
|
-
workflow: run.workflow
|
|
2996
|
+
workflow: run.workflow,
|
|
2997
|
+
namespace: run.namespace
|
|
2867
2998
|
});
|
|
2868
2999
|
return {
|
|
2869
3000
|
runId: run.id,
|
|
@@ -2884,6 +3015,7 @@ var WorkflowEngine = class {
|
|
|
2884
3015
|
type: "run.failed",
|
|
2885
3016
|
runId: run.id,
|
|
2886
3017
|
workflow: run.workflow,
|
|
3018
|
+
namespace: run.namespace,
|
|
2887
3019
|
error
|
|
2888
3020
|
});
|
|
2889
3021
|
this.notifyCancelled(run.id);
|
|
@@ -2911,7 +3043,8 @@ var WorkflowEngine = class {
|
|
|
2911
3043
|
this.emit({
|
|
2912
3044
|
type: "run.started",
|
|
2913
3045
|
runId: run.id,
|
|
2914
|
-
workflow: run.workflow
|
|
3046
|
+
workflow: run.workflow,
|
|
3047
|
+
namespace: run.namespace
|
|
2915
3048
|
});
|
|
2916
3049
|
}
|
|
2917
3050
|
if (registered.singleton && !await this.singletons.admit(run, registered.singleton)) {
|
|
@@ -2924,7 +3057,8 @@ var WorkflowEngine = class {
|
|
|
2924
3057
|
this.emit({
|
|
2925
3058
|
type: "run.suspended",
|
|
2926
3059
|
runId: run.id,
|
|
2927
|
-
workflow: run.workflow
|
|
3060
|
+
workflow: run.workflow,
|
|
3061
|
+
namespace: run.namespace
|
|
2928
3062
|
});
|
|
2929
3063
|
await this.store.releaseRunLock(run.id);
|
|
2930
3064
|
return {
|
|
@@ -2946,7 +3080,8 @@ var WorkflowEngine = class {
|
|
|
2946
3080
|
this.emit({
|
|
2947
3081
|
type: "run.suspended",
|
|
2948
3082
|
runId: run.id,
|
|
2949
|
-
workflow: run.workflow
|
|
3083
|
+
workflow: run.workflow,
|
|
3084
|
+
namespace: run.namespace
|
|
2950
3085
|
});
|
|
2951
3086
|
await this.store.releaseRunLock(run.id);
|
|
2952
3087
|
await remote.executor.dispatch(run, history, taskId);
|
|
@@ -2985,6 +3120,7 @@ var WorkflowEngine = class {
|
|
|
2985
3120
|
type: "run.failed",
|
|
2986
3121
|
runId: run.id,
|
|
2987
3122
|
workflow: run.workflow,
|
|
3123
|
+
namespace: run.namespace,
|
|
2988
3124
|
error
|
|
2989
3125
|
});
|
|
2990
3126
|
return {
|
|
@@ -3135,13 +3271,16 @@ var WorkflowEngine = class {
|
|
|
3135
3271
|
status: "pending",
|
|
3136
3272
|
input: cmd.input,
|
|
3137
3273
|
attempts: 1,
|
|
3138
|
-
|
|
3274
|
+
// `cmd.group` is the routing declaration the worker's replay emitted (cross-SDK, or a
|
|
3275
|
+
// group-served TS body) — treated as an isolation partition on top of the step's own
|
|
3276
|
+
// name, mirroring the StepDef formula below (identical expression everywhere).
|
|
3277
|
+
workerGroup: tenantGroup(sanitizeQueueToken(cmd.name), cmd.group),
|
|
3139
3278
|
enqueuedAt: at,
|
|
3140
3279
|
startedAt: at,
|
|
3141
3280
|
finishedAt: at,
|
|
3142
3281
|
// A `ctx.gather_calls([...])` fan-out stamps every dispatched `call` in the fan with the
|
|
3143
|
-
// same group, so the dashboard renders the
|
|
3144
|
-
// the gathered `recordStep`/`startChild` tags). Undefined for a lone sequential `ctx.
|
|
3282
|
+
// same group, so the dashboard renders the dispatched steps as one parallel fan (parity
|
|
3283
|
+
// with the gathered `recordStep`/`startChild` tags). Undefined for a lone sequential `ctx.step`.
|
|
3145
3284
|
parallelGroup: cmd.parallelGroup
|
|
3146
3285
|
}));
|
|
3147
3286
|
await this.pool.dispatch({
|
|
@@ -3149,7 +3288,7 @@ var WorkflowEngine = class {
|
|
|
3149
3288
|
seq: cmd.seq,
|
|
3150
3289
|
name: cmd.name,
|
|
3151
3290
|
stepId: id,
|
|
3152
|
-
group: cmd.group,
|
|
3291
|
+
group: tenantGroup(sanitizeQueueToken(cmd.name), cmd.group),
|
|
3153
3292
|
input: cmd.input,
|
|
3154
3293
|
traceparent: this.traceparent?.(),
|
|
3155
3294
|
context: this.context?.(),
|
|
@@ -3208,7 +3347,9 @@ var WorkflowEngine = class {
|
|
|
3208
3347
|
parallelGroup: cmd.parallelGroup
|
|
3209
3348
|
});
|
|
3210
3349
|
if (!await this.store.getRun(childId)) {
|
|
3211
|
-
queueMicrotask(() => void this.start(cmd.workflow, cmd.input, childId
|
|
3350
|
+
queueMicrotask(() => void this.start(cmd.workflow, cmd.input, childId, {
|
|
3351
|
+
namespace: run.namespace
|
|
3352
|
+
}).catch(() => void 0));
|
|
3212
3353
|
}
|
|
3213
3354
|
} else {
|
|
3214
3355
|
throw new Error(`remote workflow command '${cmd.kind}' is not supported yet`);
|
|
@@ -3240,6 +3381,7 @@ var WorkflowEngine = class {
|
|
|
3240
3381
|
type: "run.failed",
|
|
3241
3382
|
runId: run.id,
|
|
3242
3383
|
workflow: run.workflow,
|
|
3384
|
+
namespace: run.namespace,
|
|
3243
3385
|
error
|
|
3244
3386
|
});
|
|
3245
3387
|
return {
|
|
@@ -3260,7 +3402,8 @@ var WorkflowEngine = class {
|
|
|
3260
3402
|
this.emit({
|
|
3261
3403
|
type: "run.started",
|
|
3262
3404
|
runId: run.id,
|
|
3263
|
-
workflow: run.workflow
|
|
3405
|
+
workflow: run.workflow,
|
|
3406
|
+
namespace: run.namespace
|
|
3264
3407
|
});
|
|
3265
3408
|
}
|
|
3266
3409
|
if (run.status !== "cancelling" && registered?.singleton && !await this.singletons.admit(run, registered.singleton)) {
|
|
@@ -3273,7 +3416,8 @@ var WorkflowEngine = class {
|
|
|
3273
3416
|
this.emit({
|
|
3274
3417
|
type: "run.suspended",
|
|
3275
3418
|
runId: run.id,
|
|
3276
|
-
workflow: run.workflow
|
|
3419
|
+
workflow: run.workflow,
|
|
3420
|
+
namespace: run.namespace
|
|
3277
3421
|
});
|
|
3278
3422
|
await this.store.releaseRunLock(run.id);
|
|
3279
3423
|
return {
|
|
@@ -3285,7 +3429,7 @@ var WorkflowEngine = class {
|
|
|
3285
3429
|
const snapshot = await this.store.listCheckpoints(run.id);
|
|
3286
3430
|
const replay = /* @__PURE__ */ new Map();
|
|
3287
3431
|
for (const cp of snapshot) replay.set(cp.seq, cp);
|
|
3288
|
-
const ctx = createWorkflowCtx(this.ctxHostFor(replay), run.id, compensations, run.workflow);
|
|
3432
|
+
const ctx = createWorkflowCtx(this.ctxHostFor(replay, run.namespace), run.id, compensations, run.workflow);
|
|
3289
3433
|
try {
|
|
3290
3434
|
const output = await fn(ctx, run.input);
|
|
3291
3435
|
if (this.cancelRequested.has(run.id)) return this.compensateAndCancel(run, compensations);
|
|
@@ -3305,7 +3449,8 @@ var WorkflowEngine = class {
|
|
|
3305
3449
|
this.emit({
|
|
3306
3450
|
type: "run.completed",
|
|
3307
3451
|
runId: run.id,
|
|
3308
|
-
workflow: run.workflow
|
|
3452
|
+
workflow: run.workflow,
|
|
3453
|
+
namespace: run.namespace
|
|
3309
3454
|
});
|
|
3310
3455
|
void this.notifyParent(run.id, {
|
|
3311
3456
|
ok: true,
|
|
@@ -3383,7 +3528,7 @@ var WorkflowEngine = class {
|
|
|
3383
3528
|
/** The seam handed to {@link createWorkflowCtx}: the authoring API reaches durability + lifecycle
|
|
3384
3529
|
* (checkpointing, dispatch, child start) through this, so the ctx primitives live in their own
|
|
3385
3530
|
* module and the engine stays the orchestrator. */
|
|
3386
|
-
ctxHostFor(replay) {
|
|
3531
|
+
ctxHostFor(replay, parentNamespace) {
|
|
3387
3532
|
return {
|
|
3388
3533
|
store: this.store,
|
|
3389
3534
|
replay,
|
|
@@ -3393,10 +3538,16 @@ var WorkflowEngine = class {
|
|
|
3393
3538
|
completeStep: /* @__PURE__ */ __name((s) => this.completeStep(s), "completeStep"),
|
|
3394
3539
|
failStep: /* @__PURE__ */ __name((s) => this.failStep(s), "failStep"),
|
|
3395
3540
|
callRemote: /* @__PURE__ */ __name((runId, seq, step, input, queue, transport, admission) => this.callRemote(runId, seq, step, input, queue, transport, replay, admission), "callRemote"),
|
|
3396
|
-
// Defer so a fast child can't reentrantly resume a still-running parent.
|
|
3541
|
+
// Defer so a fast child can't reentrantly resume a still-running parent. Stamp the child with
|
|
3542
|
+
// the PARENT run's namespace (not this engine's) so a child belongs to the tenant/partition of
|
|
3543
|
+
// the run it was spawned from — even when an operator (namespace: undefined, drives every
|
|
3544
|
+
// namespace) is the one executing the parent. Otherwise an operator recovery-resuming a
|
|
3545
|
+
// `davi-local` pipeline would stamp its `processing` child `default` and it would leak off the
|
|
3546
|
+
// tenant's worker pool onto the shared/dev workers.
|
|
3397
3547
|
startChild: /* @__PURE__ */ __name((workflow, input, id, priority) => {
|
|
3398
3548
|
queueMicrotask(() => void this.start(workflow, input, id, {
|
|
3399
|
-
priority
|
|
3549
|
+
priority,
|
|
3550
|
+
namespace: parentNamespace
|
|
3400
3551
|
}).catch(() => void 0));
|
|
3401
3552
|
}, "startChild"),
|
|
3402
3553
|
// Shallow-merge into the run's searchAttributes (the ctx primitive makes this exactly-once).
|
|
@@ -3524,7 +3675,6 @@ var WorkflowEngine = class {
|
|
|
3524
3675
|
this.queueWaiters.get(queue)?.delete(runId);
|
|
3525
3676
|
this.stepQueue.set(id, queue);
|
|
3526
3677
|
}
|
|
3527
|
-
const validInput = step.input.parse(input);
|
|
3528
3678
|
const enqueuedAt = /* @__PURE__ */ new Date();
|
|
3529
3679
|
await this.store.saveCheckpoint({
|
|
3530
3680
|
runId,
|
|
@@ -3533,9 +3683,9 @@ var WorkflowEngine = class {
|
|
|
3533
3683
|
kind: "remote",
|
|
3534
3684
|
stepId: id,
|
|
3535
3685
|
status: "pending",
|
|
3536
|
-
input
|
|
3686
|
+
input,
|
|
3537
3687
|
attempts: attempt,
|
|
3538
|
-
workerGroup: step.
|
|
3688
|
+
workerGroup: tenantGroup(sanitizeQueueToken(step.name), step.partition),
|
|
3539
3689
|
enqueuedAt,
|
|
3540
3690
|
startedAt: enqueuedAt,
|
|
3541
3691
|
finishedAt: enqueuedAt
|
|
@@ -3545,8 +3695,8 @@ var WorkflowEngine = class {
|
|
|
3545
3695
|
seq,
|
|
3546
3696
|
name: step.name,
|
|
3547
3697
|
stepId: id,
|
|
3548
|
-
group: step.
|
|
3549
|
-
input
|
|
3698
|
+
group: tenantGroup(sanitizeQueueToken(step.name), step.partition),
|
|
3699
|
+
input,
|
|
3550
3700
|
traceparent: this.traceparent?.(),
|
|
3551
3701
|
context: this.context?.(),
|
|
3552
3702
|
priority: admission?.priority,
|
|
@@ -3611,7 +3761,9 @@ var WorkflowEngine = class {
|
|
|
3611
3761
|
const run = await this.store.getRun(decision.runId);
|
|
3612
3762
|
if (!run || run.awaitingDecisionTaskId !== decision.taskId) return;
|
|
3613
3763
|
if (run.status === "cancelled" || run.status === "completed" || run.status === "dead") return;
|
|
3614
|
-
if (run.namespace !== void 0 && run.namespace !== this.namespace)
|
|
3764
|
+
if (this.namespace !== void 0 && run.namespace !== void 0 && run.namespace !== this.namespace) {
|
|
3765
|
+
return;
|
|
3766
|
+
}
|
|
3615
3767
|
const nowMs = this.clock();
|
|
3616
3768
|
if (!await this.store.tryLockRun(decision.runId, this.instanceId, nowMs + this.leaseMs, nowMs)) {
|
|
3617
3769
|
return;
|
|
@@ -3677,7 +3829,7 @@ var WorkflowEngine = class {
|
|
|
3677
3829
|
/** In-memory await path for a remote step with a liveness `timeoutMs` (re-dispatch on timeout). */
|
|
3678
3830
|
async callRemoteInMemory(runId, seq, step, input, transport) {
|
|
3679
3831
|
if (this.pool.size === 0) throw new Error("remote steps require a Transport");
|
|
3680
|
-
const validInput = step.input.parse(input);
|
|
3832
|
+
const validInput = step.input ? step.input.parse(input) : input;
|
|
3681
3833
|
const id = stepId(runId, seq);
|
|
3682
3834
|
const enqueuedAt = /* @__PURE__ */ new Date();
|
|
3683
3835
|
this.emit({
|
|
@@ -3700,7 +3852,7 @@ var WorkflowEngine = class {
|
|
|
3700
3852
|
seq,
|
|
3701
3853
|
name: step.name,
|
|
3702
3854
|
stepId: id,
|
|
3703
|
-
group: step.
|
|
3855
|
+
group: tenantGroup(sanitizeQueueToken(step.name), step.partition),
|
|
3704
3856
|
input: validInput,
|
|
3705
3857
|
traceparent: this.traceparent?.(),
|
|
3706
3858
|
context: this.context?.(),
|
|
@@ -3708,7 +3860,7 @@ var WorkflowEngine = class {
|
|
|
3708
3860
|
}, transport);
|
|
3709
3861
|
try {
|
|
3710
3862
|
const resolution = step.timeoutMs ? await this.awaitWithHeartbeat(id, resultPromise, step.timeoutMs) : await resultPromise;
|
|
3711
|
-
const output = step.output.parse(resolution.output);
|
|
3863
|
+
const output = step.output ? step.output.parse(resolution.output) : resolution.output;
|
|
3712
3864
|
const startedAt = resolution.startedAt ? new Date(resolution.startedAt) : enqueuedAt;
|
|
3713
3865
|
await this.completeStep({
|
|
3714
3866
|
runId,
|
|
@@ -3719,7 +3871,7 @@ var WorkflowEngine = class {
|
|
|
3719
3871
|
output,
|
|
3720
3872
|
events: resolution.events,
|
|
3721
3873
|
attempts: attempt,
|
|
3722
|
-
workerGroup: step.
|
|
3874
|
+
workerGroup: tenantGroup(sanitizeQueueToken(step.name), step.partition),
|
|
3723
3875
|
enqueuedAt,
|
|
3724
3876
|
startedAt
|
|
3725
3877
|
});
|
|
@@ -3795,16 +3947,6 @@ var TERMINAL_RUN_STATUSES = [
|
|
|
3795
3947
|
"dead"
|
|
3796
3948
|
];
|
|
3797
3949
|
|
|
3798
|
-
// src/remote-step-factory.ts
|
|
3799
|
-
function remoteStep(config) {
|
|
3800
|
-
return {
|
|
3801
|
-
...config,
|
|
3802
|
-
group: config.group ?? config.name.split(".")[0] ?? config.name,
|
|
3803
|
-
__remote: true
|
|
3804
|
-
};
|
|
3805
|
-
}
|
|
3806
|
-
__name(remoteStep, "remoteStep");
|
|
3807
|
-
|
|
3808
3950
|
// src/codec-state-store.ts
|
|
3809
3951
|
var CodecStateStore = class {
|
|
3810
3952
|
static {
|
|
@@ -4528,6 +4670,8 @@ export {
|
|
|
4528
4670
|
ContinueAsNew,
|
|
4529
4671
|
DURABLE_OPTIONS,
|
|
4530
4672
|
DURABLE_OPTIONS_CANONICAL,
|
|
4673
|
+
DURABLE_STEP_CONFIG,
|
|
4674
|
+
DURABLE_STEP_NAME,
|
|
4531
4675
|
Entities,
|
|
4532
4676
|
EventAccumulators,
|
|
4533
4677
|
FatalError,
|
|
@@ -4565,12 +4709,15 @@ export {
|
|
|
4565
4709
|
normalizeAttributeRows,
|
|
4566
4710
|
parseDuration,
|
|
4567
4711
|
prevCronFireMs,
|
|
4568
|
-
remoteStep,
|
|
4569
4712
|
runSchedules,
|
|
4570
4713
|
runStepHandler,
|
|
4714
|
+
sanitizeQueueToken,
|
|
4571
4715
|
scheduledRunId,
|
|
4572
4716
|
sqlComparator,
|
|
4717
|
+
stepConfigOf,
|
|
4573
4718
|
stepId,
|
|
4719
|
+
stepNameOf,
|
|
4720
|
+
tenantGroup,
|
|
4574
4721
|
workflowName
|
|
4575
4722
|
};
|
|
4576
4723
|
//# sourceMappingURL=index.js.map
|