@dudousxd/nestjs-durable-core 0.44.1 → 0.46.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 +214 -58
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +373 -73
- package/dist/index.d.ts +373 -73
- package/dist/index.js +208 -57
- 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();
|
|
@@ -985,6 +1012,17 @@ var TransportPool = class {
|
|
|
985
1012
|
...groups
|
|
986
1013
|
];
|
|
987
1014
|
}
|
|
1015
|
+
/**
|
|
1016
|
+
* Propagate the engine's `namespace` to every transport that partitions by it — so the same
|
|
1017
|
+
* namespace that scopes the store also scopes each transport's queues/keys. A transport that
|
|
1018
|
+
* doesn't partition (no `useNamespace`) is skipped. Idempotent; a transport given an explicit
|
|
1019
|
+
* namespace at construction ignores this (see {@link Transport.useNamespace}).
|
|
1020
|
+
*/
|
|
1021
|
+
useNamespace(namespace) {
|
|
1022
|
+
for (const { transport } of this.transports) {
|
|
1023
|
+
transport.useNamespace?.(namespace);
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
988
1026
|
/** Pinned `preferId` first, then the rest (failover order). */
|
|
989
1027
|
ordered(preferId) {
|
|
990
1028
|
if (!preferId) return this.transports;
|
|
@@ -1007,6 +1045,26 @@ function unwrapCompletion(payload, label) {
|
|
|
1007
1045
|
}
|
|
1008
1046
|
__name(unwrapCompletion, "unwrapCompletion");
|
|
1009
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
|
+
|
|
1010
1068
|
// src/workflow-ref.ts
|
|
1011
1069
|
var WORKFLOW_NAME_KEY = Symbol.for("nestjs-durable:workflow-name");
|
|
1012
1070
|
function workflowName(ref) {
|
|
@@ -1026,6 +1084,23 @@ function normalizeChildOptions(options) {
|
|
|
1026
1084
|
} : options ?? {};
|
|
1027
1085
|
}
|
|
1028
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");
|
|
1029
1104
|
var Position = class Position2 {
|
|
1030
1105
|
static {
|
|
1031
1106
|
__name(this, "Position");
|
|
@@ -1051,7 +1126,7 @@ function createWorkflowCtx(host, runId, compensations, workflow = "") {
|
|
|
1051
1126
|
await store.saveCheckpoint(cp);
|
|
1052
1127
|
replay?.set(cp.seq, cp);
|
|
1053
1128
|
}, "writeCheckpoint");
|
|
1054
|
-
const
|
|
1129
|
+
const localStep = /* @__PURE__ */ __name(async (name, fn, options) => {
|
|
1055
1130
|
const current = pos.next();
|
|
1056
1131
|
const existing = await readCheckpoint(current);
|
|
1057
1132
|
if (existing && existing.name !== name) {
|
|
@@ -1125,7 +1200,7 @@ function createWorkflowCtx(host, runId, compensations, workflow = "") {
|
|
|
1125
1200
|
if (wait > 0) await new Promise((resolve) => setTimeout(resolve, wait));
|
|
1126
1201
|
}
|
|
1127
1202
|
}
|
|
1128
|
-
}, "
|
|
1203
|
+
}, "localStep");
|
|
1129
1204
|
const transaction = /* @__PURE__ */ __name(async (name, fn) => {
|
|
1130
1205
|
if (!store.transaction) {
|
|
1131
1206
|
throw new Error("ctx.transaction needs a store that supports transactions (the SQL adapters do). Use ctx.step for non-transactional work.");
|
|
@@ -1272,7 +1347,7 @@ function createWorkflowCtx(host, runId, compensations, workflow = "") {
|
|
|
1272
1347
|
throw new WorkflowSuspended(deadline);
|
|
1273
1348
|
}, "waitForEvent");
|
|
1274
1349
|
const task = /* @__PURE__ */ __name(async (name, dispatch, options) => {
|
|
1275
|
-
await
|
|
1350
|
+
await localStep(`task:dispatch:${name}`, dispatch, options);
|
|
1276
1351
|
return unwrapCompletion(await waitForSignal(`task:${runId}:${name}`), `task "${name}"`);
|
|
1277
1352
|
}, "task");
|
|
1278
1353
|
const child = /* @__PURE__ */ __name(async (workflow2, input, options) => {
|
|
@@ -1485,9 +1560,8 @@ function createWorkflowCtx(host, runId, compensations, workflow = "") {
|
|
|
1485
1560
|
wait
|
|
1486
1561
|
};
|
|
1487
1562
|
}, "webhook");
|
|
1488
|
-
const
|
|
1489
|
-
const
|
|
1490
|
-
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");
|
|
1491
1565
|
const upsertSearchAttributes = /* @__PURE__ */ __name(async (attrs) => {
|
|
1492
1566
|
const current = pos.next();
|
|
1493
1567
|
const existing = await readCheckpoint(current);
|
|
@@ -1506,9 +1580,26 @@ function createWorkflowCtx(host, runId, compensations, workflow = "") {
|
|
|
1506
1580
|
output: attrs
|
|
1507
1581
|
}));
|
|
1508
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");
|
|
1509
1599
|
return {
|
|
1510
1600
|
runId,
|
|
1511
1601
|
step,
|
|
1602
|
+
localStep,
|
|
1512
1603
|
upsertSearchAttributes,
|
|
1513
1604
|
transaction,
|
|
1514
1605
|
callEntity,
|
|
@@ -1528,12 +1619,7 @@ function createWorkflowCtx(host, runId, compensations, workflow = "") {
|
|
|
1528
1619
|
onUpdate,
|
|
1529
1620
|
patched,
|
|
1530
1621
|
now,
|
|
1531
|
-
|
|
1532
|
-
uuid,
|
|
1533
|
-
call: /* @__PURE__ */ __name((remote, input, opts) => host.callRemote(runId, pos.next(), remote, input, opts?.queue, opts?.transport, {
|
|
1534
|
-
priority: opts?.priority,
|
|
1535
|
-
fairnessKey: opts?.fairnessKey
|
|
1536
|
-
}), "call")
|
|
1622
|
+
sideEffect
|
|
1537
1623
|
};
|
|
1538
1624
|
}
|
|
1539
1625
|
__name(createWorkflowCtx, "createWorkflowCtx");
|
|
@@ -1651,7 +1737,10 @@ var WorkflowEngine = class {
|
|
|
1651
1737
|
this.admission = deps.admission ?? new InMemoryAdmissionBackend(this.clock);
|
|
1652
1738
|
this.admission.onFreed?.((queue) => this.wakeQueueWaiters(queue));
|
|
1653
1739
|
this.instanceId = deps.instanceId ?? globalThis.crypto.randomUUID();
|
|
1654
|
-
this.namespace = deps.namespace
|
|
1740
|
+
this.namespace = deps.namespace;
|
|
1741
|
+
if (this.namespace !== void 0) {
|
|
1742
|
+
this.pool.useNamespace(this.namespace);
|
|
1743
|
+
}
|
|
1655
1744
|
this.leaseMs = deps.leaseMs ?? 3e4;
|
|
1656
1745
|
this.maxRecoveryAttempts = deps.maxRecoveryAttempts;
|
|
1657
1746
|
this.remoteAdvanceSilenceMs = deps.remoteAdvanceSilenceMs;
|
|
@@ -1716,6 +1805,13 @@ var WorkflowEngine = class {
|
|
|
1716
1805
|
await this.completeRemoteDecision(decision);
|
|
1717
1806
|
}
|
|
1718
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
|
+
});
|
|
1719
1815
|
this.controlPlane?.onControl((msg) => {
|
|
1720
1816
|
if (msg.from === this.instanceId) return;
|
|
1721
1817
|
if (msg.kind === "event") {
|
|
@@ -1880,7 +1976,7 @@ var WorkflowEngine = class {
|
|
|
1880
1976
|
});
|
|
1881
1977
|
}
|
|
1882
1978
|
/**
|
|
1883
|
-
* Register a flow-control queue referenced by `ctx.
|
|
1979
|
+
* Register a flow-control queue referenced by `ctx.step(step, input, { queue })`. Caps concurrent
|
|
1884
1980
|
* in-flight steps and/or the admission rate; blocked calls re-suspend and retry, so the limit is
|
|
1885
1981
|
* durable. Per engine instance (see {@link QueueConfig}). Registering the same name replaces it.
|
|
1886
1982
|
*/
|
|
@@ -1983,6 +2079,23 @@ var WorkflowEngine = class {
|
|
|
1983
2079
|
const ancestor = await this.findRemoteAncestor(runId);
|
|
1984
2080
|
if (ancestor) registered = this.synthesizeRemoteChild(name, ancestor.version, ancestor);
|
|
1985
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 (:893) 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
|
+
}
|
|
1986
2099
|
if (!registered) throw new Error(`workflow ${name} is not registered`);
|
|
1987
2100
|
await registered.validateInput?.(input);
|
|
1988
2101
|
const prior = await this.store.getRun(runId);
|
|
@@ -2007,7 +2120,7 @@ var WorkflowEngine = class {
|
|
|
2007
2120
|
workflow: name,
|
|
2008
2121
|
workflowVersion: registered.version,
|
|
2009
2122
|
status: "pending",
|
|
2010
|
-
namespace: this.namespace,
|
|
2123
|
+
namespace: opts?.namespace ?? this.namespace,
|
|
2011
2124
|
input,
|
|
2012
2125
|
tags,
|
|
2013
2126
|
searchAttributes: opts?.searchAttributes,
|
|
@@ -2044,11 +2157,11 @@ var WorkflowEngine = class {
|
|
|
2044
2157
|
error: run.error
|
|
2045
2158
|
};
|
|
2046
2159
|
}
|
|
2047
|
-
if (run.namespace !== void 0 && run.namespace !== this.namespace) {
|
|
2160
|
+
if (this.namespace !== void 0 && run.namespace !== void 0 && run.namespace !== this.namespace) {
|
|
2048
2161
|
await this.store.releaseRunLock(runId);
|
|
2049
2162
|
throw new NamespaceMismatch();
|
|
2050
2163
|
}
|
|
2051
|
-
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);
|
|
2052
2165
|
if (!registered) {
|
|
2053
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)`);
|
|
2054
2167
|
}
|
|
@@ -2094,6 +2207,34 @@ var WorkflowEngine = class {
|
|
|
2094
2207
|
};
|
|
2095
2208
|
}
|
|
2096
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
|
+
/**
|
|
2097
2238
|
* Walk the parent chain of `childRunId` to the nearest ancestor registered as REMOTE, returning its
|
|
2098
2239
|
* registration (or `undefined` if there is none). The parent of an awaited child is found via its
|
|
2099
2240
|
* live `child:<childId>` signal waiter — present for as long as the parent is suspended awaiting the
|
|
@@ -2190,6 +2331,7 @@ var WorkflowEngine = class {
|
|
|
2190
2331
|
type: "run.failed",
|
|
2191
2332
|
runId: run.id,
|
|
2192
2333
|
workflow: run.workflow,
|
|
2334
|
+
namespace: run.namespace,
|
|
2193
2335
|
error
|
|
2194
2336
|
});
|
|
2195
2337
|
}
|
|
@@ -2247,6 +2389,7 @@ var WorkflowEngine = class {
|
|
|
2247
2389
|
type: "run.failed",
|
|
2248
2390
|
runId: run.id,
|
|
2249
2391
|
workflow: run.workflow,
|
|
2392
|
+
namespace: run.namespace,
|
|
2250
2393
|
error
|
|
2251
2394
|
});
|
|
2252
2395
|
this.notifyDead({
|
|
@@ -2321,7 +2464,8 @@ var WorkflowEngine = class {
|
|
|
2321
2464
|
if (!run) return null;
|
|
2322
2465
|
const id = newRunId ?? `${runId}~retry~${globalThis.crypto.randomUUID().slice(0, 8)}`;
|
|
2323
2466
|
await this.start(run.workflow, input, id, {
|
|
2324
|
-
tags: run.tags
|
|
2467
|
+
tags: run.tags,
|
|
2468
|
+
namespace: run.namespace
|
|
2325
2469
|
});
|
|
2326
2470
|
return {
|
|
2327
2471
|
runId: id
|
|
@@ -2555,6 +2699,7 @@ var WorkflowEngine = class {
|
|
|
2555
2699
|
type: "run.failed",
|
|
2556
2700
|
runId,
|
|
2557
2701
|
workflow: run.workflow,
|
|
2702
|
+
namespace: run.namespace,
|
|
2558
2703
|
error
|
|
2559
2704
|
});
|
|
2560
2705
|
await this.cancelChildren(runId, opts);
|
|
@@ -2831,7 +2976,7 @@ var WorkflowEngine = class {
|
|
|
2831
2976
|
});
|
|
2832
2977
|
}
|
|
2833
2978
|
async execute(run, fn) {
|
|
2834
|
-
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);
|
|
2835
2980
|
const lockNow = this.clock();
|
|
2836
2981
|
if (run.lockedBy !== this.instanceId || (run.lockedUntil ?? 0) <= lockNow) {
|
|
2837
2982
|
if (!await this.store.tryLockRun(run.id, this.instanceId, lockNow + this.leaseMs, lockNow)) {
|
|
@@ -2881,6 +3026,7 @@ var WorkflowEngine = class {
|
|
|
2881
3026
|
type: "run.completed",
|
|
2882
3027
|
runId: run.id,
|
|
2883
3028
|
workflow: run.workflow,
|
|
3029
|
+
namespace: run.namespace,
|
|
2884
3030
|
output: outcome.output
|
|
2885
3031
|
});
|
|
2886
3032
|
void this.notifyParent(run.id, {
|
|
@@ -2903,6 +3049,7 @@ var WorkflowEngine = class {
|
|
|
2903
3049
|
type: "run.failed",
|
|
2904
3050
|
runId: run.id,
|
|
2905
3051
|
workflow: run.workflow,
|
|
3052
|
+
namespace: run.namespace,
|
|
2906
3053
|
error: outcome.error
|
|
2907
3054
|
});
|
|
2908
3055
|
void this.notifyParent(run.id, {
|
|
@@ -2923,7 +3070,8 @@ var WorkflowEngine = class {
|
|
|
2923
3070
|
this.emit({
|
|
2924
3071
|
type: "run.suspended",
|
|
2925
3072
|
runId: run.id,
|
|
2926
|
-
workflow: run.workflow
|
|
3073
|
+
workflow: run.workflow,
|
|
3074
|
+
namespace: run.namespace
|
|
2927
3075
|
});
|
|
2928
3076
|
return {
|
|
2929
3077
|
runId: run.id,
|
|
@@ -2944,6 +3092,7 @@ var WorkflowEngine = class {
|
|
|
2944
3092
|
type: "run.failed",
|
|
2945
3093
|
runId: run.id,
|
|
2946
3094
|
workflow: run.workflow,
|
|
3095
|
+
namespace: run.namespace,
|
|
2947
3096
|
error
|
|
2948
3097
|
});
|
|
2949
3098
|
this.notifyCancelled(run.id);
|
|
@@ -2971,7 +3120,8 @@ var WorkflowEngine = class {
|
|
|
2971
3120
|
this.emit({
|
|
2972
3121
|
type: "run.started",
|
|
2973
3122
|
runId: run.id,
|
|
2974
|
-
workflow: run.workflow
|
|
3123
|
+
workflow: run.workflow,
|
|
3124
|
+
namespace: run.namespace
|
|
2975
3125
|
});
|
|
2976
3126
|
}
|
|
2977
3127
|
if (registered.singleton && !await this.singletons.admit(run, registered.singleton)) {
|
|
@@ -2984,7 +3134,8 @@ var WorkflowEngine = class {
|
|
|
2984
3134
|
this.emit({
|
|
2985
3135
|
type: "run.suspended",
|
|
2986
3136
|
runId: run.id,
|
|
2987
|
-
workflow: run.workflow
|
|
3137
|
+
workflow: run.workflow,
|
|
3138
|
+
namespace: run.namespace
|
|
2988
3139
|
});
|
|
2989
3140
|
await this.store.releaseRunLock(run.id);
|
|
2990
3141
|
return {
|
|
@@ -3006,7 +3157,8 @@ var WorkflowEngine = class {
|
|
|
3006
3157
|
this.emit({
|
|
3007
3158
|
type: "run.suspended",
|
|
3008
3159
|
runId: run.id,
|
|
3009
|
-
workflow: run.workflow
|
|
3160
|
+
workflow: run.workflow,
|
|
3161
|
+
namespace: run.namespace
|
|
3010
3162
|
});
|
|
3011
3163
|
await this.store.releaseRunLock(run.id);
|
|
3012
3164
|
await remote.executor.dispatch(run, history, taskId);
|
|
@@ -3045,6 +3197,7 @@ var WorkflowEngine = class {
|
|
|
3045
3197
|
type: "run.failed",
|
|
3046
3198
|
runId: run.id,
|
|
3047
3199
|
workflow: run.workflow,
|
|
3200
|
+
namespace: run.namespace,
|
|
3048
3201
|
error
|
|
3049
3202
|
});
|
|
3050
3203
|
return {
|
|
@@ -3195,13 +3348,16 @@ var WorkflowEngine = class {
|
|
|
3195
3348
|
status: "pending",
|
|
3196
3349
|
input: cmd.input,
|
|
3197
3350
|
attempts: 1,
|
|
3198
|
-
|
|
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),
|
|
3199
3355
|
enqueuedAt: at,
|
|
3200
3356
|
startedAt: at,
|
|
3201
3357
|
finishedAt: at,
|
|
3202
3358
|
// A `ctx.gather_calls([...])` fan-out stamps every dispatched `call` in the fan with the
|
|
3203
|
-
// same group, so the dashboard renders the
|
|
3204
|
-
// 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`.
|
|
3205
3361
|
parallelGroup: cmd.parallelGroup
|
|
3206
3362
|
}));
|
|
3207
3363
|
await this.pool.dispatch({
|
|
@@ -3209,7 +3365,7 @@ var WorkflowEngine = class {
|
|
|
3209
3365
|
seq: cmd.seq,
|
|
3210
3366
|
name: cmd.name,
|
|
3211
3367
|
stepId: id,
|
|
3212
|
-
group: cmd.group,
|
|
3368
|
+
group: tenantGroup(sanitizeQueueToken(cmd.name), cmd.group),
|
|
3213
3369
|
input: cmd.input,
|
|
3214
3370
|
traceparent: this.traceparent?.(),
|
|
3215
3371
|
context: this.context?.(),
|
|
@@ -3300,6 +3456,7 @@ var WorkflowEngine = class {
|
|
|
3300
3456
|
type: "run.failed",
|
|
3301
3457
|
runId: run.id,
|
|
3302
3458
|
workflow: run.workflow,
|
|
3459
|
+
namespace: run.namespace,
|
|
3303
3460
|
error
|
|
3304
3461
|
});
|
|
3305
3462
|
return {
|
|
@@ -3320,7 +3477,8 @@ var WorkflowEngine = class {
|
|
|
3320
3477
|
this.emit({
|
|
3321
3478
|
type: "run.started",
|
|
3322
3479
|
runId: run.id,
|
|
3323
|
-
workflow: run.workflow
|
|
3480
|
+
workflow: run.workflow,
|
|
3481
|
+
namespace: run.namespace
|
|
3324
3482
|
});
|
|
3325
3483
|
}
|
|
3326
3484
|
if (run.status !== "cancelling" && registered?.singleton && !await this.singletons.admit(run, registered.singleton)) {
|
|
@@ -3333,7 +3491,8 @@ var WorkflowEngine = class {
|
|
|
3333
3491
|
this.emit({
|
|
3334
3492
|
type: "run.suspended",
|
|
3335
3493
|
runId: run.id,
|
|
3336
|
-
workflow: run.workflow
|
|
3494
|
+
workflow: run.workflow,
|
|
3495
|
+
namespace: run.namespace
|
|
3337
3496
|
});
|
|
3338
3497
|
await this.store.releaseRunLock(run.id);
|
|
3339
3498
|
return {
|
|
@@ -3365,7 +3524,8 @@ var WorkflowEngine = class {
|
|
|
3365
3524
|
this.emit({
|
|
3366
3525
|
type: "run.completed",
|
|
3367
3526
|
runId: run.id,
|
|
3368
|
-
workflow: run.workflow
|
|
3527
|
+
workflow: run.workflow,
|
|
3528
|
+
namespace: run.namespace
|
|
3369
3529
|
});
|
|
3370
3530
|
void this.notifyParent(run.id, {
|
|
3371
3531
|
ok: true,
|
|
@@ -3584,7 +3744,6 @@ var WorkflowEngine = class {
|
|
|
3584
3744
|
this.queueWaiters.get(queue)?.delete(runId);
|
|
3585
3745
|
this.stepQueue.set(id, queue);
|
|
3586
3746
|
}
|
|
3587
|
-
const validInput = step.input.parse(input);
|
|
3588
3747
|
const enqueuedAt = /* @__PURE__ */ new Date();
|
|
3589
3748
|
await this.store.saveCheckpoint({
|
|
3590
3749
|
runId,
|
|
@@ -3593,9 +3752,9 @@ var WorkflowEngine = class {
|
|
|
3593
3752
|
kind: "remote",
|
|
3594
3753
|
stepId: id,
|
|
3595
3754
|
status: "pending",
|
|
3596
|
-
input
|
|
3755
|
+
input,
|
|
3597
3756
|
attempts: attempt,
|
|
3598
|
-
workerGroup: step.
|
|
3757
|
+
workerGroup: tenantGroup(sanitizeQueueToken(step.name), step.partition),
|
|
3599
3758
|
enqueuedAt,
|
|
3600
3759
|
startedAt: enqueuedAt,
|
|
3601
3760
|
finishedAt: enqueuedAt
|
|
@@ -3605,8 +3764,8 @@ var WorkflowEngine = class {
|
|
|
3605
3764
|
seq,
|
|
3606
3765
|
name: step.name,
|
|
3607
3766
|
stepId: id,
|
|
3608
|
-
group: step.
|
|
3609
|
-
input
|
|
3767
|
+
group: tenantGroup(sanitizeQueueToken(step.name), step.partition),
|
|
3768
|
+
input,
|
|
3610
3769
|
traceparent: this.traceparent?.(),
|
|
3611
3770
|
context: this.context?.(),
|
|
3612
3771
|
priority: admission?.priority,
|
|
@@ -3671,7 +3830,9 @@ var WorkflowEngine = class {
|
|
|
3671
3830
|
const run = await this.store.getRun(decision.runId);
|
|
3672
3831
|
if (!run || run.awaitingDecisionTaskId !== decision.taskId) return;
|
|
3673
3832
|
if (run.status === "cancelled" || run.status === "completed" || run.status === "dead") return;
|
|
3674
|
-
if (run.namespace !== void 0 && run.namespace !== this.namespace)
|
|
3833
|
+
if (this.namespace !== void 0 && run.namespace !== void 0 && run.namespace !== this.namespace) {
|
|
3834
|
+
return;
|
|
3835
|
+
}
|
|
3675
3836
|
const nowMs = this.clock();
|
|
3676
3837
|
if (!await this.store.tryLockRun(decision.runId, this.instanceId, nowMs + this.leaseMs, nowMs)) {
|
|
3677
3838
|
return;
|
|
@@ -3737,7 +3898,7 @@ var WorkflowEngine = class {
|
|
|
3737
3898
|
/** In-memory await path for a remote step with a liveness `timeoutMs` (re-dispatch on timeout). */
|
|
3738
3899
|
async callRemoteInMemory(runId, seq, step, input, transport) {
|
|
3739
3900
|
if (this.pool.size === 0) throw new Error("remote steps require a Transport");
|
|
3740
|
-
const validInput = step.input.parse(input);
|
|
3901
|
+
const validInput = step.input ? step.input.parse(input) : input;
|
|
3741
3902
|
const id = stepId(runId, seq);
|
|
3742
3903
|
const enqueuedAt = /* @__PURE__ */ new Date();
|
|
3743
3904
|
this.emit({
|
|
@@ -3760,7 +3921,7 @@ var WorkflowEngine = class {
|
|
|
3760
3921
|
seq,
|
|
3761
3922
|
name: step.name,
|
|
3762
3923
|
stepId: id,
|
|
3763
|
-
group: step.
|
|
3924
|
+
group: tenantGroup(sanitizeQueueToken(step.name), step.partition),
|
|
3764
3925
|
input: validInput,
|
|
3765
3926
|
traceparent: this.traceparent?.(),
|
|
3766
3927
|
context: this.context?.(),
|
|
@@ -3768,7 +3929,7 @@ var WorkflowEngine = class {
|
|
|
3768
3929
|
}, transport);
|
|
3769
3930
|
try {
|
|
3770
3931
|
const resolution = step.timeoutMs ? await this.awaitWithHeartbeat(id, resultPromise, step.timeoutMs) : await resultPromise;
|
|
3771
|
-
const output = step.output.parse(resolution.output);
|
|
3932
|
+
const output = step.output ? step.output.parse(resolution.output) : resolution.output;
|
|
3772
3933
|
const startedAt = resolution.startedAt ? new Date(resolution.startedAt) : enqueuedAt;
|
|
3773
3934
|
await this.completeStep({
|
|
3774
3935
|
runId,
|
|
@@ -3779,7 +3940,7 @@ var WorkflowEngine = class {
|
|
|
3779
3940
|
output,
|
|
3780
3941
|
events: resolution.events,
|
|
3781
3942
|
attempts: attempt,
|
|
3782
|
-
workerGroup: step.
|
|
3943
|
+
workerGroup: tenantGroup(sanitizeQueueToken(step.name), step.partition),
|
|
3783
3944
|
enqueuedAt,
|
|
3784
3945
|
startedAt
|
|
3785
3946
|
});
|
|
@@ -3855,16 +4016,6 @@ var TERMINAL_RUN_STATUSES = [
|
|
|
3855
4016
|
"dead"
|
|
3856
4017
|
];
|
|
3857
4018
|
|
|
3858
|
-
// src/remote-step-factory.ts
|
|
3859
|
-
function remoteStep(config) {
|
|
3860
|
-
return {
|
|
3861
|
-
...config,
|
|
3862
|
-
group: config.group ?? config.name.split(".")[0] ?? config.name,
|
|
3863
|
-
__remote: true
|
|
3864
|
-
};
|
|
3865
|
-
}
|
|
3866
|
-
__name(remoteStep, "remoteStep");
|
|
3867
|
-
|
|
3868
4019
|
// src/codec-state-store.ts
|
|
3869
4020
|
var CodecStateStore = class {
|
|
3870
4021
|
static {
|
|
@@ -4589,6 +4740,8 @@ var InMemoryTransport = class {
|
|
|
4589
4740
|
ContinueAsNew,
|
|
4590
4741
|
DURABLE_OPTIONS,
|
|
4591
4742
|
DURABLE_OPTIONS_CANONICAL,
|
|
4743
|
+
DURABLE_STEP_CONFIG,
|
|
4744
|
+
DURABLE_STEP_NAME,
|
|
4592
4745
|
Entities,
|
|
4593
4746
|
EventAccumulators,
|
|
4594
4747
|
FatalError,
|
|
@@ -4626,12 +4779,15 @@ var InMemoryTransport = class {
|
|
|
4626
4779
|
normalizeAttributeRows,
|
|
4627
4780
|
parseDuration,
|
|
4628
4781
|
prevCronFireMs,
|
|
4629
|
-
remoteStep,
|
|
4630
4782
|
runSchedules,
|
|
4631
4783
|
runStepHandler,
|
|
4784
|
+
sanitizeQueueToken,
|
|
4632
4785
|
scheduledRunId,
|
|
4633
4786
|
sqlComparator,
|
|
4787
|
+
stepConfigOf,
|
|
4634
4788
|
stepId,
|
|
4789
|
+
stepNameOf,
|
|
4790
|
+
tenantGroup,
|
|
4635
4791
|
workflowName
|
|
4636
4792
|
});
|
|
4637
4793
|
//# sourceMappingURL=index.cjs.map
|