@rangojs/router 0.26.6 → 0.28.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 (234) 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-coordinator.d.ts +0 -4
  4. package/dist/types/browser/action-fence.d.ts +11 -3
  5. package/dist/types/browser/event-controller.d.ts +20 -1
  6. package/dist/types/browser/history-state.d.ts +7 -9
  7. package/dist/types/browser/intercept-utils.d.ts +23 -0
  8. package/dist/types/browser/navigation-store.d.ts +8 -6
  9. package/dist/types/browser/react/filter-segment-order.d.ts +2 -2
  10. package/dist/types/browser/rsc-router.d.ts +1 -1
  11. package/dist/types/browser/segment-reconciler.d.ts +6 -4
  12. package/dist/types/browser/types.d.ts +2 -1
  13. package/dist/types/browser/wire-protocol.d.ts +25 -0
  14. package/dist/types/cache/cache-key-utils.d.ts +11 -5
  15. package/dist/types/cache/cache-scope.d.ts +34 -22
  16. package/dist/types/cache/cf/cf-cache-constants.d.ts +2 -2
  17. package/dist/types/cache/cf/cf-cache-store.d.ts +47 -65
  18. package/dist/types/cache/cf/cf-kv-utils.d.ts +1 -1
  19. package/dist/types/cache/handle-snapshot.d.ts +9 -4
  20. package/dist/types/cache/segment-codec.d.ts +14 -0
  21. package/dist/types/cache/shell-snapshot.d.ts +1 -1
  22. package/dist/types/cache/tag-invalidation.d.ts +10 -0
  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 +4 -6
  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 +4 -4
  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 +12 -11
  35. package/dist/types/router/match-handlers.d.ts +2 -6
  36. package/dist/types/router/match-middleware/background-revalidation.d.ts +10 -20
  37. package/dist/types/router/match-middleware/cache-lookup.d.ts +35 -87
  38. package/dist/types/router/match-middleware/cache-store.d.ts +2 -2
  39. package/dist/types/router/match-middleware/segment-resolution.d.ts +8 -6
  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/revalidation.d.ts +6 -1
  44. package/dist/types/router/router-context.d.ts +9 -63
  45. package/dist/types/router/router-interfaces.d.ts +8 -14
  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 +93 -0
  50. package/dist/types/router/segment-resolution/revalidation.d.ts +82 -31
  51. package/dist/types/router/segment-resolution.d.ts +1 -2
  52. package/dist/types/router/segment-wrappers.d.ts +11 -48
  53. package/dist/types/router/telemetry.d.ts +2 -0
  54. package/dist/types/router/types.d.ts +10 -0
  55. package/dist/types/rsc/full-payload.d.ts +2 -2
  56. package/dist/types/rsc/handler-context.d.ts +6 -6
  57. package/dist/types/rsc/json-route-result.d.ts +5 -9
  58. package/dist/types/rsc/request-stages.d.ts +203 -0
  59. package/dist/types/rsc/response-cache-serve.d.ts +7 -12
  60. package/dist/types/rsc/response-route-handler.d.ts +2 -2
  61. package/dist/types/rsc/routine-plan.d.ts +1 -1
  62. package/dist/types/rsc/server-action.d.ts +2 -2
  63. package/dist/types/rsc/shell-capture-constants.d.ts +2 -2
  64. package/dist/types/rsc/shell-capture.d.ts +132 -15
  65. package/dist/types/rsc/shell-serve.d.ts +37 -12
  66. package/dist/types/server/context.d.ts +9 -8
  67. package/dist/types/server/handle-store.d.ts +16 -0
  68. package/dist/types/server/render-pass.d.ts +72 -0
  69. package/dist/types/server/request-context.d.ts +24 -104
  70. package/dist/types/server/rewrite-trail.d.ts +2 -2
  71. package/dist/types/testing/dispatch.d.ts +35 -18
  72. package/dist/types/testing/serve-shell-request.d.ts +7 -0
  73. package/dist/types/types/cache-types.d.ts +6 -3
  74. package/dist/types/types/error-types.d.ts +9 -1
  75. package/dist/types/types/handler-context.d.ts +18 -7
  76. package/dist/types/types/segments.d.ts +1 -1
  77. package/dist/types/vite/discovery/build-discovery-phase.d.ts +40 -0
  78. package/dist/types/vite/discovery/build-env.d.ts +22 -0
  79. package/dist/types/vite/discovery/bundle-scan-phase.d.ts +12 -0
  80. package/dist/types/vite/discovery/dev-prerender-cache.d.ts +3 -2
  81. package/dist/types/vite/discovery/dev-server-phase.d.ts +26 -0
  82. package/dist/types/vite/discovery/discovery-errors.d.ts +1 -1
  83. package/dist/types/vite/discovery/state.d.ts +12 -0
  84. package/dist/types/vite/plugins/version-plugin.d.ts +1 -1
  85. package/dist/types/vite/plugins/virtual-entries.d.ts +1 -1
  86. package/dist/types/vite/router-discovery.d.ts +8 -47
  87. package/dist/types/vite/temp-rsc-server.d.ts +39 -0
  88. package/dist/types/vite/utils/prerender-utils.d.ts +9 -0
  89. package/dist/types/wire-params.d.ts +34 -0
  90. package/dist/vite/index.js +3809 -3710
  91. package/package.json +1 -1
  92. package/skills/cache-guide/SKILL.md +8 -5
  93. package/skills/caching/SKILL.md +37 -18
  94. package/skills/client-urls/SKILL.md +2 -1
  95. package/skills/document-cache/SKILL.md +18 -1
  96. package/skills/handler-use/SKILL.md +1 -1
  97. package/skills/intercept/SKILL.md +53 -13
  98. package/skills/layout/SKILL.md +11 -7
  99. package/skills/loader/SKILL.md +30 -13
  100. package/skills/middleware/SKILL.md +19 -1
  101. package/skills/migrate-nextjs/SKILL.md +5 -4
  102. package/skills/parallel/SKILL.md +5 -4
  103. package/skills/ppr/SKILL.md +19 -3
  104. package/skills/prerender/SKILL.md +39 -15
  105. package/skills/rango/SKILL.md +23 -11
  106. package/skills/server-actions/SKILL.md +51 -6
  107. package/skills/testing/predicates.md +1 -1
  108. package/skills/testing/response-routes.md +6 -4
  109. package/skills/testing/serve-shell-request.md +7 -7
  110. package/skills/use-cache/SKILL.md +46 -8
  111. package/skills/ux-architecture/SKILL.md +3 -2
  112. package/src/__internal.ts +7 -1
  113. package/src/browser/action-coordinator.ts +2 -13
  114. package/src/browser/action-fence.ts +11 -3
  115. package/src/browser/connection-warmup.ts +3 -1
  116. package/src/browser/event-controller.ts +42 -3
  117. package/src/browser/history-state.ts +15 -13
  118. package/src/browser/intercept-utils.ts +51 -0
  119. package/src/browser/navigation-bridge.ts +34 -30
  120. package/src/browser/navigation-client.ts +21 -33
  121. package/src/browser/navigation-store.ts +25 -27
  122. package/src/browser/navigation-transaction.ts +7 -11
  123. package/src/browser/partial-update.ts +8 -0
  124. package/src/browser/prefetch/cache.ts +2 -1
  125. package/src/browser/prefetch/fetch.ts +8 -16
  126. package/src/browser/prefetch/queue.ts +4 -4
  127. package/src/browser/react/NavigationProvider.tsx +2 -1
  128. package/src/browser/react/filter-segment-order.ts +2 -2
  129. package/src/browser/rsc-router.tsx +17 -2
  130. package/src/browser/segment-reconciler.ts +66 -53
  131. package/src/browser/server-action-bridge.ts +265 -127
  132. package/src/browser/types.ts +2 -1
  133. package/src/browser/wire-protocol.ts +41 -0
  134. package/src/cache/cache-key-utils.ts +11 -5
  135. package/src/cache/cache-runtime.ts +110 -50
  136. package/src/cache/cache-scope.ts +110 -64
  137. package/src/cache/cf/cf-cache-constants.ts +2 -2
  138. package/src/cache/cf/cf-cache-store.ts +486 -874
  139. package/src/cache/cf/cf-kv-utils.ts +1 -1
  140. package/src/cache/document-cache.ts +42 -15
  141. package/src/cache/handle-snapshot.ts +19 -7
  142. package/src/cache/segment-codec.ts +55 -0
  143. package/src/cache/shell-snapshot.ts +2 -2
  144. package/src/cache/tag-invalidation.ts +21 -2
  145. package/src/cache/taint.ts +5 -3
  146. package/src/cache/types.ts +7 -3
  147. package/src/cache/vercel/vercel-cache-store.ts +243 -310
  148. package/src/handles/pushed-by.ts +1 -1
  149. package/src/prefetch-markers.ts +4 -7
  150. package/src/prerender/store.ts +76 -0
  151. package/src/prerender/warm-request.ts +8 -4
  152. package/src/prerender.ts +2 -2
  153. package/src/route-name.ts +8 -0
  154. package/src/router/error-handling.ts +4 -4
  155. package/src/router/instrument.ts +4 -3
  156. package/src/router/intercept-resolution.ts +77 -75
  157. package/src/router/loader-resolution.ts +5 -4
  158. package/src/router/match-api.ts +4 -5
  159. package/src/router/match-context.ts +33 -12
  160. package/src/router/match-handlers.ts +8 -20
  161. package/src/router/match-middleware/background-revalidation.ts +41 -53
  162. package/src/router/match-middleware/cache-lookup.ts +680 -720
  163. package/src/router/match-middleware/cache-store.ts +14 -12
  164. package/src/router/match-middleware/intercept-resolution.ts +3 -23
  165. package/src/router/match-middleware/segment-resolution.ts +34 -61
  166. package/src/router/match-result.ts +1 -20
  167. package/src/router/middleware.ts +69 -11
  168. package/src/router/navigation-snapshot.ts +4 -3
  169. package/src/router/prerender-match.ts +100 -44
  170. package/src/router/preview-match.ts +6 -41
  171. package/src/router/request-classification.ts +13 -7
  172. package/src/router/revalidation.ts +33 -7
  173. package/src/router/route-snapshot.ts +2 -5
  174. package/src/router/router-context.ts +14 -117
  175. package/src/router/router-interfaces.ts +8 -14
  176. package/src/router/segment-resolution/loader-cache.ts +14 -9
  177. package/src/router/segment-resolution/loader-mask.ts +5 -5
  178. package/src/router/segment-resolution/prefetch-deferral.ts +30 -27
  179. package/src/router/segment-resolution/resolution-pass.ts +137 -0
  180. package/src/router/segment-resolution/revalidation.ts +897 -776
  181. package/src/router/segment-resolution.ts +4 -13
  182. package/src/router/segment-wrappers.ts +51 -269
  183. package/src/router/telemetry.ts +5 -0
  184. package/src/router/types.ts +10 -0
  185. package/src/router.ts +12 -13
  186. package/src/rsc/full-payload.ts +6 -5
  187. package/src/rsc/handler-context.ts +8 -14
  188. package/src/rsc/handler.ts +126 -507
  189. package/src/rsc/json-route-result.ts +5 -9
  190. package/src/rsc/loader-fetch.ts +6 -5
  191. package/src/rsc/progressive-enhancement.ts +5 -0
  192. package/src/rsc/request-stages.ts +757 -0
  193. package/src/rsc/response-cache-serve.ts +10 -14
  194. package/src/rsc/response-route-handler.ts +13 -5
  195. package/src/rsc/rewrite-passes.ts +2 -1
  196. package/src/rsc/routine-plan.ts +1 -1
  197. package/src/rsc/rsc-rendering.ts +172 -349
  198. package/src/rsc/server-action.ts +87 -113
  199. package/src/rsc/shell-capture-constants.ts +3 -1
  200. package/src/rsc/shell-capture.ts +212 -169
  201. package/src/rsc/shell-serve.ts +121 -18
  202. package/src/rsc/ssr-setup.ts +5 -4
  203. package/src/segment-system.tsx +3 -3
  204. package/src/server/context.ts +22 -25
  205. package/src/server/handle-store.ts +55 -0
  206. package/src/server/render-pass.ts +291 -0
  207. package/src/server/request-context.ts +26 -115
  208. package/src/server/rewrite-trail.ts +6 -4
  209. package/src/ssr/index.tsx +1 -1
  210. package/src/ssr/ssr-root.tsx +1 -0
  211. package/src/testing/dispatch.ts +223 -710
  212. package/src/testing/e2e/page-helpers.ts +2 -2
  213. package/src/testing/serve-shell-request.ts +37 -32
  214. package/src/types/cache-types.ts +6 -3
  215. package/src/types/error-types.ts +9 -1
  216. package/src/types/handler-context.ts +21 -7
  217. package/src/types/segments.ts +1 -1
  218. package/src/vite/discovery/build-discovery-phase.ts +204 -0
  219. package/src/vite/discovery/build-env.ts +148 -0
  220. package/src/vite/discovery/bundle-scan-phase.ts +128 -0
  221. package/src/vite/discovery/dev-prerender-cache.ts +8 -3
  222. package/src/vite/discovery/dev-server-phase.ts +1776 -0
  223. package/src/vite/discovery/discover-routers.ts +1 -1
  224. package/src/vite/discovery/discovery-errors.ts +1 -1
  225. package/src/vite/discovery/prerender-collection.ts +24 -39
  226. package/src/vite/discovery/state.ts +15 -0
  227. package/src/vite/plugins/version-plugin.ts +1 -1
  228. package/src/vite/plugins/virtual-entries.ts +1 -1
  229. package/src/vite/router-discovery.ts +29 -2428
  230. package/src/vite/temp-rsc-server.ts +223 -0
  231. package/src/vite/utils/prerender-utils.ts +47 -0
  232. package/src/wire-params.ts +35 -0
  233. package/dist/types/router/segment-resolution/fresh.d.ts +0 -72
  234. package/src/router/segment-resolution/fresh.ts +0 -945
@@ -19,7 +19,7 @@
19
19
  * +---------------------+
20
20
  * | Should skip cache? |
21
21
  * | - !cacheScope |──yes──> return
22
- * | - isAction |
22
+ * | - rendersLive |
23
23
  * | - cacheHit |
24
24
  * | - method !== GET |
25
25
  * +---------------------+
@@ -105,11 +105,16 @@ import {
105
105
  getRequestContext,
106
106
  type RequestContext,
107
107
  } from "../../server/request-context.js";
108
- import type { MatchContext, MatchPipelineState } from "../match-context.js";
108
+ import {
109
+ rendersLive,
110
+ type MatchContext,
111
+ type MatchPipelineState,
112
+ } from "../match-context.js";
109
113
  import { createShellImplicitDocScope } from "../../cache/cache-scope.js";
110
114
  import { recordSegmentTags } from "../../cache/cache-tag.js";
111
115
  import { executionStart } from "../../cache/tag-invalidation.js";
112
116
  import { getRouterContext } from "../router-context.js";
117
+ import { interceptRecordKey } from "../intercept-resolution.js";
113
118
  import { debugLog, debugWarn, getOrCreateRequestId } from "../logging.js";
114
119
  import { INTERNAL_RANGO_DEBUG } from "../../internal-debug.js";
115
120
  import type { GeneratorMiddleware } from "./cache-lookup.js";
@@ -155,7 +160,7 @@ export function withCacheStore<TEnv>(
155
160
  // and could only write an incomplete one.
156
161
  if (
157
162
  !ctx.cacheScope?.enabled ||
158
- ctx.isAction ||
163
+ rendersLive(ctx) ||
159
164
  state.cacheHit ||
160
165
  ctx.request.method !== "GET" ||
161
166
  !recordUse(ctx.handlerContext).write
@@ -280,7 +285,7 @@ export function withCacheStore<TEnv>(
280
285
  ctx.pathname,
281
286
  ctx.matched.params,
282
287
  allSegmentsToCache,
283
- ctx.isIntercept,
288
+ interceptRecordKey(ctx.interceptResult),
284
289
  // A capture's doc record lives only in the shell entry, which
285
290
  // putShell gates by the capture start; skipping it here would
286
291
  // read as a render that produced no record.
@@ -350,12 +355,14 @@ function recordShellCaptureDocRecord<TEnv>(
350
355
  segments: ResolvedSegment[],
351
356
  ): void {
352
357
  const requestCtx = getRequestContext();
353
- if (!requestCtx?._shellCaptureRun) return;
358
+ if (!requestCtx?._shellCaptureState?.run) return;
354
359
  const marker = requestCtx._shellImplicitCache;
355
360
  if (!marker?.store) return;
356
361
  const scope = ctx.cacheScope;
357
362
  if (!scope || scope.isShellImplicitDocScope) return;
358
- if (ctx.isAction || ctx.isIntercept || ctx.request.method !== "GET") return;
363
+ if (rendersLive(ctx) || ctx.isIntercept || ctx.request.method !== "GET") {
364
+ return;
365
+ }
359
366
  if (state.cacheSource === "prerender") return;
360
367
  if (!scope.allowsCache("write")) return;
361
368
 
@@ -375,12 +382,7 @@ function recordShellCaptureDocRecord<TEnv>(
375
382
  // pushes settled, so the record's handle encode never races them.
376
383
  requestCtx.onResponse((response) => {
377
384
  requestCtx.waitUntil(() =>
378
- docScope.cacheRoute(
379
- ctx.pathname,
380
- ctx.matched.params,
381
- segments,
382
- ctx.isIntercept,
383
- ),
385
+ docScope.cacheRoute(ctx.pathname, ctx.matched.params, segments),
384
386
  );
385
387
  return response;
386
388
  });
@@ -103,6 +103,7 @@
103
103
  */
104
104
  import type { ResolvedSegment } from "../../types.js";
105
105
  import type { MatchContext, MatchPipelineState } from "../match-context.js";
106
+ import { resolutionPassInput } from "../match-context.js";
106
107
  import { getRouterContext } from "../router-context.js";
107
108
  import type { GeneratorMiddleware } from "./cache-lookup.js";
108
109
  import { debugLog } from "../logging.js";
@@ -175,21 +176,10 @@ export function withInterceptResolution<TEnv>(
175
176
  const Store = ctx.Store;
176
177
  const interceptSegments = await Store.run(() =>
177
178
  resolveInterceptEntry(
179
+ resolutionPassInput(ctx),
178
180
  ctx.interceptResult!.intercept,
179
181
  ctx.interceptResult!.entry,
180
- ctx.matched.params,
181
- ctx.handlerContext,
182
182
  true, // belongsToRoute
183
- {
184
- clientSegmentIds: ctx.clientSegmentSet,
185
- prevParams: ctx.prevParams,
186
- request: ctx.request,
187
- prevUrl: ctx.prevUrl,
188
- nextUrl: ctx.url,
189
- routeKey: ctx.routeKey,
190
- actionContext: ctx.actionContext,
191
- stale: ctx.stale,
192
- },
193
183
  ),
194
184
  );
195
185
 
@@ -242,20 +232,10 @@ async function handleCacheHitIntercept<TEnv>(
242
232
  const loaderSegments = resolveInterceptLoadersOnly
243
233
  ? await ctx.Store.run(() =>
244
234
  resolveInterceptLoadersOnly(
235
+ resolutionPassInput(ctx),
245
236
  ctx.interceptResult!.intercept,
246
237
  ctx.interceptResult!.entry,
247
- ctx.handlerContext,
248
238
  true, // belongsToRoute
249
- {
250
- clientSegmentIds: ctx.clientSegmentSet,
251
- prevParams: ctx.prevParams,
252
- request: ctx.request,
253
- prevUrl: ctx.prevUrl,
254
- nextUrl: ctx.url,
255
- routeKey: ctx.routeKey,
256
- actionContext: ctx.actionContext,
257
- stale: ctx.stale,
258
- },
259
239
  ),
260
240
  )
261
241
  : [];
@@ -29,15 +29,16 @@
29
29
  * yes no
30
30
  * | |
31
31
  * v v
32
- * resolveAll resolveAllWithRevalidation
33
- * Segments Segments
34
- * | |
32
+ * FRESH pass partial pass
35
33
  * | | (compares with prev state)
36
34
  * | | (handles null components)
37
35
  * | |
38
36
  * +-----------+
39
37
  * |
40
38
  * v
39
+ * resolveAllSegments()
40
+ * |
41
+ * v
41
42
  * +---------------------------+
42
43
  * | Update state: |
43
44
  * | - state.segments |
@@ -54,13 +55,14 @@
54
55
  * RESOLUTION MODES
55
56
  * ================
56
57
  *
58
+ * Both use resolveAllSegments().
59
+ *
57
60
  * Full Match (document request):
58
- * - Uses resolveAllSegments()
59
- * - No revalidation logic (nothing to compare against)
61
+ * - A FRESH pass (freshPassInput): no revalidation logic (nothing to
62
+ * compare against)
60
63
  * - Simple resolution of all route entries
61
64
  *
62
65
  * Partial Match (navigation):
63
- * - Uses resolveAllSegmentsWithRevalidation()
64
66
  * - Compares current vs previous params/URL
65
67
  * - Sets component = null for segments client already has
66
68
  * - Respects custom revalidation rules
@@ -90,6 +92,8 @@ import type { ResolvedSegment } from "../../types.js";
90
92
  import type { EntryData } from "../../server/context.js";
91
93
  import { _getRequestContext } from "../../server/request-context.js";
92
94
  import type { MatchContext, MatchPipelineState } from "../match-context.js";
95
+ import { resolutionPassInput } from "../match-context.js";
96
+ import { freshPassInput } from "../segment-resolution/resolution-pass.js";
93
97
  import { getRouterContext } from "../router-context.js";
94
98
  import type { GeneratorMiddleware } from "./cache-lookup.js";
95
99
 
@@ -155,64 +159,33 @@ export function withSegmentResolution<TEnv>(
155
159
  reqCtx._treeHasStreaming = treeHasStreaming(ctx.entries);
156
160
  }
157
161
 
158
- const { resolveAllSegmentsWithRevalidation, resolveAllSegments } =
159
- getRouterContext<TEnv>();
160
-
161
- const Store = ctx.Store;
162
-
163
- if (ctx.isFullMatch) {
164
- const segments = await Store.run(() =>
165
- resolveAllSegments(
166
- ctx.entries,
167
- ctx.routeKey,
168
- ctx.matched.params,
169
- ctx.handlerContext,
170
- ctx.loaderPromises,
171
- ),
172
- );
173
-
174
- state.segments = segments;
175
- state.matchedIds = segments.map((s: { id: string }) => s.id);
162
+ const { resolveAllSegments } = getRouterContext<TEnv>();
176
163
 
177
- if (reqCtx) {
178
- reqCtx._resolveRenderBarrier(segments);
179
- }
180
- for (const segment of segments) {
181
- yield segment;
182
- }
183
- } else {
184
- // Partial match (navigation) - resolution with revalidation logic
185
- const result = await Store.run(() =>
186
- resolveAllSegmentsWithRevalidation(
187
- ctx.entries,
188
- ctx.routeKey,
189
- ctx.matched.params,
190
- ctx.handlerContext,
191
- ctx.clientSegmentSet,
192
- ctx.prevParams,
193
- ctx.request,
194
- ctx.prevUrl,
195
- ctx.url,
196
- ctx.actionContext,
197
- ctx.interceptResult,
198
- ctx.localRouteName,
199
- ctx.pathname,
200
- ctx.stale,
201
- ),
202
- );
164
+ // A full match (interceptResult null) is a FRESH pass.
165
+ const result = await ctx.Store.run(() =>
166
+ resolveAllSegments(
167
+ ctx.isFullMatch
168
+ ? freshPassInput(ctx.handlerContext, ctx.routeKey, ctx.matched.params)
169
+ : resolutionPassInput(ctx),
170
+ ctx.entries,
171
+ {
172
+ interceptResult: ctx.interceptResult,
173
+ localRouteName: ctx.localRouteName,
174
+ pathname: ctx.pathname,
175
+ },
176
+ ),
177
+ );
203
178
 
204
- // Update state with resolved segments
205
- state.segments = result.segments;
206
- state.matchedIds = result.matchedIds;
207
-
208
- if (reqCtx) {
209
- reqCtx._resolveRenderBarrier(result.segments);
210
- }
179
+ state.segments = result.segments;
180
+ state.matchedIds = ctx.isFullMatch
181
+ ? result.segments.map((s: { id: string }) => s.id)
182
+ : result.matchedIds;
211
183
 
212
- // Yield all resolved segments
213
- for (const segment of result.segments) {
214
- yield segment;
215
- }
184
+ if (reqCtx) {
185
+ reqCtx._resolveRenderBarrier(result.segments);
186
+ }
187
+ for (const segment of result.segments) {
188
+ yield segment;
216
189
  }
217
190
 
218
191
  if (ms) {
@@ -109,7 +109,7 @@
109
109
  */
110
110
  import type { MatchResult, ResolvedSegment } from "../types.js";
111
111
  import { isPlaceholder } from "../types/segments.js";
112
- import { loaderParentId } from "../browser/react/filter-segment-order.js";
112
+ import { interceptMatched } from "../browser/intercept-utils.js";
113
113
  import type { MatchContext, MatchPipelineState } from "./match-context.js";
114
114
  import { debugLog } from "./logging.js";
115
115
  import { appendMetric } from "./metrics.js";
@@ -196,25 +196,6 @@ function deduplicateLoaderSegments(
196
196
  return { segments: result, removedIds };
197
197
  }
198
198
 
199
- /**
200
- * An intercept's `matched`: the client's ids without any of the slot's (its
201
- * id and every loader segment on it, another intercept's into the same slot
202
- * included), then the slot's (interceptSegmentIds). One order for the
203
- * prefetch, its fill, an action and a revisit: a fill whose `matched` differs
204
- * from its tree's is rendered as a new tree (browser/partial-update.ts
205
- * fillHoles).
206
- */
207
- function interceptMatched(
208
- clientIds: readonly string[],
209
- slotIds: readonly string[],
210
- ): string[] {
211
- // loaderParentId leaves a non-loader id as it is: the slot's own id goes too.
212
- return [
213
- ...clientIds.filter((id) => loaderParentId(id) !== slotIds[0]),
214
- ...slotIds,
215
- ];
216
- }
217
-
218
199
  export function buildMatchResult<TEnv>(
219
200
  allSegments: ResolvedSegment[],
220
201
  ctx: MatchContext<TEnv>,
@@ -542,6 +542,51 @@ function mergeResponse(
542
542
  return merged;
543
543
  }
544
544
 
545
+ /**
546
+ * The response holder past one middleware. Attached, it reads and writes its
547
+ * middleware's holder, so a chain shares one response. A middleware that
548
+ * settles without that run's response while it runs (it returns or throws
549
+ * its own Response, throws, or rewrites: a timeout fallback, a stale
550
+ * document cache entry's background re-run, cache/document-cache.ts), or
551
+ * that calls next() after it returned, detaches that run: the rest of the
552
+ * chain builds its own response, and the middleware ahead keep what the
553
+ * middleware settled with. With one holder, a
554
+ * forgiving middleware ahead of the cache returned the re-run's response,
555
+ * whose body the cache had drained into the store.
556
+ */
557
+ class DownstreamHolder implements ResponseHolder {
558
+ private own: Response | null = null;
559
+ private attached = true;
560
+
561
+ constructor(private readonly parent: ResponseHolder) {}
562
+
563
+ get response(): Response | null {
564
+ return this.attached ? this.parent.response : this.own;
565
+ }
566
+
567
+ set response(response: Response | null) {
568
+ if (this.attached) this.parent.response = response;
569
+ else this.own = response;
570
+ }
571
+
572
+ /** From `from`: what the run has seen so far, or a late run's stub. */
573
+ detach(from: Response | null = this.parent.response): void {
574
+ this.own = from;
575
+ this.attached = false;
576
+ }
577
+ }
578
+
579
+ /**
580
+ * A middleware settled without the response of the run its next() started
581
+ * (its own Response, a throw, a rewrite error) while that run still goes.
582
+ */
583
+ function detachIfRunning(
584
+ holder: DownstreamHolder | undefined,
585
+ nextResolvedAt: number | undefined,
586
+ ): void {
587
+ if (holder && nextResolvedAt === undefined) holder.detach();
588
+ }
589
+
545
590
  /**
546
591
  * Execute middleware chain
547
592
  *
@@ -575,23 +620,23 @@ export async function executeMiddleware<TEnv>(
575
620
  const stubResponse = new Response(null, { status: 200 });
576
621
  const responseHolder: ResponseHolder = { response: stubResponse };
577
622
 
578
- const next = async (): Promise<Response> => {
623
+ const next = async (holder: ResponseHolder): Promise<Response> => {
579
624
  if (index >= middlewares.length) {
580
625
  // End of chain - call actual RSC handler
581
626
  const response = await finalHandler();
582
627
 
583
628
  if (isWebSocketUpgradeResponse(response)) {
584
- responseHolder.response = response;
629
+ holder.response = response;
585
630
  return response;
586
631
  }
587
632
 
588
633
  // Chain ran to completion: stub headers overwrite (stubOverridesNonCookie)
589
634
  // and reqCtx stub headers are merged in.
590
- responseHolder.response = mergeResponse(response, stubResponse.headers, {
635
+ holder.response = mergeResponse(response, stubResponse.headers, {
591
636
  stubOverridesNonCookie: true,
592
637
  });
593
638
 
594
- return responseHolder.response;
639
+ return holder.response;
595
640
  }
596
641
 
597
642
  const middlewareOrdinal = index;
@@ -601,7 +646,7 @@ export async function executeMiddleware<TEnv>(
601
646
  env,
602
647
  params,
603
648
  variables,
604
- responseHolder,
649
+ holder,
605
650
  reverse,
606
651
  );
607
652
  const metricStart = performance.now();
@@ -631,6 +676,8 @@ export async function executeMiddleware<TEnv>(
631
676
  // throws after its next(), the render's own error), so a rewrite thrown
632
677
  // after it would re-dispatch onto the used handle store.
633
678
  let nextRejectedWithRewrite = false;
679
+ let downstreamHolder: DownstreamHolder | undefined;
680
+ let settled = false;
634
681
  const wrappedNext = (): Promise<Response> => {
635
682
  if (nextPromise) {
636
683
  throw new Error(
@@ -638,7 +685,9 @@ export async function executeMiddleware<TEnv>(
638
685
  );
639
686
  }
640
687
  finishMiddleware();
641
- const downstream = next();
688
+ downstreamHolder = new DownstreamHolder(holder);
689
+ if (settled) downstreamHolder.detach(stubResponse);
690
+ const downstream = next(downstreamHolder);
642
691
  nextPromise = downstream.then(
643
692
  (res) => {
644
693
  nextResolvedAt = performance.now();
@@ -687,10 +736,14 @@ export async function executeMiddleware<TEnv>(
687
736
  throw error;
688
737
  }
689
738
  });
739
+ } catch (error) {
740
+ detachIfRunning(downstreamHolder, nextResolvedAt);
741
+ throw error;
690
742
  } finally {
691
743
  // Settle the middleware own-time metric once on both the success and
692
744
  // error paths (idempotent guard in finishMiddleware).
693
745
  finishMiddleware();
746
+ settled = true;
694
747
  }
695
748
  // A rewrite after this middleware's next() started the downstream chain
696
749
  // (resolved, still running, or failed) would leave that render running
@@ -698,10 +751,14 @@ export async function executeMiddleware<TEnv>(
698
751
  // already pushed, and drop the response it built. Only a rewrite after
699
752
  // next() rejected with a Rewrite (nothing rendered) unwinds.
700
753
  if (rewrite && nextPromise && !nextRejectedWithRewrite) {
754
+ detachIfRunning(downstreamHolder, nextResolvedAt);
701
755
  throw rewriteAfterNextError(entry.handler, rewrite);
702
756
  }
703
757
  if (rewrite) throw rewrite;
704
- if (isRewrite(result)) throw returnedRewriteError(entry.handler);
758
+ if (isRewrite(result)) {
759
+ detachIfRunning(downstreamHolder, nextResolvedAt);
760
+ throw returnedRewriteError(entry.handler);
761
+ }
705
762
 
706
763
  // Record post-next() processing time when middleware did work after
707
764
  // the downstream chain resolved (e.g. adding headers, logging).
@@ -723,8 +780,9 @@ export async function executeMiddleware<TEnv>(
723
780
  // RequestContext stub headers (from cookies().set()) into the
724
781
  // returned Response so they are not lost.
725
782
  if (result instanceof Response) {
783
+ detachIfRunning(downstreamHolder, nextResolvedAt);
726
784
  if (isWebSocketUpgradeResponse(result)) {
727
- responseHolder.response = result;
785
+ holder.response = result;
728
786
  return result;
729
787
  }
730
788
  // Explicit short-circuit: the returned Response's own headers win
@@ -732,7 +790,7 @@ export async function executeMiddleware<TEnv>(
732
790
  const merged = mergeResponse(result, stubResponse.headers, {
733
791
  stubOverridesNonCookie: false,
734
792
  });
735
- responseHolder.response = merged;
793
+ holder.response = merged;
736
794
  return merged;
737
795
  }
738
796
 
@@ -749,7 +807,7 @@ export async function executeMiddleware<TEnv>(
749
807
  // If middleware called next(), await it and return the response
750
808
  if (nextPromise) {
751
809
  await nextPromise;
752
- return responseHolder.response!;
810
+ return holder.response!;
753
811
  }
754
812
 
755
813
  // Middleware didn't call next() and didn't return a Response - that's an error
@@ -763,7 +821,7 @@ export async function executeMiddleware<TEnv>(
763
821
  );
764
822
  };
765
823
 
766
- await next();
824
+ await next(responseHolder);
767
825
 
768
826
  // Use the final response from responseHolder (may have been modified by middleware)
769
827
  const finalResponse = responseHolder.response;
@@ -1,6 +1,7 @@
1
1
  import { isLoaderSegmentId } from "../browser/react/filter-segment-order.js";
2
2
  import { requestHeaders } from "../server/request-headers.js";
3
3
  import type { RouteMatchResult } from "./pattern-matching.js";
4
+ import { WIRE } from "../wire-params.js";
4
5
 
5
6
  export interface NavigationSnapshot {
6
7
  prevUrl: URL;
@@ -105,7 +106,7 @@ export function getInterceptSourceHeader(request: Request): string | null {
105
106
  * left, never what renders, so a forged value runs or skips no middleware.
106
107
  */
107
108
  function getShownRouteUrl(url: URL): URL | null {
108
- const shown = url.searchParams.get("_rsc_rt");
109
+ const shown = url.searchParams.get(WIRE.rewriteTarget);
109
110
  if (!shown) return null;
110
111
  try {
111
112
  const parsed = new URL(shown, url.origin);
@@ -133,8 +134,8 @@ export async function resolveNavigation(
133
134
  deps: ResolveNavigationDeps,
134
135
  ): Promise<NavigationSnapshot | null> {
135
136
  const clientSegmentIds =
136
- url.searchParams.get("_rsc_segments")?.split(",").filter(Boolean) || [];
137
- const stale = url.searchParams.get("_rsc_stale") === "true";
137
+ url.searchParams.get(WIRE.segments)?.split(",").filter(Boolean) || [];
138
+ const stale = url.searchParams.get(WIRE.stale) === "true";
138
139
  const previousUrl = getNavigationContextHeader(request, url.origin);
139
140
  const interceptSourceUrl = getInterceptSourceHeader(request);
140
141
  const isHmr = !!requestHeaders(request).get("X-RSC-HMR");