---
title: "Cache safely"
description: "Memoise slow calls per tenant, share a public read action with a CDN, and see why a signed-in response is never shared."
section: "guides"
status: "Building for 0.4"
spec: "http-cache"
canonical: "https://mainsdk.dev/docs/guides/cache-safely"
updated: "2026-10-28"
---

# 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.

Status: Building for 0.4 · Canonical: https://mainsdk.dev/docs/guides/cache-safely · 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

```yaml title="main.yaml"
cache:
  driver: kv
  timeout: 50ms
```

```ts title="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

```ts title="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](https://mainsdk.dev/docs/interfaces/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

- [Cache](https://mainsdk.dev/docs/interfaces/cache)
- [Key-value store](https://mainsdk.dev/docs/interfaces/kv)
- [Protect web responses](https://mainsdk.dev/docs/guides/sanitize-and-protect-inbound)
