Files
tracksteel_app/src/utils/OptimizedCache.ts
T

363 lines
9.8 KiB
TypeScript

/**
* 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<string, { data: any; timestamp: number }>();
private static readonly MEMORY_TTL = 30 * 1000; // 30 segundos
/**
* Recupera dados do cache (memória primeiro, depois localStorage)
*/
static async get<T>(key: string, type: keyof typeof OptimizedCache.TTL_CONFIG): Promise<T | null> {
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<T>(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 = <T>(
key: string,
fetcher: () => Promise<T>,
type: keyof typeof OptimizedCache.TTL_CONFIG,
options: {
enabled?: boolean;
onSuccess?: (data: T) => void;
onError?: (error: Error) => void;
} = {}
) => {
const [data, setData] = React.useState<T | null>(null);
const [loading, setLoading] = React.useState(false);
const [error, setError] = React.useState<Error | null>(null);
React.useEffect(() => {
if (options.enabled === false) return;
const loadData = async () => {
setLoading(true);
setError(null);
try {
// Tentar cache primeiro
const cached = await OptimizedCache.get<T>(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;