UNPKG

@aaswe/codebase-ai

Version:

AI-Assisted Software Engineering (AASWE) - Rich codebase context for IDE LLMs

488 lines 17.4 kB
"use strict"; /** * Cache Manager * * Implements intelligent caching strategies for the Hybrid Storage Manager * with support for multiple eviction policies, TTL management, and persistence. */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.CacheManager = void 0; const events_1 = require("events"); const fs_1 = require("fs"); const path = __importStar(require("path")); const logger_1 = __importDefault(require("../../../utils/logger")); const types_1 = require("./types"); class CacheManager extends events_1.EventEmitter { config; cache = new Map(); accessOrder = []; // For LRU accessCount = new Map(); // For LFU timers = new Map(); // For TTL cleanup metricsCollectionTimer; persistenceTimer; metrics; hits = 0; misses = 0; constructor(config) { super(); this.config = config; this.metrics = this.initializeMetrics(); this.setupMetricsCollection(); this.setupPersistence(); } /** * Initialize the cache manager */ async initialize() { try { logger_1.default.info('Initializing Cache Manager'); if (this.config.persistenceEnabled && this.config.persistenceFile) { await this.loadFromPersistence(); } logger_1.default.info('Cache Manager initialized successfully'); this.emit('initialized'); } catch (error) { logger_1.default.error('Failed to initialize Cache Manager:', error); throw error; } } /** * Get value from cache */ async get(key) { const entry = this.cache.get(key); if (!entry) { this.misses++; this.metrics.missRate = this.calculateMissRate(); this.emit('cache_miss', { key }); return null; } // Check TTL expiration if (this.isExpired(entry)) { await this.delete(key); this.misses++; this.metrics.missRate = this.calculateMissRate(); this.emit('cache_miss', { key, reason: 'expired' }); return null; } // Update access tracking this.updateAccessTracking(key, entry); this.hits++; this.metrics.hitRate = this.calculateHitRate(); this.emit('cache_hit', { key }); return entry.value; } /** * Set value in cache */ async set(key, value, ttl, tags) { try { // Check if we need to evict entries if (this.cache.size >= this.config.maxEntries) { await this.evictEntries(1); } // Calculate entry size const size = this.calculateSize(value); // Check memory limits if (this.getTotalSize() + size > this.config.maxMemoryMB * 1024 * 1024) { await this.evictByMemory(size); } const effectiveTTL = ttl || this.config.defaultTTL; const entry = { key, value, ttl: effectiveTTL, createdAt: new Date(), accessCount: 1, lastAccessed: new Date(), size, ...(tags && { tags }) }; // Remove existing entry if present if (this.cache.has(key)) { await this.delete(key); } // Add new entry this.cache.set(key, entry); this.accessOrder.push(key); this.accessCount.set(key, 1); // Set TTL timer if (effectiveTTL > 0) { const timer = setTimeout(() => { this.delete(key).catch(error => { logger_1.default.error(`Failed to delete expired cache entry ${key}:`, error); }); }, effectiveTTL); this.timers.set(key, timer); } this.updateMetrics(); this.emit('cache_set', { key, size, ttl: effectiveTTL }); } catch (error) { logger_1.default.error(`Failed to set cache entry ${key}:`, error); const errorMessage = error instanceof Error ? error.message : 'Unknown error'; const errorObj = error instanceof Error ? error : undefined; throw new types_1.CacheError(`Failed to set cache entry: ${errorMessage}`, key, errorObj); } } /** * Delete entry from cache */ async delete(key) { const entry = this.cache.get(key); if (!entry) { return false; } // Clear TTL timer const timer = this.timers.get(key); if (timer) { clearTimeout(timer); this.timers.delete(key); } // Remove from tracking structures this.cache.delete(key); this.accessCount.delete(key); const orderIndex = this.accessOrder.indexOf(key); if (orderIndex !== -1) { this.accessOrder.splice(orderIndex, 1); } this.updateMetrics(); this.emit('cache_delete', { key }); return true; } /** * Clear cache entries by pattern or tags */ async invalidate(pattern, tags) { let deletedCount = 0; const keysToDelete = []; for (const [key, entry] of this.cache) { let shouldDelete = false; // Check pattern match if (pattern) { const regex = new RegExp(pattern); shouldDelete = regex.test(key); } // Check tag match if (tags && entry.tags) { shouldDelete = shouldDelete || tags.some(tag => entry.tags.includes(tag)); } // If no pattern or tags specified, delete all if (!pattern && !tags) { shouldDelete = true; } if (shouldDelete) { keysToDelete.push(key); } } // Delete matched entries for (const key of keysToDelete) { if (await this.delete(key)) { deletedCount++; } } this.emit('cache_invalidate', { pattern, tags, deletedCount }); return deletedCount; } /** * Get cache metrics */ getMetrics() { return { ...this.metrics }; } /** * Get cache statistics */ getStats() { return { totalEntries: this.cache.size, totalSize: this.getTotalSize(), memoryUsageMB: this.getTotalSize() / (1024 * 1024), hitRate: this.metrics.hitRate, missRate: this.metrics.missRate, evictionRate: this.metrics.evictionRate, averageAccessTime: this.metrics.averageAccessTime, oldestEntry: this.getOldestEntry(), newestEntry: this.getNewestEntry() }; } /** * Shutdown the cache manager */ async shutdown() { try { logger_1.default.info('Shutting down Cache Manager'); // Clear all timers for (const timer of this.timers.values()) { clearTimeout(timer); } this.timers.clear(); if (this.metricsCollectionTimer) { clearInterval(this.metricsCollectionTimer); this.metricsCollectionTimer = undefined; } if (this.persistenceTimer) { clearInterval(this.persistenceTimer); this.persistenceTimer = undefined; } // Save to persistence if enabled if (this.config.persistenceEnabled) { await this.saveToPersistence(); } // Clear cache this.cache.clear(); this.accessOrder.length = 0; this.accessCount.clear(); logger_1.default.info('Cache Manager shutdown completed'); this.emit('shutdown'); } catch (error) { logger_1.default.error('Cache Manager shutdown failed:', error); throw error; } } // Private helper methods initializeMetrics() { return { hitRate: 0, missRate: 0, evictionRate: 0, totalEntries: 0, totalSize: 0, averageAccessTime: 0, memoryUsage: 0 }; } setupMetricsCollection() { this.metricsCollectionTimer = setInterval(() => { this.updateMetrics(); }, 10000); // Update every 10 seconds // Ensure timer doesn't keep Node.js process alive this.metricsCollectionTimer.unref(); } setupPersistence() { if (this.config.persistenceEnabled && this.config.persistenceFile) { this.persistenceTimer = setInterval(() => { this.saveToPersistence().catch(error => { logger_1.default.error('Failed to save cache to persistence:', error); }); }, 60000); // Save every minute // Ensure timer doesn't keep Node.js process alive this.persistenceTimer.unref(); } } updateAccessTracking(key, entry) { entry.accessCount++; entry.lastAccessed = new Date(); // Update access count tracking this.accessCount.set(key, entry.accessCount); // Update LRU order const orderIndex = this.accessOrder.indexOf(key); if (orderIndex !== -1) { this.accessOrder.splice(orderIndex, 1); } this.accessOrder.push(key); } isExpired(entry) { if (entry.ttl <= 0) return false; return Date.now() - entry.createdAt.getTime() > entry.ttl; } calculateSize(value) { try { return JSON.stringify(value).length * 2; // Rough estimate (UTF-16) } catch { return 1024; // Default size for non-serializable objects } } getTotalSize() { let totalSize = 0; for (const entry of this.cache.values()) { totalSize += entry.size; } return totalSize; } async evictEntries(count) { const keysToEvict = this.selectEvictionCandidates(count); for (const key of keysToEvict) { await this.delete(key); this.metrics.evictionRate++; } this.emit('cache_eviction', { count: keysToEvict.length, policy: this.config.evictionPolicy }); } async evictByMemory(requiredSize) { let freedSize = 0; const keysToEvict = []; while (freedSize < requiredSize && this.cache.size > 0) { const candidates = this.selectEvictionCandidates(1); if (candidates.length === 0) break; const key = candidates[0]; const entry = this.cache.get(key); if (entry) { freedSize += entry.size; keysToEvict.push(key); } } for (const key of keysToEvict) { await this.delete(key); this.metrics.evictionRate++; } } selectEvictionCandidates(count) { const candidates = []; switch (this.config.evictionPolicy) { case 'lru': candidates.push(...this.accessOrder.slice(0, count)); break; case 'lfu': const sortedByFrequency = Array.from(this.accessCount.entries()) .sort(([, a], [, b]) => a - b) .slice(0, count) .map(([key]) => key); candidates.push(...sortedByFrequency); break; case 'ttl': const sortedByAge = Array.from(this.cache.entries()) .sort(([, a], [, b]) => a.createdAt.getTime() - b.createdAt.getTime()) .slice(0, count) .map(([key]) => key); candidates.push(...sortedByAge); break; case 'random': const keys = Array.from(this.cache.keys()); for (let i = 0; i < Math.min(count, keys.length); i++) { const randomIndex = Math.floor(Math.random() * keys.length); candidates.push(keys[randomIndex]); } break; } return candidates; } calculateHitRate() { const totalRequests = this.hits + this.misses; if (totalRequests === 0) { return 0; } return this.hits / totalRequests; } calculateMissRate() { return 1 - this.calculateHitRate(); } updateMetrics() { this.metrics.totalEntries = this.cache.size; this.metrics.totalSize = this.getTotalSize(); this.metrics.memoryUsage = this.metrics.totalSize / (this.config.maxMemoryMB * 1024 * 1024); // Calculate average access time (simplified) let totalAccessTime = 0; let accessCount = 0; for (const entry of this.cache.values()) { totalAccessTime += entry.lastAccessed.getTime() - entry.createdAt.getTime(); accessCount += entry.accessCount; } this.metrics.averageAccessTime = accessCount > 0 ? totalAccessTime / accessCount : 0; } getOldestEntry() { let oldest = null; for (const entry of this.cache.values()) { if (!oldest || entry.createdAt < oldest) { oldest = entry.createdAt; } } return oldest; } getNewestEntry() { let newest = null; for (const entry of this.cache.values()) { if (!newest || entry.createdAt > newest) { newest = entry.createdAt; } } return newest; } async loadFromPersistence() { if (!this.config.persistenceFile) return; try { const data = await fs_1.promises.readFile(this.config.persistenceFile, 'utf-8'); const persistedData = JSON.parse(data); for (const entryData of persistedData.entries || []) { const entry = { ...entryData, createdAt: new Date(entryData.createdAt), lastAccessed: new Date(entryData.lastAccessed) }; // Check if entry is still valid if (!this.isExpired(entry)) { this.cache.set(entry.key, entry); this.accessOrder.push(entry.key); this.accessCount.set(entry.key, entry.accessCount); } } logger_1.default.info(`Loaded ${this.cache.size} entries from cache persistence`); } catch (error) { if (error.code !== 'ENOENT') { logger_1.default.error('Failed to load cache from persistence:', error); } } } async saveToPersistence() { if (!this.config.persistenceFile) return; try { const persistenceDir = path.dirname(this.config.persistenceFile); await fs_1.promises.mkdir(persistenceDir, { recursive: true }); const persistedData = { timestamp: new Date().toISOString(), entries: Array.from(this.cache.values()) }; await fs_1.promises.writeFile(this.config.persistenceFile, JSON.stringify(persistedData, null, 2), 'utf-8'); logger_1.default.debug(`Saved ${this.cache.size} entries to cache persistence`); } catch (error) { logger_1.default.error('Failed to save cache to persistence:', error); } } } exports.CacheManager = CacheManager; exports.default = CacheManager; //# sourceMappingURL=CacheManager.js.map