Files
bi-agents/src/cache.js
root 022993b201 feat: novo dashboard admin com KPIs, tendencias e ranking
- Adiciona src/admin-dashboard.js com lazy loading
- KPIs: hoje vs media 30 dias por fluxo
- Graficos de tendencia 30 dias (consolidado e por fluxo)
- Ranking top 5 agentes com filtro de periodo
- Adiciona sistema de cache (src/cache.js)
- Cache com TTL e auto-refresh periodico (5-10min)
- APIs: /admin/api/kpis, /admin/api/trend, /admin/api/top-agentes

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-08 13:53:55 -05:00

165 lines
3.6 KiB
JavaScript

/**
* 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
};