@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 CHANGED
@@ -1,11 +1,17 @@
1
1
  # @void/isr
2
2
 
3
- Shared incremental revalidation helpers for Void runtimes and deployment adapters. Applications use `void/isr` to revalidate pages; see the [Revalidation guide](https://void.cloud/guide/edge/revalidation).
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, the package provides three entry points:
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
- The writer and purger must use the same key helpers. Keep incompatible format changes behind a new cache-key version. See the [source](./src/index.ts) for the format contract and [Platform Development](https://void.cloud/guide/platform-development) for runtime work.
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 legacy
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
- * Prefix shared by every ISR key for a project at the current version —
62
- * `<projectId>/isr/v3/`. Used internally by `isrCachePrefix`.
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
- if (deploymentId !== void 0) return `${isrCacheVersionPrefix(projectId)}${deploymentId}/`;
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
- if (deploymentId !== void 0) return `${isrLegacyCacheVersionPrefix(projectId)}${deploymentId}/`;
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 legacy
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, and the previous base64url
193
- * encoding let any operator with list access read the original URLs
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
  *
@@ -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` (Task 7). The KV helpers read
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, or a read/hit
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.headers.get("content-type")?.toLowerCase().split(";", 1)[0]?.trim() === "text/event-stream";
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 === 0 || hasSharedCacheOptOut(response)) await deleteSharedIsrVariants(env, cache, host, pathname, keys, variantUrl);
276
+ if (isUncacheableRender(response, ttl, false)) await deleteSharedIsrVariants(env, cache, host, pathname, keys, variantUrl);
253
277
  return;
254
278
  }
255
- if (ttl > 0 && isSharedCacheableResponse(response) && (!htmlOnly || isHtmlResponse(response))) {
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.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);
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, or a read/hit
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
- const ttl = resolveRevalidateTtl(response, isrTtl);
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, request, host, pathname, isrTtl, {
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 (ttl > 0 && isSharedCacheableResponse(response) && (!htmlOnly || isHtmlResponse(response))) {
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: edgeHeaders
387
+ headers: sharedHtmlCacheHeaders(response, ttl, "HIT")
375
388
  });
376
389
  ctx.waitUntil(safeCachePut(cache, htmlKey, cacheResponse));
377
- const clientHeaders = new Headers(cacheHeaders);
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.status === 200 && (ttl === 0 || hasSharedCacheOptOut(response) || htmlOnly && !isHtmlResponse(response))) ctx.waitUntil(deleteSharedIsrVariants(env, cache, host, pathname, {
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.26.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": "^4.20260702.1",
32
- "@void/edge": "0.26.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.0.0"
42
+ "vite-plus": "1.1.0"
37
43
  },
38
44
  "scripts": {
39
45
  "build": "vp pack",