vibe-coder-mcp
Version:
Production-ready MCP server with complete agent integration, multi-transport support, and comprehensive development automation tools for AI-assisted workflows.
150 lines (149 loc) • 5.05 kB
JavaScript
import logger from '../../logger.js';
export class CacheManager {
cache = new Map();
accessOrder = new Map();
stats;
config;
accessCounter = 0;
constructor(config = {}) {
this.config = {
maxEntries: config.maxEntries || 1000,
defaultTtl: config.defaultTtl || 5 * 60 * 1000,
maxMemoryUsage: config.maxMemoryUsage || 50 * 1024 * 1024,
enableStats: config.enableStats !== false
};
this.stats = {
totalEntries: 0,
hitRate: 0,
memoryUsage: 0,
evictions: 0,
avgQueryTime: 0
};
logger.debug({ config: this.config }, 'Cache manager initialized');
}
generateKey(query, options) {
const keyData = {
query,
strategy: options.searchStrategy || 'fuzzy',
fileTypes: options.fileTypes?.sort(),
maxResults: options.maxResults,
caseSensitive: options.caseSensitive,
minScore: options.minScore,
excludeDirs: options.excludeDirs?.sort()
};
return JSON.stringify(keyData);
}
get(query, options) {
if (!options.cacheResults)
return null;
const key = this.generateKey(query, options);
const entry = this.cache.get(key);
if (!entry) {
logger.debug({ query }, 'Cache miss');
return null;
}
const now = Date.now();
if (now - entry.timestamp.getTime() > entry.ttl) {
logger.debug({ query }, 'Cache entry expired');
this.cache.delete(key);
this.accessOrder.delete(key);
this.updateStats();
return null;
}
entry.hitCount++;
this.accessOrder.set(key, ++this.accessCounter);
this.updateStats();
logger.debug({ query, hitCount: entry.hitCount }, 'Cache hit');
return entry.results;
}
set(query, options, results) {
if (!options.cacheResults)
return;
const key = this.generateKey(query, options);
const ttl = this.config.defaultTtl;
const entry = {
query,
options: { ...options },
results: [...results],
timestamp: new Date(),
ttl,
hitCount: 0
};
this.evictIfNecessary();
this.cache.set(key, entry);
this.accessOrder.set(key, ++this.accessCounter);
this.updateStats();
logger.debug({ query, resultsCount: results.length }, 'Results cached');
}
clear(projectPath) {
if (projectPath) {
const keysToDelete = [];
for (const [key, entry] of this.cache.entries()) {
if (entry.results.some(result => result.filePath.startsWith(projectPath))) {
keysToDelete.push(key);
}
}
keysToDelete.forEach(key => {
this.cache.delete(key);
this.accessOrder.delete(key);
});
logger.info({ projectPath, clearedEntries: keysToDelete.length }, 'Cache cleared for project');
}
else {
const totalEntries = this.cache.size;
this.cache.clear();
this.accessOrder.clear();
this.accessCounter = 0;
logger.info({ clearedEntries: totalEntries }, 'Cache cleared completely');
}
this.updateStats();
}
getStats() {
return { ...this.stats };
}
evictIfNecessary() {
if (this.cache.size >= this.config.maxEntries) {
this.evictLRU();
}
const memoryUsage = this.calculateMemoryUsage();
if (memoryUsage > this.config.maxMemoryUsage) {
this.evictLRU();
}
}
evictLRU() {
let oldestKey = null;
let oldestAccess = Infinity;
for (const [key, accessTime] of this.accessOrder.entries()) {
if (accessTime < oldestAccess) {
oldestAccess = accessTime;
oldestKey = key;
}
}
if (oldestKey) {
this.cache.delete(oldestKey);
this.accessOrder.delete(oldestKey);
this.stats.evictions++;
logger.debug({ evictedKey: oldestKey }, 'Evicted LRU cache entry');
}
}
calculateMemoryUsage() {
let totalSize = 0;
for (const entry of this.cache.values()) {
totalSize += JSON.stringify(entry).length * 2;
}
return totalSize;
}
updateStats() {
if (!this.config.enableStats)
return;
this.stats.totalEntries = this.cache.size;
this.stats.memoryUsage = this.calculateMemoryUsage();
let totalHits = 0;
let totalAccesses = 0;
for (const entry of this.cache.values()) {
totalHits += entry.hitCount;
totalAccesses += entry.hitCount + 1;
}
this.stats.hitRate = totalAccesses > 0 ? totalHits / totalAccesses : 0;
}
}