@void/isr 0.20.2 → 0.21.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/dist/core.d.mts +2 -0
- package/dist/core.mjs +13 -1
- package/dist/runtime.d.mts +3 -1
- package/dist/runtime.mjs +23 -10
- package/package.json +2 -2
package/dist/core.d.mts
CHANGED
|
@@ -53,6 +53,8 @@ export declare function isrCacheResponse(entry: IsrCacheEntry, stale: boolean, n
|
|
|
53
53
|
export declare function resolveRevalidateTtl(response: Response, defaultTtl: number): number;
|
|
54
54
|
/** Return whether credentials or EventSource semantics require an ISR bypass. */
|
|
55
55
|
export declare function shouldBypassIsr(request: Request): boolean;
|
|
56
|
+
/** Only document navigations may read a framework's shared HTML cache. */
|
|
57
|
+
export declare function isFrameworkDocumentRequest(request: Request): boolean;
|
|
56
58
|
/** Return whether a response explicitly forbids storage in a shared cache. */
|
|
57
59
|
export declare function hasSharedCacheOptOut(response: Response): boolean;
|
|
58
60
|
/** Return whether a complete render may be persisted in the shared ISR cache. */
|
package/dist/core.mjs
CHANGED
|
@@ -139,6 +139,18 @@ function acceptsEventStream(request) {
|
|
|
139
139
|
function shouldBypassIsr(request) {
|
|
140
140
|
return request.headers.has("Cookie") || request.headers.has("Authorization") || acceptsEventStream(request);
|
|
141
141
|
}
|
|
142
|
+
/** Only document navigations may read a framework's shared HTML cache. */
|
|
143
|
+
function isFrameworkDocumentRequest(request) {
|
|
144
|
+
if (request.method !== "GET" || request.headers.has("X-VoidPages")) return false;
|
|
145
|
+
if (new URL(request.url).pathname.startsWith("/__void/")) return false;
|
|
146
|
+
if (!request.headers.get("accept")?.split(",").some((value) => value.trim().toLowerCase().split(";", 1)[0] === "text/html")) return false;
|
|
147
|
+
const mode = request.headers.get("sec-fetch-mode")?.toLowerCase();
|
|
148
|
+
if (mode && mode !== "navigate") return false;
|
|
149
|
+
const destination = request.headers.get("sec-fetch-dest")?.toLowerCase();
|
|
150
|
+
if (destination && destination !== "document") return false;
|
|
151
|
+
if (request.headers.has("rsc") || request.headers.has("x-nextjs-data")) return false;
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
142
154
|
/** Return whether a response explicitly forbids storage in a shared cache. */
|
|
143
155
|
function hasSharedCacheOptOut(response) {
|
|
144
156
|
if (response.headers.has("Set-Cookie")) return true;
|
|
@@ -197,4 +209,4 @@ function extractPageDataFromHtml(html) {
|
|
|
197
209
|
return match ? normalizePageDataForJsonCache(match[2]) : null;
|
|
198
210
|
}
|
|
199
211
|
//#endregion
|
|
200
|
-
export { SHARED_CACHE_POLICY_HEADER, SHARED_CACHE_POLICY_VERSION, buildVariantOriginalUrl, decodeIsrCacheEntry, exactQueryAllowlist, extractPageDataFromHtml, hasCurrentSharedCachePolicy, hasSharedCacheOptOut, isSharedCacheableResponse, isStale, isrCacheResponse, markSharedCachePolicy, normalizePageDataForJsonCache, packIsrValue, resolvePageTtl, resolvePathTtl, resolveRevalidateTtl, serializeHeaders, shouldBypassIsr, unpackIsrValue };
|
|
212
|
+
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 };
|
package/dist/runtime.d.mts
CHANGED
|
@@ -5,7 +5,7 @@ import { matchSegmentPattern } from "@void/edge";
|
|
|
5
5
|
//#region src/runtime.d.ts
|
|
6
6
|
/**
|
|
7
7
|
* Single-tenant ISR (incremental static regeneration) cache ladder for the
|
|
8
|
-
*
|
|
8
|
+
* direct Cloudflare Worker (Void apps and framework deploy wrappers).
|
|
9
9
|
*
|
|
10
10
|
* On managed Void, ISR lives in the dispatch worker that sits IN FRONT of the
|
|
11
11
|
* user worker. Self-host has no dispatch worker, so this module ports that
|
|
@@ -121,6 +121,8 @@ export declare function populateJsonEdgeCache(cache: Cache | undefined, cacheKey
|
|
|
121
121
|
* only runs for GET, so there is no body to preserve.
|
|
122
122
|
*/
|
|
123
123
|
export declare function cloneWithoutVoidPages(request: Request): Request;
|
|
124
|
+
/** Cache only framework document HTML; leave client data requests and other response types live. */
|
|
125
|
+
export declare function handleFrameworkIsr(request: Request, env: IsrCacheEnv, ctx: ExecutionContext, options: HandleIsrOptions, render: RenderFn): Promise<Response | null>;
|
|
124
126
|
/**
|
|
125
127
|
* Run the ISR caching ladder for a request.
|
|
126
128
|
*
|
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, 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, 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;
|
|
@@ -242,7 +242,7 @@ function cloneWithoutVoidPages(request) {
|
|
|
242
242
|
* stale, don't retry. An explicit cache opt-out (including `ttl === 0`)
|
|
243
243
|
* deletes the KV entry and both edge variants.
|
|
244
244
|
*/
|
|
245
|
-
async function revalidateInBackground(env, request, host, pathname, defaultTtl, keys, render, variantUrl) {
|
|
245
|
+
async function revalidateInBackground(env, request, host, pathname, defaultTtl, keys, render, variantUrl, htmlOnly) {
|
|
246
246
|
try {
|
|
247
247
|
const response = await render(cloneWithoutVoidPages(request));
|
|
248
248
|
const ttl = resolveRevalidateTtl(response, defaultTtl);
|
|
@@ -251,7 +251,7 @@ async function revalidateInBackground(env, request, host, pathname, defaultTtl,
|
|
|
251
251
|
if (ttl === 0 || hasSharedCacheOptOut(response)) await deleteSharedIsrVariants(env, cache, host, pathname, keys, variantUrl);
|
|
252
252
|
return;
|
|
253
253
|
}
|
|
254
|
-
if (ttl > 0 && isSharedCacheableResponse(response)) {
|
|
254
|
+
if (ttl > 0 && isSharedCacheableResponse(response) && (!htmlOnly || isHtmlResponse(response))) {
|
|
255
255
|
await writeIsrCache(env, host, pathname, response, ttl, variantUrl);
|
|
256
256
|
const headers = new Headers(response.headers);
|
|
257
257
|
headers.set("x-isr-cache", "HIT");
|
|
@@ -264,11 +264,19 @@ async function revalidateInBackground(env, request, host, pathname, defaultTtl,
|
|
|
264
264
|
headers
|
|
265
265
|
}));
|
|
266
266
|
await populateJsonEdgeCache(cache, keys.jsonKey, response, ttl, "HIT");
|
|
267
|
-
} else if (ttl === 0 || hasSharedCacheOptOut(response)) await deleteSharedIsrVariants(env, cache, host, pathname, keys, variantUrl);
|
|
267
|
+
} else if (ttl === 0 || hasSharedCacheOptOut(response) || htmlOnly && !isHtmlResponse(response)) await deleteSharedIsrVariants(env, cache, host, pathname, keys, variantUrl);
|
|
268
268
|
} catch (err) {
|
|
269
269
|
console.error("ISR revalidation failed:", err);
|
|
270
270
|
}
|
|
271
271
|
}
|
|
272
|
+
function isHtmlResponse(response) {
|
|
273
|
+
return response.headers.get("content-type")?.toLowerCase().split(";", 1)[0]?.trim() === "text/html";
|
|
274
|
+
}
|
|
275
|
+
/** Cache only framework document HTML; leave client data requests and other response types live. */
|
|
276
|
+
async function handleFrameworkIsr(request, env, ctx, options, render) {
|
|
277
|
+
if (!isFrameworkDocumentRequest(request)) return null;
|
|
278
|
+
return handleIsrInternal(request, env, ctx, options, render, true);
|
|
279
|
+
}
|
|
272
280
|
/**
|
|
273
281
|
* Run the ISR caching ladder for a request.
|
|
274
282
|
*
|
|
@@ -289,6 +297,9 @@ async function revalidateInBackground(env, request, host, pathname, defaultTtl,
|
|
|
289
297
|
* when cacheable, writes KV + JSON edge + HTML edge in the background.
|
|
290
298
|
*/
|
|
291
299
|
async function handleIsr(request, env, ctx, options, render) {
|
|
300
|
+
return handleIsrInternal(request, env, ctx, options, render, false);
|
|
301
|
+
}
|
|
302
|
+
async function handleIsrInternal(request, env, ctx, options, render, htmlOnly) {
|
|
292
303
|
const { revalidate, pages, pathRevalidate, deploymentId, queryAllowlist } = options;
|
|
293
304
|
if (request.method !== "GET") return null;
|
|
294
305
|
if (request.headers.has("range")) return null;
|
|
@@ -336,7 +347,7 @@ async function handleIsr(request, env, ctx, options, render) {
|
|
|
336
347
|
if (stale) ctx.waitUntil(revalidateInBackground(env, request, host, pathname, isrTtl, {
|
|
337
348
|
htmlKey,
|
|
338
349
|
jsonKey
|
|
339
|
-
}, render, variantUrl));
|
|
350
|
+
}, render, variantUrl, htmlOnly));
|
|
340
351
|
const cacheResponse = isrCacheResponse(cached, stale);
|
|
341
352
|
if (!stale) ctx.waitUntil(safeCachePut(cache, htmlKey, cacheResponse.clone()));
|
|
342
353
|
return clientRevalidateResponse(cacheResponse);
|
|
@@ -346,18 +357,20 @@ async function handleIsr(request, env, ctx, options, render) {
|
|
|
346
357
|
const response = await render(request);
|
|
347
358
|
if (isEventStreamResponse(response)) return response;
|
|
348
359
|
const ttl = resolveRevalidateTtl(response, isrTtl);
|
|
349
|
-
if (ttl > 0 && isSharedCacheableResponse(response)) {
|
|
360
|
+
if (ttl > 0 && isSharedCacheableResponse(response) && (!htmlOnly || isHtmlResponse(response))) {
|
|
350
361
|
ctx.waitUntil(writeIsrCache(env, host, pathname, response, ttl, variantUrl));
|
|
351
|
-
ctx.waitUntil(populateJsonEdgeCache(cache, jsonKey, response, ttl, "
|
|
362
|
+
ctx.waitUntil(populateJsonEdgeCache(cache, jsonKey, response, ttl, "HIT"));
|
|
352
363
|
const cacheHeaders = new Headers(response.headers);
|
|
353
364
|
cacheHeaders.set("Cache-Control", `public, s-maxage=${ttl}, max-age=0, must-revalidate`);
|
|
354
365
|
cacheHeaders.set("x-isr-cache", "MISS");
|
|
355
366
|
markSharedCachePolicy(cacheHeaders);
|
|
356
367
|
cacheHeaders.delete("x-void-page-data");
|
|
357
368
|
cacheHeaders.delete("set-cookie");
|
|
369
|
+
const edgeHeaders = new Headers(cacheHeaders);
|
|
370
|
+
edgeHeaders.set("x-isr-cache", "HIT");
|
|
358
371
|
const cacheResponse = new Response(response.clone().body, {
|
|
359
372
|
status: response.status,
|
|
360
|
-
headers:
|
|
373
|
+
headers: edgeHeaders
|
|
361
374
|
});
|
|
362
375
|
ctx.waitUntil(safeCachePut(cache, htmlKey, cacheResponse));
|
|
363
376
|
const clientHeaders = new Headers(cacheHeaders);
|
|
@@ -368,11 +381,11 @@ async function handleIsr(request, env, ctx, options, render) {
|
|
|
368
381
|
headers: clientHeaders
|
|
369
382
|
});
|
|
370
383
|
}
|
|
371
|
-
if (response.status === 200 && (ttl === 0 || hasSharedCacheOptOut(response))) ctx.waitUntil(deleteSharedIsrVariants(env, cache, host, pathname, {
|
|
384
|
+
if (response.status === 200 && (ttl === 0 || hasSharedCacheOptOut(response) || htmlOnly && !isHtmlResponse(response))) ctx.waitUntil(deleteSharedIsrVariants(env, cache, host, pathname, {
|
|
372
385
|
htmlKey,
|
|
373
386
|
jsonKey
|
|
374
387
|
}, variantUrl));
|
|
375
388
|
return response;
|
|
376
389
|
}
|
|
377
390
|
//#endregion
|
|
378
|
-
export { MAX_ISR_VARIANTS_PER_PATH, SHARED_CACHE_POLICY_HEADER, SHARED_CACHE_POLICY_VERSION, canonicalIsrPathname, clientRevalidateResponse, cloneWithoutVoidPages, deleteIsrCache, denormalizeIsrPath, extractPageDataFromHtml, handleIsr, hasCurrentSharedCachePolicy, hasSharedCacheOptOut, hostRegistryKey, htmlEdgeKey, isEventStreamResponse, isSharedCacheableResponse, isStale, isrCacheResponse, isrKey, isrPrefix, isrVariantPrefix, jsonEdgeKey, listIsrHosts, markSharedCachePolicy, matchSegmentPattern as matchLiteralPattern, normalizeIsrPath, normalizePageDataForJsonCache, packIsrValue, populateJsonEdgeCache, readIsrCache, resolvePathTtl, resolveRevalidateTtl, serializeHeaders, shouldBypassIsr, unpackIsrValue, writeIsrCache };
|
|
391
|
+
export { MAX_ISR_VARIANTS_PER_PATH, SHARED_CACHE_POLICY_HEADER, SHARED_CACHE_POLICY_VERSION, canonicalIsrPathname, clientRevalidateResponse, cloneWithoutVoidPages, deleteIsrCache, denormalizeIsrPath, extractPageDataFromHtml, handleFrameworkIsr, handleIsr, hasCurrentSharedCachePolicy, hasSharedCacheOptOut, hostRegistryKey, htmlEdgeKey, isEventStreamResponse, isSharedCacheableResponse, isStale, isrCacheResponse, isrKey, isrPrefix, isrVariantPrefix, jsonEdgeKey, listIsrHosts, markSharedCachePolicy, matchSegmentPattern as matchLiteralPattern, normalizeIsrPath, normalizePageDataForJsonCache, packIsrValue, populateJsonEdgeCache, readIsrCache, resolvePathTtl, resolveRevalidateTtl, serializeHeaders, shouldBypassIsr, unpackIsrValue, writeIsrCache };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@void/isr",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/voidzero-dev/void.git",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@cloudflare/workers-types": "^4.20260702.1",
|
|
32
|
-
"@void/edge": "0.
|
|
32
|
+
"@void/edge": "0.21.0"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"typescript": "7.0.2",
|