@dudousxd/nestjs-durable-core 0.74.0 → 0.75.1
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 +61 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +21 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +61 -10
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -3385,6 +3385,8 @@ declare class ScheduleNotFoundError extends Error {
|
|
|
3385
3385
|
interface ScheduleHost {
|
|
3386
3386
|
start(workflow: string, input: unknown, runId: string, opts?: StartOptions): Promise<RunResult>;
|
|
3387
3387
|
getRun(runId: string): Promise<WorkflowRun | null>;
|
|
3388
|
+
/** True once the engine has begun shutting down; `tick` fires nothing more after that. */
|
|
3389
|
+
isDraining?(): boolean;
|
|
3388
3390
|
}
|
|
3389
3391
|
/** Engine-level knobs for the client. */
|
|
3390
3392
|
interface ScheduleClientOptions {
|
|
@@ -3797,6 +3799,10 @@ declare class WorkflowEngine {
|
|
|
3797
3799
|
/** Executions currently in flight, so a graceful shutdown can wait for them to settle. */
|
|
3798
3800
|
private readonly inflight;
|
|
3799
3801
|
private draining;
|
|
3802
|
+
/** Wait already spent on this shutdown before `drain()` (see `beginDrain`). */
|
|
3803
|
+
private drainSpentMs;
|
|
3804
|
+
/** Re-arms the deadline of every `drain()` currently waiting, when more wait is reported. */
|
|
3805
|
+
private readonly drainRearms;
|
|
3800
3806
|
constructor(deps: WorkflowEngineDeps);
|
|
3801
3807
|
/**
|
|
3802
3808
|
* Register a **durable entity** (a virtual object): a keyed actor whose `handlers` run **serialized
|
|
@@ -4050,10 +4056,25 @@ declare class WorkflowEngine {
|
|
|
4050
4056
|
private findParentRunId;
|
|
4051
4057
|
/** Track an in-flight execution so {@link drain} can wait for it. */
|
|
4052
4058
|
private track;
|
|
4059
|
+
/**
|
|
4060
|
+
* Stop picking up new work without waiting: recovery, timer, schedule and timeout sweeps (and
|
|
4061
|
+
* their per-run lock loops) start nothing once this is called, and {@link runOne} returns null. A
|
|
4062
|
+
* poller calls it on its own shutdown so an in-flight sweep stops locking runs before it is awaited.
|
|
4063
|
+
*
|
|
4064
|
+
* `spentMs` is wait time ALREADY spent on this shutdown (the poller awaiting its in-flight sweep).
|
|
4065
|
+
* It is deducted from the budget of a later {@link drain}, so that wait plus the drain stay within
|
|
4066
|
+
* one `shutdownTimeoutMs`. Only time actually spent waiting is charged: time that merely passes
|
|
4067
|
+
* between this call and `drain()` (other hooks, the HTTP server closing) never is. Calls add up.
|
|
4068
|
+
*/
|
|
4069
|
+
beginDrain(spentMs?: number): void;
|
|
4053
4070
|
/**
|
|
4054
4071
|
* Graceful shutdown: stop picking up new runs (recovery/timer become no-ops) and wait for
|
|
4055
4072
|
* in-flight executions to settle, up to `timeoutMs`. Call from your app's shutdown hook so a
|
|
4056
4073
|
* deploy hands off cleanly instead of leaving runs to the lease timeout.
|
|
4074
|
+
*
|
|
4075
|
+
* `timeoutMs` is the total budget for waiting on in-flight runs; any wait already reported through
|
|
4076
|
+
* {@link beginDrain} (`spentMs`) is deducted, so a shutdown that already waited on the timer poll
|
|
4077
|
+
* for part of it waits only for the remainder. With nothing reported, the full `timeoutMs` applies.
|
|
4057
4078
|
*/
|
|
4058
4079
|
drain(timeoutMs?: number): Promise<void>;
|
|
4059
4080
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -3385,6 +3385,8 @@ declare class ScheduleNotFoundError extends Error {
|
|
|
3385
3385
|
interface ScheduleHost {
|
|
3386
3386
|
start(workflow: string, input: unknown, runId: string, opts?: StartOptions): Promise<RunResult>;
|
|
3387
3387
|
getRun(runId: string): Promise<WorkflowRun | null>;
|
|
3388
|
+
/** True once the engine has begun shutting down; `tick` fires nothing more after that. */
|
|
3389
|
+
isDraining?(): boolean;
|
|
3388
3390
|
}
|
|
3389
3391
|
/** Engine-level knobs for the client. */
|
|
3390
3392
|
interface ScheduleClientOptions {
|
|
@@ -3797,6 +3799,10 @@ declare class WorkflowEngine {
|
|
|
3797
3799
|
/** Executions currently in flight, so a graceful shutdown can wait for them to settle. */
|
|
3798
3800
|
private readonly inflight;
|
|
3799
3801
|
private draining;
|
|
3802
|
+
/** Wait already spent on this shutdown before `drain()` (see `beginDrain`). */
|
|
3803
|
+
private drainSpentMs;
|
|
3804
|
+
/** Re-arms the deadline of every `drain()` currently waiting, when more wait is reported. */
|
|
3805
|
+
private readonly drainRearms;
|
|
3800
3806
|
constructor(deps: WorkflowEngineDeps);
|
|
3801
3807
|
/**
|
|
3802
3808
|
* Register a **durable entity** (a virtual object): a keyed actor whose `handlers` run **serialized
|
|
@@ -4050,10 +4056,25 @@ declare class WorkflowEngine {
|
|
|
4050
4056
|
private findParentRunId;
|
|
4051
4057
|
/** Track an in-flight execution so {@link drain} can wait for it. */
|
|
4052
4058
|
private track;
|
|
4059
|
+
/**
|
|
4060
|
+
* Stop picking up new work without waiting: recovery, timer, schedule and timeout sweeps (and
|
|
4061
|
+
* their per-run lock loops) start nothing once this is called, and {@link runOne} returns null. A
|
|
4062
|
+
* poller calls it on its own shutdown so an in-flight sweep stops locking runs before it is awaited.
|
|
4063
|
+
*
|
|
4064
|
+
* `spentMs` is wait time ALREADY spent on this shutdown (the poller awaiting its in-flight sweep).
|
|
4065
|
+
* It is deducted from the budget of a later {@link drain}, so that wait plus the drain stay within
|
|
4066
|
+
* one `shutdownTimeoutMs`. Only time actually spent waiting is charged: time that merely passes
|
|
4067
|
+
* between this call and `drain()` (other hooks, the HTTP server closing) never is. Calls add up.
|
|
4068
|
+
*/
|
|
4069
|
+
beginDrain(spentMs?: number): void;
|
|
4053
4070
|
/**
|
|
4054
4071
|
* Graceful shutdown: stop picking up new runs (recovery/timer become no-ops) and wait for
|
|
4055
4072
|
* in-flight executions to settle, up to `timeoutMs`. Call from your app's shutdown hook so a
|
|
4056
4073
|
* deploy hands off cleanly instead of leaving runs to the lease timeout.
|
|
4074
|
+
*
|
|
4075
|
+
* `timeoutMs` is the total budget for waiting on in-flight runs; any wait already reported through
|
|
4076
|
+
* {@link beginDrain} (`spentMs`) is deducted, so a shutdown that already waited on the timer poll
|
|
4077
|
+
* for part of it waits only for the remainder. With nothing reported, the full `timeoutMs` applies.
|
|
4057
4078
|
*/
|
|
4058
4079
|
drain(timeoutMs?: number): Promise<void>;
|
|
4059
4080
|
/**
|
package/dist/index.js
CHANGED
|
@@ -2187,12 +2187,14 @@ var ScheduleClient = class {
|
|
|
2187
2187
|
const store = this.requireStore();
|
|
2188
2188
|
const started = [];
|
|
2189
2189
|
for (let round = 0; round < 50; round++) {
|
|
2190
|
+
if (this.host.isDraining?.()) break;
|
|
2190
2191
|
const due = await store.listSchedules({
|
|
2191
2192
|
dueBy: nowMs,
|
|
2192
2193
|
namespace: this.options.namespace,
|
|
2193
2194
|
limit: TICK_BATCH
|
|
2194
2195
|
});
|
|
2195
2196
|
for (const record of due) {
|
|
2197
|
+
if (this.host.isDraining?.()) return started;
|
|
2196
2198
|
const runId = await this.fire(record, nowMs);
|
|
2197
2199
|
if (runId) started.push(runId);
|
|
2198
2200
|
}
|
|
@@ -3705,6 +3707,10 @@ var WorkflowEngine = class {
|
|
|
3705
3707
|
/** Executions currently in flight, so a graceful shutdown can wait for them to settle. */
|
|
3706
3708
|
inflight = /* @__PURE__ */ new Set();
|
|
3707
3709
|
draining = false;
|
|
3710
|
+
/** Wait already spent on this shutdown before `drain()` (see `beginDrain`). */
|
|
3711
|
+
drainSpentMs = 0;
|
|
3712
|
+
/** Re-arms the deadline of every `drain()` currently waiting, when more wait is reported. */
|
|
3713
|
+
drainRearms = /* @__PURE__ */ new Set();
|
|
3708
3714
|
constructor(deps) {
|
|
3709
3715
|
this.store = deps.store;
|
|
3710
3716
|
this.pool = new TransportPool(deps.transports ?? (deps.transport ? [
|
|
@@ -3753,7 +3759,8 @@ var WorkflowEngine = class {
|
|
|
3753
3759
|
});
|
|
3754
3760
|
this.schedules = new ScheduleClient(this.store, {
|
|
3755
3761
|
start: /* @__PURE__ */ __name((workflow, input, runId, opts) => this.start(workflow, input, runId, opts), "start"),
|
|
3756
|
-
getRun: /* @__PURE__ */ __name((runId) => this.store.getRun(runId), "getRun")
|
|
3762
|
+
getRun: /* @__PURE__ */ __name((runId) => this.store.getRun(runId), "getRun"),
|
|
3763
|
+
isDraining: /* @__PURE__ */ __name(() => this.draining, "isDraining")
|
|
3757
3764
|
}, {
|
|
3758
3765
|
namespace: this.namespace,
|
|
3759
3766
|
clock: this.clock
|
|
@@ -4378,23 +4385,59 @@ var WorkflowEngine = class {
|
|
|
4378
4385
|
return p;
|
|
4379
4386
|
}
|
|
4380
4387
|
/**
|
|
4388
|
+
* Stop picking up new work without waiting: recovery, timer, schedule and timeout sweeps (and
|
|
4389
|
+
* their per-run lock loops) start nothing once this is called, and {@link runOne} returns null. A
|
|
4390
|
+
* poller calls it on its own shutdown so an in-flight sweep stops locking runs before it is awaited.
|
|
4391
|
+
*
|
|
4392
|
+
* `spentMs` is wait time ALREADY spent on this shutdown (the poller awaiting its in-flight sweep).
|
|
4393
|
+
* It is deducted from the budget of a later {@link drain}, so that wait plus the drain stay within
|
|
4394
|
+
* one `shutdownTimeoutMs`. Only time actually spent waiting is charged: time that merely passes
|
|
4395
|
+
* between this call and `drain()` (other hooks, the HTTP server closing) never is. Calls add up.
|
|
4396
|
+
*/
|
|
4397
|
+
beginDrain(spentMs = 0) {
|
|
4398
|
+
this.draining = true;
|
|
4399
|
+
if (Number.isFinite(spentMs) && spentMs > 0) {
|
|
4400
|
+
this.drainSpentMs += spentMs;
|
|
4401
|
+
for (const rearm of this.drainRearms) rearm();
|
|
4402
|
+
}
|
|
4403
|
+
}
|
|
4404
|
+
/**
|
|
4381
4405
|
* Graceful shutdown: stop picking up new runs (recovery/timer become no-ops) and wait for
|
|
4382
4406
|
* in-flight executions to settle, up to `timeoutMs`. Call from your app's shutdown hook so a
|
|
4383
4407
|
* deploy hands off cleanly instead of leaving runs to the lease timeout.
|
|
4408
|
+
*
|
|
4409
|
+
* `timeoutMs` is the total budget for waiting on in-flight runs; any wait already reported through
|
|
4410
|
+
* {@link beginDrain} (`spentMs`) is deducted, so a shutdown that already waited on the timer poll
|
|
4411
|
+
* for part of it waits only for the remainder. With nothing reported, the full `timeoutMs` applies.
|
|
4384
4412
|
*/
|
|
4385
4413
|
async drain(timeoutMs = 1e4) {
|
|
4386
|
-
this.
|
|
4414
|
+
this.beginDrain();
|
|
4387
4415
|
if (this.inflight.size === 0) return;
|
|
4416
|
+
const start = Date.now();
|
|
4417
|
+
let rearm = /* @__PURE__ */ __name(() => {
|
|
4418
|
+
}, "rearm");
|
|
4419
|
+
let timeout;
|
|
4388
4420
|
const timer = new Promise((resolve) => {
|
|
4389
|
-
|
|
4390
|
-
|
|
4421
|
+
rearm = /* @__PURE__ */ __name(() => {
|
|
4422
|
+
clearTimeout(timeout);
|
|
4423
|
+
const remaining = Math.max(0, start + timeoutMs - this.drainSpentMs - Date.now());
|
|
4424
|
+
timeout = setTimeout(resolve, remaining);
|
|
4425
|
+
timeout.unref?.();
|
|
4426
|
+
}, "rearm");
|
|
4427
|
+
rearm();
|
|
4391
4428
|
});
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4429
|
+
this.drainRearms.add(rearm);
|
|
4430
|
+
try {
|
|
4431
|
+
await Promise.race([
|
|
4432
|
+
Promise.allSettled([
|
|
4433
|
+
...this.inflight
|
|
4434
|
+
]),
|
|
4435
|
+
timer
|
|
4436
|
+
]);
|
|
4437
|
+
} finally {
|
|
4438
|
+
this.drainRearms.delete(rearm);
|
|
4439
|
+
clearTimeout(timeout);
|
|
4440
|
+
}
|
|
4398
4441
|
}
|
|
4399
4442
|
/**
|
|
4400
4443
|
* Cancel in-flight runs that have outlived their workflow's `executionTimeout`. Call it from the
|
|
@@ -4428,6 +4471,7 @@ var WorkflowEngine = class {
|
|
|
4428
4471
|
*/
|
|
4429
4472
|
async sweepTimeouts(now = this.clock()) {
|
|
4430
4473
|
for (const reg of new Set(this.latest.values())) {
|
|
4474
|
+
if (this.draining) return;
|
|
4431
4475
|
if (reg.executionTimeoutMs == null) continue;
|
|
4432
4476
|
const deadline = now - reg.executionTimeoutMs;
|
|
4433
4477
|
const inflight = [
|
|
@@ -4443,6 +4487,7 @@ var WorkflowEngine = class {
|
|
|
4443
4487
|
})
|
|
4444
4488
|
];
|
|
4445
4489
|
for (const run of inflight) {
|
|
4490
|
+
if (this.draining) return;
|
|
4446
4491
|
if (run.createdAt.getTime() > deadline) continue;
|
|
4447
4492
|
const error2 = {
|
|
4448
4493
|
message: "execution timeout",
|
|
@@ -4472,6 +4517,7 @@ var WorkflowEngine = class {
|
|
|
4472
4517
|
if (this.draining) return [];
|
|
4473
4518
|
const results = [];
|
|
4474
4519
|
for (const listed of await this.store.listIncompleteRuns(this.namespace)) {
|
|
4520
|
+
if (this.draining) break;
|
|
4475
4521
|
const acquired = await this.store.tryLockRun(listed.id, this.instanceId, nowMs + this.leaseMs, nowMs);
|
|
4476
4522
|
if (!acquired) continue;
|
|
4477
4523
|
const run = await this.store.getRun(listed.id);
|
|
@@ -4907,8 +4953,13 @@ var WorkflowEngine = class {
|
|
|
4907
4953
|
if (this.draining) return [];
|
|
4908
4954
|
const results = [];
|
|
4909
4955
|
for (const run of runs) {
|
|
4956
|
+
if (this.draining) break;
|
|
4910
4957
|
const acquired = await this.store.tryLockRun(run.id, this.instanceId, nowMs + this.leaseMs, nowMs);
|
|
4911
4958
|
if (!acquired) continue;
|
|
4959
|
+
if (this.draining) {
|
|
4960
|
+
await this.store.releaseRunLock(run.id);
|
|
4961
|
+
break;
|
|
4962
|
+
}
|
|
4912
4963
|
try {
|
|
4913
4964
|
const settled = onLocked ? await onLocked(run) : void 0;
|
|
4914
4965
|
results.push(settled ?? await this.resumeImplicit(run.id));
|