🚀 Initial commit: Versão atual do TrackSteel APP
This commit is contained in:
@@ -0,0 +1,363 @@
|
||||
/**
|
||||
* 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;
|
||||
Reference in New Issue
Block a user