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
JavaScript
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