@simonecoelhosfo/optimizely-mcp-server
Version:
Optimizely MCP Server for AI assistants with integrated CLI tools
213 lines • 6.19 kB
JavaScript
/**
* 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