@dudousxd/nestjs-durable-core 0.85.2 → 0.85.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
@@ -5704,6 +5704,22 @@ declare class WorkflowEngine {
5704
5704
  private markStarted;
5705
5705
  private singletonWaitResult;
5706
5706
  private execute;
5707
+ /**
5708
+ * Keep a wake that found `runId`'s lease held by a turn executing elsewhere, durably, so that turn
5709
+ * runs again once it ends instead of suspending on a snapshot that predates what the wake brought
5710
+ * (a signal, a child's completion). The request is a buffered signal on a token no workflow waits
5711
+ * on; the holder takes it after it releases the lease ({@link honorWakeRequests}).
5712
+ *
5713
+ * Written first and looked at second, against a holder that releases first and looks second:
5714
+ * whichever look runs last sees the other side's write. So when the run is no longer executing by
5715
+ * the time this re-reads it, the holder may have looked already, and this side takes the request
5716
+ * and wakes the run itself once the lease is free.
5717
+ */
5718
+ private requestWakeAfterTurn;
5719
+ /** After a turn that suspended released its lease: run again if a wake was kept for it. */
5720
+ private honorWakeRequests;
5721
+ /** Take every wake request kept for `runId`; true when there was one. */
5722
+ private takeWakeRequests;
5707
5723
  /**
5708
5724
  * A wake that found the run leased by another instance is dropped, and normally that is fine: the
5709
5725
  * holder's turn either sees what the wake wrote, or re-checks once it releases the lease
package/dist/index.d.ts CHANGED
@@ -5704,6 +5704,22 @@ declare class WorkflowEngine {
5704
5704
  private markStarted;
5705
5705
  private singletonWaitResult;
5706
5706
  private execute;
5707
+ /**
5708
+ * Keep a wake that found `runId`'s lease held by a turn executing elsewhere, durably, so that turn
5709
+ * runs again once it ends instead of suspending on a snapshot that predates what the wake brought
5710
+ * (a signal, a child's completion). The request is a buffered signal on a token no workflow waits
5711
+ * on; the holder takes it after it releases the lease ({@link honorWakeRequests}).
5712
+ *
5713
+ * Written first and looked at second, against a holder that releases first and looks second:
5714
+ * whichever look runs last sees the other side's write. So when the run is no longer executing by
5715
+ * the time this re-reads it, the holder may have looked already, and this side takes the request
5716
+ * and wakes the run itself once the lease is free.
5717
+ */
5718
+ private requestWakeAfterTurn;
5719
+ /** After a turn that suspended released its lease: run again if a wake was kept for it. */
5720
+ private honorWakeRequests;
5721
+ /** Take every wake request kept for `runId`; true when there was one. */
5722
+ private takeWakeRequests;
5707
5723
  /**
5708
5724
  * A wake that found the run leased by another instance is dropped, and normally that is fine: the
5709
5725
  * holder's turn either sees what the wake wrote, or re-checks once it releases the lease
package/dist/index.js CHANGED
@@ -3824,7 +3824,6 @@ function createWorkflowCtx(host, runId, compensations, workflow = "", searchAttr
3824
3824
  }));
3825
3825
  return unwrapCompletion(buffered.payload, `child "${id}"`);
3826
3826
  }
3827
- if (!await store.getRun(id)) host.startChild(workflowName(workflow2), input, id, priority, version);
3828
3827
  if (!existing) {
3829
3828
  await writeCheckpoint(instantCheckpoint({
3830
3829
  runId,
@@ -3834,6 +3833,7 @@ function createWorkflowCtx(host, runId, compensations, workflow = "", searchAttr
3834
3833
  status: "running"
3835
3834
  }));
3836
3835
  }
3836
+ if (!await store.getRun(id)) host.startChild(workflowName(workflow2), input, id, priority, version);
3837
3837
  throw new WorkflowSuspended(void 0, fanJoin([
3838
3838
  {
3839
3839
  waiterPrefix: `child:${id}`,
@@ -4452,6 +4452,7 @@ function deferred() {
4452
4452
  __name(deferred, "deferred");
4453
4453
  var pendingStateKey = /* @__PURE__ */ __name((at) => String(new Date(at).getTime()), "pendingStateKey");
4454
4454
  var freshPendingKey = /* @__PURE__ */ __name(() => `${Date.now()}-${globalThis.crypto.randomUUID().slice(0, 8)}`, "freshPendingKey");
4455
+ var wakeRequestToken = /* @__PURE__ */ __name((runId) => `durable:wake-after-turn:${runId}`, "wakeRequestToken");
4455
4456
  var CHILD_SIGNAL_PREFIX = "signal:child:";
4456
4457
  var DELIVER_CHUNK = 50;
4457
4458
  function childCompletion(child) {
@@ -7638,6 +7639,7 @@ var WorkflowEngine = class {
7638
7639
  const lockNow = this.clock();
7639
7640
  if (run.lockedBy !== this.instanceId || (run.lockedUntil ?? 0) <= lockNow) {
7640
7641
  if (!await this.store.tryLockRun(run.id, this.instanceId, lockNow + this.leaseMs, lockNow)) {
7642
+ if (!this.leaseRetries.has(run.id)) this.inBackground(this.requestWakeAfterTurn(run.id));
7641
7643
  this.recheckAfterLease(run.id, run.lockedUntil);
7642
7644
  return {
7643
7645
  runId: run.id,
@@ -7651,12 +7653,52 @@ var WorkflowEngine = class {
7651
7653
  if (registered?.singleton && (result.status === "completed" || result.status === "failed" || result.status === "cancelled" || result.status === "dead")) {
7652
7654
  void this.singletons.wakeNext(run).catch(() => void 0);
7653
7655
  }
7656
+ if (result.status === "suspended") this.inBackground(this.honorWakeRequests(run.id));
7657
+ else if (TERMINAL_RUN_STATUSES.includes(result.status)) {
7658
+ this.inBackground(this.takeWakeRequests(run.id));
7659
+ }
7654
7660
  return result;
7655
7661
  } finally {
7656
7662
  held.stop();
7657
7663
  }
7658
7664
  }
7659
7665
  /**
7666
+ * Keep a wake that found `runId`'s lease held by a turn executing elsewhere, durably, so that turn
7667
+ * runs again once it ends instead of suspending on a snapshot that predates what the wake brought
7668
+ * (a signal, a child's completion). The request is a buffered signal on a token no workflow waits
7669
+ * on; the holder takes it after it releases the lease ({@link honorWakeRequests}).
7670
+ *
7671
+ * Written first and looked at second, against a holder that releases first and looks second:
7672
+ * whichever look runs last sees the other side's write. So when the run is no longer executing by
7673
+ * the time this re-reads it, the holder may have looked already, and this side takes the request
7674
+ * and wakes the run itself once the lease is free.
7675
+ */
7676
+ async requestWakeAfterTurn(runId) {
7677
+ await this.store.bufferSignal(wakeRequestToken(runId), null);
7678
+ const run = await this.store.getRun(runId);
7679
+ if (run?.status === "running") return;
7680
+ if (!run) {
7681
+ await this.takeWakeRequests(runId);
7682
+ return;
7683
+ }
7684
+ if (!await this.takeWakeRequests(runId)) return;
7685
+ if (TERMINAL_RUN_STATUSES.includes(run.status)) return;
7686
+ this.recheckAfterLease(runId, run.lockedUntil);
7687
+ }
7688
+ /** After a turn that suspended released its lease: run again if a wake was kept for it. */
7689
+ async honorWakeRequests(runId) {
7690
+ if (!await this.takeWakeRequests(runId)) return;
7691
+ await this.wake(runId).catch((err) => {
7692
+ if (!(err instanceof NamespaceMismatch)) throw err;
7693
+ });
7694
+ }
7695
+ /** Take every wake request kept for `runId`; true when there was one. */
7696
+ async takeWakeRequests(runId) {
7697
+ let taken = false;
7698
+ while (await this.store.takeBufferedSignal(wakeRequestToken(runId))) taken = true;
7699
+ return taken;
7700
+ }
7701
+ /**
7660
7702
  * A wake that found the run leased by another instance is dropped, and normally that is fine: the
7661
7703
  * holder's turn either sees what the wake wrote, or re-checks once it releases the lease
7662
7704
  * ({@link redriveSettledSteps}, {@link redriveSettledChildren}). Not if the holder goes down first