MainSDK
Search⌘K
How-to

Cache safely

Memoise slow calls per tenant, share a public read action with a CDN, and see why a signed-in response is never shared.

Building for 0.4

Status Status is where the work is: Planned (in a milestone, not started), Building (tickets in progress), Preview (usable, may change), then the stability it ships at.

Stability The promise an interface ships with.

  • Stable Follows semantic versioning; breaking changes need a new major, 12 months of overlap and a codemod.
  • Beta Usable in production; may change before it becomes stable.
  • Experimental May change or go away; needs an explicit opt-in in main.yaml.
Stability levels
Updated 2026-10-28

You will memoise a slow query per tenant, declare one read action as shareable, and check that nothing personal can reach a shared cache. It takes about 10 minutes and needs no account.

You need

  • A project with main.yaml and an adapter (mainsdk/hono, mainsdk/next or mainsdk/express).

1. Memoise a slow call

main.yaml
cache:
  driver: kv
  timeout: 50ms
lib/stats.ts
import { cache } from "mainsdk/cache";

declare function loadStats(): Promise<{ orders: number }>;   // your slow query

export const stats = () =>
  cache.wrap("dashboard-stats", () => loadStats(), { ttl: "1m", swr: "10m", tags: ["stats"] });

You should see loadStats run once per tenant a minute, and once more in the background after that while callers keep the old value. The key is stored as t:<tenant>:r:<region>:dashboard-stats, so no tenant reads another's.

2. Drop it when the data changes

lib/orders.ts
import { cache } from "mainsdk/cache";

await cache.delete({ tag: "stats" });   // this tenant only, on every instance

3. Share one read action

Declare http.cache on a plain GET read action and serve it with serveRead (see Cache). Nothing else makes a response shareable: a header you set by hand on a signed-in response is replaced with private, no-store and logged as MAIN-CACHE-0001.

4. Check it

bash
pnpm mainsdk check

MAIN-CACHE-0011 names a public declaration on a write, a non-GET or a permissioned action; MAIN-CACHE-0012 names an app-scoped cache around a db read.

Ask your agent

text
Goal: speed up the products page without leaking one tenant's data to another.
Touch: main.yaml (cache section), the products lib, one read action, tests with the fake clock.
Rules: cache.wrap with the default tenant scope; scope "app" only for values with no person or tenant data; never set Cache-Control by hand on a signed-in response.
Verify: pnpm mainsdk check && pnpm test
Never weaken or skip a check; fix the finding it names.

Next

Was this page helpful?

↑↓ moveEnter openEsc close

Not decided yet