@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/index.mjs
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
//#region src/index.ts
|
|
2
|
+
/**
|
|
3
|
+
* Shared ISR key/URL helpers used by both the API (purge) and the dispatch
|
|
4
|
+
* worker (read/write). These functions MUST produce byte-identical output:
|
|
5
|
+
* the API purges KV keys and edge URLs that dispatch writes and reads.
|
|
6
|
+
*
|
|
7
|
+
* Dispatch derives the deployment id for its keys via
|
|
8
|
+
* `routing.version ?? 'legacy'`, which allows any non-nullish value —
|
|
9
|
+
* including the empty string — to pass through unchanged. The helpers below
|
|
10
|
+
* preserve that shape so the API can compute the exact same key for purge.
|
|
11
|
+
*/
|
|
12
|
+
const ISR_CACHE_KEY_VERSION = "v3";
|
|
13
|
+
/**
|
|
14
|
+
* Previous key version. `v3` keys are ALWAYS host-scoped
|
|
15
|
+
* (`<proj>/isr/v3/<D>/<host>/<path>`); `v2` keys are ALWAYS hostless
|
|
16
|
+
* (`<proj>/isr/v2/<D>/<path>`) — the `<host>` segment did not exist yet.
|
|
17
|
+
*
|
|
18
|
+
* The bump to `v3` was forced by a real aliasing hazard: under a single
|
|
19
|
+
* version, a host-scoped key `<D>/<host>/<path>` and a legacy hostless key
|
|
20
|
+
* `<D>/<host>/<path>` are byte-identical whenever the legacy path's leading
|
|
21
|
+
* segment happens to look like a hostname. Dotted leading path segments are
|
|
22
|
+
* legal (`[domain]` → `:domain`, `[...slug]` → `(.+)`), so a hostless key for
|
|
23
|
+
* the page `/example.com/about` collides exactly with the host-scoped key for
|
|
24
|
+
* `(host=example.com, /about)`. The stored metadata is only `{s,c,t}` (no
|
|
25
|
+
* path/host), so a reader cannot tell them apart and could serve one page's
|
|
26
|
+
* body for the other. Encoding the shape in the version removes the ambiguity:
|
|
27
|
+
* the version alone determines whether a `<host>` segment is present, so no
|
|
28
|
+
* runtime heuristic (e.g. "is the first segment an attached hostname?") is
|
|
29
|
+
* needed to classify a key. Package-internal, like `ISR_CACHE_KEY_VERSION`.
|
|
30
|
+
*/
|
|
31
|
+
const ISR_LEGACY_CACHE_KEY_VERSION = "v2";
|
|
32
|
+
/**
|
|
33
|
+
* Build the normalized path segment for an ISR-cached page.
|
|
34
|
+
* Root path `/` maps to `__index__` to avoid trailing-slash ambiguity.
|
|
35
|
+
*/
|
|
36
|
+
function normalizeIsrPath(pathname) {
|
|
37
|
+
let normalized = pathname;
|
|
38
|
+
if (normalized === "/") normalized = "__index__";
|
|
39
|
+
else {
|
|
40
|
+
normalized = normalized.startsWith("/") ? normalized.slice(1) : normalized;
|
|
41
|
+
if (normalized.endsWith("/")) normalized = normalized.slice(0, -1);
|
|
42
|
+
}
|
|
43
|
+
return normalized;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Inverse of `normalizeIsrPath`: turn a normalized segment back into a
|
|
47
|
+
* pathname starting with `/`.
|
|
48
|
+
*/
|
|
49
|
+
function denormalizeIsrPath(normalized) {
|
|
50
|
+
return normalized === "__index__" ? "/" : `/${normalized}`;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Canonicalize an inbound pathname to the form used inside edge cache URLs.
|
|
54
|
+
* Equivalent to `denormalizeIsrPath(normalizeIsrPath(pathname))` — strips any
|
|
55
|
+
* trailing slash except for root.
|
|
56
|
+
*/
|
|
57
|
+
function canonicalIsrPathname(pathname) {
|
|
58
|
+
return denormalizeIsrPath(normalizeIsrPath(pathname));
|
|
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}/`;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Prefix for listing/purging ISR keys.
|
|
76
|
+
*
|
|
77
|
+
* `deploymentId === ''` must still produce the versioned prefix with an
|
|
78
|
+
* empty segment (`<projectId>/isr/v3//`) because dispatch uses
|
|
79
|
+
* `routing.version ?? 'legacy'`, which allows empty strings and writes
|
|
80
|
+
* keys at `<projectId>/isr/v3//<path>`. Only `undefined` selects the
|
|
81
|
+
* legacy scan-all prefix (`<projectId>/isr/`).
|
|
82
|
+
*/
|
|
83
|
+
function isrCachePrefix(projectId, deploymentId) {
|
|
84
|
+
if (deploymentId !== void 0) return `${isrCacheVersionPrefix(projectId)}${deploymentId}/`;
|
|
85
|
+
return `${projectId}/isr/`;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Prefix for listing/purging LEGACY (pre-host-scoping) ISR keys — the `v2`
|
|
89
|
+
* namespace `<projectId>/isr/v2/<deploymentId>/`. Mirrors `isrCachePrefix`'s
|
|
90
|
+
* optional-`deploymentId` behavior exactly (empty string yields the versioned
|
|
91
|
+
* prefix with an empty segment; only `undefined` selects the scan-all
|
|
92
|
+
* `<projectId>/isr/` prefix).
|
|
93
|
+
*
|
|
94
|
+
* Exists SOLELY for the host-scoping rollout: the purger sweeps this namespace
|
|
95
|
+
* to reap orphaned `v2` KV keys and purge their edge URLs. Current dispatch
|
|
96
|
+
* neither reads nor writes `v2` keys. Transition-only — remove once `v2`
|
|
97
|
+
* entries have aged out.
|
|
98
|
+
*/
|
|
99
|
+
function isrLegacyCachePrefix(projectId, deploymentId) {
|
|
100
|
+
if (deploymentId !== void 0) return `${isrLegacyCacheVersionPrefix(projectId)}${deploymentId}/`;
|
|
101
|
+
return `${projectId}/isr/`;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Separator that marks the rewrite-variant suffix on an ISR cache key or
|
|
105
|
+
* the corresponding edge-cache URL parameter. `#` cannot appear in
|
|
106
|
+
* `url.pathname` (fragments are stripped before the request reaches the
|
|
107
|
+
* server) and does not survive `normalizeIsrPath`, so it unambiguously
|
|
108
|
+
* identifies the variant suffix.
|
|
109
|
+
*/
|
|
110
|
+
const ISR_VARIANT_SEPARATOR = "#rw=";
|
|
111
|
+
/**
|
|
112
|
+
* Build the cache key for an ISR-cached page.
|
|
113
|
+
* ISR is deployment-scoped so cached HTML cannot outlive the hashed assets
|
|
114
|
+
* referenced by the deployment that produced it, and host-scoped (`<host>`
|
|
115
|
+
* segment after the deployment id) so the same path served under different
|
|
116
|
+
* hostnames — e.g. a project slug and a custom domain, or two custom
|
|
117
|
+
* domains — never share a cache slot. `host` is the request hostname the
|
|
118
|
+
* dispatch worker saw (`new URL(request.url).hostname`).
|
|
119
|
+
*
|
|
120
|
+
* Host-scoping is a `v3` key format: `<proj>/isr/v3/<D>/<host>/<path>`. The
|
|
121
|
+
* 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
|
+
*
|
|
129
|
+
* When `originalUrl` is provided, the request was served via a dispatch-side
|
|
130
|
+
* rewrite (`redirectRules`/`fallbackRules` with status 200). The original
|
|
131
|
+
* request URL is mixed into the key so a direct hit to `/en/docs` and a
|
|
132
|
+
* rewrite from `/docs` to `/en/docs` do not collide — user code can branch
|
|
133
|
+
* 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).
|
|
137
|
+
*/
|
|
138
|
+
function isrCacheKey(projectId, deploymentId, host, pathname, originalUrl) {
|
|
139
|
+
const base = `${isrCachePrefix(projectId, deploymentId)}${host}/${normalizeIsrPath(pathname)}`;
|
|
140
|
+
if (!originalUrl) return base;
|
|
141
|
+
return `${base}${ISR_VARIANT_SEPARATOR}${hashOriginalUrl(originalUrl)}`;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Prefix used to list/purge every variant of a given pathname under a
|
|
145
|
+
* deployment — both the direct-hit key and any rewrite-variant keys.
|
|
146
|
+
*/
|
|
147
|
+
function isrCacheVariantPrefix(projectId, deploymentId, host, pathname) {
|
|
148
|
+
return `${isrCachePrefix(projectId, deploymentId)}${host}/${normalizeIsrPath(pathname)}${ISR_VARIANT_SEPARATOR}`;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Build the exact LEGACY (pre-host-scoping) direct-hit key for a pathname under
|
|
152
|
+
* a deployment: `<projectId>/isr/v2/<deploymentId>/<normalizedPath>` — the
|
|
153
|
+
* hostless counterpart of `isrCacheKey` with no `<host>/` segment and no
|
|
154
|
+
* rewrite-variant suffix. Used by the purger to reap the orphaned `v2`
|
|
155
|
+
* direct-hit KV key for a requested path.
|
|
156
|
+
*
|
|
157
|
+
* Exists SOLELY for the host-scoping rollout — current dispatch neither writes
|
|
158
|
+
* nor reads hostless keys. Transition-only. Do not use it on the hot path.
|
|
159
|
+
*/
|
|
160
|
+
function isrLegacyCacheKey(projectId, deploymentId, pathname) {
|
|
161
|
+
return `${isrLegacyCachePrefix(projectId, deploymentId)}${normalizeIsrPath(pathname)}`;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Prefix used to list/purge the pre-host-scoped ("legacy") rewrite variants of
|
|
165
|
+
* a given pathname under a deployment. Before ISR keys grew a `<host>` segment,
|
|
166
|
+
* variant keys were written as
|
|
167
|
+
* `<projectId>/isr/v2/<deploymentId>/<normalizedPath>#rw=<token>` — identical to
|
|
168
|
+
* `isrCacheVariantPrefix` but with NO `<host>/` segment. Those entries still
|
|
169
|
+
* persist during the host-scoping rollout (ISR puts set no `expirationTtl`),
|
|
170
|
+
* so the purger must scan this hostless `v2` prefix in addition to the
|
|
171
|
+
* host-scoped `v3` one to recover their variant tokens and purge the matching
|
|
172
|
+
* edge URLs.
|
|
173
|
+
*
|
|
174
|
+
* Built off `isrLegacyCachePrefix` (the `v2` namespace), NOT `isrCachePrefix`
|
|
175
|
+
* (now `v3`), so it targets the legacy hostless keys it is meant to sweep.
|
|
176
|
+
*
|
|
177
|
+
* Exists SOLELY for that rollout cleanup — current dispatch neither writes nor
|
|
178
|
+
* reads hostless keys. Transition-only. Do not use it on the hot path.
|
|
179
|
+
*/
|
|
180
|
+
function isrLegacyCacheVariantPrefix(projectId, deploymentId, pathname) {
|
|
181
|
+
return `${isrLegacyCachePrefix(projectId, deploymentId)}${normalizeIsrPath(pathname)}${ISR_VARIANT_SEPARATOR}`;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Hash an original-URL value into a fixed-length, opaque variant token.
|
|
185
|
+
* Used as both the KV-key suffix and the `__void_orig` query value, so the
|
|
186
|
+
* writer and purger paths see byte-identical tokens for the same URL.
|
|
187
|
+
*
|
|
188
|
+
* Properties:
|
|
189
|
+
* - Sync (Workers `crypto.subtle.digest` is async; we don't want to push
|
|
190
|
+
* async through every read/write path for this).
|
|
191
|
+
* - 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=…`).
|
|
195
|
+
* - Not cryptographic: collision resistance is ~2^32 (birthday). Fine for
|
|
196
|
+
* the per-deployment, per-pathname variant set we ever store in one slot.
|
|
197
|
+
*
|
|
198
|
+
* Implementation: FNV-1a 64-bit over UTF-8 bytes. BigInt arithmetic for the
|
|
199
|
+
* multiply (no native u64 in JS), but inputs are short (URLs, sub-1KB) and
|
|
200
|
+
* this only runs once per ISR-touched request.
|
|
201
|
+
*/
|
|
202
|
+
const FNV_OFFSET_BASIS = 14695981039346656037n;
|
|
203
|
+
const FNV_PRIME = 1099511628211n;
|
|
204
|
+
const FNV_MASK = 18446744073709551615n;
|
|
205
|
+
function hashOriginalUrl(originalUrl) {
|
|
206
|
+
const bytes = new TextEncoder().encode(originalUrl);
|
|
207
|
+
let h = FNV_OFFSET_BASIS;
|
|
208
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
209
|
+
h = (h ^ BigInt(bytes[i])) & FNV_MASK;
|
|
210
|
+
h = h * FNV_PRIME & FNV_MASK;
|
|
211
|
+
}
|
|
212
|
+
return h.toString(16).padStart(16, "0");
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Split a fully-qualified ISR KV key into the normalized pathname segment
|
|
216
|
+
* and the optional rewrite-variant token. Returns `null` token when the
|
|
217
|
+
* key has no `#rw=` suffix (direct hit).
|
|
218
|
+
*
|
|
219
|
+
* The token is opaque (a hash); the original URL it was derived from is not
|
|
220
|
+
* recoverable. Callers that need to reproduce the matching edge-cache URL
|
|
221
|
+
* pass the token through `isrEdgeCacheUrlForToken`.
|
|
222
|
+
*/
|
|
223
|
+
function splitIsrCacheKey(fullKey) {
|
|
224
|
+
const sepIdx = fullKey.indexOf(ISR_VARIANT_SEPARATOR);
|
|
225
|
+
if (sepIdx === -1) return {
|
|
226
|
+
normalizedPath: fullKey,
|
|
227
|
+
variantToken: null
|
|
228
|
+
};
|
|
229
|
+
return {
|
|
230
|
+
normalizedPath: fullKey.slice(0, sepIdx),
|
|
231
|
+
variantToken: fullKey.slice(sepIdx + 4)
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Build the edge cache URL that dispatch uses as its `caches.default` key
|
|
236
|
+
* (wrapped in a GET `Request`). The public URL stays clean; the internal
|
|
237
|
+
* cache key carries the deployment id so a new deploy never reads HTML
|
|
238
|
+
* produced by an older deploy. The `json` variant separates pages-protocol
|
|
239
|
+
* JSON responses from HTML for the same path+deployment.
|
|
240
|
+
*
|
|
241
|
+
* This MUST match the URL dispatch writes. Dispatch builds the URL from
|
|
242
|
+
* `new URL(request.url).hostname`; for standard-port HTTPS traffic (all CF
|
|
243
|
+
* edge traffic) that yields the same origin as the one produced here.
|
|
244
|
+
*/
|
|
245
|
+
function isrEdgeCacheUrl(hostname, pathname, deploymentId, variant, originalUrl) {
|
|
246
|
+
return buildIsrEdgeCacheUrl(hostname, pathname, deploymentId, variant, originalUrl ? hashOriginalUrl(originalUrl) : null);
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Like `isrEdgeCacheUrl`, but takes a pre-hashed variant token (from
|
|
250
|
+
* `splitIsrCacheKey`). Used by the purger, which only knows the hashed
|
|
251
|
+
* token: the original URL is not recoverable from a KV key.
|
|
252
|
+
*/
|
|
253
|
+
function isrEdgeCacheUrlForToken(hostname, pathname, deploymentId, variant, variantToken) {
|
|
254
|
+
return buildIsrEdgeCacheUrl(hostname, pathname, deploymentId, variant, variantToken ?? null);
|
|
255
|
+
}
|
|
256
|
+
function buildIsrEdgeCacheUrl(hostname, pathname, deploymentId, variant, variantToken) {
|
|
257
|
+
const normalizedPathname = canonicalIsrPathname(pathname);
|
|
258
|
+
const url = new URL(`https://${hostname}${normalizedPathname}`);
|
|
259
|
+
if (variant === "json") url.searchParams.set("__void", "json");
|
|
260
|
+
url.searchParams.set("__void_isr", deploymentId);
|
|
261
|
+
if (variantToken) url.searchParams.set("__void_orig", variantToken);
|
|
262
|
+
return url.toString();
|
|
263
|
+
}
|
|
264
|
+
//#endregion
|
|
265
|
+
export { canonicalIsrPathname, denormalizeIsrPath, hashOriginalUrl, isrCacheKey, isrCachePrefix, isrCacheVariantPrefix, isrEdgeCacheUrl, isrEdgeCacheUrlForToken, isrLegacyCacheKey, isrLegacyCachePrefix, isrLegacyCacheVariantPrefix, normalizeIsrPath, splitIsrCacheKey };
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/// <reference types="@cloudflare/workers-types" />
|
|
2
|
+
import { IsrCacheEntry, IsrCacheMetadata, QueryAllowlist, SHARED_CACHE_POLICY_HEADER, SHARED_CACHE_POLICY_VERSION, extractPageDataFromHtml, hasCurrentSharedCachePolicy, hasSharedCacheOptOut, isSharedCacheableResponse, isStale, isrCacheResponse, markSharedCachePolicy, normalizePageDataForJsonCache, packIsrValue, resolvePathTtl, resolveRevalidateTtl, serializeHeaders, shouldBypassIsr, unpackIsrValue } from "./core.mjs";
|
|
3
|
+
import { canonicalIsrPathname, denormalizeIsrPath, normalizeIsrPath } from "./index.mjs";
|
|
4
|
+
import { matchSegmentPattern } from "@void/edge";
|
|
5
|
+
//#region src/runtime.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* Single-tenant ISR (incremental static regeneration) cache ladder for the
|
|
8
|
+
* self-hosted Void worker (`vite build && wrangler deploy`).
|
|
9
|
+
*
|
|
10
|
+
* On managed Void, ISR lives in the dispatch worker that sits IN FRONT of the
|
|
11
|
+
* user worker. Self-host has no dispatch worker, so this module ports that
|
|
12
|
+
* caching ladder INTO the user worker, collapsed from multi-tenant to a single
|
|
13
|
+
* deployment:
|
|
14
|
+
*
|
|
15
|
+
* - No `projectId`, no cross-tenant metering.
|
|
16
|
+
* - No `#rw=` rewrite variant, no query-string variant fanout, no variant cap.
|
|
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
|
|
19
|
+
* it from `env` (matching the `revalidate()` purge path in `runtime/isr.ts`),
|
|
20
|
+
* while `handleIsr` uses the `deploymentId` option for the edge-cache keys —
|
|
21
|
+
* the two are the same value.
|
|
22
|
+
*
|
|
23
|
+
* Fail-open contract: every `env.ISR_CACHE` (KV) and `caches.default` operation
|
|
24
|
+
* is wrapped so a misconfigured or erroring cache degrades to a live render,
|
|
25
|
+
* never a 500. Read/hit-path failures bail (caller renders live); write-path
|
|
26
|
+
* failures are swallowed.
|
|
27
|
+
*
|
|
28
|
+
* Worker-only: no `lib.dom` types — globals come from `@cloudflare/workers-types`.
|
|
29
|
+
*
|
|
30
|
+
* Ported from the hosted dispatch runtime and the shared `@void/isr` contract.
|
|
31
|
+
*/
|
|
32
|
+
/**
|
|
33
|
+
* Minimal env surface this module needs. Both fields are optional so a worker
|
|
34
|
+
* with no ISR binding (or a caller that only knows the deployment id) still
|
|
35
|
+
* typechecks and fails open at runtime.
|
|
36
|
+
*/
|
|
37
|
+
export interface IsrCacheEnv {
|
|
38
|
+
ISR_CACHE?: KVNamespace;
|
|
39
|
+
__VOID_ISR_DEPLOYMENT_ID?: string;
|
|
40
|
+
}
|
|
41
|
+
/** Renders a live response for a request (worker dispatch + asset serving). */
|
|
42
|
+
export type RenderFn = (request: Request) => Promise<Response>;
|
|
43
|
+
export type HandleIsrOptions = Readonly<{
|
|
44
|
+
revalidate: number | Record<string, number> | undefined;
|
|
45
|
+
pages?: ReadonlyArray<{
|
|
46
|
+
pattern: string;
|
|
47
|
+
revalidate?: number;
|
|
48
|
+
}>;
|
|
49
|
+
pathRevalidate?: Record<string, number>;
|
|
50
|
+
deploymentId: string;
|
|
51
|
+
queryAllowlist?: QueryAllowlist;
|
|
52
|
+
}>;
|
|
53
|
+
export declare const MAX_ISR_VARIANTS_PER_PATH = 200;
|
|
54
|
+
/** Prefix for the current direct deployment's host-scoped cache entries. */
|
|
55
|
+
export declare function isrPrefix(deploymentId: string): string;
|
|
56
|
+
/**
|
|
57
|
+
* KV cache key for an ISR-cached page. Deployment- AND host-scoped: cached HTML
|
|
58
|
+
* cannot outlive the hashed assets of the deployment that produced it (a fresh
|
|
59
|
+
* `wrangler deploy` mints a new deployment id → cold cache), and one host's
|
|
60
|
+
* cached content never bleeds into another host served by the same worker
|
|
61
|
+
* (custom domain + workers.dev, apex + www, etc.).
|
|
62
|
+
*/
|
|
63
|
+
export declare function isrKey(deploymentId: string, host: string, pathname: string, variantUrl?: string | null): string;
|
|
64
|
+
export declare function isrVariantPrefix(deploymentId: string, host: string, pathname: string): string;
|
|
65
|
+
/**
|
|
66
|
+
* Host-registry key. Every host that has written cached content registers itself
|
|
67
|
+
* under `void-isr-hosts/<id>/<host>` (value `''`) so a purge with no request host
|
|
68
|
+
* (cron / queue `revalidate({ paths })`) can enumerate the hosts to purge.
|
|
69
|
+
* Scoped by deployment id so registrations from an old deploy do not leak.
|
|
70
|
+
*/
|
|
71
|
+
export declare function hostRegistryKey(deploymentId: string, host: string): string;
|
|
72
|
+
/**
|
|
73
|
+
* Edge-cache (`caches.default`) key for the HTML variant. The public URL stays
|
|
74
|
+
* clean; this internal key carries the deployment id so a new deploy never
|
|
75
|
+
* reads HTML produced by an older deploy.
|
|
76
|
+
*/
|
|
77
|
+
export declare function htmlEdgeKey(host: string, pathname: string, id: string, variantUrl?: string | null): string;
|
|
78
|
+
/**
|
|
79
|
+
* Edge-cache key for the JSON (pages-protocol) variant. Separated from HTML by
|
|
80
|
+
* the `&__void=json` marker.
|
|
81
|
+
*/
|
|
82
|
+
export declare function jsonEdgeKey(host: string, pathname: string, id: string, variantUrl?: string | null): string;
|
|
83
|
+
/**
|
|
84
|
+
* Read an ISR cache entry from KV. Returns null on cache miss, malformed
|
|
85
|
+
* metadata, or ANY KV error (fail-open → caller renders live). The KV key is
|
|
86
|
+
* deployment-scoped via `env.__VOID_ISR_DEPLOYMENT_ID` (see module header) and
|
|
87
|
+
* host-scoped via `host` so one host never reads another host's cached bytes.
|
|
88
|
+
*/
|
|
89
|
+
export declare function readIsrCache(env: IsrCacheEnv, host: string, pathname: string, variantUrl?: string | null): Promise<IsrCacheEntry | null>;
|
|
90
|
+
/**
|
|
91
|
+
* Write an ISR cache entry to KV. The `response.clone()` is taken SYNCHRONOUSLY
|
|
92
|
+
* before any `await` so a caller that consumes `response.body` for the client
|
|
93
|
+
* response (after kicking us off via `ctx.waitUntil`) cannot race the body
|
|
94
|
+
* away. Any KV error is swallowed (fail-open on the write path).
|
|
95
|
+
*/
|
|
96
|
+
export declare function writeIsrCache(env: IsrCacheEnv, host: string, pathname: string, response: Response, ttl: number, variantUrl?: string | null): Promise<void>;
|
|
97
|
+
/**
|
|
98
|
+
* List every host that has registered cached content for a deployment. Used by
|
|
99
|
+
* the on-demand `revalidate({ paths })` purge when there is no request host
|
|
100
|
+
* (cron / queue). Fail-open: a registry read error yields the hosts collected so
|
|
101
|
+
* far (or none) rather than throwing — degraded purge coverage, never a 500.
|
|
102
|
+
*/
|
|
103
|
+
export declare function listIsrHosts(kv: KVNamespace, deploymentId: string): Promise<Array<string>>;
|
|
104
|
+
/**
|
|
105
|
+
* Delete an ISR cache entry from KV. Used when a worker permanently opts out
|
|
106
|
+
* via `x-revalidate: 0`. Any KV error is swallowed (fail-open).
|
|
107
|
+
*/
|
|
108
|
+
export declare function deleteIsrCache(env: IsrCacheEnv, host: string, pathname: string, variantUrl?: string | null): Promise<void>;
|
|
109
|
+
export declare function clientRevalidateResponse(response: Response): Response;
|
|
110
|
+
export declare function isEventStreamResponse(response: Response): boolean;
|
|
111
|
+
/**
|
|
112
|
+
* Populate the JSON edge cache from a response's embedded page data (the
|
|
113
|
+
* `x-void-page-data` header or the `__VOID_PAGE_DATA__` inline script). No-ops
|
|
114
|
+
* when there is no extractable page data. Any cache error is swallowed
|
|
115
|
+
* (fail-open) so this is always safe to fire inside `ctx.waitUntil`.
|
|
116
|
+
*/
|
|
117
|
+
export declare function populateJsonEdgeCache(cache: Cache | undefined, cacheKey: string, response: Response, ttl: number, state: "HIT" | "MISS"): Promise<void>;
|
|
118
|
+
/**
|
|
119
|
+
* Build a new Request with the `X-VoidPages` header removed. Headers on an
|
|
120
|
+
* existing Request are immutable, so we must clone into a fresh Request. ISR
|
|
121
|
+
* only runs for GET, so there is no body to preserve.
|
|
122
|
+
*/
|
|
123
|
+
export declare function cloneWithoutVoidPages(request: Request): Request;
|
|
124
|
+
/**
|
|
125
|
+
* Run the ISR caching ladder for a request.
|
|
126
|
+
*
|
|
127
|
+
* Returns a cached/rendered `Response` when ISR applies, or `null` when the
|
|
128
|
+
* caller should render live itself (request not ISR-eligible, or a read/hit
|
|
129
|
+
* cache op failed open).
|
|
130
|
+
*
|
|
131
|
+
* Ladder:
|
|
132
|
+
* - Bail (`null`) if not GET, an API path, a Range request, `shouldBypassIsr`,
|
|
133
|
+
* or the path has no ISR TTL. Range requests bail BEFORE any cache read/write so they
|
|
134
|
+
* render live (correct 206) and never touch the shared full-response entry.
|
|
135
|
+
* - JSON (X-VoidPages) path: edge match → hit serves `clientRevalidateResponse`;
|
|
136
|
+
* miss renders live JSON and returns it — the JSON path never writes the edge
|
|
137
|
+
* cache. The JSON edge variant is warmed only as a side-effect of the HTML
|
|
138
|
+
* render path (cold colo re-renders JSON).
|
|
139
|
+
* - HTML path: L1 edge match → L2 KV. Hit serves `isrCacheResponse`
|
|
140
|
+
* (stale hits also queue a background revalidation). Miss renders live and,
|
|
141
|
+
* when cacheable, writes KV + JSON edge + HTML edge in the background.
|
|
142
|
+
*/
|
|
143
|
+
export declare function handleIsr(request: Request, env: IsrCacheEnv, ctx: ExecutionContext, options: HandleIsrOptions, render: RenderFn): Promise<Response | null>;
|
|
144
|
+
//#endregion
|
|
145
|
+
export { type IsrCacheEntry, type IsrCacheMetadata, SHARED_CACHE_POLICY_HEADER, SHARED_CACHE_POLICY_VERSION, canonicalIsrPathname, denormalizeIsrPath, extractPageDataFromHtml, hasCurrentSharedCachePolicy, hasSharedCacheOptOut, isSharedCacheableResponse, isStale, isrCacheResponse, markSharedCachePolicy, matchSegmentPattern as matchLiteralPattern, normalizeIsrPath, normalizePageDataForJsonCache, packIsrValue, resolvePathTtl, resolveRevalidateTtl, serializeHeaders, shouldBypassIsr, unpackIsrValue };
|