FlowCache
🧠 O que é
FlowCache é uma biblioteca de cache em memória para Node.js que armazena valores por chave com tempo de expiração automático. Funciona localmente no seu processo Node.js, sem dependências externas, e oferece recursos avançados como deduplicação de requisições simultâneas e "stale-while-revalidate".
Analogia simples: é como ter um anotador que guarda o resultado da última pergunta e responde imediatamente se alguém fizer a mesma pergunta novamente (enquanto o resultado ainda é válido).
❓ Por que existe
Programas Node.js frequentemente consultam as mesmas informações repetidamente:
- Um endpoint de API é chamado 50 vezes em paralelo → você faz 50 requests.
- Um bot Discord responde sempre com os mesmos dados → overhead desnecessário.
- Um dashboard recarrega a página → a mesma query no banco roda 3x.
FlowCache evita isso:
- Elimina regeneração desnecessária: guarda resultado para poupar processamento.
- Deduplica requests concorrentes: 50 chamadas simultâneas = 1 request real.
- Melhora latência: retorna do memória em microsegundos (vs ms/s da rede).
- Oferece controle granular: você decide quando espiar dados frescos ou "antigos mas rápidos".
✅ Quando usar
Use FlowCache quando:
- Você chama uma API/banco de dados múltiplas vezes com os mesmos parâmetros.
- Seus dados mudam lentamente (segundos~minutos).
- Latência importa (cada ms conta para UX).
- Você quer evitar sobrecarga no upstream (API/banco).
- Trabalha com um único processo Node.js ou precisa de cache local por instância.
Casos de uso reais:
- Bots (Discord, Telegram): usuários pedem dados iguais ao mesmo tempo → uma chamada supre todos.
- Dashboards: página recarrega mas dados de ontem ainda servem temporariamente.
- APIs internas: múltiplos microserviços consultam a mesma coisa.
- Workers/Jobs: processam filas repetidamente com dados estáticos.
🚫 Quando NÃO usar
NÃO use FlowCache quando:
- Você precisa compartilhar cache entre múltiplos processos/servidores (use Redis, Memcached).
- Os dados precisam sobreviver a restart do processo (use banco de dados).
- Você exige consistência forte entre instâncias (Redis com replicação seria solução).
- Os dados mudam a cada microsegundo (cache não ajuda se TTL precisa ser 0ms).
Padrão híbrido comum:
Seu código
↓
[FlowCache L1 - memória local, rápido]
↓
[Redis/Memcached L2 - compartilhado, persistência]
↓
[API/Banco L3 - fonte real]
⚡ Exemplo mínimo funcional
O menor código que demonstra cache funcionando:
import { Cache } from "flowcache";
const cache = new Cache({ defaultTTL: 10_000 }); // TTL = 10 segundos
async function getData() {
return cache.fetch("minha-chave", async () => {
console.log("❌ Executando operação lenta...");
await new Promise(r => setTimeout(r, 1000)); // simula atraso
return { resultado: "pronto" };
});
}
await getData(); // ❌ Executando operação lenta... → {resultado: "pronto"}
await getData(); // ✅ (retorna do cache instantaneamente) → {resultado: "pronto"}
await getData(); // ✅ (retorna do cache instantaneamente) → {resultado: "pronto"}
console.log(cache.stats()); // {hits: 2, misses: 1, ...}
O que acontece:
- Primeira chamada: cache vazio → executa função → guarda resultado.
- Próximas chamadas (dentro de 10s): retorna resultado guardado sem executar função novamente.
- Depois de 10s: TTL expira → próxima chamada reexecuta função.
🧩 Exemplo real de produção
Situação: você tem um bot Discord que retorna informações de perfil de jogador.
import { Cache } from "flowcache";
type PlayerProfile = {
id: string;
name: string;
level: number;
score: number;
};
const profileCache = new Cache<PlayerProfile>({
defaultTTL: 30_000, // 30 segundos
maxEntries: 1000, // máximo 1000 perfis em memória
sweepIntervalMs: 60_000, // limpar expirados a cada 60s
hooks: {
onHit: (key) => console.log(`Cache hit: ${key}`),
onMiss: (key) => console.log(`Cache miss: ${key} - buscando do servidor`),
},
});
export async function getPlayerProfile(playerId: string): Promise<PlayerProfile> {
return profileCache.fetch(
`player:${playerId}`,
async () => {
// Apenas esta função é chamada se não estiver em cache
const response = await fetch(`https://api.game.com/players/${playerId}`);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json() as Promise<PlayerProfile>;
},
{
stale: true, // Se expirar, retorna antigo e atualiza em paralelo
tags: ["player-profile", `player:${playerId}`],
}
);
}
// Uso em bot Discord:
// bot.on('interactionCreate', async (i) => {
// const profile = await getPlayerProfile(i.user.id);
// await i.reply(`Seu nível: ${profile.level}`);
// });
Ganhos concretos:
- 10 usuários pedem perfil ao mesmo tempo → 1 chamada API real (deduplica).
- O perfil já existe mas expirou → retorna versão antiga (1ms) enquanto busca novo em background.
- Servidor de API recebe 90% menos requisições.
- Usuários veem resposta em
<5msvs200ms+de rede.
⚠️ Erros comuns
1. Esquecer de incluir dados na chave
❌ Errado:
await cache.fetch("user", async () => fetchUser(id)); // Chave ignora id!
// Dois usuários diferentes recebem o mesmo cache
const user1 = await cache.fetch("user", () => fetchUser("alice")); // cache miss
const user2 = await cache.fetch("user", () => fetchUser("bob")); // cache HIT (retorna alice!)
✅ Correto:
await cache.fetch(`user:${id}`, async () => fetchUser(id)); // Chave inclui id
2. TTL muito alto para dados dinâmicos
❌ Errado:
const cache = new Cache({ defaultTTL: 60 * 60 * 1000 }); // 1 hora
await cache.fetch("exchange-rate", async () => getExchangeRate());
// Taxa de câmbio é velha por até 1 hora → números errados em transações
✅ Correto:
const cache = new Cache({ defaultTTL: 60_000 }); // 1 minuto
await cache.fetch("exchange-rate", async () => getExchangeRate());
3. Não limpar cache quando dados mudam
❌ Errado:
await updateUserName(userId, "novo-nome");
// Cache ainda retorna nome antigo por 30 segundos
✅ Correto:
await updateUserName(userId, "novo-nome");
cache.delete(`user:${userId}`); // Para apagar uma chave específica
// OU
cache.invalidateTag(`user:${userId}`); // Para apagar por tag
💡 Boas práticas
-
Padronize sua nomenclatura de chaves → facilitará Debug depois.
// Bom: prefixo:entidade:id
`user:${userId}`, `post:${postId}:comments`, `config:feature-flags` -
Use tags para invalidação em lote.
await cache.fetch(`user:123`, loadUser, { tags: ["user", "tenant:abc"] });
// Depois, ao deletar tenant:
cache.invalidateTag("tenant:abc"); // Remove todos de uma vez -
Combine
stale: truecom TTLs realistas.// Retorna dado antigo IMEDIATAMENTE enquanto busca novo em background
await cache.fetch(key, fn, { stale: true, ttl: 10_000 }); -
Monitore stats em produção.
setInterval(() => {
console.log(cache.stats()); // hit%, miss%, pending promises
}, 60_000); -
Configure
maxEntriesconforme sua memória disponível.const cache = new Cache({ maxEntries: 5000 }); // Remove entradas antigas se exceder
📦 Instalação
npm install flowcache
🔧 Requisitos
- Node.js 20+
- Suporta ESM e CommonJS
- Sem dependências externas