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