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.js
CHANGED
|
@@ -22,6 +22,127 @@ import { EventEmitter } from "events";
|
|
|
22
22
|
|
|
23
23
|
// src/CacheNamespace.ts
|
|
24
24
|
import { Mutex } from "async-mutex";
|
|
25
|
+
|
|
26
|
+
// src/internal/CacheNamespaceMetrics.ts
|
|
27
|
+
function createEmptyNamespaceMetrics(resetAt = Date.now()) {
|
|
28
|
+
return {
|
|
29
|
+
hits: 0,
|
|
30
|
+
misses: 0,
|
|
31
|
+
fetches: 0,
|
|
32
|
+
sets: 0,
|
|
33
|
+
deletes: 0,
|
|
34
|
+
backfills: 0,
|
|
35
|
+
invalidations: 0,
|
|
36
|
+
staleHits: 0,
|
|
37
|
+
refreshes: 0,
|
|
38
|
+
refreshErrors: 0,
|
|
39
|
+
writeFailures: 0,
|
|
40
|
+
singleFlightWaits: 0,
|
|
41
|
+
negativeCacheHits: 0,
|
|
42
|
+
circuitBreakerTrips: 0,
|
|
43
|
+
degradedOperations: 0,
|
|
44
|
+
hitsByLayer: {},
|
|
45
|
+
missesByLayer: {},
|
|
46
|
+
latencyByLayer: {},
|
|
47
|
+
resetAt
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function cloneNamespaceMetrics(metrics) {
|
|
51
|
+
return {
|
|
52
|
+
...metrics,
|
|
53
|
+
hitsByLayer: { ...metrics.hitsByLayer },
|
|
54
|
+
missesByLayer: { ...metrics.missesByLayer },
|
|
55
|
+
latencyByLayer: Object.fromEntries(
|
|
56
|
+
Object.entries(metrics.latencyByLayer).map(([key, value]) => [key, { ...value }])
|
|
57
|
+
)
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function diffNamespaceMetrics(before, after) {
|
|
61
|
+
const latencyByLayer = Object.fromEntries(
|
|
62
|
+
Object.entries(after.latencyByLayer).map(([layer, value]) => [
|
|
63
|
+
layer,
|
|
64
|
+
{
|
|
65
|
+
avgMs: value.avgMs,
|
|
66
|
+
maxMs: value.maxMs,
|
|
67
|
+
count: Math.max(0, value.count - (before.latencyByLayer[layer]?.count ?? 0))
|
|
68
|
+
}
|
|
69
|
+
])
|
|
70
|
+
);
|
|
71
|
+
return {
|
|
72
|
+
hits: after.hits - before.hits,
|
|
73
|
+
misses: after.misses - before.misses,
|
|
74
|
+
fetches: after.fetches - before.fetches,
|
|
75
|
+
sets: after.sets - before.sets,
|
|
76
|
+
deletes: after.deletes - before.deletes,
|
|
77
|
+
backfills: after.backfills - before.backfills,
|
|
78
|
+
invalidations: after.invalidations - before.invalidations,
|
|
79
|
+
staleHits: after.staleHits - before.staleHits,
|
|
80
|
+
refreshes: after.refreshes - before.refreshes,
|
|
81
|
+
refreshErrors: after.refreshErrors - before.refreshErrors,
|
|
82
|
+
writeFailures: after.writeFailures - before.writeFailures,
|
|
83
|
+
singleFlightWaits: after.singleFlightWaits - before.singleFlightWaits,
|
|
84
|
+
negativeCacheHits: after.negativeCacheHits - before.negativeCacheHits,
|
|
85
|
+
circuitBreakerTrips: after.circuitBreakerTrips - before.circuitBreakerTrips,
|
|
86
|
+
degradedOperations: after.degradedOperations - before.degradedOperations,
|
|
87
|
+
hitsByLayer: diffMetricMap(before.hitsByLayer, after.hitsByLayer),
|
|
88
|
+
missesByLayer: diffMetricMap(before.missesByLayer, after.missesByLayer),
|
|
89
|
+
latencyByLayer,
|
|
90
|
+
resetAt: after.resetAt
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
function addNamespaceMetrics(base, delta) {
|
|
94
|
+
return {
|
|
95
|
+
hits: base.hits + delta.hits,
|
|
96
|
+
misses: base.misses + delta.misses,
|
|
97
|
+
fetches: base.fetches + delta.fetches,
|
|
98
|
+
sets: base.sets + delta.sets,
|
|
99
|
+
deletes: base.deletes + delta.deletes,
|
|
100
|
+
backfills: base.backfills + delta.backfills,
|
|
101
|
+
invalidations: base.invalidations + delta.invalidations,
|
|
102
|
+
staleHits: base.staleHits + delta.staleHits,
|
|
103
|
+
refreshes: base.refreshes + delta.refreshes,
|
|
104
|
+
refreshErrors: base.refreshErrors + delta.refreshErrors,
|
|
105
|
+
writeFailures: base.writeFailures + delta.writeFailures,
|
|
106
|
+
singleFlightWaits: base.singleFlightWaits + delta.singleFlightWaits,
|
|
107
|
+
negativeCacheHits: base.negativeCacheHits + delta.negativeCacheHits,
|
|
108
|
+
circuitBreakerTrips: base.circuitBreakerTrips + delta.circuitBreakerTrips,
|
|
109
|
+
degradedOperations: base.degradedOperations + delta.degradedOperations,
|
|
110
|
+
hitsByLayer: addMetricMap(base.hitsByLayer, delta.hitsByLayer),
|
|
111
|
+
missesByLayer: addMetricMap(base.missesByLayer, delta.missesByLayer),
|
|
112
|
+
latencyByLayer: cloneNamespaceMetrics(delta).latencyByLayer,
|
|
113
|
+
resetAt: base.resetAt
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function computeNamespaceHitRate(metrics) {
|
|
117
|
+
const total = metrics.hits + metrics.misses;
|
|
118
|
+
const overall = total === 0 ? 0 : metrics.hits / total;
|
|
119
|
+
const byLayer = {};
|
|
120
|
+
const layers = /* @__PURE__ */ new Set([...Object.keys(metrics.hitsByLayer), ...Object.keys(metrics.missesByLayer)]);
|
|
121
|
+
for (const layer of layers) {
|
|
122
|
+
const hits = metrics.hitsByLayer[layer] ?? 0;
|
|
123
|
+
const misses = metrics.missesByLayer[layer] ?? 0;
|
|
124
|
+
byLayer[layer] = hits + misses === 0 ? 0 : hits / (hits + misses);
|
|
125
|
+
}
|
|
126
|
+
return { overall, byLayer };
|
|
127
|
+
}
|
|
128
|
+
function diffMetricMap(before, after) {
|
|
129
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)]);
|
|
130
|
+
const result = {};
|
|
131
|
+
for (const key of keys) {
|
|
132
|
+
result[key] = (after[key] ?? 0) - (before[key] ?? 0);
|
|
133
|
+
}
|
|
134
|
+
return result;
|
|
135
|
+
}
|
|
136
|
+
function addMetricMap(base, delta) {
|
|
137
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(base), ...Object.keys(delta)]);
|
|
138
|
+
const result = {};
|
|
139
|
+
for (const key of keys) {
|
|
140
|
+
result[key] = (base[key] ?? 0) + (delta[key] ?? 0);
|
|
141
|
+
}
|
|
142
|
+
return result;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// src/CacheNamespace.ts
|
|
25
146
|
var CacheNamespace = class _CacheNamespace {
|
|
26
147
|
constructor(cache, prefix) {
|
|
27
148
|
this.cache = cache;
|
|
@@ -31,7 +152,7 @@ var CacheNamespace = class _CacheNamespace {
|
|
|
31
152
|
cache;
|
|
32
153
|
prefix;
|
|
33
154
|
static metricsMutexes = /* @__PURE__ */ new WeakMap();
|
|
34
|
-
metrics =
|
|
155
|
+
metrics = createEmptyNamespaceMetrics();
|
|
35
156
|
async get(key, fetcher, options) {
|
|
36
157
|
return this.trackMetrics(() => this.cache.get(this.qualify(key), fetcher, this.qualifyGetOptions(options)));
|
|
37
158
|
}
|
|
@@ -128,19 +249,10 @@ var CacheNamespace = class _CacheNamespace {
|
|
|
128
249
|
);
|
|
129
250
|
}
|
|
130
251
|
getMetrics() {
|
|
131
|
-
return
|
|
252
|
+
return cloneNamespaceMetrics(this.metrics);
|
|
132
253
|
}
|
|
133
254
|
getHitRate() {
|
|
134
|
-
|
|
135
|
-
const overall = total === 0 ? 0 : this.metrics.hits / total;
|
|
136
|
-
const byLayer = {};
|
|
137
|
-
const layers = /* @__PURE__ */ new Set([...Object.keys(this.metrics.hitsByLayer), ...Object.keys(this.metrics.missesByLayer)]);
|
|
138
|
-
for (const layer of layers) {
|
|
139
|
-
const hits = this.metrics.hitsByLayer[layer] ?? 0;
|
|
140
|
-
const misses = this.metrics.missesByLayer[layer] ?? 0;
|
|
141
|
-
byLayer[layer] = hits + misses === 0 ? 0 : hits / (hits + misses);
|
|
142
|
-
}
|
|
143
|
-
return { overall, byLayer };
|
|
255
|
+
return computeNamespaceHitRate(this.metrics);
|
|
144
256
|
}
|
|
145
257
|
/**
|
|
146
258
|
* Creates a nested namespace. Keys are prefixed with `parentPrefix:childPrefix:`.
|
|
@@ -181,7 +293,7 @@ var CacheNamespace = class _CacheNamespace {
|
|
|
181
293
|
const before = this.cache.getMetrics();
|
|
182
294
|
const result = await operation();
|
|
183
295
|
const after = this.cache.getMetrics();
|
|
184
|
-
this.metrics =
|
|
296
|
+
this.metrics = addNamespaceMetrics(this.metrics, diffNamespaceMetrics(before, after));
|
|
185
297
|
return result;
|
|
186
298
|
});
|
|
187
299
|
}
|
|
@@ -195,111 +307,6 @@ var CacheNamespace = class _CacheNamespace {
|
|
|
195
307
|
return mutex;
|
|
196
308
|
}
|
|
197
309
|
};
|
|
198
|
-
function emptyMetrics() {
|
|
199
|
-
return {
|
|
200
|
-
hits: 0,
|
|
201
|
-
misses: 0,
|
|
202
|
-
fetches: 0,
|
|
203
|
-
sets: 0,
|
|
204
|
-
deletes: 0,
|
|
205
|
-
backfills: 0,
|
|
206
|
-
invalidations: 0,
|
|
207
|
-
staleHits: 0,
|
|
208
|
-
refreshes: 0,
|
|
209
|
-
refreshErrors: 0,
|
|
210
|
-
writeFailures: 0,
|
|
211
|
-
singleFlightWaits: 0,
|
|
212
|
-
negativeCacheHits: 0,
|
|
213
|
-
circuitBreakerTrips: 0,
|
|
214
|
-
degradedOperations: 0,
|
|
215
|
-
hitsByLayer: {},
|
|
216
|
-
missesByLayer: {},
|
|
217
|
-
latencyByLayer: {},
|
|
218
|
-
resetAt: Date.now()
|
|
219
|
-
};
|
|
220
|
-
}
|
|
221
|
-
function cloneMetrics(metrics) {
|
|
222
|
-
return {
|
|
223
|
-
...metrics,
|
|
224
|
-
hitsByLayer: { ...metrics.hitsByLayer },
|
|
225
|
-
missesByLayer: { ...metrics.missesByLayer },
|
|
226
|
-
latencyByLayer: Object.fromEntries(
|
|
227
|
-
Object.entries(metrics.latencyByLayer).map(([key, value]) => [key, { ...value }])
|
|
228
|
-
)
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
function diffMetrics(before, after) {
|
|
232
|
-
const latencyByLayer = Object.fromEntries(
|
|
233
|
-
Object.entries(after.latencyByLayer).map(([layer, value]) => [
|
|
234
|
-
layer,
|
|
235
|
-
{
|
|
236
|
-
avgMs: value.avgMs,
|
|
237
|
-
maxMs: value.maxMs,
|
|
238
|
-
count: Math.max(0, value.count - (before.latencyByLayer[layer]?.count ?? 0))
|
|
239
|
-
}
|
|
240
|
-
])
|
|
241
|
-
);
|
|
242
|
-
return {
|
|
243
|
-
hits: after.hits - before.hits,
|
|
244
|
-
misses: after.misses - before.misses,
|
|
245
|
-
fetches: after.fetches - before.fetches,
|
|
246
|
-
sets: after.sets - before.sets,
|
|
247
|
-
deletes: after.deletes - before.deletes,
|
|
248
|
-
backfills: after.backfills - before.backfills,
|
|
249
|
-
invalidations: after.invalidations - before.invalidations,
|
|
250
|
-
staleHits: after.staleHits - before.staleHits,
|
|
251
|
-
refreshes: after.refreshes - before.refreshes,
|
|
252
|
-
refreshErrors: after.refreshErrors - before.refreshErrors,
|
|
253
|
-
writeFailures: after.writeFailures - before.writeFailures,
|
|
254
|
-
singleFlightWaits: after.singleFlightWaits - before.singleFlightWaits,
|
|
255
|
-
negativeCacheHits: after.negativeCacheHits - before.negativeCacheHits,
|
|
256
|
-
circuitBreakerTrips: after.circuitBreakerTrips - before.circuitBreakerTrips,
|
|
257
|
-
degradedOperations: after.degradedOperations - before.degradedOperations,
|
|
258
|
-
hitsByLayer: diffMap(before.hitsByLayer, after.hitsByLayer),
|
|
259
|
-
missesByLayer: diffMap(before.missesByLayer, after.missesByLayer),
|
|
260
|
-
latencyByLayer,
|
|
261
|
-
resetAt: after.resetAt
|
|
262
|
-
};
|
|
263
|
-
}
|
|
264
|
-
function addMetrics(base, delta) {
|
|
265
|
-
return {
|
|
266
|
-
hits: base.hits + delta.hits,
|
|
267
|
-
misses: base.misses + delta.misses,
|
|
268
|
-
fetches: base.fetches + delta.fetches,
|
|
269
|
-
sets: base.sets + delta.sets,
|
|
270
|
-
deletes: base.deletes + delta.deletes,
|
|
271
|
-
backfills: base.backfills + delta.backfills,
|
|
272
|
-
invalidations: base.invalidations + delta.invalidations,
|
|
273
|
-
staleHits: base.staleHits + delta.staleHits,
|
|
274
|
-
refreshes: base.refreshes + delta.refreshes,
|
|
275
|
-
refreshErrors: base.refreshErrors + delta.refreshErrors,
|
|
276
|
-
writeFailures: base.writeFailures + delta.writeFailures,
|
|
277
|
-
singleFlightWaits: base.singleFlightWaits + delta.singleFlightWaits,
|
|
278
|
-
negativeCacheHits: base.negativeCacheHits + delta.negativeCacheHits,
|
|
279
|
-
circuitBreakerTrips: base.circuitBreakerTrips + delta.circuitBreakerTrips,
|
|
280
|
-
degradedOperations: base.degradedOperations + delta.degradedOperations,
|
|
281
|
-
hitsByLayer: addMap(base.hitsByLayer, delta.hitsByLayer),
|
|
282
|
-
missesByLayer: addMap(base.missesByLayer, delta.missesByLayer),
|
|
283
|
-
latencyByLayer: cloneMetrics(delta).latencyByLayer,
|
|
284
|
-
resetAt: base.resetAt
|
|
285
|
-
};
|
|
286
|
-
}
|
|
287
|
-
function diffMap(before, after) {
|
|
288
|
-
const keys = /* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)]);
|
|
289
|
-
const result = {};
|
|
290
|
-
for (const key of keys) {
|
|
291
|
-
result[key] = (after[key] ?? 0) - (before[key] ?? 0);
|
|
292
|
-
}
|
|
293
|
-
return result;
|
|
294
|
-
}
|
|
295
|
-
function addMap(base, delta) {
|
|
296
|
-
const keys = /* @__PURE__ */ new Set([...Object.keys(base), ...Object.keys(delta)]);
|
|
297
|
-
const result = {};
|
|
298
|
-
for (const key of keys) {
|
|
299
|
-
result[key] = (base[key] ?? 0) + (delta[key] ?? 0);
|
|
300
|
-
}
|
|
301
|
-
return result;
|
|
302
|
-
}
|
|
303
310
|
function validateNamespaceKey(key) {
|
|
304
311
|
if (key.length === 0) {
|
|
305
312
|
throw new Error("Namespace prefix must not be empty.");
|
|
@@ -557,6 +564,187 @@ async function readUtf8HandleWithLimit(handle, byteLimit) {
|
|
|
557
564
|
return Buffer.concat(chunks).toString("utf8");
|
|
558
565
|
}
|
|
559
566
|
|
|
567
|
+
// src/internal/CacheStackGeneration.ts
|
|
568
|
+
var DEFAULT_GENERATION_CLEANUP_BATCH_SIZE = 500;
|
|
569
|
+
function generationPrefix(generation) {
|
|
570
|
+
return generation === void 0 ? "" : `v${generation}:`;
|
|
571
|
+
}
|
|
572
|
+
function qualifyGenerationKey(key, generation) {
|
|
573
|
+
const prefix = generationPrefix(generation);
|
|
574
|
+
return prefix ? `${prefix}${key}` : key;
|
|
575
|
+
}
|
|
576
|
+
function qualifyGenerationPattern(pattern, generation) {
|
|
577
|
+
return qualifyGenerationKey(pattern, generation);
|
|
578
|
+
}
|
|
579
|
+
function stripGenerationPrefix(key, generation) {
|
|
580
|
+
const prefix = generationPrefix(generation);
|
|
581
|
+
if (!prefix || !key.startsWith(prefix)) {
|
|
582
|
+
return key;
|
|
583
|
+
}
|
|
584
|
+
return key.slice(prefix.length);
|
|
585
|
+
}
|
|
586
|
+
function resolveGenerationCleanupTarget({
|
|
587
|
+
previousGeneration,
|
|
588
|
+
nextGeneration,
|
|
589
|
+
generationCleanup
|
|
590
|
+
}) {
|
|
591
|
+
if (!generationCleanup || previousGeneration === void 0 || previousGeneration === nextGeneration) {
|
|
592
|
+
return null;
|
|
593
|
+
}
|
|
594
|
+
return previousGeneration;
|
|
595
|
+
}
|
|
596
|
+
function resolveGenerationCleanupBatchSize(generationCleanup) {
|
|
597
|
+
if (typeof generationCleanup !== "object" || generationCleanup === null) {
|
|
598
|
+
return DEFAULT_GENERATION_CLEANUP_BATCH_SIZE;
|
|
599
|
+
}
|
|
600
|
+
return generationCleanup.batchSize ?? DEFAULT_GENERATION_CLEANUP_BATCH_SIZE;
|
|
601
|
+
}
|
|
602
|
+
function planGenerationCleanupBatches(keys, generationCleanup) {
|
|
603
|
+
if (keys.length === 0) {
|
|
604
|
+
return [];
|
|
605
|
+
}
|
|
606
|
+
const batchSize = resolveGenerationCleanupBatchSize(generationCleanup);
|
|
607
|
+
const batches = [];
|
|
608
|
+
for (let index = 0; index < keys.length; index += batchSize) {
|
|
609
|
+
batches.push(keys.slice(index, index + batchSize));
|
|
610
|
+
}
|
|
611
|
+
return batches;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// src/internal/CacheStackMaintenance.ts
|
|
615
|
+
var CacheStackMaintenance = class {
|
|
616
|
+
keyEpochs = /* @__PURE__ */ new Map();
|
|
617
|
+
writeBehindQueue = [];
|
|
618
|
+
writeBehindTimer;
|
|
619
|
+
writeBehindFlushPromise;
|
|
620
|
+
generationCleanupPromise;
|
|
621
|
+
clearEpoch = 0;
|
|
622
|
+
initializeWriteBehindTimer(writeStrategy, options, flush) {
|
|
623
|
+
if (writeStrategy !== "write-behind") {
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
const flushIntervalMs = options?.flushIntervalMs;
|
|
627
|
+
if (!flushIntervalMs || flushIntervalMs <= 0) {
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
this.disposeWriteBehindTimer();
|
|
631
|
+
this.writeBehindTimer = setInterval(() => {
|
|
632
|
+
void flush();
|
|
633
|
+
}, flushIntervalMs);
|
|
634
|
+
this.writeBehindTimer.unref?.();
|
|
635
|
+
}
|
|
636
|
+
disposeWriteBehindTimer() {
|
|
637
|
+
if (!this.writeBehindTimer) {
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
640
|
+
clearInterval(this.writeBehindTimer);
|
|
641
|
+
this.writeBehindTimer = void 0;
|
|
642
|
+
}
|
|
643
|
+
beginClearEpoch() {
|
|
644
|
+
this.clearEpoch += 1;
|
|
645
|
+
this.keyEpochs.clear();
|
|
646
|
+
this.writeBehindQueue.length = 0;
|
|
647
|
+
}
|
|
648
|
+
currentClearEpoch() {
|
|
649
|
+
return this.clearEpoch;
|
|
650
|
+
}
|
|
651
|
+
currentKeyEpoch(key) {
|
|
652
|
+
return this.keyEpochs.get(key) ?? 0;
|
|
653
|
+
}
|
|
654
|
+
bumpKeyEpochs(keys) {
|
|
655
|
+
for (const key of keys) {
|
|
656
|
+
this.keyEpochs.set(key, this.currentKeyEpoch(key) + 1);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
isWriteOutdated(key, expectedClearEpoch, expectedKeyEpoch) {
|
|
660
|
+
if (expectedClearEpoch !== void 0 && expectedClearEpoch !== this.clearEpoch) {
|
|
661
|
+
return true;
|
|
662
|
+
}
|
|
663
|
+
if (expectedKeyEpoch !== void 0 && expectedKeyEpoch !== this.currentKeyEpoch(key)) {
|
|
664
|
+
return true;
|
|
665
|
+
}
|
|
666
|
+
return false;
|
|
667
|
+
}
|
|
668
|
+
async enqueueWriteBehind(operation, options, flushBatch) {
|
|
669
|
+
this.writeBehindQueue.push(operation);
|
|
670
|
+
const batchSize = options?.batchSize ?? 100;
|
|
671
|
+
const maxQueueSize = options?.maxQueueSize ?? batchSize * 10;
|
|
672
|
+
if (this.writeBehindQueue.length >= batchSize) {
|
|
673
|
+
await this.flushWriteBehindQueue(options, flushBatch);
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
if (this.writeBehindQueue.length >= maxQueueSize) {
|
|
677
|
+
await this.flushWriteBehindQueue(options, flushBatch);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
async flushWriteBehindQueue(options, flushBatch) {
|
|
681
|
+
if (this.writeBehindFlushPromise || this.writeBehindQueue.length === 0) {
|
|
682
|
+
await this.writeBehindFlushPromise;
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
const batchSize = options?.batchSize ?? 100;
|
|
686
|
+
const batch = this.writeBehindQueue.splice(0, batchSize);
|
|
687
|
+
this.writeBehindFlushPromise = flushBatch(batch);
|
|
688
|
+
try {
|
|
689
|
+
await this.writeBehindFlushPromise;
|
|
690
|
+
} finally {
|
|
691
|
+
this.writeBehindFlushPromise = void 0;
|
|
692
|
+
}
|
|
693
|
+
if (this.writeBehindQueue.length > 0) {
|
|
694
|
+
await this.flushWriteBehindQueue(options, flushBatch);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
scheduleGenerationCleanup(generation, task, onError) {
|
|
698
|
+
const scheduledTask = (this.generationCleanupPromise ?? Promise.resolve()).then(() => task(generation)).catch((error) => {
|
|
699
|
+
onError(generation, error);
|
|
700
|
+
});
|
|
701
|
+
this.generationCleanupPromise = scheduledTask.finally(() => {
|
|
702
|
+
if (this.generationCleanupPromise === scheduledTask) {
|
|
703
|
+
this.generationCleanupPromise = void 0;
|
|
704
|
+
}
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
async waitForGenerationCleanup() {
|
|
708
|
+
await this.generationCleanupPromise;
|
|
709
|
+
}
|
|
710
|
+
};
|
|
711
|
+
|
|
712
|
+
// src/internal/CacheStackRuntimePolicy.ts
|
|
713
|
+
function shouldSkipLayer(degradedUntil, now = Date.now()) {
|
|
714
|
+
return degradedUntil !== void 0 && degradedUntil > now;
|
|
715
|
+
}
|
|
716
|
+
function shouldStartBackgroundRefresh({
|
|
717
|
+
isDisconnecting,
|
|
718
|
+
hasRefreshInFlight
|
|
719
|
+
}) {
|
|
720
|
+
return !isDisconnecting && !hasRefreshInFlight;
|
|
721
|
+
}
|
|
722
|
+
function resolveRecoverableLayerFailure(gracefulDegradation, now = Date.now()) {
|
|
723
|
+
if (!gracefulDegradation) {
|
|
724
|
+
return { degrade: false };
|
|
725
|
+
}
|
|
726
|
+
const retryAfterMs = typeof gracefulDegradation === "object" ? gracefulDegradation.retryAfterMs ?? 1e4 : 1e4;
|
|
727
|
+
return {
|
|
728
|
+
degrade: true,
|
|
729
|
+
degradedUntil: now + retryAfterMs
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
function planFreshReadPolicies({
|
|
733
|
+
stored,
|
|
734
|
+
hasFetcher,
|
|
735
|
+
slidingTtl,
|
|
736
|
+
refreshAheadSeconds
|
|
737
|
+
}) {
|
|
738
|
+
const refreshedStored = slidingTtl && isStoredValueEnvelope(stored) ? refreshStoredEnvelope(stored) : void 0;
|
|
739
|
+
const refreshedStoredTtl = refreshedStored ? remainingStoredTtlSeconds(refreshedStored) ?? void 0 : void 0;
|
|
740
|
+
const remainingFreshTtl = remainingFreshTtlSeconds(stored) ?? 0;
|
|
741
|
+
return {
|
|
742
|
+
refreshedStored,
|
|
743
|
+
refreshedStoredTtl,
|
|
744
|
+
shouldScheduleBackgroundRefresh: hasFetcher && refreshAheadSeconds > 0 && remainingFreshTtl > 0 && remainingFreshTtl <= refreshAheadSeconds
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
|
|
560
748
|
// src/internal/CacheStackValidation.ts
|
|
561
749
|
var MAX_CACHE_KEY_LENGTH = 1024;
|
|
562
750
|
var MAX_PATTERN_LENGTH = 1024;
|
|
@@ -710,7 +898,6 @@ var CircuitBreakerManager = class {
|
|
|
710
898
|
if (!options) {
|
|
711
899
|
return;
|
|
712
900
|
}
|
|
713
|
-
this.pruneIfNeeded();
|
|
714
901
|
const failureThreshold = options.failureThreshold ?? 3;
|
|
715
902
|
const cooldownMs = options.cooldownMs ?? 3e4;
|
|
716
903
|
const state = this.breakers.get(key) ?? { failures: 0, openUntil: null, createdAt: Date.now() };
|
|
@@ -719,6 +906,7 @@ var CircuitBreakerManager = class {
|
|
|
719
906
|
state.openUntil = Date.now() + cooldownMs;
|
|
720
907
|
}
|
|
721
908
|
this.breakers.set(key, state);
|
|
909
|
+
this.pruneIfNeeded();
|
|
722
910
|
}
|
|
723
911
|
recordSuccess(key) {
|
|
724
912
|
this.breakers.delete(key);
|
|
@@ -1346,15 +1534,10 @@ var CacheStack = class extends EventEmitter {
|
|
|
1346
1534
|
snapshotSerializer = new JsonSerializer();
|
|
1347
1535
|
backgroundRefreshes = /* @__PURE__ */ new Map();
|
|
1348
1536
|
layerDegradedUntil = /* @__PURE__ */ new Map();
|
|
1349
|
-
|
|
1537
|
+
maintenance = new CacheStackMaintenance();
|
|
1350
1538
|
ttlResolver;
|
|
1351
1539
|
circuitBreakerManager;
|
|
1352
1540
|
currentGeneration;
|
|
1353
|
-
writeBehindQueue = [];
|
|
1354
|
-
writeBehindTimer;
|
|
1355
|
-
writeBehindFlushPromise;
|
|
1356
|
-
generationCleanupPromise;
|
|
1357
|
-
clearEpoch = 0;
|
|
1358
1541
|
isDisconnecting = false;
|
|
1359
1542
|
disconnectPromise;
|
|
1360
1543
|
/**
|
|
@@ -1510,7 +1693,7 @@ var CacheStack = class extends EventEmitter {
|
|
|
1510
1693
|
}
|
|
1511
1694
|
async clear() {
|
|
1512
1695
|
await this.awaitStartup("clear");
|
|
1513
|
-
this.beginClearEpoch();
|
|
1696
|
+
this.maintenance.beginClearEpoch();
|
|
1514
1697
|
await Promise.all(this.layers.map((layer) => layer.clear()));
|
|
1515
1698
|
await this.tagIndex.clear();
|
|
1516
1699
|
this.ttlResolver.clearProfiles();
|
|
@@ -1768,9 +1951,15 @@ var CacheStack = class extends EventEmitter {
|
|
|
1768
1951
|
bumpGeneration(nextGeneration) {
|
|
1769
1952
|
const current = this.currentGeneration ?? 0;
|
|
1770
1953
|
const previousGeneration = this.currentGeneration;
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1954
|
+
const updatedGeneration = nextGeneration ?? current + 1;
|
|
1955
|
+
const generationToCleanup = resolveGenerationCleanupTarget({
|
|
1956
|
+
previousGeneration,
|
|
1957
|
+
nextGeneration: updatedGeneration,
|
|
1958
|
+
generationCleanup: this.options.generationCleanup
|
|
1959
|
+
});
|
|
1960
|
+
this.currentGeneration = updatedGeneration;
|
|
1961
|
+
if (generationToCleanup !== null) {
|
|
1962
|
+
this.scheduleGenerationCleanup(generationToCleanup);
|
|
1774
1963
|
}
|
|
1775
1964
|
return this.currentGeneration;
|
|
1776
1965
|
}
|
|
@@ -1914,12 +2103,9 @@ var CacheStack = class extends EventEmitter {
|
|
|
1914
2103
|
await this.startup;
|
|
1915
2104
|
await this.unsubscribeInvalidation?.();
|
|
1916
2105
|
await this.flushWriteBehindQueue();
|
|
1917
|
-
await this.
|
|
2106
|
+
await this.maintenance.waitForGenerationCleanup();
|
|
1918
2107
|
await Promise.allSettled([...this.backgroundRefreshes.values()]);
|
|
1919
|
-
|
|
1920
|
-
clearInterval(this.writeBehindTimer);
|
|
1921
|
-
this.writeBehindTimer = void 0;
|
|
1922
|
-
}
|
|
2108
|
+
this.maintenance.disposeWriteBehindTimer();
|
|
1923
2109
|
await Promise.allSettled(this.layers.map((layer) => layer.dispose?.() ?? Promise.resolve()));
|
|
1924
2110
|
})();
|
|
1925
2111
|
}
|
|
@@ -1995,13 +2181,13 @@ var CacheStack = class extends EventEmitter {
|
|
|
1995
2181
|
if (!this.shouldNegativeCache(options)) {
|
|
1996
2182
|
return null;
|
|
1997
2183
|
}
|
|
1998
|
-
if (this.isWriteOutdated(key, expectedClearEpoch, expectedKeyEpoch)) {
|
|
2184
|
+
if (this.maintenance.isWriteOutdated(key, expectedClearEpoch, expectedKeyEpoch)) {
|
|
1999
2185
|
this.logger.debug?.("skip-negative-store-after-invalidation", {
|
|
2000
2186
|
key,
|
|
2001
2187
|
expectedClearEpoch,
|
|
2002
|
-
clearEpoch: this.
|
|
2188
|
+
clearEpoch: this.maintenance.currentClearEpoch(),
|
|
2003
2189
|
expectedKeyEpoch,
|
|
2004
|
-
keyEpoch: this.currentKeyEpoch(key)
|
|
2190
|
+
keyEpoch: this.maintenance.currentKeyEpoch(key)
|
|
2005
2191
|
});
|
|
2006
2192
|
return null;
|
|
2007
2193
|
}
|
|
@@ -2017,13 +2203,13 @@ var CacheStack = class extends EventEmitter {
|
|
|
2017
2203
|
this.logger.warn?.("shouldCache-error", { key, error: this.formatError(error) });
|
|
2018
2204
|
}
|
|
2019
2205
|
}
|
|
2020
|
-
if (this.isWriteOutdated(key, expectedClearEpoch, expectedKeyEpoch)) {
|
|
2206
|
+
if (this.maintenance.isWriteOutdated(key, expectedClearEpoch, expectedKeyEpoch)) {
|
|
2021
2207
|
this.logger.debug?.("skip-store-after-invalidation", {
|
|
2022
2208
|
key,
|
|
2023
2209
|
expectedClearEpoch,
|
|
2024
|
-
clearEpoch: this.
|
|
2210
|
+
clearEpoch: this.maintenance.currentClearEpoch(),
|
|
2025
2211
|
expectedKeyEpoch,
|
|
2026
|
-
keyEpoch: this.currentKeyEpoch(key)
|
|
2212
|
+
keyEpoch: this.maintenance.currentKeyEpoch(key)
|
|
2027
2213
|
});
|
|
2028
2214
|
return fetched;
|
|
2029
2215
|
}
|
|
@@ -2031,10 +2217,10 @@ var CacheStack = class extends EventEmitter {
|
|
|
2031
2217
|
return fetched;
|
|
2032
2218
|
}
|
|
2033
2219
|
async storeEntry(key, kind, value, options) {
|
|
2034
|
-
const clearEpoch = this.
|
|
2035
|
-
const keyEpoch = this.currentKeyEpoch(key);
|
|
2220
|
+
const clearEpoch = this.maintenance.currentClearEpoch();
|
|
2221
|
+
const keyEpoch = this.maintenance.currentKeyEpoch(key);
|
|
2036
2222
|
await this.writeAcrossLayers(key, kind, value, options);
|
|
2037
|
-
if (this.isWriteOutdated(key, clearEpoch, keyEpoch)) {
|
|
2223
|
+
if (this.maintenance.isWriteOutdated(key, clearEpoch, keyEpoch)) {
|
|
2038
2224
|
return;
|
|
2039
2225
|
}
|
|
2040
2226
|
if (options?.tags) {
|
|
@@ -2051,8 +2237,8 @@ var CacheStack = class extends EventEmitter {
|
|
|
2051
2237
|
}
|
|
2052
2238
|
async writeBatch(entries) {
|
|
2053
2239
|
const now = Date.now();
|
|
2054
|
-
const clearEpoch = this.
|
|
2055
|
-
const entryEpochs = new Map(entries.map((entry) => [entry.key, this.currentKeyEpoch(entry.key)]));
|
|
2240
|
+
const clearEpoch = this.maintenance.currentClearEpoch();
|
|
2241
|
+
const entryEpochs = new Map(entries.map((entry) => [entry.key, this.maintenance.currentKeyEpoch(entry.key)]));
|
|
2056
2242
|
const entriesByLayer = /* @__PURE__ */ new Map();
|
|
2057
2243
|
const immediateOperations = [];
|
|
2058
2244
|
const deferredOperations = [];
|
|
@@ -2069,11 +2255,11 @@ var CacheStack = class extends EventEmitter {
|
|
|
2069
2255
|
}
|
|
2070
2256
|
for (const [layer, layerEntries] of entriesByLayer.entries()) {
|
|
2071
2257
|
const operation = async () => {
|
|
2072
|
-
if (clearEpoch !== this.
|
|
2258
|
+
if (clearEpoch !== this.maintenance.currentClearEpoch()) {
|
|
2073
2259
|
return;
|
|
2074
2260
|
}
|
|
2075
2261
|
const activeEntries = layerEntries.filter(
|
|
2076
|
-
(entry) => (entryEpochs.get(entry.key) ?? 0) === this.currentKeyEpoch(entry.key)
|
|
2262
|
+
(entry) => (entryEpochs.get(entry.key) ?? 0) === this.maintenance.currentKeyEpoch(entry.key)
|
|
2077
2263
|
);
|
|
2078
2264
|
if (activeEntries.length === 0) {
|
|
2079
2265
|
return;
|
|
@@ -2096,11 +2282,11 @@ var CacheStack = class extends EventEmitter {
|
|
|
2096
2282
|
}
|
|
2097
2283
|
await this.executeLayerOperations(immediateOperations, { key: "batch", action: "mset" });
|
|
2098
2284
|
await Promise.all(deferredOperations.map((operation) => this.enqueueWriteBehind(operation)));
|
|
2099
|
-
if (clearEpoch !== this.
|
|
2285
|
+
if (clearEpoch !== this.maintenance.currentClearEpoch()) {
|
|
2100
2286
|
return;
|
|
2101
2287
|
}
|
|
2102
2288
|
for (const entry of entries) {
|
|
2103
|
-
if (this.isWriteOutdated(entry.key, clearEpoch, entryEpochs.get(entry.key))) {
|
|
2289
|
+
if (this.maintenance.isWriteOutdated(entry.key, clearEpoch, entryEpochs.get(entry.key))) {
|
|
2104
2290
|
continue;
|
|
2105
2291
|
}
|
|
2106
2292
|
if (entry.options?.tags) {
|
|
@@ -2204,13 +2390,13 @@ var CacheStack = class extends EventEmitter {
|
|
|
2204
2390
|
}
|
|
2205
2391
|
async writeAcrossLayers(key, kind, value, options) {
|
|
2206
2392
|
const now = Date.now();
|
|
2207
|
-
const clearEpoch = this.
|
|
2208
|
-
const keyEpoch = this.currentKeyEpoch(key);
|
|
2393
|
+
const clearEpoch = this.maintenance.currentClearEpoch();
|
|
2394
|
+
const keyEpoch = this.maintenance.currentKeyEpoch(key);
|
|
2209
2395
|
const immediateOperations = [];
|
|
2210
2396
|
const deferredOperations = [];
|
|
2211
2397
|
for (const layer of this.layers) {
|
|
2212
2398
|
const operation = async () => {
|
|
2213
|
-
if (this.isWriteOutdated(key, clearEpoch, keyEpoch)) {
|
|
2399
|
+
if (this.maintenance.isWriteOutdated(key, clearEpoch, keyEpoch)) {
|
|
2214
2400
|
return;
|
|
2215
2401
|
}
|
|
2216
2402
|
if (this.shouldSkipLayer(layer)) {
|
|
@@ -2273,11 +2459,14 @@ var CacheStack = class extends EventEmitter {
|
|
|
2273
2459
|
return options?.negativeCache ?? this.options.negativeCaching ?? false;
|
|
2274
2460
|
}
|
|
2275
2461
|
scheduleBackgroundRefresh(key, fetcher, options) {
|
|
2276
|
-
if (
|
|
2462
|
+
if (!shouldStartBackgroundRefresh({
|
|
2463
|
+
isDisconnecting: this.isDisconnecting,
|
|
2464
|
+
hasRefreshInFlight: this.backgroundRefreshes.has(key)
|
|
2465
|
+
})) {
|
|
2277
2466
|
return;
|
|
2278
2467
|
}
|
|
2279
|
-
const clearEpoch = this.
|
|
2280
|
-
const keyEpoch = this.currentKeyEpoch(key);
|
|
2468
|
+
const clearEpoch = this.maintenance.currentClearEpoch();
|
|
2469
|
+
const keyEpoch = this.maintenance.currentKeyEpoch(key);
|
|
2281
2470
|
const refresh = (async () => {
|
|
2282
2471
|
this.metricsCollector.increment("refreshes");
|
|
2283
2472
|
try {
|
|
@@ -2315,7 +2504,7 @@ var CacheStack = class extends EventEmitter {
|
|
|
2315
2504
|
if (keys.length === 0) {
|
|
2316
2505
|
return;
|
|
2317
2506
|
}
|
|
2318
|
-
this.bumpKeyEpochs(keys);
|
|
2507
|
+
this.maintenance.bumpKeyEpochs(keys);
|
|
2319
2508
|
await this.deleteKeysFromLayers(this.layers, keys);
|
|
2320
2509
|
for (const key of keys) {
|
|
2321
2510
|
await this.tagIndex.remove(key);
|
|
@@ -2339,7 +2528,7 @@ var CacheStack = class extends EventEmitter {
|
|
|
2339
2528
|
}
|
|
2340
2529
|
const localLayers = this.layers.filter((layer) => layer.isLocal);
|
|
2341
2530
|
if (message.scope === "clear") {
|
|
2342
|
-
this.beginClearEpoch();
|
|
2531
|
+
this.maintenance.beginClearEpoch();
|
|
2343
2532
|
await Promise.all(localLayers.map((layer) => layer.clear()));
|
|
2344
2533
|
await this.tagIndex.clear();
|
|
2345
2534
|
this.ttlResolver.clearProfiles();
|
|
@@ -2347,7 +2536,7 @@ var CacheStack = class extends EventEmitter {
|
|
|
2347
2536
|
return;
|
|
2348
2537
|
}
|
|
2349
2538
|
const keys = message.keys ?? [];
|
|
2350
|
-
this.bumpKeyEpochs(keys);
|
|
2539
|
+
this.maintenance.bumpKeyEpochs(keys);
|
|
2351
2540
|
await this.deleteKeysFromLayers(localLayers, keys);
|
|
2352
2541
|
if (message.operation !== "write") {
|
|
2353
2542
|
for (const key of keys) {
|
|
@@ -2405,35 +2594,22 @@ var CacheStack = class extends EventEmitter {
|
|
|
2405
2594
|
shouldBroadcastL1Invalidation() {
|
|
2406
2595
|
return this.options.broadcastL1Invalidation ?? this.options.publishSetInvalidation ?? false;
|
|
2407
2596
|
}
|
|
2408
|
-
shouldCleanupGenerations() {
|
|
2409
|
-
return Boolean(this.options.generationCleanup);
|
|
2410
|
-
}
|
|
2411
|
-
generationCleanupBatchSize() {
|
|
2412
|
-
const configured = typeof this.options.generationCleanup === "object" ? this.options.generationCleanup.batchSize : void 0;
|
|
2413
|
-
return configured ?? 500;
|
|
2414
|
-
}
|
|
2415
2597
|
scheduleGenerationCleanup(generation) {
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
this.generationCleanupPromise = void 0;
|
|
2598
|
+
this.maintenance.scheduleGenerationCleanup(
|
|
2599
|
+
generation,
|
|
2600
|
+
async (generationToClean) => this.cleanupGeneration(generationToClean),
|
|
2601
|
+
(failedGeneration, error) => {
|
|
2602
|
+
this.logger.warn?.("generation-cleanup-error", {
|
|
2603
|
+
generation: failedGeneration,
|
|
2604
|
+
error: this.formatError(error)
|
|
2605
|
+
});
|
|
2425
2606
|
}
|
|
2426
|
-
|
|
2607
|
+
);
|
|
2427
2608
|
}
|
|
2428
2609
|
async cleanupGeneration(generation) {
|
|
2429
2610
|
const prefix = `v${generation}:`;
|
|
2430
2611
|
const keys = await this.keyDiscovery.collectKeysWithPrefix(prefix);
|
|
2431
|
-
|
|
2432
|
-
return;
|
|
2433
|
-
}
|
|
2434
|
-
const batchSize = this.generationCleanupBatchSize();
|
|
2435
|
-
for (let index = 0; index < keys.length; index += batchSize) {
|
|
2436
|
-
const batch = keys.slice(index, index + batchSize);
|
|
2612
|
+
for (const batch of planGenerationCleanupBatches(keys, this.options.generationCleanup)) {
|
|
2437
2613
|
await this.deleteKeys(batch);
|
|
2438
2614
|
await this.publishInvalidation({
|
|
2439
2615
|
scope: "keys",
|
|
@@ -2444,80 +2620,34 @@ var CacheStack = class extends EventEmitter {
|
|
|
2444
2620
|
}
|
|
2445
2621
|
}
|
|
2446
2622
|
initializeWriteBehind(options) {
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
return;
|
|
2453
|
-
}
|
|
2454
|
-
this.writeBehindTimer = setInterval(() => {
|
|
2455
|
-
void this.flushWriteBehindQueue();
|
|
2456
|
-
}, flushIntervalMs);
|
|
2457
|
-
this.writeBehindTimer.unref?.();
|
|
2623
|
+
this.maintenance.initializeWriteBehindTimer(
|
|
2624
|
+
this.options.writeStrategy,
|
|
2625
|
+
options,
|
|
2626
|
+
this.flushWriteBehindQueue.bind(this)
|
|
2627
|
+
);
|
|
2458
2628
|
}
|
|
2459
2629
|
shouldWriteBehind(layer) {
|
|
2460
2630
|
return this.options.writeStrategy === "write-behind" && !layer.isLocal;
|
|
2461
2631
|
}
|
|
2462
|
-
beginClearEpoch() {
|
|
2463
|
-
this.clearEpoch += 1;
|
|
2464
|
-
this.keyEpochs.clear();
|
|
2465
|
-
this.writeBehindQueue.length = 0;
|
|
2466
|
-
}
|
|
2467
|
-
currentKeyEpoch(key) {
|
|
2468
|
-
return this.keyEpochs.get(key) ?? 0;
|
|
2469
|
-
}
|
|
2470
|
-
bumpKeyEpochs(keys) {
|
|
2471
|
-
for (const key of keys) {
|
|
2472
|
-
this.keyEpochs.set(key, this.currentKeyEpoch(key) + 1);
|
|
2473
|
-
}
|
|
2474
|
-
}
|
|
2475
|
-
isWriteOutdated(key, expectedClearEpoch, expectedKeyEpoch) {
|
|
2476
|
-
if (expectedClearEpoch !== void 0 && expectedClearEpoch !== this.clearEpoch) {
|
|
2477
|
-
return true;
|
|
2478
|
-
}
|
|
2479
|
-
if (expectedKeyEpoch !== void 0 && expectedKeyEpoch !== this.currentKeyEpoch(key)) {
|
|
2480
|
-
return true;
|
|
2481
|
-
}
|
|
2482
|
-
return false;
|
|
2483
|
-
}
|
|
2484
2632
|
async enqueueWriteBehind(operation) {
|
|
2485
|
-
this.
|
|
2486
|
-
const batchSize = this.options.writeBehind?.batchSize ?? 100;
|
|
2487
|
-
const maxQueueSize = this.options.writeBehind?.maxQueueSize ?? batchSize * 10;
|
|
2488
|
-
if (this.writeBehindQueue.length >= batchSize) {
|
|
2489
|
-
await this.flushWriteBehindQueue();
|
|
2490
|
-
return;
|
|
2491
|
-
}
|
|
2492
|
-
if (this.writeBehindQueue.length >= maxQueueSize) {
|
|
2493
|
-
await this.flushWriteBehindQueue();
|
|
2494
|
-
}
|
|
2633
|
+
await this.maintenance.enqueueWriteBehind(operation, this.options.writeBehind, this.runWriteBehindBatch.bind(this));
|
|
2495
2634
|
}
|
|
2496
2635
|
async flushWriteBehindQueue() {
|
|
2497
|
-
|
|
2498
|
-
|
|
2636
|
+
await this.maintenance.flushWriteBehindQueue(this.options.writeBehind, this.runWriteBehindBatch.bind(this));
|
|
2637
|
+
}
|
|
2638
|
+
async runWriteBehindBatch(batch) {
|
|
2639
|
+
const results = await Promise.allSettled(batch.map((operation) => operation()));
|
|
2640
|
+
const failures = results.filter((result) => result.status === "rejected");
|
|
2641
|
+
if (failures.length === 0) {
|
|
2499
2642
|
return;
|
|
2500
2643
|
}
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
this.logger.error?.("write-behind-flush-failure", {
|
|
2509
|
-
failed: failures.length,
|
|
2510
|
-
total: batch.length,
|
|
2511
|
-
errors: failures.map((failure) => this.formatError(failure.reason))
|
|
2512
|
-
});
|
|
2513
|
-
this.emitError("write-behind", { failed: failures.length, total: batch.length });
|
|
2514
|
-
}
|
|
2515
|
-
})();
|
|
2516
|
-
await this.writeBehindFlushPromise;
|
|
2517
|
-
this.writeBehindFlushPromise = void 0;
|
|
2518
|
-
if (this.writeBehindQueue.length > 0) {
|
|
2519
|
-
await this.flushWriteBehindQueue();
|
|
2520
|
-
}
|
|
2644
|
+
this.metricsCollector.increment("writeFailures", failures.length);
|
|
2645
|
+
this.logger.error?.("write-behind-flush-failure", {
|
|
2646
|
+
failed: failures.length,
|
|
2647
|
+
total: batch.length,
|
|
2648
|
+
errors: failures.map((failure) => this.formatError(failure.reason))
|
|
2649
|
+
});
|
|
2650
|
+
this.emitError("write-behind", { failed: failures.length, total: batch.length });
|
|
2521
2651
|
}
|
|
2522
2652
|
buildLayerSetEntry(layer, key, kind, value, options, now) {
|
|
2523
2653
|
const freshTtl = this.resolveFreshTtl(key, layer.name, kind, options, layer.defaultTtl, value);
|
|
@@ -2547,32 +2677,17 @@ var CacheStack = class extends EventEmitter {
|
|
|
2547
2677
|
return [];
|
|
2548
2678
|
}
|
|
2549
2679
|
const [firstGroup, ...rest] = groups;
|
|
2550
|
-
if (!firstGroup) {
|
|
2551
|
-
return [];
|
|
2552
|
-
}
|
|
2553
2680
|
const restSets = rest.map((group) => new Set(group));
|
|
2554
2681
|
return [...new Set(firstGroup)].filter((key) => restSets.every((group) => group.has(key)));
|
|
2555
2682
|
}
|
|
2556
2683
|
qualifyKey(key) {
|
|
2557
|
-
|
|
2558
|
-
return prefix ? `${prefix}${key}` : key;
|
|
2684
|
+
return qualifyGenerationKey(key, this.currentGeneration);
|
|
2559
2685
|
}
|
|
2560
2686
|
qualifyPattern(pattern) {
|
|
2561
|
-
|
|
2562
|
-
return prefix ? `${prefix}${pattern}` : pattern;
|
|
2687
|
+
return qualifyGenerationPattern(pattern, this.currentGeneration);
|
|
2563
2688
|
}
|
|
2564
2689
|
stripQualifiedKey(key) {
|
|
2565
|
-
|
|
2566
|
-
if (!prefix || !key.startsWith(prefix)) {
|
|
2567
|
-
return key;
|
|
2568
|
-
}
|
|
2569
|
-
return key.slice(prefix.length);
|
|
2570
|
-
}
|
|
2571
|
-
generationPrefix() {
|
|
2572
|
-
if (this.currentGeneration === void 0) {
|
|
2573
|
-
return "";
|
|
2574
|
-
}
|
|
2575
|
-
return `v${this.currentGeneration}:`;
|
|
2690
|
+
return stripGenerationPrefix(key, this.currentGeneration);
|
|
2576
2691
|
}
|
|
2577
2692
|
async deleteKeysFromLayers(layers, keys) {
|
|
2578
2693
|
await Promise.all(
|
|
@@ -2663,37 +2778,38 @@ var CacheStack = class extends EventEmitter {
|
|
|
2663
2778
|
this.assertActive(operation);
|
|
2664
2779
|
}
|
|
2665
2780
|
async applyFreshReadPolicies(key, hit, options, fetcher) {
|
|
2666
|
-
const
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2781
|
+
const plan = planFreshReadPolicies({
|
|
2782
|
+
stored: hit.stored,
|
|
2783
|
+
hasFetcher: Boolean(fetcher),
|
|
2784
|
+
slidingTtl: options?.slidingTtl ?? false,
|
|
2785
|
+
refreshAheadSeconds: this.resolveLayerSeconds(hit.layerName, options?.refreshAhead, this.options.refreshAhead, 0) ?? 0
|
|
2786
|
+
});
|
|
2787
|
+
if (plan.refreshedStored) {
|
|
2671
2788
|
for (let index = 0; index <= hit.layerIndex; index += 1) {
|
|
2672
2789
|
const layer = this.layers[index];
|
|
2673
2790
|
if (!layer || this.shouldSkipLayer(layer)) {
|
|
2674
2791
|
continue;
|
|
2675
2792
|
}
|
|
2676
2793
|
try {
|
|
2677
|
-
await layer.set(key,
|
|
2794
|
+
await layer.set(key, plan.refreshedStored, plan.refreshedStoredTtl);
|
|
2678
2795
|
} catch (error) {
|
|
2679
2796
|
await this.handleLayerFailure(layer, "sliding-ttl", error);
|
|
2680
2797
|
}
|
|
2681
2798
|
}
|
|
2682
2799
|
}
|
|
2683
|
-
if (fetcher &&
|
|
2800
|
+
if (fetcher && plan.shouldScheduleBackgroundRefresh) {
|
|
2684
2801
|
this.scheduleBackgroundRefresh(key, fetcher, options);
|
|
2685
2802
|
}
|
|
2686
2803
|
}
|
|
2687
2804
|
shouldSkipLayer(layer) {
|
|
2688
|
-
|
|
2689
|
-
return degradedUntil !== void 0 && degradedUntil > Date.now();
|
|
2805
|
+
return shouldSkipLayer(this.layerDegradedUntil.get(layer.name));
|
|
2690
2806
|
}
|
|
2691
2807
|
async handleLayerFailure(layer, operation, error) {
|
|
2692
|
-
|
|
2808
|
+
const recovery = resolveRecoverableLayerFailure(this.options.gracefulDegradation);
|
|
2809
|
+
if (!recovery.degrade) {
|
|
2693
2810
|
throw error;
|
|
2694
2811
|
}
|
|
2695
|
-
|
|
2696
|
-
this.layerDegradedUntil.set(layer.name, Date.now() + retryAfterMs);
|
|
2812
|
+
this.layerDegradedUntil.set(layer.name, recovery.degradedUntil);
|
|
2697
2813
|
this.metricsCollector.increment("degradedOperations");
|
|
2698
2814
|
this.logger.warn?.("layer-degraded", { layer: layer.name, operation, error: this.formatError(error) });
|
|
2699
2815
|
this.emitError(operation, { layer: layer.name, degraded: true, error: this.formatError(error) });
|
|
@@ -3897,7 +4013,7 @@ var MsgpackSerializer = class {
|
|
|
3897
4013
|
return Buffer.from(encode(value));
|
|
3898
4014
|
}
|
|
3899
4015
|
deserialize(payload) {
|
|
3900
|
-
const normalized = Buffer.isBuffer(payload) ? payload : Buffer.from(payload);
|
|
4016
|
+
const normalized = Buffer.isBuffer(payload) ? payload : Buffer.from(payload, "latin1");
|
|
3901
4017
|
return sanitizeMsgpackValue(decode(normalized), 0, { count: 0 });
|
|
3902
4018
|
}
|
|
3903
4019
|
};
|