layercache 1.2.6 → 1.2.7
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/README.md +1 -1
- package/dist/{edge-DLstcDMn.d.cts → edge-BMmPVqaD.d.cts} +2 -13
- package/dist/{edge-DLstcDMn.d.ts → edge-BMmPVqaD.d.ts} +2 -13
- package/dist/edge.d.cts +1 -1
- package/dist/edge.d.ts +1 -1
- package/dist/index.cjs +552 -436
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +393 -277
- package/package.json +1 -1
- package/packages/nestjs/dist/index.cjs +549 -435
- package/packages/nestjs/dist/index.d.cts +2 -13
- package/packages/nestjs/dist/index.d.ts +2 -13
- package/packages/nestjs/dist/index.js +549 -435
|
@@ -223,6 +223,125 @@ var Mutex = class {
|
|
|
223
223
|
}
|
|
224
224
|
};
|
|
225
225
|
|
|
226
|
+
// ../../src/internal/CacheNamespaceMetrics.ts
|
|
227
|
+
function createEmptyNamespaceMetrics(resetAt = Date.now()) {
|
|
228
|
+
return {
|
|
229
|
+
hits: 0,
|
|
230
|
+
misses: 0,
|
|
231
|
+
fetches: 0,
|
|
232
|
+
sets: 0,
|
|
233
|
+
deletes: 0,
|
|
234
|
+
backfills: 0,
|
|
235
|
+
invalidations: 0,
|
|
236
|
+
staleHits: 0,
|
|
237
|
+
refreshes: 0,
|
|
238
|
+
refreshErrors: 0,
|
|
239
|
+
writeFailures: 0,
|
|
240
|
+
singleFlightWaits: 0,
|
|
241
|
+
negativeCacheHits: 0,
|
|
242
|
+
circuitBreakerTrips: 0,
|
|
243
|
+
degradedOperations: 0,
|
|
244
|
+
hitsByLayer: {},
|
|
245
|
+
missesByLayer: {},
|
|
246
|
+
latencyByLayer: {},
|
|
247
|
+
resetAt
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
function cloneNamespaceMetrics(metrics) {
|
|
251
|
+
return {
|
|
252
|
+
...metrics,
|
|
253
|
+
hitsByLayer: { ...metrics.hitsByLayer },
|
|
254
|
+
missesByLayer: { ...metrics.missesByLayer },
|
|
255
|
+
latencyByLayer: Object.fromEntries(
|
|
256
|
+
Object.entries(metrics.latencyByLayer).map(([key, value]) => [key, { ...value }])
|
|
257
|
+
)
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
function diffNamespaceMetrics(before, after) {
|
|
261
|
+
const latencyByLayer = Object.fromEntries(
|
|
262
|
+
Object.entries(after.latencyByLayer).map(([layer, value]) => [
|
|
263
|
+
layer,
|
|
264
|
+
{
|
|
265
|
+
avgMs: value.avgMs,
|
|
266
|
+
maxMs: value.maxMs,
|
|
267
|
+
count: Math.max(0, value.count - (before.latencyByLayer[layer]?.count ?? 0))
|
|
268
|
+
}
|
|
269
|
+
])
|
|
270
|
+
);
|
|
271
|
+
return {
|
|
272
|
+
hits: after.hits - before.hits,
|
|
273
|
+
misses: after.misses - before.misses,
|
|
274
|
+
fetches: after.fetches - before.fetches,
|
|
275
|
+
sets: after.sets - before.sets,
|
|
276
|
+
deletes: after.deletes - before.deletes,
|
|
277
|
+
backfills: after.backfills - before.backfills,
|
|
278
|
+
invalidations: after.invalidations - before.invalidations,
|
|
279
|
+
staleHits: after.staleHits - before.staleHits,
|
|
280
|
+
refreshes: after.refreshes - before.refreshes,
|
|
281
|
+
refreshErrors: after.refreshErrors - before.refreshErrors,
|
|
282
|
+
writeFailures: after.writeFailures - before.writeFailures,
|
|
283
|
+
singleFlightWaits: after.singleFlightWaits - before.singleFlightWaits,
|
|
284
|
+
negativeCacheHits: after.negativeCacheHits - before.negativeCacheHits,
|
|
285
|
+
circuitBreakerTrips: after.circuitBreakerTrips - before.circuitBreakerTrips,
|
|
286
|
+
degradedOperations: after.degradedOperations - before.degradedOperations,
|
|
287
|
+
hitsByLayer: diffMetricMap(before.hitsByLayer, after.hitsByLayer),
|
|
288
|
+
missesByLayer: diffMetricMap(before.missesByLayer, after.missesByLayer),
|
|
289
|
+
latencyByLayer,
|
|
290
|
+
resetAt: after.resetAt
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
function addNamespaceMetrics(base, delta) {
|
|
294
|
+
return {
|
|
295
|
+
hits: base.hits + delta.hits,
|
|
296
|
+
misses: base.misses + delta.misses,
|
|
297
|
+
fetches: base.fetches + delta.fetches,
|
|
298
|
+
sets: base.sets + delta.sets,
|
|
299
|
+
deletes: base.deletes + delta.deletes,
|
|
300
|
+
backfills: base.backfills + delta.backfills,
|
|
301
|
+
invalidations: base.invalidations + delta.invalidations,
|
|
302
|
+
staleHits: base.staleHits + delta.staleHits,
|
|
303
|
+
refreshes: base.refreshes + delta.refreshes,
|
|
304
|
+
refreshErrors: base.refreshErrors + delta.refreshErrors,
|
|
305
|
+
writeFailures: base.writeFailures + delta.writeFailures,
|
|
306
|
+
singleFlightWaits: base.singleFlightWaits + delta.singleFlightWaits,
|
|
307
|
+
negativeCacheHits: base.negativeCacheHits + delta.negativeCacheHits,
|
|
308
|
+
circuitBreakerTrips: base.circuitBreakerTrips + delta.circuitBreakerTrips,
|
|
309
|
+
degradedOperations: base.degradedOperations + delta.degradedOperations,
|
|
310
|
+
hitsByLayer: addMetricMap(base.hitsByLayer, delta.hitsByLayer),
|
|
311
|
+
missesByLayer: addMetricMap(base.missesByLayer, delta.missesByLayer),
|
|
312
|
+
latencyByLayer: cloneNamespaceMetrics(delta).latencyByLayer,
|
|
313
|
+
resetAt: base.resetAt
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
function computeNamespaceHitRate(metrics) {
|
|
317
|
+
const total = metrics.hits + metrics.misses;
|
|
318
|
+
const overall = total === 0 ? 0 : metrics.hits / total;
|
|
319
|
+
const byLayer = {};
|
|
320
|
+
const layers = /* @__PURE__ */ new Set([...Object.keys(metrics.hitsByLayer), ...Object.keys(metrics.missesByLayer)]);
|
|
321
|
+
for (const layer of layers) {
|
|
322
|
+
const hits = metrics.hitsByLayer[layer] ?? 0;
|
|
323
|
+
const misses = metrics.missesByLayer[layer] ?? 0;
|
|
324
|
+
byLayer[layer] = hits + misses === 0 ? 0 : hits / (hits + misses);
|
|
325
|
+
}
|
|
326
|
+
return { overall, byLayer };
|
|
327
|
+
}
|
|
328
|
+
function diffMetricMap(before, after) {
|
|
329
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)]);
|
|
330
|
+
const result = {};
|
|
331
|
+
for (const key of keys) {
|
|
332
|
+
result[key] = (after[key] ?? 0) - (before[key] ?? 0);
|
|
333
|
+
}
|
|
334
|
+
return result;
|
|
335
|
+
}
|
|
336
|
+
function addMetricMap(base, delta) {
|
|
337
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(base), ...Object.keys(delta)]);
|
|
338
|
+
const result = {};
|
|
339
|
+
for (const key of keys) {
|
|
340
|
+
result[key] = (base[key] ?? 0) + (delta[key] ?? 0);
|
|
341
|
+
}
|
|
342
|
+
return result;
|
|
343
|
+
}
|
|
344
|
+
|
|
226
345
|
// ../../src/CacheNamespace.ts
|
|
227
346
|
var CacheNamespace = class _CacheNamespace {
|
|
228
347
|
constructor(cache, prefix) {
|
|
@@ -233,7 +352,7 @@ var CacheNamespace = class _CacheNamespace {
|
|
|
233
352
|
cache;
|
|
234
353
|
prefix;
|
|
235
354
|
static metricsMutexes = /* @__PURE__ */ new WeakMap();
|
|
236
|
-
metrics =
|
|
355
|
+
metrics = createEmptyNamespaceMetrics();
|
|
237
356
|
async get(key, fetcher, options) {
|
|
238
357
|
return this.trackMetrics(() => this.cache.get(this.qualify(key), fetcher, this.qualifyGetOptions(options)));
|
|
239
358
|
}
|
|
@@ -330,19 +449,10 @@ var CacheNamespace = class _CacheNamespace {
|
|
|
330
449
|
);
|
|
331
450
|
}
|
|
332
451
|
getMetrics() {
|
|
333
|
-
return
|
|
452
|
+
return cloneNamespaceMetrics(this.metrics);
|
|
334
453
|
}
|
|
335
454
|
getHitRate() {
|
|
336
|
-
|
|
337
|
-
const overall = total === 0 ? 0 : this.metrics.hits / total;
|
|
338
|
-
const byLayer = {};
|
|
339
|
-
const layers = /* @__PURE__ */ new Set([...Object.keys(this.metrics.hitsByLayer), ...Object.keys(this.metrics.missesByLayer)]);
|
|
340
|
-
for (const layer of layers) {
|
|
341
|
-
const hits = this.metrics.hitsByLayer[layer] ?? 0;
|
|
342
|
-
const misses = this.metrics.missesByLayer[layer] ?? 0;
|
|
343
|
-
byLayer[layer] = hits + misses === 0 ? 0 : hits / (hits + misses);
|
|
344
|
-
}
|
|
345
|
-
return { overall, byLayer };
|
|
455
|
+
return computeNamespaceHitRate(this.metrics);
|
|
346
456
|
}
|
|
347
457
|
/**
|
|
348
458
|
* Creates a nested namespace. Keys are prefixed with `parentPrefix:childPrefix:`.
|
|
@@ -383,7 +493,7 @@ var CacheNamespace = class _CacheNamespace {
|
|
|
383
493
|
const before = this.cache.getMetrics();
|
|
384
494
|
const result = await operation();
|
|
385
495
|
const after = this.cache.getMetrics();
|
|
386
|
-
this.metrics =
|
|
496
|
+
this.metrics = addNamespaceMetrics(this.metrics, diffNamespaceMetrics(before, after));
|
|
387
497
|
return result;
|
|
388
498
|
});
|
|
389
499
|
}
|
|
@@ -397,111 +507,6 @@ var CacheNamespace = class _CacheNamespace {
|
|
|
397
507
|
return mutex;
|
|
398
508
|
}
|
|
399
509
|
};
|
|
400
|
-
function emptyMetrics() {
|
|
401
|
-
return {
|
|
402
|
-
hits: 0,
|
|
403
|
-
misses: 0,
|
|
404
|
-
fetches: 0,
|
|
405
|
-
sets: 0,
|
|
406
|
-
deletes: 0,
|
|
407
|
-
backfills: 0,
|
|
408
|
-
invalidations: 0,
|
|
409
|
-
staleHits: 0,
|
|
410
|
-
refreshes: 0,
|
|
411
|
-
refreshErrors: 0,
|
|
412
|
-
writeFailures: 0,
|
|
413
|
-
singleFlightWaits: 0,
|
|
414
|
-
negativeCacheHits: 0,
|
|
415
|
-
circuitBreakerTrips: 0,
|
|
416
|
-
degradedOperations: 0,
|
|
417
|
-
hitsByLayer: {},
|
|
418
|
-
missesByLayer: {},
|
|
419
|
-
latencyByLayer: {},
|
|
420
|
-
resetAt: Date.now()
|
|
421
|
-
};
|
|
422
|
-
}
|
|
423
|
-
function cloneMetrics(metrics) {
|
|
424
|
-
return {
|
|
425
|
-
...metrics,
|
|
426
|
-
hitsByLayer: { ...metrics.hitsByLayer },
|
|
427
|
-
missesByLayer: { ...metrics.missesByLayer },
|
|
428
|
-
latencyByLayer: Object.fromEntries(
|
|
429
|
-
Object.entries(metrics.latencyByLayer).map(([key, value]) => [key, { ...value }])
|
|
430
|
-
)
|
|
431
|
-
};
|
|
432
|
-
}
|
|
433
|
-
function diffMetrics(before, after) {
|
|
434
|
-
const latencyByLayer = Object.fromEntries(
|
|
435
|
-
Object.entries(after.latencyByLayer).map(([layer, value]) => [
|
|
436
|
-
layer,
|
|
437
|
-
{
|
|
438
|
-
avgMs: value.avgMs,
|
|
439
|
-
maxMs: value.maxMs,
|
|
440
|
-
count: Math.max(0, value.count - (before.latencyByLayer[layer]?.count ?? 0))
|
|
441
|
-
}
|
|
442
|
-
])
|
|
443
|
-
);
|
|
444
|
-
return {
|
|
445
|
-
hits: after.hits - before.hits,
|
|
446
|
-
misses: after.misses - before.misses,
|
|
447
|
-
fetches: after.fetches - before.fetches,
|
|
448
|
-
sets: after.sets - before.sets,
|
|
449
|
-
deletes: after.deletes - before.deletes,
|
|
450
|
-
backfills: after.backfills - before.backfills,
|
|
451
|
-
invalidations: after.invalidations - before.invalidations,
|
|
452
|
-
staleHits: after.staleHits - before.staleHits,
|
|
453
|
-
refreshes: after.refreshes - before.refreshes,
|
|
454
|
-
refreshErrors: after.refreshErrors - before.refreshErrors,
|
|
455
|
-
writeFailures: after.writeFailures - before.writeFailures,
|
|
456
|
-
singleFlightWaits: after.singleFlightWaits - before.singleFlightWaits,
|
|
457
|
-
negativeCacheHits: after.negativeCacheHits - before.negativeCacheHits,
|
|
458
|
-
circuitBreakerTrips: after.circuitBreakerTrips - before.circuitBreakerTrips,
|
|
459
|
-
degradedOperations: after.degradedOperations - before.degradedOperations,
|
|
460
|
-
hitsByLayer: diffMap(before.hitsByLayer, after.hitsByLayer),
|
|
461
|
-
missesByLayer: diffMap(before.missesByLayer, after.missesByLayer),
|
|
462
|
-
latencyByLayer,
|
|
463
|
-
resetAt: after.resetAt
|
|
464
|
-
};
|
|
465
|
-
}
|
|
466
|
-
function addMetrics(base, delta) {
|
|
467
|
-
return {
|
|
468
|
-
hits: base.hits + delta.hits,
|
|
469
|
-
misses: base.misses + delta.misses,
|
|
470
|
-
fetches: base.fetches + delta.fetches,
|
|
471
|
-
sets: base.sets + delta.sets,
|
|
472
|
-
deletes: base.deletes + delta.deletes,
|
|
473
|
-
backfills: base.backfills + delta.backfills,
|
|
474
|
-
invalidations: base.invalidations + delta.invalidations,
|
|
475
|
-
staleHits: base.staleHits + delta.staleHits,
|
|
476
|
-
refreshes: base.refreshes + delta.refreshes,
|
|
477
|
-
refreshErrors: base.refreshErrors + delta.refreshErrors,
|
|
478
|
-
writeFailures: base.writeFailures + delta.writeFailures,
|
|
479
|
-
singleFlightWaits: base.singleFlightWaits + delta.singleFlightWaits,
|
|
480
|
-
negativeCacheHits: base.negativeCacheHits + delta.negativeCacheHits,
|
|
481
|
-
circuitBreakerTrips: base.circuitBreakerTrips + delta.circuitBreakerTrips,
|
|
482
|
-
degradedOperations: base.degradedOperations + delta.degradedOperations,
|
|
483
|
-
hitsByLayer: addMap(base.hitsByLayer, delta.hitsByLayer),
|
|
484
|
-
missesByLayer: addMap(base.missesByLayer, delta.missesByLayer),
|
|
485
|
-
latencyByLayer: cloneMetrics(delta).latencyByLayer,
|
|
486
|
-
resetAt: base.resetAt
|
|
487
|
-
};
|
|
488
|
-
}
|
|
489
|
-
function diffMap(before, after) {
|
|
490
|
-
const keys = /* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)]);
|
|
491
|
-
const result = {};
|
|
492
|
-
for (const key of keys) {
|
|
493
|
-
result[key] = (after[key] ?? 0) - (before[key] ?? 0);
|
|
494
|
-
}
|
|
495
|
-
return result;
|
|
496
|
-
}
|
|
497
|
-
function addMap(base, delta) {
|
|
498
|
-
const keys = /* @__PURE__ */ new Set([...Object.keys(base), ...Object.keys(delta)]);
|
|
499
|
-
const result = {};
|
|
500
|
-
for (const key of keys) {
|
|
501
|
-
result[key] = (base[key] ?? 0) + (delta[key] ?? 0);
|
|
502
|
-
}
|
|
503
|
-
return result;
|
|
504
|
-
}
|
|
505
510
|
function validateNamespaceKey(key) {
|
|
506
511
|
if (key.length === 0) {
|
|
507
512
|
throw new Error("Namespace prefix must not be empty.");
|
|
@@ -804,7 +809,346 @@ async function readUtf8HandleWithLimit(handle, byteLimit) {
|
|
|
804
809
|
chunks.push(buffer.subarray(0, bytesRead));
|
|
805
810
|
position += bytesRead;
|
|
806
811
|
}
|
|
807
|
-
return Buffer.concat(chunks).toString("utf8");
|
|
812
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// ../../src/internal/CacheStackGeneration.ts
|
|
816
|
+
var DEFAULT_GENERATION_CLEANUP_BATCH_SIZE = 500;
|
|
817
|
+
function generationPrefix(generation) {
|
|
818
|
+
return generation === void 0 ? "" : `v${generation}:`;
|
|
819
|
+
}
|
|
820
|
+
function qualifyGenerationKey(key, generation) {
|
|
821
|
+
const prefix = generationPrefix(generation);
|
|
822
|
+
return prefix ? `${prefix}${key}` : key;
|
|
823
|
+
}
|
|
824
|
+
function qualifyGenerationPattern(pattern, generation) {
|
|
825
|
+
return qualifyGenerationKey(pattern, generation);
|
|
826
|
+
}
|
|
827
|
+
function stripGenerationPrefix(key, generation) {
|
|
828
|
+
const prefix = generationPrefix(generation);
|
|
829
|
+
if (!prefix || !key.startsWith(prefix)) {
|
|
830
|
+
return key;
|
|
831
|
+
}
|
|
832
|
+
return key.slice(prefix.length);
|
|
833
|
+
}
|
|
834
|
+
function resolveGenerationCleanupTarget({
|
|
835
|
+
previousGeneration,
|
|
836
|
+
nextGeneration,
|
|
837
|
+
generationCleanup
|
|
838
|
+
}) {
|
|
839
|
+
if (!generationCleanup || previousGeneration === void 0 || previousGeneration === nextGeneration) {
|
|
840
|
+
return null;
|
|
841
|
+
}
|
|
842
|
+
return previousGeneration;
|
|
843
|
+
}
|
|
844
|
+
function resolveGenerationCleanupBatchSize(generationCleanup) {
|
|
845
|
+
if (typeof generationCleanup !== "object" || generationCleanup === null) {
|
|
846
|
+
return DEFAULT_GENERATION_CLEANUP_BATCH_SIZE;
|
|
847
|
+
}
|
|
848
|
+
return generationCleanup.batchSize ?? DEFAULT_GENERATION_CLEANUP_BATCH_SIZE;
|
|
849
|
+
}
|
|
850
|
+
function planGenerationCleanupBatches(keys, generationCleanup) {
|
|
851
|
+
if (keys.length === 0) {
|
|
852
|
+
return [];
|
|
853
|
+
}
|
|
854
|
+
const batchSize = resolveGenerationCleanupBatchSize(generationCleanup);
|
|
855
|
+
const batches = [];
|
|
856
|
+
for (let index = 0; index < keys.length; index += batchSize) {
|
|
857
|
+
batches.push(keys.slice(index, index + batchSize));
|
|
858
|
+
}
|
|
859
|
+
return batches;
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
// ../../src/internal/CacheStackMaintenance.ts
|
|
863
|
+
var CacheStackMaintenance = class {
|
|
864
|
+
keyEpochs = /* @__PURE__ */ new Map();
|
|
865
|
+
writeBehindQueue = [];
|
|
866
|
+
writeBehindTimer;
|
|
867
|
+
writeBehindFlushPromise;
|
|
868
|
+
generationCleanupPromise;
|
|
869
|
+
clearEpoch = 0;
|
|
870
|
+
initializeWriteBehindTimer(writeStrategy, options, flush) {
|
|
871
|
+
if (writeStrategy !== "write-behind") {
|
|
872
|
+
return;
|
|
873
|
+
}
|
|
874
|
+
const flushIntervalMs = options?.flushIntervalMs;
|
|
875
|
+
if (!flushIntervalMs || flushIntervalMs <= 0) {
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
878
|
+
this.disposeWriteBehindTimer();
|
|
879
|
+
this.writeBehindTimer = setInterval(() => {
|
|
880
|
+
void flush();
|
|
881
|
+
}, flushIntervalMs);
|
|
882
|
+
this.writeBehindTimer.unref?.();
|
|
883
|
+
}
|
|
884
|
+
disposeWriteBehindTimer() {
|
|
885
|
+
if (!this.writeBehindTimer) {
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
clearInterval(this.writeBehindTimer);
|
|
889
|
+
this.writeBehindTimer = void 0;
|
|
890
|
+
}
|
|
891
|
+
beginClearEpoch() {
|
|
892
|
+
this.clearEpoch += 1;
|
|
893
|
+
this.keyEpochs.clear();
|
|
894
|
+
this.writeBehindQueue.length = 0;
|
|
895
|
+
}
|
|
896
|
+
currentClearEpoch() {
|
|
897
|
+
return this.clearEpoch;
|
|
898
|
+
}
|
|
899
|
+
currentKeyEpoch(key) {
|
|
900
|
+
return this.keyEpochs.get(key) ?? 0;
|
|
901
|
+
}
|
|
902
|
+
bumpKeyEpochs(keys) {
|
|
903
|
+
for (const key of keys) {
|
|
904
|
+
this.keyEpochs.set(key, this.currentKeyEpoch(key) + 1);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
isWriteOutdated(key, expectedClearEpoch, expectedKeyEpoch) {
|
|
908
|
+
if (expectedClearEpoch !== void 0 && expectedClearEpoch !== this.clearEpoch) {
|
|
909
|
+
return true;
|
|
910
|
+
}
|
|
911
|
+
if (expectedKeyEpoch !== void 0 && expectedKeyEpoch !== this.currentKeyEpoch(key)) {
|
|
912
|
+
return true;
|
|
913
|
+
}
|
|
914
|
+
return false;
|
|
915
|
+
}
|
|
916
|
+
async enqueueWriteBehind(operation, options, flushBatch) {
|
|
917
|
+
this.writeBehindQueue.push(operation);
|
|
918
|
+
const batchSize = options?.batchSize ?? 100;
|
|
919
|
+
const maxQueueSize = options?.maxQueueSize ?? batchSize * 10;
|
|
920
|
+
if (this.writeBehindQueue.length >= batchSize) {
|
|
921
|
+
await this.flushWriteBehindQueue(options, flushBatch);
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
if (this.writeBehindQueue.length >= maxQueueSize) {
|
|
925
|
+
await this.flushWriteBehindQueue(options, flushBatch);
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
async flushWriteBehindQueue(options, flushBatch) {
|
|
929
|
+
if (this.writeBehindFlushPromise || this.writeBehindQueue.length === 0) {
|
|
930
|
+
await this.writeBehindFlushPromise;
|
|
931
|
+
return;
|
|
932
|
+
}
|
|
933
|
+
const batchSize = options?.batchSize ?? 100;
|
|
934
|
+
const batch = this.writeBehindQueue.splice(0, batchSize);
|
|
935
|
+
this.writeBehindFlushPromise = flushBatch(batch);
|
|
936
|
+
try {
|
|
937
|
+
await this.writeBehindFlushPromise;
|
|
938
|
+
} finally {
|
|
939
|
+
this.writeBehindFlushPromise = void 0;
|
|
940
|
+
}
|
|
941
|
+
if (this.writeBehindQueue.length > 0) {
|
|
942
|
+
await this.flushWriteBehindQueue(options, flushBatch);
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
scheduleGenerationCleanup(generation, task, onError) {
|
|
946
|
+
const scheduledTask = (this.generationCleanupPromise ?? Promise.resolve()).then(() => task(generation)).catch((error) => {
|
|
947
|
+
onError(generation, error);
|
|
948
|
+
});
|
|
949
|
+
this.generationCleanupPromise = scheduledTask.finally(() => {
|
|
950
|
+
if (this.generationCleanupPromise === scheduledTask) {
|
|
951
|
+
this.generationCleanupPromise = void 0;
|
|
952
|
+
}
|
|
953
|
+
});
|
|
954
|
+
}
|
|
955
|
+
async waitForGenerationCleanup() {
|
|
956
|
+
await this.generationCleanupPromise;
|
|
957
|
+
}
|
|
958
|
+
};
|
|
959
|
+
|
|
960
|
+
// ../../src/internal/StoredValue.ts
|
|
961
|
+
function isStoredValueEnvelope(value) {
|
|
962
|
+
if (typeof value !== "object" || value === null) {
|
|
963
|
+
return false;
|
|
964
|
+
}
|
|
965
|
+
const v = value;
|
|
966
|
+
if (v.__layercache !== 1) {
|
|
967
|
+
return false;
|
|
968
|
+
}
|
|
969
|
+
if (v.kind !== "value" && v.kind !== "empty") {
|
|
970
|
+
return false;
|
|
971
|
+
}
|
|
972
|
+
if (v.freshUntil !== null && (!Number.isFinite(v.freshUntil) || typeof v.freshUntil !== "number")) {
|
|
973
|
+
return false;
|
|
974
|
+
}
|
|
975
|
+
if (v.staleUntil !== null && (!Number.isFinite(v.staleUntil) || typeof v.staleUntil !== "number")) {
|
|
976
|
+
return false;
|
|
977
|
+
}
|
|
978
|
+
if (v.errorUntil !== null && (!Number.isFinite(v.errorUntil) || typeof v.errorUntil !== "number")) {
|
|
979
|
+
return false;
|
|
980
|
+
}
|
|
981
|
+
const maxTimestamp = Date.now() + 10 * 365 * 24 * 60 * 60 * 1e3;
|
|
982
|
+
if (typeof v.freshUntil === "number" && v.freshUntil > maxTimestamp) {
|
|
983
|
+
return false;
|
|
984
|
+
}
|
|
985
|
+
if (typeof v.staleUntil === "number" && v.staleUntil > maxTimestamp) {
|
|
986
|
+
return false;
|
|
987
|
+
}
|
|
988
|
+
if (typeof v.errorUntil === "number" && v.errorUntil > maxTimestamp) {
|
|
989
|
+
return false;
|
|
990
|
+
}
|
|
991
|
+
if (v.freshUntil === null && (v.staleUntil !== null || v.errorUntil !== null)) {
|
|
992
|
+
return false;
|
|
993
|
+
}
|
|
994
|
+
if (typeof v.freshUntil === "number" && typeof v.staleUntil === "number" && v.staleUntil < v.freshUntil) {
|
|
995
|
+
return false;
|
|
996
|
+
}
|
|
997
|
+
if (typeof v.freshUntil === "number" && typeof v.errorUntil === "number" && v.errorUntil < v.freshUntil) {
|
|
998
|
+
return false;
|
|
999
|
+
}
|
|
1000
|
+
const maxTtlSeconds = 10 * 365 * 24 * 60 * 60;
|
|
1001
|
+
if (!isValidEnvelopeTtlSeconds(v.freshTtlSeconds, maxTtlSeconds)) {
|
|
1002
|
+
return false;
|
|
1003
|
+
}
|
|
1004
|
+
if (!isValidEnvelopeTtlSeconds(v.staleWhileRevalidateSeconds, maxTtlSeconds)) {
|
|
1005
|
+
return false;
|
|
1006
|
+
}
|
|
1007
|
+
if (!isValidEnvelopeTtlSeconds(v.staleIfErrorSeconds, maxTtlSeconds)) {
|
|
1008
|
+
return false;
|
|
1009
|
+
}
|
|
1010
|
+
if (v.freshTtlSeconds == null && (v.staleWhileRevalidateSeconds != null || v.staleIfErrorSeconds != null)) {
|
|
1011
|
+
return false;
|
|
1012
|
+
}
|
|
1013
|
+
return true;
|
|
1014
|
+
}
|
|
1015
|
+
function createStoredValueEnvelope(options) {
|
|
1016
|
+
const now = options.now ?? Date.now();
|
|
1017
|
+
const freshTtlSeconds = normalizePositiveSeconds(options.freshTtlSeconds);
|
|
1018
|
+
const staleWhileRevalidateSeconds = normalizePositiveSeconds(options.staleWhileRevalidateSeconds);
|
|
1019
|
+
const staleIfErrorSeconds = normalizePositiveSeconds(options.staleIfErrorSeconds);
|
|
1020
|
+
const freshUntil = freshTtlSeconds ? now + freshTtlSeconds * 1e3 : null;
|
|
1021
|
+
const staleUntil = freshUntil && staleWhileRevalidateSeconds ? freshUntil + staleWhileRevalidateSeconds * 1e3 : null;
|
|
1022
|
+
const errorUntil = freshUntil && staleIfErrorSeconds ? freshUntil + staleIfErrorSeconds * 1e3 : null;
|
|
1023
|
+
return {
|
|
1024
|
+
__layercache: 1,
|
|
1025
|
+
kind: options.kind,
|
|
1026
|
+
value: options.value,
|
|
1027
|
+
freshUntil,
|
|
1028
|
+
staleUntil,
|
|
1029
|
+
errorUntil,
|
|
1030
|
+
freshTtlSeconds: freshTtlSeconds ?? null,
|
|
1031
|
+
staleWhileRevalidateSeconds: staleWhileRevalidateSeconds ?? null,
|
|
1032
|
+
staleIfErrorSeconds: staleIfErrorSeconds ?? null
|
|
1033
|
+
};
|
|
1034
|
+
}
|
|
1035
|
+
function resolveStoredValue(stored, now = Date.now()) {
|
|
1036
|
+
if (!isStoredValueEnvelope(stored)) {
|
|
1037
|
+
return { state: "fresh", value: stored, stored };
|
|
1038
|
+
}
|
|
1039
|
+
if (stored.freshUntil === null || stored.freshUntil > now) {
|
|
1040
|
+
return { state: "fresh", value: unwrapStoredValue(stored), stored, envelope: stored };
|
|
1041
|
+
}
|
|
1042
|
+
if (stored.staleUntil !== null && stored.staleUntil > now) {
|
|
1043
|
+
return { state: "stale-while-revalidate", value: unwrapStoredValue(stored), stored, envelope: stored };
|
|
1044
|
+
}
|
|
1045
|
+
if (stored.errorUntil !== null && stored.errorUntil > now) {
|
|
1046
|
+
return { state: "stale-if-error", value: unwrapStoredValue(stored), stored, envelope: stored };
|
|
1047
|
+
}
|
|
1048
|
+
return { state: "expired", value: null, stored, envelope: stored };
|
|
1049
|
+
}
|
|
1050
|
+
function unwrapStoredValue(stored) {
|
|
1051
|
+
if (!isStoredValueEnvelope(stored)) {
|
|
1052
|
+
return stored;
|
|
1053
|
+
}
|
|
1054
|
+
if (stored.kind === "empty") {
|
|
1055
|
+
return null;
|
|
1056
|
+
}
|
|
1057
|
+
return stored.value ?? null;
|
|
1058
|
+
}
|
|
1059
|
+
function remainingStoredTtlSeconds(stored, now = Date.now()) {
|
|
1060
|
+
if (!isStoredValueEnvelope(stored)) {
|
|
1061
|
+
return void 0;
|
|
1062
|
+
}
|
|
1063
|
+
const expiry = maxExpiry(stored);
|
|
1064
|
+
if (expiry === null) {
|
|
1065
|
+
return void 0;
|
|
1066
|
+
}
|
|
1067
|
+
const remainingMs = expiry - now;
|
|
1068
|
+
if (remainingMs <= 0) {
|
|
1069
|
+
return 1;
|
|
1070
|
+
}
|
|
1071
|
+
return Math.max(1, Math.ceil(remainingMs / 1e3));
|
|
1072
|
+
}
|
|
1073
|
+
function remainingFreshTtlSeconds(stored, now = Date.now()) {
|
|
1074
|
+
if (!isStoredValueEnvelope(stored) || stored.freshUntil === null) {
|
|
1075
|
+
return void 0;
|
|
1076
|
+
}
|
|
1077
|
+
const remainingMs = stored.freshUntil - now;
|
|
1078
|
+
if (remainingMs <= 0) {
|
|
1079
|
+
return 0;
|
|
1080
|
+
}
|
|
1081
|
+
return Math.max(1, Math.ceil(remainingMs / 1e3));
|
|
1082
|
+
}
|
|
1083
|
+
function refreshStoredEnvelope(stored, now = Date.now()) {
|
|
1084
|
+
if (!isStoredValueEnvelope(stored)) {
|
|
1085
|
+
return stored;
|
|
1086
|
+
}
|
|
1087
|
+
return createStoredValueEnvelope({
|
|
1088
|
+
kind: stored.kind,
|
|
1089
|
+
value: stored.value,
|
|
1090
|
+
freshTtlSeconds: stored.freshTtlSeconds ?? void 0,
|
|
1091
|
+
staleWhileRevalidateSeconds: stored.staleWhileRevalidateSeconds ?? void 0,
|
|
1092
|
+
staleIfErrorSeconds: stored.staleIfErrorSeconds ?? void 0,
|
|
1093
|
+
now
|
|
1094
|
+
});
|
|
1095
|
+
}
|
|
1096
|
+
function maxExpiry(stored) {
|
|
1097
|
+
const values = [stored.freshUntil, stored.staleUntil, stored.errorUntil].filter(
|
|
1098
|
+
(value) => value !== null
|
|
1099
|
+
);
|
|
1100
|
+
if (values.length === 0) {
|
|
1101
|
+
return null;
|
|
1102
|
+
}
|
|
1103
|
+
return Math.max(...values);
|
|
1104
|
+
}
|
|
1105
|
+
function normalizePositiveSeconds(value) {
|
|
1106
|
+
if (!value || value <= 0) {
|
|
1107
|
+
return void 0;
|
|
1108
|
+
}
|
|
1109
|
+
return value;
|
|
1110
|
+
}
|
|
1111
|
+
function isValidEnvelopeTtlSeconds(value, maxTtlSeconds) {
|
|
1112
|
+
if (value == null) {
|
|
1113
|
+
return true;
|
|
1114
|
+
}
|
|
1115
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 && value <= maxTtlSeconds;
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
// ../../src/internal/CacheStackRuntimePolicy.ts
|
|
1119
|
+
function shouldSkipLayer(degradedUntil, now = Date.now()) {
|
|
1120
|
+
return degradedUntil !== void 0 && degradedUntil > now;
|
|
1121
|
+
}
|
|
1122
|
+
function shouldStartBackgroundRefresh({
|
|
1123
|
+
isDisconnecting,
|
|
1124
|
+
hasRefreshInFlight
|
|
1125
|
+
}) {
|
|
1126
|
+
return !isDisconnecting && !hasRefreshInFlight;
|
|
1127
|
+
}
|
|
1128
|
+
function resolveRecoverableLayerFailure(gracefulDegradation, now = Date.now()) {
|
|
1129
|
+
if (!gracefulDegradation) {
|
|
1130
|
+
return { degrade: false };
|
|
1131
|
+
}
|
|
1132
|
+
const retryAfterMs = typeof gracefulDegradation === "object" ? gracefulDegradation.retryAfterMs ?? 1e4 : 1e4;
|
|
1133
|
+
return {
|
|
1134
|
+
degrade: true,
|
|
1135
|
+
degradedUntil: now + retryAfterMs
|
|
1136
|
+
};
|
|
1137
|
+
}
|
|
1138
|
+
function planFreshReadPolicies({
|
|
1139
|
+
stored,
|
|
1140
|
+
hasFetcher,
|
|
1141
|
+
slidingTtl,
|
|
1142
|
+
refreshAheadSeconds
|
|
1143
|
+
}) {
|
|
1144
|
+
const refreshedStored = slidingTtl && isStoredValueEnvelope(stored) ? refreshStoredEnvelope(stored) : void 0;
|
|
1145
|
+
const refreshedStoredTtl = refreshedStored ? remainingStoredTtlSeconds(refreshedStored) ?? void 0 : void 0;
|
|
1146
|
+
const remainingFreshTtl = remainingFreshTtlSeconds(stored) ?? 0;
|
|
1147
|
+
return {
|
|
1148
|
+
refreshedStored,
|
|
1149
|
+
refreshedStoredTtl,
|
|
1150
|
+
shouldScheduleBackgroundRefresh: hasFetcher && refreshAheadSeconds > 0 && remainingFreshTtl > 0 && remainingFreshTtl <= refreshAheadSeconds
|
|
1151
|
+
};
|
|
808
1152
|
}
|
|
809
1153
|
|
|
810
1154
|
// ../../src/internal/CacheStackValidation.ts
|
|
@@ -960,7 +1304,6 @@ var CircuitBreakerManager = class {
|
|
|
960
1304
|
if (!options) {
|
|
961
1305
|
return;
|
|
962
1306
|
}
|
|
963
|
-
this.pruneIfNeeded();
|
|
964
1307
|
const failureThreshold = options.failureThreshold ?? 3;
|
|
965
1308
|
const cooldownMs = options.cooldownMs ?? 3e4;
|
|
966
1309
|
const state = this.breakers.get(key) ?? { failures: 0, openUntil: null, createdAt: Date.now() };
|
|
@@ -969,6 +1312,7 @@ var CircuitBreakerManager = class {
|
|
|
969
1312
|
state.openUntil = Date.now() + cooldownMs;
|
|
970
1313
|
}
|
|
971
1314
|
this.breakers.set(key, state);
|
|
1315
|
+
this.pruneIfNeeded();
|
|
972
1316
|
}
|
|
973
1317
|
recordSuccess(key) {
|
|
974
1318
|
this.breakers.delete(key);
|
|
@@ -1312,164 +1656,6 @@ var MetricsCollector = class {
|
|
|
1312
1656
|
}
|
|
1313
1657
|
};
|
|
1314
1658
|
|
|
1315
|
-
// ../../src/internal/StoredValue.ts
|
|
1316
|
-
function isStoredValueEnvelope(value) {
|
|
1317
|
-
if (typeof value !== "object" || value === null) {
|
|
1318
|
-
return false;
|
|
1319
|
-
}
|
|
1320
|
-
const v = value;
|
|
1321
|
-
if (v.__layercache !== 1) {
|
|
1322
|
-
return false;
|
|
1323
|
-
}
|
|
1324
|
-
if (v.kind !== "value" && v.kind !== "empty") {
|
|
1325
|
-
return false;
|
|
1326
|
-
}
|
|
1327
|
-
if (v.freshUntil !== null && (!Number.isFinite(v.freshUntil) || typeof v.freshUntil !== "number")) {
|
|
1328
|
-
return false;
|
|
1329
|
-
}
|
|
1330
|
-
if (v.staleUntil !== null && (!Number.isFinite(v.staleUntil) || typeof v.staleUntil !== "number")) {
|
|
1331
|
-
return false;
|
|
1332
|
-
}
|
|
1333
|
-
if (v.errorUntil !== null && (!Number.isFinite(v.errorUntil) || typeof v.errorUntil !== "number")) {
|
|
1334
|
-
return false;
|
|
1335
|
-
}
|
|
1336
|
-
const maxTimestamp = Date.now() + 10 * 365 * 24 * 60 * 60 * 1e3;
|
|
1337
|
-
if (typeof v.freshUntil === "number" && v.freshUntil > maxTimestamp) {
|
|
1338
|
-
return false;
|
|
1339
|
-
}
|
|
1340
|
-
if (typeof v.staleUntil === "number" && v.staleUntil > maxTimestamp) {
|
|
1341
|
-
return false;
|
|
1342
|
-
}
|
|
1343
|
-
if (typeof v.errorUntil === "number" && v.errorUntil > maxTimestamp) {
|
|
1344
|
-
return false;
|
|
1345
|
-
}
|
|
1346
|
-
if (v.freshUntil === null && (v.staleUntil !== null || v.errorUntil !== null)) {
|
|
1347
|
-
return false;
|
|
1348
|
-
}
|
|
1349
|
-
if (typeof v.freshUntil === "number" && typeof v.staleUntil === "number" && v.staleUntil < v.freshUntil) {
|
|
1350
|
-
return false;
|
|
1351
|
-
}
|
|
1352
|
-
if (typeof v.freshUntil === "number" && typeof v.errorUntil === "number" && v.errorUntil < v.freshUntil) {
|
|
1353
|
-
return false;
|
|
1354
|
-
}
|
|
1355
|
-
const maxTtlSeconds = 10 * 365 * 24 * 60 * 60;
|
|
1356
|
-
if (!isValidEnvelopeTtlSeconds(v.freshTtlSeconds, maxTtlSeconds)) {
|
|
1357
|
-
return false;
|
|
1358
|
-
}
|
|
1359
|
-
if (!isValidEnvelopeTtlSeconds(v.staleWhileRevalidateSeconds, maxTtlSeconds)) {
|
|
1360
|
-
return false;
|
|
1361
|
-
}
|
|
1362
|
-
if (!isValidEnvelopeTtlSeconds(v.staleIfErrorSeconds, maxTtlSeconds)) {
|
|
1363
|
-
return false;
|
|
1364
|
-
}
|
|
1365
|
-
if (v.freshTtlSeconds == null && (v.staleWhileRevalidateSeconds != null || v.staleIfErrorSeconds != null)) {
|
|
1366
|
-
return false;
|
|
1367
|
-
}
|
|
1368
|
-
return true;
|
|
1369
|
-
}
|
|
1370
|
-
function createStoredValueEnvelope(options) {
|
|
1371
|
-
const now = options.now ?? Date.now();
|
|
1372
|
-
const freshTtlSeconds = normalizePositiveSeconds(options.freshTtlSeconds);
|
|
1373
|
-
const staleWhileRevalidateSeconds = normalizePositiveSeconds(options.staleWhileRevalidateSeconds);
|
|
1374
|
-
const staleIfErrorSeconds = normalizePositiveSeconds(options.staleIfErrorSeconds);
|
|
1375
|
-
const freshUntil = freshTtlSeconds ? now + freshTtlSeconds * 1e3 : null;
|
|
1376
|
-
const staleUntil = freshUntil && staleWhileRevalidateSeconds ? freshUntil + staleWhileRevalidateSeconds * 1e3 : null;
|
|
1377
|
-
const errorUntil = freshUntil && staleIfErrorSeconds ? freshUntil + staleIfErrorSeconds * 1e3 : null;
|
|
1378
|
-
return {
|
|
1379
|
-
__layercache: 1,
|
|
1380
|
-
kind: options.kind,
|
|
1381
|
-
value: options.value,
|
|
1382
|
-
freshUntil,
|
|
1383
|
-
staleUntil,
|
|
1384
|
-
errorUntil,
|
|
1385
|
-
freshTtlSeconds: freshTtlSeconds ?? null,
|
|
1386
|
-
staleWhileRevalidateSeconds: staleWhileRevalidateSeconds ?? null,
|
|
1387
|
-
staleIfErrorSeconds: staleIfErrorSeconds ?? null
|
|
1388
|
-
};
|
|
1389
|
-
}
|
|
1390
|
-
function resolveStoredValue(stored, now = Date.now()) {
|
|
1391
|
-
if (!isStoredValueEnvelope(stored)) {
|
|
1392
|
-
return { state: "fresh", value: stored, stored };
|
|
1393
|
-
}
|
|
1394
|
-
if (stored.freshUntil === null || stored.freshUntil > now) {
|
|
1395
|
-
return { state: "fresh", value: unwrapStoredValue(stored), stored, envelope: stored };
|
|
1396
|
-
}
|
|
1397
|
-
if (stored.staleUntil !== null && stored.staleUntil > now) {
|
|
1398
|
-
return { state: "stale-while-revalidate", value: unwrapStoredValue(stored), stored, envelope: stored };
|
|
1399
|
-
}
|
|
1400
|
-
if (stored.errorUntil !== null && stored.errorUntil > now) {
|
|
1401
|
-
return { state: "stale-if-error", value: unwrapStoredValue(stored), stored, envelope: stored };
|
|
1402
|
-
}
|
|
1403
|
-
return { state: "expired", value: null, stored, envelope: stored };
|
|
1404
|
-
}
|
|
1405
|
-
function unwrapStoredValue(stored) {
|
|
1406
|
-
if (!isStoredValueEnvelope(stored)) {
|
|
1407
|
-
return stored;
|
|
1408
|
-
}
|
|
1409
|
-
if (stored.kind === "empty") {
|
|
1410
|
-
return null;
|
|
1411
|
-
}
|
|
1412
|
-
return stored.value ?? null;
|
|
1413
|
-
}
|
|
1414
|
-
function remainingStoredTtlSeconds(stored, now = Date.now()) {
|
|
1415
|
-
if (!isStoredValueEnvelope(stored)) {
|
|
1416
|
-
return void 0;
|
|
1417
|
-
}
|
|
1418
|
-
const expiry = maxExpiry(stored);
|
|
1419
|
-
if (expiry === null) {
|
|
1420
|
-
return void 0;
|
|
1421
|
-
}
|
|
1422
|
-
const remainingMs = expiry - now;
|
|
1423
|
-
if (remainingMs <= 0) {
|
|
1424
|
-
return 1;
|
|
1425
|
-
}
|
|
1426
|
-
return Math.max(1, Math.ceil(remainingMs / 1e3));
|
|
1427
|
-
}
|
|
1428
|
-
function remainingFreshTtlSeconds(stored, now = Date.now()) {
|
|
1429
|
-
if (!isStoredValueEnvelope(stored) || stored.freshUntil === null) {
|
|
1430
|
-
return void 0;
|
|
1431
|
-
}
|
|
1432
|
-
const remainingMs = stored.freshUntil - now;
|
|
1433
|
-
if (remainingMs <= 0) {
|
|
1434
|
-
return 0;
|
|
1435
|
-
}
|
|
1436
|
-
return Math.max(1, Math.ceil(remainingMs / 1e3));
|
|
1437
|
-
}
|
|
1438
|
-
function refreshStoredEnvelope(stored, now = Date.now()) {
|
|
1439
|
-
if (!isStoredValueEnvelope(stored)) {
|
|
1440
|
-
return stored;
|
|
1441
|
-
}
|
|
1442
|
-
return createStoredValueEnvelope({
|
|
1443
|
-
kind: stored.kind,
|
|
1444
|
-
value: stored.value,
|
|
1445
|
-
freshTtlSeconds: stored.freshTtlSeconds ?? void 0,
|
|
1446
|
-
staleWhileRevalidateSeconds: stored.staleWhileRevalidateSeconds ?? void 0,
|
|
1447
|
-
staleIfErrorSeconds: stored.staleIfErrorSeconds ?? void 0,
|
|
1448
|
-
now
|
|
1449
|
-
});
|
|
1450
|
-
}
|
|
1451
|
-
function maxExpiry(stored) {
|
|
1452
|
-
const values = [stored.freshUntil, stored.staleUntil, stored.errorUntil].filter(
|
|
1453
|
-
(value) => value !== null
|
|
1454
|
-
);
|
|
1455
|
-
if (values.length === 0) {
|
|
1456
|
-
return null;
|
|
1457
|
-
}
|
|
1458
|
-
return Math.max(...values);
|
|
1459
|
-
}
|
|
1460
|
-
function normalizePositiveSeconds(value) {
|
|
1461
|
-
if (!value || value <= 0) {
|
|
1462
|
-
return void 0;
|
|
1463
|
-
}
|
|
1464
|
-
return value;
|
|
1465
|
-
}
|
|
1466
|
-
function isValidEnvelopeTtlSeconds(value, maxTtlSeconds) {
|
|
1467
|
-
if (value == null) {
|
|
1468
|
-
return true;
|
|
1469
|
-
}
|
|
1470
|
-
return typeof value === "number" && Number.isFinite(value) && value > 0 && value <= maxTtlSeconds;
|
|
1471
|
-
}
|
|
1472
|
-
|
|
1473
1659
|
// ../../src/internal/TtlResolver.ts
|
|
1474
1660
|
var DEFAULT_NEGATIVE_TTL_SECONDS = 60;
|
|
1475
1661
|
var TtlResolver = class {
|
|
@@ -2002,15 +2188,10 @@ var CacheStack = class extends EventEmitter {
|
|
|
2002
2188
|
snapshotSerializer = new JsonSerializer();
|
|
2003
2189
|
backgroundRefreshes = /* @__PURE__ */ new Map();
|
|
2004
2190
|
layerDegradedUntil = /* @__PURE__ */ new Map();
|
|
2005
|
-
|
|
2191
|
+
maintenance = new CacheStackMaintenance();
|
|
2006
2192
|
ttlResolver;
|
|
2007
2193
|
circuitBreakerManager;
|
|
2008
2194
|
currentGeneration;
|
|
2009
|
-
writeBehindQueue = [];
|
|
2010
|
-
writeBehindTimer;
|
|
2011
|
-
writeBehindFlushPromise;
|
|
2012
|
-
generationCleanupPromise;
|
|
2013
|
-
clearEpoch = 0;
|
|
2014
2195
|
isDisconnecting = false;
|
|
2015
2196
|
disconnectPromise;
|
|
2016
2197
|
/**
|
|
@@ -2166,7 +2347,7 @@ var CacheStack = class extends EventEmitter {
|
|
|
2166
2347
|
}
|
|
2167
2348
|
async clear() {
|
|
2168
2349
|
await this.awaitStartup("clear");
|
|
2169
|
-
this.beginClearEpoch();
|
|
2350
|
+
this.maintenance.beginClearEpoch();
|
|
2170
2351
|
await Promise.all(this.layers.map((layer) => layer.clear()));
|
|
2171
2352
|
await this.tagIndex.clear();
|
|
2172
2353
|
this.ttlResolver.clearProfiles();
|
|
@@ -2424,9 +2605,15 @@ var CacheStack = class extends EventEmitter {
|
|
|
2424
2605
|
bumpGeneration(nextGeneration) {
|
|
2425
2606
|
const current = this.currentGeneration ?? 0;
|
|
2426
2607
|
const previousGeneration = this.currentGeneration;
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2608
|
+
const updatedGeneration = nextGeneration ?? current + 1;
|
|
2609
|
+
const generationToCleanup = resolveGenerationCleanupTarget({
|
|
2610
|
+
previousGeneration,
|
|
2611
|
+
nextGeneration: updatedGeneration,
|
|
2612
|
+
generationCleanup: this.options.generationCleanup
|
|
2613
|
+
});
|
|
2614
|
+
this.currentGeneration = updatedGeneration;
|
|
2615
|
+
if (generationToCleanup !== null) {
|
|
2616
|
+
this.scheduleGenerationCleanup(generationToCleanup);
|
|
2430
2617
|
}
|
|
2431
2618
|
return this.currentGeneration;
|
|
2432
2619
|
}
|
|
@@ -2570,12 +2757,9 @@ var CacheStack = class extends EventEmitter {
|
|
|
2570
2757
|
await this.startup;
|
|
2571
2758
|
await this.unsubscribeInvalidation?.();
|
|
2572
2759
|
await this.flushWriteBehindQueue();
|
|
2573
|
-
await this.
|
|
2760
|
+
await this.maintenance.waitForGenerationCleanup();
|
|
2574
2761
|
await Promise.allSettled([...this.backgroundRefreshes.values()]);
|
|
2575
|
-
|
|
2576
|
-
clearInterval(this.writeBehindTimer);
|
|
2577
|
-
this.writeBehindTimer = void 0;
|
|
2578
|
-
}
|
|
2762
|
+
this.maintenance.disposeWriteBehindTimer();
|
|
2579
2763
|
await Promise.allSettled(this.layers.map((layer) => layer.dispose?.() ?? Promise.resolve()));
|
|
2580
2764
|
})();
|
|
2581
2765
|
}
|
|
@@ -2651,13 +2835,13 @@ var CacheStack = class extends EventEmitter {
|
|
|
2651
2835
|
if (!this.shouldNegativeCache(options)) {
|
|
2652
2836
|
return null;
|
|
2653
2837
|
}
|
|
2654
|
-
if (this.isWriteOutdated(key, expectedClearEpoch, expectedKeyEpoch)) {
|
|
2838
|
+
if (this.maintenance.isWriteOutdated(key, expectedClearEpoch, expectedKeyEpoch)) {
|
|
2655
2839
|
this.logger.debug?.("skip-negative-store-after-invalidation", {
|
|
2656
2840
|
key,
|
|
2657
2841
|
expectedClearEpoch,
|
|
2658
|
-
clearEpoch: this.
|
|
2842
|
+
clearEpoch: this.maintenance.currentClearEpoch(),
|
|
2659
2843
|
expectedKeyEpoch,
|
|
2660
|
-
keyEpoch: this.currentKeyEpoch(key)
|
|
2844
|
+
keyEpoch: this.maintenance.currentKeyEpoch(key)
|
|
2661
2845
|
});
|
|
2662
2846
|
return null;
|
|
2663
2847
|
}
|
|
@@ -2673,13 +2857,13 @@ var CacheStack = class extends EventEmitter {
|
|
|
2673
2857
|
this.logger.warn?.("shouldCache-error", { key, error: this.formatError(error) });
|
|
2674
2858
|
}
|
|
2675
2859
|
}
|
|
2676
|
-
if (this.isWriteOutdated(key, expectedClearEpoch, expectedKeyEpoch)) {
|
|
2860
|
+
if (this.maintenance.isWriteOutdated(key, expectedClearEpoch, expectedKeyEpoch)) {
|
|
2677
2861
|
this.logger.debug?.("skip-store-after-invalidation", {
|
|
2678
2862
|
key,
|
|
2679
2863
|
expectedClearEpoch,
|
|
2680
|
-
clearEpoch: this.
|
|
2864
|
+
clearEpoch: this.maintenance.currentClearEpoch(),
|
|
2681
2865
|
expectedKeyEpoch,
|
|
2682
|
-
keyEpoch: this.currentKeyEpoch(key)
|
|
2866
|
+
keyEpoch: this.maintenance.currentKeyEpoch(key)
|
|
2683
2867
|
});
|
|
2684
2868
|
return fetched;
|
|
2685
2869
|
}
|
|
@@ -2687,10 +2871,10 @@ var CacheStack = class extends EventEmitter {
|
|
|
2687
2871
|
return fetched;
|
|
2688
2872
|
}
|
|
2689
2873
|
async storeEntry(key, kind, value, options) {
|
|
2690
|
-
const clearEpoch = this.
|
|
2691
|
-
const keyEpoch = this.currentKeyEpoch(key);
|
|
2874
|
+
const clearEpoch = this.maintenance.currentClearEpoch();
|
|
2875
|
+
const keyEpoch = this.maintenance.currentKeyEpoch(key);
|
|
2692
2876
|
await this.writeAcrossLayers(key, kind, value, options);
|
|
2693
|
-
if (this.isWriteOutdated(key, clearEpoch, keyEpoch)) {
|
|
2877
|
+
if (this.maintenance.isWriteOutdated(key, clearEpoch, keyEpoch)) {
|
|
2694
2878
|
return;
|
|
2695
2879
|
}
|
|
2696
2880
|
if (options?.tags) {
|
|
@@ -2707,8 +2891,8 @@ var CacheStack = class extends EventEmitter {
|
|
|
2707
2891
|
}
|
|
2708
2892
|
async writeBatch(entries) {
|
|
2709
2893
|
const now = Date.now();
|
|
2710
|
-
const clearEpoch = this.
|
|
2711
|
-
const entryEpochs = new Map(entries.map((entry) => [entry.key, this.currentKeyEpoch(entry.key)]));
|
|
2894
|
+
const clearEpoch = this.maintenance.currentClearEpoch();
|
|
2895
|
+
const entryEpochs = new Map(entries.map((entry) => [entry.key, this.maintenance.currentKeyEpoch(entry.key)]));
|
|
2712
2896
|
const entriesByLayer = /* @__PURE__ */ new Map();
|
|
2713
2897
|
const immediateOperations = [];
|
|
2714
2898
|
const deferredOperations = [];
|
|
@@ -2725,11 +2909,11 @@ var CacheStack = class extends EventEmitter {
|
|
|
2725
2909
|
}
|
|
2726
2910
|
for (const [layer, layerEntries] of entriesByLayer.entries()) {
|
|
2727
2911
|
const operation = async () => {
|
|
2728
|
-
if (clearEpoch !== this.
|
|
2912
|
+
if (clearEpoch !== this.maintenance.currentClearEpoch()) {
|
|
2729
2913
|
return;
|
|
2730
2914
|
}
|
|
2731
2915
|
const activeEntries = layerEntries.filter(
|
|
2732
|
-
(entry) => (entryEpochs.get(entry.key) ?? 0) === this.currentKeyEpoch(entry.key)
|
|
2916
|
+
(entry) => (entryEpochs.get(entry.key) ?? 0) === this.maintenance.currentKeyEpoch(entry.key)
|
|
2733
2917
|
);
|
|
2734
2918
|
if (activeEntries.length === 0) {
|
|
2735
2919
|
return;
|
|
@@ -2752,11 +2936,11 @@ var CacheStack = class extends EventEmitter {
|
|
|
2752
2936
|
}
|
|
2753
2937
|
await this.executeLayerOperations(immediateOperations, { key: "batch", action: "mset" });
|
|
2754
2938
|
await Promise.all(deferredOperations.map((operation) => this.enqueueWriteBehind(operation)));
|
|
2755
|
-
if (clearEpoch !== this.
|
|
2939
|
+
if (clearEpoch !== this.maintenance.currentClearEpoch()) {
|
|
2756
2940
|
return;
|
|
2757
2941
|
}
|
|
2758
2942
|
for (const entry of entries) {
|
|
2759
|
-
if (this.isWriteOutdated(entry.key, clearEpoch, entryEpochs.get(entry.key))) {
|
|
2943
|
+
if (this.maintenance.isWriteOutdated(entry.key, clearEpoch, entryEpochs.get(entry.key))) {
|
|
2760
2944
|
continue;
|
|
2761
2945
|
}
|
|
2762
2946
|
if (entry.options?.tags) {
|
|
@@ -2860,13 +3044,13 @@ var CacheStack = class extends EventEmitter {
|
|
|
2860
3044
|
}
|
|
2861
3045
|
async writeAcrossLayers(key, kind, value, options) {
|
|
2862
3046
|
const now = Date.now();
|
|
2863
|
-
const clearEpoch = this.
|
|
2864
|
-
const keyEpoch = this.currentKeyEpoch(key);
|
|
3047
|
+
const clearEpoch = this.maintenance.currentClearEpoch();
|
|
3048
|
+
const keyEpoch = this.maintenance.currentKeyEpoch(key);
|
|
2865
3049
|
const immediateOperations = [];
|
|
2866
3050
|
const deferredOperations = [];
|
|
2867
3051
|
for (const layer of this.layers) {
|
|
2868
3052
|
const operation = async () => {
|
|
2869
|
-
if (this.isWriteOutdated(key, clearEpoch, keyEpoch)) {
|
|
3053
|
+
if (this.maintenance.isWriteOutdated(key, clearEpoch, keyEpoch)) {
|
|
2870
3054
|
return;
|
|
2871
3055
|
}
|
|
2872
3056
|
if (this.shouldSkipLayer(layer)) {
|
|
@@ -2929,11 +3113,14 @@ var CacheStack = class extends EventEmitter {
|
|
|
2929
3113
|
return options?.negativeCache ?? this.options.negativeCaching ?? false;
|
|
2930
3114
|
}
|
|
2931
3115
|
scheduleBackgroundRefresh(key, fetcher, options) {
|
|
2932
|
-
if (
|
|
3116
|
+
if (!shouldStartBackgroundRefresh({
|
|
3117
|
+
isDisconnecting: this.isDisconnecting,
|
|
3118
|
+
hasRefreshInFlight: this.backgroundRefreshes.has(key)
|
|
3119
|
+
})) {
|
|
2933
3120
|
return;
|
|
2934
3121
|
}
|
|
2935
|
-
const clearEpoch = this.
|
|
2936
|
-
const keyEpoch = this.currentKeyEpoch(key);
|
|
3122
|
+
const clearEpoch = this.maintenance.currentClearEpoch();
|
|
3123
|
+
const keyEpoch = this.maintenance.currentKeyEpoch(key);
|
|
2937
3124
|
const refresh = (async () => {
|
|
2938
3125
|
this.metricsCollector.increment("refreshes");
|
|
2939
3126
|
try {
|
|
@@ -2971,7 +3158,7 @@ var CacheStack = class extends EventEmitter {
|
|
|
2971
3158
|
if (keys.length === 0) {
|
|
2972
3159
|
return;
|
|
2973
3160
|
}
|
|
2974
|
-
this.bumpKeyEpochs(keys);
|
|
3161
|
+
this.maintenance.bumpKeyEpochs(keys);
|
|
2975
3162
|
await this.deleteKeysFromLayers(this.layers, keys);
|
|
2976
3163
|
for (const key of keys) {
|
|
2977
3164
|
await this.tagIndex.remove(key);
|
|
@@ -2995,7 +3182,7 @@ var CacheStack = class extends EventEmitter {
|
|
|
2995
3182
|
}
|
|
2996
3183
|
const localLayers = this.layers.filter((layer) => layer.isLocal);
|
|
2997
3184
|
if (message.scope === "clear") {
|
|
2998
|
-
this.beginClearEpoch();
|
|
3185
|
+
this.maintenance.beginClearEpoch();
|
|
2999
3186
|
await Promise.all(localLayers.map((layer) => layer.clear()));
|
|
3000
3187
|
await this.tagIndex.clear();
|
|
3001
3188
|
this.ttlResolver.clearProfiles();
|
|
@@ -3003,7 +3190,7 @@ var CacheStack = class extends EventEmitter {
|
|
|
3003
3190
|
return;
|
|
3004
3191
|
}
|
|
3005
3192
|
const keys = message.keys ?? [];
|
|
3006
|
-
this.bumpKeyEpochs(keys);
|
|
3193
|
+
this.maintenance.bumpKeyEpochs(keys);
|
|
3007
3194
|
await this.deleteKeysFromLayers(localLayers, keys);
|
|
3008
3195
|
if (message.operation !== "write") {
|
|
3009
3196
|
for (const key of keys) {
|
|
@@ -3061,35 +3248,22 @@ var CacheStack = class extends EventEmitter {
|
|
|
3061
3248
|
shouldBroadcastL1Invalidation() {
|
|
3062
3249
|
return this.options.broadcastL1Invalidation ?? this.options.publishSetInvalidation ?? false;
|
|
3063
3250
|
}
|
|
3064
|
-
shouldCleanupGenerations() {
|
|
3065
|
-
return Boolean(this.options.generationCleanup);
|
|
3066
|
-
}
|
|
3067
|
-
generationCleanupBatchSize() {
|
|
3068
|
-
const configured = typeof this.options.generationCleanup === "object" ? this.options.generationCleanup.batchSize : void 0;
|
|
3069
|
-
return configured ?? 500;
|
|
3070
|
-
}
|
|
3071
3251
|
scheduleGenerationCleanup(generation) {
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
this.generationCleanupPromise = void 0;
|
|
3252
|
+
this.maintenance.scheduleGenerationCleanup(
|
|
3253
|
+
generation,
|
|
3254
|
+
async (generationToClean) => this.cleanupGeneration(generationToClean),
|
|
3255
|
+
(failedGeneration, error) => {
|
|
3256
|
+
this.logger.warn?.("generation-cleanup-error", {
|
|
3257
|
+
generation: failedGeneration,
|
|
3258
|
+
error: this.formatError(error)
|
|
3259
|
+
});
|
|
3081
3260
|
}
|
|
3082
|
-
|
|
3261
|
+
);
|
|
3083
3262
|
}
|
|
3084
3263
|
async cleanupGeneration(generation) {
|
|
3085
3264
|
const prefix = `v${generation}:`;
|
|
3086
3265
|
const keys = await this.keyDiscovery.collectKeysWithPrefix(prefix);
|
|
3087
|
-
|
|
3088
|
-
return;
|
|
3089
|
-
}
|
|
3090
|
-
const batchSize = this.generationCleanupBatchSize();
|
|
3091
|
-
for (let index = 0; index < keys.length; index += batchSize) {
|
|
3092
|
-
const batch = keys.slice(index, index + batchSize);
|
|
3266
|
+
for (const batch of planGenerationCleanupBatches(keys, this.options.generationCleanup)) {
|
|
3093
3267
|
await this.deleteKeys(batch);
|
|
3094
3268
|
await this.publishInvalidation({
|
|
3095
3269
|
scope: "keys",
|
|
@@ -3100,80 +3274,34 @@ var CacheStack = class extends EventEmitter {
|
|
|
3100
3274
|
}
|
|
3101
3275
|
}
|
|
3102
3276
|
initializeWriteBehind(options) {
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
return;
|
|
3109
|
-
}
|
|
3110
|
-
this.writeBehindTimer = setInterval(() => {
|
|
3111
|
-
void this.flushWriteBehindQueue();
|
|
3112
|
-
}, flushIntervalMs);
|
|
3113
|
-
this.writeBehindTimer.unref?.();
|
|
3277
|
+
this.maintenance.initializeWriteBehindTimer(
|
|
3278
|
+
this.options.writeStrategy,
|
|
3279
|
+
options,
|
|
3280
|
+
this.flushWriteBehindQueue.bind(this)
|
|
3281
|
+
);
|
|
3114
3282
|
}
|
|
3115
3283
|
shouldWriteBehind(layer) {
|
|
3116
3284
|
return this.options.writeStrategy === "write-behind" && !layer.isLocal;
|
|
3117
3285
|
}
|
|
3118
|
-
beginClearEpoch() {
|
|
3119
|
-
this.clearEpoch += 1;
|
|
3120
|
-
this.keyEpochs.clear();
|
|
3121
|
-
this.writeBehindQueue.length = 0;
|
|
3122
|
-
}
|
|
3123
|
-
currentKeyEpoch(key) {
|
|
3124
|
-
return this.keyEpochs.get(key) ?? 0;
|
|
3125
|
-
}
|
|
3126
|
-
bumpKeyEpochs(keys) {
|
|
3127
|
-
for (const key of keys) {
|
|
3128
|
-
this.keyEpochs.set(key, this.currentKeyEpoch(key) + 1);
|
|
3129
|
-
}
|
|
3130
|
-
}
|
|
3131
|
-
isWriteOutdated(key, expectedClearEpoch, expectedKeyEpoch) {
|
|
3132
|
-
if (expectedClearEpoch !== void 0 && expectedClearEpoch !== this.clearEpoch) {
|
|
3133
|
-
return true;
|
|
3134
|
-
}
|
|
3135
|
-
if (expectedKeyEpoch !== void 0 && expectedKeyEpoch !== this.currentKeyEpoch(key)) {
|
|
3136
|
-
return true;
|
|
3137
|
-
}
|
|
3138
|
-
return false;
|
|
3139
|
-
}
|
|
3140
3286
|
async enqueueWriteBehind(operation) {
|
|
3141
|
-
this.
|
|
3142
|
-
const batchSize = this.options.writeBehind?.batchSize ?? 100;
|
|
3143
|
-
const maxQueueSize = this.options.writeBehind?.maxQueueSize ?? batchSize * 10;
|
|
3144
|
-
if (this.writeBehindQueue.length >= batchSize) {
|
|
3145
|
-
await this.flushWriteBehindQueue();
|
|
3146
|
-
return;
|
|
3147
|
-
}
|
|
3148
|
-
if (this.writeBehindQueue.length >= maxQueueSize) {
|
|
3149
|
-
await this.flushWriteBehindQueue();
|
|
3150
|
-
}
|
|
3287
|
+
await this.maintenance.enqueueWriteBehind(operation, this.options.writeBehind, this.runWriteBehindBatch.bind(this));
|
|
3151
3288
|
}
|
|
3152
3289
|
async flushWriteBehindQueue() {
|
|
3153
|
-
|
|
3154
|
-
|
|
3290
|
+
await this.maintenance.flushWriteBehindQueue(this.options.writeBehind, this.runWriteBehindBatch.bind(this));
|
|
3291
|
+
}
|
|
3292
|
+
async runWriteBehindBatch(batch) {
|
|
3293
|
+
const results = await Promise.allSettled(batch.map((operation) => operation()));
|
|
3294
|
+
const failures = results.filter((result) => result.status === "rejected");
|
|
3295
|
+
if (failures.length === 0) {
|
|
3155
3296
|
return;
|
|
3156
3297
|
}
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
this.logger.error?.("write-behind-flush-failure", {
|
|
3165
|
-
failed: failures.length,
|
|
3166
|
-
total: batch.length,
|
|
3167
|
-
errors: failures.map((failure) => this.formatError(failure.reason))
|
|
3168
|
-
});
|
|
3169
|
-
this.emitError("write-behind", { failed: failures.length, total: batch.length });
|
|
3170
|
-
}
|
|
3171
|
-
})();
|
|
3172
|
-
await this.writeBehindFlushPromise;
|
|
3173
|
-
this.writeBehindFlushPromise = void 0;
|
|
3174
|
-
if (this.writeBehindQueue.length > 0) {
|
|
3175
|
-
await this.flushWriteBehindQueue();
|
|
3176
|
-
}
|
|
3298
|
+
this.metricsCollector.increment("writeFailures", failures.length);
|
|
3299
|
+
this.logger.error?.("write-behind-flush-failure", {
|
|
3300
|
+
failed: failures.length,
|
|
3301
|
+
total: batch.length,
|
|
3302
|
+
errors: failures.map((failure) => this.formatError(failure.reason))
|
|
3303
|
+
});
|
|
3304
|
+
this.emitError("write-behind", { failed: failures.length, total: batch.length });
|
|
3177
3305
|
}
|
|
3178
3306
|
buildLayerSetEntry(layer, key, kind, value, options, now) {
|
|
3179
3307
|
const freshTtl = this.resolveFreshTtl(key, layer.name, kind, options, layer.defaultTtl, value);
|
|
@@ -3203,32 +3331,17 @@ var CacheStack = class extends EventEmitter {
|
|
|
3203
3331
|
return [];
|
|
3204
3332
|
}
|
|
3205
3333
|
const [firstGroup, ...rest] = groups;
|
|
3206
|
-
if (!firstGroup) {
|
|
3207
|
-
return [];
|
|
3208
|
-
}
|
|
3209
3334
|
const restSets = rest.map((group) => new Set(group));
|
|
3210
3335
|
return [...new Set(firstGroup)].filter((key) => restSets.every((group) => group.has(key)));
|
|
3211
3336
|
}
|
|
3212
3337
|
qualifyKey(key) {
|
|
3213
|
-
|
|
3214
|
-
return prefix ? `${prefix}${key}` : key;
|
|
3338
|
+
return qualifyGenerationKey(key, this.currentGeneration);
|
|
3215
3339
|
}
|
|
3216
3340
|
qualifyPattern(pattern) {
|
|
3217
|
-
|
|
3218
|
-
return prefix ? `${prefix}${pattern}` : pattern;
|
|
3341
|
+
return qualifyGenerationPattern(pattern, this.currentGeneration);
|
|
3219
3342
|
}
|
|
3220
3343
|
stripQualifiedKey(key) {
|
|
3221
|
-
|
|
3222
|
-
if (!prefix || !key.startsWith(prefix)) {
|
|
3223
|
-
return key;
|
|
3224
|
-
}
|
|
3225
|
-
return key.slice(prefix.length);
|
|
3226
|
-
}
|
|
3227
|
-
generationPrefix() {
|
|
3228
|
-
if (this.currentGeneration === void 0) {
|
|
3229
|
-
return "";
|
|
3230
|
-
}
|
|
3231
|
-
return `v${this.currentGeneration}:`;
|
|
3344
|
+
return stripGenerationPrefix(key, this.currentGeneration);
|
|
3232
3345
|
}
|
|
3233
3346
|
async deleteKeysFromLayers(layers, keys) {
|
|
3234
3347
|
await Promise.all(
|
|
@@ -3319,37 +3432,38 @@ var CacheStack = class extends EventEmitter {
|
|
|
3319
3432
|
this.assertActive(operation);
|
|
3320
3433
|
}
|
|
3321
3434
|
async applyFreshReadPolicies(key, hit, options, fetcher) {
|
|
3322
|
-
const
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3435
|
+
const plan = planFreshReadPolicies({
|
|
3436
|
+
stored: hit.stored,
|
|
3437
|
+
hasFetcher: Boolean(fetcher),
|
|
3438
|
+
slidingTtl: options?.slidingTtl ?? false,
|
|
3439
|
+
refreshAheadSeconds: this.resolveLayerSeconds(hit.layerName, options?.refreshAhead, this.options.refreshAhead, 0) ?? 0
|
|
3440
|
+
});
|
|
3441
|
+
if (plan.refreshedStored) {
|
|
3327
3442
|
for (let index = 0; index <= hit.layerIndex; index += 1) {
|
|
3328
3443
|
const layer = this.layers[index];
|
|
3329
3444
|
if (!layer || this.shouldSkipLayer(layer)) {
|
|
3330
3445
|
continue;
|
|
3331
3446
|
}
|
|
3332
3447
|
try {
|
|
3333
|
-
await layer.set(key,
|
|
3448
|
+
await layer.set(key, plan.refreshedStored, plan.refreshedStoredTtl);
|
|
3334
3449
|
} catch (error) {
|
|
3335
3450
|
await this.handleLayerFailure(layer, "sliding-ttl", error);
|
|
3336
3451
|
}
|
|
3337
3452
|
}
|
|
3338
3453
|
}
|
|
3339
|
-
if (fetcher &&
|
|
3454
|
+
if (fetcher && plan.shouldScheduleBackgroundRefresh) {
|
|
3340
3455
|
this.scheduleBackgroundRefresh(key, fetcher, options);
|
|
3341
3456
|
}
|
|
3342
3457
|
}
|
|
3343
3458
|
shouldSkipLayer(layer) {
|
|
3344
|
-
|
|
3345
|
-
return degradedUntil !== void 0 && degradedUntil > Date.now();
|
|
3459
|
+
return shouldSkipLayer(this.layerDegradedUntil.get(layer.name));
|
|
3346
3460
|
}
|
|
3347
3461
|
async handleLayerFailure(layer, operation, error) {
|
|
3348
|
-
|
|
3462
|
+
const recovery = resolveRecoverableLayerFailure(this.options.gracefulDegradation);
|
|
3463
|
+
if (!recovery.degrade) {
|
|
3349
3464
|
throw error;
|
|
3350
3465
|
}
|
|
3351
|
-
|
|
3352
|
-
this.layerDegradedUntil.set(layer.name, Date.now() + retryAfterMs);
|
|
3466
|
+
this.layerDegradedUntil.set(layer.name, recovery.degradedUntil);
|
|
3353
3467
|
this.metricsCollector.increment("degradedOperations");
|
|
3354
3468
|
this.logger.warn?.("layer-degraded", { layer: layer.name, operation, error: this.formatError(error) });
|
|
3355
3469
|
this.emitError(operation, { layer: layer.name, degraded: true, error: this.formatError(error) });
|