bc-code-intelligence-mcp
Version:
BC Code Intelligence MCP Server - Complete Specialist Bundle with AI-driven expert consultation, seamless handoffs, and context-preserving workflows
328 lines โข 11.3 kB
JavaScript
/**
* Advanced Cache Manager with Multi-Level Caching
*
* Implements intelligent caching for topics, search results, and layer data
* with TTL, LRU eviction, memory pressure handling, and performance analytics.
*/
export class AdvancedCacheManager {
topicCache = new Map();
searchCache = new Map();
layerCache = new Map();
stats = {
hits: 0,
misses: 0,
evictions: 0,
total_requests: 0
};
config;
cleanupInterval;
constructor(config) {
this.config = config;
this.startPeriodicCleanup();
}
/**
* Cache a topic with intelligent TTL based on source type
*/
cacheTopic(key, topic, sourceType) {
const ttl = this.getTTLForSourceType(sourceType);
const entry = {
data: topic,
timestamp: Date.now(),
ttl,
access_count: 0,
last_accessed: Date.now(),
size_bytes: this.estimateSize(topic)
};
this.topicCache.set(key, entry);
this.enforceMemoryLimits();
}
/**
* Retrieve topic from cache with hit tracking
*/
getTopic(key) {
this.stats.total_requests++;
const entry = this.topicCache.get(key);
if (!entry) {
this.stats.misses++;
return null;
}
// Check if entry has expired
if (this.isExpired(entry)) {
this.topicCache.delete(key);
this.stats.misses++;
return null;
}
// Update access tracking
entry.access_count++;
entry.last_accessed = Date.now();
this.stats.hits++;
return entry.data;
}
/**
* Cache search results with query-based invalidation
*/
cacheSearchResults(query, results, ttl = 5 * 60 * 1000) {
const cacheKey = this.generateSearchKey(query);
const entry = {
data: results,
timestamp: Date.now(),
ttl,
access_count: 0,
last_accessed: Date.now(),
size_bytes: this.estimateSize(results)
};
this.searchCache.set(cacheKey, entry);
this.enforceMemoryLimits();
}
/**
* Retrieve cached search results
*/
getSearchResults(query) {
const cacheKey = this.generateSearchKey(query);
const entry = this.searchCache.get(cacheKey);
if (!entry || this.isExpired(entry)) {
if (entry)
this.searchCache.delete(cacheKey);
return null;
}
entry.access_count++;
entry.last_accessed = Date.now();
return entry.data;
}
/**
* Cache layer data (indexes, metadata, etc.)
*/
cacheLayerData(layerName, key, data, sourceType) {
const cacheKey = `${layerName}:${key}`;
const ttl = this.getTTLForSourceType(sourceType);
const entry = {
data,
timestamp: Date.now(),
ttl,
access_count: 0,
last_accessed: Date.now(),
size_bytes: this.estimateSize(data)
};
this.layerCache.set(cacheKey, entry);
this.enforceMemoryLimits();
}
/**
* Retrieve cached layer data
*/
getLayerData(layerName, key) {
const cacheKey = `${layerName}:${key}`;
const entry = this.layerCache.get(cacheKey);
if (!entry || this.isExpired(entry)) {
if (entry)
this.layerCache.delete(cacheKey);
return null;
}
entry.access_count++;
entry.last_accessed = Date.now();
return entry.data;
}
/**
* Invalidate cache entries for a specific layer
*/
invalidateLayer(layerName) {
let invalidated = 0;
// Remove topic cache entries from this layer
for (const [key, entry] of this.topicCache.entries()) {
if (key.startsWith(`${layerName}:`)) {
this.topicCache.delete(key);
invalidated++;
}
}
// Remove layer cache entries
for (const [key, entry] of this.layerCache.entries()) {
if (key.startsWith(`${layerName}:`)) {
this.layerCache.delete(key);
invalidated++;
}
}
// Clear all search results as they may be affected
this.searchCache.clear();
invalidated += this.searchCache.size;
console.log(`๐๏ธ Cache invalidation: Removed ${invalidated} entries for layer ${layerName}`);
return invalidated;
}
/**
* Warm up cache with frequently accessed topics
*/
async warmUpCache(topics) {
console.log(`๐ฅ Warming up cache with ${topics.length} topics...`);
const startTime = Date.now();
let cached = 0;
for (const { key, topic, sourceType } of topics) {
// Skip if already cached and not expired
const existing = this.topicCache.get(key);
if (existing && !this.isExpired(existing)) {
continue;
}
this.cacheTopic(key, topic, sourceType);
cached++;
}
const duration = Date.now() - startTime;
console.log(`โ
Cache warm-up complete: ${cached} topics cached in ${duration}ms`);
}
/**
* Preload critical cache entries based on usage patterns
*/
async preloadCriticalPaths(criticalTopics) {
console.log(`โก Preloading ${criticalTopics.length} critical topics...`);
// This would typically fetch from layers and cache
// For now, we'll mark these as high-priority in future requests
for (const topicId of criticalTopics) {
// Future implementation: fetch topic and cache with extended TTL
}
}
/**
* Get comprehensive cache statistics
*/
getStats() {
const totalMemory = this.calculateTotalMemoryUsage();
const totalEntries = this.topicCache.size + this.searchCache.size + this.layerCache.size;
return {
hits: this.stats.hits,
misses: this.stats.misses,
evictions: this.stats.evictions,
total_requests: this.stats.total_requests,
hit_rate: this.stats.total_requests > 0
? (this.stats.hits / this.stats.total_requests * 100)
: 0,
memory_usage_bytes: totalMemory,
cache_size: totalEntries
};
}
/**
* Clear all caches
*/
clearAll() {
const clearedEntries = this.topicCache.size + this.searchCache.size + this.layerCache.size;
this.topicCache.clear();
this.searchCache.clear();
this.layerCache.clear();
console.log(`๐งน Cleared all caches: ${clearedEntries} entries removed`);
}
/**
* Shutdown and cleanup
*/
shutdown() {
if (this.cleanupInterval) {
clearInterval(this.cleanupInterval);
}
this.clearAll();
}
// Private helper methods
getTTLForSourceType(sourceType) {
const ttlConfig = this.config.ttl[sourceType];
if (ttlConfig === 'permanent')
return Infinity;
if (ttlConfig === 'immediate')
return 0;
// Parse duration strings like "1h", "30m", "24h"
const match = ttlConfig.match(/^(\d+)(h|m|s)$/);
if (!match)
return 5 * 60 * 1000; // Default 5 minutes
const value = parseInt(match[1]);
const unit = match[2];
switch (unit) {
case 'h': return value * 60 * 60 * 1000;
case 'm': return value * 60 * 1000;
case 's': return value * 1000;
default: return 5 * 60 * 1000;
}
}
isExpired(entry) {
if (entry.ttl === Infinity)
return false;
if (entry.ttl === 0)
return true;
return (Date.now() - entry.timestamp) > entry.ttl;
}
generateSearchKey(query) {
// Create deterministic cache key from search parameters
return `search:${Buffer.from(query).toString('base64')}`;
}
estimateSize(obj) {
// Rough estimation of object size in bytes
return JSON.stringify(obj).length * 2; // Unicode characters are ~2 bytes
}
calculateTotalMemoryUsage() {
let total = 0;
for (const entry of this.topicCache.values()) {
total += entry.size_bytes;
}
for (const entry of this.searchCache.values()) {
total += entry.size_bytes;
}
for (const entry of this.layerCache.values()) {
total += entry.size_bytes;
}
return total;
}
enforceMemoryLimits() {
const maxMemory = this.config.max_size_mb * 1024 * 1024; // Convert MB to bytes
const currentMemory = this.calculateTotalMemoryUsage();
if (currentMemory <= maxMemory)
return;
console.log(`๐พ Memory limit exceeded (${(currentMemory / 1024 / 1024).toFixed(2)}MB / ${this.config.max_size_mb}MB), evicting entries...`);
// Evict least recently used entries across all caches
const allEntries = [];
for (const [key, entry] of this.topicCache.entries()) {
allEntries.push({ cache: this.topicCache, key, entry });
}
for (const [key, entry] of this.searchCache.entries()) {
allEntries.push({ cache: this.searchCache, key, entry });
}
for (const [key, entry] of this.layerCache.entries()) {
allEntries.push({ cache: this.layerCache, key, entry });
}
// Sort by last accessed time (oldest first)
allEntries.sort((a, b) => a.entry.last_accessed - b.entry.last_accessed);
// Evict entries until we're under the memory limit
let evicted = 0;
for (const { cache, key } of allEntries) {
cache.delete(key);
evicted++;
this.stats.evictions++;
if (this.calculateTotalMemoryUsage() <= maxMemory)
break;
}
console.log(`๐๏ธ Evicted ${evicted} cache entries to free memory`);
}
startPeriodicCleanup() {
// Clean expired entries every 5 minutes
this.cleanupInterval = setInterval(() => {
this.cleanupExpiredEntries();
}, 5 * 60 * 1000);
}
cleanupExpiredEntries() {
let cleaned = 0;
// Clean topic cache
for (const [key, entry] of this.topicCache.entries()) {
if (this.isExpired(entry)) {
this.topicCache.delete(key);
cleaned++;
}
}
// Clean search cache
for (const [key, entry] of this.searchCache.entries()) {
if (this.isExpired(entry)) {
this.searchCache.delete(key);
cleaned++;
}
}
// Clean layer cache
for (const [key, entry] of this.layerCache.entries()) {
if (this.isExpired(entry)) {
this.layerCache.delete(key);
cleaned++;
}
}
if (cleaned > 0) {
console.log(`๐งน Periodic cleanup: Removed ${cleaned} expired cache entries`);
}
}
}
//# sourceMappingURL=cache-manager.js.map