layercache 1.2.0 → 1.2.1

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.
@@ -9,6 +9,7 @@ interface LayerTtlMap {
9
9
  interface CacheWriteOptions {
10
10
  tags?: string[];
11
11
  ttl?: number | LayerTtlMap;
12
+ ttlPolicy?: CacheTtlPolicy;
12
13
  negativeCache?: boolean;
13
14
  negativeTtl?: number | LayerTtlMap;
14
15
  staleWhileRevalidate?: number | LayerTtlMap;
@@ -18,6 +19,7 @@ interface CacheWriteOptions {
18
19
  refreshAhead?: number | LayerTtlMap;
19
20
  adaptiveTtl?: boolean | CacheAdaptiveTtlOptions;
20
21
  circuitBreaker?: CacheCircuitBreakerOptions;
22
+ fetcherRateLimit?: CacheRateLimitOptions;
21
23
  /**
22
24
  * Optional predicate called with the fetcher's return value before caching.
23
25
  * Return `false` to skip storing the value in the cache (but still return it
@@ -47,12 +49,20 @@ interface CacheLayer {
47
49
  readonly isLocal?: boolean;
48
50
  get<T>(key: string): Promise<T | null>;
49
51
  getEntry?<T = unknown>(key: string): Promise<T | null>;
52
+ /**
53
+ * Bulk read fast-path. Implementations should return raw stored entries using
54
+ * the same semantics as `getEntry()` so CacheStack can resolve envelopes,
55
+ * stale windows, and negative-cache markers consistently.
56
+ */
50
57
  getMany?<T>(keys: string[]): Promise<Array<T | null>>;
58
+ setMany?(entries: CacheLayerSetManyEntry[]): Promise<void>;
51
59
  set(key: string, value: unknown, ttl?: number): Promise<void>;
52
60
  delete(key: string): Promise<void>;
53
61
  clear(): Promise<void>;
54
62
  deleteMany?(keys: string[]): Promise<void>;
55
63
  keys?(): Promise<string[]>;
64
+ ping?(): Promise<boolean>;
65
+ dispose?(): Promise<void>;
56
66
  /**
57
67
  * Returns true if the key exists and has not expired.
58
68
  * Implementations may omit this; CacheStack will fall back to `get()`.
@@ -121,11 +131,17 @@ interface CacheTagIndex {
121
131
  track(key: string, tags: string[]): Promise<void>;
122
132
  remove(key: string): Promise<void>;
123
133
  keysForTag(tag: string): Promise<string[]>;
134
+ keysForPrefix?(prefix: string): Promise<string[]>;
124
135
  /** Returns the tags associated with a specific key, or an empty array. */
125
136
  tagsForKey?(key: string): Promise<string[]>;
126
137
  matchPattern(pattern: string): Promise<string[]>;
127
138
  clear(): Promise<void>;
128
139
  }
140
+ interface CacheLayerSetManyEntry {
141
+ key: string;
142
+ value: unknown;
143
+ ttl?: number;
144
+ }
129
145
  interface InvalidationMessage {
130
146
  scope: 'key' | 'keys' | 'clear';
131
147
  sourceId: string;
@@ -150,6 +166,7 @@ interface CacheStackOptions {
150
166
  stampedePrevention?: boolean;
151
167
  invalidationBus?: InvalidationBus;
152
168
  tagIndex?: CacheTagIndex;
169
+ generation?: number;
153
170
  broadcastL1Invalidation?: boolean;
154
171
  /**
155
172
  * @deprecated Use `broadcastL1Invalidation` instead.
@@ -165,6 +182,9 @@ interface CacheStackOptions {
165
182
  circuitBreaker?: CacheCircuitBreakerOptions;
166
183
  gracefulDegradation?: boolean | CacheDegradationOptions;
167
184
  writePolicy?: 'strict' | 'best-effort';
185
+ writeStrategy?: 'write-through' | 'write-behind';
186
+ writeBehind?: CacheWriteBehindOptions;
187
+ fetcherRateLimit?: CacheRateLimitOptions;
168
188
  singleFlightCoordinator?: CacheSingleFlightCoordinator;
169
189
  singleFlightLeaseMs?: number;
170
190
  singleFlightTimeoutMs?: number;
@@ -181,6 +201,13 @@ interface CacheAdaptiveTtlOptions {
181
201
  step?: number | LayerTtlMap;
182
202
  maxTtl?: number | LayerTtlMap;
183
203
  }
204
+ type CacheTtlPolicy = 'until-midnight' | 'next-hour' | {
205
+ alignTo: number;
206
+ } | ((context: CacheTtlPolicyContext) => number | undefined);
207
+ interface CacheTtlPolicyContext {
208
+ key: string;
209
+ value: unknown;
210
+ }
184
211
  interface CacheCircuitBreakerOptions {
185
212
  failureThreshold?: number;
186
213
  cooldownMs?: number;
@@ -188,6 +215,16 @@ interface CacheCircuitBreakerOptions {
188
215
  interface CacheDegradationOptions {
189
216
  retryAfterMs?: number;
190
217
  }
218
+ interface CacheRateLimitOptions {
219
+ maxConcurrent?: number;
220
+ intervalMs?: number;
221
+ maxPerInterval?: number;
222
+ }
223
+ interface CacheWriteBehindOptions {
224
+ flushIntervalMs?: number;
225
+ batchSize?: number;
226
+ maxQueueSize?: number;
227
+ }
191
228
  interface CacheWarmEntry<T = unknown> {
192
229
  key: string;
193
230
  fetcher: () => Promise<T>;
@@ -225,6 +262,12 @@ interface CacheStatsSnapshot {
225
262
  }>;
226
263
  backgroundRefreshes: number;
227
264
  }
265
+ interface CacheHealthCheckResult {
266
+ layer: string;
267
+ healthy: boolean;
268
+ latencyMs: number;
269
+ error?: string;
270
+ }
228
271
  /** Detailed inspection result for a single cache key. */
229
272
  interface CacheInspectResult {
230
273
  key: string;
@@ -293,6 +336,8 @@ interface CacheStackEvents {
293
336
  declare class CacheNamespace {
294
337
  private readonly cache;
295
338
  private readonly prefix;
339
+ private static readonly metricsMutexes;
340
+ private metrics;
296
341
  constructor(cache: CacheStack, prefix: string);
297
342
  get<T>(key: string, fetcher?: () => Promise<T>, options?: CacheGetOptions): Promise<T | null>;
298
343
  getOrSet<T>(key: string, fetcher: () => Promise<T>, options?: CacheGetOptions): Promise<T | null>;
@@ -309,7 +354,9 @@ declare class CacheNamespace {
309
354
  mget<T>(entries: CacheMGetEntry<T>[]): Promise<Array<T | null>>;
310
355
  mset<T>(entries: CacheMSetEntry<T>[]): Promise<void>;
311
356
  invalidateByTag(tag: string): Promise<void>;
357
+ invalidateByTags(tags: string[], mode?: 'any' | 'all'): Promise<void>;
312
358
  invalidateByPattern(pattern: string): Promise<void>;
359
+ invalidateByPrefix(prefix: string): Promise<void>;
313
360
  /**
314
361
  * Returns detailed metadata about a single cache key within this namespace.
315
362
  */
@@ -329,6 +376,8 @@ declare class CacheNamespace {
329
376
  */
330
377
  namespace(childPrefix: string): CacheNamespace;
331
378
  qualify(key: string): string;
379
+ private trackMetrics;
380
+ private getMetricsMutex;
332
381
  }
333
382
 
334
383
  /** Typed overloads for EventEmitter so callers get autocomplete on event names. */
@@ -336,6 +385,9 @@ interface CacheStack {
336
385
  on<K extends keyof CacheStackEvents>(event: K, listener: (data: CacheStackEvents[K]) => void): this;
337
386
  once<K extends keyof CacheStackEvents>(event: K, listener: (data: CacheStackEvents[K]) => void): this;
338
387
  off<K extends keyof CacheStackEvents>(event: K, listener: (data: CacheStackEvents[K]) => void): this;
388
+ removeAllListeners<K extends keyof CacheStackEvents>(event?: K): this;
389
+ listeners<K extends keyof CacheStackEvents>(event: K): Array<(data: CacheStackEvents[K]) => void>;
390
+ listenerCount<K extends keyof CacheStackEvents>(event: K): number;
339
391
  emit<K extends keyof CacheStackEvents>(event: K, data: CacheStackEvents[K]): boolean;
340
392
  }
341
393
  declare class CacheStack extends EventEmitter {
@@ -348,10 +400,15 @@ declare class CacheStack extends EventEmitter {
348
400
  private unsubscribeInvalidation?;
349
401
  private readonly logger;
350
402
  private readonly tagIndex;
403
+ private readonly fetchRateLimiter;
351
404
  private readonly backgroundRefreshes;
352
405
  private readonly layerDegradedUntil;
353
406
  private readonly ttlResolver;
354
407
  private readonly circuitBreakerManager;
408
+ private currentGeneration?;
409
+ private readonly writeBehindQueue;
410
+ private writeBehindTimer?;
411
+ private writeBehindFlushPromise?;
355
412
  private isDisconnecting;
356
413
  private disconnectPromise?;
357
414
  constructor(layers: CacheLayer[], options?: CacheStackOptions);
@@ -409,7 +466,9 @@ declare class CacheStack extends EventEmitter {
409
466
  */
410
467
  namespace(prefix: string): CacheNamespace;
411
468
  invalidateByTag(tag: string): Promise<void>;
469
+ invalidateByTags(tags: string[], mode?: 'any' | 'all'): Promise<void>;
412
470
  invalidateByPattern(pattern: string): Promise<void>;
471
+ invalidateByPrefix(prefix: string): Promise<void>;
413
472
  getMetrics(): CacheMetricsSnapshot;
414
473
  getStats(): CacheStatsSnapshot;
415
474
  resetMetrics(): void;
@@ -417,6 +476,8 @@ declare class CacheStack extends EventEmitter {
417
476
  * Returns computed hit-rate statistics (overall and per-layer).
418
477
  */
419
478
  getHitRate(): CacheHitRateSnapshot;
479
+ healthCheck(): Promise<CacheHealthCheckResult[]>;
480
+ bumpGeneration(nextGeneration?: number): number;
420
481
  /**
421
482
  * Returns detailed metadata about a single cache key: which layers contain it,
422
483
  * remaining fresh/stale/error TTLs, and associated tags.
@@ -433,6 +494,7 @@ declare class CacheStack extends EventEmitter {
433
494
  private waitForFreshValue;
434
495
  private fetchAndPopulate;
435
496
  private storeEntry;
497
+ private writeBatch;
436
498
  private readFromLayers;
437
499
  private readLayerEntry;
438
500
  private backfill;
@@ -450,6 +512,16 @@ declare class CacheStack extends EventEmitter {
450
512
  private formatError;
451
513
  private sleep;
452
514
  private shouldBroadcastL1Invalidation;
515
+ private initializeWriteBehind;
516
+ private shouldWriteBehind;
517
+ private enqueueWriteBehind;
518
+ private flushWriteBehindQueue;
519
+ private buildLayerSetEntry;
520
+ private intersectKeys;
521
+ private qualifyKey;
522
+ private qualifyPattern;
523
+ private stripQualifiedKey;
524
+ private generationPrefix;
453
525
  private deleteKeysFromLayers;
454
526
  private validateConfiguration;
455
527
  private validateWriteOptions;
@@ -457,6 +529,9 @@ declare class CacheStack extends EventEmitter {
457
529
  private validatePositiveNumber;
458
530
  private validateNonNegativeNumber;
459
531
  private validateCacheKey;
532
+ private validateTtlPolicy;
533
+ private assertActive;
534
+ private awaitStartup;
460
535
  private serializeOptions;
461
536
  private validateAdaptiveTtlOptions;
462
537
  private validateCircuitBreakerOptions;
@@ -9,6 +9,7 @@ interface LayerTtlMap {
9
9
  interface CacheWriteOptions {
10
10
  tags?: string[];
11
11
  ttl?: number | LayerTtlMap;
12
+ ttlPolicy?: CacheTtlPolicy;
12
13
  negativeCache?: boolean;
13
14
  negativeTtl?: number | LayerTtlMap;
14
15
  staleWhileRevalidate?: number | LayerTtlMap;
@@ -18,6 +19,7 @@ interface CacheWriteOptions {
18
19
  refreshAhead?: number | LayerTtlMap;
19
20
  adaptiveTtl?: boolean | CacheAdaptiveTtlOptions;
20
21
  circuitBreaker?: CacheCircuitBreakerOptions;
22
+ fetcherRateLimit?: CacheRateLimitOptions;
21
23
  /**
22
24
  * Optional predicate called with the fetcher's return value before caching.
23
25
  * Return `false` to skip storing the value in the cache (but still return it
@@ -47,12 +49,20 @@ interface CacheLayer {
47
49
  readonly isLocal?: boolean;
48
50
  get<T>(key: string): Promise<T | null>;
49
51
  getEntry?<T = unknown>(key: string): Promise<T | null>;
52
+ /**
53
+ * Bulk read fast-path. Implementations should return raw stored entries using
54
+ * the same semantics as `getEntry()` so CacheStack can resolve envelopes,
55
+ * stale windows, and negative-cache markers consistently.
56
+ */
50
57
  getMany?<T>(keys: string[]): Promise<Array<T | null>>;
58
+ setMany?(entries: CacheLayerSetManyEntry[]): Promise<void>;
51
59
  set(key: string, value: unknown, ttl?: number): Promise<void>;
52
60
  delete(key: string): Promise<void>;
53
61
  clear(): Promise<void>;
54
62
  deleteMany?(keys: string[]): Promise<void>;
55
63
  keys?(): Promise<string[]>;
64
+ ping?(): Promise<boolean>;
65
+ dispose?(): Promise<void>;
56
66
  /**
57
67
  * Returns true if the key exists and has not expired.
58
68
  * Implementations may omit this; CacheStack will fall back to `get()`.
@@ -121,11 +131,17 @@ interface CacheTagIndex {
121
131
  track(key: string, tags: string[]): Promise<void>;
122
132
  remove(key: string): Promise<void>;
123
133
  keysForTag(tag: string): Promise<string[]>;
134
+ keysForPrefix?(prefix: string): Promise<string[]>;
124
135
  /** Returns the tags associated with a specific key, or an empty array. */
125
136
  tagsForKey?(key: string): Promise<string[]>;
126
137
  matchPattern(pattern: string): Promise<string[]>;
127
138
  clear(): Promise<void>;
128
139
  }
140
+ interface CacheLayerSetManyEntry {
141
+ key: string;
142
+ value: unknown;
143
+ ttl?: number;
144
+ }
129
145
  interface InvalidationMessage {
130
146
  scope: 'key' | 'keys' | 'clear';
131
147
  sourceId: string;
@@ -150,6 +166,7 @@ interface CacheStackOptions {
150
166
  stampedePrevention?: boolean;
151
167
  invalidationBus?: InvalidationBus;
152
168
  tagIndex?: CacheTagIndex;
169
+ generation?: number;
153
170
  broadcastL1Invalidation?: boolean;
154
171
  /**
155
172
  * @deprecated Use `broadcastL1Invalidation` instead.
@@ -165,6 +182,9 @@ interface CacheStackOptions {
165
182
  circuitBreaker?: CacheCircuitBreakerOptions;
166
183
  gracefulDegradation?: boolean | CacheDegradationOptions;
167
184
  writePolicy?: 'strict' | 'best-effort';
185
+ writeStrategy?: 'write-through' | 'write-behind';
186
+ writeBehind?: CacheWriteBehindOptions;
187
+ fetcherRateLimit?: CacheRateLimitOptions;
168
188
  singleFlightCoordinator?: CacheSingleFlightCoordinator;
169
189
  singleFlightLeaseMs?: number;
170
190
  singleFlightTimeoutMs?: number;
@@ -181,6 +201,13 @@ interface CacheAdaptiveTtlOptions {
181
201
  step?: number | LayerTtlMap;
182
202
  maxTtl?: number | LayerTtlMap;
183
203
  }
204
+ type CacheTtlPolicy = 'until-midnight' | 'next-hour' | {
205
+ alignTo: number;
206
+ } | ((context: CacheTtlPolicyContext) => number | undefined);
207
+ interface CacheTtlPolicyContext {
208
+ key: string;
209
+ value: unknown;
210
+ }
184
211
  interface CacheCircuitBreakerOptions {
185
212
  failureThreshold?: number;
186
213
  cooldownMs?: number;
@@ -188,6 +215,16 @@ interface CacheCircuitBreakerOptions {
188
215
  interface CacheDegradationOptions {
189
216
  retryAfterMs?: number;
190
217
  }
218
+ interface CacheRateLimitOptions {
219
+ maxConcurrent?: number;
220
+ intervalMs?: number;
221
+ maxPerInterval?: number;
222
+ }
223
+ interface CacheWriteBehindOptions {
224
+ flushIntervalMs?: number;
225
+ batchSize?: number;
226
+ maxQueueSize?: number;
227
+ }
191
228
  interface CacheWarmEntry<T = unknown> {
192
229
  key: string;
193
230
  fetcher: () => Promise<T>;
@@ -225,6 +262,12 @@ interface CacheStatsSnapshot {
225
262
  }>;
226
263
  backgroundRefreshes: number;
227
264
  }
265
+ interface CacheHealthCheckResult {
266
+ layer: string;
267
+ healthy: boolean;
268
+ latencyMs: number;
269
+ error?: string;
270
+ }
228
271
  /** Detailed inspection result for a single cache key. */
229
272
  interface CacheInspectResult {
230
273
  key: string;
@@ -293,6 +336,8 @@ interface CacheStackEvents {
293
336
  declare class CacheNamespace {
294
337
  private readonly cache;
295
338
  private readonly prefix;
339
+ private static readonly metricsMutexes;
340
+ private metrics;
296
341
  constructor(cache: CacheStack, prefix: string);
297
342
  get<T>(key: string, fetcher?: () => Promise<T>, options?: CacheGetOptions): Promise<T | null>;
298
343
  getOrSet<T>(key: string, fetcher: () => Promise<T>, options?: CacheGetOptions): Promise<T | null>;
@@ -309,7 +354,9 @@ declare class CacheNamespace {
309
354
  mget<T>(entries: CacheMGetEntry<T>[]): Promise<Array<T | null>>;
310
355
  mset<T>(entries: CacheMSetEntry<T>[]): Promise<void>;
311
356
  invalidateByTag(tag: string): Promise<void>;
357
+ invalidateByTags(tags: string[], mode?: 'any' | 'all'): Promise<void>;
312
358
  invalidateByPattern(pattern: string): Promise<void>;
359
+ invalidateByPrefix(prefix: string): Promise<void>;
313
360
  /**
314
361
  * Returns detailed metadata about a single cache key within this namespace.
315
362
  */
@@ -329,6 +376,8 @@ declare class CacheNamespace {
329
376
  */
330
377
  namespace(childPrefix: string): CacheNamespace;
331
378
  qualify(key: string): string;
379
+ private trackMetrics;
380
+ private getMetricsMutex;
332
381
  }
333
382
 
334
383
  /** Typed overloads for EventEmitter so callers get autocomplete on event names. */
@@ -336,6 +385,9 @@ interface CacheStack {
336
385
  on<K extends keyof CacheStackEvents>(event: K, listener: (data: CacheStackEvents[K]) => void): this;
337
386
  once<K extends keyof CacheStackEvents>(event: K, listener: (data: CacheStackEvents[K]) => void): this;
338
387
  off<K extends keyof CacheStackEvents>(event: K, listener: (data: CacheStackEvents[K]) => void): this;
388
+ removeAllListeners<K extends keyof CacheStackEvents>(event?: K): this;
389
+ listeners<K extends keyof CacheStackEvents>(event: K): Array<(data: CacheStackEvents[K]) => void>;
390
+ listenerCount<K extends keyof CacheStackEvents>(event: K): number;
339
391
  emit<K extends keyof CacheStackEvents>(event: K, data: CacheStackEvents[K]): boolean;
340
392
  }
341
393
  declare class CacheStack extends EventEmitter {
@@ -348,10 +400,15 @@ declare class CacheStack extends EventEmitter {
348
400
  private unsubscribeInvalidation?;
349
401
  private readonly logger;
350
402
  private readonly tagIndex;
403
+ private readonly fetchRateLimiter;
351
404
  private readonly backgroundRefreshes;
352
405
  private readonly layerDegradedUntil;
353
406
  private readonly ttlResolver;
354
407
  private readonly circuitBreakerManager;
408
+ private currentGeneration?;
409
+ private readonly writeBehindQueue;
410
+ private writeBehindTimer?;
411
+ private writeBehindFlushPromise?;
355
412
  private isDisconnecting;
356
413
  private disconnectPromise?;
357
414
  constructor(layers: CacheLayer[], options?: CacheStackOptions);
@@ -409,7 +466,9 @@ declare class CacheStack extends EventEmitter {
409
466
  */
410
467
  namespace(prefix: string): CacheNamespace;
411
468
  invalidateByTag(tag: string): Promise<void>;
469
+ invalidateByTags(tags: string[], mode?: 'any' | 'all'): Promise<void>;
412
470
  invalidateByPattern(pattern: string): Promise<void>;
471
+ invalidateByPrefix(prefix: string): Promise<void>;
413
472
  getMetrics(): CacheMetricsSnapshot;
414
473
  getStats(): CacheStatsSnapshot;
415
474
  resetMetrics(): void;
@@ -417,6 +476,8 @@ declare class CacheStack extends EventEmitter {
417
476
  * Returns computed hit-rate statistics (overall and per-layer).
418
477
  */
419
478
  getHitRate(): CacheHitRateSnapshot;
479
+ healthCheck(): Promise<CacheHealthCheckResult[]>;
480
+ bumpGeneration(nextGeneration?: number): number;
420
481
  /**
421
482
  * Returns detailed metadata about a single cache key: which layers contain it,
422
483
  * remaining fresh/stale/error TTLs, and associated tags.
@@ -433,6 +494,7 @@ declare class CacheStack extends EventEmitter {
433
494
  private waitForFreshValue;
434
495
  private fetchAndPopulate;
435
496
  private storeEntry;
497
+ private writeBatch;
436
498
  private readFromLayers;
437
499
  private readLayerEntry;
438
500
  private backfill;
@@ -450,6 +512,16 @@ declare class CacheStack extends EventEmitter {
450
512
  private formatError;
451
513
  private sleep;
452
514
  private shouldBroadcastL1Invalidation;
515
+ private initializeWriteBehind;
516
+ private shouldWriteBehind;
517
+ private enqueueWriteBehind;
518
+ private flushWriteBehindQueue;
519
+ private buildLayerSetEntry;
520
+ private intersectKeys;
521
+ private qualifyKey;
522
+ private qualifyPattern;
523
+ private stripQualifiedKey;
524
+ private generationPrefix;
453
525
  private deleteKeysFromLayers;
454
526
  private validateConfiguration;
455
527
  private validateWriteOptions;
@@ -457,6 +529,9 @@ declare class CacheStack extends EventEmitter {
457
529
  private validatePositiveNumber;
458
530
  private validateNonNegativeNumber;
459
531
  private validateCacheKey;
532
+ private validateTtlPolicy;
533
+ private assertActive;
534
+ private awaitStartup;
460
535
  private serializeOptions;
461
536
  private validateAdaptiveTtlOptions;
462
537
  private validateCircuitBreakerOptions;