layercache 1.1.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.
package/dist/index.d.ts CHANGED
@@ -1,438 +1,32 @@
1
- import { EventEmitter } from 'node:events';
1
+ import { I as InvalidationBus, C as CacheLogger, a as InvalidationMessage, b as CacheTagIndex, c as CacheStack, d as CacheWrapOptions, e as CacheGetOptions, f as CacheLayer, g as CacheSerializer, h as CacheLayerSetManyEntry, i as CacheSingleFlightCoordinator, j as CacheSingleFlightExecutionOptions } from './edge-C1sBhTfv.js';
2
+ export { k as CacheAdaptiveTtlOptions, l as CacheCircuitBreakerOptions, m as CacheDegradationOptions, n as CacheHealthCheckResult, o as CacheHitRateSnapshot, p as CacheInspectResult, q as CacheLayerLatency, r as CacheMGetEntry, s as CacheMSetEntry, t as CacheMetricsSnapshot, u as CacheMissError, v as CacheNamespace, w as CacheRateLimitOptions, x as CacheSnapshotEntry, y as CacheStackEvents, z as CacheStackOptions, A as CacheStatsSnapshot, B as CacheTtlPolicy, D as CacheTtlPolicyContext, E as CacheWarmEntry, F as CacheWarmOptions, G as CacheWarmProgress, H as CacheWriteBehindOptions, J as CacheWriteOptions, K as EvictionPolicy, L as LayerTtlMap, M as MemoryLayer, N as MemoryLayerOptions, O as MemoryLayerSnapshotEntry, P as PatternMatcher, T as TagIndex, Q as createHonoCacheMiddleware } from './edge-C1sBhTfv.js';
2
3
  import Redis from 'ioredis';
3
-
4
- interface LayerTtlMap {
5
- [layerName: string]: number | undefined;
6
- }
7
- interface CacheWriteOptions {
8
- tags?: string[];
9
- ttl?: number | LayerTtlMap;
10
- negativeCache?: boolean;
11
- negativeTtl?: number | LayerTtlMap;
12
- staleWhileRevalidate?: number | LayerTtlMap;
13
- staleIfError?: number | LayerTtlMap;
14
- ttlJitter?: number | LayerTtlMap;
15
- slidingTtl?: boolean;
16
- refreshAhead?: number | LayerTtlMap;
17
- adaptiveTtl?: boolean | CacheAdaptiveTtlOptions;
18
- circuitBreaker?: CacheCircuitBreakerOptions;
19
- }
20
- interface CacheGetOptions extends CacheWriteOptions {
21
- }
22
- interface CacheMGetEntry<T> {
23
- key: string;
24
- fetch?: () => Promise<T>;
25
- options?: CacheGetOptions;
26
- }
27
- interface CacheMSetEntry<T> {
28
- key: string;
29
- value: T;
30
- options?: CacheWriteOptions;
31
- }
32
- /** Interface that all cache backend implementations must satisfy. */
33
- interface CacheLayer {
34
- readonly name: string;
35
- readonly defaultTtl?: number;
36
- readonly isLocal?: boolean;
37
- get<T>(key: string): Promise<T | null>;
38
- getEntry?<T = unknown>(key: string): Promise<T | null>;
39
- getMany?<T>(keys: string[]): Promise<Array<T | null>>;
40
- set(key: string, value: unknown, ttl?: number): Promise<void>;
41
- delete(key: string): Promise<void>;
42
- clear(): Promise<void>;
43
- deleteMany?(keys: string[]): Promise<void>;
44
- keys?(): Promise<string[]>;
45
- /**
46
- * Returns true if the key exists and has not expired.
47
- * Implementations may omit this; CacheStack will fall back to `get()`.
48
- */
49
- has?(key: string): Promise<boolean>;
50
- /**
51
- * Returns the remaining TTL in seconds for the key, or null if the key
52
- * does not exist, has no TTL, or has already expired.
53
- * Implementations may omit this.
54
- */
55
- ttl?(key: string): Promise<number | null>;
56
- /**
57
- * Returns the number of entries currently held by this layer.
58
- * Implementations may omit this.
59
- */
60
- size?(): Promise<number>;
61
- }
62
- interface CacheSerializer {
63
- serialize(value: unknown): string | Buffer;
64
- deserialize<T>(payload: string | Buffer): T;
65
- }
66
- /** Snapshot of cumulative cache counters. */
67
- interface CacheMetricsSnapshot {
68
- hits: number;
69
- misses: number;
70
- fetches: number;
71
- sets: number;
72
- deletes: number;
73
- backfills: number;
74
- invalidations: number;
75
- staleHits: number;
76
- refreshes: number;
77
- refreshErrors: number;
78
- writeFailures: number;
79
- singleFlightWaits: number;
80
- negativeCacheHits: number;
81
- circuitBreakerTrips: number;
82
- degradedOperations: number;
83
- hitsByLayer: Record<string, number>;
84
- missesByLayer: Record<string, number>;
85
- /** Timestamp (ms since epoch) when metrics were last reset. */
86
- resetAt: number;
87
- }
88
- /** Computed hit-rate statistics derived from CacheMetricsSnapshot. */
89
- interface CacheHitRateSnapshot {
90
- /** Overall hit rate across all layers (0–1). */
91
- overall: number;
92
- /** Per-layer hit rates (0–1 each). */
93
- byLayer: Record<string, number>;
94
- }
95
- interface CacheLogger {
96
- debug?(message: string, context?: Record<string, unknown>): void;
97
- info?(message: string, context?: Record<string, unknown>): void;
98
- warn?(message: string, context?: Record<string, unknown>): void;
99
- error?(message: string, context?: Record<string, unknown>): void;
100
- }
101
- interface CacheTagIndex {
102
- touch(key: string): Promise<void>;
103
- track(key: string, tags: string[]): Promise<void>;
104
- remove(key: string): Promise<void>;
105
- keysForTag(tag: string): Promise<string[]>;
106
- matchPattern(pattern: string): Promise<string[]>;
107
- clear(): Promise<void>;
108
- }
109
- interface InvalidationMessage {
110
- scope: 'key' | 'keys' | 'clear';
111
- sourceId: string;
112
- keys?: string[];
113
- operation?: 'write' | 'delete' | 'invalidate' | 'clear';
114
- }
115
- interface InvalidationBus {
116
- subscribe(handler: (message: InvalidationMessage) => Promise<void> | void): Promise<() => Promise<void> | void>;
117
- publish(message: InvalidationMessage): Promise<void>;
118
- }
119
- interface CacheSingleFlightExecutionOptions {
120
- leaseMs: number;
121
- waitTimeoutMs: number;
122
- pollIntervalMs: number;
123
- }
124
- interface CacheSingleFlightCoordinator {
125
- execute<T>(key: string, options: CacheSingleFlightExecutionOptions, worker: () => Promise<T>, waiter: () => Promise<T>): Promise<T>;
126
- }
127
- interface CacheStackOptions {
128
- logger?: CacheLogger | boolean;
129
- metrics?: boolean;
130
- stampedePrevention?: boolean;
131
- invalidationBus?: InvalidationBus;
132
- tagIndex?: CacheTagIndex;
133
- broadcastL1Invalidation?: boolean;
134
- /**
135
- * @deprecated Use `broadcastL1Invalidation` instead.
136
- */
137
- publishSetInvalidation?: boolean;
138
- negativeCaching?: boolean;
139
- negativeTtl?: number | LayerTtlMap;
140
- staleWhileRevalidate?: number | LayerTtlMap;
141
- staleIfError?: number | LayerTtlMap;
142
- ttlJitter?: number | LayerTtlMap;
143
- refreshAhead?: number | LayerTtlMap;
144
- adaptiveTtl?: boolean | CacheAdaptiveTtlOptions;
145
- circuitBreaker?: CacheCircuitBreakerOptions;
146
- gracefulDegradation?: boolean | CacheDegradationOptions;
147
- writePolicy?: 'strict' | 'best-effort';
148
- singleFlightCoordinator?: CacheSingleFlightCoordinator;
149
- singleFlightLeaseMs?: number;
150
- singleFlightTimeoutMs?: number;
151
- singleFlightPollMs?: number;
152
- /**
153
- * Maximum number of entries in `accessProfiles` and `circuitBreakers` maps
154
- * before the oldest entries are pruned. Prevents unbounded memory growth.
155
- * Defaults to 100 000.
156
- */
157
- maxProfileEntries?: number;
158
- }
159
- interface CacheAdaptiveTtlOptions {
160
- hotAfter?: number;
161
- step?: number | LayerTtlMap;
162
- maxTtl?: number | LayerTtlMap;
163
- }
164
- interface CacheCircuitBreakerOptions {
165
- failureThreshold?: number;
166
- cooldownMs?: number;
167
- }
168
- interface CacheDegradationOptions {
169
- retryAfterMs?: number;
170
- }
171
- interface CacheWarmEntry<T = unknown> {
172
- key: string;
173
- fetcher: () => Promise<T>;
174
- options?: CacheGetOptions;
175
- priority?: number;
176
- }
177
- /** Options controlling the cache warm-up process. */
178
- interface CacheWarmOptions {
179
- concurrency?: number;
180
- continueOnError?: boolean;
181
- /** Called after each entry is processed (success or failure). */
182
- onProgress?: (progress: CacheWarmProgress) => void;
183
- }
184
- /** Progress information delivered to `CacheWarmOptions.onProgress`. */
185
- interface CacheWarmProgress {
186
- completed: number;
187
- total: number;
188
- key: string;
189
- success: boolean;
190
- }
191
- interface CacheWrapOptions<TArgs extends unknown[] = unknown[]> extends CacheGetOptions {
192
- keyResolver?: (...args: TArgs) => string;
193
- }
194
- interface CacheSnapshotEntry {
195
- key: string;
196
- value: unknown;
197
- ttl?: number;
198
- }
199
- interface CacheStatsSnapshot {
200
- metrics: CacheMetricsSnapshot;
201
- layers: Array<{
202
- name: string;
203
- isLocal: boolean;
204
- degradedUntil: number | null;
205
- }>;
206
- backgroundRefreshes: number;
207
- }
208
- /** All events emitted by CacheStack and their payload shapes. */
209
- interface CacheStackEvents {
210
- /** Fired on every cache hit. */
211
- hit: {
212
- key: string;
213
- layer: string;
214
- state: 'fresh' | 'stale-while-revalidate' | 'stale-if-error';
215
- };
216
- /** Fired on every cache miss before the fetcher runs. */
217
- miss: {
218
- key: string;
219
- mode: string;
220
- };
221
- /** Fired after a value is stored in the cache. */
222
- set: {
223
- key: string;
224
- kind: string;
225
- tags?: string[];
226
- };
227
- /** Fired after one or more keys are deleted. */
228
- delete: {
229
- keys: string[];
230
- };
231
- /** Fired when a value is backfilled into a faster layer. */
232
- backfill: {
233
- key: string;
234
- layer: string;
235
- };
236
- /** Fired when a stale value is returned to the caller. */
237
- 'stale-serve': {
238
- key: string;
239
- state: string;
240
- layer: string;
241
- };
242
- /** Fired when a duplicate request is deduplicated in stampede prevention. */
243
- 'stampede-dedupe': {
244
- key: string;
245
- };
246
- /** Fired after a key is successfully warmed. */
247
- warm: {
248
- key: string;
249
- };
250
- /** Fired when an error occurs (layer failure, circuit breaker, etc.). */
251
- error: {
252
- operation: string;
253
- [key: string]: unknown;
254
- };
255
- }
256
-
257
- declare class CacheNamespace {
258
- private readonly cache;
259
- private readonly prefix;
260
- constructor(cache: CacheStack, prefix: string);
261
- get<T>(key: string, fetcher?: () => Promise<T>, options?: CacheGetOptions): Promise<T | null>;
262
- getOrSet<T>(key: string, fetcher: () => Promise<T>, options?: CacheGetOptions): Promise<T | null>;
263
- has(key: string): Promise<boolean>;
264
- ttl(key: string): Promise<number | null>;
265
- set<T>(key: string, value: T, options?: CacheWriteOptions): Promise<void>;
266
- delete(key: string): Promise<void>;
267
- mdelete(keys: string[]): Promise<void>;
268
- clear(): Promise<void>;
269
- mget<T>(entries: CacheMGetEntry<T>[]): Promise<Array<T | null>>;
270
- mset<T>(entries: CacheMSetEntry<T>[]): Promise<void>;
271
- invalidateByTag(tag: string): Promise<void>;
272
- invalidateByPattern(pattern: string): Promise<void>;
273
- wrap<TArgs extends unknown[], TResult>(keyPrefix: string, fetcher: (...args: TArgs) => Promise<TResult>, options?: CacheWrapOptions<TArgs>): (...args: TArgs) => Promise<TResult | null>;
274
- warm(entries: CacheWarmEntry[], options?: CacheWarmOptions): Promise<void>;
275
- getMetrics(): CacheMetricsSnapshot;
276
- getHitRate(): CacheHitRateSnapshot;
277
- qualify(key: string): string;
278
- }
279
-
280
- /** Typed overloads for EventEmitter so callers get autocomplete on event names. */
281
- interface CacheStack {
282
- on<K extends keyof CacheStackEvents>(event: K, listener: (data: CacheStackEvents[K]) => void): this;
283
- once<K extends keyof CacheStackEvents>(event: K, listener: (data: CacheStackEvents[K]) => void): this;
284
- off<K extends keyof CacheStackEvents>(event: K, listener: (data: CacheStackEvents[K]) => void): this;
285
- emit<K extends keyof CacheStackEvents>(event: K, data: CacheStackEvents[K]): boolean;
286
- }
287
- declare class CacheStack extends EventEmitter {
288
- private readonly layers;
289
- private readonly options;
290
- private readonly stampedeGuard;
291
- private readonly metricsCollector;
292
- private readonly instanceId;
293
- private readonly startup;
294
- private unsubscribeInvalidation?;
295
- private readonly logger;
296
- private readonly tagIndex;
297
- private readonly backgroundRefreshes;
298
- private readonly layerDegradedUntil;
299
- private readonly ttlResolver;
300
- private readonly circuitBreakerManager;
301
- private isDisconnecting;
302
- private disconnectPromise?;
303
- constructor(layers: CacheLayer[], options?: CacheStackOptions);
304
- /**
305
- * Read-through cache get.
306
- * Returns the cached value if present and fresh, or invokes `fetcher` on a miss
307
- * and stores the result across all layers. Returns `null` if the key is not found
308
- * and no `fetcher` is provided.
309
- */
310
- get<T>(key: string, fetcher?: () => Promise<T>, options?: CacheGetOptions): Promise<T | null>;
311
- /**
312
- * Alias for `get(key, fetcher, options)` — explicit get-or-set pattern.
313
- * Fetches and caches the value if not already present.
314
- */
315
- getOrSet<T>(key: string, fetcher: () => Promise<T>, options?: CacheGetOptions): Promise<T | null>;
316
- /**
317
- * Returns true if the given key exists and is not expired in any layer.
318
- */
319
- has(key: string): Promise<boolean>;
320
- /**
321
- * Returns the remaining TTL in seconds for the key in the fastest layer
322
- * that has it, or null if the key is not found / has no TTL.
323
- */
324
- ttl(key: string): Promise<number | null>;
325
- /**
326
- * Stores a value in all cache layers. Overwrites any existing value.
327
- */
328
- set<T>(key: string, value: T, options?: CacheWriteOptions): Promise<void>;
329
- /**
330
- * Deletes the key from all layers and publishes an invalidation message.
331
- */
332
- delete(key: string): Promise<void>;
333
- clear(): Promise<void>;
334
- /**
335
- * Deletes multiple keys at once. More efficient than calling `delete()` in a loop.
336
- */
337
- mdelete(keys: string[]): Promise<void>;
338
- mget<T>(entries: CacheMGetEntry<T>[]): Promise<Array<T | null>>;
339
- mset<T>(entries: CacheMSetEntry<T>[]): Promise<void>;
340
- warm(entries: CacheWarmEntry[], options?: CacheWarmOptions): Promise<void>;
341
- /**
342
- * Returns a cached version of `fetcher`. The cache key is derived from
343
- * `prefix` plus the serialized arguments unless a `keyResolver` is provided.
344
- */
345
- wrap<TArgs extends unknown[], TResult>(prefix: string, fetcher: (...args: TArgs) => Promise<TResult>, options?: CacheWrapOptions<TArgs>): (...args: TArgs) => Promise<TResult | null>;
346
- /**
347
- * Creates a `CacheNamespace` that automatically prefixes all keys with
348
- * `prefix:`. Useful for multi-tenant or module-level isolation.
349
- */
350
- namespace(prefix: string): CacheNamespace;
351
- invalidateByTag(tag: string): Promise<void>;
352
- invalidateByPattern(pattern: string): Promise<void>;
353
- getMetrics(): CacheMetricsSnapshot;
354
- getStats(): CacheStatsSnapshot;
355
- resetMetrics(): void;
356
- /**
357
- * Returns computed hit-rate statistics (overall and per-layer).
358
- */
359
- getHitRate(): CacheHitRateSnapshot;
360
- exportState(): Promise<CacheSnapshotEntry[]>;
361
- importState(entries: CacheSnapshotEntry[]): Promise<void>;
362
- persistToFile(filePath: string): Promise<void>;
363
- restoreFromFile(filePath: string): Promise<void>;
364
- disconnect(): Promise<void>;
365
- private initialize;
366
- private fetchWithGuards;
367
- private waitForFreshValue;
368
- private fetchAndPopulate;
369
- private storeEntry;
370
- private readFromLayers;
371
- private readLayerEntry;
372
- private backfill;
373
- private writeAcrossLayers;
374
- private executeLayerOperations;
375
- private resolveFreshTtl;
376
- private resolveLayerSeconds;
377
- private shouldNegativeCache;
378
- private scheduleBackgroundRefresh;
379
- private resolveSingleFlightOptions;
380
- private deleteKeys;
381
- private publishInvalidation;
382
- private handleInvalidationMessage;
383
- private formatError;
384
- private sleep;
385
- private shouldBroadcastL1Invalidation;
386
- private deleteKeysFromLayers;
387
- private validateConfiguration;
388
- private validateWriteOptions;
389
- private validateLayerNumberOption;
390
- private validatePositiveNumber;
391
- private validateNonNegativeNumber;
392
- private validateCacheKey;
393
- private serializeOptions;
394
- private validateAdaptiveTtlOptions;
395
- private validateCircuitBreakerOptions;
396
- private applyFreshReadPolicies;
397
- private shouldSkipLayer;
398
- private handleLayerFailure;
399
- private isGracefulDegradationEnabled;
400
- private recordCircuitFailure;
401
- private isNegativeStoredValue;
402
- private emitError;
403
- private serializeKeyPart;
404
- private isCacheSnapshotEntries;
405
- private normalizeForSerialization;
406
- }
407
-
408
- declare class PatternMatcher {
409
- /**
410
- * Tests whether a glob-style pattern matches a value.
411
- * Supports `*` (any sequence of characters) and `?` (any single character).
412
- * Uses a linear-time algorithm to avoid ReDoS vulnerabilities.
413
- */
414
- static matches(pattern: string, value: string): boolean;
415
- /**
416
- * Linear-time glob matching using dynamic programming.
417
- * Avoids catastrophic backtracking that RegExp-based glob matching can cause.
418
- */
419
- private static matchLinear;
420
- }
4
+ import 'node:events';
421
5
 
422
6
  interface RedisInvalidationBusOptions {
423
7
  publisher: Redis;
424
8
  subscriber?: Redis;
425
9
  channel?: string;
10
+ logger?: CacheLogger;
426
11
  }
12
+ /**
13
+ * Redis pub/sub invalidation bus.
14
+ *
15
+ * Supports multiple concurrent subscriptions — each `CacheStack` instance
16
+ * can independently call `subscribe()` and receive its own unsubscribe handle.
17
+ * The underlying Redis SUBSCRIBE is only issued once and shared across all handlers.
18
+ */
427
19
  declare class RedisInvalidationBus implements InvalidationBus {
428
20
  private readonly channel;
429
21
  private readonly publisher;
430
22
  private readonly subscriber;
431
- private activeListener?;
23
+ private readonly logger?;
24
+ private readonly handlers;
25
+ private sharedListener?;
432
26
  constructor(options: RedisInvalidationBusOptions);
433
27
  subscribe(handler: (message: InvalidationMessage) => Promise<void> | void): Promise<() => Promise<void>>;
434
28
  publish(message: InvalidationMessage): Promise<void>;
435
- private handleMessage;
29
+ private dispatchToHandlers;
436
30
  private isInvalidationMessage;
437
31
  private reportError;
438
32
  }
@@ -451,6 +45,8 @@ declare class RedisTagIndex implements CacheTagIndex {
451
45
  track(key: string, tags: string[]): Promise<void>;
452
46
  remove(key: string): Promise<void>;
453
47
  keysForTag(tag: string): Promise<string[]>;
48
+ keysForPrefix(prefix: string): Promise<string[]>;
49
+ tagsForKey(key: string): Promise<string[]>;
454
50
  matchPattern(pattern: string): Promise<string[]>;
455
51
  clear(): Promise<void>;
456
52
  private scanIndexKeys;
@@ -459,18 +55,6 @@ declare class RedisTagIndex implements CacheTagIndex {
459
55
  private tagKeysKey;
460
56
  }
461
57
 
462
- declare class TagIndex implements CacheTagIndex {
463
- private readonly tagToKeys;
464
- private readonly keyToTags;
465
- private readonly knownKeys;
466
- touch(key: string): Promise<void>;
467
- track(key: string, tags: string[]): Promise<void>;
468
- remove(key: string): Promise<void>;
469
- keysForTag(tag: string): Promise<string[]>;
470
- matchPattern(pattern: string): Promise<string[]>;
471
- clear(): Promise<void>;
472
- }
473
-
474
58
  declare function createCacheStatsHandler(cache: CacheStack): (_request: unknown, response: {
475
59
  setHeader?: (name: string, value: string) => void;
476
60
  end: (body: string) => void;
@@ -493,11 +77,74 @@ interface FastifyLayercachePluginOptions {
493
77
  }
494
78
  declare function createFastifyLayercachePlugin(cache: CacheStack, options?: FastifyLayercachePluginOptions): (fastify: FastifyLike) => Promise<void>;
495
79
 
80
+ interface ExpressLikeRequest {
81
+ method?: string;
82
+ url?: string;
83
+ originalUrl?: string;
84
+ path?: string;
85
+ query?: Record<string, unknown>;
86
+ }
87
+ interface ExpressLikeResponse {
88
+ statusCode?: number;
89
+ setHeader?: (name: string, value: string) => void;
90
+ json?: (body: unknown) => void;
91
+ end?: (body?: string) => void;
92
+ }
93
+ type NextFunction = (error?: unknown) => void;
94
+ interface ExpressCacheMiddlewareOptions extends CacheGetOptions {
95
+ /**
96
+ * Resolves a cache key from the incoming request. Defaults to
97
+ * `GET:<req.originalUrl || req.url>`.
98
+ */
99
+ keyResolver?: (req: ExpressLikeRequest) => string;
100
+ /**
101
+ * Only cache responses for these HTTP methods. Defaults to `['GET']`.
102
+ */
103
+ methods?: string[];
104
+ }
105
+ /**
106
+ * Express/Connect-compatible middleware that caches JSON responses.
107
+ *
108
+ * ```ts
109
+ * import express from 'express'
110
+ * import { CacheStack, MemoryLayer, createExpressCacheMiddleware } from 'layercache'
111
+ *
112
+ * const cache = new CacheStack([new MemoryLayer({ ttl: 60 })])
113
+ * const app = express()
114
+ *
115
+ * app.get('/api/data', createExpressCacheMiddleware(cache, { ttl: 30 }), (req, res) => {
116
+ * res.json({ fresh: true })
117
+ * })
118
+ * ```
119
+ */
120
+ declare function createExpressCacheMiddleware(cache: CacheStack, options?: ExpressCacheMiddlewareOptions): (req: ExpressLikeRequest, res: ExpressLikeResponse, next: NextFunction) => Promise<void>;
121
+
496
122
  interface GraphqlCacheOptions<TArgs extends unknown[]> extends CacheGetOptions {
497
123
  keyResolver?: (...args: TArgs) => string;
498
124
  }
499
125
  declare function cacheGraphqlResolver<TArgs extends unknown[], TResult>(cache: CacheStack, prefix: string, resolver: (...args: TArgs) => Promise<TResult>, options?: GraphqlCacheOptions<TArgs>): (...args: TArgs) => Promise<TResult | null>;
500
126
 
127
+ interface OpenTelemetrySpan {
128
+ setAttribute?: (name: string, value: unknown) => void;
129
+ recordException?: (error: unknown) => void;
130
+ end: () => void;
131
+ }
132
+ interface OpenTelemetryTracer {
133
+ startSpan: (name: string, options?: {
134
+ attributes?: Record<string, unknown>;
135
+ }) => OpenTelemetrySpan;
136
+ }
137
+ /**
138
+ * Lightweight OpenTelemetry instrumentation for a CacheStack instance.
139
+ *
140
+ * Note: this implementation wraps instance methods directly. Avoid stacking
141
+ * multiple OpenTelemetry plugins on the same CacheStack at the same time,
142
+ * because each plugin replaces and later restores those methods.
143
+ */
144
+ declare function createOpenTelemetryPlugin(cache: CacheStack, tracer: OpenTelemetryTracer): {
145
+ uninstall(): void;
146
+ };
147
+
501
148
  interface TrpcCacheMiddlewareContext<TInput = unknown, TResult = unknown> {
502
149
  path?: string;
503
150
  type?: string;
@@ -515,83 +162,44 @@ declare function createTrpcCacheMiddleware<TInput = unknown, TResult = unknown>(
515
162
  data?: TResult;
516
163
  } | null>;
517
164
 
518
- interface MemoryLayerSnapshotEntry {
519
- key: string;
520
- value: unknown;
521
- expiresAt: number | null;
522
- }
523
- /**
524
- * Eviction policy applied when `maxSize` is reached.
525
- * - `lru` (default): evicts the Least Recently Used entry.
526
- * - `lfu`: evicts the Least Frequently Used entry.
527
- * - `fifo`: evicts the oldest inserted entry.
528
- */
529
- type EvictionPolicy = 'lru' | 'lfu' | 'fifo';
530
- interface MemoryLayerOptions {
531
- ttl?: number;
532
- maxSize?: number;
533
- name?: string;
534
- evictionPolicy?: EvictionPolicy;
535
- }
536
- declare class MemoryLayer implements CacheLayer {
537
- readonly name: string;
538
- readonly defaultTtl?: number;
539
- readonly isLocal = true;
540
- private readonly maxSize;
541
- private readonly evictionPolicy;
542
- private readonly entries;
543
- constructor(options?: MemoryLayerOptions);
544
- get<T>(key: string): Promise<T | null>;
545
- getEntry<T = unknown>(key: string): Promise<T | null>;
546
- getMany<T>(keys: string[]): Promise<Array<T | null>>;
547
- set(key: string, value: unknown, ttl?: number | undefined): Promise<void>;
548
- has(key: string): Promise<boolean>;
549
- ttl(key: string): Promise<number | null>;
550
- size(): Promise<number>;
551
- delete(key: string): Promise<void>;
552
- deleteMany(keys: string[]): Promise<void>;
553
- clear(): Promise<void>;
554
- keys(): Promise<string[]>;
555
- exportState(): MemoryLayerSnapshotEntry[];
556
- importState(entries: MemoryLayerSnapshotEntry[]): void;
557
- private evict;
558
- private pruneExpired;
559
- private isExpired;
560
- }
561
-
562
165
  type CompressionAlgorithm = 'gzip' | 'brotli';
563
166
  interface RedisLayerOptions {
564
167
  client: Redis;
565
168
  ttl?: number;
566
169
  name?: string;
567
- serializer?: CacheSerializer;
170
+ serializer?: CacheSerializer | CacheSerializer[];
568
171
  prefix?: string;
569
172
  allowUnprefixedClear?: boolean;
570
173
  scanCount?: number;
571
174
  compression?: CompressionAlgorithm;
572
175
  compressionThreshold?: number;
176
+ disconnectOnDispose?: boolean;
573
177
  }
574
178
  declare class RedisLayer implements CacheLayer {
575
179
  readonly name: string;
576
180
  readonly defaultTtl?: number;
577
181
  readonly isLocal = false;
578
182
  private readonly client;
579
- private readonly serializer;
183
+ private readonly serializers;
580
184
  private readonly prefix;
581
185
  private readonly allowUnprefixedClear;
582
186
  private readonly scanCount;
583
187
  private readonly compression?;
584
188
  private readonly compressionThreshold;
189
+ private readonly disconnectOnDispose;
585
190
  constructor(options: RedisLayerOptions);
586
191
  get<T>(key: string): Promise<T | null>;
587
192
  getEntry<T = unknown>(key: string): Promise<T | null>;
588
193
  getMany<T>(keys: string[]): Promise<Array<T | null>>;
194
+ setMany(entries: CacheLayerSetManyEntry[]): Promise<void>;
589
195
  set(key: string, value: unknown, ttl?: number | undefined): Promise<void>;
590
196
  delete(key: string): Promise<void>;
591
197
  deleteMany(keys: string[]): Promise<void>;
592
198
  has(key: string): Promise<boolean>;
593
199
  ttl(key: string): Promise<number | null>;
594
200
  size(): Promise<number>;
201
+ ping(): Promise<boolean>;
202
+ dispose(): Promise<void>;
595
203
  /**
596
204
  * Deletes all keys matching the layer's prefix in batches to avoid
597
205
  * loading millions of keys into memory at once.
@@ -601,8 +209,17 @@ declare class RedisLayer implements CacheLayer {
601
209
  private scanKeys;
602
210
  private withPrefix;
603
211
  private deserializeOrDelete;
212
+ private rewriteWithPrimarySerializer;
213
+ private primarySerializer;
604
214
  private isSerializablePayload;
215
+ /**
216
+ * Compresses the payload asynchronously if compression is enabled and the
217
+ * payload exceeds the threshold. This avoids blocking the event loop.
218
+ */
605
219
  private encodePayload;
220
+ /**
221
+ * Decompresses the payload asynchronously if a compression header is present.
222
+ */
606
223
  private decodePayload;
607
224
  }
608
225
 
@@ -611,12 +228,21 @@ interface DiskLayerOptions {
611
228
  ttl?: number;
612
229
  name?: string;
613
230
  serializer?: CacheSerializer;
231
+ /**
232
+ * Maximum number of cache files to store on disk. When exceeded, the oldest
233
+ * entries (by file mtime) are evicted to keep the directory bounded.
234
+ * Defaults to unlimited.
235
+ */
236
+ maxFiles?: number;
614
237
  }
615
238
  /**
616
239
  * A file-system backed cache layer.
617
240
  * Each key is stored as a separate JSON file in `directory`.
618
241
  * Useful for persisting cache across process restarts without needing Redis.
619
242
  *
243
+ * - `keys()` returns the original cache key strings (not hashes).
244
+ * - `maxFiles` limits on-disk entries; when exceeded, oldest files are evicted.
245
+ *
620
246
  * NOTE: DiskLayer is designed for low-to-medium traffic scenarios.
621
247
  * For high-throughput workloads, use MemoryLayer + RedisLayer.
622
248
  */
@@ -626,19 +252,34 @@ declare class DiskLayer implements CacheLayer {
626
252
  readonly isLocal = true;
627
253
  private readonly directory;
628
254
  private readonly serializer;
255
+ private readonly maxFiles;
256
+ private writeQueue;
629
257
  constructor(options: DiskLayerOptions);
630
258
  get<T>(key: string): Promise<T | null>;
631
259
  getEntry<T = unknown>(key: string): Promise<T | null>;
632
260
  set(key: string, value: unknown, ttl?: number | undefined): Promise<void>;
261
+ getMany<T>(keys: string[]): Promise<Array<T | null>>;
262
+ setMany(entries: CacheLayerSetManyEntry[]): Promise<void>;
633
263
  has(key: string): Promise<boolean>;
634
264
  ttl(key: string): Promise<number | null>;
635
265
  delete(key: string): Promise<void>;
636
266
  deleteMany(keys: string[]): Promise<void>;
637
267
  clear(): Promise<void>;
268
+ /**
269
+ * Returns the original cache key strings stored on disk.
270
+ * Expired entries are skipped and cleaned up during the scan.
271
+ */
638
272
  keys(): Promise<string[]>;
639
273
  size(): Promise<number>;
274
+ ping(): Promise<boolean>;
275
+ dispose(): Promise<void>;
640
276
  private keyToPath;
641
277
  private safeDelete;
278
+ private enqueueWrite;
279
+ /**
280
+ * Removes the oldest files (by mtime) when the directory exceeds maxFiles.
281
+ */
282
+ private enforceMaxFiles;
642
283
  }
643
284
 
644
285
  /**
@@ -663,10 +304,14 @@ interface MemcachedLayerOptions {
663
304
  ttl?: number;
664
305
  name?: string;
665
306
  keyPrefix?: string;
307
+ serializer?: CacheSerializer;
666
308
  }
667
309
  /**
668
310
  * Memcached-backed cache layer.
669
311
  *
312
+ * Now supports pluggable serializers (default: JSON), StoredValueEnvelope
313
+ * for stale-while-revalidate / stale-if-error semantics, and bulk reads.
314
+ *
670
315
  * Example usage with `memjs`:
671
316
  * ```ts
672
317
  * import Memjs from 'memjs'
@@ -685,9 +330,13 @@ declare class MemcachedLayer implements CacheLayer {
685
330
  readonly isLocal = false;
686
331
  private readonly client;
687
332
  private readonly keyPrefix;
333
+ private readonly serializer;
688
334
  constructor(options: MemcachedLayerOptions);
689
335
  get<T>(key: string): Promise<T | null>;
336
+ getEntry<T = unknown>(key: string): Promise<T | null>;
337
+ getMany<T>(keys: string[]): Promise<Array<T | null>>;
690
338
  set(key: string, value: unknown, ttl?: number | undefined): Promise<void>;
339
+ has(key: string): Promise<boolean>;
691
340
  delete(key: string): Promise<void>;
692
341
  deleteMany(keys: string[]): Promise<void>;
693
342
  clear(): Promise<void>;
@@ -725,6 +374,9 @@ declare class StampedeGuard {
725
374
  * Returns a function that generates a Prometheus-compatible text exposition
726
375
  * of the cache metrics from one or more CacheStack instances.
727
376
  *
377
+ * Now includes per-layer latency gauges (`layercache_layer_latency_avg_ms`,
378
+ * `layercache_layer_latency_max_ms`, `layercache_layer_latency_count`).
379
+ *
728
380
  * Usage example:
729
381
  * ```ts
730
382
  * const collect = createPrometheusMetricsExporter(cache)
@@ -742,4 +394,4 @@ declare function createPrometheusMetricsExporter(stacks: CacheStack | Array<{
742
394
  name: string;
743
395
  }>): () => string;
744
396
 
745
- export { type CacheAdaptiveTtlOptions, type CacheCircuitBreakerOptions, type CacheDegradationOptions, type CacheGetOptions, type CacheHitRateSnapshot, type CacheLayer, type CacheLogger, type CacheMGetEntry, type CacheMSetEntry, type CacheMetricsSnapshot, CacheNamespace, type CacheSerializer, type CacheSingleFlightCoordinator, type CacheSingleFlightExecutionOptions, type CacheSnapshotEntry, CacheStack, type CacheStackEvents, type CacheStackOptions, type CacheStatsSnapshot, type CacheTagIndex, type CacheWarmEntry, type CacheWarmOptions, type CacheWarmProgress, type CacheWrapOptions, type CacheWriteOptions, DiskLayer, type EvictionPolicy, type InvalidationBus, type InvalidationMessage, JsonSerializer, type LayerTtlMap, type MemcachedClient, MemcachedLayer, MemoryLayer, type MemoryLayerSnapshotEntry, MsgpackSerializer, PatternMatcher, RedisInvalidationBus, RedisLayer, RedisSingleFlightCoordinator, RedisTagIndex, StampedeGuard, TagIndex, cacheGraphqlResolver, createCacheStatsHandler, createCachedMethodDecorator, createFastifyLayercachePlugin, createPrometheusMetricsExporter, createTrpcCacheMiddleware };
397
+ export { CacheGetOptions, CacheLayer, CacheLayerSetManyEntry, CacheLogger, CacheSerializer, CacheSingleFlightCoordinator, CacheSingleFlightExecutionOptions, CacheStack, CacheTagIndex, CacheWrapOptions, DiskLayer, InvalidationBus, InvalidationMessage, JsonSerializer, type MemcachedClient, MemcachedLayer, MsgpackSerializer, RedisInvalidationBus, RedisLayer, RedisSingleFlightCoordinator, RedisTagIndex, StampedeGuard, cacheGraphqlResolver, createCacheStatsHandler, createCachedMethodDecorator, createExpressCacheMiddleware, createFastifyLayercachePlugin, createOpenTelemetryPlugin, createPrometheusMetricsExporter, createTrpcCacheMiddleware };