@dudousxd/nestjs-durable-core 0.85.2 → 0.85.4

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
@@ -4037,7 +4037,6 @@ function createWorkflowCtx(host, runId, compensations, workflow = "", searchAttr
4037
4037
  }));
4038
4038
  return unwrapCompletion(buffered.payload, `child "${id}"`);
4039
4039
  }
4040
- if (!await store.getRun(id)) host.startChild(workflowName(workflow2), input, id, priority, version);
4041
4040
  if (!existing) {
4042
4041
  await writeCheckpoint(instantCheckpoint({
4043
4042
  runId,
@@ -4047,6 +4046,7 @@ function createWorkflowCtx(host, runId, compensations, workflow = "", searchAttr
4047
4046
  status: "running"
4048
4047
  }));
4049
4048
  }
4049
+ if (!await store.getRun(id)) host.startChild(workflowName(workflow2), input, id, priority, version);
4050
4050
  throw new WorkflowSuspended(void 0, fanJoin([
4051
4051
  {
4052
4052
  waiterPrefix: `child:${id}`,
@@ -4665,6 +4665,7 @@ function deferred() {
4665
4665
  __name(deferred, "deferred");
4666
4666
  var pendingStateKey = /* @__PURE__ */ __name((at) => String(new Date(at).getTime()), "pendingStateKey");
4667
4667
  var freshPendingKey = /* @__PURE__ */ __name(() => `${Date.now()}-${globalThis.crypto.randomUUID().slice(0, 8)}`, "freshPendingKey");
4668
+ var wakeRequestToken = /* @__PURE__ */ __name((runId) => `durable:wake-after-turn:${runId}`, "wakeRequestToken");
4668
4669
  var CHILD_SIGNAL_PREFIX = "signal:child:";
4669
4670
  var DELIVER_CHUNK = 50;
4670
4671
  function childCompletion(child) {
@@ -7851,6 +7852,7 @@ var WorkflowEngine = class {
7851
7852
  const lockNow = this.clock();
7852
7853
  if (run.lockedBy !== this.instanceId || (run.lockedUntil ?? 0) <= lockNow) {
7853
7854
  if (!await this.store.tryLockRun(run.id, this.instanceId, lockNow + this.leaseMs, lockNow)) {
7855
+ if (!this.leaseRetries.has(run.id)) this.inBackground(this.requestWakeAfterTurn(run.id));
7854
7856
  this.recheckAfterLease(run.id, run.lockedUntil);
7855
7857
  return {
7856
7858
  runId: run.id,
@@ -7864,12 +7866,52 @@ var WorkflowEngine = class {
7864
7866
  if (registered?.singleton && (result.status === "completed" || result.status === "failed" || result.status === "cancelled" || result.status === "dead")) {
7865
7867
  void this.singletons.wakeNext(run).catch(() => void 0);
7866
7868
  }
7869
+ if (result.status === "suspended") this.inBackground(this.honorWakeRequests(run.id));
7870
+ else if (TERMINAL_RUN_STATUSES.includes(result.status)) {
7871
+ this.inBackground(this.takeWakeRequests(run.id));
7872
+ }
7867
7873
  return result;
7868
7874
  } finally {
7869
7875
  held.stop();
7870
7876
  }
7871
7877
  }
7872
7878
  /**
7879
+ * Keep a wake that found `runId`'s lease held by a turn executing elsewhere, durably, so that turn
7880
+ * runs again once it ends instead of suspending on a snapshot that predates what the wake brought
7881
+ * (a signal, a child's completion). The request is a buffered signal on a token no workflow waits
7882
+ * on; the holder takes it after it releases the lease ({@link honorWakeRequests}).
7883
+ *
7884
+ * Written first and looked at second, against a holder that releases first and looks second:
7885
+ * whichever look runs last sees the other side's write. So when the run is no longer executing by
7886
+ * the time this re-reads it, the holder may have looked already, and this side takes the request
7887
+ * and wakes the run itself once the lease is free.
7888
+ */
7889
+ async requestWakeAfterTurn(runId) {
7890
+ await this.store.bufferSignal(wakeRequestToken(runId), null);
7891
+ const run = await this.store.getRun(runId);
7892
+ if (run?.status === "running") return;
7893
+ if (!run) {
7894
+ await this.takeWakeRequests(runId);
7895
+ return;
7896
+ }
7897
+ if (!await this.takeWakeRequests(runId)) return;
7898
+ if (TERMINAL_RUN_STATUSES.includes(run.status)) return;
7899
+ this.recheckAfterLease(runId, run.lockedUntil);
7900
+ }
7901
+ /** After a turn that suspended released its lease: run again if a wake was kept for it. */
7902
+ async honorWakeRequests(runId) {
7903
+ if (!await this.takeWakeRequests(runId)) return;
7904
+ await this.wake(runId).catch((err) => {
7905
+ if (!(err instanceof NamespaceMismatch)) throw err;
7906
+ });
7907
+ }
7908
+ /** Take every wake request kept for `runId`; true when there was one. */
7909
+ async takeWakeRequests(runId) {
7910
+ let taken = false;
7911
+ while (await this.store.takeBufferedSignal(wakeRequestToken(runId))) taken = true;
7912
+ return taken;
7913
+ }
7914
+ /**
7873
7915
  * A wake that found the run leased by another instance is dropped, and normally that is fine: the
7874
7916
  * holder's turn either sees what the wake wrote, or re-checks once it releases the lease
7875
7917
  * ({@link redriveSettledSteps}, {@link redriveSettledChildren}). Not if the holder goes down first