@dudousxd/nestjs-durable-core 0.74.0 → 0.75.0

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
@@ -2350,12 +2350,14 @@ var ScheduleClient = class {
2350
2350
  const store = this.requireStore();
2351
2351
  const started = [];
2352
2352
  for (let round = 0; round < 50; round++) {
2353
+ if (this.host.isDraining?.()) break;
2353
2354
  const due = await store.listSchedules({
2354
2355
  dueBy: nowMs,
2355
2356
  namespace: this.options.namespace,
2356
2357
  limit: TICK_BATCH
2357
2358
  });
2358
2359
  for (const record of due) {
2360
+ if (this.host.isDraining?.()) return started;
2359
2361
  const runId = await this.fire(record, nowMs);
2360
2362
  if (runId) started.push(runId);
2361
2363
  }
@@ -3868,6 +3870,8 @@ var WorkflowEngine = class {
3868
3870
  /** Executions currently in flight, so a graceful shutdown can wait for them to settle. */
3869
3871
  inflight = /* @__PURE__ */ new Set();
3870
3872
  draining = false;
3873
+ /** Wait already spent on this shutdown before `drain()` (see `beginDrain`). */
3874
+ drainSpentMs = 0;
3871
3875
  constructor(deps) {
3872
3876
  this.store = deps.store;
3873
3877
  this.pool = new TransportPool(deps.transports ?? (deps.transport ? [
@@ -3916,7 +3920,8 @@ var WorkflowEngine = class {
3916
3920
  });
3917
3921
  this.schedules = new ScheduleClient(this.store, {
3918
3922
  start: /* @__PURE__ */ __name((workflow, input, runId, opts) => this.start(workflow, input, runId, opts), "start"),
3919
- getRun: /* @__PURE__ */ __name((runId) => this.store.getRun(runId), "getRun")
3923
+ getRun: /* @__PURE__ */ __name((runId) => this.store.getRun(runId), "getRun"),
3924
+ isDraining: /* @__PURE__ */ __name(() => this.draining, "isDraining")
3920
3925
  }, {
3921
3926
  namespace: this.namespace,
3922
3927
  clock: this.clock
@@ -4541,15 +4546,34 @@ var WorkflowEngine = class {
4541
4546
  return p;
4542
4547
  }
4543
4548
  /**
4549
+ * Stop picking up new work without waiting: recovery, timer, schedule and timeout sweeps (and
4550
+ * their per-run lock loops) start nothing once this is called, and {@link runOne} returns null. A
4551
+ * poller calls it on its own shutdown so an in-flight sweep stops locking runs before it is awaited.
4552
+ *
4553
+ * `spentMs` is wait time ALREADY spent on this shutdown (the poller awaiting its in-flight sweep).
4554
+ * It is deducted from the budget of a later {@link drain}, so that wait plus the drain stay within
4555
+ * one `shutdownTimeoutMs`. Only time actually spent waiting is charged: time that merely passes
4556
+ * between this call and `drain()` (other hooks, the HTTP server closing) never is. Calls add up.
4557
+ */
4558
+ beginDrain(spentMs = 0) {
4559
+ this.draining = true;
4560
+ this.drainSpentMs += Math.max(0, spentMs);
4561
+ }
4562
+ /**
4544
4563
  * Graceful shutdown: stop picking up new runs (recovery/timer become no-ops) and wait for
4545
4564
  * in-flight executions to settle, up to `timeoutMs`. Call from your app's shutdown hook so a
4546
4565
  * deploy hands off cleanly instead of leaving runs to the lease timeout.
4566
+ *
4567
+ * `timeoutMs` is the total budget for waiting on in-flight runs; any wait already reported through
4568
+ * {@link beginDrain} (`spentMs`) is deducted, so a shutdown that already waited on the timer poll
4569
+ * for part of it waits only for the remainder. With nothing reported, the full `timeoutMs` applies.
4547
4570
  */
4548
4571
  async drain(timeoutMs = 1e4) {
4549
- this.draining = true;
4572
+ this.beginDrain();
4573
+ const budget = Math.max(0, timeoutMs - this.drainSpentMs);
4550
4574
  if (this.inflight.size === 0) return;
4551
4575
  const timer = new Promise((resolve) => {
4552
- const t = setTimeout(resolve, timeoutMs);
4576
+ const t = setTimeout(resolve, budget);
4553
4577
  t.unref?.();
4554
4578
  });
4555
4579
  await Promise.race([
@@ -4591,6 +4615,7 @@ var WorkflowEngine = class {
4591
4615
  */
4592
4616
  async sweepTimeouts(now = this.clock()) {
4593
4617
  for (const reg of new Set(this.latest.values())) {
4618
+ if (this.draining) return;
4594
4619
  if (reg.executionTimeoutMs == null) continue;
4595
4620
  const deadline = now - reg.executionTimeoutMs;
4596
4621
  const inflight = [
@@ -4606,6 +4631,7 @@ var WorkflowEngine = class {
4606
4631
  })
4607
4632
  ];
4608
4633
  for (const run of inflight) {
4634
+ if (this.draining) return;
4609
4635
  if (run.createdAt.getTime() > deadline) continue;
4610
4636
  const error2 = {
4611
4637
  message: "execution timeout",
@@ -4635,6 +4661,7 @@ var WorkflowEngine = class {
4635
4661
  if (this.draining) return [];
4636
4662
  const results = [];
4637
4663
  for (const listed of await this.store.listIncompleteRuns(this.namespace)) {
4664
+ if (this.draining) break;
4638
4665
  const acquired = await this.store.tryLockRun(listed.id, this.instanceId, nowMs + this.leaseMs, nowMs);
4639
4666
  if (!acquired) continue;
4640
4667
  const run = await this.store.getRun(listed.id);
@@ -5070,8 +5097,13 @@ var WorkflowEngine = class {
5070
5097
  if (this.draining) return [];
5071
5098
  const results = [];
5072
5099
  for (const run of runs) {
5100
+ if (this.draining) break;
5073
5101
  const acquired = await this.store.tryLockRun(run.id, this.instanceId, nowMs + this.leaseMs, nowMs);
5074
5102
  if (!acquired) continue;
5103
+ if (this.draining) {
5104
+ await this.store.releaseRunLock(run.id);
5105
+ break;
5106
+ }
5075
5107
  try {
5076
5108
  const settled = onLocked ? await onLocked(run) : void 0;
5077
5109
  results.push(settled ?? await this.resumeImplicit(run.id));