Skip to main content
Glama

SFCC Development MCP Server

by taurgis
cache.ts7.35 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 interface CacheEntry<T> { value: T; timestamp: number; accessCount: number; lastAccessed: number; } export interface CacheOptions { maxSize?: number; ttlMs?: number; cleanupIntervalMs?: number; } export class InMemoryCache<T> { private cache = new Map<string, CacheEntry<T>>(); private readonly maxSize: number; private readonly ttlMs: number; private cleanupTimer?: NodeJS.Timeout; constructor(options: CacheOptions = {}) { 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: string, value: T): void { 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: string): T | undefined { 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: string): boolean { 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: string): boolean { return this.cache.delete(key); } /** * Clear all entries from the cache */ clear(): void { this.cache.clear(); } /** * Get cache statistics */ getStats(): { size: number; maxSize: number; hitRate: number; entries: Array<{ key: string; accessCount: number; age: number }>; } { 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 */ private cleanup(): void { const now = Date.now(); const expiredKeys: string[] = []; 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 */ private evictLRU(): void { let oldestKey: string | undefined; 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(): void { if (this.cleanupTimer) { clearInterval(this.cleanupTimer); this.cleanupTimer = undefined; } this.clear(); } } /** * Multi-layer cache manager for different types of data */ export class CacheManager { private fileContentCache: InMemoryCache<string>; private classDetailsCache: InMemoryCache<any>; private searchResultsCache: InMemoryCache<any>; private methodSearchCache: InMemoryCache<any>; constructor() { // Much longer TTL for static documentation data that doesn't change during server runtime this.fileContentCache = new InMemoryCache<string>({ 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: string): string | undefined { return this.fileContentCache.get(key); } setFileContent(key: string, content: string): void { this.fileContentCache.set(key, content); } getClassDetails(key: string): any { return this.classDetailsCache.get(key); } setClassDetails(key: string, details: any): void { this.classDetailsCache.set(key, details); } getSearchResults(key: string): any { return this.searchResultsCache.get(key); } setSearchResults(key: string, results: any): void { this.searchResultsCache.set(key, results); } getMethodSearch(key: string): any { return this.methodSearchCache.get(key); } setMethodSearch(key: string, results: any): void { this.methodSearchCache.set(key, results); } /** * Get comprehensive cache statistics */ getAllStats(): { fileContent: ReturnType<InMemoryCache<any>['getStats']>; classDetails: ReturnType<InMemoryCache<any>['getStats']>; searchResults: ReturnType<InMemoryCache<any>['getStats']>; methodSearch: ReturnType<InMemoryCache<any>['getStats']>; } { return { fileContent: this.fileContentCache.getStats(), classDetails: this.classDetailsCache.getStats(), searchResults: this.searchResultsCache.getStats(), methodSearch: this.methodSearchCache.getStats(), }; } /** * Clear all caches */ clearAll(): void { this.fileContentCache.clear(); this.classDetailsCache.clear(); this.searchResultsCache.clear(); this.methodSearchCache.clear(); } /** * Cleanup all resources */ destroy(): void { this.fileContentCache.destroy(); this.classDetailsCache.destroy(); this.searchResultsCache.destroy(); this.methodSearchCache.destroy(); } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/taurgis/sfcc-dev-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server