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
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 |