Data cache policies
Configure native data caching, identify eligible reads, choose sharing boundaries, and define freshness or fallback behavior.
Configure a data cache
For a browser or one-process service, create a stable bounded memory cache outside rendering:
import { createDataCache } from "brix/cache";
import { createApiClient } from "brix/contracts";
export const dataCache = createDataCache();
export const catalogClient = createApiClient({
baseUrl: "https://catalog.example.com",
cache: dataCache.public("30s"),
});
Each createDataCache() call gets an isolated process-local LRU store, limited to 1,000 entries by default. Configure server ownership once when other server integrations need the same cache:
import { createMemoryDataCacheStore } from "brix/cache";
import { defineBrixServer } from "brix/server";
import { brix } from "./brix.ts";
export const brixServer = defineBrixServer(brix, {
dataCache: {
namespace: "storefront-production",
store: createMemoryDataCacheStore(),
},
});
export const { dataCache } = brixServer;
Use a stable application-and-environment namespace. Replicas share data only when they use that namespace and a shared store.
Cache eligible reads
const product = await catalogClient
.execute(getProductApiContract, { params: { productId: "123" } })
.unwrap();
Only validated JSON GET responses participate. Mutations, text and streams, validateResponse: false, and cache: false bypass reuse. HTTP failures, malformed JSON, and schema-invalid values are never stored.
Cache-first calls share fresh results and pending loads. Identity includes the complete URL, normalized explicit headers, effective Fetch credentials, redirect policy, scope, policy, version, and tags. Omitted credentials and explicit same-origin share identity; include and omit remain distinct. Brix stores JSON wire data, then validates it for each caller before returning parsed output.
Visible Cache-Control: no-store, no-cache, or private, Set-Cookie, and Vary: * veto storage. Browsers may hide Set-Cookie, so sensitive browser endpoints must send JavaScript-visible Cache-Control: private or no-store.
Choose public or private scope
const shared = dataCache.public("5m");
const account = dataCache.private(session.cacheScope, "1m");
Public scope explicitly permits sharing between callers. Private scope requires a nonsecret user or tenant partition. Partitioning is not authorization: authorize normally, and do not cache sessions, permissions, carts, booking drafts, or revocation-sensitive decisions.
Rotate the scope when identity or permissions change. On logout, reset or unmount user UI, then await dataCache.invalidateAll() before reuse. Browser-managed cookies are invisible to Brix's identity calculation; never rely on a cookie alone to partition private data.
Freshness and fallback
Duration shorthand is cache-first. Stale fallback is explicit:
dataCache.public("10m");
dataCache.public({ ttl: "1h", staleIfError: "1d" });
staleIfError may serve expired data after retries only for network or timeout failures and HTTP 429, 500, 502, 503, or 504. It never hides authorization, not-found, cancellation, decoding, or validation failures.
Use network-first fallback when every call must try the origin:
dataCache.public({ fallbackFor: "1h" });
This mode does not deduplicate origin calls. It serves the last successful cacheable response only after an eligible failure. fallbackFor cannot be mixed with ttl or staleIfError; total retention is capped at 30 days.
Store reads happen before origin work. Read/write errors fail open to the origin, and a successful origin response may still be written after a failed read. Explicit invalidation fails loud. Store operations default to a one-second deadline. Request timeout and cancellation remain caller-specific.