@vinext/cloudflare 1.0.0-beta.7 → 1.0.0-beta.9
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 +64 -22
- package/dist/cache/cdn-adapter-config.d.ts +16 -8
- package/dist/cache/cdn-adapter-config.js +57 -17
- package/dist/cache/cdn-adapter.d.ts +25 -24
- package/dist/cache/cdn-adapter.js +24 -20
- package/dist/cache/cdn-adapter.runtime.d.ts +9 -3
- package/dist/cache/cdn-adapter.runtime.js +63 -40
- 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 +4 -3
- package/dist/cache/cdn-build-id.js +5 -1
- package/dist/cache/kv-data-adapter.d.ts +25 -5
- package/dist/cache/kv-data-adapter.js +5 -1
- package/dist/cache/kv-data-adapter.runtime.d.ts +38 -4
- package/dist/cache/kv-data-adapter.runtime.js +115 -41
- package/dist/cache/kv-key.d.ts +4 -5
- package/dist/cache/response-store-adapter.d.ts +70 -0
- package/dist/cache/response-store-adapter.js +45 -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 +264 -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 +289 -0
- package/dist/cacheability-artifact.d.ts +2 -3
- package/dist/cacheability-artifact.js +27 -1
- package/dist/cacheability-manifest-limits.d.ts +3 -4
- package/dist/cacheability-manifest-limits.js +1 -1
- package/dist/cacheability-probe.d.ts +8 -9
- package/dist/cacheability-probe.js +255 -91
- package/dist/cdn-warm.d.ts +31 -25
- package/dist/cdn-warm.js +228 -54
- package/dist/deploy-config.d.ts +11 -12
- package/dist/deploy-config.js +8 -3
- package/dist/deploy-help.d.ts +3 -4
- package/dist/deploy-help.js +20 -14
- package/dist/deploy.d.ts +35 -28
- package/dist/deploy.js +204 -95
- package/dist/images/images-optimizer.d.ts +3 -4
- package/dist/prerender-kv-populate.d.ts +3 -4
- package/dist/prerender-kv-populate.js +3 -2
- package/dist/tpr.d.ts +26 -74
- package/dist/tpr.js +45 -603
- package/dist/utils/cache-control-metadata.d.ts +3 -4
- package/dist/version-deploy.d.ts +17 -17
- package/dist/version-deploy.js +10 -5
- package/dist/worker-deployment-url.d.ts +3 -4
- 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 +2 -3
- package/package.json +13 -11
|
@@ -135,7 +135,33 @@ function assertManifestModuleReachable(configPath) {
|
|
|
135
135
|
if (typeof main !== "string" || main.length === 0) throw new Error("Two-stage CDN warming requires a generated Wrangler main module.");
|
|
136
136
|
const mainPath = path.resolve(serverDirectory, main);
|
|
137
137
|
if (!fs.existsSync(mainPath) || !fs.lstatSync(mainPath).isFile()) throw new Error("Two-stage CDN warming could not find the generated Wrangler main module.");
|
|
138
|
-
|
|
138
|
+
const viteManifestPath = path.join(serverDirectory, ".vite", "manifest.json");
|
|
139
|
+
let viteManifest;
|
|
140
|
+
try {
|
|
141
|
+
viteManifest = JSON.parse(fs.readFileSync(viteManifestPath, "utf8"));
|
|
142
|
+
} catch (cause) {
|
|
143
|
+
throw new Error("Two-stage CDN warming could not read the generated Vite manifest.", { cause });
|
|
144
|
+
}
|
|
145
|
+
const normalizedMain = main.replace(/^\.\//, "");
|
|
146
|
+
const entryKey = Object.entries(viteManifest).find(([, entry]) => entry?.file === normalizedMain)?.[0];
|
|
147
|
+
const queue = entryKey ? [entryKey] : [];
|
|
148
|
+
const visited = /* @__PURE__ */ new Set();
|
|
149
|
+
let reachable = false;
|
|
150
|
+
while (queue.length > 0 && !reachable) {
|
|
151
|
+
const key = queue.shift();
|
|
152
|
+
if (visited.has(key)) continue;
|
|
153
|
+
visited.add(key);
|
|
154
|
+
const entry = viteManifest[key];
|
|
155
|
+
if (!entry || typeof entry.file !== "string") continue;
|
|
156
|
+
const modulePath = path.resolve(serverDirectory, entry.file);
|
|
157
|
+
if (fs.existsSync(modulePath) && fs.lstatSync(modulePath).isFile()) {
|
|
158
|
+
const relativeManifest = path.relative(path.dirname(modulePath), path.join(serverDirectory, CACHEABILITY_MANIFEST_MODULE)).split(path.sep).join("/");
|
|
159
|
+
const specifier = relativeManifest.startsWith(".") ? relativeManifest : `./${relativeManifest}`;
|
|
160
|
+
reachable = hasStaticModuleSpecifier(fs.readFileSync(modulePath, "utf8"), specifier);
|
|
161
|
+
}
|
|
162
|
+
for (const references of [entry.imports, entry.dynamicImports]) if (Array.isArray(references)) queue.push(...references.filter((value) => typeof value === "string"));
|
|
163
|
+
}
|
|
164
|
+
if (!reachable) throw new Error(`Two-stage CDN warming requires the generated Worker graph to statically import ${CACHEABILITY_MANIFEST_MODULE}.`);
|
|
139
165
|
}
|
|
140
166
|
/**
|
|
141
167
|
* Write the version-specific manifest into the built Worker artifact.
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
//#region src/cacheability-manifest-limits.d.ts
|
|
2
|
-
declare const MAX_CACHEABILITY_MANIFEST_BYTES: number;
|
|
3
|
-
declare function cacheabilityManifestByteLimitError(manifestBytes: number, limit?: number): Error;
|
|
4
|
-
//#endregion
|
|
5
|
-
export { MAX_CACHEABILITY_MANIFEST_BYTES, cacheabilityManifestByteLimitError };
|
|
2
|
+
export declare const MAX_CACHEABILITY_MANIFEST_BYTES: number;
|
|
3
|
+
export declare function cacheabilityManifestByteLimitError(manifestBytes: number, limit?: number): Error;
|
|
4
|
+
//#endregion
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
//#region src/cacheability-manifest-limits.ts
|
|
2
|
-
const MAX_CACHEABILITY_MANIFEST_BYTES =
|
|
2
|
+
const MAX_CACHEABILITY_MANIFEST_BYTES = 2097152;
|
|
3
3
|
function cacheabilityManifestByteLimitError(manifestBytes, limit = MAX_CACHEABILITY_MANIFEST_BYTES) {
|
|
4
4
|
return /* @__PURE__ */ new Error(`Two-stage CDN warming produced a ${manifestBytes}-byte route-pattern cacheability manifest; the limit is ${limit} bytes. Reduce the number or length of route patterns, or split the deployment before retrying.`);
|
|
5
5
|
}
|
|
@@ -2,10 +2,10 @@ import { CdnWarmTarget } from "./cdn-warm.js";
|
|
|
2
2
|
import { CacheabilityManifest } from "vinext/internal/server/cacheability-manifest";
|
|
3
3
|
import { PrerenderRoutePattern } from "vinext/internal/build/prerender-paths";
|
|
4
4
|
//#region src/cacheability-probe.d.ts
|
|
5
|
-
declare const DEFAULT_CACHEABILITY_PROBE_PHASE_TIMEOUT_MS = 120000;
|
|
6
|
-
declare const DEFAULT_CACHEABILITY_PROBE_RETRIES = 2;
|
|
7
|
-
declare const DEFAULT_CACHEABILITY_PROBE_RETRY_DELAY_MS = 1000;
|
|
8
|
-
type CacheabilityProbeProgress = {
|
|
5
|
+
export declare const DEFAULT_CACHEABILITY_PROBE_PHASE_TIMEOUT_MS = 120000;
|
|
6
|
+
export declare const DEFAULT_CACHEABILITY_PROBE_RETRIES = 2;
|
|
7
|
+
export declare const DEFAULT_CACHEABILITY_PROBE_RETRY_DELAY_MS = 1000;
|
|
8
|
+
export type CacheabilityProbeProgress = {
|
|
9
9
|
completed: number;
|
|
10
10
|
dynamic: number;
|
|
11
11
|
failed: number;
|
|
@@ -14,7 +14,7 @@ type CacheabilityProbeProgress = {
|
|
|
14
14
|
static: number;
|
|
15
15
|
total: number;
|
|
16
16
|
};
|
|
17
|
-
type CacheabilityProbeResult = {
|
|
17
|
+
export type CacheabilityProbeResult = {
|
|
18
18
|
cacheableTargets: CdnWarmTarget[];
|
|
19
19
|
classified: number;
|
|
20
20
|
dynamic: number;
|
|
@@ -25,8 +25,8 @@ type CacheabilityProbeResult = {
|
|
|
25
25
|
/** Paired representations admitted only if their own final warm render remains cacheable. */
|
|
26
26
|
speculativeTargets: CdnWarmTarget[];
|
|
27
27
|
};
|
|
28
|
-
declare function readPrerenderSecret(root: string): string;
|
|
29
|
-
declare function probeStagedWorkerCacheability(options: {
|
|
28
|
+
export declare function readPrerenderSecret(root: string): string;
|
|
29
|
+
export declare function probeStagedWorkerCacheability(options: {
|
|
30
30
|
buildId: string;
|
|
31
31
|
concurrency?: number;
|
|
32
32
|
expectedResponseBuildId?: string;
|
|
@@ -46,5 +46,4 @@ declare function probeStagedWorkerCacheability(options: {
|
|
|
46
46
|
maxBytes?: number;
|
|
47
47
|
};
|
|
48
48
|
}): Promise<CacheabilityProbeResult>;
|
|
49
|
-
//#endregion
|
|
50
|
-
export { CacheabilityProbeProgress, CacheabilityProbeResult, DEFAULT_CACHEABILITY_PROBE_PHASE_TIMEOUT_MS, DEFAULT_CACHEABILITY_PROBE_RETRIES, DEFAULT_CACHEABILITY_PROBE_RETRY_DELAY_MS, probeStagedWorkerCacheability, readPrerenderSecret };
|
|
49
|
+
//#endregion
|
|
@@ -6,13 +6,13 @@ import { Buffer } from "node:buffer";
|
|
|
6
6
|
import { cacheabilityManifestRouteKey, cacheabilityRequestIdentity, cacheabilityRoutePathname, normalizeCacheabilityRoutePathname } from "vinext/internal/server/cacheability-manifest";
|
|
7
7
|
import { randomUUID } from "node:crypto";
|
|
8
8
|
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
9
|
-
import { VINEXT_CACHEABILITY_PROBE_HEADER, VINEXT_CACHEABILITY_PROBE_QUERY_PARAM, VINEXT_PRERENDER_SECRET_HEADER } from "vinext/internal/server/headers";
|
|
9
|
+
import { VINEXT_CACHEABILITY_PROBE_HEADER, VINEXT_CACHEABILITY_PROBE_QUERY_PARAM, VINEXT_CACHEABILITY_PROBE_ROUTE_HEADER, VINEXT_PRERENDER_SECRET_HEADER } from "vinext/internal/server/headers";
|
|
10
10
|
//#region src/cacheability-probe.ts
|
|
11
11
|
const DEFAULT_CACHEABILITY_PROBE_PHASE_TIMEOUT_MS = 12e4;
|
|
12
12
|
const DEFAULT_CACHEABILITY_PROBE_RETRIES = 2;
|
|
13
13
|
const DEFAULT_CACHEABILITY_PROBE_RETRY_DELAY_MS = 1e3;
|
|
14
14
|
const DEFAULT_CACHEABILITY_PROBE_REQUEST_TIMEOUT_MS = 3e4;
|
|
15
|
-
const MAX_CACHEABILITY_PROBE_ENVELOPE_BYTES =
|
|
15
|
+
const MAX_CACHEABILITY_PROBE_ENVELOPE_BYTES = 65536;
|
|
16
16
|
function sharedPathPrefix(pathnames) {
|
|
17
17
|
if (pathnames.length === 0) return null;
|
|
18
18
|
let prefix = pathnames[0];
|
|
@@ -43,6 +43,9 @@ function compactManifestRoutePaths(route) {
|
|
|
43
43
|
function isProbeRouteState(value) {
|
|
44
44
|
return value === "static-candidate" || value === "dynamic" || value === "probe-failed";
|
|
45
45
|
}
|
|
46
|
+
function isRetryableProbeHttpStatus(status) {
|
|
47
|
+
return status === 404 || status === 408 || status === 429 || status >= 502;
|
|
48
|
+
}
|
|
46
49
|
function readPrerenderSecret(root) {
|
|
47
50
|
const manifestPath = path.join(root, "dist", "server", "vinext-server.json");
|
|
48
51
|
const parsed = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
|
|
@@ -51,11 +54,7 @@ function readPrerenderSecret(root) {
|
|
|
51
54
|
return secret;
|
|
52
55
|
}
|
|
53
56
|
async function readProbeEnvelope(response) {
|
|
54
|
-
if (!response.body)
|
|
55
|
-
reason: "probe returned invalid JSON",
|
|
56
|
-
state: "probe-failed",
|
|
57
|
-
version: 1
|
|
58
|
-
};
|
|
57
|
+
if (!response.body) throw new Error("probe returned invalid JSON");
|
|
59
58
|
const reader = response.body.getReader();
|
|
60
59
|
const chunks = [];
|
|
61
60
|
let bytes = 0;
|
|
@@ -74,30 +73,20 @@ async function readProbeEnvelope(response) {
|
|
|
74
73
|
}
|
|
75
74
|
chunks.push(result.value);
|
|
76
75
|
}
|
|
77
|
-
} catch (error) {
|
|
78
|
-
return {
|
|
79
|
-
reason: error instanceof Error ? error.message : String(error),
|
|
80
|
-
state: "probe-failed",
|
|
81
|
-
version: 1
|
|
82
|
-
};
|
|
83
76
|
} finally {
|
|
84
77
|
reader.releaseLock();
|
|
85
78
|
}
|
|
86
79
|
try {
|
|
87
80
|
return JSON.parse(Buffer.concat(chunks, bytes).toString("utf8"));
|
|
88
81
|
} catch {
|
|
89
|
-
|
|
90
|
-
reason: "probe returned invalid JSON",
|
|
91
|
-
state: "probe-failed",
|
|
92
|
-
version: 1
|
|
93
|
-
};
|
|
82
|
+
throw new Error("probe returned invalid JSON");
|
|
94
83
|
}
|
|
95
84
|
}
|
|
96
85
|
async function probeTarget(options) {
|
|
97
86
|
const headers = new Headers(options.headers);
|
|
98
87
|
for (const [name, value] of new Headers(options.target.headers)) headers.set(name, value);
|
|
99
|
-
headers.set("Cache-Control", "no-cache");
|
|
100
88
|
headers.set(VINEXT_CACHEABILITY_PROBE_HEADER, "1");
|
|
89
|
+
if (options.target.route) headers.set(VINEXT_CACHEABILITY_PROBE_ROUTE_HEADER, encodeURIComponent(JSON.stringify([options.target.route.kind, options.target.route.pattern])));
|
|
101
90
|
headers.set(VINEXT_PRERENDER_SECRET_HEADER, options.secret);
|
|
102
91
|
let reason = "probe failed";
|
|
103
92
|
const probeId = randomUUID();
|
|
@@ -107,13 +96,16 @@ async function probeTarget(options) {
|
|
|
107
96
|
state: "probe-failed",
|
|
108
97
|
version: 1
|
|
109
98
|
});
|
|
110
|
-
|
|
99
|
+
let ordinaryFailures = 0;
|
|
100
|
+
let retryable = true;
|
|
101
|
+
for (let attempt = 0;; attempt++) {
|
|
111
102
|
if (options.getDeadlineAt() - Date.now() <= 0) return phaseTimeoutPayload();
|
|
103
|
+
retryable = true;
|
|
112
104
|
const controller = new AbortController();
|
|
113
105
|
const requestDeadlineAt = Date.now() + options.timeoutMs;
|
|
114
106
|
let timeout;
|
|
115
107
|
let timedOutBy = null;
|
|
116
|
-
let
|
|
108
|
+
let retryUntilDeadline = false;
|
|
117
109
|
try {
|
|
118
110
|
const url = new URL(options.target.pathname, options.targetUrl);
|
|
119
111
|
url.searchParams.set(VINEXT_CACHEABILITY_PROBE_QUERY_PARAM, `${probeId}-${attempt}`);
|
|
@@ -128,7 +120,8 @@ async function probeTarget(options) {
|
|
|
128
120
|
return {
|
|
129
121
|
kind: "retry",
|
|
130
122
|
reason: "probe reached an unexpected Worker build",
|
|
131
|
-
retryable: true
|
|
123
|
+
retryable: true,
|
|
124
|
+
retryUntilDeadline: true
|
|
132
125
|
};
|
|
133
126
|
}
|
|
134
127
|
if (!response.ok) {
|
|
@@ -136,12 +129,20 @@ async function probeTarget(options) {
|
|
|
136
129
|
return {
|
|
137
130
|
kind: "retry",
|
|
138
131
|
reason: `probe returned HTTP ${response.status}`,
|
|
139
|
-
retryable: response.status
|
|
132
|
+
retryable: isRetryableProbeHttpStatus(response.status),
|
|
133
|
+
retryUntilDeadline: false
|
|
140
134
|
};
|
|
141
135
|
}
|
|
136
|
+
const payload = await readProbeEnvelope(response);
|
|
137
|
+
if (payload.state === "probe-failed" && payload.retryable === true) return {
|
|
138
|
+
kind: "retry",
|
|
139
|
+
reason: payload.reason ?? "probe failed",
|
|
140
|
+
retryable: true,
|
|
141
|
+
retryUntilDeadline: false
|
|
142
|
+
};
|
|
142
143
|
return {
|
|
143
144
|
kind: "complete",
|
|
144
|
-
payload
|
|
145
|
+
payload
|
|
145
146
|
};
|
|
146
147
|
})();
|
|
147
148
|
const timedOut = new Promise((_resolve, reject) => {
|
|
@@ -163,13 +164,15 @@ async function probeTarget(options) {
|
|
|
163
164
|
if (result.kind === "complete") return result.payload;
|
|
164
165
|
reason = result.reason;
|
|
165
166
|
retryable = result.retryable;
|
|
167
|
+
retryUntilDeadline = result.retryUntilDeadline;
|
|
166
168
|
} catch (error) {
|
|
167
169
|
if (timedOutBy === "phase" || Date.now() >= options.getDeadlineAt()) return phaseTimeoutPayload();
|
|
168
170
|
reason = error instanceof Error && error.name === "AbortError" ? `probe timed out after ${options.timeoutMs}ms` : error instanceof Error ? error.message : String(error);
|
|
169
171
|
} finally {
|
|
170
172
|
if (timeout !== void 0) clearTimeout(timeout);
|
|
171
173
|
}
|
|
172
|
-
if (!retryable
|
|
174
|
+
if (!retryable) break;
|
|
175
|
+
if (!retryUntilDeadline && ++ordinaryFailures > options.retries) break;
|
|
173
176
|
if (options.retryDelayMs > 0) {
|
|
174
177
|
const delayMs = Math.min(options.retryDelayMs, Math.max(0, options.getDeadlineAt() - Date.now()));
|
|
175
178
|
if (delayMs <= 0) return phaseTimeoutPayload();
|
|
@@ -178,6 +181,7 @@ async function probeTarget(options) {
|
|
|
178
181
|
}
|
|
179
182
|
return {
|
|
180
183
|
reason,
|
|
184
|
+
...retryable ? { retryable: true } : {},
|
|
181
185
|
state: "probe-failed",
|
|
182
186
|
version: 1
|
|
183
187
|
};
|
|
@@ -215,7 +219,7 @@ async function probeStagedWorkerCacheability(options) {
|
|
|
215
219
|
return target.kind === "html" ? 0 : target.kind === "rsc-full" ? 1 : 2;
|
|
216
220
|
};
|
|
217
221
|
const patterns = /* @__PURE__ */ new Map();
|
|
218
|
-
const
|
|
222
|
+
const routableTargets = [];
|
|
219
223
|
let missingRouteMetadata = 0;
|
|
220
224
|
for (const target of options.targets) {
|
|
221
225
|
if (!target.route) {
|
|
@@ -227,36 +231,46 @@ async function probeStagedWorkerCacheability(options) {
|
|
|
227
231
|
canPrune: true,
|
|
228
232
|
groups: [],
|
|
229
233
|
key,
|
|
230
|
-
|
|
234
|
+
resultKeys: /* @__PURE__ */ new Set(),
|
|
231
235
|
pruned: false,
|
|
232
236
|
results: /* @__PURE__ */ new Map(),
|
|
233
|
-
route: target.route
|
|
237
|
+
route: target.route,
|
|
238
|
+
requestStageMayTerminate: false
|
|
234
239
|
};
|
|
235
|
-
pattern.
|
|
240
|
+
pattern.requestStageMayTerminate ||= target.route.cacheabilityProbe?.requestStageMayTerminate === true;
|
|
241
|
+
pattern.canPrune &&= target.route.cacheabilityProbe?.canPrunePattern === true && target.route.cacheabilityProbe.routeMayResolve !== true && target.route.cacheabilityProbe.requestStageMayTerminate !== true;
|
|
236
242
|
patterns.set(key, pattern);
|
|
237
|
-
|
|
238
|
-
pattern.pathnames.add(routePathname);
|
|
239
|
-
const concreteKey = `${key}\0${routePathname}`;
|
|
240
|
-
const groupTargets = targetsByConcretePath.get(concreteKey) ?? [];
|
|
241
|
-
groupTargets.push(target);
|
|
242
|
-
targetsByConcretePath.set(concreteKey, groupTargets);
|
|
243
|
+
routableTargets.push(target);
|
|
243
244
|
}
|
|
244
245
|
if (missingRouteMetadata > 0) failures.push(`${missingRouteMetadata} warm target${missingRouteMetadata === 1 ? " is" : "s are"} missing route-pattern metadata`);
|
|
245
|
-
const
|
|
246
|
-
|
|
246
|
+
const targetGroups = /* @__PURE__ */ new Map();
|
|
247
|
+
for (const target of routableTargets) {
|
|
248
|
+
const route = target.route;
|
|
247
249
|
const key = cacheabilityManifestRouteKey(route.kind, route.pattern);
|
|
248
250
|
const pattern = patterns.get(key);
|
|
249
|
-
const routePathname =
|
|
250
|
-
|
|
251
|
+
const routePathname = route.cacheabilityProbe?.concretePathname ?? cacheabilityRoutePathname(target.pathname, target.kind);
|
|
252
|
+
const resultKey = routePathname;
|
|
253
|
+
pattern.resultKeys.add(resultKey);
|
|
254
|
+
const concreteKey = `${key}\0${resultKey}`;
|
|
255
|
+
const group = targetGroups.get(concreteKey) ?? {
|
|
256
|
+
pattern,
|
|
257
|
+
resultKey,
|
|
258
|
+
routePathname,
|
|
259
|
+
targets: []
|
|
260
|
+
};
|
|
261
|
+
group.targets.push(target);
|
|
262
|
+
targetGroups.set(concreteKey, group);
|
|
263
|
+
}
|
|
264
|
+
const groups = Array.from(targetGroups.values(), (targetGroup) => {
|
|
265
|
+
targetGroup.targets.sort((first, second) => {
|
|
251
266
|
return targetPreference(first) - targetPreference(second) || first.sourcePathname.localeCompare(second.sourcePathname);
|
|
252
267
|
});
|
|
253
268
|
const group = {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
targets: groupTargets
|
|
269
|
+
...targetGroup,
|
|
270
|
+
deferred: false,
|
|
271
|
+
primary: targetGroup.targets[0]
|
|
258
272
|
};
|
|
259
|
-
pattern.groups.push(group);
|
|
273
|
+
targetGroup.pattern.groups.push(group);
|
|
260
274
|
return group;
|
|
261
275
|
});
|
|
262
276
|
const reportProgress = () => {
|
|
@@ -283,27 +297,85 @@ async function probeStagedWorkerCacheability(options) {
|
|
|
283
297
|
manifestBytes = nextManifestBytes;
|
|
284
298
|
return true;
|
|
285
299
|
};
|
|
286
|
-
const
|
|
300
|
+
const moveGroupToResolvedRoute = (group, route) => {
|
|
301
|
+
const previousPattern = group.pattern;
|
|
302
|
+
const key = cacheabilityManifestRouteKey(route.kind, route.pattern);
|
|
303
|
+
let pattern = patterns.get(key);
|
|
304
|
+
if (!pattern) {
|
|
305
|
+
pattern = {
|
|
306
|
+
canPrune: false,
|
|
307
|
+
groups: [],
|
|
308
|
+
key,
|
|
309
|
+
resultKeys: /* @__PURE__ */ new Set(),
|
|
310
|
+
pruned: false,
|
|
311
|
+
results: /* @__PURE__ */ new Map(),
|
|
312
|
+
route,
|
|
313
|
+
requestStageMayTerminate: previousPattern.requestStageMayTerminate
|
|
314
|
+
};
|
|
315
|
+
patterns.set(key, pattern);
|
|
316
|
+
}
|
|
317
|
+
pattern.canPrune = false;
|
|
318
|
+
pattern.requestStageMayTerminate ||= previousPattern.requestStageMayTerminate;
|
|
319
|
+
pattern.pruned = false;
|
|
320
|
+
if (pattern === previousPattern) return pattern;
|
|
321
|
+
const previousGroupIndex = previousPattern.groups.indexOf(group);
|
|
322
|
+
if (previousGroupIndex !== -1) previousPattern.groups.splice(previousGroupIndex, 1);
|
|
323
|
+
if (!previousPattern.groups.some((candidate) => candidate.resultKey === group.resultKey)) previousPattern.resultKeys.delete(group.resultKey);
|
|
324
|
+
pattern.groups.push(group);
|
|
325
|
+
pattern.resultKeys.add(group.resultKey);
|
|
326
|
+
group.pattern = pattern;
|
|
327
|
+
return pattern;
|
|
328
|
+
};
|
|
329
|
+
const updateGroupRoutePathname = (group, routePathname) => {
|
|
330
|
+
const normalized = normalizeCacheabilityRoutePathname(routePathname);
|
|
331
|
+
if (normalized === group.routePathname) return;
|
|
332
|
+
const pattern = group.pattern;
|
|
333
|
+
const previousResultKey = group.resultKey;
|
|
334
|
+
group.routePathname = normalized;
|
|
335
|
+
group.resultKey = normalized;
|
|
336
|
+
if (!pattern.groups.some((candidate) => candidate.resultKey === previousResultKey)) pattern.resultKeys.delete(previousResultKey);
|
|
337
|
+
pattern.resultKeys.add(group.resultKey);
|
|
338
|
+
};
|
|
339
|
+
const deferPairedRepresentationsAtOriginalRoute = (group) => {
|
|
340
|
+
const pairedTargets = group.targets.filter((target) => target !== group.primary);
|
|
341
|
+
if (pairedTargets.length === 0) return;
|
|
342
|
+
group.targets = [group.primary];
|
|
343
|
+
for (const target of pairedTargets) {
|
|
344
|
+
const routePathname = target.route?.cacheabilityProbe?.concretePathname ?? cacheabilityRoutePathname(target.pathname, target.kind);
|
|
345
|
+
const deferredGroup = {
|
|
346
|
+
deferred: true,
|
|
347
|
+
pattern: group.pattern,
|
|
348
|
+
primary: target,
|
|
349
|
+
resultKey: routePathname,
|
|
350
|
+
routePathname,
|
|
351
|
+
targets: [target]
|
|
352
|
+
};
|
|
353
|
+
group.pattern.groups.push(deferredGroup);
|
|
354
|
+
group.pattern.resultKeys.add(routePathname);
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
const classifyConcretePath = async (group, attemptOptions) => {
|
|
287
358
|
if (group.pattern.pruned) {
|
|
288
359
|
skippedPathCount += 1;
|
|
289
360
|
completedPathCount += 1;
|
|
290
361
|
reportProgress();
|
|
291
|
-
return;
|
|
362
|
+
return "done";
|
|
292
363
|
}
|
|
293
364
|
const target = group.primary;
|
|
294
|
-
const
|
|
365
|
+
const request = new Request(new URL(target.pathname, options.targetUrl), { headers: target.headers });
|
|
366
|
+
const identity = cacheabilityRequestIdentity(request);
|
|
295
367
|
if (!identity || identity.representation !== target.kind) {
|
|
296
368
|
failures.push(`${target.label}: warm request does not have a cacheable request identity`);
|
|
297
369
|
completedPathCount += 1;
|
|
298
370
|
reportProgress();
|
|
299
|
-
return;
|
|
371
|
+
return "done";
|
|
300
372
|
}
|
|
301
373
|
const result = await probeTarget({
|
|
302
374
|
expectedBuildId: options.expectedResponseBuildId,
|
|
303
375
|
fetchImpl: options.fetchImpl ?? fetch,
|
|
304
376
|
getDeadlineAt,
|
|
305
377
|
headers: options.headers,
|
|
306
|
-
retries,
|
|
378
|
+
retries: attemptOptions.inlineRetries,
|
|
307
379
|
retryDelayMs,
|
|
308
380
|
phaseTimeoutMs,
|
|
309
381
|
secret,
|
|
@@ -311,66 +383,146 @@ async function probeStagedWorkerCacheability(options) {
|
|
|
311
383
|
targetUrl: options.targetUrl,
|
|
312
384
|
timeoutMs
|
|
313
385
|
});
|
|
314
|
-
|
|
315
|
-
lastProgressAt = Date.now();
|
|
316
|
-
if (limitFailure) return;
|
|
386
|
+
if (limitFailure) return "done";
|
|
317
387
|
if (result.phaseTimedOut) {
|
|
318
388
|
phaseTimedOut = true;
|
|
319
|
-
return;
|
|
389
|
+
return "done";
|
|
320
390
|
}
|
|
321
|
-
if (result.
|
|
391
|
+
if (result.state === "probe-failed" && result.retryable === true && attemptOptions.deferRetry) return "retry";
|
|
392
|
+
lastProgressAt = Date.now();
|
|
393
|
+
probed += 1;
|
|
394
|
+
if (result.version !== 1 || result.kind !== "app-page" && result.kind !== "app-route" && result.kind !== "pages-page" || typeof result.pattern !== "string" || !result.pattern.startsWith("/") || !isProbeRouteState(result.state) || result.scope !== void 0 && result.scope !== "identity" && result.scope !== "pattern" || result.scope === "pattern" && result.state !== "dynamic" || result.routePathname !== void 0 && (typeof result.routePathname !== "string" || !result.routePathname.startsWith("/") || result.routePathname.includes("?") || result.routePathname.includes("#")) || result.terminal !== void 0 && result.terminal !== true || result.terminal === true && (result.state !== "dynamic" || result.scope !== "identity" || !group.pattern.requestStageMayTerminate) || result.rendererStatic !== void 0 && typeof result.rendererStatic !== "boolean" || result.retryable !== void 0 && result.retryable !== true || result.retryable === true && result.state !== "probe-failed" || !Number.isInteger(result.status) || result.status < 100 || result.status > 599) {
|
|
322
395
|
failures.push(`${target.label}: ${result.reason ?? "probe returned an invalid envelope"}`);
|
|
323
396
|
completedPathCount += 1;
|
|
324
397
|
reportProgress();
|
|
325
|
-
return;
|
|
398
|
+
return "done";
|
|
326
399
|
}
|
|
327
400
|
if (result.state === "probe-failed") {
|
|
328
401
|
failures.push(`${target.label}: ${result.reason ?? "probe failed"}`);
|
|
329
402
|
completedPathCount += 1;
|
|
330
403
|
reportProgress();
|
|
331
|
-
return;
|
|
404
|
+
return "done";
|
|
332
405
|
}
|
|
333
|
-
if (!target.route
|
|
334
|
-
failures.push(`${target.label}: probe resolved
|
|
406
|
+
if (!target.route) {
|
|
407
|
+
failures.push(`${target.label}: probe resolved without route metadata`);
|
|
335
408
|
completedPathCount += 1;
|
|
336
409
|
reportProgress();
|
|
337
|
-
return;
|
|
410
|
+
return "done";
|
|
338
411
|
}
|
|
412
|
+
const resolvedRouteChanged = result.kind !== target.route.kind || result.pattern !== target.route.pattern;
|
|
413
|
+
const resolvedPathnameChanged = result.routePathname !== void 0 && normalizeCacheabilityRoutePathname(result.routePathname) !== group.routePathname;
|
|
414
|
+
if (resolvedRouteChanged) {
|
|
415
|
+
if (target.route.cacheabilityProbe?.routeMayResolve !== true) {
|
|
416
|
+
failures.push(`${target.label}: probe resolved to unexpected route ${result.pattern ?? ""}`);
|
|
417
|
+
completedPathCount += 1;
|
|
418
|
+
reportProgress();
|
|
419
|
+
return "done";
|
|
420
|
+
}
|
|
421
|
+
if (result.routePathname === void 0) {
|
|
422
|
+
failures.push(`${target.label}: probe resolved without a concrete route pathname`);
|
|
423
|
+
completedPathCount += 1;
|
|
424
|
+
reportProgress();
|
|
425
|
+
return "done";
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
if (target.route.cacheabilityProbe?.routeMayResolve === true && (resolvedRouteChanged || resolvedPathnameChanged)) deferPairedRepresentationsAtOriginalRoute(group);
|
|
429
|
+
if (resolvedRouteChanged) moveGroupToResolvedRoute(group, {
|
|
430
|
+
kind: result.kind,
|
|
431
|
+
pattern: result.pattern
|
|
432
|
+
});
|
|
433
|
+
if (result.routePathname !== void 0) updateGroupRoutePathname(group, result.routePathname);
|
|
434
|
+
const classification = {
|
|
435
|
+
rendererStatic: result.rendererStatic === true,
|
|
436
|
+
representation: target.kind,
|
|
437
|
+
state: result.state,
|
|
438
|
+
terminal: result.terminal === true
|
|
439
|
+
};
|
|
440
|
+
group.result = classification;
|
|
441
|
+
const previousClassification = group.pattern.results.get(group.resultKey);
|
|
442
|
+
if (!previousClassification || previousClassification.state === "static-candidate" && classification.state === "dynamic" || previousClassification.state === classification.state && previousClassification.rendererStatic && !classification.rendererStatic) group.pattern.results.set(group.resultKey, classification);
|
|
339
443
|
if (result.state === "dynamic" && result.scope === "pattern" && group.pattern.canPrune) {
|
|
340
444
|
group.pattern.pruned = true;
|
|
341
445
|
dynamicPathCount += 1;
|
|
342
446
|
completedPathCount += 1;
|
|
343
447
|
reportProgress();
|
|
344
|
-
return;
|
|
448
|
+
return "done";
|
|
345
449
|
}
|
|
346
|
-
group.pattern.results.set(group.routePathname, {
|
|
347
|
-
rendererStatic: result.rendererStatic === true,
|
|
348
|
-
representation: target.kind,
|
|
349
|
-
state: result.state
|
|
350
|
-
});
|
|
351
450
|
if (result.state === "static-candidate") staticPathCount += 1;
|
|
352
451
|
else dynamicPathCount += 1;
|
|
353
452
|
completedPathCount += 1;
|
|
354
453
|
reportProgress();
|
|
355
|
-
|
|
356
|
-
const runGroups = async (scheduledGroups) => {
|
|
357
|
-
let nextIndex = 0;
|
|
358
|
-
const worker = async () => {
|
|
359
|
-
while (!limitFailure && !phaseTimedOut && nextIndex < scheduledGroups.length) await classifyConcretePath(scheduledGroups[nextIndex++]);
|
|
360
|
-
};
|
|
361
|
-
await Promise.all(Array.from({ length: Math.min(concurrency, scheduledGroups.length) }, () => worker()));
|
|
454
|
+
return "done";
|
|
362
455
|
};
|
|
363
456
|
reportProgress();
|
|
364
|
-
const
|
|
365
|
-
const
|
|
366
|
-
const
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
457
|
+
const routeMovingGroups = groups.filter((group) => group.primary.route?.cacheabilityProbe?.routeMayResolve === true);
|
|
458
|
+
const routeMovingGroupSet = new Set(routeMovingGroups);
|
|
459
|
+
const readyGroups = [...routeMovingGroups];
|
|
460
|
+
const readyGroupSet = new Set(routeMovingGroups);
|
|
461
|
+
for (const pattern of patterns.values()) {
|
|
462
|
+
const representative = pattern.groups[0];
|
|
463
|
+
if (!representative || readyGroupSet.has(representative)) continue;
|
|
464
|
+
readyGroups.push(representative);
|
|
465
|
+
readyGroupSet.add(representative);
|
|
466
|
+
}
|
|
467
|
+
for (const group of groups) {
|
|
468
|
+
if (readyGroupSet.has(group)) continue;
|
|
469
|
+
readyGroups.push(group);
|
|
470
|
+
readyGroupSet.add(group);
|
|
471
|
+
}
|
|
472
|
+
const deferredUntilRouteMoversSettle = [];
|
|
473
|
+
const retriesUsedByGroup = /* @__PURE__ */ new Map();
|
|
474
|
+
const retryGroupSet = /* @__PURE__ */ new Set();
|
|
475
|
+
const active = /* @__PURE__ */ new Set();
|
|
476
|
+
let nextReadyIndex = 0;
|
|
477
|
+
let pendingRouteMovers = routeMovingGroups.length;
|
|
478
|
+
const enqueue = (scheduledGroups) => {
|
|
479
|
+
readyGroups.push(...scheduledGroups);
|
|
480
|
+
};
|
|
481
|
+
const startGroup = (group) => {
|
|
482
|
+
const task = (async () => {
|
|
483
|
+
const isRouteMover = routeMovingGroupSet.has(group);
|
|
484
|
+
if (!isRouteMover && group.pattern.pruned && pendingRouteMovers > 0) {
|
|
485
|
+
deferredUntilRouteMoversSettle.push(group);
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
const retriesUsed = retriesUsedByGroup.get(group) ?? 0;
|
|
489
|
+
if (await classifyConcretePath(group, {
|
|
490
|
+
deferRetry: retriesUsed < retries,
|
|
491
|
+
inlineRetries: 0
|
|
492
|
+
}) === "retry") {
|
|
493
|
+
retriesUsedByGroup.set(group, retriesUsed + 1);
|
|
494
|
+
retryGroupSet.add(group);
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
if (isRouteMover && --pendingRouteMovers === 0) enqueue(deferredUntilRouteMoversSettle.splice(0));
|
|
498
|
+
})();
|
|
499
|
+
active.add(task);
|
|
500
|
+
task.then(() => active.delete(task), () => active.delete(task));
|
|
501
|
+
};
|
|
502
|
+
const runReadyGroups = async () => {
|
|
503
|
+
while (!limitFailure && !phaseTimedOut && (nextReadyIndex < readyGroups.length || active.size > 0)) {
|
|
504
|
+
while (active.size < concurrency && nextReadyIndex < readyGroups.length) startGroup(readyGroups[nextReadyIndex++]);
|
|
505
|
+
if (active.size > 0) await Promise.race(active);
|
|
506
|
+
}
|
|
507
|
+
await Promise.all(active);
|
|
508
|
+
};
|
|
509
|
+
await runReadyGroups();
|
|
510
|
+
let retryGroups = groups.filter((group) => retryGroupSet.has(group));
|
|
511
|
+
while (retryGroups.length > 0) {
|
|
512
|
+
if (retryDelayMs > 0) {
|
|
513
|
+
const delayMs = Math.min(retryDelayMs, Math.max(0, getDeadlineAt() - Date.now()));
|
|
514
|
+
if (delayMs <= 0) {
|
|
515
|
+
phaseTimedOut = true;
|
|
516
|
+
break;
|
|
517
|
+
}
|
|
518
|
+
await setTimeout$1(delayMs);
|
|
519
|
+
}
|
|
520
|
+
retryGroupSet.clear();
|
|
521
|
+
readyGroups.splice(0, readyGroups.length, ...retryGroups);
|
|
522
|
+
nextReadyIndex = 0;
|
|
523
|
+
await runReadyGroups();
|
|
524
|
+
retryGroups = groups.filter((group) => retryGroupSet.has(group));
|
|
371
525
|
}
|
|
372
|
-
await runGroups(representativeGroups);
|
|
373
|
-
if (!limitFailure && !phaseTimedOut) await runGroups(siblingGroups);
|
|
374
526
|
if (limitFailure) throw limitFailure;
|
|
375
527
|
if (phaseTimedOut || Date.now() >= getDeadlineAt()) throw new Error(`cacheability probing made no progress for ${phaseTimeoutMs}ms`);
|
|
376
528
|
let classified = 0;
|
|
@@ -410,38 +562,50 @@ async function probeStagedWorkerCacheability(options) {
|
|
|
410
562
|
}
|
|
411
563
|
continue;
|
|
412
564
|
}
|
|
413
|
-
if (pattern.results.size === 0) continue;
|
|
565
|
+
if (pattern.results.size === 0 && !pattern.groups.some((group) => group.deferred)) continue;
|
|
414
566
|
classified += 1;
|
|
415
567
|
if (Array.from(pattern.results.values()).some((result) => result.state === "dynamic")) dynamic += 1;
|
|
416
|
-
const
|
|
568
|
+
const rendererStaticTargets = /* @__PURE__ */ new Map();
|
|
417
569
|
const runtimePathSet = /* @__PURE__ */ new Set();
|
|
418
570
|
for (const group of pattern.groups) {
|
|
419
|
-
|
|
571
|
+
if (group.deferred) {
|
|
572
|
+
runtimePathSet.add(group.routePathname);
|
|
573
|
+
cacheableTargets.push(...group.targets);
|
|
574
|
+
speculativeTargets.push(...group.targets);
|
|
575
|
+
continue;
|
|
576
|
+
}
|
|
577
|
+
const result = group.result;
|
|
420
578
|
if (result?.state === "static-candidate") {
|
|
421
579
|
if (result.rendererStatic) {
|
|
422
|
-
const
|
|
423
|
-
|
|
424
|
-
staticPaths[result.representation] = paths;
|
|
580
|
+
const previous = rendererStaticTargets.get(group.routePathname);
|
|
581
|
+
if (!previous || targetPreference(group.primary) < targetPreference(previous)) rendererStaticTargets.set(group.routePathname, group.primary);
|
|
425
582
|
} else runtimePathSet.add(group.routePathname);
|
|
426
583
|
cacheableTargets.push(...group.targets);
|
|
427
584
|
speculativeTargets.push(...group.targets.filter((target) => target !== group.primary));
|
|
428
585
|
continue;
|
|
429
586
|
}
|
|
430
|
-
|
|
587
|
+
const pairedTargets = group.targets.filter((target) => target !== group.primary);
|
|
588
|
+
if (result?.terminal !== true || pairedTargets.length > 0) runtimePathSet.add(group.routePathname);
|
|
431
589
|
if (!pattern.pruned) {
|
|
432
|
-
const pairedTargets = group.targets.filter((target) => target !== group.primary);
|
|
433
590
|
cacheableTargets.push(...pairedTargets);
|
|
434
591
|
speculativeTargets.push(...pairedTargets);
|
|
435
592
|
}
|
|
436
593
|
}
|
|
594
|
+
const staticPaths = {};
|
|
595
|
+
for (const [routePathname, staticTarget] of rendererStaticTargets) {
|
|
596
|
+
if (runtimePathSet.has(routePathname)) continue;
|
|
597
|
+
const paths = staticPaths[staticTarget.kind] ?? [];
|
|
598
|
+
paths.push(routePathname);
|
|
599
|
+
staticPaths[staticTarget.kind] = paths;
|
|
600
|
+
}
|
|
437
601
|
for (const paths of Object.values(staticPaths)) paths?.sort();
|
|
438
|
-
const allObservedPathsStaticallyGenerated = pattern.results.size === pattern.
|
|
602
|
+
const allObservedPathsStaticallyGenerated = pattern.results.size === pattern.resultKeys.size && Array.from(pattern.results.values()).every((result) => result.state === "static-candidate") && Array.from(pattern.results.values()).every((result) => result.rendererStatic);
|
|
439
603
|
const hasStaticFallback = fallbackRoutes.has(pattern.key);
|
|
440
604
|
const soleGroup = pattern.groups.length === 1 ? pattern.groups[0] : null;
|
|
441
605
|
const literalPatternNamesSolePath = soleGroup !== null && !/(^|\/):/.test(pattern.route.pattern) && normalizeCacheabilityRoutePathname(pattern.route.pattern) === soleGroup.routePathname;
|
|
442
606
|
let route;
|
|
443
607
|
if (literalPatternNamesSolePath) {
|
|
444
|
-
const result = pattern.results.get(soleGroup.
|
|
608
|
+
const result = pattern.results.get(soleGroup.resultKey);
|
|
445
609
|
route = result?.state === "static-candidate" ? pattern.route.kind === "app-route" ? {
|
|
446
610
|
kind: pattern.route.kind,
|
|
447
611
|
pattern: pattern.route.pattern,
|