@void/isr 0.26.0 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -3
- package/dist/core.d.mts +6 -0
- package/dist/core.mjs +13 -1
- package/dist/index.d.mts +1 -9
- package/dist/index.mjs +8 -29
- package/dist/runtime.d.mts +4 -3
- package/dist/runtime.mjs +45 -32
- package/package.json +10 -4
package/README.md
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
# @void/isr
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Incremental revalidation helpers for [Void](https://void.cloud) runtimes and deployment adapters. Applications use `void/isr` to revalidate pages.
|
|
4
4
|
|
|
5
|
-
For runtime authors,
|
|
5
|
+
For runtime authors, this package provides three entry points:
|
|
6
6
|
|
|
7
7
|
- `@void/isr`: versioned cache keys and edge-cache URLs
|
|
8
8
|
- `@void/isr/core`: codecs, cache policy, TTL selection, and response helpers
|
|
9
9
|
- `@void/isr/runtime`: the direct Cloudflare KV and Cache API runtime
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Cache writes and purges must use the same key helpers. Incompatible format changes require a new cache-key version.
|
|
12
|
+
|
|
13
|
+
## Documentation
|
|
14
|
+
|
|
15
|
+
See [Revalidation](https://void.cloud/guide/edge/revalidation) for application usage and [Platform Development](https://void.cloud/guide/platform-development) for building a runtime or adapter.
|
|
16
|
+
|
|
17
|
+
Read the [Void documentation](https://void.cloud) online or use the Markdown guides and references in `node_modules/void/docs/`, included with the `void` package.
|
package/dist/core.d.mts
CHANGED
|
@@ -34,6 +34,12 @@ export type QueryAllowlist = Record<string, ReadonlyArray<string>>;
|
|
|
34
34
|
* explicitly allowlisted for its user-facing pathname.
|
|
35
35
|
*/
|
|
36
36
|
export declare function buildVariantOriginalUrl(requestUrl: string, pathname: string, queryAllowlist: QueryAllowlist | undefined): string;
|
|
37
|
+
/**
|
|
38
|
+
* True when the request carries a query parameter that the cache key leaves
|
|
39
|
+
* out. A render made from such a URL may depend on that parameter, so it must
|
|
40
|
+
* not be stored under the shared key.
|
|
41
|
+
*/
|
|
42
|
+
export declare function hasUnlistedQueryParams(requestUrl: string, pathname: string, allowlist: QueryAllowlist | undefined): boolean;
|
|
37
43
|
/** Keep only exact allowlist patterns for uncapped static-cache consumers. */
|
|
38
44
|
export declare function exactQueryAllowlist(allowlist: QueryAllowlist | undefined): QueryAllowlist | undefined;
|
|
39
45
|
/** Pack response headers and body into the canonical ISR binary value. */
|
package/dist/core.mjs
CHANGED
|
@@ -41,6 +41,18 @@ function buildVariantOriginalUrl(requestUrl, pathname, queryAllowlist) {
|
|
|
41
41
|
url.search = filteredString ? `?${filteredString}` : "";
|
|
42
42
|
return url.toString();
|
|
43
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* True when the request carries a query parameter that the cache key leaves
|
|
46
|
+
* out. A render made from such a URL may depend on that parameter, so it must
|
|
47
|
+
* not be stored under the shared key.
|
|
48
|
+
*/
|
|
49
|
+
function hasUnlistedQueryParams(requestUrl, pathname, allowlist) {
|
|
50
|
+
const url = new URL(requestUrl);
|
|
51
|
+
if (!url.search) return false;
|
|
52
|
+
const allowed = resolveQueryAllowlist(allowlist, pathname);
|
|
53
|
+
for (const key of url.searchParams.keys()) if (!allowed.has(key)) return true;
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
44
56
|
/** Keep only exact allowlist patterns for uncapped static-cache consumers. */
|
|
45
57
|
function exactQueryAllowlist(allowlist) {
|
|
46
58
|
if (!allowlist) return;
|
|
@@ -213,4 +225,4 @@ function extractPageDataFromHtml(html) {
|
|
|
213
225
|
return match ? normalizePageDataForJsonCache(match[2]) : null;
|
|
214
226
|
}
|
|
215
227
|
//#endregion
|
|
216
|
-
export { SHARED_CACHE_POLICY_HEADER, SHARED_CACHE_POLICY_VERSION, buildVariantOriginalUrl, decodeIsrCacheEntry, exactQueryAllowlist, extractPageDataFromHtml, hasCurrentSharedCachePolicy, hasSharedCacheOptOut, isFrameworkDocumentRequest, isSharedCacheableResponse, isStale, isrCacheResponse, markSharedCachePolicy, normalizePageDataForJsonCache, packIsrValue, resolvePageTtl, resolvePathTtl, resolveRevalidateTtl, serializeHeaders, shouldBypassIsr, unpackIsrValue };
|
|
228
|
+
export { SHARED_CACHE_POLICY_HEADER, SHARED_CACHE_POLICY_VERSION, buildVariantOriginalUrl, decodeIsrCacheEntry, exactQueryAllowlist, extractPageDataFromHtml, hasCurrentSharedCachePolicy, hasSharedCacheOptOut, hasUnlistedQueryParams, isFrameworkDocumentRequest, isSharedCacheableResponse, isStale, isrCacheResponse, markSharedCachePolicy, normalizePageDataForJsonCache, packIsrValue, resolvePageTtl, resolvePathTtl, resolveRevalidateTtl, serializeHeaders, shouldBypassIsr, unpackIsrValue };
|
package/dist/index.d.mts
CHANGED
|
@@ -50,21 +50,13 @@ export declare function isrLegacyCachePrefix(projectId: string, deploymentId?: s
|
|
|
50
50
|
*
|
|
51
51
|
* Host-scoping is a `v3` key format: `<proj>/isr/v3/<D>/<host>/<path>`. The
|
|
52
52
|
* version bump is load-bearing, not cosmetic — see `ISR_LEGACY_CACHE_KEY_VERSION`.
|
|
53
|
-
* Under a single version, this key would be byte-identical to a legacy hostless
|
|
54
|
-
* key for a page whose leading path segment looks like a hostname (e.g.
|
|
55
|
-
* `(host=example.com, /about)` vs the hostless page `/example.com/about`, both
|
|
56
|
-
* `<D>/example.com/about`). Dotted leading segments are legal, so the collision
|
|
57
|
-
* is reachable. `v3` keys are ALWAYS host-scoped and `v2` keys ALWAYS hostless,
|
|
58
|
-
* so the version segment alone disambiguates the two shapes.
|
|
59
53
|
*
|
|
60
54
|
* When `originalUrl` is provided, the request was served via a dispatch-side
|
|
61
55
|
* rewrite (`redirectRules`/`fallbackRules` with status 200). The original
|
|
62
56
|
* request URL is mixed into the key so a direct hit to `/en/docs` and a
|
|
63
57
|
* rewrite from `/docs` to `/en/docs` do not collide — user code can branch
|
|
64
58
|
* on `c.isRewritten()` / `c.originalUrl()`, and the cache must honor that.
|
|
65
|
-
* A missing `originalUrl` means "direct hit" and produces the
|
|
66
|
-
* rewriteless key (backward-compatible with entries written before this
|
|
67
|
-
* feature landed).
|
|
59
|
+
* A missing `originalUrl` means "direct hit" and produces the unsuffixed key.
|
|
68
60
|
*/
|
|
69
61
|
export declare function isrCacheKey(projectId: string, deploymentId: string, host: string, pathname: string, originalUrl?: string | null): string;
|
|
70
62
|
/**
|
package/dist/index.mjs
CHANGED
|
@@ -57,19 +57,9 @@ function denormalizeIsrPath(normalized) {
|
|
|
57
57
|
function canonicalIsrPathname(pathname) {
|
|
58
58
|
return denormalizeIsrPath(normalizeIsrPath(pathname));
|
|
59
59
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
*/
|
|
64
|
-
function isrCacheVersionPrefix(projectId) {
|
|
65
|
-
return `${projectId}/isr/${ISR_CACHE_KEY_VERSION}/`;
|
|
66
|
-
}
|
|
67
|
-
/**
|
|
68
|
-
* Prefix shared by every LEGACY (hostless) ISR key for a project —
|
|
69
|
-
* `<projectId>/isr/v2/`. Used internally by `isrLegacyCachePrefix`.
|
|
70
|
-
*/
|
|
71
|
-
function isrLegacyCacheVersionPrefix(projectId) {
|
|
72
|
-
return `${projectId}/isr/${ISR_LEGACY_CACHE_KEY_VERSION}/`;
|
|
60
|
+
function versionedIsrCachePrefix(projectId, version, deploymentId) {
|
|
61
|
+
if (deploymentId !== void 0) return `${projectId}/isr/${version}/${deploymentId}/`;
|
|
62
|
+
return `${projectId}/isr/`;
|
|
73
63
|
}
|
|
74
64
|
/**
|
|
75
65
|
* Prefix for listing/purging ISR keys.
|
|
@@ -81,8 +71,7 @@ function isrLegacyCacheVersionPrefix(projectId) {
|
|
|
81
71
|
* legacy scan-all prefix (`<projectId>/isr/`).
|
|
82
72
|
*/
|
|
83
73
|
function isrCachePrefix(projectId, deploymentId) {
|
|
84
|
-
|
|
85
|
-
return `${projectId}/isr/`;
|
|
74
|
+
return versionedIsrCachePrefix(projectId, ISR_CACHE_KEY_VERSION, deploymentId);
|
|
86
75
|
}
|
|
87
76
|
/**
|
|
88
77
|
* Prefix for listing/purging LEGACY (pre-host-scoping) ISR keys — the `v2`
|
|
@@ -97,8 +86,7 @@ function isrCachePrefix(projectId, deploymentId) {
|
|
|
97
86
|
* entries have aged out.
|
|
98
87
|
*/
|
|
99
88
|
function isrLegacyCachePrefix(projectId, deploymentId) {
|
|
100
|
-
|
|
101
|
-
return `${projectId}/isr/`;
|
|
89
|
+
return versionedIsrCachePrefix(projectId, ISR_LEGACY_CACHE_KEY_VERSION, deploymentId);
|
|
102
90
|
}
|
|
103
91
|
/**
|
|
104
92
|
* Separator that marks the rewrite-variant suffix on an ISR cache key or
|
|
@@ -119,21 +107,13 @@ const ISR_VARIANT_SEPARATOR = "#rw=";
|
|
|
119
107
|
*
|
|
120
108
|
* Host-scoping is a `v3` key format: `<proj>/isr/v3/<D>/<host>/<path>`. The
|
|
121
109
|
* version bump is load-bearing, not cosmetic — see `ISR_LEGACY_CACHE_KEY_VERSION`.
|
|
122
|
-
* Under a single version, this key would be byte-identical to a legacy hostless
|
|
123
|
-
* key for a page whose leading path segment looks like a hostname (e.g.
|
|
124
|
-
* `(host=example.com, /about)` vs the hostless page `/example.com/about`, both
|
|
125
|
-
* `<D>/example.com/about`). Dotted leading segments are legal, so the collision
|
|
126
|
-
* is reachable. `v3` keys are ALWAYS host-scoped and `v2` keys ALWAYS hostless,
|
|
127
|
-
* so the version segment alone disambiguates the two shapes.
|
|
128
110
|
*
|
|
129
111
|
* When `originalUrl` is provided, the request was served via a dispatch-side
|
|
130
112
|
* rewrite (`redirectRules`/`fallbackRules` with status 200). The original
|
|
131
113
|
* request URL is mixed into the key so a direct hit to `/en/docs` and a
|
|
132
114
|
* rewrite from `/docs` to `/en/docs` do not collide — user code can branch
|
|
133
115
|
* on `c.isRewritten()` / `c.originalUrl()`, and the cache must honor that.
|
|
134
|
-
* A missing `originalUrl` means "direct hit" and produces the
|
|
135
|
-
* rewriteless key (backward-compatible with entries written before this
|
|
136
|
-
* feature landed).
|
|
116
|
+
* A missing `originalUrl` means "direct hit" and produces the unsuffixed key.
|
|
137
117
|
*/
|
|
138
118
|
function isrCacheKey(projectId, deploymentId, host, pathname, originalUrl) {
|
|
139
119
|
const base = `${isrCachePrefix(projectId, deploymentId)}${host}/${normalizeIsrPath(pathname)}`;
|
|
@@ -189,9 +169,8 @@ function isrLegacyCacheVariantPrefix(projectId, deploymentId, pathname) {
|
|
|
189
169
|
* - Sync (Workers `crypto.subtle.digest` is async; we don't want to push
|
|
190
170
|
* async through every read/write path for this).
|
|
191
171
|
* - 16 hex chars (64 bits) — bounds the KV key length regardless of input.
|
|
192
|
-
* - Non-recoverable: KV is operator-readable,
|
|
193
|
-
*
|
|
194
|
-
* (including sensitive query strings like `?token=…`, `?email=…`).
|
|
172
|
+
* - Non-recoverable: KV is operator-readable, so the token must not reveal
|
|
173
|
+
* original URLs (including sensitive query strings like `?token=…`).
|
|
195
174
|
* - Not cryptographic: collision resistance is ~2^32 (birthday). Fine for
|
|
196
175
|
* the per-deployment, per-pathname variant set we ever store in one slot.
|
|
197
176
|
*
|
package/dist/runtime.d.mts
CHANGED
|
@@ -15,7 +15,7 @@ import { matchSegmentPattern } from "@void/edge";
|
|
|
15
15
|
* - No `projectId`, no cross-tenant metering.
|
|
16
16
|
* - No `#rw=` rewrite variant, no query-string variant fanout, no variant cap.
|
|
17
17
|
* - The deployment id is baked at build time as a compile-time literal and
|
|
18
|
-
* surfaced on `env.__VOID_ISR_DEPLOYMENT_ID
|
|
18
|
+
* surfaced on `env.__VOID_ISR_DEPLOYMENT_ID`. The KV helpers read
|
|
19
19
|
* it from `env` (matching the `revalidate()` purge path in `runtime/isr.ts`),
|
|
20
20
|
* while `handleIsr` uses the `deploymentId` option for the edge-cache keys —
|
|
21
21
|
* the two are the same value.
|
|
@@ -127,8 +127,9 @@ export declare function handleFrameworkIsr(request: Request, env: IsrCacheEnv, c
|
|
|
127
127
|
* Run the ISR caching ladder for a request.
|
|
128
128
|
*
|
|
129
129
|
* Returns a cached/rendered `Response` when ISR applies, or `null` when the
|
|
130
|
-
* caller should render live itself (request not ISR-eligible,
|
|
131
|
-
* cache op failed open
|
|
130
|
+
* caller should render live itself (request not ISR-eligible, a read/hit
|
|
131
|
+
* cache op failed open, or a cache miss for a URL carrying query params
|
|
132
|
+
* outside the allowlist).
|
|
132
133
|
*
|
|
133
134
|
* Ladder:
|
|
134
135
|
* - Bail (`null`) if not GET, an API path, a Range request, `shouldBypassIsr`,
|
package/dist/runtime.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { canonicalIsrPathname, denormalizeIsrPath, isrCacheKey, isrCachePrefix, isrCacheVariantPrefix, isrEdgeCacheUrl, normalizeIsrPath } from "./index.mjs";
|
|
2
|
-
import { SHARED_CACHE_POLICY_HEADER, SHARED_CACHE_POLICY_VERSION, buildVariantOriginalUrl, decodeIsrCacheEntry, extractPageDataFromHtml, hasCurrentSharedCachePolicy, hasSharedCacheOptOut, isFrameworkDocumentRequest, isSharedCacheableResponse, isStale, isrCacheResponse, markSharedCachePolicy, normalizePageDataForJsonCache, packIsrValue, resolvePageTtl, resolvePathTtl, resolveRevalidateTtl, serializeHeaders, shouldBypassIsr, unpackIsrValue } from "./core.mjs";
|
|
2
|
+
import { SHARED_CACHE_POLICY_HEADER, SHARED_CACHE_POLICY_VERSION, buildVariantOriginalUrl, decodeIsrCacheEntry, extractPageDataFromHtml, hasCurrentSharedCachePolicy, hasSharedCacheOptOut, hasUnlistedQueryParams, isFrameworkDocumentRequest, isSharedCacheableResponse, isStale, isrCacheResponse, markSharedCachePolicy, normalizePageDataForJsonCache, packIsrValue, resolvePageTtl, resolvePathTtl, resolveRevalidateTtl, serializeHeaders, shouldBypassIsr, unpackIsrValue } from "./core.mjs";
|
|
3
3
|
import { matchSegmentPattern } from "@void/edge";
|
|
4
4
|
//#region src/runtime.ts
|
|
5
5
|
const MAX_ISR_VARIANTS_PER_PATH = 200;
|
|
@@ -160,8 +160,32 @@ function clientRevalidateResponse(response) {
|
|
|
160
160
|
res.headers.delete(SHARED_CACHE_POLICY_HEADER);
|
|
161
161
|
return res;
|
|
162
162
|
}
|
|
163
|
+
function mediaType(response) {
|
|
164
|
+
return response.headers.get("content-type")?.toLowerCase().split(";", 1)[0]?.trim();
|
|
165
|
+
}
|
|
163
166
|
function isEventStreamResponse(response) {
|
|
164
|
-
return response
|
|
167
|
+
return mediaType(response) === "text/event-stream";
|
|
168
|
+
}
|
|
169
|
+
function isHtmlResponse(response) {
|
|
170
|
+
return mediaType(response) === "text/html";
|
|
171
|
+
}
|
|
172
|
+
/** Whether a fresh render may be written to the shared KV and edge caches. */
|
|
173
|
+
function isStorableRender(response, ttl, htmlOnly) {
|
|
174
|
+
return ttl > 0 && isSharedCacheableResponse(response) && (!htmlOnly || isHtmlResponse(response));
|
|
175
|
+
}
|
|
176
|
+
/** Whether a successful render opts out of sharing, so cached copies must go. */
|
|
177
|
+
function isUncacheableRender(response, ttl, htmlOnly) {
|
|
178
|
+
return response.status === 200 && (ttl === 0 || hasSharedCacheOptOut(response) || htmlOnly && !isHtmlResponse(response));
|
|
179
|
+
}
|
|
180
|
+
/** Headers for a shared HTML cache copy, without ISR-managed or private headers. */
|
|
181
|
+
function sharedHtmlCacheHeaders(response, ttl, state) {
|
|
182
|
+
const headers = new Headers(response.headers);
|
|
183
|
+
headers.set("x-isr-cache", state);
|
|
184
|
+
headers.set("Cache-Control", `public, s-maxage=${ttl}, max-age=0, must-revalidate`);
|
|
185
|
+
markSharedCachePolicy(headers);
|
|
186
|
+
headers.delete("set-cookie");
|
|
187
|
+
headers.delete("x-void-page-data");
|
|
188
|
+
return headers;
|
|
165
189
|
}
|
|
166
190
|
function jsonCacheResponse(pageData, ttl, state) {
|
|
167
191
|
const response = new Response(pageData, { headers: {
|
|
@@ -249,30 +273,21 @@ async function revalidateInBackground(env, request, host, pathname, defaultTtl,
|
|
|
249
273
|
const ttl = resolveRevalidateTtl(response, defaultTtl);
|
|
250
274
|
const cache = edgeCache();
|
|
251
275
|
if (response.status === 200 && isEventStreamResponse(response)) {
|
|
252
|
-
if (ttl
|
|
276
|
+
if (isUncacheableRender(response, ttl, false)) await deleteSharedIsrVariants(env, cache, host, pathname, keys, variantUrl);
|
|
253
277
|
return;
|
|
254
278
|
}
|
|
255
|
-
if (
|
|
279
|
+
if (isStorableRender(response, ttl, htmlOnly)) {
|
|
256
280
|
await writeIsrCache(env, host, pathname, response, ttl, variantUrl);
|
|
257
|
-
const headers = new Headers(response.headers);
|
|
258
|
-
headers.set("x-isr-cache", "HIT");
|
|
259
|
-
headers.set("Cache-Control", `public, s-maxage=${ttl}, max-age=0, must-revalidate`);
|
|
260
|
-
markSharedCachePolicy(headers);
|
|
261
|
-
headers.delete("set-cookie");
|
|
262
|
-
headers.delete("x-void-page-data");
|
|
263
281
|
await safeCachePut(cache, keys.htmlKey, new Response(response.clone().body, {
|
|
264
282
|
status: response.status,
|
|
265
|
-
headers
|
|
283
|
+
headers: sharedHtmlCacheHeaders(response, ttl, "HIT")
|
|
266
284
|
}));
|
|
267
285
|
await populateJsonEdgeCache(cache, keys.jsonKey, response, ttl, "HIT");
|
|
268
|
-
} else if (response
|
|
286
|
+
} else if (isUncacheableRender(response, ttl, htmlOnly) || (response.status === 404 || response.status === 410) && Date.now() - staleSinceMs > ISR_STALE_EVICT_GRACE_MS) await deleteSharedIsrVariants(env, cache, host, pathname, keys, variantUrl);
|
|
269
287
|
} catch (err) {
|
|
270
288
|
console.error("ISR revalidation failed:", err);
|
|
271
289
|
}
|
|
272
290
|
}
|
|
273
|
-
function isHtmlResponse(response) {
|
|
274
|
-
return response.headers.get("content-type")?.toLowerCase().split(";", 1)[0]?.trim() === "text/html";
|
|
275
|
-
}
|
|
276
291
|
/** Cache only framework document HTML; leave client data requests and other response types live. */
|
|
277
292
|
async function handleFrameworkIsr(request, env, ctx, options, render) {
|
|
278
293
|
if (!isFrameworkDocumentRequest(request)) return null;
|
|
@@ -282,8 +297,9 @@ async function handleFrameworkIsr(request, env, ctx, options, render) {
|
|
|
282
297
|
* Run the ISR caching ladder for a request.
|
|
283
298
|
*
|
|
284
299
|
* Returns a cached/rendered `Response` when ISR applies, or `null` when the
|
|
285
|
-
* caller should render live itself (request not ISR-eligible,
|
|
286
|
-
* cache op failed open
|
|
300
|
+
* caller should render live itself (request not ISR-eligible, a read/hit
|
|
301
|
+
* cache op failed open, or a cache miss for a URL carrying query params
|
|
302
|
+
* outside the allowlist).
|
|
287
303
|
*
|
|
288
304
|
* Ladder:
|
|
289
305
|
* - Bail (`null`) if not GET, an API path, a Range request, `shouldBypassIsr`,
|
|
@@ -314,6 +330,7 @@ async function handleIsrInternal(request, env, ctx, options, render, htmlOnly) {
|
|
|
314
330
|
const host = url.hostname;
|
|
315
331
|
const normalizedVariantUrl = buildVariantOriginalUrl(request.url, pathname, queryAllowlist);
|
|
316
332
|
const variantUrl = new URL(normalizedVariantUrl).search ? normalizedVariantUrl : null;
|
|
333
|
+
const unlisted = hasUnlistedQueryParams(request.url, pathname, queryAllowlist);
|
|
317
334
|
const cache = edgeCache();
|
|
318
335
|
const htmlKey = htmlEdgeKey(host, pathname, deploymentId, variantUrl);
|
|
319
336
|
const jsonKey = jsonEdgeKey(host, pathname, deploymentId, variantUrl);
|
|
@@ -327,8 +344,7 @@ async function handleIsrInternal(request, env, ctx, options, render, htmlOnly) {
|
|
|
327
344
|
if (jsonCached && hasCurrentSharedCachePolicy(jsonCached)) return clientRevalidateResponse(jsonCached);
|
|
328
345
|
if (jsonCached) await safeCacheDelete(cache, jsonKey);
|
|
329
346
|
const response = await render(request);
|
|
330
|
-
|
|
331
|
-
if (response.status === 200 && (ttl === 0 || hasSharedCacheOptOut(response))) ctx.waitUntil(deleteSharedIsrVariants(env, cache, host, pathname, {
|
|
347
|
+
if (isUncacheableRender(response, resolveRevalidateTtl(response, isrTtl), false)) ctx.waitUntil(deleteSharedIsrVariants(env, cache, host, pathname, {
|
|
332
348
|
htmlKey,
|
|
333
349
|
jsonKey
|
|
334
350
|
}, variantUrl));
|
|
@@ -345,7 +361,11 @@ async function handleIsrInternal(request, env, ctx, options, render, htmlOnly) {
|
|
|
345
361
|
const cached = await readIsrCache(env, host, pathname, variantUrl);
|
|
346
362
|
if (cached) try {
|
|
347
363
|
const stale = isStale(cached);
|
|
348
|
-
if (stale) ctx.waitUntil(revalidateInBackground(env,
|
|
364
|
+
if (stale) ctx.waitUntil(revalidateInBackground(env, unlisted ? new Request(normalizedVariantUrl, {
|
|
365
|
+
method: request.method,
|
|
366
|
+
headers: request.headers,
|
|
367
|
+
cf: request.cf
|
|
368
|
+
}) : request, host, pathname, isrTtl, {
|
|
349
369
|
htmlKey,
|
|
350
370
|
jsonKey
|
|
351
371
|
}, render, variantUrl, htmlOnly, cached.createdAt + cached.ttl * 1e3));
|
|
@@ -355,26 +375,19 @@ async function handleIsrInternal(request, env, ctx, options, render, htmlOnly) {
|
|
|
355
375
|
} catch {
|
|
356
376
|
return null;
|
|
357
377
|
}
|
|
378
|
+
if (unlisted) return null;
|
|
358
379
|
const response = await render(request);
|
|
359
380
|
if (isEventStreamResponse(response)) return response;
|
|
360
381
|
const ttl = resolveRevalidateTtl(response, isrTtl);
|
|
361
|
-
if (
|
|
382
|
+
if (isStorableRender(response, ttl, htmlOnly)) {
|
|
362
383
|
ctx.waitUntil(writeIsrCache(env, host, pathname, response, ttl, variantUrl));
|
|
363
384
|
ctx.waitUntil(populateJsonEdgeCache(cache, jsonKey, response, ttl, "HIT"));
|
|
364
|
-
const cacheHeaders = new Headers(response.headers);
|
|
365
|
-
cacheHeaders.set("Cache-Control", `public, s-maxage=${ttl}, max-age=0, must-revalidate`);
|
|
366
|
-
cacheHeaders.set("x-isr-cache", "MISS");
|
|
367
|
-
markSharedCachePolicy(cacheHeaders);
|
|
368
|
-
cacheHeaders.delete("x-void-page-data");
|
|
369
|
-
cacheHeaders.delete("set-cookie");
|
|
370
|
-
const edgeHeaders = new Headers(cacheHeaders);
|
|
371
|
-
edgeHeaders.set("x-isr-cache", "HIT");
|
|
372
385
|
const cacheResponse = new Response(response.clone().body, {
|
|
373
386
|
status: response.status,
|
|
374
|
-
headers:
|
|
387
|
+
headers: sharedHtmlCacheHeaders(response, ttl, "HIT")
|
|
375
388
|
});
|
|
376
389
|
ctx.waitUntil(safeCachePut(cache, htmlKey, cacheResponse));
|
|
377
|
-
const clientHeaders =
|
|
390
|
+
const clientHeaders = sharedHtmlCacheHeaders(response, ttl, "MISS");
|
|
378
391
|
clientHeaders.set("Cache-Control", CLIENT_REVALIDATE_CACHE_CONTROL);
|
|
379
392
|
clientHeaders.delete(SHARED_CACHE_POLICY_HEADER);
|
|
380
393
|
return new Response(response.body, {
|
|
@@ -382,7 +395,7 @@ async function handleIsrInternal(request, env, ctx, options, render, htmlOnly) {
|
|
|
382
395
|
headers: clientHeaders
|
|
383
396
|
});
|
|
384
397
|
}
|
|
385
|
-
if (response
|
|
398
|
+
if (isUncacheableRender(response, ttl, htmlOnly)) ctx.waitUntil(deleteSharedIsrVariants(env, cache, host, pathname, {
|
|
386
399
|
htmlKey,
|
|
387
400
|
jsonKey
|
|
388
401
|
}, variantUrl));
|
package/package.json
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@void/isr",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.0",
|
|
4
|
+
"description": "Incremental revalidation and cache helpers for Void runtimes.",
|
|
5
|
+
"homepage": "https://void.cloud",
|
|
6
|
+
"bugs": {
|
|
7
|
+
"url": "https://github.com/voidzero-dev/void/issues"
|
|
8
|
+
},
|
|
9
|
+
"license": "MIT",
|
|
4
10
|
"repository": {
|
|
5
11
|
"type": "git",
|
|
6
12
|
"url": "git+https://github.com/voidzero-dev/void.git",
|
|
@@ -28,12 +34,12 @@
|
|
|
28
34
|
"access": "public"
|
|
29
35
|
},
|
|
30
36
|
"dependencies": {
|
|
31
|
-
"@cloudflare/workers-types": "^
|
|
32
|
-
"@void/edge": "0.
|
|
37
|
+
"@cloudflare/workers-types": "^5.20261009.1",
|
|
38
|
+
"@void/edge": "0.27.0"
|
|
33
39
|
},
|
|
34
40
|
"devDependencies": {
|
|
35
41
|
"typescript": "7.0.2",
|
|
36
|
-
"vite-plus": "1.
|
|
42
|
+
"vite-plus": "1.1.0"
|
|
37
43
|
},
|
|
38
44
|
"scripts": {
|
|
39
45
|
"build": "vp pack",
|