layercache 1.4.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/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)();