layercache 1.2.1 → 1.2.3

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/cli.cjs CHANGED
@@ -38,7 +38,30 @@ var import_ioredis = __toESM(require("ioredis"), 1);
38
38
 
39
39
  // src/internal/StoredValue.ts
40
40
  function isStoredValueEnvelope(value) {
41
- return typeof value === "object" && value !== null && "__layercache" in value && value.__layercache === 1 && "kind" in value;
41
+ if (typeof value !== "object" || value === null) {
42
+ return false;
43
+ }
44
+ const v = value;
45
+ if (v.__layercache !== 1) {
46
+ return false;
47
+ }
48
+ if (v.kind !== "value" && v.kind !== "empty") {
49
+ return false;
50
+ }
51
+ if (v.freshUntil !== null && typeof v.freshUntil !== "number") {
52
+ return false;
53
+ }
54
+ if (v.staleUntil !== null && typeof v.staleUntil !== "number") {
55
+ return false;
56
+ }
57
+ if (v.errorUntil !== null && typeof v.errorUntil !== "number") {
58
+ return false;
59
+ }
60
+ const maxTimestamp = Date.now() + 10 * 365 * 24 * 60 * 60 * 1e3;
61
+ if (typeof v.freshUntil === "number" && v.freshUntil > maxTimestamp) {
62
+ return false;
63
+ }
64
+ return true;
42
65
  }
43
66
  function resolveStoredValue(stored, now = Date.now()) {
44
67
  if (!isStoredValueEnvelope(stored)) {
@@ -118,19 +141,21 @@ var RedisTagIndex = class {
118
141
  client;
119
142
  prefix;
120
143
  scanCount;
144
+ knownKeysShards;
121
145
  constructor(options) {
122
146
  this.client = options.client;
123
147
  this.prefix = options.prefix ?? "layercache:tag-index";
124
148
  this.scanCount = options.scanCount ?? 100;
149
+ this.knownKeysShards = normalizeKnownKeysShards(options.knownKeysShards);
125
150
  }
126
151
  async touch(key) {
127
- await this.client.sadd(this.knownKeysKey(), key);
152
+ await this.client.sadd(this.knownKeysKeyFor(key), key);
128
153
  }
129
154
  async track(key, tags) {
130
155
  const keyTagsKey = this.keyTagsKey(key);
131
156
  const existingTags = await this.client.smembers(keyTagsKey);
132
157
  const pipeline = this.client.pipeline();
133
- pipeline.sadd(this.knownKeysKey(), key);
158
+ pipeline.sadd(this.knownKeysKeyFor(key), key);
134
159
  for (const tag of existingTags) {
135
160
  pipeline.srem(this.tagKeysKey(tag), key);
136
161
  }
@@ -147,7 +172,7 @@ var RedisTagIndex = class {
147
172
  const keyTagsKey = this.keyTagsKey(key);
148
173
  const existingTags = await this.client.smembers(keyTagsKey);
149
174
  const pipeline = this.client.pipeline();
150
- pipeline.srem(this.knownKeysKey(), key);
175
+ pipeline.srem(this.knownKeysKeyFor(key), key);
151
176
  pipeline.del(keyTagsKey);
152
177
  for (const tag of existingTags) {
153
178
  pipeline.srem(this.tagKeysKey(tag), key);
@@ -159,12 +184,14 @@ var RedisTagIndex = class {
159
184
  }
160
185
  async keysForPrefix(prefix) {
161
186
  const matches = [];
162
- let cursor = "0";
163
- do {
164
- const [nextCursor, keys] = await this.client.sscan(this.knownKeysKey(), cursor, "COUNT", this.scanCount);
165
- cursor = nextCursor;
166
- matches.push(...keys.filter((key) => key.startsWith(prefix)));
167
- } while (cursor !== "0");
187
+ for (const knownKeysKey of this.knownKeysKeys()) {
188
+ let cursor = "0";
189
+ do {
190
+ const [nextCursor, keys] = await this.client.sscan(knownKeysKey, cursor, "COUNT", this.scanCount);
191
+ cursor = nextCursor;
192
+ matches.push(...keys.filter((key) => key.startsWith(prefix)));
193
+ } while (cursor !== "0");
194
+ }
168
195
  return matches;
169
196
  }
170
197
  async tagsForKey(key) {
@@ -172,19 +199,21 @@ var RedisTagIndex = class {
172
199
  }
173
200
  async matchPattern(pattern) {
174
201
  const matches = [];
175
- let cursor = "0";
176
- do {
177
- const [nextCursor, keys] = await this.client.sscan(
178
- this.knownKeysKey(),
179
- cursor,
180
- "MATCH",
181
- pattern,
182
- "COUNT",
183
- this.scanCount
184
- );
185
- cursor = nextCursor;
186
- matches.push(...keys.filter((key) => PatternMatcher.matches(pattern, key)));
187
- } while (cursor !== "0");
202
+ for (const knownKeysKey of this.knownKeysKeys()) {
203
+ let cursor = "0";
204
+ do {
205
+ const [nextCursor, keys] = await this.client.sscan(
206
+ knownKeysKey,
207
+ cursor,
208
+ "MATCH",
209
+ pattern,
210
+ "COUNT",
211
+ this.scanCount
212
+ );
213
+ cursor = nextCursor;
214
+ matches.push(...keys.filter((key) => PatternMatcher.matches(pattern, key)));
215
+ } while (cursor !== "0");
216
+ }
188
217
  return matches;
189
218
  }
190
219
  async clear() {
@@ -205,8 +234,17 @@ var RedisTagIndex = class {
205
234
  } while (cursor !== "0");
206
235
  return matches;
207
236
  }
208
- knownKeysKey() {
209
- return `${this.prefix}:keys`;
237
+ knownKeysKeyFor(key) {
238
+ if (this.knownKeysShards === 1) {
239
+ return `${this.prefix}:keys`;
240
+ }
241
+ return `${this.prefix}:keys:${simpleHash(key) % this.knownKeysShards}`;
242
+ }
243
+ knownKeysKeys() {
244
+ if (this.knownKeysShards === 1) {
245
+ return [`${this.prefix}:keys`];
246
+ }
247
+ return Array.from({ length: this.knownKeysShards }, (_, index) => `${this.prefix}:keys:${index}`);
210
248
  }
211
249
  keyTagsKey(key) {
212
250
  return `${this.prefix}:key:${encodeURIComponent(key)}`;
@@ -215,6 +253,22 @@ var RedisTagIndex = class {
215
253
  return `${this.prefix}:tag:${encodeURIComponent(tag)}`;
216
254
  }
217
255
  };
256
+ function normalizeKnownKeysShards(value) {
257
+ if (value === void 0) {
258
+ return 1;
259
+ }
260
+ if (!Number.isInteger(value) || value <= 0) {
261
+ throw new Error("RedisTagIndex.knownKeysShards must be a positive integer.");
262
+ }
263
+ return value;
264
+ }
265
+ function simpleHash(value) {
266
+ let hash = 0;
267
+ for (let index = 0; index < value.length; index += 1) {
268
+ hash = hash * 31 + value.charCodeAt(index) >>> 0;
269
+ }
270
+ return hash;
271
+ }
218
272
 
219
273
  // src/cli.ts
220
274
  var CONNECT_TIMEOUT_MS = 5e3;
@@ -228,7 +282,7 @@ async function main(argv = process.argv.slice(2)) {
228
282
  const redisUrl = validateRedisUrl(args.redisUrl);
229
283
  if (!redisUrl) {
230
284
  process.stderr.write(
231
- `Error: invalid Redis URL "${args.redisUrl}". Expected format: redis://[user:password@]host[:port][/db]
285
+ `Error: invalid Redis URL "${maskRedisUrl(args.redisUrl)}". Expected format: redis://[user:password@]host[:port][/db]
232
286
  `
233
287
  );
234
288
  process.exitCode = 1;
@@ -242,7 +296,7 @@ async function main(argv = process.argv.slice(2)) {
242
296
  try {
243
297
  await redis.connect().catch((error) => {
244
298
  const message = error instanceof Error ? error.message : String(error);
245
- throw new Error(`Failed to connect to Redis at "${redisUrl}": ${message}`);
299
+ throw new Error(`Failed to connect to Redis at "${maskRedisUrl(redisUrl)}": ${message}`);
246
300
  });
247
301
  if (args.command === "stats") {
248
302
  const keys = await scanKeys(redis, args.pattern ?? "*");
@@ -397,6 +451,17 @@ function summarizeInspectableValue(value) {
397
451
  }
398
452
  return value;
399
453
  }
454
+ function maskRedisUrl(url) {
455
+ try {
456
+ const parsed = new URL(url);
457
+ if (parsed.password) {
458
+ parsed.password = "***";
459
+ }
460
+ return parsed.toString();
461
+ } catch {
462
+ return url.replace(/:([^@/]+)@/, ":***@");
463
+ }
464
+ }
400
465
  if (process.argv[1]?.includes("cli.")) {
401
466
  void main();
402
467
  }
package/dist/cli.js CHANGED
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  RedisTagIndex
4
- } from "./chunk-GF47Y3XR.js";
4
+ } from "./chunk-QHWG7QS5.js";
5
5
  import {
6
6
  isStoredValueEnvelope,
7
7
  resolveStoredValue
8
- } from "./chunk-ZMDB5KOK.js";
8
+ } from "./chunk-7V7XAB74.js";
9
9
 
10
10
  // src/cli.ts
11
11
  import Redis from "ioredis";
@@ -20,7 +20,7 @@ async function main(argv = process.argv.slice(2)) {
20
20
  const redisUrl = validateRedisUrl(args.redisUrl);
21
21
  if (!redisUrl) {
22
22
  process.stderr.write(
23
- `Error: invalid Redis URL "${args.redisUrl}". Expected format: redis://[user:password@]host[:port][/db]
23
+ `Error: invalid Redis URL "${maskRedisUrl(args.redisUrl)}". Expected format: redis://[user:password@]host[:port][/db]
24
24
  `
25
25
  );
26
26
  process.exitCode = 1;
@@ -34,7 +34,7 @@ async function main(argv = process.argv.slice(2)) {
34
34
  try {
35
35
  await redis.connect().catch((error) => {
36
36
  const message = error instanceof Error ? error.message : String(error);
37
- throw new Error(`Failed to connect to Redis at "${redisUrl}": ${message}`);
37
+ throw new Error(`Failed to connect to Redis at "${maskRedisUrl(redisUrl)}": ${message}`);
38
38
  });
39
39
  if (args.command === "stats") {
40
40
  const keys = await scanKeys(redis, args.pattern ?? "*");
@@ -189,6 +189,17 @@ function summarizeInspectableValue(value) {
189
189
  }
190
190
  return value;
191
191
  }
192
+ function maskRedisUrl(url) {
193
+ try {
194
+ const parsed = new URL(url);
195
+ if (parsed.password) {
196
+ parsed.password = "***";
197
+ }
198
+ return parsed.toString();
199
+ } catch {
200
+ return url.replace(/:([^@/]+)@/, ":***@");
201
+ }
202
+ }
192
203
  if (process.argv[1]?.includes("cli.")) {
193
204
  void main();
194
205
  }
@@ -165,6 +165,7 @@ interface CacheSingleFlightExecutionOptions {
165
165
  leaseMs: number;
166
166
  waitTimeoutMs: number;
167
167
  pollIntervalMs: number;
168
+ renewIntervalMs?: number;
168
169
  }
169
170
  interface CacheSingleFlightCoordinator {
170
171
  execute<T>(key: string, options: CacheSingleFlightExecutionOptions, worker: () => Promise<T>, waiter: () => Promise<T>): Promise<T>;
@@ -176,6 +177,7 @@ interface CacheStackOptions {
176
177
  invalidationBus?: InvalidationBus;
177
178
  tagIndex?: CacheTagIndex;
178
179
  generation?: number;
180
+ generationCleanup?: boolean | CacheGenerationCleanupOptions;
179
181
  broadcastL1Invalidation?: boolean;
180
182
  /**
181
183
  * @deprecated Use `broadcastL1Invalidation` instead.
@@ -194,10 +196,13 @@ interface CacheStackOptions {
194
196
  writeStrategy?: 'write-through' | 'write-behind';
195
197
  writeBehind?: CacheWriteBehindOptions;
196
198
  fetcherRateLimit?: CacheRateLimitOptions;
199
+ backgroundRefreshTimeoutMs?: number;
197
200
  singleFlightCoordinator?: CacheSingleFlightCoordinator;
198
201
  singleFlightLeaseMs?: number;
199
202
  singleFlightTimeoutMs?: number;
200
203
  singleFlightPollMs?: number;
204
+ singleFlightRenewIntervalMs?: number;
205
+ snapshotBaseDir?: string | false;
201
206
  /**
202
207
  * Maximum number of entries in `accessProfiles` and `circuitBreakers` maps
203
208
  * before the oldest entries are pruned. Prevents unbounded memory growth.
@@ -210,6 +215,9 @@ interface CacheAdaptiveTtlOptions {
210
215
  step?: number | LayerTtlMap;
211
216
  maxTtl?: number | LayerTtlMap;
212
217
  }
218
+ interface CacheGenerationCleanupOptions {
219
+ batchSize?: number;
220
+ }
213
221
  type CacheTtlPolicy = 'until-midnight' | 'next-hour' | {
214
222
  alignTo: number;
215
223
  } | ((context: CacheTtlPolicyContext) => number | undefined);
@@ -228,6 +236,8 @@ interface CacheRateLimitOptions {
228
236
  maxConcurrent?: number;
229
237
  intervalMs?: number;
230
238
  maxPerInterval?: number;
239
+ scope?: 'global' | 'key' | 'fetcher';
240
+ bucketKey?: string;
231
241
  }
232
242
  interface CacheWriteBehindOptions {
233
243
  flushIntervalMs?: number;
@@ -411,7 +421,7 @@ interface TagIndexOptions {
411
421
  /**
412
422
  * Maximum number of keys tracked in `knownKeys`. When exceeded, the oldest
413
423
  * 10 % of keys are pruned to keep memory bounded.
414
- * Defaults to unlimited.
424
+ * Defaults to 100,000.
415
425
  */
416
426
  maxKnownKeys?: number;
417
427
  }
@@ -420,6 +430,8 @@ declare class TagIndex implements CacheTagIndex {
420
430
  private readonly keyToTags;
421
431
  private readonly knownKeys;
422
432
  private readonly maxKnownKeys;
433
+ private nextNodeId;
434
+ private readonly root;
423
435
  constructor(options?: TagIndexOptions);
424
436
  touch(key: string): Promise<void>;
425
437
  track(key: string, tags: string[]): Promise<void>;
@@ -429,8 +441,14 @@ declare class TagIndex implements CacheTagIndex {
429
441
  tagsForKey(key: string): Promise<string[]>;
430
442
  matchPattern(pattern: string): Promise<string[]>;
431
443
  clear(): Promise<void>;
444
+ private createTrieNode;
445
+ private insertKnownKey;
446
+ private findNode;
447
+ private collectFromNode;
448
+ private collectPatternMatches;
432
449
  private pruneKnownKeysIfNeeded;
433
450
  private removeKey;
451
+ private removeKnownKey;
434
452
  }
435
453
 
436
454
  declare class CacheNamespace {
@@ -501,6 +519,7 @@ declare class CacheStack extends EventEmitter {
501
519
  private readonly logger;
502
520
  private readonly tagIndex;
503
521
  private readonly fetchRateLimiter;
522
+ private readonly snapshotSerializer;
504
523
  private readonly backgroundRefreshes;
505
524
  private readonly layerDegradedUntil;
506
525
  private readonly ttlResolver;
@@ -509,6 +528,7 @@ declare class CacheStack extends EventEmitter {
509
528
  private readonly writeBehindQueue;
510
529
  private writeBehindTimer?;
511
530
  private writeBehindFlushPromise?;
531
+ private generationCleanupPromise?;
512
532
  private isDisconnecting;
513
533
  private disconnectPromise?;
514
534
  constructor(layers: CacheLayer[], options?: CacheStackOptions);
@@ -519,6 +539,7 @@ declare class CacheStack extends EventEmitter {
519
539
  * and no `fetcher` is provided.
520
540
  */
521
541
  get<T>(key: string, fetcher?: () => Promise<T>, options?: CacheGetOptions): Promise<T | null>;
542
+ private getPrepared;
522
543
  /**
523
544
  * Alias for `get(key, fetcher, options)` — explicit get-or-set pattern.
524
545
  * Fetches and caches the value if not already present.
@@ -577,6 +598,11 @@ declare class CacheStack extends EventEmitter {
577
598
  */
578
599
  getHitRate(): CacheHitRateSnapshot;
579
600
  healthCheck(): Promise<CacheHealthCheckResult[]>;
601
+ /**
602
+ * Rotates the active generation prefix used for all future cache keys.
603
+ * Previous-generation keys remain in the underlying layers until they expire,
604
+ * unless `generationCleanup` is enabled to prune them in the background.
605
+ */
580
606
  bumpGeneration(nextGeneration?: number): number;
581
607
  /**
582
608
  * Returns detailed metadata about a single cache key: which layers contain it,
@@ -604,6 +630,7 @@ declare class CacheStack extends EventEmitter {
604
630
  private resolveLayerSeconds;
605
631
  private shouldNegativeCache;
606
632
  private scheduleBackgroundRefresh;
633
+ private runBackgroundRefresh;
607
634
  private resolveSingleFlightOptions;
608
635
  private deleteKeys;
609
636
  private publishInvalidation;
@@ -611,7 +638,14 @@ declare class CacheStack extends EventEmitter {
611
638
  private getTagsForKey;
612
639
  private formatError;
613
640
  private sleep;
641
+ private withTimeout;
614
642
  private shouldBroadcastL1Invalidation;
643
+ private collectKeysWithPrefix;
644
+ private collectKeysMatchingPattern;
645
+ private shouldCleanupGenerations;
646
+ private generationCleanupBatchSize;
647
+ private scheduleGenerationCleanup;
648
+ private cleanupGeneration;
615
649
  private initializeWriteBehind;
616
650
  private shouldWriteBehind;
617
651
  private enqueueWriteBehind;
@@ -627,6 +661,7 @@ declare class CacheStack extends EventEmitter {
627
661
  private validateWriteOptions;
628
662
  private validateLayerNumberOption;
629
663
  private validatePositiveNumber;
664
+ private validateRateLimitOptions;
630
665
  private validateNonNegativeNumber;
631
666
  private validateCacheKey;
632
667
  private validateTtlPolicy;
@@ -638,12 +673,15 @@ declare class CacheStack extends EventEmitter {
638
673
  private applyFreshReadPolicies;
639
674
  private shouldSkipLayer;
640
675
  private handleLayerFailure;
676
+ private reportRecoverableLayerFailure;
641
677
  private isGracefulDegradationEnabled;
642
678
  private recordCircuitFailure;
643
679
  private isNegativeStoredValue;
644
680
  private emitError;
645
681
  private serializeKeyPart;
646
682
  private isCacheSnapshotEntries;
683
+ private sanitizeSnapshotValue;
684
+ private validateSnapshotFilePath;
647
685
  private normalizeForSerialization;
648
686
  }
649
687
 
@@ -165,6 +165,7 @@ interface CacheSingleFlightExecutionOptions {
165
165
  leaseMs: number;
166
166
  waitTimeoutMs: number;
167
167
  pollIntervalMs: number;
168
+ renewIntervalMs?: number;
168
169
  }
169
170
  interface CacheSingleFlightCoordinator {
170
171
  execute<T>(key: string, options: CacheSingleFlightExecutionOptions, worker: () => Promise<T>, waiter: () => Promise<T>): Promise<T>;
@@ -176,6 +177,7 @@ interface CacheStackOptions {
176
177
  invalidationBus?: InvalidationBus;
177
178
  tagIndex?: CacheTagIndex;
178
179
  generation?: number;
180
+ generationCleanup?: boolean | CacheGenerationCleanupOptions;
179
181
  broadcastL1Invalidation?: boolean;
180
182
  /**
181
183
  * @deprecated Use `broadcastL1Invalidation` instead.
@@ -194,10 +196,13 @@ interface CacheStackOptions {
194
196
  writeStrategy?: 'write-through' | 'write-behind';
195
197
  writeBehind?: CacheWriteBehindOptions;
196
198
  fetcherRateLimit?: CacheRateLimitOptions;
199
+ backgroundRefreshTimeoutMs?: number;
197
200
  singleFlightCoordinator?: CacheSingleFlightCoordinator;
198
201
  singleFlightLeaseMs?: number;
199
202
  singleFlightTimeoutMs?: number;
200
203
  singleFlightPollMs?: number;
204
+ singleFlightRenewIntervalMs?: number;
205
+ snapshotBaseDir?: string | false;
201
206
  /**
202
207
  * Maximum number of entries in `accessProfiles` and `circuitBreakers` maps
203
208
  * before the oldest entries are pruned. Prevents unbounded memory growth.
@@ -210,6 +215,9 @@ interface CacheAdaptiveTtlOptions {
210
215
  step?: number | LayerTtlMap;
211
216
  maxTtl?: number | LayerTtlMap;
212
217
  }
218
+ interface CacheGenerationCleanupOptions {
219
+ batchSize?: number;
220
+ }
213
221
  type CacheTtlPolicy = 'until-midnight' | 'next-hour' | {
214
222
  alignTo: number;
215
223
  } | ((context: CacheTtlPolicyContext) => number | undefined);
@@ -228,6 +236,8 @@ interface CacheRateLimitOptions {
228
236
  maxConcurrent?: number;
229
237
  intervalMs?: number;
230
238
  maxPerInterval?: number;
239
+ scope?: 'global' | 'key' | 'fetcher';
240
+ bucketKey?: string;
231
241
  }
232
242
  interface CacheWriteBehindOptions {
233
243
  flushIntervalMs?: number;
@@ -411,7 +421,7 @@ interface TagIndexOptions {
411
421
  /**
412
422
  * Maximum number of keys tracked in `knownKeys`. When exceeded, the oldest
413
423
  * 10 % of keys are pruned to keep memory bounded.
414
- * Defaults to unlimited.
424
+ * Defaults to 100,000.
415
425
  */
416
426
  maxKnownKeys?: number;
417
427
  }
@@ -420,6 +430,8 @@ declare class TagIndex implements CacheTagIndex {
420
430
  private readonly keyToTags;
421
431
  private readonly knownKeys;
422
432
  private readonly maxKnownKeys;
433
+ private nextNodeId;
434
+ private readonly root;
423
435
  constructor(options?: TagIndexOptions);
424
436
  touch(key: string): Promise<void>;
425
437
  track(key: string, tags: string[]): Promise<void>;
@@ -429,8 +441,14 @@ declare class TagIndex implements CacheTagIndex {
429
441
  tagsForKey(key: string): Promise<string[]>;
430
442
  matchPattern(pattern: string): Promise<string[]>;
431
443
  clear(): Promise<void>;
444
+ private createTrieNode;
445
+ private insertKnownKey;
446
+ private findNode;
447
+ private collectFromNode;
448
+ private collectPatternMatches;
432
449
  private pruneKnownKeysIfNeeded;
433
450
  private removeKey;
451
+ private removeKnownKey;
434
452
  }
435
453
 
436
454
  declare class CacheNamespace {
@@ -501,6 +519,7 @@ declare class CacheStack extends EventEmitter {
501
519
  private readonly logger;
502
520
  private readonly tagIndex;
503
521
  private readonly fetchRateLimiter;
522
+ private readonly snapshotSerializer;
504
523
  private readonly backgroundRefreshes;
505
524
  private readonly layerDegradedUntil;
506
525
  private readonly ttlResolver;
@@ -509,6 +528,7 @@ declare class CacheStack extends EventEmitter {
509
528
  private readonly writeBehindQueue;
510
529
  private writeBehindTimer?;
511
530
  private writeBehindFlushPromise?;
531
+ private generationCleanupPromise?;
512
532
  private isDisconnecting;
513
533
  private disconnectPromise?;
514
534
  constructor(layers: CacheLayer[], options?: CacheStackOptions);
@@ -519,6 +539,7 @@ declare class CacheStack extends EventEmitter {
519
539
  * and no `fetcher` is provided.
520
540
  */
521
541
  get<T>(key: string, fetcher?: () => Promise<T>, options?: CacheGetOptions): Promise<T | null>;
542
+ private getPrepared;
522
543
  /**
523
544
  * Alias for `get(key, fetcher, options)` — explicit get-or-set pattern.
524
545
  * Fetches and caches the value if not already present.
@@ -577,6 +598,11 @@ declare class CacheStack extends EventEmitter {
577
598
  */
578
599
  getHitRate(): CacheHitRateSnapshot;
579
600
  healthCheck(): Promise<CacheHealthCheckResult[]>;
601
+ /**
602
+ * Rotates the active generation prefix used for all future cache keys.
603
+ * Previous-generation keys remain in the underlying layers until they expire,
604
+ * unless `generationCleanup` is enabled to prune them in the background.
605
+ */
580
606
  bumpGeneration(nextGeneration?: number): number;
581
607
  /**
582
608
  * Returns detailed metadata about a single cache key: which layers contain it,
@@ -604,6 +630,7 @@ declare class CacheStack extends EventEmitter {
604
630
  private resolveLayerSeconds;
605
631
  private shouldNegativeCache;
606
632
  private scheduleBackgroundRefresh;
633
+ private runBackgroundRefresh;
607
634
  private resolveSingleFlightOptions;
608
635
  private deleteKeys;
609
636
  private publishInvalidation;
@@ -611,7 +638,14 @@ declare class CacheStack extends EventEmitter {
611
638
  private getTagsForKey;
612
639
  private formatError;
613
640
  private sleep;
641
+ private withTimeout;
614
642
  private shouldBroadcastL1Invalidation;
643
+ private collectKeysWithPrefix;
644
+ private collectKeysMatchingPattern;
645
+ private shouldCleanupGenerations;
646
+ private generationCleanupBatchSize;
647
+ private scheduleGenerationCleanup;
648
+ private cleanupGeneration;
615
649
  private initializeWriteBehind;
616
650
  private shouldWriteBehind;
617
651
  private enqueueWriteBehind;
@@ -627,6 +661,7 @@ declare class CacheStack extends EventEmitter {
627
661
  private validateWriteOptions;
628
662
  private validateLayerNumberOption;
629
663
  private validatePositiveNumber;
664
+ private validateRateLimitOptions;
630
665
  private validateNonNegativeNumber;
631
666
  private validateCacheKey;
632
667
  private validateTtlPolicy;
@@ -638,12 +673,15 @@ declare class CacheStack extends EventEmitter {
638
673
  private applyFreshReadPolicies;
639
674
  private shouldSkipLayer;
640
675
  private handleLayerFailure;
676
+ private reportRecoverableLayerFailure;
641
677
  private isGracefulDegradationEnabled;
642
678
  private recordCircuitFailure;
643
679
  private isNegativeStoredValue;
644
680
  private emitError;
645
681
  private serializeKeyPart;
646
682
  private isCacheSnapshotEntries;
683
+ private sanitizeSnapshotValue;
684
+ private validateSnapshotFilePath;
647
685
  private normalizeForSerialization;
648
686
  }
649
687