@rangojs/router 0.24.4 → 0.26.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 (301) hide show
  1. package/README.md +1 -0
  2. package/dist/bin/rango.js +363 -39
  3. package/dist/testing/vitest.js +37 -4
  4. package/dist/types/browser/event-controller.d.ts +24 -5
  5. package/dist/types/browser/intercept-utils.d.ts +5 -2
  6. package/dist/types/browser/merge-segment-loaders.d.ts +0 -23
  7. package/dist/types/browser/navigation-transaction.d.ts +5 -5
  8. package/dist/types/browser/partial-update.d.ts +21 -13
  9. package/dist/types/browser/prefetch/cache.d.ts +8 -0
  10. package/dist/types/browser/rango-marker.d.ts +7 -0
  11. package/dist/types/browser/react/filter-segment-order.d.ts +10 -0
  12. package/dist/types/browser/react/use-handle.d.ts +20 -0
  13. package/dist/types/browser/react/use-href.d.ts +6 -0
  14. package/dist/types/browser/react/use-router.d.ts +6 -1
  15. package/dist/types/browser/response-adapter.d.ts +5 -3
  16. package/dist/types/browser/scroll-restoration.d.ts +9 -3
  17. package/dist/types/browser/segment-reconciler.d.ts +10 -13
  18. package/dist/types/browser/types.d.ts +38 -8
  19. package/dist/types/build/route-types/per-module-writer.d.ts +12 -0
  20. package/dist/types/cache/cf/cf-cache-store.d.ts +0 -2
  21. package/dist/types/cache/cf/cf-cache-types.d.ts +0 -16
  22. package/dist/types/cache/cf/cf-shell-frame.d.ts +2 -0
  23. package/dist/types/cache/handle-snapshot.d.ts +2 -2
  24. package/dist/types/cache/types.d.ts +16 -6
  25. package/dist/types/cache/vercel/vercel-cache-store.d.ts +1 -15
  26. package/dist/types/client-urls/client-urls.d.ts +6 -2
  27. package/dist/types/client-urls/revalidate-registry.d.ts +1 -1
  28. package/dist/types/client-urls/server-projection.d.ts +9 -5
  29. package/dist/types/client-urls/types.d.ts +29 -16
  30. package/dist/types/client.d.ts +2 -2
  31. package/dist/types/client.rsc.d.ts +2 -2
  32. package/dist/types/decode-loader-results.d.ts +28 -7
  33. package/dist/types/handles/deferred-resolution.d.ts +3 -2
  34. package/dist/types/handles/meta.d.ts +5 -0
  35. package/dist/types/href-client.d.ts +3 -1
  36. package/dist/types/index.rsc.d.ts +1 -1
  37. package/dist/types/loader-redirect.d.ts +2 -3
  38. package/dist/types/outlet-context.d.ts +7 -6
  39. package/dist/types/prefetch-markers.d.ts +14 -5
  40. package/dist/types/prerender/build-shell-capture.d.ts +1 -2
  41. package/dist/types/prerender/writable-store.d.ts +2 -3
  42. package/dist/types/route-content-wrapper.d.ts +17 -14
  43. package/dist/types/route-definition/helpers-types.d.ts +7 -2
  44. package/dist/types/route-definition/resolve-handler-use.d.ts +6 -1
  45. package/dist/types/route-definition/rewrite.d.ts +48 -5
  46. package/dist/types/route-types.d.ts +2 -2
  47. package/dist/types/router/handler-context.d.ts +14 -2
  48. package/dist/types/router/intercept-resolution.d.ts +19 -46
  49. package/dist/types/router/lazy-includes.d.ts +7 -0
  50. package/dist/types/router/match-middleware/intercept-resolution.d.ts +2 -2
  51. package/dist/types/router/middleware.d.ts +0 -16
  52. package/dist/types/router/navigation-snapshot.d.ts +2 -0
  53. package/dist/types/router/prefetch-cache-ttl.d.ts +8 -11
  54. package/dist/types/router/revalidation.d.ts +8 -1
  55. package/dist/types/router/router-context.d.ts +4 -16
  56. package/dist/types/router/router-interfaces.d.ts +0 -11
  57. package/dist/types/router/router-options.d.ts +4 -27
  58. package/dist/types/router/segment-resolution/mask-nested.d.ts +5 -3
  59. package/dist/types/router/segment-resolution/prefetch-deferral.d.ts +31 -8
  60. package/dist/types/router/segment-resolution/revalidation.d.ts +38 -4
  61. package/dist/types/router/segment-resolution.d.ts +1 -1
  62. package/dist/types/router/segment-wrappers.d.ts +3 -5
  63. package/dist/types/router/substitute-pattern-params.d.ts +3 -2
  64. package/dist/types/router/tracing.d.ts +2 -2
  65. package/dist/types/router-versions.d.ts +8 -6
  66. package/dist/types/rsc/full-payload.d.ts +14 -2
  67. package/dist/types/rsc/helpers.d.ts +8 -0
  68. package/dist/types/rsc/manifest-init.d.ts +20 -0
  69. package/dist/types/rsc/prefetch-render-error.d.ts +22 -0
  70. package/dist/types/rsc/progressive-enhancement.d.ts +20 -0
  71. package/dist/types/rsc/rewrite-passes.d.ts +12 -2
  72. package/dist/types/rsc/server-action.d.ts +6 -0
  73. package/dist/types/rsc/shell-capture-constants.d.ts +14 -4
  74. package/dist/types/rsc/shell-capture.d.ts +23 -2
  75. package/dist/types/rsc/types.d.ts +5 -8
  76. package/dist/types/segment-system.d.ts +15 -0
  77. package/dist/types/server/build-version-table.d.ts +10 -13
  78. package/dist/types/server/context.d.ts +90 -6
  79. package/dist/types/server/handle-store.d.ts +17 -6
  80. package/dist/types/server/request-context.d.ts +45 -7
  81. package/dist/types/server/request-headers.d.ts +12 -0
  82. package/dist/types/server/rewrite-trail.d.ts +8 -1
  83. package/dist/types/testing/build-versions.d.ts +0 -3
  84. package/dist/types/testing/e2e/index.d.ts +1 -1
  85. package/dist/types/testing/flight.entry.d.ts +1 -1
  86. package/dist/types/testing/index.d.ts +1 -1
  87. package/dist/types/testing/internal/client-urls-projection.d.ts +4 -3
  88. package/dist/types/testing/internal/seed-vars.d.ts +4 -4
  89. package/dist/types/testing/render-route.d.ts +21 -11
  90. package/dist/types/testing/run-loader.d.ts +7 -0
  91. package/dist/types/testing/run-middleware.d.ts +7 -0
  92. package/dist/types/testing/run-revalidate.d.ts +5 -4
  93. package/dist/types/testing/serve-shell-request.d.ts +42 -5
  94. package/dist/types/testing/shell-status.d.ts +17 -0
  95. package/dist/types/testing/vitest-stubs/version.d.ts +1 -0
  96. package/dist/types/testing/vitest.d.ts +10 -1
  97. package/dist/types/thenable-status.d.ts +0 -6
  98. package/dist/types/types/handler-context.d.ts +12 -1
  99. package/dist/types/types/loader-types.d.ts +10 -4
  100. package/dist/types/types/route-entry.d.ts +2 -1
  101. package/dist/types/urls/path-helper-types.d.ts +4 -2
  102. package/dist/types/vite/discovery/client-urls-projection.d.ts +55 -5
  103. package/dist/types/vite/discovery/state.d.ts +20 -0
  104. package/dist/types/vite/plugins/client-urls-revalidate-hoist.d.ts +28 -16
  105. package/dist/types/vite/plugins/hoist/source.d.ts +8 -7
  106. package/dist/types/vite/plugins/virtual-entries.d.ts +6 -0
  107. package/dist/types/vite/utils/prerender-utils.d.ts +7 -0
  108. package/dist/vite/index.js +6819 -6555
  109. package/package.json +1 -1
  110. package/skills/app-structure/SKILL.md +255 -0
  111. package/skills/app-structure/content-site.md +333 -0
  112. package/skills/app-structure/e-commerce.md +435 -0
  113. package/skills/app-structure/large-app.md +885 -0
  114. package/skills/app-structure/multi-app.md +280 -0
  115. package/skills/breadcrumbs/SKILL.md +6 -1
  116. package/skills/cache-guide/SKILL.md +4 -8
  117. package/skills/caching/SKILL.md +6 -6
  118. package/skills/catalog.json +12 -0
  119. package/skills/client-urls/SKILL.md +185 -32
  120. package/skills/comparison/references/framework-comparison.md +1 -1
  121. package/skills/composability/SKILL.md +10 -8
  122. package/skills/handler-use/SKILL.md +48 -9
  123. package/skills/hooks/handle-and-actions.md +104 -0
  124. package/skills/hooks/navigation.md +24 -10
  125. package/skills/hooks/urls.md +1 -1
  126. package/skills/host-router/SKILL.md +1 -1
  127. package/skills/i18n/SKILL.md +2 -1
  128. package/skills/intercept/SKILL.md +60 -29
  129. package/skills/layout/SKILL.md +6 -3
  130. package/skills/links/SKILL.md +151 -17
  131. package/skills/loader/SKILL.md +42 -19
  132. package/skills/middleware/SKILL.md +111 -15
  133. package/skills/observability/SKILL.md +11 -0
  134. package/skills/parallel/SKILL.md +5 -5
  135. package/skills/ppr/SKILL.md +103 -52
  136. package/skills/prerender/SKILL.md +4 -5
  137. package/skills/rango/SKILL.md +3 -0
  138. package/skills/response-routes/SKILL.md +4 -1
  139. package/skills/router-setup/SKILL.md +0 -2
  140. package/skills/scripts/SKILL.md +12 -1
  141. package/skills/server-actions/SKILL.md +60 -20
  142. package/skills/testing/SKILL.md +1 -0
  143. package/skills/testing/client-components.md +13 -10
  144. package/skills/testing/loader.md +25 -24
  145. package/skills/testing/middleware.md +11 -11
  146. package/skills/testing/render-handler.md +17 -17
  147. package/skills/testing/response-routes.md +4 -1
  148. package/skills/testing/serve-shell-request.md +86 -22
  149. package/skills/testing/server-actions.md +16 -16
  150. package/skills/ux-architecture/SKILL.md +980 -0
  151. package/skills/vercel/SKILL.md +1 -1
  152. package/skills/view-transitions/SKILL.md +43 -10
  153. package/src/__augment-tests__/augmented.check.ts +28 -0
  154. package/src/browser/event-controller.ts +44 -18
  155. package/src/browser/intercept-utils.ts +3 -2
  156. package/src/browser/merge-segment-loaders.ts +0 -85
  157. package/src/browser/navigation-bridge.ts +112 -84
  158. package/src/browser/navigation-client.ts +20 -15
  159. package/src/browser/navigation-store.ts +4 -2
  160. package/src/browser/navigation-transaction.ts +9 -8
  161. package/src/browser/partial-update.ts +90 -32
  162. package/src/browser/prefetch/cache.ts +8 -0
  163. package/src/browser/prefetch/fetch.ts +13 -3
  164. package/src/browser/rango-marker.ts +13 -0
  165. package/src/browser/react/NavigationProvider.tsx +25 -7
  166. package/src/browser/react/filter-segment-order.ts +13 -0
  167. package/src/browser/react/use-action.ts +9 -15
  168. package/src/browser/react/use-handle.ts +63 -71
  169. package/src/browser/react/use-href.tsx +24 -5
  170. package/src/browser/react/use-link-status.ts +1 -1
  171. package/src/browser/react/use-navigation.ts +30 -40
  172. package/src/browser/react/use-router.ts +37 -6
  173. package/src/browser/react/use-segments.ts +8 -12
  174. package/src/browser/response-adapter.ts +11 -5
  175. package/src/browser/rsc-router.tsx +3 -1
  176. package/src/browser/scroll-restoration.ts +34 -20
  177. package/src/browser/segment-reconciler.ts +11 -29
  178. package/src/browser/server-action-bridge.ts +52 -16
  179. package/src/browser/types.ts +40 -8
  180. package/src/build/generate-manifest.ts +12 -1
  181. package/src/build/route-types/codegen.ts +6 -2
  182. package/src/build/route-types/per-module-writer.ts +94 -61
  183. package/src/cache/cache-runtime.ts +9 -0
  184. package/src/cache/cache-scope.ts +2 -1
  185. package/src/cache/cf/cf-cache-store.ts +6 -7
  186. package/src/cache/cf/cf-cache-types.ts +0 -17
  187. package/src/cache/cf/cf-shell-frame.ts +3 -0
  188. package/src/cache/handle-snapshot.ts +2 -2
  189. package/src/cache/types.ts +16 -6
  190. package/src/cache/vercel/vercel-cache-store.ts +8 -19
  191. package/src/client-urls/client-urls.ts +202 -153
  192. package/src/client-urls/revalidate-registry.ts +12 -3
  193. package/src/client-urls/server-projection.ts +137 -66
  194. package/src/client-urls/types.ts +30 -15
  195. package/src/client.rsc.tsx +11 -1
  196. package/src/client.tsx +9 -1
  197. package/src/decode-loader-results.ts +64 -31
  198. package/src/handles/deferred-resolution.ts +3 -2
  199. package/src/handles/meta.ts +5 -0
  200. package/src/href-client.ts +3 -1
  201. package/src/index.rsc.ts +1 -0
  202. package/src/index.ts +12 -4
  203. package/src/loader-redirect.tsx +2 -3
  204. package/src/outlet-context.ts +7 -6
  205. package/src/prefetch-markers.ts +15 -5
  206. package/src/prerender/build-shell-capture.ts +1 -2
  207. package/src/prerender/writable-store.ts +2 -3
  208. package/src/reverse.ts +3 -3
  209. package/src/route-content-wrapper.tsx +19 -17
  210. package/src/route-definition/dsl-helpers.ts +41 -21
  211. package/src/route-definition/helpers-types.ts +7 -2
  212. package/src/route-definition/resolve-handler-use.ts +23 -10
  213. package/src/route-definition/rewrite.ts +86 -19
  214. package/src/route-types.ts +1 -2
  215. package/src/router/find-match.ts +37 -1
  216. package/src/router/handler-context.ts +85 -40
  217. package/src/router/intercept-resolution.ts +161 -307
  218. package/src/router/lazy-includes.ts +43 -27
  219. package/src/router/loader-resolution.ts +21 -1
  220. package/src/router/manifest.ts +15 -10
  221. package/src/router/match-api.ts +11 -1
  222. package/src/router/match-middleware/background-revalidation.ts +2 -4
  223. package/src/router/match-middleware/cache-lookup.ts +16 -20
  224. package/src/router/match-middleware/intercept-resolution.ts +41 -60
  225. package/src/router/match-result.ts +31 -2
  226. package/src/router/middleware.ts +6 -147
  227. package/src/router/navigation-snapshot.ts +38 -2
  228. package/src/router/prefetch-cache-ttl.ts +10 -18
  229. package/src/router/prerender-match.ts +2 -1
  230. package/src/router/revalidation.ts +24 -3
  231. package/src/router/router-context.ts +5 -18
  232. package/src/router/router-interfaces.ts +0 -13
  233. package/src/router/router-options.ts +4 -28
  234. package/src/router/segment-resolution/fresh.ts +6 -2
  235. package/src/router/segment-resolution/helpers.ts +7 -1
  236. package/src/router/segment-resolution/loader-cache.ts +4 -3
  237. package/src/router/segment-resolution/mask-nested.ts +5 -3
  238. package/src/router/segment-resolution/prefetch-deferral.ts +62 -12
  239. package/src/router/segment-resolution/revalidation.ts +75 -35
  240. package/src/router/segment-resolution.ts +0 -1
  241. package/src/router/segment-wrappers.ts +8 -13
  242. package/src/router/substitute-pattern-params.ts +3 -2
  243. package/src/router/tracing.ts +2 -2
  244. package/src/router-versions.ts +7 -7
  245. package/src/router.ts +5 -17
  246. package/src/rsc/full-payload.ts +26 -4
  247. package/src/rsc/handler.ts +86 -48
  248. package/src/rsc/helpers.ts +13 -0
  249. package/src/rsc/manifest-init.ts +56 -3
  250. package/src/rsc/prefetch-render-error.ts +38 -0
  251. package/src/rsc/progressive-enhancement.ts +161 -211
  252. package/src/rsc/rewrite-passes.ts +56 -9
  253. package/src/rsc/rsc-rendering.ts +79 -23
  254. package/src/rsc/server-action.ts +25 -2
  255. package/src/rsc/shell-capture-constants.ts +26 -3
  256. package/src/rsc/shell-capture.ts +137 -51
  257. package/src/rsc/types.ts +5 -9
  258. package/src/segment-system.tsx +125 -119
  259. package/src/server/build-version-table.ts +12 -24
  260. package/src/server/context.ts +160 -7
  261. package/src/server/handle-store.ts +80 -12
  262. package/src/server/request-context.ts +46 -6
  263. package/src/server/request-headers.ts +32 -0
  264. package/src/server/rewrite-trail.ts +55 -9
  265. package/src/ssr/ssr-root.tsx +6 -10
  266. package/src/testing/build-versions.ts +0 -3
  267. package/src/testing/dispatch.ts +5 -8
  268. package/src/testing/e2e/index.ts +1 -0
  269. package/src/testing/flight.entry.ts +1 -0
  270. package/src/testing/index.ts +1 -0
  271. package/src/testing/internal/client-urls-projection.ts +31 -11
  272. package/src/testing/internal/seed-vars.ts +4 -4
  273. package/src/testing/render-route.tsx +83 -40
  274. package/src/testing/run-loader.ts +26 -2
  275. package/src/testing/run-middleware.ts +10 -0
  276. package/src/testing/run-revalidate.ts +13 -6
  277. package/src/testing/serve-shell-request.ts +79 -9
  278. package/src/testing/shell-status.ts +40 -0
  279. package/src/testing/vitest-stubs/version.ts +1 -0
  280. package/src/testing/vitest.ts +74 -7
  281. package/src/thenable-status.ts +0 -11
  282. package/src/types/handler-context.ts +12 -1
  283. package/src/types/loader-types.ts +10 -4
  284. package/src/types/route-entry.ts +2 -1
  285. package/src/urls/include-helper.ts +38 -11
  286. package/src/urls/path-helper-types.ts +4 -2
  287. package/src/vite/discovery/client-urls-projection.ts +205 -16
  288. package/src/vite/discovery/discover-routers.ts +2 -2
  289. package/src/vite/discovery/prerender-collection.ts +2 -6
  290. package/src/vite/discovery/shell-prerender-phase.ts +1 -5
  291. package/src/vite/discovery/state.ts +26 -0
  292. package/src/vite/plugins/client-urls-revalidate-hoist.ts +190 -77
  293. package/src/vite/plugins/expose-internal-ids.ts +46 -40
  294. package/src/vite/plugins/hoist/source.ts +8 -7
  295. package/src/vite/plugins/transition-when-hoist.ts +1 -0
  296. package/src/vite/plugins/version-plugin.ts +7 -0
  297. package/src/vite/plugins/version.d.ts +7 -0
  298. package/src/vite/plugins/virtual-entries.ts +20 -0
  299. package/src/vite/rango.ts +4 -1
  300. package/src/vite/router-discovery.ts +39 -7
  301. package/src/vite/utils/prerender-utils.ts +17 -0
@@ -2,7 +2,7 @@
2
2
  * serveShellRequest — one request through the production PPR serve path.
3
3
  *
4
4
  * Builds the router's request handler as `router.fetch` does
5
- * (`createRSCHandler` with the router's `nonce`, `version` and `cache`
5
+ * (`createRSCHandler` with the router's `nonce` and `cache`
6
6
  * config) and serves one GET through it, so middleware, the shell serve path
7
7
  * and a MISS's background capture all run production code, on real Flight
8
8
  * (the vendored react-server-dom serializer). The call settles every
@@ -62,6 +62,8 @@ import { FILL_PARAM, PREFETCH_HEADER } from "../prefetch-markers.js";
62
62
  import { resolveDeferredHandleValues } from "../handles/deferred-resolution.js";
63
63
  import { withoutPushedBy } from "../handles/pushed-by.js";
64
64
  import type { HandleData } from "../server/handle-store.js";
65
+ import type { ResolvedSegment } from "../types.js";
66
+ import { revalidationErrorOf } from "../decode-loader-results.js";
65
67
  import { _getRequestContext } from "../server/request-context.js";
66
68
  import {
67
69
  parsePprReplayStatus,
@@ -97,15 +99,22 @@ export interface ServeShellRequestOptions<TEnv = any> {
97
99
  * entries are deferred: `result.readDeferred()` lists them. `fill: true`
98
100
  * sends the request the browser follows an adopted prefetch with
99
101
  * (`_rsc_fill`): pass the ids the prefetch delivered, without the deferred
100
- * ones, as `segments`, and the same URL as `from`.
102
+ * ones, as `segments`, and the same URL as `from`. `interceptSource` sends
103
+ * the intercept source (`X-RSC-Router-Intercept-Source`) the browser sends
104
+ * from an open modal and with the fill of an intercept, whose `from` is
105
+ * that source. `rewriteTarget` sends the `metadata.rewriteTarget` the page at
106
+ * `from` carried when middleware rewrote it (`_rsc_rt`), as the browser
107
+ * does: the server then diffs against the route that rendered that page.
101
108
  */
102
109
  partial?:
103
110
  | true
104
111
  | {
105
112
  from?: string;
113
+ rewriteTarget?: string;
106
114
  segments?: readonly string[];
107
115
  prefetch?: boolean;
108
116
  fill?: boolean;
117
+ interceptSource?: string;
109
118
  };
110
119
  /**
111
120
  * POST this form as a submit before hydration: the no-JS request the
@@ -139,6 +148,8 @@ export interface ServeShellRequestAction {
139
148
  args?: readonly unknown[];
140
149
  /** The segment ids the page holds (`_rsc_segments`). */
141
150
  segments?: readonly string[];
151
+ /** The page's payload `metadata.rewriteTarget`, as for `partial`. */
152
+ rewriteTarget?: string;
142
153
  }
143
154
 
144
155
  /** A response's handle data, as the browser consumes it. */
@@ -157,6 +168,25 @@ export interface ShellRequestHandles {
157
168
  late: HandleData[];
158
169
  }
159
170
 
171
+ /**
172
+ * An `action` request's answer, as browser/server-action-bridge.ts reads it.
173
+ */
174
+ export interface ShellRequestAction {
175
+ /** False when the action threw: the call rejects with `result`. */
176
+ ok: boolean;
177
+ /** What the call resolves (or rejects) with. */
178
+ result: unknown;
179
+ /**
180
+ * The error of the first loader that threw while it re-ran in the action's
181
+ * revalidation, the value `useAction(action).revalidationError` reports.
182
+ * That loader throws at its `useLoader` read, where the nearest boundary
183
+ * catches it, and the call still resolves. `null` when every loader
184
+ * answered (a `notFound()` or `redirect()` is not an error), or `ok` is
185
+ * false, as `useAction` reports it.
186
+ */
187
+ revalidationError: Error | null;
188
+ }
189
+
160
190
  /** Result of {@link serveShellRequest}. */
161
191
  export interface ServeShellRequestResult {
162
192
  /** The handler's Response. Its body is already read: see `body`. */
@@ -182,9 +212,12 @@ export interface ServeShellRequestResult {
182
212
  * The shell key production resolved for this request: for a document the
183
213
  * serve path read and captured (a MISS or a HIT), the key it used, request
184
214
  * partition included (a route partitioned by `cache({ key })` or the
185
- * store's `keyGenerator`). Otherwise (a route without `ppr`, a request
186
- * passed to axis 1 before its key was resolved, a partial request) the
187
- * document URL's key without a partition, as `shellCacheKey` builds it.
215
+ * store's `keyGenerator`), and for a request middleware rewrote with
216
+ * `rewrite(...).shell("visitor")` the visitor's key, as
217
+ * `visitorShellCacheKey` builds it. Otherwise (a route without `ppr`, a
218
+ * request passed to axis 1 before its key was resolved, a rewritten
219
+ * request whose rewrite chose no shell, a partial request) the document
220
+ * URL's key without a partition, as `shellCacheKey` builds it.
188
221
  */
189
222
  key: string;
190
223
  /**
@@ -217,6 +250,13 @@ export interface ServeShellRequestResult {
217
250
  * hydrates without it.
218
251
  */
219
252
  readPayloadFormState(): Promise<unknown>;
253
+ /**
254
+ * For an `action` request, the call's answer as the browser reads it: its
255
+ * result and the `revalidationError` its loaders' reruns report.
256
+ * Undefined when no Flight was rendered or the payload carries no action
257
+ * result.
258
+ */
259
+ readAction(): Promise<ShellRequestAction | undefined>;
220
260
  }
221
261
 
222
262
  type ShellHandler = ReturnType<typeof createRSCHandler>;
@@ -352,6 +392,24 @@ async function decodePayloadHandles(
352
392
  };
353
393
  }
354
394
 
395
+ /** An action payload's answer, read as the browser's action bridge reads it. */
396
+ async function decodeAction(
397
+ flight: string,
398
+ ): Promise<ShellRequestAction | undefined> {
399
+ const { returnValue, metadata } = await decodePayload<{
400
+ returnValue?: { ok: boolean; data: unknown };
401
+ metadata?: { segments?: ResolvedSegment[] };
402
+ }>(flight);
403
+ if (!returnValue) return undefined;
404
+ return {
405
+ ok: returnValue.ok,
406
+ result: returnValue.data,
407
+ revalidationError:
408
+ (returnValue.ok && (await revalidationErrorOf(metadata?.segments))) ||
409
+ null,
410
+ };
411
+ }
412
+
355
413
  /** The ids of the segments a payload marks deferred. */
356
414
  async function decodeDeferredIds(flight: string): Promise<string[]> {
357
415
  const { metadata } = await decodePayload<{
@@ -479,10 +537,7 @@ async function getHandler(
479
537
  const { resolveRouterVersions } =
480
538
  await import("../server/build-version-table.js");
481
539
  // What createRSCHandler would resolve for this router now.
482
- const { document: versions } = resolveRouterVersions(
483
- internal.id,
484
- internal.version,
485
- );
540
+ const { document: versions } = resolveRouterVersions(internal.id);
486
541
  const cached = byStore.get(cacheStore ?? router);
487
542
  if (cached?.versions === versions) return cached.handler;
488
543
 
@@ -522,6 +577,9 @@ async function buildActionRequest(
522
577
  const target = new URL(url);
523
578
  target.searchParams.set("_rsc_action", action.id);
524
579
  target.searchParams.set("_rsc_segments", (action.segments ?? []).join(","));
580
+ if (action.rewriteTarget) {
581
+ target.searchParams.set("_rsc_rt", action.rewriteTarget);
582
+ }
525
583
  headers.set("rsc-action", action.id);
526
584
  if (!headers.has("X-RSC-Router-Client-Path")) {
527
585
  headers.set("X-RSC-Router-Client-Path", url.href);
@@ -565,13 +623,16 @@ async function buildRequest(
565
623
  }
566
624
  const {
567
625
  from = "/",
626
+ rewriteTarget,
568
627
  segments = [],
569
628
  prefetch = false,
570
629
  fill = false,
630
+ interceptSource,
571
631
  } = partial === true ? {} : partial;
572
632
  const target = new URL(url);
573
633
  target.searchParams.set("_rsc_partial", "true");
574
634
  target.searchParams.set("_rsc_segments", segments.join(","));
635
+ if (rewriteTarget) target.searchParams.set("_rsc_rt", rewriteTarget);
575
636
  // As the browser sends them: browser/navigation-client.ts (fill) and
576
637
  // browser/prefetch/fetch.ts (prefetch).
577
638
  if (fill) target.searchParams.set(FILL_PARAM, "1");
@@ -581,6 +642,12 @@ async function buildRequest(
581
642
  if (!headers.has("X-RSC-Router-Client-Path")) {
582
643
  headers.set("X-RSC-Router-Client-Path", new URL(from, url).href);
583
644
  }
645
+ if (interceptSource !== undefined) {
646
+ headers.set(
647
+ "X-RSC-Router-Intercept-Source",
648
+ new URL(interceptSource, url).href,
649
+ );
650
+ }
584
651
  if (!headers.has(SEGMENT_FRAGMENT_CAPABILITY_HEADER)) {
585
652
  headers.set(SEGMENT_FRAGMENT_CAPABILITY_HEADER, "1");
586
653
  }
@@ -725,5 +792,8 @@ export async function serveShellRequest<TEnv = any>(
725
792
  }>(flight);
726
793
  return payload.formState;
727
794
  },
795
+ async readAction() {
796
+ return flight ? decodeAction(flight) : undefined;
797
+ },
728
798
  };
729
799
  }
@@ -39,6 +39,7 @@ import {
39
39
  SHELL_STATUS_HEADER,
40
40
  buildShellKey,
41
41
  partitionShellKey,
42
+ visitorShellKey,
42
43
  type PprReplayBypassReason,
43
44
  } from "../rsc/shell-capture-constants.js";
44
45
  import {
@@ -119,6 +120,45 @@ export function shellCacheKey(
119
120
  resolved,
120
121
  compileSearchParamsFilter(searchParams),
121
122
  );
123
+ return withPartition(key, partition);
124
+ }
125
+
126
+ /**
127
+ * Shell store key of a request middleware rewrote with
128
+ * `throw rewrite(...).shell("visitor")`: the visitor's `url`, partitioned by
129
+ * the rewrite's `target` (its path and search), as production builds it
130
+ * (`visitorShellKey`, `rsc/shell-capture-constants.ts`). `searchParams` is
131
+ * the target route's `cache.searchParams` config, applied to the visitor's
132
+ * search; `partition` is the target route's own, as for
133
+ * {@link shellCacheKey}, composed after the target's.
134
+ *
135
+ * ```ts
136
+ * const hit = await serveShellRequest(router, "/summer", { cacheStore });
137
+ * expect(hit.key).toBe(visitorShellCacheKey(router, "/summer", "/item/1"));
138
+ * ```
139
+ */
140
+ export function visitorShellCacheKey(
141
+ router: { readonly id: string },
142
+ url: URL | string,
143
+ target: URL | string,
144
+ searchParams?: CacheSearchParams,
145
+ partition?: string | readonly string[] | ShellCachePartition,
146
+ ): string {
147
+ const visitor =
148
+ typeof url === "string" ? new URL(url, "http://localhost") : url;
149
+ const key = visitorShellKey(
150
+ router.id,
151
+ visitor,
152
+ new URL(target, visitor),
153
+ compileSearchParamsFilter(searchParams),
154
+ );
155
+ return withPartition(key, partition);
156
+ }
157
+
158
+ function withPartition(
159
+ key: string,
160
+ partition: string | readonly string[] | ShellCachePartition | undefined,
161
+ ): string {
122
162
  if (partition === undefined) return key;
123
163
  const { keys = [], generated = [] }: ShellCachePartition =
124
164
  typeof partition === "string"
@@ -6,3 +6,4 @@
6
6
  // for a test that needs per-router versions.
7
7
  export const VERSION = "";
8
8
  export const ROUTER_VERSIONS: undefined = undefined;
9
+ export const PACKAGE_VERSION = "0.0.0-test";
@@ -76,8 +76,10 @@
76
76
  * a focused include, or use e2e.
77
77
  */
78
78
 
79
+ import { readFile } from "node:fs/promises";
79
80
  import { createRequire } from "node:module";
80
81
  import { fileURLToPath, pathToFileURL } from "node:url";
82
+ import { parseLang } from "../vite/plugins/hoist/literal.js";
81
83
  import {
82
84
  isUseCacheCandidate,
83
85
  transformUseCache,
@@ -156,6 +158,41 @@ const CLIENT_URLS_INSTALLER_PATH: string = here(
156
158
  "src/testing/internal/client-urls-projection.ts",
157
159
  ).replaceAll("\\", "/");
158
160
 
161
+ /**
162
+ * Whether a clientUrls() source's import loads as source too: a
163
+ * `"use client"` module, or a barrel (imports and `export ... from` only).
164
+ * Vite loads lazily, as in the transform: this module must load where it is
165
+ * not installed (installed-vitest-config.test.ts).
166
+ */
167
+ async function loadsAsSource(code: string, file: string): Promise<boolean> {
168
+ if (!code.includes("use client") && !code.includes("export")) return false;
169
+ const { parseAstAsync } = await import("vite");
170
+ const { hasDirective } = await import("@vitejs/plugin-rsc/transforms");
171
+ type Body = Parameters<typeof hasDirective>[0];
172
+ let body: Body;
173
+ try {
174
+ // The file as it is on disk, TypeScript included.
175
+ body = (
176
+ (await parseAstAsync(code, { lang: parseLang(file) })) as unknown as {
177
+ body: Body;
178
+ }
179
+ ).body;
180
+ } catch {
181
+ return false;
182
+ }
183
+ if (hasDirective(body, "use client")) return true;
184
+ const nodes = body as unknown as { type: string; source?: unknown }[];
185
+ return (
186
+ nodes.some((node) => node.type !== "ImportDeclaration") &&
187
+ nodes.every(
188
+ (node) =>
189
+ node.type === "ImportDeclaration" ||
190
+ node.type === "ExportAllDeclaration" ||
191
+ (node.type === "ExportNamedDeclaration" && node.source != null),
192
+ )
193
+ );
194
+ }
195
+
159
196
  /**
160
197
  * Appended to a transformed `"use client"` clientUrls() module: install the
161
198
  * projection of its own source under its default export's reference id
@@ -270,11 +307,24 @@ export function rangoTestConfig(
270
307
  }
271
308
 
272
309
  /** A minimal Vite plugin shape (avoids a hard dependency on Vite's types). */
310
+ /** The part of a Vite plugin context {@link rangoUseClientTransform} resolves with. */
311
+ interface SourceResolveContext {
312
+ resolve(
313
+ source: string,
314
+ importer?: string,
315
+ options?: { skipSelf?: boolean },
316
+ ): Promise<{ id: string; external?: boolean | string } | null>;
317
+ }
318
+
273
319
  interface FlightTransformPlugin {
274
320
  name: string;
275
321
  resolveId: {
276
322
  order: "pre";
277
- handler(source: string, importer: string | undefined): string | undefined;
323
+ handler(
324
+ this: SourceResolveContext,
325
+ source: string,
326
+ importer: string | undefined,
327
+ ): Promise<string | undefined>;
278
328
  };
279
329
  transform(
280
330
  code: string,
@@ -347,19 +397,36 @@ export function rangoUseClientTransform(): FlightTransformPlugin {
347
397
  resolveId: {
348
398
  // Before Vite's resolver, which resolves the bare specifier first.
349
399
  order: "pre",
350
- handler(source, importer) {
400
+ async handler(source, importer) {
401
+ if (!importer?.includes(CLIENT_URLS_SOURCE_QUERY)) return undefined;
351
402
  // A clientUrls() module's source resolves the router's client entry
352
403
  // as the RSC environment does (the react-server condition): this
353
404
  // project resolves it to client.tsx, a "use client" module whose
354
405
  // clientUrls is a client reference here. Both entries export the
355
- // same clientUrls.
406
+ // same clientUrls and helpers.
407
+ if (source === "@rangojs/router/client") return CLIENT_ENTRY_RSC_PATH;
408
+ // Its "use client" project imports load as source too, as the app's
409
+ // SSR copy runs them: a component's `.use` and its name are
410
+ // unreadable on a client reference. So do the barrels between them,
411
+ // which hold no state; any other module stays one shared instance.
412
+ const resolved = await this.resolve(source, importer, {
413
+ skipSelf: true,
414
+ });
415
+ if (!resolved || resolved.external) return undefined;
416
+ const { id } = resolved;
417
+ const file = id.split("?", 1)[0]!;
356
418
  if (
357
- source === "@rangojs/router/client" &&
358
- importer?.includes(CLIENT_URLS_SOURCE_QUERY)
419
+ id.startsWith("\0") ||
420
+ id.includes("/node_modules/") ||
421
+ id.includes(CLIENT_URLS_SOURCE_QUERY) ||
422
+ !/\.[cm]?[jt]sx?$/.test(file)
359
423
  ) {
360
- return CLIENT_ENTRY_RSC_PATH;
424
+ return undefined;
361
425
  }
362
- return undefined;
426
+ const code = await readFile(file, "utf-8").catch(() => "");
427
+ return (await loadsAsSource(code, file))
428
+ ? `${id}${id.includes("?") ? "&" : "?"}${CLIENT_URLS_SOURCE_QUERY}`
429
+ : undefined;
363
430
  },
364
431
  },
365
432
  async transform(code, id) {
@@ -24,14 +24,3 @@ export const SETTLED_THENABLE: Promise<undefined> = Object.assign(
24
24
  Promise.resolve(undefined),
25
25
  { status: "fulfilled", value: undefined, _debugInfo: [] },
26
26
  );
27
-
28
- /**
29
- * The value React reads from `value` without suspending: `value` itself when
30
- * it is not a promise, a promise's `value` once it carries React's or Flight's
31
- * `status: "fulfilled"`. Undefined while it is pending or rejected.
32
- */
33
- export function settledValue<T>(value: T | Promise<T>): T | undefined {
34
- if (!(value instanceof Promise)) return value;
35
- const thenable = value as Promise<T> & { status?: string; value?: T };
36
- return thenable.status === "fulfilled" ? thenable.value : undefined;
37
- }
@@ -89,7 +89,13 @@ type AutofillSearchFromEntry<TMap, TKey> = TKey extends keyof TMap
89
89
  : Record<string, unknown>
90
90
  : Record<string, unknown>;
91
91
 
92
- type AutofillAwareReverseFunction<TLocalRoutes, TGlobalRoutes> =
92
+ /**
93
+ * `ScopedReverseFunction` whose global-name params are optional: the runtime
94
+ * fills missing ones from the matched route's params (`createReverseFunction`
95
+ * in `router/handler-context.ts`, explicit params win). Shared by handler,
96
+ * loader and request-context `reverse`.
97
+ */
98
+ export type AutofillAwareReverseFunction<TLocalRoutes, TGlobalRoutes> =
93
99
  ScopedReverseFunction<TLocalRoutes, TGlobalRoutes> & {
94
100
  <TName extends keyof TGlobalRoutes & string>(
95
101
  name: TName,
@@ -449,6 +455,11 @@ export type InternalHandlerContext<
449
455
  _responseType?: string;
450
456
  /** Route name for cache key scoping (prevents cross-route collisions). */
451
457
  _routeName?: string;
458
+ /**
459
+ * The segment's ReverseScope on a layout's / parallel's ctx and its
460
+ * loaders' (withReverseScope): keys a "use cache" call taking the ctx.
461
+ */
462
+ _reverseScope?: import("../server/context.js").ReverseScope;
452
463
  /**
453
464
  * @internal The on-demand overlay holds a "removed" marker for this request
454
465
  * (tryPrerenderLookup, cache-lookup.ts); gateOnDemandProducer reads it. On
@@ -2,7 +2,7 @@ import type { ContextVar } from "../context-var.js";
2
2
  import type { Handle } from "../handle.js";
3
3
  import type { HandlePush } from "../defer.js";
4
4
  import type { MiddlewareFn } from "../router/middleware.js";
5
- import type { ScopedReverseFunction } from "../reverse.js";
5
+ import type { AutofillAwareReverseFunction } from "./handler-context.js";
6
6
  import type { SearchSchema, ResolveSearchSchema } from "../search-params.js";
7
7
  import type { UseItems, LoaderUseItem } from "../route-types.js";
8
8
  import type {
@@ -134,9 +134,10 @@ export type LoaderContext<
134
134
  /**
135
135
  * Generate URLs from route names.
136
136
  * Same scoped reverse as route handlers — `.name` resolves within the
137
- * current include() scope, `name` resolves globally.
137
+ * current include() scope, `name` resolves globally, and params the matched
138
+ * route already carries (e.g. include() mount params) are filled in.
138
139
  */
139
- reverse: ScopedReverseFunction<
140
+ reverse: AutofillAwareReverseFunction<
140
141
  Record<string, string>,
141
142
  DefaultReverseRouteMap
142
143
  >;
@@ -188,7 +189,12 @@ export type LoaderFn<
188
189
  * deterministically precedes Response construction, where the streamed
189
190
  * default only wins that race opportunistically). Under ppr it is also the
190
191
  * BAKE lane: the loader executes at shell capture and its settled return is
191
- * shell material (nested promises stay live holes) — see the ppr docs.
192
+ * shell material (nested promises stay live holes), and so are its handle
193
+ * pushes, a pushed promise or `.defer()` included: the capture awaits it and
194
+ * every HIT serves the value it settled to (#1182). A pushed promise that
195
+ * rejects, or misses `ppr.captureTimeout`, keeps the route on MISS. A value
196
+ * that must stay per request belongs to a loader without `ssr: false` — see
197
+ * the ppr docs.
192
198
  *
193
199
  * Scoped per LOADER, not per segment: an `ssr: false` loader alongside a
194
200
  * deliberately streaming sibling awaits only itself, and the sibling keeps
@@ -1,7 +1,8 @@
1
1
  import type { AllUseItems } from "../route-types.js";
2
+ import type { IncludeScopePosition } from "../server/context.js";
2
3
  import type { TrailingSlashMode, ResolvedRouteMap } from "./route-config.js";
3
4
 
4
- export interface LazyIncludeContext {
5
+ export interface LazyIncludeContext extends IncludeScopePosition {
5
6
  urlPrefix: string;
6
7
  namePrefix: string | undefined;
7
8
  parent: unknown; // EntryData - avoid circular import
@@ -3,6 +3,7 @@ import {
3
3
  getUrlPrefix,
4
4
  getNamePrefix,
5
5
  requireDslContext,
6
+ type IncludeScopePosition,
6
7
  } from "../server/context";
7
8
  import {
8
9
  INTERNAL_INCLUDE_SCOPE_PREFIX,
@@ -21,13 +22,32 @@ function hasExplicitNameOption(options: IncludeOptions | undefined): boolean {
21
22
  return !!options && Object.prototype.hasOwnProperty.call(options, "name");
22
23
  }
23
24
 
24
- function allocateInternalIncludeScopeId(
25
+ /**
26
+ * This include's index among the includes its handler run has called. The
27
+ * counter is keyed by the run's scopePath, which no ancestor run's counter
28
+ * snapshot holds, so every run counts from 0 in every realm (build discovery,
29
+ * runtime expansion, render-time manifest).
30
+ */
31
+ function nextIncludePosition(
25
32
  counters: Record<string, number>,
26
- ): string {
27
- const key = "__include_scope__";
33
+ scopePath: string | undefined,
34
+ ): number {
35
+ const key = `__include_position__:${scopePath ?? ""}`;
28
36
  const index = counters[key] ?? 0;
29
37
  counters[key] = index + 1;
30
- return `${INTERNAL_INCLUDE_SCOPE_PREFIX}${index}`;
38
+ return index;
39
+ }
40
+
41
+ function joinScopePath(parent: string | undefined, index: number): string {
42
+ return parent ? `${parent}_${index}` : `${index}`;
43
+ }
44
+
45
+ function joinNames(
46
+ prefix: string | undefined,
47
+ segment: string | undefined,
48
+ ): string | undefined {
49
+ if (!segment) return prefix;
50
+ return prefix ? `${prefix}.${segment}` : segment;
31
51
  }
32
52
 
33
53
  /**
@@ -100,15 +120,20 @@ export function createIncludeHelper<TEnv>(): IncludeFn<TEnv> {
100
120
  ? capturedUrlPrefix + prefix.slice(1)
101
121
  : capturedUrlPrefix + prefix
102
122
  : prefix;
123
+ // Route names are a pure function of the route tree: an unnamed include's
124
+ // hidden segment is its position path since the nearest include that added
125
+ // a name segment (a transparent { name: "" } adds none), e.g. `$prefix_1_0`.
126
+ const index = nextIncludePosition(ctx.counters, ctx.scopePath);
127
+ const hiddenSoFar = joinScopePath(ctx.hiddenScopePath, index);
103
128
  const nextSegment = hasExplicitName
104
129
  ? explicitName
105
- : allocateInternalIncludeScopeId(ctx.counters);
106
- const fullNamePrefix =
107
- nextSegment !== undefined && nextSegment !== ""
108
- ? capturedNamePrefix
109
- ? `${capturedNamePrefix}.${nextSegment}`
110
- : nextSegment
111
- : capturedNamePrefix;
130
+ : `${INTERNAL_INCLUDE_SCOPE_PREFIX}${hiddenSoFar}`;
131
+ const fullNamePrefix = joinNames(capturedNamePrefix, nextSegment);
132
+ const position: IncludeScopePosition = {
133
+ scopePath: joinScopePath(ctx.scopePath, index),
134
+ // A name segment restarts the hidden path for this include's children.
135
+ hiddenScopePath: nextSegment ? "" : hiddenSoFar,
136
+ };
112
137
 
113
138
  // Track this include for build-time manifest generation
114
139
  if (ctx.trackedIncludes) {
@@ -118,6 +143,7 @@ export function createIncludeHelper<TEnv>(): IncludeFn<TEnv> {
118
143
  namePrefix: fullNamePrefix,
119
144
  patterns,
120
145
  lazy: true,
146
+ ...position,
121
147
  });
122
148
  }
123
149
 
@@ -171,6 +197,7 @@ export function createIncludeHelper<TEnv>(): IncludeFn<TEnv> {
171
197
  rootScoped: capturedRootScoped,
172
198
  routerId: ctx.routerId,
173
199
  includeScope,
200
+ ...position,
174
201
  },
175
202
  } as IncludeItem;
176
203
  };
@@ -346,9 +346,11 @@ export type PathHelpers<TEnv> = {
346
346
  };
347
347
 
348
348
  /**
349
- * Control when a segment should revalidate during navigation
349
+ * Control when a segment should revalidate during navigation.
350
+ * `revalidate(true)` / `revalidate(false)` are shorthand for
351
+ * `revalidate(() => true)` / `revalidate(() => false)`.
350
352
  */
351
- revalidate: (fn: ShouldRevalidateFn<any, TEnv>) => RevalidateItem;
353
+ revalidate: (fn: ShouldRevalidateFn<any, TEnv> | boolean) => RevalidateItem;
352
354
 
353
355
  /**
354
356
  * Attach a data loader to the current route/layout.