@void/isr 0.20.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/LICENSE +21 -0
- package/README.md +89 -0
- package/dist/core.d.mts +81 -0
- package/dist/core.mjs +200 -0
- package/dist/index.d.mts +137 -0
- package/dist/index.mjs +265 -0
- package/dist/runtime.d.mts +145 -0
- package/dist/runtime.mjs +378 -0
- package/package.json +43 -0
package/dist/runtime.mjs
ADDED
|
@@ -0,0 +1,378 @@
|
|
|
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";
|
|
3
|
+
import { matchSegmentPattern } from "@void/edge";
|
|
4
|
+
//#region src/runtime.ts
|
|
5
|
+
const MAX_ISR_VARIANTS_PER_PATH = 200;
|
|
6
|
+
const DIRECT_ISR_PROJECT_ID = "__void_direct__";
|
|
7
|
+
/** Prefix for the current direct deployment's host-scoped cache entries. */
|
|
8
|
+
function isrPrefix(deploymentId) {
|
|
9
|
+
return isrCachePrefix(DIRECT_ISR_PROJECT_ID, deploymentId);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* KV cache key for an ISR-cached page. Deployment- AND host-scoped: cached HTML
|
|
13
|
+
* cannot outlive the hashed assets of the deployment that produced it (a fresh
|
|
14
|
+
* `wrangler deploy` mints a new deployment id → cold cache), and one host's
|
|
15
|
+
* cached content never bleeds into another host served by the same worker
|
|
16
|
+
* (custom domain + workers.dev, apex + www, etc.).
|
|
17
|
+
*/
|
|
18
|
+
function isrKey(deploymentId, host, pathname, variantUrl) {
|
|
19
|
+
return isrCacheKey(DIRECT_ISR_PROJECT_ID, deploymentId, host, pathname, variantUrl);
|
|
20
|
+
}
|
|
21
|
+
function isrVariantPrefix(deploymentId, host, pathname) {
|
|
22
|
+
return isrCacheVariantPrefix(DIRECT_ISR_PROJECT_ID, deploymentId, host, pathname);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Host-registry key. Every host that has written cached content registers itself
|
|
26
|
+
* under `void-isr-hosts/<id>/<host>` (value `''`) so a purge with no request host
|
|
27
|
+
* (cron / queue `revalidate({ paths })`) can enumerate the hosts to purge.
|
|
28
|
+
* Scoped by deployment id so registrations from an old deploy do not leak.
|
|
29
|
+
*/
|
|
30
|
+
function hostRegistryKey(deploymentId, host) {
|
|
31
|
+
return `void-isr-hosts/${deploymentId}/${host}`;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Edge-cache (`caches.default`) key for the HTML variant. The public URL stays
|
|
35
|
+
* clean; this internal key carries the deployment id so a new deploy never
|
|
36
|
+
* reads HTML produced by an older deploy.
|
|
37
|
+
*/
|
|
38
|
+
function htmlEdgeKey(host, pathname, id, variantUrl) {
|
|
39
|
+
return isrEdgeCacheUrl(host, pathname, id, "html", variantUrl);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Edge-cache key for the JSON (pages-protocol) variant. Separated from HTML by
|
|
43
|
+
* the `&__void=json` marker.
|
|
44
|
+
*/
|
|
45
|
+
function jsonEdgeKey(host, pathname, id, variantUrl) {
|
|
46
|
+
return isrEdgeCacheUrl(host, pathname, id, "json", variantUrl);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Read an ISR cache entry from KV. Returns null on cache miss, malformed
|
|
50
|
+
* metadata, or ANY KV error (fail-open → caller renders live). The KV key is
|
|
51
|
+
* deployment-scoped via `env.__VOID_ISR_DEPLOYMENT_ID` (see module header) and
|
|
52
|
+
* host-scoped via `host` so one host never reads another host's cached bytes.
|
|
53
|
+
*/
|
|
54
|
+
async function readIsrCache(env, host, pathname, variantUrl) {
|
|
55
|
+
const kv = env.ISR_CACHE;
|
|
56
|
+
if (!kv) return null;
|
|
57
|
+
const key = isrKey(env.__VOID_ISR_DEPLOYMENT_ID ?? "", host, pathname, variantUrl);
|
|
58
|
+
let value;
|
|
59
|
+
let metadata;
|
|
60
|
+
try {
|
|
61
|
+
const result = await kv.getWithMetadata(key, "arrayBuffer");
|
|
62
|
+
value = result.value;
|
|
63
|
+
metadata = result.metadata;
|
|
64
|
+
} catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
return decodeIsrCacheEntry(value, metadata);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Write an ISR cache entry to KV. The `response.clone()` is taken SYNCHRONOUSLY
|
|
71
|
+
* before any `await` so a caller that consumes `response.body` for the client
|
|
72
|
+
* response (after kicking us off via `ctx.waitUntil`) cannot race the body
|
|
73
|
+
* away. Any KV error is swallowed (fail-open on the write path).
|
|
74
|
+
*/
|
|
75
|
+
async function writeIsrCache(env, host, pathname, response, ttl, variantUrl) {
|
|
76
|
+
const kv = env.ISR_CACHE;
|
|
77
|
+
if (!kv) return;
|
|
78
|
+
const responseClone = response.clone();
|
|
79
|
+
const headers = serializeHeaders(responseClone);
|
|
80
|
+
const status = responseClone.status;
|
|
81
|
+
const id = env.__VOID_ISR_DEPLOYMENT_ID ?? "";
|
|
82
|
+
try {
|
|
83
|
+
const key = isrKey(id, host, pathname, variantUrl);
|
|
84
|
+
if (variantUrl && !await isVariantWriteAllowed(kv, id, host, pathname, key)) {
|
|
85
|
+
await registerIsrHost(kv, id, host);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const body = await responseClone.arrayBuffer();
|
|
89
|
+
const packed = packIsrValue(headers, body);
|
|
90
|
+
await kv.put(key, packed, { metadata: {
|
|
91
|
+
s: status,
|
|
92
|
+
c: Date.now(),
|
|
93
|
+
t: ttl,
|
|
94
|
+
p: 1
|
|
95
|
+
} });
|
|
96
|
+
} catch {}
|
|
97
|
+
await registerIsrHost(kv, id, host);
|
|
98
|
+
}
|
|
99
|
+
async function isVariantWriteAllowed(kv, deploymentId, host, pathname, key) {
|
|
100
|
+
const listed = await kv.list({
|
|
101
|
+
prefix: isrVariantPrefix(deploymentId, host, pathname),
|
|
102
|
+
limit: 1e3
|
|
103
|
+
});
|
|
104
|
+
return listed.keys.length < 200 || listed.keys.some((entry) => entry.name === key);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* In-isolate guard: `${id}/${host}` values whose host-registry key this isolate
|
|
108
|
+
* has already written (or attempted). Bounds host registration to ~one KV write
|
|
109
|
+
* per (isolate, deployment, host) instead of one per page write.
|
|
110
|
+
*/
|
|
111
|
+
const registeredHosts = /* @__PURE__ */ new Set();
|
|
112
|
+
async function registerIsrHost(kv, deploymentId, host) {
|
|
113
|
+
const guardKey = `${deploymentId}/${host}`;
|
|
114
|
+
if (registeredHosts.has(guardKey)) return;
|
|
115
|
+
registeredHosts.add(guardKey);
|
|
116
|
+
try {
|
|
117
|
+
await kv.put(hostRegistryKey(deploymentId, host), "");
|
|
118
|
+
} catch {
|
|
119
|
+
registeredHosts.delete(guardKey);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* List every host that has registered cached content for a deployment. Used by
|
|
124
|
+
* the on-demand `revalidate({ paths })` purge when there is no request host
|
|
125
|
+
* (cron / queue). Fail-open: a registry read error yields the hosts collected so
|
|
126
|
+
* far (or none) rather than throwing — degraded purge coverage, never a 500.
|
|
127
|
+
*/
|
|
128
|
+
async function listIsrHosts(kv, deploymentId) {
|
|
129
|
+
const prefix = `void-isr-hosts/${deploymentId}/`;
|
|
130
|
+
const hosts = [];
|
|
131
|
+
try {
|
|
132
|
+
let cursor;
|
|
133
|
+
do {
|
|
134
|
+
const list = await kv.list({
|
|
135
|
+
prefix,
|
|
136
|
+
cursor
|
|
137
|
+
});
|
|
138
|
+
for (const key of list.keys) hosts.push(key.name.slice(prefix.length));
|
|
139
|
+
cursor = list.list_complete ? void 0 : list.cursor;
|
|
140
|
+
} while (cursor);
|
|
141
|
+
} catch {}
|
|
142
|
+
return hosts;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Delete an ISR cache entry from KV. Used when a worker permanently opts out
|
|
146
|
+
* via `x-revalidate: 0`. Any KV error is swallowed (fail-open).
|
|
147
|
+
*/
|
|
148
|
+
async function deleteIsrCache(env, host, pathname, variantUrl) {
|
|
149
|
+
const kv = env.ISR_CACHE;
|
|
150
|
+
if (!kv) return;
|
|
151
|
+
try {
|
|
152
|
+
await kv.delete(isrKey(env.__VOID_ISR_DEPLOYMENT_ID ?? "", host, pathname, variantUrl));
|
|
153
|
+
} catch {}
|
|
154
|
+
}
|
|
155
|
+
const CLIENT_REVALIDATE_CACHE_CONTROL = "public, max-age=0, must-revalidate";
|
|
156
|
+
function clientRevalidateResponse(response) {
|
|
157
|
+
const res = new Response(response.body, response);
|
|
158
|
+
res.headers.set("Cache-Control", CLIENT_REVALIDATE_CACHE_CONTROL);
|
|
159
|
+
res.headers.delete(SHARED_CACHE_POLICY_HEADER);
|
|
160
|
+
return res;
|
|
161
|
+
}
|
|
162
|
+
function isEventStreamResponse(response) {
|
|
163
|
+
return response.headers.get("content-type")?.toLowerCase().split(";", 1)[0]?.trim() === "text/event-stream";
|
|
164
|
+
}
|
|
165
|
+
function jsonCacheResponse(pageData, ttl, state) {
|
|
166
|
+
const response = new Response(pageData, { headers: {
|
|
167
|
+
"content-type": "application/json",
|
|
168
|
+
"X-VoidPages": "true",
|
|
169
|
+
Vary: "X-VoidPages",
|
|
170
|
+
"Cache-Control": `public, s-maxage=${ttl}, max-age=0, must-revalidate`,
|
|
171
|
+
"x-isr-cache": state
|
|
172
|
+
} });
|
|
173
|
+
markSharedCachePolicy(response.headers);
|
|
174
|
+
return response;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Populate the JSON edge cache from a response's embedded page data (the
|
|
178
|
+
* `x-void-page-data` header or the `__VOID_PAGE_DATA__` inline script). No-ops
|
|
179
|
+
* when there is no extractable page data. Any cache error is swallowed
|
|
180
|
+
* (fail-open) so this is always safe to fire inside `ctx.waitUntil`.
|
|
181
|
+
*/
|
|
182
|
+
async function populateJsonEdgeCache(cache, cacheKey, response, ttl, state) {
|
|
183
|
+
if (!cache) return;
|
|
184
|
+
try {
|
|
185
|
+
const fromHeader = response.headers.get("x-void-page-data");
|
|
186
|
+
const pageData = fromHeader ? normalizePageDataForJsonCache(fromHeader) : extractPageDataFromHtml(await response.clone().text());
|
|
187
|
+
if (!pageData) return;
|
|
188
|
+
await cache.put(cacheKey, jsonCacheResponse(pageData, ttl, state));
|
|
189
|
+
} catch {}
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* The per-colo edge cache (`caches.default`), or `undefined` when the Cache API
|
|
193
|
+
* is absent/degraded. Accessed through a cast because this package has both
|
|
194
|
+
* `@types/node` and `@cloudflare/workers-types` in scope, and Node's ambient
|
|
195
|
+
* `CacheStorage` (which lacks `.default`) otherwise shadows the Workers one on
|
|
196
|
+
* the `caches` global.
|
|
197
|
+
*
|
|
198
|
+
* Non-throwing by contract: dereferencing `caches.default` can throw if the
|
|
199
|
+
* Cache API is missing (e.g. a non-worker runtime) or degraded. We swallow that
|
|
200
|
+
* here and return `undefined` so `handleIsr` never rejects before its fail-open
|
|
201
|
+
* guards run — the serving invariant is a degraded cache must render live, never
|
|
202
|
+
* 500. Every call site guards `undefined` (via `if (!cache)` or `cache?.`).
|
|
203
|
+
*/
|
|
204
|
+
function edgeCache() {
|
|
205
|
+
try {
|
|
206
|
+
return caches.default;
|
|
207
|
+
} catch {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
async function safeCachePut(cache, key, response) {
|
|
212
|
+
if (!cache) return;
|
|
213
|
+
try {
|
|
214
|
+
await cache.put(key, response);
|
|
215
|
+
} catch {}
|
|
216
|
+
}
|
|
217
|
+
async function safeCacheDelete(cache, key) {
|
|
218
|
+
if (!cache) return;
|
|
219
|
+
try {
|
|
220
|
+
await cache.delete(key);
|
|
221
|
+
} catch {}
|
|
222
|
+
}
|
|
223
|
+
async function deleteSharedIsrVariants(env, cache, host, pathname, keys, variantUrl) {
|
|
224
|
+
await deleteIsrCache(env, host, pathname, variantUrl);
|
|
225
|
+
await safeCacheDelete(cache, keys.htmlKey);
|
|
226
|
+
await safeCacheDelete(cache, keys.jsonKey);
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Build a new Request with the `X-VoidPages` header removed. Headers on an
|
|
230
|
+
* existing Request are immutable, so we must clone into a fresh Request. ISR
|
|
231
|
+
* only runs for GET, so there is no body to preserve.
|
|
232
|
+
*/
|
|
233
|
+
function cloneWithoutVoidPages(request) {
|
|
234
|
+
const headers = new Headers(request.headers);
|
|
235
|
+
headers.delete("X-VoidPages");
|
|
236
|
+
return new Request(request, { headers });
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Revalidate an ISR page in the background. Re-renders as HTML (X-VoidPages
|
|
240
|
+
* stripped), writes fresh HTML to KV + HTML edge cache, and populates the JSON
|
|
241
|
+
* edge cache from embedded page data. Errors are caught and logged — serve
|
|
242
|
+
* stale, don't retry. An explicit cache opt-out (including `ttl === 0`)
|
|
243
|
+
* deletes the KV entry and both edge variants.
|
|
244
|
+
*/
|
|
245
|
+
async function revalidateInBackground(env, request, host, pathname, defaultTtl, keys, render, variantUrl) {
|
|
246
|
+
try {
|
|
247
|
+
const response = await render(cloneWithoutVoidPages(request));
|
|
248
|
+
const ttl = resolveRevalidateTtl(response, defaultTtl);
|
|
249
|
+
const cache = edgeCache();
|
|
250
|
+
if (isEventStreamResponse(response)) {
|
|
251
|
+
if (ttl === 0 || hasSharedCacheOptOut(response)) await deleteSharedIsrVariants(env, cache, host, pathname, keys, variantUrl);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
if (ttl > 0 && isSharedCacheableResponse(response)) {
|
|
255
|
+
await writeIsrCache(env, host, pathname, response, ttl, variantUrl);
|
|
256
|
+
const headers = new Headers(response.headers);
|
|
257
|
+
headers.set("x-isr-cache", "HIT");
|
|
258
|
+
headers.set("Cache-Control", `public, s-maxage=${ttl}, max-age=0, must-revalidate`);
|
|
259
|
+
markSharedCachePolicy(headers);
|
|
260
|
+
headers.delete("set-cookie");
|
|
261
|
+
headers.delete("x-void-page-data");
|
|
262
|
+
await safeCachePut(cache, keys.htmlKey, new Response(response.clone().body, {
|
|
263
|
+
status: response.status,
|
|
264
|
+
headers
|
|
265
|
+
}));
|
|
266
|
+
await populateJsonEdgeCache(cache, keys.jsonKey, response, ttl, "HIT");
|
|
267
|
+
} else if (ttl === 0 || hasSharedCacheOptOut(response)) await deleteSharedIsrVariants(env, cache, host, pathname, keys, variantUrl);
|
|
268
|
+
} catch (err) {
|
|
269
|
+
console.error("ISR revalidation failed:", err);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Run the ISR caching ladder for a request.
|
|
274
|
+
*
|
|
275
|
+
* Returns a cached/rendered `Response` when ISR applies, or `null` when the
|
|
276
|
+
* caller should render live itself (request not ISR-eligible, or a read/hit
|
|
277
|
+
* cache op failed open).
|
|
278
|
+
*
|
|
279
|
+
* Ladder:
|
|
280
|
+
* - Bail (`null`) if not GET, an API path, a Range request, `shouldBypassIsr`,
|
|
281
|
+
* or the path has no ISR TTL. Range requests bail BEFORE any cache read/write so they
|
|
282
|
+
* render live (correct 206) and never touch the shared full-response entry.
|
|
283
|
+
* - JSON (X-VoidPages) path: edge match → hit serves `clientRevalidateResponse`;
|
|
284
|
+
* miss renders live JSON and returns it — the JSON path never writes the edge
|
|
285
|
+
* cache. The JSON edge variant is warmed only as a side-effect of the HTML
|
|
286
|
+
* render path (cold colo re-renders JSON).
|
|
287
|
+
* - HTML path: L1 edge match → L2 KV. Hit serves `isrCacheResponse`
|
|
288
|
+
* (stale hits also queue a background revalidation). Miss renders live and,
|
|
289
|
+
* when cacheable, writes KV + JSON edge + HTML edge in the background.
|
|
290
|
+
*/
|
|
291
|
+
async function handleIsr(request, env, ctx, options, render) {
|
|
292
|
+
const { revalidate, pages, pathRevalidate, deploymentId, queryAllowlist } = options;
|
|
293
|
+
if (request.method !== "GET") return null;
|
|
294
|
+
if (request.headers.has("range")) return null;
|
|
295
|
+
if (shouldBypassIsr(request)) return null;
|
|
296
|
+
const url = new URL(request.url);
|
|
297
|
+
const pathname = url.pathname;
|
|
298
|
+
if (pathname === "/api" || pathname.startsWith("/api/")) return null;
|
|
299
|
+
const isrTtl = resolvePageTtl(pages, revalidate, pathRevalidate, pathname);
|
|
300
|
+
if (isrTtl <= 0) return null;
|
|
301
|
+
env.__VOID_ISR_DEPLOYMENT_ID ??= deploymentId;
|
|
302
|
+
const host = url.hostname;
|
|
303
|
+
const normalizedVariantUrl = buildVariantOriginalUrl(request.url, pathname, queryAllowlist);
|
|
304
|
+
const variantUrl = new URL(normalizedVariantUrl).search ? normalizedVariantUrl : null;
|
|
305
|
+
const cache = edgeCache();
|
|
306
|
+
const htmlKey = htmlEdgeKey(host, pathname, deploymentId, variantUrl);
|
|
307
|
+
const jsonKey = jsonEdgeKey(host, pathname, deploymentId, variantUrl);
|
|
308
|
+
if (request.headers.has("X-VoidPages")) {
|
|
309
|
+
let jsonCached;
|
|
310
|
+
try {
|
|
311
|
+
jsonCached = await cache?.match(jsonKey);
|
|
312
|
+
} catch {
|
|
313
|
+
return null;
|
|
314
|
+
}
|
|
315
|
+
if (jsonCached && hasCurrentSharedCachePolicy(jsonCached)) return clientRevalidateResponse(jsonCached);
|
|
316
|
+
if (jsonCached) await safeCacheDelete(cache, jsonKey);
|
|
317
|
+
const response = await render(request);
|
|
318
|
+
const ttl = resolveRevalidateTtl(response, isrTtl);
|
|
319
|
+
if (response.status === 200 && (ttl === 0 || hasSharedCacheOptOut(response))) ctx.waitUntil(deleteSharedIsrVariants(env, cache, host, pathname, {
|
|
320
|
+
htmlKey,
|
|
321
|
+
jsonKey
|
|
322
|
+
}, variantUrl));
|
|
323
|
+
return response;
|
|
324
|
+
}
|
|
325
|
+
let edgeCached;
|
|
326
|
+
try {
|
|
327
|
+
edgeCached = await cache?.match(htmlKey);
|
|
328
|
+
} catch {
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
if (edgeCached && hasCurrentSharedCachePolicy(edgeCached)) return clientRevalidateResponse(edgeCached);
|
|
332
|
+
if (edgeCached) await safeCacheDelete(cache, htmlKey);
|
|
333
|
+
const cached = await readIsrCache(env, host, pathname, variantUrl);
|
|
334
|
+
if (cached) try {
|
|
335
|
+
const stale = isStale(cached);
|
|
336
|
+
if (stale) ctx.waitUntil(revalidateInBackground(env, request, host, pathname, isrTtl, {
|
|
337
|
+
htmlKey,
|
|
338
|
+
jsonKey
|
|
339
|
+
}, render, variantUrl));
|
|
340
|
+
const cacheResponse = isrCacheResponse(cached, stale);
|
|
341
|
+
if (!stale) ctx.waitUntil(safeCachePut(cache, htmlKey, cacheResponse.clone()));
|
|
342
|
+
return clientRevalidateResponse(cacheResponse);
|
|
343
|
+
} catch {
|
|
344
|
+
return null;
|
|
345
|
+
}
|
|
346
|
+
const response = await render(request);
|
|
347
|
+
if (isEventStreamResponse(response)) return response;
|
|
348
|
+
const ttl = resolveRevalidateTtl(response, isrTtl);
|
|
349
|
+
if (ttl > 0 && isSharedCacheableResponse(response)) {
|
|
350
|
+
ctx.waitUntil(writeIsrCache(env, host, pathname, response, ttl, variantUrl));
|
|
351
|
+
ctx.waitUntil(populateJsonEdgeCache(cache, jsonKey, response, ttl, "MISS"));
|
|
352
|
+
const cacheHeaders = new Headers(response.headers);
|
|
353
|
+
cacheHeaders.set("Cache-Control", `public, s-maxage=${ttl}, max-age=0, must-revalidate`);
|
|
354
|
+
cacheHeaders.set("x-isr-cache", "MISS");
|
|
355
|
+
markSharedCachePolicy(cacheHeaders);
|
|
356
|
+
cacheHeaders.delete("x-void-page-data");
|
|
357
|
+
cacheHeaders.delete("set-cookie");
|
|
358
|
+
const cacheResponse = new Response(response.clone().body, {
|
|
359
|
+
status: response.status,
|
|
360
|
+
headers: cacheHeaders
|
|
361
|
+
});
|
|
362
|
+
ctx.waitUntil(safeCachePut(cache, htmlKey, cacheResponse));
|
|
363
|
+
const clientHeaders = new Headers(cacheHeaders);
|
|
364
|
+
clientHeaders.set("Cache-Control", CLIENT_REVALIDATE_CACHE_CONTROL);
|
|
365
|
+
clientHeaders.delete(SHARED_CACHE_POLICY_HEADER);
|
|
366
|
+
return new Response(response.body, {
|
|
367
|
+
status: response.status,
|
|
368
|
+
headers: clientHeaders
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
if (response.status === 200 && (ttl === 0 || hasSharedCacheOptOut(response))) ctx.waitUntil(deleteSharedIsrVariants(env, cache, host, pathname, {
|
|
372
|
+
htmlKey,
|
|
373
|
+
jsonKey
|
|
374
|
+
}, variantUrl));
|
|
375
|
+
return response;
|
|
376
|
+
}
|
|
377
|
+
//#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 };
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@void/isr",
|
|
3
|
+
"version": "0.20.0",
|
|
4
|
+
"repository": {
|
|
5
|
+
"type": "git",
|
|
6
|
+
"url": "git+https://github.com/voidzero-dev/void.git",
|
|
7
|
+
"directory": "packages/isr"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"type": "module",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.mts",
|
|
16
|
+
"import": "./dist/index.mjs"
|
|
17
|
+
},
|
|
18
|
+
"./core": {
|
|
19
|
+
"types": "./dist/core.d.mts",
|
|
20
|
+
"import": "./dist/core.mjs"
|
|
21
|
+
},
|
|
22
|
+
"./runtime": {
|
|
23
|
+
"types": "./dist/runtime.d.mts",
|
|
24
|
+
"import": "./dist/runtime.mjs"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@cloudflare/workers-types": "^4.20260702.1",
|
|
32
|
+
"@void/edge": "0.20.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"typescript": "7.0.2",
|
|
36
|
+
"vite-plus": "0.3.1"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "vp pack",
|
|
40
|
+
"dev": "vp pack --watch",
|
|
41
|
+
"typecheck": "tsc --noEmit"
|
|
42
|
+
}
|
|
43
|
+
}
|