| 12 | * position so hot keys survive eviction passes. |
| 13 | */ |
| 14 | export class LRUCache<K, V> { |
| 15 | private readonly max: number; |
| 16 | private readonly store = new Map<K, V>(); |
| 17 | |
| 18 | constructor(max: number) { |
| 19 | if (!Number.isFinite(max) || max <= 0) { |
| 20 | throw new Error(`LRUCache max must be a positive finite number, got ${max}`); |
| 21 | } |
| 22 | this.max = Math.floor(max); |
| 23 | } |
| 24 | |
| 25 | get size(): number { |
| 26 | return this.store.size; |
| 27 | } |
| 28 | |
| 29 | get(key: K): V | undefined { |
| 30 | const value = this.store.get(key); |
| 31 | if (value === undefined) { |
| 32 | // Distinguish "missing" from "stored undefined" by checking has(). |
| 33 | // We don't store undefined in practice, but be defensive. |
| 34 | return this.store.has(key) ? value : undefined; |
| 35 | } |
| 36 | // Refresh recency by re-inserting. |
| 37 | this.store.delete(key); |
| 38 | this.store.set(key, value); |
| 39 | return value; |
| 40 | } |
| 41 | |
| 42 | has(key: K): boolean { |
| 43 | return this.store.has(key); |
| 44 | } |
| 45 | |
| 46 | set(key: K, value: V): void { |
| 47 | if (this.store.has(key)) { |
| 48 | this.store.delete(key); |
| 49 | } else if (this.store.size >= this.max) { |
| 50 | // Evict the oldest entry — first key in iteration order. |
| 51 | const oldest = this.store.keys().next().value; |
| 52 | if (oldest !== undefined) { |
| 53 | this.store.delete(oldest); |
| 54 | } |
| 55 | } |
| 56 | this.store.set(key, value); |
| 57 | } |
| 58 | |
| 59 | clear(): void { |
| 60 | this.store.clear(); |
| 61 | } |
| 62 | } |
nothing calls this directly
no outgoing calls
no test coverage detected