layercache 1.2.5 → 1.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,19 +10,47 @@ function isStoredValueEnvelope(value) {
10
10
  if (v.kind !== "value" && v.kind !== "empty") {
11
11
  return false;
12
12
  }
13
- if (v.freshUntil !== null && typeof v.freshUntil !== "number") {
13
+ if (v.freshUntil !== null && (!Number.isFinite(v.freshUntil) || typeof v.freshUntil !== "number")) {
14
14
  return false;
15
15
  }
16
- if (v.staleUntil !== null && typeof v.staleUntil !== "number") {
16
+ if (v.staleUntil !== null && (!Number.isFinite(v.staleUntil) || typeof v.staleUntil !== "number")) {
17
17
  return false;
18
18
  }
19
- if (v.errorUntil !== null && typeof v.errorUntil !== "number") {
19
+ if (v.errorUntil !== null && (!Number.isFinite(v.errorUntil) || typeof v.errorUntil !== "number")) {
20
20
  return false;
21
21
  }
22
22
  const maxTimestamp = Date.now() + 10 * 365 * 24 * 60 * 60 * 1e3;
23
23
  if (typeof v.freshUntil === "number" && v.freshUntil > maxTimestamp) {
24
24
  return false;
25
25
  }
26
+ if (typeof v.staleUntil === "number" && v.staleUntil > maxTimestamp) {
27
+ return false;
28
+ }
29
+ if (typeof v.errorUntil === "number" && v.errorUntil > maxTimestamp) {
30
+ return false;
31
+ }
32
+ if (v.freshUntil === null && (v.staleUntil !== null || v.errorUntil !== null)) {
33
+ return false;
34
+ }
35
+ if (typeof v.freshUntil === "number" && typeof v.staleUntil === "number" && v.staleUntil < v.freshUntil) {
36
+ return false;
37
+ }
38
+ if (typeof v.freshUntil === "number" && typeof v.errorUntil === "number" && v.errorUntil < v.freshUntil) {
39
+ return false;
40
+ }
41
+ const maxTtlSeconds = 10 * 365 * 24 * 60 * 60;
42
+ if (!isValidEnvelopeTtlSeconds(v.freshTtlSeconds, maxTtlSeconds)) {
43
+ return false;
44
+ }
45
+ if (!isValidEnvelopeTtlSeconds(v.staleWhileRevalidateSeconds, maxTtlSeconds)) {
46
+ return false;
47
+ }
48
+ if (!isValidEnvelopeTtlSeconds(v.staleIfErrorSeconds, maxTtlSeconds)) {
49
+ return false;
50
+ }
51
+ if (v.freshTtlSeconds == null && (v.staleWhileRevalidateSeconds != null || v.staleIfErrorSeconds != null)) {
52
+ return false;
53
+ }
26
54
  return true;
27
55
  }
28
56
  function createStoredValueEnvelope(options) {
@@ -121,6 +149,12 @@ function normalizePositiveSeconds(value) {
121
149
  }
122
150
  return value;
123
151
  }
152
+ function isValidEnvelopeTtlSeconds(value, maxTtlSeconds) {
153
+ if (value == null) {
154
+ return true;
155
+ }
156
+ return typeof value === "number" && Number.isFinite(value) && value > 0 && value <= maxTtlSeconds;
157
+ }
124
158
 
125
159
  // src/invalidation/PatternMatcher.ts
126
160
  var PatternMatcher = class _PatternMatcher {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  PatternMatcher
3
- } from "./chunk-7V7XAB74.js";
3
+ } from "./chunk-4PPBOOXT.js";
4
4
 
5
5
  // src/invalidation/RedisTagIndex.ts
6
6
  var RedisTagIndex = class {
@@ -48,6 +48,17 @@ var RedisTagIndex = class {
48
48
  async keysForTag(tag) {
49
49
  return this.client.smembers(this.tagKeysKey(tag));
50
50
  }
51
+ async forEachKeyForTag(tag, visitor) {
52
+ let cursor = "0";
53
+ const tagKey = this.tagKeysKey(tag);
54
+ do {
55
+ const [nextCursor, keys] = await this.client.sscan(tagKey, cursor, "COUNT", this.scanCount);
56
+ cursor = nextCursor;
57
+ for (const key of keys) {
58
+ await visitor(key);
59
+ }
60
+ } while (cursor !== "0");
61
+ }
51
62
  async keysForPrefix(prefix) {
52
63
  const matches = [];
53
64
  for (const knownKeysKey of this.knownKeysKeys()) {
@@ -60,6 +71,20 @@ var RedisTagIndex = class {
60
71
  }
61
72
  return matches;
62
73
  }
74
+ async forEachKeyForPrefix(prefix, visitor) {
75
+ for (const knownKeysKey of this.knownKeysKeys()) {
76
+ let cursor = "0";
77
+ do {
78
+ const [nextCursor, keys] = await this.client.sscan(knownKeysKey, cursor, "COUNT", this.scanCount);
79
+ cursor = nextCursor;
80
+ for (const key of keys) {
81
+ if (key.startsWith(prefix)) {
82
+ await visitor(key);
83
+ }
84
+ }
85
+ } while (cursor !== "0");
86
+ }
87
+ }
63
88
  async tagsForKey(key) {
64
89
  return this.client.smembers(this.keyTagsKey(key));
65
90
  }
@@ -82,6 +107,27 @@ var RedisTagIndex = class {
82
107
  }
83
108
  return matches;
84
109
  }
110
+ async forEachKeyMatchingPattern(pattern, visitor) {
111
+ for (const knownKeysKey of this.knownKeysKeys()) {
112
+ let cursor = "0";
113
+ do {
114
+ const [nextCursor, keys] = await this.client.sscan(
115
+ knownKeysKey,
116
+ cursor,
117
+ "MATCH",
118
+ pattern,
119
+ "COUNT",
120
+ this.scanCount
121
+ );
122
+ cursor = nextCursor;
123
+ for (const key of keys) {
124
+ if (PatternMatcher.matches(pattern, key)) {
125
+ await visitor(key);
126
+ }
127
+ }
128
+ } while (cursor !== "0");
129
+ }
130
+ }
85
131
  async clear() {
86
132
  const indexKeys = await this.scanIndexKeys();
87
133
  if (indexKeys.length === 0) {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  unwrapStoredValue
3
- } from "./chunk-7V7XAB74.js";
3
+ } from "./chunk-4PPBOOXT.js";
4
4
 
5
5
  // src/layers/MemoryLayer.ts
6
6
  var MemoryLayer = class {
@@ -118,6 +118,12 @@ var MemoryLayer = class {
118
118
  this.pruneExpired();
119
119
  return [...this.entries.keys()];
120
120
  }
121
+ async forEachKey(visitor) {
122
+ this.pruneExpired();
123
+ for (const key of this.entries.keys()) {
124
+ await visitor(key);
125
+ }
126
+ }
121
127
  exportState() {
122
128
  this.pruneExpired();
123
129
  return [...this.entries.entries()].map(([key, entry]) => ({
@@ -226,6 +232,11 @@ var TagIndex = class {
226
232
  async keysForTag(tag) {
227
233
  return [...this.tagToKeys.get(tag) ?? /* @__PURE__ */ new Set()];
228
234
  }
235
+ async forEachKeyForTag(tag, visitor) {
236
+ for (const key of this.tagToKeys.get(tag) ?? /* @__PURE__ */ new Set()) {
237
+ await visitor(key);
238
+ }
239
+ }
229
240
  async keysForPrefix(prefix) {
230
241
  const node = this.findNode(prefix);
231
242
  if (!node) {
@@ -235,6 +246,13 @@ var TagIndex = class {
235
246
  this.collectFromNode(node, prefix, matches);
236
247
  return matches;
237
248
  }
249
+ async forEachKeyForPrefix(prefix, visitor) {
250
+ const node = this.findNode(prefix);
251
+ if (!node) {
252
+ return;
253
+ }
254
+ await this.visitFromNode(node, prefix, visitor);
255
+ }
238
256
  async tagsForKey(key) {
239
257
  return [...this.keyToTags.get(key) ?? /* @__PURE__ */ new Set()];
240
258
  }
@@ -243,6 +261,12 @@ var TagIndex = class {
243
261
  this.collectPatternMatches(this.root, "", pattern, 0, matches, /* @__PURE__ */ new Set(), 0);
244
262
  return [...matches];
245
263
  }
264
+ async forEachKeyMatchingPattern(pattern, visitor) {
265
+ const matches = await this.matchPattern(pattern);
266
+ for (const key of matches) {
267
+ await visitor(key);
268
+ }
269
+ }
246
270
  async clear() {
247
271
  this.tagToKeys.clear();
248
272
  this.keyToTags.clear();
@@ -292,6 +316,14 @@ var TagIndex = class {
292
316
  this.collectFromNode(child, `${prefix}${character}`, matches);
293
317
  }
294
318
  }
319
+ async visitFromNode(node, prefix, visitor) {
320
+ if (node.terminal) {
321
+ await visitor(prefix);
322
+ }
323
+ for (const [character, child] of node.children) {
324
+ await this.visitFromNode(child, `${prefix}${character}`, visitor);
325
+ }
326
+ }
295
327
  collectPatternMatches(node, prefix, pattern, patternIndex, matches, visited, depth) {
296
328
  if (depth > MAX_PATTERN_RECURSION_DEPTH) {
297
329
  return;
@@ -416,14 +448,17 @@ function createHonoCacheMiddleware(cache, options = {}) {
416
448
  await next();
417
449
  return;
418
450
  }
451
+ if (!options.keyResolver && options.allowPrivateCaching !== true) {
452
+ await next();
453
+ return;
454
+ }
419
455
  const rawPath = context.req.path ?? context.req.url ?? "/";
420
456
  const key = options.keyResolver ? options.keyResolver(context.req) : `${method}:${normalizeUrl(rawPath)}`;
421
457
  const cached = await cache.get(key, void 0, options);
422
458
  if (cached !== null) {
423
459
  context.header?.("x-cache", "HIT");
424
460
  context.header?.("content-type", "application/json; charset=utf-8");
425
- context.json(cached);
426
- return;
461
+ return context.json(cached);
427
462
  }
428
463
  const originalJson = context.json.bind(context);
429
464
  context.json = (body, status) => {
package/dist/cli.cjs CHANGED
@@ -48,19 +48,47 @@ function isStoredValueEnvelope(value) {
48
48
  if (v.kind !== "value" && v.kind !== "empty") {
49
49
  return false;
50
50
  }
51
- if (v.freshUntil !== null && typeof v.freshUntil !== "number") {
51
+ if (v.freshUntil !== null && (!Number.isFinite(v.freshUntil) || typeof v.freshUntil !== "number")) {
52
52
  return false;
53
53
  }
54
- if (v.staleUntil !== null && typeof v.staleUntil !== "number") {
54
+ if (v.staleUntil !== null && (!Number.isFinite(v.staleUntil) || typeof v.staleUntil !== "number")) {
55
55
  return false;
56
56
  }
57
- if (v.errorUntil !== null && typeof v.errorUntil !== "number") {
57
+ if (v.errorUntil !== null && (!Number.isFinite(v.errorUntil) || typeof v.errorUntil !== "number")) {
58
58
  return false;
59
59
  }
60
60
  const maxTimestamp = Date.now() + 10 * 365 * 24 * 60 * 60 * 1e3;
61
61
  if (typeof v.freshUntil === "number" && v.freshUntil > maxTimestamp) {
62
62
  return false;
63
63
  }
64
+ if (typeof v.staleUntil === "number" && v.staleUntil > maxTimestamp) {
65
+ return false;
66
+ }
67
+ if (typeof v.errorUntil === "number" && v.errorUntil > maxTimestamp) {
68
+ return false;
69
+ }
70
+ if (v.freshUntil === null && (v.staleUntil !== null || v.errorUntil !== null)) {
71
+ return false;
72
+ }
73
+ if (typeof v.freshUntil === "number" && typeof v.staleUntil === "number" && v.staleUntil < v.freshUntil) {
74
+ return false;
75
+ }
76
+ if (typeof v.freshUntil === "number" && typeof v.errorUntil === "number" && v.errorUntil < v.freshUntil) {
77
+ return false;
78
+ }
79
+ const maxTtlSeconds = 10 * 365 * 24 * 60 * 60;
80
+ if (!isValidEnvelopeTtlSeconds(v.freshTtlSeconds, maxTtlSeconds)) {
81
+ return false;
82
+ }
83
+ if (!isValidEnvelopeTtlSeconds(v.staleWhileRevalidateSeconds, maxTtlSeconds)) {
84
+ return false;
85
+ }
86
+ if (!isValidEnvelopeTtlSeconds(v.staleIfErrorSeconds, maxTtlSeconds)) {
87
+ return false;
88
+ }
89
+ if (v.freshTtlSeconds == null && (v.staleWhileRevalidateSeconds != null || v.staleIfErrorSeconds != null)) {
90
+ return false;
91
+ }
64
92
  return true;
65
93
  }
66
94
  function resolveStoredValue(stored, now = Date.now()) {
@@ -87,6 +115,12 @@ function unwrapStoredValue(stored) {
87
115
  }
88
116
  return stored.value ?? null;
89
117
  }
118
+ function isValidEnvelopeTtlSeconds(value, maxTtlSeconds) {
119
+ if (value == null) {
120
+ return true;
121
+ }
122
+ return typeof value === "number" && Number.isFinite(value) && value > 0 && value <= maxTtlSeconds;
123
+ }
90
124
 
91
125
  // src/invalidation/PatternMatcher.ts
92
126
  var PatternMatcher = class _PatternMatcher {
@@ -182,6 +216,17 @@ var RedisTagIndex = class {
182
216
  async keysForTag(tag) {
183
217
  return this.client.smembers(this.tagKeysKey(tag));
184
218
  }
219
+ async forEachKeyForTag(tag, visitor) {
220
+ let cursor = "0";
221
+ const tagKey = this.tagKeysKey(tag);
222
+ do {
223
+ const [nextCursor, keys] = await this.client.sscan(tagKey, cursor, "COUNT", this.scanCount);
224
+ cursor = nextCursor;
225
+ for (const key of keys) {
226
+ await visitor(key);
227
+ }
228
+ } while (cursor !== "0");
229
+ }
185
230
  async keysForPrefix(prefix) {
186
231
  const matches = [];
187
232
  for (const knownKeysKey of this.knownKeysKeys()) {
@@ -194,6 +239,20 @@ var RedisTagIndex = class {
194
239
  }
195
240
  return matches;
196
241
  }
242
+ async forEachKeyForPrefix(prefix, visitor) {
243
+ for (const knownKeysKey of this.knownKeysKeys()) {
244
+ let cursor = "0";
245
+ do {
246
+ const [nextCursor, keys] = await this.client.sscan(knownKeysKey, cursor, "COUNT", this.scanCount);
247
+ cursor = nextCursor;
248
+ for (const key of keys) {
249
+ if (key.startsWith(prefix)) {
250
+ await visitor(key);
251
+ }
252
+ }
253
+ } while (cursor !== "0");
254
+ }
255
+ }
197
256
  async tagsForKey(key) {
198
257
  return this.client.smembers(this.keyTagsKey(key));
199
258
  }
@@ -216,6 +275,27 @@ var RedisTagIndex = class {
216
275
  }
217
276
  return matches;
218
277
  }
278
+ async forEachKeyMatchingPattern(pattern, visitor) {
279
+ for (const knownKeysKey of this.knownKeysKeys()) {
280
+ let cursor = "0";
281
+ do {
282
+ const [nextCursor, keys] = await this.client.sscan(
283
+ knownKeysKey,
284
+ cursor,
285
+ "MATCH",
286
+ pattern,
287
+ "COUNT",
288
+ this.scanCount
289
+ );
290
+ cursor = nextCursor;
291
+ for (const key of keys) {
292
+ if (PatternMatcher.matches(pattern, key)) {
293
+ await visitor(key);
294
+ }
295
+ }
296
+ } while (cursor !== "0");
297
+ }
298
+ }
219
299
  async clear() {
220
300
  const indexKeys = await this.scanIndexKeys();
221
301
  if (indexKeys.length === 0) {
package/dist/cli.js CHANGED
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  RedisTagIndex
4
- } from "./chunk-QHWG7QS5.js";
4
+ } from "./chunk-BQLL6IM5.js";
5
5
  import {
6
6
  isStoredValueEnvelope,
7
7
  resolveStoredValue
8
- } from "./chunk-7V7XAB74.js";
8
+ } from "./chunk-4PPBOOXT.js";
9
9
 
10
10
  // src/cli.ts
11
11
  import Redis from "ioredis";
@@ -66,6 +66,7 @@ interface CacheLayer {
66
66
  clear(): Promise<void>;
67
67
  deleteMany?(keys: string[]): Promise<void>;
68
68
  keys?(): Promise<string[]>;
69
+ forEachKey?(visitor: (key: string) => void | Promise<void>): Promise<void>;
69
70
  ping?(): Promise<boolean>;
70
71
  dispose?(): Promise<void>;
71
72
  /**
@@ -140,10 +141,13 @@ interface CacheTagIndex {
140
141
  track(key: string, tags: string[]): Promise<void>;
141
142
  remove(key: string): Promise<void>;
142
143
  keysForTag(tag: string): Promise<string[]>;
144
+ forEachKeyForTag?(tag: string, visitor: (key: string) => void | Promise<void>): Promise<void>;
143
145
  keysForPrefix?(prefix: string): Promise<string[]>;
146
+ forEachKeyForPrefix?(prefix: string, visitor: (key: string) => void | Promise<void>): Promise<void>;
144
147
  /** Returns the tags associated with a specific key, or an empty array. */
145
148
  tagsForKey?(key: string): Promise<string[]>;
146
149
  matchPattern(pattern: string): Promise<string[]>;
150
+ forEachKeyMatchingPattern?(pattern: string, visitor: (key: string) => void | Promise<void>): Promise<void>;
147
151
  clear(): Promise<void>;
148
152
  }
149
153
  interface CacheLayerSetManyEntry {
@@ -203,6 +207,9 @@ interface CacheStackOptions {
203
207
  singleFlightPollMs?: number;
204
208
  singleFlightRenewIntervalMs?: number;
205
209
  snapshotBaseDir?: string | false;
210
+ snapshotMaxBytes?: number | false;
211
+ snapshotMaxEntries?: number | false;
212
+ invalidationMaxKeys?: number | false;
206
213
  /**
207
214
  * Maximum number of entries in `accessProfiles` and `circuitBreakers` maps
208
215
  * before the oldest entries are pruned. Prevents unbounded memory growth.
@@ -396,6 +403,7 @@ declare class MemoryLayer implements CacheLayer {
396
403
  ping(): Promise<boolean>;
397
404
  dispose(): Promise<void>;
398
405
  keys(): Promise<string[]>;
406
+ forEachKey(visitor: (key: string) => void | Promise<void>): Promise<void>;
399
407
  exportState(): MemoryLayerSnapshotEntry[];
400
408
  importState(entries: MemoryLayerSnapshotEntry[]): void;
401
409
  private evict;
@@ -437,14 +445,18 @@ declare class TagIndex implements CacheTagIndex {
437
445
  track(key: string, tags: string[]): Promise<void>;
438
446
  remove(key: string): Promise<void>;
439
447
  keysForTag(tag: string): Promise<string[]>;
448
+ forEachKeyForTag(tag: string, visitor: (key: string) => void | Promise<void>): Promise<void>;
440
449
  keysForPrefix(prefix: string): Promise<string[]>;
450
+ forEachKeyForPrefix(prefix: string, visitor: (key: string) => void | Promise<void>): Promise<void>;
441
451
  tagsForKey(key: string): Promise<string[]>;
442
452
  matchPattern(pattern: string): Promise<string[]>;
453
+ forEachKeyMatchingPattern(pattern: string, visitor: (key: string) => void | Promise<void>): Promise<void>;
443
454
  clear(): Promise<void>;
444
455
  private createTrieNode;
445
456
  private insertKnownKey;
446
457
  private findNode;
447
458
  private collectFromNode;
459
+ private visitFromNode;
448
460
  private collectPatternMatches;
449
461
  private pruneKnownKeysIfNeeded;
450
462
  private removeKey;
@@ -494,6 +506,10 @@ declare class CacheNamespace {
494
506
  */
495
507
  namespace(childPrefix: string): CacheNamespace;
496
508
  qualify(key: string): string;
509
+ private qualifyTag;
510
+ private qualifyGetOptions;
511
+ private qualifyWrapOptions;
512
+ private qualifyWriteOptions;
497
513
  private trackMetrics;
498
514
  private getMetricsMutex;
499
515
  }
@@ -523,13 +539,10 @@ declare class CacheStack extends EventEmitter {
523
539
  private readonly snapshotSerializer;
524
540
  private readonly backgroundRefreshes;
525
541
  private readonly layerDegradedUntil;
542
+ private readonly maintenance;
526
543
  private readonly ttlResolver;
527
544
  private readonly circuitBreakerManager;
528
545
  private currentGeneration?;
529
- private readonly writeBehindQueue;
530
- private writeBehindTimer?;
531
- private writeBehindFlushPromise?;
532
- private generationCleanupPromise?;
533
546
  private isDisconnecting;
534
547
  private disconnectPromise?;
535
548
  constructor(layers: CacheLayer[], options?: CacheStackOptions);
@@ -641,35 +654,23 @@ declare class CacheStack extends EventEmitter {
641
654
  private sleep;
642
655
  private withTimeout;
643
656
  private shouldBroadcastL1Invalidation;
644
- private shouldCleanupGenerations;
645
- private generationCleanupBatchSize;
646
657
  private scheduleGenerationCleanup;
647
658
  private cleanupGeneration;
648
659
  private initializeWriteBehind;
649
660
  private shouldWriteBehind;
650
661
  private enqueueWriteBehind;
651
662
  private flushWriteBehindQueue;
663
+ private runWriteBehindBatch;
652
664
  private buildLayerSetEntry;
653
665
  private intersectKeys;
654
666
  private qualifyKey;
655
667
  private qualifyPattern;
656
668
  private stripQualifiedKey;
657
- private generationPrefix;
658
669
  private deleteKeysFromLayers;
659
670
  private validateConfiguration;
660
671
  private validateWriteOptions;
661
- private validateLayerNumberOption;
662
- private validatePositiveNumber;
663
- private validateRateLimitOptions;
664
- private validateNonNegativeNumber;
665
- private validateCacheKey;
666
- private validatePattern;
667
- private validateTtlPolicy;
668
672
  private assertActive;
669
673
  private awaitStartup;
670
- private serializeOptions;
671
- private validateAdaptiveTtlOptions;
672
- private validateCircuitBreakerOptions;
673
674
  private applyFreshReadPolicies;
674
675
  private shouldSkipLayer;
675
676
  private handleLayerFailure;
@@ -678,11 +679,14 @@ declare class CacheStack extends EventEmitter {
678
679
  private recordCircuitFailure;
679
680
  private isNegativeStoredValue;
680
681
  private emitError;
681
- private serializeKeyPart;
682
682
  private isCacheSnapshotEntries;
683
683
  private sanitizeSnapshotValue;
684
- private validateSnapshotFilePath;
685
- private normalizeForSerialization;
684
+ private snapshotMaxBytes;
685
+ private snapshotMaxEntries;
686
+ private invalidationMaxKeys;
687
+ private collectKeysForTag;
688
+ private assertWithinInvalidationKeyLimit;
689
+ private visitExportEntries;
686
690
  }
687
691
 
688
692
  interface HonoLikeRequest {
@@ -690,6 +694,8 @@ interface HonoLikeRequest {
690
694
  url?: string;
691
695
  path?: string;
692
696
  query?: Record<string, unknown>;
697
+ headers?: Headers | Record<string, unknown>;
698
+ header?: (name: string) => string | undefined;
693
699
  }
694
700
  interface HonoLikeContext {
695
701
  req: HonoLikeRequest;
@@ -699,7 +705,8 @@ interface HonoLikeContext {
699
705
  interface HonoCacheMiddlewareOptions extends CacheGetOptions {
700
706
  keyResolver?: (request: HonoLikeRequest) => string;
701
707
  methods?: string[];
708
+ allowPrivateCaching?: boolean;
702
709
  }
703
- declare function createHonoCacheMiddleware(cache: CacheStack, options?: HonoCacheMiddlewareOptions): (context: HonoLikeContext, next: () => Promise<void>) => Promise<void>;
710
+ declare function createHonoCacheMiddleware(cache: CacheStack, options?: HonoCacheMiddlewareOptions): (context: HonoLikeContext, next: () => Promise<void>) => Promise<unknown>;
704
711
 
705
712
  export { type CacheStatsSnapshot as A, type CacheTtlPolicy as B, type CacheLogger as C, type CacheTtlPolicyContext as D, type CacheWarmEntry as E, type CacheWarmOptions as F, type CacheWarmProgress as G, type CacheWriteBehindOptions as H, type InvalidationBus as I, type CacheWriteOptions as J, type EvictionPolicy as K, type LayerTtlMap as L, MemoryLayer as M, type MemoryLayerOptions as N, type MemoryLayerSnapshotEntry as O, PatternMatcher as P, createHonoCacheMiddleware as Q, TagIndex as T, type InvalidationMessage as a, type CacheTagIndex as b, CacheStack as c, type CacheWrapOptions as d, type CacheGetOptions as e, type CacheLayer as f, type CacheSerializer as g, type CacheLayerSetManyEntry as h, type CacheSingleFlightCoordinator as i, type CacheSingleFlightExecutionOptions as j, type CacheAdaptiveTtlOptions as k, type CacheCircuitBreakerOptions as l, type CacheDegradationOptions as m, type CacheHealthCheckResult as n, type CacheHitRateSnapshot as o, type CacheInspectResult as p, type CacheLayerLatency as q, type CacheMGetEntry as r, type CacheMSetEntry as s, type CacheMetricsSnapshot as t, CacheMissError as u, CacheNamespace as v, type CacheRateLimitOptions as w, type CacheSnapshotEntry as x, type CacheStackEvents as y, type CacheStackOptions as z };
@@ -66,6 +66,7 @@ interface CacheLayer {
66
66
  clear(): Promise<void>;
67
67
  deleteMany?(keys: string[]): Promise<void>;
68
68
  keys?(): Promise<string[]>;
69
+ forEachKey?(visitor: (key: string) => void | Promise<void>): Promise<void>;
69
70
  ping?(): Promise<boolean>;
70
71
  dispose?(): Promise<void>;
71
72
  /**
@@ -140,10 +141,13 @@ interface CacheTagIndex {
140
141
  track(key: string, tags: string[]): Promise<void>;
141
142
  remove(key: string): Promise<void>;
142
143
  keysForTag(tag: string): Promise<string[]>;
144
+ forEachKeyForTag?(tag: string, visitor: (key: string) => void | Promise<void>): Promise<void>;
143
145
  keysForPrefix?(prefix: string): Promise<string[]>;
146
+ forEachKeyForPrefix?(prefix: string, visitor: (key: string) => void | Promise<void>): Promise<void>;
144
147
  /** Returns the tags associated with a specific key, or an empty array. */
145
148
  tagsForKey?(key: string): Promise<string[]>;
146
149
  matchPattern(pattern: string): Promise<string[]>;
150
+ forEachKeyMatchingPattern?(pattern: string, visitor: (key: string) => void | Promise<void>): Promise<void>;
147
151
  clear(): Promise<void>;
148
152
  }
149
153
  interface CacheLayerSetManyEntry {
@@ -203,6 +207,9 @@ interface CacheStackOptions {
203
207
  singleFlightPollMs?: number;
204
208
  singleFlightRenewIntervalMs?: number;
205
209
  snapshotBaseDir?: string | false;
210
+ snapshotMaxBytes?: number | false;
211
+ snapshotMaxEntries?: number | false;
212
+ invalidationMaxKeys?: number | false;
206
213
  /**
207
214
  * Maximum number of entries in `accessProfiles` and `circuitBreakers` maps
208
215
  * before the oldest entries are pruned. Prevents unbounded memory growth.
@@ -396,6 +403,7 @@ declare class MemoryLayer implements CacheLayer {
396
403
  ping(): Promise<boolean>;
397
404
  dispose(): Promise<void>;
398
405
  keys(): Promise<string[]>;
406
+ forEachKey(visitor: (key: string) => void | Promise<void>): Promise<void>;
399
407
  exportState(): MemoryLayerSnapshotEntry[];
400
408
  importState(entries: MemoryLayerSnapshotEntry[]): void;
401
409
  private evict;
@@ -437,14 +445,18 @@ declare class TagIndex implements CacheTagIndex {
437
445
  track(key: string, tags: string[]): Promise<void>;
438
446
  remove(key: string): Promise<void>;
439
447
  keysForTag(tag: string): Promise<string[]>;
448
+ forEachKeyForTag(tag: string, visitor: (key: string) => void | Promise<void>): Promise<void>;
440
449
  keysForPrefix(prefix: string): Promise<string[]>;
450
+ forEachKeyForPrefix(prefix: string, visitor: (key: string) => void | Promise<void>): Promise<void>;
441
451
  tagsForKey(key: string): Promise<string[]>;
442
452
  matchPattern(pattern: string): Promise<string[]>;
453
+ forEachKeyMatchingPattern(pattern: string, visitor: (key: string) => void | Promise<void>): Promise<void>;
443
454
  clear(): Promise<void>;
444
455
  private createTrieNode;
445
456
  private insertKnownKey;
446
457
  private findNode;
447
458
  private collectFromNode;
459
+ private visitFromNode;
448
460
  private collectPatternMatches;
449
461
  private pruneKnownKeysIfNeeded;
450
462
  private removeKey;
@@ -494,6 +506,10 @@ declare class CacheNamespace {
494
506
  */
495
507
  namespace(childPrefix: string): CacheNamespace;
496
508
  qualify(key: string): string;
509
+ private qualifyTag;
510
+ private qualifyGetOptions;
511
+ private qualifyWrapOptions;
512
+ private qualifyWriteOptions;
497
513
  private trackMetrics;
498
514
  private getMetricsMutex;
499
515
  }
@@ -523,13 +539,10 @@ declare class CacheStack extends EventEmitter {
523
539
  private readonly snapshotSerializer;
524
540
  private readonly backgroundRefreshes;
525
541
  private readonly layerDegradedUntil;
542
+ private readonly maintenance;
526
543
  private readonly ttlResolver;
527
544
  private readonly circuitBreakerManager;
528
545
  private currentGeneration?;
529
- private readonly writeBehindQueue;
530
- private writeBehindTimer?;
531
- private writeBehindFlushPromise?;
532
- private generationCleanupPromise?;
533
546
  private isDisconnecting;
534
547
  private disconnectPromise?;
535
548
  constructor(layers: CacheLayer[], options?: CacheStackOptions);
@@ -641,35 +654,23 @@ declare class CacheStack extends EventEmitter {
641
654
  private sleep;
642
655
  private withTimeout;
643
656
  private shouldBroadcastL1Invalidation;
644
- private shouldCleanupGenerations;
645
- private generationCleanupBatchSize;
646
657
  private scheduleGenerationCleanup;
647
658
  private cleanupGeneration;
648
659
  private initializeWriteBehind;
649
660
  private shouldWriteBehind;
650
661
  private enqueueWriteBehind;
651
662
  private flushWriteBehindQueue;
663
+ private runWriteBehindBatch;
652
664
  private buildLayerSetEntry;
653
665
  private intersectKeys;
654
666
  private qualifyKey;
655
667
  private qualifyPattern;
656
668
  private stripQualifiedKey;
657
- private generationPrefix;
658
669
  private deleteKeysFromLayers;
659
670
  private validateConfiguration;
660
671
  private validateWriteOptions;
661
- private validateLayerNumberOption;
662
- private validatePositiveNumber;
663
- private validateRateLimitOptions;
664
- private validateNonNegativeNumber;
665
- private validateCacheKey;
666
- private validatePattern;
667
- private validateTtlPolicy;
668
672
  private assertActive;
669
673
  private awaitStartup;
670
- private serializeOptions;
671
- private validateAdaptiveTtlOptions;
672
- private validateCircuitBreakerOptions;
673
674
  private applyFreshReadPolicies;
674
675
  private shouldSkipLayer;
675
676
  private handleLayerFailure;
@@ -678,11 +679,14 @@ declare class CacheStack extends EventEmitter {
678
679
  private recordCircuitFailure;
679
680
  private isNegativeStoredValue;
680
681
  private emitError;
681
- private serializeKeyPart;
682
682
  private isCacheSnapshotEntries;
683
683
  private sanitizeSnapshotValue;
684
- private validateSnapshotFilePath;
685
- private normalizeForSerialization;
684
+ private snapshotMaxBytes;
685
+ private snapshotMaxEntries;
686
+ private invalidationMaxKeys;
687
+ private collectKeysForTag;
688
+ private assertWithinInvalidationKeyLimit;
689
+ private visitExportEntries;
686
690
  }
687
691
 
688
692
  interface HonoLikeRequest {
@@ -690,6 +694,8 @@ interface HonoLikeRequest {
690
694
  url?: string;
691
695
  path?: string;
692
696
  query?: Record<string, unknown>;
697
+ headers?: Headers | Record<string, unknown>;
698
+ header?: (name: string) => string | undefined;
693
699
  }
694
700
  interface HonoLikeContext {
695
701
  req: HonoLikeRequest;
@@ -699,7 +705,8 @@ interface HonoLikeContext {
699
705
  interface HonoCacheMiddlewareOptions extends CacheGetOptions {
700
706
  keyResolver?: (request: HonoLikeRequest) => string;
701
707
  methods?: string[];
708
+ allowPrivateCaching?: boolean;
702
709
  }
703
- declare function createHonoCacheMiddleware(cache: CacheStack, options?: HonoCacheMiddlewareOptions): (context: HonoLikeContext, next: () => Promise<void>) => Promise<void>;
710
+ declare function createHonoCacheMiddleware(cache: CacheStack, options?: HonoCacheMiddlewareOptions): (context: HonoLikeContext, next: () => Promise<void>) => Promise<unknown>;
704
711
 
705
712
  export { type CacheStatsSnapshot as A, type CacheTtlPolicy as B, type CacheLogger as C, type CacheTtlPolicyContext as D, type CacheWarmEntry as E, type CacheWarmOptions as F, type CacheWarmProgress as G, type CacheWriteBehindOptions as H, type InvalidationBus as I, type CacheWriteOptions as J, type EvictionPolicy as K, type LayerTtlMap as L, MemoryLayer as M, type MemoryLayerOptions as N, type MemoryLayerSnapshotEntry as O, PatternMatcher as P, createHonoCacheMiddleware as Q, TagIndex as T, type InvalidationMessage as a, type CacheTagIndex as b, CacheStack as c, type CacheWrapOptions as d, type CacheGetOptions as e, type CacheLayer as f, type CacheSerializer as g, type CacheLayerSetManyEntry as h, type CacheSingleFlightCoordinator as i, type CacheSingleFlightExecutionOptions as j, type CacheAdaptiveTtlOptions as k, type CacheCircuitBreakerOptions as l, type CacheDegradationOptions as m, type CacheHealthCheckResult as n, type CacheHitRateSnapshot as o, type CacheInspectResult as p, type CacheLayerLatency as q, type CacheMGetEntry as r, type CacheMSetEntry as s, type CacheMetricsSnapshot as t, CacheMissError as u, CacheNamespace as v, type CacheRateLimitOptions as w, type CacheSnapshotEntry as x, type CacheStackEvents as y, type CacheStackOptions as z };