@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.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();
@@ -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 step = /* @__PURE__ */ __name(async (name, fn, options) => {
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
- }, "step");
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 step(`task:dispatch:${name}`, dispatch, options);
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 now = /* @__PURE__ */ __name(() => step("now", async () => host.clock()), "now");
1428
- const random = /* @__PURE__ */ __name(() => step("random", async () => Math.random()), "random");
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
- random,
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 ?? "default";
1594
- this.pool.useNamespace(this.namespace);
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.call(step, input, { queue })`. Caps concurrent
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 (: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
+ }
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
- 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),
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 remote steps as one parallel fan (parity with
3144
- // 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`.
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?.(),
@@ -3240,6 +3379,7 @@ var WorkflowEngine = class {
3240
3379
  type: "run.failed",
3241
3380
  runId: run.id,
3242
3381
  workflow: run.workflow,
3382
+ namespace: run.namespace,
3243
3383
  error
3244
3384
  });
3245
3385
  return {
@@ -3260,7 +3400,8 @@ var WorkflowEngine = class {
3260
3400
  this.emit({
3261
3401
  type: "run.started",
3262
3402
  runId: run.id,
3263
- workflow: run.workflow
3403
+ workflow: run.workflow,
3404
+ namespace: run.namespace
3264
3405
  });
3265
3406
  }
3266
3407
  if (run.status !== "cancelling" && registered?.singleton && !await this.singletons.admit(run, registered.singleton)) {
@@ -3273,7 +3414,8 @@ var WorkflowEngine = class {
3273
3414
  this.emit({
3274
3415
  type: "run.suspended",
3275
3416
  runId: run.id,
3276
- workflow: run.workflow
3417
+ workflow: run.workflow,
3418
+ namespace: run.namespace
3277
3419
  });
3278
3420
  await this.store.releaseRunLock(run.id);
3279
3421
  return {
@@ -3305,7 +3447,8 @@ var WorkflowEngine = class {
3305
3447
  this.emit({
3306
3448
  type: "run.completed",
3307
3449
  runId: run.id,
3308
- workflow: run.workflow
3450
+ workflow: run.workflow,
3451
+ namespace: run.namespace
3309
3452
  });
3310
3453
  void this.notifyParent(run.id, {
3311
3454
  ok: true,
@@ -3524,7 +3667,6 @@ var WorkflowEngine = class {
3524
3667
  this.queueWaiters.get(queue)?.delete(runId);
3525
3668
  this.stepQueue.set(id, queue);
3526
3669
  }
3527
- const validInput = step.input.parse(input);
3528
3670
  const enqueuedAt = /* @__PURE__ */ new Date();
3529
3671
  await this.store.saveCheckpoint({
3530
3672
  runId,
@@ -3533,9 +3675,9 @@ var WorkflowEngine = class {
3533
3675
  kind: "remote",
3534
3676
  stepId: id,
3535
3677
  status: "pending",
3536
- input: validInput,
3678
+ input,
3537
3679
  attempts: attempt,
3538
- workerGroup: step.group,
3680
+ workerGroup: tenantGroup(sanitizeQueueToken(step.name), step.partition),
3539
3681
  enqueuedAt,
3540
3682
  startedAt: enqueuedAt,
3541
3683
  finishedAt: enqueuedAt
@@ -3545,8 +3687,8 @@ var WorkflowEngine = class {
3545
3687
  seq,
3546
3688
  name: step.name,
3547
3689
  stepId: id,
3548
- group: step.group,
3549
- input: validInput,
3690
+ group: tenantGroup(sanitizeQueueToken(step.name), step.partition),
3691
+ input,
3550
3692
  traceparent: this.traceparent?.(),
3551
3693
  context: this.context?.(),
3552
3694
  priority: admission?.priority,
@@ -3611,7 +3753,9 @@ var WorkflowEngine = class {
3611
3753
  const run = await this.store.getRun(decision.runId);
3612
3754
  if (!run || run.awaitingDecisionTaskId !== decision.taskId) return;
3613
3755
  if (run.status === "cancelled" || run.status === "completed" || run.status === "dead") return;
3614
- 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
+ }
3615
3759
  const nowMs = this.clock();
3616
3760
  if (!await this.store.tryLockRun(decision.runId, this.instanceId, nowMs + this.leaseMs, nowMs)) {
3617
3761
  return;
@@ -3677,7 +3821,7 @@ var WorkflowEngine = class {
3677
3821
  /** In-memory await path for a remote step with a liveness `timeoutMs` (re-dispatch on timeout). */
3678
3822
  async callRemoteInMemory(runId, seq, step, input, transport) {
3679
3823
  if (this.pool.size === 0) throw new Error("remote steps require a Transport");
3680
- const validInput = step.input.parse(input);
3824
+ const validInput = step.input ? step.input.parse(input) : input;
3681
3825
  const id = stepId(runId, seq);
3682
3826
  const enqueuedAt = /* @__PURE__ */ new Date();
3683
3827
  this.emit({
@@ -3700,7 +3844,7 @@ var WorkflowEngine = class {
3700
3844
  seq,
3701
3845
  name: step.name,
3702
3846
  stepId: id,
3703
- group: step.group,
3847
+ group: tenantGroup(sanitizeQueueToken(step.name), step.partition),
3704
3848
  input: validInput,
3705
3849
  traceparent: this.traceparent?.(),
3706
3850
  context: this.context?.(),
@@ -3708,7 +3852,7 @@ var WorkflowEngine = class {
3708
3852
  }, transport);
3709
3853
  try {
3710
3854
  const resolution = step.timeoutMs ? await this.awaitWithHeartbeat(id, resultPromise, step.timeoutMs) : await resultPromise;
3711
- const output = step.output.parse(resolution.output);
3855
+ const output = step.output ? step.output.parse(resolution.output) : resolution.output;
3712
3856
  const startedAt = resolution.startedAt ? new Date(resolution.startedAt) : enqueuedAt;
3713
3857
  await this.completeStep({
3714
3858
  runId,
@@ -3719,7 +3863,7 @@ var WorkflowEngine = class {
3719
3863
  output,
3720
3864
  events: resolution.events,
3721
3865
  attempts: attempt,
3722
- workerGroup: step.group,
3866
+ workerGroup: tenantGroup(sanitizeQueueToken(step.name), step.partition),
3723
3867
  enqueuedAt,
3724
3868
  startedAt
3725
3869
  });
@@ -3795,16 +3939,6 @@ var TERMINAL_RUN_STATUSES = [
3795
3939
  "dead"
3796
3940
  ];
3797
3941
 
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
3942
  // src/codec-state-store.ts
3809
3943
  var CodecStateStore = class {
3810
3944
  static {
@@ -4528,6 +4662,8 @@ export {
4528
4662
  ContinueAsNew,
4529
4663
  DURABLE_OPTIONS,
4530
4664
  DURABLE_OPTIONS_CANONICAL,
4665
+ DURABLE_STEP_CONFIG,
4666
+ DURABLE_STEP_NAME,
4531
4667
  Entities,
4532
4668
  EventAccumulators,
4533
4669
  FatalError,
@@ -4565,12 +4701,15 @@ export {
4565
4701
  normalizeAttributeRows,
4566
4702
  parseDuration,
4567
4703
  prevCronFireMs,
4568
- remoteStep,
4569
4704
  runSchedules,
4570
4705
  runStepHandler,
4706
+ sanitizeQueueToken,
4571
4707
  scheduledRunId,
4572
4708
  sqlComparator,
4709
+ stepConfigOf,
4573
4710
  stepId,
4711
+ stepNameOf,
4712
+ tenantGroup,
4574
4713
  workflowName
4575
4714
  };
4576
4715
  //# sourceMappingURL=index.js.map