UNPKG

sfcc-dev-mcp

Version:

MCP server for Salesforce B2C Commerce Cloud development assistance including logs, debugging, and development tools

229 lines 6.95 kB
/** * In-Memory Caching Module * * Provides efficient caching with TTL (Time-To-Live) and LRU (Least Recently Used) eviction * to reduce IO operations and improve response times for SFCC documentation queries. */ export class InMemoryCache { cache = new Map(); maxSize; ttlMs; cleanupTimer; constructor(options = {}) { this.maxSize = options.maxSize ?? 1000; this.ttlMs = options.ttlMs ?? 60 * 60 * 1000; // 1 hour default for static data // Setup automatic cleanup - less frequent for static data const cleanupInterval = options.cleanupIntervalMs ?? 10 * 60 * 1000; // 10 minutes this.cleanupTimer = setInterval(() => this.cleanup(), cleanupInterval); } /** * Store a value in the cache */ set(key, value) { const now = Date.now(); // Handle zero max size - don't store anything if (this.maxSize === 0) { return; } // If at max capacity and adding a new key, remove LRU item first if (this.cache.size >= this.maxSize && !this.cache.has(key)) { this.evictLRU(); } this.cache.set(key, { value, timestamp: now, accessCount: 0, lastAccessed: now, }); } /** * Retrieve a value from the cache */ get(key) { const entry = this.cache.get(key); if (!entry) { return undefined; } const now = Date.now(); // Check if entry has expired if (now - entry.timestamp > this.ttlMs) { this.cache.delete(key); return undefined; } // Update access statistics entry.accessCount++; entry.lastAccessed = now; return entry.value; } /** * Check if a key exists in the cache (without updating access stats) */ has(key) { const entry = this.cache.get(key); if (!entry) { return false; } // Check if entry has expired if (Date.now() - entry.timestamp > this.ttlMs) { this.cache.delete(key); return false; } return true; } /** * Remove a specific key from the cache */ delete(key) { return this.cache.delete(key); } /** * Clear all entries from the cache */ clear() { this.cache.clear(); } /** * Get cache statistics */ getStats() { const now = Date.now(); const entries = Array.from(this.cache.entries()).map(([key, entry]) => ({ key, accessCount: entry.accessCount, age: now - entry.timestamp, })); const totalAccesses = entries.reduce((sum, entry) => sum + entry.accessCount, 0); const totalHits = entries.filter(entry => entry.accessCount > 0).length; return { size: this.cache.size, maxSize: this.maxSize, hitRate: totalAccesses > 0 ? totalHits / totalAccesses : 0, entries, }; } /** * Remove expired entries from the cache */ cleanup() { const now = Date.now(); const expiredKeys = []; for (const [key, entry] of this.cache.entries()) { if (now - entry.timestamp > this.ttlMs) { expiredKeys.push(key); } } expiredKeys.forEach(key => this.cache.delete(key)); } /** * Evict the least recently used item */ evictLRU() { let oldestKey; let oldestTime = Date.now(); for (const [key, entry] of this.cache.entries()) { if (entry.lastAccessed < oldestTime) { oldestTime = entry.lastAccessed; oldestKey = key; } } if (oldestKey) { this.cache.delete(oldestKey); } } /** * Cleanup resources */ destroy() { if (this.cleanupTimer) { clearInterval(this.cleanupTimer); this.cleanupTimer = undefined; } this.clear(); } } /** * Multi-layer cache manager for different types of data */ export class CacheManager { fileContentCache; classDetailsCache; searchResultsCache; methodSearchCache; constructor() { // Much longer TTL for static documentation data that doesn't change during server runtime this.fileContentCache = new InMemoryCache({ maxSize: 500, ttlMs: 4 * 60 * 60 * 1000, // 4 hours - raw file content is completely static cleanupIntervalMs: 30 * 60 * 1000, // 30 minutes cleanup interval }); this.classDetailsCache = new InMemoryCache({ maxSize: 300, ttlMs: 2 * 60 * 60 * 1000, // 2 hours - parsed data is static cleanupIntervalMs: 20 * 60 * 1000, // 20 minutes cleanup interval }); this.searchResultsCache = new InMemoryCache({ maxSize: 200, ttlMs: 60 * 60 * 1000, // 1 hour - search results are static cleanupIntervalMs: 15 * 60 * 1000, // 15 minutes cleanup interval }); this.methodSearchCache = new InMemoryCache({ maxSize: 100, ttlMs: 60 * 60 * 1000, // 1 hour - method search results are static cleanupIntervalMs: 15 * 60 * 1000, // 15 minutes cleanup interval }); } getFileContent(key) { return this.fileContentCache.get(key); } setFileContent(key, content) { this.fileContentCache.set(key, content); } getClassDetails(key) { return this.classDetailsCache.get(key); } setClassDetails(key, details) { this.classDetailsCache.set(key, details); } getSearchResults(key) { return this.searchResultsCache.get(key); } setSearchResults(key, results) { this.searchResultsCache.set(key, results); } getMethodSearch(key) { return this.methodSearchCache.get(key); } setMethodSearch(key, results) { this.methodSearchCache.set(key, results); } /** * Get comprehensive cache statistics */ getAllStats() { return { fileContent: this.fileContentCache.getStats(), classDetails: this.classDetailsCache.getStats(), searchResults: this.searchResultsCache.getStats(), methodSearch: this.methodSearchCache.getStats(), }; } /** * Clear all caches */ clearAll() { this.fileContentCache.clear(); this.classDetailsCache.clear(); this.searchResultsCache.clear(); this.methodSearchCache.clear(); } /** * Cleanup all resources */ destroy() { this.fileContentCache.destroy(); this.classDetailsCache.destroy(); this.searchResultsCache.destroy(); this.methodSearchCache.destroy(); } } //# sourceMappingURL=cache.js.map