@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.d.cts CHANGED
@@ -947,12 +947,13 @@ interface WorkflowRun {
947
947
  * from the creating engine. It is DERIVED, never caller input: `StartOptions` has no `origin`, so
948
948
  * nothing a caller passes to `start` can make a run claim another lib's name.
949
949
  *
950
- * `undefined` means UNKNOWN — never "the app". It is what you get for a run created before this
950
+ * `undefined` means NO package origin — the console labels that bucket `app` (the app's own
951
+ * workflows, not declared by any library). It is what you get for a run created before this
951
952
  * field existed, for a registration path that carries no origin (`registerRemote`, convention
952
953
  * routing, a synthesized remote child, or a bare `engine.register` with no `origin` option), and
953
954
  * for a workflow whose declaring package could not be resolved with confidence. A wrong origin is
954
955
  * worse than an absent one: a filter that silently drops runs looks exactly like runs that never
955
- * existed, so callers must render unknown as unknown and never fold it into a real package.
956
+ * existed, so callers must keep the no-origin bucket distinct and never fold it into a real package.
956
957
  */
957
958
  origin?: string | undefined;
958
959
  /**
@@ -1747,11 +1748,11 @@ interface RunQuery {
1747
1748
  * Restrict to runs whose workflow was declared by this package (exact match against
1748
1749
  * {@link WorkflowRun.origin}), ANDed with the other predicates.
1749
1750
  *
1750
- * A run with NO origin (unknown — see the field) matches no `origin` VALUE at all, so a UI facet
1751
+ * A run with NO origin (the console's `app` bucket — see the field) matches no `origin` VALUE at all, so a UI facet
1751
1752
  * built on this must keep an "all origins" option: filtering by any single package silently hides
1752
1753
  * every unattributed run, which reads to an operator as "those runs do not exist". `null` selects
1753
- * exactly that absent bucket (`origin IS NULL`), so a paginated console can offer an "unknown"
1754
- * facet without having to hold every run in the browser to find them.
1754
+ * exactly that absent bucket (`origin IS NULL`), so a paginated console can offer an `app`
1755
+ * (no-origin) facet without having to hold every run in the browser to find them.
1755
1756
  */
1756
1757
  origin?: string | null | undefined;
1757
1758
  /**
@@ -5704,6 +5705,22 @@ declare class WorkflowEngine {
5704
5705
  private markStarted;
5705
5706
  private singletonWaitResult;
5706
5707
  private execute;
5708
+ /**
5709
+ * Keep a wake that found `runId`'s lease held by a turn executing elsewhere, durably, so that turn
5710
+ * runs again once it ends instead of suspending on a snapshot that predates what the wake brought
5711
+ * (a signal, a child's completion). The request is a buffered signal on a token no workflow waits
5712
+ * on; the holder takes it after it releases the lease ({@link honorWakeRequests}).
5713
+ *
5714
+ * Written first and looked at second, against a holder that releases first and looks second:
5715
+ * whichever look runs last sees the other side's write. So when the run is no longer executing by
5716
+ * the time this re-reads it, the holder may have looked already, and this side takes the request
5717
+ * and wakes the run itself once the lease is free.
5718
+ */
5719
+ private requestWakeAfterTurn;
5720
+ /** After a turn that suspended released its lease: run again if a wake was kept for it. */
5721
+ private honorWakeRequests;
5722
+ /** Take every wake request kept for `runId`; true when there was one. */
5723
+ private takeWakeRequests;
5707
5724
  /**
5708
5725
  * A wake that found the run leased by another instance is dropped, and normally that is fine: the
5709
5726
  * holder's turn either sees what the wake wrote, or re-checks once it releases the lease
package/dist/index.d.ts CHANGED
@@ -947,12 +947,13 @@ interface WorkflowRun {
947
947
  * from the creating engine. It is DERIVED, never caller input: `StartOptions` has no `origin`, so
948
948
  * nothing a caller passes to `start` can make a run claim another lib's name.
949
949
  *
950
- * `undefined` means UNKNOWN — never "the app". It is what you get for a run created before this
950
+ * `undefined` means NO package origin — the console labels that bucket `app` (the app's own
951
+ * workflows, not declared by any library). It is what you get for a run created before this
951
952
  * field existed, for a registration path that carries no origin (`registerRemote`, convention
952
953
  * routing, a synthesized remote child, or a bare `engine.register` with no `origin` option), and
953
954
  * for a workflow whose declaring package could not be resolved with confidence. A wrong origin is
954
955
  * worse than an absent one: a filter that silently drops runs looks exactly like runs that never
955
- * existed, so callers must render unknown as unknown and never fold it into a real package.
956
+ * existed, so callers must keep the no-origin bucket distinct and never fold it into a real package.
956
957
  */
957
958
  origin?: string | undefined;
958
959
  /**
@@ -1747,11 +1748,11 @@ interface RunQuery {
1747
1748
  * Restrict to runs whose workflow was declared by this package (exact match against
1748
1749
  * {@link WorkflowRun.origin}), ANDed with the other predicates.
1749
1750
  *
1750
- * A run with NO origin (unknown — see the field) matches no `origin` VALUE at all, so a UI facet
1751
+ * A run with NO origin (the console's `app` bucket — see the field) matches no `origin` VALUE at all, so a UI facet
1751
1752
  * built on this must keep an "all origins" option: filtering by any single package silently hides
1752
1753
  * every unattributed run, which reads to an operator as "those runs do not exist". `null` selects
1753
- * exactly that absent bucket (`origin IS NULL`), so a paginated console can offer an "unknown"
1754
- * facet without having to hold every run in the browser to find them.
1754
+ * exactly that absent bucket (`origin IS NULL`), so a paginated console can offer an `app`
1755
+ * (no-origin) facet without having to hold every run in the browser to find them.
1755
1756
  */
1756
1757
  origin?: string | null | undefined;
1757
1758
  /**
@@ -5704,6 +5705,22 @@ declare class WorkflowEngine {
5704
5705
  private markStarted;
5705
5706
  private singletonWaitResult;
5706
5707
  private execute;
5708
+ /**
5709
+ * Keep a wake that found `runId`'s lease held by a turn executing elsewhere, durably, so that turn
5710
+ * runs again once it ends instead of suspending on a snapshot that predates what the wake brought
5711
+ * (a signal, a child's completion). The request is a buffered signal on a token no workflow waits
5712
+ * on; the holder takes it after it releases the lease ({@link honorWakeRequests}).
5713
+ *
5714
+ * Written first and looked at second, against a holder that releases first and looks second:
5715
+ * whichever look runs last sees the other side's write. So when the run is no longer executing by
5716
+ * the time this re-reads it, the holder may have looked already, and this side takes the request
5717
+ * and wakes the run itself once the lease is free.
5718
+ */
5719
+ private requestWakeAfterTurn;
5720
+ /** After a turn that suspended released its lease: run again if a wake was kept for it. */
5721
+ private honorWakeRequests;
5722
+ /** Take every wake request kept for `runId`; true when there was one. */
5723
+ private takeWakeRequests;
5707
5724
  /**
5708
5725
  * A wake that found the run leased by another instance is dropped, and normally that is fine: the
5709
5726
  * 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