layercache 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-7KMKQ6QZ.js → chunk-6X7NV5BG.js} +33 -0
- package/dist/{chunk-FFZCC7EQ.js → chunk-IVX6ABFX.js} +87 -0
- package/dist/cli.cjs +33 -0
- package/dist/cli.js +1 -1
- package/dist/{edge-D2FpRlyS.d.cts → edge-BCU8D-Yd.d.cts} +504 -0
- package/dist/{edge-D2FpRlyS.d.ts → edge-BCU8D-Yd.d.ts} +504 -0
- package/dist/edge.cjs +87 -0
- package/dist/edge.d.cts +1 -1
- package/dist/edge.d.ts +1 -1
- package/dist/edge.js +1 -1
- package/dist/index.cjs +485 -0
- package/dist/index.d.cts +244 -2
- package/dist/index.d.ts +244 -2
- package/dist/index.js +367 -2
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -184,6 +184,9 @@ function addMetricMap(base, delta) {
|
|
|
184
184
|
|
|
185
185
|
// src/CacheNamespace.ts
|
|
186
186
|
var CacheNamespace = class _CacheNamespace {
|
|
187
|
+
/**
|
|
188
|
+
* Creates a namespace backed by an existing cache stack.
|
|
189
|
+
*/
|
|
187
190
|
constructor(cache, prefix) {
|
|
188
191
|
this.cache = cache;
|
|
189
192
|
this.prefix = prefix;
|
|
@@ -193,9 +196,16 @@ var CacheNamespace = class _CacheNamespace {
|
|
|
193
196
|
prefix;
|
|
194
197
|
static metricsMutexes = /* @__PURE__ */ new WeakMap();
|
|
195
198
|
metrics = createEmptyNamespaceMetrics();
|
|
199
|
+
/**
|
|
200
|
+
* Reads a key inside this namespace and optionally runs a read-through fetcher
|
|
201
|
+
* on miss or refresh.
|
|
202
|
+
*/
|
|
196
203
|
async get(key, fetcher, options) {
|
|
197
204
|
return this.trackMetrics(() => this.cache.get(this.qualify(key), fetcher, this.qualifyGetOptions(options)));
|
|
198
205
|
}
|
|
206
|
+
/**
|
|
207
|
+
* Alias for `get(key, fetcher, options)` that makes the get-or-set behavior explicit.
|
|
208
|
+
*/
|
|
199
209
|
async getOrSet(key, fetcher, options) {
|
|
200
210
|
return this.trackMetrics(() => this.cache.getOrSet(this.qualify(key), fetcher, this.qualifyGetOptions(options)));
|
|
201
211
|
}
|
|
@@ -205,24 +215,69 @@ var CacheNamespace = class _CacheNamespace {
|
|
|
205
215
|
async getOrThrow(key, fetcher, options) {
|
|
206
216
|
return this.trackMetrics(() => this.cache.getOrThrow(this.qualify(key), fetcher, this.qualifyGetOptions(options)));
|
|
207
217
|
}
|
|
218
|
+
/**
|
|
219
|
+
* Returns true when the namespaced key exists and has not expired in any layer.
|
|
220
|
+
*/
|
|
208
221
|
async has(key) {
|
|
209
222
|
return this.trackMetrics(() => this.cache.has(this.qualify(key)));
|
|
210
223
|
}
|
|
224
|
+
/**
|
|
225
|
+
* Returns the remaining TTL in milliseconds for the namespaced key.
|
|
226
|
+
*/
|
|
211
227
|
async ttl(key) {
|
|
212
228
|
return this.trackMetrics(() => this.cache.ttl(this.qualify(key)));
|
|
213
229
|
}
|
|
230
|
+
/**
|
|
231
|
+
* Stores a value under a namespaced key.
|
|
232
|
+
*/
|
|
214
233
|
async set(key, value, options) {
|
|
215
234
|
await this.trackMetrics(() => this.cache.set(this.qualify(key), value, this.qualifyWriteOptions(options)));
|
|
216
235
|
}
|
|
236
|
+
/**
|
|
237
|
+
* Deletes a namespaced key from all layers.
|
|
238
|
+
*/
|
|
217
239
|
async delete(key) {
|
|
218
240
|
await this.trackMetrics(() => this.cache.delete(this.qualify(key)));
|
|
219
241
|
}
|
|
242
|
+
/**
|
|
243
|
+
* Deletes multiple namespaced keys from all layers.
|
|
244
|
+
*/
|
|
220
245
|
async mdelete(keys) {
|
|
221
246
|
await this.trackMetrics(() => this.cache.mdelete(keys.map((k) => this.qualify(k))));
|
|
222
247
|
}
|
|
248
|
+
/**
|
|
249
|
+
* Alias for `delete(key)` scoped to this namespace.
|
|
250
|
+
*/
|
|
251
|
+
async invalidateByKey(key) {
|
|
252
|
+
await this.trackMetrics(() => this.cache.invalidateByKey(this.qualify(key)));
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Alias for `mdelete(keys)` scoped to this namespace.
|
|
256
|
+
*/
|
|
257
|
+
async invalidateByKeys(keys) {
|
|
258
|
+
await this.trackMetrics(() => this.cache.invalidateByKeys(keys.map((k) => this.qualify(k))));
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Marks one exact namespaced key expired without deleting its stale value.
|
|
262
|
+
*/
|
|
263
|
+
async expireByKey(key) {
|
|
264
|
+
await this.trackMetrics(() => this.cache.expireByKey(this.qualify(key)));
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Marks multiple exact namespaced keys expired without deleting their stale values.
|
|
268
|
+
*/
|
|
269
|
+
async expireByKeys(keys) {
|
|
270
|
+
await this.trackMetrics(() => this.cache.expireByKeys(keys.map((k) => this.qualify(k))));
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Clears all keys in this namespace by invalidating the namespace prefix.
|
|
274
|
+
*/
|
|
223
275
|
async clear() {
|
|
224
276
|
await this.trackMetrics(() => this.cache.invalidateByPrefix(this.prefix));
|
|
225
277
|
}
|
|
278
|
+
/**
|
|
279
|
+
* Reads many namespaced keys concurrently.
|
|
280
|
+
*/
|
|
226
281
|
async mget(entries) {
|
|
227
282
|
return this.trackMetrics(
|
|
228
283
|
() => this.cache.mget(
|
|
@@ -234,6 +289,9 @@ var CacheNamespace = class _CacheNamespace {
|
|
|
234
289
|
)
|
|
235
290
|
);
|
|
236
291
|
}
|
|
292
|
+
/**
|
|
293
|
+
* Writes many namespaced entries concurrently.
|
|
294
|
+
*/
|
|
237
295
|
async mset(entries) {
|
|
238
296
|
await this.trackMetrics(
|
|
239
297
|
() => this.cache.mset(
|
|
@@ -245,12 +303,21 @@ var CacheNamespace = class _CacheNamespace {
|
|
|
245
303
|
)
|
|
246
304
|
);
|
|
247
305
|
}
|
|
306
|
+
/**
|
|
307
|
+
* Deletes keys associated with a tag scoped to this namespace.
|
|
308
|
+
*/
|
|
248
309
|
async invalidateByTag(tag) {
|
|
249
310
|
await this.trackMetrics(() => this.cache.invalidateByTag(this.qualifyTag(tag)));
|
|
250
311
|
}
|
|
312
|
+
/**
|
|
313
|
+
* Expires keys associated with a tag scoped to this namespace while preserving stale windows.
|
|
314
|
+
*/
|
|
251
315
|
async expireByTag(tag) {
|
|
252
316
|
await this.trackMetrics(() => this.cache.expireByTag(this.qualifyTag(tag)));
|
|
253
317
|
}
|
|
318
|
+
/**
|
|
319
|
+
* Deletes keys associated with any or all namespace-scoped tags.
|
|
320
|
+
*/
|
|
254
321
|
async invalidateByTags(tags, mode = "any") {
|
|
255
322
|
await this.trackMetrics(
|
|
256
323
|
() => this.cache.invalidateByTags(
|
|
@@ -259,6 +326,9 @@ var CacheNamespace = class _CacheNamespace {
|
|
|
259
326
|
)
|
|
260
327
|
);
|
|
261
328
|
}
|
|
329
|
+
/**
|
|
330
|
+
* Expires keys associated with any or all namespace-scoped tags while preserving stale windows.
|
|
331
|
+
*/
|
|
262
332
|
async expireByTags(tags, mode = "any") {
|
|
263
333
|
await this.trackMetrics(
|
|
264
334
|
() => this.cache.expireByTags(
|
|
@@ -267,15 +337,27 @@ var CacheNamespace = class _CacheNamespace {
|
|
|
267
337
|
)
|
|
268
338
|
);
|
|
269
339
|
}
|
|
340
|
+
/**
|
|
341
|
+
* Deletes namespaced keys matching a wildcard pattern.
|
|
342
|
+
*/
|
|
270
343
|
async invalidateByPattern(pattern) {
|
|
271
344
|
await this.trackMetrics(() => this.cache.invalidateByPattern(this.qualify(pattern)));
|
|
272
345
|
}
|
|
346
|
+
/**
|
|
347
|
+
* Expires namespaced keys matching a wildcard pattern while preserving stale windows.
|
|
348
|
+
*/
|
|
273
349
|
async expireByPattern(pattern) {
|
|
274
350
|
await this.trackMetrics(() => this.cache.expireByPattern(this.qualify(pattern)));
|
|
275
351
|
}
|
|
352
|
+
/**
|
|
353
|
+
* Deletes namespaced keys with the provided prefix.
|
|
354
|
+
*/
|
|
276
355
|
async invalidateByPrefix(prefix) {
|
|
277
356
|
await this.trackMetrics(() => this.cache.invalidateByPrefix(this.qualify(prefix)));
|
|
278
357
|
}
|
|
358
|
+
/**
|
|
359
|
+
* Expires namespaced keys with the provided prefix while preserving stale windows.
|
|
360
|
+
*/
|
|
279
361
|
async expireByPrefix(prefix) {
|
|
280
362
|
await this.trackMetrics(() => this.cache.expireByPrefix(this.qualify(prefix)));
|
|
281
363
|
}
|
|
@@ -292,9 +374,15 @@ var CacheNamespace = class _CacheNamespace {
|
|
|
292
374
|
tags: result.tags.filter((tag) => tag.startsWith(`${this.prefix}:`)).map((tag) => tag.slice(this.prefix.length + 1))
|
|
293
375
|
};
|
|
294
376
|
}
|
|
377
|
+
/**
|
|
378
|
+
* Returns a cached wrapper whose generated keys are scoped to this namespace.
|
|
379
|
+
*/
|
|
295
380
|
wrap(keyPrefix, fetcher, options) {
|
|
296
381
|
return this.cache.wrap(`${this.prefix}:${keyPrefix}`, fetcher, this.qualifyWrapOptions(options));
|
|
297
382
|
}
|
|
383
|
+
/**
|
|
384
|
+
* Warms entries after qualifying each key and tag with this namespace prefix.
|
|
385
|
+
*/
|
|
298
386
|
warm(entries, options) {
|
|
299
387
|
return this.cache.warm(
|
|
300
388
|
entries.map((entry) => ({
|
|
@@ -305,9 +393,15 @@ var CacheNamespace = class _CacheNamespace {
|
|
|
305
393
|
options
|
|
306
394
|
);
|
|
307
395
|
}
|
|
396
|
+
/**
|
|
397
|
+
* Returns metrics accumulated by operations performed through this namespace.
|
|
398
|
+
*/
|
|
308
399
|
getMetrics() {
|
|
309
400
|
return cloneNamespaceMetrics(this.metrics);
|
|
310
401
|
}
|
|
402
|
+
/**
|
|
403
|
+
* Returns hit-rate statistics for operations performed through this namespace.
|
|
404
|
+
*/
|
|
311
405
|
getHitRate() {
|
|
312
406
|
return computeNamespaceHitRate(this.metrics);
|
|
313
407
|
}
|
|
@@ -324,6 +418,9 @@ var CacheNamespace = class _CacheNamespace {
|
|
|
324
418
|
validateNamespaceKey(childPrefix);
|
|
325
419
|
return new _CacheNamespace(this.cache, `${this.prefix}:${childPrefix}`);
|
|
326
420
|
}
|
|
421
|
+
/**
|
|
422
|
+
* Qualifies a raw key with this namespace prefix.
|
|
423
|
+
*/
|
|
327
424
|
qualify(key) {
|
|
328
425
|
return `${this.prefix}:${key}`;
|
|
329
426
|
}
|
|
@@ -2558,10 +2655,16 @@ var TagIndex = class {
|
|
|
2558
2655
|
constructor(options = {}) {
|
|
2559
2656
|
this.maxKnownKeys = options.maxKnownKeys ?? 1e5;
|
|
2560
2657
|
}
|
|
2658
|
+
/**
|
|
2659
|
+
* Records a key as known without changing tag assignments.
|
|
2660
|
+
*/
|
|
2561
2661
|
async touch(key) {
|
|
2562
2662
|
this.insertKnownKey(key);
|
|
2563
2663
|
this.pruneKnownKeysIfNeeded();
|
|
2564
2664
|
}
|
|
2665
|
+
/**
|
|
2666
|
+
* Replaces the tags associated with a key and records the key as known.
|
|
2667
|
+
*/
|
|
2565
2668
|
async track(key, tags) {
|
|
2566
2669
|
this.insertKnownKey(key);
|
|
2567
2670
|
this.pruneKnownKeysIfNeeded();
|
|
@@ -2582,17 +2685,29 @@ var TagIndex = class {
|
|
|
2582
2685
|
this.tagToKeys.set(tag, keys);
|
|
2583
2686
|
}
|
|
2584
2687
|
}
|
|
2688
|
+
/**
|
|
2689
|
+
* Removes a key from all tag mappings and known-key tracking.
|
|
2690
|
+
*/
|
|
2585
2691
|
async remove(key) {
|
|
2586
2692
|
this.removeKey(key);
|
|
2587
2693
|
}
|
|
2694
|
+
/**
|
|
2695
|
+
* Returns keys currently associated with a tag.
|
|
2696
|
+
*/
|
|
2588
2697
|
async keysForTag(tag) {
|
|
2589
2698
|
return [...this.tagToKeys.get(tag) ?? /* @__PURE__ */ new Set()];
|
|
2590
2699
|
}
|
|
2700
|
+
/**
|
|
2701
|
+
* Visits keys currently associated with a tag.
|
|
2702
|
+
*/
|
|
2591
2703
|
async forEachKeyForTag(tag, visitor) {
|
|
2592
2704
|
for (const key of this.tagToKeys.get(tag) ?? /* @__PURE__ */ new Set()) {
|
|
2593
2705
|
await visitor(key);
|
|
2594
2706
|
}
|
|
2595
2707
|
}
|
|
2708
|
+
/**
|
|
2709
|
+
* Returns known keys that start with a prefix.
|
|
2710
|
+
*/
|
|
2596
2711
|
async keysForPrefix(prefix) {
|
|
2597
2712
|
const node = this.findNode(prefix);
|
|
2598
2713
|
if (!node) {
|
|
@@ -2602,6 +2717,9 @@ var TagIndex = class {
|
|
|
2602
2717
|
this.collectFromNode(node, prefix, matches);
|
|
2603
2718
|
return matches;
|
|
2604
2719
|
}
|
|
2720
|
+
/**
|
|
2721
|
+
* Visits known keys that start with a prefix.
|
|
2722
|
+
*/
|
|
2605
2723
|
async forEachKeyForPrefix(prefix, visitor) {
|
|
2606
2724
|
const node = this.findNode(prefix);
|
|
2607
2725
|
if (!node) {
|
|
@@ -2609,20 +2727,32 @@ var TagIndex = class {
|
|
|
2609
2727
|
}
|
|
2610
2728
|
await this.visitFromNode(node, prefix, visitor);
|
|
2611
2729
|
}
|
|
2730
|
+
/**
|
|
2731
|
+
* Returns the tags currently associated with a key.
|
|
2732
|
+
*/
|
|
2612
2733
|
async tagsForKey(key) {
|
|
2613
2734
|
return [...this.keyToTags.get(key) ?? /* @__PURE__ */ new Set()];
|
|
2614
2735
|
}
|
|
2736
|
+
/**
|
|
2737
|
+
* Returns known keys matching a wildcard pattern.
|
|
2738
|
+
*/
|
|
2615
2739
|
async matchPattern(pattern) {
|
|
2616
2740
|
const matches = /* @__PURE__ */ new Set();
|
|
2617
2741
|
this.collectPatternMatches(this.root, "", pattern, 0, matches, /* @__PURE__ */ new Set(), 0);
|
|
2618
2742
|
return [...matches];
|
|
2619
2743
|
}
|
|
2744
|
+
/**
|
|
2745
|
+
* Visits known keys matching a wildcard pattern.
|
|
2746
|
+
*/
|
|
2620
2747
|
async forEachKeyMatchingPattern(pattern, visitor) {
|
|
2621
2748
|
const matches = await this.matchPattern(pattern);
|
|
2622
2749
|
for (const key of matches) {
|
|
2623
2750
|
await visitor(key);
|
|
2624
2751
|
}
|
|
2625
2752
|
}
|
|
2753
|
+
/**
|
|
2754
|
+
* Clears all tag and known-key index state.
|
|
2755
|
+
*/
|
|
2626
2756
|
async clear() {
|
|
2627
2757
|
this.tagToKeys.clear();
|
|
2628
2758
|
this.keyToTags.clear();
|
|
@@ -2797,9 +2927,15 @@ var TagIndex = class {
|
|
|
2797
2927
|
|
|
2798
2928
|
// src/serialization/JsonSerializer.ts
|
|
2799
2929
|
var JsonSerializer = class {
|
|
2930
|
+
/**
|
|
2931
|
+
* Serializes a value to JSON.
|
|
2932
|
+
*/
|
|
2800
2933
|
serialize(value) {
|
|
2801
2934
|
return JSON.stringify(value);
|
|
2802
2935
|
}
|
|
2936
|
+
/**
|
|
2937
|
+
* Parses JSON and sanitizes the result before returning it.
|
|
2938
|
+
*/
|
|
2803
2939
|
deserialize(payload) {
|
|
2804
2940
|
const normalized = Buffer.isBuffer(payload) ? payload.toString("utf8") : payload;
|
|
2805
2941
|
let parsed;
|
|
@@ -2826,6 +2962,9 @@ var StampedeGuard = class {
|
|
|
2826
2962
|
this.maxInFlight = options.maxInFlight ?? 1e4;
|
|
2827
2963
|
this.entryTimeoutMs = options.entryTimeoutMs;
|
|
2828
2964
|
}
|
|
2965
|
+
/**
|
|
2966
|
+
* Deduplicates concurrent work for the same key in this process.
|
|
2967
|
+
*/
|
|
2829
2968
|
async execute(key, task) {
|
|
2830
2969
|
const existing = this.inFlight.get(key);
|
|
2831
2970
|
if (existing) {
|
|
@@ -2925,6 +3064,9 @@ var DebugLogger = class {
|
|
|
2925
3064
|
}
|
|
2926
3065
|
};
|
|
2927
3066
|
var CacheStack = class extends import_node_events.EventEmitter {
|
|
3067
|
+
/**
|
|
3068
|
+
* Creates a cache stack from ordered layers and optional global behavior settings.
|
|
3069
|
+
*/
|
|
2928
3070
|
constructor(layers, options = {}) {
|
|
2929
3071
|
super();
|
|
2930
3072
|
this.layers = layers;
|
|
@@ -3190,6 +3332,10 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
3190
3332
|
});
|
|
3191
3333
|
});
|
|
3192
3334
|
}
|
|
3335
|
+
/**
|
|
3336
|
+
* Clears every configured layer, removes tag metadata, resets internal TTL
|
|
3337
|
+
* profiles, and broadcasts a clear invalidation message.
|
|
3338
|
+
*/
|
|
3193
3339
|
async clear() {
|
|
3194
3340
|
await this.awaitStartup("clear");
|
|
3195
3341
|
this.maintenance.beginClearEpoch();
|
|
@@ -3219,6 +3365,58 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
3219
3365
|
operation: "delete"
|
|
3220
3366
|
});
|
|
3221
3367
|
}
|
|
3368
|
+
/**
|
|
3369
|
+
* Alias for `delete(key)` that matches the `invalidateBy*` API family.
|
|
3370
|
+
*/
|
|
3371
|
+
async invalidateByKey(key) {
|
|
3372
|
+
await this.delete(key);
|
|
3373
|
+
}
|
|
3374
|
+
/**
|
|
3375
|
+
* Alias for `mdelete(keys)` that matches the `invalidateBy*` API family.
|
|
3376
|
+
*/
|
|
3377
|
+
async invalidateByKeys(keys) {
|
|
3378
|
+
await this.mdelete(keys);
|
|
3379
|
+
}
|
|
3380
|
+
/**
|
|
3381
|
+
* Marks one exact key expired without deleting its stale value.
|
|
3382
|
+
*/
|
|
3383
|
+
async expireByKey(key) {
|
|
3384
|
+
await this.observeOperation("layercache.expire_by_key", { "layercache.key": String(key ?? "") }, async () => {
|
|
3385
|
+
const normalizedKey = this.qualifyKey(validateCacheKey(key));
|
|
3386
|
+
await this.awaitStartup("expireByKey");
|
|
3387
|
+
await this.expireKeys([normalizedKey]);
|
|
3388
|
+
await this.publishInvalidation({
|
|
3389
|
+
scope: "key",
|
|
3390
|
+
keys: [normalizedKey],
|
|
3391
|
+
sourceId: this.instanceId,
|
|
3392
|
+
operation: "expire"
|
|
3393
|
+
});
|
|
3394
|
+
});
|
|
3395
|
+
}
|
|
3396
|
+
/**
|
|
3397
|
+
* Marks multiple exact keys expired without deleting their stale values.
|
|
3398
|
+
*/
|
|
3399
|
+
async expireByKeys(keys) {
|
|
3400
|
+
await this.observeOperation("layercache.expire_by_keys", void 0, async () => {
|
|
3401
|
+
if (keys.length === 0) {
|
|
3402
|
+
return;
|
|
3403
|
+
}
|
|
3404
|
+
const normalizedKeys = keys.map((k) => validateCacheKey(k));
|
|
3405
|
+
const cacheKeys = normalizedKeys.map((key) => this.qualifyKey(key));
|
|
3406
|
+
await this.awaitStartup("expireByKeys");
|
|
3407
|
+
await this.expireKeys(cacheKeys);
|
|
3408
|
+
await this.publishInvalidation({
|
|
3409
|
+
scope: "keys",
|
|
3410
|
+
keys: cacheKeys,
|
|
3411
|
+
sourceId: this.instanceId,
|
|
3412
|
+
operation: "expire"
|
|
3413
|
+
});
|
|
3414
|
+
});
|
|
3415
|
+
}
|
|
3416
|
+
/**
|
|
3417
|
+
* Reads many keys concurrently. Simple reads use layer-level bulk fast paths;
|
|
3418
|
+
* entries with fetchers or options fall back to per-entry read-through logic.
|
|
3419
|
+
*/
|
|
3222
3420
|
async mget(entries) {
|
|
3223
3421
|
return this.observeOperation("layercache.mget", void 0, async () => {
|
|
3224
3422
|
this.assertActive("mget");
|
|
@@ -3306,6 +3504,10 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
3306
3504
|
return normalizedEntries.map((entry) => resultsByKey.get(entry.key) ?? null);
|
|
3307
3505
|
});
|
|
3308
3506
|
}
|
|
3507
|
+
/**
|
|
3508
|
+
* Writes many entries concurrently using each layer's bulk write fast path
|
|
3509
|
+
* when available.
|
|
3510
|
+
*/
|
|
3309
3511
|
async mset(entries) {
|
|
3310
3512
|
await this.observeOperation("layercache.mset", void 0, async () => {
|
|
3311
3513
|
this.assertActive("mset");
|
|
@@ -3318,6 +3520,10 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
3318
3520
|
await this.writeBatch(normalizedEntries);
|
|
3319
3521
|
});
|
|
3320
3522
|
}
|
|
3523
|
+
/**
|
|
3524
|
+
* Pre-populates cache entries by running their fetchers with bounded
|
|
3525
|
+
* concurrency. Higher-priority entries run first.
|
|
3526
|
+
*/
|
|
3321
3527
|
async warm(entries, options = {}) {
|
|
3322
3528
|
this.assertActive("warm");
|
|
3323
3529
|
const concurrency = Math.max(1, options.concurrency ?? 4);
|
|
@@ -3368,6 +3574,10 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
3368
3574
|
validateNamespaceKey(prefix);
|
|
3369
3575
|
return new CacheNamespace(this, prefix);
|
|
3370
3576
|
}
|
|
3577
|
+
/**
|
|
3578
|
+
* Deletes every key currently associated with `tag` and broadcasts an
|
|
3579
|
+
* invalidation message.
|
|
3580
|
+
*/
|
|
3371
3581
|
async invalidateByTag(tag) {
|
|
3372
3582
|
await this.observeOperation("layercache.invalidate_by_tag", void 0, async () => {
|
|
3373
3583
|
validateTag(tag);
|
|
@@ -3377,6 +3587,10 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
3377
3587
|
await this.publishInvalidation({ scope: "keys", keys, sourceId: this.instanceId, operation: "invalidate" });
|
|
3378
3588
|
});
|
|
3379
3589
|
}
|
|
3590
|
+
/**
|
|
3591
|
+
* Marks every key associated with `tag` as expired while preserving stale
|
|
3592
|
+
* windows for stale serving.
|
|
3593
|
+
*/
|
|
3380
3594
|
async expireByTag(tag) {
|
|
3381
3595
|
await this.observeOperation("layercache.expire_by_tag", void 0, async () => {
|
|
3382
3596
|
validateTag(tag);
|
|
@@ -3386,6 +3600,10 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
3386
3600
|
await this.publishInvalidation({ scope: "keys", keys, sourceId: this.instanceId, operation: "expire" });
|
|
3387
3601
|
});
|
|
3388
3602
|
}
|
|
3603
|
+
/**
|
|
3604
|
+
* Deletes keys associated with any or all of the provided tags and broadcasts
|
|
3605
|
+
* an invalidation message.
|
|
3606
|
+
*/
|
|
3389
3607
|
async invalidateByTags(tags, mode = "any") {
|
|
3390
3608
|
await this.observeOperation("layercache.invalidate_by_tags", void 0, async () => {
|
|
3391
3609
|
if (tags.length === 0) {
|
|
@@ -3402,6 +3620,10 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
3402
3620
|
await this.publishInvalidation({ scope: "keys", keys, sourceId: this.instanceId, operation: "invalidate" });
|
|
3403
3621
|
});
|
|
3404
3622
|
}
|
|
3623
|
+
/**
|
|
3624
|
+
* Marks keys associated with any or all of the provided tags as expired while
|
|
3625
|
+
* preserving stale windows for stale serving.
|
|
3626
|
+
*/
|
|
3405
3627
|
async expireByTags(tags, mode = "any") {
|
|
3406
3628
|
await this.observeOperation("layercache.expire_by_tags", void 0, async () => {
|
|
3407
3629
|
if (tags.length === 0) {
|
|
@@ -3418,6 +3640,9 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
3418
3640
|
await this.publishInvalidation({ scope: "keys", keys, sourceId: this.instanceId, operation: "expire" });
|
|
3419
3641
|
});
|
|
3420
3642
|
}
|
|
3643
|
+
/**
|
|
3644
|
+
* Deletes keys matching a wildcard pattern such as `user:*`.
|
|
3645
|
+
*/
|
|
3421
3646
|
async invalidateByPattern(pattern) {
|
|
3422
3647
|
await this.observeOperation("layercache.invalidate_by_pattern", void 0, async () => {
|
|
3423
3648
|
validatePattern(pattern);
|
|
@@ -3430,6 +3655,10 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
3430
3655
|
await this.publishInvalidation({ scope: "keys", keys, sourceId: this.instanceId, operation: "invalidate" });
|
|
3431
3656
|
});
|
|
3432
3657
|
}
|
|
3658
|
+
/**
|
|
3659
|
+
* Marks keys matching a wildcard pattern as expired while preserving stale
|
|
3660
|
+
* windows for stale serving.
|
|
3661
|
+
*/
|
|
3433
3662
|
async expireByPattern(pattern) {
|
|
3434
3663
|
await this.observeOperation("layercache.expire_by_pattern", void 0, async () => {
|
|
3435
3664
|
validatePattern(pattern);
|
|
@@ -3442,6 +3671,9 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
3442
3671
|
await this.publishInvalidation({ scope: "keys", keys, sourceId: this.instanceId, operation: "expire" });
|
|
3443
3672
|
});
|
|
3444
3673
|
}
|
|
3674
|
+
/**
|
|
3675
|
+
* Deletes keys that start with the provided prefix.
|
|
3676
|
+
*/
|
|
3445
3677
|
async invalidateByPrefix(prefix) {
|
|
3446
3678
|
await this.observeOperation("layercache.invalidate_by_prefix", void 0, async () => {
|
|
3447
3679
|
await this.awaitStartup("invalidateByPrefix");
|
|
@@ -3451,6 +3683,10 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
3451
3683
|
await this.publishInvalidation({ scope: "keys", keys, sourceId: this.instanceId, operation: "invalidate" });
|
|
3452
3684
|
});
|
|
3453
3685
|
}
|
|
3686
|
+
/**
|
|
3687
|
+
* Marks keys that start with the provided prefix as expired while preserving
|
|
3688
|
+
* stale windows for stale serving.
|
|
3689
|
+
*/
|
|
3454
3690
|
async expireByPrefix(prefix) {
|
|
3455
3691
|
await this.observeOperation("layercache.expire_by_prefix", void 0, async () => {
|
|
3456
3692
|
await this.awaitStartup("expireByPrefix");
|
|
@@ -3460,9 +3696,15 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
3460
3696
|
await this.publishInvalidation({ scope: "keys", keys, sourceId: this.instanceId, operation: "expire" });
|
|
3461
3697
|
});
|
|
3462
3698
|
}
|
|
3699
|
+
/**
|
|
3700
|
+
* Returns cumulative cache metrics since startup or the last `resetMetrics()`.
|
|
3701
|
+
*/
|
|
3463
3702
|
getMetrics() {
|
|
3464
3703
|
return this.metricsCollector.snapshot;
|
|
3465
3704
|
}
|
|
3705
|
+
/**
|
|
3706
|
+
* Returns metrics plus layer degradation state and active background refresh count.
|
|
3707
|
+
*/
|
|
3466
3708
|
getStats() {
|
|
3467
3709
|
return {
|
|
3468
3710
|
metrics: this.getMetrics(),
|
|
@@ -3474,6 +3716,9 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
3474
3716
|
backgroundRefreshes: this.reader.activeRefreshCount
|
|
3475
3717
|
};
|
|
3476
3718
|
}
|
|
3719
|
+
/**
|
|
3720
|
+
* Resets cumulative metrics counters.
|
|
3721
|
+
*/
|
|
3477
3722
|
resetMetrics() {
|
|
3478
3723
|
this.metricsCollector.reset();
|
|
3479
3724
|
}
|
|
@@ -3483,6 +3728,10 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
3483
3728
|
getHitRate() {
|
|
3484
3729
|
return this.metricsCollector.hitRate();
|
|
3485
3730
|
}
|
|
3731
|
+
/**
|
|
3732
|
+
* Runs each layer's `ping()` hook when available and returns per-layer health
|
|
3733
|
+
* and latency information.
|
|
3734
|
+
*/
|
|
3486
3735
|
async healthCheck() {
|
|
3487
3736
|
await this.startup;
|
|
3488
3737
|
return Promise.all(
|
|
@@ -3567,22 +3816,38 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
3567
3816
|
const tags = await this.getTagsForKey(normalizedKey);
|
|
3568
3817
|
return { key: userKey, foundInLayers, freshTtlMs, staleTtlMs, errorTtlMs, isStale, tags };
|
|
3569
3818
|
}
|
|
3819
|
+
/**
|
|
3820
|
+
* Exports cache entries from configured layers for process-local snapshots.
|
|
3821
|
+
*/
|
|
3570
3822
|
async exportState() {
|
|
3571
3823
|
await this.awaitStartup("exportState");
|
|
3572
3824
|
return this.snapshots.exportState(this.snapshotMaxEntries());
|
|
3573
3825
|
}
|
|
3826
|
+
/**
|
|
3827
|
+
* Imports entries produced by `exportState()` into the configured layers.
|
|
3828
|
+
*/
|
|
3574
3829
|
async importState(entries) {
|
|
3575
3830
|
await this.awaitStartup("importState");
|
|
3576
3831
|
await this.snapshots.importState(entries);
|
|
3577
3832
|
}
|
|
3833
|
+
/**
|
|
3834
|
+
* Writes a snapshot file containing current cache entries.
|
|
3835
|
+
*/
|
|
3578
3836
|
async persistToFile(filePath) {
|
|
3579
3837
|
this.assertActive("persistToFile");
|
|
3580
3838
|
await this.snapshots.persistToFile(filePath, this.options.snapshotBaseDir, this.snapshotMaxEntries());
|
|
3581
3839
|
}
|
|
3840
|
+
/**
|
|
3841
|
+
* Restores cache entries from a snapshot file.
|
|
3842
|
+
*/
|
|
3582
3843
|
async restoreFromFile(filePath) {
|
|
3583
3844
|
this.assertActive("restoreFromFile");
|
|
3584
3845
|
await this.snapshots.restoreFromFile(filePath, this.options.snapshotBaseDir, this.snapshotMaxBytes());
|
|
3585
3846
|
}
|
|
3847
|
+
/**
|
|
3848
|
+
* Flushes background work, unsubscribes from buses, disposes timers, and then
|
|
3849
|
+
* disposes each layer that provides `dispose()`.
|
|
3850
|
+
*/
|
|
3586
3851
|
async disconnect() {
|
|
3587
3852
|
if (!this.disconnectPromise) {
|
|
3588
3853
|
this.isDisconnecting = true;
|
|
@@ -4082,6 +4347,9 @@ var RedisInvalidationBus = class {
|
|
|
4082
4347
|
this.channel = options.channel ?? "layercache:invalidation";
|
|
4083
4348
|
this.logger = options.logger;
|
|
4084
4349
|
}
|
|
4350
|
+
/**
|
|
4351
|
+
* Subscribes to invalidation messages and returns an unsubscribe function.
|
|
4352
|
+
*/
|
|
4085
4353
|
async subscribe(handler) {
|
|
4086
4354
|
const previousPromise = this.subscribePromise;
|
|
4087
4355
|
let resolveThis;
|
|
@@ -4113,6 +4381,9 @@ var RedisInvalidationBus = class {
|
|
|
4113
4381
|
}
|
|
4114
4382
|
};
|
|
4115
4383
|
}
|
|
4384
|
+
/**
|
|
4385
|
+
* Publishes an invalidation message to other subscribers.
|
|
4386
|
+
*/
|
|
4116
4387
|
async publish(message) {
|
|
4117
4388
|
await this.publisher.publish(this.channel, JSON.stringify(message));
|
|
4118
4389
|
}
|
|
@@ -4174,9 +4445,15 @@ var RedisTagIndex = class {
|
|
|
4174
4445
|
this.scanCount = options.scanCount ?? 100;
|
|
4175
4446
|
this.knownKeysShards = normalizeKnownKeysShards(options.knownKeysShards);
|
|
4176
4447
|
}
|
|
4448
|
+
/**
|
|
4449
|
+
* Records a key as known without changing tag assignments.
|
|
4450
|
+
*/
|
|
4177
4451
|
async touch(key) {
|
|
4178
4452
|
await this.client.sadd(this.knownKeysKeyFor(key), key);
|
|
4179
4453
|
}
|
|
4454
|
+
/**
|
|
4455
|
+
* Replaces the tags associated with a key and records the key as known.
|
|
4456
|
+
*/
|
|
4180
4457
|
async track(key, tags) {
|
|
4181
4458
|
const keyTagsKey = this.keyTagsKey(key);
|
|
4182
4459
|
const existingTags = await this.client.smembers(keyTagsKey);
|
|
@@ -4194,6 +4471,9 @@ var RedisTagIndex = class {
|
|
|
4194
4471
|
}
|
|
4195
4472
|
await pipeline.exec();
|
|
4196
4473
|
}
|
|
4474
|
+
/**
|
|
4475
|
+
* Removes a key from all tag mappings and known-key tracking.
|
|
4476
|
+
*/
|
|
4197
4477
|
async remove(key) {
|
|
4198
4478
|
const keyTagsKey = this.keyTagsKey(key);
|
|
4199
4479
|
const existingTags = await this.client.smembers(keyTagsKey);
|
|
@@ -4205,9 +4485,15 @@ var RedisTagIndex = class {
|
|
|
4205
4485
|
}
|
|
4206
4486
|
await pipeline.exec();
|
|
4207
4487
|
}
|
|
4488
|
+
/**
|
|
4489
|
+
* Returns keys currently associated with a tag.
|
|
4490
|
+
*/
|
|
4208
4491
|
async keysForTag(tag) {
|
|
4209
4492
|
return this.client.smembers(this.tagKeysKey(tag));
|
|
4210
4493
|
}
|
|
4494
|
+
/**
|
|
4495
|
+
* Visits keys currently associated with a tag.
|
|
4496
|
+
*/
|
|
4211
4497
|
async forEachKeyForTag(tag, visitor) {
|
|
4212
4498
|
let cursor = "0";
|
|
4213
4499
|
const tagKey = this.tagKeysKey(tag);
|
|
@@ -4219,6 +4505,9 @@ var RedisTagIndex = class {
|
|
|
4219
4505
|
}
|
|
4220
4506
|
} while (cursor !== "0");
|
|
4221
4507
|
}
|
|
4508
|
+
/**
|
|
4509
|
+
* Returns known keys that start with a prefix.
|
|
4510
|
+
*/
|
|
4222
4511
|
async keysForPrefix(prefix) {
|
|
4223
4512
|
const matches = [];
|
|
4224
4513
|
for (const knownKeysKey of this.knownKeysKeys()) {
|
|
@@ -4231,6 +4520,9 @@ var RedisTagIndex = class {
|
|
|
4231
4520
|
}
|
|
4232
4521
|
return matches;
|
|
4233
4522
|
}
|
|
4523
|
+
/**
|
|
4524
|
+
* Visits known keys that start with a prefix.
|
|
4525
|
+
*/
|
|
4234
4526
|
async forEachKeyForPrefix(prefix, visitor) {
|
|
4235
4527
|
for (const knownKeysKey of this.knownKeysKeys()) {
|
|
4236
4528
|
let cursor = "0";
|
|
@@ -4245,9 +4537,15 @@ var RedisTagIndex = class {
|
|
|
4245
4537
|
} while (cursor !== "0");
|
|
4246
4538
|
}
|
|
4247
4539
|
}
|
|
4540
|
+
/**
|
|
4541
|
+
* Returns the tags currently associated with a key.
|
|
4542
|
+
*/
|
|
4248
4543
|
async tagsForKey(key) {
|
|
4249
4544
|
return this.client.smembers(this.keyTagsKey(key));
|
|
4250
4545
|
}
|
|
4546
|
+
/**
|
|
4547
|
+
* Returns known keys matching a wildcard pattern.
|
|
4548
|
+
*/
|
|
4251
4549
|
async matchPattern(pattern) {
|
|
4252
4550
|
const matches = [];
|
|
4253
4551
|
for (const knownKeysKey of this.knownKeysKeys()) {
|
|
@@ -4267,6 +4565,9 @@ var RedisTagIndex = class {
|
|
|
4267
4565
|
}
|
|
4268
4566
|
return matches;
|
|
4269
4567
|
}
|
|
4568
|
+
/**
|
|
4569
|
+
* Visits known keys matching a wildcard pattern.
|
|
4570
|
+
*/
|
|
4270
4571
|
async forEachKeyMatchingPattern(pattern, visitor) {
|
|
4271
4572
|
for (const knownKeysKey of this.knownKeysKeys()) {
|
|
4272
4573
|
let cursor = "0";
|
|
@@ -4288,6 +4589,9 @@ var RedisTagIndex = class {
|
|
|
4288
4589
|
} while (cursor !== "0");
|
|
4289
4590
|
}
|
|
4290
4591
|
}
|
|
4592
|
+
/**
|
|
4593
|
+
* Clears all Redis tag-index state under this prefix.
|
|
4594
|
+
*/
|
|
4291
4595
|
async clear() {
|
|
4292
4596
|
const indexKeys = await this.scanIndexKeys();
|
|
4293
4597
|
if (indexKeys.length === 0) {
|
|
@@ -4627,6 +4931,9 @@ var MemoryLayer = class {
|
|
|
4627
4931
|
onEvict;
|
|
4628
4932
|
entries = /* @__PURE__ */ new Map();
|
|
4629
4933
|
cleanupTimer;
|
|
4934
|
+
/**
|
|
4935
|
+
* Creates an in-memory cache layer.
|
|
4936
|
+
*/
|
|
4630
4937
|
constructor(options = {}) {
|
|
4631
4938
|
this.name = options.name ?? "memory";
|
|
4632
4939
|
this.defaultTtl = options.ttl;
|
|
@@ -4640,10 +4947,16 @@ var MemoryLayer = class {
|
|
|
4640
4947
|
this.cleanupTimer.unref?.();
|
|
4641
4948
|
}
|
|
4642
4949
|
}
|
|
4950
|
+
/**
|
|
4951
|
+
* Reads and unwraps a fresh value from memory.
|
|
4952
|
+
*/
|
|
4643
4953
|
async get(key) {
|
|
4644
4954
|
const value = await this.getEntry(key);
|
|
4645
4955
|
return unwrapStoredValue(value);
|
|
4646
4956
|
}
|
|
4957
|
+
/**
|
|
4958
|
+
* Reads the raw stored value or envelope from memory.
|
|
4959
|
+
*/
|
|
4647
4960
|
async getEntry(key) {
|
|
4648
4961
|
const entry = this.entries.get(key);
|
|
4649
4962
|
if (!entry) {
|
|
@@ -4662,12 +4975,21 @@ var MemoryLayer = class {
|
|
|
4662
4975
|
}
|
|
4663
4976
|
return entry.value;
|
|
4664
4977
|
}
|
|
4978
|
+
/**
|
|
4979
|
+
* Reads many raw entries from memory.
|
|
4980
|
+
*/
|
|
4665
4981
|
async getMany(keys) {
|
|
4666
4982
|
return Promise.all(keys.map((key) => this.getEntry(key)));
|
|
4667
4983
|
}
|
|
4984
|
+
/**
|
|
4985
|
+
* Writes many entries to memory.
|
|
4986
|
+
*/
|
|
4668
4987
|
async setMany(entries) {
|
|
4669
4988
|
await Promise.all(entries.map((entry) => this.set(entry.key, entry.value, entry.ttl)));
|
|
4670
4989
|
}
|
|
4990
|
+
/**
|
|
4991
|
+
* Stores a value in memory using the provided TTL or layer default TTL.
|
|
4992
|
+
*/
|
|
4671
4993
|
async set(key, value, ttl = this.defaultTtl) {
|
|
4672
4994
|
this.entries.delete(key);
|
|
4673
4995
|
this.entries.set(key, {
|
|
@@ -4680,6 +5002,9 @@ var MemoryLayer = class {
|
|
|
4680
5002
|
this.evict();
|
|
4681
5003
|
}
|
|
4682
5004
|
}
|
|
5005
|
+
/**
|
|
5006
|
+
* Returns true when the key exists and has not expired.
|
|
5007
|
+
*/
|
|
4683
5008
|
async has(key) {
|
|
4684
5009
|
const entry = this.entries.get(key);
|
|
4685
5010
|
if (!entry) {
|
|
@@ -4691,6 +5016,9 @@ var MemoryLayer = class {
|
|
|
4691
5016
|
}
|
|
4692
5017
|
return true;
|
|
4693
5018
|
}
|
|
5019
|
+
/**
|
|
5020
|
+
* Returns remaining TTL in milliseconds, or null when absent or non-expiring.
|
|
5021
|
+
*/
|
|
4694
5022
|
async ttl(key) {
|
|
4695
5023
|
const entry = this.entries.get(key);
|
|
4696
5024
|
if (!entry) {
|
|
@@ -4705,40 +5033,67 @@ var MemoryLayer = class {
|
|
|
4705
5033
|
}
|
|
4706
5034
|
return Math.max(0, Math.ceil(entry.expiresAt - Date.now()));
|
|
4707
5035
|
}
|
|
5036
|
+
/**
|
|
5037
|
+
* Returns the number of currently retained, non-expired entries.
|
|
5038
|
+
*/
|
|
4708
5039
|
async size() {
|
|
4709
5040
|
this.pruneExpired();
|
|
4710
5041
|
return this.entries.size;
|
|
4711
5042
|
}
|
|
5043
|
+
/**
|
|
5044
|
+
* Deletes a key from memory.
|
|
5045
|
+
*/
|
|
4712
5046
|
async delete(key) {
|
|
4713
5047
|
this.entries.delete(key);
|
|
4714
5048
|
}
|
|
5049
|
+
/**
|
|
5050
|
+
* Deletes multiple keys from memory.
|
|
5051
|
+
*/
|
|
4715
5052
|
async deleteMany(keys) {
|
|
4716
5053
|
for (const key of keys) {
|
|
4717
5054
|
this.entries.delete(key);
|
|
4718
5055
|
}
|
|
4719
5056
|
}
|
|
5057
|
+
/**
|
|
5058
|
+
* Removes all entries from memory.
|
|
5059
|
+
*/
|
|
4720
5060
|
async clear() {
|
|
4721
5061
|
this.entries.clear();
|
|
4722
5062
|
}
|
|
5063
|
+
/**
|
|
5064
|
+
* Health check hook that always succeeds for the in-process layer.
|
|
5065
|
+
*/
|
|
4723
5066
|
async ping() {
|
|
4724
5067
|
return true;
|
|
4725
5068
|
}
|
|
5069
|
+
/**
|
|
5070
|
+
* Stops the cleanup timer, when one is active.
|
|
5071
|
+
*/
|
|
4726
5072
|
async dispose() {
|
|
4727
5073
|
if (this.cleanupTimer) {
|
|
4728
5074
|
clearInterval(this.cleanupTimer);
|
|
4729
5075
|
this.cleanupTimer = void 0;
|
|
4730
5076
|
}
|
|
4731
5077
|
}
|
|
5078
|
+
/**
|
|
5079
|
+
* Returns all currently retained, non-expired keys.
|
|
5080
|
+
*/
|
|
4732
5081
|
async keys() {
|
|
4733
5082
|
this.pruneExpired();
|
|
4734
5083
|
return [...this.entries.keys()];
|
|
4735
5084
|
}
|
|
5085
|
+
/**
|
|
5086
|
+
* Visits all currently retained, non-expired keys.
|
|
5087
|
+
*/
|
|
4736
5088
|
async forEachKey(visitor) {
|
|
4737
5089
|
this.pruneExpired();
|
|
4738
5090
|
for (const key of this.entries.keys()) {
|
|
4739
5091
|
await visitor(key);
|
|
4740
5092
|
}
|
|
4741
5093
|
}
|
|
5094
|
+
/**
|
|
5095
|
+
* Exports memory entries for process-local snapshots.
|
|
5096
|
+
*/
|
|
4742
5097
|
exportState() {
|
|
4743
5098
|
this.pruneExpired();
|
|
4744
5099
|
return [...this.entries.entries()].map(([key, entry]) => ({
|
|
@@ -4747,6 +5102,9 @@ var MemoryLayer = class {
|
|
|
4747
5102
|
expiresAt: entry.expiresAt
|
|
4748
5103
|
}));
|
|
4749
5104
|
}
|
|
5105
|
+
/**
|
|
5106
|
+
* Imports entries previously produced by `exportState()`.
|
|
5107
|
+
*/
|
|
4750
5108
|
importState(entries) {
|
|
4751
5109
|
for (const entry of entries) {
|
|
4752
5110
|
if (entry.expiresAt !== null && entry.expiresAt <= Date.now()) {
|
|
@@ -4826,6 +5184,9 @@ var RedisLayer = class {
|
|
|
4826
5184
|
decompressionMaxBytes;
|
|
4827
5185
|
commandTimeoutMs;
|
|
4828
5186
|
disconnectOnDispose;
|
|
5187
|
+
/**
|
|
5188
|
+
* Creates a Redis cache layer using an existing ioredis client.
|
|
5189
|
+
*/
|
|
4829
5190
|
constructor(options) {
|
|
4830
5191
|
this.client = options.client;
|
|
4831
5192
|
this.defaultTtl = options.ttl;
|
|
@@ -4840,10 +5201,16 @@ var RedisLayer = class {
|
|
|
4840
5201
|
this.commandTimeoutMs = this.normalizeCommandTimeoutMs(options.commandTimeoutMs);
|
|
4841
5202
|
this.disconnectOnDispose = options.disconnectOnDispose ?? false;
|
|
4842
5203
|
}
|
|
5204
|
+
/**
|
|
5205
|
+
* Reads and unwraps a fresh value from Redis.
|
|
5206
|
+
*/
|
|
4843
5207
|
async get(key) {
|
|
4844
5208
|
const payload = await this.getEntry(key);
|
|
4845
5209
|
return unwrapStoredValue(payload);
|
|
4846
5210
|
}
|
|
5211
|
+
/**
|
|
5212
|
+
* Reads the raw stored value or envelope from Redis.
|
|
5213
|
+
*/
|
|
4847
5214
|
async getEntry(key) {
|
|
4848
5215
|
this.validateKey(key);
|
|
4849
5216
|
const payload = await this.runCommand(
|
|
@@ -4855,6 +5222,9 @@ var RedisLayer = class {
|
|
|
4855
5222
|
}
|
|
4856
5223
|
return this.deserializeOrDelete(key, payload);
|
|
4857
5224
|
}
|
|
5225
|
+
/**
|
|
5226
|
+
* Reads many raw entries from Redis using a pipeline.
|
|
5227
|
+
*/
|
|
4858
5228
|
async getMany(keys) {
|
|
4859
5229
|
if (keys.length === 0) {
|
|
4860
5230
|
return [];
|
|
@@ -4880,6 +5250,9 @@ var RedisLayer = class {
|
|
|
4880
5250
|
})
|
|
4881
5251
|
);
|
|
4882
5252
|
}
|
|
5253
|
+
/**
|
|
5254
|
+
* Writes many entries to Redis using a pipeline.
|
|
5255
|
+
*/
|
|
4883
5256
|
async setMany(entries) {
|
|
4884
5257
|
if (entries.length === 0) {
|
|
4885
5258
|
return;
|
|
@@ -4900,6 +5273,9 @@ var RedisLayer = class {
|
|
|
4900
5273
|
}
|
|
4901
5274
|
await this.runCommand(`mset(${entries.length})`, () => pipeline.exec());
|
|
4902
5275
|
}
|
|
5276
|
+
/**
|
|
5277
|
+
* Stores a value in Redis using the provided TTL or layer default TTL.
|
|
5278
|
+
*/
|
|
4903
5279
|
async set(key, value, ttl = this.defaultTtl) {
|
|
4904
5280
|
this.validateKey(key);
|
|
4905
5281
|
const serialized = this.primarySerializer().serialize(value);
|
|
@@ -4914,10 +5290,16 @@ var RedisLayer = class {
|
|
|
4914
5290
|
}
|
|
4915
5291
|
await this.runCommand(`set(${this.displayKey(key)})`, () => this.client.set(normalizedKey, payload));
|
|
4916
5292
|
}
|
|
5293
|
+
/**
|
|
5294
|
+
* Deletes a key from Redis.
|
|
5295
|
+
*/
|
|
4917
5296
|
async delete(key) {
|
|
4918
5297
|
this.validateKey(key);
|
|
4919
5298
|
await this.runCommand(`delete(${this.displayKey(key)})`, () => this.client.del(this.withPrefix(key)));
|
|
4920
5299
|
}
|
|
5300
|
+
/**
|
|
5301
|
+
* Deletes multiple keys from Redis in batches.
|
|
5302
|
+
*/
|
|
4921
5303
|
async deleteMany(keys) {
|
|
4922
5304
|
if (keys.length === 0) {
|
|
4923
5305
|
return;
|
|
@@ -4930,11 +5312,17 @@ var RedisLayer = class {
|
|
|
4930
5312
|
() => this.client.del(...keys.map((key) => this.withPrefix(key)))
|
|
4931
5313
|
);
|
|
4932
5314
|
}
|
|
5315
|
+
/**
|
|
5316
|
+
* Returns true when the key exists in Redis.
|
|
5317
|
+
*/
|
|
4933
5318
|
async has(key) {
|
|
4934
5319
|
this.validateKey(key);
|
|
4935
5320
|
const exists = await this.runCommand(`has(${this.displayKey(key)})`, () => this.client.exists(this.withPrefix(key)));
|
|
4936
5321
|
return exists > 0;
|
|
4937
5322
|
}
|
|
5323
|
+
/**
|
|
5324
|
+
* Returns remaining Redis TTL in milliseconds, or null when absent or non-expiring.
|
|
5325
|
+
*/
|
|
4938
5326
|
async ttl(key) {
|
|
4939
5327
|
this.validateKey(key);
|
|
4940
5328
|
const remaining = await this.runCommand(
|
|
@@ -4946,6 +5334,9 @@ var RedisLayer = class {
|
|
|
4946
5334
|
}
|
|
4947
5335
|
return remaining;
|
|
4948
5336
|
}
|
|
5337
|
+
/**
|
|
5338
|
+
* Returns the number of keys under this layer's prefix.
|
|
5339
|
+
*/
|
|
4949
5340
|
async size() {
|
|
4950
5341
|
if (!this.prefix) {
|
|
4951
5342
|
return this.runCommand("dbsize()", () => this.client.dbsize());
|
|
@@ -4963,6 +5354,9 @@ var RedisLayer = class {
|
|
|
4963
5354
|
} while (cursor !== "0");
|
|
4964
5355
|
return count;
|
|
4965
5356
|
}
|
|
5357
|
+
/**
|
|
5358
|
+
* Runs a Redis ping command.
|
|
5359
|
+
*/
|
|
4966
5360
|
async ping() {
|
|
4967
5361
|
try {
|
|
4968
5362
|
return await this.runCommand("ping()", () => this.client.ping()) === "PONG";
|
|
@@ -4970,6 +5364,9 @@ var RedisLayer = class {
|
|
|
4970
5364
|
return false;
|
|
4971
5365
|
}
|
|
4972
5366
|
}
|
|
5367
|
+
/**
|
|
5368
|
+
* Disconnects the Redis client when `disconnectOnDispose` is enabled.
|
|
5369
|
+
*/
|
|
4973
5370
|
async dispose() {
|
|
4974
5371
|
if (this.disconnectOnDispose) {
|
|
4975
5372
|
this.client.disconnect();
|
|
@@ -5002,6 +5399,9 @@ var RedisLayer = class {
|
|
|
5002
5399
|
}
|
|
5003
5400
|
} while (cursor !== "0");
|
|
5004
5401
|
}
|
|
5402
|
+
/**
|
|
5403
|
+
* Returns keys under this layer's prefix without the prefix included.
|
|
5404
|
+
*/
|
|
5005
5405
|
async keys() {
|
|
5006
5406
|
const pattern = `${this.prefix}*`;
|
|
5007
5407
|
const keys = await this.scanKeys(pattern);
|
|
@@ -5010,6 +5410,9 @@ var RedisLayer = class {
|
|
|
5010
5410
|
}
|
|
5011
5411
|
return keys.map((key) => key.slice(this.prefix.length));
|
|
5012
5412
|
}
|
|
5413
|
+
/**
|
|
5414
|
+
* Visits keys under this layer's prefix without materializing all results.
|
|
5415
|
+
*/
|
|
5013
5416
|
async forEachKey(visitor) {
|
|
5014
5417
|
const pattern = `${this.prefix}*`;
|
|
5015
5418
|
let cursor = "0";
|
|
@@ -5367,6 +5770,9 @@ var DiskLayer = class {
|
|
|
5367
5770
|
maxEntryBytes;
|
|
5368
5771
|
protection;
|
|
5369
5772
|
writeQueue = Promise.resolve();
|
|
5773
|
+
/**
|
|
5774
|
+
* Creates a disk-backed cache layer.
|
|
5775
|
+
*/
|
|
5370
5776
|
constructor(options) {
|
|
5371
5777
|
this.directory = this.resolveDirectory(options.directory);
|
|
5372
5778
|
this.defaultTtl = options.ttl;
|
|
@@ -5379,9 +5785,15 @@ var DiskLayer = class {
|
|
|
5379
5785
|
signingKey: options.signingKey
|
|
5380
5786
|
});
|
|
5381
5787
|
}
|
|
5788
|
+
/**
|
|
5789
|
+
* Reads and unwraps a fresh value from disk.
|
|
5790
|
+
*/
|
|
5382
5791
|
async get(key) {
|
|
5383
5792
|
return unwrapStoredValue(await this.getEntry(key));
|
|
5384
5793
|
}
|
|
5794
|
+
/**
|
|
5795
|
+
* Reads the raw stored value or envelope from disk.
|
|
5796
|
+
*/
|
|
5385
5797
|
async getEntry(key) {
|
|
5386
5798
|
const filePath = this.keyToPath(key);
|
|
5387
5799
|
const raw = await this.readEntryFile(filePath);
|
|
@@ -5401,6 +5813,9 @@ var DiskLayer = class {
|
|
|
5401
5813
|
}
|
|
5402
5814
|
return entry.value;
|
|
5403
5815
|
}
|
|
5816
|
+
/**
|
|
5817
|
+
* Stores a value on disk using the provided TTL or layer default TTL.
|
|
5818
|
+
*/
|
|
5404
5819
|
async set(key, value, ttl = this.defaultTtl) {
|
|
5405
5820
|
await this.enqueueWrite(async () => {
|
|
5406
5821
|
await import_node_fs2.promises.mkdir(this.directory, { recursive: true });
|
|
@@ -5426,16 +5841,28 @@ var DiskLayer = class {
|
|
|
5426
5841
|
}
|
|
5427
5842
|
});
|
|
5428
5843
|
}
|
|
5844
|
+
/**
|
|
5845
|
+
* Reads many raw entries from disk.
|
|
5846
|
+
*/
|
|
5429
5847
|
async getMany(keys) {
|
|
5430
5848
|
return Promise.all(keys.map((key) => this.getEntry(key)));
|
|
5431
5849
|
}
|
|
5850
|
+
/**
|
|
5851
|
+
* Writes many entries to disk.
|
|
5852
|
+
*/
|
|
5432
5853
|
async setMany(entries) {
|
|
5433
5854
|
await Promise.all(entries.map((entry) => this.set(entry.key, entry.value, entry.ttl)));
|
|
5434
5855
|
}
|
|
5856
|
+
/**
|
|
5857
|
+
* Returns true when the key exists and has not expired.
|
|
5858
|
+
*/
|
|
5435
5859
|
async has(key) {
|
|
5436
5860
|
const value = await this.getEntry(key);
|
|
5437
5861
|
return value !== null;
|
|
5438
5862
|
}
|
|
5863
|
+
/**
|
|
5864
|
+
* Returns remaining TTL in milliseconds, or null when absent or non-expiring.
|
|
5865
|
+
*/
|
|
5439
5866
|
async ttl(key) {
|
|
5440
5867
|
const filePath = this.keyToPath(key);
|
|
5441
5868
|
const raw = await this.readEntryFile(filePath);
|
|
@@ -5458,14 +5885,23 @@ var DiskLayer = class {
|
|
|
5458
5885
|
}
|
|
5459
5886
|
return remaining;
|
|
5460
5887
|
}
|
|
5888
|
+
/**
|
|
5889
|
+
* Deletes a key from disk.
|
|
5890
|
+
*/
|
|
5461
5891
|
async delete(key) {
|
|
5462
5892
|
await this.enqueueWrite(() => this.safeDelete(this.keyToPath(key)));
|
|
5463
5893
|
}
|
|
5894
|
+
/**
|
|
5895
|
+
* Deletes multiple keys from disk.
|
|
5896
|
+
*/
|
|
5464
5897
|
async deleteMany(keys) {
|
|
5465
5898
|
await this.enqueueWrite(async () => {
|
|
5466
5899
|
await this.deletePathsWithConcurrency(keys.map((key) => this.keyToPath(key)));
|
|
5467
5900
|
});
|
|
5468
5901
|
}
|
|
5902
|
+
/**
|
|
5903
|
+
* Removes all cache entry files from this layer's directory.
|
|
5904
|
+
*/
|
|
5469
5905
|
async clear() {
|
|
5470
5906
|
await this.enqueueWrite(async () => {
|
|
5471
5907
|
let entries;
|
|
@@ -5490,11 +5926,17 @@ var DiskLayer = class {
|
|
|
5490
5926
|
});
|
|
5491
5927
|
return keys;
|
|
5492
5928
|
}
|
|
5929
|
+
/**
|
|
5930
|
+
* Visits all non-expired keys stored on disk.
|
|
5931
|
+
*/
|
|
5493
5932
|
async forEachKey(visitor) {
|
|
5494
5933
|
await this.scanEntries(async (entry) => {
|
|
5495
5934
|
await visitor(entry.key);
|
|
5496
5935
|
});
|
|
5497
5936
|
}
|
|
5937
|
+
/**
|
|
5938
|
+
* Returns the number of non-expired entries stored on disk.
|
|
5939
|
+
*/
|
|
5498
5940
|
async size() {
|
|
5499
5941
|
let count = 0;
|
|
5500
5942
|
await this.scanEntries(async () => {
|
|
@@ -5502,6 +5944,9 @@ var DiskLayer = class {
|
|
|
5502
5944
|
});
|
|
5503
5945
|
return count;
|
|
5504
5946
|
}
|
|
5947
|
+
/**
|
|
5948
|
+
* Verifies the cache directory can be created.
|
|
5949
|
+
*/
|
|
5505
5950
|
async ping() {
|
|
5506
5951
|
try {
|
|
5507
5952
|
await import_node_fs2.promises.mkdir(this.directory, { recursive: true });
|
|
@@ -5510,6 +5955,9 @@ var DiskLayer = class {
|
|
|
5510
5955
|
return false;
|
|
5511
5956
|
}
|
|
5512
5957
|
}
|
|
5958
|
+
/**
|
|
5959
|
+
* Reserved for interface compatibility; DiskLayer does not hold persistent handles.
|
|
5960
|
+
*/
|
|
5513
5961
|
async dispose() {
|
|
5514
5962
|
}
|
|
5515
5963
|
keyToPath(key) {
|
|
@@ -5713,6 +6161,9 @@ var MemcachedLayer = class {
|
|
|
5713
6161
|
client;
|
|
5714
6162
|
keyPrefix;
|
|
5715
6163
|
serializer;
|
|
6164
|
+
/**
|
|
6165
|
+
* Creates a Memcached cache layer using a compatible client.
|
|
6166
|
+
*/
|
|
5716
6167
|
constructor(options) {
|
|
5717
6168
|
this.client = options.client;
|
|
5718
6169
|
this.defaultTtl = options.ttl;
|
|
@@ -5720,9 +6171,15 @@ var MemcachedLayer = class {
|
|
|
5720
6171
|
this.keyPrefix = options.keyPrefix ?? "";
|
|
5721
6172
|
this.serializer = options.serializer ?? new JsonSerializer();
|
|
5722
6173
|
}
|
|
6174
|
+
/**
|
|
6175
|
+
* Reads and unwraps a fresh value from Memcached.
|
|
6176
|
+
*/
|
|
5723
6177
|
async get(key) {
|
|
5724
6178
|
return unwrapStoredValue(await this.getEntry(key));
|
|
5725
6179
|
}
|
|
6180
|
+
/**
|
|
6181
|
+
* Reads the raw stored value or envelope from Memcached.
|
|
6182
|
+
*/
|
|
5726
6183
|
async getEntry(key) {
|
|
5727
6184
|
this.validateKey(key);
|
|
5728
6185
|
const result = await this.client.get(this.withPrefix(key));
|
|
@@ -5735,9 +6192,15 @@ var MemcachedLayer = class {
|
|
|
5735
6192
|
return null;
|
|
5736
6193
|
}
|
|
5737
6194
|
}
|
|
6195
|
+
/**
|
|
6196
|
+
* Reads many raw entries from Memcached.
|
|
6197
|
+
*/
|
|
5738
6198
|
async getMany(keys) {
|
|
5739
6199
|
return Promise.all(keys.map((key) => this.getEntry(key)));
|
|
5740
6200
|
}
|
|
6201
|
+
/**
|
|
6202
|
+
* Stores a value in Memcached using the provided TTL or layer default TTL.
|
|
6203
|
+
*/
|
|
5741
6204
|
async set(key, value, ttl = this.defaultTtl) {
|
|
5742
6205
|
this.validateKey(key);
|
|
5743
6206
|
const payload = this.serializer.serialize(value);
|
|
@@ -5745,18 +6208,30 @@ var MemcachedLayer = class {
|
|
|
5745
6208
|
expires: ttl && ttl > 0 ? Math.ceil(ttl / 1e3) : void 0
|
|
5746
6209
|
});
|
|
5747
6210
|
}
|
|
6211
|
+
/**
|
|
6212
|
+
* Returns true when the key exists in Memcached.
|
|
6213
|
+
*/
|
|
5748
6214
|
async has(key) {
|
|
5749
6215
|
this.validateKey(key);
|
|
5750
6216
|
const result = await this.client.get(this.withPrefix(key));
|
|
5751
6217
|
return result !== null && result.value !== null;
|
|
5752
6218
|
}
|
|
6219
|
+
/**
|
|
6220
|
+
* Deletes a key from Memcached.
|
|
6221
|
+
*/
|
|
5753
6222
|
async delete(key) {
|
|
5754
6223
|
this.validateKey(key);
|
|
5755
6224
|
await this.client.delete(this.withPrefix(key));
|
|
5756
6225
|
}
|
|
6226
|
+
/**
|
|
6227
|
+
* Deletes multiple keys from Memcached.
|
|
6228
|
+
*/
|
|
5757
6229
|
async deleteMany(keys) {
|
|
5758
6230
|
await Promise.all(keys.map((key) => this.delete(key)));
|
|
5759
6231
|
}
|
|
6232
|
+
/**
|
|
6233
|
+
* Always throws because Memcached has no safe prefix clear primitive.
|
|
6234
|
+
*/
|
|
5760
6235
|
async clear() {
|
|
5761
6236
|
throw new Error(
|
|
5762
6237
|
"MemcachedLayer.clear() is not supported. Use a key prefix and rotate it to effectively invalidate all keys."
|
|
@@ -5784,9 +6259,15 @@ var MemcachedLayer = class {
|
|
|
5784
6259
|
// src/serialization/MsgpackSerializer.ts
|
|
5785
6260
|
var import_msgpack = require("@msgpack/msgpack");
|
|
5786
6261
|
var MsgpackSerializer = class {
|
|
6262
|
+
/**
|
|
6263
|
+
* Serializes a value to MessagePack bytes.
|
|
6264
|
+
*/
|
|
5787
6265
|
serialize(value) {
|
|
5788
6266
|
return Buffer.from((0, import_msgpack.encode)(value));
|
|
5789
6267
|
}
|
|
6268
|
+
/**
|
|
6269
|
+
* Decodes MessagePack bytes and sanitizes the result before returning it.
|
|
6270
|
+
*/
|
|
5790
6271
|
deserialize(payload) {
|
|
5791
6272
|
const normalized = Buffer.isBuffer(payload) ? payload : Buffer.from(payload, "latin1");
|
|
5792
6273
|
return sanitizeStructuredData((0, import_msgpack.decode)(normalized), {
|
|
@@ -5820,6 +6301,10 @@ var RedisSingleFlightCoordinator = class {
|
|
|
5820
6301
|
this.prefix = options.prefix ?? "layercache:singleflight";
|
|
5821
6302
|
this.commandTimeoutMs = this.normalizeCommandTimeoutMs(options.commandTimeoutMs);
|
|
5822
6303
|
}
|
|
6304
|
+
/**
|
|
6305
|
+
* Executes `worker` when this process acquires the Redis lock; otherwise runs
|
|
6306
|
+
* `waiter` while another process owns the work.
|
|
6307
|
+
*/
|
|
5823
6308
|
async execute(key, options, worker, waiter) {
|
|
5824
6309
|
const lockKey = `${this.prefix}:${encodeURIComponent(key)}`;
|
|
5825
6310
|
const token = (0, import_node_crypto5.randomUUID)();
|