Downstream header
GraphQL only A plain HTTP response reaches the client with your origin's Cache-Control unchanged.
The precedence rule decides whether
GraphPilot stores a response in its own cache. Separately, cache.policy.downstream decides
whether GraphPilot writes a Cache-Control header derived from your schema on the response
it sends back to the client, similar to what Apollo Server’s cache-control plugin does.
The setting only ever synthesizes a header for a response your schema’s @cacheControl policy
decided. It never rewrites, removes or replaces a Cache-Control header your origin sent:
- Your origin sent
Cache-Control. The client receives that header unchanged, in every mode,offincluded. Aprivate, max-age=60from your origin reaches the client asprivate, max-age=60. - Your origin sent none, and the schema decided. This is the case the setting is for. With
off, the client gets noCache-Control. Withif_cacheableoralways, GraphPilot writes one from the schema policy, as described below.
These are independent of what GraphPilot stores. It can cache a schema-decided response without telling the client, or a downstream CDN, anything about it, and the header it writes does not by itself change what GraphPilot stores.
Configuration
Section titled “Configuration”[cache.policy]downstream = "off" # "off" | "if_cacheable" | "always"off (default)
Section titled “off (default)”GraphPilot never synthesizes a Cache-Control header. An origin header still passes through
unchanged. This is the default so that a service without the setting keeps sending exactly what
its origin sent.
if_cacheable
Section titled “if_cacheable”GraphPilot writes a Cache-Control header for a schema-decided response only when the schema
policy is actually cacheable, that is, it has a positive maxAge. A Cache-Control header
from your origin is forwarded as it is, as in every mode.
always
Section titled “always”GraphPilot writes a Cache-Control header whenever a schema policy resolved, even when that
policy is not cacheable. If no policy resolved, no header is written, in always mode either:
there is nothing to reflect, so GraphPilot never fabricates a public guess.
A query that selects no field carrying a @cacheControl hint, directly or through its return
type, resolves no schema policy at all. Neither mode writes a header for it. The client then
sees only what your origin sent, if anything.
A query that selects a field your deployed schema does not declare resolves a policy with a
maxAge of 0 and no stale window, however long its other fields could be kept (see a field the
deployed schema does not
declare). if_cacheable
writes no header for it, and always reflects that 0, so the header never advertises a lifetime
GraphPilot itself did not store the response for.
What gets written
Section titled “What gets written”For a response your schema’s policy decided, the header never claims to be more permissive than the policy GraphPilot used for its own storage decision:
| Schema policy | Header value |
|---|---|
| No policy resolved at all | No header written, in any mode |
| Private, or any named scope | Cache-Control: private |
Public, with a vary rule that is not scope_only | Cache-Control: private |
Public, with a known maxAge | Cache- |
Public, with a maxAge and an swr | Cache- |
Public, with no known maxAge | Cache-Control: public |
A stale window is written only beside a max-age, because that is what it qualifies: it says
how long past the fresh window a stale answer stays acceptable, and with no window to be past it
describes nothing. A stated swr: 0 is left off for the same reason, so the token that appears
always means a window somebody asked for.
When the schema policy is private or names a named scope,
GraphPilot writes Cache-Control: private in both non-off modes, with no freshness of any
kind on it, so that a downstream CDN or shared cache does not store a response that is meant for
one user or one group only. A private policy’s own swr still governs GraphPilot’s cache; it is
simply not announced downstream. See who shares an entry for how a
response ends up private in the first place.
The same holds for a public policy while any [[cache.policy.vary]] entry that is not
scope_only is configured. Such an entry splits the edge’s entry by a value GraphPilot derives
itself, so no Vary header could tell a downstream cache about the split, and the response is
announced as private. An entry marked scope_only partitions only named scopes and leaves a
public policy public.