UNPKG

mira-consciousness

Version:

Memory & Intelligence Retention Archive - Preserving The Spark

232 lines 7.51 kB
/** * Caching Strategy for MIRA * ========================= * * Implements a multi-level caching system to improve performance * for expensive operations like file analysis and memory searches. */ import fs from 'fs-extra'; import * as path from 'path'; import * as crypto from 'crypto'; export class CacheManager { memoryCache = new Map(); cacheDir; maxMemorySize; currentMemorySize = 0; constructor(cacheDir = path.join(process.cwd(), '.mira_cache'), maxMemorySize = 100 * 1024 * 1024 // 100MB default ) { this.cacheDir = cacheDir; this.maxMemorySize = maxMemorySize; this.initializeCacheDir(); } async initializeCacheDir() { await fs.ensureDir(this.cacheDir); } /** * Get cached value or compute it if not cached */ async getOrCompute(key, computeFn, options = {}) { const { ttl = 3600000, persistent = false } = options; // 1 hour default TTL // Check memory cache first const memoryEntry = this.memoryCache.get(key); if (memoryEntry && this.isValid(memoryEntry)) { return memoryEntry.data; } // Check persistent cache if enabled if (persistent) { const diskEntry = await this.loadFromDisk(key); if (diskEntry && this.isValid(diskEntry)) { // Restore to memory cache this.setMemoryCache(key, diskEntry); return diskEntry.data; } } // Compute the value const data = await computeFn(); // Cache the result const entry = { data, timestamp: Date.now(), ttl }; this.setMemoryCache(key, entry); if (persistent) { await this.saveToDisk(key, entry); } return data; } /** * Cache file analysis results with content hash */ async cacheFileAnalysis(filePath, analysisFn, options = {}) { const fileStats = await fs.stat(filePath); const fileContent = await fs.readFile(filePath, 'utf-8'); const contentHash = this.hashContent(fileContent); const cacheKey = `file:${filePath}:${contentHash}:${fileStats.mtime.getTime()}`; return this.getOrCompute(cacheKey, analysisFn, { ...options, persistent: true, ttl: 24 * 60 * 60 * 1000 // 24 hours for file analysis }); } /** * Cache expensive glob operations */ async cacheGlobResult(pattern, globFn, options = {}) { const cacheKey = `glob:${pattern}`; return this.getOrCompute(cacheKey, globFn, { ...options, ttl: 5 * 60 * 1000 // 5 minutes for glob results }); } /** * Invalidate cache entries */ async invalidate(pattern) { if (!pattern) { // Clear all cache this.memoryCache.clear(); this.currentMemorySize = 0; await fs.emptyDir(this.cacheDir); return; } // Clear matching entries for (const [key] of this.memoryCache) { if (key.includes(pattern)) { this.memoryCache.delete(key); } } // Clear from disk const files = await fs.readdir(this.cacheDir); for (const file of files) { if (file.includes(this.sanitizeKey(pattern))) { await fs.remove(path.join(this.cacheDir, file)); } } } /** * Get cache statistics */ getStats() { return { memoryEntries: this.memoryCache.size, memorySize: this.currentMemorySize, hitRate: this.calculateHitRate() }; } isValid(entry) { const now = Date.now(); return now - entry.timestamp < entry.ttl; } setMemoryCache(key, entry) { const size = this.estimateSize(entry.data); // Evict old entries if needed while (this.currentMemorySize + size > this.maxMemorySize && this.memoryCache.size > 0) { const oldestKey = this.findOldestEntry(); if (oldestKey) { this.memoryCache.delete(oldestKey); this.currentMemorySize -= this.estimateSize(this.memoryCache.get(oldestKey)?.data); } } this.memoryCache.set(key, entry); this.currentMemorySize += size; } findOldestEntry() { let oldestKey; let oldestTime = Infinity; for (const [key, entry] of this.memoryCache) { if (entry.timestamp < oldestTime) { oldestTime = entry.timestamp; oldestKey = key; } } return oldestKey; } async loadFromDisk(key) { const filePath = path.join(this.cacheDir, this.sanitizeKey(key) + '.json'); try { if (await fs.pathExists(filePath)) { const data = await fs.readJson(filePath); return data; } } catch (error) { // Cache file corrupted, remove it await fs.remove(filePath); } return null; } async saveToDisk(key, entry) { const filePath = path.join(this.cacheDir, this.sanitizeKey(key) + '.json'); try { await fs.writeJson(filePath, entry, { spaces: 2 }); } catch (error) { // Ignore disk cache errors } } sanitizeKey(key) { return key.replace(/[^a-zA-Z0-9-_]/g, '_').substring(0, 100); } hashContent(content) { return crypto.createHash('sha256').update(content).digest('hex').substring(0, 16); } estimateSize(data) { try { return JSON.stringify(data).length; } catch { return 1000; // Default estimate } } calculateHitRate() { // This would need to track hits/misses in a real implementation return 0; } } // Singleton instance let cacheInstance = null; export function getCache() { if (!cacheInstance) { cacheInstance = new CacheManager(); } return cacheInstance; } /** * Decorator for caching method results */ export function cached(options = {}) { return function (target, propertyKey, descriptor) { const originalMethod = descriptor.value; descriptor.value = async function (...args) { const cache = getCache(); const cacheKey = `${target.constructor.name}:${propertyKey}:${JSON.stringify(args)}`; return cache.getOrCompute(cacheKey, () => originalMethod.apply(this, args), options); }; return descriptor; }; } /** * Memoization helper for expensive computations */ export function memoize(fn, options = {}) { const cache = new Map(); const { maxSize = 100, ttl = 3600000 } = options; return ((...args) => { const key = JSON.stringify(args); const cached = cache.get(key); if (cached && Date.now() - cached.timestamp < ttl) { return cached.value; } const result = fn(...args); // Evict oldest if at capacity if (cache.size >= maxSize) { const oldestKey = Array.from(cache.entries()) .sort(([, a], [, b]) => a.timestamp - b.timestamp)[0][0]; cache.delete(oldestKey); } cache.set(key, { value: result, timestamp: Date.now() }); return result; }); } //# sourceMappingURL=cache.js.map