@dudousxd/nestjs-durable-core 0.54.0 → 0.55.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -1162,6 +1162,15 @@ interface Transport {
1162
1162
  * transports that don't partition simply omit it.
1163
1163
  */
1164
1164
  useNamespace?(namespace: string): void;
1165
+ /**
1166
+ * A SIBLING transport on the same broker/connection, pinned to `namespace` (explicit — never
1167
+ * re-scoped by a later {@link useNamespace}). Powers the tenant-worker bridge preset
1168
+ * (`topology: { role: 'control-plane', tenant, tenantWorkers: 'bridge' }`): a tenant-scoped
1169
+ * operator pairs its namespaced primary with `withNamespace('default')` — a bare-prefix sibling —
1170
+ * so operator-convention tenant workers (`<group>@<tenant>` under the bare prefix) are
1171
+ * discoverable and dispatchable. Optional — transports that don't partition simply omit it.
1172
+ */
1173
+ withNamespace?(namespace: string): Transport;
1165
1174
  /** Release the transport's resources (broker workers, queues, connections) for a clean shutdown.
1166
1175
  * Optional — an in-process transport has nothing to close. Called on `onApplicationShutdown`
1167
1176
  * after the engine drains, so a deploy hands off instead of leaving the broker to time out. */
@@ -2618,6 +2627,19 @@ declare class WorkflowEngine {
2618
2627
  * knowing about the child feature.
2619
2628
  */
2620
2629
  private notifyParent;
2630
+ /**
2631
+ * Deferred child start shared by the in-process ctx host and the remote `startChild` command.
2632
+ * Deferred (microtask) so a fast child can't reentrantly resume a still-suspending parent. A start
2633
+ * that THROWS (unregistered/unroutable workflow, input validation, singleton back-pressure, store
2634
+ * failure) must NOT be swallowed: the parent is already suspended on `child:<childId>` (the waiter
2635
+ * is put BEFORE start on every path), so a silent drop parks it in suspended-forever, invisibly
2636
+ * re-attempting on every recovery wake — a misconfigured remote child looks exactly like a healthy
2637
+ * long wait. Instead the failure is delivered to that waiter like a failed child (notifyParent):
2638
+ * the parent resumes and fails loudly with the cause. For a fire-and-forget `ctx.startChild`
2639
+ * (spawn — no waiter) the completion is buffered; a later join by the same id consumes it and
2640
+ * correctly observes the failed start.
2641
+ */
2642
+ private startChildDeferred;
2621
2643
  /**
2622
2644
  * Cancel a run (e.g. from the dashboard). Returns null if the run does not exist. Pass
2623
2645
  * `{ compensate: true }` to undo the saga first: the suspended run is resumed so its completed
package/dist/index.d.ts CHANGED
@@ -1162,6 +1162,15 @@ interface Transport {
1162
1162
  * transports that don't partition simply omit it.
1163
1163
  */
1164
1164
  useNamespace?(namespace: string): void;
1165
+ /**
1166
+ * A SIBLING transport on the same broker/connection, pinned to `namespace` (explicit — never
1167
+ * re-scoped by a later {@link useNamespace}). Powers the tenant-worker bridge preset
1168
+ * (`topology: { role: 'control-plane', tenant, tenantWorkers: 'bridge' }`): a tenant-scoped
1169
+ * operator pairs its namespaced primary with `withNamespace('default')` — a bare-prefix sibling —
1170
+ * so operator-convention tenant workers (`<group>@<tenant>` under the bare prefix) are
1171
+ * discoverable and dispatchable. Optional — transports that don't partition simply omit it.
1172
+ */
1173
+ withNamespace?(namespace: string): Transport;
1165
1174
  /** Release the transport's resources (broker workers, queues, connections) for a clean shutdown.
1166
1175
  * Optional — an in-process transport has nothing to close. Called on `onApplicationShutdown`
1167
1176
  * after the engine drains, so a deploy hands off instead of leaving the broker to time out. */
@@ -2618,6 +2627,19 @@ declare class WorkflowEngine {
2618
2627
  * knowing about the child feature.
2619
2628
  */
2620
2629
  private notifyParent;
2630
+ /**
2631
+ * Deferred child start shared by the in-process ctx host and the remote `startChild` command.
2632
+ * Deferred (microtask) so a fast child can't reentrantly resume a still-suspending parent. A start
2633
+ * that THROWS (unregistered/unroutable workflow, input validation, singleton back-pressure, store
2634
+ * failure) must NOT be swallowed: the parent is already suspended on `child:<childId>` (the waiter
2635
+ * is put BEFORE start on every path), so a silent drop parks it in suspended-forever, invisibly
2636
+ * re-attempting on every recovery wake — a misconfigured remote child looks exactly like a healthy
2637
+ * long wait. Instead the failure is delivered to that waiter like a failed child (notifyParent):
2638
+ * the parent resumes and fails loudly with the cause. For a fire-and-forget `ctx.startChild`
2639
+ * (spawn — no waiter) the completion is buffered; a later join by the same id consumes it and
2640
+ * correctly observes the failed start.
2641
+ */
2642
+ private startChildDeferred;
2621
2643
  /**
2622
2644
  * Cancel a run (e.g. from the dashboard). Returns null if the run does not exist. Pass
2623
2645
  * `{ compensate: true }` to undo the saga first: the suspended run is resumed so its completed
package/dist/index.js CHANGED
@@ -1099,6 +1099,21 @@ var TransportPool = class {
1099
1099
  ];
1100
1100
  }
1101
1101
  /**
1102
+ * The first transport (in pool order) reporting a LIVE heartbeat for `group` — the one a
1103
+ * convention-routed remote workflow must dispatch on. In a mixed pool (e.g. a tenant-scoped
1104
+ * control plane pairing its namespaced transport with a bare-prefix one for operator-convention
1105
+ * tenant workers), each transport sees a DIFFERENT keyspace: dispatching on `primary` while the
1106
+ * live worker heartbeats on another transport parks the task on a queue nobody consumes. Returns
1107
+ * `undefined` when no transport reports the group (mirrors the merged `listWorkerGroups` miss).
1108
+ */
1109
+ async transportWithLiveGroup(group) {
1110
+ for (const { transport } of this.transports) {
1111
+ if (!transport.listWorkerGroups) continue;
1112
+ if ((await transport.listWorkerGroups()).includes(group)) return transport;
1113
+ }
1114
+ return void 0;
1115
+ }
1116
+ /**
1102
1117
  * Propagate the engine's `namespace` to every transport that partitions by it — so the same
1103
1118
  * namespace that scopes the store also scopes each transport's queues/keys. A transport that
1104
1119
  * doesn't partition (no `useNamespace`) is skipped. Idempotent; a transport given an explicit
@@ -2414,10 +2429,10 @@ var WorkflowEngine = class {
2414
2429
  * liveness changes between calls, exactly as {@link synthesizeRemoteChild} does.
2415
2430
  */
2416
2431
  async resolveRemoteByConvention(run) {
2417
- const liveGroups = await this.pool.listWorkerGroups();
2418
2432
  const group = tenantGroup(sanitizeQueueToken(run.workflow), run.namespace);
2419
- if (!liveGroups.includes(group)) return void 0;
2420
- const executor = new RemoteWorkflowExecutor(this.pool.primary, run.workflow, run.namespace);
2433
+ const liveTransport = await this.pool.transportWithLiveGroup(group);
2434
+ if (!liveTransport) return void 0;
2435
+ const executor = new RemoteWorkflowExecutor(liveTransport, run.workflow, run.namespace);
2421
2436
  return {
2422
2437
  name: run.workflow,
2423
2438
  version: run.workflowVersion,
@@ -3036,6 +3051,27 @@ var WorkflowEngine = class {
3036
3051
  void this.signal(`child:${runId}`, completion).catch(() => void 0);
3037
3052
  }
3038
3053
  /**
3054
+ * Deferred child start shared by the in-process ctx host and the remote `startChild` command.
3055
+ * Deferred (microtask) so a fast child can't reentrantly resume a still-suspending parent. A start
3056
+ * that THROWS (unregistered/unroutable workflow, input validation, singleton back-pressure, store
3057
+ * failure) must NOT be swallowed: the parent is already suspended on `child:<childId>` (the waiter
3058
+ * is put BEFORE start on every path), so a silent drop parks it in suspended-forever, invisibly
3059
+ * re-attempting on every recovery wake — a misconfigured remote child looks exactly like a healthy
3060
+ * long wait. Instead the failure is delivered to that waiter like a failed child (notifyParent):
3061
+ * the parent resumes and fails loudly with the cause. For a fire-and-forget `ctx.startChild`
3062
+ * (spawn — no waiter) the completion is buffered; a later join by the same id consumes it and
3063
+ * correctly observes the failed start.
3064
+ */
3065
+ startChildDeferred(workflow, input, childId, opts) {
3066
+ queueMicrotask(() => void this.start(workflow, input, childId, opts).catch((err) => {
3067
+ const message = err instanceof Error ? err.message : String(err);
3068
+ this.notifyParent(childId, {
3069
+ ok: false,
3070
+ error: `child workflow "${workflow}" failed to start: ${message}`
3071
+ });
3072
+ }));
3073
+ }
3074
+ /**
3039
3075
  * Cancel a run (e.g. from the dashboard). Returns null if the run does not exist. Pass
3040
3076
  * `{ compensate: true }` to undo the saga first: the suspended run is resumed so its completed
3041
3077
  * steps' compensations run in reverse (visible as `compensate:<step>` events), then it's marked
@@ -3859,9 +3895,9 @@ var WorkflowEngine = class {
3859
3895
  parallelGroup: cmd.parallelGroup
3860
3896
  });
3861
3897
  if (!await this.store.getRun(childId)) {
3862
- queueMicrotask(() => void this.start(cmd.workflow, cmd.input, childId, {
3898
+ this.startChildDeferred(cmd.workflow, cmd.input, childId, {
3863
3899
  namespace: run.namespace
3864
- }).catch(() => void 0));
3900
+ });
3865
3901
  }
3866
3902
  } else {
3867
3903
  throw new Error(`remote workflow command '${cmd.kind}' is not supported yet`);
@@ -4240,10 +4276,10 @@ var WorkflowEngine = class {
4240
4276
  // `davi-local` pipeline would stamp its `processing` child `default` and it would leak off the
4241
4277
  // tenant's worker pool onto the shared/dev workers.
4242
4278
  startChild: /* @__PURE__ */ __name((workflow, input, id, priority) => {
4243
- queueMicrotask(() => void this.start(workflow, input, id, {
4279
+ this.startChildDeferred(workflow, input, id, {
4244
4280
  priority,
4245
4281
  namespace: parentNamespace
4246
- }).catch(() => void 0));
4282
+ });
4247
4283
  }, "startChild"),
4248
4284
  // Deferred for the same reentrancy reason as `startChild` above. `cancel()` is already
4249
4285
  // idempotent on a terminal/cancelled run (returns its existing status without side effects), so