/** * Sistema de Cache Inteligente para Otimização de Performance * Implementa cache em memória e localStorage com TTL e versionamento */ interface CacheEntry { data: any; timestamp: number; ttl: number; version: string; } interface PerformanceMetrics { operation: string; duration_ms: number; cache_hit: boolean; items_count: number; query_count: number; timestamp: Date; } export class OptimizedCache { private static readonly CACHE_VERSIONS = { HISTORICO: '1.0', ITENS_DISPONIVEIS: '1.1', COMPONENTES: '1.0', PECAS: '1.0', PROCESSOS: '1.0' } as const; public static readonly TTL_CONFIG = { HISTORICO_OF: 5 * 60 * 1000, // 5 minutos ITENS_DISPONIVEIS: 2 * 60 * 1000, // 2 minutos COMPONENTES: 10 * 60 * 1000, // 10 minutos PECAS: 10 * 60 * 1000, // 10 minutos PROCESSOS: 15 * 60 * 1000 // 15 minutos } as const; private static memoryCache = new Map(); private static readonly MEMORY_TTL = 30 * 1000; // 30 segundos /** * Recupera dados do cache (memória primeiro, depois localStorage) */ static async get(key: string, type: keyof typeof OptimizedCache.TTL_CONFIG): Promise { const startTime = performance.now(); // Verificar memória primeiro const memoryKey = `mem_${key}`; if (this.memoryCache.has(memoryKey)) { const entry = this.memoryCache.get(memoryKey)!; if (Date.now() - entry.timestamp < this.MEMORY_TTL) { this.trackPerformance({ operation: `cache_get_${type}`, duration_ms: Math.round(performance.now() - startTime), cache_hit: true, items_count: Array.isArray(entry.data) ? entry.data.length : 1, query_count: 0, timestamp: new Date() }); return entry.data; } else { this.memoryCache.delete(memoryKey); } } // Verificar localStorage const storageKey = `cache_${key}`; const stored = localStorage.getItem(storageKey); if (stored) { try { const entry: CacheEntry = JSON.parse(stored); const ttl = this.TTL_CONFIG[type]; const versionKey = type.split('_')[0] as keyof typeof this.CACHE_VERSIONS; if (Date.now() - entry.timestamp < ttl && entry.version === this.CACHE_VERSIONS[versionKey]) { // Armazenar em memória para próximas consultas this.memoryCache.set(memoryKey, { data: entry.data, timestamp: Date.now() }); this.trackPerformance({ operation: `cache_get_${type}`, duration_ms: Math.round(performance.now() - startTime), cache_hit: true, items_count: Array.isArray(entry.data) ? entry.data.length : 1, query_count: 0, timestamp: new Date() }); return entry.data; } else { localStorage.removeItem(storageKey); } } catch (error) { console.warn('Erro ao recuperar cache:', error); localStorage.removeItem(storageKey); } } this.trackPerformance({ operation: `cache_get_${type}`, duration_ms: Math.round(performance.now() - startTime), cache_hit: false, items_count: 0, query_count: 0, timestamp: new Date() }); return null; } /** * Armazena dados no cache (memória e localStorage) */ static set(key: string, data: T, type: keyof typeof OptimizedCache.TTL_CONFIG): void { const versionKey = type.split('_')[0] as keyof typeof this.CACHE_VERSIONS; const entry: CacheEntry = { data, timestamp: Date.now(), ttl: this.TTL_CONFIG[type], version: this.CACHE_VERSIONS[versionKey] || '1.0' }; // Armazenar em ambos os caches this.memoryCache.set(`mem_${key}`, { data, timestamp: Date.now() }); try { localStorage.setItem(`cache_${key}`, JSON.stringify(entry)); } catch (error) { console.warn('Erro ao armazenar cache no localStorage:', error); // Se localStorage estiver cheio, limpar caches antigos this.cleanupOldCache(); try { localStorage.setItem(`cache_${key}`, JSON.stringify(entry)); } catch (retryError) { console.error('Falha ao armazenar cache após limpeza:', retryError); } } } /** * Invalida cache por padrão de chave */ static invalidatePattern(pattern: string): void { // Limpar memória for (const key of this.memoryCache.keys()) { if (key.includes(pattern)) { this.memoryCache.delete(key); } } // Limpar localStorage for (let i = localStorage.length - 1; i >= 0; i--) { const key = localStorage.key(i); if (key && key.includes(pattern)) { localStorage.removeItem(key); } } } /** * Remove entrada específica do cache */ static remove(key: string): void { this.memoryCache.delete(`mem_${key}`); localStorage.removeItem(`cache_${key}`); } /** * Limpa caches antigos para liberar espaço */ private static cleanupOldCache(): void { const now = Date.now(); const keysToRemove: string[] = []; for (let i = 0; i < localStorage.length; i++) { const key = localStorage.key(i); if (key && key.startsWith('cache_')) { try { const entry: CacheEntry = JSON.parse(localStorage.getItem(key)!); if (now - entry.timestamp > entry.ttl) { keysToRemove.push(key); } } catch (error) { keysToRemove.push(key); } } } keysToRemove.forEach(key => localStorage.removeItem(key)); console.log(`Cache cleanup: removidas ${keysToRemove.length} entradas antigas`); } /** * Obtém estatísticas do cache */ static getStats(): { memoryEntries: number; localStorageEntries: number; totalSize: number; } { let localStorageEntries = 0; let totalSize = 0; for (let i = 0; i < localStorage.length; i++) { const key = localStorage.key(i); if (key && key.startsWith('cache_')) { localStorageEntries++; totalSize += localStorage.getItem(key)!.length; } } return { memoryEntries: this.memoryCache.size, localStorageEntries, totalSize }; } /** * Limpa todo o cache */ static clear(): void { this.memoryCache.clear(); const keysToRemove: string[] = []; for (let i = 0; i < localStorage.length; i++) { const key = localStorage.key(i); if (key && key.startsWith('cache_')) { keysToRemove.push(key); } } keysToRemove.forEach(key => localStorage.removeItem(key)); } /** * Registra métricas de performance */ private static trackPerformance(metrics: PerformanceMetrics): void { if (process.env.NODE_ENV === 'development') { console.log('🚀 Cache Performance:', metrics); } // Armazenar métricas para análise posterior const metricsKey = 'cache_performance_metrics'; const existingMetrics = JSON.parse(localStorage.getItem(metricsKey) || '[]'); existingMetrics.push(metrics); // Manter apenas as últimas 100 métricas if (existingMetrics.length > 100) { existingMetrics.splice(0, existingMetrics.length - 100); } try { localStorage.setItem(metricsKey, JSON.stringify(existingMetrics)); } catch (error) { // Se não conseguir armazenar métricas, não é crítico } } /** * Obtém métricas de performance */ static getPerformanceMetrics(): PerformanceMetrics[] { const metricsKey = 'cache_performance_metrics'; return JSON.parse(localStorage.getItem(metricsKey) || '[]'); } } /** * Hook para cache inteligente com React Query/SWR style */ export const useSmartCache = ( key: string, fetcher: () => Promise, type: keyof typeof OptimizedCache.TTL_CONFIG, options: { enabled?: boolean; onSuccess?: (data: T) => void; onError?: (error: Error) => void; } = {} ) => { const [data, setData] = React.useState(null); const [loading, setLoading] = React.useState(false); const [error, setError] = React.useState(null); React.useEffect(() => { if (options.enabled === false) return; const loadData = async () => { setLoading(true); setError(null); try { // Tentar cache primeiro const cached = await OptimizedCache.get(key, type); if (cached) { setData(cached); setLoading(false); options.onSuccess?.(cached); return; } // Buscar dados frescos const freshData = await fetcher(); OptimizedCache.set(key, freshData, type); setData(freshData); options.onSuccess?.(freshData); } catch (err) { const error = err instanceof Error ? err : new Error('Erro desconhecido'); setError(error); options.onError?.(error); } finally { setLoading(false); } }; loadData(); }, [key, type, options.enabled]); const invalidate = () => { OptimizedCache.remove(key); }; const refetch = async () => { OptimizedCache.remove(key); setLoading(true); setError(null); try { const freshData = await fetcher(); OptimizedCache.set(key, freshData, type); setData(freshData); options.onSuccess?.(freshData); } catch (err) { const error = err instanceof Error ? err : new Error('Erro desconhecido'); setError(error); options.onError?.(error); } finally { setLoading(false); } }; return { data, loading, error, invalidate, refetch }; }; // Importar React para o hook import React from 'react'; export default OptimizedCache;