/** * `Cache-Control`, fail-closed. * * A global `onRequest` hook stamps `private, no-store` on every reply before any * route runs, and a route that wants a CDN or a browser to keep a copy has to * say so out loud with `publicCache()`. The order matters: doing this in * `onSend` "only if the header is missing" would leave error paths, 404s and * anything thrown before the handler with no policy at all, and the one body * that must never be cached is the one that came out of a mistake. * * CONTRACT.md ยง5. */ import type { FastifyInstance, FastifyReply, FastifyRequest } from "fastify"; export function registerCachePolicy(app: FastifyInstance): void { app.addHook("onRequest", async (_req, reply) => { reply.header("cache-control", "private, no-store"); }); } /** * Opt this reply in to shared caching. * * The credential check is not paranoia for its own sake. A route can be * *usually* public and still be reached with a session attached, and a shared * cache that stored that response would serve one viewer's body to the next. If * the request carried anything that could personalise the answer, the fail-closed * default stands. */ export function publicCache(req: FastifyRequest, reply: FastifyReply, seconds: number): void { if (req.headers.authorization !== undefined || req.headers.cookie !== undefined) return; const maxAge = Math.max(0, Math.floor(seconds)); reply.header("cache-control", `public, max-age=${maxAge}`); reply.header("vary", "Origin"); }