sfcc-dev-mcp
Version:
MCP server for Salesforce B2C Commerce Cloud development assistance including logs, debugging, and development tools
229 lines • 6.95 kB
JavaScript
/**
* 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