180 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			180 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /**
 | |
|  * 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
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 |