@vinext/cloudflare 1.0.0-beta.1 → 1.0.0-beta.11
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 +193 -1
- package/dist/cache/cdn-adapter-config.d.ts +31 -0
- package/dist/cache/cdn-adapter-config.js +95 -0
- package/dist/cache/cdn-adapter.d.ts +48 -15
- package/dist/cache/cdn-adapter.js +50 -13
- package/dist/cache/cdn-adapter.runtime.d.ts +26 -4
- package/dist/cache/cdn-adapter.runtime.js +150 -37
- package/dist/cache/cdn-adapter.worker.d.ts +31 -0
- package/dist/cache/cdn-adapter.worker.js +373 -0
- package/dist/cache/cdn-build-id.d.ts +6 -0
- package/dist/cache/cdn-build-id.js +9 -0
- package/dist/cache/config.d.ts +137 -0
- package/dist/cache/config.js +87 -0
- package/dist/cache/kv-data-adapter.d.ts +32 -8
- package/dist/cache/kv-data-adapter.js +5 -1
- package/dist/cache/kv-data-adapter.runtime.d.ts +45 -13
- package/dist/cache/kv-data-adapter.runtime.js +132 -52
- package/dist/cache/kv-key.d.ts +15 -0
- package/dist/cache/kv-key.js +43 -0
- package/dist/cache/response-store-adapter.d.ts +73 -0
- package/dist/cache/response-store-adapter.js +64 -0
- package/dist/cache/response-store-adapter.self-contained.worker.d.ts +18 -0
- package/dist/cache/response-store-adapter.self-contained.worker.js +12 -0
- package/dist/cache/response-store-adapter.service-binding.worker.d.ts +16 -0
- package/dist/cache/response-store-adapter.service-binding.worker.js +11 -0
- package/dist/cache/response-store-adapter.worker.d.ts +14 -0
- package/dist/cache/response-store-adapter.worker.js +278 -0
- package/dist/cache/response-store-cdn.runtime.d.ts +5 -0
- package/dist/cache/response-store-cdn.runtime.js +41 -0
- package/dist/cache/response-store-data.runtime.d.ts +34 -0
- package/dist/cache/response-store-data.runtime.js +300 -0
- package/dist/cacheability-artifact.d.ts +10 -0
- package/dist/cacheability-artifact.js +215 -0
- package/dist/cacheability-manifest-limits.d.ts +4 -0
- package/dist/cacheability-manifest-limits.js +7 -0
- package/dist/cacheability-probe.d.ts +49 -0
- package/dist/cacheability-probe.js +660 -0
- package/dist/cdn-warm.d.ts +110 -12
- package/dist/cdn-warm.js +880 -82
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +15 -1
- package/dist/deploy-config.d.ts +14 -11
- package/dist/deploy-config.js +24 -5
- package/dist/deploy-help.d.ts +3 -4
- package/dist/deploy-help.js +65 -22
- package/dist/deploy.d.ts +139 -42
- package/dist/deploy.js +946 -132
- package/dist/images/images-optimizer.d.ts +5 -5
- package/dist/images/images-optimizer.runtime.d.ts +1 -5
- package/dist/prerender-kv-populate.d.ts +3 -4
- package/dist/prerender-kv-populate.js +51 -18
- package/dist/tpr.d.ts +34 -70
- package/dist/tpr.js +50 -603
- package/dist/utils/cache-control-metadata.d.ts +3 -4
- package/dist/version-deploy.d.ts +46 -17
- package/dist/version-deploy.js +151 -14
- package/dist/version-headers.d.ts +2 -0
- package/dist/version-headers.js +2 -0
- package/dist/worker-deployment-url.d.ts +9 -0
- package/dist/worker-deployment-url.js +45 -0
- package/dist/workers-dev-url.d.ts +2 -3
- package/dist/wrangler-config.d.ts +19 -0
- package/dist/wrangler-config.js +294 -0
- package/dist/wrangler-version-metadata.d.ts +7 -0
- package/dist/wrangler-version-metadata.js +9 -0
- package/package.json +21 -11
|
@@ -1,5 +1,80 @@
|
|
|
1
|
+
import { VINEXT_CDN_BUILD_ID_HEADER, getVinextCdnBuildIdentity } from "./cdn-build-id.js";
|
|
2
|
+
import { VINEXT_EXPECTED_WORKER_VERSION_HEADER } from "../version-headers.js";
|
|
3
|
+
import { isNonCacheableCacheControl } from "vinext/shims/cdn-cache";
|
|
4
|
+
import { fnv1a64 } from "vinext/internal/utils/hash";
|
|
1
5
|
import { getRequestExecutionContext } from "vinext/shims/request-context";
|
|
2
6
|
//#region src/cache/cdn-adapter.runtime.ts
|
|
7
|
+
/**
|
|
8
|
+
* Cloudflare CDN cache adapter — edge-managed page-level ISR backed by the
|
|
9
|
+
* Cloudflare Workers Cache (`ctx.cache`).
|
|
10
|
+
*
|
|
11
|
+
* Unlike the origin-managed default adapter (which stores rendered artifacts in
|
|
12
|
+
* the data cache and serves HIT/STALE itself), this adapter delegates serving
|
|
13
|
+
* to Cloudflare's edge:
|
|
14
|
+
*
|
|
15
|
+
* - The origin never serves from a store — `get` returns `null`, so any
|
|
16
|
+
* request that reaches the Worker renders fresh. The edge absorbs HIT/STALE
|
|
17
|
+
* traffic and revalidates in the background (the `UPDATING` cache status).
|
|
18
|
+
* - `set` is a no-op: the platform caches the *response* based on its
|
|
19
|
+
* cache headers, so there is nothing to persist at the origin.
|
|
20
|
+
* - `buildResponseHeaders` emits the SWR policy as `Cloudflare-CDN-Cache-Control`
|
|
21
|
+
* (`public, max-age=…, stale-while-revalidate=…`) so the edge caches and
|
|
22
|
+
* revalidates. The inner response's `Cache-Control` is
|
|
23
|
+
* `public, max-age=0, must-revalidate` so a browser never serves a stored copy
|
|
24
|
+
* without revalidating against the edge; the uncached gateway changes that to
|
|
25
|
+
* `private, max-age=0, must-revalidate` before public egress so personalized
|
|
26
|
+
* request-stage headers cannot enter another shared cache. A `Cache-Tag`
|
|
27
|
+
* header lets entries be purged by tag. Note the edge directive uses `max-age`
|
|
28
|
+
* (not `s-maxage`):
|
|
29
|
+
* the framework computes the policy with `s-maxage` for shared caches, but
|
|
30
|
+
* `Cloudflare-CDN-Cache-Control` is already CDN-scoped so `max-age` is the correct knob
|
|
31
|
+
* for the edge to honor max-age + stale-while-revalidate.
|
|
32
|
+
* - `revalidateTag` purges the edge via the request context's `cache.purge({ tags })`.
|
|
33
|
+
*
|
|
34
|
+
* Tags use fixed-size lowercase digests before emission and purge because
|
|
35
|
+
* Workers Cache tags are case-insensitive printable ASCII, while Next.js tags
|
|
36
|
+
* are case-sensitive arbitrary strings.
|
|
37
|
+
*
|
|
38
|
+
* The default export is the adapter factory the generated
|
|
39
|
+
* `virtual:vinext-cache-adapters` registration imports; configure it from
|
|
40
|
+
* vite.config via the {@link cdnAdapter} builder in `./cdn-adapter.ts` (which
|
|
41
|
+
* `require.resolve`s this file).
|
|
42
|
+
*/
|
|
43
|
+
const DEFAULT_VERSION_METADATA_BINDING = "CF_VERSION_METADATA";
|
|
44
|
+
const WORKER_VERSION_OVERRIDE_HEADER = "Cloudflare-Workers-Version-Overrides";
|
|
45
|
+
function versionValidationFailure(message, status = 500) {
|
|
46
|
+
return Response.json({ error: message }, {
|
|
47
|
+
status,
|
|
48
|
+
headers: { "Cache-Control": "no-store" }
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
const CACHEABLE_EDGE_DIRECTIVE_RE = /(?:^|,)\s*(?:s-maxage|max-age)\s*=/i;
|
|
52
|
+
const EDGE_POLICY_HEADERS = ["CDN-Cache-Control", "Cloudflare-CDN-Cache-Control"];
|
|
53
|
+
function getBuildIdentityResponseHeader() {
|
|
54
|
+
const buildId = getVinextCdnBuildIdentity();
|
|
55
|
+
return buildId ? { [VINEXT_CDN_BUILD_ID_HEADER]: buildId } : {};
|
|
56
|
+
}
|
|
57
|
+
/** Remove every response header whose cache semantics are owned by Cloudflare. */
|
|
58
|
+
function clearCloudflareCdnResponseHeaders(cacheControl) {
|
|
59
|
+
return {
|
|
60
|
+
...getBuildIdentityResponseHeader(),
|
|
61
|
+
"Cache-Control": cacheControl,
|
|
62
|
+
"CDN-Cache-Control": null,
|
|
63
|
+
"Cloudflare-CDN-Cache-Control": null,
|
|
64
|
+
"Cache-Tag": null
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/** Interpret Cloudflare's edge policy before core applies a replacement policy. */
|
|
68
|
+
function hasExplicitCloudflareNonCacheableResponsePolicy(headers) {
|
|
69
|
+
const edgePolicies = EDGE_POLICY_HEADERS.map((name) => headers.get(name));
|
|
70
|
+
if (edgePolicies.some((value) => value && isNonCacheableCacheControl(value))) return true;
|
|
71
|
+
const hasCacheableEdgePolicy = edgePolicies.some((value) => value && CACHEABLE_EDGE_DIRECTIVE_RE.test(value));
|
|
72
|
+
const browserPolicy = headers.get("Cache-Control");
|
|
73
|
+
return Boolean(!hasCacheableEdgePolicy && browserPolicy && isNonCacheableCacheControl(browserPolicy));
|
|
74
|
+
}
|
|
75
|
+
function readCloudflareResponseCacheControl(headers) {
|
|
76
|
+
return headers.get("Cloudflare-CDN-Cache-Control") ?? headers.get("CDN-Cache-Control") ?? headers.get("Cache-Control");
|
|
77
|
+
}
|
|
3
78
|
function getWorkersCache() {
|
|
4
79
|
const cache = getRequestExecutionContext()?.cache;
|
|
5
80
|
if (cache && typeof cache.purge === "function") return cache;
|
|
@@ -28,7 +103,7 @@ const UNBOUNDED_SWR_SECONDS = 31536e3;
|
|
|
28
103
|
/**
|
|
29
104
|
* Convert the framework's shared-cache policy into a CDN-scoped one:
|
|
30
105
|
* `s-maxage=…` → `max-age=…` (the edge honors `max-age` inside
|
|
31
|
-
* `CDN-Cache-Control`), give a value-less `stale-while-revalidate` an explicit
|
|
106
|
+
* `Cloudflare-CDN-Cache-Control`), give a value-less `stale-while-revalidate` an explicit
|
|
32
107
|
* seconds value (Cloudflare ignores the bare directive), and ensure a leading
|
|
33
108
|
* `public`.
|
|
34
109
|
*/
|
|
@@ -37,30 +112,61 @@ function toEdgeCacheControl(cacheControl) {
|
|
|
37
112
|
return /\bpublic\b/.test(withMaxAge) ? withMaxAge : `public, ${withMaxAge}`;
|
|
38
113
|
}
|
|
39
114
|
/**
|
|
40
|
-
* Cloudflare's `Cache-Tag` header budget is 16 KB total
|
|
41
|
-
*
|
|
42
|
-
* produces an oversized (silently-dropped) header.
|
|
115
|
+
* Cloudflare's `Cache-Tag` header budget is 16 KB total. Fixed-size digests let
|
|
116
|
+
* the full Next.js tag set fit without dropping valid long or Unicode tags.
|
|
43
117
|
*/
|
|
44
|
-
const MAX_CACHE_TAG_BYTES =
|
|
45
|
-
const
|
|
118
|
+
const MAX_CACHE_TAG_BYTES = 16384;
|
|
119
|
+
const CACHE_TAG_PREFIX = "vinext-";
|
|
120
|
+
/** Encode a case-sensitive Next.js tag into a fixed Workers Cache tag. */
|
|
121
|
+
function encodeCloudflareCacheTag(tag) {
|
|
122
|
+
return `${CACHE_TAG_PREFIX}${fnv1a64(`0:${tag}`)}${fnv1a64(`1:${tag}`)}`;
|
|
123
|
+
}
|
|
46
124
|
/**
|
|
47
|
-
* Build a `Cache-Tag` header value from canonicalised tags.
|
|
48
|
-
*
|
|
49
|
-
*
|
|
125
|
+
* Build a complete `Cache-Tag` header value from canonicalised tags. Returning
|
|
126
|
+
* null makes the response uncacheable rather than caching with incomplete
|
|
127
|
+
* invalidation metadata.
|
|
50
128
|
*/
|
|
51
129
|
function formatCacheTag(tags) {
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
for (const tag of tags) {
|
|
55
|
-
if (!tag || tag.includes(",") || tag.length > MAX_SINGLE_TAG_BYTES) continue;
|
|
56
|
-
const next = total + tag.length + (parts.length > 0 ? 1 : 0);
|
|
57
|
-
if (next > MAX_CACHE_TAG_BYTES) break;
|
|
58
|
-
parts.push(tag);
|
|
59
|
-
total = next;
|
|
60
|
-
}
|
|
61
|
-
return parts.length > 0 ? parts.join(",") : null;
|
|
130
|
+
const value = tags.map(encodeCloudflareCacheTag).join(",");
|
|
131
|
+
return value && value.length <= MAX_CACHE_TAG_BYTES ? value : null;
|
|
62
132
|
}
|
|
63
133
|
var CloudflareCdnCacheAdapter = class {
|
|
134
|
+
versionMetadata;
|
|
135
|
+
versionMetadataBinding;
|
|
136
|
+
requiresCompletedResponseAdmission = true;
|
|
137
|
+
responseVary = "verbatim";
|
|
138
|
+
responsePolicy = {
|
|
139
|
+
isHeader(name) {
|
|
140
|
+
const normalized = name.toLowerCase();
|
|
141
|
+
return EDGE_POLICY_HEADERS.some((header) => header.toLowerCase() === normalized);
|
|
142
|
+
},
|
|
143
|
+
readCacheControl(headers) {
|
|
144
|
+
return readCloudflareResponseCacheControl(headers);
|
|
145
|
+
},
|
|
146
|
+
hasExplicitNonCacheablePolicy(headers, baseline) {
|
|
147
|
+
if (baseline) {
|
|
148
|
+
for (const name of EDGE_POLICY_HEADERS) {
|
|
149
|
+
const value = headers.get(name);
|
|
150
|
+
if (value !== baseline.get(name) && value !== null && isNonCacheableCacheControl(value)) return true;
|
|
151
|
+
}
|
|
152
|
+
const browserPolicy = headers.get("Cache-Control");
|
|
153
|
+
return Boolean(browserPolicy !== baseline.get("Cache-Control") && browserPolicy && isNonCacheableCacheControl(browserPolicy));
|
|
154
|
+
}
|
|
155
|
+
return hasExplicitCloudflareNonCacheableResponsePolicy(headers);
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
constructor(versionMetadata, versionMetadataBinding = DEFAULT_VERSION_METADATA_BINDING) {
|
|
159
|
+
this.versionMetadata = versionMetadata;
|
|
160
|
+
this.versionMetadataBinding = versionMetadataBinding;
|
|
161
|
+
}
|
|
162
|
+
validateRequest(request) {
|
|
163
|
+
const expectedVersionId = request.headers.get(VINEXT_EXPECTED_WORKER_VERSION_HEADER);
|
|
164
|
+
if (expectedVersionId === null) return null;
|
|
165
|
+
if (!request.headers.has(WORKER_VERSION_OVERRIDE_HEADER)) return versionValidationFailure(`received ${VINEXT_EXPECTED_WORKER_VERSION_HEADER} without ${WORKER_VERSION_OVERRIDE_HEADER}.`);
|
|
166
|
+
if (!this.versionMetadata) return versionValidationFailure(`Cloudflare CDN prewarming requires the \`${this.versionMetadataBinding}\` version metadata binding. Add "version_metadata": { "binding": "${this.versionMetadataBinding}" } to the active Wrangler environment. Named environments do not inherit this binding from the top level.`);
|
|
167
|
+
if (expectedVersionId !== this.versionMetadata.id) return versionValidationFailure(`Cloudflare invoked Worker version ${this.versionMetadata.id}, but vinext warmup expected ${expectedVersionId}.`, 503);
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
64
170
|
ownsBackgroundRevalidation = false;
|
|
65
171
|
/**
|
|
66
172
|
* The origin keeps no page store — return null so the request renders fresh.
|
|
@@ -71,33 +177,40 @@ var CloudflareCdnCacheAdapter = class {
|
|
|
71
177
|
}
|
|
72
178
|
/** No-op: the platform caches the response via its headers, not an origin store. */
|
|
73
179
|
async set(_key, _data, _ctx) {}
|
|
180
|
+
buildResponseIdentityHeaders() {
|
|
181
|
+
return getBuildIdentityResponseHeader();
|
|
182
|
+
}
|
|
74
183
|
buildResponseHeaders(input) {
|
|
75
|
-
if (
|
|
76
|
-
if (
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const headers = {
|
|
184
|
+
if (input.pendingDynamicCheck) return clearCloudflareCdnResponseHeaders(NO_STORE);
|
|
185
|
+
if (!input.cacheControl) return clearCloudflareCdnResponseHeaders(NO_STORE);
|
|
186
|
+
if (isNonCacheableCacheControl(input.cacheControl)) return clearCloudflareCdnResponseHeaders(input.cacheControl);
|
|
187
|
+
const cacheTag = input.tags?.length ? formatCacheTag(input.tags) : null;
|
|
188
|
+
if (input.tags?.length && !cacheTag) return clearCloudflareCdnResponseHeaders(NO_STORE);
|
|
189
|
+
return {
|
|
190
|
+
...getBuildIdentityResponseHeader(),
|
|
83
191
|
"Cache-Control": BROWSER_REVALIDATE,
|
|
84
|
-
"CDN-Cache-Control":
|
|
192
|
+
"CDN-Cache-Control": null,
|
|
193
|
+
"Cloudflare-CDN-Cache-Control": toEdgeCacheControl(input.cacheControl),
|
|
194
|
+
"Cache-Tag": cacheTag
|
|
85
195
|
};
|
|
86
|
-
if (input.tags && input.tags.length > 0) {
|
|
87
|
-
const cacheTag = formatCacheTag(input.tags);
|
|
88
|
-
if (cacheTag) headers["Cache-Tag"] = cacheTag;
|
|
89
|
-
}
|
|
90
|
-
return headers;
|
|
91
196
|
}
|
|
92
197
|
/** Purge edge-cached responses by tag via the request context's `cache.purge`. */
|
|
93
198
|
async revalidateTag(tags, _durations) {
|
|
94
199
|
const cache = getWorkersCache();
|
|
95
200
|
if (!cache) return;
|
|
96
|
-
const tagList = (Array.isArray(tags) ? tags : [tags]).filter((t) => typeof t === "string"
|
|
201
|
+
const tagList = (Array.isArray(tags) ? tags : [tags]).filter((t) => typeof t === "string");
|
|
97
202
|
if (tagList.length === 0) return;
|
|
98
|
-
await cache.purge({ tags: tagList });
|
|
203
|
+
const result = await cache.purge({ tags: tagList.map(encodeCloudflareCacheTag) });
|
|
204
|
+
if (result?.success === false) {
|
|
205
|
+
const errors = result.errors.map(({ code, message }) => `${code}: ${message}`).join(", ");
|
|
206
|
+
throw new Error(`[vinext] Workers Cache purge failed${errors ? `: ${errors}` : ""}`);
|
|
207
|
+
}
|
|
99
208
|
}
|
|
100
209
|
};
|
|
101
|
-
const createCloudflareCdnCacheAdapter = () =>
|
|
210
|
+
const createCloudflareCdnCacheAdapter = ({ env, options } = {}) => {
|
|
211
|
+
const binding = options?.versionMetadataBinding ?? DEFAULT_VERSION_METADATA_BINDING;
|
|
212
|
+
const candidate = env?.[binding];
|
|
213
|
+
return new CloudflareCdnCacheAdapter(candidate && typeof candidate === "object" && "id" in candidate && typeof candidate.id === "string" ? { id: candidate.id } : void 0, binding);
|
|
214
|
+
};
|
|
102
215
|
//#endregion
|
|
103
|
-
export { CloudflareCdnCacheAdapter, createCloudflareCdnCacheAdapter as default };
|
|
216
|
+
export { CloudflareCdnCacheAdapter, createCloudflareCdnCacheAdapter as default, encodeCloudflareCacheTag };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { WorkerEntrypoint } from "cloudflare:workers";
|
|
2
|
+
import "vinext/internal/server/cloudflare-workers-tracing";
|
|
3
|
+
import { VinextAssetFetcher } from "vinext/server/multi-stage";
|
|
4
|
+
//#region src/cache/cdn-adapter.worker.d.ts
|
|
5
|
+
type CloudflareStageContext = {
|
|
6
|
+
assets?: VinextAssetFetcher;
|
|
7
|
+
cache?: unknown;
|
|
8
|
+
exports?: Record<string, unknown>;
|
|
9
|
+
props?: unknown;
|
|
10
|
+
hostRuntime?: "worker";
|
|
11
|
+
passThroughOnException?(): void;
|
|
12
|
+
waitUntil?(promise: Promise<unknown>): void;
|
|
13
|
+
};
|
|
14
|
+
type CachePurgeOptions = {
|
|
15
|
+
tags: string[];
|
|
16
|
+
};
|
|
17
|
+
/** Cache-bearing entrypoint. Workers Cache HITs bypass this class entirely. */
|
|
18
|
+
export declare class VinextCachedResponse extends WorkerEntrypoint<unknown, unknown> {
|
|
19
|
+
fetch(request: Request): Promise<Response>;
|
|
20
|
+
purge(options: CachePurgeOptions): Promise<unknown>;
|
|
21
|
+
}
|
|
22
|
+
/** Uncached response entrypoint. Bypass and probe renders execute only here. */
|
|
23
|
+
export declare class VinextUncachedResponse extends WorkerEntrypoint<unknown, unknown> {
|
|
24
|
+
fetch(request: Request): Promise<Response>;
|
|
25
|
+
}
|
|
26
|
+
/** Uncached gateway: request routing and middleware always execute here. */
|
|
27
|
+
declare const _default: {
|
|
28
|
+
fetch(request: Request, env: unknown, context: CloudflareStageContext | undefined): Promise<Response>;
|
|
29
|
+
};
|
|
30
|
+
//#endregion
|
|
31
|
+
export { _default as default };
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import { VINEXT_CDN_BUILD_ID_HEADER, getVinextCdnBuildIdentity } from "./cdn-build-id.js";
|
|
2
|
+
import { NEXTJS_CACHE_HEADER, VINEXT_CACHE_HEADER, VINEXT_PRERENDER_READINESS_PATH, VINEXT_RSC_VARY_HEADER } from "vinext/internal/server/headers";
|
|
3
|
+
import { isNonCacheableCacheControl } from "vinext/shims/cdn-cache";
|
|
4
|
+
import { WorkerEntrypoint } from "cloudflare:workers";
|
|
5
|
+
import "vinext/internal/server/cloudflare-workers-tracing";
|
|
6
|
+
import { loadVinextRequestStage } from "vinext/server/request-stage";
|
|
7
|
+
import { loadVinextResponseStage } from "vinext/server/response-stage";
|
|
8
|
+
import { traceCachedResponseStart } from "vinext/internal/server/response-start-tracing";
|
|
9
|
+
//#region src/cache/cdn-adapter.worker.ts
|
|
10
|
+
const CACHED_RESPONSE_STAGE_EXPORT = "VinextCachedResponse";
|
|
11
|
+
const UNCACHED_RESPONSE_STAGE_EXPORT = "VinextUncachedResponse";
|
|
12
|
+
const AUTHORIZATION_TRANSPORT_HEADER = "x-vinext-internal-authorization";
|
|
13
|
+
const REQUEST_CACHE_CONTROL_TRANSPORT_HEADER = "x-vinext-internal-request-cache-control";
|
|
14
|
+
const REQUEST_CF_TRANSPORT_HEADER = "x-vinext-internal-request-cf";
|
|
15
|
+
const REQUEST_PRAGMA_TRANSPORT_HEADER = "x-vinext-internal-request-pragma";
|
|
16
|
+
const CLOUDFLARE_EDGE_POLICY_HEADER = "Cloudflare-CDN-Cache-Control";
|
|
17
|
+
const SHARED_RESPONSE_STAGE_HEADER = "x-vinext-cloudflare-shared-response-stage";
|
|
18
|
+
const RESPONSE_STAGE_WIRE_CACHE = {
|
|
19
|
+
bypass: "vinext-cloudflare-v1:bypass",
|
|
20
|
+
shared: "vinext-cloudflare-v1:shared"
|
|
21
|
+
};
|
|
22
|
+
const FRAMEWORK_RESPONSE_VARY_FIELDS = new Set(VINEXT_RSC_VARY_HEADER.split(",").map((name) => name.trim().toLowerCase()));
|
|
23
|
+
function isResponseStageReadinessRequest(request) {
|
|
24
|
+
return request.url.includes(VINEXT_PRERENDER_READINESS_PATH) && new URL(request.url).pathname === VINEXT_PRERENDER_READINESS_PATH;
|
|
25
|
+
}
|
|
26
|
+
function responseStageUnavailable() {
|
|
27
|
+
return new Response(null, {
|
|
28
|
+
status: 503,
|
|
29
|
+
headers: { "Cache-Control": "no-store" }
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
/** Stamp the entrypoint that actually produced a response, before Workers Cache stores it. */
|
|
33
|
+
function stampResponseStageBuildIdentity(response) {
|
|
34
|
+
const buildIdentity = getVinextCdnBuildIdentity();
|
|
35
|
+
if (!buildIdentity) return response;
|
|
36
|
+
try {
|
|
37
|
+
response.headers.set(VINEXT_CDN_BUILD_ID_HEADER, buildIdentity);
|
|
38
|
+
return response;
|
|
39
|
+
} catch {
|
|
40
|
+
const headers = new Headers(response.headers);
|
|
41
|
+
headers.set(VINEXT_CDN_BUILD_ID_HEADER, buildIdentity);
|
|
42
|
+
const webSocket = response.webSocket;
|
|
43
|
+
if (!webSocket && (response.status < 200 || response.status > 599)) {
|
|
44
|
+
const unavailable = responseStageUnavailable();
|
|
45
|
+
unavailable.headers.set(VINEXT_CDN_BUILD_ID_HEADER, buildIdentity);
|
|
46
|
+
return unavailable;
|
|
47
|
+
}
|
|
48
|
+
const init = {
|
|
49
|
+
headers,
|
|
50
|
+
status: response.status,
|
|
51
|
+
statusText: response.statusText
|
|
52
|
+
};
|
|
53
|
+
if (webSocket) init.webSocket = webSocket;
|
|
54
|
+
return new Response(response.body, init);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/** Reject a response routed to an entrypoint from another propagating build. */
|
|
58
|
+
function validateResponseStageBuildIdentity(response) {
|
|
59
|
+
const expectedBuildIdentity = getVinextCdnBuildIdentity();
|
|
60
|
+
if (!expectedBuildIdentity || response.headers.get("X-Vinext-Build-Id") === expectedBuildIdentity) return response;
|
|
61
|
+
response.body?.cancel().catch(() => {});
|
|
62
|
+
return responseStageUnavailable();
|
|
63
|
+
}
|
|
64
|
+
function stripUntrustedTransportHeaders(request) {
|
|
65
|
+
if (!request.headers.has(AUTHORIZATION_TRANSPORT_HEADER) && !request.headers.has(REQUEST_CACHE_CONTROL_TRANSPORT_HEADER) && !request.headers.has(REQUEST_CF_TRANSPORT_HEADER) && !request.headers.has(REQUEST_PRAGMA_TRANSPORT_HEADER)) return request;
|
|
66
|
+
const headers = new Headers(request.headers);
|
|
67
|
+
headers.delete(AUTHORIZATION_TRANSPORT_HEADER);
|
|
68
|
+
headers.delete(REQUEST_CACHE_CONTROL_TRANSPORT_HEADER);
|
|
69
|
+
headers.delete(REQUEST_CF_TRANSPORT_HEADER);
|
|
70
|
+
headers.delete(REQUEST_PRAGMA_TRANSPORT_HEADER);
|
|
71
|
+
const sanitized = new Request(request, { headers });
|
|
72
|
+
const requestCf = Reflect.get(request, "cf");
|
|
73
|
+
if (requestCf !== void 0) Object.defineProperty(sanitized, "cf", {
|
|
74
|
+
configurable: true,
|
|
75
|
+
enumerable: true,
|
|
76
|
+
value: requestCf
|
|
77
|
+
});
|
|
78
|
+
return sanitized;
|
|
79
|
+
}
|
|
80
|
+
function withWorkerHostRuntime(context, env) {
|
|
81
|
+
const candidateAssets = env && typeof env === "object" && Reflect.has(env, "ASSETS") ? Reflect.get(env, "ASSETS") : void 0;
|
|
82
|
+
const assets = candidateAssets && (typeof candidateAssets === "object" || typeof candidateAssets === "function") && Reflect.has(candidateAssets, "fetch") && typeof Reflect.get(candidateAssets, "fetch") === "function" ? candidateAssets : context?.assets;
|
|
83
|
+
return {
|
|
84
|
+
...assets === void 0 ? {} : { assets },
|
|
85
|
+
...context?.cache === void 0 ? {} : { cache: context.cache },
|
|
86
|
+
...context?.exports === void 0 ? {} : { exports: context.exports },
|
|
87
|
+
hostRuntime: "worker",
|
|
88
|
+
...typeof context?.passThroughOnException === "function" ? { passThroughOnException: () => context.passThroughOnException?.() } : {},
|
|
89
|
+
...context?.props === void 0 ? {} : { props: context.props },
|
|
90
|
+
...typeof context?.waitUntil === "function" ? { waitUntil: (promise) => context.waitUntil?.(promise) } : {}
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
function hasFetch(value) {
|
|
94
|
+
return value !== null && (typeof value === "object" || typeof value === "function") && "fetch" in value && typeof value.fetch === "function";
|
|
95
|
+
}
|
|
96
|
+
function hasPurge(value) {
|
|
97
|
+
return value !== null && (typeof value === "object" || typeof value === "function") && "purge" in value && typeof value.purge === "function";
|
|
98
|
+
}
|
|
99
|
+
function getResponseStageBinding(context, exportName, serializedInvocation) {
|
|
100
|
+
const binding = context.exports?.[exportName];
|
|
101
|
+
if (typeof binding !== "function") return null;
|
|
102
|
+
const target = binding({ props: JSON.parse(serializedInvocation) });
|
|
103
|
+
return hasFetch(target) ? target : null;
|
|
104
|
+
}
|
|
105
|
+
async function createCacheFacingRequest(request, serializedInvocation) {
|
|
106
|
+
const authorization = request.headers.get("Authorization");
|
|
107
|
+
let serializedRequestCf = null;
|
|
108
|
+
const requestCf = Reflect.get(request, "cf");
|
|
109
|
+
if (requestCf !== void 0) try {
|
|
110
|
+
const json = JSON.stringify(requestCf);
|
|
111
|
+
if (json !== void 0) serializedRequestCf = encodeURIComponent(json);
|
|
112
|
+
} catch {}
|
|
113
|
+
const authorizationIdentity = authorization === null ? "absent" : `present:${authorization.length}:${authorization}`;
|
|
114
|
+
const frameworkVaryIdentity = JSON.stringify([...FRAMEWORK_RESPONSE_VARY_FIELDS].map((name) => [name, request.headers.get(name)]));
|
|
115
|
+
const responseStageBuildIdentity = getVinextCdnBuildIdentity() ?? "";
|
|
116
|
+
const bytes = new TextEncoder().encode(`${request.url}\0${serializedInvocation}\0${authorizationIdentity}\0${frameworkVaryIdentity}\0${responseStageBuildIdentity}`);
|
|
117
|
+
const key = [...new Uint8Array(await crypto.subtle.digest("SHA-256", bytes))].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
118
|
+
const url = new URL(request.url);
|
|
119
|
+
url.searchParams.set("__vinext_cache_key", key);
|
|
120
|
+
const headers = new Headers(request.headers);
|
|
121
|
+
const requestCacheControl = headers.get("Cache-Control");
|
|
122
|
+
const requestPragma = headers.get("Pragma");
|
|
123
|
+
headers.delete("Cache-Control");
|
|
124
|
+
headers.delete("Pragma");
|
|
125
|
+
headers.delete("Authorization");
|
|
126
|
+
headers.delete(AUTHORIZATION_TRANSPORT_HEADER);
|
|
127
|
+
headers.delete(REQUEST_CACHE_CONTROL_TRANSPORT_HEADER);
|
|
128
|
+
headers.delete(REQUEST_CF_TRANSPORT_HEADER);
|
|
129
|
+
headers.delete(REQUEST_PRAGMA_TRANSPORT_HEADER);
|
|
130
|
+
if (authorization !== null) headers.set(AUTHORIZATION_TRANSPORT_HEADER, encodeURIComponent(authorization));
|
|
131
|
+
if (serializedRequestCf !== null) headers.set(REQUEST_CF_TRANSPORT_HEADER, serializedRequestCf);
|
|
132
|
+
if (requestCacheControl !== null) headers.set(REQUEST_CACHE_CONTROL_TRANSPORT_HEADER, encodeURIComponent(requestCacheControl));
|
|
133
|
+
if (requestPragma !== null) headers.set(REQUEST_PRAGMA_TRANSPORT_HEADER, encodeURIComponent(requestPragma));
|
|
134
|
+
return new Request(url, {
|
|
135
|
+
cf: { vary: { default: { action: "passthrough" } } },
|
|
136
|
+
headers,
|
|
137
|
+
method: request.method
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
function restoreResponseStageRequest(request, requestUrl, requestMethod) {
|
|
141
|
+
const headers = new Headers(request.headers);
|
|
142
|
+
const serializedAuthorization = headers.get(AUTHORIZATION_TRANSPORT_HEADER);
|
|
143
|
+
const serializedRequestCacheControl = headers.get(REQUEST_CACHE_CONTROL_TRANSPORT_HEADER);
|
|
144
|
+
const serializedRequestCf = headers.get(REQUEST_CF_TRANSPORT_HEADER);
|
|
145
|
+
const serializedRequestPragma = headers.get(REQUEST_PRAGMA_TRANSPORT_HEADER);
|
|
146
|
+
headers.delete("Cache-Control");
|
|
147
|
+
headers.delete("Pragma");
|
|
148
|
+
headers.delete("Authorization");
|
|
149
|
+
headers.delete(AUTHORIZATION_TRANSPORT_HEADER);
|
|
150
|
+
headers.delete(REQUEST_CACHE_CONTROL_TRANSPORT_HEADER);
|
|
151
|
+
headers.delete(REQUEST_CF_TRANSPORT_HEADER);
|
|
152
|
+
headers.delete(REQUEST_PRAGMA_TRANSPORT_HEADER);
|
|
153
|
+
if (serializedAuthorization !== null) try {
|
|
154
|
+
headers.set("Authorization", decodeURIComponent(serializedAuthorization));
|
|
155
|
+
} catch {}
|
|
156
|
+
let requestCf;
|
|
157
|
+
if (serializedRequestCf !== null) try {
|
|
158
|
+
requestCf = JSON.parse(decodeURIComponent(serializedRequestCf));
|
|
159
|
+
} catch {}
|
|
160
|
+
if (serializedRequestCacheControl !== null) try {
|
|
161
|
+
headers.set("Cache-Control", decodeURIComponent(serializedRequestCacheControl));
|
|
162
|
+
} catch {}
|
|
163
|
+
if (serializedRequestPragma !== null) try {
|
|
164
|
+
headers.set("Pragma", decodeURIComponent(serializedRequestPragma));
|
|
165
|
+
} catch {}
|
|
166
|
+
const restored = new Request(new Request(requestUrl, request), {
|
|
167
|
+
headers,
|
|
168
|
+
method: requestMethod
|
|
169
|
+
});
|
|
170
|
+
let didAccessRequestCf = false;
|
|
171
|
+
if (requestCf !== void 0) Object.defineProperty(restored, "cf", {
|
|
172
|
+
configurable: true,
|
|
173
|
+
enumerable: true,
|
|
174
|
+
get() {
|
|
175
|
+
didAccessRequestCf = true;
|
|
176
|
+
return requestCf;
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
return {
|
|
180
|
+
didAccessRequestCf: () => didAccessRequestCf,
|
|
181
|
+
request: restored
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
function preventResponseCaching(response) {
|
|
185
|
+
const headers = new Headers(response.headers);
|
|
186
|
+
headers.set("Cache-Control", "no-store");
|
|
187
|
+
headers.delete("CDN-Cache-Control");
|
|
188
|
+
headers.delete("Cloudflare-CDN-Cache-Control");
|
|
189
|
+
headers.delete("Cache-Tag");
|
|
190
|
+
return new Response(response.body, {
|
|
191
|
+
headers,
|
|
192
|
+
status: response.status,
|
|
193
|
+
statusText: response.statusText
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Cloudflare consumes its private cache policy before returning a cached
|
|
198
|
+
* entrypoint response. Strip it here as well for uncached/local fallbacks so
|
|
199
|
+
* the outer gateway never forwards an inner shared-cache policy after adding
|
|
200
|
+
* request-specific middleware or routing headers.
|
|
201
|
+
*/
|
|
202
|
+
function markSharedResponseStage(response, provenanceToken, responseStageProps, exposeEntrypointCacheStatus = false) {
|
|
203
|
+
const headers = new Headers(response.headers);
|
|
204
|
+
const cacheStatus = exposeEntrypointCacheStatus ? headers.get("CF-Cache-Status") : null;
|
|
205
|
+
headers.set(SHARED_RESPONSE_STAGE_HEADER, cacheStatus ? `${provenanceToken}:${encodeURIComponent(cacheStatus)}` : provenanceToken);
|
|
206
|
+
return traceCachedResponseStart(new Response(response.body, {
|
|
207
|
+
headers,
|
|
208
|
+
status: response.status,
|
|
209
|
+
statusText: response.statusText
|
|
210
|
+
}), cacheStatus, responseStageProps);
|
|
211
|
+
}
|
|
212
|
+
function finalizeGatewayResponse(response, provenanceToken) {
|
|
213
|
+
const provenance = response.headers.get(SHARED_RESPONSE_STAGE_HEADER);
|
|
214
|
+
const usedSharedResponseStage = provenance === provenanceToken || provenance?.startsWith(`${provenanceToken}:`) === true;
|
|
215
|
+
const sharedResponseStageCollision = provenance !== null && !usedSharedResponseStage;
|
|
216
|
+
const cacheControl = response.headers.get("Cache-Control");
|
|
217
|
+
if (!response.headers.has(CLOUDFLARE_EDGE_POLICY_HEADER) && !usedSharedResponseStage && !sharedResponseStageCollision) return response;
|
|
218
|
+
const headers = new Headers(response.headers);
|
|
219
|
+
headers.delete(SHARED_RESPONSE_STAGE_HEADER);
|
|
220
|
+
headers.delete(CLOUDFLARE_EDGE_POLICY_HEADER);
|
|
221
|
+
if (usedSharedResponseStage || sharedResponseStageCollision) {
|
|
222
|
+
headers.delete("CDN-Cache-Control");
|
|
223
|
+
headers.delete("Cache-Tag");
|
|
224
|
+
headers.delete(VINEXT_CACHE_HEADER);
|
|
225
|
+
headers.delete(NEXTJS_CACHE_HEADER);
|
|
226
|
+
if (!cacheControl || !isNonCacheableCacheControl(cacheControl)) headers.set("Cache-Control", "private, max-age=0, must-revalidate");
|
|
227
|
+
}
|
|
228
|
+
if (usedSharedResponseStage && provenance?.startsWith(`${provenanceToken}:`)) {
|
|
229
|
+
const cacheStatus = decodeURIComponent(provenance.slice(provenanceToken.length + 1));
|
|
230
|
+
headers.set(VINEXT_CACHE_HEADER, cacheStatus);
|
|
231
|
+
headers.set(NEXTJS_CACHE_HEADER, cacheStatus);
|
|
232
|
+
}
|
|
233
|
+
return new Response(response.body, {
|
|
234
|
+
headers,
|
|
235
|
+
status: response.status,
|
|
236
|
+
statusText: response.statusText
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
function hasTaggedCustomVary(response) {
|
|
240
|
+
if (!response.headers.get("Cache-Tag")) return false;
|
|
241
|
+
return (response.headers.get("Vary") ?? "").split(",").map((name) => name.trim().toLowerCase()).filter(Boolean).some((name) => !FRAMEWORK_RESPONSE_VARY_FIELDS.has(name));
|
|
242
|
+
}
|
|
243
|
+
function withResponseStagePurge(context) {
|
|
244
|
+
const factory = context.exports?.[CACHED_RESPONSE_STAGE_EXPORT];
|
|
245
|
+
if (typeof factory !== "function") return context;
|
|
246
|
+
const fallback = context.cache;
|
|
247
|
+
return {
|
|
248
|
+
...context,
|
|
249
|
+
cache: { purge(options) {
|
|
250
|
+
const target = factory({ props: {} });
|
|
251
|
+
if (hasPurge(target)) return target.purge(options);
|
|
252
|
+
return hasPurge(fallback) ? fallback.purge(options) : void 0;
|
|
253
|
+
} }
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
function getResponseStageInvocation(value, expectedCache) {
|
|
257
|
+
if (!value || typeof value !== "object") return null;
|
|
258
|
+
const expectedResponseStageBuildIdentity = Reflect.get(value, "expectedResponseStageBuildIdentity");
|
|
259
|
+
if (expectedResponseStageBuildIdentity !== void 0 && typeof expectedResponseStageBuildIdentity !== "string") return null;
|
|
260
|
+
const options = Reflect.get(value, "options");
|
|
261
|
+
if (!options || typeof options !== "object") return null;
|
|
262
|
+
const wireCache = Reflect.get(options, "cache");
|
|
263
|
+
let cache;
|
|
264
|
+
if (expectedResponseStageBuildIdentity !== void 0 && wireCache === RESPONSE_STAGE_WIRE_CACHE.shared) cache = "shared";
|
|
265
|
+
else if (expectedResponseStageBuildIdentity !== void 0 && wireCache === RESPONSE_STAGE_WIRE_CACHE.bypass) cache = "bypass";
|
|
266
|
+
else if (expectedResponseStageBuildIdentity === void 0 && getVinextCdnBuildIdentity() === null && (wireCache === "shared" || wireCache === "bypass")) cache = wireCache;
|
|
267
|
+
else return null;
|
|
268
|
+
if (expectedCache !== void 0 && cache !== expectedCache) return null;
|
|
269
|
+
const requestUrl = Reflect.get(value, "requestUrl");
|
|
270
|
+
if (typeof requestUrl !== "string") return null;
|
|
271
|
+
const requestMethod = Reflect.get(value, "requestMethod");
|
|
272
|
+
if (typeof requestMethod !== "string" || requestMethod.length === 0) return null;
|
|
273
|
+
try {
|
|
274
|
+
new URL(requestUrl);
|
|
275
|
+
} catch {
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
return {
|
|
279
|
+
...typeof expectedResponseStageBuildIdentity === "string" ? { expectedResponseStageBuildIdentity } : {},
|
|
280
|
+
options: {
|
|
281
|
+
...options,
|
|
282
|
+
cache
|
|
283
|
+
},
|
|
284
|
+
props: Reflect.get(value, "props"),
|
|
285
|
+
requestMethod,
|
|
286
|
+
requestUrl
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
async function invokeResponseStage(request, env, context, invocation) {
|
|
290
|
+
const dispatchResponseStage = (stageRequest, props, options) => invokeResponseStage(stageRequest, env, context, {
|
|
291
|
+
options,
|
|
292
|
+
props,
|
|
293
|
+
requestMethod: stageRequest.method,
|
|
294
|
+
requestUrl: stageRequest.url
|
|
295
|
+
});
|
|
296
|
+
const dispatchRequestStage = async (stageRequest) => {
|
|
297
|
+
const { handleRequestStage } = await loadVinextRequestStage();
|
|
298
|
+
return handleRequestStage(stageRequest, env, context, dispatchResponseStage);
|
|
299
|
+
};
|
|
300
|
+
const { handleResponseStage } = await loadVinextResponseStage();
|
|
301
|
+
return handleResponseStage(request, env, context, invocation.props, dispatchRequestStage, invocation.options);
|
|
302
|
+
}
|
|
303
|
+
/** Cache-bearing entrypoint. Workers Cache HITs bypass this class entirely. */
|
|
304
|
+
var VinextCachedResponse = class extends WorkerEntrypoint {
|
|
305
|
+
async fetch(request) {
|
|
306
|
+
const context = withWorkerHostRuntime(this.ctx, this.env);
|
|
307
|
+
const invocation = getResponseStageInvocation(context.props, "shared");
|
|
308
|
+
if (!invocation) return stampResponseStageBuildIdentity(new Response("Invalid vinext response-stage invocation", {
|
|
309
|
+
status: 400,
|
|
310
|
+
headers: { "Cache-Control": "no-store" }
|
|
311
|
+
}));
|
|
312
|
+
if (invocation.expectedResponseStageBuildIdentity !== void 0 && invocation.expectedResponseStageBuildIdentity !== getVinextCdnBuildIdentity()) return stampResponseStageBuildIdentity(responseStageUnavailable());
|
|
313
|
+
const restored = restoreResponseStageRequest(request, invocation.requestUrl, invocation.requestMethod);
|
|
314
|
+
const response = await invokeResponseStage(restored.request, this.env, context, invocation);
|
|
315
|
+
return stampResponseStageBuildIdentity(restored.didAccessRequestCf() || hasTaggedCustomVary(response) ? preventResponseCaching(response) : response);
|
|
316
|
+
}
|
|
317
|
+
async purge(options) {
|
|
318
|
+
const cache = Reflect.get(this.ctx, "cache");
|
|
319
|
+
if (!hasPurge(cache)) return void 0;
|
|
320
|
+
return cache.purge(options);
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
/** Uncached response entrypoint. Bypass and probe renders execute only here. */
|
|
324
|
+
var VinextUncachedResponse = class extends WorkerEntrypoint {
|
|
325
|
+
async fetch(request) {
|
|
326
|
+
const context = withResponseStagePurge(withWorkerHostRuntime(this.ctx, this.env));
|
|
327
|
+
const invocation = getResponseStageInvocation(context.props, "bypass");
|
|
328
|
+
if (!invocation) return stampResponseStageBuildIdentity(new Response("Invalid vinext response-stage invocation", {
|
|
329
|
+
status: 400,
|
|
330
|
+
headers: { "Cache-Control": "no-store" }
|
|
331
|
+
}));
|
|
332
|
+
if (invocation.expectedResponseStageBuildIdentity !== void 0 && invocation.expectedResponseStageBuildIdentity !== getVinextCdnBuildIdentity()) return stampResponseStageBuildIdentity(responseStageUnavailable());
|
|
333
|
+
return stampResponseStageBuildIdentity(await invokeResponseStage(request, this.env, context, invocation));
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
/** Uncached gateway: request routing and middleware always execute here. */
|
|
337
|
+
var cdn_adapter_worker_default = { async fetch(request, env, context) {
|
|
338
|
+
request = stripUntrustedTransportHeaders(request);
|
|
339
|
+
const sharedResponseStageProvenance = crypto.randomUUID();
|
|
340
|
+
const stageContext = withResponseStagePurge(withWorkerHostRuntime(context, env));
|
|
341
|
+
const dispatchResponseStage = async (stageRequest, props, options) => {
|
|
342
|
+
const expectedResponseStageBuildIdentity = getVinextCdnBuildIdentity();
|
|
343
|
+
const invocation = {
|
|
344
|
+
...expectedResponseStageBuildIdentity === null ? {} : { expectedResponseStageBuildIdentity },
|
|
345
|
+
options,
|
|
346
|
+
props,
|
|
347
|
+
requestMethod: stageRequest.method,
|
|
348
|
+
requestUrl: stageRequest.url
|
|
349
|
+
};
|
|
350
|
+
const usesSharedCache = options.cache === "shared";
|
|
351
|
+
try {
|
|
352
|
+
const serializedInvocation = JSON.stringify({
|
|
353
|
+
...invocation,
|
|
354
|
+
options: expectedResponseStageBuildIdentity === null ? options : {
|
|
355
|
+
...options,
|
|
356
|
+
cache: RESPONSE_STAGE_WIRE_CACHE[options.cache]
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
const binding = getResponseStageBinding(stageContext, usesSharedCache ? CACHED_RESPONSE_STAGE_EXPORT : UNCACHED_RESPONSE_STAGE_EXPORT, serializedInvocation);
|
|
360
|
+
if (!binding) return responseStageUnavailable();
|
|
361
|
+
const entrypointRequest = usesSharedCache ? await createCacheFacingRequest(stageRequest, serializedInvocation) : stageRequest;
|
|
362
|
+
const response = validateResponseStageBuildIdentity(await binding.fetch(entrypointRequest));
|
|
363
|
+
return usesSharedCache ? markSharedResponseStage(response, sharedResponseStageProvenance, props, true) : response;
|
|
364
|
+
} catch (error) {
|
|
365
|
+
if (isResponseStageReadinessRequest(stageRequest)) return responseStageUnavailable();
|
|
366
|
+
throw error;
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
const { handleRequestStage } = await loadVinextRequestStage();
|
|
370
|
+
return finalizeGatewayResponse(await handleRequestStage(request, env, stageContext, dispatchResponseStage), sharedResponseStageProvenance);
|
|
371
|
+
} };
|
|
372
|
+
//#endregion
|
|
373
|
+
export { VinextCachedResponse, VinextUncachedResponse, cdn_adapter_worker_default as default };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
//#region src/cache/cdn-build-id.d.ts
|
|
2
|
+
/** Build identity stamped by the Cloudflare CDN adapter on page responses. */
|
|
3
|
+
export declare const VINEXT_CDN_BUILD_ID_HEADER = "X-Vinext-Build-Id";
|
|
4
|
+
/** Opaque identity shared by every server entry emitted by one vite build. */
|
|
5
|
+
export declare function getVinextCdnBuildIdentity(): string | null;
|
|
6
|
+
//#endregion
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
//#region src/cache/cdn-build-id.ts
|
|
2
|
+
/** Build identity stamped by the Cloudflare CDN adapter on page responses. */
|
|
3
|
+
const VINEXT_CDN_BUILD_ID_HEADER = "X-Vinext-Build-Id";
|
|
4
|
+
/** Opaque identity shared by every server entry emitted by one vite build. */
|
|
5
|
+
function getVinextCdnBuildIdentity() {
|
|
6
|
+
return process.env.__VINEXT_RSC_BUILD_IDENTITY || process.env.__VINEXT_BUILD_ID || null;
|
|
7
|
+
}
|
|
8
|
+
//#endregion
|
|
9
|
+
export { VINEXT_CDN_BUILD_ID_HEADER, getVinextCdnBuildIdentity };
|