UNPKG

claude-flow-novice

Version:

Claude Flow Novice - Advanced orchestration platform for multi-agent AI workflows with CFN Loop architecture Includes Local RuVector Accelerator and all CFN skills for complete functionality.

311 lines (310 loc) 8.84 kB
/** * Correlation Cache Layer * * Provides LRU caching for correlation key queries with TTL and invalidation. * Part of Task 3.3: Query Correlation Key Layer * * @example * ```typescript * const cache = new CorrelationCache({ * maxSize: 100, * ttlMinutes: 5, * }); * * cache.set('task:abc123', { data: 'value' }); * const value = cache.get('task:abc123'); // { data: 'value' } * ``` */ import { createLogger } from './logging.js'; /** * LRU Cache for correlation key queries * * Implements Least Recently Used eviction policy with TTL and metrics tracking. */ export class CorrelationCache { cache; maxSize; ttlMinutes; logger; // Metrics hits = 0; misses = 0; evictions = 0; invalidations = 0; // Cache warming warmingEnabled; warmingPatterns; // Timer cleanup cleanupTimer = null; constructor(config = {}){ this.cache = new Map(); this.maxSize = config.maxSize || 100; this.ttlMinutes = config.ttlMinutes || 5; this.warmingEnabled = config.enableWarming || false; this.warmingPatterns = config.warmingPatterns || []; this.logger = config.logger || createLogger('correlation-cache'); // Start periodic TTL cleanup this.startTTLCleanup(); } /** * Get value from cache * * @param key - Cache key * @returns Cached value or undefined */ get(key) { const entry = this.cache.get(key); if (!entry) { this.misses++; this.logger.debug('Cache miss', { key }); return undefined; } // Check TTL if (this.isExpired(entry)) { this.invalidate(key, 'ttl'); this.misses++; this.logger.debug('Cache miss (expired)', { key }); return undefined; } // Update LRU metadata entry.lastAccessed = new Date(); entry.accessCount++; // Move to end (most recently used) this.cache.delete(key); this.cache.set(key, entry); this.hits++; this.logger.debug('Cache hit', { key, accessCount: entry.accessCount }); return entry.value; } /** * Set value in cache * * @param key - Cache key * @param value - Value to cache * @param ttlMinutes - Optional TTL override */ set(key, value, ttlMinutes) { const ttl = (ttlMinutes || this.ttlMinutes) * 60 * 1000; // Convert to milliseconds const entry = { value, createdAt: new Date(), lastAccessed: new Date(), accessCount: 0, ttl }; // Check if we need to evict if (this.cache.size >= this.maxSize && !this.cache.has(key)) { this.evictLRU(); } // Add/update entry this.cache.set(key, entry); this.logger.debug('Cache set', { key, ttl }); } /** * Check if key exists in cache * * @param key - Cache key * @returns True if key exists and not expired */ has(key) { const entry = this.cache.get(key); if (!entry) { return false; } if (this.isExpired(entry)) { this.invalidate(key, 'ttl'); return false; } return true; } /** * Delete key from cache * * @param key - Cache key * @returns True if key was deleted */ delete(key) { return this.invalidate(key, 'delete'); } /** * Invalidate cache entry * * @param key - Cache key * @param trigger - Invalidation trigger * @returns True if entry was invalidated */ invalidate(key, trigger = 'manual') { const deleted = this.cache.delete(key); if (deleted) { this.invalidations++; this.logger.debug('Cache invalidated', { key, trigger }); } return deleted; } /** * Invalidate multiple keys by pattern * * @param pattern - Key pattern (supports wildcards) * @returns Number of keys invalidated */ invalidatePattern(pattern) { const regex = this.patternToRegex(pattern); let count = 0; for (const key of this.cache.keys()){ if (regex.test(key)) { this.invalidate(key, 'manual'); count++; } } this.logger.info('Pattern invalidation', { pattern, count }); return count; } /** * Clear entire cache */ clear() { const size = this.cache.size; this.cache.clear(); this.invalidations += size; this.logger.info('Cache cleared', { entriesCleared: size }); } /** * Get cache metrics * * @returns Cache metrics */ getMetrics() { const totalRequests = this.hits + this.misses; const hitRatio = totalRequests > 0 ? this.hits / totalRequests : 0; return { hits: this.hits, misses: this.misses, hitRatio, size: this.cache.size, maxSize: this.maxSize, evictions: this.evictions, invalidations: this.invalidations }; } /** * Reset cache metrics */ resetMetrics() { this.hits = 0; this.misses = 0; this.evictions = 0; this.invalidations = 0; this.logger.info('Cache metrics reset'); } /** * Warm cache with common patterns * * @param dataLoader - Function to load data for warming */ async warm(dataLoader) { if (!this.warmingEnabled || this.warmingPatterns.length === 0) { return; } this.logger.info('Cache warming started', { patterns: this.warmingPatterns }); for (const pattern of this.warmingPatterns){ try { const data = await dataLoader(pattern); for (const [key, value] of data.entries()){ this.set(key, value); } this.logger.debug('Pattern warmed', { pattern, count: data.size }); } catch (error) { this.logger.warn('Cache warming failed for pattern', { pattern, error }); } } this.logger.info('Cache warming completed'); } /** * Evict least recently used entry */ evictLRU() { // Map maintains insertion order, so first entry is LRU const firstKey = this.cache.keys().next().value; if (firstKey) { this.cache.delete(firstKey); this.evictions++; this.logger.debug('LRU eviction', { key: firstKey }); } } /** * Check if entry is expired */ isExpired(entry) { const now = Date.now(); const createdAt = entry.createdAt.getTime(); return now - createdAt > entry.ttl; } /** * Convert pattern to regex */ patternToRegex(pattern) { // Escape special regex characters except * const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&'); // Convert * to .* const regexPattern = escaped.replace(/\*/g, '.*'); return new RegExp(`^${regexPattern}$`); } /** * Start periodic TTL cleanup */ startTTLCleanup() { // Run cleanup every minute this.cleanupTimer = setInterval(()=>{ this.cleanupExpired(); }, 60 * 1000); } /** * Destroy cache and clean up resources * * Clears the interval timer to prevent memory leaks and clears all cache entries. */ destroy() { if (this.cleanupTimer) { clearInterval(this.cleanupTimer); this.cleanupTimer = null; } this.clear(); this.logger.info('Cache destroyed and cleanup timer stopped'); } /** * Clean up expired entries */ cleanupExpired() { let count = 0; for (const [key, entry] of this.cache.entries()){ if (this.isExpired(entry)) { this.invalidate(key, 'ttl'); count++; } } if (count > 0) { this.logger.debug('TTL cleanup', { expired: count }); } } } /** * Create correlation cache instance * * @param config - Cache configuration * @returns Correlation cache instance */ export function createCorrelationCache(config) { return new CorrelationCache(config); } //# sourceMappingURL=correlation-cache.js.map