layercache 1.3.3 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +42 -41
  2. package/dist/{chunk-BORDQ3LA.js → chunk-7KMKQ6QZ.js} +15 -1
  3. package/dist/{chunk-5RCAX2BQ.js → chunk-FFZCC7EQ.js} +3 -3
  4. package/dist/{chunk-4PPBOOXT.js → chunk-KJDFYE5T.js} +38 -26
  5. package/dist/cli.cjs +9 -9
  6. package/dist/cli.js +4 -4
  7. package/dist/{edge-CUHTP9Bc.d.cts → edge-D2FpRlyS.d.cts} +74 -36
  8. package/dist/{edge-CUHTP9Bc.d.ts → edge-D2FpRlyS.d.ts} +74 -36
  9. package/dist/edge.cjs +9 -9
  10. package/dist/edge.d.cts +1 -1
  11. package/dist/edge.d.ts +1 -1
  12. package/dist/edge.js +2 -2
  13. package/dist/index.cjs +787 -466
  14. package/dist/index.d.cts +6 -6
  15. package/dist/index.d.ts +6 -6
  16. package/dist/index.js +682 -383
  17. package/package.json +6 -6
  18. package/benchmarks/direct.ts +0 -221
  19. package/benchmarks/edge-utils.ts +0 -28
  20. package/benchmarks/edge.ts +0 -491
  21. package/benchmarks/http.ts +0 -99
  22. package/benchmarks/latency.ts +0 -45
  23. package/benchmarks/memory-pressure.ts +0 -144
  24. package/benchmarks/multi-process-fanout.ts +0 -231
  25. package/benchmarks/multi-process-worker.ts +0 -151
  26. package/benchmarks/paths.ts +0 -25
  27. package/benchmarks/queue-amplification-utils.ts +0 -48
  28. package/benchmarks/queue-amplification.ts +0 -230
  29. package/benchmarks/redis-latency-proxy.ts +0 -100
  30. package/benchmarks/redis.ts +0 -107
  31. package/benchmarks/scenario-utils.ts +0 -38
  32. package/benchmarks/server.ts +0 -157
  33. package/benchmarks/slow-redis-latency.ts +0 -309
  34. package/benchmarks/slow-redis-utils.ts +0 -29
  35. package/benchmarks/slow-redis.ts +0 -47
  36. package/benchmarks/stampede.ts +0 -26
  37. package/benchmarks/stats.ts +0 -46
  38. package/benchmarks/workload.ts +0 -77
  39. package/examples/express-api/index.ts +0 -31
  40. package/examples/nextjs-api-routes/route.ts +0 -16
@@ -11,18 +11,26 @@ declare class CacheMissError extends Error {
11
11
  interface LayerTtlMap {
12
12
  [layerName: string]: number | undefined;
13
13
  }
14
- interface CacheWriteOptions {
14
+ type CacheEntryWriteKind = 'value' | 'empty';
15
+ interface CacheEntryWriteOptions {
15
16
  tags?: string[];
16
17
  ttl?: number | LayerTtlMap;
17
18
  ttlPolicy?: CacheTtlPolicy;
18
- negativeCache?: boolean;
19
19
  negativeTtl?: number | LayerTtlMap;
20
20
  staleWhileRevalidate?: number | LayerTtlMap;
21
21
  staleIfError?: number | LayerTtlMap;
22
22
  ttlJitter?: number | LayerTtlMap;
23
+ adaptiveTtl?: boolean | CacheAdaptiveTtlOptions;
24
+ }
25
+ interface CacheContextOptionsContext {
26
+ key: string;
27
+ value: unknown;
28
+ kind: CacheEntryWriteKind;
29
+ }
30
+ interface CacheWriteOptions extends CacheEntryWriteOptions {
31
+ negativeCache?: boolean;
23
32
  slidingTtl?: boolean;
24
33
  refreshAhead?: number | LayerTtlMap;
25
- adaptiveTtl?: boolean | CacheAdaptiveTtlOptions;
26
34
  circuitBreaker?: CacheCircuitBreakerOptions;
27
35
  fetcherRateLimit?: CacheRateLimitOptions;
28
36
  /**
@@ -34,12 +42,37 @@ interface CacheWriteOptions {
34
42
  * cache.get('key', fetchData, { shouldCache: (v) => v.status === 200 })
35
43
  */
36
44
  shouldCache?: (value: unknown) => boolean;
45
+ /**
46
+ * Optional resolver that can override cache entry options using the current
47
+ * write context. This runs right before a value is stored, so callers can
48
+ * derive TTLs or tags from the fetched value instead of guessing upfront.
49
+ *
50
+ * Returned values override any static entry options already present on the
51
+ * same object. Fetch controls like `shouldCache`, `negativeCache`,
52
+ * `refreshAhead`, or `circuitBreaker` are not affected.
53
+ *
54
+ * @example
55
+ * cache.get('oauth:token', fetchToken, {
56
+ * ttl: 300_000,
57
+ * contextOptions: ({ value }) => ({
58
+ * ttl: Math.max(1, Math.floor(((value as { refreshExpiresIn: number }).refreshExpiresIn ?? 0) / 1_000))
59
+ * })
60
+ * })
61
+ */
62
+ contextOptions?: (context: CacheContextOptionsContext) => CacheEntryWriteOptions | undefined;
37
63
  }
38
64
  interface CacheGetOptions extends CacheWriteOptions {
39
65
  }
66
+ interface CacheFetcherContext<T = unknown> {
67
+ key: string;
68
+ currentValue: T | undefined;
69
+ state: 'miss' | 'fresh' | 'stale-while-revalidate' | 'stale-if-error';
70
+ layer?: string;
71
+ }
72
+ type CacheFetcher<T = unknown> = (context: CacheFetcherContext<T>) => Promise<T>;
40
73
  interface CacheMGetEntry<T> {
41
74
  key: string;
42
- fetch?: () => Promise<T>;
75
+ fetch?: CacheFetcher<T>;
43
76
  options?: CacheGetOptions;
44
77
  }
45
78
  interface CacheMSetEntry<T> {
@@ -75,7 +108,7 @@ interface CacheLayer {
75
108
  */
76
109
  has?(key: string): Promise<boolean>;
77
110
  /**
78
- * Returns the remaining TTL in seconds for the key, or null if the key
111
+ * Returns the remaining TTL in milliseconds for the key, or null if the key
79
112
  * does not exist, has no TTL, or has already expired.
80
113
  * Implementations may omit this.
81
114
  */
@@ -159,7 +192,7 @@ interface InvalidationMessage {
159
192
  scope: 'key' | 'keys' | 'clear';
160
193
  sourceId: string;
161
194
  keys?: string[];
162
- operation?: 'write' | 'delete' | 'invalidate' | 'clear';
195
+ operation?: 'write' | 'delete' | 'invalidate' | 'expire' | 'clear';
163
196
  }
164
197
  interface InvalidationBus {
165
198
  subscribe(handler: (message: InvalidationMessage) => Promise<void> | void): Promise<() => Promise<void> | void>;
@@ -255,7 +288,7 @@ interface CacheWriteBehindOptions {
255
288
  }
256
289
  interface CacheWarmEntry<T = unknown> {
257
290
  key: string;
258
- fetcher: () => Promise<T>;
291
+ fetcher: CacheFetcher<T>;
259
292
  options?: CacheGetOptions;
260
293
  priority?: number;
261
294
  }
@@ -301,12 +334,12 @@ interface CacheInspectResult {
301
334
  key: string;
302
335
  /** Layers in which the key is currently stored (not expired). */
303
336
  foundInLayers: string[];
304
- /** Remaining fresh TTL in seconds, or null if no expiry or not an envelope. */
305
- freshTtlSeconds: number | null;
306
- /** Remaining stale-while-revalidate window in seconds, or null. */
307
- staleTtlSeconds: number | null;
308
- /** Remaining stale-if-error window in seconds, or null. */
309
- errorTtlSeconds: number | null;
337
+ /** Remaining fresh TTL in milliseconds, or null if no expiry or not an envelope. */
338
+ freshTtlMs: number | null;
339
+ /** Remaining stale-while-revalidate window in milliseconds, or null. */
340
+ staleTtlMs: number | null;
341
+ /** Remaining stale-if-error window in milliseconds, or null. */
342
+ errorTtlMs: number | null;
310
343
  /** Whether the key is currently serving stale-while-revalidate. */
311
344
  isStale: boolean;
312
345
  /** Tags associated with this key (from the TagIndex). */
@@ -335,6 +368,10 @@ interface CacheStackEvents {
335
368
  delete: {
336
369
  keys: string[];
337
370
  };
371
+ /** Fired after one or more keys are marked expired but retained. */
372
+ expire: {
373
+ keys: string[];
374
+ };
338
375
  /** Fired when a value is backfilled into a faster layer. */
339
376
  backfill: {
340
377
  key: string;
@@ -486,12 +523,12 @@ declare class CacheNamespace {
486
523
  private static readonly metricsMutexes;
487
524
  private metrics;
488
525
  constructor(cache: CacheStack, prefix: string);
489
- get<T>(key: string, fetcher?: () => Promise<T>, options?: CacheGetOptions): Promise<T | null>;
490
- getOrSet<T>(key: string, fetcher: () => Promise<T>, options?: CacheGetOptions): Promise<T | null>;
526
+ get<T>(key: string, fetcher?: CacheFetcher<T>, options?: CacheGetOptions): Promise<T | null>;
527
+ getOrSet<T>(key: string, fetcher: CacheFetcher<T>, options?: CacheGetOptions): Promise<T | null>;
491
528
  /**
492
529
  * Like `get()`, but throws `CacheMissError` instead of returning `null`.
493
530
  */
494
- getOrThrow<T>(key: string, fetcher?: () => Promise<T>, options?: CacheGetOptions): Promise<T>;
531
+ getOrThrow<T>(key: string, fetcher?: CacheFetcher<T>, options?: CacheGetOptions): Promise<T>;
495
532
  has(key: string): Promise<boolean>;
496
533
  ttl(key: string): Promise<number | null>;
497
534
  set<T>(key: string, value: T, options?: CacheWriteOptions): Promise<void>;
@@ -501,9 +538,13 @@ declare class CacheNamespace {
501
538
  mget<T>(entries: CacheMGetEntry<T>[]): Promise<Array<T | null>>;
502
539
  mset<T>(entries: CacheMSetEntry<T>[]): Promise<void>;
503
540
  invalidateByTag(tag: string): Promise<void>;
541
+ expireByTag(tag: string): Promise<void>;
504
542
  invalidateByTags(tags: string[], mode?: 'any' | 'all'): Promise<void>;
543
+ expireByTags(tags: string[], mode?: 'any' | 'all'): Promise<void>;
505
544
  invalidateByPattern(pattern: string): Promise<void>;
545
+ expireByPattern(pattern: string): Promise<void>;
506
546
  invalidateByPrefix(prefix: string): Promise<void>;
547
+ expireByPrefix(prefix: string): Promise<void>;
507
548
  /**
508
549
  * Returns detailed metadata about a single cache key within this namespace.
509
550
  */
@@ -557,8 +598,6 @@ declare class CacheStack extends EventEmitter {
557
598
  private readonly invalidation;
558
599
  private readonly layerWriter;
559
600
  private readonly snapshots;
560
- private readonly backgroundRefreshes;
561
- private readonly backgroundRefreshAbort;
562
601
  private readonly layerDegradedUntil;
563
602
  private readonly maintenance;
564
603
  private readonly ttlResolver;
@@ -566,6 +605,7 @@ declare class CacheStack extends EventEmitter {
566
605
  private nextOperationId;
567
606
  private currentGeneration?;
568
607
  private isDisconnecting;
608
+ private readonly reader;
569
609
  private disconnectPromise?;
570
610
  constructor(layers: CacheLayer[], options?: CacheStackOptions);
571
611
  /**
@@ -574,25 +614,24 @@ declare class CacheStack extends EventEmitter {
574
614
  * and stores the result across all layers. Returns `null` if the key is not found
575
615
  * and no `fetcher` is provided.
576
616
  */
577
- get<T>(key: string, fetcher?: () => Promise<T>, options?: CacheGetOptions): Promise<T | null>;
578
- private getPrepared;
617
+ get<T>(key: string, fetcher?: CacheFetcher<T>, options?: CacheGetOptions): Promise<T | null>;
579
618
  /**
580
619
  * Alias for `get(key, fetcher, options)` — explicit get-or-set pattern.
581
620
  * Fetches and caches the value if not already present.
582
621
  */
583
- getOrSet<T>(key: string, fetcher: () => Promise<T>, options?: CacheGetOptions): Promise<T | null>;
622
+ getOrSet<T>(key: string, fetcher: CacheFetcher<T>, options?: CacheGetOptions): Promise<T | null>;
584
623
  /**
585
624
  * Like `get()`, but throws `CacheMissError` instead of returning `null`.
586
625
  * Useful when the value is expected to exist or the fetcher is expected to
587
626
  * return non-null.
588
627
  */
589
- getOrThrow<T>(key: string, fetcher?: () => Promise<T>, options?: CacheGetOptions): Promise<T>;
628
+ getOrThrow<T>(key: string, fetcher?: CacheFetcher<T>, options?: CacheGetOptions): Promise<T>;
590
629
  /**
591
630
  * Returns true if the given key exists and is not expired in any layer.
592
631
  */
593
632
  has(key: string): Promise<boolean>;
594
633
  /**
595
- * Returns the remaining TTL in seconds for the key in the fastest layer
634
+ * Returns the remaining TTL in milliseconds for the key in the fastest layer
596
635
  * that has it, or null if the key is not found / has no TTL.
597
636
  */
598
637
  ttl(key: string): Promise<number | null>;
@@ -623,9 +662,13 @@ declare class CacheStack extends EventEmitter {
623
662
  */
624
663
  namespace(prefix: string): CacheNamespace;
625
664
  invalidateByTag(tag: string): Promise<void>;
665
+ expireByTag(tag: string): Promise<void>;
626
666
  invalidateByTags(tags: string[], mode?: 'any' | 'all'): Promise<void>;
667
+ expireByTags(tags: string[], mode?: 'any' | 'all'): Promise<void>;
627
668
  invalidateByPattern(pattern: string): Promise<void>;
669
+ expireByPattern(pattern: string): Promise<void>;
628
670
  invalidateByPrefix(prefix: string): Promise<void>;
671
+ expireByPrefix(prefix: string): Promise<void>;
629
672
  getMetrics(): CacheMetricsSnapshot;
630
673
  getStats(): CacheStatsSnapshot;
631
674
  resetMetrics(): void;
@@ -652,21 +695,15 @@ declare class CacheStack extends EventEmitter {
652
695
  restoreFromFile(filePath: string): Promise<void>;
653
696
  disconnect(): Promise<void>;
654
697
  private initialize;
655
- private fetchWithGuards;
656
- private waitForFreshValue;
657
- private fetchAndPopulate;
658
698
  private storeEntry;
659
699
  private writeBatch;
660
- private readFromLayers;
661
- private readLayerEntry;
662
- private backfill;
663
700
  private resolveFreshTtl;
664
- private resolveLayerSeconds;
665
- private shouldNegativeCache;
666
- private scheduleBackgroundRefresh;
667
- private runBackgroundRefresh;
668
- private resolveSingleFlightOptions;
701
+ private resolveLayerMs;
702
+ private resolveContextOptions;
703
+ private isPlainObject;
669
704
  private deleteKeys;
705
+ private expireKeys;
706
+ private expireKeysInLayers;
670
707
  private publishInvalidation;
671
708
  private handleInvalidationMessage;
672
709
  private getTagsForKey;
@@ -689,13 +726,14 @@ declare class CacheStack extends EventEmitter {
689
726
  private validateWriteOptions;
690
727
  private assertActive;
691
728
  private awaitStartup;
729
+ private readLayerEntry;
730
+ private scheduleBackgroundRefresh;
692
731
  private applyFreshReadPolicies;
693
732
  private shouldSkipLayer;
694
733
  private handleLayerFailure;
695
734
  private reportRecoverableLayerFailure;
696
735
  private isGracefulDegradationEnabled;
697
736
  private recordCircuitFailure;
698
- private isNegativeStoredValue;
699
737
  private emitError;
700
738
  private snapshotMaxBytes;
701
739
  private snapshotMaxEntries;
@@ -722,4 +760,4 @@ interface HonoCacheMiddlewareOptions extends CacheGetOptions {
722
760
  }
723
761
  declare function createHonoCacheMiddleware(cache: CacheStack, options?: HonoCacheMiddlewareOptions): (context: HonoLikeContext, next: () => Promise<void>) => Promise<unknown>;
724
762
 
725
- 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 };
763
+ export { CacheNamespace as A, type CacheRateLimitOptions as B, type CacheLogger as C, type CacheSnapshotEntry as D, type CacheStackEvents as E, type CacheStackOptions as F, type CacheStatsSnapshot as G, type CacheTtlPolicy as H, type InvalidationBus as I, type CacheTtlPolicyContext as J, type CacheWarmEntry as K, type CacheWarmOptions as L, type CacheWarmProgress as M, type CacheWriteBehindOptions as N, type CacheWriteOptions as O, type EvictionPolicy as P, type LayerTtlMap as Q, MemoryLayer as R, type MemoryLayerOptions as S, type MemoryLayerSnapshotEntry as T, PatternMatcher as U, TagIndex as V, createHonoCacheMiddleware as W, 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 CacheContextOptionsContext as m, type CacheDegradationOptions as n, type CacheEntryWriteKind as o, type CacheEntryWriteOptions as p, type CacheFetcher as q, type CacheFetcherContext as r, type CacheHealthCheckResult as s, type CacheHitRateSnapshot as t, type CacheInspectResult as u, type CacheLayerLatency as v, type CacheMGetEntry as w, type CacheMSetEntry as x, type CacheMetricsSnapshot as y, CacheMissError as z };
@@ -11,18 +11,26 @@ declare class CacheMissError extends Error {
11
11
  interface LayerTtlMap {
12
12
  [layerName: string]: number | undefined;
13
13
  }
14
- interface CacheWriteOptions {
14
+ type CacheEntryWriteKind = 'value' | 'empty';
15
+ interface CacheEntryWriteOptions {
15
16
  tags?: string[];
16
17
  ttl?: number | LayerTtlMap;
17
18
  ttlPolicy?: CacheTtlPolicy;
18
- negativeCache?: boolean;
19
19
  negativeTtl?: number | LayerTtlMap;
20
20
  staleWhileRevalidate?: number | LayerTtlMap;
21
21
  staleIfError?: number | LayerTtlMap;
22
22
  ttlJitter?: number | LayerTtlMap;
23
+ adaptiveTtl?: boolean | CacheAdaptiveTtlOptions;
24
+ }
25
+ interface CacheContextOptionsContext {
26
+ key: string;
27
+ value: unknown;
28
+ kind: CacheEntryWriteKind;
29
+ }
30
+ interface CacheWriteOptions extends CacheEntryWriteOptions {
31
+ negativeCache?: boolean;
23
32
  slidingTtl?: boolean;
24
33
  refreshAhead?: number | LayerTtlMap;
25
- adaptiveTtl?: boolean | CacheAdaptiveTtlOptions;
26
34
  circuitBreaker?: CacheCircuitBreakerOptions;
27
35
  fetcherRateLimit?: CacheRateLimitOptions;
28
36
  /**
@@ -34,12 +42,37 @@ interface CacheWriteOptions {
34
42
  * cache.get('key', fetchData, { shouldCache: (v) => v.status === 200 })
35
43
  */
36
44
  shouldCache?: (value: unknown) => boolean;
45
+ /**
46
+ * Optional resolver that can override cache entry options using the current
47
+ * write context. This runs right before a value is stored, so callers can
48
+ * derive TTLs or tags from the fetched value instead of guessing upfront.
49
+ *
50
+ * Returned values override any static entry options already present on the
51
+ * same object. Fetch controls like `shouldCache`, `negativeCache`,
52
+ * `refreshAhead`, or `circuitBreaker` are not affected.
53
+ *
54
+ * @example
55
+ * cache.get('oauth:token', fetchToken, {
56
+ * ttl: 300_000,
57
+ * contextOptions: ({ value }) => ({
58
+ * ttl: Math.max(1, Math.floor(((value as { refreshExpiresIn: number }).refreshExpiresIn ?? 0) / 1_000))
59
+ * })
60
+ * })
61
+ */
62
+ contextOptions?: (context: CacheContextOptionsContext) => CacheEntryWriteOptions | undefined;
37
63
  }
38
64
  interface CacheGetOptions extends CacheWriteOptions {
39
65
  }
66
+ interface CacheFetcherContext<T = unknown> {
67
+ key: string;
68
+ currentValue: T | undefined;
69
+ state: 'miss' | 'fresh' | 'stale-while-revalidate' | 'stale-if-error';
70
+ layer?: string;
71
+ }
72
+ type CacheFetcher<T = unknown> = (context: CacheFetcherContext<T>) => Promise<T>;
40
73
  interface CacheMGetEntry<T> {
41
74
  key: string;
42
- fetch?: () => Promise<T>;
75
+ fetch?: CacheFetcher<T>;
43
76
  options?: CacheGetOptions;
44
77
  }
45
78
  interface CacheMSetEntry<T> {
@@ -75,7 +108,7 @@ interface CacheLayer {
75
108
  */
76
109
  has?(key: string): Promise<boolean>;
77
110
  /**
78
- * Returns the remaining TTL in seconds for the key, or null if the key
111
+ * Returns the remaining TTL in milliseconds for the key, or null if the key
79
112
  * does not exist, has no TTL, or has already expired.
80
113
  * Implementations may omit this.
81
114
  */
@@ -159,7 +192,7 @@ interface InvalidationMessage {
159
192
  scope: 'key' | 'keys' | 'clear';
160
193
  sourceId: string;
161
194
  keys?: string[];
162
- operation?: 'write' | 'delete' | 'invalidate' | 'clear';
195
+ operation?: 'write' | 'delete' | 'invalidate' | 'expire' | 'clear';
163
196
  }
164
197
  interface InvalidationBus {
165
198
  subscribe(handler: (message: InvalidationMessage) => Promise<void> | void): Promise<() => Promise<void> | void>;
@@ -255,7 +288,7 @@ interface CacheWriteBehindOptions {
255
288
  }
256
289
  interface CacheWarmEntry<T = unknown> {
257
290
  key: string;
258
- fetcher: () => Promise<T>;
291
+ fetcher: CacheFetcher<T>;
259
292
  options?: CacheGetOptions;
260
293
  priority?: number;
261
294
  }
@@ -301,12 +334,12 @@ interface CacheInspectResult {
301
334
  key: string;
302
335
  /** Layers in which the key is currently stored (not expired). */
303
336
  foundInLayers: string[];
304
- /** Remaining fresh TTL in seconds, or null if no expiry or not an envelope. */
305
- freshTtlSeconds: number | null;
306
- /** Remaining stale-while-revalidate window in seconds, or null. */
307
- staleTtlSeconds: number | null;
308
- /** Remaining stale-if-error window in seconds, or null. */
309
- errorTtlSeconds: number | null;
337
+ /** Remaining fresh TTL in milliseconds, or null if no expiry or not an envelope. */
338
+ freshTtlMs: number | null;
339
+ /** Remaining stale-while-revalidate window in milliseconds, or null. */
340
+ staleTtlMs: number | null;
341
+ /** Remaining stale-if-error window in milliseconds, or null. */
342
+ errorTtlMs: number | null;
310
343
  /** Whether the key is currently serving stale-while-revalidate. */
311
344
  isStale: boolean;
312
345
  /** Tags associated with this key (from the TagIndex). */
@@ -335,6 +368,10 @@ interface CacheStackEvents {
335
368
  delete: {
336
369
  keys: string[];
337
370
  };
371
+ /** Fired after one or more keys are marked expired but retained. */
372
+ expire: {
373
+ keys: string[];
374
+ };
338
375
  /** Fired when a value is backfilled into a faster layer. */
339
376
  backfill: {
340
377
  key: string;
@@ -486,12 +523,12 @@ declare class CacheNamespace {
486
523
  private static readonly metricsMutexes;
487
524
  private metrics;
488
525
  constructor(cache: CacheStack, prefix: string);
489
- get<T>(key: string, fetcher?: () => Promise<T>, options?: CacheGetOptions): Promise<T | null>;
490
- getOrSet<T>(key: string, fetcher: () => Promise<T>, options?: CacheGetOptions): Promise<T | null>;
526
+ get<T>(key: string, fetcher?: CacheFetcher<T>, options?: CacheGetOptions): Promise<T | null>;
527
+ getOrSet<T>(key: string, fetcher: CacheFetcher<T>, options?: CacheGetOptions): Promise<T | null>;
491
528
  /**
492
529
  * Like `get()`, but throws `CacheMissError` instead of returning `null`.
493
530
  */
494
- getOrThrow<T>(key: string, fetcher?: () => Promise<T>, options?: CacheGetOptions): Promise<T>;
531
+ getOrThrow<T>(key: string, fetcher?: CacheFetcher<T>, options?: CacheGetOptions): Promise<T>;
495
532
  has(key: string): Promise<boolean>;
496
533
  ttl(key: string): Promise<number | null>;
497
534
  set<T>(key: string, value: T, options?: CacheWriteOptions): Promise<void>;
@@ -501,9 +538,13 @@ declare class CacheNamespace {
501
538
  mget<T>(entries: CacheMGetEntry<T>[]): Promise<Array<T | null>>;
502
539
  mset<T>(entries: CacheMSetEntry<T>[]): Promise<void>;
503
540
  invalidateByTag(tag: string): Promise<void>;
541
+ expireByTag(tag: string): Promise<void>;
504
542
  invalidateByTags(tags: string[], mode?: 'any' | 'all'): Promise<void>;
543
+ expireByTags(tags: string[], mode?: 'any' | 'all'): Promise<void>;
505
544
  invalidateByPattern(pattern: string): Promise<void>;
545
+ expireByPattern(pattern: string): Promise<void>;
506
546
  invalidateByPrefix(prefix: string): Promise<void>;
547
+ expireByPrefix(prefix: string): Promise<void>;
507
548
  /**
508
549
  * Returns detailed metadata about a single cache key within this namespace.
509
550
  */
@@ -557,8 +598,6 @@ declare class CacheStack extends EventEmitter {
557
598
  private readonly invalidation;
558
599
  private readonly layerWriter;
559
600
  private readonly snapshots;
560
- private readonly backgroundRefreshes;
561
- private readonly backgroundRefreshAbort;
562
601
  private readonly layerDegradedUntil;
563
602
  private readonly maintenance;
564
603
  private readonly ttlResolver;
@@ -566,6 +605,7 @@ declare class CacheStack extends EventEmitter {
566
605
  private nextOperationId;
567
606
  private currentGeneration?;
568
607
  private isDisconnecting;
608
+ private readonly reader;
569
609
  private disconnectPromise?;
570
610
  constructor(layers: CacheLayer[], options?: CacheStackOptions);
571
611
  /**
@@ -574,25 +614,24 @@ declare class CacheStack extends EventEmitter {
574
614
  * and stores the result across all layers. Returns `null` if the key is not found
575
615
  * and no `fetcher` is provided.
576
616
  */
577
- get<T>(key: string, fetcher?: () => Promise<T>, options?: CacheGetOptions): Promise<T | null>;
578
- private getPrepared;
617
+ get<T>(key: string, fetcher?: CacheFetcher<T>, options?: CacheGetOptions): Promise<T | null>;
579
618
  /**
580
619
  * Alias for `get(key, fetcher, options)` — explicit get-or-set pattern.
581
620
  * Fetches and caches the value if not already present.
582
621
  */
583
- getOrSet<T>(key: string, fetcher: () => Promise<T>, options?: CacheGetOptions): Promise<T | null>;
622
+ getOrSet<T>(key: string, fetcher: CacheFetcher<T>, options?: CacheGetOptions): Promise<T | null>;
584
623
  /**
585
624
  * Like `get()`, but throws `CacheMissError` instead of returning `null`.
586
625
  * Useful when the value is expected to exist or the fetcher is expected to
587
626
  * return non-null.
588
627
  */
589
- getOrThrow<T>(key: string, fetcher?: () => Promise<T>, options?: CacheGetOptions): Promise<T>;
628
+ getOrThrow<T>(key: string, fetcher?: CacheFetcher<T>, options?: CacheGetOptions): Promise<T>;
590
629
  /**
591
630
  * Returns true if the given key exists and is not expired in any layer.
592
631
  */
593
632
  has(key: string): Promise<boolean>;
594
633
  /**
595
- * Returns the remaining TTL in seconds for the key in the fastest layer
634
+ * Returns the remaining TTL in milliseconds for the key in the fastest layer
596
635
  * that has it, or null if the key is not found / has no TTL.
597
636
  */
598
637
  ttl(key: string): Promise<number | null>;
@@ -623,9 +662,13 @@ declare class CacheStack extends EventEmitter {
623
662
  */
624
663
  namespace(prefix: string): CacheNamespace;
625
664
  invalidateByTag(tag: string): Promise<void>;
665
+ expireByTag(tag: string): Promise<void>;
626
666
  invalidateByTags(tags: string[], mode?: 'any' | 'all'): Promise<void>;
667
+ expireByTags(tags: string[], mode?: 'any' | 'all'): Promise<void>;
627
668
  invalidateByPattern(pattern: string): Promise<void>;
669
+ expireByPattern(pattern: string): Promise<void>;
628
670
  invalidateByPrefix(prefix: string): Promise<void>;
671
+ expireByPrefix(prefix: string): Promise<void>;
629
672
  getMetrics(): CacheMetricsSnapshot;
630
673
  getStats(): CacheStatsSnapshot;
631
674
  resetMetrics(): void;
@@ -652,21 +695,15 @@ declare class CacheStack extends EventEmitter {
652
695
  restoreFromFile(filePath: string): Promise<void>;
653
696
  disconnect(): Promise<void>;
654
697
  private initialize;
655
- private fetchWithGuards;
656
- private waitForFreshValue;
657
- private fetchAndPopulate;
658
698
  private storeEntry;
659
699
  private writeBatch;
660
- private readFromLayers;
661
- private readLayerEntry;
662
- private backfill;
663
700
  private resolveFreshTtl;
664
- private resolveLayerSeconds;
665
- private shouldNegativeCache;
666
- private scheduleBackgroundRefresh;
667
- private runBackgroundRefresh;
668
- private resolveSingleFlightOptions;
701
+ private resolveLayerMs;
702
+ private resolveContextOptions;
703
+ private isPlainObject;
669
704
  private deleteKeys;
705
+ private expireKeys;
706
+ private expireKeysInLayers;
670
707
  private publishInvalidation;
671
708
  private handleInvalidationMessage;
672
709
  private getTagsForKey;
@@ -689,13 +726,14 @@ declare class CacheStack extends EventEmitter {
689
726
  private validateWriteOptions;
690
727
  private assertActive;
691
728
  private awaitStartup;
729
+ private readLayerEntry;
730
+ private scheduleBackgroundRefresh;
692
731
  private applyFreshReadPolicies;
693
732
  private shouldSkipLayer;
694
733
  private handleLayerFailure;
695
734
  private reportRecoverableLayerFailure;
696
735
  private isGracefulDegradationEnabled;
697
736
  private recordCircuitFailure;
698
- private isNegativeStoredValue;
699
737
  private emitError;
700
738
  private snapshotMaxBytes;
701
739
  private snapshotMaxEntries;
@@ -722,4 +760,4 @@ interface HonoCacheMiddlewareOptions extends CacheGetOptions {
722
760
  }
723
761
  declare function createHonoCacheMiddleware(cache: CacheStack, options?: HonoCacheMiddlewareOptions): (context: HonoLikeContext, next: () => Promise<void>) => Promise<unknown>;
724
762
 
725
- 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 };
763
+ export { CacheNamespace as A, type CacheRateLimitOptions as B, type CacheLogger as C, type CacheSnapshotEntry as D, type CacheStackEvents as E, type CacheStackOptions as F, type CacheStatsSnapshot as G, type CacheTtlPolicy as H, type InvalidationBus as I, type CacheTtlPolicyContext as J, type CacheWarmEntry as K, type CacheWarmOptions as L, type CacheWarmProgress as M, type CacheWriteBehindOptions as N, type CacheWriteOptions as O, type EvictionPolicy as P, type LayerTtlMap as Q, MemoryLayer as R, type MemoryLayerOptions as S, type MemoryLayerSnapshotEntry as T, PatternMatcher as U, TagIndex as V, createHonoCacheMiddleware as W, 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 CacheContextOptionsContext as m, type CacheDegradationOptions as n, type CacheEntryWriteKind as o, type CacheEntryWriteOptions as p, type CacheFetcher as q, type CacheFetcherContext as r, type CacheHealthCheckResult as s, type CacheHitRateSnapshot as t, type CacheInspectResult as u, type CacheLayerLatency as v, type CacheMGetEntry as w, type CacheMSetEntry as x, type CacheMetricsSnapshot as y, CacheMissError as z };
package/dist/edge.cjs CHANGED
@@ -67,17 +67,17 @@ function isStoredValueEnvelope(value) {
67
67
  if (typeof v.freshUntil === "number" && typeof v.errorUntil === "number" && v.errorUntil < v.freshUntil) {
68
68
  return false;
69
69
  }
70
- const maxTtlSeconds = 10 * 365 * 24 * 60 * 60;
71
- if (!isValidEnvelopeTtlSeconds(v.freshTtlSeconds, maxTtlSeconds)) {
70
+ const maxTtlMs = 10 * 365 * 24 * 60 * 60 * 1e3;
71
+ if (!isValidEnvelopeTtlMs(v.freshTtlMs, maxTtlMs)) {
72
72
  return false;
73
73
  }
74
- if (!isValidEnvelopeTtlSeconds(v.staleWhileRevalidateSeconds, maxTtlSeconds)) {
74
+ if (!isValidEnvelopeTtlMs(v.staleWhileRevalidateMs, maxTtlMs)) {
75
75
  return false;
76
76
  }
77
- if (!isValidEnvelopeTtlSeconds(v.staleIfErrorSeconds, maxTtlSeconds)) {
77
+ if (!isValidEnvelopeTtlMs(v.staleIfErrorMs, maxTtlMs)) {
78
78
  return false;
79
79
  }
80
- if (v.freshTtlSeconds == null && (v.staleWhileRevalidateSeconds != null || v.staleIfErrorSeconds != null)) {
80
+ if (v.freshTtlMs == null && (v.staleWhileRevalidateMs != null || v.staleIfErrorMs != null)) {
81
81
  return false;
82
82
  }
83
83
  return true;
@@ -91,11 +91,11 @@ function unwrapStoredValue(stored) {
91
91
  }
92
92
  return stored.value ?? null;
93
93
  }
94
- function isValidEnvelopeTtlSeconds(value, maxTtlSeconds) {
94
+ function isValidEnvelopeTtlMs(value, maxTtlMs) {
95
95
  if (value == null) {
96
96
  return true;
97
97
  }
98
- return typeof value === "number" && Number.isFinite(value) && value > 0 && value <= maxTtlSeconds;
98
+ return typeof value === "number" && Number.isFinite(value) && value > 0 && value <= maxTtlMs;
99
99
  }
100
100
 
101
101
  // src/layers/MemoryLayer.ts
@@ -153,7 +153,7 @@ var MemoryLayer = class {
153
153
  this.entries.delete(key);
154
154
  this.entries.set(key, {
155
155
  value,
156
- expiresAt: ttl && ttl > 0 ? Date.now() + ttl * 1e3 : null,
156
+ expiresAt: ttl && ttl > 0 ? Date.now() + ttl : null,
157
157
  accessCount: 0,
158
158
  insertedAt: Date.now()
159
159
  });
@@ -184,7 +184,7 @@ var MemoryLayer = class {
184
184
  if (entry.expiresAt === null) {
185
185
  return null;
186
186
  }
187
- return Math.max(0, Math.ceil((entry.expiresAt - Date.now()) / 1e3));
187
+ return Math.max(0, Math.ceil(entry.expiresAt - Date.now()));
188
188
  }
189
189
  async size() {
190
190
  this.pruneExpired();
package/dist/edge.d.cts CHANGED
@@ -1,2 +1,2 @@
1
- export { e as CacheGetOptions, f as CacheLayer, h as CacheLayerSetManyEntry, t as CacheMetricsSnapshot, w as CacheRateLimitOptions, B as CacheTtlPolicy, D as CacheTtlPolicyContext, J as CacheWriteOptions, K as EvictionPolicy, M as MemoryLayer, N as MemoryLayerOptions, O as MemoryLayerSnapshotEntry, P as PatternMatcher, T as TagIndex, Q as createHonoCacheMiddleware } from './edge-CUHTP9Bc.cjs';
1
+ export { m as CacheContextOptionsContext, o as CacheEntryWriteKind, p as CacheEntryWriteOptions, e as CacheGetOptions, f as CacheLayer, h as CacheLayerSetManyEntry, y as CacheMetricsSnapshot, B as CacheRateLimitOptions, H as CacheTtlPolicy, J as CacheTtlPolicyContext, O as CacheWriteOptions, P as EvictionPolicy, R as MemoryLayer, S as MemoryLayerOptions, T as MemoryLayerSnapshotEntry, U as PatternMatcher, V as TagIndex, W as createHonoCacheMiddleware } from './edge-D2FpRlyS.cjs';
2
2
  import 'node:events';
package/dist/edge.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export { e as CacheGetOptions, f as CacheLayer, h as CacheLayerSetManyEntry, t as CacheMetricsSnapshot, w as CacheRateLimitOptions, B as CacheTtlPolicy, D as CacheTtlPolicyContext, J as CacheWriteOptions, K as EvictionPolicy, M as MemoryLayer, N as MemoryLayerOptions, O as MemoryLayerSnapshotEntry, P as PatternMatcher, T as TagIndex, Q as createHonoCacheMiddleware } from './edge-CUHTP9Bc.js';
1
+ export { m as CacheContextOptionsContext, o as CacheEntryWriteKind, p as CacheEntryWriteOptions, e as CacheGetOptions, f as CacheLayer, h as CacheLayerSetManyEntry, y as CacheMetricsSnapshot, B as CacheRateLimitOptions, H as CacheTtlPolicy, J as CacheTtlPolicyContext, O as CacheWriteOptions, P as EvictionPolicy, R as MemoryLayer, S as MemoryLayerOptions, T as MemoryLayerSnapshotEntry, U as PatternMatcher, V as TagIndex, W as createHonoCacheMiddleware } from './edge-D2FpRlyS.js';
2
2
  import 'node:events';
package/dist/edge.js CHANGED
@@ -2,10 +2,10 @@ import {
2
2
  MemoryLayer,
3
3
  TagIndex,
4
4
  createHonoCacheMiddleware
5
- } from "./chunk-5RCAX2BQ.js";
5
+ } from "./chunk-FFZCC7EQ.js";
6
6
  import {
7
7
  PatternMatcher
8
- } from "./chunk-4PPBOOXT.js";
8
+ } from "./chunk-KJDFYE5T.js";
9
9
  export {
10
10
  MemoryLayer,
11
11
  PatternMatcher,