@dudousxd/nestjs-durable-core 0.45.0 → 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 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.step(`op:${i}`, async () => {
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.step(`reply:${i}`, async () => {
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
- group;
782
- constructor(transport, group, _opts = {}) {
796
+ name;
797
+ partition;
798
+ constructor(transport, name, partition, _opts = {}) {
783
799
  this.transport = transport;
784
- this.group = group;
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.group,
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 step = /* @__PURE__ */ __name(async (name, fn, options) => {
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
- }, "step");
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 step(`task:dispatch:${name}`, dispatch, options);
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 now = /* @__PURE__ */ __name(() => step("now", async () => host.clock()), "now");
1500
- const random = /* @__PURE__ */ __name(() => step("random", async () => Math.random()), "random");
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
- random,
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 ?? "default";
1666
- this.pool.useNamespace(this.namespace);
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.call(step, input, { queue })`. Caps concurrent
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 (: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
+ }
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
- workerGroup: cmd.group,
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 remote steps as one parallel fan (parity with
3216
- // the gathered `recordStep`/`startChild` tags). Undefined for a lone sequential `ctx.call`.
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?.(),
@@ -3312,6 +3456,7 @@ var WorkflowEngine = class {
3312
3456
  type: "run.failed",
3313
3457
  runId: run.id,
3314
3458
  workflow: run.workflow,
3459
+ namespace: run.namespace,
3315
3460
  error
3316
3461
  });
3317
3462
  return {
@@ -3332,7 +3477,8 @@ var WorkflowEngine = class {
3332
3477
  this.emit({
3333
3478
  type: "run.started",
3334
3479
  runId: run.id,
3335
- workflow: run.workflow
3480
+ workflow: run.workflow,
3481
+ namespace: run.namespace
3336
3482
  });
3337
3483
  }
3338
3484
  if (run.status !== "cancelling" && registered?.singleton && !await this.singletons.admit(run, registered.singleton)) {
@@ -3345,7 +3491,8 @@ var WorkflowEngine = class {
3345
3491
  this.emit({
3346
3492
  type: "run.suspended",
3347
3493
  runId: run.id,
3348
- workflow: run.workflow
3494
+ workflow: run.workflow,
3495
+ namespace: run.namespace
3349
3496
  });
3350
3497
  await this.store.releaseRunLock(run.id);
3351
3498
  return {
@@ -3377,7 +3524,8 @@ var WorkflowEngine = class {
3377
3524
  this.emit({
3378
3525
  type: "run.completed",
3379
3526
  runId: run.id,
3380
- workflow: run.workflow
3527
+ workflow: run.workflow,
3528
+ namespace: run.namespace
3381
3529
  });
3382
3530
  void this.notifyParent(run.id, {
3383
3531
  ok: true,
@@ -3596,7 +3744,6 @@ var WorkflowEngine = class {
3596
3744
  this.queueWaiters.get(queue)?.delete(runId);
3597
3745
  this.stepQueue.set(id, queue);
3598
3746
  }
3599
- const validInput = step.input.parse(input);
3600
3747
  const enqueuedAt = /* @__PURE__ */ new Date();
3601
3748
  await this.store.saveCheckpoint({
3602
3749
  runId,
@@ -3605,9 +3752,9 @@ var WorkflowEngine = class {
3605
3752
  kind: "remote",
3606
3753
  stepId: id,
3607
3754
  status: "pending",
3608
- input: validInput,
3755
+ input,
3609
3756
  attempts: attempt,
3610
- workerGroup: step.group,
3757
+ workerGroup: tenantGroup(sanitizeQueueToken(step.name), step.partition),
3611
3758
  enqueuedAt,
3612
3759
  startedAt: enqueuedAt,
3613
3760
  finishedAt: enqueuedAt
@@ -3617,8 +3764,8 @@ var WorkflowEngine = class {
3617
3764
  seq,
3618
3765
  name: step.name,
3619
3766
  stepId: id,
3620
- group: step.group,
3621
- input: validInput,
3767
+ group: tenantGroup(sanitizeQueueToken(step.name), step.partition),
3768
+ input,
3622
3769
  traceparent: this.traceparent?.(),
3623
3770
  context: this.context?.(),
3624
3771
  priority: admission?.priority,
@@ -3683,7 +3830,9 @@ var WorkflowEngine = class {
3683
3830
  const run = await this.store.getRun(decision.runId);
3684
3831
  if (!run || run.awaitingDecisionTaskId !== decision.taskId) return;
3685
3832
  if (run.status === "cancelled" || run.status === "completed" || run.status === "dead") return;
3686
- if (run.namespace !== void 0 && run.namespace !== this.namespace) return;
3833
+ if (this.namespace !== void 0 && run.namespace !== void 0 && run.namespace !== this.namespace) {
3834
+ return;
3835
+ }
3687
3836
  const nowMs = this.clock();
3688
3837
  if (!await this.store.tryLockRun(decision.runId, this.instanceId, nowMs + this.leaseMs, nowMs)) {
3689
3838
  return;
@@ -3749,7 +3898,7 @@ var WorkflowEngine = class {
3749
3898
  /** In-memory await path for a remote step with a liveness `timeoutMs` (re-dispatch on timeout). */
3750
3899
  async callRemoteInMemory(runId, seq, step, input, transport) {
3751
3900
  if (this.pool.size === 0) throw new Error("remote steps require a Transport");
3752
- const validInput = step.input.parse(input);
3901
+ const validInput = step.input ? step.input.parse(input) : input;
3753
3902
  const id = stepId(runId, seq);
3754
3903
  const enqueuedAt = /* @__PURE__ */ new Date();
3755
3904
  this.emit({
@@ -3772,7 +3921,7 @@ var WorkflowEngine = class {
3772
3921
  seq,
3773
3922
  name: step.name,
3774
3923
  stepId: id,
3775
- group: step.group,
3924
+ group: tenantGroup(sanitizeQueueToken(step.name), step.partition),
3776
3925
  input: validInput,
3777
3926
  traceparent: this.traceparent?.(),
3778
3927
  context: this.context?.(),
@@ -3780,7 +3929,7 @@ var WorkflowEngine = class {
3780
3929
  }, transport);
3781
3930
  try {
3782
3931
  const resolution = step.timeoutMs ? await this.awaitWithHeartbeat(id, resultPromise, step.timeoutMs) : await resultPromise;
3783
- const output = step.output.parse(resolution.output);
3932
+ const output = step.output ? step.output.parse(resolution.output) : resolution.output;
3784
3933
  const startedAt = resolution.startedAt ? new Date(resolution.startedAt) : enqueuedAt;
3785
3934
  await this.completeStep({
3786
3935
  runId,
@@ -3791,7 +3940,7 @@ var WorkflowEngine = class {
3791
3940
  output,
3792
3941
  events: resolution.events,
3793
3942
  attempts: attempt,
3794
- workerGroup: step.group,
3943
+ workerGroup: tenantGroup(sanitizeQueueToken(step.name), step.partition),
3795
3944
  enqueuedAt,
3796
3945
  startedAt
3797
3946
  });
@@ -3867,16 +4016,6 @@ var TERMINAL_RUN_STATUSES = [
3867
4016
  "dead"
3868
4017
  ];
3869
4018
 
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
4019
  // src/codec-state-store.ts
3881
4020
  var CodecStateStore = class {
3882
4021
  static {
@@ -4601,6 +4740,8 @@ var InMemoryTransport = class {
4601
4740
  ContinueAsNew,
4602
4741
  DURABLE_OPTIONS,
4603
4742
  DURABLE_OPTIONS_CANONICAL,
4743
+ DURABLE_STEP_CONFIG,
4744
+ DURABLE_STEP_NAME,
4604
4745
  Entities,
4605
4746
  EventAccumulators,
4606
4747
  FatalError,
@@ -4638,12 +4779,15 @@ var InMemoryTransport = class {
4638
4779
  normalizeAttributeRows,
4639
4780
  parseDuration,
4640
4781
  prevCronFireMs,
4641
- remoteStep,
4642
4782
  runSchedules,
4643
4783
  runStepHandler,
4784
+ sanitizeQueueToken,
4644
4785
  scheduledRunId,
4645
4786
  sqlComparator,
4787
+ stepConfigOf,
4646
4788
  stepId,
4789
+ stepNameOf,
4790
+ tenantGroup,
4647
4791
  workflowName
4648
4792
  });
4649
4793
  //# sourceMappingURL=index.cjs.map