@rangojs/router 0.0.0-experimental.32 → 0.0.0-experimental.3232cd17

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 (376) hide show
  1. package/AGENTS.md +4 -0
  2. package/README.md +198 -44
  3. package/dist/bin/rango.js +287 -105
  4. package/dist/testing/vitest.js +82 -0
  5. package/dist/vite/index.js +3248 -1117
  6. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  7. package/package.json +73 -21
  8. package/skills/api-client/SKILL.md +211 -0
  9. package/skills/breadcrumbs/SKILL.md +107 -1
  10. package/skills/bundle-analysis/SKILL.md +159 -0
  11. package/skills/cache-guide/SKILL.md +245 -21
  12. package/skills/caching/SKILL.md +302 -6
  13. package/skills/composability/SKILL.md +27 -2
  14. package/skills/css/SKILL.md +76 -0
  15. package/skills/document-cache/SKILL.md +78 -55
  16. package/skills/handler-use/SKILL.md +364 -0
  17. package/skills/hooks/SKILL.md +270 -30
  18. package/skills/host-router/SKILL.md +82 -22
  19. package/skills/i18n/SKILL.md +276 -0
  20. package/skills/intercept/SKILL.md +49 -5
  21. package/skills/layout/SKILL.md +35 -9
  22. package/skills/links/SKILL.md +249 -17
  23. package/skills/loader/SKILL.md +294 -30
  24. package/skills/middleware/SKILL.md +52 -13
  25. package/skills/migrate-nextjs/SKILL.md +584 -0
  26. package/skills/migrate-react-router/SKILL.md +769 -0
  27. package/skills/mime-routes/SKILL.md +27 -0
  28. package/skills/observability/SKILL.md +137 -0
  29. package/skills/parallel/SKILL.md +203 -7
  30. package/skills/prerender/SKILL.md +123 -100
  31. package/skills/rango/SKILL.md +250 -22
  32. package/skills/react-compiler/SKILL.md +168 -0
  33. package/skills/response-routes/SKILL.md +122 -47
  34. package/skills/route/SKILL.md +97 -5
  35. package/skills/router-setup/SKILL.md +90 -5
  36. package/skills/server-actions/SKILL.md +775 -0
  37. package/skills/streams-and-websockets/SKILL.md +283 -0
  38. package/skills/tailwind/SKILL.md +27 -3
  39. package/skills/testing/SKILL.md +129 -0
  40. package/skills/testing/bindings.md +89 -0
  41. package/skills/testing/cache-prerender.md +124 -0
  42. package/skills/testing/client-components.md +122 -0
  43. package/skills/testing/e2e-parity.md +125 -0
  44. package/skills/testing/flight.md +92 -0
  45. package/skills/testing/handles.md +129 -0
  46. package/skills/testing/loader.md +128 -0
  47. package/skills/testing/middleware.md +99 -0
  48. package/skills/testing/render-handler.md +121 -0
  49. package/skills/testing/response-routes.md +95 -0
  50. package/skills/testing/reverse-and-types.md +84 -0
  51. package/skills/testing/server-actions.md +107 -0
  52. package/skills/testing/server-tree.md +128 -0
  53. package/skills/testing/setup.md +120 -0
  54. package/skills/typesafety/SKILL.md +329 -27
  55. package/skills/use-cache/SKILL.md +36 -5
  56. package/skills/view-transitions/SKILL.md +294 -0
  57. package/src/__augment-tests__/augment.ts +81 -0
  58. package/src/__augment-tests__/augmented.check.ts +116 -0
  59. package/src/__internal.ts +67 -40
  60. package/src/browser/action-coordinator.ts +53 -36
  61. package/src/browser/action-fence.ts +47 -0
  62. package/src/browser/app-shell.ts +39 -0
  63. package/src/browser/app-version.ts +14 -0
  64. package/src/browser/cookie-name.ts +140 -0
  65. package/src/browser/event-controller.ts +86 -147
  66. package/src/browser/history-state.ts +21 -0
  67. package/src/browser/index.ts +3 -3
  68. package/src/browser/invalidate-client-cache.ts +52 -0
  69. package/src/browser/link-interceptor.ts +4 -0
  70. package/src/browser/navigation-bridge.ts +148 -19
  71. package/src/browser/navigation-client.ts +187 -67
  72. package/src/browser/navigation-store-handle.ts +38 -0
  73. package/src/browser/navigation-store.ts +76 -67
  74. package/src/browser/navigation-transaction.ts +18 -66
  75. package/src/browser/partial-update.ts +123 -94
  76. package/src/browser/prefetch/cache.ts +214 -36
  77. package/src/browser/prefetch/fetch.ts +260 -38
  78. package/src/browser/prefetch/policy.ts +6 -0
  79. package/src/browser/prefetch/queue.ts +126 -20
  80. package/src/browser/prefetch/resource-ready.ts +77 -0
  81. package/src/browser/rango-state.ts +158 -76
  82. package/src/browser/react/Link.tsx +93 -11
  83. package/src/browser/react/NavigationProvider.tsx +115 -34
  84. package/src/browser/react/ScrollRestoration.tsx +10 -6
  85. package/src/browser/react/context.ts +7 -2
  86. package/src/browser/react/filter-segment-order.ts +49 -7
  87. package/src/browser/react/index.ts +0 -48
  88. package/src/browser/react/location-state-shared.ts +166 -8
  89. package/src/browser/react/location-state.ts +39 -14
  90. package/src/browser/react/use-action.ts +6 -15
  91. package/src/browser/react/use-handle.ts +23 -69
  92. package/src/browser/react/use-link-status.ts +0 -4
  93. package/src/browser/react/use-navigation.ts +22 -5
  94. package/src/browser/react/use-params.ts +20 -10
  95. package/src/browser/react/use-reverse.ts +106 -0
  96. package/src/browser/react/use-router.ts +46 -11
  97. package/src/browser/react/use-search-params.ts +0 -5
  98. package/src/browser/react/use-segments.ts +11 -21
  99. package/src/browser/response-adapter.ts +52 -1
  100. package/src/browser/rsc-router.tsx +215 -76
  101. package/src/browser/scroll-restoration.ts +46 -39
  102. package/src/browser/segment-reconciler.ts +36 -9
  103. package/src/browser/segment-structure-assert.ts +2 -2
  104. package/src/browser/server-action-bridge.ts +176 -50
  105. package/src/browser/types.ts +95 -11
  106. package/src/browser/validate-redirect-origin.ts +43 -16
  107. package/src/build/collect-fallback-refs.ts +107 -0
  108. package/src/build/generate-manifest.ts +65 -40
  109. package/src/build/generate-route-types.ts +5 -0
  110. package/src/build/index.ts +8 -2
  111. package/src/build/prefix-tree-utils.ts +123 -0
  112. package/src/build/route-trie.ts +137 -32
  113. package/src/build/route-types/codegen.ts +4 -4
  114. package/src/build/route-types/include-resolution.ts +9 -2
  115. package/src/build/route-types/param-extraction.ts +6 -3
  116. package/src/build/route-types/per-module-writer.ts +7 -4
  117. package/src/build/route-types/router-processing.ts +278 -96
  118. package/src/build/route-types/scan-filter.ts +9 -2
  119. package/src/build/route-types/source-scan.ts +118 -0
  120. package/src/build/runtime-discovery.ts +9 -20
  121. package/src/cache/cache-error.ts +104 -0
  122. package/src/cache/cache-policy.ts +68 -28
  123. package/src/cache/cache-runtime.ts +149 -43
  124. package/src/cache/cache-scope.ts +148 -81
  125. package/src/cache/cache-tag.ts +98 -0
  126. package/src/cache/cf/cf-cache-store.ts +2550 -93
  127. package/src/cache/cf/index.ts +11 -17
  128. package/src/cache/document-cache.ts +78 -27
  129. package/src/cache/handle-snapshot.ts +63 -0
  130. package/src/cache/index.ts +23 -20
  131. package/src/cache/memory-segment-store.ts +136 -37
  132. package/src/cache/profile-registry.ts +6 -30
  133. package/src/cache/read-through-swr.ts +41 -11
  134. package/src/cache/segment-codec.ts +0 -16
  135. package/src/cache/tag-invalidation.ts +230 -0
  136. package/src/cache/taint.ts +55 -0
  137. package/src/cache/types.ts +33 -100
  138. package/src/cache/vercel/index.ts +11 -0
  139. package/src/cache/vercel/vercel-cache-store.ts +799 -0
  140. package/src/client.rsc.tsx +6 -21
  141. package/src/client.tsx +108 -290
  142. package/src/component-utils.ts +19 -0
  143. package/src/context-var.ts +84 -2
  144. package/src/debug.ts +2 -2
  145. package/src/decode-loader-results.ts +36 -0
  146. package/src/defer.ts +196 -0
  147. package/src/deps/ssr.ts +0 -1
  148. package/src/errors.ts +30 -4
  149. package/src/handle.ts +70 -22
  150. package/src/handles/MetaTags.tsx +0 -14
  151. package/src/handles/breadcrumbs.ts +16 -5
  152. package/src/handles/meta.ts +0 -39
  153. package/src/host/cookie-handler.ts +0 -36
  154. package/src/host/errors.ts +0 -24
  155. package/src/host/index.ts +8 -2
  156. package/src/host/pattern-matcher.ts +7 -50
  157. package/src/host/router.ts +107 -99
  158. package/src/host/testing.ts +40 -27
  159. package/src/host/types.ts +37 -4
  160. package/src/host/utils.ts +1 -1
  161. package/src/href-client.ts +137 -22
  162. package/src/index.rsc.ts +52 -26
  163. package/src/index.ts +100 -38
  164. package/src/internal-debug.ts +2 -4
  165. package/src/loader-store.ts +500 -0
  166. package/src/loader.rsc.ts +20 -13
  167. package/src/loader.ts +12 -11
  168. package/src/missing-id-error.ts +68 -0
  169. package/src/network-error-thrower.tsx +1 -6
  170. package/src/outlet-context.ts +1 -1
  171. package/src/outlet-provider.tsx +1 -5
  172. package/src/prerender/param-hash.ts +10 -11
  173. package/src/prerender/store.ts +37 -41
  174. package/src/prerender.ts +198 -82
  175. package/src/redirect-origin.ts +100 -0
  176. package/src/response-utils.ts +37 -0
  177. package/src/reverse.ts +65 -15
  178. package/src/root-error-boundary.tsx +1 -19
  179. package/src/route-content-wrapper.tsx +7 -72
  180. package/src/route-definition/dsl-helpers.ts +437 -274
  181. package/src/route-definition/helper-factories.ts +29 -139
  182. package/src/route-definition/helpers-types.ts +113 -37
  183. package/src/route-definition/index.ts +3 -0
  184. package/src/route-definition/redirect.ts +52 -10
  185. package/src/route-definition/resolve-handler-use.ts +161 -0
  186. package/src/route-definition/use-item-types.ts +32 -0
  187. package/src/route-map-builder.ts +7 -17
  188. package/src/route-types.ts +37 -41
  189. package/src/router/basename.ts +14 -0
  190. package/src/router/content-negotiation.ts +108 -9
  191. package/src/router/error-handling.ts +13 -17
  192. package/src/router/find-match.ts +45 -22
  193. package/src/router/handler-context.ts +83 -41
  194. package/src/router/intercept-resolution.ts +25 -23
  195. package/src/router/lazy-includes.ts +19 -53
  196. package/src/router/loader-resolution.ts +213 -30
  197. package/src/router/logging.ts +5 -8
  198. package/src/router/manifest.ts +49 -45
  199. package/src/router/match-api.ts +120 -204
  200. package/src/router/match-context.ts +0 -22
  201. package/src/router/match-handlers.ts +58 -58
  202. package/src/router/match-middleware/background-revalidation.ts +27 -6
  203. package/src/router/match-middleware/cache-lookup.ts +205 -249
  204. package/src/router/match-middleware/cache-store.ts +45 -32
  205. package/src/router/match-middleware/intercept-resolution.ts +8 -28
  206. package/src/router/match-middleware/segment-resolution.ts +52 -18
  207. package/src/router/match-pipelines.ts +1 -42
  208. package/src/router/match-result.ts +104 -40
  209. package/src/router/metrics.ts +5 -34
  210. package/src/router/middleware-types.ts +13 -142
  211. package/src/router/middleware.ts +173 -143
  212. package/src/router/navigation-snapshot.ts +131 -0
  213. package/src/router/params-util.ts +23 -0
  214. package/src/router/pattern-matching.ts +109 -63
  215. package/src/router/prerender-match.ts +190 -54
  216. package/src/router/preview-match.ts +32 -102
  217. package/src/router/request-classification.ts +276 -0
  218. package/src/router/revalidation.ts +63 -55
  219. package/src/router/route-snapshot.ts +244 -0
  220. package/src/router/router-context.ts +6 -28
  221. package/src/router/router-interfaces.ts +100 -35
  222. package/src/router/router-options.ts +91 -11
  223. package/src/router/router-registry.ts +2 -5
  224. package/src/router/segment-resolution/fresh.ts +242 -75
  225. package/src/router/segment-resolution/helpers.ts +63 -24
  226. package/src/router/segment-resolution/loader-cache.ts +41 -37
  227. package/src/router/segment-resolution/revalidation.ts +456 -372
  228. package/src/router/segment-resolution/static-store.ts +19 -5
  229. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  230. package/src/router/segment-resolution/view-transition-default.ts +36 -0
  231. package/src/router/segment-resolution.ts +4 -1
  232. package/src/router/segment-wrappers.ts +2 -3
  233. package/src/router/state-cookie-name.ts +33 -0
  234. package/src/router/substitute-pattern-params.ts +56 -0
  235. package/src/router/telemetry-otel.ts +0 -20
  236. package/src/router/telemetry.ts +96 -19
  237. package/src/router/timeout.ts +0 -20
  238. package/src/router/trie-matching.ts +91 -46
  239. package/src/router/types.ts +10 -63
  240. package/src/router/url-params.ts +44 -0
  241. package/src/router.ts +134 -43
  242. package/src/rsc/handler-context.ts +3 -2
  243. package/src/rsc/handler.ts +492 -383
  244. package/src/rsc/helpers.ts +162 -46
  245. package/src/rsc/index.ts +1 -1
  246. package/src/rsc/json-route-result.ts +38 -0
  247. package/src/rsc/loader-fetch.ts +23 -3
  248. package/src/rsc/manifest-init.ts +33 -42
  249. package/src/rsc/origin-guard.ts +39 -25
  250. package/src/rsc/progressive-enhancement.ts +30 -3
  251. package/src/rsc/redirect-guard.ts +99 -0
  252. package/src/rsc/response-error.ts +79 -12
  253. package/src/rsc/response-route-handler.ts +90 -63
  254. package/src/rsc/rsc-rendering.ts +56 -54
  255. package/src/rsc/runtime-warnings.ts +23 -10
  256. package/src/rsc/server-action.ts +74 -67
  257. package/src/rsc/ssr-setup.ts +18 -2
  258. package/src/rsc/types.ts +25 -6
  259. package/src/runtime-env.ts +18 -0
  260. package/src/search-params.ts +4 -20
  261. package/src/segment-content-promise.ts +67 -0
  262. package/src/segment-loader-promise.ts +134 -0
  263. package/src/segment-system.tsx +272 -129
  264. package/src/serialize.ts +243 -0
  265. package/src/server/context.ts +309 -61
  266. package/src/server/cookie-store.ts +80 -5
  267. package/src/server/handle-store.ts +26 -24
  268. package/src/server/loader-registry.ts +10 -28
  269. package/src/server/request-context.ts +338 -126
  270. package/src/ssr/index.tsx +23 -15
  271. package/src/static-handler.ts +27 -18
  272. package/src/testing/cache-status.ts +162 -0
  273. package/src/testing/collect-handle.ts +40 -0
  274. package/src/testing/dispatch.ts +618 -0
  275. package/src/testing/dom.entry.ts +22 -0
  276. package/src/testing/e2e/fixture.ts +188 -0
  277. package/src/testing/e2e/index.ts +128 -0
  278. package/src/testing/e2e/matchers.ts +35 -0
  279. package/src/testing/e2e/page-helpers.ts +272 -0
  280. package/src/testing/e2e/parity.ts +387 -0
  281. package/src/testing/e2e/server.ts +195 -0
  282. package/src/testing/flight-matchers.ts +97 -0
  283. package/src/testing/flight-normalize.ts +11 -0
  284. package/src/testing/flight-runtime.d.ts +57 -0
  285. package/src/testing/flight-tree.ts +682 -0
  286. package/src/testing/flight.entry.ts +52 -0
  287. package/src/testing/flight.ts +232 -0
  288. package/src/testing/generated-routes.ts +183 -0
  289. package/src/testing/index.ts +99 -0
  290. package/src/testing/internal/context.ts +348 -0
  291. package/src/testing/internal/flight-client-globals.ts +30 -0
  292. package/src/testing/internal/seed-vars.ts +54 -0
  293. package/src/testing/render-handler.ts +330 -0
  294. package/src/testing/render-route.tsx +566 -0
  295. package/src/testing/run-loader.ts +378 -0
  296. package/src/testing/run-middleware.ts +205 -0
  297. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  298. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  299. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  300. package/src/testing/vitest-stubs/version.ts +5 -0
  301. package/src/testing/vitest.ts +305 -0
  302. package/src/theme/ThemeProvider.tsx +0 -52
  303. package/src/theme/ThemeScript.tsx +0 -6
  304. package/src/theme/constants.ts +0 -12
  305. package/src/theme/index.ts +0 -7
  306. package/src/theme/theme-context.ts +1 -5
  307. package/src/theme/theme-script.ts +0 -14
  308. package/src/theme/use-theme.ts +0 -3
  309. package/src/types/boundaries.ts +0 -35
  310. package/src/types/cache-types.ts +17 -8
  311. package/src/types/error-types.ts +30 -90
  312. package/src/types/global-namespace.ts +54 -41
  313. package/src/types/handler-context.ts +233 -81
  314. package/src/types/index.ts +1 -10
  315. package/src/types/loader-types.ts +44 -15
  316. package/src/types/request-scope.ts +107 -0
  317. package/src/types/route-config.ts +6 -50
  318. package/src/types/route-entry.ts +19 -7
  319. package/src/types/segments.ts +37 -14
  320. package/src/urls/include-helper.ts +33 -70
  321. package/src/urls/index.ts +1 -11
  322. package/src/urls/path-helper-types.ts +58 -11
  323. package/src/urls/path-helper.ts +57 -111
  324. package/src/urls/pattern-types.ts +48 -19
  325. package/src/urls/response-types.ts +25 -22
  326. package/src/urls/type-extraction.ts +58 -139
  327. package/src/urls/urls-function.ts +1 -18
  328. package/src/use-loader.tsx +346 -89
  329. package/src/vite/debug.ts +185 -0
  330. package/src/vite/discovery/bundle-postprocess.ts +36 -38
  331. package/src/vite/discovery/discover-routers.ts +130 -85
  332. package/src/vite/discovery/discovery-errors.ts +194 -0
  333. package/src/vite/discovery/gate-state.ts +171 -0
  334. package/src/vite/discovery/prerender-collection.ts +192 -99
  335. package/src/vite/discovery/route-types-writer.ts +40 -84
  336. package/src/vite/discovery/self-gen-tracking.ts +27 -1
  337. package/src/vite/discovery/state.ts +51 -6
  338. package/src/vite/discovery/virtual-module-codegen.ts +14 -34
  339. package/src/vite/index.ts +8 -0
  340. package/src/vite/plugin-types.ts +187 -69
  341. package/src/vite/plugins/cjs-to-esm.ts +8 -18
  342. package/src/vite/plugins/client-ref-dedup.ts +16 -11
  343. package/src/vite/plugins/client-ref-hashing.ts +28 -15
  344. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  345. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  346. package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
  347. package/src/vite/plugins/expose-action-id.ts +49 -98
  348. package/src/vite/plugins/expose-id-utils.ts +11 -50
  349. package/src/vite/plugins/expose-ids/export-analysis.ts +76 -34
  350. package/src/vite/plugins/expose-ids/handler-transform.ts +10 -48
  351. package/src/vite/plugins/expose-ids/loader-transform.ts +3 -20
  352. package/src/vite/plugins/expose-ids/router-transform.ts +20 -16
  353. package/src/vite/plugins/expose-internal-ids.ts +554 -317
  354. package/src/vite/plugins/performance-tracks.ts +89 -0
  355. package/src/vite/plugins/refresh-cmd.ts +89 -27
  356. package/src/vite/plugins/use-cache-transform.ts +73 -83
  357. package/src/vite/plugins/vercel-output.ts +258 -0
  358. package/src/vite/plugins/version-injector.ts +21 -25
  359. package/src/vite/plugins/version-plugin.ts +41 -20
  360. package/src/vite/plugins/virtual-entries.ts +2 -17
  361. package/src/vite/rango.ts +257 -289
  362. package/src/vite/router-discovery.ts +930 -140
  363. package/src/vite/utils/ast-handler-extract.ts +15 -31
  364. package/src/vite/utils/banner.ts +4 -4
  365. package/src/vite/utils/bundle-analysis.ts +10 -15
  366. package/src/vite/utils/client-chunks.ts +184 -0
  367. package/src/vite/utils/forward-user-plugins.ts +171 -0
  368. package/src/vite/utils/manifest-utils.ts +4 -59
  369. package/src/vite/utils/package-resolution.ts +20 -52
  370. package/src/vite/utils/prerender-utils.ts +27 -29
  371. package/src/vite/utils/shared-utils.ts +92 -42
  372. package/src/browser/action-response-classifier.ts +0 -99
  373. package/src/browser/react/use-client-cache.ts +0 -58
  374. package/src/browser/shallow.ts +0 -40
  375. package/src/handles/index.ts +0 -7
  376. package/src/router/middleware-cookies.ts +0 -55
@@ -0,0 +1,161 @@
1
+ import type { AllUseItems } from "../route-types.js";
2
+ import { isPrerenderHandler, isPassthroughHandler } from "../prerender.js";
3
+ import { isStaticHandler } from "../static-handler.js";
4
+
5
+ /**
6
+ * Extract the .use callback from any handler shape.
7
+ *
8
+ * Checks definition brands first (objects with __brand), then plain functions.
9
+ * ReactNode handlers return undefined (no .use possible).
10
+ */
11
+ export function resolveHandlerUse(handler: unknown): (() => any[]) | undefined {
12
+ if (handler == null) return undefined;
13
+
14
+ // Check branded definitions first — they're objects but also have typeof "object"
15
+ if (isPassthroughHandler(handler)) {
16
+ return (handler as any).use;
17
+ }
18
+ if (isPrerenderHandler(handler)) {
19
+ return (handler as any).use;
20
+ }
21
+ if (isStaticHandler(handler)) {
22
+ return (handler as any).use;
23
+ }
24
+ // Loader definitions from createLoader() — branded objects with optional .use
25
+ if (typeof handler === "object" && (handler as any).__brand === "loader") {
26
+ return (handler as any).use;
27
+ }
28
+ // Plain handler function
29
+ if (typeof handler === "function") {
30
+ return (handler as any).use;
31
+ }
32
+ // ReactNode or other — no .use
33
+ return undefined;
34
+ }
35
+
36
+ /**
37
+ * Allowed item types per mount site.
38
+ * Mirrors the RouteUseItem / ParallelUseItem / InterceptUseItem / LayoutUseItem unions
39
+ * from route-types.ts for runtime validation.
40
+ */
41
+ const MOUNT_SITE_ALLOWED_TYPES: Record<string, Set<string>> = {
42
+ path: new Set([
43
+ "layout",
44
+ "parallel",
45
+ "intercept",
46
+ "middleware",
47
+ "revalidate",
48
+ "loader",
49
+ "loading",
50
+ "errorBoundary",
51
+ "notFoundBoundary",
52
+ "cache",
53
+ "transition",
54
+ ]),
55
+ // Response routes (path.json, path.text, etc.) — mirrors ResponseRouteUseItem
56
+ response: new Set(["middleware", "cache"]),
57
+ route: new Set([
58
+ "layout",
59
+ "parallel",
60
+ "intercept",
61
+ "middleware",
62
+ "revalidate",
63
+ "loader",
64
+ "loading",
65
+ "errorBoundary",
66
+ "notFoundBoundary",
67
+ "cache",
68
+ "transition",
69
+ ]),
70
+ // layout allows AllUseItems — no validation needed, but included for completeness
71
+ layout: new Set([
72
+ "layout",
73
+ "route",
74
+ "middleware",
75
+ "revalidate",
76
+ "parallel",
77
+ "intercept",
78
+ "loader",
79
+ "loading",
80
+ "errorBoundary",
81
+ "notFoundBoundary",
82
+ "cache",
83
+ "transition",
84
+ "include",
85
+ ]),
86
+ parallel: new Set([
87
+ "revalidate",
88
+ "loader",
89
+ "loading",
90
+ "errorBoundary",
91
+ "notFoundBoundary",
92
+ "transition",
93
+ ]),
94
+ intercept: new Set([
95
+ "middleware",
96
+ "revalidate",
97
+ "loader",
98
+ "loading",
99
+ "errorBoundary",
100
+ "notFoundBoundary",
101
+ "layout",
102
+ "route",
103
+ "when",
104
+ "transition",
105
+ ]),
106
+ // LoaderUseItem — only revalidate + cache can attach to a loader entry
107
+ loader: new Set(["revalidate", "cache"]),
108
+ };
109
+
110
+ /**
111
+ * Validate that items from handler.use() are valid for the given mount site.
112
+ * Throws a descriptive error if any item is not allowed.
113
+ */
114
+ export function validateHandlerUseItems(
115
+ items: AllUseItems[],
116
+ mountSite: string,
117
+ ): void {
118
+ const allowed = MOUNT_SITE_ALLOWED_TYPES[mountSite];
119
+ if (!allowed) return;
120
+ for (const item of items) {
121
+ if (item == null) continue;
122
+ if (!allowed.has((item as any).type)) {
123
+ throw new Error(
124
+ `handler.use() returned ${(item as any).type}() which is not valid inside ${mountSite}(). ` +
125
+ `Allowed types: ${[...allowed].join(", ")}.`,
126
+ );
127
+ }
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Create a merged use callback from handler.use and explicit use.
133
+ * handler.use items come first (defaults), explicit items second (overrides).
134
+ * Returns undefined if both are absent.
135
+ */
136
+ export function mergeHandlerUse(
137
+ handlerUse: (() => any[]) | undefined,
138
+ explicitUse: (() => any[]) | undefined,
139
+ mountSite: string,
140
+ ): (() => any[]) | undefined {
141
+ if (!handlerUse && !explicitUse) return undefined;
142
+ // Validation asymmetry (intentional, pre-1.0): only handler.use() items are
143
+ // checked against the mount-site allow-list (validateHandlerUseItems below).
144
+ // Explicit use() items pass through unvalidated on both the explicit-only
145
+ // branch here and the merged branch, so a structurally-valid-but-prohibited
146
+ // item (e.g. middleware() inside a parallel slot) is not rejected at this seam.
147
+ // Documented rather than enforced for now; revisit before 1.0 (#569).
148
+ if (!handlerUse) return explicitUse;
149
+ if (!explicitUse) {
150
+ return () => {
151
+ const items = handlerUse().flat(3);
152
+ validateHandlerUseItems(items, mountSite);
153
+ return items;
154
+ };
155
+ }
156
+ return () => {
157
+ const hItems = handlerUse().flat(3);
158
+ validateHandlerUseItems(hItems, mountSite);
159
+ return [...hItems, ...explicitUse()];
160
+ };
161
+ }
@@ -0,0 +1,32 @@
1
+ import type { AllUseItems, WhenItem } from "../route-types.js";
2
+
3
+ /**
4
+ * The set of valid use-item `type` discriminants — the single runtime source of
5
+ * truth for "is this a well-formed use item?" shape validation.
6
+ *
7
+ * Declared via a `Record<...>` so that adding a member to the union without
8
+ * updating this map is a compile error. `when` is included because when() items
9
+ * are valid inside intercept() even though WhenItem is not part of AllUseItems
10
+ * (it lives only in InterceptUseItem). This is shape validation only; per-mount-
11
+ * site rules remain the narrower hand-written tables in resolve-handler-use.ts.
12
+ */
13
+ const USE_ITEM_TYPES: Record<AllUseItems["type"] | WhenItem["type"], true> = {
14
+ layout: true,
15
+ route: true,
16
+ middleware: true,
17
+ revalidate: true,
18
+ parallel: true,
19
+ intercept: true,
20
+ loader: true,
21
+ loading: true,
22
+ errorBoundary: true,
23
+ notFoundBoundary: true,
24
+ when: true,
25
+ cache: true,
26
+ transition: true,
27
+ include: true,
28
+ };
29
+
30
+ export const ALL_USE_ITEM_TYPES: ReadonlySet<string> = new Set(
31
+ Object.keys(USE_ITEM_TYPES),
32
+ );
@@ -8,15 +8,10 @@
8
8
  * See docs/manifests.md for the full data flow.
9
9
  */
10
10
 
11
- // Singleton route map instance - populated incrementally as routes are encountered
12
11
  let globalRouteMap: Record<string, string> = {};
13
12
 
14
- // Cached complete manifest - includes all routes (including lazy includes)
15
- // Set from runtime cache or build-time import
16
13
  let cachedManifest: Record<string, string> | null = null;
17
14
 
18
- // Pre-computed route entries from build-time prefix tree leaf nodes.
19
- // Used by evaluateLazyEntry() to skip running the handler for route matching.
20
15
  let cachedPrecomputedEntries: Array<{
21
16
  staticPrefix: string;
22
17
  routes: Record<string, string>;
@@ -43,7 +38,6 @@ export function registerRouteMap(map: Record<string, string>): void {
43
38
  * @internal
44
39
  */
45
40
  export function getGlobalRouteMap(): Record<string, string> {
46
- // Cached manifest is complete (includes lazy routes), so prefer it
47
41
  if (cachedManifest) {
48
42
  return cachedManifest;
49
43
  }
@@ -199,7 +193,13 @@ export function registerRouterManifestLoader(
199
193
  }
200
194
 
201
195
  export async function ensureRouterManifest(routerId: string): Promise<void> {
202
- if (perRouterManifestMap.has(routerId)) return;
196
+ // Check both manifest AND trie. The virtual module's setRouterManifest()
197
+ // pre-sets the manifest at startup, but the per-router trie is only
198
+ // available from the lazy loader. Without this, the lazy loader never
199
+ // runs and findMatch falls back to the global merged trie — which
200
+ // contains routes from ALL routers and breaks multi-router setups.
201
+ if (perRouterManifestMap.has(routerId) && perRouterTrieMap.has(routerId))
202
+ return;
203
203
  const loader = routerManifestLoaders.get(routerId);
204
204
  if (loader) {
205
205
  const mod = await loader();
@@ -225,10 +225,6 @@ export function waitForManifestReady(): Promise<void> | null {
225
225
  return manifestReadyPromise;
226
226
  }
227
227
 
228
- // ============================================================================
229
- // Route Scope Registry
230
- // ============================================================================
231
-
232
228
  // Tracks whether each route is at root scope (no named include boundary above).
233
229
  // Used by dot-local reverse resolution to decide whether bare-name fallback
234
230
  // is allowed after scoped lookups are exhausted.
@@ -253,14 +249,8 @@ export function isRouteRootScoped(routeName: string): boolean | undefined {
253
249
  return rootScopeRoutes.get(routeName);
254
250
  }
255
251
 
256
- // ============================================================================
257
- // Search Schema Registry
258
- // ============================================================================
259
-
260
252
  import type { SearchSchema } from "./search-params.js";
261
253
 
262
- // Global search schema map: route name -> search schema descriptor.
263
- // Populated by path() when a search option is provided.
264
254
  const globalSearchSchemas: Map<string, SearchSchema> = new Map();
265
255
 
266
256
  export function registerSearchSchema(
@@ -5,47 +5,43 @@
5
5
  */
6
6
 
7
7
  /**
8
- * Branded return types for route helpers
8
+ * Brand for UrlPatterns nominal typing (see pattern-types.ts). The route-item
9
+ * types below are discriminated by their `type` literal, so they carry no brand.
9
10
  */
10
- export declare const LayoutBrand: unique symbol;
11
- export declare const RouteBrand: unique symbol;
12
- export declare const ParallelBrand: unique symbol;
13
- export declare const InterceptBrand: unique symbol;
14
- export declare const MiddlewareBrand: unique symbol;
15
- export declare const RevalidateBrand: unique symbol;
16
- export declare const LoaderBrand: unique symbol;
17
- export declare const LoadingBrand: unique symbol;
18
- export declare const ErrorBoundaryBrand: unique symbol;
19
- export declare const NotFoundBoundaryBrand: unique symbol;
20
- export declare const WhenBrand: unique symbol;
21
- export declare const CacheBrand: unique symbol;
22
- export declare const TransitionBrand: unique symbol;
23
- export declare const IncludeBrand: unique symbol;
24
11
  export declare const UrlPatternsBrand: unique symbol;
25
12
 
26
13
  export type LayoutItem = {
27
14
  name: string;
28
15
  type: "layout";
29
16
  uses?: AllUseItems[];
30
- [LayoutBrand]: void;
31
17
  };
32
18
 
33
19
  /**
34
- * Typed layout item that carries child routes as phantom type
35
- * Used for type inference in urls() API
20
+ * Phantom inference fields attached to wrapper items (layout/cache/transition)
21
+ * so the urls() type extractor can read their child routes/responses. The fields
22
+ * never exist at runtime.
36
23
  */
37
- export type TypedLayoutItem<
24
+ type WithChildren<
25
+ TBase,
38
26
  TChildRoutes extends Record<string, any> = Record<string, string>,
39
27
  TChildResponses extends Record<string, unknown> = Record<string, unknown>,
40
- > = LayoutItem & {
28
+ > = TBase & {
41
29
  readonly __childRoutes?: TChildRoutes;
42
30
  readonly __childResponses?: TChildResponses;
43
31
  };
32
+
33
+ /**
34
+ * Typed layout item that carries child routes as phantom type
35
+ * Used for type inference in urls() API
36
+ */
37
+ export type TypedLayoutItem<
38
+ TChildRoutes extends Record<string, any> = Record<string, string>,
39
+ TChildResponses extends Record<string, unknown> = Record<string, unknown>,
40
+ > = WithChildren<LayoutItem, TChildRoutes, TChildResponses>;
44
41
  export type RouteItem = {
45
42
  name: string;
46
43
  type: "route";
47
44
  uses?: AllUseItems[];
48
- [RouteBrand]: void;
49
45
  };
50
46
 
51
47
  /**
@@ -67,64 +63,53 @@ export type ParallelItem = {
67
63
  name: string;
68
64
  type: "parallel";
69
65
  uses?: ParallelUseItem[];
70
- [ParallelBrand]: void;
71
66
  };
72
67
  export type InterceptItem = {
73
68
  name: string;
74
69
  type: "intercept";
75
70
  uses?: InterceptUseItem[];
76
- [InterceptBrand]: void;
77
71
  };
78
72
  export type LoaderItem = {
79
73
  name: string;
80
74
  type: "loader";
81
75
  uses?: LoaderUseItem[];
82
- [LoaderBrand]: void;
83
76
  };
84
77
  export type MiddlewareItem = {
85
78
  name: string;
86
79
  type: "middleware";
87
80
  uses?: AllUseItems[];
88
- [MiddlewareBrand]: void;
89
81
  };
90
82
  export type RevalidateItem = {
91
83
  name: string;
92
84
  type: "revalidate";
93
85
  uses?: AllUseItems[];
94
- [RevalidateBrand]: void;
95
86
  };
96
87
  export type LoadingItem = {
97
88
  name: string;
98
89
  type: "loading";
99
- [LoadingBrand]: void;
100
90
  };
101
91
  export type ErrorBoundaryItem = {
102
92
  name: string;
103
93
  type: "errorBoundary";
104
94
  uses?: AllUseItems[];
105
- [ErrorBoundaryBrand]: void;
106
95
  };
107
96
  export type NotFoundBoundaryItem = {
108
97
  name: string;
109
98
  type: "notFoundBoundary";
110
99
  uses?: AllUseItems[];
111
- [NotFoundBoundaryBrand]: void;
112
100
  };
113
101
  export type WhenItem = {
114
102
  name: string;
115
103
  type: "when";
116
- [WhenBrand]: void;
117
104
  };
118
105
  export type CacheItem = {
119
106
  name: string;
120
107
  type: "cache";
121
108
  uses?: AllUseItems[];
122
- [CacheBrand]: void;
123
109
  };
124
110
  export type TransitionItem = {
125
111
  name: string;
126
112
  type: "transition";
127
- [TransitionBrand]: void;
128
113
  };
129
114
 
130
115
  /**
@@ -134,10 +119,7 @@ export type TransitionItem = {
134
119
  export type TypedTransitionItem<
135
120
  TChildRoutes extends Record<string, any> = Record<string, string>,
136
121
  TChildResponses extends Record<string, unknown> = Record<string, unknown>,
137
- > = TransitionItem & {
138
- readonly __childRoutes?: TChildRoutes;
139
- readonly __childResponses?: TChildResponses;
140
- };
122
+ > = WithChildren<TransitionItem, TChildRoutes, TChildResponses>;
141
123
 
142
124
  /**
143
125
  * Typed cache item that carries child routes as phantom type
@@ -146,10 +128,7 @@ export type TypedTransitionItem<
146
128
  export type TypedCacheItem<
147
129
  TChildRoutes extends Record<string, any> = Record<string, string>,
148
130
  TChildResponses extends Record<string, unknown> = Record<string, unknown>,
149
- > = CacheItem & {
150
- readonly __childRoutes?: TChildRoutes;
151
- readonly __childResponses?: TChildResponses;
152
- };
131
+ > = WithChildren<CacheItem, TChildRoutes, TChildResponses>;
153
132
 
154
133
  /**
155
134
  * Include item for URL pattern composition (used by urls() API)
@@ -176,8 +155,14 @@ export type IncludeItem = {
176
155
  >;
177
156
  /** Root scope flag for dot-local reverse resolution */
178
157
  rootScoped?: boolean;
158
+ /**
159
+ * Positional include scope token composed from the parent scope plus this
160
+ * include's sibling index (`${parentScope}I${idx}`). Applied to direct-
161
+ * descendant shortCodes during lazy evaluation so routes inside the
162
+ * include cannot collide with siblings declared outside it.
163
+ */
164
+ includeScope?: string;
179
165
  };
180
- [IncludeBrand]: void;
181
166
  };
182
167
 
183
168
  /**
@@ -257,3 +242,14 @@ export type LoaderUseItem = RevalidateItem | CacheItem;
257
242
  * runtime via .flat(3).
258
243
  */
259
244
  export type UseItems<T> = (T | readonly T[])[];
245
+
246
+ /**
247
+ * Union of all items that handler.use() may return.
248
+ * A handler doesn't know its mount site at definition time, so the type
249
+ * is intentionally broad — validation happens per-mount-site at runtime.
250
+ */
251
+ export type HandlerUseItem =
252
+ | RouteUseItem
253
+ | LayoutUseItem
254
+ | ParallelUseItem
255
+ | InterceptUseItem;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Normalize a router basename to its canonical form: a single leading slash,
3
+ * no trailing slash, and `undefined` for an empty or bare-"/" value.
4
+ *
5
+ * This is the single source of truth used by both createRouter() (so the RSC
6
+ * handler stores a canonical basename on the request context) and the testing
7
+ * primitives (so a consumer can pass the same un-normalized string their
8
+ * createRouter() accepts and observe the same redirect() prefixing).
9
+ */
10
+ export function normalizeBasename(basename?: string): string | undefined {
11
+ if (!basename) return undefined;
12
+ const trimmed = basename.replace(/^\/+|\/+$/g, "");
13
+ return trimmed ? "/" + trimmed : undefined;
14
+ }
@@ -2,11 +2,18 @@
2
2
  * Content Negotiation Utilities
3
3
  *
4
4
  * Pure functions for HTTP Accept header parsing and response type matching.
5
- * Used by createRouter's previewMatch for content negotiation between
5
+ * Used by previewMatch and classifyRequest for content negotiation between
6
6
  * RSC routes and response routes (JSON, text, image, stream, etc.).
7
7
  */
8
8
 
9
- // Response type -> MIME type used for Accept header matching
9
+ import type { EntryData } from "../server/context.js";
10
+ import type { CollectedMiddleware } from "./middleware-types.js";
11
+ import { collectRouteMiddleware } from "./middleware.js";
12
+ import { loadManifest } from "./manifest.js";
13
+ import { traverseBack } from "./pattern-matching.js";
14
+ import type { RouteMatchResult } from "./pattern-matching.js";
15
+ import type { RouteSnapshot } from "./route-snapshot.js";
16
+
10
17
  export const RESPONSE_TYPE_MIME: Record<string, string> = {
11
18
  json: "application/json",
12
19
  text: "text/plain",
@@ -15,7 +22,6 @@ export const RESPONSE_TYPE_MIME: Record<string, string> = {
15
22
  md: "text/markdown",
16
23
  };
17
24
 
18
- // Reverse lookup: MIME type -> response type tag (e.g. "text/html" -> "html")
19
25
  export const MIME_RESPONSE_TYPE: Record<string, string> = Object.fromEntries(
20
26
  Object.entries(RESPONSE_TYPE_MIME).map(([tag, mime]) => [mime, tag]),
21
27
  );
@@ -63,12 +69,10 @@ export function parseAcceptTypes(accept: string): AcceptEntry[] {
63
69
  }
64
70
  entries.push({ mime, q, order: i });
65
71
  }
66
- // Sort: highest q first, then lowest client order first (stable)
67
72
  entries.sort((a, b) => b.q - a.q || a.order - b.order);
68
73
  return entries;
69
74
  }
70
75
 
71
- // Sentinel response type for RSC routes in negotiation candidates
72
76
  export const RSC_RESPONSE_TYPE = "__rsc__";
73
77
 
74
78
  /**
@@ -81,7 +85,6 @@ export function pickNegotiateVariant(
81
85
  acceptEntries: AcceptEntry[],
82
86
  candidates: Array<{ routeKey: string; responseType: string }>,
83
87
  ): { routeKey: string; responseType: string } {
84
- // Build a MIME -> candidate lookup for O(1) matching
85
88
  const byCandidateMime = new Map<
86
89
  string,
87
90
  { routeKey: string; responseType: string }
@@ -98,9 +101,7 @@ export function pickNegotiateVariant(
98
101
 
99
102
  for (const entry of acceptEntries) {
100
103
  if (entry.q === 0) continue;
101
- // Wildcard matches first candidate
102
104
  if (entry.mime === "*/*") return candidates[0]!;
103
- // Type wildcard (e.g. "text/*") -- match first candidate with that type
104
105
  if (entry.mime.endsWith("/*")) {
105
106
  const typePrefix = entry.mime.slice(0, entry.mime.indexOf("/"));
106
107
  for (const [mime, candidate] of byCandidateMime) {
@@ -111,6 +112,104 @@ export function pickNegotiateVariant(
111
112
  const match = byCandidateMime.get(entry.mime);
112
113
  if (match) return match;
113
114
  }
114
- // No match -- use first candidate as default
115
115
  return candidates[0]!;
116
116
  }
117
+
118
+ /**
119
+ * Result of content negotiation for a route with negotiate variants.
120
+ */
121
+ export interface NegotiationResult {
122
+ /** The winning response type */
123
+ responseType: string;
124
+ /** Handler function for the winning variant */
125
+ handler: Function;
126
+ /** Manifest entry for the winning variant (may differ from primary) */
127
+ manifestEntry: EntryData;
128
+ /** Route middleware for the winning variant */
129
+ routeMiddleware: CollectedMiddleware[];
130
+ /** True when negotiation selected a variant; false for a plain response route. */
131
+ negotiated: boolean;
132
+ }
133
+
134
+ /**
135
+ * Perform content negotiation for a route with negotiate variants.
136
+ *
137
+ * Returns a NegotiationResult when a response route wins negotiation.
138
+ * Returns null when RSC wins or no negotiation is needed.
139
+ *
140
+ * Shared by previewMatch and classifyRequest to avoid duplicating
141
+ * the candidate-building and variant-loading logic.
142
+ */
143
+ export async function negotiateRoute(
144
+ request: Request,
145
+ pathname: string,
146
+ snapshot: RouteSnapshot,
147
+ ): Promise<NegotiationResult | null> {
148
+ const { matched, manifestEntry, routeMiddleware, responseType } = snapshot;
149
+ if (!matched.negotiateVariants || matched.negotiateVariants.length === 0) {
150
+ // No variants: a plain response route still yields a result (negotiated:false)
151
+ // so callers don't re-derive it; RSC routes (no responseType/handler) -> null.
152
+ const handler =
153
+ manifestEntry.type === "route" ? manifestEntry.handler : undefined;
154
+ if (responseType && handler) {
155
+ return {
156
+ responseType,
157
+ handler: handler as Function,
158
+ manifestEntry,
159
+ routeMiddleware,
160
+ negotiated: false,
161
+ };
162
+ }
163
+ return null;
164
+ }
165
+
166
+ const acceptEntries = parseAcceptTypes(request.headers.get("accept") || "");
167
+
168
+ const variants = matched.negotiateVariants;
169
+ let candidates: Array<{ routeKey: string; responseType: string }>;
170
+ if (responseType) {
171
+ candidates = [...variants, { routeKey: matched.routeKey, responseType }];
172
+ } else {
173
+ const rscCandidate = {
174
+ routeKey: matched.routeKey,
175
+ responseType: RSC_RESPONSE_TYPE,
176
+ };
177
+ candidates = matched.rscFirst
178
+ ? [rscCandidate, ...variants]
179
+ : [...variants, rscCandidate];
180
+ }
181
+
182
+ const variant = pickNegotiateVariant(acceptEntries, candidates);
183
+
184
+ if (variant.responseType === RSC_RESPONSE_TYPE) {
185
+ return null;
186
+ }
187
+
188
+ if (responseType && variant.routeKey === matched.routeKey) {
189
+ return {
190
+ responseType,
191
+ handler: manifestEntry.handler as Function,
192
+ manifestEntry,
193
+ routeMiddleware,
194
+ negotiated: true,
195
+ };
196
+ }
197
+ const negotiateEntry = await loadManifest(
198
+ matched.entry,
199
+ variant.routeKey,
200
+ pathname,
201
+ undefined,
202
+ false,
203
+ );
204
+ const variantMiddleware = collectRouteMiddleware(
205
+ traverseBack(negotiateEntry),
206
+ matched.params,
207
+ );
208
+ return {
209
+ responseType: variant.responseType,
210
+ handler: negotiateEntry.handler as Function,
211
+ manifestEntry: negotiateEntry,
212
+ routeMiddleware: variantMiddleware,
213
+ negotiated: true,
214
+ };
215
+ }