/** * Cache System - Stale-While-Revalidate * * Mantém dados em memória com TTL e atualização periódica. * Retorna dados do cache imediatamente enquanto atualiza em background. */ const cache = new Map(); const refreshIntervals = new Map(); const DEFAULT_TTL = 5 * 60 * 1000; // 5 minutos const REFRESH_INTERVAL = 5 * 60 * 1000; // Atualiza a cada 5 minutos /** * Armazena valor no cache * @param {string} key - Chave do cache * @param {any} value - Valor a armazenar * @param {number} ttl - Time-to-live em ms (opcional) */ function set(key, value, ttl = DEFAULT_TTL) { cache.set(key, { value, timestamp: Date.now(), ttl }); console.log(`[Cache] SET ${key} (TTL: ${ttl/1000}s)`); } /** * Recupera valor do cache * @param {string} key - Chave do cache * @returns {any|null} - Valor ou null se não encontrado/expirado */ function get(key) { const entry = cache.get(key); if (!entry) return null; const age = Date.now() - entry.timestamp; const isStale = age > entry.ttl; if (isStale) { console.log(`[Cache] STALE ${key} (age: ${Math.round(age/1000)}s)`); } return { value: entry.value, isStale, age: Math.round(age / 1000) }; } /** * Verifica se cache existe (mesmo que stale) */ function has(key) { return cache.has(key); } /** * Remove do cache */ function del(key) { cache.delete(key); if (refreshIntervals.has(key)) { clearInterval(refreshIntervals.get(key)); refreshIntervals.delete(key); } } /** * Limpa todo o cache */ function clear() { cache.clear(); refreshIntervals.forEach(interval => clearInterval(interval)); refreshIntervals.clear(); } /** * Registra função para refresh periódico * @param {string} key - Chave do cache * @param {Function} fetchFn - Função async que busca os dados * @param {number} interval - Intervalo de refresh em ms */ function registerAutoRefresh(key, fetchFn, interval = REFRESH_INTERVAL) { // Limpa interval anterior se existir if (refreshIntervals.has(key)) { clearInterval(refreshIntervals.get(key)); } // Função de refresh const refresh = async () => { try { console.log(`[Cache] REFRESH ${key}`); const value = await fetchFn(); set(key, value); } catch (err) { console.error(`[Cache] REFRESH ERROR ${key}:`, err.message); } }; // Faz refresh inicial refresh(); // Agenda refreshes periódicos const intervalId = setInterval(refresh, interval); refreshIntervals.set(key, intervalId); console.log(`[Cache] AUTO-REFRESH registered for ${key} (every ${interval/1000}s)`); } /** * Helper: get-or-fetch com stale-while-revalidate * Retorna cache (mesmo stale) imediatamente e atualiza em background se stale */ async function getOrFetch(key, fetchFn, ttl = DEFAULT_TTL) { const cached = get(key); if (cached) { // Se stale, atualiza em background if (cached.isStale) { fetchFn().then(value => set(key, value, ttl)).catch(err => { console.error(`[Cache] Background fetch error for ${key}:`, err.message); }); } return cached.value; } // Não tem cache, busca e aguarda const value = await fetchFn(); set(key, value, ttl); return value; } /** * Stats do cache */ function stats() { const entries = []; cache.forEach((entry, key) => { const age = Date.now() - entry.timestamp; entries.push({ key, age: Math.round(age / 1000), ttl: entry.ttl / 1000, isStale: age > entry.ttl }); }); return { size: cache.size, entries }; } module.exports = { set, get, has, del, clear, registerAutoRefresh, getOrFetch, stats };