UNPKG

pms-analysis-reports-mcp-server

Version:

PMS analysis reports server handling maintenance reports, equipment analysis, compliance tracking, and performance metrics with ERP access for data extraction

260 lines 8.25 kB
import { logger } from "../utils/logger.js"; import { BaseService } from "./base-service.js"; export class CacheService extends BaseService { constructor(config = {}) { super(config); this.cache = new Map(); this.stats = { hits: 0, misses: 0, evictions: 0 }; this.maxSize = config.maxSize || 1000; this.defaultTtl = config.defaultTtl || 300000; // 5 minutes this.cleanupInterval = config.cleanupInterval || 60000; // 1 minute this.startCleanupTimer(); } /** * Get item from cache */ async get(key) { return this.executeWithResilience(async () => { const item = this.cache.get(key); if (!item) { this.stats.misses++; return null; } // Check if item has expired if (Date.now() - item.timestamp > item.ttl) { this.cache.delete(key); this.stats.misses++; return null; } // Update hit count and stats item.hits++; this.stats.hits++; logger.debug(`Cache hit for key: ${key}`, { hits: item.hits, age: Date.now() - item.timestamp }); return item.data; }, { toolName: 'cache_get' }); } /** * Set item in cache */ async set(key, data, ttl = this.defaultTtl) { return this.executeWithResilience(async () => { // Check cache size and evict if necessary if (this.cache.size >= this.maxSize && !this.cache.has(key)) { this.evictLeastRecentlyUsed(); } const item = { data, timestamp: Date.now(), ttl, hits: 0 }; this.cache.set(key, item); logger.debug(`Cache set for key: ${key}`, { ttl, cacheSize: this.cache.size }); return true; }, { toolName: 'cache_set' }); } /** * Delete item from cache */ async delete(key) { return this.executeWithResilience(async () => { const deleted = this.cache.delete(key); if (deleted) { logger.debug(`Cache delete for key: ${key}`); } return deleted; }, { toolName: 'cache_delete' }); } /** * Check if key exists in cache */ async has(key) { return this.executeWithResilience(async () => { const item = this.cache.get(key); if (!item) { return false; } // Check if expired if (Date.now() - item.timestamp > item.ttl) { this.cache.delete(key); return false; } return true; }, { toolName: 'cache_has' }); } /** * Get or set pattern - get from cache, or compute and cache if not found */ async getOrSet(key, computeFn, ttl = this.defaultTtl) { return this.executeWithResilience(async () => { // Try to get from cache first const cached = await this.get(key); if (cached.success && cached.data !== null && cached.data !== undefined) { return cached.data; } // Not in cache, compute value logger.debug(`Cache miss for key: ${key}, computing value`); const computed = await computeFn(); // Store in cache await this.set(key, computed, ttl); return computed; }, { toolName: 'cache_get_or_set' }); } /** * Clear all cache entries */ async clear() { return this.executeWithResilience(async () => { const size = this.cache.size; this.cache.clear(); logger.info(`Cache cleared, removed ${size} items`); return true; }, { toolName: 'cache_clear' }); } /** * Get cache statistics */ getStats() { const totalRequests = this.stats.hits + this.stats.misses; return { size: this.cache.size, maxSize: this.maxSize, hits: this.stats.hits, misses: this.stats.misses, hitRate: totalRequests > 0 ? this.stats.hits / totalRequests : 0, evictions: this.stats.evictions }; } /** * Evict expired entries */ cleanupExpired() { const now = Date.now(); let evicted = 0; for (const [key, item] of this.cache.entries()) { if (now - item.timestamp > item.ttl) { this.cache.delete(key); evicted++; } } if (evicted > 0) { logger.debug(`Cache cleanup removed ${evicted} expired items`); this.stats.evictions += evicted; } return evicted; } /** * Evict least recently used item */ evictLeastRecentlyUsed() { let lruKey = null; let lruHits = Infinity; let oldestTimestamp = Date.now(); for (const [key, item] of this.cache.entries()) { // Prefer items with fewer hits, and among those, the oldest if (item.hits < lruHits || (item.hits === lruHits && item.timestamp < oldestTimestamp)) { lruKey = key; lruHits = item.hits; oldestTimestamp = item.timestamp; } } if (lruKey) { this.cache.delete(lruKey); this.stats.evictions++; logger.debug(`Evicted LRU cache entry: ${lruKey}`, { hits: lruHits, age: Date.now() - oldestTimestamp }); } } /** * Start automatic cleanup timer */ startCleanupTimer() { this.cleanupTimer = setInterval(() => { this.cleanupExpired(); }, this.cleanupInterval); // Don't keep the process alive for cleanup timer this.cleanupTimer.unref(); } /** * Stop cleanup timer */ stopCleanup() { if (this.cleanupTimer) { clearInterval(this.cleanupTimer); this.cleanupTimer = undefined; } } /** * Health check implementation */ async healthCheck() { try { const testKey = '__health_check__'; const testValue = { timestamp: Date.now() }; // Test set operation const setResult = await this.set(testKey, testValue, 1000); if (!setResult.success) return false; // Test get operation const getResult = await this.get(testKey); if (!getResult.success || !getResult.data) return false; // Test delete operation const deleteResult = await this.delete(testKey); if (!deleteResult.success) return false; return true; } catch (error) { logger.error('Cache health check failed', { error: error.message }); return false; } } /** * Generate cache key from object */ static generateKey(prefix, params) { const sortedParams = Object.keys(params) .sort() .reduce((result, key) => { result[key] = params[key]; return result; }, {}); const paramString = JSON.stringify(sortedParams); const hash = this.simpleHash(paramString); return `${prefix}:${hash}`; } /** * Simple hash function for cache keys */ static simpleHash(str) { let hash = 0; for (let i = 0; i < str.length; i++) { const char = str.charCodeAt(i); hash = ((hash << 5) - hash) + char; hash = hash & hash; // Convert to 32-bit integer } return Math.abs(hash).toString(36); } /** * Cleanup on service shutdown */ async shutdown() { logger.info('Shutting down cache service'); this.stopCleanup(); await this.clear(); } } //# sourceMappingURL=cache-service.js.map