Skip to content

Commit ee25616

Browse files
author
Taz Singh
committed
Use s-maxage for staleAt calculation (to start revalidation)
1 parent 109b35b commit ee25616

File tree

2 files changed

+69
-36
lines changed

2 files changed

+69
-36
lines changed

src/workerSwr.ts

Lines changed: 44 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,13 @@ export const workerSwr = ({
6161

6262
const processResponse = (response: Response) => {
6363
const upstreamCacheControl = response.headers.get("cache-control");
64+
65+
// Parse s-maxage for staleAt calculation (when content becomes stale)
66+
// Parse stale-while-revalidate and stale-if-error for the stale serving windows
67+
let sMaxage: number | undefined;
68+
let staleWhileRevalidate: number | undefined;
69+
let staleIfError: number | undefined;
70+
6471
if (upstreamCacheControl) {
6572
const directives = upstreamCacheControl
6673
.split(",")
@@ -69,18 +76,16 @@ export const workerSwr = ({
6976
for (const directive of directives) {
7077
const [name, value] = directive.split("=", 2);
7178

79+
if (name === "s-maxage" && value) {
80+
sMaxage = parseInt(value, 10);
81+
}
82+
7283
if (name === "stale-while-revalidate" && value) {
73-
response.headers.set(
74-
swrConfig.staleAtHeaderName,
75-
(Date.now() + parseInt(value, 10) * 1000).toString()
76-
);
84+
staleWhileRevalidate = parseInt(value, 10);
7785
}
7886

7987
if (name === "stale-if-error" && value) {
80-
response.headers.set(
81-
swrConfig.staleErrorAtHeaderName,
82-
(Date.now() + parseInt(value, 10) * 1000).toString()
83-
);
88+
staleIfError = parseInt(value, 10);
8489
}
8590
}
8691
}
@@ -102,22 +107,43 @@ export const workerSwr = ({
102107
);
103108
}
104109

105-
if (name === "stale-while-revalidate") {
106-
response.headers.set(
107-
swrConfig.staleAtHeaderName,
108-
(Date.now() + parseInt(value, 10) * 1000).toString()
109-
);
110+
// Also parse from config directives
111+
if (name === "s-maxage" && value && sMaxage === undefined) {
112+
sMaxage = parseInt(value, 10);
110113
}
111114

112-
if (name === "stale-if-error") {
113-
response.headers.set(
114-
swrConfig.staleErrorAtHeaderName,
115-
(Date.now() + parseInt(value, 10) * 1000).toString()
116-
);
115+
if (name === "stale-while-revalidate" && value && staleWhileRevalidate === undefined) {
116+
staleWhileRevalidate = parseInt(value, 10);
117+
}
118+
119+
if (name === "stale-if-error" && value && staleIfError === undefined) {
120+
staleIfError = parseInt(value, 10);
117121
}
118122
}
119123
}
120124

125+
// Set staleAt based on s-maxage (when content becomes "stale" and revalidation should start)
126+
// If no s-maxage, fall back to stale-while-revalidate for backwards compatibility
127+
// If neither, use defaultStaleSeconds
128+
const staleAtSeconds = sMaxage ?? staleWhileRevalidate ?? swrConfig.defaultStaleSeconds;
129+
response.headers.set(
130+
swrConfig.staleAtHeaderName,
131+
(Date.now() + staleAtSeconds * 1000).toString()
132+
);
133+
134+
// Set staleErrorAt based on stale-if-error (if present)
135+
if (staleIfError !== undefined) {
136+
response.headers.set(
137+
swrConfig.staleErrorAtHeaderName,
138+
(Date.now() + staleIfError * 1000).toString()
139+
);
140+
} else if (swrConfig.defaultStaleErrorSeconds) {
141+
response.headers.set(
142+
swrConfig.staleErrorAtHeaderName,
143+
(Date.now() + swrConfig.defaultStaleErrorSeconds * 1000).toString()
144+
);
145+
}
146+
121147
if (varyDirectives) {
122148
const existingVary =
123149
response.headers
@@ -137,23 +163,6 @@ export const workerSwr = ({
137163
response.headers.set("vary", vary.join(", "));
138164
}
139165
}
140-
141-
if (!response.headers.has(swrConfig.staleAtHeaderName)) {
142-
response.headers.set(
143-
swrConfig.staleAtHeaderName,
144-
(Date.now() + swrConfig.defaultStaleSeconds * 1000).toString()
145-
);
146-
}
147-
148-
if (
149-
!response.headers.has(swrConfig.staleErrorAtHeaderName) &&
150-
swrConfig.defaultStaleErrorSeconds
151-
) {
152-
response.headers.set(
153-
swrConfig.staleErrorAtHeaderName,
154-
(Date.now() + swrConfig.defaultStaleErrorSeconds * 1000).toString()
155-
);
156-
}
157166
};
158167

159168
return async (request: Request, env: any, ctx: ExecutionContext) => {

test/workerSwr.test.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -497,7 +497,30 @@ describe("workerSwr", () => {
497497
expect(diff).toBeLessThan(110000);
498498
});
499499

500-
it("respects upstream stale-while-revalidate header", async () => {
500+
it("uses s-maxage for staleAt calculation (when content becomes stale)", async () => {
501+
const swrFetcher = workerSwr({
502+
fetcher: async () =>
503+
new Response("ok", {
504+
headers: { "cache-control": "s-maxage=10, stale-while-revalidate=3600" },
505+
}),
506+
});
507+
508+
await swrFetcher(
509+
new Request("https://example.com"),
510+
{},
511+
executionContext
512+
);
513+
514+
const cachedResponse = (cachePut as Mock).mock.calls[0][1] as Response;
515+
const staleAt = cachedResponse.headers.get("x-edge-cache-stale-at");
516+
517+
// Should be roughly 10 seconds from now (s-maxage), not 3600 (stale-while-revalidate)
518+
const diff = parseInt(staleAt!) - Date.now();
519+
expect(diff).toBeGreaterThan(9000);
520+
expect(diff).toBeLessThan(11000);
521+
});
522+
523+
it("falls back to stale-while-revalidate for staleAt if no s-maxage", async () => {
501524
const swrFetcher = workerSwr({
502525
fetcher: async () =>
503526
new Response("ok", {
@@ -514,6 +537,7 @@ describe("workerSwr", () => {
514537
const cachedResponse = (cachePut as Mock).mock.calls[0][1] as Response;
515538
const staleAt = cachedResponse.headers.get("x-edge-cache-stale-at");
516539

540+
// Falls back to stale-while-revalidate when no s-maxage
517541
const diff = parseInt(staleAt!) - Date.now();
518542
expect(diff).toBeGreaterThan(9000);
519543
expect(diff).toBeLessThan(11000);

0 commit comments

Comments
 (0)