@dudousxd/nestjs-durable 0.45.1 → 0.46.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 +85 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +86 -15
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/dist/index.cjs
CHANGED
|
@@ -1259,6 +1259,31 @@ ProxyRunGateway = _ts_decorate6([
|
|
|
1259
1259
|
// src/retention-poller.ts
|
|
1260
1260
|
var import_nestjs_durable_core8 = require("@dudousxd/nestjs-durable-core");
|
|
1261
1261
|
var import_common7 = require("@nestjs/common");
|
|
1262
|
+
|
|
1263
|
+
// src/settle-within.ts
|
|
1264
|
+
async function settleWithin(p, timeoutMs) {
|
|
1265
|
+
let timer;
|
|
1266
|
+
let timedOut = false;
|
|
1267
|
+
const timeout = new Promise((resolve) => {
|
|
1268
|
+
timer = setTimeout(() => {
|
|
1269
|
+
timedOut = true;
|
|
1270
|
+
resolve();
|
|
1271
|
+
}, timeoutMs);
|
|
1272
|
+
timer.unref?.();
|
|
1273
|
+
});
|
|
1274
|
+
try {
|
|
1275
|
+
await Promise.race([
|
|
1276
|
+
p.then(void 0, () => void 0),
|
|
1277
|
+
timeout
|
|
1278
|
+
]);
|
|
1279
|
+
} finally {
|
|
1280
|
+
if (timer) clearTimeout(timer);
|
|
1281
|
+
}
|
|
1282
|
+
return timedOut;
|
|
1283
|
+
}
|
|
1284
|
+
__name(settleWithin, "settleWithin");
|
|
1285
|
+
|
|
1286
|
+
// src/retention-poller.ts
|
|
1262
1287
|
function _ts_decorate7(decorators, target, key, desc) {
|
|
1263
1288
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1264
1289
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
|
|
@@ -1329,6 +1354,9 @@ var RetentionPoller = class {
|
|
|
1329
1354
|
options;
|
|
1330
1355
|
timer;
|
|
1331
1356
|
sweeping = false;
|
|
1357
|
+
stopping = false;
|
|
1358
|
+
/** The sweep in flight, so shutdown can wait for it before the store closes. */
|
|
1359
|
+
current;
|
|
1332
1360
|
constructor(store, options) {
|
|
1333
1361
|
this.store = store;
|
|
1334
1362
|
this.options = options;
|
|
@@ -1356,8 +1384,16 @@ var RetentionPoller = class {
|
|
|
1356
1384
|
this.timer.unref?.();
|
|
1357
1385
|
}
|
|
1358
1386
|
}
|
|
1359
|
-
|
|
1387
|
+
/** Stop sweeping and wait for the sweep in flight (bounded by `shutdownTimeoutMs`, default 10s),
|
|
1388
|
+
* so no prune is still pending when the host closes its connection pool. */
|
|
1389
|
+
async onModuleDestroy() {
|
|
1360
1390
|
if (this.timer) clearInterval(this.timer);
|
|
1391
|
+
this.stopping = true;
|
|
1392
|
+
if (!this.current) return;
|
|
1393
|
+
const budget = this.options.shutdownTimeoutMs ?? 1e4;
|
|
1394
|
+
if (await settleWithin(this.current, budget)) {
|
|
1395
|
+
console.warn(`[nestjs-durable] retention sweep still running after ${budget}ms at shutdown; continuing`);
|
|
1396
|
+
}
|
|
1361
1397
|
}
|
|
1362
1398
|
/**
|
|
1363
1399
|
* Prune one round.
|
|
@@ -1368,8 +1404,16 @@ var RetentionPoller = class {
|
|
|
1368
1404
|
* Pruning history is the least urgent thing this package does — a `pruneTerminalRuns` that fails
|
|
1369
1405
|
* on a locked table must cost this sweep and nothing more.
|
|
1370
1406
|
*/
|
|
1371
|
-
|
|
1372
|
-
if (this.sweeping) return;
|
|
1407
|
+
sweep() {
|
|
1408
|
+
if (this.sweeping) return this.current ?? Promise.resolve();
|
|
1409
|
+
const run = this.sweepOnce().finally(() => {
|
|
1410
|
+
if (this.current === run) this.current = void 0;
|
|
1411
|
+
});
|
|
1412
|
+
this.current = run;
|
|
1413
|
+
return run;
|
|
1414
|
+
}
|
|
1415
|
+
async sweepOnce() {
|
|
1416
|
+
if (this.stopping) return;
|
|
1373
1417
|
const retention = this.options.retention;
|
|
1374
1418
|
const store = this.requireStore();
|
|
1375
1419
|
const prune = store.pruneTerminalRuns;
|
|
@@ -1380,6 +1424,7 @@ var RetentionPoller = class {
|
|
|
1380
1424
|
const now = Date.now();
|
|
1381
1425
|
for (const policy of retention.policies) {
|
|
1382
1426
|
for (let batch = 0; batch < MAX_BATCHES_PER_POLICY; batch++) {
|
|
1427
|
+
if (this.stopping) return;
|
|
1383
1428
|
const deleted = await prune.call(store, policy, now, batchSize);
|
|
1384
1429
|
if (deleted < batchSize) break;
|
|
1385
1430
|
}
|
|
@@ -1612,7 +1657,7 @@ var StoreRunGateway = class {
|
|
|
1612
1657
|
}
|
|
1613
1658
|
async listRuns(query) {
|
|
1614
1659
|
const runs = await this.store.listRuns(query);
|
|
1615
|
-
const waiterByRun = (0, import_nestjs_durable_core9.indexWaitersByRun)(await this.store.
|
|
1660
|
+
const waiterByRun = (0, import_nestjs_durable_core9.indexWaitersByRun)(await (0, import_nestjs_durable_core9.listSignalWaitersOfRuns)(this.store, runs.filter((run) => run.status === "suspended").map((run) => run.id)));
|
|
1616
1661
|
return runs.map((run) => {
|
|
1617
1662
|
const waiting = (0, import_nestjs_durable_core9.resolveRunWaiting)(run, waiterByRun);
|
|
1618
1663
|
return waiting ? {
|
|
@@ -1649,12 +1694,12 @@ var StoreRunGateway = class {
|
|
|
1649
1694
|
/**
|
|
1650
1695
|
* Bulk-resolve what each of `runIds` is currently parked on — for a consumer with its own filtered/
|
|
1651
1696
|
* paginated run listing (e.g. "which of MY suspended runs are stuck at a breakpoint") without
|
|
1652
|
-
* re-deriving `listRuns`' waiter
|
|
1653
|
-
* `listRuns`' waiting computation (the
|
|
1697
|
+
* re-deriving `listRuns`' waiter read or querying `durable_step_checkpoints` directly. Mirrors
|
|
1698
|
+
* `listRuns`' waiting computation (the requested runs' signal waiters + `resolveRunWaiting`), but ALSO
|
|
1654
1699
|
* bulk-fetches the currently-suspended runs to check real status: `engine.cancel` (the non-compensate
|
|
1655
1700
|
* path) doesn't clear a run's signal waiter row, so a cancelled run can leave an ORPHANED waiter
|
|
1656
1701
|
* behind — trusting waiter presence alone would wrongly report a terminal run as still waiting.
|
|
1657
|
-
* Two bulk
|
|
1702
|
+
* Two bulk reads total (never one query per requested id), same as `listRuns`.
|
|
1658
1703
|
*/
|
|
1659
1704
|
async waitingFor(runIds) {
|
|
1660
1705
|
if (runIds.length === 0) return {};
|
|
@@ -1665,7 +1710,7 @@ var StoreRunGateway = class {
|
|
|
1665
1710
|
"suspended"
|
|
1666
1711
|
]
|
|
1667
1712
|
}),
|
|
1668
|
-
this.store
|
|
1713
|
+
(0, import_nestjs_durable_core9.listSignalWaitersOfRuns)(this.store, runIds)
|
|
1669
1714
|
]);
|
|
1670
1715
|
const waiterByRun = (0, import_nestjs_durable_core9.indexWaitersByRun)(waiters);
|
|
1671
1716
|
const result = {};
|
|
@@ -1791,6 +1836,8 @@ var TimerPoller = class {
|
|
|
1791
1836
|
options;
|
|
1792
1837
|
timer;
|
|
1793
1838
|
polling = false;
|
|
1839
|
+
/** The sweep in flight, so shutdown can wait for it instead of racing the store's close. */
|
|
1840
|
+
current;
|
|
1794
1841
|
unsubscribeEnqueued;
|
|
1795
1842
|
constructor(engine, options) {
|
|
1796
1843
|
this.engine = engine;
|
|
@@ -1810,9 +1857,27 @@ var TimerPoller = class {
|
|
|
1810
1857
|
this.timer.unref?.();
|
|
1811
1858
|
}
|
|
1812
1859
|
}
|
|
1813
|
-
|
|
1860
|
+
/**
|
|
1861
|
+
* Stop polling and wait for the sweep in flight (bounded by `shutdownTimeoutMs`, default 10s).
|
|
1862
|
+
* Nest runs every `onModuleDestroy` before any `onApplicationShutdown`, so returning here only once
|
|
1863
|
+
* the sweep has settled means no store call is still pending when the registrar drains and the
|
|
1864
|
+
* host closes its connection pool. The engine is told to stop first so a sweep working through a
|
|
1865
|
+
* backlog stops locking runs instead of finishing the backlog. The time spent waiting is deducted
|
|
1866
|
+
* from the later `engine.drain()`, so poll wait plus drain stay within one `shutdownTimeoutMs`.
|
|
1867
|
+
*/
|
|
1868
|
+
async onModuleDestroy() {
|
|
1814
1869
|
if (this.timer) clearInterval(this.timer);
|
|
1815
1870
|
this.unsubscribeEnqueued?.();
|
|
1871
|
+
if (!isDrivingOperator(this.options)) return;
|
|
1872
|
+
this.engine.beginDrain?.();
|
|
1873
|
+
if (!this.current) return;
|
|
1874
|
+
const budget = this.options.shutdownTimeoutMs ?? 1e4;
|
|
1875
|
+
const started = Date.now();
|
|
1876
|
+
const timedOut = await settleWithin(this.current, budget);
|
|
1877
|
+
this.engine.beginDrain?.(Date.now() - started);
|
|
1878
|
+
if (timedOut) {
|
|
1879
|
+
console.warn(`[nestjs-durable] timer poll still running after ${budget}ms at shutdown; continuing (it may report store errors if the host ends its pool now)`);
|
|
1880
|
+
}
|
|
1816
1881
|
}
|
|
1817
1882
|
/**
|
|
1818
1883
|
* One sweep of the engine's recovery work.
|
|
@@ -1829,10 +1894,18 @@ var TimerPoller = class {
|
|
|
1829
1894
|
* `recoverIncomplete` says nothing about whether durable timers should still fire, and skipping
|
|
1830
1895
|
* the rest of the tick would silently widen one subsystem's outage into all of them.
|
|
1831
1896
|
*/
|
|
1832
|
-
|
|
1833
|
-
if (this.polling) return;
|
|
1897
|
+
poll() {
|
|
1898
|
+
if (this.polling) return this.current ?? Promise.resolve();
|
|
1834
1899
|
this.polling = true;
|
|
1835
|
-
|
|
1900
|
+
const run = this.pollOnce().finally(() => {
|
|
1901
|
+
this.polling = false;
|
|
1902
|
+
if (this.current === run) this.current = void 0;
|
|
1903
|
+
});
|
|
1904
|
+
this.current = run;
|
|
1905
|
+
return run;
|
|
1906
|
+
}
|
|
1907
|
+
async pollOnce() {
|
|
1908
|
+
{
|
|
1836
1909
|
await this.sweep("runPending", () => this.engine.runPending());
|
|
1837
1910
|
await this.sweep("recoverIncomplete", () => this.engine.recoverIncomplete());
|
|
1838
1911
|
await this.sweep("resumeDueTimers", () => this.engine.resumeDueTimers());
|
|
@@ -1844,8 +1917,6 @@ var TimerPoller = class {
|
|
|
1844
1917
|
if (this.options.persistedSchedules) {
|
|
1845
1918
|
await this.sweep("persistedSchedules", () => this.engine.schedules.tick());
|
|
1846
1919
|
}
|
|
1847
|
-
} finally {
|
|
1848
|
-
this.polling = false;
|
|
1849
1920
|
}
|
|
1850
1921
|
}
|
|
1851
1922
|
/** Run one sweep, absorbing and reporting its failure — see {@link poll}. */
|