UNPKG

mushcode-mcp-server

Version:

A specialized Model Context Protocol server for MUSHCODE development assistance. Provides AI-powered code generation, validation, optimization, and examples for MUD development.

326 lines 9.36 kB
/** * Caching system for frequently requested examples and patterns * Provides LRU cache with TTL support and performance monitoring */ import { logger } from './logger.js'; /** * LRU Cache with TTL support and performance monitoring */ export class LRUCache { options; cache = new Map(); accessOrder = []; stats = { hits: 0, misses: 0, hitRate: 0, size: 0, maxSize: 1000, evictions: 0, totalRequests: 0 }; cleanupTimer; constructor(options = {}) { this.options = options; this.stats.maxSize = options.maxSize || 1000; // Start cleanup timer if TTL is enabled if (options.defaultTtl || options.cleanupInterval) { const interval = options.cleanupInterval || 60000; // 1 minute default this.cleanupTimer = setInterval(() => this.cleanup(), interval); } } /** * Get value from cache */ get(key) { this.stats.totalRequests++; const entry = this.cache.get(key); if (!entry) { this.stats.misses++; this.updateHitRate(); return undefined; } // Check TTL expiration if (entry.ttl && Date.now() - entry.timestamp > entry.ttl) { this.cache.delete(key); this.removeFromAccessOrder(key); this.stats.size--; this.stats.misses++; this.updateHitRate(); return undefined; } // Update access statistics entry.accessCount++; entry.lastAccessed = Date.now(); this.stats.hits++; this.updateHitRate(); // Move to end of access order (most recently used) this.moveToEnd(key); logger.debug(`Cache hit for key: ${key}`, { operation: 'cache_get', accessCount: entry.accessCount }); return entry.value; } /** * Set value in cache */ set(key, value, ttl) { const now = Date.now(); const entry = { value, timestamp: now, accessCount: 1, lastAccessed: now, ...(ttl !== undefined && { ttl }), ...(ttl === undefined && this.options.defaultTtl !== undefined && { ttl: this.options.defaultTtl }) }; // If key already exists, update it if (this.cache.has(key)) { this.cache.set(key, entry); this.moveToEnd(key); logger.debug(`Cache updated for key: ${key}`, { operation: 'cache_update' }); return; } // Check if we need to evict if (this.cache.size >= this.stats.maxSize) { this.evictLeastRecentlyUsed(); } // Add new entry this.cache.set(key, entry); this.accessOrder.push(key); this.stats.size++; logger.debug(`Cache set for key: ${key}`, { operation: 'cache_set', ttl: entry.ttl, cacheSize: this.stats.size }); } /** * Check if key exists in cache (without updating access stats) */ has(key) { const entry = this.cache.get(key); if (!entry) return false; // Check TTL expiration if (entry.ttl && Date.now() - entry.timestamp > entry.ttl) { this.cache.delete(key); this.removeFromAccessOrder(key); this.stats.size--; return false; } return true; } /** * Delete key from cache */ delete(key) { const existed = this.cache.delete(key); if (existed) { this.removeFromAccessOrder(key); this.stats.size--; logger.debug(`Cache deleted key: ${key}`, { operation: 'cache_delete' }); } return existed; } /** * Clear all cache entries */ clear() { const previousSize = this.cache.size; this.cache.clear(); this.accessOrder = []; this.stats.size = 0; this.stats.evictions += previousSize; logger.info(`Cache cleared, evicted ${previousSize} entries`, { operation: 'cache_clear' }); } /** * Get cache statistics */ getStats() { return { ...this.stats }; } /** * Get cache keys sorted by access frequency */ getKeysByFrequency() { const entries = []; for (const [key, entry] of this.cache.entries()) { entries.push({ key, accessCount: entry.accessCount, lastAccessed: entry.lastAccessed }); } return entries.sort((a, b) => b.accessCount - a.accessCount); } /** * Get cache entries that are about to expire */ getExpiringEntries(withinMs = 300000) { const now = Date.now(); const expiring = []; for (const [key, entry] of this.cache.entries()) { if (entry.ttl) { const expiresAt = entry.timestamp + entry.ttl; if (expiresAt - now <= withinMs && expiresAt > now) { expiring.push(key); } } } return expiring; } /** * Refresh TTL for a key */ refreshTtl(key, newTtl) { const entry = this.cache.get(key); if (!entry) return false; entry.timestamp = Date.now(); if (newTtl !== undefined) { entry.ttl = newTtl; } logger.debug(`Cache TTL refreshed for key: ${key}`, { operation: 'cache_refresh_ttl', newTtl: entry.ttl }); return true; } /** * Cleanup expired entries */ cleanup() { const now = Date.now(); let expiredCount = 0; for (const [key, entry] of this.cache.entries()) { if (entry.ttl && now - entry.timestamp > entry.ttl) { this.cache.delete(key); this.removeFromAccessOrder(key); this.stats.size--; expiredCount++; } } if (expiredCount > 0) { logger.debug(`Cache cleanup removed ${expiredCount} expired entries`, { operation: 'cache_cleanup', remainingSize: this.stats.size }); } } /** * Evict least recently used entry */ evictLeastRecentlyUsed() { if (this.accessOrder.length === 0) return; const lruKey = this.accessOrder.shift(); this.cache.delete(lruKey); this.stats.size--; this.stats.evictions++; logger.debug(`Cache evicted LRU key: ${lruKey}`, { operation: 'cache_evict', reason: 'lru' }); } /** * Move key to end of access order */ moveToEnd(key) { this.removeFromAccessOrder(key); this.accessOrder.push(key); } /** * Remove key from access order array */ removeFromAccessOrder(key) { const index = this.accessOrder.indexOf(key); if (index > -1) { this.accessOrder.splice(index, 1); } } /** * Update hit rate calculation */ updateHitRate() { this.stats.hitRate = this.stats.totalRequests > 0 ? this.stats.hits / this.stats.totalRequests : 0; } /** * Destroy cache and cleanup timers */ destroy() { if (this.cleanupTimer) { clearInterval(this.cleanupTimer); } this.clear(); } } /** * Cache manager for different types of cached data */ export class CacheManager { caches = new Map(); defaultOptions = { maxSize: 1000, defaultTtl: 300000, // 5 minutes cleanupInterval: 60000 // 1 minute }; /** * Get or create a cache instance */ getCache(name, options) { if (!this.caches.has(name)) { const cacheOptions = { ...this.defaultOptions, ...options }; this.caches.set(name, new LRUCache(cacheOptions)); logger.info(`Created cache: ${name}`, { operation: 'cache_create', maxSize: cacheOptions.maxSize, defaultTtl: cacheOptions.defaultTtl }); } return this.caches.get(name); } /** * Get statistics for all caches */ getAllStats() { const stats = {}; for (const [name, cache] of this.caches.entries()) { stats[name] = cache.getStats(); } return stats; } /** * Clear all caches */ clearAll() { for (const [name, cache] of this.caches.entries()) { cache.clear(); logger.info(`Cleared cache: ${name}`, { operation: 'cache_clear_all' }); } } /** * Destroy all caches */ destroy() { for (const [name, cache] of this.caches.entries()) { cache.destroy(); logger.info(`Destroyed cache: ${name}`, { operation: 'cache_destroy' }); } this.caches.clear(); } /** * Get cache names */ getCacheNames() { return Array.from(this.caches.keys()); } } // Global cache manager instance export const cacheManager = new CacheManager(); //# sourceMappingURL=cache.js.map