@dudousxd/nestjs-durable-core 0.42.0 → 0.43.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.d.cts CHANGED
@@ -1636,6 +1636,29 @@ declare class WorkflowEngine {
1636
1636
  executionTimeout?: string | number;
1637
1637
  validateInput?: (input: unknown) => void | Promise<void>;
1638
1638
  }): void;
1639
+ /**
1640
+ * The convenience form of {@link registerRemote}: register a workflow whose body runs on a worker
1641
+ * `group` (e.g. the Python `durable-worker`, or a separate TS worker), without hand-building a
1642
+ * {@link RemoteWorkflowExecutor}. It wires the standard broker executor — a `RemoteWorkflowExecutor`
1643
+ * over the engine's own transport, dispatching to `opts.group` — for you, so you write
1644
+ * `engine.remote('processing', { group: 'processing' })` instead of
1645
+ * `engine.registerRemote('processing', '1', { group: 'processing', executor: new RemoteWorkflowExecutor(transport, 'processing') })`.
1646
+ *
1647
+ * Defaults the version to `'1'`. All the other {@link registerRemote} options
1648
+ * (`tags` / `singleton` / `executionTimeout` / `validateInput`) pass straight through. Reach for the
1649
+ * low-level {@link registerRemote} when you need a custom {@link WorkflowExecutor} (a different
1650
+ * transport, a fixed `timeoutMs`, a polyglot stub) — `remote()` is the standard wiring on the rails.
1651
+ */
1652
+ remote(name: string, opts: {
1653
+ /** Worker group this workflow's turns are dispatched to (also the executor's target group). */
1654
+ group: string;
1655
+ /** Workflow version. Defaults to `'1'`. */
1656
+ version?: string;
1657
+ tags?: string[];
1658
+ singleton?: SingletonConfig;
1659
+ executionTimeout?: string | number;
1660
+ validateInput?: (input: unknown) => void | Promise<void>;
1661
+ }): void;
1639
1662
  /**
1640
1663
  * Register a flow-control queue referenced by `ctx.call(step, input, { queue })`. Caps concurrent
1641
1664
  * in-flight steps and/or the admission rate; blocked calls re-suspend and retry, so the limit is
package/dist/index.d.ts CHANGED
@@ -1636,6 +1636,29 @@ declare class WorkflowEngine {
1636
1636
  executionTimeout?: string | number;
1637
1637
  validateInput?: (input: unknown) => void | Promise<void>;
1638
1638
  }): void;
1639
+ /**
1640
+ * The convenience form of {@link registerRemote}: register a workflow whose body runs on a worker
1641
+ * `group` (e.g. the Python `durable-worker`, or a separate TS worker), without hand-building a
1642
+ * {@link RemoteWorkflowExecutor}. It wires the standard broker executor — a `RemoteWorkflowExecutor`
1643
+ * over the engine's own transport, dispatching to `opts.group` — for you, so you write
1644
+ * `engine.remote('processing', { group: 'processing' })` instead of
1645
+ * `engine.registerRemote('processing', '1', { group: 'processing', executor: new RemoteWorkflowExecutor(transport, 'processing') })`.
1646
+ *
1647
+ * Defaults the version to `'1'`. All the other {@link registerRemote} options
1648
+ * (`tags` / `singleton` / `executionTimeout` / `validateInput`) pass straight through. Reach for the
1649
+ * low-level {@link registerRemote} when you need a custom {@link WorkflowExecutor} (a different
1650
+ * transport, a fixed `timeoutMs`, a polyglot stub) — `remote()` is the standard wiring on the rails.
1651
+ */
1652
+ remote(name: string, opts: {
1653
+ /** Worker group this workflow's turns are dispatched to (also the executor's target group). */
1654
+ group: string;
1655
+ /** Workflow version. Defaults to `'1'`. */
1656
+ version?: string;
1657
+ tags?: string[];
1658
+ singleton?: SingletonConfig;
1659
+ executionTimeout?: string | number;
1660
+ validateInput?: (input: unknown) => void | Promise<void>;
1661
+ }): void;
1639
1662
  /**
1640
1663
  * Register a flow-control queue referenced by `ctx.call(step, input, { queue })`. Caps concurrent
1641
1664
  * in-flight steps and/or the admission rate; blocked calls re-suspend and retry, so the limit is
package/dist/index.js CHANGED
@@ -700,6 +700,70 @@ function eventMatches(payload, match) {
700
700
  }
701
701
  __name(eventMatches, "eventMatches");
702
702
 
703
+ // src/remote-workflow-executor.ts
704
+ var taskCounter = 0;
705
+ var RemoteWorkflowExecutor = class {
706
+ static {
707
+ __name(this, "RemoteWorkflowExecutor");
708
+ }
709
+ transport;
710
+ group;
711
+ opts;
712
+ pending = /* @__PURE__ */ new Map();
713
+ subscribed = false;
714
+ constructor(transport, group, opts = {}) {
715
+ this.transport = transport;
716
+ this.group = group;
717
+ this.opts = opts;
718
+ }
719
+ ensureSubscribed() {
720
+ if (this.subscribed) return;
721
+ if (!this.transport.onDecision) {
722
+ throw new Error("transport does not support workflow decisions (onDecision)");
723
+ }
724
+ this.subscribed = true;
725
+ this.transport.onDecision(async (decision) => {
726
+ const resolve = this.pending.get(decision.taskId);
727
+ if (resolve) {
728
+ this.pending.delete(decision.taskId);
729
+ resolve(decision);
730
+ }
731
+ });
732
+ }
733
+ async advance(run, history) {
734
+ if (!this.transport.dispatchWorkflowTask) {
735
+ throw new Error("transport does not support workflow tasks (dispatchWorkflowTask)");
736
+ }
737
+ this.ensureSubscribed();
738
+ taskCounter += 1;
739
+ const taskId = `${run.id}:wf:${taskCounter}`;
740
+ const task = {
741
+ taskId,
742
+ runId: run.id,
743
+ workflow: run.workflow,
744
+ workflowVersion: run.workflowVersion,
745
+ input: run.input,
746
+ history,
747
+ group: this.group,
748
+ priority: run.priority,
749
+ attempt: 1
750
+ };
751
+ const decision = new Promise((resolve, reject) => {
752
+ this.pending.set(taskId, resolve);
753
+ const { timeoutMs } = this.opts;
754
+ if (timeoutMs) {
755
+ const timer = setTimeout(() => {
756
+ this.pending.delete(taskId);
757
+ reject(new RemoteWorkflowTimeout(taskId, timeoutMs));
758
+ }, timeoutMs);
759
+ timer.unref?.();
760
+ }
761
+ });
762
+ await this.transport.dispatchWorkflowTask(task);
763
+ return decision;
764
+ }
765
+ };
766
+
703
767
  // src/singleton-gate.ts
704
768
  var SINGLETON_RETRY_MS = 1e3;
705
769
  var SINGLETON_RETRY_JITTER_MS = 250;
@@ -801,6 +865,19 @@ var TransportPool = class {
801
865
  get size() {
802
866
  return this.transports.length;
803
867
  }
868
+ /**
869
+ * The primary (first) transport — the one `dispatch` prefers when no `preferId` is pinned. A
870
+ * {@link RemoteWorkflowExecutor} built by `engine.remote()` rides it (it needs a single
871
+ * `dispatchWorkflowTask`/`onDecision` Transport, not the whole failover pool). Throws if the pool
872
+ * is empty — there is nothing to dispatch a remote workflow over.
873
+ */
874
+ get primary() {
875
+ const first = this.transports[0];
876
+ if (!first) {
877
+ throw new Error("no transport configured \u2014 engine.remote() needs a transport (or transports)");
878
+ }
879
+ return first.transport;
880
+ }
804
881
  /** Register the engine's result/heartbeat/step-event handlers on EVERY transport — a result can come
805
882
  * back on whichever one delivered the task, so failover stays symmetric. `onStepEvent` is wired only
806
883
  * on transports that carry streamed workflow step lifecycle (BullMQ); others simply skip it. */
@@ -1720,6 +1797,28 @@ var WorkflowEngine = class {
1720
1797
  if (!current || isNewerVersion(version, current.version)) this.latest.set(name, registered);
1721
1798
  }
1722
1799
  /**
1800
+ * The convenience form of {@link registerRemote}: register a workflow whose body runs on a worker
1801
+ * `group` (e.g. the Python `durable-worker`, or a separate TS worker), without hand-building a
1802
+ * {@link RemoteWorkflowExecutor}. It wires the standard broker executor — a `RemoteWorkflowExecutor`
1803
+ * over the engine's own transport, dispatching to `opts.group` — for you, so you write
1804
+ * `engine.remote('processing', { group: 'processing' })` instead of
1805
+ * `engine.registerRemote('processing', '1', { group: 'processing', executor: new RemoteWorkflowExecutor(transport, 'processing') })`.
1806
+ *
1807
+ * Defaults the version to `'1'`. All the other {@link registerRemote} options
1808
+ * (`tags` / `singleton` / `executionTimeout` / `validateInput`) pass straight through. Reach for the
1809
+ * low-level {@link registerRemote} when you need a custom {@link WorkflowExecutor} (a different
1810
+ * transport, a fixed `timeoutMs`, a polyglot stub) — `remote()` is the standard wiring on the rails.
1811
+ */
1812
+ remote(name, opts) {
1813
+ const { group, version, ...rest } = opts;
1814
+ const executor = new RemoteWorkflowExecutor(this.pool.primary, group);
1815
+ this.registerRemote(name, version ?? "1", {
1816
+ group,
1817
+ executor,
1818
+ ...rest
1819
+ });
1820
+ }
1821
+ /**
1723
1822
  * Register a flow-control queue referenced by `ctx.call(step, input, { queue })`. Caps concurrent
1724
1823
  * in-flight steps and/or the admission rate; blocked calls re-suspend and retry, so the limit is
1725
1824
  * durable. Per engine instance (see {@link QueueConfig}). Registering the same name replaces it.
@@ -3615,70 +3714,6 @@ function remoteStep(config) {
3615
3714
  }
3616
3715
  __name(remoteStep, "remoteStep");
3617
3716
 
3618
- // src/remote-workflow-executor.ts
3619
- var taskCounter = 0;
3620
- var RemoteWorkflowExecutor = class {
3621
- static {
3622
- __name(this, "RemoteWorkflowExecutor");
3623
- }
3624
- transport;
3625
- group;
3626
- opts;
3627
- pending = /* @__PURE__ */ new Map();
3628
- subscribed = false;
3629
- constructor(transport, group, opts = {}) {
3630
- this.transport = transport;
3631
- this.group = group;
3632
- this.opts = opts;
3633
- }
3634
- ensureSubscribed() {
3635
- if (this.subscribed) return;
3636
- if (!this.transport.onDecision) {
3637
- throw new Error("transport does not support workflow decisions (onDecision)");
3638
- }
3639
- this.subscribed = true;
3640
- this.transport.onDecision(async (decision) => {
3641
- const resolve = this.pending.get(decision.taskId);
3642
- if (resolve) {
3643
- this.pending.delete(decision.taskId);
3644
- resolve(decision);
3645
- }
3646
- });
3647
- }
3648
- async advance(run, history) {
3649
- if (!this.transport.dispatchWorkflowTask) {
3650
- throw new Error("transport does not support workflow tasks (dispatchWorkflowTask)");
3651
- }
3652
- this.ensureSubscribed();
3653
- taskCounter += 1;
3654
- const taskId = `${run.id}:wf:${taskCounter}`;
3655
- const task = {
3656
- taskId,
3657
- runId: run.id,
3658
- workflow: run.workflow,
3659
- workflowVersion: run.workflowVersion,
3660
- input: run.input,
3661
- history,
3662
- group: this.group,
3663
- priority: run.priority,
3664
- attempt: 1
3665
- };
3666
- const decision = new Promise((resolve, reject) => {
3667
- this.pending.set(taskId, resolve);
3668
- const { timeoutMs } = this.opts;
3669
- if (timeoutMs) {
3670
- const timer = setTimeout(() => {
3671
- this.pending.delete(taskId);
3672
- reject(new RemoteWorkflowTimeout(taskId, timeoutMs));
3673
- }, timeoutMs);
3674
- timer.unref?.();
3675
- }
3676
- });
3677
- await this.transport.dispatchWorkflowTask(task);
3678
- return decision;
3679
- }
3680
- };
3681
-
3682
3717
  // src/codec-state-store.ts
3683
3718
  var CodecStateStore = class {
3684
3719
  static {