Overview

Voltro's caching layer (@voltro/cache) — an always-on memory default, swappable Redis-compatible backends, a low-level wrap primitive, and automatic query-result invalidation.

Voltro ships a first-class cache in @voltro/cache. It is always on — the in-process memory backend is the zero-config default, so ctx.cache works in every handler from the first boot with nothing to install. You opt into a distributed backend (Redis & friends) only when you need cross-instance sharing.

There are two ways to use it, and they share one backend:

  1. Low-level — ctx.cache.wrap(key, { ttl, tags }, () => compute()) in any handler. Cache-aside with tags, stale-while-revalidate, and single-flight de-duplication. Use it for expensive derived work: aggregations, external-API enrichment, rendered fragments.
  2. Automatic query caching — add a cache field to a defineQuery and the framework caches the server snapshot and invalidates it automatically when a mutation writes any table the query depends on. Zero manual busting.

The defining idea mirrors the rest of Voltro: the backend is a configuration choice, the code is identical. The same wrap call and the same cache: field run against the in-process map in dev and against Redis (or Valkey / KeyDB / Dragonfly / Upstash) in production — you flip CACHE_BACKEND, not your handlers.

// app.config.ts — memory (default) or redis
export default { type: 'api', name: 'myApi', store: 'postgres', cache: 'redis' }
// any handler — works on every backend
export default async (input: { orgId: string }, ctx) =>
  ctx.cache.wrap(
    `dashboard:${input.orgId}`,
    { ttlMs: 60_000, tags: [`org:${input.orgId}`] },
    async () => expensiveDashboard(ctx, input.orgId),
  )

What it is not

  • Not the reactive query engine. Live useSubscription queries already stay fresh by pushing deltas — caching is for non-live derived work and for sharing a query's initial snapshot across many subscribers/instances.
  • Not a runtime plugin. A cache backend is infrastructure, so it's an app.config + baseline concern (voltro add redis), not a plugins: entry.

Where to go next

  • Backends & engines — memory vs Redis, the five RESP-compatible engines, connection presets, env vars.
  • Low-level wrap — ctx.cache / the Effect Cache service, tags, SWR, single-flight.
  • Query caching — the cache: field, the scope security rule, auto-invalidation.
  • Durable key-value — ctx.kv (durable, never evicted — for state you can't recompute): the full API, TTL, tenant namespacing.
  • Key-value backends — database (durable) vs redis vs memory, the KV_BACKEND selector, the KvStore port, custom backends.
  • Named connections — the shared registry behind the cache / KV / rate limiter / broadcast, the <NAME>_REDIS_URL → REDIS_URL scheme, and per-concern enablement.
  • Enabling Redis — voltro add redis, create-project --cache=redis, the voltro cache CLI, the dashboard panel.

The dashboard cache panel reports process-local counters since startup, even with a shared Redis backend. Missing cache instrumentation returns HTTP 503 instead of synthetic zero values. Read failures show the source error and hide prior measurements. No cache traffic is displayed separately from a measured zero hit rate.