@rangojs/router 0.27.0 → 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (243) hide show
  1. package/dist/bin/rango.js +1 -1
  2. package/dist/types/__internal.d.ts +7 -1
  3. package/dist/types/browser/action-fence.d.ts +2 -1
  4. package/dist/types/browser/event-controller.d.ts +8 -2
  5. package/dist/types/browser/history-state.d.ts +3 -9
  6. package/dist/types/browser/navigation-store.d.ts +8 -6
  7. package/dist/types/browser/partial-update.d.ts +11 -0
  8. package/dist/types/browser/react/filter-segment-order.d.ts +2 -2
  9. package/dist/types/browser/react/use-action.d.ts +7 -2
  10. package/dist/types/browser/react/use-handle.d.ts +19 -16
  11. package/dist/types/browser/react/use-link-status.d.ts +5 -2
  12. package/dist/types/browser/segment-reconciler.d.ts +32 -4
  13. package/dist/types/browser/types.d.ts +24 -5
  14. package/dist/types/browser/wire-protocol.d.ts +26 -0
  15. package/dist/types/cache/cache-key-utils.d.ts +11 -5
  16. package/dist/types/cache/cache-scope.d.ts +34 -22
  17. package/dist/types/cache/cf/cf-cache-constants.d.ts +2 -2
  18. package/dist/types/cache/cf/cf-cache-store.d.ts +47 -65
  19. package/dist/types/cache/cf/cf-kv-utils.d.ts +1 -1
  20. package/dist/types/cache/handle-snapshot.d.ts +9 -4
  21. package/dist/types/cache/segment-codec.d.ts +14 -0
  22. package/dist/types/cache/shell-snapshot.d.ts +1 -1
  23. package/dist/types/cache/taint.d.ts +5 -3
  24. package/dist/types/cache/types.d.ts +7 -3
  25. package/dist/types/cache/vercel/vercel-cache-store.d.ts +8 -6
  26. package/dist/types/prefetch-markers.d.ts +5 -14
  27. package/dist/types/prerender/store.d.ts +31 -0
  28. package/dist/types/prerender/warm-request.d.ts +3 -3
  29. package/dist/types/prerender.d.ts +2 -2
  30. package/dist/types/route-name.d.ts +5 -0
  31. package/dist/types/router/error-handling.d.ts +11 -5
  32. package/dist/types/router/instrument.d.ts +4 -3
  33. package/dist/types/router/intercept-resolution.d.ts +25 -9
  34. package/dist/types/router/match-context.d.ts +6 -11
  35. package/dist/types/router/match-handlers.d.ts +2 -6
  36. package/dist/types/router/match-middleware/background-revalidation.d.ts +13 -22
  37. package/dist/types/router/match-middleware/cache-lookup.d.ts +34 -86
  38. package/dist/types/router/match-middleware/segment-resolution.d.ts +8 -6
  39. package/dist/types/router/navigation-snapshot.d.ts +7 -0
  40. package/dist/types/router/prerender-match.d.ts +21 -6
  41. package/dist/types/router/preview-match.d.ts +2 -7
  42. package/dist/types/router/request-classification.d.ts +5 -0
  43. package/dist/types/router/router-context.d.ts +7 -46
  44. package/dist/types/router/router-interfaces.d.ts +8 -14
  45. package/dist/types/router/segment-resolution/helpers.d.ts +19 -3
  46. package/dist/types/router/segment-resolution/loader-cache.d.ts +3 -3
  47. package/dist/types/router/segment-resolution/loader-mask.d.ts +4 -4
  48. package/dist/types/router/segment-resolution/prefetch-deferral.d.ts +20 -18
  49. package/dist/types/router/segment-resolution/resolution-pass.d.ts +95 -0
  50. package/dist/types/router/segment-resolution/revalidation.d.ts +82 -31
  51. package/dist/types/router/segment-resolution/streamed-handler-boundary.d.ts +2 -0
  52. package/dist/types/router/segment-resolution.d.ts +1 -2
  53. package/dist/types/router/segment-wrappers.d.ts +11 -48
  54. package/dist/types/router/telemetry.d.ts +2 -0
  55. package/dist/types/router/types.d.ts +11 -1
  56. package/dist/types/rsc/full-payload.d.ts +2 -2
  57. package/dist/types/rsc/handler-context.d.ts +6 -6
  58. package/dist/types/rsc/json-route-result.d.ts +5 -9
  59. package/dist/types/rsc/request-stages.d.ts +203 -0
  60. package/dist/types/rsc/response-cache-serve.d.ts +7 -12
  61. package/dist/types/rsc/response-route-handler.d.ts +2 -2
  62. package/dist/types/rsc/routine-plan.d.ts +1 -1
  63. package/dist/types/rsc/server-action.d.ts +2 -2
  64. package/dist/types/rsc/shell-capture-constants.d.ts +1 -1
  65. package/dist/types/rsc/shell-capture.d.ts +132 -15
  66. package/dist/types/rsc/shell-serve.d.ts +37 -12
  67. package/dist/types/server/context.d.ts +9 -8
  68. package/dist/types/server/handle-store.d.ts +16 -0
  69. package/dist/types/server/render-pass.d.ts +72 -0
  70. package/dist/types/server/request-context.d.ts +53 -104
  71. package/dist/types/server/rewrite-trail.d.ts +2 -2
  72. package/dist/types/testing/dispatch.d.ts +35 -18
  73. package/dist/types/testing/render-route.d.ts +43 -8
  74. package/dist/types/testing/serve-shell-request.d.ts +8 -1
  75. package/dist/types/types/cache-types.d.ts +6 -3
  76. package/dist/types/types/error-types.d.ts +30 -3
  77. package/dist/types/types/handler-context.d.ts +9 -1
  78. package/dist/types/types/segments.d.ts +1 -1
  79. package/dist/types/vite/discovery/build-discovery-phase.d.ts +40 -0
  80. package/dist/types/vite/discovery/build-env.d.ts +22 -0
  81. package/dist/types/vite/discovery/bundle-scan-phase.d.ts +12 -0
  82. package/dist/types/vite/discovery/dev-prerender-cache.d.ts +3 -2
  83. package/dist/types/vite/discovery/dev-server-phase.d.ts +26 -0
  84. package/dist/types/vite/discovery/discovery-errors.d.ts +1 -1
  85. package/dist/types/vite/discovery/state.d.ts +12 -0
  86. package/dist/types/vite/plugins/version-plugin.d.ts +1 -1
  87. package/dist/types/vite/plugins/virtual-entries.d.ts +1 -1
  88. package/dist/types/vite/router-discovery.d.ts +8 -47
  89. package/dist/types/vite/temp-rsc-server.d.ts +39 -0
  90. package/dist/types/vite/utils/prerender-utils.d.ts +9 -0
  91. package/dist/types/wire-params.d.ts +39 -0
  92. package/dist/vite/index.js +3822 -3727
  93. package/package.json +1 -1
  94. package/skills/cache-guide/SKILL.md +8 -5
  95. package/skills/caching/SKILL.md +37 -18
  96. package/skills/client-urls/SKILL.md +4 -2
  97. package/skills/document-cache/SKILL.md +13 -1
  98. package/skills/handler-use/SKILL.md +1 -1
  99. package/skills/hooks/SKILL.md +1 -1
  100. package/skills/hooks/data.md +8 -1
  101. package/skills/hooks/handle-and-actions.md +33 -2
  102. package/skills/hooks/navigation.md +121 -15
  103. package/skills/hooks/state.md +6 -4
  104. package/skills/hooks/urls.md +16 -5
  105. package/skills/intercept/SKILL.md +23 -7
  106. package/skills/middleware/SKILL.md +18 -0
  107. package/skills/observability/SKILL.md +30 -0
  108. package/skills/ppr/SKILL.md +18 -3
  109. package/skills/prerender/SKILL.md +39 -15
  110. package/skills/rango/SKILL.md +5 -1
  111. package/skills/server-actions/SKILL.md +15 -1
  112. package/skills/testing/client-components.md +25 -18
  113. package/skills/testing/response-routes.md +6 -4
  114. package/skills/testing/serve-shell-request.md +7 -7
  115. package/skills/use-cache/SKILL.md +46 -8
  116. package/skills/view-transitions/SKILL.md +1 -1
  117. package/src/__internal.ts +7 -1
  118. package/src/browser/action-fence.ts +2 -1
  119. package/src/browser/connection-warmup.ts +3 -1
  120. package/src/browser/event-controller.ts +37 -13
  121. package/src/browser/history-state.ts +6 -12
  122. package/src/browser/navigation-bridge.ts +59 -26
  123. package/src/browser/navigation-client.ts +20 -25
  124. package/src/browser/navigation-store.ts +25 -27
  125. package/src/browser/navigation-transaction.ts +4 -1
  126. package/src/browser/network-error-handler.ts +4 -0
  127. package/src/browser/partial-update.ts +33 -6
  128. package/src/browser/prefetch/cache.ts +2 -1
  129. package/src/browser/prefetch/fetch.ts +8 -16
  130. package/src/browser/prefetch/queue.ts +4 -4
  131. package/src/browser/react/NavigationProvider.tsx +2 -1
  132. package/src/browser/react/filter-segment-order.ts +2 -2
  133. package/src/browser/react/use-action.ts +24 -48
  134. package/src/browser/react/use-handle.ts +22 -18
  135. package/src/browser/react/use-link-status.ts +48 -60
  136. package/src/browser/react/use-navigation.ts +92 -41
  137. package/src/browser/rsc-router.tsx +1 -0
  138. package/src/browser/segment-reconciler.ts +109 -53
  139. package/src/browser/server-action-bridge.ts +81 -61
  140. package/src/browser/types.ts +25 -5
  141. package/src/browser/wire-protocol.ts +43 -0
  142. package/src/cache/cache-key-utils.ts +11 -5
  143. package/src/cache/cache-runtime.ts +132 -75
  144. package/src/cache/cache-scope.ts +110 -64
  145. package/src/cache/cf/cf-cache-constants.ts +2 -2
  146. package/src/cache/cf/cf-cache-store.ts +486 -874
  147. package/src/cache/cf/cf-kv-utils.ts +1 -1
  148. package/src/cache/document-cache.ts +64 -46
  149. package/src/cache/handle-snapshot.ts +19 -7
  150. package/src/cache/read-through-swr.ts +20 -18
  151. package/src/cache/segment-codec.ts +55 -0
  152. package/src/cache/shell-snapshot.ts +2 -2
  153. package/src/cache/tag-invalidation.ts +6 -2
  154. package/src/cache/taint.ts +5 -3
  155. package/src/cache/types.ts +7 -3
  156. package/src/cache/vercel/vercel-cache-store.ts +243 -310
  157. package/src/handles/pushed-by.ts +1 -1
  158. package/src/prefetch-markers.ts +5 -16
  159. package/src/prerender/store.ts +76 -0
  160. package/src/prerender/warm-request.ts +8 -4
  161. package/src/prerender.ts +2 -2
  162. package/src/route-name.ts +8 -0
  163. package/src/router/error-handling.ts +22 -10
  164. package/src/router/instrument.ts +4 -3
  165. package/src/router/intercept-resolution.ts +82 -75
  166. package/src/router/loader-resolution.ts +5 -4
  167. package/src/router/match-api.ts +5 -5
  168. package/src/router/match-context.ts +23 -12
  169. package/src/router/match-handlers.ts +8 -20
  170. package/src/router/match-middleware/background-revalidation.ts +46 -57
  171. package/src/router/match-middleware/cache-lookup.ts +677 -726
  172. package/src/router/match-middleware/cache-store.ts +4 -8
  173. package/src/router/match-middleware/intercept-resolution.ts +3 -23
  174. package/src/router/match-middleware/segment-resolution.ts +34 -61
  175. package/src/router/middleware.ts +69 -11
  176. package/src/router/navigation-snapshot.ts +14 -4
  177. package/src/router/prerender-match.ts +100 -44
  178. package/src/router/preview-match.ts +6 -41
  179. package/src/router/request-classification.ts +13 -7
  180. package/src/router/route-snapshot.ts +2 -5
  181. package/src/router/router-context.ts +10 -95
  182. package/src/router/router-interfaces.ts +8 -14
  183. package/src/router/segment-resolution/helpers.ts +49 -13
  184. package/src/router/segment-resolution/loader-cache.ts +15 -9
  185. package/src/router/segment-resolution/loader-mask.ts +5 -5
  186. package/src/router/segment-resolution/prefetch-deferral.ts +30 -27
  187. package/src/router/segment-resolution/resolution-pass.ts +139 -0
  188. package/src/router/segment-resolution/revalidation.ts +939 -789
  189. package/src/router/segment-resolution/streamed-handler-boundary.ts +3 -0
  190. package/src/router/segment-resolution.ts +4 -13
  191. package/src/router/segment-wrappers.ts +51 -269
  192. package/src/router/telemetry.ts +5 -0
  193. package/src/router/types.ts +11 -1
  194. package/src/router.ts +19 -17
  195. package/src/rsc/full-payload.ts +6 -5
  196. package/src/rsc/handler-context.ts +8 -14
  197. package/src/rsc/handler.ts +126 -507
  198. package/src/rsc/json-route-result.ts +5 -9
  199. package/src/rsc/loader-fetch.ts +6 -5
  200. package/src/rsc/progressive-enhancement.ts +5 -0
  201. package/src/rsc/request-stages.ts +759 -0
  202. package/src/rsc/response-cache-serve.ts +10 -14
  203. package/src/rsc/response-route-handler.ts +13 -5
  204. package/src/rsc/rewrite-passes.ts +2 -1
  205. package/src/rsc/routine-plan.ts +1 -1
  206. package/src/rsc/rsc-rendering.ts +164 -349
  207. package/src/rsc/server-action.ts +87 -113
  208. package/src/rsc/shell-capture-constants.ts +1 -1
  209. package/src/rsc/shell-capture.ts +275 -227
  210. package/src/rsc/shell-serve.ts +121 -18
  211. package/src/rsc/ssr-setup.ts +5 -4
  212. package/src/segment-system.tsx +3 -3
  213. package/src/server/context.ts +22 -25
  214. package/src/server/handle-store.ts +55 -0
  215. package/src/server/render-pass.ts +291 -0
  216. package/src/server/request-context.ts +75 -115
  217. package/src/server/rewrite-trail.ts +6 -4
  218. package/src/ssr/index.tsx +1 -1
  219. package/src/testing/dispatch.ts +226 -710
  220. package/src/testing/e2e/page-helpers.ts +2 -2
  221. package/src/testing/render-route.tsx +199 -13
  222. package/src/testing/serve-shell-request.ts +36 -40
  223. package/src/types/cache-types.ts +6 -3
  224. package/src/types/error-types.ts +30 -3
  225. package/src/types/handler-context.ts +12 -1
  226. package/src/types/segments.ts +1 -1
  227. package/src/vite/discovery/build-discovery-phase.ts +204 -0
  228. package/src/vite/discovery/build-env.ts +148 -0
  229. package/src/vite/discovery/bundle-scan-phase.ts +128 -0
  230. package/src/vite/discovery/dev-prerender-cache.ts +8 -3
  231. package/src/vite/discovery/dev-server-phase.ts +1776 -0
  232. package/src/vite/discovery/discover-routers.ts +1 -1
  233. package/src/vite/discovery/discovery-errors.ts +1 -1
  234. package/src/vite/discovery/prerender-collection.ts +24 -39
  235. package/src/vite/discovery/state.ts +15 -0
  236. package/src/vite/plugins/version-plugin.ts +1 -1
  237. package/src/vite/plugins/virtual-entries.ts +1 -1
  238. package/src/vite/router-discovery.ts +29 -2428
  239. package/src/vite/temp-rsc-server.ts +223 -0
  240. package/src/vite/utils/prerender-utils.ts +47 -0
  241. package/src/wire-params.ts +40 -0
  242. package/dist/types/router/segment-resolution/fresh.d.ts +0 -72
  243. package/src/router/segment-resolution/fresh.ts +0 -945
@@ -204,15 +204,49 @@ export interface ErrorReportContext {
204
204
  routeKey?: string;
205
205
  env?: any;
206
206
  isPartial?: boolean;
207
- requestStartTime?: number;
208
207
  telemetry?: TelemetrySink;
209
208
  }
210
209
 
210
+ type Thrower = { id: string; type: ErrorInfo["segmentType"] };
211
+
212
+ /**
213
+ * Note that `id` threw `error`, for an error its owning entry's boundary
214
+ * catches (a parallel slot's sync throw, an orphan layout's): onError names
215
+ * the segment that threw, as a streamed slot's rejection does. Kept on the
216
+ * request context, never the module: a reused error object (a constant, a
217
+ * memoized rejection) can be thrown by another request, or by a Prerender
218
+ * pass that rethrows before catchSegmentError reads it. Returns `error`, to
219
+ * rethrow.
220
+ */
221
+ export function noteThrower(
222
+ error: unknown,
223
+ id: string,
224
+ type: ErrorInfo["segmentType"],
225
+ ): unknown {
226
+ const reqCtx = _getRequestContext();
227
+ if (reqCtx && error !== null && typeof error === "object") {
228
+ (reqCtx._throwers ??= new WeakMap()).set(error, { id, type });
229
+ }
230
+ return error;
231
+ }
232
+
233
+ /** The segment noteThrower recorded for `error`, read once. */
234
+ function takeThrower(error: unknown): Thrower | undefined {
235
+ const throwers = _getRequestContext()?._throwers;
236
+ if (!throwers || error === null || typeof error !== "object") return;
237
+ const noted = throwers.get(error) as Thrower | undefined;
238
+ throwers.delete(error);
239
+ return noted;
240
+ }
241
+
211
242
  /**
212
243
  * Handle a caught error during segment resolution by creating an
213
244
  * error or not-found segment with the nearest boundary.
214
245
  *
215
246
  * Called by resolveWithErrorBoundary to produce error/notFound segments.
247
+ * `entry` is where the boundary is looked up; `thrower` names the segment
248
+ * that threw when it is not `entry` (an intercept's slot, under its declaring
249
+ * entry), for the report, the server print and the client's ErrorInfo.
216
250
  */
217
251
  export function catchSegmentError<TEnv>(
218
252
  error: unknown,
@@ -221,7 +255,11 @@ export function catchSegmentError<TEnv>(
221
255
  deps: SegmentResolutionDeps<TEnv>,
222
256
  report?: ErrorReportContext,
223
257
  pathname?: string,
258
+ thrower?: Thrower,
224
259
  ): ResolvedSegment {
260
+ const named = thrower ?? takeThrower(error);
261
+ const segmentId = named?.id ?? entry.shortCode;
262
+ const segmentType: ErrorInfo["segmentType"] = named?.type ?? entry.type;
225
263
  const reportError = (
226
264
  handledByBoundary: boolean,
227
265
  metadata?: Record<string, unknown>,
@@ -232,13 +270,12 @@ export function catchSegmentError<TEnv>(
232
270
  url: report.url,
233
271
  routeKey: report.routeKey,
234
272
  params,
235
- segmentId: entry.shortCode,
236
- segmentType: entry.type as any,
273
+ segmentId,
274
+ segmentType: segmentType as any,
237
275
  env: report.env,
238
276
  isPartial: report.isPartial,
239
277
  handledByBoundary,
240
278
  metadata,
241
- requestStartTime: report.requestStartTime,
242
279
  });
243
280
  if (report.telemetry) {
244
281
  const errorObj =
@@ -247,8 +284,8 @@ export function catchSegmentError<TEnv>(
247
284
  type: "handler.error",
248
285
  timestamp: performance.now(),
249
286
  requestId: report.request ? getRequestId(report.request) : undefined,
250
- segmentId: entry.shortCode,
251
- segmentType: entry.type,
287
+ segmentId,
288
+ segmentType,
252
289
  error: errorObj,
253
290
  handledByBoundary,
254
291
  pathname,
@@ -274,8 +311,8 @@ export function catchSegmentError<TEnv>(
274
311
 
275
312
  const notFoundInfo = createNotFoundInfo(
276
313
  error,
277
- entry.shortCode,
278
- entry.type,
314
+ segmentId,
315
+ segmentType,
279
316
  pathname,
280
317
  );
281
318
 
@@ -285,7 +322,7 @@ export function catchSegmentError<TEnv>(
285
322
  });
286
323
 
287
324
  debugLog("segment", "notFound boundary handled error", {
288
- segmentId: entry.shortCode,
325
+ segmentId,
289
326
  message: notFoundInfo.message,
290
327
  });
291
328
 
@@ -302,19 +339,18 @@ export function catchSegmentError<TEnv>(
302
339
  // The client gets the masked ErrorInfo; the original prints here.
303
340
  printServerError(
304
341
  error,
305
- { label: "Handler error", name: entry.shortCode, pathname },
342
+ { label: "Handler error", name: segmentId, pathname },
306
343
  _getRequestContext(),
307
344
  );
308
345
 
309
346
  const fallback = deps.findNearestErrorBoundary(entry);
310
- const segmentType: ErrorInfo["segmentType"] = entry.type;
311
- const errorInfo = createErrorInfo(error, entry.shortCode, segmentType);
347
+ const errorInfo = createErrorInfo(error, segmentId, segmentType);
312
348
  const effectiveFallback = fallback ?? DefaultErrorFallback;
313
349
 
314
350
  reportError(!!effectiveFallback);
315
351
 
316
352
  debugLog("segment", "error boundary handled error", {
317
- segmentId: entry.shortCode,
353
+ segmentId,
318
354
  boundary: fallback ? "custom" : "default",
319
355
  message: errorInfo.message,
320
356
  });
@@ -72,7 +72,7 @@ import { runIdentityExempt } from "../../cache/cache-exec-scope.js";
72
72
  import {
73
73
  getRequestContext,
74
74
  _getRequestContext,
75
- runWithRequestContext,
75
+ runAsBackgroundWork,
76
76
  type RequestContext,
77
77
  } from "../../server/request-context.js";
78
78
  import { observePhase, PHASES } from "../instrument.js";
@@ -458,8 +458,8 @@ function recordOwnerKey(cachedLoaderId: string): () => string {
458
458
  * PPR loader lane rule — the canonical source statement; other sites point
459
459
  * here (consumer docs: skills/ppr/SKILL.md "The loader lane rule";
460
460
  * docs/design/loader-container-bake.md). This is the single funnel every
461
- * loader segment path routes through (fresh resolveLoaders, cache-hit
462
- * resolveLoadersOnly, revalidation resolveLoadersOnlyWithRevalidation, and
461
+ * loader segment path routes through (resolveOwnerLoaders: the full match's
462
+ * FRESH pass, cache-hit and revalidation resolveLoadersOnly, and
463
463
  * intercept loaders in intercept-resolution.ts, which pass no bake key and
464
464
  * never run under a shell capture), so the lane is decided here, per LOADER,
465
465
  * never by the entry's loading():
@@ -473,7 +473,7 @@ function recordOwnerKey(cachedLoaderId: string): () => string {
473
473
  * plain objects, arrays and JSX props are masked (mask-nested.ts) and stay
474
474
  * live holes at the consumer's own Suspense — per-request material must be
475
475
  * promise-shaped (the #692 cross-session scar). The masked container
476
- * registers on `_shellCaptureLoaderRecords`: it holds the capture gate
476
+ * registers on `_shellCaptureState.loaderRecords`: it holds the capture gate
477
477
  * (bounded by `ppr.captureTimeout`) and pins into the snapshot's loader
478
478
  * family, and on a HIT the recorded container is overlaid onto the fresh
479
479
  * run (below) so the payload matches the frozen prelude outside the holes.
@@ -511,7 +511,10 @@ export function resolveLoaderData<TEnv>(
511
511
  maskNestedContainerThenables(container),
512
512
  );
513
513
  maskedPromise.catch(() => {});
514
- reqCtx?._shellCaptureLoaderRecords?.set(bakeSegmentKey, maskedPromise);
514
+ reqCtx?._shellCaptureState?.loaderRecords.set(
515
+ bakeSegmentKey,
516
+ maskedPromise,
517
+ );
515
518
  // Its data bakes into the shell, so its tags tag the shell.
516
519
  linkLoaderTagsTo(SHELL_BAKE_TAG_OWNER, loaderEntry.loader.$$id, reqCtx);
517
520
  return maskedPromise;
@@ -629,14 +632,14 @@ function executeLoaderData<TEnv>(
629
632
 
630
633
  // Dedup the cache read-through across repeated resolutions of the SAME
631
634
  // loaderId in one request. An orphan layout with parallel slots inherits its
632
- // parent route's loaders, so resolveOrphanLayout (fresh.ts) re-resolves the
635
+ // parent route's loaders, so resolveOrphanLayout re-resolves the
633
636
  // parent's loaders under a different shortCode — calling resolveLoaderData
634
637
  // again for the same loaderId. The cache key (loader:{loaderId}:{host}
635
638
  // {pathname}:{sortedParams}) does not include the shortCode and ctx/params
636
639
  // are identical, so both resolutions produce the same data. Reuse the already
637
640
  // in-flight dataPromise instead of issuing a second getItem/setItem (e.g. a
638
641
  // second KV round-trip) for one logical cached loader. The shortCode only
639
- // affects the emitted segmentId in resolveLoaders, not the cached value.
642
+ // affects the emitted segmentId in resolveOwnerLoaders, not the cached value.
640
643
  const existing = overrides.get(loaderId);
641
644
  if (existing) return existing;
642
645
 
@@ -809,11 +812,14 @@ function executeLoaderData<TEnv>(
809
812
  // (read-through-swr routes the full task through wrapBackground) — so
810
813
  // the loader's rango.loader span, its fetch/KV platform spans, and the
811
814
  // store write all nest under one explanatory parent instead of dangling
812
- // under the ended foreground phases. Inside runWithRequestContext so
815
+ // under the ended foreground phases. Inside the request context so
813
816
  // observePhase can read tracing on workerd (ALS detaches in waitUntil).
817
+ // No visitor made the invalidations it runs: they set no fresh-reads
818
+ // cookie, and onError marks its errors `background`
819
+ // (runAsBackgroundWork).
814
820
  wrapBackground: (run) => {
815
821
  revalidating = true;
816
- return runWithRequestContext(requestCtxForExecute, () =>
822
+ return runAsBackgroundWork(requestCtxForExecute, () =>
817
823
  observePhase(PHASES.background("loader-revalidation"), run),
818
824
  );
819
825
  },
@@ -7,11 +7,11 @@
7
7
  * consuming Suspense subtree postpones as a hole, and the serve pass runs them
8
8
  * fresh into it.
9
9
  *
10
- * Capture mode is signalled by `requestCtx._shellCaptureRun`, set to true ONLY on
10
+ * Capture mode is signalled by `requestCtx._shellCaptureState.run`, set ONLY on
11
11
  * the derived request context of the background capture task (shell-capture.ts);
12
- * the foreground render never sets it. This module is the single home for the
12
+ * the foreground render never has it. This module is the single home for the
13
13
  * mask so every loader execution site gates the same way (loader-cache.ts
14
- * `resolveLoaderData`, fresh.ts `resolveLoaders`).
14
+ * `resolveLoaderData`, revalidation.ts `resolveOwnerLoaders`).
15
15
  */
16
16
 
17
17
  import {
@@ -22,7 +22,7 @@ import { isRenderableLoading } from "../../types/boundaries.js";
22
22
 
23
23
  /**
24
24
  * True when the current render is the active PPR shell capture and route loaders
25
- * must be masked rather than executed. Reads `_shellCaptureRun` off the ALS
25
+ * must be masked rather than executed. Reads `_shellCaptureState` off the ALS
26
26
  * request context (the capture task re-establishes its derived context via
27
27
  * runWithRequestContext), so it is accurate at the loader resolution sites, which
28
28
  * run synchronously inside the pipeline's context frame.
@@ -30,7 +30,7 @@ import { isRenderableLoading } from "../../types/boundaries.js";
30
30
  export function isShellCaptureActive(
31
31
  reqCtx: RequestContext<any> | undefined = _getRequestContext(),
32
32
  ): boolean {
33
- return reqCtx?._shellCaptureRun === true;
33
+ return reqCtx?._shellCaptureState?.run === true;
34
34
  }
35
35
 
36
36
  // createMaskedLoaderPromise lives in the leaf module mask-nested.ts, beside
@@ -36,7 +36,8 @@ import {
36
36
  import { requestHeaders } from "../../server/request-headers.js";
37
37
  import type { HandlerContext, InternalHandlerContext } from "../../types.js";
38
38
  import type { CacheScope } from "../../cache/cache-scope.js";
39
- import { FILL_PARAM, PREFETCH_HEADER } from "../../prefetch-markers.js";
39
+ import { PREFETCH_HEADER } from "../../prefetch-markers.js";
40
+ import { WIRE } from "../../wire-params.js";
40
41
  import { isInterceptSegment } from "../../browser/intercept-utils.js";
41
42
 
42
43
  /**
@@ -50,7 +51,7 @@ export function requestKind(
50
51
  request: Request,
51
52
  rawUrl: URL,
52
53
  ): "prefetch" | "fill" | undefined {
53
- if (rawUrl.searchParams.has(FILL_PARAM)) return "fill";
54
+ if (rawUrl.searchParams.has(WIRE.fill)) return "fill";
54
55
  return requestHeaders(request).has(PREFETCH_HEADER) ? "prefetch" : undefined;
55
56
  }
56
57
 
@@ -114,14 +115,6 @@ export interface PrefetchDeferral {
114
115
  * a navigation to the page lists.
115
116
  */
116
117
  below?: true;
117
- /**
118
- * The loaders this prefetch deferred, by `$$id` (registerDeferredLoader).
119
- * A deferred loader runs only if something reads it: a handler's or another
120
- * loader's ctx.use() starts it through its registration
121
- * (startDeferredLoader), and revalidation.ts deliverStartedLoaders delivers
122
- * what was started by the end of resolution.
123
- */
124
- deferredLoaders?: Map<string, DeferredLoader>;
125
118
  }
126
119
 
127
120
  /**
@@ -149,7 +142,14 @@ export interface RecordUse {
149
142
  readonly refresh: boolean;
150
143
  }
151
144
 
152
- /** A loader this prefetch deferred: PrefetchDeferral.deferredLoaders. */
145
+ /**
146
+ * A DSL loader registered and not started (InternalHandlerContext
147
+ * ._deferredLoaders): a prefetch deferred it, or a pass that skips loaders
148
+ * (`skipLoaders`) did not run it. It runs only if something reads it: a
149
+ * handler's or another loader's ctx.use() starts it through its registration
150
+ * (startDeferredLoader), and revalidation.ts deliverStartedLoaders delivers
151
+ * what a prefetch started by the end of resolution.
152
+ */
153
153
  export interface DeferredLoader {
154
154
  /**
155
155
  * The segment that registers it. Its run files its handle pushes there,
@@ -232,7 +232,7 @@ export function resolveDeferralScope(
232
232
  loaderIds.add(loaderEntry.loader.$$id);
233
233
  }
234
234
  for (const parallelEntry of getParallelEntries(entry.parallel)) {
235
- // A slot's segment id, as resolveParallelSegmentsWithRevalidation
235
+ // A slot's segment id, as resolveParallelSegments
236
236
  // (revalidation.ts) forms it.
237
237
  const slotIds = flagsLoading(parallelEntry)
238
238
  ? getParallelSlotEntries(entry.parallel)
@@ -245,7 +245,7 @@ export function resolveDeferralScope(
245
245
  for (const orphan of entry.layout) {
246
246
  if (chain.has(orphan)) continue;
247
247
  // An orphan layout's handler is awaited after its owner's ran
248
- // (revalidation.ts resolveOrphanLayoutWithRevalidation): never a unit,
248
+ // (revalidation.ts resolveOrphanLayout): never a unit,
249
249
  // its loaders are still behind its fallback.
250
250
  collect(
251
251
  orphan,
@@ -446,8 +446,8 @@ export function recordUse(
446
446
  }
447
447
 
448
448
  /**
449
- * Register a loader this prefetch deferred, with the segment that registers
450
- * it and how to start it there (PrefetchDeferral.deferredLoaders). The first
449
+ * Register a loader resolution does not start, with the segment that
450
+ * registers it and how to start it there (DeferredLoader). The first
451
451
  * registration wins, as the loader's one run per request does.
452
452
  */
453
453
  export function registerDeferredLoader(
@@ -456,18 +456,21 @@ export function registerDeferredLoader(
456
456
  owner: string,
457
457
  start: () => Promise<unknown>,
458
458
  ): void {
459
- const plan = getPrefetchDeferral(ctx);
460
- if (plan && !plan.deferredLoaders?.has(loaderId)) {
461
- (plan.deferredLoaders ??= new Map()).set(loaderId, { owner, start });
459
+ const internal = ctx as InternalHandlerContext<any, any>;
460
+ if (!internal._deferredLoaders?.has(loaderId)) {
461
+ (internal._deferredLoaders ??= new Map()).set(loaderId, { owner, start });
462
462
  }
463
463
  }
464
464
 
465
465
  /**
466
- * A reader asks for a loader this prefetch deferred and nothing has started:
467
- * start it through its registration and return the run, or undefined when
468
- * the read takes the ordinary path. Without this the reader ran the loader
469
- * bare, without its cache() binding, and the segment delivered afterwards
470
- * read the binding: two values on one page and an extra run on a HIT.
466
+ * A reader asks for a registered loader (registerDeferredLoader) and nothing
467
+ * has started it: start it through its registration and return the run, or
468
+ * undefined when the read takes the ordinary path. Without this the reader
469
+ * ran the loader bare, as a handler-invoked loader: in a prefetch without its
470
+ * cache() binding, so the segment delivered afterwards read the binding (two
471
+ * values on one page, an extra run on a HIT); in a route record's background
472
+ * re-render outside its DSL scope, so the record kept its handle pushes and
473
+ * every HIT showed them twice.
471
474
  *
472
475
  * A started cache() binding of the loader (`_loaderCacheOverrides`) is never
473
476
  * redirected here: the read is that binding's own MISS execution, and
@@ -478,7 +481,7 @@ export function startDeferredLoader(
478
481
  loaderId: string,
479
482
  ): Promise<unknown> | undefined {
480
483
  const internal = ctx as InternalHandlerContext<any, any>;
481
- const deferred = internal._prefetchDeferral?.deferredLoaders?.get(loaderId);
484
+ const deferred = internal._deferredLoaders?.get(loaderId);
482
485
  if (
483
486
  !deferred ||
484
487
  deferred.started ||
@@ -492,8 +495,8 @@ export function startDeferredLoader(
492
495
 
493
496
  /**
494
497
  * The segment a run of `loaderId` files its handle pushes under: the one
495
- * that registers it when this prefetch deferred it, else the segment being
496
- * resolved.
498
+ * that registers it when it is registered (registerDeferredLoader), else the
499
+ * segment being resolved.
497
500
  */
498
501
  export function loaderOwner(
499
502
  ctx: HandlerContext<any, any>,
@@ -501,7 +504,7 @@ export function loaderOwner(
501
504
  ): string | undefined {
502
505
  const internal = ctx as InternalHandlerContext<any, any>;
503
506
  return (
504
- internal._prefetchDeferral?.deferredLoaders?.get(loaderId)?.owner ??
507
+ internal._deferredLoaders?.get(loaderId)?.owner ??
505
508
  internal._currentSegmentId
506
509
  );
507
510
  }
@@ -0,0 +1,139 @@
1
+ /**
2
+ * ResolutionPass: what one walk over a matched chain reads.
3
+ *
4
+ * Every function of the segment walker (revalidation.ts, intercept-resolution.ts)
5
+ * resolves against the same request: the handler context, the segments the
6
+ * client holds, the previous and next location, the action, the staleness and
7
+ * the router's deps. They take this one object instead of threading the same
8
+ * ten values through each signature in a fixed order, where two adjacent
9
+ * `URL`s or `Record<string, string>`s could swap without a type error.
10
+ *
11
+ * A full match (a document, the background re-render of a route record, a
12
+ * build-time prerender) is a FRESH pass: see FRESH below.
13
+ */
14
+
15
+ import type { HandlerContext } from "../../types.js";
16
+ import type { ActionContext, SegmentResolutionDeps } from "../types.js";
17
+ import { NOTHING_HELD } from "./prefetch-deferral.js";
18
+
19
+ /**
20
+ * The segment ids the client holds, the set resolution works on (match-api.ts
21
+ * may delete a route's id from the listed set to force a render). Empty on a
22
+ * full refetch.
23
+ */
24
+ export type ClientHeld = ReadonlySet<string>;
25
+
26
+ /**
27
+ * The client of a full match: it holds nothing and asks for nothing to be
28
+ * kept. Compared by identity (`pass.client === FRESH`), never by size: a
29
+ * partial request whose client holds nothing (NOTHING_HELD, an empty
30
+ * `_rsc_segments`) still asks the revalidate() rules. On a FRESH pass every
31
+ * segment is new without a decision, trace entry or `revalidation.decision`
32
+ * event, and the walker takes the full match's branches (revalidation.ts,
33
+ * each marked FRESH).
34
+ */
35
+ export const FRESH: ClientHeld = new Set<string>();
36
+
37
+ /**
38
+ * A pass without the router's deps: what match middleware knows about the
39
+ * request. createSegmentWrappers (segment-wrappers.ts) binds the deps.
40
+ */
41
+ export interface ResolutionPassInput<TEnv = any> {
42
+ readonly context: HandlerContext<any, TEnv>;
43
+ readonly client: ClientHeld;
44
+ /** The matched route's params, stamped on every segment the walk emits. */
45
+ readonly params: Record<string, string>;
46
+ readonly prevParams: Record<string, string>;
47
+ /** Undefined only on a build-time FRESH pass (freshPassInput). */
48
+ readonly request: Request | undefined;
49
+ readonly prevUrl: URL;
50
+ readonly nextUrl: URL;
51
+ readonly routeKey: string;
52
+ readonly actionContext?: ActionContext;
53
+ readonly stale?: boolean;
54
+ /** NavigationSnapshot.heldBoundary: the walk cuts there, sending no segment. */
55
+ readonly heldBoundary?: string;
56
+ /**
57
+ * No loader is started and no loader segment is emitted: build time
58
+ * (prerender-match.ts) and the background re-render of a route record
59
+ * (background-revalidation.ts), whose records never hold loaders.
60
+ * resolveOwnerLoaders registers each loader instead: in the re-render a
61
+ * handler's read starts it as its declaration; at build time a read
62
+ * throws (setupBuildUse).
63
+ */
64
+ readonly skipLoaders?: boolean;
65
+ /**
66
+ * A render error rethrows instead of rendering the error boundary, and a
67
+ * streamed handler's rejection is not recovered. Build time only, so a
68
+ * failure (or `throw new Skip()`) reaches the build instead of being baked
69
+ * into a frozen 200 (issue #587).
70
+ */
71
+ readonly throwOnError?: boolean;
72
+ /**
73
+ * The request is a navigation, though the pass is FRESH: the background
74
+ * re-render of a record a navigation read stale or wrote
75
+ * (background-revalidation.ts). An entry's error reports isPartial, as the
76
+ * navigation's own pass does.
77
+ */
78
+ readonly partialRequest?: boolean;
79
+ }
80
+
81
+ export interface ResolutionPass<TEnv = any> extends ResolutionPassInput<TEnv> {
82
+ readonly deps: SegmentResolutionDeps<TEnv>;
83
+ }
84
+
85
+ /**
86
+ * The input of a full match's pass (FRESH). At build time the prerender
87
+ * context's `request` is a throwing getter (handler-context.ts
88
+ * createPrerenderContext): the pass carries undefined instead.
89
+ */
90
+ export function freshPassInput<TEnv>(
91
+ context: HandlerContext<any, TEnv>,
92
+ routeKey: string,
93
+ params: Record<string, string>,
94
+ options?: {
95
+ skipLoaders?: boolean;
96
+ throwOnError?: boolean;
97
+ partialRequest?: boolean;
98
+ },
99
+ ): ResolutionPassInput<TEnv> {
100
+ let request: Request | undefined;
101
+ try {
102
+ request = context.request;
103
+ } catch {}
104
+ return {
105
+ context,
106
+ client: FRESH,
107
+ params,
108
+ prevParams: {},
109
+ request,
110
+ prevUrl: context.url,
111
+ nextUrl: context.url,
112
+ routeKey,
113
+ ...options,
114
+ };
115
+ }
116
+
117
+ /**
118
+ * The input of a pass whose client holds nothing: every segment is new and
119
+ * renders. The background re-render of an intercept (background-revalidation.ts)
120
+ * resolves with it.
121
+ */
122
+ export function heldNothingPassInput<TEnv>(
123
+ context: HandlerContext<any, TEnv>,
124
+ routeKey: string,
125
+ params: Record<string, string>,
126
+ options?: { skipLoaders?: boolean },
127
+ ): ResolutionPassInput<TEnv> {
128
+ return {
129
+ context,
130
+ client: NOTHING_HELD,
131
+ params,
132
+ prevParams: {},
133
+ request: context.request,
134
+ prevUrl: context.url,
135
+ nextUrl: context.url,
136
+ routeKey,
137
+ ...options,
138
+ };
139
+ }