UNPKG

@simonecoelhosfo/optimizely-mcp-server

Version:

Optimizely MCP Server for AI assistants with integrated CLI tools

213 lines 6.19 kB
/** * Schema Cache Manager * * Caches API schemas in memory to improve validation performance. * Provides TTL-based cache expiration and lazy loading. */ import { FIELDS } from '../generated/fields.generated.js'; import { getLogger } from '../logging/Logger.js'; export class SchemaCache { cache = new Map(); logger = getLogger(); stats = { hits: 0, misses: 0, evictions: 0, size: 0 }; // Configuration ttl; // Time to live in milliseconds maxSize; // Maximum cache size preloadAll; // Whether to preload all schemas on init constructor(options) { this.ttl = options?.ttl || 3600000; // Default: 1 hour this.maxSize = options?.maxSize || 100; // Default: 100 schemas this.preloadAll = options?.preloadAll ?? true; // Default: preload all if (this.preloadAll) { this.preloadSchemas(); } } /** * Preload all schemas into cache */ preloadSchemas() { this.logger.debug('Preloading all schemas into cache'); for (const entityType of Object.keys(FIELDS)) { this.set(entityType, FIELDS[entityType]); } this.logger.info({ schemasLoaded: this.cache.size, cacheSize: this.stats.size }, 'Schema cache preloaded'); } /** * Get schema from cache or load it */ get(entityType) { const key = this.getCacheKey(entityType); const entry = this.cache.get(key); if (entry && !this.isExpired(entry)) { // Cache hit entry.hits++; this.stats.hits++; this.logger.debug({ entityType, hits: entry.hits, age: Date.now() - entry.timestamp }, 'Schema cache hit'); return entry.data; } // Cache miss this.stats.misses++; // Try to load from FIELDS const schema = FIELDS[entityType]; if (schema) { this.set(entityType, schema); return schema; } this.logger.warn({ entityType }, 'Schema not found in cache or source'); return null; } /** * Set schema in cache */ set(entityType, schema) { const key = this.getCacheKey(entityType); // Check cache size limit if (this.cache.size >= this.maxSize && !this.cache.has(key)) { this.evictLRU(); } this.cache.set(key, { data: schema, timestamp: Date.now(), hits: 0 }); this.stats.size = this.cache.size; this.logger.debug({ entityType, cacheSize: this.cache.size }, 'Schema cached'); } /** * Clear specific schema from cache */ clear(entityType) { const key = this.getCacheKey(entityType); const deleted = this.cache.delete(key); if (deleted) { this.stats.size = this.cache.size; this.logger.debug({ entityType }, 'Schema cleared from cache'); } return deleted; } /** * Clear all schemas from cache */ clearAll() { const previousSize = this.cache.size; this.cache.clear(); this.stats.size = 0; this.logger.info({ clearedCount: previousSize }, 'Schema cache cleared'); } /** * Get cache statistics */ getStats() { return { ...this.stats }; } /** * Get all cached entity types */ getCachedTypes() { return Array.from(this.cache.keys()).map(key => key.replace('schema:', '')); } /** * Check if a schema is cached and not expired */ has(entityType) { const key = this.getCacheKey(entityType); const entry = this.cache.get(key); return entry !== undefined && !this.isExpired(entry); } /** * Refresh schema in cache (force reload) */ refresh(entityType) { this.clear(entityType); return this.get(entityType); } /** * Get cache key for entity type */ getCacheKey(entityType) { return `schema:${entityType}`; } /** * Check if cache entry is expired */ isExpired(entry) { return Date.now() - entry.timestamp > this.ttl; } /** * Evict least recently used entry */ evictLRU() { let lruKey = null; let lruHits = Infinity; let lruTimestamp = Date.now(); // Find LRU entry for (const [key, entry] of this.cache.entries()) { const score = entry.hits * 1000 + (Date.now() - entry.timestamp); if (score < lruHits * 1000 + (Date.now() - lruTimestamp)) { lruKey = key; lruHits = entry.hits; lruTimestamp = entry.timestamp; } } // Evict LRU entry if (lruKey) { this.cache.delete(lruKey); this.stats.evictions++; this.stats.size = this.cache.size; this.logger.debug({ evictedKey: lruKey, evictions: this.stats.evictions }, 'Schema evicted from cache (LRU)'); } } /** * Clean up expired entries */ cleanup() { let cleaned = 0; for (const [key, entry] of this.cache.entries()) { if (this.isExpired(entry)) { this.cache.delete(key); cleaned++; } } if (cleaned > 0) { this.stats.size = this.cache.size; this.logger.info({ cleaned, remaining: this.cache.size }, 'Cleaned expired schemas from cache'); } return cleaned; } /** * Get cache memory usage estimate (rough) */ getMemoryUsage() { let size = 0; for (const entry of this.cache.values()) { // Rough estimate: stringify and get length size += JSON.stringify(entry.data).length; size += 24; // Overhead for timestamp and hits } return size; } } //# sourceMappingURL=SchemaCache.js.map