@rangojs/router 0.0.0-experimental.15 → 0.0.0-experimental.151

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 (1284) hide show
  1. package/AGENTS.md +13 -0
  2. package/README.md +308 -451
  3. package/dist/__internal.d.ts +83 -0
  4. package/dist/__internal.d.ts.map +1 -0
  5. package/dist/__internal.js +19 -0
  6. package/dist/__internal.js.map +1 -0
  7. package/dist/__mocks__/version.d.ts +7 -0
  8. package/dist/__mocks__/version.d.ts.map +1 -0
  9. package/dist/__mocks__/version.js +7 -0
  10. package/dist/__mocks__/version.js.map +1 -0
  11. package/dist/__tests__/client-href.test.d.ts +2 -0
  12. package/dist/__tests__/client-href.test.d.ts.map +1 -0
  13. package/dist/__tests__/client-href.test.js +74 -0
  14. package/dist/__tests__/client-href.test.js.map +1 -0
  15. package/dist/__tests__/component-utils.test.d.ts +2 -0
  16. package/dist/__tests__/component-utils.test.d.ts.map +1 -0
  17. package/dist/__tests__/component-utils.test.js +51 -0
  18. package/dist/__tests__/component-utils.test.js.map +1 -0
  19. package/dist/__tests__/event-controller.test.d.ts +2 -0
  20. package/dist/__tests__/event-controller.test.d.ts.map +1 -0
  21. package/dist/__tests__/event-controller.test.js +538 -0
  22. package/dist/__tests__/event-controller.test.js.map +1 -0
  23. package/dist/__tests__/helpers/route-tree.d.ts +118 -0
  24. package/dist/__tests__/helpers/route-tree.d.ts.map +1 -0
  25. package/dist/__tests__/helpers/route-tree.js +374 -0
  26. package/dist/__tests__/helpers/route-tree.js.map +1 -0
  27. package/dist/__tests__/match-result.test.d.ts +2 -0
  28. package/dist/__tests__/match-result.test.d.ts.map +1 -0
  29. package/dist/__tests__/match-result.test.js +154 -0
  30. package/dist/__tests__/match-result.test.js.map +1 -0
  31. package/dist/__tests__/navigation-store.test.d.ts +2 -0
  32. package/dist/__tests__/navigation-store.test.d.ts.map +1 -0
  33. package/dist/__tests__/navigation-store.test.js +440 -0
  34. package/dist/__tests__/navigation-store.test.js.map +1 -0
  35. package/dist/__tests__/partial-update.test.d.ts +2 -0
  36. package/dist/__tests__/partial-update.test.d.ts.map +1 -0
  37. package/dist/__tests__/partial-update.test.js +1009 -0
  38. package/dist/__tests__/partial-update.test.js.map +1 -0
  39. package/dist/__tests__/reverse-types.test.d.ts +8 -0
  40. package/dist/__tests__/reverse-types.test.d.ts.map +1 -0
  41. package/dist/__tests__/reverse-types.test.js +656 -0
  42. package/dist/__tests__/reverse-types.test.js.map +1 -0
  43. package/dist/__tests__/route-definition.test.d.ts +2 -0
  44. package/dist/__tests__/route-definition.test.d.ts.map +1 -0
  45. package/dist/__tests__/route-definition.test.js +55 -0
  46. package/dist/__tests__/route-definition.test.js.map +1 -0
  47. package/dist/__tests__/router-helpers.test.d.ts +2 -0
  48. package/dist/__tests__/router-helpers.test.d.ts.map +1 -0
  49. package/dist/__tests__/router-helpers.test.js +377 -0
  50. package/dist/__tests__/router-helpers.test.js.map +1 -0
  51. package/dist/__tests__/router-integration-2.test.d.ts +2 -0
  52. package/dist/__tests__/router-integration-2.test.d.ts.map +1 -0
  53. package/dist/__tests__/router-integration-2.test.js +426 -0
  54. package/dist/__tests__/router-integration-2.test.js.map +1 -0
  55. package/dist/__tests__/router-integration.test.d.ts +2 -0
  56. package/dist/__tests__/router-integration.test.d.ts.map +1 -0
  57. package/dist/__tests__/router-integration.test.js +1051 -0
  58. package/dist/__tests__/router-integration.test.js.map +1 -0
  59. package/dist/__tests__/search-params.test.d.ts +5 -0
  60. package/dist/__tests__/search-params.test.d.ts.map +1 -0
  61. package/dist/__tests__/search-params.test.js +306 -0
  62. package/dist/__tests__/search-params.test.js.map +1 -0
  63. package/dist/__tests__/segment-system.test.d.ts +2 -0
  64. package/dist/__tests__/segment-system.test.d.ts.map +1 -0
  65. package/dist/__tests__/segment-system.test.js +627 -0
  66. package/dist/__tests__/segment-system.test.js.map +1 -0
  67. package/dist/__tests__/static-handler-types.test.d.ts +8 -0
  68. package/dist/__tests__/static-handler-types.test.d.ts.map +1 -0
  69. package/dist/__tests__/static-handler-types.test.js +63 -0
  70. package/dist/__tests__/static-handler-types.test.js.map +1 -0
  71. package/dist/__tests__/urls.test.d.ts +2 -0
  72. package/dist/__tests__/urls.test.d.ts.map +1 -0
  73. package/dist/__tests__/urls.test.js +421 -0
  74. package/dist/__tests__/urls.test.js.map +1 -0
  75. package/dist/__tests__/use-mount.test.d.ts +2 -0
  76. package/dist/__tests__/use-mount.test.d.ts.map +1 -0
  77. package/dist/__tests__/use-mount.test.js +35 -0
  78. package/dist/__tests__/use-mount.test.js.map +1 -0
  79. package/dist/bin/rango.d.ts +2 -0
  80. package/dist/bin/rango.d.ts.map +1 -0
  81. package/dist/bin/rango.js +1844 -237
  82. package/dist/bin/rango.js.map +1 -0
  83. package/dist/browser/event-controller.d.ts +191 -0
  84. package/dist/browser/event-controller.d.ts.map +1 -0
  85. package/dist/browser/event-controller.js +559 -0
  86. package/dist/browser/event-controller.js.map +1 -0
  87. package/dist/browser/index.d.ts +2 -0
  88. package/dist/browser/index.d.ts.map +1 -0
  89. package/dist/browser/index.js +14 -0
  90. package/dist/browser/index.js.map +1 -0
  91. package/dist/browser/link-interceptor.d.ts +38 -0
  92. package/dist/browser/link-interceptor.d.ts.map +1 -0
  93. package/dist/browser/link-interceptor.js +99 -0
  94. package/dist/browser/link-interceptor.js.map +1 -0
  95. package/dist/browser/logging.d.ts +10 -0
  96. package/dist/browser/logging.d.ts.map +1 -0
  97. package/dist/browser/logging.js +29 -0
  98. package/dist/browser/logging.js.map +1 -0
  99. package/dist/browser/lru-cache.d.ts +17 -0
  100. package/dist/browser/lru-cache.d.ts.map +1 -0
  101. package/dist/browser/lru-cache.js +50 -0
  102. package/dist/browser/lru-cache.js.map +1 -0
  103. package/dist/browser/merge-segment-loaders.d.ts +39 -0
  104. package/dist/browser/merge-segment-loaders.d.ts.map +1 -0
  105. package/dist/browser/merge-segment-loaders.js +102 -0
  106. package/dist/browser/merge-segment-loaders.js.map +1 -0
  107. package/dist/browser/navigation-bridge.d.ts +102 -0
  108. package/dist/browser/navigation-bridge.d.ts.map +1 -0
  109. package/dist/browser/navigation-bridge.js +708 -0
  110. package/dist/browser/navigation-bridge.js.map +1 -0
  111. package/dist/browser/navigation-client.d.ts +25 -0
  112. package/dist/browser/navigation-client.d.ts.map +1 -0
  113. package/dist/browser/navigation-client.js +157 -0
  114. package/dist/browser/navigation-client.js.map +1 -0
  115. package/dist/browser/navigation-store.d.ts +101 -0
  116. package/dist/browser/navigation-store.d.ts.map +1 -0
  117. package/dist/browser/navigation-store.js +625 -0
  118. package/dist/browser/navigation-store.js.map +1 -0
  119. package/dist/browser/partial-update.d.ts +75 -0
  120. package/dist/browser/partial-update.d.ts.map +1 -0
  121. package/dist/browser/partial-update.js +426 -0
  122. package/dist/browser/partial-update.js.map +1 -0
  123. package/dist/browser/react/Link.d.ts +86 -0
  124. package/dist/browser/react/Link.d.ts.map +1 -0
  125. package/dist/browser/react/Link.js +128 -0
  126. package/dist/browser/react/Link.js.map +1 -0
  127. package/dist/browser/react/NavigationProvider.d.ts +63 -0
  128. package/dist/browser/react/NavigationProvider.d.ts.map +1 -0
  129. package/dist/browser/react/NavigationProvider.js +216 -0
  130. package/dist/browser/react/NavigationProvider.js.map +1 -0
  131. package/dist/browser/react/ScrollRestoration.d.ts +75 -0
  132. package/dist/browser/react/ScrollRestoration.d.ts.map +1 -0
  133. package/dist/browser/react/ScrollRestoration.js +57 -0
  134. package/dist/browser/react/ScrollRestoration.js.map +1 -0
  135. package/dist/browser/react/context.d.ts +46 -0
  136. package/dist/browser/react/context.d.ts.map +1 -0
  137. package/dist/browser/react/context.js +10 -0
  138. package/dist/browser/react/context.js.map +1 -0
  139. package/dist/browser/react/index.d.ts +11 -0
  140. package/dist/browser/react/index.d.ts.map +1 -0
  141. package/dist/browser/react/index.js +22 -0
  142. package/dist/browser/react/index.js.map +1 -0
  143. package/dist/browser/react/location-state-shared.d.ts +63 -0
  144. package/dist/browser/react/location-state-shared.d.ts.map +1 -0
  145. package/dist/browser/react/location-state-shared.js +81 -0
  146. package/dist/browser/react/location-state-shared.js.map +1 -0
  147. package/dist/browser/react/location-state.d.ts +23 -0
  148. package/dist/browser/react/location-state.d.ts.map +1 -0
  149. package/dist/browser/react/location-state.js +29 -0
  150. package/dist/browser/react/location-state.js.map +1 -0
  151. package/dist/browser/react/mount-context.d.ts +24 -0
  152. package/dist/browser/react/mount-context.d.ts.map +1 -0
  153. package/dist/browser/react/mount-context.js +24 -0
  154. package/dist/browser/react/mount-context.js.map +1 -0
  155. package/dist/browser/react/use-action.d.ts +64 -0
  156. package/dist/browser/react/use-action.d.ts.map +1 -0
  157. package/dist/browser/react/use-action.js +134 -0
  158. package/dist/browser/react/use-action.js.map +1 -0
  159. package/dist/browser/react/use-client-cache.d.ts +41 -0
  160. package/dist/browser/react/use-client-cache.d.ts.map +1 -0
  161. package/{src/browser/react/use-client-cache.ts → dist/browser/react/use-client-cache.js} +9 -26
  162. package/dist/browser/react/use-client-cache.js.map +1 -0
  163. package/dist/browser/react/use-handle.d.ts +31 -0
  164. package/dist/browser/react/use-handle.d.ts.map +1 -0
  165. package/dist/browser/react/use-handle.js +144 -0
  166. package/dist/browser/react/use-handle.js.map +1 -0
  167. package/dist/browser/react/use-href.d.ts +33 -0
  168. package/dist/browser/react/use-href.d.ts.map +1 -0
  169. package/dist/browser/react/use-href.js +39 -0
  170. package/dist/browser/react/use-href.js.map +1 -0
  171. package/dist/browser/react/use-link-status.d.ts +37 -0
  172. package/dist/browser/react/use-link-status.d.ts.map +1 -0
  173. package/dist/browser/react/use-link-status.js +99 -0
  174. package/dist/browser/react/use-link-status.js.map +1 -0
  175. package/dist/browser/react/use-mount.d.ts +25 -0
  176. package/dist/browser/react/use-mount.d.ts.map +1 -0
  177. package/dist/browser/react/use-mount.js +30 -0
  178. package/dist/browser/react/use-mount.js.map +1 -0
  179. package/dist/browser/react/use-navigation.d.ts +27 -0
  180. package/dist/browser/react/use-navigation.d.ts.map +1 -0
  181. package/dist/browser/react/use-navigation.js +87 -0
  182. package/dist/browser/react/use-navigation.js.map +1 -0
  183. package/dist/browser/react/use-segments.d.ts +38 -0
  184. package/dist/browser/react/use-segments.d.ts.map +1 -0
  185. package/dist/browser/react/use-segments.js +130 -0
  186. package/dist/browser/react/use-segments.js.map +1 -0
  187. package/dist/browser/request-controller.d.ts +26 -0
  188. package/dist/browser/request-controller.d.ts.map +1 -0
  189. package/dist/browser/request-controller.js +147 -0
  190. package/dist/browser/request-controller.js.map +1 -0
  191. package/dist/browser/rsc-router.d.ts +129 -0
  192. package/dist/browser/rsc-router.d.ts.map +1 -0
  193. package/dist/browser/rsc-router.js +195 -0
  194. package/dist/browser/rsc-router.js.map +1 -0
  195. package/dist/browser/scroll-restoration.d.ts +93 -0
  196. package/dist/browser/scroll-restoration.d.ts.map +1 -0
  197. package/dist/browser/scroll-restoration.js +321 -0
  198. package/dist/browser/scroll-restoration.js.map +1 -0
  199. package/dist/browser/segment-structure-assert.d.ts +17 -0
  200. package/dist/browser/segment-structure-assert.d.ts.map +1 -0
  201. package/dist/browser/segment-structure-assert.js +59 -0
  202. package/dist/browser/segment-structure-assert.js.map +1 -0
  203. package/dist/browser/server-action-bridge.d.ts +26 -0
  204. package/dist/browser/server-action-bridge.d.ts.map +1 -0
  205. package/dist/browser/server-action-bridge.js +668 -0
  206. package/dist/browser/server-action-bridge.js.map +1 -0
  207. package/dist/browser/shallow.d.ts +12 -0
  208. package/dist/browser/shallow.d.ts.map +1 -0
  209. package/dist/browser/shallow.js +34 -0
  210. package/dist/browser/shallow.js.map +1 -0
  211. package/dist/browser/types.d.ts +369 -0
  212. package/dist/browser/types.d.ts.map +1 -0
  213. package/dist/browser/types.js +2 -0
  214. package/dist/browser/types.js.map +1 -0
  215. package/dist/build/__tests__/generate-cli.test.d.ts +2 -0
  216. package/dist/build/__tests__/generate-cli.test.d.ts.map +1 -0
  217. package/dist/build/__tests__/generate-cli.test.js +237 -0
  218. package/dist/build/__tests__/generate-cli.test.js.map +1 -0
  219. package/dist/build/__tests__/generate-manifest.test.d.ts +2 -0
  220. package/dist/build/__tests__/generate-manifest.test.d.ts.map +1 -0
  221. package/dist/build/__tests__/generate-manifest.test.js +119 -0
  222. package/dist/build/__tests__/generate-manifest.test.js.map +1 -0
  223. package/dist/build/__tests__/generate-route-types.test.d.ts +2 -0
  224. package/dist/build/__tests__/generate-route-types.test.d.ts.map +1 -0
  225. package/dist/build/__tests__/generate-route-types.test.js +620 -0
  226. package/dist/build/__tests__/generate-route-types.test.js.map +1 -0
  227. package/dist/build/__tests__/per-router-manifest.test.d.ts +2 -0
  228. package/dist/build/__tests__/per-router-manifest.test.d.ts.map +1 -0
  229. package/dist/build/__tests__/per-router-manifest.test.js +308 -0
  230. package/dist/build/__tests__/per-router-manifest.test.js.map +1 -0
  231. package/dist/build/generate-manifest.d.ts +81 -0
  232. package/dist/build/generate-manifest.d.ts.map +1 -0
  233. package/dist/build/generate-manifest.js +276 -0
  234. package/dist/build/generate-manifest.js.map +1 -0
  235. package/dist/build/generate-route-types.d.ts +115 -0
  236. package/dist/build/generate-route-types.d.ts.map +1 -0
  237. package/dist/build/generate-route-types.js +740 -0
  238. package/dist/build/generate-route-types.js.map +1 -0
  239. package/dist/build/index.d.ts +21 -0
  240. package/dist/build/index.d.ts.map +1 -0
  241. package/dist/build/index.js +21 -0
  242. package/dist/build/index.js.map +1 -0
  243. package/dist/build/route-trie.d.ts +71 -0
  244. package/dist/build/route-trie.d.ts.map +1 -0
  245. package/dist/build/route-trie.js +175 -0
  246. package/dist/build/route-trie.js.map +1 -0
  247. package/dist/cache/__tests__/cache-scope.test.d.ts +2 -0
  248. package/dist/cache/__tests__/cache-scope.test.d.ts.map +1 -0
  249. package/dist/cache/__tests__/cache-scope.test.js +208 -0
  250. package/dist/cache/__tests__/cache-scope.test.js.map +1 -0
  251. package/dist/cache/__tests__/document-cache.test.d.ts +2 -0
  252. package/dist/cache/__tests__/document-cache.test.d.ts.map +1 -0
  253. package/dist/cache/__tests__/document-cache.test.js +345 -0
  254. package/dist/cache/__tests__/document-cache.test.js.map +1 -0
  255. package/dist/cache/__tests__/memory-segment-store.test.d.ts +2 -0
  256. package/dist/cache/__tests__/memory-segment-store.test.d.ts.map +1 -0
  257. package/dist/cache/__tests__/memory-segment-store.test.js +425 -0
  258. package/dist/cache/__tests__/memory-segment-store.test.js.map +1 -0
  259. package/dist/cache/__tests__/memory-store.test.d.ts +2 -0
  260. package/dist/cache/__tests__/memory-store.test.d.ts.map +1 -0
  261. package/dist/cache/__tests__/memory-store.test.js +367 -0
  262. package/dist/cache/__tests__/memory-store.test.js.map +1 -0
  263. package/dist/cache/cache-scope.d.ts +102 -0
  264. package/dist/cache/cache-scope.d.ts.map +1 -0
  265. package/dist/cache/cache-scope.js +440 -0
  266. package/dist/cache/cache-scope.js.map +1 -0
  267. package/dist/cache/cf/__tests__/cf-cache-store.test.d.ts +2 -0
  268. package/dist/cache/cf/__tests__/cf-cache-store.test.d.ts.map +1 -0
  269. package/dist/cache/cf/__tests__/cf-cache-store.test.js +330 -0
  270. package/dist/cache/cf/__tests__/cf-cache-store.test.js.map +1 -0
  271. package/dist/cache/cf/cf-cache-store.d.ts +165 -0
  272. package/dist/cache/cf/cf-cache-store.d.ts.map +1 -0
  273. package/dist/cache/cf/cf-cache-store.js +242 -0
  274. package/dist/cache/cf/cf-cache-store.js.map +1 -0
  275. package/dist/cache/cf/index.d.ts +14 -0
  276. package/dist/cache/cf/index.d.ts.map +1 -0
  277. package/dist/cache/cf/index.js +17 -0
  278. package/dist/cache/cf/index.js.map +1 -0
  279. package/dist/cache/document-cache.d.ts +64 -0
  280. package/dist/cache/document-cache.d.ts.map +1 -0
  281. package/dist/cache/document-cache.js +228 -0
  282. package/dist/cache/document-cache.js.map +1 -0
  283. package/dist/cache/index.d.ts +19 -0
  284. package/dist/cache/index.d.ts.map +1 -0
  285. package/dist/cache/index.js +21 -0
  286. package/dist/cache/index.js.map +1 -0
  287. package/dist/cache/memory-segment-store.d.ts +110 -0
  288. package/dist/cache/memory-segment-store.d.ts.map +1 -0
  289. package/dist/cache/memory-segment-store.js +117 -0
  290. package/dist/cache/memory-segment-store.js.map +1 -0
  291. package/dist/cache/memory-store.d.ts +41 -0
  292. package/dist/cache/memory-store.d.ts.map +1 -0
  293. package/dist/cache/memory-store.js +191 -0
  294. package/dist/cache/memory-store.js.map +1 -0
  295. package/dist/cache/types.d.ts +317 -0
  296. package/dist/cache/types.d.ts.map +1 -0
  297. package/dist/cache/types.js +12 -0
  298. package/dist/cache/types.js.map +1 -0
  299. package/dist/client.d.ts +248 -0
  300. package/dist/client.d.ts.map +1 -0
  301. package/dist/client.js +367 -0
  302. package/dist/client.js.map +1 -0
  303. package/dist/client.rsc.d.ts +26 -0
  304. package/dist/client.rsc.d.ts.map +1 -0
  305. package/dist/client.rsc.js +46 -0
  306. package/dist/client.rsc.js.map +1 -0
  307. package/dist/component-utils.d.ts +36 -0
  308. package/dist/component-utils.d.ts.map +1 -0
  309. package/dist/component-utils.js +61 -0
  310. package/dist/component-utils.js.map +1 -0
  311. package/dist/components/DefaultDocument.d.ts +13 -0
  312. package/dist/components/DefaultDocument.d.ts.map +1 -0
  313. package/dist/components/DefaultDocument.js +15 -0
  314. package/dist/components/DefaultDocument.js.map +1 -0
  315. package/dist/debug.d.ts +58 -0
  316. package/dist/debug.d.ts.map +1 -0
  317. package/dist/debug.js +157 -0
  318. package/dist/debug.js.map +1 -0
  319. package/dist/default-error-boundary.d.ts +11 -0
  320. package/dist/default-error-boundary.d.ts.map +1 -0
  321. package/dist/default-error-boundary.js +45 -0
  322. package/dist/default-error-boundary.js.map +1 -0
  323. package/dist/deps/browser.d.ts +2 -0
  324. package/dist/deps/browser.d.ts.map +1 -0
  325. package/dist/deps/browser.js +3 -0
  326. package/dist/deps/browser.js.map +1 -0
  327. package/dist/deps/html-stream-client.d.ts +2 -0
  328. package/dist/deps/html-stream-client.d.ts.map +1 -0
  329. package/dist/deps/html-stream-client.js +3 -0
  330. package/dist/deps/html-stream-client.js.map +1 -0
  331. package/dist/deps/html-stream-server.d.ts +2 -0
  332. package/dist/deps/html-stream-server.d.ts.map +1 -0
  333. package/dist/deps/html-stream-server.js +3 -0
  334. package/dist/deps/html-stream-server.js.map +1 -0
  335. package/dist/deps/rsc.d.ts +2 -0
  336. package/dist/deps/rsc.d.ts.map +1 -0
  337. package/dist/deps/rsc.js +4 -0
  338. package/dist/deps/rsc.js.map +1 -0
  339. package/dist/deps/ssr.d.ts +2 -0
  340. package/dist/deps/ssr.d.ts.map +1 -0
  341. package/dist/deps/ssr.js +3 -0
  342. package/dist/deps/ssr.js.map +1 -0
  343. package/dist/errors.d.ts +174 -0
  344. package/dist/errors.d.ts.map +1 -0
  345. package/dist/errors.js +241 -0
  346. package/dist/errors.js.map +1 -0
  347. package/dist/handle.d.ts +78 -0
  348. package/dist/handle.d.ts.map +1 -0
  349. package/dist/handle.js +82 -0
  350. package/dist/handle.js.map +1 -0
  351. package/dist/handles/MetaTags.d.ts +14 -0
  352. package/dist/handles/MetaTags.d.ts.map +1 -0
  353. package/dist/handles/MetaTags.js +136 -0
  354. package/dist/handles/MetaTags.js.map +1 -0
  355. package/dist/handles/index.d.ts +6 -0
  356. package/dist/handles/index.d.ts.map +1 -0
  357. package/{src/handles/index.ts → dist/handles/index.js} +1 -1
  358. package/dist/handles/index.js.map +1 -0
  359. package/dist/handles/meta.d.ts +39 -0
  360. package/dist/handles/meta.d.ts.map +1 -0
  361. package/dist/handles/meta.js +202 -0
  362. package/dist/handles/meta.js.map +1 -0
  363. package/dist/host/__tests__/errors.test.d.ts +2 -0
  364. package/dist/host/__tests__/errors.test.d.ts.map +1 -0
  365. package/dist/host/__tests__/errors.test.js +76 -0
  366. package/dist/host/__tests__/errors.test.js.map +1 -0
  367. package/dist/host/__tests__/pattern-comprehensive.test.d.ts +2 -0
  368. package/dist/host/__tests__/pattern-comprehensive.test.d.ts.map +1 -0
  369. package/dist/host/__tests__/pattern-comprehensive.test.js +732 -0
  370. package/dist/host/__tests__/pattern-comprehensive.test.js.map +1 -0
  371. package/dist/host/__tests__/pattern-matcher.test.d.ts +2 -0
  372. package/dist/host/__tests__/pattern-matcher.test.d.ts.map +1 -0
  373. package/dist/host/__tests__/pattern-matcher.test.js +251 -0
  374. package/dist/host/__tests__/pattern-matcher.test.js.map +1 -0
  375. package/dist/host/__tests__/router.test.d.ts +2 -0
  376. package/dist/host/__tests__/router.test.d.ts.map +1 -0
  377. package/dist/host/__tests__/router.test.js +241 -0
  378. package/dist/host/__tests__/router.test.js.map +1 -0
  379. package/dist/host/__tests__/testing.test.d.ts +2 -0
  380. package/dist/host/__tests__/testing.test.d.ts.map +1 -0
  381. package/dist/host/__tests__/testing.test.js +64 -0
  382. package/dist/host/__tests__/testing.test.js.map +1 -0
  383. package/dist/host/__tests__/utils.test.d.ts +2 -0
  384. package/dist/host/__tests__/utils.test.d.ts.map +1 -0
  385. package/dist/host/__tests__/utils.test.js +29 -0
  386. package/dist/host/__tests__/utils.test.js.map +1 -0
  387. package/dist/host/cookie-handler.d.ts +34 -0
  388. package/dist/host/cookie-handler.d.ts.map +1 -0
  389. package/dist/host/cookie-handler.js +124 -0
  390. package/dist/host/cookie-handler.js.map +1 -0
  391. package/dist/host/errors.d.ts +56 -0
  392. package/dist/host/errors.d.ts.map +1 -0
  393. package/dist/host/errors.js +79 -0
  394. package/dist/host/errors.js.map +1 -0
  395. package/dist/host/index.d.ts +29 -0
  396. package/dist/host/index.d.ts.map +1 -0
  397. package/dist/host/index.js +32 -0
  398. package/dist/host/index.js.map +1 -0
  399. package/dist/host/pattern-matcher.d.ts +36 -0
  400. package/dist/host/pattern-matcher.d.ts.map +1 -0
  401. package/dist/host/pattern-matcher.js +172 -0
  402. package/dist/host/pattern-matcher.js.map +1 -0
  403. package/dist/host/router.d.ts +26 -0
  404. package/dist/host/router.d.ts.map +1 -0
  405. package/dist/host/router.js +218 -0
  406. package/dist/host/router.js.map +1 -0
  407. package/dist/host/testing.d.ts +36 -0
  408. package/dist/host/testing.d.ts.map +1 -0
  409. package/dist/host/testing.js +55 -0
  410. package/dist/host/testing.js.map +1 -0
  411. package/dist/host/types.d.ts +115 -0
  412. package/dist/host/types.d.ts.map +1 -0
  413. package/dist/host/types.js +7 -0
  414. package/dist/host/types.js.map +1 -0
  415. package/dist/host/utils.d.ts +21 -0
  416. package/dist/host/utils.d.ts.map +1 -0
  417. package/dist/host/utils.js +23 -0
  418. package/dist/host/utils.js.map +1 -0
  419. package/dist/href-client.d.ts +131 -0
  420. package/dist/href-client.d.ts.map +1 -0
  421. package/dist/href-client.js +64 -0
  422. package/dist/href-client.js.map +1 -0
  423. package/{src/href-context.ts → dist/href-context.d.ts} +7 -11
  424. package/dist/href-context.d.ts.map +1 -0
  425. package/dist/href-context.js +21 -0
  426. package/dist/href-context.js.map +1 -0
  427. package/dist/index.d.ts +73 -0
  428. package/dist/index.d.ts.map +1 -0
  429. package/dist/index.js +91 -0
  430. package/dist/index.js.map +1 -0
  431. package/dist/index.rsc.d.ts +32 -0
  432. package/dist/index.rsc.d.ts.map +1 -0
  433. package/dist/index.rsc.js +40 -0
  434. package/dist/index.rsc.js.map +1 -0
  435. package/dist/internal-debug.d.ts +2 -0
  436. package/dist/internal-debug.d.ts.map +1 -0
  437. package/dist/internal-debug.js +5 -0
  438. package/dist/internal-debug.js.map +1 -0
  439. package/dist/loader.d.ts +14 -0
  440. package/dist/loader.d.ts.map +1 -0
  441. package/dist/loader.js +20 -0
  442. package/dist/loader.js.map +1 -0
  443. package/dist/loader.rsc.d.ts +19 -0
  444. package/dist/loader.rsc.d.ts.map +1 -0
  445. package/dist/loader.rsc.js +99 -0
  446. package/dist/loader.rsc.js.map +1 -0
  447. package/{src/network-error-thrower.tsx → dist/network-error-thrower.d.ts} +4 -8
  448. package/dist/network-error-thrower.d.ts.map +1 -0
  449. package/dist/network-error-thrower.js +14 -0
  450. package/dist/network-error-thrower.js.map +1 -0
  451. package/dist/outlet-context.d.ts +13 -0
  452. package/dist/outlet-context.d.ts.map +1 -0
  453. package/dist/outlet-context.js +3 -0
  454. package/dist/outlet-context.js.map +1 -0
  455. package/dist/prerender/__tests__/param-hash.test.d.ts +2 -0
  456. package/dist/prerender/__tests__/param-hash.test.d.ts.map +1 -0
  457. package/dist/prerender/__tests__/param-hash.test.js +148 -0
  458. package/dist/prerender/__tests__/param-hash.test.js.map +1 -0
  459. package/dist/prerender/param-hash.d.ts +16 -0
  460. package/dist/prerender/param-hash.d.ts.map +1 -0
  461. package/dist/prerender/param-hash.js +36 -0
  462. package/dist/prerender/param-hash.js.map +1 -0
  463. package/dist/prerender/store.d.ts +38 -0
  464. package/dist/prerender/store.d.ts.map +1 -0
  465. package/dist/prerender/store.js +61 -0
  466. package/dist/prerender/store.js.map +1 -0
  467. package/dist/prerender.d.ts +66 -0
  468. package/dist/prerender.d.ts.map +1 -0
  469. package/dist/prerender.js +57 -0
  470. package/dist/prerender.js.map +1 -0
  471. package/dist/reverse.d.ts +196 -0
  472. package/dist/reverse.d.ts.map +1 -0
  473. package/dist/reverse.js +78 -0
  474. package/dist/reverse.js.map +1 -0
  475. package/dist/root-error-boundary.d.ts +33 -0
  476. package/dist/root-error-boundary.d.ts.map +1 -0
  477. package/dist/root-error-boundary.js +165 -0
  478. package/dist/root-error-boundary.js.map +1 -0
  479. package/dist/route-content-wrapper.d.ts +46 -0
  480. package/dist/route-content-wrapper.d.ts.map +1 -0
  481. package/dist/route-content-wrapper.js +77 -0
  482. package/dist/route-content-wrapper.js.map +1 -0
  483. package/dist/route-definition.d.ts +421 -0
  484. package/dist/route-definition.d.ts.map +1 -0
  485. package/dist/route-definition.js +868 -0
  486. package/dist/route-definition.js.map +1 -0
  487. package/dist/route-map-builder.d.ts +155 -0
  488. package/dist/route-map-builder.d.ts.map +1 -0
  489. package/dist/route-map-builder.js +237 -0
  490. package/dist/route-map-builder.js.map +1 -0
  491. package/dist/route-types.d.ts +165 -0
  492. package/dist/route-types.d.ts.map +1 -0
  493. package/dist/route-types.js +7 -0
  494. package/dist/route-types.js.map +1 -0
  495. package/dist/router/__tests__/handler-context.test.d.ts +2 -0
  496. package/dist/router/__tests__/handler-context.test.d.ts.map +1 -0
  497. package/dist/router/__tests__/handler-context.test.js +65 -0
  498. package/dist/router/__tests__/handler-context.test.js.map +1 -0
  499. package/dist/router/__tests__/loader-cycle-detection.test.d.ts +2 -0
  500. package/dist/router/__tests__/loader-cycle-detection.test.d.ts.map +1 -0
  501. package/dist/router/__tests__/loader-cycle-detection.test.js +221 -0
  502. package/dist/router/__tests__/loader-cycle-detection.test.js.map +1 -0
  503. package/dist/router/__tests__/match-context.test.d.ts +2 -0
  504. package/dist/router/__tests__/match-context.test.d.ts.map +1 -0
  505. package/dist/router/__tests__/match-context.test.js +92 -0
  506. package/dist/router/__tests__/match-context.test.js.map +1 -0
  507. package/dist/router/__tests__/match-pipelines.test.d.ts +2 -0
  508. package/dist/router/__tests__/match-pipelines.test.d.ts.map +1 -0
  509. package/dist/router/__tests__/match-pipelines.test.js +417 -0
  510. package/dist/router/__tests__/match-pipelines.test.js.map +1 -0
  511. package/dist/router/__tests__/match-result.test.d.ts +2 -0
  512. package/dist/router/__tests__/match-result.test.d.ts.map +1 -0
  513. package/dist/router/__tests__/match-result.test.js +457 -0
  514. package/dist/router/__tests__/match-result.test.js.map +1 -0
  515. package/dist/router/__tests__/on-error.test.d.ts +2 -0
  516. package/dist/router/__tests__/on-error.test.d.ts.map +1 -0
  517. package/dist/router/__tests__/on-error.test.js +678 -0
  518. package/dist/router/__tests__/on-error.test.js.map +1 -0
  519. package/dist/router/__tests__/pattern-matching.test.d.ts +2 -0
  520. package/dist/router/__tests__/pattern-matching.test.d.ts.map +1 -0
  521. package/dist/router/__tests__/pattern-matching.test.js +629 -0
  522. package/dist/router/__tests__/pattern-matching.test.js.map +1 -0
  523. package/dist/router/__tests__/segment-resolution-parallel-loading.test.d.ts +2 -0
  524. package/dist/router/__tests__/segment-resolution-parallel-loading.test.d.ts.map +1 -0
  525. package/dist/router/__tests__/segment-resolution-parallel-loading.test.js +155 -0
  526. package/dist/router/__tests__/segment-resolution-parallel-loading.test.js.map +1 -0
  527. package/dist/router/error-handling.d.ts +77 -0
  528. package/dist/router/error-handling.d.ts.map +1 -0
  529. package/dist/router/error-handling.js +202 -0
  530. package/dist/router/error-handling.js.map +1 -0
  531. package/dist/router/handler-context.d.ts +20 -0
  532. package/dist/router/handler-context.d.ts.map +1 -0
  533. package/dist/router/handler-context.js +198 -0
  534. package/dist/router/handler-context.js.map +1 -0
  535. package/dist/router/intercept-resolution.d.ts +66 -0
  536. package/dist/router/intercept-resolution.d.ts.map +1 -0
  537. package/dist/router/intercept-resolution.js +246 -0
  538. package/dist/router/intercept-resolution.js.map +1 -0
  539. package/dist/router/loader-resolution.d.ts +64 -0
  540. package/dist/router/loader-resolution.d.ts.map +1 -0
  541. package/dist/router/loader-resolution.js +284 -0
  542. package/dist/router/loader-resolution.js.map +1 -0
  543. package/dist/router/logging.d.ts +15 -0
  544. package/dist/router/logging.d.ts.map +1 -0
  545. package/dist/router/logging.js +99 -0
  546. package/dist/router/logging.js.map +1 -0
  547. package/dist/router/manifest.d.ts +22 -0
  548. package/dist/router/manifest.d.ts.map +1 -0
  549. package/dist/router/manifest.js +181 -0
  550. package/dist/router/manifest.js.map +1 -0
  551. package/dist/router/match-api.d.ts +35 -0
  552. package/dist/router/match-api.d.ts.map +1 -0
  553. package/dist/router/match-api.js +406 -0
  554. package/dist/router/match-api.js.map +1 -0
  555. package/dist/router/match-context.d.ts +206 -0
  556. package/dist/router/match-context.d.ts.map +1 -0
  557. package/dist/router/match-context.js +17 -0
  558. package/dist/router/match-context.js.map +1 -0
  559. package/dist/router/match-middleware/background-revalidation.d.ts +127 -0
  560. package/dist/router/match-middleware/background-revalidation.d.ts.map +1 -0
  561. package/dist/router/match-middleware/background-revalidation.js +75 -0
  562. package/dist/router/match-middleware/background-revalidation.js.map +1 -0
  563. package/dist/router/match-middleware/cache-lookup.d.ts +112 -0
  564. package/dist/router/match-middleware/cache-lookup.d.ts.map +1 -0
  565. package/dist/router/match-middleware/cache-lookup.js +257 -0
  566. package/dist/router/match-middleware/cache-lookup.js.map +1 -0
  567. package/dist/router/match-middleware/cache-store.d.ts +113 -0
  568. package/dist/router/match-middleware/cache-store.d.ts.map +1 -0
  569. package/dist/router/match-middleware/cache-store.js +108 -0
  570. package/dist/router/match-middleware/cache-store.js.map +1 -0
  571. package/dist/router/match-middleware/index.d.ts +81 -0
  572. package/dist/router/match-middleware/index.d.ts.map +1 -0
  573. package/dist/router/match-middleware/index.js +80 -0
  574. package/dist/router/match-middleware/index.js.map +1 -0
  575. package/dist/router/match-middleware/intercept-resolution.d.ts +117 -0
  576. package/dist/router/match-middleware/intercept-resolution.d.ts.map +1 -0
  577. package/dist/router/match-middleware/intercept-resolution.js +134 -0
  578. package/dist/router/match-middleware/intercept-resolution.js.map +1 -0
  579. package/dist/router/match-middleware/segment-resolution.d.ts +99 -0
  580. package/dist/router/match-middleware/segment-resolution.d.ts.map +1 -0
  581. package/dist/router/match-middleware/segment-resolution.js +53 -0
  582. package/dist/router/match-middleware/segment-resolution.js.map +1 -0
  583. package/dist/router/match-pipelines.d.ts +147 -0
  584. package/dist/router/match-pipelines.d.ts.map +1 -0
  585. package/dist/router/match-pipelines.js +82 -0
  586. package/dist/router/match-pipelines.js.map +1 -0
  587. package/dist/router/match-result.d.ts +126 -0
  588. package/dist/router/match-result.d.ts.map +1 -0
  589. package/dist/router/match-result.js +93 -0
  590. package/dist/router/match-result.js.map +1 -0
  591. package/dist/router/metrics.d.ts +20 -0
  592. package/dist/router/metrics.d.ts.map +1 -0
  593. package/dist/router/metrics.js +47 -0
  594. package/dist/router/metrics.js.map +1 -0
  595. package/dist/router/middleware.d.ts +249 -0
  596. package/dist/router/middleware.d.ts.map +1 -0
  597. package/dist/router/middleware.js +434 -0
  598. package/dist/router/middleware.js.map +1 -0
  599. package/dist/router/middleware.test.d.ts +2 -0
  600. package/dist/router/middleware.test.d.ts.map +1 -0
  601. package/dist/router/middleware.test.js +816 -0
  602. package/dist/router/middleware.test.js.map +1 -0
  603. package/dist/router/pattern-matching.d.ts +149 -0
  604. package/dist/router/pattern-matching.d.ts.map +1 -0
  605. package/dist/router/pattern-matching.js +349 -0
  606. package/dist/router/pattern-matching.js.map +1 -0
  607. package/dist/router/revalidation.d.ts +44 -0
  608. package/dist/router/revalidation.d.ts.map +1 -0
  609. package/dist/router/revalidation.js +147 -0
  610. package/dist/router/revalidation.js.map +1 -0
  611. package/dist/router/router-context.d.ts +135 -0
  612. package/dist/router/router-context.d.ts.map +1 -0
  613. package/dist/router/router-context.js +36 -0
  614. package/dist/router/router-context.js.map +1 -0
  615. package/dist/router/segment-resolution.d.ts +127 -0
  616. package/dist/router/segment-resolution.d.ts.map +1 -0
  617. package/dist/router/segment-resolution.js +919 -0
  618. package/dist/router/segment-resolution.js.map +1 -0
  619. package/dist/router/trie-matching.d.ts +40 -0
  620. package/dist/router/trie-matching.d.ts.map +1 -0
  621. package/dist/router/trie-matching.js +127 -0
  622. package/dist/router/trie-matching.js.map +1 -0
  623. package/dist/router/types.d.ts +136 -0
  624. package/dist/router/types.d.ts.map +1 -0
  625. package/dist/router/types.js +7 -0
  626. package/dist/router/types.js.map +1 -0
  627. package/dist/router.d.ts +753 -0
  628. package/dist/router.d.ts.map +1 -0
  629. package/dist/router.gen.d.ts +6 -0
  630. package/dist/router.gen.d.ts.map +1 -0
  631. package/dist/router.gen.js +6 -0
  632. package/dist/router.gen.js.map +1 -0
  633. package/dist/router.js +1304 -0
  634. package/dist/router.js.map +1 -0
  635. package/dist/rsc/__tests__/helpers.test.d.ts +2 -0
  636. package/dist/rsc/__tests__/helpers.test.d.ts.map +1 -0
  637. package/dist/rsc/__tests__/helpers.test.js +140 -0
  638. package/dist/rsc/__tests__/helpers.test.js.map +1 -0
  639. package/dist/rsc/handler.d.ts +45 -0
  640. package/dist/rsc/handler.d.ts.map +1 -0
  641. package/dist/rsc/handler.js +1172 -0
  642. package/dist/rsc/handler.js.map +1 -0
  643. package/dist/rsc/helpers.d.ts +16 -0
  644. package/dist/rsc/helpers.d.ts.map +1 -0
  645. package/dist/rsc/helpers.js +55 -0
  646. package/dist/rsc/helpers.js.map +1 -0
  647. package/dist/rsc/index.d.ts +22 -0
  648. package/dist/rsc/index.d.ts.map +1 -0
  649. package/dist/rsc/index.js +23 -0
  650. package/dist/rsc/index.js.map +1 -0
  651. package/dist/rsc/nonce.d.ts +9 -0
  652. package/dist/rsc/nonce.d.ts.map +1 -0
  653. package/dist/rsc/nonce.js +18 -0
  654. package/dist/rsc/nonce.js.map +1 -0
  655. package/dist/rsc/types.d.ts +206 -0
  656. package/dist/rsc/types.d.ts.map +1 -0
  657. package/dist/rsc/types.js +8 -0
  658. package/dist/rsc/types.js.map +1 -0
  659. package/dist/search-params.d.ts +103 -0
  660. package/dist/search-params.d.ts.map +1 -0
  661. package/dist/search-params.js +74 -0
  662. package/dist/search-params.js.map +1 -0
  663. package/dist/segment-system.d.ts +75 -0
  664. package/dist/segment-system.d.ts.map +1 -0
  665. package/dist/segment-system.js +336 -0
  666. package/dist/segment-system.js.map +1 -0
  667. package/dist/server/context.d.ts +245 -0
  668. package/dist/server/context.d.ts.map +1 -0
  669. package/dist/server/context.js +197 -0
  670. package/dist/server/context.js.map +1 -0
  671. package/dist/server/fetchable-loader-store.d.ts +18 -0
  672. package/dist/server/fetchable-loader-store.d.ts.map +1 -0
  673. package/dist/server/fetchable-loader-store.js +18 -0
  674. package/dist/server/fetchable-loader-store.js.map +1 -0
  675. package/dist/server/handle-store.d.ts +85 -0
  676. package/dist/server/handle-store.d.ts.map +1 -0
  677. package/dist/server/handle-store.js +142 -0
  678. package/dist/server/handle-store.js.map +1 -0
  679. package/dist/server/loader-registry.d.ts +55 -0
  680. package/dist/server/loader-registry.d.ts.map +1 -0
  681. package/dist/server/loader-registry.js +132 -0
  682. package/dist/server/loader-registry.js.map +1 -0
  683. package/dist/server/request-context.d.ts +226 -0
  684. package/dist/server/request-context.d.ts.map +1 -0
  685. package/dist/server/request-context.js +290 -0
  686. package/dist/server/request-context.js.map +1 -0
  687. package/dist/server/root-layout.d.ts +4 -0
  688. package/dist/server/root-layout.d.ts.map +1 -0
  689. package/dist/server/root-layout.js +5 -0
  690. package/dist/server/root-layout.js.map +1 -0
  691. package/dist/server.d.ts +15 -0
  692. package/dist/server.d.ts.map +1 -0
  693. package/dist/server.js +20 -0
  694. package/dist/server.js.map +1 -0
  695. package/dist/ssr/__tests__/ssr-handler.test.d.ts +2 -0
  696. package/dist/ssr/__tests__/ssr-handler.test.d.ts.map +1 -0
  697. package/dist/ssr/__tests__/ssr-handler.test.js +132 -0
  698. package/dist/ssr/__tests__/ssr-handler.test.js.map +1 -0
  699. package/dist/ssr/index.d.ts +98 -0
  700. package/dist/ssr/index.d.ts.map +1 -0
  701. package/dist/ssr/index.js +158 -0
  702. package/dist/ssr/index.js.map +1 -0
  703. package/dist/static-handler.d.ts +50 -0
  704. package/dist/static-handler.d.ts.map +1 -0
  705. package/dist/static-handler.gen.d.ts +5 -0
  706. package/dist/static-handler.gen.d.ts.map +1 -0
  707. package/dist/static-handler.gen.js +5 -0
  708. package/dist/static-handler.gen.js.map +1 -0
  709. package/dist/static-handler.js +29 -0
  710. package/dist/static-handler.js.map +1 -0
  711. package/dist/testing/vitest.js +82 -0
  712. package/dist/theme/ThemeProvider.d.ts +20 -0
  713. package/dist/theme/ThemeProvider.d.ts.map +1 -0
  714. package/dist/theme/ThemeProvider.js +240 -0
  715. package/dist/theme/ThemeProvider.js.map +1 -0
  716. package/dist/theme/ThemeScript.d.ts +48 -0
  717. package/dist/theme/ThemeScript.d.ts.map +1 -0
  718. package/dist/theme/ThemeScript.js +13 -0
  719. package/dist/theme/ThemeScript.js.map +1 -0
  720. package/dist/theme/__tests__/theme.test.d.ts +2 -0
  721. package/dist/theme/__tests__/theme.test.d.ts.map +1 -0
  722. package/dist/theme/__tests__/theme.test.js +103 -0
  723. package/dist/theme/__tests__/theme.test.js.map +1 -0
  724. package/dist/theme/constants.d.ts +29 -0
  725. package/dist/theme/constants.d.ts.map +1 -0
  726. package/dist/theme/constants.js +48 -0
  727. package/dist/theme/constants.js.map +1 -0
  728. package/dist/theme/index.d.ts +31 -0
  729. package/dist/theme/index.d.ts.map +1 -0
  730. package/dist/theme/index.js +36 -0
  731. package/dist/theme/index.js.map +1 -0
  732. package/dist/theme/theme-context.d.ts +40 -0
  733. package/dist/theme/theme-context.d.ts.map +1 -0
  734. package/dist/theme/theme-context.js +60 -0
  735. package/dist/theme/theme-context.js.map +1 -0
  736. package/dist/theme/theme-script.d.ts +27 -0
  737. package/dist/theme/theme-script.d.ts.map +1 -0
  738. package/dist/theme/theme-script.js +147 -0
  739. package/dist/theme/theme-script.js.map +1 -0
  740. package/dist/theme/types.d.ts +163 -0
  741. package/dist/theme/types.d.ts.map +1 -0
  742. package/dist/theme/types.js +11 -0
  743. package/dist/theme/types.js.map +1 -0
  744. package/dist/theme/use-theme.d.ts +12 -0
  745. package/dist/theme/use-theme.d.ts.map +1 -0
  746. package/dist/theme/use-theme.js +40 -0
  747. package/dist/theme/use-theme.js.map +1 -0
  748. package/dist/types.d.ts +1479 -0
  749. package/dist/types.d.ts.map +1 -0
  750. package/dist/types.js +10 -0
  751. package/dist/types.js.map +1 -0
  752. package/dist/urls.d.ts +441 -0
  753. package/dist/urls.d.ts.map +1 -0
  754. package/dist/urls.gen.d.ts +8 -0
  755. package/dist/urls.gen.d.ts.map +1 -0
  756. package/dist/urls.gen.js +8 -0
  757. package/dist/urls.gen.js.map +1 -0
  758. package/dist/urls.js +443 -0
  759. package/dist/urls.js.map +1 -0
  760. package/dist/use-loader.d.ts +127 -0
  761. package/dist/use-loader.d.ts.map +1 -0
  762. package/dist/use-loader.js +237 -0
  763. package/dist/use-loader.js.map +1 -0
  764. package/dist/vite/__tests__/ast-handler-extract.test.d.ts +2 -0
  765. package/dist/vite/__tests__/ast-handler-extract.test.d.ts.map +1 -0
  766. package/dist/vite/__tests__/ast-handler-extract.test.js +294 -0
  767. package/dist/vite/__tests__/ast-handler-extract.test.js.map +1 -0
  768. package/dist/vite/__tests__/expose-id-utils.test.d.ts +2 -0
  769. package/dist/vite/__tests__/expose-id-utils.test.d.ts.map +1 -0
  770. package/dist/vite/__tests__/expose-id-utils.test.js +224 -0
  771. package/dist/vite/__tests__/expose-id-utils.test.js.map +1 -0
  772. package/dist/vite/__tests__/expose-internal-ids.test.d.ts +2 -0
  773. package/dist/vite/__tests__/expose-internal-ids.test.d.ts.map +1 -0
  774. package/dist/vite/__tests__/expose-internal-ids.test.js +647 -0
  775. package/dist/vite/__tests__/expose-internal-ids.test.js.map +1 -0
  776. package/dist/vite/__tests__/expose-router-id.test.d.ts +2 -0
  777. package/dist/vite/__tests__/expose-router-id.test.d.ts.map +1 -0
  778. package/dist/vite/__tests__/expose-router-id.test.js +39 -0
  779. package/dist/vite/__tests__/expose-router-id.test.js.map +1 -0
  780. package/dist/vite/ast-handler-extract.d.ts +49 -0
  781. package/dist/vite/ast-handler-extract.d.ts.map +1 -0
  782. package/dist/vite/ast-handler-extract.js +249 -0
  783. package/dist/vite/ast-handler-extract.js.map +1 -0
  784. package/dist/vite/expose-action-id.d.ts +19 -0
  785. package/dist/vite/expose-action-id.d.ts.map +1 -0
  786. package/dist/vite/expose-action-id.js +250 -0
  787. package/dist/vite/expose-action-id.js.map +1 -0
  788. package/dist/vite/expose-id-utils.d.ts +69 -0
  789. package/dist/vite/expose-id-utils.d.ts.map +1 -0
  790. package/dist/vite/expose-id-utils.js +289 -0
  791. package/dist/vite/expose-id-utils.js.map +1 -0
  792. package/dist/vite/expose-internal-ids.d.ts +22 -0
  793. package/dist/vite/expose-internal-ids.d.ts.map +1 -0
  794. package/dist/vite/expose-internal-ids.js +886 -0
  795. package/dist/vite/expose-internal-ids.js.map +1 -0
  796. package/dist/vite/index.d.ts +149 -0
  797. package/dist/vite/index.d.ts.map +1 -0
  798. package/dist/vite/index.js +7554 -2070
  799. package/dist/vite/index.js.bak +5448 -0
  800. package/dist/vite/index.js.map +1 -0
  801. package/dist/vite/package-resolution.d.ts +43 -0
  802. package/dist/vite/package-resolution.d.ts.map +1 -0
  803. package/{src/vite/package-resolution.ts → dist/vite/package-resolution.js} +53 -66
  804. package/dist/vite/package-resolution.js.map +1 -0
  805. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  806. package/dist/vite/virtual-entries.d.ts +25 -0
  807. package/dist/vite/virtual-entries.d.ts.map +1 -0
  808. package/{src/vite/virtual-entries.ts → dist/vite/virtual-entries.js} +12 -16
  809. package/dist/vite/virtual-entries.js.map +1 -0
  810. package/package.json +142 -61
  811. package/skills/api-client/SKILL.md +211 -0
  812. package/skills/breadcrumbs/SKILL.md +329 -0
  813. package/skills/bundle-analysis/SKILL.md +159 -0
  814. package/skills/cache-guide/SKILL.md +489 -0
  815. package/skills/caching/SKILL.md +413 -26
  816. package/skills/catalog.json +271 -0
  817. package/skills/comparison/SKILL.md +50 -0
  818. package/skills/comparison/agents/openai.yaml +4 -0
  819. package/skills/comparison/references/framework-comparison.md +837 -0
  820. package/skills/composability/SKILL.md +278 -0
  821. package/skills/css/SKILL.md +76 -0
  822. package/skills/debug-manifest/SKILL.md +16 -18
  823. package/skills/defer-hydration/SKILL.md +235 -0
  824. package/skills/document-cache/SKILL.md +96 -63
  825. package/skills/fonts/SKILL.md +7 -5
  826. package/skills/handler-use/SKILL.md +364 -0
  827. package/skills/hooks/SKILL.md +76 -436
  828. package/skills/hooks/data.md +273 -0
  829. package/skills/hooks/handle-and-actions.md +103 -0
  830. package/skills/hooks/navigation.md +110 -0
  831. package/skills/hooks/outlets.md +41 -0
  832. package/skills/hooks/state.md +228 -0
  833. package/skills/hooks/urls.md +135 -0
  834. package/skills/host-router/SKILL.md +320 -0
  835. package/skills/i18n/SKILL.md +276 -0
  836. package/skills/intercept/SKILL.md +215 -16
  837. package/skills/layout/SKILL.md +147 -7
  838. package/skills/links/SKILL.md +304 -25
  839. package/skills/loader/SKILL.md +629 -55
  840. package/skills/middleware/SKILL.md +251 -38
  841. package/skills/migrate-nextjs/SKILL.md +745 -0
  842. package/skills/migrate-react-router/SKILL.md +153 -0
  843. package/skills/migrate-react-router/cloudflare-workers.md +129 -0
  844. package/skills/migrate-react-router/component-migration.md +196 -0
  845. package/skills/migrate-react-router/data-and-actions.md +225 -0
  846. package/skills/migrate-react-router/route-mapping.md +271 -0
  847. package/skills/mime-routes/SKILL.md +60 -21
  848. package/skills/observability/SKILL.md +202 -0
  849. package/skills/parallel/SKILL.md +296 -4
  850. package/skills/ppr/SKILL.md +658 -0
  851. package/skills/prerender/SKILL.md +487 -52
  852. package/skills/rango/SKILL.md +324 -29
  853. package/skills/react-compiler/SKILL.md +168 -0
  854. package/skills/response-routes/SKILL.md +264 -122
  855. package/skills/route/SKILL.md +359 -22
  856. package/skills/router-setup/SKILL.md +247 -34
  857. package/skills/scripts/SKILL.md +179 -0
  858. package/skills/server-actions/SKILL.md +776 -0
  859. package/skills/shell-manifest/SKILL.md +185 -0
  860. package/skills/streams-and-websockets/SKILL.md +283 -0
  861. package/skills/tailwind/SKILL.md +28 -4
  862. package/skills/testing/SKILL.md +126 -222
  863. package/skills/testing/bindings.md +103 -0
  864. package/skills/testing/cache-prerender.md +127 -0
  865. package/skills/testing/client-components.md +124 -0
  866. package/skills/testing/e2e-parity.md +125 -0
  867. package/skills/testing/flight.md +91 -0
  868. package/skills/testing/handles.md +131 -0
  869. package/skills/testing/loader.md +128 -0
  870. package/skills/testing/middleware.md +99 -0
  871. package/skills/testing/render-handler.md +122 -0
  872. package/skills/testing/response-routes.md +95 -0
  873. package/skills/testing/reverse-and-types.md +85 -0
  874. package/skills/testing/server-actions.md +107 -0
  875. package/skills/testing/server-tree.md +128 -0
  876. package/skills/testing/setup.md +123 -0
  877. package/skills/theme/SKILL.md +10 -9
  878. package/skills/typesafety/SKILL.md +45 -496
  879. package/skills/typesafety/env-and-bindings.md +254 -0
  880. package/skills/typesafety/generated-files-and-cli.md +335 -0
  881. package/skills/typesafety/params-and-search.md +153 -0
  882. package/skills/typesafety/route-types.md +209 -0
  883. package/skills/use-cache/SKILL.md +383 -0
  884. package/skills/vercel/SKILL.md +128 -0
  885. package/skills/view-transitions/SKILL.md +337 -0
  886. package/src/__augment-tests__/augment.ts +81 -0
  887. package/src/__augment-tests__/augmented.check.ts +116 -0
  888. package/src/__internal.ts +77 -44
  889. package/src/bin/rango.ts +275 -29
  890. package/src/browser/action-coordinator.ts +114 -0
  891. package/src/browser/action-fence.ts +47 -0
  892. package/src/browser/app-shell.ts +39 -0
  893. package/src/browser/app-version.ts +14 -0
  894. package/src/browser/connection-warmup.ts +134 -0
  895. package/src/browser/cookie-name.ts +140 -0
  896. package/src/browser/event-controller.ts +348 -212
  897. package/src/browser/history-state.ts +101 -0
  898. package/src/browser/index.ts +3 -3
  899. package/src/browser/intercept-utils.ts +52 -0
  900. package/src/browser/invalidate-client-cache.ts +52 -0
  901. package/src/browser/link-interceptor.ts +24 -4
  902. package/src/browser/logging.ts +39 -0
  903. package/src/browser/merge-segment-loaders.ts +23 -13
  904. package/src/browser/navigation-bridge.ts +385 -576
  905. package/src/browser/navigation-client.ts +249 -75
  906. package/src/browser/navigation-store-handle.ts +38 -0
  907. package/src/browser/navigation-store.ts +230 -124
  908. package/src/browser/navigation-transaction.ts +247 -0
  909. package/src/browser/network-error-handler.ts +88 -0
  910. package/src/browser/partial-update.ts +432 -365
  911. package/src/browser/prefetch/cache.ts +381 -0
  912. package/src/browser/prefetch/fetch.ts +462 -0
  913. package/src/browser/prefetch/observer.ts +65 -0
  914. package/src/browser/prefetch/policy.ts +48 -0
  915. package/src/browser/prefetch/queue.ts +209 -0
  916. package/src/browser/prefetch/resource-ready.ts +77 -0
  917. package/src/browser/rango-state.ts +194 -0
  918. package/src/browser/react/Link.tsx +284 -67
  919. package/src/browser/react/NavigationProvider.tsx +267 -109
  920. package/src/browser/react/ScrollRestoration.tsx +10 -6
  921. package/src/browser/react/context.ts +11 -0
  922. package/src/browser/react/filter-segment-order.ts +70 -0
  923. package/src/browser/react/index.ts +0 -48
  924. package/src/browser/react/location-state-shared.ts +272 -60
  925. package/src/browser/react/location-state.ts +90 -20
  926. package/src/browser/react/mount-context.ts +6 -1
  927. package/src/browser/react/nonce-context.ts +23 -0
  928. package/src/browser/react/shallow-equal.ts +27 -0
  929. package/src/browser/react/use-action.ts +35 -66
  930. package/src/browser/react/use-handle.ts +39 -126
  931. package/src/browser/react/use-href.tsx +8 -1
  932. package/src/browser/react/use-link-status.ts +39 -13
  933. package/src/browser/react/use-navigation.ts +53 -69
  934. package/src/browser/react/use-params.ts +75 -0
  935. package/src/browser/react/use-pathname.ts +47 -0
  936. package/src/browser/react/use-reverse.ts +106 -0
  937. package/src/browser/react/use-router.ts +98 -0
  938. package/src/browser/react/use-search-params.ts +51 -0
  939. package/src/browser/react/use-segments.ts +72 -99
  940. package/src/browser/response-adapter.ts +164 -0
  941. package/src/browser/rsc-router.tsx +355 -73
  942. package/src/browser/scroll-restoration.ts +140 -50
  943. package/src/browser/segment-reconciler.ts +253 -0
  944. package/src/browser/segment-structure-assert.ts +17 -1
  945. package/src/browser/server-action-bridge.ts +668 -613
  946. package/src/browser/types.ts +235 -51
  947. package/src/browser/validate-redirect-origin.ts +56 -0
  948. package/src/build/collect-fallback-refs.ts +107 -0
  949. package/src/build/generate-manifest.ts +243 -183
  950. package/src/build/generate-route-types.ts +41 -848
  951. package/src/build/index.ts +12 -7
  952. package/src/build/prefix-tree-utils.ts +123 -0
  953. package/src/build/route-trie.ts +218 -80
  954. package/src/build/route-types/ast-helpers.ts +25 -0
  955. package/src/build/route-types/ast-route-extraction.ts +105 -0
  956. package/src/build/route-types/codegen.ts +113 -0
  957. package/src/build/route-types/include-resolution.ts +812 -0
  958. package/src/build/route-types/param-extraction.ts +51 -0
  959. package/src/build/route-types/per-module-writer.ts +144 -0
  960. package/src/build/route-types/router-processing.ts +695 -0
  961. package/src/build/route-types/scan-filter.ts +85 -0
  962. package/src/build/route-types/source-scan.ts +216 -0
  963. package/src/build/runtime-discovery.ts +223 -0
  964. package/src/cache/background-task.ts +34 -0
  965. package/src/cache/cache-error.ts +104 -0
  966. package/src/cache/cache-key-utils.ts +88 -0
  967. package/src/cache/cache-policy.ts +199 -0
  968. package/src/cache/cache-runtime.ts +695 -0
  969. package/src/cache/cache-scope.ts +324 -335
  970. package/src/cache/cache-tag.ts +149 -0
  971. package/src/cache/cf/cf-base64.ts +33 -0
  972. package/src/cache/cf/cf-cache-constants.ts +127 -0
  973. package/src/cache/cf/cf-cache-store.ts +2602 -156
  974. package/src/cache/cf/cf-cache-types.ts +349 -0
  975. package/src/cache/cf/cf-kv-utils.ts +46 -0
  976. package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
  977. package/src/cache/cf/index.ts +17 -17
  978. package/src/cache/document-cache.ts +226 -102
  979. package/src/cache/handle-capture.ts +81 -0
  980. package/src/cache/handle-snapshot.ts +132 -0
  981. package/src/cache/index.ts +24 -35
  982. package/src/cache/memory-segment-store.ts +446 -30
  983. package/src/cache/profile-registry.ts +88 -0
  984. package/src/cache/read-through-swr.ts +178 -0
  985. package/src/cache/segment-codec.ts +295 -0
  986. package/src/cache/shell-snapshot.ts +464 -0
  987. package/src/cache/tag-invalidation.ts +230 -0
  988. package/src/cache/taint.ts +153 -0
  989. package/src/cache/types.ts +283 -211
  990. package/src/cache/vercel/index.ts +11 -0
  991. package/src/cache/vercel/vercel-cache-store.ts +1201 -0
  992. package/src/client.rsc.tsx +43 -21
  993. package/src/client.tsx +131 -347
  994. package/src/cloudflare/index.ts +11 -0
  995. package/src/cloudflare/tracing.ts +108 -0
  996. package/src/component-utils.ts +23 -4
  997. package/src/components/DefaultDocument.tsx +13 -3
  998. package/src/context-var.ts +168 -0
  999. package/src/debug.ts +19 -9
  1000. package/src/decode-loader-results.ts +52 -0
  1001. package/src/defer.ts +185 -0
  1002. package/src/deps/ssr.ts +4 -2
  1003. package/src/encode-kv.ts +49 -0
  1004. package/src/errors.ts +106 -10
  1005. package/src/escape-script.ts +52 -0
  1006. package/src/handle.ts +110 -35
  1007. package/src/handles/MetaTags.tsx +83 -59
  1008. package/src/handles/Scripts.tsx +183 -0
  1009. package/src/handles/breadcrumbs.ts +93 -0
  1010. package/src/handles/deferred-resolution.ts +127 -0
  1011. package/src/handles/is-thenable.ts +18 -0
  1012. package/src/handles/meta.ts +44 -53
  1013. package/src/handles/script.ts +244 -0
  1014. package/src/host/cookie-handler.ts +20 -65
  1015. package/src/host/errors.ts +21 -30
  1016. package/src/host/index.ts +13 -9
  1017. package/src/host/pattern-matcher.ts +50 -79
  1018. package/src/host/router.ts +151 -121
  1019. package/src/host/testing.ts +45 -32
  1020. package/src/host/types.ts +52 -11
  1021. package/src/host/utils.ts +2 -2
  1022. package/src/href-client.ts +192 -57
  1023. package/src/index.rsc.ts +180 -35
  1024. package/src/index.ts +245 -73
  1025. package/src/internal-debug.ts +9 -2
  1026. package/src/loader-store.ts +500 -0
  1027. package/src/loader.rsc.ts +31 -99
  1028. package/src/loader.ts +30 -12
  1029. package/src/missing-id-error.ts +68 -0
  1030. package/src/outlet-context.ts +1 -1
  1031. package/src/outlet-provider.tsx +41 -0
  1032. package/src/prerender/build-shell-capture.ts +447 -0
  1033. package/src/prerender/param-hash.ts +16 -14
  1034. package/src/prerender/shell-manifest-key.ts +20 -0
  1035. package/src/prerender/store.ts +131 -22
  1036. package/src/prerender.ts +460 -26
  1037. package/src/redirect-origin.ts +114 -0
  1038. package/src/regex-escape.ts +8 -0
  1039. package/src/render-error-thrower.tsx +20 -0
  1040. package/src/response-utils.ts +62 -0
  1041. package/src/reverse.ts +198 -128
  1042. package/src/root-error-boundary.tsx +42 -48
  1043. package/src/route-content-wrapper.tsx +22 -77
  1044. package/src/route-definition/dsl-helpers.ts +1116 -0
  1045. package/src/route-definition/helper-factories.ts +88 -0
  1046. package/src/route-definition/helpers-types.ts +506 -0
  1047. package/src/route-definition/index.ts +54 -0
  1048. package/src/route-definition/redirect.ts +134 -0
  1049. package/src/route-definition/resolve-handler-use.ts +160 -0
  1050. package/src/route-definition/use-item-types.ts +29 -0
  1051. package/src/route-definition.ts +1 -1481
  1052. package/src/route-map-builder.ts +120 -145
  1053. package/src/route-name.ts +53 -0
  1054. package/src/route-types.ts +71 -45
  1055. package/src/router/basename.ts +14 -0
  1056. package/src/router/content-negotiation.ts +305 -0
  1057. package/src/router/debug-manifest.ts +72 -0
  1058. package/src/router/error-handling.ts +54 -27
  1059. package/src/router/find-match.ts +259 -0
  1060. package/src/router/handler-context.ts +385 -125
  1061. package/src/router/instrument.ts +355 -0
  1062. package/src/router/intercept-resolution.ts +88 -28
  1063. package/src/router/lazy-includes.ts +260 -0
  1064. package/src/router/loader-resolution.ts +449 -157
  1065. package/src/router/logging.ts +106 -6
  1066. package/src/router/manifest.ts +144 -62
  1067. package/src/router/match-api.ts +224 -256
  1068. package/src/router/match-context.ts +4 -24
  1069. package/src/router/match-handlers.ts +499 -0
  1070. package/src/router/match-middleware/background-revalidation.ts +117 -93
  1071. package/src/router/match-middleware/cache-lookup.ts +308 -150
  1072. package/src/router/match-middleware/cache-store.ts +123 -51
  1073. package/src/router/match-middleware/intercept-resolution.ts +44 -43
  1074. package/src/router/match-middleware/segment-resolution.ts +64 -22
  1075. package/src/router/match-pipelines.ts +11 -87
  1076. package/src/router/match-result.ts +143 -52
  1077. package/src/router/metrics.ts +235 -29
  1078. package/src/router/middleware-types.ts +110 -0
  1079. package/src/router/middleware.ts +526 -441
  1080. package/src/router/navigation-snapshot.ts +133 -0
  1081. package/src/router/params-util.ts +23 -0
  1082. package/src/router/parse-pattern.ts +115 -0
  1083. package/src/router/pattern-matching.ts +311 -142
  1084. package/src/router/prefetch-cache-ttl.ts +51 -0
  1085. package/src/router/prefetch-limits.ts +37 -0
  1086. package/src/router/prerender-match.ts +575 -0
  1087. package/src/router/preview-match.ts +102 -0
  1088. package/src/router/request-classification.ts +291 -0
  1089. package/src/router/revalidation.ts +203 -62
  1090. package/src/router/route-snapshot.ts +256 -0
  1091. package/src/router/router-context.ts +45 -48
  1092. package/src/router/router-interfaces.ts +564 -0
  1093. package/src/router/router-options.ts +784 -0
  1094. package/src/router/router-registry.ts +21 -0
  1095. package/src/router/segment-resolution/fresh.ts +812 -0
  1096. package/src/router/segment-resolution/helpers.ts +348 -0
  1097. package/src/router/segment-resolution/loader-cache.ts +315 -0
  1098. package/src/router/segment-resolution/loader-mask.ts +60 -0
  1099. package/src/router/segment-resolution/loader-snapshot.ts +259 -0
  1100. package/src/router/segment-resolution/mask-nested.ts +99 -0
  1101. package/src/router/segment-resolution/revalidation.ts +1340 -0
  1102. package/src/router/segment-resolution/static-store.ts +81 -0
  1103. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  1104. package/src/router/segment-resolution/view-transition-default.ts +56 -0
  1105. package/src/router/segment-resolution.ts +25 -1354
  1106. package/src/router/segment-wrappers.ts +292 -0
  1107. package/src/router/state-cookie-name.ts +33 -0
  1108. package/src/router/substitute-pattern-params.ts +75 -0
  1109. package/src/router/telemetry-otel.ts +259 -0
  1110. package/src/router/telemetry.ts +385 -0
  1111. package/src/router/timeout.ts +128 -0
  1112. package/src/router/tracing.ts +215 -0
  1113. package/src/router/trie-matching.ts +240 -61
  1114. package/src/router/types.ts +23 -70
  1115. package/src/router/url-params.ts +57 -0
  1116. package/src/router.ts +795 -2390
  1117. package/src/rsc/capture-queue.ts +67 -0
  1118. package/src/rsc/full-payload.ts +70 -0
  1119. package/src/rsc/handler-context.ts +46 -0
  1120. package/src/rsc/handler.ts +956 -1164
  1121. package/src/rsc/helpers.ts +736 -19
  1122. package/src/rsc/index.ts +2 -25
  1123. package/src/rsc/json-route-result.ts +38 -0
  1124. package/src/rsc/loader-fetch.ts +305 -0
  1125. package/src/rsc/manifest-init.ts +77 -0
  1126. package/src/rsc/nonce.ts +23 -0
  1127. package/src/rsc/origin-guard.ts +155 -0
  1128. package/src/rsc/progressive-enhancement.ts +541 -0
  1129. package/src/rsc/redirect-guard.ts +100 -0
  1130. package/src/rsc/response-cache-serve.ts +238 -0
  1131. package/src/rsc/response-error.ts +104 -0
  1132. package/src/rsc/response-route-handler.ts +257 -0
  1133. package/src/rsc/rsc-rendering.ts +856 -0
  1134. package/src/rsc/runtime-warnings.ts +55 -0
  1135. package/src/rsc/server-action.ts +525 -0
  1136. package/src/rsc/shell-build-manifest.ts +316 -0
  1137. package/src/rsc/shell-capture-constants.ts +27 -0
  1138. package/src/rsc/shell-capture.ts +1716 -0
  1139. package/src/rsc/shell-serve.ts +226 -0
  1140. package/src/rsc/ssr-setup.ts +176 -0
  1141. package/src/rsc/transition-gate.ts +89 -0
  1142. package/src/rsc/types.ts +95 -12
  1143. package/src/runtime-env.ts +18 -0
  1144. package/src/search-params.ts +99 -82
  1145. package/src/segment-content-promise.ts +67 -0
  1146. package/src/segment-fragments.ts +124 -0
  1147. package/src/segment-loader-promise.ts +167 -0
  1148. package/src/segment-system.tsx +464 -135
  1149. package/src/serialize.ts +243 -0
  1150. package/src/server/context.ts +640 -85
  1151. package/src/server/cookie-parse.ts +32 -0
  1152. package/src/server/cookie-store.ts +337 -0
  1153. package/src/server/fetchable-loader-store.ts +11 -6
  1154. package/src/server/handle-store.ts +123 -42
  1155. package/src/server/loader-registry.ts +51 -100
  1156. package/src/server/request-context.ts +1069 -152
  1157. package/src/server.ts +15 -8
  1158. package/src/ssr/index.tsx +661 -140
  1159. package/src/ssr/inject-rsc-eager.ts +167 -0
  1160. package/src/ssr/preinit-client-references.ts +106 -0
  1161. package/src/ssr/ssr-root.tsx +261 -0
  1162. package/src/static-handler.ts +45 -18
  1163. package/src/testing/cache-status.ts +162 -0
  1164. package/src/testing/collect-handle.ts +46 -0
  1165. package/src/testing/dispatch.ts +813 -0
  1166. package/src/testing/dom.entry.ts +22 -0
  1167. package/src/testing/e2e/fixture.ts +188 -0
  1168. package/src/testing/e2e/index.ts +128 -0
  1169. package/src/testing/e2e/matchers.ts +35 -0
  1170. package/src/testing/e2e/page-helpers.ts +272 -0
  1171. package/src/testing/e2e/parity.ts +387 -0
  1172. package/src/testing/e2e/server.ts +195 -0
  1173. package/src/testing/flight-matchers.ts +97 -0
  1174. package/src/testing/flight-normalize.ts +11 -0
  1175. package/src/testing/flight-runtime.d.ts +57 -0
  1176. package/src/testing/flight-tree.ts +682 -0
  1177. package/src/testing/flight.entry.ts +52 -0
  1178. package/src/testing/flight.ts +257 -0
  1179. package/src/testing/generated-routes.ts +199 -0
  1180. package/src/testing/index.ts +105 -0
  1181. package/src/testing/internal/context.ts +380 -0
  1182. package/src/testing/internal/flight-client-globals.ts +30 -0
  1183. package/src/testing/internal/seed-vars.ts +54 -0
  1184. package/src/testing/render-handler.ts +371 -0
  1185. package/src/testing/render-route.tsx +584 -0
  1186. package/src/testing/run-loader.ts +385 -0
  1187. package/src/testing/run-middleware.ts +219 -0
  1188. package/src/testing/run-transition-when.ts +164 -0
  1189. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  1190. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  1191. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  1192. package/src/testing/vitest-stubs/version.ts +5 -0
  1193. package/src/testing/vitest.ts +305 -0
  1194. package/src/theme/ThemeProvider.tsx +76 -98
  1195. package/src/theme/ThemeScript.tsx +12 -14
  1196. package/src/theme/constants.ts +57 -15
  1197. package/src/theme/index.ts +3 -20
  1198. package/src/theme/theme-context.ts +5 -35
  1199. package/src/theme/theme-script.ts +43 -39
  1200. package/src/theme/use-theme.ts +0 -3
  1201. package/src/types/boundaries.ts +123 -0
  1202. package/src/types/cache-types.ts +207 -0
  1203. package/src/types/error-types.ts +132 -0
  1204. package/src/types/global-namespace.ts +113 -0
  1205. package/src/types/handler-context.ts +850 -0
  1206. package/src/types/index.ts +81 -0
  1207. package/src/types/loader-types.ts +212 -0
  1208. package/src/types/request-scope.ts +112 -0
  1209. package/src/types/route-config.ts +138 -0
  1210. package/src/types/route-entry.ts +114 -0
  1211. package/src/types/segments.ts +271 -0
  1212. package/src/types.ts +1 -1795
  1213. package/src/urls/include-helper.ts +162 -0
  1214. package/src/urls/include-provider.ts +71 -0
  1215. package/src/urls/index.ts +44 -0
  1216. package/src/urls/path-helper-types.ts +418 -0
  1217. package/src/urls/path-helper.ts +280 -0
  1218. package/src/urls/pattern-types.ts +189 -0
  1219. package/src/urls/response-types.ts +109 -0
  1220. package/src/urls/type-extraction.ts +316 -0
  1221. package/src/urls/urls-function.ts +80 -0
  1222. package/src/urls.ts +1 -1352
  1223. package/src/use-loader.tsx +406 -141
  1224. package/src/vercel/index.ts +11 -0
  1225. package/src/vercel/tracing.ts +88 -0
  1226. package/src/vite/debug.ts +185 -0
  1227. package/src/vite/discovery/bundle-postprocess.ts +182 -0
  1228. package/src/vite/discovery/dev-prerender-cache.ts +117 -0
  1229. package/src/vite/discovery/discover-routers.ts +408 -0
  1230. package/src/vite/discovery/discovery-errors.ts +255 -0
  1231. package/src/vite/discovery/gate-state.ts +171 -0
  1232. package/src/vite/discovery/prerender-collection.ts +483 -0
  1233. package/src/vite/discovery/route-types-writer.ts +214 -0
  1234. package/src/vite/discovery/self-gen-tracking.ts +73 -0
  1235. package/src/vite/discovery/shell-prerender-phase.ts +397 -0
  1236. package/src/vite/discovery/state.ts +205 -0
  1237. package/src/vite/discovery/virtual-module-codegen.ts +293 -0
  1238. package/src/vite/index.ts +31 -2255
  1239. package/src/vite/inject-client-debug.ts +88 -0
  1240. package/src/vite/plugin-types.ts +336 -0
  1241. package/src/vite/plugins/cjs-to-esm.ts +90 -0
  1242. package/src/vite/plugins/client-ref-dedup.ts +120 -0
  1243. package/src/vite/plugins/client-ref-hashing.ts +118 -0
  1244. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  1245. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  1246. package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
  1247. package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +88 -110
  1248. package/src/vite/{expose-id-utils.ts → plugins/expose-id-utils.ts} +89 -79
  1249. package/src/vite/plugins/expose-ids/export-analysis.ts +363 -0
  1250. package/src/vite/plugins/expose-ids/handler-transform.ts +130 -0
  1251. package/src/vite/plugins/expose-ids/loader-transform.ts +64 -0
  1252. package/src/vite/plugins/expose-ids/router-transform.ts +199 -0
  1253. package/src/vite/plugins/expose-ids/types.ts +45 -0
  1254. package/src/vite/plugins/expose-internal-ids.ts +805 -0
  1255. package/src/vite/plugins/performance-tracks.ts +89 -0
  1256. package/src/vite/plugins/refresh-cmd.ts +127 -0
  1257. package/src/vite/plugins/use-cache-transform.ts +313 -0
  1258. package/src/vite/plugins/vercel-output.ts +384 -0
  1259. package/src/vite/plugins/version-injector.ts +94 -0
  1260. package/src/vite/plugins/version-plugin.ts +271 -0
  1261. package/src/vite/plugins/virtual-entries.ts +267 -0
  1262. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  1263. package/src/vite/rango.ts +600 -0
  1264. package/src/vite/router-discovery.ts +2120 -0
  1265. package/src/vite/{ast-handler-extract.ts → utils/ast-handler-extract.ts} +200 -37
  1266. package/src/vite/utils/banner.ts +36 -0
  1267. package/src/vite/utils/bundle-analysis.ts +132 -0
  1268. package/src/vite/utils/client-chunks.ts +184 -0
  1269. package/src/vite/utils/directive-prologue.ts +40 -0
  1270. package/src/vite/utils/forward-user-plugins.ts +171 -0
  1271. package/src/vite/utils/manifest-utils.ts +15 -0
  1272. package/src/vite/utils/package-resolution.ts +89 -0
  1273. package/src/vite/utils/prerender-utils.ts +268 -0
  1274. package/src/vite/utils/shared-utils.ts +271 -0
  1275. package/CLAUDE.md +0 -43
  1276. package/src/browser/lru-cache.ts +0 -69
  1277. package/src/browser/request-controller.ts +0 -164
  1278. package/src/browser/shallow.ts +0 -35
  1279. package/src/cache/memory-store.ts +0 -253
  1280. package/src/router.gen.ts +0 -6
  1281. package/src/static-handler.gen.ts +0 -5
  1282. package/src/urls.gen.ts +0 -8
  1283. package/src/vite/expose-internal-ids.ts +0 -1167
  1284. /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
package/src/router.ts CHANGED
@@ -1,70 +1,43 @@
1
- import type { ComponentType } from "react";
2
1
  import { type ReactNode } from "react";
3
2
  import { createCacheScope } from "./cache/cache-scope.js";
4
- import type { SegmentCacheStore } from "./cache/types.js";
3
+ import { resolveCacheProfiles } from "./cache/profile-registry.js";
4
+ import { isCachedFunction } from "./cache/taint.js";
5
5
  import { assertClientComponent } from "./component-utils.js";
6
6
  import { DefaultDocument } from "./components/DefaultDocument.js";
7
- import {
8
- sanitizeError,
9
- } from "./errors";
10
- import { serializeManifest, type SerializedManifest } from "./debug.js";
11
- import {
12
- createReverse,
13
- type ReverseFunction,
14
- type PrefixRoutePatterns,
15
- } from "./reverse.js";
7
+ import type { SerializedManifest } from "./debug.js";
8
+ import { createReverse, type ReverseFunction } from "./reverse.js";
16
9
  import {
17
10
  registerRouteMap,
18
- getGlobalRouteMap,
19
11
  getPrecomputedEntries,
20
- getRouteTrie,
21
12
  getRouterManifest,
22
- getRouterTrie,
23
13
  getRouterPrecomputedEntries,
24
14
  ensureRouterManifest,
25
15
  } from "./route-map-builder.js";
26
- import { tryTrieMatch } from "./router/trie-matching.js";
27
- import {
28
- createRouteHelpers,
29
- type RouteHandlers,
30
- } from "./route-definition.js";
31
16
  import MapRootLayout from "./server/root-layout.js";
32
- import type { AllUseItems, IncludeItem } from "./route-types.js";
17
+ import type { AllUseItems } from "./route-types.js";
33
18
  import type { UrlPatterns } from "./urls.js";
19
+ import type { UrlBuilder } from "./urls/pattern-types.js";
20
+ import { urls } from "./urls.js";
21
+ import { buildPrecomputedByPrefix } from "./build/prefix-tree-utils.js";
34
22
  import {
35
- EntryData,
36
- InterceptEntry,
37
- InterceptSelectorContext,
23
+ type EntryData,
38
24
  getContext,
39
- RSCRouterContext,
40
- runWithPrefixes,
25
+ RangoContext,
41
26
  type MetricsStore,
42
27
  } from "./server/context";
43
28
  import { createHandleStore, type HandleStore } from "./server/handle-store.js";
44
- import { getRequestContext } from "./server/request-context.js";
29
+ import {
30
+ getRequestContext,
31
+ _getRequestContext,
32
+ } from "./server/request-context.js";
45
33
  import type {
46
- ErrorBoundaryHandler,
47
- ErrorInfo,
48
34
  ErrorPhase,
49
35
  HandlerContext,
50
36
  LoaderDataResult,
51
- MatchResult,
52
- NotFoundBoundaryHandler,
53
- OnErrorCallback,
54
37
  ResolvedRouteMap,
55
- RouteDefinition,
56
38
  RouteEntry,
57
39
  TrailingSlashMode,
58
40
  } from "./types";
59
- import type {
60
- NonceProvider,
61
- } from "./rsc/types.js";
62
- import {
63
- runWithRequestContext,
64
- type RequestContext,
65
- type ExecutionContext,
66
- } from "./server/request-context.js";
67
- import type { SerializedSegmentData, SegmentHandleData } from "./cache/types.js";
68
41
 
69
42
  // Extracted router utilities
70
43
  import {
@@ -74,1072 +47,98 @@ import {
74
47
  invokeOnError,
75
48
  } from "./router/error-handling.js";
76
49
 
77
- // Extracted segment resolution functions
78
- import {
79
- resolveAllSegments as _resolveAllSegments,
80
- resolveLoadersOnly as _resolveLoadersOnly,
81
- resolveLoadersOnlyWithRevalidation as _resolveLoadersOnlyWithRevalidation,
82
- buildEntryRevalidateMap as _buildEntryRevalidateMap,
83
- resolveAllSegmentsWithRevalidation as _resolveAllSegmentsWithRevalidation,
84
- } from "./router/segment-resolution.js";
85
-
86
- // Extracted intercept resolution functions
87
- import {
88
- findInterceptForRoute as _findInterceptForRoute,
89
- resolveInterceptEntry as _resolveInterceptEntry,
90
- resolveInterceptLoadersOnly as _resolveInterceptLoadersOnly,
91
- } from "./router/intercept-resolution.js";
92
-
93
- // Extracted match API functions
94
- import {
95
- createMatchContextForFull as _createMatchContextForFull,
96
- createMatchContextForPartial as _createMatchContextForPartial,
97
- matchError as _matchError,
98
- } from "./router/match-api.js";
50
+ // Extracted module factories
51
+ import { createSegmentWrappers } from "./router/segment-wrappers.js";
52
+ import { createMatchHandlers } from "./router/match-handlers.js";
53
+ import { buildDebugManifest } from "./router/debug-manifest.js";
99
54
 
100
55
  import type { SegmentResolutionDeps, MatchApiDeps } from "./router/types.js";
101
56
  import { createHandlerContext } from "./router/handler-context.js";
57
+ import { normalizeBasename } from "./router/basename.js";
102
58
  import {
103
59
  setupLoaderAccess,
104
60
  setupLoaderAccessSilent,
105
- setupBuildUse,
106
61
  wrapLoaderWithErrorHandling,
107
62
  } from "./router/loader-resolution.js";
108
63
  import { loadManifest } from "./router/manifest.js";
64
+ import { createMetricsStore } from "./router/metrics.js";
109
65
  import {
110
- createMetricsStore,
111
- } from "./router/metrics.js";
112
- import {
113
- collectRouteMiddleware,
114
- parsePattern,
66
+ compileMiddlewarePattern,
115
67
  type MiddlewareEntry,
116
68
  type MiddlewareFn,
117
69
  } from "./router/middleware.js";
118
70
  import {
119
71
  extractStaticPrefix,
120
- findMatch as findRouteMatch,
121
- isLazyEvaluationNeeded,
72
+ joinPrefix,
122
73
  traverseBack,
123
- type RouteMatchResult,
124
74
  } from "./router/pattern-matching.js";
75
+ import { resolveSink, safeEmit, getRequestId } from "./router/telemetry.js";
76
+ import { resolveTracing } from "./router/tracing.js";
125
77
  import { evaluateRevalidation } from "./router/revalidation.js";
126
78
  import {
127
79
  type RouterContext,
128
80
  runWithRouterContext,
129
81
  } from "./router/router-context.js";
130
- import {
131
- type ActionContext,
132
- type MatchContext,
133
- createPipelineState,
134
- } from "./router/match-context.js";
135
- import { createMatchPartialPipeline } from "./router/match-pipelines.js";
136
- import { collectMatchResult } from "./router/match-result.js";
137
- import {
138
- runWithRouterLogContext,
139
- withRouterLogScope,
140
- } from "./router/logging.js";
141
82
  import { resolveThemeConfig } from "./theme/constants.js";
83
+ import { resolveTimeouts } from "./router/timeout.js";
142
84
 
143
- // Response type -> MIME type used for Accept header matching
144
- const RESPONSE_TYPE_MIME: Record<string, string> = {
145
- json: "application/json",
146
- text: "text/plain",
147
- xml: "application/xml",
148
- html: "text/html",
149
- md: "text/markdown",
150
- };
151
-
152
- // Reverse lookup: MIME type -> response type tag (e.g. "text/html" -> "html")
153
- const MIME_RESPONSE_TYPE: Record<string, string> = Object.fromEntries(
154
- Object.entries(RESPONSE_TYPE_MIME).map(([tag, mime]) => [mime, tag]),
155
- );
156
-
157
- interface AcceptEntry {
158
- mime: string;
159
- q: number;
160
- order: number;
161
- }
162
-
163
- /**
164
- * Parse an Accept header into a sorted array of MIME entries.
165
- * Respects q-values (default 1.0) and uses client order as tiebreaker
166
- * when q-values are equal (matching Express/Hono behavior).
167
- */
168
- function parseAcceptTypes(accept: string): AcceptEntry[] {
169
- const entries: AcceptEntry[] = [];
170
- const parts = accept.split(",");
171
- for (let i = 0; i < parts.length; i++) {
172
- const part = parts[i]!;
173
- const segments = part.split(";");
174
- const mime = segments[0]!.trim();
175
- if (!mime) continue;
176
- let q = 1.0;
177
- for (let j = 1; j < segments.length; j++) {
178
- const param = segments[j]!.trim();
179
- if (param.startsWith("q=")) {
180
- q = Math.max(0, Math.min(1, Number(param.slice(2)) || 0));
181
- }
182
- }
183
- entries.push({ mime, q, order: i });
184
- }
185
- // Sort: highest q first, then lowest client order first (stable)
186
- entries.sort((a, b) => b.q - a.q || a.order - b.order);
187
- return entries;
188
- }
189
-
190
- // Sentinel response type for RSC routes in negotiation candidates
191
- const RSC_RESPONSE_TYPE = "__rsc__";
192
-
193
- /**
194
- * Pick the best negotiate variant by walking the client's sorted Accept list.
195
- * For each accepted MIME type (in q-value/order priority), check if any
196
- * candidate serves that type. Wildcards (*\/*) match the first candidate.
197
- * Falls back to the first candidate if nothing matches.
198
- */
199
- function pickNegotiateVariant(
200
- acceptEntries: AcceptEntry[],
201
- candidates: Array<{ routeKey: string; responseType: string }>,
202
- ): { routeKey: string; responseType: string } {
203
- // Build a MIME -> candidate lookup for O(1) matching
204
- const byCandidateMime = new Map<string, { routeKey: string; responseType: string }>();
205
- for (const c of candidates) {
206
- const mime = c.responseType === RSC_RESPONSE_TYPE ? "text/html" : RESPONSE_TYPE_MIME[c.responseType];
207
- if (mime && !byCandidateMime.has(mime)) {
208
- byCandidateMime.set(mime, c);
209
- }
210
- }
211
-
212
- for (const entry of acceptEntries) {
213
- if (entry.q === 0) continue;
214
- // Wildcard matches first candidate
215
- if (entry.mime === "*/*") return candidates[0]!;
216
- // Type wildcard (e.g. "text/*") — match first candidate with that type
217
- if (entry.mime.endsWith("/*")) {
218
- const typePrefix = entry.mime.slice(0, entry.mime.indexOf("/"));
219
- for (const [mime, candidate] of byCandidateMime) {
220
- if (mime.startsWith(typePrefix + "/")) return candidate;
221
- }
222
- continue;
223
- }
224
- const match = byCandidateMime.get(entry.mime);
225
- if (match) return match;
226
- }
227
- // No match — use first candidate as default
228
- return candidates[0]!;
229
- }
230
-
231
- /**
232
- * Props passed to the root layout component
233
- */
234
- export interface RootLayoutProps {
235
- children: ReactNode;
236
- }
237
-
238
- /**
239
- * Router configuration options
240
- */
241
- /**
242
- * Brand marker for identifying router instances at build time.
243
- * Used by the Vite plugin to auto-discover routers from module exports.
244
- */
245
- export const RSC_ROUTER_BRAND: "__rsc_router__" = "__rsc_router__";
246
-
247
- /**
248
- * Global registry of all router instances created via createRouter().
249
- * Each router is keyed by its id (auto-generated or user-provided).
250
- * Used by the Vite plugin at build time to discover routers and extract
251
- * manifests, prefix trees, and pre-render candidates.
252
- */
253
- export const RouterRegistry: Map<string, RSCRouter<any, any>> = new Map();
254
-
255
- let routerAutoId = 0;
256
-
257
- export interface RSCRouterOptions<TEnv = any> {
258
- /**
259
- * Unique identifier for this router instance.
260
- * Used to namespace static output files and route maps.
261
- * Auto-generated if not provided.
262
- */
263
- id?: string;
264
-
265
- /**
266
- * Injected by the Vite transform at compile time.
267
- * Hash of filename + line number for stable cross-environment ID.
268
- * @internal
269
- */
270
- $$id?: string;
271
-
272
- /**
273
- * Enable performance metrics collection
274
- * When enabled, metrics are output to console and available via Server-Timing header
275
- */
276
- debugPerformance?: boolean;
277
-
278
- /**
279
- * Allow the `?__debug_manifest` query parameter to return route manifest data as JSON.
280
- * In development mode this is always enabled regardless of this setting.
281
- * Defaults to true. Set to false to disable in production.
282
- * @internal
283
- */
284
- allowDebugManifest?: boolean;
285
-
286
- /**
287
- * Document component that wraps the entire application.
288
- *
289
- * This component provides the HTML structure for your app and wraps
290
- * both normal route content AND error states, preventing the app shell
291
- * from unmounting during errors (avoids FOUC).
292
- *
293
- * Must be a client component ("use client") that accepts { children }.
294
- *
295
- * If not provided, a default document with basic HTML structure is used:
296
- * `<html><head><meta charset/viewport></head><body>{children}</body></html>`
297
- *
298
- * @example
299
- * ```typescript
300
- * // components/Document.tsx
301
- * "use client";
302
- * export function Document({ children }: { children: ReactNode }) {
303
- * return (
304
- * <html lang="en">
305
- * <head>
306
- * <link rel="stylesheet" href="/styles.css" />
307
- * </head>
308
- * <body>
309
- * <nav>...</nav>
310
- * {children}
311
- * </body>
312
- * </html>
313
- * );
314
- * }
315
- *
316
- * // router.tsx
317
- * const router = createRouter<AppEnv>({
318
- * document: Document,
319
- * });
320
- * ```
321
- */
322
- document?: ComponentType<RootLayoutProps>;
323
-
324
- /**
325
- * Default error boundary fallback used when no error boundary is defined in the route tree
326
- * If not provided, errors will propagate and crash the request
327
- */
328
- defaultErrorBoundary?: ReactNode | ErrorBoundaryHandler;
329
-
330
- /**
331
- * Default not-found boundary fallback used when no notFoundBoundary is defined in the route tree
332
- * If not provided, DataNotFoundError will be treated as a regular error
333
- */
334
- defaultNotFoundBoundary?: ReactNode | NotFoundBoundaryHandler;
335
-
336
- /**
337
- * Component to render when no route matches the requested URL.
338
- *
339
- * This is rendered within your document/app shell with a 404 status code.
340
- * Use this for a custom 404 page that maintains your app's look and feel.
341
- *
342
- * If not provided, a default "Page not found" component is rendered.
343
- *
344
- * Can be a static ReactNode or a function receiving the pathname.
345
- *
346
- * @example
347
- * ```typescript
348
- * // Simple static component
349
- * const router = createRouter<AppEnv>({
350
- * document: Document,
351
- * notFound: <NotFound404 />,
352
- * });
353
- *
354
- * // Dynamic component with pathname
355
- * const router = createRouter<AppEnv>({
356
- * document: Document,
357
- * notFound: ({ pathname }) => (
358
- * <div>
359
- * <h1>404 - Not Found</h1>
360
- * <p>No page exists at {pathname}</p>
361
- * <a href="/">Go home</a>
362
- * </div>
363
- * ),
364
- * });
365
- * ```
366
- */
367
- notFound?: ReactNode | ((props: { pathname: string }) => ReactNode);
368
-
369
- /**
370
- * Callback invoked when an error occurs during request handling.
371
- *
372
- * This callback is for notification/logging purposes - it cannot modify
373
- * the error handling flow. Use errorBoundary() in route definitions to
374
- * customize error UI.
375
- *
376
- * The callback receives comprehensive context about the error including:
377
- * - The error itself
378
- * - Phase where it occurred (routing, middleware, loader, handler, etc.)
379
- * - Request info (URL, method, params)
380
- * - Route info (routeKey, segmentId)
381
- * - Environment/bindings
382
- * - Duration from request start
383
- *
384
- * @example
385
- * ```typescript
386
- * const router = createRouter<AppEnv>({
387
- * onError: (context) => {
388
- * // Send to error tracking service
389
- * Sentry.captureException(context.error, {
390
- * tags: {
391
- * phase: context.phase,
392
- * route: context.routeKey,
393
- * },
394
- * extra: {
395
- * url: context.url.toString(),
396
- * params: context.params,
397
- * duration: context.duration,
398
- * },
399
- * });
400
- * },
401
- * });
402
- * ```
403
- */
404
- onError?: OnErrorCallback<TEnv>;
405
-
406
- /**
407
- * Cache store for segment caching.
408
- *
409
- * When provided, enables route-level caching via cache() boundaries.
410
- * The store handles persistence (memory, KV, Redis, etc.).
411
- *
412
- * Can be a static config or a function receiving env for runtime bindings.
413
- *
414
- * @example Static config
415
- * ```typescript
416
- * import { MemorySegmentCacheStore } from "rsc-router/rsc";
417
- *
418
- * const router = createRouter({
419
- * cache: {
420
- * store: new MemorySegmentCacheStore({ defaults: { ttl: 60 } }),
421
- * },
422
- * });
423
- * ```
424
- *
425
- * @example Dynamic config with env (e.g., Cloudflare Workers with ExecutionContext)
426
- * ```typescript
427
- * const router = createRouter<AppEnv>({
428
- * cache: (env) => ({
429
- * store: new CFCacheStore({
430
- * defaults: { ttl: 60 },
431
- * ctx: env.ctx, // ExecutionContext for non-blocking writes
432
- * }),
433
- * }),
434
- * });
435
- * ```
436
- */
437
- cache?:
438
- | { store: SegmentCacheStore; enabled?: boolean }
439
- | ((env: TEnv & { ctx?: ExecutionContext }) => {
440
- store: SegmentCacheStore;
441
- enabled?: boolean;
442
- });
443
-
444
- /**
445
- * Theme configuration for automatic theme management.
446
- *
447
- * When provided, enables:
448
- * - ctx.theme and ctx.setTheme() in route handlers
449
- * - useTheme() hook for client components
450
- * - FOUC prevention via inline script in MetaTags
451
- * - Automatic ThemeProvider wrapping in NavigationProvider
452
- *
453
- * @example
454
- * ```typescript
455
- * const router = createRouter<AppEnv>({
456
- * theme: {
457
- * defaultTheme: "system",
458
- * themes: ["light", "dark"],
459
- * }
460
- * });
461
- *
462
- * // In route handler:
463
- * route("settings", (ctx) => {
464
- * const theme = ctx.theme; // "light" | "dark" | "system"
465
- * ctx.setTheme("dark"); // Sets cookie
466
- * return <SettingsPage />;
467
- * });
468
- *
469
- * // In client component:
470
- * import { useTheme } from "@rangojs/router/theme";
471
- *
472
- * function ThemeToggle() {
473
- * const { theme, setTheme, themes } = useTheme();
474
- * return <select value={theme} onChange={e => setTheme(e.target.value)}>
475
- * {themes.map(t => <option key={t}>{t}</option>)}
476
- * </select>;
477
- * }
478
- * ```
479
- *
480
- * Use `theme: true` to enable with all defaults.
481
- */
482
- theme?: import("./theme/types.js").ThemeConfig | true;
483
-
484
- /**
485
- * URL patterns to register with the router.
486
- *
487
- * Alternative to calling `.routes()` method - allows passing patterns
488
- * directly in the config for a more concise setup.
489
- *
490
- * @example
491
- * ```typescript
492
- * import { urls } from "@rangojs/router/server";
493
- *
494
- * const urlpatterns = urls(({ path, layout }) => [
495
- * path("/", HomePage, { name: "home" }),
496
- * path("/about", AboutPage, { name: "about" }),
497
- * ]);
498
- *
499
- * const router = createRouter<AppEnv>({
500
- * document: Document,
501
- * urls: urlpatterns,
502
- * });
503
- * ```
504
- */
505
- urls?: UrlPatterns<TEnv, any>;
506
-
507
- /**
508
- * Injected by the Vite transform at compile time.
509
- * Static import of NamedRoutes from the generated named-routes file.
510
- * Provides O(1) reverse() fallback when lazy includes haven't resolved.
511
- * @internal
512
- */
513
- $$routeNames?: Record<string, string>;
85
+ // Extracted content negotiation utilities
86
+ import { flattenNamedRoutes } from "./router/content-negotiation.js";
514
87
 
515
- /**
516
- * Nonce provider for Content Security Policy (CSP).
517
- *
518
- * Can be:
519
- * - A function that returns a nonce string
520
- * - A function that returns `true` to auto-generate a nonce
521
- * - Undefined to disable nonce (default)
522
- *
523
- * The nonce will be applied to inline scripts injected by the RSC payload.
524
- * It's also available to middleware via `ctx.get('nonce')`.
525
- *
526
- * @example Auto-generate nonce
527
- * ```tsx
528
- * createRouter({
529
- * nonce: () => true,
530
- * });
531
- * ```
532
- *
533
- * @example Custom nonce from request context
534
- * ```tsx
535
- * createRouter({
536
- * nonce: (request, env) => env.nonce,
537
- * });
538
- * ```
539
- */
540
- nonce?: NonceProvider<TEnv>;
541
-
542
- /**
543
- * RSC version string included in metadata.
544
- * The browser sends this back on partial requests to detect version mismatches.
545
- *
546
- * Defaults to the auto-generated VERSION from `@rangojs/router:version` virtual module.
547
- * Only set this if you need a custom versioning strategy.
548
- *
549
- * @default VERSION from @rangojs/router:version
550
- */
551
- version?: string;
552
-
553
- /**
554
- * Enable connection warmup to keep TCP+TLS alive after idle periods.
555
- *
556
- * When enabled, the client sends a HEAD request after the user returns
557
- * from an idle period (60s+), prewarming the TLS connection before
558
- * the next navigation.
559
- *
560
- * @default true
561
- */
562
- warmup?: boolean;
563
- }
564
-
565
- /**
566
- * Merge route patterns with response types into a single route map.
567
- * Routes with response types get { path, response } objects; others stay as strings.
568
- * Handles both plain string routes and { path, search } object routes.
569
- */
570
- type MergeRoutesWithResponses<
571
- TRoutes extends Record<string, unknown>,
572
- TResponses,
573
- > = {
574
- [K in keyof TRoutes]: K extends keyof NonNullable<TResponses>
575
- ? unknown extends NonNullable<TResponses>[K]
576
- ? TRoutes[K] // RSC route — TData defaults to unknown, keep as-is
577
- : TRoutes[K] extends { readonly path: infer P extends string }
578
- ? TRoutes[K] & { readonly response: NonNullable<TResponses>[K] }
579
- : { readonly path: TRoutes[K] & string; readonly response: NonNullable<TResponses>[K] }
580
- : TRoutes[K]
581
- };
582
-
583
- /**
584
- * Extract the URL pattern from a route entry (string or { path, response } object)
585
- */
586
- type PatternOfEntry<V> =
587
- V extends string ? V
588
- : V extends { readonly path: infer P extends string } ? P
589
- : never;
590
-
591
- /**
592
- * Type-level detection of conflicting route keys.
593
- * Extracts keys that exist in both TExisting and TNew but with different URL patterns.
594
- * Returns `never` if no conflicts exist.
595
- * Compares patterns (not full entries) to handle both string and { path, response } values.
596
- *
597
- * @example
598
- * ```typescript
599
- * ConflictingKeys<{ a: "/a" }, { a: "/b" }> // "a" (conflict - same key, different URLs)
600
- * ConflictingKeys<{ a: "/a" }, { a: "/a" }> // never (no conflict - same key and URL)
601
- * ConflictingKeys<{ a: "/a" }, { b: "/b" }> // never (no conflict - different keys)
602
- * ```
603
- */
604
- type ConflictingKeys<
605
- TExisting extends Record<string, unknown>,
606
- TNew extends Record<string, unknown>,
607
- > = {
608
- [K in keyof TExisting & keyof TNew]: PatternOfEntry<TExisting[K]> extends PatternOfEntry<TNew[K]>
609
- ? PatternOfEntry<TNew[K]> extends PatternOfEntry<TExisting[K]>
610
- ? never // Same pattern, no conflict
611
- : K // Different patterns, conflict
612
- : K; // Different patterns, conflict
613
- }[keyof TExisting & keyof TNew];
614
-
615
- /**
616
- * Error type returned when route keys conflict.
617
- * Methods require an impossible `never` parameter so TypeScript errors at the call site.
618
- */
619
- type RouteConflictError<TConflicts extends string> = {
620
- __error: `Route key conflict! Key "${TConflicts}" already exists with a different URL pattern.`;
621
- hint: "Route keys must be globally unique. Use prefixed names like 'blog.index' instead of 'index'.";
622
- conflictingKeys: TConflicts;
623
- // These methods require `never` so calling them produces an error at the call site
624
- routes: (
625
- __conflict: `Fix route key conflict: "${TConflicts}" is already defined with a different URL pattern`,
626
- ) => never;
627
- map: (
628
- __conflict: `Fix route key conflict: "${TConflicts}" is already defined with a different URL pattern`,
629
- ) => never;
630
- };
631
-
632
- /**
633
- * Simplified route helpers for inline route definitions.
634
- * Uses TRoutes (Record<string, string>) instead of RouteDefinition.
635
- *
636
- * Note: Some helpers use `any` for context types as a trade-off for simpler usage.
637
- * The main type safety is in the `route` helper which enforces valid route names.
638
- * For full type safety, use the standard map() API with separate handler files.
639
- */
640
- type InlineRouteHelpers<TRoutes extends Record<string, string>, TEnv> = {
641
- /**
642
- * Define a route handler for a specific route pattern
643
- */
644
- route: <K extends keyof TRoutes & string>(
645
- name: K,
646
- handler:
647
- | ((ctx: HandlerContext<{}, TEnv>) => ReactNode | Promise<ReactNode>)
648
- | ReactNode,
649
- ) => AllUseItems;
650
-
651
- /**
652
- * Define a layout that wraps child routes
653
- */
654
- layout: (
655
- component:
656
- | ReactNode
657
- | ((ctx: HandlerContext<any, TEnv>) => ReactNode | Promise<ReactNode>),
658
- use?: () => AllUseItems[],
659
- ) => AllUseItems;
660
-
661
- /**
662
- * Define parallel routes
663
- */
664
- parallel: (
665
- slots: Record<
666
- `@${string}`,
667
- | ReactNode
668
- | ((ctx: HandlerContext<any, TEnv>) => ReactNode | Promise<ReactNode>)
669
- >,
670
- use?: () => AllUseItems[],
671
- ) => AllUseItems;
672
-
673
- /**
674
- * Define route middleware
675
- */
676
- middleware: (
677
- fn: (ctx: any, next: () => Promise<void>) => Promise<void>,
678
- ) => AllUseItems;
679
-
680
- /**
681
- * Define revalidation handlers
682
- */
683
- revalidate: (fn: (ctx: any) => boolean | Promise<boolean>) => AllUseItems;
684
-
685
- /**
686
- * Define data loaders
687
- */
688
- loader: (loader: any, use?: () => AllUseItems[]) => AllUseItems;
689
-
690
- /**
691
- * Define loading states
692
- */
693
- loading: (component: ReactNode) => AllUseItems;
694
-
695
- /**
696
- * Define error boundaries
697
- */
698
- errorBoundary: (
699
- handler: ReactNode | ((props: { error: Error }) => ReactNode),
700
- ) => AllUseItems;
701
-
702
- /**
703
- * Define not found boundaries
704
- */
705
- notFoundBoundary: (
706
- handler: ReactNode | ((props: { pathname: string }) => ReactNode),
707
- ) => AllUseItems;
708
-
709
- /**
710
- * Define intercept routes
711
- */
712
- intercept: (
713
- name: string,
714
- handler:
715
- | ReactNode
716
- | ((ctx: HandlerContext<any, TEnv>) => ReactNode | Promise<ReactNode>),
717
- use?: () => AllUseItems[],
718
- ) => AllUseItems;
719
-
720
- /**
721
- * Define when conditions for intercepts
722
- */
723
- when: (condition: (ctx: any) => boolean | Promise<boolean>) => AllUseItems;
724
-
725
- /**
726
- * Define cache configuration
727
- */
728
- cache: (
729
- config: { ttl?: number; swr?: number } | false,
730
- use?: () => AllUseItems[],
731
- ) => AllUseItems;
732
- };
733
-
734
- /**
735
- * Router builder for chaining .use() and .map()
736
- * TRoutes accumulates all registered route types through the chain
737
- * TLocalRoutes contains the routes for the current .routes() call (for inline handler typing)
738
- */
739
- interface RouteBuilder<
740
- T extends RouteDefinition,
741
- TEnv,
742
- TRoutes extends Record<string, unknown>,
743
- TLocalRoutes extends Record<string, string> = Record<string, string>,
744
- > {
745
- /**
746
- * Add middleware scoped to this mount
747
- * Called between .routes() and .map()
748
- *
749
- * @example
750
- * ```typescript
751
- * .routes("/admin", adminRoutes)
752
- * .use(authMiddleware) // All of /admin/*
753
- * .use("/danger/*", superAuth) // Only /admin/danger/*
754
- * .map(() => import("./admin"))
755
- * ```
756
- */
757
- use(
758
- patternOrMiddleware: string | MiddlewareFn<TEnv>,
759
- middleware?: MiddlewareFn<TEnv>,
760
- ): RouteBuilder<T, TEnv, TRoutes, TLocalRoutes>;
761
-
762
- /**
763
- * Map routes to handlers
764
- *
765
- * Supports two patterns:
766
- *
767
- * 1. Lazy loading (code-split):
768
- * ```typescript
769
- * .routes(homeRoutes)
770
- * .map(() => import("./handlers/home"))
771
- * ```
772
- *
773
- * 2. Inline definition:
774
- * ```typescript
775
- * .routes({ index: "/", about: "/about" })
776
- * .map(({ route }) => [
777
- * route("index", () => <HomePage />),
778
- * route("about", () => <AboutPage />),
779
- * ])
780
- * ```
781
- */
782
- // Inline definition overload - handler receives helpers (must be first for correct inference)
783
- // Uses TLocalRoutes so route names don't need the prefix
784
- map<
785
- H extends (
786
- helpers: InlineRouteHelpers<TLocalRoutes, TEnv>,
787
- ) => Array<AllUseItems>,
788
- >(
789
- handler: H,
790
- ): RSCRouter<TEnv, TRoutes>;
791
- // Lazy loading overload - verifies imported handlers match route definition
792
- map(
793
- handler: () =>
794
- | Array<AllUseItems>
795
- | Promise<{ default: RouteHandlers<TLocalRoutes> }>
796
- | Promise<RouteHandlers<TLocalRoutes>>,
797
- ): RSCRouter<TEnv, TRoutes>;
798
-
799
- /**
800
- * Accumulated route map for typeof extraction
801
- * Used for module augmentation: `type AppRoutes = typeof _router.routeMap`
802
- */
803
- readonly routeMap: TRoutes;
804
- }
805
-
806
- /**
807
- * RSC Router interface
808
- * TRoutes accumulates all registered route types through the builder chain
809
- */
810
- export interface RSCRouter<
811
- TEnv = any,
812
- TRoutes extends Record<string, unknown> = Record<string, string>,
813
- > {
814
- /**
815
- * Brand marker for build-time discovery.
816
- * The Vite plugin uses this to identify router instances in module exports.
817
- */
818
- readonly __brand: typeof RSC_ROUTER_BRAND;
819
-
820
- /**
821
- * Unique identifier for this router instance.
822
- * Used to namespace static output and isolate route maps between routers.
823
- */
824
- readonly id: string;
825
-
826
- /**
827
- * Register routes with a prefix
828
- * Route keys stay unchanged, only URL patterns get the prefix applied.
829
- * This enables composable route modules that work regardless of mount point.
830
- *
831
- * @throws Compile-time error if route keys conflict with previously registered routes
832
- */
833
- routes<const TPrefix extends string, const T extends Record<string, string>>(
834
- prefix: TPrefix,
835
- routes: T,
836
- ): ConflictingKeys<TRoutes, PrefixRoutePatterns<T, TPrefix>> extends never
837
- ? RouteBuilder<
838
- RouteDefinition,
839
- TEnv,
840
- TRoutes & PrefixRoutePatterns<T, TPrefix>,
841
- T
842
- >
843
- : RouteConflictError<
844
- ConflictingKeys<TRoutes, PrefixRoutePatterns<T, TPrefix>> & string
845
- >;
846
-
847
- /**
848
- * Register routes without a prefix
849
- * Route types are accumulated through the chain
850
- *
851
- * @throws Compile-time error if route keys conflict with previously registered routes
852
- */
853
- routes<const T extends Record<string, string>>(
854
- routes: T,
855
- ): ConflictingKeys<TRoutes, T> extends never
856
- ? RouteBuilder<RouteDefinition, TEnv, TRoutes & T, T>
857
- : RouteConflictError<ConflictingKeys<TRoutes, T> & string>;
858
-
859
- /**
860
- * Register routes using Django-style URL patterns
861
- * This is the new API for @rangojs/router - call once with urls() result
862
- *
863
- * @example
864
- * ```typescript
865
- * createRouter({})
866
- * .routes(urlpatterns) // Single call with urls() result
867
- * ```
868
- */
869
- routes<T extends UrlPatterns<TEnv, any>>(
870
- patterns: T,
871
- ): RSCRouter<
872
- TEnv,
873
- TRoutes &
874
- (NonNullable<T["_routes"]> extends Record<string, unknown>
875
- ? MergeRoutesWithResponses<NonNullable<T["_routes"]>, T["_responses"]>
876
- : Record<string, string>)
877
- >;
878
-
879
- /**
880
- * Add global middleware that runs on all routes
881
- * Position matters: middleware before any .routes() is global
882
- *
883
- * @example
884
- * ```typescript
885
- * createRouter({ document: RootLayout })
886
- * .use(loggerMiddleware) // All routes
887
- * .use("/api/*", rateLimiter) // Pattern match
888
- * .routes(homeRoutes)
889
- * .map(() => import("./home"))
890
- * ```
891
- */
892
- use(
893
- patternOrMiddleware: string | MiddlewareFn<TEnv>,
894
- middleware?: MiddlewareFn<TEnv>,
895
- ): RSCRouter<TEnv, TRoutes>;
896
-
897
- /**
898
- * Type-safe URL builder for registered routes
899
- * Types are inferred from the accumulated route registrations
900
- * Route keys stay unchanged regardless of mount prefix.
901
- *
902
- * @example
903
- * ```typescript
904
- * // Given: .routes("/shop", { cart: "/cart", detail: "/product/:slug" })
905
- * router.reverse("cart"); // "/shop/cart"
906
- * router.reverse("detail", { slug: "widget" }); // "/shop/product/widget"
907
- * ```
908
- */
909
- reverse: ReverseFunction<TRoutes>;
910
-
911
- /**
912
- * Accumulated route map for typeof extraction
913
- * Used for module augmentation: `type AppRoutes = typeof _router.routeMap`
914
- *
915
- * @example
916
- * ```typescript
917
- * const _router = createRouter<AppEnv>()
918
- * .routes(homeRoutes).map(() => import('./home'))
919
- * .routes('/shop', shopRoutes).map(() => import('./shop'));
920
- *
921
- * type AppRoutes = typeof _router.routeMap;
922
- *
923
- * declare global {
924
- * namespace RSCRouter {
925
- * interface RegisteredRoutes extends AppRoutes {}
926
- * }
927
- * }
928
- * ```
929
- */
930
- readonly routeMap: TRoutes;
931
-
932
- /**
933
- * Root layout component that wraps the entire application
934
- * Access this to pass to renderSegments
935
- */
936
- readonly rootLayout?: ComponentType<RootLayoutProps>;
937
-
938
- /**
939
- * Error callback for monitoring/alerting
940
- * Called when errors occur in loaders, actions, or routes
941
- */
942
- readonly onError?: RSCRouterOptions<TEnv>["onError"];
943
-
944
- /**
945
- * Cache configuration (for internal use by RSC handler)
946
- */
947
- readonly cache?: RSCRouterOptions<TEnv>["cache"];
948
-
949
- /**
950
- * Not found component to render when no route matches (for internal use by RSC handler)
951
- */
952
- readonly notFound?: RSCRouterOptions<TEnv>["notFound"];
953
-
954
- /**
955
- * Resolved theme configuration (null if theme not enabled)
956
- * Used by NavigationProvider to include ThemeProvider and by MetaTags to render theme script
957
- */
958
- readonly themeConfig: import("./theme/types.js").ResolvedThemeConfig | null;
959
-
960
- /**
961
- * Whether connection warmup is enabled.
962
- * When true, the client sends HEAD /?_rsc_warmup after idle periods
963
- * and the server responds with 204 No Content.
964
- */
965
- readonly warmupEnabled: boolean;
966
-
967
- /**
968
- * Whether ?__debug_manifest is allowed in production.
969
- * Always enabled in development.
970
- * @internal
971
- */
972
- readonly allowDebugManifest: boolean;
973
-
974
- /**
975
- * App-level middleware entries (for internal use by RSC handler)
976
- * These wrap the entire request/response cycle
977
- */
978
- readonly middleware: MiddlewareEntry<TEnv>[];
979
-
980
- /**
981
- * Nonce provider for CSP (for internal use by createHandler)
982
- */
983
- readonly nonce?: NonceProvider<TEnv>;
984
-
985
- /**
986
- * RSC version string (for internal use by createHandler)
987
- */
988
- readonly version?: string;
989
-
990
- /**
991
- * URL patterns reference for build-time manifest generation
992
- * @internal
993
- */
994
- readonly urlpatterns?: UrlPatterns<TEnv, any>;
995
-
996
- /**
997
- * Source file path where createRouter() was called.
998
- * Set via Error.stack parsing at construction time.
999
- * Used by the Vite plugin to write per-router named-routes.gen.ts files.
1000
- * @internal
1001
- */
1002
- readonly __sourceFile?: string;
1003
-
1004
- match(request: Request, context: TEnv): Promise<MatchResult>;
1005
-
1006
- /**
1007
- * Build-time pre-render match. Resolves segments with a BuildContext
1008
- * (no request/env/headers/cookies), skipping middleware and loaders.
1009
- * Used by the Vite plugin to collect pre-render data at build time.
1010
- * @internal
1011
- */
1012
- matchForPrerender(
1013
- pathname: string,
1014
- params: Record<string, string>,
1015
- ): Promise<{
1016
- segments: SerializedSegmentData[];
1017
- handles: Record<string, SegmentHandleData>;
1018
- routeName: string;
1019
- params: Record<string, string>;
1020
- } | null>;
1021
-
1022
- /**
1023
- * Preview match - returns route middleware without segment resolution.
1024
- * Also returns responseType and handler for response routes (non-RSC short-circuit).
1025
- */
1026
- previewMatch(
1027
- request: Request,
1028
- context: TEnv,
1029
- ): Promise<{
1030
- routeMiddleware?: Array<{
1031
- handler: import("./router/middleware.js").MiddlewareFn;
1032
- params: Record<string, string>;
1033
- }>;
1034
- responseType?: string;
1035
- handler?: Function;
1036
- params?: Record<string, string>;
1037
- negotiated?: boolean;
1038
- } | null>;
1039
-
1040
- matchPartial(
1041
- request: Request,
1042
- context: TEnv,
1043
- actionContext?: {
1044
- actionId?: string;
1045
- actionUrl?: URL;
1046
- actionResult?: any;
1047
- formData?: FormData;
1048
- },
1049
- ): Promise<MatchResult | null>;
1050
-
1051
- /**
1052
- * Match an error to the nearest error boundary and return error segments
1053
- *
1054
- * Used when an action or other operation fails and we need to render
1055
- * the error boundary UI. Finds the nearest errorBoundary in the route tree
1056
- * for the current URL and renders it with the error info.
1057
- *
1058
- * @param request - The current request (used to match the route)
1059
- * @param context - Environment context
1060
- * @param error - The error that occurred
1061
- * @param segmentType - Type of segment where error occurred (default: "route")
1062
- * @returns MatchResult with error segment, or null if no error boundary found
1063
- */
1064
- matchError(
1065
- request: Request,
1066
- context: TEnv,
1067
- error: unknown,
1068
- segmentType?: ErrorInfo["segmentType"],
1069
- ): Promise<MatchResult | null>;
1070
-
1071
- /**
1072
- * @internal
1073
- * Debug utility to serialize the manifest for inspection
1074
- * Returns a JSON-friendly representation of all routes and layouts
1075
- */
1076
- debugManifest(): Promise<SerializedManifest>;
1077
-
1078
- /**
1079
- * Handle an RSC request.
1080
- *
1081
- * Uses the router's configuration (nonce, version, cache) automatically.
1082
- * The handler is lazily created on first call.
1083
- *
1084
- * @example Cloudflare Workers
1085
- * ```tsx
1086
- * import { router } from "./router";
1087
- *
1088
- * export default { fetch: router.fetch };
1089
- * ```
1090
- *
1091
- * @example Direct export
1092
- * ```tsx
1093
- * const router = createRouter({
1094
- * document: Document,
1095
- * urls: urlpatterns,
1096
- * nonce: () => true,
1097
- * });
1098
- *
1099
- * export const fetch = router.fetch;
1100
- * ```
1101
- */
1102
- fetch(
1103
- request: Request,
1104
- env: TEnv & { ctx?: ExecutionContext },
1105
- ): Promise<Response>;
1106
- }
88
+ // Extracted router types and registry
89
+ import {
90
+ RSC_ROUTER_BRAND,
91
+ RouterRegistry,
92
+ nextRouterAutoId,
93
+ } from "./router/router-registry.js";
94
+ import type { RangoOptions, RootLayoutProps } from "./router/router-options.js";
95
+ import type {
96
+ Rango,
97
+ RangoInternal,
98
+ RouterRequestInput,
99
+ } from "./router/router-interfaces.js";
1107
100
 
1108
- /**
1109
- * Create an RSC router with generic context type
1110
- * Route types are accumulated automatically through the builder chain
1111
- *
1112
- * @example
1113
- * ```typescript
1114
- * interface AppContext {
1115
- * db: Database;
1116
- * user?: User;
1117
- * }
1118
- *
1119
- * const router = createRouter<AppContext>({
1120
- * debugPerformance: true // Enable metrics
1121
- * });
1122
- *
1123
- * // Route types accumulate through the chain - no module augmentation needed!
1124
- * // Keys stay unchanged, only URL patterns get the prefix
1125
- * router
1126
- * .routes(homeRoutes) // accumulates homeRoutes
1127
- * .map(() => import('./home'))
1128
- * .routes('/shop', shopRoutes) // accumulates shopRoutes with prefixed URLs
1129
- * .map(() => import('./shop'));
1130
- *
1131
- * // router.reverse now has type-safe autocomplete for all registered routes
1132
- * // Given shopRoutes = { cart: "/cart" }, reverse uses original key:
1133
- * router.reverse("cart"); // "/shop/cart"
1134
- * ```
1135
- */
101
+ // Extracted closure functions
102
+ import {
103
+ findLazyIncludes,
104
+ evaluateLazyEntry as _evaluateLazyEntry,
105
+ type LazyEvalDeps,
106
+ } from "./router/lazy-includes.js";
107
+ import { createFindMatch } from "./router/find-match.js";
108
+ import {
109
+ matchForPrerender as _matchForPrerender,
110
+ renderStaticSegment as _renderStaticSegment,
111
+ } from "./router/prerender-match.js";
112
+ import { resolveStateCookieName } from "./router/state-cookie-name.js";
113
+ import { resolvePrefetchCacheTTL } from "./router/prefetch-cache-ttl.js";
114
+ import {
115
+ resolvePrefetchCacheSize,
116
+ resolvePrefetchConcurrency,
117
+ } from "./router/prefetch-limits.js";
118
+
119
+ // Re-export public types and values from extracted modules
120
+ export { RSC_ROUTER_BRAND, RouterRegistry } from "./router/router-registry.js";
121
+ export type {
122
+ RangoOptions,
123
+ RootLayoutProps,
124
+ SSRStreamMode,
125
+ SSROptions,
126
+ ResolveStreamingContext,
127
+ } from "./router/router-options.js";
128
+ export type {
129
+ Rango,
130
+ RangoInternal,
131
+ RouterRequestInput,
132
+ } from "./router/router-interfaces.js";
133
+ export { toInternal } from "./router/router-interfaces.js";
1136
134
 
1137
135
  export function createRouter<TEnv = any>(
1138
- options: RSCRouterOptions<TEnv> = {},
1139
- ): RSCRouter<TEnv, {}> {
136
+ options: RangoOptions<TEnv> = {},
137
+ ): Rango<TEnv, {}> {
1140
138
  const {
1141
139
  id: userProvidedId,
1142
140
  $$id: injectedId,
141
+ basename: basenameOption,
1143
142
  debugPerformance = false,
1144
143
  document: documentOption,
1145
144
  defaultErrorBoundary,
@@ -1147,62 +146,163 @@ export function createRouter<TEnv = any>(
1147
146
  notFound,
1148
147
  onError,
1149
148
  cache,
149
+ cacheProfiles: cacheProfilesOption,
1150
150
  theme: themeOption,
1151
151
  urls: urlsOption,
1152
152
  $$routeNames: staticRouteNames,
153
+ $$sourceFile: injectedSourceFile,
1153
154
  nonce,
1154
155
  version,
156
+ prefetchCacheTTL: prefetchCacheTTLOption,
157
+ prefetchCacheSize: prefetchCacheSizeOption,
158
+ prefetchConcurrency: prefetchConcurrencyOption,
159
+ stateCookiePrefix: stateCookiePrefixOption,
1155
160
  warmup: warmupOption,
1156
- allowDebugManifest: allowDebugManifestOption = true,
161
+ telemetry: telemetrySink,
162
+ tracing: tracingOption,
163
+ ssr: ssrOption,
164
+ timeout: timeoutShorthand,
165
+ timeouts: timeoutsOption,
166
+ onTimeout,
167
+ originCheck: originCheckOption,
168
+ viewTransition: viewTransitionOption = "auto",
169
+ debugCacheSignal: debugCacheSignalOption = false,
170
+ debugShellCapture: debugShellCaptureOption,
171
+ strictMode: strictModeOption = true,
1157
172
  } = options;
1158
173
 
1159
- // Capture the source file that called createRouter() via stack trace parsing.
1160
- // Used by the Vite plugin to write per-router named-routes.gen.ts files.
1161
- let __sourceFile: string | undefined;
1162
- try {
1163
- const stack = new Error().stack;
1164
- if (stack) {
1165
- const lines = stack.split("\n");
1166
- for (const line of lines) {
1167
- const match = line.match(/\((.+?\.(ts|tsx|js|jsx)):\d+:\d+\)/);
1168
- if (match && !match[1].endsWith("/router.ts") && !match[1].includes("@rangojs/router") && !match[1].includes("node_modules")) {
1169
- // Strip file: URL protocol prefix from Vite module runner stack traces
1170
- __sourceFile = match[1].startsWith("file:") ? match[1].slice(5) : match[1];
1171
- break;
174
+ // Debug cache signal gate (DEVELOPMENT/TEST ONLY). Enabled by the
175
+ // debugCacheSignal option OR the RANGO_TEST_SIGNALS=1 env flag. When off,
176
+ // no X-Rango-Cache header is emitted and output is byte-identical.
177
+ const cacheSignalEnabled =
178
+ debugCacheSignalOption ||
179
+ (typeof process !== "undefined" &&
180
+ (process as { env?: Record<string, string | undefined> }).env
181
+ ?.RANGO_TEST_SIGNALS === "1");
182
+
183
+ // Normalize basename: ensure leading slash, strip trailing slash.
184
+ // A bare "/" is equivalent to no basename. Shared with the testing
185
+ // primitives via normalizeBasename so they can never drift.
186
+ const basename = normalizeBasename(basenameOption);
187
+
188
+ // Resolve telemetry sink (no-op when not configured)
189
+ const telemetry = resolveSink(telemetrySink);
190
+
191
+ // Resolve span tracing (undefined when not configured; every traceSpan() call
192
+ // is then a direct pass-through with zero behavior change).
193
+ const resolvedTracing = resolveTracing(tracingOption);
194
+
195
+ // Resolve cache profiles: merge user config with the guaranteed default
196
+ // profile. This resolved map is threaded onto each request context; the
197
+ // "use cache: <profile>" runtime path reads it request-scoped.
198
+ const resolvedCacheProfiles = resolveCacheProfiles(cacheProfilesOption);
199
+
200
+ // Source file: prefer Vite-injected path (zero cost), fall back to
201
+ // stack trace parsing for non-Vite environments (e.g. tests).
202
+ let __sourceFile: string | undefined = injectedSourceFile;
203
+ if (!__sourceFile) {
204
+ try {
205
+ const stack = new Error().stack;
206
+ if (stack) {
207
+ const lines = stack.split("\n");
208
+ for (const line of lines) {
209
+ const match = line.match(/\((.+?\.(ts|tsx|js|jsx)):\d+:\d+\)/);
210
+ if (
211
+ match &&
212
+ !match[1].endsWith("/router.ts") &&
213
+ !match[1].includes("@rangojs/router") &&
214
+ !match[1].includes("node_modules")
215
+ ) {
216
+ __sourceFile = match[1].startsWith("file:")
217
+ ? match[1].slice(5)
218
+ : match[1];
219
+ break;
220
+ }
1172
221
  }
1173
222
  }
1174
- }
1175
- } catch {}
223
+ } catch {}
224
+ }
1176
225
 
1177
226
  // Router ID priority: explicit id > Vite-injected $$id > counter fallback.
1178
227
  // $$id is a hash of filename+line injected by the Vite transform at compile
1179
228
  // time, so it's stable across build/runtime regardless of module evaluation
1180
229
  // order (unlike the counter which depends on import order).
1181
- const routerId = userProvidedId ?? injectedId ?? `router_${routerAutoId++}`;
230
+ const routerId =
231
+ userProvidedId ?? injectedId ?? `router_${nextRouterAutoId()}`;
232
+
233
+ // Resolve the rango state cookie name once, here, so the two cookie writers
234
+ // (the client document.cookie writer and the server Set-Cookie writer)
235
+ // consume one pre-composed name and cannot drift.
236
+ const resolvedStateCookieName = resolveStateCookieName(
237
+ stateCookiePrefixOption,
238
+ routerId,
239
+ );
240
+
241
+ // Resolve prefetch cache TTL (default: 300 seconds / 5 minutes). Clamps to a
242
+ // non-negative integer and guards non-finite (NaN/Infinity) inputs so a
243
+ // malformed `Cache-Control: max-age=NaN` can never reach the wire.
244
+ const resolvedPrefetchCacheTTL = resolvePrefetchCacheTTL(
245
+ prefetchCacheTTLOption,
246
+ );
247
+ const prefetchCacheTTL = resolvedPrefetchCacheTTL.ms;
248
+ const prefetchCacheControl: string | false =
249
+ resolvedPrefetchCacheTTL.cacheControl;
250
+
251
+ // Resolve client-side prefetch limits (in-memory cache size and queue
252
+ // concurrency). Both are positive-integer counts; sub-1/non-finite inputs
253
+ // fall back to the defaults. Shipped to the client in payload metadata.
254
+ const prefetchCacheSize = resolvePrefetchCacheSize(prefetchCacheSizeOption);
255
+ const prefetchConcurrency = resolvePrefetchConcurrency(
256
+ prefetchConcurrencyOption,
257
+ );
1182
258
 
1183
259
  // Resolve warmup enabled flag (default: true)
1184
260
  const warmupEnabled = warmupOption !== false;
1185
261
 
262
+ // Resolve StrictMode flag (default: true). Shipped to the client in payload
263
+ // metadata; the browser entry reads it once to decide whether to wrap the
264
+ // hydrated tree in React.StrictMode.
265
+ const strictMode = strictModeOption !== false;
266
+
1186
267
  // Resolve theme config (null if theme not enabled)
1187
268
  const resolvedThemeConfig = themeOption
1188
269
  ? resolveThemeConfig(themeOption)
1189
270
  : null;
1190
271
 
272
+ // Resolve timeout config (merge shorthand + structured)
273
+ const resolvedTimeouts = resolveTimeouts(timeoutShorthand, timeoutsOption);
274
+
1191
275
  /**
1192
276
  * Wrapper for invokeOnError that binds the router's onError callback.
1193
277
  * Uses the shared utility from router/error-handling.ts for consistent behavior.
278
+ *
279
+ * Deduplicates via per-request WeakSet stored on the ALS request context.
280
+ * A closure-level WeakSet would silently swallow errors if the same object
281
+ * instance is thrown across separate requests (e.g. a singleton error).
1194
282
  */
1195
283
  function callOnError(
1196
284
  error: unknown,
1197
285
  phase: ErrorPhase,
1198
286
  context: Parameters<typeof invokeOnError<TEnv>>[3],
1199
287
  ): void {
288
+ if (error != null && typeof error === "object") {
289
+ const reportedErrors = _getRequestContext()?._reportedErrors;
290
+ if (reportedErrors) {
291
+ if (reportedErrors.has(error)) return;
292
+ reportedErrors.add(error);
293
+ }
294
+ }
1200
295
  invokeOnError(onError, error, phase, context, "Router");
1201
296
  }
1202
297
 
1203
- // Validate document is a client component
298
+ // Validate document is a client component. Under a test runner the "use
299
+ // client" transform has not run, so a real exported document has no marker;
300
+ // allowServerInTest lets the router construct in a bare unit test (for
301
+ // dispatch / assertGeneratedRoutesMatch) while a real build still throws.
1204
302
  if (documentOption !== undefined) {
1205
- assertClientComponent(documentOption, "document");
303
+ assertClientComponent(documentOption, "document", {
304
+ allowServerInTest: true,
305
+ });
1206
306
  }
1207
307
 
1208
308
  // Use default document if none provided (keeps internal name as rootLayout)
@@ -1239,6 +339,18 @@ export function createRouter<TEnv = any>(
1239
339
  handler = patternOrMiddleware;
1240
340
  }
1241
341
 
342
+ // Prevent "use cache" functions from being used as middleware.
343
+ // They return data/JSX and do not call next() — silently accepting
344
+ // them would be a confusing no-op.
345
+ if (isCachedFunction(handler)) {
346
+ throw new Error(
347
+ `A "use cache" function cannot be used as middleware. ` +
348
+ `Cached functions return data and do not participate in the ` +
349
+ `middleware chain. Remove the "use cache" directive or use a ` +
350
+ `regular middleware function instead.`,
351
+ );
352
+ }
353
+
1242
354
  // If mount-scoped, prepend mount prefix to pattern
1243
355
  let fullPattern = pattern;
1244
356
  if (mountPrefix && pattern) {
@@ -1254,7 +366,7 @@ export function createRouter<TEnv = any>(
1254
366
  let regex: RegExp | null = null;
1255
367
  let paramNames: string[] = [];
1256
368
  if (fullPattern) {
1257
- const parsed = parsePattern(fullPattern);
369
+ const parsed = compileMiddlewarePattern(fullPattern);
1258
370
  regex = parsed.regex;
1259
371
  paramNames = parsed.paramNames;
1260
372
  }
@@ -1263,844 +375,274 @@ export function createRouter<TEnv = any>(
1263
375
  pattern: fullPattern,
1264
376
  regex,
1265
377
  paramNames,
1266
- handler,
1267
- mountPrefix,
1268
- });
1269
- }
1270
-
1271
- // Track all registered routes with their prefixes for reverse()
1272
- const mergedRouteMap: Record<string, string> = {};
1273
-
1274
- // Lazy precomputed entries lookup: rebuilt when per-router data arrives.
1275
- // In production multi-router setups, per-router data is loaded lazily via
1276
- // ensureRouterManifest(). At createRouter() time the data isn't available yet,
1277
- // so we defer building the Map until first use and invalidate when the
1278
- // per-router source changes.
1279
- let precomputedByPrefix: Map<string, Record<string, string>> | null = null;
1280
- let precomputedSource: Array<{ staticPrefix: string; routes: Record<string, string> }> | null | undefined;
1281
-
1282
- function getPrecomputedByPrefix(): Map<string, Record<string, string>> | null {
1283
- const current = getRouterPrecomputedEntries(routerId) ?? getPrecomputedEntries();
1284
- if (current !== precomputedSource) {
1285
- precomputedSource = current;
1286
- precomputedByPrefix = current
1287
- ? new Map(current.map((e) => [e.staticPrefix, e.routes]))
1288
- : null;
1289
- }
1290
- return precomputedByPrefix;
1291
- }
1292
-
1293
-
1294
- // Wrapper to pass debugPerformance to external createMetricsStore
1295
- const getMetricsStore = () => createMetricsStore(debugPerformance);
1296
-
1297
- // Wrapper to pass defaults to error/notFound boundary finders
1298
- const findNearestErrorBoundary = (entry: EntryData | null) =>
1299
- findErrorBoundary(entry, defaultErrorBoundary);
1300
-
1301
- const findNearestNotFoundBoundary = (entry: EntryData | null) =>
1302
- findNotFoundBoundary(entry, defaultNotFoundBoundary);
1303
-
1304
- // Helper to get handleStore from request context
1305
- const getHandleStore = (): HandleStore | undefined => {
1306
- return getRequestContext()?._handleStore;
1307
- };
1308
-
1309
- // Track a pending handler promise (non-blocking)
1310
- const trackHandler = <T>(promise: Promise<T>): Promise<T> => {
1311
- const store = getHandleStore();
1312
- return store ? store.track(promise) : promise;
1313
- };
1314
-
1315
- // Wrapper for wrapLoaderWithErrorHandling that uses router's error boundary finder
1316
- // Includes onError callback for loader error notification
1317
- function wrapLoaderPromise<T>(
1318
- promise: Promise<T>,
1319
- entry: EntryData,
1320
- segmentId: string,
1321
- pathname: string,
1322
- errorContext?: {
1323
- request: Request;
1324
- url: URL;
1325
- routeKey?: string;
1326
- params?: Record<string, string>;
1327
- env?: TEnv;
1328
- isPartial?: boolean;
1329
- requestStartTime?: number;
1330
- },
1331
- ): Promise<LoaderDataResult<T>> {
1332
- return wrapLoaderWithErrorHandling(
1333
- promise,
1334
- entry,
1335
- segmentId,
1336
- pathname,
1337
- findNearestErrorBoundary,
1338
- createErrorInfo,
1339
- // Invoke onError when loader fails
1340
- errorContext
1341
- ? (error, ctx) => {
1342
- callOnError(error, "loader", {
1343
- request: errorContext.request,
1344
- url: errorContext.url,
1345
- routeKey: errorContext.routeKey,
1346
- params: errorContext.params,
1347
- segmentId: ctx.segmentId,
1348
- segmentType: "loader",
1349
- loaderName: ctx.loaderName,
1350
- env: errorContext.env,
1351
- isPartial: errorContext.isPartial,
1352
- handledByBoundary: ctx.handledByBoundary,
1353
- requestStartTime: errorContext.requestStartTime,
1354
- });
1355
- }
1356
- : undefined,
1357
- );
1358
- }
1359
-
1360
- // Dependencies object for extracted segment resolution functions.
1361
- // Captures closure-bound helpers from createRouter.
1362
- const segmentDeps: SegmentResolutionDeps<TEnv> = {
1363
- wrapLoaderPromise,
1364
- trackHandler,
1365
- findNearestErrorBoundary,
1366
- findNearestNotFoundBoundary,
1367
- callOnError,
1368
- };
1369
-
1370
- // Match API dependencies
1371
- const matchApiDeps: MatchApiDeps<TEnv> = {
1372
- findMatch: (pathname: string, ms?: any) => findMatch(pathname, ms),
1373
- getMetricsStore,
1374
- findInterceptForRoute: (routeKey, parentEntry, selectorContext, isAction) =>
1375
- findInterceptForRoute(routeKey, parentEntry, selectorContext, isAction),
1376
- callOnError,
1377
- findNearestErrorBoundary,
1378
- getRouteMap: () => getRouterManifest(routerId) ?? getGlobalRouteMap(),
1379
- };
1380
-
1381
- // Thin wrappers that bind the deps to extracted functions.
1382
- // These maintain the same signatures as the original inline functions
1383
- // so that RouterContext and call sites don't need to change.
1384
-
1385
- function resolveAllSegments(
1386
- entries: EntryData[],
1387
- routeKey: string,
1388
- params: Record<string, string>,
1389
- context: HandlerContext<any, TEnv>,
1390
- loaderPromises: Map<string, Promise<any>>,
1391
- options?: { skipLoaders?: boolean },
1392
- ) {
1393
- return _resolveAllSegments(entries, routeKey, params, context, loaderPromises, segmentDeps, options);
1394
- }
1395
-
1396
- function resolveLoadersOnly(
1397
- entries: EntryData[],
1398
- context: HandlerContext<any, TEnv>,
1399
- ) {
1400
- return _resolveLoadersOnly(entries, context, segmentDeps);
1401
- }
1402
-
1403
- function resolveLoadersOnlyWithRevalidation(
1404
- entries: EntryData[],
1405
- context: HandlerContext<any, TEnv>,
1406
- clientSegmentIds: Set<string>,
1407
- prevParams: Record<string, string>,
1408
- request: Request,
1409
- prevUrl: URL,
1410
- nextUrl: URL,
1411
- routeKey: string,
1412
- actionContext?: { actionId?: string; actionUrl?: URL; actionResult?: any; formData?: FormData },
1413
- ) {
1414
- return _resolveLoadersOnlyWithRevalidation(
1415
- entries, context, clientSegmentIds, prevParams, request,
1416
- prevUrl, nextUrl, routeKey, segmentDeps, actionContext,
1417
- );
1418
- }
1419
-
1420
- function buildEntryRevalidateMap(entries: EntryData[]) {
1421
- return _buildEntryRevalidateMap(entries);
1422
- }
1423
-
1424
- function resolveAllSegmentsWithRevalidation(
1425
- entries: EntryData[],
1426
- routeKey: string,
1427
- params: Record<string, string>,
1428
- context: HandlerContext<any, TEnv>,
1429
- clientSegmentSet: Set<string>,
1430
- prevParams: Record<string, string>,
1431
- request: Request,
1432
- prevUrl: URL,
1433
- nextUrl: URL,
1434
- loaderPromises: Map<string, Promise<any>>,
1435
- actionContext: { actionId?: string; actionUrl?: URL; actionResult?: any; formData?: FormData } | undefined,
1436
- interceptResult: { intercept: InterceptEntry; entry: EntryData } | null,
1437
- localRouteName: string,
1438
- pathname: string,
1439
- ) {
1440
- return _resolveAllSegmentsWithRevalidation(
1441
- entries, routeKey, params, context, clientSegmentSet, prevParams, request,
1442
- prevUrl, nextUrl, loaderPromises, actionContext, interceptResult,
1443
- localRouteName, pathname, segmentDeps,
1444
- );
1445
- }
1446
-
1447
- function findInterceptForRoute(
1448
- targetRouteKey: string,
1449
- fromEntry: EntryData | null,
1450
- selectorContext: InterceptSelectorContext | null = null,
1451
- isAction: boolean = false,
1452
- ) {
1453
- return _findInterceptForRoute(targetRouteKey, fromEntry, selectorContext, isAction);
1454
- }
1455
-
1456
- function resolveInterceptEntry(
1457
- interceptEntry: InterceptEntry,
1458
- parentEntry: EntryData,
1459
- params: Record<string, string>,
1460
- context: HandlerContext<any, TEnv>,
1461
- belongsToRoute: boolean = true,
1462
- revalidationContext?: any,
1463
- ) {
1464
- return _resolveInterceptEntry(
1465
- interceptEntry, parentEntry, params, context, belongsToRoute,
1466
- segmentDeps, revalidationContext,
1467
- );
1468
- }
1469
-
1470
- function resolveInterceptLoadersOnly(
1471
- interceptEntry: InterceptEntry,
1472
- parentEntry: EntryData,
1473
- params: Record<string, string>,
1474
- context: HandlerContext<any, TEnv>,
1475
- belongsToRoute: boolean = true,
1476
- revalidationContext: any,
1477
- ) {
1478
- return _resolveInterceptLoadersOnly(
1479
- interceptEntry, parentEntry, params, context, belongsToRoute,
1480
- segmentDeps, revalidationContext,
1481
- );
1482
- }
1483
-
1484
- // Detect lazy includes in handler result and create placeholder entries
1485
- // Lazy includes are IncludeItem with lazy: true and _lazyContext
1486
- // Moved to outer scope so it can be reused by evaluateLazyEntry for nested includes
1487
- function findLazyIncludes(items: AllUseItems[]): Array<{
1488
- prefix: string;
1489
- patterns: UrlPatterns<TEnv>;
1490
- context: {
1491
- urlPrefix: string;
1492
- namePrefix: string | undefined;
1493
- parent: unknown;
1494
- };
1495
- }> {
1496
- const lazyItems: Array<{
1497
- prefix: string;
1498
- patterns: UrlPatterns<TEnv>;
1499
- context: {
1500
- urlPrefix: string;
1501
- namePrefix: string | undefined;
1502
- parent: unknown;
1503
- };
1504
- }> = [];
1505
-
1506
- for (const item of items) {
1507
- if (!item) continue;
1508
- if (item.type === "include") {
1509
- const includeItem = item as IncludeItem;
1510
- if (includeItem.lazy === true && includeItem._lazyContext) {
1511
- lazyItems.push({
1512
- prefix: includeItem.prefix,
1513
- patterns: includeItem.patterns as UrlPatterns<TEnv>,
1514
- context: includeItem._lazyContext,
1515
- });
1516
- }
1517
- }
1518
- // Recursively check nested items (in layouts, etc.)
1519
- if ((item as any).uses && Array.isArray((item as any).uses)) {
1520
- lazyItems.push(...findLazyIncludes((item as any).uses));
1521
- }
1522
- }
1523
-
1524
- return lazyItems;
1525
- }
1526
-
1527
- /**
1528
- * Evaluate a lazy entry's patterns and populate its routes
1529
- * This runs the lazy patterns handler and updates the entry in-place
1530
- * Also detects nested lazy includes and registers them as new entries
1531
- */
1532
- function evaluateLazyEntry(entry: RouteEntry<TEnv>): void {
1533
- if (!entry.lazy || entry.lazyEvaluated || !entry.lazyPatterns) {
1534
- return;
1535
- }
1536
-
1537
- // Check for pre-computed routes from build-time data.
1538
- // Only leaf nodes (no nested includes) are precomputed, so entries with
1539
- // nested lazy includes fall through to the handler below.
1540
- // When multiple entries share the same staticPrefix (e.g., several
1541
- // include("/", ...) calls), the precomputed data merges all their routes
1542
- // into one entry. Assigning that merged set to the first matching entry
1543
- // causes findMatch to pick the wrong handler for routes belonging to a
1544
- // different include. Skip the shortcut when the prefix is shared.
1545
- const currentPrecomputed = getPrecomputedByPrefix();
1546
- if (currentPrecomputed) {
1547
- const routes = currentPrecomputed.get(entry.staticPrefix);
1548
- if (routes) {
1549
- const prefixIsShared = routesEntries.filter(
1550
- (e) => e.staticPrefix === entry.staticPrefix,
1551
- ).length > 1;
1552
- if (!prefixIsShared) {
1553
- entry.lazyEvaluated = true;
1554
- entry.routes = routes as ResolvedRouteMap<any>;
1555
- for (const [name, pattern] of Object.entries(routes)) {
1556
- mergedRouteMap[name] = pattern;
1557
- }
1558
- registerRouteMap(mergedRouteMap);
1559
- return;
1560
- }
1561
- }
1562
- }
1563
-
1564
- // Mark as evaluated immediately to prevent concurrent evaluation.
1565
- // JS is single-threaded but handlers.handler() could theoretically yield,
1566
- // and the while-loop in findMatch retries after evaluation.
1567
- entry.lazyEvaluated = true;
1568
-
1569
- const lazyPatterns = entry.lazyPatterns as UrlPatterns<TEnv>;
1570
- const lazyContext = entry.lazyContext;
1571
-
1572
- // Create a new context for evaluating the lazy patterns
1573
- const manifest = new Map<string, EntryData>();
1574
- const patterns = new Map<string, string>();
1575
- const patternsByPrefix = new Map<string, Map<string, string>>();
1576
- const trailingSlashMap = new Map<string, TrailingSlashMode>();
1577
-
1578
- // Capture the handler result to detect nested lazy includes
1579
- let handlerResult: AllUseItems[] = [];
1580
-
1581
- // Merge captured counters from include() to maintain consistent
1582
- // shortCode indices with sibling entries from pattern extraction
1583
- const lazyCounters: Record<string, number> = {};
1584
- if (lazyContext && (lazyContext as any).counters) {
1585
- const captured = (lazyContext as any).counters as Record<string, number>;
1586
- for (const [key, value] of Object.entries(captured)) {
1587
- lazyCounters[key] = value;
1588
- }
1589
- }
1590
-
1591
- RSCRouterContext.run(
1592
- {
1593
- manifest,
1594
- patterns,
1595
- patternsByPrefix,
1596
- trailingSlash: trailingSlashMap,
1597
- namespace: "lazy",
1598
- parent: (lazyContext?.parent as EntryData | null) ?? null,
1599
- counters: lazyCounters,
1600
- },
1601
- () => {
1602
- // Run the lazy patterns handler with the original context prefixes
1603
- // The prefix comes from the IncludeItem stored in lazyPatterns
1604
- const includePrefix = (entry as any)._lazyPrefix || "";
1605
- const fullPrefix = (lazyContext?.urlPrefix || "") + includePrefix;
1606
-
1607
- if (fullPrefix || lazyContext?.namePrefix) {
1608
- runWithPrefixes(fullPrefix, lazyContext?.namePrefix, () => {
1609
- handlerResult = lazyPatterns.handler() as AllUseItems[];
1610
- });
1611
- } else {
1612
- handlerResult = lazyPatterns.handler() as AllUseItems[];
1613
- }
1614
- },
1615
- );
1616
-
1617
- // Populate the entry's routes from the patterns
1618
- const routesObject: Record<string, string> = {};
1619
- for (const [name, pattern] of patterns.entries()) {
1620
- routesObject[name] = pattern;
1621
- // Also add to merged route map for reverse() support
1622
- const existingPattern = mergedRouteMap[name];
1623
- if (existingPattern !== undefined && existingPattern !== pattern) {
1624
- console.warn(
1625
- `[@rangojs/router] Route name conflict: "${name}" already maps to "${existingPattern}", ` +
1626
- `overwriting with "${pattern}" (from lazy include). Use unique route names to avoid this.`,
1627
- );
1628
- }
1629
- mergedRouteMap[name] = pattern;
1630
- }
1631
-
1632
- // Update the entry in-place
1633
- entry.routes = routesObject as ResolvedRouteMap<any>;
1634
-
1635
- // Note: Do NOT clear lazyPatterns/lazyContext here.
1636
- // loadManifest() needs them on every request to re-run the handler
1637
- // in the correct AsyncLocalStorage context (Store.manifest).
1638
-
1639
- // Update trailing slash config if available
1640
- if (trailingSlashMap.size > 0) {
1641
- entry.trailingSlash = Object.fromEntries(trailingSlashMap);
1642
- }
1643
-
1644
- // Detect nested lazy includes and register them as new entries
1645
- const nestedLazyIncludes = findLazyIncludes(handlerResult);
1646
- for (const lazyInclude of nestedLazyIncludes) {
1647
- // Compute the full URL prefix (combining parent prefix if any)
1648
- const fullPrefix = lazyInclude.context.urlPrefix
1649
- ? lazyInclude.context.urlPrefix + lazyInclude.prefix
1650
- : lazyInclude.prefix;
1651
-
1652
- const nestedEntry: RouteEntry<TEnv> & { _lazyPrefix?: string } = {
1653
- prefix: "",
1654
- staticPrefix: extractStaticPrefix(fullPrefix),
1655
- routes: {} as ResolvedRouteMap<any>, // Empty until first match
1656
- trailingSlash: entry.trailingSlash,
1657
- handler: (lazyInclude.patterns as UrlPatterns<TEnv>).handler,
1658
- mountIndex: mountIndex++,
1659
- // Lazy evaluation fields
1660
- lazy: true,
1661
- lazyPatterns: lazyInclude.patterns,
1662
- lazyContext: lazyInclude.context,
1663
- lazyEvaluated: false,
1664
- // Store the include prefix for evaluation
1665
- _lazyPrefix: lazyInclude.prefix,
1666
- };
1667
- // Insert nested lazy entry before any entry whose staticPrefix is a
1668
- // prefix of (but shorter than) this lazy entry's staticPrefix.
1669
- // This ensures more specific lazy includes are matched before
1670
- // less specific eager entries (e.g., "/href/nested" before "/href/:id").
1671
- const nestedPrefix = nestedEntry.staticPrefix;
1672
- let insertIndex = routesEntries.length;
1673
- if (nestedPrefix) {
1674
- for (let i = 0; i < routesEntries.length; i++) {
1675
- const existing = routesEntries[i]!;
1676
- if (
1677
- nestedPrefix.startsWith(existing.staticPrefix) &&
1678
- nestedPrefix.length > existing.staticPrefix.length
1679
- ) {
1680
- insertIndex = i;
1681
- break;
1682
- }
1683
- }
1684
- }
1685
- routesEntries.splice(insertIndex, 0, nestedEntry);
1686
- }
1687
-
1688
- // Re-register route map for runtime reverse() usage
1689
- registerRouteMap(mergedRouteMap);
1690
- }
1691
-
1692
- // Single-entry cache for findMatch to avoid redundant matching within the same request.
1693
- // previewMatch and match both call findMatch with the same pathname — this ensures
1694
- // the route matching work (which may check thousands of routes) only happens once.
1695
- let lastFindMatchPathname: string | null = null;
1696
- let lastFindMatchResult: RouteMatchResult<TEnv> | null = null;
1697
-
1698
- // Wrapper for findMatch that uses routesEntries
1699
- // Handles lazy evaluation by evaluating lazy entries on first match.
1700
- // Phase 1: try O(path_length) trie match.
1701
- // Phase 2: fall back to regex iteration.
1702
- function findMatch(
1703
- pathname: string,
1704
- ms?: MetricsStore,
1705
- ): RouteMatchResult<TEnv> | null {
1706
- // Return cached result if same pathname (avoids double-match per request)
1707
- if (lastFindMatchPathname === pathname) {
1708
- return lastFindMatchResult;
1709
- }
1710
-
1711
- // Helper to push sub-metrics
1712
- const pushMetric = ms
1713
- ? (label: string, start: number) => {
1714
- ms.metrics.push({
1715
- label,
1716
- duration: performance.now() - start,
1717
- startTime: start - ms.requestStart,
1718
- });
1719
- }
1720
- : undefined;
1721
-
1722
- // Phase 1: Try trie match (O(path_length))
1723
- // Prefer per-router trie (isolated) over global trie (merged).
1724
- const routeTrie = getRouterTrie(routerId) ?? getRouteTrie();
1725
- if (routeTrie) {
1726
- const trieStart = performance.now();
1727
- const trieResult = tryTrieMatch(routeTrie, pathname);
1728
- pushMetric?.("match:trie", trieStart);
1729
-
1730
- if (trieResult) {
1731
- // Find the RouteEntry that contains this route.
1732
- // Multiple entries can share the same staticPrefix (e.g., several
1733
- // include("/", patterns) calls all produce staticPrefix=""). Evaluate
1734
- // each candidate and pick the one whose routes include the matched key.
1735
- const entryStart = performance.now();
1736
- let entry: RouteEntry<TEnv> | undefined;
1737
- let fallbackEntry: RouteEntry<TEnv> | undefined;
1738
-
1739
- for (const e of routesEntries) {
1740
- if (e.staticPrefix !== trieResult.sp) continue;
1741
- if (!fallbackEntry) fallbackEntry = e;
1742
- evaluateLazyEntry(e);
1743
- if (
1744
- e.routes &&
1745
- trieResult.routeKey in (e.routes as Record<string, unknown>)
1746
- ) {
1747
- entry = e;
1748
- break;
1749
- }
1750
- }
1751
-
1752
- // If no entry had the route in its routes map, use the first matching
1753
- // entry as fallback (handles main entry with inline routes not yet
1754
- // reflected in its routes object).
1755
- if (!entry) entry = fallbackEntry;
1756
-
1757
- // If entry not found (nested include not yet discovered), evaluate parent
1758
- if (!entry) {
1759
- const parent = routesEntries.find(
1760
- (e) =>
1761
- trieResult.sp.startsWith(e.staticPrefix) &&
1762
- e.staticPrefix !== trieResult.sp,
1763
- );
1764
- if (parent) {
1765
- const lazyStart = performance.now();
1766
- evaluateLazyEntry(parent);
1767
- pushMetric?.("match:lazy-eval", lazyStart);
1768
- }
1769
- entry = routesEntries.find((e) => e.staticPrefix === trieResult.sp);
1770
- }
1771
- pushMetric?.("match:entry-resolve", entryStart);
1772
-
1773
- if (entry) {
1774
- lastFindMatchPathname = pathname;
1775
- lastFindMatchResult = {
1776
- entry,
1777
- routeKey: trieResult.routeKey,
1778
- params: trieResult.params,
1779
- optionalParams: new Set(trieResult.optionalParams || []),
1780
- redirectTo: trieResult.redirectTo,
1781
- ancestry: trieResult.ancestry,
1782
- ...(trieResult.pr ? { pr: true } : {}),
1783
- ...(trieResult.pt ? { pt: true } : {}),
1784
- ...(trieResult.responseType ? { responseType: trieResult.responseType } : {}),
1785
- ...(trieResult.negotiateVariants ? { negotiateVariants: trieResult.negotiateVariants } : {}),
1786
- ...(trieResult.rscFirst ? { rscFirst: true } : {}),
1787
- };
1788
- return lastFindMatchResult;
1789
- }
1790
- }
1791
- }
1792
-
1793
- // Phase 2: Fall back to existing matching (regex iteration)
1794
- const regexStart = performance.now();
1795
- let result = findRouteMatch(pathname, routesEntries);
1796
-
1797
- // If we hit a lazy entry that needs evaluation, evaluate and retry.
1798
- // Cap iterations to prevent infinite loops from pathological nesting.
1799
- const MAX_LAZY_ITERATIONS = 100;
1800
- let iterations = 0;
1801
- while (isLazyEvaluationNeeded(result)) {
1802
- if (++iterations > MAX_LAZY_ITERATIONS) {
1803
- console.error(
1804
- `[@rangojs/router] Exceeded ${MAX_LAZY_ITERATIONS} lazy evaluation iterations ` +
1805
- `for pathname "${pathname}". This likely indicates circular lazy includes.`,
1806
- );
1807
- lastFindMatchPathname = pathname;
1808
- lastFindMatchResult = null;
1809
- return null;
1810
- }
1811
- evaluateLazyEntry(result.lazyEntry);
1812
- result = findRouteMatch(pathname, routesEntries);
1813
- }
1814
- pushMetric?.("match:regex-fallback", regexStart);
1815
-
1816
- lastFindMatchPathname = pathname;
1817
- lastFindMatchResult = result;
1818
- return result;
1819
- }
1820
-
1821
-
1822
- /**
1823
- * Build-time pre-render match. Resolves segments with a BuildContext
1824
- * (no request/env/headers/cookies), skipping middleware and loaders.
1825
- */
1826
- async function matchForPrerender(
1827
- pathname: string,
1828
- params: Record<string, string>,
1829
- ): Promise<{
1830
- segments: SerializedSegmentData[];
1831
- handles: Record<string, SegmentHandleData>;
1832
- routeName: string;
1833
- params: Record<string, string>;
1834
- } | null> {
1835
- // 1. Find the matching route entry
1836
- const matched = findMatch(pathname);
1837
- if (!matched) return null;
1838
-
1839
- // Use params from trie match if available, fall back to provided params
1840
- const matchedParams = matched.params ?? params;
1841
-
1842
- // Build a minimal RouterContext for loadManifest/traverseBack
1843
- const routerCtx: RouterContext<TEnv> = {
1844
- findMatch,
1845
- loadManifest,
1846
- traverseBack,
1847
- createHandlerContext,
1848
- setupLoaderAccess,
1849
- setupLoaderAccessSilent,
1850
- getContext,
1851
- getMetricsStore,
1852
- createCacheScope,
1853
- findInterceptForRoute,
1854
- resolveAllSegmentsWithRevalidation,
1855
- resolveInterceptEntry,
1856
- evaluateRevalidation,
1857
- getRequestContext,
1858
- resolveAllSegments,
1859
- createHandleStore,
1860
- buildEntryRevalidateMap,
1861
- resolveLoadersOnlyWithRevalidation,
1862
- resolveInterceptLoadersOnly,
1863
- resolveLoadersOnly,
1864
- };
1865
-
1866
- return runWithRouterContext(routerCtx, async () => {
1867
- // 2. Load the manifest entry tree
1868
- const manifestEntry = await loadManifest(
1869
- matched.entry,
1870
- matched.routeKey,
1871
- pathname,
1872
- undefined,
1873
- false,
1874
- );
1875
-
1876
- // 3. Build ancestor chain [root, ..., route]
1877
- const entries: EntryData[] = [];
1878
- for (const entry of traverseBack(manifestEntry)) {
1879
- entries.push(entry);
1880
- }
1881
-
1882
- // 4. Create handle store for collecting handle data
1883
- const handleStore = createHandleStore();
1884
-
1885
- // 5. Create a minimal request context with the handle store
1886
- const stubRes = new Response(null, { status: 200 });
1887
- const minimalRequestContext: RequestContext<TEnv> = {
1888
- env: {} as TEnv,
1889
- request: new Request("http://prerender" + pathname),
1890
- url: new URL("http://prerender" + pathname),
1891
- pathname,
1892
- searchParams: new URLSearchParams(),
1893
- var: {},
1894
- get: () => undefined as any,
1895
- set: () => {},
1896
- params: matchedParams,
1897
- res: stubRes,
1898
- cookie: () => undefined,
1899
- cookies: () => ({}),
1900
- setCookie: () => {},
1901
- deleteCookie: () => {},
1902
- header: () => {},
1903
- use: (() => {
1904
- throw new Error("use() not available during pre-rendering");
1905
- }) as any,
1906
- method: "GET",
1907
- _handleStore: handleStore,
1908
- waitUntil: () => {},
1909
- onResponse: () => {},
1910
- _onResponseCallbacks: [],
1911
- };
1912
-
1913
- return runWithRequestContext(minimalRequestContext, async () => {
1914
- // 6. Create handler context with synthetic request for pre-rendering.
1915
- // The synthetic request and route map are available at build time,
1916
- // so reverse() and other context properties work normally.
1917
- const buildCtx = createHandlerContext<TEnv>(
1918
- matchedParams,
1919
- minimalRequestContext.request,
1920
- minimalRequestContext.url.searchParams,
1921
- pathname,
1922
- minimalRequestContext.url,
1923
- {},
1924
- mergedRouteMap,
1925
- matched.routeKey,
1926
- );
378
+ handler,
379
+ });
380
+ }
1927
381
 
1928
- // 7. Wire use() for handles only (loaders throw)
1929
- setupBuildUse(buildCtx);
1930
-
1931
- // 8. Resolve all segments with skipLoaders
1932
- const loaderPromises = new Map<string, Promise<any>>();
1933
- const allSegments = await resolveAllSegments(
1934
- entries,
1935
- matched.routeKey,
1936
- matchedParams,
1937
- buildCtx,
1938
- loaderPromises,
1939
- { skipLoaders: true },
1940
- );
382
+ // Track all registered routes with their prefixes for reverse().
383
+ // Seed from injected NamedRoutes so reverse() works at module load time
384
+ // for routes that come from lazy includes.
385
+ const mergedRouteMap: Record<string, string> =
386
+ flattenNamedRoutes(staticRouteNames);
1941
387
 
1942
- // 9. Filter out any loader segments (belt-and-suspenders)
1943
- const nonLoaderSegments = allSegments.filter((s) => s.type !== "loader");
388
+ // Track names that came from the static seed so we can silently overwrite
389
+ // them during routes() registration. The gen file may be stale during HMR,
390
+ // so conflicts between seeded and runtime-registered values are expected.
391
+ const seededNames = new Set(Object.keys(mergedRouteMap));
1944
392
 
1945
- // 10. Wait for handles to settle
1946
- await handleStore.settled;
393
+ // Lazy precomputed entries lookup: rebuilt when per-router data arrives.
394
+ // In production multi-router setups, per-router data is loaded lazily via
395
+ // ensureRouterManifest(). At createRouter() time the data isn't available yet,
396
+ // so we defer building the Map until first use and invalidate when the
397
+ // per-router source changes.
398
+ let precomputedByPrefix: Map<string, Record<string, string>> | null = null;
399
+ let precomputedSource:
400
+ | Array<{ staticPrefix: string; routes: Record<string, string> }>
401
+ | null
402
+ | undefined;
403
+
404
+ function getPrecomputedByPrefix(): Map<
405
+ string,
406
+ Record<string, string>
407
+ > | null {
408
+ const current =
409
+ getRouterPrecomputedEntries(routerId) ?? getPrecomputedEntries();
410
+ if (current !== precomputedSource) {
411
+ precomputedSource = current;
412
+ // buildPrecomputedByPrefix drops any staticPrefix owned by more than one
413
+ // leaf include instead of collapsing it last-wins (which would mis-assign
414
+ // one include's routes to another's entry and 500 a valid sibling route).
415
+ // Such shared-prefix includes resolve via the handler path instead.
416
+ precomputedByPrefix = current ? buildPrecomputedByPrefix(current) : null;
417
+ }
418
+ return precomputedByPrefix;
419
+ }
1947
420
 
1948
- // 11. Serialize segments using the cache serializer
1949
- const { serializeSegments } = await import("./cache/cache-scope.js");
1950
- const serializedSegments = await serializeSegments(nonLoaderSegments);
421
+ // Wrapper to pass debugPerformance to external createMetricsStore.
422
+ // Also checks per-request flag set by ctx.debugPerformance() in middleware.
423
+ // With no active request context there is nowhere to hang the store, so return
424
+ // undefined: an orphan store would collect metrics no reader can reach (nothing
425
+ // holds it, and appendMetric(undefined, ...) is already a no-op).
426
+ const getMetricsStore = () => {
427
+ const reqCtx = _getRequestContext();
428
+ const enabled = debugPerformance || !!reqCtx?._debugPerformance;
429
+ if (!enabled || !reqCtx) return undefined;
430
+ // Anchor a mid-request store to the true request entry (reqCtx._handlerStart),
431
+ // not this call's performance.now(); undefined falls back to now() inside
432
+ // createMetricsStore (metrics.ts).
433
+ reqCtx._metricsStore ??= createMetricsStore(true, reqCtx._handlerStart);
434
+ return reqCtx._metricsStore;
435
+ };
1951
436
 
1952
- // 12. Collect handle data per segment (skip segments with no handle data)
1953
- const handles: Record<string, SegmentHandleData> = {};
1954
- for (const seg of nonLoaderSegments) {
1955
- const segHandles = handleStore.getDataForSegment(seg.id);
1956
- if (Object.keys(segHandles).length > 0) {
1957
- handles[seg.id] = segHandles;
1958
- }
1959
- }
437
+ // Wrapper to pass defaults to error/notFound boundary finders
438
+ const findNearestErrorBoundary = (entry: EntryData | null) =>
439
+ findErrorBoundary(entry, defaultErrorBoundary);
1960
440
 
1961
- // Use the trie-level route key (e.g., "docs", "docs.article")
1962
- const routeName = matched.routeKey;
441
+ const findNearestNotFoundBoundary = (entry: EntryData | null) =>
442
+ findNotFoundBoundary(entry, defaultNotFoundBoundary);
1963
443
 
1964
- return {
1965
- segments: serializedSegments,
1966
- handles,
1967
- routeName,
1968
- params: matchedParams,
1969
- };
444
+ // Track a pending handler promise (non-blocking).
445
+ // Attaches a side-effect .catch() to report streaming handler errors to onError
446
+ // without altering the rejection chain (React's streaming error boundary still handles it).
447
+ const trackHandler = <T>(
448
+ promise: Promise<T>,
449
+ errorContext?: {
450
+ segmentId?: string;
451
+ segmentType?: string;
452
+ },
453
+ ): Promise<T> => {
454
+ // One ALS read serves both the store lookup and the onError closure.
455
+ const reqCtx = _getRequestContext();
456
+ const store = reqCtx?._handleStore;
457
+ const tracked = store ? store.track(promise) : promise;
458
+
459
+ // Report streaming handler errors to onError as a side-effect.
460
+ // The rejection still propagates to the RSC stream for client error boundaries.
461
+ // Captures request context eagerly (closure) so the catch handler has full context.
462
+ if (reqCtx && onError) {
463
+ tracked.catch((error) => {
464
+ callOnError(error, "handler", {
465
+ request: reqCtx.request,
466
+ url: reqCtx.url,
467
+ routeKey: reqCtx._routeName,
468
+ params: reqCtx.params as Record<string, string>,
469
+ env: reqCtx.env as TEnv,
470
+ segmentId: errorContext?.segmentId,
471
+ segmentType: errorContext?.segmentType as any,
472
+ handledByBoundary: true,
473
+ });
1970
474
  });
1971
- });
1972
- }
475
+ }
1973
476
 
1974
- /**
1975
- * Match request and return segments (document/SSR requests)
1976
- *
1977
- * Uses generator middleware pipeline for clean separation of concerns:
1978
- * - cache-lookup: Check cache first
1979
- * - segment-resolution: Resolve segments on cache miss
1980
- * - cache-store: Store results in cache
1981
- * - background-revalidation: SWR revalidation
1982
- */
1983
- async function match(request: Request, env: TEnv): Promise<MatchResult> {
1984
- // Build RouterContext with all closure functions needed by middleware
1985
- const routerCtx: RouterContext<TEnv> = {
1986
- findMatch,
1987
- loadManifest,
1988
- traverseBack,
1989
- createHandlerContext,
1990
- setupLoaderAccess,
1991
- setupLoaderAccessSilent,
1992
- getContext,
1993
- getMetricsStore,
1994
- createCacheScope,
1995
- findInterceptForRoute,
1996
- resolveAllSegmentsWithRevalidation,
1997
- resolveInterceptEntry,
1998
- evaluateRevalidation,
1999
- getRequestContext,
2000
- resolveAllSegments,
2001
- createHandleStore,
2002
- buildEntryRevalidateMap,
2003
- resolveLoadersOnlyWithRevalidation,
2004
- resolveInterceptLoadersOnly,
2005
- resolveLoadersOnly,
2006
- };
477
+ return tracked;
478
+ };
2007
479
 
2008
- return runWithRouterLogContext(
2009
- { request, transaction: "match" },
2010
- () =>
2011
- runWithRouterContext(routerCtx, async () =>
2012
- withRouterLogScope("match", async () => {
2013
- const result = await createMatchContextForFull(request, env);
2014
-
2015
- // Handle redirect case
2016
- if ("type" in result && result.type === "redirect") {
2017
- return {
2018
- segments: [],
2019
- matched: [],
2020
- diff: [],
2021
- params: {},
2022
- redirect: result.redirectUrl,
2023
- };
2024
- }
480
+ // Wrapper for wrapLoaderWithErrorHandling that uses router's error boundary finder
481
+ // Includes onError callback for loader error notification and telemetry emission.
482
+ function wrapLoaderPromise<T>(
483
+ promise: Promise<T>,
484
+ entry: EntryData,
485
+ segmentId: string,
486
+ pathname: string,
487
+ errorContext?: {
488
+ request: Request;
489
+ url: URL;
490
+ routeKey?: string;
491
+ params?: Record<string, string>;
492
+ env?: TEnv;
493
+ isPartial?: boolean;
494
+ requestStartTime?: number;
495
+ },
496
+ ): Promise<LoaderDataResult<T>> {
497
+ const loaderStart = telemetrySink ? performance.now() : 0;
498
+ const loaderRequestId = telemetrySink
499
+ ? errorContext?.request
500
+ ? getRequestId(errorContext.request)
501
+ : undefined
502
+ : undefined;
503
+ // Derived once here for both the loader.start and loader.end emits (the
504
+ // loader.error emit uses ctx.loaderName from wrapLoaderWithErrorHandling).
505
+ const loaderName = telemetrySink
506
+ ? segmentId.split(".").pop() || "unknown"
507
+ : "";
508
+ if (telemetrySink) {
509
+ safeEmit(telemetry, {
510
+ type: "loader.start",
511
+ timestamp: loaderStart,
512
+ requestId: loaderRequestId,
513
+ segmentId,
514
+ loaderName,
515
+ pathname,
516
+ });
517
+ }
2025
518
 
2026
- const ctx = result as MatchContext<TEnv>;
2027
-
2028
- try {
2029
- const state = createPipelineState();
2030
- const pipeline = createMatchPartialPipeline(ctx, state);
2031
- return await collectMatchResult(pipeline, ctx, state);
2032
- } catch (error) {
2033
- if (error instanceof Response) throw error;
2034
- // Report unhandled errors during full match pipeline
2035
- callOnError(error, "routing", {
2036
- request,
2037
- url: ctx.url,
2038
- env,
2039
- isPartial: false,
2040
- handledByBoundary: false,
519
+ const result = wrapLoaderWithErrorHandling(
520
+ promise,
521
+ entry,
522
+ segmentId,
523
+ pathname,
524
+ findNearestErrorBoundary,
525
+ createErrorInfo,
526
+ // Invoke onError when loader fails
527
+ errorContext
528
+ ? (error, ctx) => {
529
+ callOnError(error, "loader", {
530
+ request: errorContext.request,
531
+ url: errorContext.url,
532
+ routeKey: errorContext.routeKey,
533
+ params: errorContext.params,
534
+ segmentId: ctx.segmentId,
535
+ segmentType: "loader",
536
+ loaderName: ctx.loaderName,
537
+ env: errorContext.env,
538
+ isPartial: errorContext.isPartial,
539
+ handledByBoundary: ctx.handledByBoundary,
540
+ requestStartTime: errorContext.requestStartTime,
541
+ });
542
+ if (telemetrySink) {
543
+ const errorObj =
544
+ error instanceof Error ? error : new Error(String(error));
545
+ safeEmit(telemetry, {
546
+ type: "loader.error",
547
+ timestamp: performance.now(),
548
+ requestId: loaderRequestId,
549
+ segmentId: ctx.segmentId,
550
+ loaderName: ctx.loaderName,
551
+ pathname,
552
+ error: errorObj,
553
+ handledByBoundary: ctx.handledByBoundary,
2041
554
  });
2042
- throw sanitizeError(error);
2043
555
  }
2044
- }),
2045
- ),
556
+ }
557
+ : undefined,
2046
558
  );
2047
- }
2048
559
 
2049
- async function matchError(
2050
- request: Request,
2051
- _context: TEnv,
2052
- error: unknown,
2053
- segmentType: ErrorInfo["segmentType"] = "route",
2054
- ): Promise<MatchResult | null> {
2055
- return runWithRouterLogContext(
2056
- { request, transaction: "matchError" },
2057
- () =>
2058
- withRouterLogScope("matchError", () =>
2059
- _matchError(
2060
- request,
2061
- _context,
2062
- error,
2063
- matchApiDeps,
2064
- defaultErrorBoundary,
2065
- segmentType,
2066
- ),
2067
- ),
2068
- );
560
+ // Emit loader.end after the promise settles (fire-and-forget)
561
+ if (telemetrySink) {
562
+ result.then((r) => {
563
+ safeEmit(telemetry, {
564
+ type: "loader.end",
565
+ timestamp: performance.now(),
566
+ requestId: loaderRequestId,
567
+ segmentId,
568
+ loaderName,
569
+ pathname,
570
+ durationMs: performance.now() - loaderStart,
571
+ ok: r.ok,
572
+ });
573
+ });
574
+ }
575
+
576
+ return result;
2069
577
  }
2070
578
 
579
+ // Dependencies object for extracted segment resolution functions.
580
+ // Captures closure-bound helpers from createRouter.
581
+ const segmentDeps: SegmentResolutionDeps<TEnv> = {
582
+ wrapLoaderPromise,
583
+ trackHandler,
584
+ findNearestErrorBoundary,
585
+ findNearestNotFoundBoundary,
586
+ notFoundComponent: notFound,
587
+ callOnError,
588
+ viewTransitionDefault: viewTransitionOption,
589
+ };
2071
590
 
2072
- async function createMatchContextForFull(
2073
- request: Request,
2074
- env: TEnv,
2075
- ) {
2076
- return _createMatchContextForFull(request, env, matchApiDeps, findInterceptForRoute);
2077
- }
591
+ // Match API dependencies
592
+ const matchApiDeps: MatchApiDeps<TEnv> = {
593
+ findMatch: (pathname: string, ms?: any) => findMatch(pathname, ms),
594
+ getMetricsStore,
595
+ findInterceptForRoute: (routeKey, parentEntry, selectorContext, isAction) =>
596
+ findInterceptForRoute(routeKey, parentEntry, selectorContext, isAction),
597
+ callOnError,
598
+ findNearestErrorBoundary,
599
+ // Use per-router manifest when available, otherwise the static named map
600
+ // seeded into mergedRouteMap at router creation.
601
+ getRouteMap: () => getRouterManifest(routerId) ?? mergedRouteMap,
602
+ };
2078
603
 
2079
- async function createMatchContextForPartial(
2080
- request: Request,
2081
- env: TEnv,
2082
- actionContext?: { actionId?: string; actionUrl?: URL; actionResult?: any; formData?: FormData },
2083
- ) {
2084
- return _createMatchContextForPartial(request, env, matchApiDeps, findInterceptForRoute, actionContext);
604
+ // Create segment resolution wrappers bound to segmentDeps
605
+ const {
606
+ resolveAllSegments,
607
+ resolveLoadersOnly,
608
+ resolveLoadersOnlyWithRevalidation,
609
+ buildEntryRevalidateMap,
610
+ resolveAllSegmentsWithRevalidation,
611
+ findInterceptForRoute,
612
+ resolveInterceptEntry,
613
+ resolveInterceptLoadersOnly,
614
+ } = createSegmentWrappers<TEnv>(segmentDeps);
615
+
616
+ // Lazy evaluation deps — captures closure state for extracted evaluateLazyEntry
617
+ const lazyEvalDeps: LazyEvalDeps<TEnv> = {
618
+ routesEntries,
619
+ mergedRouteMap,
620
+ nextMountIndex: () => mountIndex++,
621
+ getPrecomputedByPrefix,
622
+ routerId,
623
+ };
624
+
625
+ // Must return the Promise from _evaluateLazyEntry: an async include provider
626
+ // (`() => import("./routes")`) resolves off the startup path, and createFindMatch
627
+ // awaits this to know when the import + expansion have completed. Dropping it
628
+ // (typing this `void`) makes the import fire-and-forget, so findMatch spins the
629
+ // lazy-eval retry loop to its cap and returns null on the first request to any
630
+ // async include whose prefix isn't already covered by a unique precomputed entry
631
+ // (nested includes, shared prefixes, regex fallback).
632
+ function evaluateLazyEntry(entry: RouteEntry<TEnv>): void | Promise<void> {
633
+ return _evaluateLazyEntry(entry, lazyEvalDeps);
2085
634
  }
2086
635
 
2087
- /**
2088
- * Match partial request with revalidation
2089
- *
2090
- * Uses generator middleware pipeline for clean separation of concerns:
2091
- * - cache-lookup: Check cache first
2092
- * - segment-resolution: Resolve segments on cache miss
2093
- * - intercept-resolution: Handle intercept routes
2094
- * - cache-store: Store results in cache
2095
- * - background-revalidation: SWR revalidation
2096
- */
2097
- async function matchPartial(
2098
- request: Request,
2099
- context: TEnv,
2100
- actionContext?: ActionContext,
2101
- ): Promise<MatchResult | null> {
2102
- // Build RouterContext with all closure functions needed by middleware
2103
- const routerCtx: RouterContext<TEnv> = {
636
+ // Create findMatch with single-entry cache, bound to router state
637
+ const findMatch = createFindMatch<TEnv>({
638
+ routesEntries,
639
+ evaluateLazyEntry,
640
+ routerId,
641
+ });
642
+
643
+ // Build a RouterContext once — shared by match, matchPartial, matchForPrerender
644
+ function buildRouterContext(): RouterContext<TEnv> {
645
+ return {
2104
646
  findMatch,
2105
647
  loadManifest,
2106
648
  traverseBack,
@@ -2120,479 +662,307 @@ export function createRouter<TEnv = any>(
2120
662
  buildEntryRevalidateMap,
2121
663
  resolveLoadersOnlyWithRevalidation,
2122
664
  resolveInterceptLoadersOnly,
665
+ resolveLoadersOnly,
666
+ telemetry: telemetrySink,
2123
667
  };
2124
-
2125
- return runWithRouterLogContext(
2126
- { request, transaction: "matchPartial" },
2127
- () =>
2128
- runWithRouterContext(routerCtx, async () =>
2129
- withRouterLogScope("matchPartial", async () => {
2130
- const ctx = await createMatchContextForPartial(
2131
- request,
2132
- context,
2133
- actionContext,
2134
- );
2135
- if (!ctx) return null;
2136
-
2137
- try {
2138
- const state = createPipelineState();
2139
- const pipeline = createMatchPartialPipeline(ctx, state);
2140
- return await collectMatchResult(pipeline, ctx, state);
2141
- } catch (error) {
2142
- if (error instanceof Response) throw error;
2143
- // Report unhandled errors during partial match pipeline
2144
- callOnError(error, actionContext ? "action" : "revalidation", {
2145
- request,
2146
- url: ctx.url,
2147
- env: context,
2148
- actionId: actionContext?.actionId,
2149
- isPartial: true,
2150
- handledByBoundary: false,
2151
- });
2152
- throw sanitizeError(error);
2153
- }
2154
- }),
2155
- ),
2156
- );
2157
668
  }
2158
669
 
2159
- /**
2160
- * Preview match - returns route middleware without segment resolution.
2161
- * Also returns responseType and handler for response routes (non-RSC short-circuit).
2162
- */
2163
- async function previewMatch(
2164
- request: Request,
2165
- _context: TEnv,
2166
- ): Promise<{
2167
- routeMiddleware?: Array<{
2168
- handler: import("./router/middleware.js").MiddlewareFn;
2169
- params: Record<string, string>;
2170
- }>;
2171
- responseType?: string;
2172
- handler?: Function;
2173
- params?: Record<string, string>;
2174
- negotiated?: boolean;
2175
- } | null> {
2176
- return runWithRouterLogContext(
2177
- { request, transaction: "previewMatch" },
2178
- async () =>
2179
- withRouterLogScope("previewMatch", async () => {
2180
- const url = new URL(request.url);
2181
- const pathname = url.pathname;
2182
-
2183
- // Quick route matching
2184
- const matched = findMatch(pathname);
2185
- if (!matched) {
2186
- return null;
2187
- }
2188
-
2189
- // Skip redirect check - will be handled in full match
2190
- if (matched.redirectTo) {
2191
- return { routeMiddleware: undefined };
2192
- }
670
+ // Prerender/static match deps (bind closure state for extracted functions)
671
+ const prerenderDeps = {
672
+ findMatch,
673
+ buildRouterContext,
674
+ mergedRouteMap,
675
+ resolveAllSegments,
676
+ };
2193
677
 
2194
- // Load manifest (without segment resolution)
2195
- const manifestEntry = await loadManifest(
2196
- matched.entry,
2197
- matched.routeKey,
678
+ async function matchForPrerender(
679
+ pathname: string,
680
+ params: Record<string, string>,
681
+ buildVars?: Record<string, any>,
682
+ isPassthroughRoute?: boolean,
683
+ buildEnv?: TEnv,
684
+ devMode?: boolean,
685
+ ) {
686
+ return _matchForPrerender(
2198
687
  pathname,
2199
- undefined, // No metrics store for preview
2200
- false, // isSSR - doesn't matter for preview
2201
- );
2202
-
2203
- // Collect route-level middleware from entry tree
2204
- // Includes middleware from orphan layouts (inline layouts within routes)
2205
- const routeMiddleware = collectRouteMiddleware(
2206
- traverseBack(manifestEntry),
2207
- matched.params,
688
+ params,
689
+ prerenderDeps,
690
+ buildVars,
691
+ isPassthroughRoute,
692
+ buildEnv,
693
+ devMode,
2208
694
  );
695
+ }
2209
696
 
2210
- // Check for response type (from trie match or manifest entry)
2211
- const responseType = matched.responseType ||
2212
- (manifestEntry.type === "route" ? manifestEntry.responseType : undefined);
2213
-
2214
- // Content negotiation: when negotiate variants exist, pick the best
2215
- // handler based on the Accept header. Uses q-values and client order
2216
- // as tiebreaker (matching Express/Hono behavior). RSC routes participate
2217
- // as text/html candidates so browsers naturally get HTML without
2218
- // special-casing.
2219
- if (matched.negotiateVariants && matched.negotiateVariants.length > 0) {
2220
- const acceptEntries = parseAcceptTypes(request.headers.get("accept") || "");
2221
-
2222
- // Build candidate list preserving definition order.
2223
- // For wildcard (*/*) and no-Accept fallback, the first candidate wins.
2224
- const variants = matched.negotiateVariants;
2225
- let candidates: Array<{ routeKey: string; responseType: string }>;
2226
- if (responseType) {
2227
- // Primary is response-type — include it as a candidate
2228
- candidates = [...variants, { routeKey: matched.routeKey, responseType }];
2229
- } else {
2230
- // Primary is RSC — insert as text/html candidate in definition order
2231
- const rscCandidate = { routeKey: matched.routeKey, responseType: RSC_RESPONSE_TYPE };
2232
- candidates = matched.rscFirst
2233
- ? [rscCandidate, ...variants]
2234
- : [...variants, rscCandidate];
2235
- }
2236
-
2237
- const variant = pickNegotiateVariant(acceptEntries, candidates);
2238
-
2239
- // If the winner is RSC, fall through to default RSC handling
2240
- if (variant.responseType === RSC_RESPONSE_TYPE) {
2241
- // Fall through — RSC won negotiation
2242
- } else if (responseType && variant.routeKey === matched.routeKey) {
2243
- // Fall through — response-type primary won, already set
2244
- } else {
2245
- const negotiateEntry = await loadManifest(
2246
- matched.entry,
2247
- variant.routeKey,
2248
- pathname,
2249
- undefined,
2250
- false,
2251
- );
2252
- return {
2253
- routeMiddleware: routeMiddleware.length > 0 ? routeMiddleware : undefined,
2254
- responseType: variant.responseType,
2255
- handler: negotiateEntry.type === "route" ? negotiateEntry.handler : undefined,
2256
- params: matched.params,
2257
- negotiated: true,
2258
- };
2259
- }
2260
- }
2261
-
2262
- // If we passed through the negotiation block (variants exist), mark as
2263
- // negotiated so the handler sets Vary: Accept on the response.
2264
- const hasVariants = matched.negotiateVariants && matched.negotiateVariants.length > 0;
2265
- return {
2266
- routeMiddleware: routeMiddleware.length > 0 ? routeMiddleware : undefined,
2267
- ...(responseType ? {
2268
- responseType,
2269
- handler: manifestEntry.type === "route" ? manifestEntry.handler : undefined,
2270
- params: matched.params,
2271
- } : {}),
2272
- ...(hasVariants ? { negotiated: true } : {}),
2273
- };
2274
- }),
697
+ async function renderStaticSegment(
698
+ handler: Function,
699
+ handlerId: string,
700
+ routeName?: string,
701
+ buildEnv?: TEnv,
702
+ devMode?: boolean,
703
+ ) {
704
+ return _renderStaticSegment<TEnv>(
705
+ handler,
706
+ handlerId,
707
+ mergedRouteMap,
708
+ routeName,
709
+ buildEnv,
710
+ devMode,
2275
711
  );
2276
712
  }
2277
713
 
2278
- /**
2279
- * Create route builder with accumulated route types
2280
- * The TNewRoutes type parameter captures the new routes being added
2281
- */
2282
- function createRouteBuilder<TNewRoutes extends Record<string, string>>(
2283
- prefix: string,
2284
- routes: TNewRoutes,
2285
- ): RouteBuilder<RouteDefinition, TEnv, any, TNewRoutes> {
2286
- const currentMountIndex = mountIndex++;
2287
-
2288
- // Merge routes into the reverse map
2289
- // Keys stay unchanged for composability - only URL patterns get prefixed
2290
- const routeEntries = routes as Record<string, string>;
2291
- for (const [key, pattern] of Object.entries(routeEntries)) {
2292
- // Build prefixed pattern: "/shop" + "/cart" -> "/shop/cart"
2293
- // Root prefix "/" is a no-op — don't double the leading slash.
2294
- const effectivePrefix = prefix === "/" ? "" : prefix;
2295
- const prefixedPattern =
2296
- effectivePrefix && pattern !== "/"
2297
- ? `${effectivePrefix}${pattern}`
2298
- : effectivePrefix && pattern === "/"
2299
- ? effectivePrefix
2300
- : pattern;
2301
-
2302
- // Runtime validation: warn if key already exists with different pattern
2303
- const existingPattern = mergedRouteMap[key];
2304
- if (
2305
- existingPattern !== undefined &&
2306
- existingPattern !== prefixedPattern
2307
- ) {
2308
- console.warn(
2309
- `[rsc-router] Route key conflict: "${key}" already maps to "${existingPattern}", ` +
2310
- `overwriting with "${prefixedPattern}". Use unique key names to avoid this.`,
2311
- );
2312
- }
2313
-
2314
- // Use original key - enables reusable route modules
2315
- mergedRouteMap[key] = prefixedPattern;
2316
- }
2317
-
2318
- // Auto-register route map for runtime reverse() usage
2319
- registerRouteMap(mergedRouteMap);
2320
-
2321
- // Extract trailing slash config if present (attached by route())
2322
- const trailingSlashConfig = (routes as any).__trailingSlash as
2323
- | Record<string, TrailingSlashMode>
2324
- | undefined;
2325
-
2326
- // Create builder object so .use() can return it
2327
- const builder: RouteBuilder<RouteDefinition, TEnv, any, TNewRoutes> = {
2328
- use(
2329
- patternOrMiddleware: string | MiddlewareFn<TEnv>,
2330
- middleware?: MiddlewareFn<TEnv>,
2331
- ) {
2332
- // Mount-scoped middleware - prefix is the mount prefix
2333
- addMiddleware(patternOrMiddleware, middleware, prefix || null);
2334
- return builder;
2335
- },
2336
-
2337
- map(
2338
- handler:
2339
- | ((
2340
- helpers: InlineRouteHelpers<TNewRoutes, TEnv>,
2341
- ) => Array<AllUseItems>)
2342
- | (() =>
2343
- | Array<AllUseItems>
2344
- | Promise<{ default: () => Array<AllUseItems> }>
2345
- | Promise<() => Array<AllUseItems>>),
2346
- ) {
2347
- // Store handler as-is - detection happens at call time based on return type
2348
- // Both patterns use the same signature:
2349
- // - Inline: ({ route }) => [...] - receives helpers, returns Array
2350
- // - Lazy: () => import(...) - ignores helpers, returns Promise
2351
- routesEntries.push({
2352
- prefix,
2353
- staticPrefix: extractStaticPrefix(prefix),
2354
- routes: routes as ResolvedRouteMap<any>,
2355
- trailingSlash: trailingSlashConfig,
2356
- handler: handler as any,
2357
- mountIndex: currentMountIndex,
2358
- });
2359
- // Return router with accumulated types
2360
- // At runtime this is the same object, but TypeScript tracks the accumulated route types
2361
- return router as any;
2362
- },
2363
-
2364
- // Expose accumulated route map for typeof extraction
2365
- get routeMap() {
2366
- return mergedRouteMap as TNewRoutes;
2367
- },
2368
- };
714
+ // Create match handler functions bound to router state
715
+ const matchHandlers = createMatchHandlers<TEnv>({
716
+ buildRouterContext,
717
+ callOnError,
718
+ matchApiDeps,
719
+ defaultErrorBoundary,
720
+ findMatch,
721
+ findInterceptForRoute,
722
+ telemetry: telemetrySink,
723
+ cacheSignalEnabled,
724
+ });
2369
725
 
2370
- return builder;
2371
- }
726
+ const { match, matchPartial, matchError, previewMatch } = matchHandlers;
2372
727
 
2373
728
  /**
2374
729
  * Router instance
2375
730
  * The type system tracks accumulated routes through the builder chain
2376
731
  * Initial TRoutes is {} (empty) to avoid poisoning accumulated types with Record<string, string>
2377
732
  */
2378
- const router: RSCRouter<TEnv, {}> = {
733
+ const router: RangoInternal<TEnv, {}> = {
2379
734
  __brand: RSC_ROUTER_BRAND,
2380
735
  id: routerId,
736
+ basename,
2381
737
 
2382
- routes(
2383
- prefixOrRoutes: string | Record<string, string> | UrlPatterns<TEnv>,
2384
- maybeRoutes?: Record<string, string>,
2385
- ): any {
2386
- // Note: Multiple .routes() calls are allowed for backwards compatibility
2387
- // with the old map() pattern. For new code, prefer urls() with include().
2388
-
2389
- // Check if argument is UrlPatterns (new Django-style API)
2390
- // Detect by checking for handler and definitions properties
2391
- if (
2392
- typeof prefixOrRoutes === "object" &&
2393
- prefixOrRoutes !== null &&
2394
- "handler" in prefixOrRoutes &&
2395
- "definitions" in prefixOrRoutes &&
2396
- typeof (prefixOrRoutes as UrlPatterns<TEnv>).handler === "function"
2397
- ) {
2398
- const urlPatterns = prefixOrRoutes as UrlPatterns<TEnv>;
2399
- // Store reference for runtime manifest generation
2400
- storedUrlPatterns = urlPatterns;
2401
- const currentMountIndex = mountIndex++;
2402
-
2403
- // Create manifest and patterns maps for route registration
2404
- const manifest = new Map<string, EntryData>();
2405
- const patterns = new Map<string, string>();
2406
- const patternsByPrefix = new Map<string, Map<string, string>>();
2407
- const trailingSlashMap = new Map<string, TrailingSlashMode>();
2408
-
2409
- // Run the handler once to extract patterns for route matching.
2410
- // Note: loadManifest will re-run the handler to register entries in its context.
2411
- // Lazy includes are detected in the return value and handled separately.
2412
- //
2413
- // Pattern extraction must use the same mountIndex and MapRootLayout root
2414
- // parent as loadManifest so that shortCodes produced here match those at
2415
- // runtime. include() captures the current parent and counters; if those
2416
- // shortCodes diverge from the runtime tree the segment reconciliation on
2417
- // the client will see a full mismatch and remount the entire page.
2418
- const syntheticMapRoot: EntryData = {
2419
- type: "layout",
2420
- id: `#synthetic-maproot-M${currentMountIndex}`,
2421
- shortCode: `M${currentMountIndex}L0`,
2422
- parent: null,
2423
- handler: MapRootLayout,
2424
- middleware: [],
2425
- revalidate: [],
2426
- errorBoundary: [],
2427
- notFoundBoundary: [],
2428
- layout: [],
2429
- parallel: [],
2430
- intercept: [],
2431
- loader: [],
2432
- };
738
+ routes(patternsOrBuilder: UrlPatterns<TEnv> | UrlBuilder<TEnv>): any {
739
+ // Wrap builder functions in urls() automatically
740
+ const urlPatterns: UrlPatterns<TEnv> =
741
+ typeof patternsOrBuilder === "function"
742
+ ? (urls(patternsOrBuilder) as UrlPatterns<TEnv>)
743
+ : patternsOrBuilder;
2433
744
 
2434
- let handlerResult: AllUseItems[] = [];
2435
- RSCRouterContext.run(
2436
- {
2437
- manifest,
2438
- patterns,
2439
- patternsByPrefix,
2440
- trailingSlash: trailingSlashMap,
2441
- namespace: "root",
2442
- parent: syntheticMapRoot,
2443
- counters: {},
2444
- mountIndex: currentMountIndex,
2445
- },
2446
- () => {
2447
- handlerResult = urlPatterns.handler() as AllUseItems[];
2448
- },
2449
- );
745
+ // Store reference for runtime manifest generation
746
+ storedUrlPatterns = urlPatterns;
747
+ const currentMountIndex = mountIndex++;
2450
748
 
2451
- // Store the ORIGINAL handler - loadManifest will re-run it to register manifest entries
2452
- // Convert trailingSlash map to object for the router
2453
- const trailingSlashConfig =
2454
- trailingSlashMap.size > 0
2455
- ? Object.fromEntries(trailingSlashMap)
2456
- : undefined;
2457
-
2458
- // Collect route keys that have prerender handlers (for non-trie match path)
2459
- let prerenderRouteKeys: Set<string> | undefined;
2460
- for (const [name, entry] of manifest.entries()) {
2461
- if (entry.type === "route" && entry.isPrerender) {
2462
- if (!prerenderRouteKeys) prerenderRouteKeys = new Set();
2463
- prerenderRouteKeys.add(name);
2464
- }
2465
- }
749
+ // Create manifest and patterns maps for route registration
750
+ const manifest = new Map<string, EntryData>();
751
+ const routePatterns = new Map<string, string>();
752
+ const patternsByPrefix = new Map<string, Map<string, string>>();
753
+ const trailingSlashMap = new Map<string, TrailingSlashMode>();
754
+
755
+ // Run the handler once to extract patterns for route matching.
756
+ // Note: loadManifest will re-run the handler to register entries in its context.
757
+ // Lazy includes are detected in the return value and handled separately.
758
+ //
759
+ // Pattern extraction must use the same mountIndex and MapRootLayout root
760
+ // parent as loadManifest so that shortCodes produced here match those at
761
+ // runtime. include() captures the current parent and counters; if those
762
+ // shortCodes diverge from the runtime tree the segment reconciliation on
763
+ // the client will see a full mismatch and remount the entire page.
764
+ const syntheticMapRoot: EntryData = {
765
+ type: "layout",
766
+ id: `#synthetic-maproot-M${currentMountIndex}`,
767
+ shortCode: `M${currentMountIndex}L0`,
768
+ parent: null,
769
+ handler: MapRootLayout,
770
+ middleware: [],
771
+ revalidate: [],
772
+ errorBoundary: [],
773
+ notFoundBoundary: [],
774
+ layout: [],
775
+ parallel: {},
776
+ intercept: [],
777
+ loader: [],
778
+ };
2466
779
 
2467
- // Create separate RouteEntry for each URL prefix group
2468
- // This enables prefix-based short-circuit optimization
2469
- if (patternsByPrefix.size > 0) {
2470
- for (const [prefix, prefixPatterns] of patternsByPrefix.entries()) {
2471
- const routesObject: Record<string, string> = {};
2472
- for (const [name, pattern] of prefixPatterns.entries()) {
2473
- routesObject[name] = pattern;
2474
- }
780
+ let handlerResult: AllUseItems[] = [];
781
+ RangoContext.run(
782
+ {
783
+ manifest,
784
+ patterns: routePatterns,
785
+ patternsByPrefix,
786
+ trailingSlash: trailingSlashMap,
787
+ namespace: "root",
788
+ parent: syntheticMapRoot,
789
+ counters: {},
790
+ mountIndex: currentMountIndex,
791
+ cacheProfiles: resolvedCacheProfiles,
792
+ // basename sets the initial URL prefix so all path() patterns
793
+ // are registered with the prefix (e.g. "/admin" + "/users" = "/admin/users").
794
+ // No namePrefix — route names stay unprefixed.
795
+ ...(basename ? { urlPrefix: basename } : {}),
796
+ },
797
+ () => {
798
+ handlerResult = urlPatterns.handler() as AllUseItems[];
799
+ },
800
+ );
2475
801
 
2476
- routesEntries.push({
2477
- // prefix is "" because patterns already include the URL prefix
2478
- // (e.g., "/site/:locale/user1/:id" not just "/user1/:id")
2479
- prefix: "",
2480
- // staticPrefix is the actual prefix for short-circuit optimization
2481
- staticPrefix: extractStaticPrefix(prefix),
2482
- routes: routesObject as ResolvedRouteMap<any>,
2483
- trailingSlash: trailingSlashConfig,
2484
- handler: urlPatterns.handler,
2485
- mountIndex: currentMountIndex,
2486
- ...(prerenderRouteKeys ? { prerenderRouteKeys } : {}),
2487
- });
802
+ // Convert trailingSlash map to object for the router
803
+ const trailingSlashConfig =
804
+ trailingSlashMap.size > 0
805
+ ? Object.fromEntries(trailingSlashMap)
806
+ : undefined;
807
+
808
+ // Collect route keys that have prerender handlers (for non-trie match path)
809
+ let prerenderRouteKeys: Set<string> | undefined;
810
+ let passthroughRouteKeys: Set<string> | undefined;
811
+ for (const [name, entry] of manifest.entries()) {
812
+ if (entry.type === "route" && entry.isPrerender) {
813
+ if (!prerenderRouteKeys) prerenderRouteKeys = new Set();
814
+ prerenderRouteKeys.add(name);
815
+ if (entry.isPassthrough === true) {
816
+ if (!passthroughRouteKeys) passthroughRouteKeys = new Set();
817
+ passthroughRouteKeys.add(name);
2488
818
  }
2489
- } else {
2490
- // Fallback: no prefix grouping, use flat patterns map
819
+ }
820
+ }
821
+
822
+ // Create separate RouteEntry for each URL prefix group
823
+ // This enables prefix-based short-circuit optimization
824
+ if (patternsByPrefix.size > 0) {
825
+ for (const [prefix, prefixPatterns] of patternsByPrefix.entries()) {
2491
826
  const routesObject: Record<string, string> = {};
2492
- for (const [name, pattern] of patterns.entries()) {
827
+ for (const [name, pattern] of prefixPatterns.entries()) {
2493
828
  routesObject[name] = pattern;
2494
829
  }
2495
830
 
2496
831
  routesEntries.push({
832
+ // prefix is "" because patterns already include the URL prefix
833
+ // (e.g., "/site/:locale/user1/:id" not just "/user1/:id")
2497
834
  prefix: "",
2498
- staticPrefix: "",
835
+ // staticPrefix is the actual prefix for short-circuit optimization
836
+ staticPrefix: extractStaticPrefix(prefix),
2499
837
  routes: routesObject as ResolvedRouteMap<any>,
2500
838
  trailingSlash: trailingSlashConfig,
2501
839
  handler: urlPatterns.handler,
2502
840
  mountIndex: currentMountIndex,
841
+ routerId,
842
+ cacheProfiles: resolvedCacheProfiles,
2503
843
  ...(prerenderRouteKeys ? { prerenderRouteKeys } : {}),
844
+ ...(passthroughRouteKeys ? { passthroughRouteKeys } : {}),
2504
845
  });
2505
846
  }
2506
-
2507
- // Build route map from registered patterns
2508
- for (const [name, pattern] of patterns.entries()) {
2509
- // Runtime validation: warn if key already exists with different pattern
2510
- const existingPattern = mergedRouteMap[name];
2511
- if (existingPattern !== undefined && existingPattern !== pattern) {
2512
- console.warn(
2513
- `[@rangojs/router] Route name conflict: "${name}" already maps to "${existingPattern}", ` +
2514
- `overwriting with "${pattern}". Use unique route names to avoid this.`,
2515
- );
2516
- }
2517
- mergedRouteMap[name] = pattern;
847
+ } else {
848
+ // Fallback: no prefix grouping, use flat patterns map
849
+ const routesObject: Record<string, string> = {};
850
+ for (const [name, pattern] of routePatterns.entries()) {
851
+ routesObject[name] = pattern;
2518
852
  }
2519
853
 
2520
- // Detect lazy includes in handler result and create placeholder entries
2521
- // Uses findLazyIncludes from outer scope (shared with evaluateLazyEntry)
2522
- const lazyIncludes = findLazyIncludes(handlerResult);
854
+ routesEntries.push({
855
+ prefix: "",
856
+ staticPrefix: "",
857
+ routes: routesObject as ResolvedRouteMap<any>,
858
+ trailingSlash: trailingSlashConfig,
859
+ handler: urlPatterns.handler,
860
+ mountIndex: currentMountIndex,
861
+ routerId,
862
+ cacheProfiles: resolvedCacheProfiles,
863
+ ...(prerenderRouteKeys ? { prerenderRouteKeys } : {}),
864
+ ...(passthroughRouteKeys ? { passthroughRouteKeys } : {}),
865
+ });
866
+ }
867
+
868
+ // Build route map from registered patterns
869
+ for (const [name, pattern] of routePatterns.entries()) {
870
+ // Runtime validation: warn if key already exists with different pattern.
871
+ // Skip warning for entries that came from the static seed — the gen file
872
+ // can be stale during HMR, so runtime registration is authoritative.
873
+ const existingPattern = mergedRouteMap[name];
874
+ if (
875
+ existingPattern !== undefined &&
876
+ existingPattern !== pattern &&
877
+ !seededNames.has(name)
878
+ ) {
879
+ console.warn(
880
+ `[@rangojs/router] Route name conflict: "${name}" already maps to "${existingPattern}", ` +
881
+ `overwriting with "${pattern}". Use unique route names to avoid this.`,
882
+ );
883
+ }
884
+ mergedRouteMap[name] = pattern;
885
+ seededNames.delete(name);
886
+ }
2523
887
 
2524
- // Create placeholder RouteEntry for each lazy include
2525
- for (const lazyInclude of lazyIncludes) {
2526
- // Compute the full URL prefix (combining parent prefix if any)
2527
- const fullPrefix = lazyInclude.context.urlPrefix
2528
- ? lazyInclude.context.urlPrefix + lazyInclude.prefix
2529
- : lazyInclude.prefix;
888
+ // Detect lazy includes in handler result and create placeholder entries
889
+ const lazyIncludes = findLazyIncludes(handlerResult);
890
+
891
+ // Create placeholder RouteEntry for each lazy include
892
+ for (const lazyInclude of lazyIncludes) {
893
+ // Compute the full URL prefix (combining parent prefix if any). Use the
894
+ // slash-collapsing join so a trailing-slash parent prefix does not
895
+ // produce a double-slash staticPrefix the trie's sp can never match.
896
+ const fullPrefix = joinPrefix(
897
+ lazyInclude.context.urlPrefix,
898
+ lazyInclude.prefix,
899
+ );
2530
900
 
2531
- const lazyEntry: RouteEntry<TEnv> & { _lazyPrefix?: string } = {
2532
- prefix: "",
2533
- staticPrefix: extractStaticPrefix(fullPrefix),
2534
- routes: {} as ResolvedRouteMap<any>, // Empty until first match
2535
- trailingSlash: trailingSlashConfig,
2536
- handler: urlPatterns.handler,
2537
- mountIndex: mountIndex++,
2538
- // Lazy evaluation fields
2539
- lazy: true,
2540
- lazyPatterns: lazyInclude.patterns,
2541
- lazyContext: lazyInclude.context,
2542
- lazyEvaluated: false,
2543
- // Store the include prefix for evaluation
2544
- _lazyPrefix: lazyInclude.prefix,
2545
- };
2546
- // Insert lazy entry before any entry whose staticPrefix is a
2547
- // prefix of (but shorter than) this lazy entry's staticPrefix.
2548
- // This ensures more specific lazy includes are matched before
2549
- // less specific eager entries (e.g., "/href/nested" before "/href/:id").
2550
- const lazyPrefix = lazyEntry.staticPrefix;
2551
- let insertIndex = routesEntries.length;
2552
- if (lazyPrefix) {
2553
- for (let i = 0; i < routesEntries.length; i++) {
2554
- const existing = routesEntries[i]!;
2555
- if (
2556
- lazyPrefix.startsWith(existing.staticPrefix) &&
2557
- lazyPrefix.length > existing.staticPrefix.length
2558
- ) {
2559
- insertIndex = i;
2560
- break;
2561
- }
901
+ const lazyEntry: RouteEntry<TEnv> & { _lazyPrefix?: string } = {
902
+ prefix: "",
903
+ staticPrefix: extractStaticPrefix(fullPrefix),
904
+ routes: {} as ResolvedRouteMap<any>, // Empty until first match
905
+ trailingSlash: trailingSlashConfig,
906
+ handler: urlPatterns.handler,
907
+ mountIndex: mountIndex++,
908
+ routerId,
909
+ // Lazy evaluation fields
910
+ lazy: true,
911
+ lazyPatterns: lazyInclude.patterns,
912
+ lazyContext: lazyInclude.context,
913
+ lazyEvaluated: false,
914
+ _lazyPrefix: lazyInclude.prefix,
915
+ };
916
+ // Insert lazy entry before any entry whose staticPrefix is a
917
+ // prefix of (but shorter than) this lazy entry's staticPrefix.
918
+ // This ensures more specific lazy includes are matched before
919
+ // less specific eager entries (e.g., "/href/nested" before "/href/:id").
920
+ const lazyPrefix = lazyEntry.staticPrefix;
921
+ let insertIndex = routesEntries.length;
922
+ if (lazyPrefix) {
923
+ for (let i = 0; i < routesEntries.length; i++) {
924
+ const existing = routesEntries[i]!;
925
+ if (
926
+ lazyPrefix.startsWith(existing.staticPrefix) &&
927
+ lazyPrefix.length > existing.staticPrefix.length
928
+ ) {
929
+ insertIndex = i;
930
+ break;
2562
931
  }
2563
932
  }
2564
- routesEntries.splice(insertIndex, 0, lazyEntry);
2565
933
  }
2566
-
2567
- // Auto-register route map for runtime reverse() usage
2568
- registerRouteMap(mergedRouteMap);
2569
-
2570
- // Return the router (no .map() needed for UrlPatterns)
2571
- return router;
934
+ routesEntries.splice(insertIndex, 0, lazyEntry);
2572
935
  }
2573
936
 
2574
- // Legacy API: route() + map() pattern
2575
- // If second argument exists, first is prefix
2576
- if (maybeRoutes !== undefined) {
2577
- return createRouteBuilder(prefixOrRoutes as string, maybeRoutes);
2578
- }
2579
- // Otherwise, first argument is routes with empty prefix
2580
- return createRouteBuilder("", prefixOrRoutes as Record<string, string>);
937
+ // Auto-register route map for runtime reverse() usage
938
+ registerRouteMap(mergedRouteMap);
939
+
940
+ return router;
2581
941
  },
2582
942
 
2583
943
  use(
2584
944
  patternOrMiddleware: string | MiddlewareFn<TEnv>,
2585
945
  middleware?: MiddlewareFn<TEnv>,
2586
946
  ): any {
2587
- // Global middleware - no mount prefix
2588
- addMiddleware(patternOrMiddleware, middleware, null);
947
+ // Auto-prefix pattern with basename so router-level middleware
948
+ // patterns are router-relative (e.g. "/users/*" matches "/app/users/*").
949
+ if (basename && typeof patternOrMiddleware === "string") {
950
+ const pattern = patternOrMiddleware;
951
+ const prefixed =
952
+ pattern === "/*" || pattern === "*"
953
+ ? `${basename}/*`
954
+ : `${basename}${pattern}`;
955
+ addMiddleware(prefixed, middleware, null);
956
+ } else {
957
+ addMiddleware(patternOrMiddleware, middleware, null);
958
+ }
2589
959
  return router;
2590
960
  },
2591
961
 
2592
962
  // Type-safe URL builder using merged route map
2593
963
  // Types are tracked through the builder chain via TRoutes parameter
2594
- // Falls back to static route names from the generated file (injected by Vite)
2595
- reverse: createReverse(mergedRouteMap, () => staticRouteNames),
964
+ // Seeded with static route names from the generated file (injected by Vite)
965
+ reverse: createReverse(mergedRouteMap),
2596
966
 
2597
967
  // Expose accumulated route map for typeof extraction
2598
968
  // Returns {} initially, but builder chain accumulates specific route types
@@ -2615,20 +985,81 @@ export function createRouter<TEnv = any>(
2615
985
  // Expose resolved theme configuration for NavigationProvider and MetaTags
2616
986
  themeConfig: resolvedThemeConfig,
2617
987
 
988
+ // Expose resolved cache profiles for per-request resolution
989
+ cacheProfiles: resolvedCacheProfiles,
990
+
991
+ // Expose prefetch cache settings
992
+ prefetchCacheControl,
993
+ prefetchCacheTTL,
994
+ prefetchCacheSize,
995
+ prefetchConcurrency,
996
+
997
+ // Expose the resolved rango state cookie name for the server-side writer
998
+ // (invalidateClientCache) and for shipping to the client in metadata.
999
+ resolvedStateCookieName,
1000
+
2618
1001
  // Expose warmup enabled flag for handler and client
2619
1002
  warmupEnabled,
2620
1003
 
2621
- // Expose debug manifest flag for handler
2622
- allowDebugManifest: allowDebugManifestOption,
1004
+ // Expose StrictMode flag for the initial-render payload metadata
1005
+ strictMode,
1006
+
1007
+ // Expose router-wide performance debugging for request-level metrics setup
1008
+ debugPerformance,
1009
+
1010
+ // Expose the PPR shell-capture debug sink for the render layer
1011
+ // (rsc-rendering resolves it into the capture descriptor)
1012
+ debugShellCapture: debugShellCaptureOption,
1013
+
1014
+ // Expose resolved span tracing for the handler (Cloudflare custom spans)
1015
+ tracing: resolvedTracing,
1016
+
1017
+ // Expose the raw telemetry sink so handler-level emitters (timeout, origin
1018
+ // rejection, late-handle handler.error) can emit outside the match ALS.
1019
+ // Raw (not the resolveSink no-op wrapper) so router.telemetry stays
1020
+ // undefined when unconfigured and call sites gate on truthiness.
1021
+ telemetry: telemetrySink,
1022
+
1023
+ // Expose origin check configuration for handler (default: enabled)
1024
+ originCheck: originCheckOption ?? true,
1025
+
1026
+ // Expose SSR configuration for handler
1027
+ ssr: ssrOption,
1028
+
1029
+ // Expose resolved timeouts for RSC handler
1030
+ timeouts: resolvedTimeouts,
1031
+ onTimeout,
2623
1032
 
2624
1033
  // Expose global middleware for RSC handler
2625
1034
  middleware: globalMiddleware,
2626
1035
 
2627
- match,
1036
+ match: (request: Request, input: RouterRequestInput<TEnv> = {}) => {
1037
+ const env = input.env ?? ({} as TEnv);
1038
+ return match(request, env);
1039
+ },
2628
1040
  matchForPrerender,
2629
- matchPartial,
2630
- matchError,
2631
- previewMatch,
1041
+ renderStaticSegment,
1042
+ matchPartial: (
1043
+ request: Request,
1044
+ input: RouterRequestInput<TEnv> = {},
1045
+ actionContext?: Parameters<typeof matchPartial>[2],
1046
+ ) => {
1047
+ const env = input.env ?? ({} as TEnv);
1048
+ return matchPartial(request, env, actionContext);
1049
+ },
1050
+ matchError: (
1051
+ request: Request,
1052
+ input: RouterRequestInput<TEnv> | undefined,
1053
+ error: unknown,
1054
+ segmentType?: Parameters<typeof matchError>[3],
1055
+ ) => {
1056
+ const env = input?.env ?? ({} as TEnv);
1057
+ return matchError(request, env, error, segmentType);
1058
+ },
1059
+ previewMatch: (request: Request, input: RouterRequestInput<TEnv> = {}) => {
1060
+ const env = input.env ?? ({} as TEnv);
1061
+ return previewMatch(request, env);
1062
+ },
2632
1063
 
2633
1064
  // Expose nonce provider for fetch
2634
1065
  nonce,
@@ -2644,93 +1075,67 @@ export function createRouter<TEnv = any>(
2644
1075
  // Expose source file for per-router type generation
2645
1076
  __sourceFile,
2646
1077
 
1078
+ // Expose basename for runtime manifest generation
1079
+ __basename: basename,
1080
+
1081
+ // Expose router-level boundary defaults for build-time clientChunks
1082
+ // discovery (so a "use client" default boundary lands in app-fallback).
1083
+ // These are createRouter options, never pushed onto EntryData.
1084
+ __defaultErrorBoundary: defaultErrorBoundary,
1085
+ __defaultNotFoundBoundary: defaultNotFoundBoundary,
1086
+ __notFound: notFound,
1087
+
2647
1088
  // RSC request handler (lazily created on first call)
2648
1089
  fetch: (() => {
2649
1090
  // Handler is created on first call and reused
2650
1091
  let handler:
2651
1092
  | ((
2652
1093
  request: Request,
2653
- env: TEnv & { ctx?: ExecutionContext },
1094
+ input: RouterRequestInput<TEnv>,
2654
1095
  ) => Promise<Response>)
2655
1096
  | null = null;
2656
1097
 
2657
- return async (
2658
- request: Request,
2659
- env: TEnv & { ctx?: ExecutionContext },
2660
- ) => {
1098
+ return async (request: Request, input: RouterRequestInput<TEnv> = {}) => {
2661
1099
  // Trigger lazy import of per-router manifest data before route matching.
2662
1100
  // No-op if data is already loaded or no loader is registered.
2663
1101
  await ensureRouterManifest(routerId);
2664
1102
  if (!handler) {
2665
1103
  // Lazy import deferred to first request to avoid dev mode issues
2666
1104
  const { createRSCHandler } = await import("./rsc/handler.js");
1105
+ // Cast: createRSCHandler receives `router as any`, which erases TEnv
1106
+ // and infers its handler as RouterRequestInput<unknown>. Re-narrow the
1107
+ // returned handler to RouterRequestInput<TEnv> so the call below stays
1108
+ // typed. (The handler already accepts (request, RouterRequestInput).)
2667
1109
  handler = createRSCHandler({
2668
1110
  router: router as any,
2669
1111
  cache,
2670
1112
  nonce,
2671
1113
  version,
2672
- });
1114
+ }) as (
1115
+ request: Request,
1116
+ input: RouterRequestInput<TEnv>,
1117
+ ) => Promise<Response>;
2673
1118
  }
2674
- return handler(request, env);
1119
+ return handler!(request, input);
2675
1120
  };
2676
1121
  })(),
2677
1122
 
2678
- // Debug utility for manifest inspection
2679
- async debugManifest(): Promise<SerializedManifest> {
2680
- const manifest = new Map<string, EntryData>();
2681
-
2682
- for (const entry of routesEntries) {
2683
- const Store = {
2684
- manifest,
2685
- namespace: `debug.M${entry.mountIndex}`,
2686
- parent: null as EntryData | null,
2687
- counters: {} as Record<string, number>,
2688
- mountIndex: entry.mountIndex,
2689
- patterns: new Map<string, string>(),
2690
- trailingSlash: new Map<string, TrailingSlashMode>(),
2691
- };
2692
-
2693
- await getContext().runWithStore(
2694
- Store,
2695
- `debug.M${entry.mountIndex}`,
2696
- null,
2697
- async () => {
2698
- const helpers = createRouteHelpers();
2699
-
2700
- // Wrap handler execution in root layout (same as loadManifest)
2701
- let promiseResult: Promise<any> | null = null;
2702
- helpers.layout(MapRootLayout, () => {
2703
- const result = entry.handler();
2704
- if (result instanceof Promise) {
2705
- promiseResult = result;
2706
- return [];
2707
- }
2708
- return result;
2709
- });
2710
-
2711
- if (promiseResult !== null) {
2712
- const load = await (promiseResult as Promise<any>);
2713
- if (load && typeof load === "object" && "default" in load) {
2714
- const useItems = load.default;
2715
- if (typeof useItems === "function") {
2716
- useItems(helpers);
2717
- }
2718
- }
2719
- }
2720
- },
2721
- );
2722
- }
1123
+ // Low-level route matching for request classification
1124
+ findMatch: (pathname: string, metricsStore?: any) =>
1125
+ findMatch(pathname, metricsStore),
2723
1126
 
2724
- return serializeManifest(manifest);
2725
- },
1127
+ // Debug utility for manifest inspection
1128
+ debugManifest: () => buildDebugManifest<TEnv>(routesEntries),
2726
1129
  };
2727
1130
 
2728
1131
  // Register router in the global registry for build-time discovery
2729
1132
  RouterRegistry.set(routerId, router);
2730
1133
 
2731
1134
  // If urls option was provided, auto-register them
2732
- if (urlsOption) {
2733
- return router.routes(urlsOption) as RSCRouter<TEnv, {}>;
1135
+ if (typeof urlsOption === "function") {
1136
+ return router.routes(urlsOption) as Rango<TEnv, {}>;
1137
+ } else if (urlsOption) {
1138
+ return router.routes(urlsOption) as Rango<TEnv, {}>;
2734
1139
  }
2735
1140
 
2736
1141
  return router;