@agentbadge/cache
Shared cache layer for AgentBadge. Exposes a CacheProvider contract with
pluggable backends:
InMemoryCache — zero-dependency per-process fallback (default when
CACHE_ENABLED=false on the server).
ValkeyCache — local dev backend over ioredis (docker-compose
Valkey, host port 6336). Accepts valkey:// / redis:// URLs.
UpstashCache — production backend over @upstash/redis HTTP REST
(no persistent TCP; works from Fly.io).
All Redis-backed providers share RedisCacheBase over a minimal
RedisCommands surface — values are JSON-encoded, tags use
tag:{tag}/keytags:{key} sets, and every op degrades to a safe default
(null/false/0) on backend failure instead of throwing.
Factory
import { createCache } from "@agentbadge/cache";
const cache = createCache({
enabled: true,
backend: "valkey",
url: "valkey://localhost:6336",
});
enabled:false, unknown backend, or missing url/token → InMemoryCache.
createCache never throws.
Contract
interface CacheProvider {
get<T>(key: string): Promise<T | null>;
set<T>(key: string, value: T, opts?: { ttlSec?: number; tags?: string[] }): Promise<void>;
delete(key: string): Promise<boolean>;
invalidateTag(tag: string): Promise<number>;
incr(key: string, ttlSec?: number): Promise<number>;
health(): Promise<boolean>;
close(): Promise<void>;
}
Rules (EPIC-144):
- TTL always set by callers as a safety net; tags only where a real
invalidation event exists (e.g. rescan →
domain:{d}).
- Never block the request path on cache failure — degrade to passthrough.
Usage
import { InMemoryCache } from "@agentbadge/cache";
const cache = new InMemoryCache({ maxEntries: 10_000 });
await cache.set("badge:example.com", { score: 92 }, { ttlSec: 300, tags: ["domain:example.com"] });
const hit = await cache.get("badge:example.com");
await cache.invalidateTag("domain:example.com");
await cache.close();
Dev backend
Valkey runs in packages/database/docker-compose.yml (host port 6336):
cd ../database && bun run db:up
valkey-cli -p 6336 ping
Gotchas
- Never throw into the request path — providers degrade to safe
defaults (null/false/0) on backend failure.
- TTL is mandatory by convention — callers always pass
ttlSec.
- Tag scheme:
domain:{d} per-entry (rescan invalidation) + shared tags
like badge for bulk clear.
See RUNBOOK.md for dev/prod setup, key map, and
troubleshooting.
Scripts
bun run build — tsc → dist/
bun run test / bunx vitest run — unit tests
Questions & Contact