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
|
@@ -259,6 +259,125 @@ var Mutex = class {
|
|
|
259
259
|
}
|
|
260
260
|
};
|
|
261
261
|
|
|
262
|
+
// ../../src/internal/CacheNamespaceMetrics.ts
|
|
263
|
+
function createEmptyNamespaceMetrics(resetAt = Date.now()) {
|
|
264
|
+
return {
|
|
265
|
+
hits: 0,
|
|
266
|
+
misses: 0,
|
|
267
|
+
fetches: 0,
|
|
268
|
+
sets: 0,
|
|
269
|
+
deletes: 0,
|
|
270
|
+
backfills: 0,
|
|
271
|
+
invalidations: 0,
|
|
272
|
+
staleHits: 0,
|
|
273
|
+
refreshes: 0,
|
|
274
|
+
refreshErrors: 0,
|
|
275
|
+
writeFailures: 0,
|
|
276
|
+
singleFlightWaits: 0,
|
|
277
|
+
negativeCacheHits: 0,
|
|
278
|
+
circuitBreakerTrips: 0,
|
|
279
|
+
degradedOperations: 0,
|
|
280
|
+
hitsByLayer: {},
|
|
281
|
+
missesByLayer: {},
|
|
282
|
+
latencyByLayer: {},
|
|
283
|
+
resetAt
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
function cloneNamespaceMetrics(metrics) {
|
|
287
|
+
return {
|
|
288
|
+
...metrics,
|
|
289
|
+
hitsByLayer: { ...metrics.hitsByLayer },
|
|
290
|
+
missesByLayer: { ...metrics.missesByLayer },
|
|
291
|
+
latencyByLayer: Object.fromEntries(
|
|
292
|
+
Object.entries(metrics.latencyByLayer).map(([key, value]) => [key, { ...value }])
|
|
293
|
+
)
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
function diffNamespaceMetrics(before, after) {
|
|
297
|
+
const latencyByLayer = Object.fromEntries(
|
|
298
|
+
Object.entries(after.latencyByLayer).map(([layer, value]) => [
|
|
299
|
+
layer,
|
|
300
|
+
{
|
|
301
|
+
avgMs: value.avgMs,
|
|
302
|
+
maxMs: value.maxMs,
|
|
303
|
+
count: Math.max(0, value.count - (before.latencyByLayer[layer]?.count ?? 0))
|
|
304
|
+
}
|
|
305
|
+
])
|
|
306
|
+
);
|
|
307
|
+
return {
|
|
308
|
+
hits: after.hits - before.hits,
|
|
309
|
+
misses: after.misses - before.misses,
|
|
310
|
+
fetches: after.fetches - before.fetches,
|
|
311
|
+
sets: after.sets - before.sets,
|
|
312
|
+
deletes: after.deletes - before.deletes,
|
|
313
|
+
backfills: after.backfills - before.backfills,
|
|
314
|
+
invalidations: after.invalidations - before.invalidations,
|
|
315
|
+
staleHits: after.staleHits - before.staleHits,
|
|
316
|
+
refreshes: after.refreshes - before.refreshes,
|
|
317
|
+
refreshErrors: after.refreshErrors - before.refreshErrors,
|
|
318
|
+
writeFailures: after.writeFailures - before.writeFailures,
|
|
319
|
+
singleFlightWaits: after.singleFlightWaits - before.singleFlightWaits,
|
|
320
|
+
negativeCacheHits: after.negativeCacheHits - before.negativeCacheHits,
|
|
321
|
+
circuitBreakerTrips: after.circuitBreakerTrips - before.circuitBreakerTrips,
|
|
322
|
+
degradedOperations: after.degradedOperations - before.degradedOperations,
|
|
323
|
+
hitsByLayer: diffMetricMap(before.hitsByLayer, after.hitsByLayer),
|
|
324
|
+
missesByLayer: diffMetricMap(before.missesByLayer, after.missesByLayer),
|
|
325
|
+
latencyByLayer,
|
|
326
|
+
resetAt: after.resetAt
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
function addNamespaceMetrics(base, delta) {
|
|
330
|
+
return {
|
|
331
|
+
hits: base.hits + delta.hits,
|
|
332
|
+
misses: base.misses + delta.misses,
|
|
333
|
+
fetches: base.fetches + delta.fetches,
|
|
334
|
+
sets: base.sets + delta.sets,
|
|
335
|
+
deletes: base.deletes + delta.deletes,
|
|
336
|
+
backfills: base.backfills + delta.backfills,
|
|
337
|
+
invalidations: base.invalidations + delta.invalidations,
|
|
338
|
+
staleHits: base.staleHits + delta.staleHits,
|
|
339
|
+
refreshes: base.refreshes + delta.refreshes,
|
|
340
|
+
refreshErrors: base.refreshErrors + delta.refreshErrors,
|
|
341
|
+
writeFailures: base.writeFailures + delta.writeFailures,
|
|
342
|
+
singleFlightWaits: base.singleFlightWaits + delta.singleFlightWaits,
|
|
343
|
+
negativeCacheHits: base.negativeCacheHits + delta.negativeCacheHits,
|
|
344
|
+
circuitBreakerTrips: base.circuitBreakerTrips + delta.circuitBreakerTrips,
|
|
345
|
+
degradedOperations: base.degradedOperations + delta.degradedOperations,
|
|
346
|
+
hitsByLayer: addMetricMap(base.hitsByLayer, delta.hitsByLayer),
|
|
347
|
+
missesByLayer: addMetricMap(base.missesByLayer, delta.missesByLayer),
|
|
348
|
+
latencyByLayer: cloneNamespaceMetrics(delta).latencyByLayer,
|
|
349
|
+
resetAt: base.resetAt
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
function computeNamespaceHitRate(metrics) {
|
|
353
|
+
const total = metrics.hits + metrics.misses;
|
|
354
|
+
const overall = total === 0 ? 0 : metrics.hits / total;
|
|
355
|
+
const byLayer = {};
|
|
356
|
+
const layers = /* @__PURE__ */ new Set([...Object.keys(metrics.hitsByLayer), ...Object.keys(metrics.missesByLayer)]);
|
|
357
|
+
for (const layer of layers) {
|
|
358
|
+
const hits = metrics.hitsByLayer[layer] ?? 0;
|
|
359
|
+
const misses = metrics.missesByLayer[layer] ?? 0;
|
|
360
|
+
byLayer[layer] = hits + misses === 0 ? 0 : hits / (hits + misses);
|
|
361
|
+
}
|
|
362
|
+
return { overall, byLayer };
|
|
363
|
+
}
|
|
364
|
+
function diffMetricMap(before, after) {
|
|
365
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)]);
|
|
366
|
+
const result = {};
|
|
367
|
+
for (const key of keys) {
|
|
368
|
+
result[key] = (after[key] ?? 0) - (before[key] ?? 0);
|
|
369
|
+
}
|
|
370
|
+
return result;
|
|
371
|
+
}
|
|
372
|
+
function addMetricMap(base, delta) {
|
|
373
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(base), ...Object.keys(delta)]);
|
|
374
|
+
const result = {};
|
|
375
|
+
for (const key of keys) {
|
|
376
|
+
result[key] = (base[key] ?? 0) + (delta[key] ?? 0);
|
|
377
|
+
}
|
|
378
|
+
return result;
|
|
379
|
+
}
|
|
380
|
+
|
|
262
381
|
// ../../src/CacheNamespace.ts
|
|
263
382
|
var CacheNamespace = class _CacheNamespace {
|
|
264
383
|
constructor(cache, prefix) {
|
|
@@ -269,7 +388,7 @@ var CacheNamespace = class _CacheNamespace {
|
|
|
269
388
|
cache;
|
|
270
389
|
prefix;
|
|
271
390
|
static metricsMutexes = /* @__PURE__ */ new WeakMap();
|
|
272
|
-
metrics =
|
|
391
|
+
metrics = createEmptyNamespaceMetrics();
|
|
273
392
|
async get(key, fetcher, options) {
|
|
274
393
|
return this.trackMetrics(() => this.cache.get(this.qualify(key), fetcher, this.qualifyGetOptions(options)));
|
|
275
394
|
}
|
|
@@ -366,19 +485,10 @@ var CacheNamespace = class _CacheNamespace {
|
|
|
366
485
|
);
|
|
367
486
|
}
|
|
368
487
|
getMetrics() {
|
|
369
|
-
return
|
|
488
|
+
return cloneNamespaceMetrics(this.metrics);
|
|
370
489
|
}
|
|
371
490
|
getHitRate() {
|
|
372
|
-
|
|
373
|
-
const overall = total === 0 ? 0 : this.metrics.hits / total;
|
|
374
|
-
const byLayer = {};
|
|
375
|
-
const layers = /* @__PURE__ */ new Set([...Object.keys(this.metrics.hitsByLayer), ...Object.keys(this.metrics.missesByLayer)]);
|
|
376
|
-
for (const layer of layers) {
|
|
377
|
-
const hits = this.metrics.hitsByLayer[layer] ?? 0;
|
|
378
|
-
const misses = this.metrics.missesByLayer[layer] ?? 0;
|
|
379
|
-
byLayer[layer] = hits + misses === 0 ? 0 : hits / (hits + misses);
|
|
380
|
-
}
|
|
381
|
-
return { overall, byLayer };
|
|
491
|
+
return computeNamespaceHitRate(this.metrics);
|
|
382
492
|
}
|
|
383
493
|
/**
|
|
384
494
|
* Creates a nested namespace. Keys are prefixed with `parentPrefix:childPrefix:`.
|
|
@@ -419,7 +529,7 @@ var CacheNamespace = class _CacheNamespace {
|
|
|
419
529
|
const before = this.cache.getMetrics();
|
|
420
530
|
const result = await operation();
|
|
421
531
|
const after = this.cache.getMetrics();
|
|
422
|
-
this.metrics =
|
|
532
|
+
this.metrics = addNamespaceMetrics(this.metrics, diffNamespaceMetrics(before, after));
|
|
423
533
|
return result;
|
|
424
534
|
});
|
|
425
535
|
}
|
|
@@ -433,111 +543,6 @@ var CacheNamespace = class _CacheNamespace {
|
|
|
433
543
|
return mutex;
|
|
434
544
|
}
|
|
435
545
|
};
|
|
436
|
-
function emptyMetrics() {
|
|
437
|
-
return {
|
|
438
|
-
hits: 0,
|
|
439
|
-
misses: 0,
|
|
440
|
-
fetches: 0,
|
|
441
|
-
sets: 0,
|
|
442
|
-
deletes: 0,
|
|
443
|
-
backfills: 0,
|
|
444
|
-
invalidations: 0,
|
|
445
|
-
staleHits: 0,
|
|
446
|
-
refreshes: 0,
|
|
447
|
-
refreshErrors: 0,
|
|
448
|
-
writeFailures: 0,
|
|
449
|
-
singleFlightWaits: 0,
|
|
450
|
-
negativeCacheHits: 0,
|
|
451
|
-
circuitBreakerTrips: 0,
|
|
452
|
-
degradedOperations: 0,
|
|
453
|
-
hitsByLayer: {},
|
|
454
|
-
missesByLayer: {},
|
|
455
|
-
latencyByLayer: {},
|
|
456
|
-
resetAt: Date.now()
|
|
457
|
-
};
|
|
458
|
-
}
|
|
459
|
-
function cloneMetrics(metrics) {
|
|
460
|
-
return {
|
|
461
|
-
...metrics,
|
|
462
|
-
hitsByLayer: { ...metrics.hitsByLayer },
|
|
463
|
-
missesByLayer: { ...metrics.missesByLayer },
|
|
464
|
-
latencyByLayer: Object.fromEntries(
|
|
465
|
-
Object.entries(metrics.latencyByLayer).map(([key, value]) => [key, { ...value }])
|
|
466
|
-
)
|
|
467
|
-
};
|
|
468
|
-
}
|
|
469
|
-
function diffMetrics(before, after) {
|
|
470
|
-
const latencyByLayer = Object.fromEntries(
|
|
471
|
-
Object.entries(after.latencyByLayer).map(([layer, value]) => [
|
|
472
|
-
layer,
|
|
473
|
-
{
|
|
474
|
-
avgMs: value.avgMs,
|
|
475
|
-
maxMs: value.maxMs,
|
|
476
|
-
count: Math.max(0, value.count - (before.latencyByLayer[layer]?.count ?? 0))
|
|
477
|
-
}
|
|
478
|
-
])
|
|
479
|
-
);
|
|
480
|
-
return {
|
|
481
|
-
hits: after.hits - before.hits,
|
|
482
|
-
misses: after.misses - before.misses,
|
|
483
|
-
fetches: after.fetches - before.fetches,
|
|
484
|
-
sets: after.sets - before.sets,
|
|
485
|
-
deletes: after.deletes - before.deletes,
|
|
486
|
-
backfills: after.backfills - before.backfills,
|
|
487
|
-
invalidations: after.invalidations - before.invalidations,
|
|
488
|
-
staleHits: after.staleHits - before.staleHits,
|
|
489
|
-
refreshes: after.refreshes - before.refreshes,
|
|
490
|
-
refreshErrors: after.refreshErrors - before.refreshErrors,
|
|
491
|
-
writeFailures: after.writeFailures - before.writeFailures,
|
|
492
|
-
singleFlightWaits: after.singleFlightWaits - before.singleFlightWaits,
|
|
493
|
-
negativeCacheHits: after.negativeCacheHits - before.negativeCacheHits,
|
|
494
|
-
circuitBreakerTrips: after.circuitBreakerTrips - before.circuitBreakerTrips,
|
|
495
|
-
degradedOperations: after.degradedOperations - before.degradedOperations,
|
|
496
|
-
hitsByLayer: diffMap(before.hitsByLayer, after.hitsByLayer),
|
|
497
|
-
missesByLayer: diffMap(before.missesByLayer, after.missesByLayer),
|
|
498
|
-
latencyByLayer,
|
|
499
|
-
resetAt: after.resetAt
|
|
500
|
-
};
|
|
501
|
-
}
|
|
502
|
-
function addMetrics(base, delta) {
|
|
503
|
-
return {
|
|
504
|
-
hits: base.hits + delta.hits,
|
|
505
|
-
misses: base.misses + delta.misses,
|
|
506
|
-
fetches: base.fetches + delta.fetches,
|
|
507
|
-
sets: base.sets + delta.sets,
|
|
508
|
-
deletes: base.deletes + delta.deletes,
|
|
509
|
-
backfills: base.backfills + delta.backfills,
|
|
510
|
-
invalidations: base.invalidations + delta.invalidations,
|
|
511
|
-
staleHits: base.staleHits + delta.staleHits,
|
|
512
|
-
refreshes: base.refreshes + delta.refreshes,
|
|
513
|
-
refreshErrors: base.refreshErrors + delta.refreshErrors,
|
|
514
|
-
writeFailures: base.writeFailures + delta.writeFailures,
|
|
515
|
-
singleFlightWaits: base.singleFlightWaits + delta.singleFlightWaits,
|
|
516
|
-
negativeCacheHits: base.negativeCacheHits + delta.negativeCacheHits,
|
|
517
|
-
circuitBreakerTrips: base.circuitBreakerTrips + delta.circuitBreakerTrips,
|
|
518
|
-
degradedOperations: base.degradedOperations + delta.degradedOperations,
|
|
519
|
-
hitsByLayer: addMap(base.hitsByLayer, delta.hitsByLayer),
|
|
520
|
-
missesByLayer: addMap(base.missesByLayer, delta.missesByLayer),
|
|
521
|
-
latencyByLayer: cloneMetrics(delta).latencyByLayer,
|
|
522
|
-
resetAt: base.resetAt
|
|
523
|
-
};
|
|
524
|
-
}
|
|
525
|
-
function diffMap(before, after) {
|
|
526
|
-
const keys = /* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)]);
|
|
527
|
-
const result = {};
|
|
528
|
-
for (const key of keys) {
|
|
529
|
-
result[key] = (after[key] ?? 0) - (before[key] ?? 0);
|
|
530
|
-
}
|
|
531
|
-
return result;
|
|
532
|
-
}
|
|
533
|
-
function addMap(base, delta) {
|
|
534
|
-
const keys = /* @__PURE__ */ new Set([...Object.keys(base), ...Object.keys(delta)]);
|
|
535
|
-
const result = {};
|
|
536
|
-
for (const key of keys) {
|
|
537
|
-
result[key] = (base[key] ?? 0) + (delta[key] ?? 0);
|
|
538
|
-
}
|
|
539
|
-
return result;
|
|
540
|
-
}
|
|
541
546
|
function validateNamespaceKey(key) {
|
|
542
547
|
if (key.length === 0) {
|
|
543
548
|
throw new Error("Namespace prefix must not be empty.");
|
|
@@ -840,7 +845,346 @@ async function readUtf8HandleWithLimit(handle, byteLimit) {
|
|
|
840
845
|
chunks.push(buffer.subarray(0, bytesRead));
|
|
841
846
|
position += bytesRead;
|
|
842
847
|
}
|
|
843
|
-
return Buffer.concat(chunks).toString("utf8");
|
|
848
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
// ../../src/internal/CacheStackGeneration.ts
|
|
852
|
+
var DEFAULT_GENERATION_CLEANUP_BATCH_SIZE = 500;
|
|
853
|
+
function generationPrefix(generation) {
|
|
854
|
+
return generation === void 0 ? "" : `v${generation}:`;
|
|
855
|
+
}
|
|
856
|
+
function qualifyGenerationKey(key, generation) {
|
|
857
|
+
const prefix = generationPrefix(generation);
|
|
858
|
+
return prefix ? `${prefix}${key}` : key;
|
|
859
|
+
}
|
|
860
|
+
function qualifyGenerationPattern(pattern, generation) {
|
|
861
|
+
return qualifyGenerationKey(pattern, generation);
|
|
862
|
+
}
|
|
863
|
+
function stripGenerationPrefix(key, generation) {
|
|
864
|
+
const prefix = generationPrefix(generation);
|
|
865
|
+
if (!prefix || !key.startsWith(prefix)) {
|
|
866
|
+
return key;
|
|
867
|
+
}
|
|
868
|
+
return key.slice(prefix.length);
|
|
869
|
+
}
|
|
870
|
+
function resolveGenerationCleanupTarget({
|
|
871
|
+
previousGeneration,
|
|
872
|
+
nextGeneration,
|
|
873
|
+
generationCleanup
|
|
874
|
+
}) {
|
|
875
|
+
if (!generationCleanup || previousGeneration === void 0 || previousGeneration === nextGeneration) {
|
|
876
|
+
return null;
|
|
877
|
+
}
|
|
878
|
+
return previousGeneration;
|
|
879
|
+
}
|
|
880
|
+
function resolveGenerationCleanupBatchSize(generationCleanup) {
|
|
881
|
+
if (typeof generationCleanup !== "object" || generationCleanup === null) {
|
|
882
|
+
return DEFAULT_GENERATION_CLEANUP_BATCH_SIZE;
|
|
883
|
+
}
|
|
884
|
+
return generationCleanup.batchSize ?? DEFAULT_GENERATION_CLEANUP_BATCH_SIZE;
|
|
885
|
+
}
|
|
886
|
+
function planGenerationCleanupBatches(keys, generationCleanup) {
|
|
887
|
+
if (keys.length === 0) {
|
|
888
|
+
return [];
|
|
889
|
+
}
|
|
890
|
+
const batchSize = resolveGenerationCleanupBatchSize(generationCleanup);
|
|
891
|
+
const batches = [];
|
|
892
|
+
for (let index = 0; index < keys.length; index += batchSize) {
|
|
893
|
+
batches.push(keys.slice(index, index + batchSize));
|
|
894
|
+
}
|
|
895
|
+
return batches;
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
// ../../src/internal/CacheStackMaintenance.ts
|
|
899
|
+
var CacheStackMaintenance = class {
|
|
900
|
+
keyEpochs = /* @__PURE__ */ new Map();
|
|
901
|
+
writeBehindQueue = [];
|
|
902
|
+
writeBehindTimer;
|
|
903
|
+
writeBehindFlushPromise;
|
|
904
|
+
generationCleanupPromise;
|
|
905
|
+
clearEpoch = 0;
|
|
906
|
+
initializeWriteBehindTimer(writeStrategy, options, flush) {
|
|
907
|
+
if (writeStrategy !== "write-behind") {
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
910
|
+
const flushIntervalMs = options?.flushIntervalMs;
|
|
911
|
+
if (!flushIntervalMs || flushIntervalMs <= 0) {
|
|
912
|
+
return;
|
|
913
|
+
}
|
|
914
|
+
this.disposeWriteBehindTimer();
|
|
915
|
+
this.writeBehindTimer = setInterval(() => {
|
|
916
|
+
void flush();
|
|
917
|
+
}, flushIntervalMs);
|
|
918
|
+
this.writeBehindTimer.unref?.();
|
|
919
|
+
}
|
|
920
|
+
disposeWriteBehindTimer() {
|
|
921
|
+
if (!this.writeBehindTimer) {
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
clearInterval(this.writeBehindTimer);
|
|
925
|
+
this.writeBehindTimer = void 0;
|
|
926
|
+
}
|
|
927
|
+
beginClearEpoch() {
|
|
928
|
+
this.clearEpoch += 1;
|
|
929
|
+
this.keyEpochs.clear();
|
|
930
|
+
this.writeBehindQueue.length = 0;
|
|
931
|
+
}
|
|
932
|
+
currentClearEpoch() {
|
|
933
|
+
return this.clearEpoch;
|
|
934
|
+
}
|
|
935
|
+
currentKeyEpoch(key) {
|
|
936
|
+
return this.keyEpochs.get(key) ?? 0;
|
|
937
|
+
}
|
|
938
|
+
bumpKeyEpochs(keys) {
|
|
939
|
+
for (const key of keys) {
|
|
940
|
+
this.keyEpochs.set(key, this.currentKeyEpoch(key) + 1);
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
isWriteOutdated(key, expectedClearEpoch, expectedKeyEpoch) {
|
|
944
|
+
if (expectedClearEpoch !== void 0 && expectedClearEpoch !== this.clearEpoch) {
|
|
945
|
+
return true;
|
|
946
|
+
}
|
|
947
|
+
if (expectedKeyEpoch !== void 0 && expectedKeyEpoch !== this.currentKeyEpoch(key)) {
|
|
948
|
+
return true;
|
|
949
|
+
}
|
|
950
|
+
return false;
|
|
951
|
+
}
|
|
952
|
+
async enqueueWriteBehind(operation, options, flushBatch) {
|
|
953
|
+
this.writeBehindQueue.push(operation);
|
|
954
|
+
const batchSize = options?.batchSize ?? 100;
|
|
955
|
+
const maxQueueSize = options?.maxQueueSize ?? batchSize * 10;
|
|
956
|
+
if (this.writeBehindQueue.length >= batchSize) {
|
|
957
|
+
await this.flushWriteBehindQueue(options, flushBatch);
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
960
|
+
if (this.writeBehindQueue.length >= maxQueueSize) {
|
|
961
|
+
await this.flushWriteBehindQueue(options, flushBatch);
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
async flushWriteBehindQueue(options, flushBatch) {
|
|
965
|
+
if (this.writeBehindFlushPromise || this.writeBehindQueue.length === 0) {
|
|
966
|
+
await this.writeBehindFlushPromise;
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
969
|
+
const batchSize = options?.batchSize ?? 100;
|
|
970
|
+
const batch = this.writeBehindQueue.splice(0, batchSize);
|
|
971
|
+
this.writeBehindFlushPromise = flushBatch(batch);
|
|
972
|
+
try {
|
|
973
|
+
await this.writeBehindFlushPromise;
|
|
974
|
+
} finally {
|
|
975
|
+
this.writeBehindFlushPromise = void 0;
|
|
976
|
+
}
|
|
977
|
+
if (this.writeBehindQueue.length > 0) {
|
|
978
|
+
await this.flushWriteBehindQueue(options, flushBatch);
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
scheduleGenerationCleanup(generation, task, onError) {
|
|
982
|
+
const scheduledTask = (this.generationCleanupPromise ?? Promise.resolve()).then(() => task(generation)).catch((error) => {
|
|
983
|
+
onError(generation, error);
|
|
984
|
+
});
|
|
985
|
+
this.generationCleanupPromise = scheduledTask.finally(() => {
|
|
986
|
+
if (this.generationCleanupPromise === scheduledTask) {
|
|
987
|
+
this.generationCleanupPromise = void 0;
|
|
988
|
+
}
|
|
989
|
+
});
|
|
990
|
+
}
|
|
991
|
+
async waitForGenerationCleanup() {
|
|
992
|
+
await this.generationCleanupPromise;
|
|
993
|
+
}
|
|
994
|
+
};
|
|
995
|
+
|
|
996
|
+
// ../../src/internal/StoredValue.ts
|
|
997
|
+
function isStoredValueEnvelope(value) {
|
|
998
|
+
if (typeof value !== "object" || value === null) {
|
|
999
|
+
return false;
|
|
1000
|
+
}
|
|
1001
|
+
const v = value;
|
|
1002
|
+
if (v.__layercache !== 1) {
|
|
1003
|
+
return false;
|
|
1004
|
+
}
|
|
1005
|
+
if (v.kind !== "value" && v.kind !== "empty") {
|
|
1006
|
+
return false;
|
|
1007
|
+
}
|
|
1008
|
+
if (v.freshUntil !== null && (!Number.isFinite(v.freshUntil) || typeof v.freshUntil !== "number")) {
|
|
1009
|
+
return false;
|
|
1010
|
+
}
|
|
1011
|
+
if (v.staleUntil !== null && (!Number.isFinite(v.staleUntil) || typeof v.staleUntil !== "number")) {
|
|
1012
|
+
return false;
|
|
1013
|
+
}
|
|
1014
|
+
if (v.errorUntil !== null && (!Number.isFinite(v.errorUntil) || typeof v.errorUntil !== "number")) {
|
|
1015
|
+
return false;
|
|
1016
|
+
}
|
|
1017
|
+
const maxTimestamp = Date.now() + 10 * 365 * 24 * 60 * 60 * 1e3;
|
|
1018
|
+
if (typeof v.freshUntil === "number" && v.freshUntil > maxTimestamp) {
|
|
1019
|
+
return false;
|
|
1020
|
+
}
|
|
1021
|
+
if (typeof v.staleUntil === "number" && v.staleUntil > maxTimestamp) {
|
|
1022
|
+
return false;
|
|
1023
|
+
}
|
|
1024
|
+
if (typeof v.errorUntil === "number" && v.errorUntil > maxTimestamp) {
|
|
1025
|
+
return false;
|
|
1026
|
+
}
|
|
1027
|
+
if (v.freshUntil === null && (v.staleUntil !== null || v.errorUntil !== null)) {
|
|
1028
|
+
return false;
|
|
1029
|
+
}
|
|
1030
|
+
if (typeof v.freshUntil === "number" && typeof v.staleUntil === "number" && v.staleUntil < v.freshUntil) {
|
|
1031
|
+
return false;
|
|
1032
|
+
}
|
|
1033
|
+
if (typeof v.freshUntil === "number" && typeof v.errorUntil === "number" && v.errorUntil < v.freshUntil) {
|
|
1034
|
+
return false;
|
|
1035
|
+
}
|
|
1036
|
+
const maxTtlSeconds = 10 * 365 * 24 * 60 * 60;
|
|
1037
|
+
if (!isValidEnvelopeTtlSeconds(v.freshTtlSeconds, maxTtlSeconds)) {
|
|
1038
|
+
return false;
|
|
1039
|
+
}
|
|
1040
|
+
if (!isValidEnvelopeTtlSeconds(v.staleWhileRevalidateSeconds, maxTtlSeconds)) {
|
|
1041
|
+
return false;
|
|
1042
|
+
}
|
|
1043
|
+
if (!isValidEnvelopeTtlSeconds(v.staleIfErrorSeconds, maxTtlSeconds)) {
|
|
1044
|
+
return false;
|
|
1045
|
+
}
|
|
1046
|
+
if (v.freshTtlSeconds == null && (v.staleWhileRevalidateSeconds != null || v.staleIfErrorSeconds != null)) {
|
|
1047
|
+
return false;
|
|
1048
|
+
}
|
|
1049
|
+
return true;
|
|
1050
|
+
}
|
|
1051
|
+
function createStoredValueEnvelope(options) {
|
|
1052
|
+
const now = options.now ?? Date.now();
|
|
1053
|
+
const freshTtlSeconds = normalizePositiveSeconds(options.freshTtlSeconds);
|
|
1054
|
+
const staleWhileRevalidateSeconds = normalizePositiveSeconds(options.staleWhileRevalidateSeconds);
|
|
1055
|
+
const staleIfErrorSeconds = normalizePositiveSeconds(options.staleIfErrorSeconds);
|
|
1056
|
+
const freshUntil = freshTtlSeconds ? now + freshTtlSeconds * 1e3 : null;
|
|
1057
|
+
const staleUntil = freshUntil && staleWhileRevalidateSeconds ? freshUntil + staleWhileRevalidateSeconds * 1e3 : null;
|
|
1058
|
+
const errorUntil = freshUntil && staleIfErrorSeconds ? freshUntil + staleIfErrorSeconds * 1e3 : null;
|
|
1059
|
+
return {
|
|
1060
|
+
__layercache: 1,
|
|
1061
|
+
kind: options.kind,
|
|
1062
|
+
value: options.value,
|
|
1063
|
+
freshUntil,
|
|
1064
|
+
staleUntil,
|
|
1065
|
+
errorUntil,
|
|
1066
|
+
freshTtlSeconds: freshTtlSeconds ?? null,
|
|
1067
|
+
staleWhileRevalidateSeconds: staleWhileRevalidateSeconds ?? null,
|
|
1068
|
+
staleIfErrorSeconds: staleIfErrorSeconds ?? null
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
function resolveStoredValue(stored, now = Date.now()) {
|
|
1072
|
+
if (!isStoredValueEnvelope(stored)) {
|
|
1073
|
+
return { state: "fresh", value: stored, stored };
|
|
1074
|
+
}
|
|
1075
|
+
if (stored.freshUntil === null || stored.freshUntil > now) {
|
|
1076
|
+
return { state: "fresh", value: unwrapStoredValue(stored), stored, envelope: stored };
|
|
1077
|
+
}
|
|
1078
|
+
if (stored.staleUntil !== null && stored.staleUntil > now) {
|
|
1079
|
+
return { state: "stale-while-revalidate", value: unwrapStoredValue(stored), stored, envelope: stored };
|
|
1080
|
+
}
|
|
1081
|
+
if (stored.errorUntil !== null && stored.errorUntil > now) {
|
|
1082
|
+
return { state: "stale-if-error", value: unwrapStoredValue(stored), stored, envelope: stored };
|
|
1083
|
+
}
|
|
1084
|
+
return { state: "expired", value: null, stored, envelope: stored };
|
|
1085
|
+
}
|
|
1086
|
+
function unwrapStoredValue(stored) {
|
|
1087
|
+
if (!isStoredValueEnvelope(stored)) {
|
|
1088
|
+
return stored;
|
|
1089
|
+
}
|
|
1090
|
+
if (stored.kind === "empty") {
|
|
1091
|
+
return null;
|
|
1092
|
+
}
|
|
1093
|
+
return stored.value ?? null;
|
|
1094
|
+
}
|
|
1095
|
+
function remainingStoredTtlSeconds(stored, now = Date.now()) {
|
|
1096
|
+
if (!isStoredValueEnvelope(stored)) {
|
|
1097
|
+
return void 0;
|
|
1098
|
+
}
|
|
1099
|
+
const expiry = maxExpiry(stored);
|
|
1100
|
+
if (expiry === null) {
|
|
1101
|
+
return void 0;
|
|
1102
|
+
}
|
|
1103
|
+
const remainingMs = expiry - now;
|
|
1104
|
+
if (remainingMs <= 0) {
|
|
1105
|
+
return 1;
|
|
1106
|
+
}
|
|
1107
|
+
return Math.max(1, Math.ceil(remainingMs / 1e3));
|
|
1108
|
+
}
|
|
1109
|
+
function remainingFreshTtlSeconds(stored, now = Date.now()) {
|
|
1110
|
+
if (!isStoredValueEnvelope(stored) || stored.freshUntil === null) {
|
|
1111
|
+
return void 0;
|
|
1112
|
+
}
|
|
1113
|
+
const remainingMs = stored.freshUntil - now;
|
|
1114
|
+
if (remainingMs <= 0) {
|
|
1115
|
+
return 0;
|
|
1116
|
+
}
|
|
1117
|
+
return Math.max(1, Math.ceil(remainingMs / 1e3));
|
|
1118
|
+
}
|
|
1119
|
+
function refreshStoredEnvelope(stored, now = Date.now()) {
|
|
1120
|
+
if (!isStoredValueEnvelope(stored)) {
|
|
1121
|
+
return stored;
|
|
1122
|
+
}
|
|
1123
|
+
return createStoredValueEnvelope({
|
|
1124
|
+
kind: stored.kind,
|
|
1125
|
+
value: stored.value,
|
|
1126
|
+
freshTtlSeconds: stored.freshTtlSeconds ?? void 0,
|
|
1127
|
+
staleWhileRevalidateSeconds: stored.staleWhileRevalidateSeconds ?? void 0,
|
|
1128
|
+
staleIfErrorSeconds: stored.staleIfErrorSeconds ?? void 0,
|
|
1129
|
+
now
|
|
1130
|
+
});
|
|
1131
|
+
}
|
|
1132
|
+
function maxExpiry(stored) {
|
|
1133
|
+
const values = [stored.freshUntil, stored.staleUntil, stored.errorUntil].filter(
|
|
1134
|
+
(value) => value !== null
|
|
1135
|
+
);
|
|
1136
|
+
if (values.length === 0) {
|
|
1137
|
+
return null;
|
|
1138
|
+
}
|
|
1139
|
+
return Math.max(...values);
|
|
1140
|
+
}
|
|
1141
|
+
function normalizePositiveSeconds(value) {
|
|
1142
|
+
if (!value || value <= 0) {
|
|
1143
|
+
return void 0;
|
|
1144
|
+
}
|
|
1145
|
+
return value;
|
|
1146
|
+
}
|
|
1147
|
+
function isValidEnvelopeTtlSeconds(value, maxTtlSeconds) {
|
|
1148
|
+
if (value == null) {
|
|
1149
|
+
return true;
|
|
1150
|
+
}
|
|
1151
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 && value <= maxTtlSeconds;
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
// ../../src/internal/CacheStackRuntimePolicy.ts
|
|
1155
|
+
function shouldSkipLayer(degradedUntil, now = Date.now()) {
|
|
1156
|
+
return degradedUntil !== void 0 && degradedUntil > now;
|
|
1157
|
+
}
|
|
1158
|
+
function shouldStartBackgroundRefresh({
|
|
1159
|
+
isDisconnecting,
|
|
1160
|
+
hasRefreshInFlight
|
|
1161
|
+
}) {
|
|
1162
|
+
return !isDisconnecting && !hasRefreshInFlight;
|
|
1163
|
+
}
|
|
1164
|
+
function resolveRecoverableLayerFailure(gracefulDegradation, now = Date.now()) {
|
|
1165
|
+
if (!gracefulDegradation) {
|
|
1166
|
+
return { degrade: false };
|
|
1167
|
+
}
|
|
1168
|
+
const retryAfterMs = typeof gracefulDegradation === "object" ? gracefulDegradation.retryAfterMs ?? 1e4 : 1e4;
|
|
1169
|
+
return {
|
|
1170
|
+
degrade: true,
|
|
1171
|
+
degradedUntil: now + retryAfterMs
|
|
1172
|
+
};
|
|
1173
|
+
}
|
|
1174
|
+
function planFreshReadPolicies({
|
|
1175
|
+
stored,
|
|
1176
|
+
hasFetcher,
|
|
1177
|
+
slidingTtl,
|
|
1178
|
+
refreshAheadSeconds
|
|
1179
|
+
}) {
|
|
1180
|
+
const refreshedStored = slidingTtl && isStoredValueEnvelope(stored) ? refreshStoredEnvelope(stored) : void 0;
|
|
1181
|
+
const refreshedStoredTtl = refreshedStored ? remainingStoredTtlSeconds(refreshedStored) ?? void 0 : void 0;
|
|
1182
|
+
const remainingFreshTtl = remainingFreshTtlSeconds(stored) ?? 0;
|
|
1183
|
+
return {
|
|
1184
|
+
refreshedStored,
|
|
1185
|
+
refreshedStoredTtl,
|
|
1186
|
+
shouldScheduleBackgroundRefresh: hasFetcher && refreshAheadSeconds > 0 && remainingFreshTtl > 0 && remainingFreshTtl <= refreshAheadSeconds
|
|
1187
|
+
};
|
|
844
1188
|
}
|
|
845
1189
|
|
|
846
1190
|
// ../../src/internal/CacheStackValidation.ts
|
|
@@ -996,7 +1340,6 @@ var CircuitBreakerManager = class {
|
|
|
996
1340
|
if (!options) {
|
|
997
1341
|
return;
|
|
998
1342
|
}
|
|
999
|
-
this.pruneIfNeeded();
|
|
1000
1343
|
const failureThreshold = options.failureThreshold ?? 3;
|
|
1001
1344
|
const cooldownMs = options.cooldownMs ?? 3e4;
|
|
1002
1345
|
const state = this.breakers.get(key) ?? { failures: 0, openUntil: null, createdAt: Date.now() };
|
|
@@ -1005,6 +1348,7 @@ var CircuitBreakerManager = class {
|
|
|
1005
1348
|
state.openUntil = Date.now() + cooldownMs;
|
|
1006
1349
|
}
|
|
1007
1350
|
this.breakers.set(key, state);
|
|
1351
|
+
this.pruneIfNeeded();
|
|
1008
1352
|
}
|
|
1009
1353
|
recordSuccess(key) {
|
|
1010
1354
|
this.breakers.delete(key);
|
|
@@ -1348,164 +1692,6 @@ var MetricsCollector = class {
|
|
|
1348
1692
|
}
|
|
1349
1693
|
};
|
|
1350
1694
|
|
|
1351
|
-
// ../../src/internal/StoredValue.ts
|
|
1352
|
-
function isStoredValueEnvelope(value) {
|
|
1353
|
-
if (typeof value !== "object" || value === null) {
|
|
1354
|
-
return false;
|
|
1355
|
-
}
|
|
1356
|
-
const v = value;
|
|
1357
|
-
if (v.__layercache !== 1) {
|
|
1358
|
-
return false;
|
|
1359
|
-
}
|
|
1360
|
-
if (v.kind !== "value" && v.kind !== "empty") {
|
|
1361
|
-
return false;
|
|
1362
|
-
}
|
|
1363
|
-
if (v.freshUntil !== null && (!Number.isFinite(v.freshUntil) || typeof v.freshUntil !== "number")) {
|
|
1364
|
-
return false;
|
|
1365
|
-
}
|
|
1366
|
-
if (v.staleUntil !== null && (!Number.isFinite(v.staleUntil) || typeof v.staleUntil !== "number")) {
|
|
1367
|
-
return false;
|
|
1368
|
-
}
|
|
1369
|
-
if (v.errorUntil !== null && (!Number.isFinite(v.errorUntil) || typeof v.errorUntil !== "number")) {
|
|
1370
|
-
return false;
|
|
1371
|
-
}
|
|
1372
|
-
const maxTimestamp = Date.now() + 10 * 365 * 24 * 60 * 60 * 1e3;
|
|
1373
|
-
if (typeof v.freshUntil === "number" && v.freshUntil > maxTimestamp) {
|
|
1374
|
-
return false;
|
|
1375
|
-
}
|
|
1376
|
-
if (typeof v.staleUntil === "number" && v.staleUntil > maxTimestamp) {
|
|
1377
|
-
return false;
|
|
1378
|
-
}
|
|
1379
|
-
if (typeof v.errorUntil === "number" && v.errorUntil > maxTimestamp) {
|
|
1380
|
-
return false;
|
|
1381
|
-
}
|
|
1382
|
-
if (v.freshUntil === null && (v.staleUntil !== null || v.errorUntil !== null)) {
|
|
1383
|
-
return false;
|
|
1384
|
-
}
|
|
1385
|
-
if (typeof v.freshUntil === "number" && typeof v.staleUntil === "number" && v.staleUntil < v.freshUntil) {
|
|
1386
|
-
return false;
|
|
1387
|
-
}
|
|
1388
|
-
if (typeof v.freshUntil === "number" && typeof v.errorUntil === "number" && v.errorUntil < v.freshUntil) {
|
|
1389
|
-
return false;
|
|
1390
|
-
}
|
|
1391
|
-
const maxTtlSeconds = 10 * 365 * 24 * 60 * 60;
|
|
1392
|
-
if (!isValidEnvelopeTtlSeconds(v.freshTtlSeconds, maxTtlSeconds)) {
|
|
1393
|
-
return false;
|
|
1394
|
-
}
|
|
1395
|
-
if (!isValidEnvelopeTtlSeconds(v.staleWhileRevalidateSeconds, maxTtlSeconds)) {
|
|
1396
|
-
return false;
|
|
1397
|
-
}
|
|
1398
|
-
if (!isValidEnvelopeTtlSeconds(v.staleIfErrorSeconds, maxTtlSeconds)) {
|
|
1399
|
-
return false;
|
|
1400
|
-
}
|
|
1401
|
-
if (v.freshTtlSeconds == null && (v.staleWhileRevalidateSeconds != null || v.staleIfErrorSeconds != null)) {
|
|
1402
|
-
return false;
|
|
1403
|
-
}
|
|
1404
|
-
return true;
|
|
1405
|
-
}
|
|
1406
|
-
function createStoredValueEnvelope(options) {
|
|
1407
|
-
const now = options.now ?? Date.now();
|
|
1408
|
-
const freshTtlSeconds = normalizePositiveSeconds(options.freshTtlSeconds);
|
|
1409
|
-
const staleWhileRevalidateSeconds = normalizePositiveSeconds(options.staleWhileRevalidateSeconds);
|
|
1410
|
-
const staleIfErrorSeconds = normalizePositiveSeconds(options.staleIfErrorSeconds);
|
|
1411
|
-
const freshUntil = freshTtlSeconds ? now + freshTtlSeconds * 1e3 : null;
|
|
1412
|
-
const staleUntil = freshUntil && staleWhileRevalidateSeconds ? freshUntil + staleWhileRevalidateSeconds * 1e3 : null;
|
|
1413
|
-
const errorUntil = freshUntil && staleIfErrorSeconds ? freshUntil + staleIfErrorSeconds * 1e3 : null;
|
|
1414
|
-
return {
|
|
1415
|
-
__layercache: 1,
|
|
1416
|
-
kind: options.kind,
|
|
1417
|
-
value: options.value,
|
|
1418
|
-
freshUntil,
|
|
1419
|
-
staleUntil,
|
|
1420
|
-
errorUntil,
|
|
1421
|
-
freshTtlSeconds: freshTtlSeconds ?? null,
|
|
1422
|
-
staleWhileRevalidateSeconds: staleWhileRevalidateSeconds ?? null,
|
|
1423
|
-
staleIfErrorSeconds: staleIfErrorSeconds ?? null
|
|
1424
|
-
};
|
|
1425
|
-
}
|
|
1426
|
-
function resolveStoredValue(stored, now = Date.now()) {
|
|
1427
|
-
if (!isStoredValueEnvelope(stored)) {
|
|
1428
|
-
return { state: "fresh", value: stored, stored };
|
|
1429
|
-
}
|
|
1430
|
-
if (stored.freshUntil === null || stored.freshUntil > now) {
|
|
1431
|
-
return { state: "fresh", value: unwrapStoredValue(stored), stored, envelope: stored };
|
|
1432
|
-
}
|
|
1433
|
-
if (stored.staleUntil !== null && stored.staleUntil > now) {
|
|
1434
|
-
return { state: "stale-while-revalidate", value: unwrapStoredValue(stored), stored, envelope: stored };
|
|
1435
|
-
}
|
|
1436
|
-
if (stored.errorUntil !== null && stored.errorUntil > now) {
|
|
1437
|
-
return { state: "stale-if-error", value: unwrapStoredValue(stored), stored, envelope: stored };
|
|
1438
|
-
}
|
|
1439
|
-
return { state: "expired", value: null, stored, envelope: stored };
|
|
1440
|
-
}
|
|
1441
|
-
function unwrapStoredValue(stored) {
|
|
1442
|
-
if (!isStoredValueEnvelope(stored)) {
|
|
1443
|
-
return stored;
|
|
1444
|
-
}
|
|
1445
|
-
if (stored.kind === "empty") {
|
|
1446
|
-
return null;
|
|
1447
|
-
}
|
|
1448
|
-
return stored.value ?? null;
|
|
1449
|
-
}
|
|
1450
|
-
function remainingStoredTtlSeconds(stored, now = Date.now()) {
|
|
1451
|
-
if (!isStoredValueEnvelope(stored)) {
|
|
1452
|
-
return void 0;
|
|
1453
|
-
}
|
|
1454
|
-
const expiry = maxExpiry(stored);
|
|
1455
|
-
if (expiry === null) {
|
|
1456
|
-
return void 0;
|
|
1457
|
-
}
|
|
1458
|
-
const remainingMs = expiry - now;
|
|
1459
|
-
if (remainingMs <= 0) {
|
|
1460
|
-
return 1;
|
|
1461
|
-
}
|
|
1462
|
-
return Math.max(1, Math.ceil(remainingMs / 1e3));
|
|
1463
|
-
}
|
|
1464
|
-
function remainingFreshTtlSeconds(stored, now = Date.now()) {
|
|
1465
|
-
if (!isStoredValueEnvelope(stored) || stored.freshUntil === null) {
|
|
1466
|
-
return void 0;
|
|
1467
|
-
}
|
|
1468
|
-
const remainingMs = stored.freshUntil - now;
|
|
1469
|
-
if (remainingMs <= 0) {
|
|
1470
|
-
return 0;
|
|
1471
|
-
}
|
|
1472
|
-
return Math.max(1, Math.ceil(remainingMs / 1e3));
|
|
1473
|
-
}
|
|
1474
|
-
function refreshStoredEnvelope(stored, now = Date.now()) {
|
|
1475
|
-
if (!isStoredValueEnvelope(stored)) {
|
|
1476
|
-
return stored;
|
|
1477
|
-
}
|
|
1478
|
-
return createStoredValueEnvelope({
|
|
1479
|
-
kind: stored.kind,
|
|
1480
|
-
value: stored.value,
|
|
1481
|
-
freshTtlSeconds: stored.freshTtlSeconds ?? void 0,
|
|
1482
|
-
staleWhileRevalidateSeconds: stored.staleWhileRevalidateSeconds ?? void 0,
|
|
1483
|
-
staleIfErrorSeconds: stored.staleIfErrorSeconds ?? void 0,
|
|
1484
|
-
now
|
|
1485
|
-
});
|
|
1486
|
-
}
|
|
1487
|
-
function maxExpiry(stored) {
|
|
1488
|
-
const values = [stored.freshUntil, stored.staleUntil, stored.errorUntil].filter(
|
|
1489
|
-
(value) => value !== null
|
|
1490
|
-
);
|
|
1491
|
-
if (values.length === 0) {
|
|
1492
|
-
return null;
|
|
1493
|
-
}
|
|
1494
|
-
return Math.max(...values);
|
|
1495
|
-
}
|
|
1496
|
-
function normalizePositiveSeconds(value) {
|
|
1497
|
-
if (!value || value <= 0) {
|
|
1498
|
-
return void 0;
|
|
1499
|
-
}
|
|
1500
|
-
return value;
|
|
1501
|
-
}
|
|
1502
|
-
function isValidEnvelopeTtlSeconds(value, maxTtlSeconds) {
|
|
1503
|
-
if (value == null) {
|
|
1504
|
-
return true;
|
|
1505
|
-
}
|
|
1506
|
-
return typeof value === "number" && Number.isFinite(value) && value > 0 && value <= maxTtlSeconds;
|
|
1507
|
-
}
|
|
1508
|
-
|
|
1509
1695
|
// ../../src/internal/TtlResolver.ts
|
|
1510
1696
|
var DEFAULT_NEGATIVE_TTL_SECONDS = 60;
|
|
1511
1697
|
var TtlResolver = class {
|
|
@@ -2038,15 +2224,10 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
2038
2224
|
snapshotSerializer = new JsonSerializer();
|
|
2039
2225
|
backgroundRefreshes = /* @__PURE__ */ new Map();
|
|
2040
2226
|
layerDegradedUntil = /* @__PURE__ */ new Map();
|
|
2041
|
-
|
|
2227
|
+
maintenance = new CacheStackMaintenance();
|
|
2042
2228
|
ttlResolver;
|
|
2043
2229
|
circuitBreakerManager;
|
|
2044
2230
|
currentGeneration;
|
|
2045
|
-
writeBehindQueue = [];
|
|
2046
|
-
writeBehindTimer;
|
|
2047
|
-
writeBehindFlushPromise;
|
|
2048
|
-
generationCleanupPromise;
|
|
2049
|
-
clearEpoch = 0;
|
|
2050
2231
|
isDisconnecting = false;
|
|
2051
2232
|
disconnectPromise;
|
|
2052
2233
|
/**
|
|
@@ -2202,7 +2383,7 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
2202
2383
|
}
|
|
2203
2384
|
async clear() {
|
|
2204
2385
|
await this.awaitStartup("clear");
|
|
2205
|
-
this.beginClearEpoch();
|
|
2386
|
+
this.maintenance.beginClearEpoch();
|
|
2206
2387
|
await Promise.all(this.layers.map((layer) => layer.clear()));
|
|
2207
2388
|
await this.tagIndex.clear();
|
|
2208
2389
|
this.ttlResolver.clearProfiles();
|
|
@@ -2460,9 +2641,15 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
2460
2641
|
bumpGeneration(nextGeneration) {
|
|
2461
2642
|
const current = this.currentGeneration ?? 0;
|
|
2462
2643
|
const previousGeneration = this.currentGeneration;
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2644
|
+
const updatedGeneration = nextGeneration ?? current + 1;
|
|
2645
|
+
const generationToCleanup = resolveGenerationCleanupTarget({
|
|
2646
|
+
previousGeneration,
|
|
2647
|
+
nextGeneration: updatedGeneration,
|
|
2648
|
+
generationCleanup: this.options.generationCleanup
|
|
2649
|
+
});
|
|
2650
|
+
this.currentGeneration = updatedGeneration;
|
|
2651
|
+
if (generationToCleanup !== null) {
|
|
2652
|
+
this.scheduleGenerationCleanup(generationToCleanup);
|
|
2466
2653
|
}
|
|
2467
2654
|
return this.currentGeneration;
|
|
2468
2655
|
}
|
|
@@ -2606,12 +2793,9 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
2606
2793
|
await this.startup;
|
|
2607
2794
|
await this.unsubscribeInvalidation?.();
|
|
2608
2795
|
await this.flushWriteBehindQueue();
|
|
2609
|
-
await this.
|
|
2796
|
+
await this.maintenance.waitForGenerationCleanup();
|
|
2610
2797
|
await Promise.allSettled([...this.backgroundRefreshes.values()]);
|
|
2611
|
-
|
|
2612
|
-
clearInterval(this.writeBehindTimer);
|
|
2613
|
-
this.writeBehindTimer = void 0;
|
|
2614
|
-
}
|
|
2798
|
+
this.maintenance.disposeWriteBehindTimer();
|
|
2615
2799
|
await Promise.allSettled(this.layers.map((layer) => layer.dispose?.() ?? Promise.resolve()));
|
|
2616
2800
|
})();
|
|
2617
2801
|
}
|
|
@@ -2687,13 +2871,13 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
2687
2871
|
if (!this.shouldNegativeCache(options)) {
|
|
2688
2872
|
return null;
|
|
2689
2873
|
}
|
|
2690
|
-
if (this.isWriteOutdated(key, expectedClearEpoch, expectedKeyEpoch)) {
|
|
2874
|
+
if (this.maintenance.isWriteOutdated(key, expectedClearEpoch, expectedKeyEpoch)) {
|
|
2691
2875
|
this.logger.debug?.("skip-negative-store-after-invalidation", {
|
|
2692
2876
|
key,
|
|
2693
2877
|
expectedClearEpoch,
|
|
2694
|
-
clearEpoch: this.
|
|
2878
|
+
clearEpoch: this.maintenance.currentClearEpoch(),
|
|
2695
2879
|
expectedKeyEpoch,
|
|
2696
|
-
keyEpoch: this.currentKeyEpoch(key)
|
|
2880
|
+
keyEpoch: this.maintenance.currentKeyEpoch(key)
|
|
2697
2881
|
});
|
|
2698
2882
|
return null;
|
|
2699
2883
|
}
|
|
@@ -2709,13 +2893,13 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
2709
2893
|
this.logger.warn?.("shouldCache-error", { key, error: this.formatError(error) });
|
|
2710
2894
|
}
|
|
2711
2895
|
}
|
|
2712
|
-
if (this.isWriteOutdated(key, expectedClearEpoch, expectedKeyEpoch)) {
|
|
2896
|
+
if (this.maintenance.isWriteOutdated(key, expectedClearEpoch, expectedKeyEpoch)) {
|
|
2713
2897
|
this.logger.debug?.("skip-store-after-invalidation", {
|
|
2714
2898
|
key,
|
|
2715
2899
|
expectedClearEpoch,
|
|
2716
|
-
clearEpoch: this.
|
|
2900
|
+
clearEpoch: this.maintenance.currentClearEpoch(),
|
|
2717
2901
|
expectedKeyEpoch,
|
|
2718
|
-
keyEpoch: this.currentKeyEpoch(key)
|
|
2902
|
+
keyEpoch: this.maintenance.currentKeyEpoch(key)
|
|
2719
2903
|
});
|
|
2720
2904
|
return fetched;
|
|
2721
2905
|
}
|
|
@@ -2723,10 +2907,10 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
2723
2907
|
return fetched;
|
|
2724
2908
|
}
|
|
2725
2909
|
async storeEntry(key, kind, value, options) {
|
|
2726
|
-
const clearEpoch = this.
|
|
2727
|
-
const keyEpoch = this.currentKeyEpoch(key);
|
|
2910
|
+
const clearEpoch = this.maintenance.currentClearEpoch();
|
|
2911
|
+
const keyEpoch = this.maintenance.currentKeyEpoch(key);
|
|
2728
2912
|
await this.writeAcrossLayers(key, kind, value, options);
|
|
2729
|
-
if (this.isWriteOutdated(key, clearEpoch, keyEpoch)) {
|
|
2913
|
+
if (this.maintenance.isWriteOutdated(key, clearEpoch, keyEpoch)) {
|
|
2730
2914
|
return;
|
|
2731
2915
|
}
|
|
2732
2916
|
if (options?.tags) {
|
|
@@ -2743,8 +2927,8 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
2743
2927
|
}
|
|
2744
2928
|
async writeBatch(entries) {
|
|
2745
2929
|
const now = Date.now();
|
|
2746
|
-
const clearEpoch = this.
|
|
2747
|
-
const entryEpochs = new Map(entries.map((entry) => [entry.key, this.currentKeyEpoch(entry.key)]));
|
|
2930
|
+
const clearEpoch = this.maintenance.currentClearEpoch();
|
|
2931
|
+
const entryEpochs = new Map(entries.map((entry) => [entry.key, this.maintenance.currentKeyEpoch(entry.key)]));
|
|
2748
2932
|
const entriesByLayer = /* @__PURE__ */ new Map();
|
|
2749
2933
|
const immediateOperations = [];
|
|
2750
2934
|
const deferredOperations = [];
|
|
@@ -2761,11 +2945,11 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
2761
2945
|
}
|
|
2762
2946
|
for (const [layer, layerEntries] of entriesByLayer.entries()) {
|
|
2763
2947
|
const operation = async () => {
|
|
2764
|
-
if (clearEpoch !== this.
|
|
2948
|
+
if (clearEpoch !== this.maintenance.currentClearEpoch()) {
|
|
2765
2949
|
return;
|
|
2766
2950
|
}
|
|
2767
2951
|
const activeEntries = layerEntries.filter(
|
|
2768
|
-
(entry) => (entryEpochs.get(entry.key) ?? 0) === this.currentKeyEpoch(entry.key)
|
|
2952
|
+
(entry) => (entryEpochs.get(entry.key) ?? 0) === this.maintenance.currentKeyEpoch(entry.key)
|
|
2769
2953
|
);
|
|
2770
2954
|
if (activeEntries.length === 0) {
|
|
2771
2955
|
return;
|
|
@@ -2788,11 +2972,11 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
2788
2972
|
}
|
|
2789
2973
|
await this.executeLayerOperations(immediateOperations, { key: "batch", action: "mset" });
|
|
2790
2974
|
await Promise.all(deferredOperations.map((operation) => this.enqueueWriteBehind(operation)));
|
|
2791
|
-
if (clearEpoch !== this.
|
|
2975
|
+
if (clearEpoch !== this.maintenance.currentClearEpoch()) {
|
|
2792
2976
|
return;
|
|
2793
2977
|
}
|
|
2794
2978
|
for (const entry of entries) {
|
|
2795
|
-
if (this.isWriteOutdated(entry.key, clearEpoch, entryEpochs.get(entry.key))) {
|
|
2979
|
+
if (this.maintenance.isWriteOutdated(entry.key, clearEpoch, entryEpochs.get(entry.key))) {
|
|
2796
2980
|
continue;
|
|
2797
2981
|
}
|
|
2798
2982
|
if (entry.options?.tags) {
|
|
@@ -2896,13 +3080,13 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
2896
3080
|
}
|
|
2897
3081
|
async writeAcrossLayers(key, kind, value, options) {
|
|
2898
3082
|
const now = Date.now();
|
|
2899
|
-
const clearEpoch = this.
|
|
2900
|
-
const keyEpoch = this.currentKeyEpoch(key);
|
|
3083
|
+
const clearEpoch = this.maintenance.currentClearEpoch();
|
|
3084
|
+
const keyEpoch = this.maintenance.currentKeyEpoch(key);
|
|
2901
3085
|
const immediateOperations = [];
|
|
2902
3086
|
const deferredOperations = [];
|
|
2903
3087
|
for (const layer of this.layers) {
|
|
2904
3088
|
const operation = async () => {
|
|
2905
|
-
if (this.isWriteOutdated(key, clearEpoch, keyEpoch)) {
|
|
3089
|
+
if (this.maintenance.isWriteOutdated(key, clearEpoch, keyEpoch)) {
|
|
2906
3090
|
return;
|
|
2907
3091
|
}
|
|
2908
3092
|
if (this.shouldSkipLayer(layer)) {
|
|
@@ -2965,11 +3149,14 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
2965
3149
|
return options?.negativeCache ?? this.options.negativeCaching ?? false;
|
|
2966
3150
|
}
|
|
2967
3151
|
scheduleBackgroundRefresh(key, fetcher, options) {
|
|
2968
|
-
if (
|
|
3152
|
+
if (!shouldStartBackgroundRefresh({
|
|
3153
|
+
isDisconnecting: this.isDisconnecting,
|
|
3154
|
+
hasRefreshInFlight: this.backgroundRefreshes.has(key)
|
|
3155
|
+
})) {
|
|
2969
3156
|
return;
|
|
2970
3157
|
}
|
|
2971
|
-
const clearEpoch = this.
|
|
2972
|
-
const keyEpoch = this.currentKeyEpoch(key);
|
|
3158
|
+
const clearEpoch = this.maintenance.currentClearEpoch();
|
|
3159
|
+
const keyEpoch = this.maintenance.currentKeyEpoch(key);
|
|
2973
3160
|
const refresh = (async () => {
|
|
2974
3161
|
this.metricsCollector.increment("refreshes");
|
|
2975
3162
|
try {
|
|
@@ -3007,7 +3194,7 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
3007
3194
|
if (keys.length === 0) {
|
|
3008
3195
|
return;
|
|
3009
3196
|
}
|
|
3010
|
-
this.bumpKeyEpochs(keys);
|
|
3197
|
+
this.maintenance.bumpKeyEpochs(keys);
|
|
3011
3198
|
await this.deleteKeysFromLayers(this.layers, keys);
|
|
3012
3199
|
for (const key of keys) {
|
|
3013
3200
|
await this.tagIndex.remove(key);
|
|
@@ -3031,7 +3218,7 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
3031
3218
|
}
|
|
3032
3219
|
const localLayers = this.layers.filter((layer) => layer.isLocal);
|
|
3033
3220
|
if (message.scope === "clear") {
|
|
3034
|
-
this.beginClearEpoch();
|
|
3221
|
+
this.maintenance.beginClearEpoch();
|
|
3035
3222
|
await Promise.all(localLayers.map((layer) => layer.clear()));
|
|
3036
3223
|
await this.tagIndex.clear();
|
|
3037
3224
|
this.ttlResolver.clearProfiles();
|
|
@@ -3039,7 +3226,7 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
3039
3226
|
return;
|
|
3040
3227
|
}
|
|
3041
3228
|
const keys = message.keys ?? [];
|
|
3042
|
-
this.bumpKeyEpochs(keys);
|
|
3229
|
+
this.maintenance.bumpKeyEpochs(keys);
|
|
3043
3230
|
await this.deleteKeysFromLayers(localLayers, keys);
|
|
3044
3231
|
if (message.operation !== "write") {
|
|
3045
3232
|
for (const key of keys) {
|
|
@@ -3097,35 +3284,22 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
3097
3284
|
shouldBroadcastL1Invalidation() {
|
|
3098
3285
|
return this.options.broadcastL1Invalidation ?? this.options.publishSetInvalidation ?? false;
|
|
3099
3286
|
}
|
|
3100
|
-
shouldCleanupGenerations() {
|
|
3101
|
-
return Boolean(this.options.generationCleanup);
|
|
3102
|
-
}
|
|
3103
|
-
generationCleanupBatchSize() {
|
|
3104
|
-
const configured = typeof this.options.generationCleanup === "object" ? this.options.generationCleanup.batchSize : void 0;
|
|
3105
|
-
return configured ?? 500;
|
|
3106
|
-
}
|
|
3107
3287
|
scheduleGenerationCleanup(generation) {
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
this.generationCleanupPromise = void 0;
|
|
3288
|
+
this.maintenance.scheduleGenerationCleanup(
|
|
3289
|
+
generation,
|
|
3290
|
+
async (generationToClean) => this.cleanupGeneration(generationToClean),
|
|
3291
|
+
(failedGeneration, error) => {
|
|
3292
|
+
this.logger.warn?.("generation-cleanup-error", {
|
|
3293
|
+
generation: failedGeneration,
|
|
3294
|
+
error: this.formatError(error)
|
|
3295
|
+
});
|
|
3117
3296
|
}
|
|
3118
|
-
|
|
3297
|
+
);
|
|
3119
3298
|
}
|
|
3120
3299
|
async cleanupGeneration(generation) {
|
|
3121
3300
|
const prefix = `v${generation}:`;
|
|
3122
3301
|
const keys = await this.keyDiscovery.collectKeysWithPrefix(prefix);
|
|
3123
|
-
|
|
3124
|
-
return;
|
|
3125
|
-
}
|
|
3126
|
-
const batchSize = this.generationCleanupBatchSize();
|
|
3127
|
-
for (let index = 0; index < keys.length; index += batchSize) {
|
|
3128
|
-
const batch = keys.slice(index, index + batchSize);
|
|
3302
|
+
for (const batch of planGenerationCleanupBatches(keys, this.options.generationCleanup)) {
|
|
3129
3303
|
await this.deleteKeys(batch);
|
|
3130
3304
|
await this.publishInvalidation({
|
|
3131
3305
|
scope: "keys",
|
|
@@ -3136,80 +3310,34 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
3136
3310
|
}
|
|
3137
3311
|
}
|
|
3138
3312
|
initializeWriteBehind(options) {
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
return;
|
|
3145
|
-
}
|
|
3146
|
-
this.writeBehindTimer = setInterval(() => {
|
|
3147
|
-
void this.flushWriteBehindQueue();
|
|
3148
|
-
}, flushIntervalMs);
|
|
3149
|
-
this.writeBehindTimer.unref?.();
|
|
3313
|
+
this.maintenance.initializeWriteBehindTimer(
|
|
3314
|
+
this.options.writeStrategy,
|
|
3315
|
+
options,
|
|
3316
|
+
this.flushWriteBehindQueue.bind(this)
|
|
3317
|
+
);
|
|
3150
3318
|
}
|
|
3151
3319
|
shouldWriteBehind(layer) {
|
|
3152
3320
|
return this.options.writeStrategy === "write-behind" && !layer.isLocal;
|
|
3153
3321
|
}
|
|
3154
|
-
beginClearEpoch() {
|
|
3155
|
-
this.clearEpoch += 1;
|
|
3156
|
-
this.keyEpochs.clear();
|
|
3157
|
-
this.writeBehindQueue.length = 0;
|
|
3158
|
-
}
|
|
3159
|
-
currentKeyEpoch(key) {
|
|
3160
|
-
return this.keyEpochs.get(key) ?? 0;
|
|
3161
|
-
}
|
|
3162
|
-
bumpKeyEpochs(keys) {
|
|
3163
|
-
for (const key of keys) {
|
|
3164
|
-
this.keyEpochs.set(key, this.currentKeyEpoch(key) + 1);
|
|
3165
|
-
}
|
|
3166
|
-
}
|
|
3167
|
-
isWriteOutdated(key, expectedClearEpoch, expectedKeyEpoch) {
|
|
3168
|
-
if (expectedClearEpoch !== void 0 && expectedClearEpoch !== this.clearEpoch) {
|
|
3169
|
-
return true;
|
|
3170
|
-
}
|
|
3171
|
-
if (expectedKeyEpoch !== void 0 && expectedKeyEpoch !== this.currentKeyEpoch(key)) {
|
|
3172
|
-
return true;
|
|
3173
|
-
}
|
|
3174
|
-
return false;
|
|
3175
|
-
}
|
|
3176
3322
|
async enqueueWriteBehind(operation) {
|
|
3177
|
-
this.
|
|
3178
|
-
const batchSize = this.options.writeBehind?.batchSize ?? 100;
|
|
3179
|
-
const maxQueueSize = this.options.writeBehind?.maxQueueSize ?? batchSize * 10;
|
|
3180
|
-
if (this.writeBehindQueue.length >= batchSize) {
|
|
3181
|
-
await this.flushWriteBehindQueue();
|
|
3182
|
-
return;
|
|
3183
|
-
}
|
|
3184
|
-
if (this.writeBehindQueue.length >= maxQueueSize) {
|
|
3185
|
-
await this.flushWriteBehindQueue();
|
|
3186
|
-
}
|
|
3323
|
+
await this.maintenance.enqueueWriteBehind(operation, this.options.writeBehind, this.runWriteBehindBatch.bind(this));
|
|
3187
3324
|
}
|
|
3188
3325
|
async flushWriteBehindQueue() {
|
|
3189
|
-
|
|
3190
|
-
|
|
3326
|
+
await this.maintenance.flushWriteBehindQueue(this.options.writeBehind, this.runWriteBehindBatch.bind(this));
|
|
3327
|
+
}
|
|
3328
|
+
async runWriteBehindBatch(batch) {
|
|
3329
|
+
const results = await Promise.allSettled(batch.map((operation) => operation()));
|
|
3330
|
+
const failures = results.filter((result) => result.status === "rejected");
|
|
3331
|
+
if (failures.length === 0) {
|
|
3191
3332
|
return;
|
|
3192
3333
|
}
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
this.logger.error?.("write-behind-flush-failure", {
|
|
3201
|
-
failed: failures.length,
|
|
3202
|
-
total: batch.length,
|
|
3203
|
-
errors: failures.map((failure) => this.formatError(failure.reason))
|
|
3204
|
-
});
|
|
3205
|
-
this.emitError("write-behind", { failed: failures.length, total: batch.length });
|
|
3206
|
-
}
|
|
3207
|
-
})();
|
|
3208
|
-
await this.writeBehindFlushPromise;
|
|
3209
|
-
this.writeBehindFlushPromise = void 0;
|
|
3210
|
-
if (this.writeBehindQueue.length > 0) {
|
|
3211
|
-
await this.flushWriteBehindQueue();
|
|
3212
|
-
}
|
|
3334
|
+
this.metricsCollector.increment("writeFailures", failures.length);
|
|
3335
|
+
this.logger.error?.("write-behind-flush-failure", {
|
|
3336
|
+
failed: failures.length,
|
|
3337
|
+
total: batch.length,
|
|
3338
|
+
errors: failures.map((failure) => this.formatError(failure.reason))
|
|
3339
|
+
});
|
|
3340
|
+
this.emitError("write-behind", { failed: failures.length, total: batch.length });
|
|
3213
3341
|
}
|
|
3214
3342
|
buildLayerSetEntry(layer, key, kind, value, options, now) {
|
|
3215
3343
|
const freshTtl = this.resolveFreshTtl(key, layer.name, kind, options, layer.defaultTtl, value);
|
|
@@ -3239,32 +3367,17 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
3239
3367
|
return [];
|
|
3240
3368
|
}
|
|
3241
3369
|
const [firstGroup, ...rest] = groups;
|
|
3242
|
-
if (!firstGroup) {
|
|
3243
|
-
return [];
|
|
3244
|
-
}
|
|
3245
3370
|
const restSets = rest.map((group) => new Set(group));
|
|
3246
3371
|
return [...new Set(firstGroup)].filter((key) => restSets.every((group) => group.has(key)));
|
|
3247
3372
|
}
|
|
3248
3373
|
qualifyKey(key) {
|
|
3249
|
-
|
|
3250
|
-
return prefix ? `${prefix}${key}` : key;
|
|
3374
|
+
return qualifyGenerationKey(key, this.currentGeneration);
|
|
3251
3375
|
}
|
|
3252
3376
|
qualifyPattern(pattern) {
|
|
3253
|
-
|
|
3254
|
-
return prefix ? `${prefix}${pattern}` : pattern;
|
|
3377
|
+
return qualifyGenerationPattern(pattern, this.currentGeneration);
|
|
3255
3378
|
}
|
|
3256
3379
|
stripQualifiedKey(key) {
|
|
3257
|
-
|
|
3258
|
-
if (!prefix || !key.startsWith(prefix)) {
|
|
3259
|
-
return key;
|
|
3260
|
-
}
|
|
3261
|
-
return key.slice(prefix.length);
|
|
3262
|
-
}
|
|
3263
|
-
generationPrefix() {
|
|
3264
|
-
if (this.currentGeneration === void 0) {
|
|
3265
|
-
return "";
|
|
3266
|
-
}
|
|
3267
|
-
return `v${this.currentGeneration}:`;
|
|
3380
|
+
return stripGenerationPrefix(key, this.currentGeneration);
|
|
3268
3381
|
}
|
|
3269
3382
|
async deleteKeysFromLayers(layers, keys) {
|
|
3270
3383
|
await Promise.all(
|
|
@@ -3355,37 +3468,38 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
3355
3468
|
this.assertActive(operation);
|
|
3356
3469
|
}
|
|
3357
3470
|
async applyFreshReadPolicies(key, hit, options, fetcher) {
|
|
3358
|
-
const
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3471
|
+
const plan = planFreshReadPolicies({
|
|
3472
|
+
stored: hit.stored,
|
|
3473
|
+
hasFetcher: Boolean(fetcher),
|
|
3474
|
+
slidingTtl: options?.slidingTtl ?? false,
|
|
3475
|
+
refreshAheadSeconds: this.resolveLayerSeconds(hit.layerName, options?.refreshAhead, this.options.refreshAhead, 0) ?? 0
|
|
3476
|
+
});
|
|
3477
|
+
if (plan.refreshedStored) {
|
|
3363
3478
|
for (let index = 0; index <= hit.layerIndex; index += 1) {
|
|
3364
3479
|
const layer = this.layers[index];
|
|
3365
3480
|
if (!layer || this.shouldSkipLayer(layer)) {
|
|
3366
3481
|
continue;
|
|
3367
3482
|
}
|
|
3368
3483
|
try {
|
|
3369
|
-
await layer.set(key,
|
|
3484
|
+
await layer.set(key, plan.refreshedStored, plan.refreshedStoredTtl);
|
|
3370
3485
|
} catch (error) {
|
|
3371
3486
|
await this.handleLayerFailure(layer, "sliding-ttl", error);
|
|
3372
3487
|
}
|
|
3373
3488
|
}
|
|
3374
3489
|
}
|
|
3375
|
-
if (fetcher &&
|
|
3490
|
+
if (fetcher && plan.shouldScheduleBackgroundRefresh) {
|
|
3376
3491
|
this.scheduleBackgroundRefresh(key, fetcher, options);
|
|
3377
3492
|
}
|
|
3378
3493
|
}
|
|
3379
3494
|
shouldSkipLayer(layer) {
|
|
3380
|
-
|
|
3381
|
-
return degradedUntil !== void 0 && degradedUntil > Date.now();
|
|
3495
|
+
return shouldSkipLayer(this.layerDegradedUntil.get(layer.name));
|
|
3382
3496
|
}
|
|
3383
3497
|
async handleLayerFailure(layer, operation, error) {
|
|
3384
|
-
|
|
3498
|
+
const recovery = resolveRecoverableLayerFailure(this.options.gracefulDegradation);
|
|
3499
|
+
if (!recovery.degrade) {
|
|
3385
3500
|
throw error;
|
|
3386
3501
|
}
|
|
3387
|
-
|
|
3388
|
-
this.layerDegradedUntil.set(layer.name, Date.now() + retryAfterMs);
|
|
3502
|
+
this.layerDegradedUntil.set(layer.name, recovery.degradedUntil);
|
|
3389
3503
|
this.metricsCollector.increment("degradedOperations");
|
|
3390
3504
|
this.logger.warn?.("layer-degraded", { layer: layer.name, operation, error: this.formatError(error) });
|
|
3391
3505
|
this.emitError(operation, { layer: layer.name, degraded: true, error: this.formatError(error) });
|