@dudousxd/nestjs-durable-core 0.75.0 → 0.75.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
@@ -3861,6 +3861,12 @@ var WorkflowEngine = class {
3861
3861
  updateValidators = /* @__PURE__ */ new Map();
3862
3862
  /** Runs being cancelled WITH saga compensation — see `cancel({ compensate: true })`. */
3863
3863
  cancelRequested = /* @__PURE__ */ new Set();
3864
+ /**
3865
+ * Seqs of the dispatched remote steps a turn suspended on, by run, while that turn executes on
3866
+ * THIS instance — so the turn can re-check them once its lease is released (see
3867
+ * {@link redriveSettledSteps}).
3868
+ */
3869
+ awaitedRemoteSteps = /* @__PURE__ */ new Map();
3864
3870
  /** Flow-control admission backend for remote steps (see {@link registerQueue}). */
3865
3871
  admission;
3866
3872
  /** Runs on THIS instance blocked on admission, by queue — woken early on a freed-slot signal. */
@@ -3872,6 +3878,8 @@ var WorkflowEngine = class {
3872
3878
  draining = false;
3873
3879
  /** Wait already spent on this shutdown before `drain()` (see `beginDrain`). */
3874
3880
  drainSpentMs = 0;
3881
+ /** Re-arms the deadline of every `drain()` currently waiting, when more wait is reported. */
3882
+ drainRearms = /* @__PURE__ */ new Set();
3875
3883
  constructor(deps) {
3876
3884
  this.store = deps.store;
3877
3885
  this.pool = new TransportPool(deps.transports ?? (deps.transport ? [
@@ -4557,7 +4565,10 @@ var WorkflowEngine = class {
4557
4565
  */
4558
4566
  beginDrain(spentMs = 0) {
4559
4567
  this.draining = true;
4560
- this.drainSpentMs += Math.max(0, spentMs);
4568
+ if (Number.isFinite(spentMs) && spentMs > 0) {
4569
+ this.drainSpentMs += spentMs;
4570
+ for (const rearm of this.drainRearms) rearm();
4571
+ }
4561
4572
  }
4562
4573
  /**
4563
4574
  * Graceful shutdown: stop picking up new runs (recovery/timer become no-ops) and wait for
@@ -4570,18 +4581,32 @@ var WorkflowEngine = class {
4570
4581
  */
4571
4582
  async drain(timeoutMs = 1e4) {
4572
4583
  this.beginDrain();
4573
- const budget = Math.max(0, timeoutMs - this.drainSpentMs);
4574
4584
  if (this.inflight.size === 0) return;
4585
+ const start = Date.now();
4586
+ let rearm = /* @__PURE__ */ __name(() => {
4587
+ }, "rearm");
4588
+ let timeout;
4575
4589
  const timer = new Promise((resolve) => {
4576
- const t = setTimeout(resolve, budget);
4577
- t.unref?.();
4590
+ rearm = /* @__PURE__ */ __name(() => {
4591
+ clearTimeout(timeout);
4592
+ const remaining = Math.max(0, start + timeoutMs - this.drainSpentMs - Date.now());
4593
+ timeout = setTimeout(resolve, remaining);
4594
+ timeout.unref?.();
4595
+ }, "rearm");
4596
+ rearm();
4578
4597
  });
4579
- await Promise.race([
4580
- Promise.allSettled([
4581
- ...this.inflight
4582
- ]),
4583
- timer
4584
- ]);
4598
+ this.drainRearms.add(rearm);
4599
+ try {
4600
+ await Promise.race([
4601
+ Promise.allSettled([
4602
+ ...this.inflight
4603
+ ]),
4604
+ timer
4605
+ ]);
4606
+ } finally {
4607
+ this.drainRearms.delete(rearm);
4608
+ clearTimeout(timeout);
4609
+ }
4585
4610
  }
4586
4611
  /**
4587
4612
  * Cancel in-flight runs that have outlived their workflow's `executionTimeout`. Call it from the
@@ -5245,6 +5270,24 @@ var WorkflowEngine = class {
5245
5270
  * itself. Only re-drives for a group with no waiter left whose checkpoints are all settled, which
5246
5271
  * the next turn is then guaranteed to see — so it cannot loop.
5247
5272
  */
5273
+ /**
5274
+ * Re-drive a run that just suspended on dispatched remote steps which, by now, have a result.
5275
+ *
5276
+ * Same race as {@link redriveSettledChildren}, for `ctx.step`: a result landing while the turn
5277
+ * that dispatched it still holds the lease has its wake dropped by {@link execute}, and the turn
5278
+ * then suspends with nothing left to wake it before the step's redispatch deadline. Called after
5279
+ * the lease is released, so any later result wakes the run itself; it only re-drives for a
5280
+ * checkpoint that is already settled, which the next turn replays past — so it cannot loop.
5281
+ */
5282
+ async redriveSettledSteps(runId, seqs) {
5283
+ for (const seq of seqs) {
5284
+ const cp = await this.store.getCheckpoint(runId, seq);
5285
+ if (cp && (cp.status === "completed" || cp.status === "failed")) {
5286
+ await this.resumeImplicit(runId);
5287
+ return;
5288
+ }
5289
+ }
5290
+ }
5248
5291
  async redriveSettledChildren(runId, fan) {
5249
5292
  if (!this.store.hasSignalWaiter) return;
5250
5293
  for (const wait of fan.waits) {
@@ -6699,6 +6742,9 @@ var WorkflowEngine = class {
6699
6742
  const replay = /* @__PURE__ */ new Map();
6700
6743
  for (const cp of snapshot) replay.set(cp.seq, cp);
6701
6744
  let suspendedOnFan;
6745
+ let suspendedOnSteps = false;
6746
+ const awaitedSteps = /* @__PURE__ */ new Set();
6747
+ this.awaitedRemoteSteps.set(run.id, awaitedSteps);
6702
6748
  const ctx = createWorkflowCtx(this.ctxHostFor(replay, run.namespace), run.id, compensations, run.workflow, registered?.searchAttributesSchema);
6703
6749
  try {
6704
6750
  const output = await runInWorkflowCtx(ctx, () => fn(ctx, run.input));
@@ -6755,6 +6801,7 @@ var WorkflowEngine = class {
6755
6801
  wakeAt: err.wakeAt
6756
6802
  });
6757
6803
  suspendedOnFan = err.fan;
6804
+ suspendedOnSteps = suspended.status === "suspended";
6758
6805
  return suspended;
6759
6806
  }
6760
6807
  if (this.cancelRequested.has(run.id)) {
@@ -6774,10 +6821,16 @@ var WorkflowEngine = class {
6774
6821
  error: error2
6775
6822
  });
6776
6823
  } finally {
6824
+ if (this.awaitedRemoteSteps.get(run.id) === awaitedSteps) {
6825
+ this.awaitedRemoteSteps.delete(run.id);
6826
+ }
6777
6827
  await this.store.releaseRunLock(run.id);
6778
6828
  if (suspendedOnFan) {
6779
6829
  void this.redriveSettledChildren(run.id, suspendedOnFan).catch(() => void 0);
6780
6830
  }
6831
+ if (suspendedOnSteps && awaitedSteps.size > 0) {
6832
+ void this.redriveSettledSteps(run.id, awaitedSteps).catch(() => void 0);
6833
+ }
6781
6834
  }
6782
6835
  }
6783
6836
  /**
@@ -7197,6 +7250,7 @@ var WorkflowEngine = class {
7197
7250
  if (this.pool.size === 0) throw new Error("remote steps require a Transport");
7198
7251
  if (step.timeoutMs) return this.callRemoteInMemory(runId, seq, step, input, transport, group);
7199
7252
  if (existing?.status === "pending") {
7253
+ this.awaitedRemoteSteps.get(runId)?.add(seq);
7200
7254
  if (this.remoteRedispatchMs == null) throw new WorkflowSuspended();
7201
7255
  if (existing.wakeAt == null) {
7202
7256
  const wakeAt = this.clock() + this.remoteRedispatchMs;
@@ -7317,6 +7371,7 @@ var WorkflowEngine = class {
7317
7371
  name: step.name,
7318
7372
  kind: "remote"
7319
7373
  });
7374
+ this.awaitedRemoteSteps.get(runId)?.add(seq);
7320
7375
  throw new WorkflowSuspended();
7321
7376
  }
7322
7377
  /** Enqueue a remote step task to its worker group via the transport pool. Shared by the initial