@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.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.step(`op:${i}`, async () => {
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.step(`reply:${i}`, async () => {
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
- group;
710
- constructor(transport, group, _opts = {}) {
719
+ name;
720
+ partition;
721
+ constructor(transport, name, partition, _opts = {}) {
711
722
  this.transport = transport;
712
- this.group = group;
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.group,
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();
@@ -913,6 +935,17 @@ var TransportPool = class {
913
935
  ...groups
914
936
  ];
915
937
  }
938
+ /**
939
+ * Propagate the engine's `namespace` to every transport that partitions by it — so the same
940
+ * namespace that scopes the store also scopes each transport's queues/keys. A transport that
941
+ * doesn't partition (no `useNamespace`) is skipped. Idempotent; a transport given an explicit
942
+ * namespace at construction ignores this (see {@link Transport.useNamespace}).
943
+ */
944
+ useNamespace(namespace) {
945
+ for (const { transport } of this.transports) {
946
+ transport.useNamespace?.(namespace);
947
+ }
948
+ }
916
949
  /** Pinned `preferId` first, then the rest (failover order). */
917
950
  ordered(preferId) {
918
951
  if (!preferId) return this.transports;
@@ -935,6 +968,26 @@ function unwrapCompletion(payload, label) {
935
968
  }
936
969
  __name(unwrapCompletion, "unwrapCompletion");
937
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
+
938
991
  // src/workflow-ref.ts
939
992
  var WORKFLOW_NAME_KEY = Symbol.for("nestjs-durable:workflow-name");
940
993
  function workflowName(ref) {
@@ -954,6 +1007,23 @@ function normalizeChildOptions(options) {
954
1007
  } : options ?? {};
955
1008
  }
956
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");
957
1027
  var Position = class Position2 {
958
1028
  static {
959
1029
  __name(this, "Position");
@@ -979,7 +1049,7 @@ function createWorkflowCtx(host, runId, compensations, workflow = "") {
979
1049
  await store.saveCheckpoint(cp);
980
1050
  replay?.set(cp.seq, cp);
981
1051
  }, "writeCheckpoint");
982
- const step = /* @__PURE__ */ __name(async (name, fn, options) => {
1052
+ const localStep = /* @__PURE__ */ __name(async (name, fn, options) => {
983
1053
  const current = pos.next();
984
1054
  const existing = await readCheckpoint(current);
985
1055
  if (existing && existing.name !== name) {
@@ -1053,7 +1123,7 @@ function createWorkflowCtx(host, runId, compensations, workflow = "") {
1053
1123
  if (wait > 0) await new Promise((resolve) => setTimeout(resolve, wait));
1054
1124
  }
1055
1125
  }
1056
- }, "step");
1126
+ }, "localStep");
1057
1127
  const transaction = /* @__PURE__ */ __name(async (name, fn) => {
1058
1128
  if (!store.transaction) {
1059
1129
  throw new Error("ctx.transaction needs a store that supports transactions (the SQL adapters do). Use ctx.step for non-transactional work.");
@@ -1200,7 +1270,7 @@ function createWorkflowCtx(host, runId, compensations, workflow = "") {
1200
1270
  throw new WorkflowSuspended(deadline);
1201
1271
  }, "waitForEvent");
1202
1272
  const task = /* @__PURE__ */ __name(async (name, dispatch, options) => {
1203
- await step(`task:dispatch:${name}`, dispatch, options);
1273
+ await localStep(`task:dispatch:${name}`, dispatch, options);
1204
1274
  return unwrapCompletion(await waitForSignal(`task:${runId}:${name}`), `task "${name}"`);
1205
1275
  }, "task");
1206
1276
  const child = /* @__PURE__ */ __name(async (workflow2, input, options) => {
@@ -1413,9 +1483,8 @@ function createWorkflowCtx(host, runId, compensations, workflow = "") {
1413
1483
  wait
1414
1484
  };
1415
1485
  }, "webhook");
1416
- const now = /* @__PURE__ */ __name(() => step("now", async () => host.clock()), "now");
1417
- const random = /* @__PURE__ */ __name(() => step("random", async () => Math.random()), "random");
1418
- 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");
1419
1488
  const upsertSearchAttributes = /* @__PURE__ */ __name(async (attrs) => {
1420
1489
  const current = pos.next();
1421
1490
  const existing = await readCheckpoint(current);
@@ -1434,9 +1503,26 @@ function createWorkflowCtx(host, runId, compensations, workflow = "") {
1434
1503
  output: attrs
1435
1504
  }));
1436
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");
1437
1522
  return {
1438
1523
  runId,
1439
1524
  step,
1525
+ localStep,
1440
1526
  upsertSearchAttributes,
1441
1527
  transaction,
1442
1528
  callEntity,
@@ -1456,12 +1542,7 @@ function createWorkflowCtx(host, runId, compensations, workflow = "") {
1456
1542
  onUpdate,
1457
1543
  patched,
1458
1544
  now,
1459
- random,
1460
- uuid,
1461
- call: /* @__PURE__ */ __name((remote, input, opts) => host.callRemote(runId, pos.next(), remote, input, opts?.queue, opts?.transport, {
1462
- priority: opts?.priority,
1463
- fairnessKey: opts?.fairnessKey
1464
- }), "call")
1545
+ sideEffect
1465
1546
  };
1466
1547
  }
1467
1548
  __name(createWorkflowCtx, "createWorkflowCtx");
@@ -1579,7 +1660,10 @@ var WorkflowEngine = class {
1579
1660
  this.admission = deps.admission ?? new InMemoryAdmissionBackend(this.clock);
1580
1661
  this.admission.onFreed?.((queue) => this.wakeQueueWaiters(queue));
1581
1662
  this.instanceId = deps.instanceId ?? globalThis.crypto.randomUUID();
1582
- this.namespace = deps.namespace ?? "default";
1663
+ this.namespace = deps.namespace;
1664
+ if (this.namespace !== void 0) {
1665
+ this.pool.useNamespace(this.namespace);
1666
+ }
1583
1667
  this.leaseMs = deps.leaseMs ?? 3e4;
1584
1668
  this.maxRecoveryAttempts = deps.maxRecoveryAttempts;
1585
1669
  this.remoteAdvanceSilenceMs = deps.remoteAdvanceSilenceMs;
@@ -1644,6 +1728,13 @@ var WorkflowEngine = class {
1644
1728
  await this.completeRemoteDecision(decision);
1645
1729
  }
1646
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
+ });
1647
1738
  this.controlPlane?.onControl((msg) => {
1648
1739
  if (msg.from === this.instanceId) return;
1649
1740
  if (msg.kind === "event") {
@@ -1808,7 +1899,7 @@ var WorkflowEngine = class {
1808
1899
  });
1809
1900
  }
1810
1901
  /**
1811
- * Register a flow-control queue referenced by `ctx.call(step, input, { queue })`. Caps concurrent
1902
+ * Register a flow-control queue referenced by `ctx.step(step, input, { queue })`. Caps concurrent
1812
1903
  * in-flight steps and/or the admission rate; blocked calls re-suspend and retry, so the limit is
1813
1904
  * durable. Per engine instance (see {@link QueueConfig}). Registering the same name replaces it.
1814
1905
  */
@@ -1911,6 +2002,23 @@ var WorkflowEngine = class {
1911
2002
  const ancestor = await this.findRemoteAncestor(runId);
1912
2003
  if (ancestor) registered = this.synthesizeRemoteChild(name, ancestor.version, ancestor);
1913
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 (:893) 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
+ }
1914
2022
  if (!registered) throw new Error(`workflow ${name} is not registered`);
1915
2023
  await registered.validateInput?.(input);
1916
2024
  const prior = await this.store.getRun(runId);
@@ -1935,7 +2043,7 @@ var WorkflowEngine = class {
1935
2043
  workflow: name,
1936
2044
  workflowVersion: registered.version,
1937
2045
  status: "pending",
1938
- namespace: this.namespace,
2046
+ namespace: opts?.namespace ?? this.namespace,
1939
2047
  input,
1940
2048
  tags,
1941
2049
  searchAttributes: opts?.searchAttributes,
@@ -1972,11 +2080,11 @@ var WorkflowEngine = class {
1972
2080
  error: run.error
1973
2081
  };
1974
2082
  }
1975
- if (run.namespace !== void 0 && run.namespace !== this.namespace) {
2083
+ if (this.namespace !== void 0 && run.namespace !== void 0 && run.namespace !== this.namespace) {
1976
2084
  await this.store.releaseRunLock(runId);
1977
2085
  throw new NamespaceMismatch();
1978
2086
  }
1979
- 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);
1980
2088
  if (!registered) {
1981
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)`);
1982
2090
  }
@@ -2022,6 +2130,34 @@ var WorkflowEngine = class {
2022
2130
  };
2023
2131
  }
2024
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
+ /**
2025
2161
  * Walk the parent chain of `childRunId` to the nearest ancestor registered as REMOTE, returning its
2026
2162
  * registration (or `undefined` if there is none). The parent of an awaited child is found via its
2027
2163
  * live `child:<childId>` signal waiter — present for as long as the parent is suspended awaiting the
@@ -2118,6 +2254,7 @@ var WorkflowEngine = class {
2118
2254
  type: "run.failed",
2119
2255
  runId: run.id,
2120
2256
  workflow: run.workflow,
2257
+ namespace: run.namespace,
2121
2258
  error
2122
2259
  });
2123
2260
  }
@@ -2175,6 +2312,7 @@ var WorkflowEngine = class {
2175
2312
  type: "run.failed",
2176
2313
  runId: run.id,
2177
2314
  workflow: run.workflow,
2315
+ namespace: run.namespace,
2178
2316
  error
2179
2317
  });
2180
2318
  this.notifyDead({
@@ -2249,7 +2387,8 @@ var WorkflowEngine = class {
2249
2387
  if (!run) return null;
2250
2388
  const id = newRunId ?? `${runId}~retry~${globalThis.crypto.randomUUID().slice(0, 8)}`;
2251
2389
  await this.start(run.workflow, input, id, {
2252
- tags: run.tags
2390
+ tags: run.tags,
2391
+ namespace: run.namespace
2253
2392
  });
2254
2393
  return {
2255
2394
  runId: id
@@ -2483,6 +2622,7 @@ var WorkflowEngine = class {
2483
2622
  type: "run.failed",
2484
2623
  runId,
2485
2624
  workflow: run.workflow,
2625
+ namespace: run.namespace,
2486
2626
  error
2487
2627
  });
2488
2628
  await this.cancelChildren(runId, opts);
@@ -2759,7 +2899,7 @@ var WorkflowEngine = class {
2759
2899
  });
2760
2900
  }
2761
2901
  async execute(run, fn) {
2762
- 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);
2763
2903
  const lockNow = this.clock();
2764
2904
  if (run.lockedBy !== this.instanceId || (run.lockedUntil ?? 0) <= lockNow) {
2765
2905
  if (!await this.store.tryLockRun(run.id, this.instanceId, lockNow + this.leaseMs, lockNow)) {
@@ -2809,6 +2949,7 @@ var WorkflowEngine = class {
2809
2949
  type: "run.completed",
2810
2950
  runId: run.id,
2811
2951
  workflow: run.workflow,
2952
+ namespace: run.namespace,
2812
2953
  output: outcome.output
2813
2954
  });
2814
2955
  void this.notifyParent(run.id, {
@@ -2831,6 +2972,7 @@ var WorkflowEngine = class {
2831
2972
  type: "run.failed",
2832
2973
  runId: run.id,
2833
2974
  workflow: run.workflow,
2975
+ namespace: run.namespace,
2834
2976
  error: outcome.error
2835
2977
  });
2836
2978
  void this.notifyParent(run.id, {
@@ -2851,7 +2993,8 @@ var WorkflowEngine = class {
2851
2993
  this.emit({
2852
2994
  type: "run.suspended",
2853
2995
  runId: run.id,
2854
- workflow: run.workflow
2996
+ workflow: run.workflow,
2997
+ namespace: run.namespace
2855
2998
  });
2856
2999
  return {
2857
3000
  runId: run.id,
@@ -2872,6 +3015,7 @@ var WorkflowEngine = class {
2872
3015
  type: "run.failed",
2873
3016
  runId: run.id,
2874
3017
  workflow: run.workflow,
3018
+ namespace: run.namespace,
2875
3019
  error
2876
3020
  });
2877
3021
  this.notifyCancelled(run.id);
@@ -2899,7 +3043,8 @@ var WorkflowEngine = class {
2899
3043
  this.emit({
2900
3044
  type: "run.started",
2901
3045
  runId: run.id,
2902
- workflow: run.workflow
3046
+ workflow: run.workflow,
3047
+ namespace: run.namespace
2903
3048
  });
2904
3049
  }
2905
3050
  if (registered.singleton && !await this.singletons.admit(run, registered.singleton)) {
@@ -2912,7 +3057,8 @@ var WorkflowEngine = class {
2912
3057
  this.emit({
2913
3058
  type: "run.suspended",
2914
3059
  runId: run.id,
2915
- workflow: run.workflow
3060
+ workflow: run.workflow,
3061
+ namespace: run.namespace
2916
3062
  });
2917
3063
  await this.store.releaseRunLock(run.id);
2918
3064
  return {
@@ -2934,7 +3080,8 @@ var WorkflowEngine = class {
2934
3080
  this.emit({
2935
3081
  type: "run.suspended",
2936
3082
  runId: run.id,
2937
- workflow: run.workflow
3083
+ workflow: run.workflow,
3084
+ namespace: run.namespace
2938
3085
  });
2939
3086
  await this.store.releaseRunLock(run.id);
2940
3087
  await remote.executor.dispatch(run, history, taskId);
@@ -2973,6 +3120,7 @@ var WorkflowEngine = class {
2973
3120
  type: "run.failed",
2974
3121
  runId: run.id,
2975
3122
  workflow: run.workflow,
3123
+ namespace: run.namespace,
2976
3124
  error
2977
3125
  });
2978
3126
  return {
@@ -3123,13 +3271,16 @@ var WorkflowEngine = class {
3123
3271
  status: "pending",
3124
3272
  input: cmd.input,
3125
3273
  attempts: 1,
3126
- workerGroup: cmd.group,
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),
3127
3278
  enqueuedAt: at,
3128
3279
  startedAt: at,
3129
3280
  finishedAt: at,
3130
3281
  // A `ctx.gather_calls([...])` fan-out stamps every dispatched `call` in the fan with the
3131
- // same group, so the dashboard renders the remote steps as one parallel fan (parity with
3132
- // the gathered `recordStep`/`startChild` tags). Undefined for a lone sequential `ctx.call`.
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`.
3133
3284
  parallelGroup: cmd.parallelGroup
3134
3285
  }));
3135
3286
  await this.pool.dispatch({
@@ -3137,7 +3288,7 @@ var WorkflowEngine = class {
3137
3288
  seq: cmd.seq,
3138
3289
  name: cmd.name,
3139
3290
  stepId: id,
3140
- group: cmd.group,
3291
+ group: tenantGroup(sanitizeQueueToken(cmd.name), cmd.group),
3141
3292
  input: cmd.input,
3142
3293
  traceparent: this.traceparent?.(),
3143
3294
  context: this.context?.(),
@@ -3228,6 +3379,7 @@ var WorkflowEngine = class {
3228
3379
  type: "run.failed",
3229
3380
  runId: run.id,
3230
3381
  workflow: run.workflow,
3382
+ namespace: run.namespace,
3231
3383
  error
3232
3384
  });
3233
3385
  return {
@@ -3248,7 +3400,8 @@ var WorkflowEngine = class {
3248
3400
  this.emit({
3249
3401
  type: "run.started",
3250
3402
  runId: run.id,
3251
- workflow: run.workflow
3403
+ workflow: run.workflow,
3404
+ namespace: run.namespace
3252
3405
  });
3253
3406
  }
3254
3407
  if (run.status !== "cancelling" && registered?.singleton && !await this.singletons.admit(run, registered.singleton)) {
@@ -3261,7 +3414,8 @@ var WorkflowEngine = class {
3261
3414
  this.emit({
3262
3415
  type: "run.suspended",
3263
3416
  runId: run.id,
3264
- workflow: run.workflow
3417
+ workflow: run.workflow,
3418
+ namespace: run.namespace
3265
3419
  });
3266
3420
  await this.store.releaseRunLock(run.id);
3267
3421
  return {
@@ -3293,7 +3447,8 @@ var WorkflowEngine = class {
3293
3447
  this.emit({
3294
3448
  type: "run.completed",
3295
3449
  runId: run.id,
3296
- workflow: run.workflow
3450
+ workflow: run.workflow,
3451
+ namespace: run.namespace
3297
3452
  });
3298
3453
  void this.notifyParent(run.id, {
3299
3454
  ok: true,
@@ -3512,7 +3667,6 @@ var WorkflowEngine = class {
3512
3667
  this.queueWaiters.get(queue)?.delete(runId);
3513
3668
  this.stepQueue.set(id, queue);
3514
3669
  }
3515
- const validInput = step.input.parse(input);
3516
3670
  const enqueuedAt = /* @__PURE__ */ new Date();
3517
3671
  await this.store.saveCheckpoint({
3518
3672
  runId,
@@ -3521,9 +3675,9 @@ var WorkflowEngine = class {
3521
3675
  kind: "remote",
3522
3676
  stepId: id,
3523
3677
  status: "pending",
3524
- input: validInput,
3678
+ input,
3525
3679
  attempts: attempt,
3526
- workerGroup: step.group,
3680
+ workerGroup: tenantGroup(sanitizeQueueToken(step.name), step.partition),
3527
3681
  enqueuedAt,
3528
3682
  startedAt: enqueuedAt,
3529
3683
  finishedAt: enqueuedAt
@@ -3533,8 +3687,8 @@ var WorkflowEngine = class {
3533
3687
  seq,
3534
3688
  name: step.name,
3535
3689
  stepId: id,
3536
- group: step.group,
3537
- input: validInput,
3690
+ group: tenantGroup(sanitizeQueueToken(step.name), step.partition),
3691
+ input,
3538
3692
  traceparent: this.traceparent?.(),
3539
3693
  context: this.context?.(),
3540
3694
  priority: admission?.priority,
@@ -3599,7 +3753,9 @@ var WorkflowEngine = class {
3599
3753
  const run = await this.store.getRun(decision.runId);
3600
3754
  if (!run || run.awaitingDecisionTaskId !== decision.taskId) return;
3601
3755
  if (run.status === "cancelled" || run.status === "completed" || run.status === "dead") return;
3602
- if (run.namespace !== void 0 && run.namespace !== this.namespace) return;
3756
+ if (this.namespace !== void 0 && run.namespace !== void 0 && run.namespace !== this.namespace) {
3757
+ return;
3758
+ }
3603
3759
  const nowMs = this.clock();
3604
3760
  if (!await this.store.tryLockRun(decision.runId, this.instanceId, nowMs + this.leaseMs, nowMs)) {
3605
3761
  return;
@@ -3665,7 +3821,7 @@ var WorkflowEngine = class {
3665
3821
  /** In-memory await path for a remote step with a liveness `timeoutMs` (re-dispatch on timeout). */
3666
3822
  async callRemoteInMemory(runId, seq, step, input, transport) {
3667
3823
  if (this.pool.size === 0) throw new Error("remote steps require a Transport");
3668
- const validInput = step.input.parse(input);
3824
+ const validInput = step.input ? step.input.parse(input) : input;
3669
3825
  const id = stepId(runId, seq);
3670
3826
  const enqueuedAt = /* @__PURE__ */ new Date();
3671
3827
  this.emit({
@@ -3688,7 +3844,7 @@ var WorkflowEngine = class {
3688
3844
  seq,
3689
3845
  name: step.name,
3690
3846
  stepId: id,
3691
- group: step.group,
3847
+ group: tenantGroup(sanitizeQueueToken(step.name), step.partition),
3692
3848
  input: validInput,
3693
3849
  traceparent: this.traceparent?.(),
3694
3850
  context: this.context?.(),
@@ -3696,7 +3852,7 @@ var WorkflowEngine = class {
3696
3852
  }, transport);
3697
3853
  try {
3698
3854
  const resolution = step.timeoutMs ? await this.awaitWithHeartbeat(id, resultPromise, step.timeoutMs) : await resultPromise;
3699
- const output = step.output.parse(resolution.output);
3855
+ const output = step.output ? step.output.parse(resolution.output) : resolution.output;
3700
3856
  const startedAt = resolution.startedAt ? new Date(resolution.startedAt) : enqueuedAt;
3701
3857
  await this.completeStep({
3702
3858
  runId,
@@ -3707,7 +3863,7 @@ var WorkflowEngine = class {
3707
3863
  output,
3708
3864
  events: resolution.events,
3709
3865
  attempts: attempt,
3710
- workerGroup: step.group,
3866
+ workerGroup: tenantGroup(sanitizeQueueToken(step.name), step.partition),
3711
3867
  enqueuedAt,
3712
3868
  startedAt
3713
3869
  });
@@ -3783,16 +3939,6 @@ var TERMINAL_RUN_STATUSES = [
3783
3939
  "dead"
3784
3940
  ];
3785
3941
 
3786
- // src/remote-step-factory.ts
3787
- function remoteStep(config) {
3788
- return {
3789
- ...config,
3790
- group: config.group ?? config.name.split(".")[0] ?? config.name,
3791
- __remote: true
3792
- };
3793
- }
3794
- __name(remoteStep, "remoteStep");
3795
-
3796
3942
  // src/codec-state-store.ts
3797
3943
  var CodecStateStore = class {
3798
3944
  static {
@@ -4516,6 +4662,8 @@ export {
4516
4662
  ContinueAsNew,
4517
4663
  DURABLE_OPTIONS,
4518
4664
  DURABLE_OPTIONS_CANONICAL,
4665
+ DURABLE_STEP_CONFIG,
4666
+ DURABLE_STEP_NAME,
4519
4667
  Entities,
4520
4668
  EventAccumulators,
4521
4669
  FatalError,
@@ -4553,12 +4701,15 @@ export {
4553
4701
  normalizeAttributeRows,
4554
4702
  parseDuration,
4555
4703
  prevCronFireMs,
4556
- remoteStep,
4557
4704
  runSchedules,
4558
4705
  runStepHandler,
4706
+ sanitizeQueueToken,
4559
4707
  scheduledRunId,
4560
4708
  sqlComparator,
4709
+ stepConfigOf,
4561
4710
  stepId,
4711
+ stepNameOf,
4712
+ tenantGroup,
4562
4713
  workflowName
4563
4714
  };
4564
4715
  //# sourceMappingURL=index.js.map