@dudousxd/nestjs-durable-core 0.70.1 → 0.70.3

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
@@ -4004,6 +4004,24 @@ declare class WorkflowEngine {
4004
4004
  * — the caller has already ensured this is the decision for the currently-awaited turn.
4005
4005
  */
4006
4006
  private applyDecision;
4007
+ /**
4008
+ * Re-drive a run whose turn was computed from a history OLDER than the store's.
4009
+ *
4010
+ * A turn replays against a SNAPSHOT, and an op can settle while its decision is in flight. The
4011
+ * canonical case is a remote `gather_calls` fan-out whose last call lands during the decision's
4012
+ * lease window: `completeRemoteResult` -> `resume` -> `execute` finds the lease held and returns
4013
+ * silently, so that call's wake is spent with nothing to show for it, and the decision then parks
4014
+ * the run on a `call` that is already complete. Nothing would ever wake it again — only the
4015
+ * `reconcileMs` orphan sweep, minutes later.
4016
+ *
4017
+ * The tell is precise: the turn (re)declared a blocking op whose checkpoint is already settled. A
4018
+ * turn with an up-to-date history never does that, because a settled op is in its history and the
4019
+ * replay skips it. That also makes this convergent — the re-driven turn sees the op resolved and
4020
+ * cannot re-declare it, so one lost wake costs exactly one extra turn, never a loop.
4021
+ *
4022
+ * Scheduled off the current stack so it runs after the caller released the run lease.
4023
+ */
4024
+ private redriveIfTurnSawStaleHistory;
4007
4025
  /** The run's resolved durable ops as replay inputs: completed/failed steps + elapsed timers. */
4008
4026
  private remoteHistory;
4009
4027
  /** Apply a turn's commands: persist recorded local steps, dispatch remote calls, schedule timers.
package/dist/index.d.ts CHANGED
@@ -4004,6 +4004,24 @@ declare class WorkflowEngine {
4004
4004
  * — the caller has already ensured this is the decision for the currently-awaited turn.
4005
4005
  */
4006
4006
  private applyDecision;
4007
+ /**
4008
+ * Re-drive a run whose turn was computed from a history OLDER than the store's.
4009
+ *
4010
+ * A turn replays against a SNAPSHOT, and an op can settle while its decision is in flight. The
4011
+ * canonical case is a remote `gather_calls` fan-out whose last call lands during the decision's
4012
+ * lease window: `completeRemoteResult` -> `resume` -> `execute` finds the lease held and returns
4013
+ * silently, so that call's wake is spent with nothing to show for it, and the decision then parks
4014
+ * the run on a `call` that is already complete. Nothing would ever wake it again — only the
4015
+ * `reconcileMs` orphan sweep, minutes later.
4016
+ *
4017
+ * The tell is precise: the turn (re)declared a blocking op whose checkpoint is already settled. A
4018
+ * turn with an up-to-date history never does that, because a settled op is in its history and the
4019
+ * replay skips it. That also makes this convergent — the re-driven turn sees the op resolved and
4020
+ * cannot re-declare it, so one lost wake costs exactly one extra turn, never a loop.
4021
+ *
4022
+ * Scheduled off the current stack so it runs after the caller released the run lease.
4023
+ */
4024
+ private redriveIfTurnSawStaleHistory;
4007
4025
  /** The run's resolved durable ops as replay inputs: completed/failed steps + elapsed timers. */
4008
4026
  private remoteHistory;
4009
4027
  /** Apply a turn's commands: persist recorded local steps, dispatch remote calls, schedule timers.
package/dist/index.js CHANGED
@@ -4836,10 +4836,42 @@ var WorkflowEngine = class {
4836
4836
  };
4837
4837
  }
4838
4838
  const wakeAt = await this.applyCommands(run, decision.commands);
4839
- return this.settleRun(run, {
4839
+ const suspended = await this.settleRun(run, {
4840
4840
  kind: "suspended",
4841
4841
  wakeAt
4842
4842
  });
4843
+ await this.redriveIfTurnSawStaleHistory(run.id, decision.commands);
4844
+ return suspended;
4845
+ }
4846
+ /**
4847
+ * Re-drive a run whose turn was computed from a history OLDER than the store's.
4848
+ *
4849
+ * A turn replays against a SNAPSHOT, and an op can settle while its decision is in flight. The
4850
+ * canonical case is a remote `gather_calls` fan-out whose last call lands during the decision's
4851
+ * lease window: `completeRemoteResult` -> `resume` -> `execute` finds the lease held and returns
4852
+ * silently, so that call's wake is spent with nothing to show for it, and the decision then parks
4853
+ * the run on a `call` that is already complete. Nothing would ever wake it again — only the
4854
+ * `reconcileMs` orphan sweep, minutes later.
4855
+ *
4856
+ * The tell is precise: the turn (re)declared a blocking op whose checkpoint is already settled. A
4857
+ * turn with an up-to-date history never does that, because a settled op is in its history and the
4858
+ * replay skips it. That also makes this convergent — the re-driven turn sees the op resolved and
4859
+ * cannot re-declare it, so one lost wake costs exactly one extra turn, never a loop.
4860
+ *
4861
+ * Scheduled off the current stack so it runs after the caller released the run lease.
4862
+ */
4863
+ async redriveIfTurnSawStaleHistory(runId, commands) {
4864
+ const blocking = commands.filter((cmd) => cmd.kind === "call" || cmd.kind === "startChild" || cmd.kind === "waitSignal");
4865
+ if (blocking.length === 0) return;
4866
+ for (const cmd of blocking) {
4867
+ const checkpoint = await this.store.getCheckpoint(runId, cmd.seq);
4868
+ if (checkpoint && checkpoint.status !== "pending") {
4869
+ setTimeout(() => {
4870
+ void this.resume(runId).catch(() => void 0);
4871
+ }, 0).unref?.();
4872
+ return;
4873
+ }
4874
+ }
4843
4875
  }
4844
4876
  /** The run's resolved durable ops as replay inputs: completed/failed steps + elapsed timers. */
4845
4877
  async remoteHistory(runId) {