@vinext/cloudflare 1.1.0 → 1.1.1
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/dist/cache/cdn-adapter.worker.js +59 -7
- package/dist/cache/config.d.ts +3 -3
- package/dist/cache/config.js +1 -1
- package/dist/cache/kv-key.js +21 -2
- package/dist/cache/response-store-adapter.worker.js +70 -9
- package/dist/cache/response-store-cdn.runtime.js +4 -1
- package/dist/cache/response-store-data.runtime.d.ts +4 -0
- package/dist/cache/response-store-data.runtime.js +8 -1
- package/dist/cache/static-assets-adapter.build.js +5 -4
- package/dist/cache/static-assets-adapter.runtime.d.ts +3 -1
- package/dist/cache/static-assets-adapter.runtime.js +23 -2
- package/dist/cdn-warm.d.ts +0 -3
- package/dist/cdn-warm.js +3 -16
- package/dist/cli.js +0 -1
- package/dist/deploy-help.js +0 -2
- package/dist/deploy.d.ts +0 -3
- package/dist/deploy.js +1 -3
- package/dist/worker-deployment-url.d.ts +3 -1
- package/dist/worker-deployment-url.js +4 -2
- package/package.json +4 -4
|
@@ -2,7 +2,7 @@ import { VINEXT_CDN_BUILD_ID_HEADER, getVinextCdnBuildIdentity } from "./cdn-bui
|
|
|
2
2
|
import { SHARED_RESPONSE_STAGE_HEADER, finalizeGatewayResponse } from "./browser-cache-policy.js";
|
|
3
3
|
import { hasCacheMethod, invalidateOrPurge } from "./workers-cache-invalidation.js";
|
|
4
4
|
import { VINEXT_PRERENDER_READINESS_PATH, VINEXT_RSC_VARY_HEADER } from "vinext/internal/server/headers";
|
|
5
|
-
import { isNonCacheableCacheControl } from "vinext/shims/cdn-cache";
|
|
5
|
+
import { isNonCacheableCacheControl, splitCacheControlDirectives } from "vinext/shims/cdn-cache";
|
|
6
6
|
import { WorkerEntrypoint } from "cloudflare:workers";
|
|
7
7
|
import "vinext/internal/server/cloudflare-workers-tracing";
|
|
8
8
|
import { loadVinextRequestStage } from "vinext/server/request-stage";
|
|
@@ -183,18 +183,63 @@ function isAppPageDocumentInvocation(invocation) {
|
|
|
183
183
|
const props = invocation.props;
|
|
184
184
|
return props !== null && typeof props === "object" && Reflect.get(props, "kind") === "app-page" && Reflect.get(props, "isRscRequest") === false;
|
|
185
185
|
}
|
|
186
|
+
/** A Pages page render, as an HTML document or a `_next/data` payload. */
|
|
187
|
+
function isPagesPageInvocation(invocation) {
|
|
188
|
+
const props = invocation.props;
|
|
189
|
+
if (props === null || typeof props !== "object") return false;
|
|
190
|
+
const kind = Reflect.get(props, "kind");
|
|
191
|
+
return kind === "pages-page" || kind === "hybrid-pages" && Reflect.get(props, "resourceKind") === "page";
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Workers Cache revalidates an entry that has a validator with a conditional
|
|
195
|
+
* request, and serves the stale entry meanwhile or fills an expired one.
|
|
196
|
+
*/
|
|
197
|
+
function revalidatesStoredEntry(request) {
|
|
198
|
+
return request.headers.has("If-None-Match") || request.headers.has("If-Modified-Since");
|
|
199
|
+
}
|
|
186
200
|
/**
|
|
187
|
-
* No client waits on a deployment warm-up
|
|
188
|
-
*
|
|
189
|
-
* Store's, and serves the stale entry meanwhile or fills an expired one.
|
|
201
|
+
* No client waits on a deployment warm-up or on a revalidation, as with the
|
|
202
|
+
* Response Store's.
|
|
190
203
|
*/
|
|
191
204
|
function rendersWholeDocument(request) {
|
|
192
|
-
return request.headers.get("user-agent") === WARMUP_USER_AGENT ||
|
|
205
|
+
return request.headers.get("user-agent") === WARMUP_USER_AGENT || revalidatesStoredEntry(request);
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* A conditional revalidation of a Pages page skips getStaticPaths(), so only a
|
|
209
|
+
* successful render may be stored with a validator. Bot renders carry an ETag
|
|
210
|
+
* whatever their status. A 304 answers a revalidation of a stored 200, and a
|
|
211
|
+
* response a shared cache may not store keeps its validators.
|
|
212
|
+
*/
|
|
213
|
+
function withPagesRevalidationValidator(response) {
|
|
214
|
+
if (response.status === 200) return withRevalidationValidator(response);
|
|
215
|
+
const policy = selectedCachePolicy(response);
|
|
216
|
+
if (response.status === 304 || policy !== null && forbidsSharedStorage(policy) || !response.headers.has("ETag") && !response.headers.has("Last-Modified")) return response;
|
|
217
|
+
const headers = new Headers(response.headers);
|
|
218
|
+
headers.delete("ETag");
|
|
219
|
+
headers.delete("Last-Modified");
|
|
220
|
+
return new Response(response.body, {
|
|
221
|
+
headers,
|
|
222
|
+
status: response.status,
|
|
223
|
+
statusText: response.statusText
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* `no-cache` and a field-qualified `private` still let a shared cache store the
|
|
228
|
+
* response (RFC 9111 sections 5.2.2.4 and 5.2.2.7).
|
|
229
|
+
*/
|
|
230
|
+
function forbidsSharedStorage(policy) {
|
|
231
|
+
return splitCacheControlDirectives(policy).some((directive) => {
|
|
232
|
+
const normalized = directive.toLowerCase();
|
|
233
|
+
return normalized === "private" || normalized.split("=", 1)[0].trim() === "no-store";
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
function selectedCachePolicy(response) {
|
|
237
|
+
return response.headers.get("Cloudflare-CDN-Cache-Control") ?? response.headers.get("CDN-Cache-Control") ?? response.headers.get("Cache-Control");
|
|
193
238
|
}
|
|
194
239
|
/** Give a cacheable document a validator, so Workers Cache revalidates it conditionally. */
|
|
195
240
|
function withRevalidationValidator(response) {
|
|
196
241
|
if (response.headers.has("ETag") || response.headers.has("Last-Modified")) return response;
|
|
197
|
-
const policy =
|
|
242
|
+
const policy = selectedCachePolicy(response);
|
|
198
243
|
if (policy === null || isNonCacheableCacheControl(policy)) return response;
|
|
199
244
|
const headers = new Headers(response.headers);
|
|
200
245
|
headers.set("Last-Modified", (/* @__PURE__ */ new Date()).toUTCString());
|
|
@@ -319,14 +364,21 @@ var VinextCachedResponse = class extends WorkerEntrypoint {
|
|
|
319
364
|
if (invocation.expectedResponseStageBuildIdentity !== void 0 && invocation.expectedResponseStageBuildIdentity !== getVinextCdnBuildIdentity()) return stampResponseStageBuildIdentity(responseStageUnavailable());
|
|
320
365
|
const restored = restoreResponseStageRequest(request, invocation.requestUrl, invocation.requestMethod);
|
|
321
366
|
const isAppPageDocument = isAppPageDocumentInvocation(invocation);
|
|
367
|
+
const isPagesPage = isPagesPageInvocation(invocation);
|
|
322
368
|
const response = await invokeResponseStage(restored.request, this.env, context, isAppPageDocument && rendersWholeDocument(request) ? {
|
|
323
369
|
...invocation,
|
|
324
370
|
options: {
|
|
325
371
|
...invocation.options,
|
|
326
372
|
renderWholeDocument: true
|
|
327
373
|
}
|
|
374
|
+
} : isPagesPage && revalidatesStoredEntry(request) ? {
|
|
375
|
+
...invocation,
|
|
376
|
+
options: {
|
|
377
|
+
...invocation.options,
|
|
378
|
+
regeneratesStoredEntry: true
|
|
379
|
+
}
|
|
328
380
|
} : invocation);
|
|
329
|
-
return stampResponseStageBuildIdentity(restored.didAccessRequestCf() || hasTaggedCustomVary(response) ? preventResponseCaching(response) : isAppPageDocument ? withRevalidationValidator(response) : response);
|
|
381
|
+
return stampResponseStageBuildIdentity(restored.didAccessRequestCf() || hasTaggedCustomVary(response) ? preventResponseCaching(response) : isAppPageDocument ? withRevalidationValidator(response) : isPagesPage ? withPagesRevalidationValidator(response) : response);
|
|
330
382
|
}
|
|
331
383
|
async purge(options) {
|
|
332
384
|
const cache = Reflect.get(this.ctx, "cache");
|
package/dist/cache/config.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ type ResponseStoreStorageOptions = {
|
|
|
6
6
|
};
|
|
7
7
|
export declare function createWorkersResponseStoreSelfContainedConfig(options: ResponseStoreStorageOptions): Promise<{
|
|
8
8
|
cache: {
|
|
9
|
-
enabled:
|
|
9
|
+
enabled: false;
|
|
10
10
|
};
|
|
11
11
|
exports: {
|
|
12
12
|
CacheMetadata: {
|
|
@@ -45,7 +45,7 @@ export declare function createWorkersResponseStoreServiceBindingConfig(options:
|
|
|
45
45
|
accountId: string | undefined;
|
|
46
46
|
serviceBindingWorker: {
|
|
47
47
|
cache: {
|
|
48
|
-
enabled:
|
|
48
|
+
enabled: false;
|
|
49
49
|
};
|
|
50
50
|
exports: {
|
|
51
51
|
CacheMetadata: {
|
|
@@ -84,7 +84,7 @@ export declare function createWorkersResponseStoreServiceBindingConfig(options:
|
|
|
84
84
|
env: {
|
|
85
85
|
RESPONSE_STORE: import("cf/config").WorkerBinding<{
|
|
86
86
|
cache: {
|
|
87
|
-
enabled:
|
|
87
|
+
enabled: false;
|
|
88
88
|
};
|
|
89
89
|
exports: {
|
|
90
90
|
CacheMetadata: {
|
package/dist/cache/config.js
CHANGED
|
@@ -10,7 +10,7 @@ const RESPONSE_STORE_SERVICE_ENTRYPOINT = "@cloudflare/workers-response-store/se
|
|
|
10
10
|
async function createResponseStoreWorkerConfig(options) {
|
|
11
11
|
const { bindings, exports } = await import("cf/config");
|
|
12
12
|
return {
|
|
13
|
-
cache: { enabled:
|
|
13
|
+
cache: { enabled: false },
|
|
14
14
|
exports: {
|
|
15
15
|
[CACHE_METADATA_CLASS]: {
|
|
16
16
|
...exports.durableObject({ storage: "sqlite" }),
|
package/dist/cache/kv-key.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
import { fnv1a64 } from "vinext/internal/utils/hash";
|
|
2
3
|
//#region src/cache/kv-key.ts
|
|
3
4
|
/** Key prefix for tag invalidation timestamps. */
|
|
@@ -27,19 +28,37 @@ function kvKeyByteLength(key) {
|
|
|
27
28
|
return KV_KEY_ENCODER.encode(key).length;
|
|
28
29
|
}
|
|
29
30
|
/**
|
|
31
|
+
* Digest an entry key that is too long to store verbatim. Logical keys can
|
|
32
|
+
* carry request input, so the digest must be collision resistant: two keys
|
|
33
|
+
* sharing a digest would share an entry.
|
|
34
|
+
*/
|
|
35
|
+
function digestEntryKey(key) {
|
|
36
|
+
return createHash("sha256").update(key).digest("hex");
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Digest a tag that is too long or unsafe to store verbatim. Tags sharing a
|
|
40
|
+
* digest share one invalidation marker, which can only invalidate extra
|
|
41
|
+
* entries. Markers are permanent, so a new digest would drop the
|
|
42
|
+
* invalidations already recorded for entries still in KV.
|
|
43
|
+
*/
|
|
44
|
+
function digestTag(tag) {
|
|
45
|
+
return fnv1a64(tag);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
30
48
|
* Keep short app prefixes readable, but bound the namespace portion so a
|
|
31
49
|
* hashed entry or tag key is always able to fit within Cloudflare KV's limit.
|
|
32
50
|
*/
|
|
33
51
|
function normalizeAppPrefix(appPrefix) {
|
|
34
52
|
if (!appPrefix) return "";
|
|
35
53
|
const prefix = `${appPrefix}:`;
|
|
36
|
-
if (kvKeyByteLength(`${prefix}cache:${HASHED_KEY_PREFIX}${
|
|
54
|
+
if (kvKeyByteLength(`${prefix}cache:${HASHED_KEY_PREFIX}${digestEntryKey("")}`) <= KV_KEY_MAX_BYTES) return prefix;
|
|
37
55
|
return `__app:${fnv1a64(appPrefix)}:`;
|
|
38
56
|
}
|
|
39
57
|
function buildStorageKey(prefix, categoryPrefix, logicalKey) {
|
|
40
58
|
const key = `${prefix}${categoryPrefix}${logicalKey}`;
|
|
41
59
|
if (!(categoryPrefix === TAG_PREFIX && logicalKey.includes(":")) && kvKeyByteLength(key) <= KV_KEY_MAX_BYTES) return key;
|
|
42
|
-
|
|
60
|
+
const digest = categoryPrefix === TAG_PREFIX ? digestTag(logicalKey) : digestEntryKey(logicalKey);
|
|
61
|
+
return `${prefix}${categoryPrefix}${HASHED_KEY_PREFIX}${digest}`;
|
|
43
62
|
}
|
|
44
63
|
/** Create the deterministic key namespace for runtime cache operations. */
|
|
45
64
|
function createKvKeySpace(appPrefix) {
|
|
@@ -2,10 +2,11 @@ import { SHARED_RESPONSE_STAGE_HEADER, finalizeGatewayResponse } from "./browser
|
|
|
2
2
|
import { captureResponseStoreDataRegeneration, runWithResponseStoreInvocation, setResponseStore } from "./response-store-data.runtime.js";
|
|
3
3
|
import { VINEXT_PARAMS_HEADER, VINEXT_RENDERED_PATH_AND_SEARCH_HEADER, VINEXT_SPECIAL_ERROR_STATUS_HEADER } from "vinext/internal/server/headers";
|
|
4
4
|
import { VINEXT_RSC_CONTENT_TYPE, VINEXT_RSC_VARY_HEADER as VINEXT_RSC_VARY_HEADER$1, applyRscCompatibilityIdHeader, applyRscDeploymentIdHeader, createCanonicalRscRequestHeaders, createCanonicalRscRequestUrl } from "vinext/internal/server/app-rsc-cache-busting";
|
|
5
|
-
import { isNonCacheableCacheControl } from "vinext/shims/cdn-cache";
|
|
5
|
+
import { hasSharedCacheCredentials, isNonCacheableCacheControl } from "vinext/shims/cdn-cache";
|
|
6
6
|
import { loadVinextRequestStage } from "vinext/server/request-stage";
|
|
7
7
|
import { loadVinextResponseStage } from "vinext/server/response-stage";
|
|
8
8
|
import { traceCachedResponseStart } from "vinext/internal/server/response-start-tracing";
|
|
9
|
+
import { isPageNotModified } from "vinext/internal/server/http-conditional";
|
|
9
10
|
//#region src/cache/response-store-adapter.worker.ts
|
|
10
11
|
const ROUTE_REVALIDATOR_ID = "vinext:response";
|
|
11
12
|
const RESPONSE_STORE_KEY_PARAM = "__workers_response_store";
|
|
@@ -41,7 +42,7 @@ function replayHeaders(request) {
|
|
|
41
42
|
function isReplayableInvocation(request, props) {
|
|
42
43
|
const method = request.method.toUpperCase();
|
|
43
44
|
if (method !== "GET" && method !== "HEAD") return false;
|
|
44
|
-
if (
|
|
45
|
+
if (hasSharedCacheCredentials(request.headers)) return false;
|
|
45
46
|
return !(props && typeof props === "object" && ["draftModeCookie", "middlewareCookieOverlay"].some((name) => name in props && Reflect.get(props, name) !== null));
|
|
46
47
|
}
|
|
47
48
|
function prepareInvocation(request, props) {
|
|
@@ -96,6 +97,7 @@ function createVinextResponseStoreOptions(configuration) {
|
|
|
96
97
|
const invocation = parseInvocation(input.args.at(-1));
|
|
97
98
|
const response = await invokeResponseStage(restoreRequest(invocation), invocation.props, env, ctx, {
|
|
98
99
|
cache: "shared",
|
|
100
|
+
regeneratesStoredEntry: true,
|
|
99
101
|
renderWholeDocument: true
|
|
100
102
|
});
|
|
101
103
|
if (!isCacheable(response)) {
|
|
@@ -160,7 +162,59 @@ function isStoredErrorStatus(status) {
|
|
|
160
162
|
}
|
|
161
163
|
function isCacheable(response) {
|
|
162
164
|
const policy = response.headers.get("Cloudflare-CDN-Cache-Control") ?? response.headers.get("CDN-Cache-Control") ?? response.headers.get("Cache-Control");
|
|
163
|
-
return response.status >= 200 && (response.status < 400 || isStoredErrorStatus(response.status)) && policy !== null && !isNonCacheableCacheControl(policy);
|
|
165
|
+
return response.status >= 200 && response.status !== 304 && (response.status < 400 || isStoredErrorStatus(response.status)) && policy !== null && !isNonCacheableCacheControl(policy);
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* A page's 304 headers. Next.js sends the 304 before it sets the payload's
|
|
169
|
+
* headers, so it carries no representation or body framing headers.
|
|
170
|
+
*/
|
|
171
|
+
function notModifiedHeaders(source) {
|
|
172
|
+
const headers = new Headers(source);
|
|
173
|
+
for (const name of [
|
|
174
|
+
"Content-Encoding",
|
|
175
|
+
"Content-Length",
|
|
176
|
+
"Content-Type",
|
|
177
|
+
"Transfer-Encoding"
|
|
178
|
+
]) headers.delete(name);
|
|
179
|
+
return headers;
|
|
180
|
+
}
|
|
181
|
+
/** Send the 304 for a page whose ETag matched the request's If-None-Match. */
|
|
182
|
+
function notModifiedResponse(response) {
|
|
183
|
+
response.body?.cancel().catch(() => {});
|
|
184
|
+
return new Response(null, {
|
|
185
|
+
headers: notModifiedHeaders(response.headers),
|
|
186
|
+
status: 304
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Whether Next.js sends this stored response through `sendRenderResult`, which
|
|
191
|
+
* answers a conditional request by the page's ETag. It sends every App page
|
|
192
|
+
* that way. A Pages redirect, and a Pages data request's notFound, are sent
|
|
193
|
+
* without an ETag, as is a route handler's response. A Pages API response is
|
|
194
|
+
* never stored.
|
|
195
|
+
*/
|
|
196
|
+
function answersConditionalRequest(responseStageProps, status) {
|
|
197
|
+
if (responseStageProps === null || typeof responseStageProps !== "object") return false;
|
|
198
|
+
const kind = Reflect.get(responseStageProps, "kind");
|
|
199
|
+
if (kind === "app-page" || kind === "app-not-found") return true;
|
|
200
|
+
let isDataRequest;
|
|
201
|
+
if (kind === "hybrid-pages") {
|
|
202
|
+
if (Reflect.get(responseStageProps, "resourceKind") !== "page") return false;
|
|
203
|
+
isDataRequest = Reflect.get(responseStageProps, "isDataRequest") === true;
|
|
204
|
+
} else if (kind === "pages-page") {
|
|
205
|
+
const renderOptions = Reflect.get(responseStageProps, "renderOptions");
|
|
206
|
+
isDataRequest = renderOptions !== null && typeof renderOptions === "object" && Reflect.get(renderOptions, "isDataReq") === true;
|
|
207
|
+
} else return false;
|
|
208
|
+
return status >= 200 && status < 300 || status === 404 && !isDataRequest;
|
|
209
|
+
}
|
|
210
|
+
/** Answer a stored page's conditional request as Next.js does for a cached page. */
|
|
211
|
+
function storedForRequest(stored, request, responseStageProps) {
|
|
212
|
+
const etag = stored.headers.get("ETag");
|
|
213
|
+
return etag && answersConditionalRequest(responseStageProps, stored.status) && isPageNotModified({
|
|
214
|
+
cacheControl: request.headers.get("Cache-Control") ?? void 0,
|
|
215
|
+
ifModifiedSince: request.headers.get("If-Modified-Since") ?? void 0,
|
|
216
|
+
ifNoneMatch: request.headers.get("If-None-Match") ?? void 0
|
|
217
|
+
}, etag) ? notModifiedResponse(stored) : stored;
|
|
164
218
|
}
|
|
165
219
|
async function readStoredResponse(key) {
|
|
166
220
|
try {
|
|
@@ -235,11 +289,11 @@ const handler = { async fetch(request, env, ctx) {
|
|
|
235
289
|
const key = await cacheRequest(invocation);
|
|
236
290
|
const stored = await readStoredResponse(key);
|
|
237
291
|
if (stored) {
|
|
238
|
-
if (!rscKey) return publicResponse(stored, "HIT", props);
|
|
292
|
+
if (!rscKey) return publicResponse(storedForRequest(stored, stageRequest, props), "HIT", props);
|
|
239
293
|
const storedRsc = await readStoredResponse(rscKey);
|
|
240
294
|
if (storedRsc) {
|
|
241
295
|
storedRsc.body?.cancel().catch(() => {});
|
|
242
|
-
return publicResponse(stored, "HIT", props);
|
|
296
|
+
return publicResponse(storedForRequest(stored, stageRequest, props), "HIT", props);
|
|
243
297
|
}
|
|
244
298
|
stored.body?.cancel().catch(() => {});
|
|
245
299
|
}
|
|
@@ -252,6 +306,7 @@ const handler = { async fetch(request, env, ctx) {
|
|
|
252
306
|
replayable: isReplayableInvocation(stageRequest, props),
|
|
253
307
|
serialized: options.cacheIdentity ? serializeInvocation(stageRequest, props) : serializedInvocation
|
|
254
308
|
});
|
|
309
|
+
const foreground = (response) => capture.notModified ? notModifiedResponse(response) : response;
|
|
255
310
|
if (capture.admittedResponse) {
|
|
256
311
|
ctx.waitUntil(capture.admittedResponse.then(async (admitted) => {
|
|
257
312
|
if (!isCacheable(admitted)) {
|
|
@@ -271,17 +326,23 @@ const handler = { async fetch(request, env, ctx) {
|
|
|
271
326
|
error: error instanceof Error ? error.message : String(error)
|
|
272
327
|
}));
|
|
273
328
|
}));
|
|
274
|
-
return publicResponse(rendered, "MISS", props, true);
|
|
329
|
+
if (!capture.notModified) return publicResponse(rendered, "MISS", props, true);
|
|
330
|
+
const notModified = notModifiedResponse(rendered);
|
|
331
|
+
const admitted = await capture.admittedResponse.catch(() => null);
|
|
332
|
+
return admitted ? publicResponse(new Response(null, {
|
|
333
|
+
headers: notModifiedHeaders(admitted.headers),
|
|
334
|
+
status: 304
|
|
335
|
+
}), "MISS", props) : publicResponse(notModified, "MISS", props, true);
|
|
275
336
|
}
|
|
276
337
|
if (!isCacheable(rendered)) {
|
|
277
338
|
capture?.rscData?.catch(() => {});
|
|
278
|
-
return publicResponse(rendered, "BYPASS", props);
|
|
339
|
+
return publicResponse(foreground(rendered), "BYPASS", props);
|
|
279
340
|
}
|
|
280
341
|
if (rscSeed && !capture?.rscData) {
|
|
281
342
|
await rendered.body?.cancel();
|
|
282
343
|
throw new Error("Vinext response-store warmup did not capture the App page RSC payload");
|
|
283
344
|
}
|
|
284
|
-
const [
|
|
345
|
+
const [foregroundBody, cacheBody] = rendered.body ? rendered.body.tee() : [null, null];
|
|
285
346
|
const cacheResponse = withoutRequestScopedHeaders(new Response(cacheBody, rendered), props);
|
|
286
347
|
try {
|
|
287
348
|
await responseStore.put(key, cacheResponse, {
|
|
@@ -324,7 +385,7 @@ const handler = { async fetch(request, env, ctx) {
|
|
|
324
385
|
}
|
|
325
386
|
});
|
|
326
387
|
}
|
|
327
|
-
return publicResponse(new Response(
|
|
388
|
+
return publicResponse(foreground(new Response(foregroundBody, rendered)), "MISS", props);
|
|
328
389
|
};
|
|
329
390
|
const { handleRequestStage } = await loadVinextRequestStage();
|
|
330
391
|
const sharedResponses = /* @__PURE__ */ new Map();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import createCloudflareCdnCacheAdapter from "./cdn-adapter.runtime.js";
|
|
2
|
-
import { captureResponseStoreRscData, deferResponseStoreAdmission } from "./response-store-data.runtime.js";
|
|
2
|
+
import { captureResponseStoreRscData, deferResponseStoreAdmission, deferResponseStoreNotModified } from "./response-store-data.runtime.js";
|
|
3
3
|
//#region src/cache/response-store-cdn.runtime.ts
|
|
4
4
|
/** Response Store owns page serving and SWR; vinext only emits admitted response policy. */
|
|
5
5
|
var ResponseStoreCdnCacheAdapter = class {
|
|
@@ -33,6 +33,9 @@ var ResponseStoreCdnCacheAdapter = class {
|
|
|
33
33
|
captureAppPageRscData(rscData) {
|
|
34
34
|
captureResponseStoreRscData(rscData);
|
|
35
35
|
}
|
|
36
|
+
deferNotModifiedResponse() {
|
|
37
|
+
return deferResponseStoreNotModified();
|
|
38
|
+
}
|
|
36
39
|
async revalidateTag() {}
|
|
37
40
|
};
|
|
38
41
|
function createResponseStoreCdnCacheAdapter(context) {
|
|
@@ -4,6 +4,8 @@ import { CacheHandler, CacheHandlerValue, IncrementalCacheValue } from "vinext/s
|
|
|
4
4
|
export type ResponseStoreInvocationCapture = {
|
|
5
5
|
admittedResponse?: Promise<Response>;
|
|
6
6
|
captureRscData?: boolean;
|
|
7
|
+
/** The render matched the request's If-None-Match; the adapter sends the 304. */
|
|
8
|
+
notModified?: boolean;
|
|
7
9
|
rscData?: Promise<ArrayBuffer>;
|
|
8
10
|
streamResponse?: boolean;
|
|
9
11
|
};
|
|
@@ -15,6 +17,8 @@ export declare function setResponseStore(store: WorkersResponseStore): void;
|
|
|
15
17
|
export declare function runWithResponseStoreInvocation<T>(serialized: string, replayable: boolean, callback: () => T, capture?: ResponseStoreInvocationCapture): T;
|
|
16
18
|
/** Retain the RSC side stream only when the outer response-store invocation requested it. */
|
|
17
19
|
export declare function captureResponseStoreRscData(rscData: Promise<ArrayBuffer>): void;
|
|
20
|
+
/** A shared invocation stores the page, so its adapter sends the page's 304. */
|
|
21
|
+
export declare function deferResponseStoreNotModified(): boolean;
|
|
18
22
|
/** Stream the foreground body while retaining an independent admission branch. */
|
|
19
23
|
export declare function deferResponseStoreAdmission(response: Response, complete: (response: Response) => Promise<Response>): Response | null;
|
|
20
24
|
/** Re-render an invocation and return the exact rewritten data-cache entry. */
|
|
@@ -33,6 +33,13 @@ function captureResponseStoreRscData(rscData) {
|
|
|
33
33
|
if (capture?.captureRscData) capture.rscData = rscData;
|
|
34
34
|
else rscData.catch(() => {});
|
|
35
35
|
}
|
|
36
|
+
/** A shared invocation stores the page, so its adapter sends the page's 304. */
|
|
37
|
+
function deferResponseStoreNotModified() {
|
|
38
|
+
const capture = invocationStorage.getStore()?.capture;
|
|
39
|
+
if (!capture) return false;
|
|
40
|
+
capture.notModified = true;
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
36
43
|
/** Stream the foreground body while retaining an independent admission branch. */
|
|
37
44
|
function deferResponseStoreAdmission(response, complete) {
|
|
38
45
|
const capture = invocationStorage.getStore()?.capture;
|
|
@@ -328,4 +335,4 @@ function createResponseStoreDataCacheAdapter() {
|
|
|
328
335
|
return new WorkersResponseStoreCacheHandler();
|
|
329
336
|
}
|
|
330
337
|
//#endregion
|
|
331
|
-
export { CACHE_FUNCTION_REVALIDATOR_ID, DATA_REVALIDATOR_ID, WorkersResponseStoreCacheHandler, captureResponseStoreDataRegeneration, captureResponseStoreRscData, createResponseStoreDataCacheAdapter as default, deferResponseStoreAdmission, runWithResponseStoreInvocation, setResponseStore };
|
|
338
|
+
export { CACHE_FUNCTION_REVALIDATOR_ID, DATA_REVALIDATOR_ID, WorkersResponseStoreCacheHandler, captureResponseStoreDataRegeneration, captureResponseStoreRscData, createResponseStoreDataCacheAdapter as default, deferResponseStoreAdmission, deferResponseStoreNotModified, runWithResponseStoreInvocation, setResponseStore };
|
|
@@ -2,8 +2,8 @@ import { STATIC_ASSET_CACHE_PATH } from "./static-assets-adapter.shared.js";
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { createHash } from "node:crypto";
|
|
5
|
-
import { readPrerenderManifest } from "vinext/internal/server/prerender-manifest";
|
|
6
|
-
import { appIsrCacheKey, pagesIsrCacheKey } from "vinext/internal/server/isr-cache";
|
|
5
|
+
import { isFallbackShellArtifactPath, readPrerenderManifest } from "vinext/internal/server/prerender-manifest";
|
|
6
|
+
import { appIsrCacheKey, appIsrFallbackShellKey, pagesIsrCacheKey } from "vinext/internal/server/isr-cache";
|
|
7
7
|
import { extractVinextNextDataJson } from "vinext/internal/client/vinext-next-data";
|
|
8
8
|
import { normalizePregeneratedPathname } from "vinext/internal/server/pregenerated-concrete-paths";
|
|
9
9
|
import { getAppRouteOutputPath, getOutputPath, getRscOutputPath } from "vinext/internal/utils/prerender-output-paths";
|
|
@@ -67,8 +67,9 @@ function finalizeStaticAssetsPrerenderOutput(root, options = {}) {
|
|
|
67
67
|
const pathname = route.path ?? route.route;
|
|
68
68
|
const cachePathname = normalizePregeneratedPathname(pathname);
|
|
69
69
|
if (route.router === "app") {
|
|
70
|
-
|
|
71
|
-
count += Number(writeCacheAsset(outputDir, index,
|
|
70
|
+
const appKey = isFallbackShellArtifactPath(pathname, route) ? appIsrFallbackShellKey : appIsrCacheKey;
|
|
71
|
+
count += Number(writeCacheAsset(outputDir, index, appKey(cachePathname, "html", manifest.buildId), "html", path.join(prerenderDir, getOutputPath(pathname, manifest.trailingSlash ?? false)), route));
|
|
72
|
+
count += Number(writeCacheAsset(outputDir, index, appKey(cachePathname, "rsc", manifest.buildId), "rsc", path.join(prerenderDir, getRscOutputPath(pathname)), route));
|
|
72
73
|
} else if (route.router === "pages") {
|
|
73
74
|
const sourcePath = path.join(prerenderDir, getOutputPath(pathname, manifest.trailingSlash ?? false));
|
|
74
75
|
if (!fs.existsSync(sourcePath)) continue;
|
|
@@ -9,9 +9,11 @@ export declare class StaticAssetsCacheAdapter implements CdnCacheAdapter {
|
|
|
9
9
|
readonly assets: AssetFetcher;
|
|
10
10
|
readonly hasPrerenderedPages = true;
|
|
11
11
|
readonly ownsBackgroundRevalidation = false;
|
|
12
|
-
private
|
|
12
|
+
private index;
|
|
13
|
+
private pendingIndex;
|
|
13
14
|
constructor(assets: AssetFetcher);
|
|
14
15
|
private loadIndex;
|
|
16
|
+
private fetchIndex;
|
|
15
17
|
get(key: string): Promise<CacheHandlerValue | null>;
|
|
16
18
|
set(_key: string, _data: IncrementalCacheValue | null, _ctx?: Record<string, unknown>): Promise<void>;
|
|
17
19
|
buildResponseHeaders(input: CdnCacheableHeaderInput): CdnResponseHeaders;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { STATIC_ASSET_CACHE_PATH } from "./static-assets-adapter.shared.js";
|
|
2
2
|
import { DefaultCdnCacheAdapter } from "vinext/shims/cdn-cache";
|
|
3
|
+
import { getRequestExecutionContext } from "vinext/shims/request-context";
|
|
3
4
|
//#region src/cache/static-assets-adapter.runtime.ts
|
|
4
5
|
async function cacheAssetId(key) {
|
|
5
6
|
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(key));
|
|
@@ -12,12 +13,32 @@ var StaticAssetsCacheAdapter = class {
|
|
|
12
13
|
assets;
|
|
13
14
|
hasPrerenderedPages = true;
|
|
14
15
|
ownsBackgroundRevalidation = false;
|
|
15
|
-
|
|
16
|
+
index;
|
|
17
|
+
pendingIndex;
|
|
16
18
|
constructor(assets) {
|
|
17
19
|
this.assets = assets;
|
|
18
20
|
}
|
|
19
21
|
loadIndex() {
|
|
20
|
-
|
|
22
|
+
if (this.index !== void 0) return Promise.resolve(this.index);
|
|
23
|
+
if (this.pendingIndex) return this.pendingIndex;
|
|
24
|
+
const ctx = getRequestExecutionContext();
|
|
25
|
+
if (!ctx) return this.fetchIndex();
|
|
26
|
+
const pending = this.fetchIndex().finally(() => {
|
|
27
|
+
if (this.pendingIndex === pending) this.pendingIndex = void 0;
|
|
28
|
+
});
|
|
29
|
+
this.pendingIndex = pending;
|
|
30
|
+
ctx.waitUntil(pending.catch(() => {}));
|
|
31
|
+
return pending;
|
|
32
|
+
}
|
|
33
|
+
async fetchIndex() {
|
|
34
|
+
const response = await this.assets.fetch(`https://vinext.invalid${STATIC_ASSET_CACHE_PATH}/index.json`);
|
|
35
|
+
if (!response.ok) {
|
|
36
|
+
response.body?.cancel().catch(() => {});
|
|
37
|
+
if (response.status === 404) this.index = null;
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
const value = await response.json();
|
|
41
|
+
return this.index = value !== null && typeof value === "object" ? value : null;
|
|
21
42
|
}
|
|
22
43
|
async get(key) {
|
|
23
44
|
const id = await cacheAssetId(key);
|
package/dist/cdn-warm.d.ts
CHANGED
|
@@ -43,7 +43,6 @@ export declare const DEFAULT_STAGED_READINESS_INTERVAL_MS = 1000;
|
|
|
43
43
|
export declare const DEFAULT_STAGED_READINESS_PHASE_TIMEOUT_MS = 120000;
|
|
44
44
|
export type PrerenderCdnWarmOptions = Omit<CdnWarmOptions, "paths"> & {
|
|
45
45
|
root: string;
|
|
46
|
-
includeFallbackShells?: boolean;
|
|
47
46
|
/** Use the manifest build identity unless the configured adapter cannot expose it. */
|
|
48
47
|
validateBuildIdentity?: boolean;
|
|
49
48
|
};
|
|
@@ -92,13 +91,11 @@ export type PrerenderWarmPlan = {
|
|
|
92
91
|
};
|
|
93
92
|
type PrerenderWarmPlanOptions = {
|
|
94
93
|
includeCanonicalRsc?: boolean;
|
|
95
|
-
includeFallbackShells?: boolean;
|
|
96
94
|
strict?: boolean;
|
|
97
95
|
};
|
|
98
96
|
export declare function createPrerenderWarmPlan(root: string, manifest: PrerenderPathManifest, options?: PrerenderWarmPlanOptions): PrerenderWarmPlan;
|
|
99
97
|
export declare function readPrerenderWarmPlan(root: string, options?: PrerenderWarmPlanOptions): PrerenderWarmPlan;
|
|
100
98
|
export declare function readPrerenderWarmPaths(root: string, options?: {
|
|
101
|
-
includeFallbackShells?: boolean;
|
|
102
99
|
strict?: boolean;
|
|
103
100
|
}): string[];
|
|
104
101
|
export declare function getWarmPathsFromPrerenderManifest(manifest: PrerenderManifest, options?: PrerenderedPathSelectionOptions): string[];
|
package/dist/cdn-warm.js
CHANGED
|
@@ -6,7 +6,7 @@ import { randomUUID } from "node:crypto";
|
|
|
6
6
|
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
7
7
|
import { VINEXT_CACHE_HEADER, VINEXT_PRERENDER_READINESS_HEADER, VINEXT_PRERENDER_READINESS_PATH, VINEXT_PRERENDER_SECRET_HEADER } from "vinext/internal/server/headers";
|
|
8
8
|
import { PRERENDER_PATHS_MANIFEST } from "vinext/internal/build/prerender-paths";
|
|
9
|
-
import { getPrerenderedConcretePaths
|
|
9
|
+
import { getPrerenderedConcretePaths } from "vinext/internal/server/prerender-manifest";
|
|
10
10
|
import { VINEXT_RSC_BUILD_ID_HEADER, VINEXT_RSC_CONTENT_TYPE, VINEXT_RSC_VARY_HEADER as VINEXT_RSC_VARY_HEADER$1, createCanonicalLoadingShellRscRequestHeaders, createCanonicalRscRequestHeaders, createCanonicalRscRequestUrl, createRscRequestUrl } from "vinext/internal/server/app-rsc-cache-busting";
|
|
11
11
|
import { isNonCacheableCacheControl } from "vinext/shims/cdn-cache";
|
|
12
12
|
import { normalizePathTrailingSlash } from "vinext/shims/url-utils";
|
|
@@ -73,17 +73,7 @@ function createPrerenderWarmPlan(root, manifest, options) {
|
|
|
73
73
|
const supportsCanonicalRsc = (manifest.responseVary === "verbatim" || options?.includeCanonicalRsc === true) && manifest.rscPaths !== void 0 && manifest.rscBuildId !== void 0;
|
|
74
74
|
const applyConfig = (pathname) => applyWarmPathConfig(pathname, manifest);
|
|
75
75
|
const routePatterns = manifest.routePatterns ? Object.fromEntries(Object.entries(manifest.routePatterns).map(([pathname, route]) => [pathname.includes("/_next/data/") ? pathname : applyConfig(pathname), route])) : void 0;
|
|
76
|
-
|
|
77
|
-
if (options?.includeFallbackShells === true) {
|
|
78
|
-
const prerenderManifest = readPrerenderManifest(path.join(root, "dist", "server", "vinext-prerender.json"));
|
|
79
|
-
if (!prerenderManifest) console.warn("[vinext] CDN warmup fallback shells requested, but prerender manifest not found; warming build-discovered paths only.");
|
|
80
|
-
else if (!prerenderManifest.buildId || prerenderManifest.buildId !== manifest.buildId) {
|
|
81
|
-
const message = "[vinext] CDN warmup skipped: prerender manifest buildId does not match dist/server/BUILD_ID.";
|
|
82
|
-
if (options.strict) throw new Error(message);
|
|
83
|
-
console.warn(message);
|
|
84
|
-
htmlPaths = [];
|
|
85
|
-
} else htmlPaths = getPrerenderedConcretePaths(prerenderManifest, { includeFallbackShells: true }).filter((pathname) => pathname.startsWith("/")).filter((pathname) => !manifest.excludedWarmPaths?.includes(pathname)).map(applyConfig);
|
|
86
|
-
}
|
|
76
|
+
const htmlPaths = pathPlan.paths;
|
|
87
77
|
return {
|
|
88
78
|
...manifest.appPaths ? { appPaths: manifest.appPaths.map(applyConfig) } : {},
|
|
89
79
|
buildId: manifest.buildId,
|
|
@@ -959,10 +949,7 @@ async function warmCdnCache(options) {
|
|
|
959
949
|
return result;
|
|
960
950
|
}
|
|
961
951
|
async function warmCdnCacheFromPrerender(options) {
|
|
962
|
-
const plan = readPrerenderWarmPlan(options.root, {
|
|
963
|
-
includeFallbackShells: options.includeFallbackShells,
|
|
964
|
-
strict: options.strict
|
|
965
|
-
});
|
|
952
|
+
const plan = readPrerenderWarmPlan(options.root, { strict: options.strict });
|
|
966
953
|
const warmPlan = {
|
|
967
954
|
deploymentId: plan.deploymentId,
|
|
968
955
|
loadingShellPaths: plan.loadingShellPaths,
|
package/dist/cli.js
CHANGED
|
@@ -62,7 +62,6 @@ async function deployCommand() {
|
|
|
62
62
|
dangerouslyPromoteOnCdnWarmError: parsed.dangerouslyPromoteOnCdnWarmError,
|
|
63
63
|
warmCdnPromote: parsed.warmCdnPromote,
|
|
64
64
|
warmCdnPromotionDelay: parsed.warmCdnPromotionDelay,
|
|
65
|
-
warmCdnIncludeFallbacks: parsed.warmCdnIncludeFallbacks,
|
|
66
65
|
experimentalTPR: parsed.experimentalTPR,
|
|
67
66
|
tprCoverage: parsed.tprCoverage,
|
|
68
67
|
tprLimit: parsed.tprLimit,
|
package/dist/deploy-help.js
CHANGED
|
@@ -67,8 +67,6 @@ function formatDeployHelp() {
|
|
|
67
67
|
verified (never bypasses --warm-cache-certify)
|
|
68
68
|
--warm-cache-promotion-delay <ms>
|
|
69
69
|
Delay before promotion after warmup (default: 15000)
|
|
70
|
-
--warm-cache-include-fallbacks
|
|
71
|
-
Also warm PPR fallback-shell placeholder paths
|
|
72
70
|
-h, --help Show this help
|
|
73
71
|
|
|
74
72
|
Traffic-aware warming:
|
package/dist/deploy.d.ts
CHANGED
|
@@ -62,8 +62,6 @@ export type DeployOptions = {
|
|
|
62
62
|
warmCdnPromote?: boolean;
|
|
63
63
|
/** Delay between successful warmup and promotion in milliseconds */
|
|
64
64
|
warmCdnPromotionDelay?: number;
|
|
65
|
-
/** Include PPR fallback-shell placeholder paths during CDN warmup */
|
|
66
|
-
warmCdnIncludeFallbacks?: boolean;
|
|
67
65
|
/** Select CDN pre-warm routes using traffic analytics */
|
|
68
66
|
experimentalTPR?: boolean;
|
|
69
67
|
/** TPR: traffic coverage percentage target (0–100, default: 90) */
|
|
@@ -104,7 +102,6 @@ export declare function parseDeployArgs(args: string[]): {
|
|
|
104
102
|
dangerouslyPromoteOnCdnWarmError: boolean;
|
|
105
103
|
warmCdnPromote: boolean;
|
|
106
104
|
warmCdnPromotionDelay: number | undefined;
|
|
107
|
-
warmCdnIncludeFallbacks: boolean;
|
|
108
105
|
experimentalTPR: boolean;
|
|
109
106
|
tprCoverage: number | undefined;
|
|
110
107
|
tprLimit: number | undefined;
|
package/dist/deploy.js
CHANGED
|
@@ -228,7 +228,7 @@ function parseDeployArgs(args) {
|
|
|
228
228
|
values["warm-cache-readiness-probe-delay"] ??= values["warm-cdn-readiness-probe-delay"];
|
|
229
229
|
values["warm-cache-no-promote"] ||= values["warm-cdn-no-promote"];
|
|
230
230
|
values["warm-cache-promotion-delay"] ??= values["warm-cdn-promotion-delay"];
|
|
231
|
-
values["warm-cache-include-fallbacks"]
|
|
231
|
+
if (values["warm-cache-include-fallbacks"] || values["warm-cdn-include-fallbacks"]) console.warn("[vinext] --warm-cache-include-fallbacks is deprecated and has no effect.");
|
|
232
232
|
values["traffic-aware-warm-cache"] ||= values["experimental-traffic-aware-warm-cache"];
|
|
233
233
|
const trafficAwareWarmCache = values["traffic-aware-warm-cache"] || values["experimental-tpr"];
|
|
234
234
|
const warming = values["warm-cache"] || trafficAwareWarmCache;
|
|
@@ -271,7 +271,6 @@ function parseDeployArgs(args) {
|
|
|
271
271
|
dangerouslyPromoteOnCdnWarmError: values["dangerously-promote-on-warm-cache-error"],
|
|
272
272
|
warmCdnPromote: !values["no-promote"] && !values["warm-cache-no-promote"],
|
|
273
273
|
warmCdnPromotionDelay: values["warm-cache-promotion-delay"] === void 0 ? void 0 : validatePromotionDelay(parseNonNegativeIntegerArg(values["warm-cache-promotion-delay"], "--warm-cache-promotion-delay"), values["warm-cache-promotion-delay"]),
|
|
274
|
-
warmCdnIncludeFallbacks: values["warm-cache-include-fallbacks"],
|
|
275
274
|
experimentalTPR: trafficAwareWarmCache,
|
|
276
275
|
tprCoverage: parseIntArg("traffic-aware-coverage", values["traffic-aware-coverage"] ?? values["tpr-coverage"]),
|
|
277
276
|
tprLimit: parseIntArg("traffic-aware-limit", values["traffic-aware-limit"] ?? values["tpr-limit"]),
|
|
@@ -1328,7 +1327,6 @@ async function deploy(options) {
|
|
|
1328
1327
|
};
|
|
1329
1328
|
return createPrerenderWarmPlan(root, discovery, {
|
|
1330
1329
|
includeCanonicalRsc: hasCanonicalRscWarmup,
|
|
1331
|
-
includeFallbackShells: options.warmCdnIncludeFallbacks,
|
|
1332
1330
|
strict: options.warmCdnCertify === true || !options.dangerouslyPromoteOnCdnWarmError
|
|
1333
1331
|
});
|
|
1334
1332
|
},
|
|
@@ -3,7 +3,9 @@ export declare function parseWorkerDeploymentUrl(output: string): string | null;
|
|
|
3
3
|
/**
|
|
4
4
|
* Parse the concrete hostname that Wrangler reports after applying Worker
|
|
5
5
|
* triggers. Catch-all routes are valid warmup targets even though they are not
|
|
6
|
-
* canonical deployment URLs for general CLI reporting.
|
|
6
|
+
* canonical deployment URLs for general CLI reporting. Warmup requests carry the
|
|
7
|
+
* prerender secret, so an `http://`-only route (which Cloudflare does not match
|
|
8
|
+
* for HTTPS requests) is never used as a target.
|
|
7
9
|
*/
|
|
8
10
|
export declare function parseCdnWarmupDeploymentUrl(output: string): string | null;
|
|
9
11
|
//#endregion
|
|
@@ -6,7 +6,9 @@ function parseWorkerDeploymentUrl(output) {
|
|
|
6
6
|
/**
|
|
7
7
|
* Parse the concrete hostname that Wrangler reports after applying Worker
|
|
8
8
|
* triggers. Catch-all routes are valid warmup targets even though they are not
|
|
9
|
-
* canonical deployment URLs for general CLI reporting.
|
|
9
|
+
* canonical deployment URLs for general CLI reporting. Warmup requests carry the
|
|
10
|
+
* prerender secret, so an `http://`-only route (which Cloudflare does not match
|
|
11
|
+
* for HTTPS requests) is never used as a target.
|
|
10
12
|
*/
|
|
11
13
|
function parseCdnWarmupDeploymentUrl(output) {
|
|
12
14
|
return parseCustomDomainUrl(output) ?? parseCatchAllWorkerRouteOrigin(output) ?? parseWorkersDevUrl(output);
|
|
@@ -17,7 +19,7 @@ function parseCatchAllWorkerRouteOrigin(output) {
|
|
|
17
19
|
if (!route || !route.endsWith("/*")) continue;
|
|
18
20
|
try {
|
|
19
21
|
const url = new URL(route.includes("://") ? route : `https://${route}`);
|
|
20
|
-
if (url.protocol !== "https:"
|
|
22
|
+
if (url.protocol !== "https:" || url.hostname.includes("*") || url.username !== "" || url.password !== "" || url.port !== "" || url.pathname !== "/*" || url.search !== "" || url.hash !== "") continue;
|
|
21
23
|
return url.origin;
|
|
22
24
|
} catch {
|
|
23
25
|
continue;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vinext/cloudflare",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "Cloudflare cache and image adapters for vinext.",
|
|
5
5
|
"homepage": "https://vinext.dev/",
|
|
6
6
|
"license": "MIT",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
}
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@cloudflare/workers-response-store": "1.1.
|
|
51
|
+
"@cloudflare/workers-response-store": "1.1.1"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"@types/node": "^25.9.2",
|
|
@@ -57,12 +57,12 @@
|
|
|
57
57
|
"vite": "npm:@voidzero-dev/vite-plus-core@1.1.0",
|
|
58
58
|
"vite-plus": "1.1.0",
|
|
59
59
|
"vitest": "5.0.3",
|
|
60
|
-
"vinext": "1.
|
|
60
|
+
"vinext": "1.2.0"
|
|
61
61
|
},
|
|
62
62
|
"peerDependencies": {
|
|
63
63
|
"@cloudflare/vite-plugin": "*",
|
|
64
64
|
"cf": "*",
|
|
65
|
-
"vinext": "^1.
|
|
65
|
+
"vinext": "^1.2.0"
|
|
66
66
|
},
|
|
67
67
|
"peerDependenciesMeta": {
|
|
68
68
|
"@cloudflare/vite-plugin": {
|