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