{"_id":"@canner-ca/astro-cache","_rev":"2-c7387a7a47f9d5913196d802dd55cd30","name":"@canner-ca/astro-cache","dist-tags":{"latest":"0.2.0"},"versions":{"0.1.0":{"name":"@canner-ca/astro-cache","version":"0.1.0","keywords":["canner","astro","cache","caching","datocms","surrogate-key","cache-invalidation","cdn","ssr","canadian","hosting"],"author":{"name":"Canner"},"license":"MIT","_id":"@canner-ca/astro-cache@0.1.0","maintainers":[{"name":"gocanner","email":"colinh.shand@gmail.com"}],"homepage":"https://canner.ca/astro-cache","bugs":{"url":"https://github.com/tzshand/canner/issues"},"dist":{"shasum":"761b9ac742237793154b858418b813fa65bc42fe","tarball":"https://registry.npmjs.org/@canner-ca/astro-cache/-/astro-cache-0.1.0.tgz","fileCount":4,"integrity":"sha512-2t1RVUxeiGvanopNhCmQaalehwmQq5nGxTK9+BkaQMxTClMq5KcUHCt4XNaVT7fhC4cW2miGzE8+ZiF7oO0fPw==","signatures":[{"sig":"MEQCIEcSZp7Iigiovp1UUuEgvk5lXfJGsrIwzPCYHa6vqNwDAiB8BfjHfiG+VIEdQxXratSrIAbfmzNydk64ewEsA3obnw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":11429},"main":"./src/index.mjs","type":"module","types":"./src/index.d.ts","engines":{"node":">=20"},"exports":{".":{"types":"./src/index.d.ts","import":"./src/index.mjs"}},"gitHead":"3b23a37916660d3cd5929c12463227a4819c309d","scripts":{"test":"node --test","prepublishOnly":"node --test"},"_npmUser":{"name":"gocanner","email":"colinh.shand@gmail.com"},"repository":{"url":"git+https://github.com/tzshand/canner.git","type":"git","directory":"tools/astro-cache"},"_npmVersion":"10.9.2","description":"One-line cache headers for Canner — set Cache-Control + Surrogate-Key correctly so Canner caches and purges your pages by tag.","directories":{},"sideEffects":false,"_nodeVersion":"22.16.0","_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/astro-cache_0.1.0_1780266613757_0.03922074965062983","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@canner-ca/astro-cache","version":"0.2.0","description":"Canner caching for Astro — a native Astro 7 cache provider, draft mode, and one-line cache headers (Cache-Control + Surrogate-Key) for tag- and path-based purging.","type":"module","exports":{".":{"types":"./src/index.d.ts","import":"./src/index.mjs"},"./provider":{"types":"./src/provider.d.ts","import":"./src/provider.mjs"}},"main":"./src/index.mjs","types":"./src/index.d.ts","sideEffects":false,"scripts":{"test":"node --test","prepublishOnly":"node --test"},"engines":{"node":">=20"},"keywords":["canner","astro","astro-cache-provider","route-caching","cache","caching","draft-mode","stale-while-revalidate","surrogate-key","cache-invalidation","cdn","ssr","canadian","hosting"],"author":{"name":"Canner"},"license":"MIT","homepage":"https://canner.ca/astro-cache","repository":{"type":"git","url":"git+https://github.com/tzshand/canner.git","directory":"tools/astro-cache"},"bugs":{"url":"https://github.com/tzshand/canner/issues"},"_id":"@canner-ca/astro-cache@0.2.0","gitHead":"5e49f2106061c8e423d238484e566d6f4f7ab8b5","_nodeVersion":"22.16.0","_npmVersion":"10.9.2","dist":{"integrity":"sha512-aS8xHqGhlt1p1t5S9rRtiVsVAqKo2GAlvojvplR/gkLTOpmlQpeBlbXGxX8cT58aLxBJ6plcV6tbSg+oawq+gQ==","shasum":"1139e41cf7c2dd1a9ea9caab081b483023ce72cf","tarball":"https://registry.npmjs.org/@canner-ca/astro-cache/-/astro-cache-0.2.0.tgz","fileCount":6,"unpackedSize":27658,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDk4DsdNUxV/IWRQBCje9oZQPQhP/kXkLKyMQ5HD6noxwIhAKtyi9UuedHjkfKVw55ZyDmmuSBiDTUuUGXUTcdZhaXf"}]},"_npmUser":{"name":"gocanner","email":"colinh.shand@gmail.com"},"directories":{},"maintainers":[{"name":"gocanner","email":"colinh.shand@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/astro-cache_0.2.0_1784312813852_0.5178084816465913"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-31T22:30:13.630Z","modified":"2026-07-17T18:26:54.118Z","0.1.0":"2026-05-31T22:30:13.890Z","0.2.0":"2026-07-17T18:26:53.981Z"},"bugs":{"url":"https://github.com/tzshand/canner/issues"},"author":{"name":"Canner"},"license":"MIT","homepage":"https://canner.ca/astro-cache","keywords":["canner","astro","astro-cache-provider","route-caching","cache","caching","draft-mode","stale-while-revalidate","surrogate-key","cache-invalidation","cdn","ssr","canadian","hosting"],"repository":{"type":"git","url":"git+https://github.com/tzshand/canner.git","directory":"tools/astro-cache"},"description":"Canner caching for Astro — a native Astro 7 cache provider, draft mode, and one-line cache headers (Cache-Control + Surrogate-Key) for tag- and path-based purging.","maintainers":[{"name":"gocanner","email":"colinh.shand@gmail.com"}],"readme":"# @canner-ca/astro-cache\n\nCanner caching for Astro. Three things in one zero-dependency package:\n\n1. **A native Astro 7 cache provider** — `cacheCanner()` plugs Canner into Astro's\n   built-in [route caching](https://docs.astro.build/en/guides/caching/), the same\n   API you'd use on Netlify/Vercel/Cloudflare.\n2. **Draft mode** — let editors preview unpublished content on the real URL without\n   purging what everyone else sees.\n3. **One-line cache headers** — `cache(Astro.response, …)` sets `Cache-Control` +\n   `Surrogate-Key` correctly if you'd rather do it per route.\n\nWorks with any CMS (DatoCMS, Contentful, Sanity, Storyblak, …) — tags are just strings.\n\n## Install\n\n```bash\nnpm install @canner-ca/astro-cache\n```\n\nRequires Node.js 20 or later. Zero runtime dependencies.\n\n## Option A — the cache provider (Astro 7+)\n\n```js\n// astro.config.mjs\nimport { defineConfig } from 'astro/config';\nimport node from '@astrojs/node';\nimport { cacheCanner } from '@canner-ca/astro-cache';\n\nexport default defineConfig({\n  adapter: node({ mode: 'standalone' }),\n  cache: {\n    provider: cacheCanner({\n      slug: 'my-project',\n      token: process.env.CANNER_CACHE_TOKEN, // only for invalidate()\n    }),\n  },\n});\n```\n\n```js\n---\n// src/pages/blog/[slug].astro — control caching per route\nconst post = await getPost(Astro.params.slug);\nAstro.cache.set({ maxAge: 3600, swr: 86400, tags: [post.id, 'blog-listing'] });\n---\n```\n\nCanner caches at its proxy (in front of your app, shared across instances), so the\nprovider never spends your app's memory on an in-process cache — and `invalidate()`\npurges by tag or path through the same token.\n\n## Option B — one-line headers (any Astro version)\n\n```js\n---\n// src/pages/blog/[slug].astro\nimport { cache } from '@canner-ca/astro-cache';\n\nconst post = await getPost(Astro.params.slug);\n// Cache 1h; keep serving up to a day past that while refreshing in the background.\ncache(Astro.response, { ttl: 3600, swr: 86400, tags: [post.id, 'blog-listing'] });\n---\n```\n\nThat sets:\n\n```\nCache-Control: public, s-maxage=3600, stale-while-revalidate=86400\nSurrogate-Key: <post.id> blog-listing\n```\n\nWhen the post changes, your CMS webhook purges tag `<post.id>` and Canner clears every\npage carrying it. Add `blog-listing` to your index and to the purge to clear it too.\n\n## Draft mode\n\nPreview unpublished content on its real URL, without purging the published page:\n\n```js\n// src/pages/api/draft.ts\nimport { enableDraft } from '@canner-ca/astro-cache';\n\nexport const GET = ({ url, cookies, redirect }) => {\n  if (url.searchParams.get('secret') !== import.meta.env.PREVIEW_SECRET) {\n    return new Response('Unauthorized', { status: 401 });\n  }\n  enableDraft(cookies, import.meta.env.CANNER_BYPASS_SECRET); // secret: Caching tab\n  return redirect(url.searchParams.get('to') ?? '/');\n};\n```\n\n```js\n---\n// in your page, fetch draft content when the visitor is in draft mode\nimport { isDraft } from '@canner-ca/astro-cache';\nconst post = await getPost(Astro.params.slug, { draft: isDraft(Astro.request) });\n---\n```\n\n`enableDraft` sets a hardened `__canner_bypass` cookie carrying your bypass secret;\nCanner then renders that visitor's requests fresh from origin while everyone else keeps\ngetting the cached page. `disableDraft(cookies)` turns it back off. You can also bypass\nwith a `?__canner_bypass=<secret>` link or an `X-Canner-Bypass: <secret>` header.\n\nFull guide (dashboard token, CMS webhook, bypass secret): **https://canner.ca/docs/caching**\n\n## API\n\n### `cacheCanner(config)`\nReturns the object Astro's `cache.provider` expects. `config`: `{ slug?, token?, endpoint?, fetch? }`\n(`slug` + `token` are required for `invalidate()`).\n\n### `enableDraft(cookies, secret)` / `disableDraft(cookies)` / `isDraft(request)`\nDraft-mode helpers. `cookies` is Astro's `cookies` object; `secret` is your project's\nbypass secret (Caching tab). `isDraft` takes the `Request` and returns a boolean.\n\n### `cache(target, options)`\n`target` is a `Response`, `Astro.response`, or a `Headers`. Returns the `Headers`.\n\n### `applyCacheHeaders(headers, options)`\nSame thing, lower-level, when you already hold a `Headers` instance.\n\n### `options`\n\n| Option | Type | Notes |\n|---|---|---|\n| `ttl` | `number` (required) | Seconds Canner may serve the cached response. Sets `s-maxage`. Positive integer. |\n| `swr` | `number` | Optional. Stale-while-revalidate window in seconds. Past `ttl`, Canner serves the stale copy instantly and refreshes once in the background. Sets `stale-while-revalidate`. |\n| `tags` | `string \\| number \\| Array<string \\| number>` | Tags for purging. Sets `Surrogate-Key`. Numbers are coerced to strings; duplicates and whitespace-containing tags are dropped. |\n| `browserTtl` | `number` | Optional. Seconds the visitor's **browser** may cache (sets `max-age`). Omit to let the browser revalidate while Canner serves from cache — keeps purges instant. |\n\n(The provider's `Astro.cache.set()` uses `maxAge`/`swr`/`tags` — Astro's own names.)\n\n## What it caches (and what it won't)\n\nA response is cached by Canner only when **all** of these hold — the same rules\nthis helper produces:\n\n- method `GET`/`HEAD`, status `200`\n- `Cache-Control: public` with a positive `s-maxage` (or `max-age`)\n- no `Set-Cookie`\n- `Vary` absent or only `Accept-Encoding`\n- body under 8 MB\n\n## It only ever *adds* headers\n\nThis package never strips or mutates anything your app set. In particular it\ndoes **not** remove `Set-Cookie`: Canner already declines to cache a response\nthat sets a cookie, so the only safe thing to do is tell you. If you call\n`cache()` on a response that sets a cookie, you'll get a **development-only\nwarning** explaining it won't be cached — your cookie is left untouched.\n\nInvalid input degrades gracefully too: a bad `ttl` sets no cache headers (the\npage still renders, just uncached) and warns in dev. Only passing something\nthat isn't a `Response`/`Headers` throws.\n\n## Non-goals\n\n- It doesn't configure your CMS webhook — that's two copy-paste values in the\n  Canner dashboard (**Settings → Caching**).\n- The provider deliberately doesn't implement Astro's `onRequest` (in-process\n  runtime caching): Canner caches out-of-process at its proxy, so a second in-app\n  cache would just burn your app's memory duplicating it.\n\n## License\n\nMIT\n","readmeFilename":"README.md"}