@dudousxd/nestjs-durable-core 0.70.0 → 0.70.2

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.cjs CHANGED
@@ -4977,10 +4977,42 @@ var WorkflowEngine = class {
4977
4977
  };
4978
4978
  }
4979
4979
  const wakeAt = await this.applyCommands(run, decision.commands);
4980
- return this.settleRun(run, {
4980
+ const suspended = await this.settleRun(run, {
4981
4981
  kind: "suspended",
4982
4982
  wakeAt
4983
4983
  });
4984
+ await this.redriveIfTurnSawStaleHistory(run.id, decision.commands);
4985
+ return suspended;
4986
+ }
4987
+ /**
4988
+ * Re-drive a run whose turn was computed from a history OLDER than the store's.
4989
+ *
4990
+ * A turn replays against a SNAPSHOT, and an op can settle while its decision is in flight. The
4991
+ * canonical case is a remote `gather_calls` fan-out whose last call lands during the decision's
4992
+ * lease window: `completeRemoteResult` -> `resume` -> `execute` finds the lease held and returns
4993
+ * silently, so that call's wake is spent with nothing to show for it, and the decision then parks
4994
+ * the run on a `call` that is already complete. Nothing would ever wake it again — only the
4995
+ * `reconcileMs` orphan sweep, minutes later.
4996
+ *
4997
+ * The tell is precise: the turn (re)declared a blocking op whose checkpoint is already settled. A
4998
+ * turn with an up-to-date history never does that, because a settled op is in its history and the
4999
+ * replay skips it. That also makes this convergent — the re-driven turn sees the op resolved and
5000
+ * cannot re-declare it, so one lost wake costs exactly one extra turn, never a loop.
5001
+ *
5002
+ * Scheduled off the current stack so it runs after the caller released the run lease.
5003
+ */
5004
+ async redriveIfTurnSawStaleHistory(runId, commands) {
5005
+ const blocking = commands.filter((cmd) => cmd.kind === "call" || cmd.kind === "startChild" || cmd.kind === "waitSignal");
5006
+ if (blocking.length === 0) return;
5007
+ for (const cmd of blocking) {
5008
+ const checkpoint = await this.store.getCheckpoint(runId, cmd.seq);
5009
+ if (checkpoint && checkpoint.status !== "pending") {
5010
+ setTimeout(() => {
5011
+ void this.resume(runId).catch(() => void 0);
5012
+ }, 0).unref?.();
5013
+ return;
5014
+ }
5015
+ }
4984
5016
  }
4985
5017
  /** The run's resolved durable ops as replay inputs: completed/failed steps + elapsed timers. */
4986
5018
  async remoteHistory(runId) {