@void/isr 0.21.9 → 0.23.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 CHANGED
@@ -20,7 +20,7 @@ The package has three entry points:
20
20
  - `@void/isr/core` owns target-neutral codecs, validation, TTL selection, request bypass rules, response construction, header sanitization, Void Pages data extraction, and query-variant normalization.
21
21
  - `@void/isr/runtime` implements the fail-open, single-tenant Cloudflare KV and Cache API ladder used by direct Void deployments, including bounded variants from `routing.revalidateQueryAllowlist`.
22
22
 
23
- The direct runtime applies ISR only to public page requests. API paths, credentialed requests, Range requests, and EventSource requests render live. A rendered response is shared only when it is a complete `200` response without `Cache-Control: private`, `no-store`, or `no-cache`, and without `Set-Cookie`. When a background revalidation returns one of those opt-outs, the runtime removes the old HTML, Pages JSON, and KV variants so previously public content cannot remain reachable.
23
+ The direct runtime applies ISR only to public page requests. API paths, requests carrying `Cookie`, `Authorization`, or `Cf-Access-Jwt-Assertion` credentials, Range requests, and EventSource requests render live. A rendered response is shared only when it is a complete `200` response without `Cache-Control: private`, `no-store`, or `no-cache`, and without `Set-Cookie`. When a background revalidation returns one of those opt-outs, the runtime removes the old HTML, Pages JSON, and KV variants so previously public content cannot remain reachable.
24
24
 
25
25
  | Symbol | Description |
26
26
  | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
package/dist/core.d.mts CHANGED
@@ -4,7 +4,7 @@
4
4
  * proven safe to share. Readers reject entries from earlier policies instead
5
5
  * of replaying opaque bodies whose private headers were stripped at write time.
6
6
  */
7
- export declare const SHARED_CACHE_POLICY_VERSION: 1;
7
+ export declare const SHARED_CACHE_POLICY_VERSION: 3;
8
8
  export declare const SHARED_CACHE_POLICY_HEADER = "x-void-shared-cache-policy";
9
9
  /** Stamp an internal edge-cache representation with the policy that admitted it. */
10
10
  export declare function markSharedCachePolicy(headers: Headers): void;
package/dist/core.mjs CHANGED
@@ -5,15 +5,15 @@ import { matchLiteralPattern, matchSegmentPattern } from "@void/edge";
5
5
  * proven safe to share. Readers reject entries from earlier policies instead
6
6
  * of replaying opaque bodies whose private headers were stripped at write time.
7
7
  */
8
- const SHARED_CACHE_POLICY_VERSION = 1;
8
+ const SHARED_CACHE_POLICY_VERSION = 3;
9
9
  const SHARED_CACHE_POLICY_HEADER = "x-void-shared-cache-policy";
10
10
  /** Stamp an internal edge-cache representation with the policy that admitted it. */
11
11
  function markSharedCachePolicy(headers) {
12
- headers.set(SHARED_CACHE_POLICY_HEADER, String(1));
12
+ headers.set(SHARED_CACHE_POLICY_HEADER, String(3));
13
13
  }
14
14
  /** Reject opaque edge entries admitted before the current privacy policy. */
15
15
  function hasCurrentSharedCachePolicy(response) {
16
- return response.headers.get(SHARED_CACHE_POLICY_HEADER) === String(1);
16
+ return response.headers.get(SHARED_CACHE_POLICY_HEADER) === String(3);
17
17
  }
18
18
  const PAGE_DATA_SCRIPT_RE = /<script\b[^>]*\bid=(["'])__VOID_PAGE_DATA__\1[^>]*>([\s\S]*?)<\/script>/i;
19
19
  const SKIP_HEADERS = /* @__PURE__ */ new Set([
@@ -89,7 +89,7 @@ function decodeIsrCacheEntry(value, metadata) {
89
89
  const unpacked = unpackIsrValue(value);
90
90
  if (!unpacked) return null;
91
91
  if (!Number.isInteger(metadata.s) || metadata.s < 200 || metadata.s > 599) return null;
92
- if (!Number.isFinite(metadata.c) || metadata.c <= 0 || !Number.isFinite(metadata.t) || metadata.t <= 0 || metadata.p !== 1) return null;
92
+ if (!Number.isFinite(metadata.c) || metadata.c <= 0 || !Number.isFinite(metadata.t) || metadata.t <= 0 || metadata.p !== 3) return null;
93
93
  try {
94
94
  new Headers(unpacked.headers);
95
95
  } catch {
@@ -137,7 +137,7 @@ function acceptsEventStream(request) {
137
137
  }
138
138
  /** Return whether credentials or EventSource semantics require an ISR bypass. */
139
139
  function shouldBypassIsr(request) {
140
- return request.headers.has("Cookie") || request.headers.has("Authorization") || acceptsEventStream(request);
140
+ return request.headers.has("Cookie") || request.headers.has("Authorization") || request.headers.has("Cf-Access-Jwt-Assertion") || acceptsEventStream(request);
141
141
  }
142
142
  /** Only document navigations may read a framework's shared HTML cache. */
143
143
  function isFrameworkDocumentRequest(request) {
@@ -153,6 +153,10 @@ function isFrameworkDocumentRequest(request) {
153
153
  }
154
154
  /** Return whether a response explicitly forbids storage in a shared cache. */
155
155
  function hasSharedCacheOptOut(response) {
156
+ if (response.headers.get("Vary")?.split(",").some((name) => {
157
+ const header = name.trim().toLowerCase();
158
+ return header !== "" && header !== "x-voidpages";
159
+ })) return true;
156
160
  if (response.headers.has("Set-Cookie")) return true;
157
161
  const cacheControl = response.headers.get("Cache-Control");
158
162
  if (!cacheControl) return false;
package/dist/runtime.mjs CHANGED
@@ -3,6 +3,7 @@ import { SHARED_CACHE_POLICY_HEADER, SHARED_CACHE_POLICY_VERSION, buildVariantOr
3
3
  import { matchSegmentPattern } from "@void/edge";
4
4
  //#region src/runtime.ts
5
5
  const MAX_ISR_VARIANTS_PER_PATH = 200;
6
+ const ISR_STALE_EVICT_GRACE_MS = 864e5;
6
7
  const DIRECT_ISR_PROJECT_ID = "__void_direct__";
7
8
  /** Prefix for the current direct deployment's host-scoped cache entries. */
8
9
  function isrPrefix(deploymentId) {
@@ -91,7 +92,7 @@ async function writeIsrCache(env, host, pathname, response, ttl, variantUrl) {
91
92
  s: status,
92
93
  c: Date.now(),
93
94
  t: ttl,
94
- p: 1
95
+ p: 3
95
96
  } });
96
97
  } catch {}
97
98
  await registerIsrHost(kv, id, host);
@@ -242,12 +243,12 @@ function cloneWithoutVoidPages(request) {
242
243
  * stale, don't retry. An explicit cache opt-out (including `ttl === 0`)
243
244
  * deletes the KV entry and both edge variants.
244
245
  */
245
- async function revalidateInBackground(env, request, host, pathname, defaultTtl, keys, render, variantUrl, htmlOnly) {
246
+ async function revalidateInBackground(env, request, host, pathname, defaultTtl, keys, render, variantUrl, htmlOnly, staleSinceMs) {
246
247
  try {
247
248
  const response = await render(cloneWithoutVoidPages(request));
248
249
  const ttl = resolveRevalidateTtl(response, defaultTtl);
249
250
  const cache = edgeCache();
250
- if (isEventStreamResponse(response)) {
251
+ if (response.status === 200 && isEventStreamResponse(response)) {
251
252
  if (ttl === 0 || hasSharedCacheOptOut(response)) await deleteSharedIsrVariants(env, cache, host, pathname, keys, variantUrl);
252
253
  return;
253
254
  }
@@ -264,7 +265,7 @@ async function revalidateInBackground(env, request, host, pathname, defaultTtl,
264
265
  headers
265
266
  }));
266
267
  await populateJsonEdgeCache(cache, keys.jsonKey, response, ttl, "HIT");
267
- } else if (ttl === 0 || hasSharedCacheOptOut(response) || htmlOnly && !isHtmlResponse(response)) await deleteSharedIsrVariants(env, cache, host, pathname, keys, variantUrl);
268
+ } else if (response.status === 200 && (ttl === 0 || hasSharedCacheOptOut(response) || htmlOnly && !isHtmlResponse(response)) || (response.status === 404 || response.status === 410) && Date.now() - staleSinceMs > ISR_STALE_EVICT_GRACE_MS) await deleteSharedIsrVariants(env, cache, host, pathname, keys, variantUrl);
268
269
  } catch (err) {
269
270
  console.error("ISR revalidation failed:", err);
270
271
  }
@@ -347,7 +348,7 @@ async function handleIsrInternal(request, env, ctx, options, render, htmlOnly) {
347
348
  if (stale) ctx.waitUntil(revalidateInBackground(env, request, host, pathname, isrTtl, {
348
349
  htmlKey,
349
350
  jsonKey
350
- }, render, variantUrl, htmlOnly));
351
+ }, render, variantUrl, htmlOnly, cached.createdAt + cached.ttl * 1e3));
351
352
  const cacheResponse = isrCacheResponse(cached, stale);
352
353
  if (!stale) ctx.waitUntil(safeCachePut(cache, htmlKey, cacheResponse.clone()));
353
354
  return clientRevalidateResponse(cacheResponse);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@void/isr",
3
- "version": "0.21.9",
3
+ "version": "0.23.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/voidzero-dev/void.git",
@@ -29,11 +29,11 @@
29
29
  },
30
30
  "dependencies": {
31
31
  "@cloudflare/workers-types": "^4.20260702.1",
32
- "@void/edge": "0.21.9"
32
+ "@void/edge": "0.23.0"
33
33
  },
34
34
  "devDependencies": {
35
35
  "typescript": "7.0.2",
36
- "vite-plus": "0.3.1"
36
+ "vite-plus": "1.0.0"
37
37
  },
38
38
  "scripts": {
39
39
  "build": "vp pack",