layercache 1.2.4 → 1.2.6

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.
@@ -66,6 +66,7 @@ interface CacheLayer {
66
66
  clear(): Promise<void>;
67
67
  deleteMany?(keys: string[]): Promise<void>;
68
68
  keys?(): Promise<string[]>;
69
+ forEachKey?(visitor: (key: string) => void | Promise<void>): Promise<void>;
69
70
  ping?(): Promise<boolean>;
70
71
  dispose?(): Promise<void>;
71
72
  /**
@@ -140,10 +141,13 @@ interface CacheTagIndex {
140
141
  track(key: string, tags: string[]): Promise<void>;
141
142
  remove(key: string): Promise<void>;
142
143
  keysForTag(tag: string): Promise<string[]>;
144
+ forEachKeyForTag?(tag: string, visitor: (key: string) => void | Promise<void>): Promise<void>;
143
145
  keysForPrefix?(prefix: string): Promise<string[]>;
146
+ forEachKeyForPrefix?(prefix: string, visitor: (key: string) => void | Promise<void>): Promise<void>;
144
147
  /** Returns the tags associated with a specific key, or an empty array. */
145
148
  tagsForKey?(key: string): Promise<string[]>;
146
149
  matchPattern(pattern: string): Promise<string[]>;
150
+ forEachKeyMatchingPattern?(pattern: string, visitor: (key: string) => void | Promise<void>): Promise<void>;
147
151
  clear(): Promise<void>;
148
152
  }
149
153
  interface CacheLayerSetManyEntry {
@@ -203,6 +207,9 @@ interface CacheStackOptions {
203
207
  singleFlightPollMs?: number;
204
208
  singleFlightRenewIntervalMs?: number;
205
209
  snapshotBaseDir?: string | false;
210
+ snapshotMaxBytes?: number | false;
211
+ snapshotMaxEntries?: number | false;
212
+ invalidationMaxKeys?: number | false;
206
213
  /**
207
214
  * Maximum number of entries in `accessProfiles` and `circuitBreakers` maps
208
215
  * before the oldest entries are pruned. Prevents unbounded memory growth.
@@ -396,6 +403,7 @@ declare class MemoryLayer implements CacheLayer {
396
403
  ping(): Promise<boolean>;
397
404
  dispose(): Promise<void>;
398
405
  keys(): Promise<string[]>;
406
+ forEachKey(visitor: (key: string) => void | Promise<void>): Promise<void>;
399
407
  exportState(): MemoryLayerSnapshotEntry[];
400
408
  importState(entries: MemoryLayerSnapshotEntry[]): void;
401
409
  private evict;
@@ -437,14 +445,18 @@ declare class TagIndex implements CacheTagIndex {
437
445
  track(key: string, tags: string[]): Promise<void>;
438
446
  remove(key: string): Promise<void>;
439
447
  keysForTag(tag: string): Promise<string[]>;
448
+ forEachKeyForTag(tag: string, visitor: (key: string) => void | Promise<void>): Promise<void>;
440
449
  keysForPrefix(prefix: string): Promise<string[]>;
450
+ forEachKeyForPrefix(prefix: string, visitor: (key: string) => void | Promise<void>): Promise<void>;
441
451
  tagsForKey(key: string): Promise<string[]>;
442
452
  matchPattern(pattern: string): Promise<string[]>;
453
+ forEachKeyMatchingPattern(pattern: string, visitor: (key: string) => void | Promise<void>): Promise<void>;
443
454
  clear(): Promise<void>;
444
455
  private createTrieNode;
445
456
  private insertKnownKey;
446
457
  private findNode;
447
458
  private collectFromNode;
459
+ private visitFromNode;
448
460
  private collectPatternMatches;
449
461
  private pruneKnownKeysIfNeeded;
450
462
  private removeKey;
@@ -494,6 +506,10 @@ declare class CacheNamespace {
494
506
  */
495
507
  namespace(childPrefix: string): CacheNamespace;
496
508
  qualify(key: string): string;
509
+ private qualifyTag;
510
+ private qualifyGetOptions;
511
+ private qualifyWrapOptions;
512
+ private qualifyWriteOptions;
497
513
  private trackMetrics;
498
514
  private getMetricsMutex;
499
515
  }
@@ -523,6 +539,7 @@ declare class CacheStack extends EventEmitter {
523
539
  private readonly snapshotSerializer;
524
540
  private readonly backgroundRefreshes;
525
541
  private readonly layerDegradedUntil;
542
+ private readonly keyEpochs;
526
543
  private readonly ttlResolver;
527
544
  private readonly circuitBreakerManager;
528
545
  private currentGeneration?;
@@ -530,6 +547,7 @@ declare class CacheStack extends EventEmitter {
530
547
  private writeBehindTimer?;
531
548
  private writeBehindFlushPromise?;
532
549
  private generationCleanupPromise?;
550
+ private clearEpoch;
533
551
  private isDisconnecting;
534
552
  private disconnectPromise?;
535
553
  constructor(layers: CacheLayer[], options?: CacheStackOptions);
@@ -647,6 +665,10 @@ declare class CacheStack extends EventEmitter {
647
665
  private cleanupGeneration;
648
666
  private initializeWriteBehind;
649
667
  private shouldWriteBehind;
668
+ private beginClearEpoch;
669
+ private currentKeyEpoch;
670
+ private bumpKeyEpochs;
671
+ private isWriteOutdated;
650
672
  private enqueueWriteBehind;
651
673
  private flushWriteBehindQueue;
652
674
  private buildLayerSetEntry;
@@ -658,17 +680,8 @@ declare class CacheStack extends EventEmitter {
658
680
  private deleteKeysFromLayers;
659
681
  private validateConfiguration;
660
682
  private validateWriteOptions;
661
- private validateLayerNumberOption;
662
- private validatePositiveNumber;
663
- private validateRateLimitOptions;
664
- private validateNonNegativeNumber;
665
- private validateCacheKey;
666
- private validateTtlPolicy;
667
683
  private assertActive;
668
684
  private awaitStartup;
669
- private serializeOptions;
670
- private validateAdaptiveTtlOptions;
671
- private validateCircuitBreakerOptions;
672
685
  private applyFreshReadPolicies;
673
686
  private shouldSkipLayer;
674
687
  private handleLayerFailure;
@@ -677,11 +690,14 @@ declare class CacheStack extends EventEmitter {
677
690
  private recordCircuitFailure;
678
691
  private isNegativeStoredValue;
679
692
  private emitError;
680
- private serializeKeyPart;
681
693
  private isCacheSnapshotEntries;
682
694
  private sanitizeSnapshotValue;
683
- private validateSnapshotFilePath;
684
- private normalizeForSerialization;
695
+ private snapshotMaxBytes;
696
+ private snapshotMaxEntries;
697
+ private invalidationMaxKeys;
698
+ private collectKeysForTag;
699
+ private assertWithinInvalidationKeyLimit;
700
+ private visitExportEntries;
685
701
  }
686
702
 
687
703
  interface HonoLikeRequest {
@@ -689,6 +705,8 @@ interface HonoLikeRequest {
689
705
  url?: string;
690
706
  path?: string;
691
707
  query?: Record<string, unknown>;
708
+ headers?: Headers | Record<string, unknown>;
709
+ header?: (name: string) => string | undefined;
692
710
  }
693
711
  interface HonoLikeContext {
694
712
  req: HonoLikeRequest;
@@ -698,7 +716,8 @@ interface HonoLikeContext {
698
716
  interface HonoCacheMiddlewareOptions extends CacheGetOptions {
699
717
  keyResolver?: (request: HonoLikeRequest) => string;
700
718
  methods?: string[];
719
+ allowPrivateCaching?: boolean;
701
720
  }
702
- declare function createHonoCacheMiddleware(cache: CacheStack, options?: HonoCacheMiddlewareOptions): (context: HonoLikeContext, next: () => Promise<void>) => Promise<void>;
721
+ declare function createHonoCacheMiddleware(cache: CacheStack, options?: HonoCacheMiddlewareOptions): (context: HonoLikeContext, next: () => Promise<void>) => Promise<unknown>;
703
722
 
704
723
  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 };
package/dist/edge.cjs CHANGED
@@ -39,19 +39,47 @@ function isStoredValueEnvelope(value) {
39
39
  if (v.kind !== "value" && v.kind !== "empty") {
40
40
  return false;
41
41
  }
42
- if (v.freshUntil !== null && typeof v.freshUntil !== "number") {
42
+ if (v.freshUntil !== null && (!Number.isFinite(v.freshUntil) || typeof v.freshUntil !== "number")) {
43
43
  return false;
44
44
  }
45
- if (v.staleUntil !== null && typeof v.staleUntil !== "number") {
45
+ if (v.staleUntil !== null && (!Number.isFinite(v.staleUntil) || typeof v.staleUntil !== "number")) {
46
46
  return false;
47
47
  }
48
- if (v.errorUntil !== null && typeof v.errorUntil !== "number") {
48
+ if (v.errorUntil !== null && (!Number.isFinite(v.errorUntil) || typeof v.errorUntil !== "number")) {
49
49
  return false;
50
50
  }
51
51
  const maxTimestamp = Date.now() + 10 * 365 * 24 * 60 * 60 * 1e3;
52
52
  if (typeof v.freshUntil === "number" && v.freshUntil > maxTimestamp) {
53
53
  return false;
54
54
  }
55
+ if (typeof v.staleUntil === "number" && v.staleUntil > maxTimestamp) {
56
+ return false;
57
+ }
58
+ if (typeof v.errorUntil === "number" && v.errorUntil > maxTimestamp) {
59
+ return false;
60
+ }
61
+ if (v.freshUntil === null && (v.staleUntil !== null || v.errorUntil !== null)) {
62
+ return false;
63
+ }
64
+ if (typeof v.freshUntil === "number" && typeof v.staleUntil === "number" && v.staleUntil < v.freshUntil) {
65
+ return false;
66
+ }
67
+ if (typeof v.freshUntil === "number" && typeof v.errorUntil === "number" && v.errorUntil < v.freshUntil) {
68
+ return false;
69
+ }
70
+ const maxTtlSeconds = 10 * 365 * 24 * 60 * 60;
71
+ if (!isValidEnvelopeTtlSeconds(v.freshTtlSeconds, maxTtlSeconds)) {
72
+ return false;
73
+ }
74
+ if (!isValidEnvelopeTtlSeconds(v.staleWhileRevalidateSeconds, maxTtlSeconds)) {
75
+ return false;
76
+ }
77
+ if (!isValidEnvelopeTtlSeconds(v.staleIfErrorSeconds, maxTtlSeconds)) {
78
+ return false;
79
+ }
80
+ if (v.freshTtlSeconds == null && (v.staleWhileRevalidateSeconds != null || v.staleIfErrorSeconds != null)) {
81
+ return false;
82
+ }
55
83
  return true;
56
84
  }
57
85
  function unwrapStoredValue(stored) {
@@ -63,6 +91,12 @@ function unwrapStoredValue(stored) {
63
91
  }
64
92
  return stored.value ?? null;
65
93
  }
94
+ function isValidEnvelopeTtlSeconds(value, maxTtlSeconds) {
95
+ if (value == null) {
96
+ return true;
97
+ }
98
+ return typeof value === "number" && Number.isFinite(value) && value > 0 && value <= maxTtlSeconds;
99
+ }
66
100
 
67
101
  // src/layers/MemoryLayer.ts
68
102
  var MemoryLayer = class {
@@ -180,6 +214,12 @@ var MemoryLayer = class {
180
214
  this.pruneExpired();
181
215
  return [...this.entries.keys()];
182
216
  }
217
+ async forEachKey(visitor) {
218
+ this.pruneExpired();
219
+ for (const key of this.entries.keys()) {
220
+ await visitor(key);
221
+ }
222
+ }
183
223
  exportState() {
184
224
  this.pruneExpired();
185
225
  return [...this.entries.entries()].map(([key, entry]) => ({
@@ -295,6 +335,7 @@ var PatternMatcher = class _PatternMatcher {
295
335
  };
296
336
 
297
337
  // src/invalidation/TagIndex.ts
338
+ var MAX_PATTERN_RECURSION_DEPTH = 500;
298
339
  var TagIndex = class {
299
340
  tagToKeys = /* @__PURE__ */ new Map();
300
341
  keyToTags = /* @__PURE__ */ new Map();
@@ -335,6 +376,11 @@ var TagIndex = class {
335
376
  async keysForTag(tag) {
336
377
  return [...this.tagToKeys.get(tag) ?? /* @__PURE__ */ new Set()];
337
378
  }
379
+ async forEachKeyForTag(tag, visitor) {
380
+ for (const key of this.tagToKeys.get(tag) ?? /* @__PURE__ */ new Set()) {
381
+ await visitor(key);
382
+ }
383
+ }
338
384
  async keysForPrefix(prefix) {
339
385
  const node = this.findNode(prefix);
340
386
  if (!node) {
@@ -344,14 +390,27 @@ var TagIndex = class {
344
390
  this.collectFromNode(node, prefix, matches);
345
391
  return matches;
346
392
  }
393
+ async forEachKeyForPrefix(prefix, visitor) {
394
+ const node = this.findNode(prefix);
395
+ if (!node) {
396
+ return;
397
+ }
398
+ await this.visitFromNode(node, prefix, visitor);
399
+ }
347
400
  async tagsForKey(key) {
348
401
  return [...this.keyToTags.get(key) ?? /* @__PURE__ */ new Set()];
349
402
  }
350
403
  async matchPattern(pattern) {
351
404
  const matches = /* @__PURE__ */ new Set();
352
- this.collectPatternMatches(this.root, "", pattern, 0, matches, /* @__PURE__ */ new Set());
405
+ this.collectPatternMatches(this.root, "", pattern, 0, matches, /* @__PURE__ */ new Set(), 0);
353
406
  return [...matches];
354
407
  }
408
+ async forEachKeyMatchingPattern(pattern, visitor) {
409
+ const matches = await this.matchPattern(pattern);
410
+ for (const key of matches) {
411
+ await visitor(key);
412
+ }
413
+ }
355
414
  async clear() {
356
415
  this.tagToKeys.clear();
357
416
  this.keyToTags.clear();
@@ -401,7 +460,18 @@ var TagIndex = class {
401
460
  this.collectFromNode(child, `${prefix}${character}`, matches);
402
461
  }
403
462
  }
404
- collectPatternMatches(node, prefix, pattern, patternIndex, matches, visited) {
463
+ async visitFromNode(node, prefix, visitor) {
464
+ if (node.terminal) {
465
+ await visitor(prefix);
466
+ }
467
+ for (const [character, child] of node.children) {
468
+ await this.visitFromNode(child, `${prefix}${character}`, visitor);
469
+ }
470
+ }
471
+ collectPatternMatches(node, prefix, pattern, patternIndex, matches, visited, depth) {
472
+ if (depth > MAX_PATTERN_RECURSION_DEPTH) {
473
+ return;
474
+ }
405
475
  const stateKey = `${node.id}:${patternIndex}`;
406
476
  if (visited.has(stateKey)) {
407
477
  return;
@@ -418,21 +488,37 @@ var TagIndex = class {
418
488
  return;
419
489
  }
420
490
  if (patternChar === "*") {
421
- this.collectPatternMatches(node, prefix, pattern, patternIndex + 1, matches, visited);
491
+ this.collectPatternMatches(node, prefix, pattern, patternIndex + 1, matches, visited, depth + 1);
422
492
  for (const [character, child2] of node.children) {
423
- this.collectPatternMatches(child2, `${prefix}${character}`, pattern, patternIndex, matches, visited);
493
+ this.collectPatternMatches(child2, `${prefix}${character}`, pattern, patternIndex, matches, visited, depth + 1);
424
494
  }
425
495
  return;
426
496
  }
427
497
  if (patternChar === "?") {
428
498
  for (const [character, child2] of node.children) {
429
- this.collectPatternMatches(child2, `${prefix}${character}`, pattern, patternIndex + 1, matches, visited);
499
+ this.collectPatternMatches(
500
+ child2,
501
+ `${prefix}${character}`,
502
+ pattern,
503
+ patternIndex + 1,
504
+ matches,
505
+ visited,
506
+ depth + 1
507
+ );
430
508
  }
431
509
  return;
432
510
  }
433
511
  const child = node.children.get(patternChar);
434
512
  if (child) {
435
- this.collectPatternMatches(child, `${prefix}${patternChar}`, pattern, patternIndex + 1, matches, visited);
513
+ this.collectPatternMatches(
514
+ child,
515
+ `${prefix}${patternChar}`,
516
+ pattern,
517
+ patternIndex + 1,
518
+ matches,
519
+ visited,
520
+ depth + 1
521
+ );
436
522
  }
437
523
  }
438
524
  pruneKnownKeysIfNeeded() {
@@ -506,14 +592,17 @@ function createHonoCacheMiddleware(cache, options = {}) {
506
592
  await next();
507
593
  return;
508
594
  }
595
+ if (!options.keyResolver && options.allowPrivateCaching !== true) {
596
+ await next();
597
+ return;
598
+ }
509
599
  const rawPath = context.req.path ?? context.req.url ?? "/";
510
600
  const key = options.keyResolver ? options.keyResolver(context.req) : `${method}:${normalizeUrl(rawPath)}`;
511
601
  const cached = await cache.get(key, void 0, options);
512
602
  if (cached !== null) {
513
603
  context.header?.("x-cache", "HIT");
514
604
  context.header?.("content-type", "application/json; charset=utf-8");
515
- context.json(cached);
516
- return;
605
+ return context.json(cached);
517
606
  }
518
607
  const originalJson = context.json.bind(context);
519
608
  context.json = (body, status) => {
@@ -533,7 +622,7 @@ function normalizeUrl(url) {
533
622
  try {
534
623
  const parsed = new URL(url, "http://localhost");
535
624
  parsed.searchParams.sort();
536
- return decodeURIComponent(parsed.pathname) + parsed.search;
625
+ return parsed.pathname + parsed.search;
537
626
  } catch {
538
627
  return url;
539
628
  }
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-Dw97n89L.cjs';
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-DLstcDMn.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-Dw97n89L.js';
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-DLstcDMn.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-KOYGHLVP.js";
5
+ } from "./chunk-GJBKCFE6.js";
6
6
  import {
7
7
  PatternMatcher
8
- } from "./chunk-7V7XAB74.js";
8
+ } from "./chunk-4PPBOOXT.js";
9
9
  export {
10
10
  MemoryLayer,
11
11
  PatternMatcher,