| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import crypto from "crypto"; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| const DEFAULT_MAX_ENTRIES = 50; |
| const DEFAULT_MAX_BYTES = 2 * 1024 * 1024; |
| const DEFAULT_TTL = 300000; |
|
|
| export class LRUCache { |
| |
| #cache = new Map(); |
| #maxSize; |
| #maxBytes; |
| #defaultTTL; |
| #currentSize = 0; |
| #currentBytes = 0; |
| #stats = { hits: 0, misses: 0, evictions: 0 }; |
|
|
| |
| |
| |
| |
| |
| |
| constructor(options: { maxSize?: number; maxBytes?: number; defaultTTL?: number } = {}) { |
| this.#maxSize = options.maxSize ?? DEFAULT_MAX_ENTRIES; |
| this.#maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES; |
| this.#defaultTTL = options.defaultTTL ?? DEFAULT_TTL; |
| } |
|
|
| |
| |
| |
| |
| |
| static generateKey(params: Record<string, unknown>) { |
| const normalized = JSON.stringify(params, Object.keys(params).sort()); |
| return crypto.createHash("sha256").update(normalized).digest("hex").slice(0, 16); |
| } |
|
|
| |
| |
| |
| |
| |
| get(key: string) { |
| const entry = this.#cache.get(key); |
|
|
| if (!entry) { |
| this.#stats.misses++; |
| return undefined; |
| } |
|
|
| if (Date.now() - entry.createdAt > entry.ttl) { |
| this.#deleteEntry(key, entry); |
| this.#stats.misses++; |
| return undefined; |
| } |
|
|
| this.#cache.delete(key); |
| entry.hits++; |
| this.#cache.set(key, entry); |
|
|
| this.#stats.hits++; |
| return entry.value; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| set(key: string, value: unknown, ttl?: number) { |
| const entrySize = this.#estimateSize(value); |
|
|
| if (this.#cache.has(key)) { |
| const oldEntry = this.#cache.get(key)!; |
| this.#currentBytes -= oldEntry.size || 0; |
| this.#currentSize--; |
| this.#cache.delete(key); |
| } |
|
|
| while ( |
| (this.#currentSize >= this.#maxSize || this.#currentBytes + entrySize > this.#maxBytes) && |
| this.#cache.size > 0 |
| ) { |
| const oldestKey = this.#cache.keys().next().value; |
| const oldestEntry = this.#cache.get(oldestKey); |
| if (oldestEntry) { |
| this.#deleteEntry(oldestKey, oldestEntry); |
| } |
| this.#stats.evictions++; |
| } |
|
|
| const entry = { |
| key, |
| value, |
| createdAt: Date.now(), |
| ttl: ttl ?? this.#defaultTTL, |
| size: entrySize, |
| hits: 0, |
| }; |
|
|
| this.#cache.set(key, entry); |
| this.#currentSize++; |
| this.#currentBytes += entrySize; |
| } |
|
|
| |
| |
| |
| #estimateSize(value: unknown): number { |
| try { |
| return JSON.stringify(value).length * 2; |
| } catch { |
| return 1024; |
| } |
| } |
|
|
| |
| |
| |
| #deleteEntry(key: string, entry: { size?: number }) { |
| this.#cache.delete(key); |
| this.#currentSize--; |
| this.#currentBytes -= entry.size || 0; |
| if (this.#currentBytes < 0) this.#currentBytes = 0; |
| } |
|
|
| |
| |
| |
| |
| |
| has(key: string) { |
| const entry = this.#cache.get(key); |
| if (!entry) return false; |
| if (Date.now() - entry.createdAt > entry.ttl) { |
| this.#deleteEntry(key, entry); |
| return false; |
| } |
| return true; |
| } |
|
|
| |
| |
| |
| |
| |
| delete(key: string) { |
| const entry = this.#cache.get(key); |
| if (entry) { |
| this.#deleteEntry(key, entry); |
| return true; |
| } |
| return false; |
| } |
|
|
| |
| clear() { |
| this.#cache.clear(); |
| this.#currentSize = 0; |
| this.#currentBytes = 0; |
| } |
|
|
| |
| getStats() { |
| const total = this.#stats.hits + this.#stats.misses; |
| return { |
| size: this.#currentSize, |
| maxSize: this.#maxSize, |
| bytes: this.#currentBytes, |
| maxBytes: this.#maxBytes, |
| ...this.#stats, |
| hitRate: total > 0 ? (this.#stats.hits / total) * 100 : 0, |
| }; |
| } |
| } |
|
|
| |
|
|
| let promptCache: LRUCache | null = null; |
|
|
| |
| |
| |
| |
| |
| export function getPromptCache( |
| options?: { maxSize?: number; maxBytes?: number; defaultTTL?: number } & Record<string, unknown> |
| ) { |
| if (!promptCache) { |
| promptCache = new LRUCache({ |
| maxSize: parseInt(process.env.PROMPT_CACHE_MAX_SIZE || "50", 10), |
| maxBytes: parseInt(process.env.PROMPT_CACHE_MAX_BYTES || String(2 * 1024 * 1024), 10), |
| defaultTTL: parseInt(process.env.PROMPT_CACHE_TTL_MS || "300000", 10), |
| ...options, |
| }); |
| } |
| return promptCache; |
| } |
|
|