/** * Performance configuration for Phase 1 optimization */ export const PERFORMANCE_CONFIG = { // Metadata cache TTL (5 minutes) METADATA_CACHE_TTL: 5 * 60 * 1000, // Preload nearby notes (number of notes before/after) PRELOAD_NEARBY: 5, // Pagination size for large vaults PAGINATION_SIZE: 100, // Maximum notes to load in metadata endpoint MAX_METADATA_ITEMS: 10000, // Enable performance logging ENABLE_PERF_LOGGING: process.env.PERF_LOGGING === 'true', }; /** * Advanced in-memory cache for metadata with metrics and intelligent invalidation */ export class MetadataCache { constructor(ttl = PERFORMANCE_CONFIG.METADATA_CACHE_TTL, maxSize = 10000) { this.cache = new Map(); this.ttl = ttl; this.maxSize = maxSize; this.hits = 0; this.misses = 0; this.isLoading = false; this.loadPromise = null; } /** * Get cached metadata for a vault */ async getMetadata(vaultDir, loader) { const now = Date.now(); const cacheKey = this.getCacheKey(vaultDir); const cached = this.cache.get(cacheKey); // Cache valide ? if (cached && (now - cached.timestamp) < this.ttl) { this.hits++; return cached.data; } // Cache miss - recharger this.misses++; return await this.loadFreshMetadata(vaultDir, cacheKey, loader); } /** * Charger les métadonnées fraiches */ async loadFreshMetadata(vaultDir, cacheKey, loader) { // Éviter les chargements concurrents if (this.isLoading && this.loadPromise) { return this.loadPromise; } this.isLoading = true; try { this.loadPromise = loader(); const metadata = await this.loadPromise; // Stocker en cache this.cache.set(cacheKey, { data: metadata, timestamp: Date.now() }); // Nettoyer le cache si trop gros this.cleanupIfNeeded(); return metadata; } finally { this.isLoading = false; this.loadPromise = null; } } /** * Invalider le cache pour une voûte */ invalidate(vaultDir = null) { if (vaultDir) { const cacheKey = this.getCacheKey(vaultDir); this.cache.delete(cacheKey); } else { this.cache.clear(); } } /** * Générer une clé de cache unique */ getCacheKey(vaultDir) { return `metadata_${vaultDir.replace(/[/\\]/g, '_')}`; } /** * Nettoyer le cache si nécessaire (LRU simple) */ cleanupIfNeeded() { if (this.cache.size > this.maxSize) { // Supprimer les entrées les plus anciennes (LRU simple) const entries = Array.from(this.cache.entries()); entries.sort((a, b) => a[1].timestamp - b[1].timestamp); const toRemove = entries.slice(0, Math.floor(this.maxSize * 0.1)); toRemove.forEach(([key]) => this.cache.delete(key)); console.log(`[Cache] Cleaned up ${toRemove.length} old entries`); } } /** * Obtenir les statistiques du cache */ getStats() { const total = this.hits + this.misses; return { size: this.cache.size, hitRate: total > 0 ? (this.hits / total) * 100 : 0, hits: this.hits, misses: this.misses, maxSize: this.maxSize, ttl: this.ttl }; } /** * Réinitialiser les métriques */ resetStats() { this.hits = 0; this.misses = 0; } } /** * Performance logger */ export class PerformanceLogger { static log(operation, duration, metadata = {}) { if (!PERFORMANCE_CONFIG.ENABLE_PERF_LOGGING) { return; } const level = duration > 1000 ? 'warn' : 'info'; const message = `[PERF] ${operation}: ${duration.toFixed(0)}ms`; if (level === 'warn') { console.warn(message, metadata); } else { console.log(message, metadata); } } static mark(label) { if (typeof performance !== 'undefined' && performance.mark) { performance.mark(label); } } static measure(label, startMark, endMark) { if (typeof performance !== 'undefined' && performance.measure) { try { performance.measure(label, startMark, endMark); } catch (e) { // Ignore if marks don't exist } } } }