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
package/dist/index.cjs
CHANGED
|
@@ -62,6 +62,127 @@ var import_node_events = require("events");
|
|
|
62
62
|
|
|
63
63
|
// src/CacheNamespace.ts
|
|
64
64
|
var import_async_mutex = require("async-mutex");
|
|
65
|
+
|
|
66
|
+
// src/internal/CacheNamespaceMetrics.ts
|
|
67
|
+
function createEmptyNamespaceMetrics(resetAt = Date.now()) {
|
|
68
|
+
return {
|
|
69
|
+
hits: 0,
|
|
70
|
+
misses: 0,
|
|
71
|
+
fetches: 0,
|
|
72
|
+
sets: 0,
|
|
73
|
+
deletes: 0,
|
|
74
|
+
backfills: 0,
|
|
75
|
+
invalidations: 0,
|
|
76
|
+
staleHits: 0,
|
|
77
|
+
refreshes: 0,
|
|
78
|
+
refreshErrors: 0,
|
|
79
|
+
writeFailures: 0,
|
|
80
|
+
singleFlightWaits: 0,
|
|
81
|
+
negativeCacheHits: 0,
|
|
82
|
+
circuitBreakerTrips: 0,
|
|
83
|
+
degradedOperations: 0,
|
|
84
|
+
hitsByLayer: {},
|
|
85
|
+
missesByLayer: {},
|
|
86
|
+
latencyByLayer: {},
|
|
87
|
+
resetAt
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function cloneNamespaceMetrics(metrics) {
|
|
91
|
+
return {
|
|
92
|
+
...metrics,
|
|
93
|
+
hitsByLayer: { ...metrics.hitsByLayer },
|
|
94
|
+
missesByLayer: { ...metrics.missesByLayer },
|
|
95
|
+
latencyByLayer: Object.fromEntries(
|
|
96
|
+
Object.entries(metrics.latencyByLayer).map(([key, value]) => [key, { ...value }])
|
|
97
|
+
)
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function diffNamespaceMetrics(before, after) {
|
|
101
|
+
const latencyByLayer = Object.fromEntries(
|
|
102
|
+
Object.entries(after.latencyByLayer).map(([layer, value]) => [
|
|
103
|
+
layer,
|
|
104
|
+
{
|
|
105
|
+
avgMs: value.avgMs,
|
|
106
|
+
maxMs: value.maxMs,
|
|
107
|
+
count: Math.max(0, value.count - (before.latencyByLayer[layer]?.count ?? 0))
|
|
108
|
+
}
|
|
109
|
+
])
|
|
110
|
+
);
|
|
111
|
+
return {
|
|
112
|
+
hits: after.hits - before.hits,
|
|
113
|
+
misses: after.misses - before.misses,
|
|
114
|
+
fetches: after.fetches - before.fetches,
|
|
115
|
+
sets: after.sets - before.sets,
|
|
116
|
+
deletes: after.deletes - before.deletes,
|
|
117
|
+
backfills: after.backfills - before.backfills,
|
|
118
|
+
invalidations: after.invalidations - before.invalidations,
|
|
119
|
+
staleHits: after.staleHits - before.staleHits,
|
|
120
|
+
refreshes: after.refreshes - before.refreshes,
|
|
121
|
+
refreshErrors: after.refreshErrors - before.refreshErrors,
|
|
122
|
+
writeFailures: after.writeFailures - before.writeFailures,
|
|
123
|
+
singleFlightWaits: after.singleFlightWaits - before.singleFlightWaits,
|
|
124
|
+
negativeCacheHits: after.negativeCacheHits - before.negativeCacheHits,
|
|
125
|
+
circuitBreakerTrips: after.circuitBreakerTrips - before.circuitBreakerTrips,
|
|
126
|
+
degradedOperations: after.degradedOperations - before.degradedOperations,
|
|
127
|
+
hitsByLayer: diffMetricMap(before.hitsByLayer, after.hitsByLayer),
|
|
128
|
+
missesByLayer: diffMetricMap(before.missesByLayer, after.missesByLayer),
|
|
129
|
+
latencyByLayer,
|
|
130
|
+
resetAt: after.resetAt
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
function addNamespaceMetrics(base, delta) {
|
|
134
|
+
return {
|
|
135
|
+
hits: base.hits + delta.hits,
|
|
136
|
+
misses: base.misses + delta.misses,
|
|
137
|
+
fetches: base.fetches + delta.fetches,
|
|
138
|
+
sets: base.sets + delta.sets,
|
|
139
|
+
deletes: base.deletes + delta.deletes,
|
|
140
|
+
backfills: base.backfills + delta.backfills,
|
|
141
|
+
invalidations: base.invalidations + delta.invalidations,
|
|
142
|
+
staleHits: base.staleHits + delta.staleHits,
|
|
143
|
+
refreshes: base.refreshes + delta.refreshes,
|
|
144
|
+
refreshErrors: base.refreshErrors + delta.refreshErrors,
|
|
145
|
+
writeFailures: base.writeFailures + delta.writeFailures,
|
|
146
|
+
singleFlightWaits: base.singleFlightWaits + delta.singleFlightWaits,
|
|
147
|
+
negativeCacheHits: base.negativeCacheHits + delta.negativeCacheHits,
|
|
148
|
+
circuitBreakerTrips: base.circuitBreakerTrips + delta.circuitBreakerTrips,
|
|
149
|
+
degradedOperations: base.degradedOperations + delta.degradedOperations,
|
|
150
|
+
hitsByLayer: addMetricMap(base.hitsByLayer, delta.hitsByLayer),
|
|
151
|
+
missesByLayer: addMetricMap(base.missesByLayer, delta.missesByLayer),
|
|
152
|
+
latencyByLayer: cloneNamespaceMetrics(delta).latencyByLayer,
|
|
153
|
+
resetAt: base.resetAt
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
function computeNamespaceHitRate(metrics) {
|
|
157
|
+
const total = metrics.hits + metrics.misses;
|
|
158
|
+
const overall = total === 0 ? 0 : metrics.hits / total;
|
|
159
|
+
const byLayer = {};
|
|
160
|
+
const layers = /* @__PURE__ */ new Set([...Object.keys(metrics.hitsByLayer), ...Object.keys(metrics.missesByLayer)]);
|
|
161
|
+
for (const layer of layers) {
|
|
162
|
+
const hits = metrics.hitsByLayer[layer] ?? 0;
|
|
163
|
+
const misses = metrics.missesByLayer[layer] ?? 0;
|
|
164
|
+
byLayer[layer] = hits + misses === 0 ? 0 : hits / (hits + misses);
|
|
165
|
+
}
|
|
166
|
+
return { overall, byLayer };
|
|
167
|
+
}
|
|
168
|
+
function diffMetricMap(before, after) {
|
|
169
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)]);
|
|
170
|
+
const result = {};
|
|
171
|
+
for (const key of keys) {
|
|
172
|
+
result[key] = (after[key] ?? 0) - (before[key] ?? 0);
|
|
173
|
+
}
|
|
174
|
+
return result;
|
|
175
|
+
}
|
|
176
|
+
function addMetricMap(base, delta) {
|
|
177
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(base), ...Object.keys(delta)]);
|
|
178
|
+
const result = {};
|
|
179
|
+
for (const key of keys) {
|
|
180
|
+
result[key] = (base[key] ?? 0) + (delta[key] ?? 0);
|
|
181
|
+
}
|
|
182
|
+
return result;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// src/CacheNamespace.ts
|
|
65
186
|
var CacheNamespace = class _CacheNamespace {
|
|
66
187
|
constructor(cache, prefix) {
|
|
67
188
|
this.cache = cache;
|
|
@@ -71,7 +192,7 @@ var CacheNamespace = class _CacheNamespace {
|
|
|
71
192
|
cache;
|
|
72
193
|
prefix;
|
|
73
194
|
static metricsMutexes = /* @__PURE__ */ new WeakMap();
|
|
74
|
-
metrics =
|
|
195
|
+
metrics = createEmptyNamespaceMetrics();
|
|
75
196
|
async get(key, fetcher, options) {
|
|
76
197
|
return this.trackMetrics(() => this.cache.get(this.qualify(key), fetcher, this.qualifyGetOptions(options)));
|
|
77
198
|
}
|
|
@@ -168,19 +289,10 @@ var CacheNamespace = class _CacheNamespace {
|
|
|
168
289
|
);
|
|
169
290
|
}
|
|
170
291
|
getMetrics() {
|
|
171
|
-
return
|
|
292
|
+
return cloneNamespaceMetrics(this.metrics);
|
|
172
293
|
}
|
|
173
294
|
getHitRate() {
|
|
174
|
-
|
|
175
|
-
const overall = total === 0 ? 0 : this.metrics.hits / total;
|
|
176
|
-
const byLayer = {};
|
|
177
|
-
const layers = /* @__PURE__ */ new Set([...Object.keys(this.metrics.hitsByLayer), ...Object.keys(this.metrics.missesByLayer)]);
|
|
178
|
-
for (const layer of layers) {
|
|
179
|
-
const hits = this.metrics.hitsByLayer[layer] ?? 0;
|
|
180
|
-
const misses = this.metrics.missesByLayer[layer] ?? 0;
|
|
181
|
-
byLayer[layer] = hits + misses === 0 ? 0 : hits / (hits + misses);
|
|
182
|
-
}
|
|
183
|
-
return { overall, byLayer };
|
|
295
|
+
return computeNamespaceHitRate(this.metrics);
|
|
184
296
|
}
|
|
185
297
|
/**
|
|
186
298
|
* Creates a nested namespace. Keys are prefixed with `parentPrefix:childPrefix:`.
|
|
@@ -221,7 +333,7 @@ var CacheNamespace = class _CacheNamespace {
|
|
|
221
333
|
const before = this.cache.getMetrics();
|
|
222
334
|
const result = await operation();
|
|
223
335
|
const after = this.cache.getMetrics();
|
|
224
|
-
this.metrics =
|
|
336
|
+
this.metrics = addNamespaceMetrics(this.metrics, diffNamespaceMetrics(before, after));
|
|
225
337
|
return result;
|
|
226
338
|
});
|
|
227
339
|
}
|
|
@@ -235,111 +347,6 @@ var CacheNamespace = class _CacheNamespace {
|
|
|
235
347
|
return mutex;
|
|
236
348
|
}
|
|
237
349
|
};
|
|
238
|
-
function emptyMetrics() {
|
|
239
|
-
return {
|
|
240
|
-
hits: 0,
|
|
241
|
-
misses: 0,
|
|
242
|
-
fetches: 0,
|
|
243
|
-
sets: 0,
|
|
244
|
-
deletes: 0,
|
|
245
|
-
backfills: 0,
|
|
246
|
-
invalidations: 0,
|
|
247
|
-
staleHits: 0,
|
|
248
|
-
refreshes: 0,
|
|
249
|
-
refreshErrors: 0,
|
|
250
|
-
writeFailures: 0,
|
|
251
|
-
singleFlightWaits: 0,
|
|
252
|
-
negativeCacheHits: 0,
|
|
253
|
-
circuitBreakerTrips: 0,
|
|
254
|
-
degradedOperations: 0,
|
|
255
|
-
hitsByLayer: {},
|
|
256
|
-
missesByLayer: {},
|
|
257
|
-
latencyByLayer: {},
|
|
258
|
-
resetAt: Date.now()
|
|
259
|
-
};
|
|
260
|
-
}
|
|
261
|
-
function cloneMetrics(metrics) {
|
|
262
|
-
return {
|
|
263
|
-
...metrics,
|
|
264
|
-
hitsByLayer: { ...metrics.hitsByLayer },
|
|
265
|
-
missesByLayer: { ...metrics.missesByLayer },
|
|
266
|
-
latencyByLayer: Object.fromEntries(
|
|
267
|
-
Object.entries(metrics.latencyByLayer).map(([key, value]) => [key, { ...value }])
|
|
268
|
-
)
|
|
269
|
-
};
|
|
270
|
-
}
|
|
271
|
-
function diffMetrics(before, after) {
|
|
272
|
-
const latencyByLayer = Object.fromEntries(
|
|
273
|
-
Object.entries(after.latencyByLayer).map(([layer, value]) => [
|
|
274
|
-
layer,
|
|
275
|
-
{
|
|
276
|
-
avgMs: value.avgMs,
|
|
277
|
-
maxMs: value.maxMs,
|
|
278
|
-
count: Math.max(0, value.count - (before.latencyByLayer[layer]?.count ?? 0))
|
|
279
|
-
}
|
|
280
|
-
])
|
|
281
|
-
);
|
|
282
|
-
return {
|
|
283
|
-
hits: after.hits - before.hits,
|
|
284
|
-
misses: after.misses - before.misses,
|
|
285
|
-
fetches: after.fetches - before.fetches,
|
|
286
|
-
sets: after.sets - before.sets,
|
|
287
|
-
deletes: after.deletes - before.deletes,
|
|
288
|
-
backfills: after.backfills - before.backfills,
|
|
289
|
-
invalidations: after.invalidations - before.invalidations,
|
|
290
|
-
staleHits: after.staleHits - before.staleHits,
|
|
291
|
-
refreshes: after.refreshes - before.refreshes,
|
|
292
|
-
refreshErrors: after.refreshErrors - before.refreshErrors,
|
|
293
|
-
writeFailures: after.writeFailures - before.writeFailures,
|
|
294
|
-
singleFlightWaits: after.singleFlightWaits - before.singleFlightWaits,
|
|
295
|
-
negativeCacheHits: after.negativeCacheHits - before.negativeCacheHits,
|
|
296
|
-
circuitBreakerTrips: after.circuitBreakerTrips - before.circuitBreakerTrips,
|
|
297
|
-
degradedOperations: after.degradedOperations - before.degradedOperations,
|
|
298
|
-
hitsByLayer: diffMap(before.hitsByLayer, after.hitsByLayer),
|
|
299
|
-
missesByLayer: diffMap(before.missesByLayer, after.missesByLayer),
|
|
300
|
-
latencyByLayer,
|
|
301
|
-
resetAt: after.resetAt
|
|
302
|
-
};
|
|
303
|
-
}
|
|
304
|
-
function addMetrics(base, delta) {
|
|
305
|
-
return {
|
|
306
|
-
hits: base.hits + delta.hits,
|
|
307
|
-
misses: base.misses + delta.misses,
|
|
308
|
-
fetches: base.fetches + delta.fetches,
|
|
309
|
-
sets: base.sets + delta.sets,
|
|
310
|
-
deletes: base.deletes + delta.deletes,
|
|
311
|
-
backfills: base.backfills + delta.backfills,
|
|
312
|
-
invalidations: base.invalidations + delta.invalidations,
|
|
313
|
-
staleHits: base.staleHits + delta.staleHits,
|
|
314
|
-
refreshes: base.refreshes + delta.refreshes,
|
|
315
|
-
refreshErrors: base.refreshErrors + delta.refreshErrors,
|
|
316
|
-
writeFailures: base.writeFailures + delta.writeFailures,
|
|
317
|
-
singleFlightWaits: base.singleFlightWaits + delta.singleFlightWaits,
|
|
318
|
-
negativeCacheHits: base.negativeCacheHits + delta.negativeCacheHits,
|
|
319
|
-
circuitBreakerTrips: base.circuitBreakerTrips + delta.circuitBreakerTrips,
|
|
320
|
-
degradedOperations: base.degradedOperations + delta.degradedOperations,
|
|
321
|
-
hitsByLayer: addMap(base.hitsByLayer, delta.hitsByLayer),
|
|
322
|
-
missesByLayer: addMap(base.missesByLayer, delta.missesByLayer),
|
|
323
|
-
latencyByLayer: cloneMetrics(delta).latencyByLayer,
|
|
324
|
-
resetAt: base.resetAt
|
|
325
|
-
};
|
|
326
|
-
}
|
|
327
|
-
function diffMap(before, after) {
|
|
328
|
-
const keys = /* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)]);
|
|
329
|
-
const result = {};
|
|
330
|
-
for (const key of keys) {
|
|
331
|
-
result[key] = (after[key] ?? 0) - (before[key] ?? 0);
|
|
332
|
-
}
|
|
333
|
-
return result;
|
|
334
|
-
}
|
|
335
|
-
function addMap(base, delta) {
|
|
336
|
-
const keys = /* @__PURE__ */ new Set([...Object.keys(base), ...Object.keys(delta)]);
|
|
337
|
-
const result = {};
|
|
338
|
-
for (const key of keys) {
|
|
339
|
-
result[key] = (base[key] ?? 0) + (delta[key] ?? 0);
|
|
340
|
-
}
|
|
341
|
-
return result;
|
|
342
|
-
}
|
|
343
350
|
function validateNamespaceKey(key) {
|
|
344
351
|
if (key.length === 0) {
|
|
345
352
|
throw new Error("Namespace prefix must not be empty.");
|
|
@@ -642,7 +649,346 @@ async function readUtf8HandleWithLimit(handle, byteLimit) {
|
|
|
642
649
|
chunks.push(buffer.subarray(0, bytesRead));
|
|
643
650
|
position += bytesRead;
|
|
644
651
|
}
|
|
645
|
-
return Buffer.concat(chunks).toString("utf8");
|
|
652
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// src/internal/CacheStackGeneration.ts
|
|
656
|
+
var DEFAULT_GENERATION_CLEANUP_BATCH_SIZE = 500;
|
|
657
|
+
function generationPrefix(generation) {
|
|
658
|
+
return generation === void 0 ? "" : `v${generation}:`;
|
|
659
|
+
}
|
|
660
|
+
function qualifyGenerationKey(key, generation) {
|
|
661
|
+
const prefix = generationPrefix(generation);
|
|
662
|
+
return prefix ? `${prefix}${key}` : key;
|
|
663
|
+
}
|
|
664
|
+
function qualifyGenerationPattern(pattern, generation) {
|
|
665
|
+
return qualifyGenerationKey(pattern, generation);
|
|
666
|
+
}
|
|
667
|
+
function stripGenerationPrefix(key, generation) {
|
|
668
|
+
const prefix = generationPrefix(generation);
|
|
669
|
+
if (!prefix || !key.startsWith(prefix)) {
|
|
670
|
+
return key;
|
|
671
|
+
}
|
|
672
|
+
return key.slice(prefix.length);
|
|
673
|
+
}
|
|
674
|
+
function resolveGenerationCleanupTarget({
|
|
675
|
+
previousGeneration,
|
|
676
|
+
nextGeneration,
|
|
677
|
+
generationCleanup
|
|
678
|
+
}) {
|
|
679
|
+
if (!generationCleanup || previousGeneration === void 0 || previousGeneration === nextGeneration) {
|
|
680
|
+
return null;
|
|
681
|
+
}
|
|
682
|
+
return previousGeneration;
|
|
683
|
+
}
|
|
684
|
+
function resolveGenerationCleanupBatchSize(generationCleanup) {
|
|
685
|
+
if (typeof generationCleanup !== "object" || generationCleanup === null) {
|
|
686
|
+
return DEFAULT_GENERATION_CLEANUP_BATCH_SIZE;
|
|
687
|
+
}
|
|
688
|
+
return generationCleanup.batchSize ?? DEFAULT_GENERATION_CLEANUP_BATCH_SIZE;
|
|
689
|
+
}
|
|
690
|
+
function planGenerationCleanupBatches(keys, generationCleanup) {
|
|
691
|
+
if (keys.length === 0) {
|
|
692
|
+
return [];
|
|
693
|
+
}
|
|
694
|
+
const batchSize = resolveGenerationCleanupBatchSize(generationCleanup);
|
|
695
|
+
const batches = [];
|
|
696
|
+
for (let index = 0; index < keys.length; index += batchSize) {
|
|
697
|
+
batches.push(keys.slice(index, index + batchSize));
|
|
698
|
+
}
|
|
699
|
+
return batches;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// src/internal/CacheStackMaintenance.ts
|
|
703
|
+
var CacheStackMaintenance = class {
|
|
704
|
+
keyEpochs = /* @__PURE__ */ new Map();
|
|
705
|
+
writeBehindQueue = [];
|
|
706
|
+
writeBehindTimer;
|
|
707
|
+
writeBehindFlushPromise;
|
|
708
|
+
generationCleanupPromise;
|
|
709
|
+
clearEpoch = 0;
|
|
710
|
+
initializeWriteBehindTimer(writeStrategy, options, flush) {
|
|
711
|
+
if (writeStrategy !== "write-behind") {
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
const flushIntervalMs = options?.flushIntervalMs;
|
|
715
|
+
if (!flushIntervalMs || flushIntervalMs <= 0) {
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
this.disposeWriteBehindTimer();
|
|
719
|
+
this.writeBehindTimer = setInterval(() => {
|
|
720
|
+
void flush();
|
|
721
|
+
}, flushIntervalMs);
|
|
722
|
+
this.writeBehindTimer.unref?.();
|
|
723
|
+
}
|
|
724
|
+
disposeWriteBehindTimer() {
|
|
725
|
+
if (!this.writeBehindTimer) {
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
clearInterval(this.writeBehindTimer);
|
|
729
|
+
this.writeBehindTimer = void 0;
|
|
730
|
+
}
|
|
731
|
+
beginClearEpoch() {
|
|
732
|
+
this.clearEpoch += 1;
|
|
733
|
+
this.keyEpochs.clear();
|
|
734
|
+
this.writeBehindQueue.length = 0;
|
|
735
|
+
}
|
|
736
|
+
currentClearEpoch() {
|
|
737
|
+
return this.clearEpoch;
|
|
738
|
+
}
|
|
739
|
+
currentKeyEpoch(key) {
|
|
740
|
+
return this.keyEpochs.get(key) ?? 0;
|
|
741
|
+
}
|
|
742
|
+
bumpKeyEpochs(keys) {
|
|
743
|
+
for (const key of keys) {
|
|
744
|
+
this.keyEpochs.set(key, this.currentKeyEpoch(key) + 1);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
isWriteOutdated(key, expectedClearEpoch, expectedKeyEpoch) {
|
|
748
|
+
if (expectedClearEpoch !== void 0 && expectedClearEpoch !== this.clearEpoch) {
|
|
749
|
+
return true;
|
|
750
|
+
}
|
|
751
|
+
if (expectedKeyEpoch !== void 0 && expectedKeyEpoch !== this.currentKeyEpoch(key)) {
|
|
752
|
+
return true;
|
|
753
|
+
}
|
|
754
|
+
return false;
|
|
755
|
+
}
|
|
756
|
+
async enqueueWriteBehind(operation, options, flushBatch) {
|
|
757
|
+
this.writeBehindQueue.push(operation);
|
|
758
|
+
const batchSize = options?.batchSize ?? 100;
|
|
759
|
+
const maxQueueSize = options?.maxQueueSize ?? batchSize * 10;
|
|
760
|
+
if (this.writeBehindQueue.length >= batchSize) {
|
|
761
|
+
await this.flushWriteBehindQueue(options, flushBatch);
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
if (this.writeBehindQueue.length >= maxQueueSize) {
|
|
765
|
+
await this.flushWriteBehindQueue(options, flushBatch);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
async flushWriteBehindQueue(options, flushBatch) {
|
|
769
|
+
if (this.writeBehindFlushPromise || this.writeBehindQueue.length === 0) {
|
|
770
|
+
await this.writeBehindFlushPromise;
|
|
771
|
+
return;
|
|
772
|
+
}
|
|
773
|
+
const batchSize = options?.batchSize ?? 100;
|
|
774
|
+
const batch = this.writeBehindQueue.splice(0, batchSize);
|
|
775
|
+
this.writeBehindFlushPromise = flushBatch(batch);
|
|
776
|
+
try {
|
|
777
|
+
await this.writeBehindFlushPromise;
|
|
778
|
+
} finally {
|
|
779
|
+
this.writeBehindFlushPromise = void 0;
|
|
780
|
+
}
|
|
781
|
+
if (this.writeBehindQueue.length > 0) {
|
|
782
|
+
await this.flushWriteBehindQueue(options, flushBatch);
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
scheduleGenerationCleanup(generation, task, onError) {
|
|
786
|
+
const scheduledTask = (this.generationCleanupPromise ?? Promise.resolve()).then(() => task(generation)).catch((error) => {
|
|
787
|
+
onError(generation, error);
|
|
788
|
+
});
|
|
789
|
+
this.generationCleanupPromise = scheduledTask.finally(() => {
|
|
790
|
+
if (this.generationCleanupPromise === scheduledTask) {
|
|
791
|
+
this.generationCleanupPromise = void 0;
|
|
792
|
+
}
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
async waitForGenerationCleanup() {
|
|
796
|
+
await this.generationCleanupPromise;
|
|
797
|
+
}
|
|
798
|
+
};
|
|
799
|
+
|
|
800
|
+
// src/internal/StoredValue.ts
|
|
801
|
+
function isStoredValueEnvelope(value) {
|
|
802
|
+
if (typeof value !== "object" || value === null) {
|
|
803
|
+
return false;
|
|
804
|
+
}
|
|
805
|
+
const v = value;
|
|
806
|
+
if (v.__layercache !== 1) {
|
|
807
|
+
return false;
|
|
808
|
+
}
|
|
809
|
+
if (v.kind !== "value" && v.kind !== "empty") {
|
|
810
|
+
return false;
|
|
811
|
+
}
|
|
812
|
+
if (v.freshUntil !== null && (!Number.isFinite(v.freshUntil) || typeof v.freshUntil !== "number")) {
|
|
813
|
+
return false;
|
|
814
|
+
}
|
|
815
|
+
if (v.staleUntil !== null && (!Number.isFinite(v.staleUntil) || typeof v.staleUntil !== "number")) {
|
|
816
|
+
return false;
|
|
817
|
+
}
|
|
818
|
+
if (v.errorUntil !== null && (!Number.isFinite(v.errorUntil) || typeof v.errorUntil !== "number")) {
|
|
819
|
+
return false;
|
|
820
|
+
}
|
|
821
|
+
const maxTimestamp = Date.now() + 10 * 365 * 24 * 60 * 60 * 1e3;
|
|
822
|
+
if (typeof v.freshUntil === "number" && v.freshUntil > maxTimestamp) {
|
|
823
|
+
return false;
|
|
824
|
+
}
|
|
825
|
+
if (typeof v.staleUntil === "number" && v.staleUntil > maxTimestamp) {
|
|
826
|
+
return false;
|
|
827
|
+
}
|
|
828
|
+
if (typeof v.errorUntil === "number" && v.errorUntil > maxTimestamp) {
|
|
829
|
+
return false;
|
|
830
|
+
}
|
|
831
|
+
if (v.freshUntil === null && (v.staleUntil !== null || v.errorUntil !== null)) {
|
|
832
|
+
return false;
|
|
833
|
+
}
|
|
834
|
+
if (typeof v.freshUntil === "number" && typeof v.staleUntil === "number" && v.staleUntil < v.freshUntil) {
|
|
835
|
+
return false;
|
|
836
|
+
}
|
|
837
|
+
if (typeof v.freshUntil === "number" && typeof v.errorUntil === "number" && v.errorUntil < v.freshUntil) {
|
|
838
|
+
return false;
|
|
839
|
+
}
|
|
840
|
+
const maxTtlSeconds = 10 * 365 * 24 * 60 * 60;
|
|
841
|
+
if (!isValidEnvelopeTtlSeconds(v.freshTtlSeconds, maxTtlSeconds)) {
|
|
842
|
+
return false;
|
|
843
|
+
}
|
|
844
|
+
if (!isValidEnvelopeTtlSeconds(v.staleWhileRevalidateSeconds, maxTtlSeconds)) {
|
|
845
|
+
return false;
|
|
846
|
+
}
|
|
847
|
+
if (!isValidEnvelopeTtlSeconds(v.staleIfErrorSeconds, maxTtlSeconds)) {
|
|
848
|
+
return false;
|
|
849
|
+
}
|
|
850
|
+
if (v.freshTtlSeconds == null && (v.staleWhileRevalidateSeconds != null || v.staleIfErrorSeconds != null)) {
|
|
851
|
+
return false;
|
|
852
|
+
}
|
|
853
|
+
return true;
|
|
854
|
+
}
|
|
855
|
+
function createStoredValueEnvelope(options) {
|
|
856
|
+
const now = options.now ?? Date.now();
|
|
857
|
+
const freshTtlSeconds = normalizePositiveSeconds(options.freshTtlSeconds);
|
|
858
|
+
const staleWhileRevalidateSeconds = normalizePositiveSeconds(options.staleWhileRevalidateSeconds);
|
|
859
|
+
const staleIfErrorSeconds = normalizePositiveSeconds(options.staleIfErrorSeconds);
|
|
860
|
+
const freshUntil = freshTtlSeconds ? now + freshTtlSeconds * 1e3 : null;
|
|
861
|
+
const staleUntil = freshUntil && staleWhileRevalidateSeconds ? freshUntil + staleWhileRevalidateSeconds * 1e3 : null;
|
|
862
|
+
const errorUntil = freshUntil && staleIfErrorSeconds ? freshUntil + staleIfErrorSeconds * 1e3 : null;
|
|
863
|
+
return {
|
|
864
|
+
__layercache: 1,
|
|
865
|
+
kind: options.kind,
|
|
866
|
+
value: options.value,
|
|
867
|
+
freshUntil,
|
|
868
|
+
staleUntil,
|
|
869
|
+
errorUntil,
|
|
870
|
+
freshTtlSeconds: freshTtlSeconds ?? null,
|
|
871
|
+
staleWhileRevalidateSeconds: staleWhileRevalidateSeconds ?? null,
|
|
872
|
+
staleIfErrorSeconds: staleIfErrorSeconds ?? null
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
function resolveStoredValue(stored, now = Date.now()) {
|
|
876
|
+
if (!isStoredValueEnvelope(stored)) {
|
|
877
|
+
return { state: "fresh", value: stored, stored };
|
|
878
|
+
}
|
|
879
|
+
if (stored.freshUntil === null || stored.freshUntil > now) {
|
|
880
|
+
return { state: "fresh", value: unwrapStoredValue(stored), stored, envelope: stored };
|
|
881
|
+
}
|
|
882
|
+
if (stored.staleUntil !== null && stored.staleUntil > now) {
|
|
883
|
+
return { state: "stale-while-revalidate", value: unwrapStoredValue(stored), stored, envelope: stored };
|
|
884
|
+
}
|
|
885
|
+
if (stored.errorUntil !== null && stored.errorUntil > now) {
|
|
886
|
+
return { state: "stale-if-error", value: unwrapStoredValue(stored), stored, envelope: stored };
|
|
887
|
+
}
|
|
888
|
+
return { state: "expired", value: null, stored, envelope: stored };
|
|
889
|
+
}
|
|
890
|
+
function unwrapStoredValue(stored) {
|
|
891
|
+
if (!isStoredValueEnvelope(stored)) {
|
|
892
|
+
return stored;
|
|
893
|
+
}
|
|
894
|
+
if (stored.kind === "empty") {
|
|
895
|
+
return null;
|
|
896
|
+
}
|
|
897
|
+
return stored.value ?? null;
|
|
898
|
+
}
|
|
899
|
+
function remainingStoredTtlSeconds(stored, now = Date.now()) {
|
|
900
|
+
if (!isStoredValueEnvelope(stored)) {
|
|
901
|
+
return void 0;
|
|
902
|
+
}
|
|
903
|
+
const expiry = maxExpiry(stored);
|
|
904
|
+
if (expiry === null) {
|
|
905
|
+
return void 0;
|
|
906
|
+
}
|
|
907
|
+
const remainingMs = expiry - now;
|
|
908
|
+
if (remainingMs <= 0) {
|
|
909
|
+
return 1;
|
|
910
|
+
}
|
|
911
|
+
return Math.max(1, Math.ceil(remainingMs / 1e3));
|
|
912
|
+
}
|
|
913
|
+
function remainingFreshTtlSeconds(stored, now = Date.now()) {
|
|
914
|
+
if (!isStoredValueEnvelope(stored) || stored.freshUntil === null) {
|
|
915
|
+
return void 0;
|
|
916
|
+
}
|
|
917
|
+
const remainingMs = stored.freshUntil - now;
|
|
918
|
+
if (remainingMs <= 0) {
|
|
919
|
+
return 0;
|
|
920
|
+
}
|
|
921
|
+
return Math.max(1, Math.ceil(remainingMs / 1e3));
|
|
922
|
+
}
|
|
923
|
+
function refreshStoredEnvelope(stored, now = Date.now()) {
|
|
924
|
+
if (!isStoredValueEnvelope(stored)) {
|
|
925
|
+
return stored;
|
|
926
|
+
}
|
|
927
|
+
return createStoredValueEnvelope({
|
|
928
|
+
kind: stored.kind,
|
|
929
|
+
value: stored.value,
|
|
930
|
+
freshTtlSeconds: stored.freshTtlSeconds ?? void 0,
|
|
931
|
+
staleWhileRevalidateSeconds: stored.staleWhileRevalidateSeconds ?? void 0,
|
|
932
|
+
staleIfErrorSeconds: stored.staleIfErrorSeconds ?? void 0,
|
|
933
|
+
now
|
|
934
|
+
});
|
|
935
|
+
}
|
|
936
|
+
function maxExpiry(stored) {
|
|
937
|
+
const values = [stored.freshUntil, stored.staleUntil, stored.errorUntil].filter(
|
|
938
|
+
(value) => value !== null
|
|
939
|
+
);
|
|
940
|
+
if (values.length === 0) {
|
|
941
|
+
return null;
|
|
942
|
+
}
|
|
943
|
+
return Math.max(...values);
|
|
944
|
+
}
|
|
945
|
+
function normalizePositiveSeconds(value) {
|
|
946
|
+
if (!value || value <= 0) {
|
|
947
|
+
return void 0;
|
|
948
|
+
}
|
|
949
|
+
return value;
|
|
950
|
+
}
|
|
951
|
+
function isValidEnvelopeTtlSeconds(value, maxTtlSeconds) {
|
|
952
|
+
if (value == null) {
|
|
953
|
+
return true;
|
|
954
|
+
}
|
|
955
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 && value <= maxTtlSeconds;
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
// src/internal/CacheStackRuntimePolicy.ts
|
|
959
|
+
function shouldSkipLayer(degradedUntil, now = Date.now()) {
|
|
960
|
+
return degradedUntil !== void 0 && degradedUntil > now;
|
|
961
|
+
}
|
|
962
|
+
function shouldStartBackgroundRefresh({
|
|
963
|
+
isDisconnecting,
|
|
964
|
+
hasRefreshInFlight
|
|
965
|
+
}) {
|
|
966
|
+
return !isDisconnecting && !hasRefreshInFlight;
|
|
967
|
+
}
|
|
968
|
+
function resolveRecoverableLayerFailure(gracefulDegradation, now = Date.now()) {
|
|
969
|
+
if (!gracefulDegradation) {
|
|
970
|
+
return { degrade: false };
|
|
971
|
+
}
|
|
972
|
+
const retryAfterMs = typeof gracefulDegradation === "object" ? gracefulDegradation.retryAfterMs ?? 1e4 : 1e4;
|
|
973
|
+
return {
|
|
974
|
+
degrade: true,
|
|
975
|
+
degradedUntil: now + retryAfterMs
|
|
976
|
+
};
|
|
977
|
+
}
|
|
978
|
+
function planFreshReadPolicies({
|
|
979
|
+
stored,
|
|
980
|
+
hasFetcher,
|
|
981
|
+
slidingTtl,
|
|
982
|
+
refreshAheadSeconds
|
|
983
|
+
}) {
|
|
984
|
+
const refreshedStored = slidingTtl && isStoredValueEnvelope(stored) ? refreshStoredEnvelope(stored) : void 0;
|
|
985
|
+
const refreshedStoredTtl = refreshedStored ? remainingStoredTtlSeconds(refreshedStored) ?? void 0 : void 0;
|
|
986
|
+
const remainingFreshTtl = remainingFreshTtlSeconds(stored) ?? 0;
|
|
987
|
+
return {
|
|
988
|
+
refreshedStored,
|
|
989
|
+
refreshedStoredTtl,
|
|
990
|
+
shouldScheduleBackgroundRefresh: hasFetcher && refreshAheadSeconds > 0 && remainingFreshTtl > 0 && remainingFreshTtl <= refreshAheadSeconds
|
|
991
|
+
};
|
|
646
992
|
}
|
|
647
993
|
|
|
648
994
|
// src/internal/CacheStackValidation.ts
|
|
@@ -798,7 +1144,6 @@ var CircuitBreakerManager = class {
|
|
|
798
1144
|
if (!options) {
|
|
799
1145
|
return;
|
|
800
1146
|
}
|
|
801
|
-
this.pruneIfNeeded();
|
|
802
1147
|
const failureThreshold = options.failureThreshold ?? 3;
|
|
803
1148
|
const cooldownMs = options.cooldownMs ?? 3e4;
|
|
804
1149
|
const state = this.breakers.get(key) ?? { failures: 0, openUntil: null, createdAt: Date.now() };
|
|
@@ -807,6 +1152,7 @@ var CircuitBreakerManager = class {
|
|
|
807
1152
|
state.openUntil = Date.now() + cooldownMs;
|
|
808
1153
|
}
|
|
809
1154
|
this.breakers.set(key, state);
|
|
1155
|
+
this.pruneIfNeeded();
|
|
810
1156
|
}
|
|
811
1157
|
recordSuccess(key) {
|
|
812
1158
|
this.breakers.delete(key);
|
|
@@ -1150,164 +1496,6 @@ var MetricsCollector = class {
|
|
|
1150
1496
|
}
|
|
1151
1497
|
};
|
|
1152
1498
|
|
|
1153
|
-
// src/internal/StoredValue.ts
|
|
1154
|
-
function isStoredValueEnvelope(value) {
|
|
1155
|
-
if (typeof value !== "object" || value === null) {
|
|
1156
|
-
return false;
|
|
1157
|
-
}
|
|
1158
|
-
const v = value;
|
|
1159
|
-
if (v.__layercache !== 1) {
|
|
1160
|
-
return false;
|
|
1161
|
-
}
|
|
1162
|
-
if (v.kind !== "value" && v.kind !== "empty") {
|
|
1163
|
-
return false;
|
|
1164
|
-
}
|
|
1165
|
-
if (v.freshUntil !== null && (!Number.isFinite(v.freshUntil) || typeof v.freshUntil !== "number")) {
|
|
1166
|
-
return false;
|
|
1167
|
-
}
|
|
1168
|
-
if (v.staleUntil !== null && (!Number.isFinite(v.staleUntil) || typeof v.staleUntil !== "number")) {
|
|
1169
|
-
return false;
|
|
1170
|
-
}
|
|
1171
|
-
if (v.errorUntil !== null && (!Number.isFinite(v.errorUntil) || typeof v.errorUntil !== "number")) {
|
|
1172
|
-
return false;
|
|
1173
|
-
}
|
|
1174
|
-
const maxTimestamp = Date.now() + 10 * 365 * 24 * 60 * 60 * 1e3;
|
|
1175
|
-
if (typeof v.freshUntil === "number" && v.freshUntil > maxTimestamp) {
|
|
1176
|
-
return false;
|
|
1177
|
-
}
|
|
1178
|
-
if (typeof v.staleUntil === "number" && v.staleUntil > maxTimestamp) {
|
|
1179
|
-
return false;
|
|
1180
|
-
}
|
|
1181
|
-
if (typeof v.errorUntil === "number" && v.errorUntil > maxTimestamp) {
|
|
1182
|
-
return false;
|
|
1183
|
-
}
|
|
1184
|
-
if (v.freshUntil === null && (v.staleUntil !== null || v.errorUntil !== null)) {
|
|
1185
|
-
return false;
|
|
1186
|
-
}
|
|
1187
|
-
if (typeof v.freshUntil === "number" && typeof v.staleUntil === "number" && v.staleUntil < v.freshUntil) {
|
|
1188
|
-
return false;
|
|
1189
|
-
}
|
|
1190
|
-
if (typeof v.freshUntil === "number" && typeof v.errorUntil === "number" && v.errorUntil < v.freshUntil) {
|
|
1191
|
-
return false;
|
|
1192
|
-
}
|
|
1193
|
-
const maxTtlSeconds = 10 * 365 * 24 * 60 * 60;
|
|
1194
|
-
if (!isValidEnvelopeTtlSeconds(v.freshTtlSeconds, maxTtlSeconds)) {
|
|
1195
|
-
return false;
|
|
1196
|
-
}
|
|
1197
|
-
if (!isValidEnvelopeTtlSeconds(v.staleWhileRevalidateSeconds, maxTtlSeconds)) {
|
|
1198
|
-
return false;
|
|
1199
|
-
}
|
|
1200
|
-
if (!isValidEnvelopeTtlSeconds(v.staleIfErrorSeconds, maxTtlSeconds)) {
|
|
1201
|
-
return false;
|
|
1202
|
-
}
|
|
1203
|
-
if (v.freshTtlSeconds == null && (v.staleWhileRevalidateSeconds != null || v.staleIfErrorSeconds != null)) {
|
|
1204
|
-
return false;
|
|
1205
|
-
}
|
|
1206
|
-
return true;
|
|
1207
|
-
}
|
|
1208
|
-
function createStoredValueEnvelope(options) {
|
|
1209
|
-
const now = options.now ?? Date.now();
|
|
1210
|
-
const freshTtlSeconds = normalizePositiveSeconds(options.freshTtlSeconds);
|
|
1211
|
-
const staleWhileRevalidateSeconds = normalizePositiveSeconds(options.staleWhileRevalidateSeconds);
|
|
1212
|
-
const staleIfErrorSeconds = normalizePositiveSeconds(options.staleIfErrorSeconds);
|
|
1213
|
-
const freshUntil = freshTtlSeconds ? now + freshTtlSeconds * 1e3 : null;
|
|
1214
|
-
const staleUntil = freshUntil && staleWhileRevalidateSeconds ? freshUntil + staleWhileRevalidateSeconds * 1e3 : null;
|
|
1215
|
-
const errorUntil = freshUntil && staleIfErrorSeconds ? freshUntil + staleIfErrorSeconds * 1e3 : null;
|
|
1216
|
-
return {
|
|
1217
|
-
__layercache: 1,
|
|
1218
|
-
kind: options.kind,
|
|
1219
|
-
value: options.value,
|
|
1220
|
-
freshUntil,
|
|
1221
|
-
staleUntil,
|
|
1222
|
-
errorUntil,
|
|
1223
|
-
freshTtlSeconds: freshTtlSeconds ?? null,
|
|
1224
|
-
staleWhileRevalidateSeconds: staleWhileRevalidateSeconds ?? null,
|
|
1225
|
-
staleIfErrorSeconds: staleIfErrorSeconds ?? null
|
|
1226
|
-
};
|
|
1227
|
-
}
|
|
1228
|
-
function resolveStoredValue(stored, now = Date.now()) {
|
|
1229
|
-
if (!isStoredValueEnvelope(stored)) {
|
|
1230
|
-
return { state: "fresh", value: stored, stored };
|
|
1231
|
-
}
|
|
1232
|
-
if (stored.freshUntil === null || stored.freshUntil > now) {
|
|
1233
|
-
return { state: "fresh", value: unwrapStoredValue(stored), stored, envelope: stored };
|
|
1234
|
-
}
|
|
1235
|
-
if (stored.staleUntil !== null && stored.staleUntil > now) {
|
|
1236
|
-
return { state: "stale-while-revalidate", value: unwrapStoredValue(stored), stored, envelope: stored };
|
|
1237
|
-
}
|
|
1238
|
-
if (stored.errorUntil !== null && stored.errorUntil > now) {
|
|
1239
|
-
return { state: "stale-if-error", value: unwrapStoredValue(stored), stored, envelope: stored };
|
|
1240
|
-
}
|
|
1241
|
-
return { state: "expired", value: null, stored, envelope: stored };
|
|
1242
|
-
}
|
|
1243
|
-
function unwrapStoredValue(stored) {
|
|
1244
|
-
if (!isStoredValueEnvelope(stored)) {
|
|
1245
|
-
return stored;
|
|
1246
|
-
}
|
|
1247
|
-
if (stored.kind === "empty") {
|
|
1248
|
-
return null;
|
|
1249
|
-
}
|
|
1250
|
-
return stored.value ?? null;
|
|
1251
|
-
}
|
|
1252
|
-
function remainingStoredTtlSeconds(stored, now = Date.now()) {
|
|
1253
|
-
if (!isStoredValueEnvelope(stored)) {
|
|
1254
|
-
return void 0;
|
|
1255
|
-
}
|
|
1256
|
-
const expiry = maxExpiry(stored);
|
|
1257
|
-
if (expiry === null) {
|
|
1258
|
-
return void 0;
|
|
1259
|
-
}
|
|
1260
|
-
const remainingMs = expiry - now;
|
|
1261
|
-
if (remainingMs <= 0) {
|
|
1262
|
-
return 1;
|
|
1263
|
-
}
|
|
1264
|
-
return Math.max(1, Math.ceil(remainingMs / 1e3));
|
|
1265
|
-
}
|
|
1266
|
-
function remainingFreshTtlSeconds(stored, now = Date.now()) {
|
|
1267
|
-
if (!isStoredValueEnvelope(stored) || stored.freshUntil === null) {
|
|
1268
|
-
return void 0;
|
|
1269
|
-
}
|
|
1270
|
-
const remainingMs = stored.freshUntil - now;
|
|
1271
|
-
if (remainingMs <= 0) {
|
|
1272
|
-
return 0;
|
|
1273
|
-
}
|
|
1274
|
-
return Math.max(1, Math.ceil(remainingMs / 1e3));
|
|
1275
|
-
}
|
|
1276
|
-
function refreshStoredEnvelope(stored, now = Date.now()) {
|
|
1277
|
-
if (!isStoredValueEnvelope(stored)) {
|
|
1278
|
-
return stored;
|
|
1279
|
-
}
|
|
1280
|
-
return createStoredValueEnvelope({
|
|
1281
|
-
kind: stored.kind,
|
|
1282
|
-
value: stored.value,
|
|
1283
|
-
freshTtlSeconds: stored.freshTtlSeconds ?? void 0,
|
|
1284
|
-
staleWhileRevalidateSeconds: stored.staleWhileRevalidateSeconds ?? void 0,
|
|
1285
|
-
staleIfErrorSeconds: stored.staleIfErrorSeconds ?? void 0,
|
|
1286
|
-
now
|
|
1287
|
-
});
|
|
1288
|
-
}
|
|
1289
|
-
function maxExpiry(stored) {
|
|
1290
|
-
const values = [stored.freshUntil, stored.staleUntil, stored.errorUntil].filter(
|
|
1291
|
-
(value) => value !== null
|
|
1292
|
-
);
|
|
1293
|
-
if (values.length === 0) {
|
|
1294
|
-
return null;
|
|
1295
|
-
}
|
|
1296
|
-
return Math.max(...values);
|
|
1297
|
-
}
|
|
1298
|
-
function normalizePositiveSeconds(value) {
|
|
1299
|
-
if (!value || value <= 0) {
|
|
1300
|
-
return void 0;
|
|
1301
|
-
}
|
|
1302
|
-
return value;
|
|
1303
|
-
}
|
|
1304
|
-
function isValidEnvelopeTtlSeconds(value, maxTtlSeconds) {
|
|
1305
|
-
if (value == null) {
|
|
1306
|
-
return true;
|
|
1307
|
-
}
|
|
1308
|
-
return typeof value === "number" && Number.isFinite(value) && value > 0 && value <= maxTtlSeconds;
|
|
1309
|
-
}
|
|
1310
|
-
|
|
1311
1499
|
// src/internal/TtlResolver.ts
|
|
1312
1500
|
var DEFAULT_NEGATIVE_TTL_SECONDS = 60;
|
|
1313
1501
|
var TtlResolver = class {
|
|
@@ -1841,15 +2029,10 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
1841
2029
|
snapshotSerializer = new JsonSerializer();
|
|
1842
2030
|
backgroundRefreshes = /* @__PURE__ */ new Map();
|
|
1843
2031
|
layerDegradedUntil = /* @__PURE__ */ new Map();
|
|
1844
|
-
|
|
2032
|
+
maintenance = new CacheStackMaintenance();
|
|
1845
2033
|
ttlResolver;
|
|
1846
2034
|
circuitBreakerManager;
|
|
1847
2035
|
currentGeneration;
|
|
1848
|
-
writeBehindQueue = [];
|
|
1849
|
-
writeBehindTimer;
|
|
1850
|
-
writeBehindFlushPromise;
|
|
1851
|
-
generationCleanupPromise;
|
|
1852
|
-
clearEpoch = 0;
|
|
1853
2036
|
isDisconnecting = false;
|
|
1854
2037
|
disconnectPromise;
|
|
1855
2038
|
/**
|
|
@@ -2005,7 +2188,7 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
2005
2188
|
}
|
|
2006
2189
|
async clear() {
|
|
2007
2190
|
await this.awaitStartup("clear");
|
|
2008
|
-
this.beginClearEpoch();
|
|
2191
|
+
this.maintenance.beginClearEpoch();
|
|
2009
2192
|
await Promise.all(this.layers.map((layer) => layer.clear()));
|
|
2010
2193
|
await this.tagIndex.clear();
|
|
2011
2194
|
this.ttlResolver.clearProfiles();
|
|
@@ -2263,9 +2446,15 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
2263
2446
|
bumpGeneration(nextGeneration) {
|
|
2264
2447
|
const current = this.currentGeneration ?? 0;
|
|
2265
2448
|
const previousGeneration = this.currentGeneration;
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2449
|
+
const updatedGeneration = nextGeneration ?? current + 1;
|
|
2450
|
+
const generationToCleanup = resolveGenerationCleanupTarget({
|
|
2451
|
+
previousGeneration,
|
|
2452
|
+
nextGeneration: updatedGeneration,
|
|
2453
|
+
generationCleanup: this.options.generationCleanup
|
|
2454
|
+
});
|
|
2455
|
+
this.currentGeneration = updatedGeneration;
|
|
2456
|
+
if (generationToCleanup !== null) {
|
|
2457
|
+
this.scheduleGenerationCleanup(generationToCleanup);
|
|
2269
2458
|
}
|
|
2270
2459
|
return this.currentGeneration;
|
|
2271
2460
|
}
|
|
@@ -2409,12 +2598,9 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
2409
2598
|
await this.startup;
|
|
2410
2599
|
await this.unsubscribeInvalidation?.();
|
|
2411
2600
|
await this.flushWriteBehindQueue();
|
|
2412
|
-
await this.
|
|
2601
|
+
await this.maintenance.waitForGenerationCleanup();
|
|
2413
2602
|
await Promise.allSettled([...this.backgroundRefreshes.values()]);
|
|
2414
|
-
|
|
2415
|
-
clearInterval(this.writeBehindTimer);
|
|
2416
|
-
this.writeBehindTimer = void 0;
|
|
2417
|
-
}
|
|
2603
|
+
this.maintenance.disposeWriteBehindTimer();
|
|
2418
2604
|
await Promise.allSettled(this.layers.map((layer) => layer.dispose?.() ?? Promise.resolve()));
|
|
2419
2605
|
})();
|
|
2420
2606
|
}
|
|
@@ -2490,13 +2676,13 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
2490
2676
|
if (!this.shouldNegativeCache(options)) {
|
|
2491
2677
|
return null;
|
|
2492
2678
|
}
|
|
2493
|
-
if (this.isWriteOutdated(key, expectedClearEpoch, expectedKeyEpoch)) {
|
|
2679
|
+
if (this.maintenance.isWriteOutdated(key, expectedClearEpoch, expectedKeyEpoch)) {
|
|
2494
2680
|
this.logger.debug?.("skip-negative-store-after-invalidation", {
|
|
2495
2681
|
key,
|
|
2496
2682
|
expectedClearEpoch,
|
|
2497
|
-
clearEpoch: this.
|
|
2683
|
+
clearEpoch: this.maintenance.currentClearEpoch(),
|
|
2498
2684
|
expectedKeyEpoch,
|
|
2499
|
-
keyEpoch: this.currentKeyEpoch(key)
|
|
2685
|
+
keyEpoch: this.maintenance.currentKeyEpoch(key)
|
|
2500
2686
|
});
|
|
2501
2687
|
return null;
|
|
2502
2688
|
}
|
|
@@ -2512,13 +2698,13 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
2512
2698
|
this.logger.warn?.("shouldCache-error", { key, error: this.formatError(error) });
|
|
2513
2699
|
}
|
|
2514
2700
|
}
|
|
2515
|
-
if (this.isWriteOutdated(key, expectedClearEpoch, expectedKeyEpoch)) {
|
|
2701
|
+
if (this.maintenance.isWriteOutdated(key, expectedClearEpoch, expectedKeyEpoch)) {
|
|
2516
2702
|
this.logger.debug?.("skip-store-after-invalidation", {
|
|
2517
2703
|
key,
|
|
2518
2704
|
expectedClearEpoch,
|
|
2519
|
-
clearEpoch: this.
|
|
2705
|
+
clearEpoch: this.maintenance.currentClearEpoch(),
|
|
2520
2706
|
expectedKeyEpoch,
|
|
2521
|
-
keyEpoch: this.currentKeyEpoch(key)
|
|
2707
|
+
keyEpoch: this.maintenance.currentKeyEpoch(key)
|
|
2522
2708
|
});
|
|
2523
2709
|
return fetched;
|
|
2524
2710
|
}
|
|
@@ -2526,10 +2712,10 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
2526
2712
|
return fetched;
|
|
2527
2713
|
}
|
|
2528
2714
|
async storeEntry(key, kind, value, options) {
|
|
2529
|
-
const clearEpoch = this.
|
|
2530
|
-
const keyEpoch = this.currentKeyEpoch(key);
|
|
2715
|
+
const clearEpoch = this.maintenance.currentClearEpoch();
|
|
2716
|
+
const keyEpoch = this.maintenance.currentKeyEpoch(key);
|
|
2531
2717
|
await this.writeAcrossLayers(key, kind, value, options);
|
|
2532
|
-
if (this.isWriteOutdated(key, clearEpoch, keyEpoch)) {
|
|
2718
|
+
if (this.maintenance.isWriteOutdated(key, clearEpoch, keyEpoch)) {
|
|
2533
2719
|
return;
|
|
2534
2720
|
}
|
|
2535
2721
|
if (options?.tags) {
|
|
@@ -2546,8 +2732,8 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
2546
2732
|
}
|
|
2547
2733
|
async writeBatch(entries) {
|
|
2548
2734
|
const now = Date.now();
|
|
2549
|
-
const clearEpoch = this.
|
|
2550
|
-
const entryEpochs = new Map(entries.map((entry) => [entry.key, this.currentKeyEpoch(entry.key)]));
|
|
2735
|
+
const clearEpoch = this.maintenance.currentClearEpoch();
|
|
2736
|
+
const entryEpochs = new Map(entries.map((entry) => [entry.key, this.maintenance.currentKeyEpoch(entry.key)]));
|
|
2551
2737
|
const entriesByLayer = /* @__PURE__ */ new Map();
|
|
2552
2738
|
const immediateOperations = [];
|
|
2553
2739
|
const deferredOperations = [];
|
|
@@ -2564,11 +2750,11 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
2564
2750
|
}
|
|
2565
2751
|
for (const [layer, layerEntries] of entriesByLayer.entries()) {
|
|
2566
2752
|
const operation = async () => {
|
|
2567
|
-
if (clearEpoch !== this.
|
|
2753
|
+
if (clearEpoch !== this.maintenance.currentClearEpoch()) {
|
|
2568
2754
|
return;
|
|
2569
2755
|
}
|
|
2570
2756
|
const activeEntries = layerEntries.filter(
|
|
2571
|
-
(entry) => (entryEpochs.get(entry.key) ?? 0) === this.currentKeyEpoch(entry.key)
|
|
2757
|
+
(entry) => (entryEpochs.get(entry.key) ?? 0) === this.maintenance.currentKeyEpoch(entry.key)
|
|
2572
2758
|
);
|
|
2573
2759
|
if (activeEntries.length === 0) {
|
|
2574
2760
|
return;
|
|
@@ -2591,11 +2777,11 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
2591
2777
|
}
|
|
2592
2778
|
await this.executeLayerOperations(immediateOperations, { key: "batch", action: "mset" });
|
|
2593
2779
|
await Promise.all(deferredOperations.map((operation) => this.enqueueWriteBehind(operation)));
|
|
2594
|
-
if (clearEpoch !== this.
|
|
2780
|
+
if (clearEpoch !== this.maintenance.currentClearEpoch()) {
|
|
2595
2781
|
return;
|
|
2596
2782
|
}
|
|
2597
2783
|
for (const entry of entries) {
|
|
2598
|
-
if (this.isWriteOutdated(entry.key, clearEpoch, entryEpochs.get(entry.key))) {
|
|
2784
|
+
if (this.maintenance.isWriteOutdated(entry.key, clearEpoch, entryEpochs.get(entry.key))) {
|
|
2599
2785
|
continue;
|
|
2600
2786
|
}
|
|
2601
2787
|
if (entry.options?.tags) {
|
|
@@ -2699,13 +2885,13 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
2699
2885
|
}
|
|
2700
2886
|
async writeAcrossLayers(key, kind, value, options) {
|
|
2701
2887
|
const now = Date.now();
|
|
2702
|
-
const clearEpoch = this.
|
|
2703
|
-
const keyEpoch = this.currentKeyEpoch(key);
|
|
2888
|
+
const clearEpoch = this.maintenance.currentClearEpoch();
|
|
2889
|
+
const keyEpoch = this.maintenance.currentKeyEpoch(key);
|
|
2704
2890
|
const immediateOperations = [];
|
|
2705
2891
|
const deferredOperations = [];
|
|
2706
2892
|
for (const layer of this.layers) {
|
|
2707
2893
|
const operation = async () => {
|
|
2708
|
-
if (this.isWriteOutdated(key, clearEpoch, keyEpoch)) {
|
|
2894
|
+
if (this.maintenance.isWriteOutdated(key, clearEpoch, keyEpoch)) {
|
|
2709
2895
|
return;
|
|
2710
2896
|
}
|
|
2711
2897
|
if (this.shouldSkipLayer(layer)) {
|
|
@@ -2768,11 +2954,14 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
2768
2954
|
return options?.negativeCache ?? this.options.negativeCaching ?? false;
|
|
2769
2955
|
}
|
|
2770
2956
|
scheduleBackgroundRefresh(key, fetcher, options) {
|
|
2771
|
-
if (
|
|
2957
|
+
if (!shouldStartBackgroundRefresh({
|
|
2958
|
+
isDisconnecting: this.isDisconnecting,
|
|
2959
|
+
hasRefreshInFlight: this.backgroundRefreshes.has(key)
|
|
2960
|
+
})) {
|
|
2772
2961
|
return;
|
|
2773
2962
|
}
|
|
2774
|
-
const clearEpoch = this.
|
|
2775
|
-
const keyEpoch = this.currentKeyEpoch(key);
|
|
2963
|
+
const clearEpoch = this.maintenance.currentClearEpoch();
|
|
2964
|
+
const keyEpoch = this.maintenance.currentKeyEpoch(key);
|
|
2776
2965
|
const refresh = (async () => {
|
|
2777
2966
|
this.metricsCollector.increment("refreshes");
|
|
2778
2967
|
try {
|
|
@@ -2810,7 +2999,7 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
2810
2999
|
if (keys.length === 0) {
|
|
2811
3000
|
return;
|
|
2812
3001
|
}
|
|
2813
|
-
this.bumpKeyEpochs(keys);
|
|
3002
|
+
this.maintenance.bumpKeyEpochs(keys);
|
|
2814
3003
|
await this.deleteKeysFromLayers(this.layers, keys);
|
|
2815
3004
|
for (const key of keys) {
|
|
2816
3005
|
await this.tagIndex.remove(key);
|
|
@@ -2834,7 +3023,7 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
2834
3023
|
}
|
|
2835
3024
|
const localLayers = this.layers.filter((layer) => layer.isLocal);
|
|
2836
3025
|
if (message.scope === "clear") {
|
|
2837
|
-
this.beginClearEpoch();
|
|
3026
|
+
this.maintenance.beginClearEpoch();
|
|
2838
3027
|
await Promise.all(localLayers.map((layer) => layer.clear()));
|
|
2839
3028
|
await this.tagIndex.clear();
|
|
2840
3029
|
this.ttlResolver.clearProfiles();
|
|
@@ -2842,7 +3031,7 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
2842
3031
|
return;
|
|
2843
3032
|
}
|
|
2844
3033
|
const keys = message.keys ?? [];
|
|
2845
|
-
this.bumpKeyEpochs(keys);
|
|
3034
|
+
this.maintenance.bumpKeyEpochs(keys);
|
|
2846
3035
|
await this.deleteKeysFromLayers(localLayers, keys);
|
|
2847
3036
|
if (message.operation !== "write") {
|
|
2848
3037
|
for (const key of keys) {
|
|
@@ -2900,35 +3089,22 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
2900
3089
|
shouldBroadcastL1Invalidation() {
|
|
2901
3090
|
return this.options.broadcastL1Invalidation ?? this.options.publishSetInvalidation ?? false;
|
|
2902
3091
|
}
|
|
2903
|
-
shouldCleanupGenerations() {
|
|
2904
|
-
return Boolean(this.options.generationCleanup);
|
|
2905
|
-
}
|
|
2906
|
-
generationCleanupBatchSize() {
|
|
2907
|
-
const configured = typeof this.options.generationCleanup === "object" ? this.options.generationCleanup.batchSize : void 0;
|
|
2908
|
-
return configured ?? 500;
|
|
2909
|
-
}
|
|
2910
3092
|
scheduleGenerationCleanup(generation) {
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
this.generationCleanupPromise = void 0;
|
|
3093
|
+
this.maintenance.scheduleGenerationCleanup(
|
|
3094
|
+
generation,
|
|
3095
|
+
async (generationToClean) => this.cleanupGeneration(generationToClean),
|
|
3096
|
+
(failedGeneration, error) => {
|
|
3097
|
+
this.logger.warn?.("generation-cleanup-error", {
|
|
3098
|
+
generation: failedGeneration,
|
|
3099
|
+
error: this.formatError(error)
|
|
3100
|
+
});
|
|
2920
3101
|
}
|
|
2921
|
-
|
|
3102
|
+
);
|
|
2922
3103
|
}
|
|
2923
3104
|
async cleanupGeneration(generation) {
|
|
2924
3105
|
const prefix = `v${generation}:`;
|
|
2925
3106
|
const keys = await this.keyDiscovery.collectKeysWithPrefix(prefix);
|
|
2926
|
-
|
|
2927
|
-
return;
|
|
2928
|
-
}
|
|
2929
|
-
const batchSize = this.generationCleanupBatchSize();
|
|
2930
|
-
for (let index = 0; index < keys.length; index += batchSize) {
|
|
2931
|
-
const batch = keys.slice(index, index + batchSize);
|
|
3107
|
+
for (const batch of planGenerationCleanupBatches(keys, this.options.generationCleanup)) {
|
|
2932
3108
|
await this.deleteKeys(batch);
|
|
2933
3109
|
await this.publishInvalidation({
|
|
2934
3110
|
scope: "keys",
|
|
@@ -2939,80 +3115,34 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
2939
3115
|
}
|
|
2940
3116
|
}
|
|
2941
3117
|
initializeWriteBehind(options) {
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
return;
|
|
2948
|
-
}
|
|
2949
|
-
this.writeBehindTimer = setInterval(() => {
|
|
2950
|
-
void this.flushWriteBehindQueue();
|
|
2951
|
-
}, flushIntervalMs);
|
|
2952
|
-
this.writeBehindTimer.unref?.();
|
|
3118
|
+
this.maintenance.initializeWriteBehindTimer(
|
|
3119
|
+
this.options.writeStrategy,
|
|
3120
|
+
options,
|
|
3121
|
+
this.flushWriteBehindQueue.bind(this)
|
|
3122
|
+
);
|
|
2953
3123
|
}
|
|
2954
3124
|
shouldWriteBehind(layer) {
|
|
2955
3125
|
return this.options.writeStrategy === "write-behind" && !layer.isLocal;
|
|
2956
3126
|
}
|
|
2957
|
-
beginClearEpoch() {
|
|
2958
|
-
this.clearEpoch += 1;
|
|
2959
|
-
this.keyEpochs.clear();
|
|
2960
|
-
this.writeBehindQueue.length = 0;
|
|
2961
|
-
}
|
|
2962
|
-
currentKeyEpoch(key) {
|
|
2963
|
-
return this.keyEpochs.get(key) ?? 0;
|
|
2964
|
-
}
|
|
2965
|
-
bumpKeyEpochs(keys) {
|
|
2966
|
-
for (const key of keys) {
|
|
2967
|
-
this.keyEpochs.set(key, this.currentKeyEpoch(key) + 1);
|
|
2968
|
-
}
|
|
2969
|
-
}
|
|
2970
|
-
isWriteOutdated(key, expectedClearEpoch, expectedKeyEpoch) {
|
|
2971
|
-
if (expectedClearEpoch !== void 0 && expectedClearEpoch !== this.clearEpoch) {
|
|
2972
|
-
return true;
|
|
2973
|
-
}
|
|
2974
|
-
if (expectedKeyEpoch !== void 0 && expectedKeyEpoch !== this.currentKeyEpoch(key)) {
|
|
2975
|
-
return true;
|
|
2976
|
-
}
|
|
2977
|
-
return false;
|
|
2978
|
-
}
|
|
2979
3127
|
async enqueueWriteBehind(operation) {
|
|
2980
|
-
this.
|
|
2981
|
-
const batchSize = this.options.writeBehind?.batchSize ?? 100;
|
|
2982
|
-
const maxQueueSize = this.options.writeBehind?.maxQueueSize ?? batchSize * 10;
|
|
2983
|
-
if (this.writeBehindQueue.length >= batchSize) {
|
|
2984
|
-
await this.flushWriteBehindQueue();
|
|
2985
|
-
return;
|
|
2986
|
-
}
|
|
2987
|
-
if (this.writeBehindQueue.length >= maxQueueSize) {
|
|
2988
|
-
await this.flushWriteBehindQueue();
|
|
2989
|
-
}
|
|
3128
|
+
await this.maintenance.enqueueWriteBehind(operation, this.options.writeBehind, this.runWriteBehindBatch.bind(this));
|
|
2990
3129
|
}
|
|
2991
3130
|
async flushWriteBehindQueue() {
|
|
2992
|
-
|
|
2993
|
-
|
|
3131
|
+
await this.maintenance.flushWriteBehindQueue(this.options.writeBehind, this.runWriteBehindBatch.bind(this));
|
|
3132
|
+
}
|
|
3133
|
+
async runWriteBehindBatch(batch) {
|
|
3134
|
+
const results = await Promise.allSettled(batch.map((operation) => operation()));
|
|
3135
|
+
const failures = results.filter((result) => result.status === "rejected");
|
|
3136
|
+
if (failures.length === 0) {
|
|
2994
3137
|
return;
|
|
2995
3138
|
}
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
this.logger.error?.("write-behind-flush-failure", {
|
|
3004
|
-
failed: failures.length,
|
|
3005
|
-
total: batch.length,
|
|
3006
|
-
errors: failures.map((failure) => this.formatError(failure.reason))
|
|
3007
|
-
});
|
|
3008
|
-
this.emitError("write-behind", { failed: failures.length, total: batch.length });
|
|
3009
|
-
}
|
|
3010
|
-
})();
|
|
3011
|
-
await this.writeBehindFlushPromise;
|
|
3012
|
-
this.writeBehindFlushPromise = void 0;
|
|
3013
|
-
if (this.writeBehindQueue.length > 0) {
|
|
3014
|
-
await this.flushWriteBehindQueue();
|
|
3015
|
-
}
|
|
3139
|
+
this.metricsCollector.increment("writeFailures", failures.length);
|
|
3140
|
+
this.logger.error?.("write-behind-flush-failure", {
|
|
3141
|
+
failed: failures.length,
|
|
3142
|
+
total: batch.length,
|
|
3143
|
+
errors: failures.map((failure) => this.formatError(failure.reason))
|
|
3144
|
+
});
|
|
3145
|
+
this.emitError("write-behind", { failed: failures.length, total: batch.length });
|
|
3016
3146
|
}
|
|
3017
3147
|
buildLayerSetEntry(layer, key, kind, value, options, now) {
|
|
3018
3148
|
const freshTtl = this.resolveFreshTtl(key, layer.name, kind, options, layer.defaultTtl, value);
|
|
@@ -3042,32 +3172,17 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
3042
3172
|
return [];
|
|
3043
3173
|
}
|
|
3044
3174
|
const [firstGroup, ...rest] = groups;
|
|
3045
|
-
if (!firstGroup) {
|
|
3046
|
-
return [];
|
|
3047
|
-
}
|
|
3048
3175
|
const restSets = rest.map((group) => new Set(group));
|
|
3049
3176
|
return [...new Set(firstGroup)].filter((key) => restSets.every((group) => group.has(key)));
|
|
3050
3177
|
}
|
|
3051
3178
|
qualifyKey(key) {
|
|
3052
|
-
|
|
3053
|
-
return prefix ? `${prefix}${key}` : key;
|
|
3179
|
+
return qualifyGenerationKey(key, this.currentGeneration);
|
|
3054
3180
|
}
|
|
3055
3181
|
qualifyPattern(pattern) {
|
|
3056
|
-
|
|
3057
|
-
return prefix ? `${prefix}${pattern}` : pattern;
|
|
3182
|
+
return qualifyGenerationPattern(pattern, this.currentGeneration);
|
|
3058
3183
|
}
|
|
3059
3184
|
stripQualifiedKey(key) {
|
|
3060
|
-
|
|
3061
|
-
if (!prefix || !key.startsWith(prefix)) {
|
|
3062
|
-
return key;
|
|
3063
|
-
}
|
|
3064
|
-
return key.slice(prefix.length);
|
|
3065
|
-
}
|
|
3066
|
-
generationPrefix() {
|
|
3067
|
-
if (this.currentGeneration === void 0) {
|
|
3068
|
-
return "";
|
|
3069
|
-
}
|
|
3070
|
-
return `v${this.currentGeneration}:`;
|
|
3185
|
+
return stripGenerationPrefix(key, this.currentGeneration);
|
|
3071
3186
|
}
|
|
3072
3187
|
async deleteKeysFromLayers(layers, keys) {
|
|
3073
3188
|
await Promise.all(
|
|
@@ -3158,37 +3273,38 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
3158
3273
|
this.assertActive(operation);
|
|
3159
3274
|
}
|
|
3160
3275
|
async applyFreshReadPolicies(key, hit, options, fetcher) {
|
|
3161
|
-
const
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3276
|
+
const plan = planFreshReadPolicies({
|
|
3277
|
+
stored: hit.stored,
|
|
3278
|
+
hasFetcher: Boolean(fetcher),
|
|
3279
|
+
slidingTtl: options?.slidingTtl ?? false,
|
|
3280
|
+
refreshAheadSeconds: this.resolveLayerSeconds(hit.layerName, options?.refreshAhead, this.options.refreshAhead, 0) ?? 0
|
|
3281
|
+
});
|
|
3282
|
+
if (plan.refreshedStored) {
|
|
3166
3283
|
for (let index = 0; index <= hit.layerIndex; index += 1) {
|
|
3167
3284
|
const layer = this.layers[index];
|
|
3168
3285
|
if (!layer || this.shouldSkipLayer(layer)) {
|
|
3169
3286
|
continue;
|
|
3170
3287
|
}
|
|
3171
3288
|
try {
|
|
3172
|
-
await layer.set(key,
|
|
3289
|
+
await layer.set(key, plan.refreshedStored, plan.refreshedStoredTtl);
|
|
3173
3290
|
} catch (error) {
|
|
3174
3291
|
await this.handleLayerFailure(layer, "sliding-ttl", error);
|
|
3175
3292
|
}
|
|
3176
3293
|
}
|
|
3177
3294
|
}
|
|
3178
|
-
if (fetcher &&
|
|
3295
|
+
if (fetcher && plan.shouldScheduleBackgroundRefresh) {
|
|
3179
3296
|
this.scheduleBackgroundRefresh(key, fetcher, options);
|
|
3180
3297
|
}
|
|
3181
3298
|
}
|
|
3182
3299
|
shouldSkipLayer(layer) {
|
|
3183
|
-
|
|
3184
|
-
return degradedUntil !== void 0 && degradedUntil > Date.now();
|
|
3300
|
+
return shouldSkipLayer(this.layerDegradedUntil.get(layer.name));
|
|
3185
3301
|
}
|
|
3186
3302
|
async handleLayerFailure(layer, operation, error) {
|
|
3187
|
-
|
|
3303
|
+
const recovery = resolveRecoverableLayerFailure(this.options.gracefulDegradation);
|
|
3304
|
+
if (!recovery.degrade) {
|
|
3188
3305
|
throw error;
|
|
3189
3306
|
}
|
|
3190
|
-
|
|
3191
|
-
this.layerDegradedUntil.set(layer.name, Date.now() + retryAfterMs);
|
|
3307
|
+
this.layerDegradedUntil.set(layer.name, recovery.degradedUntil);
|
|
3192
3308
|
this.metricsCollector.increment("degradedOperations");
|
|
3193
3309
|
this.logger.warn?.("layer-degraded", { layer: layer.name, operation, error: this.formatError(error) });
|
|
3194
3310
|
this.emitError(operation, { layer: layer.name, degraded: true, error: this.formatError(error) });
|
|
@@ -4805,7 +4921,7 @@ var MsgpackSerializer = class {
|
|
|
4805
4921
|
return Buffer.from((0, import_msgpack.encode)(value));
|
|
4806
4922
|
}
|
|
4807
4923
|
deserialize(payload) {
|
|
4808
|
-
const normalized = Buffer.isBuffer(payload) ? payload : Buffer.from(payload);
|
|
4924
|
+
const normalized = Buffer.isBuffer(payload) ? payload : Buffer.from(payload, "latin1");
|
|
4809
4925
|
return sanitizeMsgpackValue((0, import_msgpack.decode)(normalized), 0, { count: 0 });
|
|
4810
4926
|
}
|
|
4811
4927
|
};
|