1interface CacheEntry<T> {
2 value: T;
3 approximateSize: number;
4}
5
6export class LRUCache<T> {
7 private cache = new Map<string, CacheEntry<T>>();
8 private totalSize = 0;
9
10 constructor(
11 private readonly maxEntries: number,
12 private readonly maxMemoryBytes: number,
13 ) {}
14
15 get(key: string): T | null {
16 const entry = this.cache.get(key);
17 if (!entry) {
18 return null;
19 }
20
21 this.promote(key, entry);
22 return entry.value;
23 }
24
25 set(key: string, value: T, approximateSize: number): void {
26 if (approximateSize > this.maxMemoryBytes) {
27 return;
28 }
29
30 const existing = this.cache.get(key);
31 if (existing) {
32 this.totalSize -= existing.approximateSize;
33 this.cache.delete(key);
34 }
35
36 this.evictIfNeeded(approximateSize);
37 this.cache.set(key, { value, approximateSize });
38 this.totalSize += approximateSize;
39 }
40
41 clear(): void {
42 this.cache.clear();
43 this.totalSize = 0;
44 }
45
46 private promote(key: string, entry: CacheEntry<T>): void {
47 this.cache.delete(key);
48 this.cache.set(key, entry);
49 }
50
51 private evictIfNeeded(incomingSize: number): void {
52 while (
53 (this.cache.size >= this.maxEntries || this.totalSize + incomingSize > this.maxMemoryBytes) &&
54 this.cache.size > 0
55 ) {
56 const oldestKey = this.cache.keys().next().value;
57 if (oldestKey === undefined) {
58 break;
59 }
60
61 const oldestEntry = this.cache.get(oldestKey);
62 if (oldestEntry) {
63 this.totalSize -= oldestEntry.approximateSize;
64 }
65 this.cache.delete(oldestKey);
66 }
67 }
68}