@adaptic/utils 0.0.1003 → 0.0.1005
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 +51 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +51 -2
- package/dist/index.mjs.map +1 -1
- package/dist/types/__tests__/stampede-load-timeout.test.d.ts +2 -0
- package/dist/types/__tests__/stampede-load-timeout.test.d.ts.map +1 -0
- package/dist/types/cache/stampede-protected-cache.d.ts +28 -0
- package/dist/types/cache/stampede-protected-cache.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -63155,6 +63155,7 @@ class StampedeProtectedCache {
|
|
|
63155
63155
|
coalescedRequests: 0,
|
|
63156
63156
|
backgroundRefreshes: 0,
|
|
63157
63157
|
refreshErrors: 0,
|
|
63158
|
+
loadTimeouts: 0,
|
|
63158
63159
|
};
|
|
63159
63160
|
constructor(options) {
|
|
63160
63161
|
this.options = {
|
|
@@ -63428,6 +63429,7 @@ class StampedeProtectedCache {
|
|
|
63428
63429
|
coalescedRequests: this.stats.coalescedRequests,
|
|
63429
63430
|
backgroundRefreshes: this.stats.backgroundRefreshes,
|
|
63430
63431
|
refreshErrors: this.stats.refreshErrors,
|
|
63432
|
+
loadTimeouts: this.stats.loadTimeouts,
|
|
63431
63433
|
};
|
|
63432
63434
|
}
|
|
63433
63435
|
/**
|
|
@@ -63478,8 +63480,9 @@ class StampedeProtectedCache {
|
|
|
63478
63480
|
this.options.logger.debug("Request coalesced", { key });
|
|
63479
63481
|
return existingPromise;
|
|
63480
63482
|
}
|
|
63481
|
-
// Create new promise and store it
|
|
63482
|
-
|
|
63483
|
+
// Create new promise and store it — bounded by loadTimeoutMs so a
|
|
63484
|
+
// never-settling loader cannot pin the key (see loadWithTimeout).
|
|
63485
|
+
const promise = this.loadWithTimeout(key, loader, ttl);
|
|
63483
63486
|
this.pendingRefreshes.set(key, promise);
|
|
63484
63487
|
try {
|
|
63485
63488
|
const result = await promise;
|
|
@@ -63490,6 +63493,51 @@ class StampedeProtectedCache {
|
|
|
63490
63493
|
this.pendingRefreshes.delete(key);
|
|
63491
63494
|
}
|
|
63492
63495
|
}
|
|
63496
|
+
/**
|
|
63497
|
+
* Race {@link loadAndCache} against the configured load timeout.
|
|
63498
|
+
*
|
|
63499
|
+
* On timeout: rejects with a typed timeout error, evicts the pending
|
|
63500
|
+
* single-flight entry so the NEXT caller retries with a fresh loader
|
|
63501
|
+
* (instead of coalescing onto the hung one), and attaches a settlement
|
|
63502
|
+
* observer to the abandoned loader so its eventual resolution/rejection
|
|
63503
|
+
* is logged rather than surfacing as an unhandled rejection.
|
|
63504
|
+
*/
|
|
63505
|
+
async loadWithTimeout(key, loader, ttl) {
|
|
63506
|
+
const timeoutMs = this.options.loadTimeoutMs ?? 30000;
|
|
63507
|
+
const loadPromise = this.loadAndCache(key, loader, ttl);
|
|
63508
|
+
let timeoutHandle;
|
|
63509
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
63510
|
+
timeoutHandle = setTimeout(() => {
|
|
63511
|
+
this.stats.loadTimeouts++;
|
|
63512
|
+
// Evict the pin so the next caller retries fresh.
|
|
63513
|
+
this.pendingRefreshes.delete(key);
|
|
63514
|
+
this.options.logger.warn("Cache loader timed out — pin evicted", {
|
|
63515
|
+
key,
|
|
63516
|
+
timeoutMs,
|
|
63517
|
+
});
|
|
63518
|
+
// Observe the abandoned loader; never let it become unhandled.
|
|
63519
|
+
loadPromise
|
|
63520
|
+
.then(() => {
|
|
63521
|
+
this.options.logger.warn("Abandoned cache loader eventually resolved", { key });
|
|
63522
|
+
})
|
|
63523
|
+
.catch((error) => {
|
|
63524
|
+
this.options.logger.warn("Abandoned cache loader eventually rejected", {
|
|
63525
|
+
key,
|
|
63526
|
+
error: error instanceof Error ? error.message : String(error),
|
|
63527
|
+
});
|
|
63528
|
+
});
|
|
63529
|
+
reject(new Error(`StampedeProtectedCache loader timed out after ${timeoutMs}ms for key "${key}"`));
|
|
63530
|
+
}, timeoutMs);
|
|
63531
|
+
});
|
|
63532
|
+
try {
|
|
63533
|
+
return await Promise.race([loadPromise, timeoutPromise]);
|
|
63534
|
+
}
|
|
63535
|
+
finally {
|
|
63536
|
+
if (timeoutHandle !== undefined) {
|
|
63537
|
+
clearTimeout(timeoutHandle);
|
|
63538
|
+
}
|
|
63539
|
+
}
|
|
63540
|
+
}
|
|
63493
63541
|
/**
|
|
63494
63542
|
* Load data and cache it
|
|
63495
63543
|
*/
|
|
@@ -63639,6 +63687,7 @@ const DEFAULT_CACHE_OPTIONS = {
|
|
|
63639
63687
|
minJitter: 0.9, // 90%
|
|
63640
63688
|
maxJitter: 1.1, // 110%
|
|
63641
63689
|
enableBackgroundRefresh: true,
|
|
63690
|
+
loadTimeoutMs: 30000, // 30s hard loader ceiling (anti-pinning)
|
|
63642
63691
|
};
|
|
63643
63692
|
|
|
63644
63693
|
/**
|