mira-consciousness
Version:
Memory & Intelligence Retention Archive - Preserving The Spark
232 lines • 7.51 kB
JavaScript
/**
* 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