feat: emit Cache-Control headers on cached responses - #201
Merged
Conversation
Closes #175. Opt-in via strategy.cacheControl, off by default: cacheControl: { enabled: false, maxAge: 'config', // 'none' | 'config' | <milliseconds> scope: 'private', staleWhileRevalidate: null, } Keeps the CacheControlResponseMaxAge union from @pinkasey's #96 but flattens its two-level nesting, since only the response direction is implemented. Milliseconds throughout, branded, with the single ms->s conversion in toSeconds(). A seconds-valued field next to maxAge/ttl is the exact ambiguity behind #126. scope defaults to private, and public is downgraded to private on any route with keys.useAuth rather than merely documented as unsafe; resolveUserStrategy warns at boot naming the content type. Emitted on HIT, on an ETag 304, and on a MISS only after isCacheable says the response will be stored. Never on hitpass, a refused response, the coalesced waiter path, or over a Cache-Control the handler already set. Co-Authored-By: Claude <noreply@anthropic.com>
derrickmehaffy
had a problem deploying
to
npm-experimental
August 14, 2026 01:32 — with
GitHub Actions
Failure
derrickmehaffy
had a problem deploying
to
npm-experimental
August 14, 2026 01:42 — with
GitHub Actions
Failure
Contributor
Experimental build
npm install @strapi-community/plugin-rest-cache@0.0.0-experimental.ff1018e35130f7a559a29eec63bdfaf182745178Install by exact version. The The |
This was referenced Aug 14, 2026
Closed
Merged
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #175.
Config
Opt-in, off by default, under
strategy.cacheControl:Keeps the
CacheControlResponseMaxAgeunion from @pinkasey's #96 — that is thepart carrying meaning — but flattens the
CacheControlHeaderConfig.responsenesting, since only the response direction is implemented and a wrapper with one
member would only make everyone type
cacheControl.response.maxAge. Credit isrecorded in the class comment, the docs, and the test header.
Two deliberate calls
Every duration is milliseconds, branded, including the explicit
maxAgenumber and
staleWhileRevalidate. A seconds-valued field sitting next tomaxAge/ttlis the exact ambiguity behind #126, where a configured hourbecame 41.7 days. The single ms→s conversion lives in
toSeconds()inbuildCacheControl.tsand floors to an integer.scopedefaults toprivate, andpublicis downgraded toprivateonany route with
keys.useAuth— enforced, not merely documented. A per-callercache key marked
publicinvites a shared proxy to serve one user's response toanother.
resolveUserStrategyalso warns at boot naming the content type.Where it emits
On a HIT, on an ETag 304, and on a MISS after
isCacheablesays theresponse will be stored. Not on hitpass, not on a refused response, not on the
coalesced-waiter path (we never saw that response's headers, so we cannot claim
it was stored), and never over a
Cache-Controlthe handler already set.Tests
10 new e2e tests in
shared/tests/flag-cache-control.test.js, each seen redfirst. Six failed initially with
Expected: "private, max-age=3600" / Received: undefined.Each guard was then re-reddened against a deliberately naive implementation
(emit straight after
next(), no already-set check, nouseAuthdowngrade):Expected: "HIT" / Received: "MISS"— emittingprivatetoo early made the plugin refuse to store its own responseno-storekeptExpected: "no-store" / Received: "private, max-age=3600"publicwithuseAuthExpected substring: not "public"/Received: "public, max-age=3600"toBeUndefined()/Received: "private, max-age=3600"Full suite on this branch: 22 suites, 126 tests passed.
Not in scope
Cache-Control(the other half of Emit Cache-Control on cached responses #175).The flat config leaves room for a
cacheControl.requestblock.max-agestill varies per route because
'config'reads the route's resolvedmaxAge.headers, so a later HIT carries the configured value. Documented rather than
changed, since storing headers is a bigger change.
🤖 Generated with Claude Code