vibe-coder-mcp
Version:
Production-ready MCP server with complete agent integration, multi-transport support, and comprehensive development automation tools for AI-assisted workflows.
414 lines (413 loc) • 15.1 kB
JavaScript
import fs from 'fs-extra';
import path from 'path';
import { TaskManagerMemoryManager } from './memory-manager-integration.js';
import logger from '../../../logger.js';
export class MultiLevelCache {
memoryCache = new Map();
accessOrder = new Map();
accessFrequency = new Map();
config;
stats;
accessCounter = 0;
warmingInterval = null;
syncInterval = null;
memoryManager = null;
constructor(config) {
this.config = config;
this.stats = {
totalEntries: 0,
memoryEntries: 0,
diskEntries: 0,
hitRate: 0,
missRate: 0,
totalHits: 0,
totalMisses: 0,
totalMemoryUsage: 0,
totalDiskUsage: 0,
averageAccessTime: 0,
evictionCount: 0
};
this.memoryManager = TaskManagerMemoryManager.getInstance();
this.memoryManager?.registerCleanupCallback('multi-level-cache', () => this.performCleanup());
if (config.diskConfig.enabled) {
fs.ensureDirSync(config.diskConfig.directory);
}
if (config.warming.enabled) {
this.startCacheWarming();
}
if (config.consistency.enabled) {
this.startConsistencySync();
}
logger.info({ config }, 'Multi-level cache initialized');
}
async get(key) {
const startTime = performance.now();
try {
const memoryEntry = this.memoryCache.get(key);
if (memoryEntry) {
const now = Date.now();
if (now - memoryEntry.timestamp.getTime() <= memoryEntry.ttl) {
memoryEntry.accessCount++;
memoryEntry.lastAccessed = new Date(now);
this.accessOrder.set(key, ++this.accessCounter);
this.stats.totalHits++;
this.stats.hitRate = this.stats.totalHits / (this.stats.totalHits + this.stats.totalMisses);
return memoryEntry.value;
}
else {
this.memoryCache.delete(key);
this.accessOrder.delete(key);
this.accessFrequency.delete(key);
}
}
if (this.config.diskConfig.enabled && this.config.strategy !== 'memory') {
const diskEntry = await this.getDiskEntry(key);
if (diskEntry && !this.isExpired(diskEntry)) {
this.memoryCache.set(key, diskEntry);
this.updateAccessMetrics(diskEntry);
this.stats.totalHits++;
this.updateHitRate();
return diskEntry.value;
}
}
this.stats.totalMisses++;
this.stats.hitRate = this.stats.totalHits / (this.stats.totalHits + this.stats.totalMisses);
return null;
}
finally {
const accessTime = performance.now() - startTime;
this.updateAverageAccessTime(accessTime);
}
}
async set(key, value, ttl, tags = []) {
const now = Date.now();
const entry = {
key,
value,
timestamp: new Date(now),
ttl: ttl || 300000,
accessCount: 0,
lastAccessed: new Date(now),
size: this.estimateSize(value),
tags
};
const currentSize = this.memoryCache.size;
if (currentSize >= this.config.memoryConfig.maxEntries * 0.9) {
await this.evictIfNecessary();
}
this.memoryCache.set(key, entry);
this.accessOrder.set(key, ++this.accessCounter);
this.accessFrequency.set(key, 0);
if (this.config.diskConfig.enabled && this.config.strategy !== 'memory') {
this.setDiskEntry(key, entry).catch(error => {
logger.warn({ err: error, key }, 'Async disk cache write failed');
});
}
this.stats.totalEntries = currentSize + 1;
this.stats.memoryEntries = this.stats.totalEntries;
this.stats.totalMemoryUsage += entry.size;
}
async delete(key) {
let deleted = false;
if (this.memoryCache.delete(key)) {
this.accessOrder.delete(key);
this.accessFrequency.delete(key);
deleted = true;
}
if (this.config.diskConfig.enabled) {
const diskDeleted = await this.deleteDiskEntry(key);
deleted = deleted || diskDeleted;
}
if (deleted) {
this.updateStats();
logger.debug({ key }, 'Cache entry deleted');
}
return deleted;
}
async clearByTags(tags) {
let cleared = 0;
for (const [key, entry] of this.memoryCache) {
if (entry.tags.some(tag => tags.includes(tag))) {
await this.delete(key);
cleared++;
}
}
logger.info({ tags, cleared }, 'Cache entries cleared by tags');
return cleared;
}
isExpired(entry) {
return Date.now() - entry.timestamp.getTime() > entry.ttl;
}
updateAccessMetrics(entry) {
entry.accessCount++;
entry.lastAccessed = new Date();
this.accessOrder.set(entry.key, ++this.accessCounter);
this.accessFrequency.set(entry.key, (this.accessFrequency.get(entry.key) || 0) + 1);
}
async evictIfNecessary() {
const memoryUsage = this.calculateMemoryUsage();
const entryCount = this.memoryCache.size;
if (entryCount >= this.config.memoryConfig.maxEntries ||
memoryUsage >= this.config.memoryConfig.maxMemoryUsage) {
const evictCount = Math.max(1, Math.floor(entryCount * 0.1));
await this.evictEntries(evictCount);
}
}
async evictEntries(count) {
const entries = Array.from(this.memoryCache.entries());
let toEvict = [];
switch (this.config.memoryConfig.evictionPolicy) {
case 'lru':
toEvict = this.selectLRUEntries(entries, count);
break;
case 'lfu':
toEvict = this.selectLFUEntries(entries, count);
break;
case 'ttl':
toEvict = this.selectTTLEntries(entries, count);
break;
case 'size':
toEvict = this.selectSizeEntries(entries, count);
break;
case 'hybrid':
toEvict = this.selectHybridEntries(entries, count);
break;
}
for (const key of toEvict) {
await this.delete(key);
this.stats.evictionCount++;
}
if (toEvict.length > 0) {
logger.debug({
evicted: toEvict.length,
policy: this.config.memoryConfig.evictionPolicy
}, 'Cache entries evicted');
}
}
selectLRUEntries(entries, count) {
return entries
.sort((a, b) => (this.accessOrder.get(a[0]) || 0) - (this.accessOrder.get(b[0]) || 0))
.slice(0, count)
.map(([key]) => key);
}
selectLFUEntries(entries, count) {
return entries
.sort((a, b) => (this.accessFrequency.get(a[0]) || 0) - (this.accessFrequency.get(b[0]) || 0))
.slice(0, count)
.map(([key]) => key);
}
selectTTLEntries(entries, count) {
return entries
.sort((a, b) => a[1].timestamp.getTime() - b[1].timestamp.getTime())
.slice(0, count)
.map(([key]) => key);
}
selectSizeEntries(entries, count) {
return entries
.sort((a, b) => b[1].size - a[1].size)
.slice(0, count)
.map(([key]) => key);
}
selectHybridEntries(entries, count) {
return entries
.map(([key, entry]) => ({
key,
score: this.calculateEvictionScore(entry)
}))
.sort((a, b) => a.score - b.score)
.slice(0, count)
.map(item => item.key);
}
calculateEvictionScore(entry) {
const now = Date.now();
const age = now - entry.timestamp.getTime();
const timeSinceAccess = now - entry.lastAccessed.getTime();
const frequency = this.accessFrequency.get(entry.key) || 0;
return (frequency * 0.4) +
((entry.ttl - age) / entry.ttl * 0.3) +
((entry.ttl - timeSinceAccess) / entry.ttl * 0.2) +
(1 / (entry.size / 1024) * 0.1);
}
async getDiskEntry(key) {
try {
const filePath = path.join(this.config.diskConfig.directory, `${key}.json`);
if (await fs.pathExists(filePath)) {
const data = await fs.readJson(filePath);
return data;
}
}
catch (error) {
logger.debug({ err: error, key }, 'Failed to read disk cache entry');
}
return null;
}
async setDiskEntry(key, entry) {
try {
const filePath = path.join(this.config.diskConfig.directory, `${key}.json`);
await fs.writeJson(filePath, entry);
}
catch (error) {
logger.warn({ err: error, key }, 'Failed to write disk cache entry');
}
}
async deleteDiskEntry(key) {
try {
const filePath = path.join(this.config.diskConfig.directory, `${key}.json`);
if (await fs.pathExists(filePath)) {
await fs.remove(filePath);
return true;
}
}
catch (error) {
logger.warn({ err: error, key }, 'Failed to delete disk cache entry');
}
return false;
}
estimateSize(value) {
try {
return Buffer.byteLength(JSON.stringify(value));
}
catch {
return 1024;
}
}
calculateMemoryUsage() {
return Array.from(this.memoryCache.values())
.reduce((sum, entry) => sum + entry.size, 0);
}
updateStats() {
this.stats.totalEntries = this.memoryCache.size;
this.stats.memoryEntries = this.memoryCache.size;
this.stats.totalMemoryUsage = this.calculateMemoryUsage();
}
updateHitRate() {
const total = this.stats.totalHits + this.stats.totalMisses;
this.stats.hitRate = total > 0 ? this.stats.totalHits / total : 0;
this.stats.missRate = 1 - this.stats.hitRate;
}
updateAverageAccessTime(accessTime) {
const total = this.stats.totalHits + this.stats.totalMisses;
this.stats.averageAccessTime = total > 1
? (this.stats.averageAccessTime * (total - 1) + accessTime) / total
: accessTime;
}
startCacheWarming() {
if (this.config.warming.strategies.includes('scheduled')) {
this.warmingInterval = setInterval(() => {
this.performCacheWarming();
}, this.config.warming.scheduledInterval);
}
}
async performCacheWarming() {
logger.debug('Cache warming performed');
}
startConsistencySync() {
this.syncInterval = setInterval(() => {
this.performConsistencySync();
}, this.config.consistency.syncInterval);
}
async performConsistencySync() {
logger.debug('Consistency sync performed');
}
async performCleanup() {
const startTime = Date.now();
const initialMemory = this.calculateMemoryUsage();
try {
const expiredKeys = Array.from(this.memoryCache.entries())
.filter(([, entry]) => this.isExpired(entry))
.map(([key]) => key);
for (const key of expiredKeys) {
await this.delete(key);
}
const currentUsage = this.calculateMemoryUsage();
const threshold = this.config.memoryConfig.maxMemoryUsage * 0.7;
if (currentUsage > threshold) {
const evictCount = Math.floor(this.memoryCache.size * 0.2);
await this.evictEntries(evictCount);
}
const finalMemory = this.calculateMemoryUsage();
const memoryFreed = Math.max(0, initialMemory - finalMemory);
logger.info({
expiredRemoved: expiredKeys.length,
memoryFreed: `${Math.round(memoryFreed / 1024 / 1024)}MB`
}, 'Cache cleanup completed');
return {
success: true,
memoryFreed,
itemsRemoved: expiredKeys.length,
duration: Date.now() - startTime
};
}
catch (error) {
logger.error({ err: error }, 'Cache cleanup failed');
return {
success: false,
memoryFreed: 0,
itemsRemoved: 0,
duration: Date.now() - startTime,
error: error instanceof Error ? error.message : String(error)
};
}
}
getStatistics() {
this.updateStats();
return { ...this.stats };
}
async clear() {
this.memoryCache.clear();
this.accessOrder.clear();
this.accessFrequency.clear();
if (this.config.diskConfig.enabled) {
try {
await fs.emptyDir(this.config.diskConfig.directory);
}
catch (error) {
logger.warn({ err: error }, 'Failed to clear disk cache');
}
}
this.updateStats();
logger.info('Cache cleared');
}
async shutdown() {
if (this.warmingInterval) {
clearInterval(this.warmingInterval);
}
if (this.syncInterval) {
clearInterval(this.syncInterval);
}
this.memoryManager?.unregisterCleanupCallback('multi-level-cache');
await this.clear();
logger.info('Multi-level cache shutdown');
}
}
export class CacheFactory {
static createCache(name, config) {
const cacheConfig = {
strategy: config.strategy,
memoryConfig: {
maxEntries: 1000,
maxMemoryUsage: config.maxCacheSize,
evictionPolicy: 'hybrid'
},
diskConfig: {
enabled: config.strategy === 'disk' || config.strategy === 'hybrid',
directory: path.join(process.cwd(), 'data', 'cache', name),
maxDiskUsage: config.maxCacheSize * 2,
compression: false
},
warming: {
enabled: config.enableWarmup,
strategies: ['scheduled'],
preloadPatterns: [],
predictiveThreshold: 0.8,
scheduledInterval: 300000
},
consistency: {
enabled: config.strategy === 'hybrid',
syncInterval: 60000,
conflictResolution: 'memory'
}
};
return new MultiLevelCache(cacheConfig);
}
}