export type PluginLruCacheResult = { hit: true; value: T } | { hit: false }; export class PluginLruCache { readonly #defaultMaxEntries: number; #maxEntries: number; readonly #entries = new Map(); constructor(defaultMaxEntries: number) { this.#defaultMaxEntries = normalizeMaxEntries(defaultMaxEntries, 1); this.#maxEntries = this.#defaultMaxEntries; } get maxEntries(): number { return this.#maxEntries; } get size(): number { return this.#entries.size; } setMaxEntriesForTest(value?: number): void { this.#maxEntries = typeof value === "number" ? normalizeMaxEntries(value, this.#defaultMaxEntries) : this.#defaultMaxEntries; this.#evictOldestEntries(); } clear(): void { this.#entries.clear(); } get(cacheKey: string): T | undefined { const cached = this.getResult(cacheKey); return cached.hit ? cached.value : undefined; } getResult(cacheKey: string): PluginLruCacheResult { if (!this.#entries.has(cacheKey)) { return { hit: false }; } const cached = this.#entries.get(cacheKey) as T; this.#entries.delete(cacheKey); this.#entries.set(cacheKey, cached); return { hit: true, value: cached }; } set(cacheKey: string, value: T): void { if (this.#entries.has(cacheKey)) { this.#entries.delete(cacheKey); } this.#entries.set(cacheKey, value); this.#evictOldestEntries(); } #evictOldestEntries(): void { while (this.#entries.size > this.#maxEntries) { const oldestEntry = this.#entries.keys().next(); if (oldestEntry.done) { break; } this.#entries.delete(oldestEntry.value); } } } function normalizeMaxEntries(value: number, fallback: number): number { if (!Number.isFinite(value) || value <= 0) { return fallback; } return Math.max(1, Math.floor(value)); }