@cactus-agents/platform-cache
Engine de cache multi-camada para Cloudflare Workers. Abstrai memória + CF Cache API + KV em uma interface única orientada a recursos, com política configurável via variável de ambiente.
Instalação
pnpm add @cactus-agents/platform-cache
Visão geral
O platform-cache implementa um pipeline de cache em camadas:
Requisição
↓
1. Memory (in-process, instantâneo)
↓ miss
2. CF Cache API (edge, compartilhado entre workers)
↓ miss
3. Origin (fetch real)
↓ erro de origem
4. KV Snapshot (fallback estável — se habilitado na policy)
A política é definida em front-ops (YAML por brand/env) e injetada em deploy-time como PLATFORM_CACHE_POLICY_JSON. Se a variável estiver ausente, o engine trabalha em modo bypass — toda requisição vai direto à origem, sem cache (ideal para dev local).
Uso básico
import {
createCacheEngine,
parseCachePolicy,
CacheApiStore,
KvSnapshotStore,
NoopSnapshotStore,
} from "@cactus-agents/platform-cache";
// Em um Cloudflare Worker loader:
const policy = parseCachePolicy(env.PLATFORM_CACHE_POLICY_JSON);
const engine = createCacheEngine({
policy,
primaryStore: new CacheApiStore(),
snapshotStore: env.PLATFORM_CACHE_KV
? new KvSnapshotStore(env.PLATFORM_CACHE_KV)
: new NoopSnapshotStore(),
domain: env.ORIGIN_DOMAIN,
});
// Buscar um recurso com cache:
const result = await engine.fetch(
"brandConfig",
() => createBrandFromClient(client, opts),
{ waitUntil: ctx.waitUntil }
);
if (result.data) {
// result.status: "hit" | "stale" | "miss" | "fallback" | "error"
// result.source: "memory" | "cache_api" | "kv" | "origin" | null
}
API
createCacheEngine(config)
Cria uma instância do engine. Aceita CacheEngineConfig:
interface CacheEngineConfig {
policy: CachePolicy | null; // null = bypass (dev local sem policy)
primaryStore: PrimaryStore; // CacheApiStore ou implementação custom
snapshotStore: SnapshotStore; // KvSnapshotStore ou NoopSnapshotStore
domain: string; // ORIGIN_DOMAIN (ex: "casateste.com")
}
Retorna CacheEngine:
| Método | Descrição |
|---|---|
fetch(resource, fetcher, opts?) | Busca via pipeline — usa a chave padrão do resource |
fetchWithKey(resource, suffix, fetcher, opts?) | Busca com chave dinâmica — útil para recursos com múltiplas entradas (ex: detalhe de jogo por slug) |
purge(resource, suffix?) | Remove um resource específico do primary cache |
purgeAll() | Remove todos os entries deste domínio do memory tier |
FetchOptions
interface FetchOptions {
mode?: "normal" | "cache-only"; // default: "normal"
waitUntil?: (promise: Promise<unknown>) => void; // ctx.waitUntil do CF
}
"normal"— pipeline completo: memory → cache_api → origin → snapshot"cache-only"— consulta apenas o cache primário, nunca chama a origem. Retornanullem miss.
O waitUntil é essencial para stale-while-revalidate: ao passar ctx.waitUntil, a revalidação em background é registrada no Worker sem bloquear a resposta.
parseCachePolicy(json)
Deserializa a policy do env var PLATFORM_CACHE_POLICY_JSON:
const policy = parseCachePolicy(env.PLATFORM_CACHE_POLICY_JSON);
// Retorna CachePolicy | null
// null quando a string está ausente, vazia ou inválida
buildCacheKey(domain, keyPrefix, suffix?)
Gera a chave de cache no formato esperado pela CF Cache API:
https://{domain}-cache/{keyPrefix}:{suffix}/v1
Normalmente não é necessário chamar diretamente — o engine resolve as chaves internamente via ResourcePolicy.storage.keyPrefix.
getResourcePolicy(policy, resource)
Retorna a ResourcePolicy para um recurso específico, ou null se a policy é nula, o recurso não existe ou está desabilitado:
const rp = getResourcePolicy(policy, "brandConfig");
// null = recurso não configurado ou desabilitado → bypass
Stores
CacheApiStore
Store primária (memory in-process + CF Cache API). Dois níveis de cache em uma store só:
- Memory: Map em memória, resolvido instantaneamente
- CF Cache API: Cache de edge compartilhado entre instâncias do Worker
const store = new CacheApiStore();
Não recebe parâmetros. Em dev local (fora do Worker), a CF Cache API não está disponível — o store opera apenas em memória.
KvSnapshotStore
Store de snapshot usando Cloudflare KV. Usado como fallback estável quando a origem está falhando:
import type { KVNamespaceLike } from "@cactus-agents/platform-cache";
const store = new KvSnapshotStore(env.PLATFORM_CACHE_KV);
O binding PLATFORM_CACHE_KV é provisionado automaticamente pelo front-ops em deploy-time. O tipo KVNamespaceLike é exportado para facilitar mocks em testes.
NoopSnapshotStore
Snapshot store noop — não salva nem lê nada. Use quando KV não estiver disponível (dev local, ou quando fallbackEnabled: false para todos os recursos):
const store = new NoopSnapshotStore();
MemoryStore
Store de memória simples para testes unitários. Não usa CF Cache API:
import { MemoryStore } from "@cactus-agents/platform-cache";
// Útil em testes onde CF Cache API não está disponível
Tipos
CacheResult<T>
Retornado por engine.fetch() e engine.fetchWithKey():
interface CacheResult<T> {
data: T | null;
status: CacheResultStatus; // "hit" | "stale" | "miss" | "fallback" | "error"
source: CacheResultSource; // "memory" | "cache_api" | "kv" | "origin" | null
revalidated: boolean; // true = revalidação em background foi disparada
resource: string; // nome do recurso
error?: unknown; // presente quando status === "error"
}
Quando status === "error", use extractApiError() do @cactus-agents/api-client para normalizar o erro.
ResourcePolicy
Política de um recurso específico (vem do YAML do front-ops):
interface ResourcePolicy {
enabled: boolean;
ttlSeconds: number; // tempo de cache "fresh"
staleWhileRevalidateSeconds: number; // extra período para retornar stale + revalidar em BG
staleIfErrorSeconds: number; // extra período para retornar stale se origem errar
fallbackEnabled: boolean; // habilita KV snapshot como last-resort
storage: ResourceStorageConfig;
}
interface ResourceStorageConfig {
primary: "cache_api"; // sempre cache_api no momento
snapshot: "kv" | "none";
kvBinding?: string; // ex: "PLATFORM_CACHE_KV"
keyPrefix: string; // prefixo lógico (ex: "brandConfig", "homeRows")
}
CachePolicy
Mapa de nome do recurso → ResourcePolicy:
type CachePolicy = Record<string, ResourcePolicy>;
Env vars relacionadas
| Variável | Tipo | Descrição |
|---|---|---|
PLATFORM_CACHE_POLICY_JSON | string (JSON) | Política serializada, injetada pelo front-ops em deploy-time. Ausente em dev = bypass. |
PLATFORM_CACHE_KV | KV binding | Binding KV para snapshots. Configurado no wrangler.toml gerado pelo front-ops. |
Veja env-vars para o contexto completo.
Recursos padrão cacheaveis
Os seguintes recursos são configurados por padrão na policy do front-ops:
| Resource | keyPrefix | TTL padrão | Descrição |
|---|---|---|---|
brandConfig | brandConfig | 3600s | Configuração da marca |
homeRows | homeRows | 300s | Linhas da home (carrosséis) |
gamesBase | gamesBase | 300s | Catálogo base de jogos |
gameStats | stats-dl | 300s | Estatísticas de jogos (por slug) |
gameDetail | game | 300s | Detalhes de jogo individual (por slug) |
Invalidação de cache
O template expõe um endpoint de purge via webhook:
POST /api/cache/games/purge
Protegido por CACHE_PURGE_SECRET. Chama engine.purgeAll() para limpar o memory tier. A CF Cache API não suporta purge programático — entries expiram pelo TTL + grace period.
Exemplos
Busca com chave dinâmica (game detail)
const result = await engine.fetchWithKey(
"gameDetail",
`${provider}/${game}`, // suffix único por jogo
() => gamesService.getGameDetail(provider, game),
{ waitUntil: ctx.waitUntil }
);
Cache-only (search pré-populado)
// Não chama a origem se não tiver no cache — retorna null em miss
const cached = await engine.fetch(
"gamesBase",
() => { throw new Error("not reached"); },
{ mode: "cache-only" }
);