@vinext/cloudflare 1.0.0-beta.7 → 1.0.0-beta.8

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.
Files changed (52) hide show
  1. package/README.md +55 -22
  2. package/dist/cache/cdn-adapter-config.d.ts +16 -8
  3. package/dist/cache/cdn-adapter-config.js +57 -17
  4. package/dist/cache/cdn-adapter.d.ts +25 -24
  5. package/dist/cache/cdn-adapter.js +24 -20
  6. package/dist/cache/cdn-adapter.runtime.d.ts +9 -3
  7. package/dist/cache/cdn-adapter.runtime.js +63 -40
  8. package/dist/cache/cdn-adapter.worker.d.ts +30 -0
  9. package/dist/cache/cdn-adapter.worker.js +371 -0
  10. package/dist/cache/cdn-build-id.d.ts +4 -3
  11. package/dist/cache/cdn-build-id.js +5 -1
  12. package/dist/cache/kv-data-adapter.d.ts +21 -5
  13. package/dist/cache/kv-data-adapter.runtime.d.ts +38 -4
  14. package/dist/cache/kv-data-adapter.runtime.js +115 -41
  15. package/dist/cache/kv-key.d.ts +4 -5
  16. package/dist/cache/response-store-adapter.d.ts +37 -0
  17. package/dist/cache/response-store-adapter.js +39 -0
  18. package/dist/cache/response-store-adapter.self-contained.worker.d.ts +17 -0
  19. package/dist/cache/response-store-adapter.self-contained.worker.js +10 -0
  20. package/dist/cache/response-store-adapter.service-binding.worker.d.ts +15 -0
  21. package/dist/cache/response-store-adapter.service-binding.worker.js +9 -0
  22. package/dist/cache/response-store-adapter.worker.d.ts +14 -0
  23. package/dist/cache/response-store-adapter.worker.js +254 -0
  24. package/dist/cache/response-store-cdn.runtime.d.ts +5 -0
  25. package/dist/cache/response-store-cdn.runtime.js +41 -0
  26. package/dist/cache/response-store-data.runtime.d.ts +34 -0
  27. package/dist/cache/response-store-data.runtime.js +289 -0
  28. package/dist/cacheability-artifact.d.ts +2 -3
  29. package/dist/cacheability-artifact.js +27 -1
  30. package/dist/cacheability-manifest-limits.d.ts +3 -4
  31. package/dist/cacheability-manifest-limits.js +1 -1
  32. package/dist/cacheability-probe.d.ts +8 -9
  33. package/dist/cacheability-probe.js +255 -91
  34. package/dist/cdn-warm.d.ts +31 -25
  35. package/dist/cdn-warm.js +208 -50
  36. package/dist/deploy-config.d.ts +11 -12
  37. package/dist/deploy-config.js +8 -3
  38. package/dist/deploy-help.d.ts +3 -4
  39. package/dist/deploy.d.ts +23 -24
  40. package/dist/deploy.js +44 -11
  41. package/dist/images/images-optimizer.d.ts +3 -4
  42. package/dist/prerender-kv-populate.d.ts +3 -4
  43. package/dist/prerender-kv-populate.js +3 -2
  44. package/dist/tpr.d.ts +9 -10
  45. package/dist/tpr.js +1 -1
  46. package/dist/utils/cache-control-metadata.d.ts +3 -4
  47. package/dist/version-deploy.d.ts +17 -17
  48. package/dist/version-deploy.js +6 -3
  49. package/dist/worker-deployment-url.d.ts +3 -4
  50. package/dist/workers-dev-url.d.ts +2 -3
  51. package/dist/wrangler-version-metadata.d.ts +2 -3
  52. package/package.json +13 -7
@@ -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 = 64 * 1024;
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) return {
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
- return {
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
- for (let attempt = 0; attempt <= options.retries; attempt++) {
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 retryable = true;
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 === 404 || response.status === 503
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: await readProbeEnvelope(response)
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 || attempt === options.retries) break;
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 targetsByConcretePath = /* @__PURE__ */ new Map();
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
- pathnames: /* @__PURE__ */ new Set(),
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.canPrune &&= target.route.cacheabilityProbe?.canPrunePattern === true;
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
- const routePathname = target.route.cacheabilityProbe?.concretePathname ?? cacheabilityRoutePathname(target.pathname, target.kind);
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 groups = Array.from(targetsByConcretePath, ([concreteKey, groupTargets]) => {
246
- const route = groupTargets[0].route;
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 = concreteKey.slice(key.length + 1);
250
- groupTargets.sort((first, second) => {
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
- pattern,
255
- primary: groupTargets[0],
256
- routePathname,
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 classifyConcretePath = async (group) => {
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 identity = cacheabilityRequestIdentity(new Request(new URL(target.pathname, options.targetUrl), { headers: target.headers }));
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
- probed += 1;
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.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.rendererStatic !== void 0 && typeof result.rendererStatic !== "boolean" || !Number.isInteger(result.status) || result.status < 100 || result.status > 599) {
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 || result.kind !== target.route.kind || result.pattern !== target.route.pattern) {
334
- failures.push(`${target.label}: probe resolved to unexpected route ${result.pattern ?? ""}`);
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 representativeGroups = [];
365
- const siblingGroups = [];
366
- const scheduledPatterns = /* @__PURE__ */ new Set();
367
- for (const group of groups) if (scheduledPatterns.has(group.pattern.key)) siblingGroups.push(group);
368
- else {
369
- scheduledPatterns.add(group.pattern.key);
370
- representativeGroups.push(group);
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 staticPaths = {};
568
+ const rendererStaticTargets = /* @__PURE__ */ new Map();
417
569
  const runtimePathSet = /* @__PURE__ */ new Set();
418
570
  for (const group of pattern.groups) {
419
- const result = pattern.results.get(group.routePathname);
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 paths = staticPaths[result.representation] ?? [];
423
- paths.push(group.routePathname);
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
- runtimePathSet.add(group.routePathname);
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.pathnames.size && runtimePathSet.size === 0 && Array.from(pattern.results.values()).every((result) => result.rendererStatic);
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.routePathname);
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,