@benjavicente/router-core 1.168.9

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 (380) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +5 -0
  3. package/bin/intent.js +25 -0
  4. package/dist/cjs/Matches.cjs +17 -0
  5. package/dist/cjs/Matches.cjs.map +1 -0
  6. package/dist/cjs/Matches.d.cts +139 -0
  7. package/dist/cjs/RouterProvider.d.cts +27 -0
  8. package/dist/cjs/config.cjs +11 -0
  9. package/dist/cjs/config.cjs.map +1 -0
  10. package/dist/cjs/config.d.cts +17 -0
  11. package/dist/cjs/defer.cjs +41 -0
  12. package/dist/cjs/defer.cjs.map +1 -0
  13. package/dist/cjs/defer.d.cts +37 -0
  14. package/dist/cjs/fileRoute.d.cts +24 -0
  15. package/dist/cjs/global.d.cts +7 -0
  16. package/dist/cjs/hash-scroll.cjs +20 -0
  17. package/dist/cjs/hash-scroll.cjs.map +1 -0
  18. package/dist/cjs/hash-scroll.d.cts +7 -0
  19. package/dist/cjs/history.d.cts +8 -0
  20. package/dist/cjs/index.cjs +96 -0
  21. package/dist/cjs/index.d.cts +53 -0
  22. package/dist/cjs/invariant.cjs +8 -0
  23. package/dist/cjs/invariant.cjs.map +1 -0
  24. package/dist/cjs/invariant.d.cts +1 -0
  25. package/dist/cjs/isServer/client.cjs +7 -0
  26. package/dist/cjs/isServer/client.cjs.map +1 -0
  27. package/dist/cjs/isServer/client.d.cts +1 -0
  28. package/dist/cjs/isServer/development.cjs +7 -0
  29. package/dist/cjs/isServer/development.cjs.map +1 -0
  30. package/dist/cjs/isServer/development.d.cts +1 -0
  31. package/dist/cjs/isServer/server.cjs +7 -0
  32. package/dist/cjs/isServer/server.cjs.map +1 -0
  33. package/dist/cjs/isServer/server.d.cts +1 -0
  34. package/dist/cjs/link.cjs +6 -0
  35. package/dist/cjs/link.cjs.map +1 -0
  36. package/dist/cjs/link.d.cts +221 -0
  37. package/dist/cjs/load-matches.cjs +659 -0
  38. package/dist/cjs/load-matches.cjs.map +1 -0
  39. package/dist/cjs/load-matches.d.cts +18 -0
  40. package/dist/cjs/location.d.cts +50 -0
  41. package/dist/cjs/lru-cache.cjs +70 -0
  42. package/dist/cjs/lru-cache.cjs.map +1 -0
  43. package/dist/cjs/lru-cache.d.cts +6 -0
  44. package/dist/cjs/manifest.cjs +18 -0
  45. package/dist/cjs/manifest.cjs.map +1 -0
  46. package/dist/cjs/manifest.d.cts +35 -0
  47. package/dist/cjs/new-process-route-tree.cjs +754 -0
  48. package/dist/cjs/new-process-route-tree.cjs.map +1 -0
  49. package/dist/cjs/new-process-route-tree.d.cts +236 -0
  50. package/dist/cjs/not-found.cjs +26 -0
  51. package/dist/cjs/not-found.cjs.map +1 -0
  52. package/dist/cjs/not-found.d.cts +32 -0
  53. package/dist/cjs/path.cjs +252 -0
  54. package/dist/cjs/path.cjs.map +1 -0
  55. package/dist/cjs/path.d.cts +56 -0
  56. package/dist/cjs/qss.cjs +70 -0
  57. package/dist/cjs/qss.cjs.map +1 -0
  58. package/dist/cjs/qss.d.cts +33 -0
  59. package/dist/cjs/redirect.cjs +56 -0
  60. package/dist/cjs/redirect.cjs.map +1 -0
  61. package/dist/cjs/redirect.d.cts +77 -0
  62. package/dist/cjs/rewrite.cjs +68 -0
  63. package/dist/cjs/rewrite.cjs.map +1 -0
  64. package/dist/cjs/rewrite.d.cts +30 -0
  65. package/dist/cjs/root.cjs +7 -0
  66. package/dist/cjs/root.cjs.map +1 -0
  67. package/dist/cjs/root.d.cts +3 -0
  68. package/dist/cjs/route.cjs +100 -0
  69. package/dist/cjs/route.cjs.map +1 -0
  70. package/dist/cjs/route.d.cts +552 -0
  71. package/dist/cjs/routeInfo.d.cts +54 -0
  72. package/dist/cjs/router.cjs +1173 -0
  73. package/dist/cjs/router.cjs.map +1 -0
  74. package/dist/cjs/router.d.cts +734 -0
  75. package/dist/cjs/scroll-restoration-inline.cjs +6 -0
  76. package/dist/cjs/scroll-restoration-inline.cjs.map +1 -0
  77. package/dist/cjs/scroll-restoration-inline.d.cts +6 -0
  78. package/dist/cjs/scroll-restoration-script/client.cjs +9 -0
  79. package/dist/cjs/scroll-restoration-script/client.cjs.map +1 -0
  80. package/dist/cjs/scroll-restoration-script/client.d.cts +2 -0
  81. package/dist/cjs/scroll-restoration-script/server.cjs +30 -0
  82. package/dist/cjs/scroll-restoration-script/server.cjs.map +1 -0
  83. package/dist/cjs/scroll-restoration-script/server.d.cts +2 -0
  84. package/dist/cjs/scroll-restoration.cjs +191 -0
  85. package/dist/cjs/scroll-restoration.cjs.map +1 -0
  86. package/dist/cjs/scroll-restoration.d.cts +38 -0
  87. package/dist/cjs/searchMiddleware.cjs +55 -0
  88. package/dist/cjs/searchMiddleware.cjs.map +1 -0
  89. package/dist/cjs/searchMiddleware.d.cts +25 -0
  90. package/dist/cjs/searchParams.cjs +65 -0
  91. package/dist/cjs/searchParams.cjs.map +1 -0
  92. package/dist/cjs/searchParams.d.cts +31 -0
  93. package/dist/cjs/ssr/client.cjs +7 -0
  94. package/dist/cjs/ssr/client.d.cts +6 -0
  95. package/dist/cjs/ssr/constants.cjs +8 -0
  96. package/dist/cjs/ssr/constants.cjs.map +1 -0
  97. package/dist/cjs/ssr/constants.d.cts +3 -0
  98. package/dist/cjs/ssr/createRequestHandler.cjs +44 -0
  99. package/dist/cjs/ssr/createRequestHandler.cjs.map +1 -0
  100. package/dist/cjs/ssr/createRequestHandler.d.cts +9 -0
  101. package/dist/cjs/ssr/handlerCallback.cjs +8 -0
  102. package/dist/cjs/ssr/handlerCallback.cjs.map +1 -0
  103. package/dist/cjs/ssr/handlerCallback.d.cts +9 -0
  104. package/dist/cjs/ssr/headers.cjs +21 -0
  105. package/dist/cjs/ssr/headers.cjs.map +1 -0
  106. package/dist/cjs/ssr/headers.d.cts +3 -0
  107. package/dist/cjs/ssr/json.cjs +11 -0
  108. package/dist/cjs/ssr/json.cjs.map +1 -0
  109. package/dist/cjs/ssr/json.d.cts +10 -0
  110. package/dist/cjs/ssr/serializer/RawStream.cjs +287 -0
  111. package/dist/cjs/ssr/serializer/RawStream.cjs.map +1 -0
  112. package/dist/cjs/ssr/serializer/RawStream.d.cts +64 -0
  113. package/dist/cjs/ssr/serializer/ShallowErrorPlugin.cjs +32 -0
  114. package/dist/cjs/ssr/serializer/ShallowErrorPlugin.cjs.map +1 -0
  115. package/dist/cjs/ssr/serializer/ShallowErrorPlugin.d.cts +9 -0
  116. package/dist/cjs/ssr/serializer/seroval-plugins.cjs +13 -0
  117. package/dist/cjs/ssr/serializer/seroval-plugins.cjs.map +1 -0
  118. package/dist/cjs/ssr/serializer/seroval-plugins.d.cts +2 -0
  119. package/dist/cjs/ssr/serializer/transformer.cjs +53 -0
  120. package/dist/cjs/ssr/serializer/transformer.cjs.map +1 -0
  121. package/dist/cjs/ssr/serializer/transformer.d.cts +91 -0
  122. package/dist/cjs/ssr/server.cjs +13 -0
  123. package/dist/cjs/ssr/server.d.cts +6 -0
  124. package/dist/cjs/ssr/ssr-client.cjs +183 -0
  125. package/dist/cjs/ssr/ssr-client.cjs.map +1 -0
  126. package/dist/cjs/ssr/ssr-client.d.cts +10 -0
  127. package/dist/cjs/ssr/ssr-match-id.cjs +12 -0
  128. package/dist/cjs/ssr/ssr-match-id.cjs.map +1 -0
  129. package/dist/cjs/ssr/ssr-match-id.d.cts +2 -0
  130. package/dist/cjs/ssr/ssr-server.cjs +279 -0
  131. package/dist/cjs/ssr/ssr-server.cjs.map +1 -0
  132. package/dist/cjs/ssr/ssr-server.d.cts +42 -0
  133. package/dist/cjs/ssr/transformStreamWithRouter.cjs +327 -0
  134. package/dist/cjs/ssr/transformStreamWithRouter.cjs.map +1 -0
  135. package/dist/cjs/ssr/transformStreamWithRouter.d.cts +11 -0
  136. package/dist/cjs/ssr/tsrScript.cjs +6 -0
  137. package/dist/cjs/ssr/tsrScript.cjs.map +1 -0
  138. package/dist/cjs/ssr/tsrScript.d.cts +1 -0
  139. package/dist/cjs/ssr/types.d.cts +30 -0
  140. package/dist/cjs/stores.cjs +148 -0
  141. package/dist/cjs/stores.cjs.map +1 -0
  142. package/dist/cjs/stores.d.cts +70 -0
  143. package/dist/cjs/structuralSharing.d.cts +4 -0
  144. package/dist/cjs/typePrimitives.d.cts +65 -0
  145. package/dist/cjs/useLoaderData.d.cts +5 -0
  146. package/dist/cjs/useLoaderDeps.d.cts +5 -0
  147. package/dist/cjs/useNavigate.d.cts +3 -0
  148. package/dist/cjs/useParams.d.cts +5 -0
  149. package/dist/cjs/useRouteContext.d.cts +9 -0
  150. package/dist/cjs/useSearch.d.cts +5 -0
  151. package/dist/cjs/utils.cjs +339 -0
  152. package/dist/cjs/utils.cjs.map +1 -0
  153. package/dist/cjs/utils.d.cts +178 -0
  154. package/dist/cjs/validators.d.cts +51 -0
  155. package/dist/esm/Matches.d.ts +139 -0
  156. package/dist/esm/Matches.js +17 -0
  157. package/dist/esm/Matches.js.map +1 -0
  158. package/dist/esm/RouterProvider.d.ts +27 -0
  159. package/dist/esm/config.d.ts +17 -0
  160. package/dist/esm/config.js +11 -0
  161. package/dist/esm/config.js.map +1 -0
  162. package/dist/esm/defer.d.ts +37 -0
  163. package/dist/esm/defer.js +40 -0
  164. package/dist/esm/defer.js.map +1 -0
  165. package/dist/esm/fileRoute.d.ts +24 -0
  166. package/dist/esm/global.d.ts +7 -0
  167. package/dist/esm/hash-scroll.d.ts +7 -0
  168. package/dist/esm/hash-scroll.js +20 -0
  169. package/dist/esm/hash-scroll.js.map +1 -0
  170. package/dist/esm/history.d.ts +8 -0
  171. package/dist/esm/index.d.ts +53 -0
  172. package/dist/esm/index.js +24 -0
  173. package/dist/esm/invariant.d.ts +1 -0
  174. package/dist/esm/invariant.js +8 -0
  175. package/dist/esm/invariant.js.map +1 -0
  176. package/dist/esm/isServer/client.d.ts +1 -0
  177. package/dist/esm/isServer/client.js +6 -0
  178. package/dist/esm/isServer/client.js.map +1 -0
  179. package/dist/esm/isServer/development.d.ts +1 -0
  180. package/dist/esm/isServer/development.js +6 -0
  181. package/dist/esm/isServer/development.js.map +1 -0
  182. package/dist/esm/isServer/server.d.ts +1 -0
  183. package/dist/esm/isServer/server.js +6 -0
  184. package/dist/esm/isServer/server.js.map +1 -0
  185. package/dist/esm/link.d.ts +221 -0
  186. package/dist/esm/link.js +6 -0
  187. package/dist/esm/link.js.map +1 -0
  188. package/dist/esm/load-matches.d.ts +18 -0
  189. package/dist/esm/load-matches.js +657 -0
  190. package/dist/esm/load-matches.js.map +1 -0
  191. package/dist/esm/location.d.ts +50 -0
  192. package/dist/esm/lru-cache.d.ts +6 -0
  193. package/dist/esm/lru-cache.js +70 -0
  194. package/dist/esm/lru-cache.js.map +1 -0
  195. package/dist/esm/manifest.d.ts +35 -0
  196. package/dist/esm/manifest.js +17 -0
  197. package/dist/esm/manifest.js.map +1 -0
  198. package/dist/esm/new-process-route-tree.d.ts +236 -0
  199. package/dist/esm/new-process-route-tree.js +749 -0
  200. package/dist/esm/new-process-route-tree.js.map +1 -0
  201. package/dist/esm/not-found.d.ts +32 -0
  202. package/dist/esm/not-found.js +25 -0
  203. package/dist/esm/not-found.js.map +1 -0
  204. package/dist/esm/path.d.ts +56 -0
  205. package/dist/esm/path.js +243 -0
  206. package/dist/esm/path.js.map +1 -0
  207. package/dist/esm/qss.d.ts +33 -0
  208. package/dist/esm/qss.js +69 -0
  209. package/dist/esm/qss.js.map +1 -0
  210. package/dist/esm/redirect.d.ts +77 -0
  211. package/dist/esm/redirect.js +53 -0
  212. package/dist/esm/redirect.js.map +1 -0
  213. package/dist/esm/rewrite.d.ts +30 -0
  214. package/dist/esm/rewrite.js +65 -0
  215. package/dist/esm/rewrite.js.map +1 -0
  216. package/dist/esm/root.d.ts +3 -0
  217. package/dist/esm/root.js +7 -0
  218. package/dist/esm/root.js.map +1 -0
  219. package/dist/esm/route.d.ts +552 -0
  220. package/dist/esm/route.js +98 -0
  221. package/dist/esm/route.js.map +1 -0
  222. package/dist/esm/routeInfo.d.ts +54 -0
  223. package/dist/esm/router.d.ts +734 -0
  224. package/dist/esm/router.js +1165 -0
  225. package/dist/esm/router.js.map +1 -0
  226. package/dist/esm/scroll-restoration-inline.d.ts +6 -0
  227. package/dist/esm/scroll-restoration-inline.js +6 -0
  228. package/dist/esm/scroll-restoration-inline.js.map +1 -0
  229. package/dist/esm/scroll-restoration-script/client.d.ts +2 -0
  230. package/dist/esm/scroll-restoration-script/client.js +8 -0
  231. package/dist/esm/scroll-restoration-script/client.js.map +1 -0
  232. package/dist/esm/scroll-restoration-script/server.d.ts +2 -0
  233. package/dist/esm/scroll-restoration-script/server.js +29 -0
  234. package/dist/esm/scroll-restoration-script/server.js.map +1 -0
  235. package/dist/esm/scroll-restoration.d.ts +38 -0
  236. package/dist/esm/scroll-restoration.js +187 -0
  237. package/dist/esm/scroll-restoration.js.map +1 -0
  238. package/dist/esm/searchMiddleware.d.ts +25 -0
  239. package/dist/esm/searchMiddleware.js +54 -0
  240. package/dist/esm/searchMiddleware.js.map +1 -0
  241. package/dist/esm/searchParams.d.ts +31 -0
  242. package/dist/esm/searchParams.js +62 -0
  243. package/dist/esm/searchParams.js.map +1 -0
  244. package/dist/esm/ssr/client.d.ts +6 -0
  245. package/dist/esm/ssr/client.js +4 -0
  246. package/dist/esm/ssr/constants.d.ts +3 -0
  247. package/dist/esm/ssr/constants.js +7 -0
  248. package/dist/esm/ssr/constants.js.map +1 -0
  249. package/dist/esm/ssr/createRequestHandler.d.ts +9 -0
  250. package/dist/esm/ssr/createRequestHandler.js +44 -0
  251. package/dist/esm/ssr/createRequestHandler.js.map +1 -0
  252. package/dist/esm/ssr/handlerCallback.d.ts +9 -0
  253. package/dist/esm/ssr/handlerCallback.js +8 -0
  254. package/dist/esm/ssr/handlerCallback.js.map +1 -0
  255. package/dist/esm/ssr/headers.d.ts +3 -0
  256. package/dist/esm/ssr/headers.js +21 -0
  257. package/dist/esm/ssr/headers.js.map +1 -0
  258. package/dist/esm/ssr/json.d.ts +10 -0
  259. package/dist/esm/ssr/json.js +11 -0
  260. package/dist/esm/ssr/json.js.map +1 -0
  261. package/dist/esm/ssr/serializer/RawStream.d.ts +64 -0
  262. package/dist/esm/ssr/serializer/RawStream.js +282 -0
  263. package/dist/esm/ssr/serializer/RawStream.js.map +1 -0
  264. package/dist/esm/ssr/serializer/ShallowErrorPlugin.d.ts +9 -0
  265. package/dist/esm/ssr/serializer/ShallowErrorPlugin.js +33 -0
  266. package/dist/esm/ssr/serializer/ShallowErrorPlugin.js.map +1 -0
  267. package/dist/esm/ssr/serializer/seroval-plugins.d.ts +2 -0
  268. package/dist/esm/ssr/serializer/seroval-plugins.js +13 -0
  269. package/dist/esm/ssr/serializer/seroval-plugins.js.map +1 -0
  270. package/dist/esm/ssr/serializer/transformer.d.ts +91 -0
  271. package/dist/esm/ssr/serializer/transformer.js +51 -0
  272. package/dist/esm/ssr/serializer/transformer.js.map +1 -0
  273. package/dist/esm/ssr/server.d.ts +6 -0
  274. package/dist/esm/ssr/server.js +5 -0
  275. package/dist/esm/ssr/ssr-client.d.ts +10 -0
  276. package/dist/esm/ssr/ssr-client.js +183 -0
  277. package/dist/esm/ssr/ssr-client.js.map +1 -0
  278. package/dist/esm/ssr/ssr-match-id.d.ts +2 -0
  279. package/dist/esm/ssr/ssr-match-id.js +11 -0
  280. package/dist/esm/ssr/ssr-match-id.js.map +1 -0
  281. package/dist/esm/ssr/ssr-server.d.ts +42 -0
  282. package/dist/esm/ssr/ssr-server.js +277 -0
  283. package/dist/esm/ssr/ssr-server.js.map +1 -0
  284. package/dist/esm/ssr/transformStreamWithRouter.d.ts +11 -0
  285. package/dist/esm/ssr/transformStreamWithRouter.js +325 -0
  286. package/dist/esm/ssr/transformStreamWithRouter.js.map +1 -0
  287. package/dist/esm/ssr/tsrScript.d.ts +0 -0
  288. package/dist/esm/ssr/tsrScript.js +6 -0
  289. package/dist/esm/ssr/tsrScript.js.map +1 -0
  290. package/dist/esm/ssr/types.d.ts +30 -0
  291. package/dist/esm/stores.d.ts +70 -0
  292. package/dist/esm/stores.js +146 -0
  293. package/dist/esm/stores.js.map +1 -0
  294. package/dist/esm/structuralSharing.d.ts +4 -0
  295. package/dist/esm/typePrimitives.d.ts +65 -0
  296. package/dist/esm/useLoaderData.d.ts +5 -0
  297. package/dist/esm/useLoaderDeps.d.ts +5 -0
  298. package/dist/esm/useNavigate.d.ts +3 -0
  299. package/dist/esm/useParams.d.ts +5 -0
  300. package/dist/esm/useRouteContext.d.ts +9 -0
  301. package/dist/esm/useSearch.d.ts +5 -0
  302. package/dist/esm/utils.d.ts +178 -0
  303. package/dist/esm/utils.js +322 -0
  304. package/dist/esm/utils.js.map +1 -0
  305. package/dist/esm/validators.d.ts +51 -0
  306. package/package.json +200 -0
  307. package/skills/router-core/SKILL.md +139 -0
  308. package/skills/router-core/auth-and-guards/SKILL.md +458 -0
  309. package/skills/router-core/code-splitting/SKILL.md +322 -0
  310. package/skills/router-core/data-loading/SKILL.md +485 -0
  311. package/skills/router-core/navigation/SKILL.md +448 -0
  312. package/skills/router-core/not-found-and-errors/SKILL.md +435 -0
  313. package/skills/router-core/path-params/SKILL.md +382 -0
  314. package/skills/router-core/search-params/SKILL.md +349 -0
  315. package/skills/router-core/search-params/references/validation-patterns.md +379 -0
  316. package/skills/router-core/ssr/SKILL.md +437 -0
  317. package/skills/router-core/type-safety/SKILL.md +497 -0
  318. package/src/Matches.ts +291 -0
  319. package/src/RouterProvider.ts +47 -0
  320. package/src/config.ts +42 -0
  321. package/src/defer.ts +69 -0
  322. package/src/fileRoute.ts +164 -0
  323. package/src/global.ts +9 -0
  324. package/src/hash-scroll.ts +21 -0
  325. package/src/history.ts +9 -0
  326. package/src/index.ts +471 -0
  327. package/src/invariant.ts +3 -0
  328. package/src/isServer/client.ts +1 -0
  329. package/src/isServer/development.ts +2 -0
  330. package/src/isServer/server.ts +1 -0
  331. package/src/link.ts +704 -0
  332. package/src/load-matches.ts +1281 -0
  333. package/src/location.ts +51 -0
  334. package/src/lru-cache.ts +74 -0
  335. package/src/manifest.ts +68 -0
  336. package/src/new-process-route-tree.ts +1387 -0
  337. package/src/not-found.ts +41 -0
  338. package/src/path.ts +436 -0
  339. package/src/qss.ts +81 -0
  340. package/src/redirect.ts +179 -0
  341. package/src/rewrite.ts +93 -0
  342. package/src/root.ts +3 -0
  343. package/src/route.ts +2235 -0
  344. package/src/routeInfo.ts +235 -0
  345. package/src/router.ts +3207 -0
  346. package/src/scroll-restoration-inline.ts +81 -0
  347. package/src/scroll-restoration-script/client.ts +5 -0
  348. package/src/scroll-restoration-script/server.ts +64 -0
  349. package/src/scroll-restoration.ts +357 -0
  350. package/src/searchMiddleware.ts +76 -0
  351. package/src/searchParams.ts +90 -0
  352. package/src/ssr/client.ts +6 -0
  353. package/src/ssr/constants.ts +3 -0
  354. package/src/ssr/createRequestHandler.ts +98 -0
  355. package/src/ssr/handlerCallback.ts +15 -0
  356. package/src/ssr/headers.ts +40 -0
  357. package/src/ssr/json.ts +16 -0
  358. package/src/ssr/serializer/RawStream.ts +464 -0
  359. package/src/ssr/serializer/ShallowErrorPlugin.ts +43 -0
  360. package/src/ssr/serializer/seroval-plugins.ts +12 -0
  361. package/src/ssr/serializer/transformer.ts +312 -0
  362. package/src/ssr/server.ts +14 -0
  363. package/src/ssr/ssr-client.ts +313 -0
  364. package/src/ssr/ssr-match-id.ts +7 -0
  365. package/src/ssr/ssr-server.ts +425 -0
  366. package/src/ssr/transformStreamWithRouter.ts +493 -0
  367. package/src/ssr/tsrScript.ts +20 -0
  368. package/src/ssr/types.ts +41 -0
  369. package/src/stores.ts +342 -0
  370. package/src/structuralSharing.ts +7 -0
  371. package/src/typePrimitives.ts +181 -0
  372. package/src/useLoaderData.ts +20 -0
  373. package/src/useLoaderDeps.ts +13 -0
  374. package/src/useNavigate.ts +13 -0
  375. package/src/useParams.ts +20 -0
  376. package/src/useRouteContext.ts +39 -0
  377. package/src/useSearch.ts +20 -0
  378. package/src/utils.ts +708 -0
  379. package/src/validators.ts +121 -0
  380. package/src/vite-env.d.ts +4 -0
package/src/router.ts ADDED
@@ -0,0 +1,3207 @@
1
+ import { createBrowserHistory, parseHref } from '@benjavicente/history'
2
+ import { isServer } from '@benjavicente/router-core/isServer'
3
+ import {
4
+ DEFAULT_PROTOCOL_ALLOWLIST,
5
+ createControlledPromise,
6
+ decodePath,
7
+ deepEqual,
8
+ encodePathLikeUrl,
9
+ findLast,
10
+ functionalUpdate,
11
+ isDangerousProtocol,
12
+ last,
13
+ nullReplaceEqualDeep,
14
+ replaceEqualDeep,
15
+ } from './utils'
16
+ import {
17
+ findFlatMatch,
18
+ findRouteMatch,
19
+ findSingleMatch,
20
+ processRouteMasks,
21
+ processRouteTree,
22
+ } from './new-process-route-tree'
23
+ import {
24
+ cleanPath,
25
+ compileDecodeCharMap,
26
+ interpolatePath,
27
+ resolvePath,
28
+ trimPath,
29
+ trimPathRight,
30
+ } from './path'
31
+ import { createLRUCache } from './lru-cache'
32
+ import { isNotFound } from './not-found'
33
+ import { setupScrollRestoration } from './scroll-restoration'
34
+ import { defaultParseSearch, defaultStringifySearch } from './searchParams'
35
+ import { rootRouteId } from './root'
36
+ import { isRedirect, redirect } from './redirect'
37
+ import { loadMatches, loadRouteChunk, routeNeedsPreload } from './load-matches'
38
+ import {
39
+ composeRewrites,
40
+ executeRewriteInput,
41
+ executeRewriteOutput,
42
+ rewriteBasepath,
43
+ } from './rewrite'
44
+ import { createRouterStores } from './stores'
45
+ import type { LRUCache } from './lru-cache'
46
+ import type {
47
+ ProcessRouteTreeResult,
48
+ ProcessedTree,
49
+ } from './new-process-route-tree'
50
+ import type { SearchParser, SearchSerializer } from './searchParams'
51
+ import type { AnyRedirect, ResolvedRedirect } from './redirect'
52
+ import type {
53
+ HistoryLocation,
54
+ HistoryState,
55
+ ParsedHistoryState,
56
+ RouterHistory,
57
+ } from '@benjavicente/history'
58
+
59
+ import type {
60
+ Awaitable,
61
+ Constrain,
62
+ ControlledPromise,
63
+ NoInfer,
64
+ NonNullableUpdater,
65
+ PickAsRequired,
66
+ Updater,
67
+ } from './utils'
68
+ import type { ParsedLocation } from './location'
69
+ import type {
70
+ AnyContext,
71
+ AnyRoute,
72
+ AnyRouteWithContext,
73
+ LoaderStaleReloadMode,
74
+ MakeRemountDepsOptionsUnion,
75
+ RouteContextOptions,
76
+ RouteLike,
77
+ RouteMask,
78
+ SearchMiddleware,
79
+ } from './route'
80
+ import type {
81
+ FullSearchSchema,
82
+ RouteById,
83
+ RoutePaths,
84
+ RoutesById,
85
+ RoutesByPath,
86
+ } from './routeInfo'
87
+ import type {
88
+ AnyRouteMatch,
89
+ MakeRouteMatch,
90
+ MakeRouteMatchUnion,
91
+ MatchRouteOptions,
92
+ } from './Matches'
93
+ import type {
94
+ BuildLocationFn,
95
+ CommitLocationOptions,
96
+ NavigateFn,
97
+ } from './RouterProvider'
98
+ import type { Manifest, RouterManagedTag } from './manifest'
99
+ import type { AnySchema, AnyValidator } from './validators'
100
+ import type { NavigateOptions, ResolveRelativePath, ToOptions } from './link'
101
+ import type { NotFoundError } from './not-found'
102
+ import type {
103
+ AnySerializationAdapter,
104
+ ValidateSerializableInput,
105
+ } from './ssr/serializer/transformer'
106
+ import type { GetStoreConfig, RouterStores } from './stores'
107
+
108
+ export type ControllablePromise<T = any> = Promise<T> & {
109
+ resolve: (value: T) => void
110
+ reject: (value?: any) => void
111
+ }
112
+
113
+ export type InjectedHtmlEntry = Promise<string>
114
+
115
+ export interface Register {
116
+ // Lots of things on here like...
117
+ // router
118
+ // config
119
+ // ssr
120
+ }
121
+
122
+ export type RegisteredSsr<TRegister = Register> = TRegister extends {
123
+ ssr: infer TSSR
124
+ }
125
+ ? TSSR
126
+ : false
127
+
128
+ export type RegisteredRouter<TRegister = Register> = TRegister extends {
129
+ router: infer TRouter
130
+ }
131
+ ? TRouter
132
+ : AnyRouter
133
+
134
+ export type RegisteredConfigType<TRegister, TKey> = TRegister extends {
135
+ config: infer TConfig
136
+ }
137
+ ? TConfig extends {
138
+ '~types': infer TTypes
139
+ }
140
+ ? TKey extends keyof TTypes
141
+ ? TTypes[TKey]
142
+ : unknown
143
+ : unknown
144
+ : unknown
145
+
146
+ export type DefaultRemountDepsFn<TRouteTree extends AnyRoute> = (
147
+ opts: MakeRemountDepsOptionsUnion<TRouteTree>,
148
+ ) => any
149
+
150
+ export interface DefaultRouterOptionsExtensions {}
151
+
152
+ export interface RouterOptionsExtensions extends DefaultRouterOptionsExtensions {}
153
+
154
+ export type SSROption = boolean | 'data-only'
155
+
156
+ export interface RouterOptions<
157
+ TRouteTree extends AnyRoute,
158
+ TTrailingSlashOption extends TrailingSlashOption,
159
+ TDefaultStructuralSharingOption extends boolean = false,
160
+ TRouterHistory extends RouterHistory = RouterHistory,
161
+ TDehydrated = undefined,
162
+ > extends RouterOptionsExtensions {
163
+ /**
164
+ * The history object that will be used to manage the browser history.
165
+ *
166
+ * If not provided, a new createBrowserHistory instance will be created and used.
167
+ *
168
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#history-property)
169
+ * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/history-types)
170
+ */
171
+ history?: TRouterHistory
172
+ /**
173
+ * A function that will be used to stringify search params when generating links.
174
+ *
175
+ * @default defaultStringifySearch
176
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#stringifysearch-method)
177
+ * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/custom-search-param-serialization)
178
+ */
179
+ stringifySearch?: SearchSerializer
180
+ /**
181
+ * A function that will be used to parse search params when parsing the current location.
182
+ *
183
+ * @default defaultParseSearch
184
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#parsesearch-method)
185
+ * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/custom-search-param-serialization)
186
+ */
187
+ parseSearch?: SearchParser
188
+ /**
189
+ * If `false`, routes will not be preloaded by default in any way.
190
+ *
191
+ * If `'intent'`, routes will be preloaded by default when the user hovers over a link or a `touchstart` event is detected on a `<Link>`.
192
+ *
193
+ * If `'viewport'`, routes will be preloaded by default when they are within the viewport.
194
+ *
195
+ * @default false
196
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#defaultpreload-property)
197
+ * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/preloading)
198
+ */
199
+ defaultPreload?: false | 'intent' | 'viewport' | 'render'
200
+ /**
201
+ * The delay in milliseconds that a route must be hovered over or touched before it is preloaded.
202
+ *
203
+ * @default 50
204
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#defaultpreloaddelay-property)
205
+ * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/preloading#preload-delay)
206
+ */
207
+ defaultPreloadDelay?: number
208
+ /**
209
+ * The default `preloadIntentProximity` a route should use if no preloadIntentProximity is provided.
210
+ *
211
+ * @default 0
212
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#defaultpreloadintentproximity-property)
213
+ * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/preloading#preload-intent-proximity)
214
+ */
215
+ defaultPreloadIntentProximity?: number
216
+ /**
217
+ * The default `pendingMs` a route should use if no pendingMs is provided.
218
+ *
219
+ * @default 1000
220
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#defaultpendingms-property)
221
+ * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/data-loading#avoiding-pending-component-flash)
222
+ */
223
+ defaultPendingMs?: number
224
+ /**
225
+ * The default `pendingMinMs` a route should use if no pendingMinMs is provided.
226
+ *
227
+ * @default 500
228
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#defaultpendingminms-property)
229
+ * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/data-loading#avoiding-pending-component-flash)
230
+ */
231
+ defaultPendingMinMs?: number
232
+ /**
233
+ * The default `staleTime` a route should use if no staleTime is provided. This is the time in milliseconds that a route will be considered fresh.
234
+ *
235
+ * @default 0
236
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#defaultstaletime-property)
237
+ * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/data-loading#key-options)
238
+ */
239
+ defaultStaleTime?: number
240
+ /**
241
+ * The default stale reload mode a route loader should use if no `loader.staleReloadMode` is provided.
242
+ *
243
+ * `'background'` preserves the current stale-while-revalidate behavior.
244
+ * `'blocking'` waits for stale loader reloads to complete before resolving navigation.
245
+ *
246
+ * @default 'background'
247
+ */
248
+ defaultStaleReloadMode?: LoaderStaleReloadMode
249
+ /**
250
+ * The default `preloadStaleTime` a route should use if no preloadStaleTime is provided.
251
+ *
252
+ * @default 30_000 `(30 seconds)`
253
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#defaultpreloadstaletime-property)
254
+ * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/preloading)
255
+ */
256
+ defaultPreloadStaleTime?: number
257
+ /**
258
+ * The default `defaultPreloadGcTime` a route should use if no preloadGcTime is provided.
259
+ *
260
+ * @default 1_800_000 `(30 minutes)`
261
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#defaultpreloadgctime-property)
262
+ * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/preloading)
263
+ */
264
+ defaultPreloadGcTime?: number
265
+ /**
266
+ * If `true`, route navigations will called using `document.startViewTransition()`.
267
+ *
268
+ * If the browser does not support this api, this option will be ignored.
269
+ *
270
+ * See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Document/startViewTransition) for more information on how this function works.
271
+ *
272
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#defaultviewtransition-property)
273
+ */
274
+ defaultViewTransition?: boolean | ViewTransitionOptions
275
+ /**
276
+ * The default `hashScrollIntoView` a route should use if no hashScrollIntoView is provided while navigating
277
+ *
278
+ * See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView) for more information on `ScrollIntoViewOptions`.
279
+ *
280
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#defaulthashscrollintoview-property)
281
+ */
282
+ defaultHashScrollIntoView?: boolean | ScrollIntoViewOptions
283
+ /**
284
+ * @default 'fuzzy'
285
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#notfoundmode-property)
286
+ * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/not-found-errors#the-notfoundmode-option)
287
+ */
288
+ notFoundMode?: 'root' | 'fuzzy'
289
+ /**
290
+ * The default `gcTime` a route should use if no gcTime is provided.
291
+ *
292
+ * @default 1_800_000 `(30 minutes)`
293
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#defaultgctime-property)
294
+ * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/data-loading#key-options)
295
+ */
296
+ defaultGcTime?: number
297
+ /**
298
+ * If `true`, all routes will be matched as case-sensitive.
299
+ *
300
+ * @default false
301
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#casesensitive-property)
302
+ */
303
+ caseSensitive?: boolean
304
+ /**
305
+ *
306
+ * The route tree that will be used to configure the router instance.
307
+ *
308
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#routetree-property)
309
+ * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/routing/route-trees)
310
+ */
311
+ routeTree?: TRouteTree
312
+ /**
313
+ * The basepath for then entire router. This is useful for mounting a router instance at a subpath.
314
+ * ```
315
+ * @default '/'
316
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#basepath-property)
317
+ */
318
+ basepath?: string
319
+ /**
320
+ * The root context that will be provided to all routes in the route tree.
321
+ *
322
+ * This can be used to provide a context to all routes in the tree without having to provide it to each route individually.
323
+ *
324
+ * Optional or required if the root route was created with [`createRootRouteWithContext()`](https://tanstack.com/router/latest/docs/framework/react/api/router/createRootRouteWithContextFunction).
325
+ *
326
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#context-property)
327
+ * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/router-context)
328
+ */
329
+ context?: InferRouterContext<TRouteTree>
330
+
331
+ additionalContext?: any
332
+
333
+ /**
334
+ * A function that will be called when the router is dehydrated.
335
+ *
336
+ * The return value of this function will be serialized and stored in the router's dehydrated state.
337
+ *
338
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#dehydrate-method)
339
+ * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/external-data-loading#critical-dehydrationhydration)
340
+ */
341
+ dehydrate?: () => Constrain<
342
+ TDehydrated,
343
+ ValidateSerializableInput<Register, TDehydrated>
344
+ >
345
+ /**
346
+ * A function that will be called when the router is hydrated.
347
+ *
348
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#hydrate-method)
349
+ * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/external-data-loading#critical-dehydrationhydration)
350
+ */
351
+ hydrate?: (dehydrated: TDehydrated) => Awaitable<void>
352
+ /**
353
+ * An array of route masks that will be used to mask routes in the route tree.
354
+ *
355
+ * Route masking is when you display a route at a different path than the one it is configured to match, like a modal popup that when shared will unmask to the modal's content instead of the modal's context.
356
+ *
357
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#routemasks-property)
358
+ * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/route-masking)
359
+ */
360
+ routeMasks?: Array<RouteMask<TRouteTree>>
361
+ /**
362
+ * If `true`, route masks will, by default, be removed when the page is reloaded.
363
+ *
364
+ * This can be overridden on a per-mask basis by setting the `unmaskOnReload` option on the mask, or on a per-navigation basis by setting the `unmaskOnReload` option in the `Navigate` options.
365
+ *
366
+ * @default false
367
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#unmaskonreload-property)
368
+ * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/route-masking#unmasking-on-page-reload)
369
+ */
370
+ unmaskOnReload?: boolean
371
+
372
+ /**
373
+ * Use `notFoundComponent` instead.
374
+ *
375
+ * @deprecated
376
+ * See https://tanstack.com/router/v1/docs/guide/not-found-errors#migrating-from-notfoundroute for more info.
377
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#notfoundroute-property)
378
+ */
379
+ notFoundRoute?: AnyRoute
380
+ /**
381
+ * Configures how trailing slashes are treated.
382
+ *
383
+ * - `'always'` will add a trailing slash if not present
384
+ * - `'never'` will remove the trailing slash if present
385
+ * - `'preserve'` will not modify the trailing slash.
386
+ *
387
+ * @default 'never'
388
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#trailingslash-property)
389
+ */
390
+ trailingSlash?: TTrailingSlashOption
391
+ /**
392
+ * While usually automatic, sometimes it can be useful to force the router into a server-side state, e.g. when using the router in a non-browser environment that has access to a global.document object.
393
+ *
394
+ * @default typeof document !== 'undefined'
395
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#isserver-property)
396
+ */
397
+ isServer?: boolean
398
+
399
+ /**
400
+ * @default false
401
+ */
402
+ isShell?: boolean
403
+
404
+ /**
405
+ * @default false
406
+ */
407
+ isPrerendering?: boolean
408
+
409
+ /**
410
+ * The default `ssr` a route should use if no `ssr` is provided.
411
+ *
412
+ * @default true
413
+ */
414
+ defaultSsr?: SSROption
415
+
416
+ search?: {
417
+ /**
418
+ * Configures how unknown search params (= not returned by any `validateSearch`) are treated.
419
+ *
420
+ * @default false
421
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#search.strict-property)
422
+ */
423
+ strict?: boolean
424
+ }
425
+
426
+ /**
427
+ * Configures whether structural sharing is enabled by default for fine-grained selectors.
428
+ *
429
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#defaultstructuralsharing-property)
430
+ */
431
+ defaultStructuralSharing?: TDefaultStructuralSharingOption
432
+
433
+ /**
434
+ * Configures which URI characters are allowed in path params that would ordinarily be escaped by encodeURIComponent.
435
+ *
436
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#pathparamsallowedcharacters-property)
437
+ * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/path-params#allowed-characters)
438
+ */
439
+ pathParamsAllowedCharacters?: Array<
440
+ ';' | ':' | '@' | '&' | '=' | '+' | '$' | ','
441
+ >
442
+
443
+ defaultRemountDeps?: DefaultRemountDepsFn<TRouteTree>
444
+
445
+ /**
446
+ * If `true`, scroll restoration will be enabled
447
+ *
448
+ * @default false
449
+ */
450
+ scrollRestoration?:
451
+ | boolean
452
+ | ((opts: { location: ParsedLocation }) => boolean)
453
+
454
+ /**
455
+ * A function that will be called to get the key for the scroll restoration cache.
456
+ *
457
+ * @default (location) => location.href
458
+ */
459
+ getScrollRestorationKey?: (location: ParsedLocation) => string
460
+ /**
461
+ * The default behavior for scroll restoration.
462
+ *
463
+ * @default 'auto'
464
+ */
465
+ scrollRestorationBehavior?: ScrollBehavior
466
+ /**
467
+ * An array of selectors that will be used to scroll to the top of the page in addition to `window`
468
+ *
469
+ * @default ['window']
470
+ */
471
+ scrollToTopSelectors?: Array<string | (() => Element | null | undefined)>
472
+
473
+ /**
474
+ * When `true`, disables the global catch boundary that normally wraps all route matches.
475
+ * This allows unhandled errors to bubble up to top-level error handlers in the browser.
476
+ *
477
+ * Useful for testing tools (like Storybook Test Runner), error reporting services,
478
+ * and debugging scenarios where you want errors to reach the browser's global error handlers.
479
+ *
480
+ * @default false
481
+ */
482
+ disableGlobalCatchBoundary?: boolean
483
+
484
+ /**
485
+ * An array of URL protocols to allow in links, redirects, and navigation.
486
+ * Absolute URLs with protocols not in this list will be rejected.
487
+ *
488
+ * @default DEFAULT_PROTOCOL_ALLOWLIST (http:, https:, mailto:, tel:)
489
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#protocolallowlist-property)
490
+ */
491
+ protocolAllowlist?: Array<string>
492
+
493
+ serializationAdapters?: ReadonlyArray<AnySerializationAdapter>
494
+ /**
495
+ * Configures how the router will rewrite the location between the actual href and the internal href of the router.
496
+ *
497
+ * @default undefined
498
+ * @description You can provide a custom rewrite pair (in/out).
499
+ * This is useful for shifting data from the origin to the path (for things like subdomain routing), or other advanced use cases.
500
+ */
501
+ rewrite?: LocationRewrite
502
+ origin?: string
503
+ ssr?: {
504
+ nonce?: string
505
+ }
506
+ }
507
+
508
+ export type LocationRewrite = {
509
+ /**
510
+ * A function that will be called to rewrite the URL before it is interpreted by the router from the history instance.
511
+ *
512
+ * @default undefined
513
+ */
514
+ input?: LocationRewriteFunction
515
+ /**
516
+ * A function that will be called to rewrite the URL before it is committed to the actual history instance from the router.
517
+ *
518
+ * @default undefined
519
+ */
520
+ output?: LocationRewriteFunction
521
+ }
522
+
523
+ /**
524
+ * A function that will be called to rewrite the URL.
525
+ *
526
+ * @param url The URL to rewrite.
527
+ * @returns The rewritten URL (as a URL instance or full href string) or undefined if no rewrite is needed.
528
+ */
529
+ export type LocationRewriteFunction = ({
530
+ url,
531
+ }: {
532
+ url: URL
533
+ }) => undefined | string | URL
534
+
535
+ export interface RouterState<
536
+ in out TRouteTree extends AnyRoute = AnyRoute,
537
+ in out TRouteMatch = MakeRouteMatchUnion,
538
+ > {
539
+ status: 'pending' | 'idle'
540
+ loadedAt: number
541
+ isLoading: boolean
542
+ isTransitioning: boolean
543
+ matches: Array<TRouteMatch>
544
+ location: ParsedLocation<FullSearchSchema<TRouteTree>>
545
+ resolvedLocation?: ParsedLocation<FullSearchSchema<TRouteTree>>
546
+ statusCode: number
547
+ redirect?: AnyRedirect
548
+ }
549
+
550
+ export interface BuildNextOptions {
551
+ to?: string | number | null
552
+ params?: true | Updater<unknown>
553
+ search?: true | Updater<unknown>
554
+ hash?: true | Updater<string>
555
+ state?: true | NonNullableUpdater<ParsedHistoryState, HistoryState>
556
+ mask?: {
557
+ to?: string | number | null
558
+ params?: true | Updater<unknown>
559
+ search?: true | Updater<unknown>
560
+ hash?: true | Updater<string>
561
+ state?: true | NonNullableUpdater<ParsedHistoryState, HistoryState>
562
+ unmaskOnReload?: boolean
563
+ }
564
+ from?: string
565
+ href?: string
566
+ _fromLocation?: ParsedLocation
567
+ unsafeRelative?: 'path'
568
+ _isNavigate?: boolean
569
+ }
570
+
571
+ type NavigationEventInfo = {
572
+ fromLocation?: ParsedLocation
573
+ toLocation: ParsedLocation
574
+ pathChanged: boolean
575
+ hrefChanged: boolean
576
+ hashChanged: boolean
577
+ }
578
+
579
+ export interface RouterEvents {
580
+ onBeforeNavigate: {
581
+ type: 'onBeforeNavigate'
582
+ } & NavigationEventInfo
583
+ onBeforeLoad: {
584
+ type: 'onBeforeLoad'
585
+ } & NavigationEventInfo
586
+ onLoad: {
587
+ type: 'onLoad'
588
+ } & NavigationEventInfo
589
+ onResolved: {
590
+ type: 'onResolved'
591
+ } & NavigationEventInfo
592
+ onBeforeRouteMount: {
593
+ type: 'onBeforeRouteMount'
594
+ } & NavigationEventInfo
595
+ onRendered: {
596
+ type: 'onRendered'
597
+ } & NavigationEventInfo
598
+ }
599
+
600
+ export type RouterEvent = RouterEvents[keyof RouterEvents]
601
+
602
+ export type ListenerFn<TEvent extends RouterEvent> = (event: TEvent) => void
603
+
604
+ export type RouterListener<TRouterEvent extends RouterEvent> = {
605
+ eventType: TRouterEvent['type']
606
+ fn: ListenerFn<TRouterEvent>
607
+ }
608
+
609
+ export type SubscribeFn = <TType extends keyof RouterEvents>(
610
+ eventType: TType,
611
+ fn: ListenerFn<RouterEvents[TType]>,
612
+ ) => () => void
613
+
614
+ export interface MatchRoutesOpts {
615
+ preload?: boolean
616
+ throwOnError?: boolean
617
+ dest?: BuildNextOptions
618
+ }
619
+
620
+ export type InferRouterContext<TRouteTree extends AnyRoute> =
621
+ TRouteTree['types']['routerContext']
622
+
623
+ export type RouterContextOptions<TRouteTree extends AnyRoute> =
624
+ AnyContext extends InferRouterContext<TRouteTree>
625
+ ? {
626
+ context?: InferRouterContext<TRouteTree>
627
+ }
628
+ : {
629
+ context: InferRouterContext<TRouteTree>
630
+ }
631
+
632
+ export type RouterConstructorOptions<
633
+ TRouteTree extends AnyRoute,
634
+ TTrailingSlashOption extends TrailingSlashOption,
635
+ TDefaultStructuralSharingOption extends boolean,
636
+ TRouterHistory extends RouterHistory,
637
+ TDehydrated extends Record<string, any>,
638
+ > = Omit<
639
+ RouterOptions<
640
+ TRouteTree,
641
+ TTrailingSlashOption,
642
+ TDefaultStructuralSharingOption,
643
+ TRouterHistory,
644
+ TDehydrated
645
+ >,
646
+ 'context' | 'serializationAdapters' | 'defaultSsr'
647
+ > &
648
+ RouterContextOptions<TRouteTree>
649
+
650
+ export type PreloadRouteFn<
651
+ TRouteTree extends AnyRoute,
652
+ TTrailingSlashOption extends TrailingSlashOption,
653
+ TDefaultStructuralSharingOption extends boolean,
654
+ TRouterHistory extends RouterHistory,
655
+ > = <
656
+ TFrom extends RoutePaths<TRouteTree> | string = string,
657
+ TTo extends string | undefined = undefined,
658
+ TMaskFrom extends RoutePaths<TRouteTree> | string = TFrom,
659
+ TMaskTo extends string = '',
660
+ >(
661
+ opts: NavigateOptions<
662
+ RouterCore<
663
+ TRouteTree,
664
+ TTrailingSlashOption,
665
+ TDefaultStructuralSharingOption,
666
+ TRouterHistory
667
+ >,
668
+ TFrom,
669
+ TTo,
670
+ TMaskFrom,
671
+ TMaskTo
672
+ > & {
673
+ /**
674
+ * @internal
675
+ * A **trusted** built location that can be used to redirect to.
676
+ */
677
+ _builtLocation?: ParsedLocation
678
+ },
679
+ ) => Promise<Array<AnyRouteMatch> | undefined>
680
+
681
+ export type MatchRouteFn<
682
+ TRouteTree extends AnyRoute,
683
+ TTrailingSlashOption extends TrailingSlashOption,
684
+ TDefaultStructuralSharingOption extends boolean,
685
+ TRouterHistory extends RouterHistory,
686
+ > = <
687
+ TFrom extends RoutePaths<TRouteTree> = '/',
688
+ TTo extends string | undefined = undefined,
689
+ TResolved = ResolveRelativePath<TFrom, NoInfer<TTo>>,
690
+ >(
691
+ location: ToOptions<
692
+ RouterCore<
693
+ TRouteTree,
694
+ TTrailingSlashOption,
695
+ TDefaultStructuralSharingOption,
696
+ TRouterHistory
697
+ >,
698
+ TFrom,
699
+ TTo
700
+ >,
701
+ opts?: MatchRouteOptions,
702
+ ) => false | RouteById<TRouteTree, TResolved>['types']['allParams']
703
+
704
+ export type UpdateFn<
705
+ TRouteTree extends AnyRoute,
706
+ TTrailingSlashOption extends TrailingSlashOption,
707
+ TDefaultStructuralSharingOption extends boolean,
708
+ TRouterHistory extends RouterHistory,
709
+ TDehydrated extends Record<string, any>,
710
+ > = (
711
+ newOptions: RouterConstructorOptions<
712
+ TRouteTree,
713
+ TTrailingSlashOption,
714
+ TDefaultStructuralSharingOption,
715
+ TRouterHistory,
716
+ TDehydrated
717
+ >,
718
+ ) => void
719
+
720
+ export type InvalidateFn<TRouter extends AnyRouter> = (opts?: {
721
+ filter?: (d: MakeRouteMatchUnion<TRouter>) => boolean
722
+ sync?: boolean
723
+ forcePending?: boolean
724
+ }) => Promise<void>
725
+
726
+ export type ParseLocationFn<TRouteTree extends AnyRoute> = (
727
+ locationToParse: HistoryLocation,
728
+ previousLocation?: ParsedLocation<FullSearchSchema<TRouteTree>>,
729
+ ) => ParsedLocation<FullSearchSchema<TRouteTree>>
730
+
731
+ export type GetMatchRoutesFn = (pathname: string) => {
732
+ matchedRoutes: ReadonlyArray<AnyRoute>
733
+ /** exhaustive params, still in their string form */
734
+ routeParams: Record<string, string>
735
+ /** partial params, parsed from routeParams during matching */
736
+ parsedParams: Record<string, unknown> | undefined
737
+ foundRoute: AnyRoute | undefined
738
+ parseError?: unknown
739
+ }
740
+
741
+ export type EmitFn = (routerEvent: RouterEvent) => void
742
+
743
+ export type LoadFn = (opts?: { sync?: boolean }) => Promise<void>
744
+
745
+ export type CommitLocationFn = ({
746
+ viewTransition,
747
+ ignoreBlocker,
748
+ ...next
749
+ }: ParsedLocation & CommitLocationOptions) => Promise<void>
750
+
751
+ export type StartTransitionFn = (fn: () => void) => void
752
+
753
+ export interface MatchRoutesFn {
754
+ (
755
+ pathname: string,
756
+ locationSearch?: AnySchema,
757
+ opts?: MatchRoutesOpts,
758
+ ): Array<MakeRouteMatchUnion>
759
+ /**
760
+ * @deprecated use the following signature instead
761
+ */
762
+ (next: ParsedLocation, opts?: MatchRoutesOpts): Array<AnyRouteMatch>
763
+ (
764
+ pathnameOrNext: string | ParsedLocation,
765
+ locationSearchOrOpts?: AnySchema | MatchRoutesOpts,
766
+ opts?: MatchRoutesOpts,
767
+ ): Array<AnyRouteMatch>
768
+ }
769
+
770
+ export type GetMatchFn = (matchId: string) => AnyRouteMatch | undefined
771
+
772
+ export type UpdateMatchFn = (
773
+ id: string,
774
+ updater: (match: AnyRouteMatch) => AnyRouteMatch,
775
+ ) => void
776
+
777
+ export type LoadRouteChunkFn = (route: AnyRoute) => Promise<Array<void>>
778
+
779
+ export type ResolveRedirect = (err: AnyRedirect) => ResolvedRedirect
780
+
781
+ export type ClearCacheFn<TRouter extends AnyRouter> = (opts?: {
782
+ filter?: (d: MakeRouteMatchUnion<TRouter>) => boolean
783
+ }) => void
784
+
785
+ export interface ServerSsr {
786
+ /**
787
+ * Injects HTML synchronously into the stream.
788
+ * Emits an onInjectedHtml event that listeners can handle.
789
+ * If no subscriber is listening, the HTML is buffered and can be retrieved via takeBufferedHtml().
790
+ */
791
+ injectHtml: (html: string) => void
792
+ /**
793
+ * Injects a script tag synchronously into the stream.
794
+ */
795
+ injectScript: (script: string) => void
796
+ isDehydrated: () => boolean
797
+ isSerializationFinished: () => boolean
798
+ onRenderFinished: (listener: () => void) => void
799
+ setRenderFinished: () => void
800
+ cleanup: () => void
801
+ onSerializationFinished: (listener: () => void) => void
802
+ dehydrate: () => Promise<void>
803
+ takeBufferedScripts: () => RouterManagedTag | undefined
804
+ /**
805
+ * Takes any buffered HTML that was injected.
806
+ * Returns the buffered HTML string (which may include multiple script tags) or undefined if empty.
807
+ */
808
+ takeBufferedHtml: () => string | undefined
809
+ liftScriptBarrier: () => void
810
+ }
811
+
812
+ export type AnyRouterWithContext<TContext> = RouterCore<
813
+ AnyRouteWithContext<TContext>,
814
+ any,
815
+ any,
816
+ any,
817
+ any
818
+ >
819
+
820
+ export type AnyRouter = RouterCore<any, any, any, any, any>
821
+
822
+ export interface ViewTransitionOptions {
823
+ types:
824
+ | Array<string>
825
+ | ((locationChangeInfo: {
826
+ fromLocation?: ParsedLocation
827
+ toLocation: ParsedLocation
828
+ pathChanged: boolean
829
+ hrefChanged: boolean
830
+ hashChanged: boolean
831
+ }) => Array<string> | false)
832
+ }
833
+
834
+ // TODO where is this used? can we remove this?
835
+ /**
836
+ * Convert an unknown error into a minimal, serializable object.
837
+ * Includes name and message (and stack in development).
838
+ */
839
+ export function defaultSerializeError(err: unknown) {
840
+ if (err instanceof Error) {
841
+ const obj = {
842
+ name: err.name,
843
+ message: err.message,
844
+ }
845
+
846
+ if (process.env.NODE_ENV === 'development') {
847
+ ;(obj as any).stack = err.stack
848
+ }
849
+
850
+ return obj
851
+ }
852
+
853
+ return {
854
+ data: err,
855
+ }
856
+ }
857
+
858
+ /** Options for configuring trailing-slash behavior. */
859
+ export const trailingSlashOptions = {
860
+ always: 'always',
861
+ never: 'never',
862
+ preserve: 'preserve',
863
+ } as const
864
+
865
+ export type TrailingSlashOption =
866
+ (typeof trailingSlashOptions)[keyof typeof trailingSlashOptions]
867
+
868
+ /**
869
+ * Compute whether path, href or hash changed between previous and current
870
+ * resolved locations.
871
+ */
872
+ export function getLocationChangeInfo(
873
+ location: ParsedLocation,
874
+ resolvedLocation?: ParsedLocation,
875
+ ) {
876
+ const fromLocation = resolvedLocation
877
+ const toLocation = location
878
+ const pathChanged = fromLocation?.pathname !== toLocation.pathname
879
+ const hrefChanged = fromLocation?.href !== toLocation.href
880
+ const hashChanged = fromLocation?.hash !== toLocation.hash
881
+ return { fromLocation, toLocation, pathChanged, hrefChanged, hashChanged }
882
+ }
883
+
884
+ export type CreateRouterFn = <
885
+ TRouteTree extends AnyRoute,
886
+ TTrailingSlashOption extends TrailingSlashOption = 'never',
887
+ TDefaultStructuralSharingOption extends boolean = false,
888
+ TRouterHistory extends RouterHistory = RouterHistory,
889
+ TDehydrated extends Record<string, any> = Record<string, any>,
890
+ >(
891
+ options: undefined extends number
892
+ ? 'strictNullChecks must be enabled in tsconfig.json'
893
+ : RouterConstructorOptions<
894
+ TRouteTree,
895
+ TTrailingSlashOption,
896
+ TDefaultStructuralSharingOption,
897
+ TRouterHistory,
898
+ TDehydrated
899
+ >,
900
+ ) => RouterCore<
901
+ TRouteTree,
902
+ TTrailingSlashOption,
903
+ TDefaultStructuralSharingOption,
904
+ TRouterHistory,
905
+ TDehydrated
906
+ >
907
+
908
+ declare global {
909
+ // eslint-disable-next-line no-var
910
+ var __TSR_CACHE__:
911
+ | {
912
+ routeTree: AnyRoute
913
+ processRouteTreeResult: ProcessRouteTreeResult<AnyRoute>
914
+ resolvePathCache: LRUCache<string, string>
915
+ }
916
+ | undefined
917
+ }
918
+
919
+ /**
920
+ * Core, framework-agnostic router engine that powers TanStack Router.
921
+ *
922
+ * Provides navigation, matching, loading, preloading, caching and event APIs
923
+ * used by framework adapters (React/Solid). Prefer framework helpers like
924
+ * `createRouter` in app code.
925
+ *
926
+ * @link https://tanstack.com/router/latest/docs/framework/react/api/router/RouterType
927
+ */
928
+ export class RouterCore<
929
+ in out TRouteTree extends AnyRoute,
930
+ in out TTrailingSlashOption extends TrailingSlashOption,
931
+ in out TDefaultStructuralSharingOption extends boolean,
932
+ in out TRouterHistory extends RouterHistory = RouterHistory,
933
+ in out TDehydrated extends Record<string, any> = Record<string, any>,
934
+ > {
935
+ // Option-independent properties
936
+ tempLocationKey: string | undefined = `${Math.round(
937
+ Math.random() * 10000000,
938
+ )}`
939
+ resetNextScroll = true
940
+ shouldViewTransition?: boolean | ViewTransitionOptions = undefined
941
+ isViewTransitionTypesSupported?: boolean = undefined
942
+ subscribers = new Set<RouterListener<RouterEvent>>()
943
+ viewTransitionPromise?: ControlledPromise<true>
944
+ isScrollRestoring = false
945
+ isScrollRestorationSetup = false
946
+
947
+ // Must build in constructor
948
+ stores!: RouterStores<TRouteTree>
949
+ private getStoreConfig!: GetStoreConfig
950
+ batch!: (fn: () => void) => void
951
+
952
+ options!: PickAsRequired<
953
+ RouterOptions<
954
+ TRouteTree,
955
+ TTrailingSlashOption,
956
+ TDefaultStructuralSharingOption,
957
+ TRouterHistory,
958
+ TDehydrated
959
+ >,
960
+ 'stringifySearch' | 'parseSearch' | 'context'
961
+ >
962
+ history!: TRouterHistory
963
+ rewrite?: LocationRewrite
964
+ origin?: string
965
+ latestLocation!: ParsedLocation<FullSearchSchema<TRouteTree>>
966
+ pendingBuiltLocation?: ParsedLocation<FullSearchSchema<TRouteTree>>
967
+ basepath!: string
968
+ routeTree!: TRouteTree
969
+ routesById!: RoutesById<TRouteTree>
970
+ routesByPath!: RoutesByPath<TRouteTree>
971
+ processedTree!: ProcessedTree<TRouteTree, any, any>
972
+ resolvePathCache!: LRUCache<string, string>
973
+ isServer!: boolean
974
+ pathParamsDecoder?: (encoded: string) => string
975
+ protocolAllowlist!: Set<string>
976
+
977
+ /**
978
+ * @deprecated Use the `createRouter` function instead
979
+ */
980
+ constructor(
981
+ options: RouterConstructorOptions<
982
+ TRouteTree,
983
+ TTrailingSlashOption,
984
+ TDefaultStructuralSharingOption,
985
+ TRouterHistory,
986
+ TDehydrated
987
+ >,
988
+ getStoreConfig: GetStoreConfig,
989
+ ) {
990
+ this.getStoreConfig = getStoreConfig
991
+
992
+ this.update({
993
+ defaultPreloadDelay: 50,
994
+ defaultPendingMs: 1000,
995
+ defaultPendingMinMs: 500,
996
+ context: undefined!,
997
+ ...options,
998
+ caseSensitive: options.caseSensitive ?? false,
999
+ notFoundMode: options.notFoundMode ?? 'fuzzy',
1000
+ stringifySearch: options.stringifySearch ?? defaultStringifySearch,
1001
+ parseSearch: options.parseSearch ?? defaultParseSearch,
1002
+ protocolAllowlist:
1003
+ options.protocolAllowlist ?? DEFAULT_PROTOCOL_ALLOWLIST,
1004
+ })
1005
+
1006
+ if (typeof document !== 'undefined') {
1007
+ self.__TSR_ROUTER__ = this
1008
+ }
1009
+ }
1010
+
1011
+ // This is a default implementation that can optionally be overridden
1012
+ // by the router provider once rendered. We provide this so that the
1013
+ // router can be used in a non-react environment if necessary
1014
+ startTransition: StartTransitionFn = (fn) => fn()
1015
+
1016
+ isShell() {
1017
+ return !!this.options.isShell
1018
+ }
1019
+
1020
+ isPrerendering() {
1021
+ return !!this.options.isPrerendering
1022
+ }
1023
+
1024
+ update: UpdateFn<
1025
+ TRouteTree,
1026
+ TTrailingSlashOption,
1027
+ TDefaultStructuralSharingOption,
1028
+ TRouterHistory,
1029
+ TDehydrated
1030
+ > = (newOptions) => {
1031
+ if (process.env.NODE_ENV !== 'production') {
1032
+ if (newOptions.notFoundRoute) {
1033
+ console.warn(
1034
+ 'The notFoundRoute API is deprecated and will be removed in the next major version. See https://tanstack.com/router/v1/docs/framework/react/guide/not-found-errors#migrating-from-notfoundroute for more info.',
1035
+ )
1036
+ }
1037
+ }
1038
+
1039
+ const prevOptions = this.options
1040
+ const prevBasepath = this.basepath ?? prevOptions?.basepath ?? '/'
1041
+ const basepathWasUnset = this.basepath === undefined
1042
+ const prevRewriteOption = prevOptions?.rewrite
1043
+
1044
+ this.options = {
1045
+ ...prevOptions,
1046
+ ...newOptions,
1047
+ }
1048
+
1049
+ this.isServer = this.options.isServer ?? typeof document === 'undefined'
1050
+
1051
+ this.protocolAllowlist = new Set(this.options.protocolAllowlist)
1052
+
1053
+ if (this.options.pathParamsAllowedCharacters)
1054
+ this.pathParamsDecoder = compileDecodeCharMap(
1055
+ this.options.pathParamsAllowedCharacters,
1056
+ )
1057
+
1058
+ if (
1059
+ !this.history ||
1060
+ (this.options.history && this.options.history !== this.history)
1061
+ ) {
1062
+ if (!this.options.history) {
1063
+ // Gate on real `window` (not `document` alone): SSR may shim `document` without `window`.
1064
+ if (typeof window !== 'undefined') {
1065
+ this.history = createBrowserHistory() as TRouterHistory
1066
+ }
1067
+ } else {
1068
+ this.history = this.options.history
1069
+ }
1070
+ }
1071
+
1072
+ this.origin = this.options.origin
1073
+ if (!this.origin) {
1074
+ const win = globalThis.window
1075
+ if (win && win.origin && win.origin !== 'null') {
1076
+ this.origin = win.origin
1077
+ } else {
1078
+ // fallback for the server, can be overridden by calling router.update({origin}) on the server
1079
+ this.origin = 'http://localhost'
1080
+ }
1081
+ }
1082
+
1083
+ if (this.history) {
1084
+ this.updateLatestLocation()
1085
+ }
1086
+
1087
+ if (this.options.routeTree !== this.routeTree) {
1088
+ this.routeTree = this.options.routeTree as TRouteTree
1089
+ let processRouteTreeResult: ProcessRouteTreeResult<TRouteTree>
1090
+ if (
1091
+ (isServer ?? this.isServer) &&
1092
+ process.env.NODE_ENV !== 'development' &&
1093
+ globalThis.__TSR_CACHE__ &&
1094
+ globalThis.__TSR_CACHE__.routeTree === this.routeTree
1095
+ ) {
1096
+ const cached = globalThis.__TSR_CACHE__
1097
+ this.resolvePathCache = cached.resolvePathCache
1098
+ processRouteTreeResult = cached.processRouteTreeResult as any
1099
+ } else {
1100
+ this.resolvePathCache = createLRUCache(1000)
1101
+ processRouteTreeResult = this.buildRouteTree()
1102
+ // only cache if nothing else is cached yet
1103
+ if (
1104
+ (isServer ?? this.isServer) &&
1105
+ process.env.NODE_ENV !== 'development' &&
1106
+ globalThis.__TSR_CACHE__ === undefined
1107
+ ) {
1108
+ globalThis.__TSR_CACHE__ = {
1109
+ routeTree: this.routeTree,
1110
+ processRouteTreeResult: processRouteTreeResult as any,
1111
+ resolvePathCache: this.resolvePathCache,
1112
+ }
1113
+ }
1114
+ }
1115
+ this.setRoutes(processRouteTreeResult)
1116
+ }
1117
+
1118
+ if (!this.stores && this.latestLocation) {
1119
+ const config = this.getStoreConfig(this)
1120
+ this.batch = config.batch
1121
+ this.stores = createRouterStores(
1122
+ getInitialRouterState(this.latestLocation),
1123
+ config,
1124
+ )
1125
+
1126
+ if (!(isServer ?? this.isServer)) {
1127
+ setupScrollRestoration(this)
1128
+ }
1129
+ }
1130
+
1131
+ let needsLocationUpdate = false
1132
+ const nextBasepath = this.options.basepath ?? '/'
1133
+ const nextRewriteOption = this.options.rewrite
1134
+ const basepathChanged = basepathWasUnset || prevBasepath !== nextBasepath
1135
+ const rewriteChanged = prevRewriteOption !== nextRewriteOption
1136
+
1137
+ if (basepathChanged || rewriteChanged) {
1138
+ this.basepath = nextBasepath
1139
+
1140
+ const rewrites: Array<LocationRewrite> = []
1141
+ const trimmed = trimPath(nextBasepath)
1142
+ if (trimmed && trimmed !== '/') {
1143
+ rewrites.push(
1144
+ rewriteBasepath({
1145
+ basepath: nextBasepath,
1146
+ }),
1147
+ )
1148
+ }
1149
+ if (nextRewriteOption) {
1150
+ rewrites.push(nextRewriteOption)
1151
+ }
1152
+
1153
+ this.rewrite =
1154
+ rewrites.length === 0
1155
+ ? undefined
1156
+ : rewrites.length === 1
1157
+ ? rewrites[0]
1158
+ : composeRewrites(rewrites)
1159
+
1160
+ if (this.history) {
1161
+ this.updateLatestLocation()
1162
+ }
1163
+
1164
+ needsLocationUpdate = true
1165
+ }
1166
+
1167
+ if (needsLocationUpdate && this.stores) {
1168
+ this.stores.location.setState(() => this.latestLocation)
1169
+ }
1170
+
1171
+ if (
1172
+ typeof window !== 'undefined' &&
1173
+ 'CSS' in window &&
1174
+ typeof window.CSS?.supports === 'function'
1175
+ ) {
1176
+ this.isViewTransitionTypesSupported = window.CSS.supports(
1177
+ 'selector(:active-view-transition-type(a)',
1178
+ )
1179
+ }
1180
+ }
1181
+
1182
+ get state(): RouterState<TRouteTree> {
1183
+ return this.stores.__store.state
1184
+ }
1185
+
1186
+ updateLatestLocation = () => {
1187
+ this.latestLocation = this.parseLocation(
1188
+ this.history.location,
1189
+ this.latestLocation,
1190
+ )
1191
+ }
1192
+
1193
+ buildRouteTree = () => {
1194
+ const result = processRouteTree(
1195
+ this.routeTree,
1196
+ this.options.caseSensitive,
1197
+ (route, i) => {
1198
+ route.init({
1199
+ originalIndex: i,
1200
+ })
1201
+ },
1202
+ )
1203
+ if (this.options.routeMasks) {
1204
+ processRouteMasks(this.options.routeMasks, result.processedTree)
1205
+ }
1206
+
1207
+ return result
1208
+ }
1209
+
1210
+ setRoutes({
1211
+ routesById,
1212
+ routesByPath,
1213
+ processedTree,
1214
+ }: ProcessRouteTreeResult<TRouteTree>) {
1215
+ this.routesById = routesById as RoutesById<TRouteTree>
1216
+ this.routesByPath = routesByPath as RoutesByPath<TRouteTree>
1217
+ this.processedTree = processedTree
1218
+
1219
+ const notFoundRoute = this.options.notFoundRoute
1220
+
1221
+ if (notFoundRoute) {
1222
+ notFoundRoute.init({
1223
+ originalIndex: 99999999999,
1224
+ })
1225
+ this.routesById[notFoundRoute.id] = notFoundRoute
1226
+ }
1227
+ }
1228
+
1229
+ /**
1230
+ * Subscribe to router lifecycle events like `onBeforeNavigate`, `onLoad`,
1231
+ * `onResolved`, etc. Returns an unsubscribe function.
1232
+ *
1233
+ * @link https://tanstack.com/router/latest/docs/framework/react/api/router/RouterEventsType
1234
+ */
1235
+ subscribe: SubscribeFn = (eventType, fn) => {
1236
+ const listener: RouterListener<any> = {
1237
+ eventType,
1238
+ fn,
1239
+ }
1240
+
1241
+ this.subscribers.add(listener)
1242
+
1243
+ return () => {
1244
+ this.subscribers.delete(listener)
1245
+ }
1246
+ }
1247
+
1248
+ emit: EmitFn = (routerEvent) => {
1249
+ this.subscribers.forEach((listener) => {
1250
+ if (listener.eventType === routerEvent.type) {
1251
+ listener.fn(routerEvent)
1252
+ }
1253
+ })
1254
+ }
1255
+
1256
+ /**
1257
+ * Parse a HistoryLocation into a strongly-typed ParsedLocation using the
1258
+ * current router options, rewrite rules and search parser/stringifier.
1259
+ */
1260
+ parseLocation: ParseLocationFn<TRouteTree> = (
1261
+ locationToParse,
1262
+ previousLocation,
1263
+ ) => {
1264
+ const parse = ({
1265
+ pathname,
1266
+ search,
1267
+ hash,
1268
+ href,
1269
+ state,
1270
+ }: HistoryLocation): ParsedLocation<FullSearchSchema<TRouteTree>> => {
1271
+ // Fast path: no rewrite configured and pathname doesn't need encoding
1272
+ // Characters that need encoding: space, high unicode, control chars
1273
+ // eslint-disable-next-line no-control-regex
1274
+ if (!this.rewrite && !/[ \x00-\x1f\x7f\u0080-\uffff]/.test(pathname)) {
1275
+ const parsedSearch = this.options.parseSearch(search)
1276
+ const searchStr = this.options.stringifySearch(parsedSearch)
1277
+
1278
+ return {
1279
+ href: pathname + searchStr + hash,
1280
+ publicHref: pathname + searchStr + hash,
1281
+ pathname: decodePath(pathname).path,
1282
+ external: false,
1283
+ searchStr,
1284
+ search: nullReplaceEqualDeep(
1285
+ previousLocation?.search,
1286
+ parsedSearch,
1287
+ ) as any,
1288
+ hash: decodePath(hash.slice(1)).path,
1289
+ state: replaceEqualDeep(previousLocation?.state, state),
1290
+ }
1291
+ }
1292
+
1293
+ // Before we do any processing, we need to allow rewrites to modify the URL
1294
+ // build up the full URL by combining the href from history with the router's origin
1295
+ const fullUrl = new URL(href, this.origin)
1296
+
1297
+ const url = executeRewriteInput(this.rewrite, fullUrl)
1298
+
1299
+ const parsedSearch = this.options.parseSearch(url.search)
1300
+ const searchStr = this.options.stringifySearch(parsedSearch)
1301
+ // Make sure our final url uses the re-stringified pathname, search, and has for consistency
1302
+ // (We were already doing this, so just keeping it for now)
1303
+ url.search = searchStr
1304
+
1305
+ const fullPath = url.href.replace(url.origin, '')
1306
+
1307
+ return {
1308
+ href: fullPath,
1309
+ publicHref: href,
1310
+ pathname: decodePath(url.pathname).path,
1311
+ external: !!this.rewrite && url.origin !== this.origin,
1312
+ searchStr,
1313
+ search: nullReplaceEqualDeep(
1314
+ previousLocation?.search,
1315
+ parsedSearch,
1316
+ ) as any,
1317
+ hash: decodePath(url.hash.slice(1)).path,
1318
+ state: replaceEqualDeep(previousLocation?.state, state),
1319
+ }
1320
+ }
1321
+
1322
+ const location = parse(locationToParse)
1323
+
1324
+ const { __tempLocation, __tempKey } = location.state
1325
+
1326
+ if (__tempLocation && (!__tempKey || __tempKey === this.tempLocationKey)) {
1327
+ // Sync up the location keys
1328
+ const parsedTempLocation = parse(__tempLocation) as any
1329
+ parsedTempLocation.state.key = location.state.key // TODO: Remove in v2 - use __TSR_key instead
1330
+ parsedTempLocation.state.__TSR_key = location.state.__TSR_key
1331
+
1332
+ delete parsedTempLocation.state.__tempLocation
1333
+
1334
+ return {
1335
+ ...parsedTempLocation,
1336
+ maskedLocation: location,
1337
+ }
1338
+ }
1339
+ return location
1340
+ }
1341
+
1342
+ /** Resolve a path against the router basepath and trailing-slash policy. */
1343
+ resolvePathWithBase = (from: string, path: string) => {
1344
+ const resolvedPath = resolvePath({
1345
+ base: from,
1346
+ to: cleanPath(path),
1347
+ trailingSlash: this.options.trailingSlash,
1348
+ cache: this.resolvePathCache,
1349
+ })
1350
+ return resolvedPath
1351
+ }
1352
+
1353
+ get looseRoutesById() {
1354
+ return this.routesById as Record<string, AnyRoute>
1355
+ }
1356
+
1357
+ matchRoutes: MatchRoutesFn = (
1358
+ pathnameOrNext: string | ParsedLocation,
1359
+ locationSearchOrOpts?: AnySchema | MatchRoutesOpts,
1360
+ opts?: MatchRoutesOpts,
1361
+ ) => {
1362
+ if (typeof pathnameOrNext === 'string') {
1363
+ return this.matchRoutesInternal(
1364
+ {
1365
+ pathname: pathnameOrNext,
1366
+ search: locationSearchOrOpts,
1367
+ } as ParsedLocation,
1368
+ opts,
1369
+ )
1370
+ }
1371
+
1372
+ return this.matchRoutesInternal(pathnameOrNext, locationSearchOrOpts)
1373
+ }
1374
+
1375
+ private getParentContext(parentMatch?: AnyRouteMatch) {
1376
+ const parentMatchId = parentMatch?.id
1377
+
1378
+ const parentContext = !parentMatchId
1379
+ ? ((this.options.context as any) ?? undefined)
1380
+ : (parentMatch.context ?? this.options.context ?? undefined)
1381
+
1382
+ return parentContext
1383
+ }
1384
+
1385
+ private matchRoutesInternal(
1386
+ next: ParsedLocation,
1387
+ opts?: MatchRoutesOpts,
1388
+ ): Array<AnyRouteMatch> {
1389
+ const matchedRoutesResult = this.getMatchedRoutes(next.pathname)
1390
+ const { foundRoute, routeParams, parsedParams } = matchedRoutesResult
1391
+ let { matchedRoutes } = matchedRoutesResult
1392
+ let isGlobalNotFound = false
1393
+
1394
+ // Check to see if the route needs a 404 entry
1395
+ if (
1396
+ // If we found a route, and it's not an index route and we have left over path
1397
+ foundRoute
1398
+ ? foundRoute.path !== '/' && routeParams['**']
1399
+ : // Or if we didn't find a route and we have left over path
1400
+ trimPathRight(next.pathname)
1401
+ ) {
1402
+ // If the user has defined an (old) 404 route, use it
1403
+ if (this.options.notFoundRoute) {
1404
+ matchedRoutes = [...matchedRoutes, this.options.notFoundRoute]
1405
+ } else {
1406
+ // If there is no routes found during path matching
1407
+ isGlobalNotFound = true
1408
+ }
1409
+ }
1410
+
1411
+ const globalNotFoundRouteId = isGlobalNotFound
1412
+ ? findGlobalNotFoundRouteId(this.options.notFoundMode, matchedRoutes)
1413
+ : undefined
1414
+
1415
+ const matches = new Array<AnyRouteMatch>(matchedRoutes.length)
1416
+ // Snapshot of active match state keyed by routeId, used to stabilise
1417
+ // params/search across navigations.
1418
+ const previousActiveMatchesByRouteId = new Map<string, AnyRouteMatch>()
1419
+ for (const store of this.stores.activeMatchStoresById.values()) {
1420
+ if (store.routeId) {
1421
+ previousActiveMatchesByRouteId.set(store.routeId, store.state)
1422
+ }
1423
+ }
1424
+
1425
+ for (let index = 0; index < matchedRoutes.length; index++) {
1426
+ const route = matchedRoutes[index]!
1427
+ // Take each matched route and resolve + validate its search params
1428
+ // This has to happen serially because each route's search params
1429
+ // can depend on the parent route's search params
1430
+ // It must also happen before we create the match so that we can
1431
+ // pass the search params to the route's potential key function
1432
+ // which is used to uniquely identify the route match in state
1433
+
1434
+ const parentMatch = matches[index - 1]
1435
+
1436
+ let preMatchSearch: Record<string, any>
1437
+ let strictMatchSearch: Record<string, any>
1438
+ let searchError: any
1439
+ {
1440
+ // Validate the search params and stabilize them
1441
+ const parentSearch = parentMatch?.search ?? next.search
1442
+ const parentStrictSearch = parentMatch?._strictSearch ?? undefined
1443
+
1444
+ try {
1445
+ const strictSearch =
1446
+ validateSearch(route.options.validateSearch, { ...parentSearch }) ??
1447
+ undefined
1448
+
1449
+ preMatchSearch = {
1450
+ ...parentSearch,
1451
+ ...strictSearch,
1452
+ }
1453
+ strictMatchSearch = { ...parentStrictSearch, ...strictSearch }
1454
+ searchError = undefined
1455
+ } catch (err: any) {
1456
+ let searchParamError = err
1457
+ if (!(err instanceof SearchParamError)) {
1458
+ searchParamError = new SearchParamError(err.message, {
1459
+ cause: err,
1460
+ })
1461
+ }
1462
+
1463
+ if (opts?.throwOnError) {
1464
+ throw searchParamError
1465
+ }
1466
+
1467
+ preMatchSearch = parentSearch
1468
+ strictMatchSearch = {}
1469
+ searchError = searchParamError
1470
+ }
1471
+ }
1472
+
1473
+ // This is where we need to call route.options.loaderDeps() to get any additional
1474
+ // deps that the route's loader function might need to run. We need to do this
1475
+ // before we create the match so that we can pass the deps to the route's
1476
+ // potential key function which is used to uniquely identify the route match in state
1477
+
1478
+ const loaderDeps =
1479
+ route.options.loaderDeps?.({
1480
+ search: preMatchSearch,
1481
+ }) ?? ''
1482
+
1483
+ const loaderDepsHash = loaderDeps ? JSON.stringify(loaderDeps) : ''
1484
+
1485
+ const { interpolatedPath, usedParams } = interpolatePath({
1486
+ path: route.fullPath,
1487
+ params: routeParams,
1488
+ decoder: this.pathParamsDecoder,
1489
+ server: this.isServer,
1490
+ })
1491
+
1492
+ // Waste not, want not. If we already have a match for this route,
1493
+ // reuse it. This is important for layout routes, which might stick
1494
+ // around between navigation actions that only change leaf routes.
1495
+
1496
+ // Existing matches are matches that are already loaded along with
1497
+ // pending matches that are still loading
1498
+ const matchId =
1499
+ // route.id for disambiguation
1500
+ route.id +
1501
+ // interpolatedPath for param changes
1502
+ interpolatedPath +
1503
+ // explicit deps
1504
+ loaderDepsHash
1505
+
1506
+ const existingMatch = this.getMatch(matchId)
1507
+
1508
+ const previousMatch = previousActiveMatchesByRouteId.get(route.id)
1509
+
1510
+ const strictParams = existingMatch?._strictParams ?? usedParams
1511
+
1512
+ let paramsError: unknown = undefined
1513
+
1514
+ if (!existingMatch) {
1515
+ try {
1516
+ extractStrictParams(route, usedParams, parsedParams!, strictParams)
1517
+ } catch (err: any) {
1518
+ if (isNotFound(err) || isRedirect(err)) {
1519
+ paramsError = err
1520
+ } else {
1521
+ paramsError = new PathParamError(err.message, {
1522
+ cause: err,
1523
+ })
1524
+ }
1525
+
1526
+ if (opts?.throwOnError) {
1527
+ throw paramsError
1528
+ }
1529
+ }
1530
+ }
1531
+
1532
+ Object.assign(routeParams, strictParams)
1533
+
1534
+ const cause = previousMatch ? 'stay' : 'enter'
1535
+
1536
+ let match: AnyRouteMatch
1537
+
1538
+ if (existingMatch) {
1539
+ match = {
1540
+ ...existingMatch,
1541
+ cause,
1542
+ params: previousMatch?.params ?? routeParams,
1543
+ _strictParams: strictParams,
1544
+ search: previousMatch
1545
+ ? nullReplaceEqualDeep(previousMatch.search, preMatchSearch)
1546
+ : nullReplaceEqualDeep(existingMatch.search, preMatchSearch),
1547
+ _strictSearch: strictMatchSearch,
1548
+ }
1549
+ } else {
1550
+ const status =
1551
+ route.options.loader ||
1552
+ route.options.beforeLoad ||
1553
+ route.lazyFn ||
1554
+ routeNeedsPreload(route)
1555
+ ? 'pending'
1556
+ : 'success'
1557
+
1558
+ match = {
1559
+ id: matchId,
1560
+ ssr: (isServer ?? this.isServer) ? undefined : route.options.ssr,
1561
+ index,
1562
+ routeId: route.id,
1563
+ params: previousMatch?.params ?? routeParams,
1564
+ _strictParams: strictParams,
1565
+ pathname: interpolatedPath,
1566
+ updatedAt: Date.now(),
1567
+ search: previousMatch
1568
+ ? nullReplaceEqualDeep(previousMatch.search, preMatchSearch)
1569
+ : preMatchSearch,
1570
+ _strictSearch: strictMatchSearch,
1571
+ searchError: undefined,
1572
+ status,
1573
+ isFetching: false,
1574
+ error: undefined,
1575
+ paramsError,
1576
+ __routeContext: undefined,
1577
+ _nonReactive: {
1578
+ loadPromise: createControlledPromise(),
1579
+ },
1580
+ __beforeLoadContext: undefined,
1581
+ context: {},
1582
+ abortController: new AbortController(),
1583
+ fetchCount: 0,
1584
+ cause,
1585
+ loaderDeps: previousMatch
1586
+ ? replaceEqualDeep(previousMatch.loaderDeps, loaderDeps)
1587
+ : loaderDeps,
1588
+ invalid: false,
1589
+ preload: false,
1590
+ links: undefined,
1591
+ scripts: undefined,
1592
+ headScripts: undefined,
1593
+ meta: undefined,
1594
+ staticData: route.options.staticData || {},
1595
+ fullPath: route.fullPath,
1596
+ }
1597
+ }
1598
+
1599
+ if (!opts?.preload) {
1600
+ // If we have a global not found, mark the right match as global not found
1601
+ match.globalNotFound = globalNotFoundRouteId === route.id
1602
+ }
1603
+
1604
+ // update the searchError if there is one
1605
+ match.searchError = searchError
1606
+
1607
+ const parentContext = this.getParentContext(parentMatch)
1608
+
1609
+ match.context = {
1610
+ ...parentContext,
1611
+ ...match.__routeContext,
1612
+ ...match.__beforeLoadContext,
1613
+ }
1614
+
1615
+ matches[index] = match
1616
+ }
1617
+
1618
+ for (let index = 0; index < matches.length; index++) {
1619
+ const match = matches[index]!
1620
+ const route = this.looseRoutesById[match.routeId]!
1621
+ const existingMatch = this.getMatch(match.id)
1622
+
1623
+ // Update the match's params
1624
+ const previousMatch = previousActiveMatchesByRouteId.get(match.routeId)
1625
+ match.params = previousMatch
1626
+ ? nullReplaceEqualDeep(previousMatch.params, routeParams)
1627
+ : routeParams
1628
+
1629
+ if (!existingMatch) {
1630
+ const parentMatch = matches[index - 1]
1631
+ const parentContext = this.getParentContext(parentMatch)
1632
+
1633
+ // Update the match's context
1634
+
1635
+ if (route.options.context) {
1636
+ const contextFnContext: RouteContextOptions<any, any, any, any, any> =
1637
+ {
1638
+ deps: match.loaderDeps,
1639
+ params: match.params,
1640
+ context: parentContext ?? {},
1641
+ location: next,
1642
+ navigate: (opts: any) =>
1643
+ this.navigate({ ...opts, _fromLocation: next }),
1644
+ buildLocation: this.buildLocation,
1645
+ cause: match.cause,
1646
+ abortController: match.abortController,
1647
+ preload: !!match.preload,
1648
+ matches,
1649
+ routeId: route.id,
1650
+ }
1651
+ // Get the route context
1652
+ match.__routeContext =
1653
+ route.options.context(contextFnContext) ?? undefined
1654
+ }
1655
+
1656
+ match.context = {
1657
+ ...parentContext,
1658
+ ...match.__routeContext,
1659
+ ...match.__beforeLoadContext,
1660
+ }
1661
+ }
1662
+ }
1663
+
1664
+ return matches
1665
+ }
1666
+
1667
+ getMatchedRoutes: GetMatchRoutesFn = (pathname) => {
1668
+ return getMatchedRoutes({
1669
+ pathname,
1670
+ routesById: this.routesById,
1671
+ processedTree: this.processedTree,
1672
+ })
1673
+ }
1674
+
1675
+ /**
1676
+ * Lightweight route matching for buildLocation.
1677
+ * Only computes fullPath, accumulated search, and params - skipping expensive
1678
+ * operations like AbortController, ControlledPromise, loaderDeps, and full match objects.
1679
+ */
1680
+ private matchRoutesLightweight(location: ParsedLocation): {
1681
+ matchedRoutes: ReadonlyArray<AnyRoute>
1682
+ fullPath: string
1683
+ search: Record<string, unknown>
1684
+ params: Record<string, unknown>
1685
+ } {
1686
+ const { matchedRoutes, routeParams, parsedParams } = this.getMatchedRoutes(
1687
+ location.pathname,
1688
+ )
1689
+ const lastRoute = last(matchedRoutes)!
1690
+
1691
+ // I don't know if we should run the full search middleware chain, or just validateSearch
1692
+ // // Accumulate search validation through the route chain
1693
+ // const accumulatedSearch: Record<string, unknown> = applySearchMiddleware({
1694
+ // search: { ...location.search },
1695
+ // dest: location,
1696
+ // destRoutes: matchedRoutes,
1697
+ // _includeValidateSearch: true,
1698
+ // })
1699
+
1700
+ // Accumulate search validation through route chain
1701
+ const accumulatedSearch = { ...location.search }
1702
+ for (const route of matchedRoutes) {
1703
+ try {
1704
+ Object.assign(
1705
+ accumulatedSearch,
1706
+ validateSearch(route.options.validateSearch, accumulatedSearch),
1707
+ )
1708
+ } catch {
1709
+ // Ignore errors, we're not actually routing
1710
+ }
1711
+ }
1712
+
1713
+ // Determine params: reuse from state if possible, otherwise parse
1714
+ const lastStateMatchId = last(this.stores.matchesId.state)
1715
+ const lastStateMatch =
1716
+ lastStateMatchId &&
1717
+ this.stores.activeMatchStoresById.get(lastStateMatchId)?.state
1718
+ const canReuseParams =
1719
+ lastStateMatch &&
1720
+ lastStateMatch.routeId === lastRoute.id &&
1721
+ lastStateMatch.pathname === location.pathname
1722
+
1723
+ let params: Record<string, unknown>
1724
+ if (canReuseParams) {
1725
+ params = lastStateMatch.params
1726
+ } else {
1727
+ // Parse params through the route chain
1728
+ const strictParams: Record<string, unknown> = Object.assign(
1729
+ Object.create(null),
1730
+ routeParams,
1731
+ )
1732
+ for (const route of matchedRoutes) {
1733
+ try {
1734
+ extractStrictParams(
1735
+ route,
1736
+ routeParams,
1737
+ parsedParams ?? {},
1738
+ strictParams,
1739
+ )
1740
+ } catch {
1741
+ // Ignore errors, we're not actually routing
1742
+ }
1743
+ }
1744
+ params = strictParams
1745
+ }
1746
+
1747
+ return {
1748
+ matchedRoutes,
1749
+ fullPath: lastRoute.fullPath,
1750
+ search: accumulatedSearch,
1751
+ params,
1752
+ }
1753
+ }
1754
+
1755
+ cancelMatch = (id: string) => {
1756
+ const match = this.getMatch(id)
1757
+
1758
+ if (!match) return
1759
+
1760
+ match.abortController.abort()
1761
+ clearTimeout(match._nonReactive.pendingTimeout)
1762
+ match._nonReactive.pendingTimeout = undefined
1763
+ }
1764
+
1765
+ cancelMatches = () => {
1766
+ this.stores.pendingMatchesId.state.forEach((matchId) => {
1767
+ this.cancelMatch(matchId)
1768
+ })
1769
+
1770
+ this.stores.matchesId.state.forEach((matchId) => {
1771
+ if (this.stores.pendingMatchStoresById.has(matchId)) {
1772
+ return
1773
+ }
1774
+
1775
+ const match = this.stores.activeMatchStoresById.get(matchId)?.state
1776
+ if (!match) {
1777
+ return
1778
+ }
1779
+
1780
+ if (match.status === 'pending' || match.isFetching === 'loader') {
1781
+ this.cancelMatch(matchId)
1782
+ }
1783
+ })
1784
+ }
1785
+
1786
+ /**
1787
+ * Build the next ParsedLocation from navigation options without committing.
1788
+ * Resolves `to`/`from`, params/search/hash/state, applies search validation
1789
+ * and middlewares, and returns a stable, stringified location object.
1790
+ *
1791
+ * @link https://tanstack.com/router/latest/docs/framework/react/api/router/RouterType#buildlocation-method
1792
+ */
1793
+ buildLocation: BuildLocationFn = (opts) => {
1794
+ const build = (
1795
+ dest: BuildNextOptions & {
1796
+ unmaskOnReload?: boolean
1797
+ } = {},
1798
+ ): ParsedLocation => {
1799
+ // We allow the caller to override the current location
1800
+ const currentLocation =
1801
+ dest._fromLocation || this.pendingBuiltLocation || this.latestLocation
1802
+
1803
+ // Use lightweight matching - only computes what buildLocation needs
1804
+ // (fullPath, search, params) without creating full match objects
1805
+ const lightweightResult = this.matchRoutesLightweight(currentLocation)
1806
+
1807
+ // check that from path exists in the current route tree
1808
+ // do this check only on navigations during test or development
1809
+ if (
1810
+ dest.from &&
1811
+ process.env.NODE_ENV !== 'production' &&
1812
+ dest._isNavigate
1813
+ ) {
1814
+ const allFromMatches = this.getMatchedRoutes(dest.from).matchedRoutes
1815
+
1816
+ const matchedFrom = findLast(lightweightResult.matchedRoutes, (d) => {
1817
+ return comparePaths(d.fullPath, dest.from!)
1818
+ })
1819
+
1820
+ const matchedCurrent = findLast(allFromMatches, (d) => {
1821
+ return comparePaths(d.fullPath, lightweightResult.fullPath)
1822
+ })
1823
+
1824
+ // for from to be invalid it shouldn't just be unmatched to currentLocation
1825
+ // but the currentLocation should also be unmatched to from
1826
+ if (!matchedFrom && !matchedCurrent) {
1827
+ console.warn(`Could not find match for from: ${dest.from}`)
1828
+ }
1829
+ }
1830
+
1831
+ const defaultedFromPath =
1832
+ dest.unsafeRelative === 'path'
1833
+ ? currentLocation.pathname
1834
+ : (dest.from ?? lightweightResult.fullPath)
1835
+
1836
+ // ensure this includes the basePath if set
1837
+ const fromPath = this.resolvePathWithBase(defaultedFromPath, '.')
1838
+
1839
+ // From search should always use the current location
1840
+ const fromSearch = lightweightResult.search
1841
+ // Same with params. It can't hurt to provide as many as possible
1842
+ const fromParams = Object.assign(
1843
+ Object.create(null),
1844
+ lightweightResult.params,
1845
+ )
1846
+
1847
+ // Resolve the next to
1848
+ // ensure this includes the basePath if set
1849
+ const nextTo = dest.to
1850
+ ? this.resolvePathWithBase(fromPath, `${dest.to}`)
1851
+ : this.resolvePathWithBase(fromPath, '.')
1852
+
1853
+ // Resolve the next params
1854
+ const nextParams =
1855
+ dest.params === false || dest.params === null
1856
+ ? Object.create(null)
1857
+ : (dest.params ?? true) === true
1858
+ ? fromParams
1859
+ : Object.assign(
1860
+ fromParams,
1861
+ functionalUpdate(dest.params as any, fromParams),
1862
+ )
1863
+
1864
+ // Use lightweight getMatchedRoutes instead of matchRoutesInternal
1865
+ // This avoids creating full match objects (AbortController, ControlledPromise, etc.)
1866
+ // which are expensive and not needed for buildLocation
1867
+ const destMatchResult = this.getMatchedRoutes(nextTo)
1868
+ let destRoutes = destMatchResult.matchedRoutes
1869
+
1870
+ // Compute globalNotFoundRouteId using the same logic as matchRoutesInternal
1871
+ const isGlobalNotFound =
1872
+ !destMatchResult.foundRoute ||
1873
+ (destMatchResult.foundRoute.path !== '/' &&
1874
+ destMatchResult.routeParams['**'])
1875
+
1876
+ if (isGlobalNotFound && this.options.notFoundRoute) {
1877
+ destRoutes = [...destRoutes, this.options.notFoundRoute]
1878
+ }
1879
+
1880
+ // If there are any params, we need to stringify them
1881
+ if (Object.keys(nextParams).length > 0) {
1882
+ for (const route of destRoutes) {
1883
+ const fn =
1884
+ route.options.params?.stringify ?? route.options.stringifyParams
1885
+ if (fn) {
1886
+ try {
1887
+ Object.assign(nextParams, fn(nextParams))
1888
+ } catch {
1889
+ // Ignore errors here. When a paired parseParams is defined,
1890
+ // extractStrictParams will re-throw during route matching,
1891
+ // storing the error on the match and allowing the route's
1892
+ // errorComponent to render. If no parseParams is defined,
1893
+ // the stringify error is silently dropped.
1894
+ }
1895
+ }
1896
+ }
1897
+ }
1898
+
1899
+ const nextPathname = opts.leaveParams
1900
+ ? // Use the original template path for interpolation
1901
+ // This preserves the original parameter syntax including optional parameters
1902
+ nextTo
1903
+ : decodePath(
1904
+ interpolatePath({
1905
+ path: nextTo,
1906
+ params: nextParams,
1907
+ decoder: this.pathParamsDecoder,
1908
+ server: this.isServer,
1909
+ }).interpolatedPath,
1910
+ ).path
1911
+
1912
+ // Resolve the next search
1913
+ let nextSearch = fromSearch
1914
+ if (opts._includeValidateSearch && this.options.search?.strict) {
1915
+ const validatedSearch = {}
1916
+ destRoutes.forEach((route) => {
1917
+ if (route.options.validateSearch) {
1918
+ try {
1919
+ Object.assign(
1920
+ validatedSearch,
1921
+ validateSearch(route.options.validateSearch, {
1922
+ ...validatedSearch,
1923
+ ...nextSearch,
1924
+ }),
1925
+ )
1926
+ } catch {
1927
+ // ignore errors here because they are already handled in matchRoutes
1928
+ }
1929
+ }
1930
+ })
1931
+ nextSearch = validatedSearch
1932
+ }
1933
+
1934
+ nextSearch = applySearchMiddleware({
1935
+ search: nextSearch,
1936
+ dest,
1937
+ destRoutes,
1938
+ _includeValidateSearch: opts._includeValidateSearch,
1939
+ })
1940
+
1941
+ // Replace the equal deep
1942
+ nextSearch = nullReplaceEqualDeep(fromSearch, nextSearch)
1943
+
1944
+ // Stringify the next search
1945
+ const searchStr = this.options.stringifySearch(nextSearch)
1946
+
1947
+ // Resolve the next hash
1948
+ const hash =
1949
+ dest.hash === true
1950
+ ? currentLocation.hash
1951
+ : dest.hash
1952
+ ? functionalUpdate(dest.hash, currentLocation.hash)
1953
+ : undefined
1954
+
1955
+ // Resolve the next hash string
1956
+ const hashStr = hash ? `#${hash}` : ''
1957
+
1958
+ // Resolve the next state
1959
+ let nextState =
1960
+ dest.state === true
1961
+ ? currentLocation.state
1962
+ : dest.state
1963
+ ? functionalUpdate(dest.state, currentLocation.state)
1964
+ : {}
1965
+
1966
+ // Replace the equal deep
1967
+ nextState = replaceEqualDeep(currentLocation.state, nextState)
1968
+
1969
+ // Create the full path of the location
1970
+ const fullPath = `${nextPathname}${searchStr}${hashStr}`
1971
+
1972
+ // Compute href and publicHref without URL construction when no rewrite
1973
+ let href: string
1974
+ let publicHref: string
1975
+ let external = false
1976
+
1977
+ if (this.rewrite) {
1978
+ // With rewrite, we need to construct URL to apply the rewrite
1979
+ const url = new URL(fullPath, this.origin)
1980
+ const rewrittenUrl = executeRewriteOutput(this.rewrite, url)
1981
+ href = url.href.replace(url.origin, '')
1982
+ // If rewrite changed the origin, publicHref needs full URL
1983
+ // Otherwise just use the path components
1984
+ if (rewrittenUrl.origin !== this.origin) {
1985
+ publicHref = rewrittenUrl.href
1986
+ external = true
1987
+ } else {
1988
+ publicHref =
1989
+ rewrittenUrl.pathname + rewrittenUrl.search + rewrittenUrl.hash
1990
+ }
1991
+ } else {
1992
+ // Fast path: no rewrite, skip URL construction entirely
1993
+ // fullPath is already the correct href (origin-stripped)
1994
+ // We need to encode non-ASCII (unicode) characters for the href
1995
+ // since decodePath decoded them from the interpolated path
1996
+ href = encodePathLikeUrl(fullPath)
1997
+ publicHref = href
1998
+ }
1999
+
2000
+ return {
2001
+ publicHref,
2002
+ href,
2003
+ pathname: nextPathname,
2004
+ search: nextSearch,
2005
+ searchStr,
2006
+ state: nextState as any,
2007
+ hash: hash ?? '',
2008
+ external,
2009
+ unmaskOnReload: dest.unmaskOnReload,
2010
+ }
2011
+ }
2012
+
2013
+ const buildWithMatches = (
2014
+ dest: BuildNextOptions = {},
2015
+ maskedDest?: BuildNextOptions,
2016
+ ) => {
2017
+ const next = build(dest)
2018
+
2019
+ let maskedNext = maskedDest ? build(maskedDest) : undefined
2020
+
2021
+ if (!maskedNext) {
2022
+ const params = Object.create(null)
2023
+
2024
+ if (this.options.routeMasks) {
2025
+ const match = findFlatMatch<RouteMask<TRouteTree>>(
2026
+ next.pathname,
2027
+ this.processedTree,
2028
+ )
2029
+ if (match) {
2030
+ Object.assign(params, match.rawParams) // Copy params, because they're cached
2031
+ const {
2032
+ from: _from,
2033
+ params: maskParams,
2034
+ ...maskProps
2035
+ } = match.route
2036
+
2037
+ // If mask has a params function, call it with the matched params as context
2038
+ // Otherwise, use the matched params or the provided params value
2039
+ const nextParams =
2040
+ maskParams === false || maskParams === null
2041
+ ? Object.create(null)
2042
+ : (maskParams ?? true) === true
2043
+ ? params
2044
+ : Object.assign(params, functionalUpdate(maskParams, params))
2045
+
2046
+ maskedDest = {
2047
+ from: opts.from,
2048
+ ...maskProps,
2049
+ params: nextParams,
2050
+ }
2051
+ maskedNext = build(maskedDest)
2052
+ }
2053
+ }
2054
+ }
2055
+
2056
+ if (maskedNext) {
2057
+ next.maskedLocation = maskedNext
2058
+ }
2059
+
2060
+ return next
2061
+ }
2062
+
2063
+ if (opts.mask) {
2064
+ return buildWithMatches(opts, {
2065
+ from: opts.from,
2066
+ ...opts.mask,
2067
+ })
2068
+ }
2069
+
2070
+ return buildWithMatches(opts)
2071
+ }
2072
+
2073
+ commitLocationPromise: undefined | ControlledPromise<void>
2074
+
2075
+ /**
2076
+ * Commit a previously built location to history (push/replace), optionally
2077
+ * using view transitions and scroll restoration options.
2078
+ */
2079
+ commitLocation: CommitLocationFn = async ({
2080
+ viewTransition,
2081
+ ignoreBlocker,
2082
+ ...next
2083
+ }) => {
2084
+ const isSameState = () => {
2085
+ // the following props are ignored but may still be provided when navigating,
2086
+ // temporarily add the previous values to the next state so they don't affect
2087
+ // the comparison
2088
+ const ignoredProps = [
2089
+ 'key', // TODO: Remove in v2 - use __TSR_key instead
2090
+ '__TSR_key',
2091
+ '__TSR_index',
2092
+ '__hashScrollIntoViewOptions',
2093
+ ] as const
2094
+ ignoredProps.forEach((prop) => {
2095
+ ;(next.state as any)[prop] = this.latestLocation.state[prop]
2096
+ })
2097
+ const isEqual = deepEqual(next.state, this.latestLocation.state)
2098
+ ignoredProps.forEach((prop) => {
2099
+ delete next.state[prop]
2100
+ })
2101
+ return isEqual
2102
+ }
2103
+
2104
+ const isSameUrl =
2105
+ trimPathRight(this.latestLocation.href) === trimPathRight(next.href)
2106
+
2107
+ let previousCommitPromise = this.commitLocationPromise
2108
+ this.commitLocationPromise = createControlledPromise<void>(() => {
2109
+ previousCommitPromise?.resolve()
2110
+ previousCommitPromise = undefined
2111
+ })
2112
+
2113
+ // Don't commit to history if nothing changed
2114
+ if (isSameUrl && isSameState()) {
2115
+ this.load()
2116
+ } else {
2117
+ let {
2118
+ // eslint-disable-next-line prefer-const
2119
+ maskedLocation,
2120
+ // eslint-disable-next-line prefer-const
2121
+ hashScrollIntoView,
2122
+ ...nextHistory
2123
+ } = next
2124
+
2125
+ if (maskedLocation) {
2126
+ nextHistory = {
2127
+ ...maskedLocation,
2128
+ state: {
2129
+ ...maskedLocation.state,
2130
+ __tempKey: undefined,
2131
+ __tempLocation: {
2132
+ ...nextHistory,
2133
+ search: nextHistory.searchStr,
2134
+ state: {
2135
+ ...nextHistory.state,
2136
+ __tempKey: undefined!,
2137
+ __tempLocation: undefined!,
2138
+ __TSR_key: undefined!,
2139
+ key: undefined!, // TODO: Remove in v2 - use __TSR_key instead
2140
+ },
2141
+ },
2142
+ },
2143
+ }
2144
+
2145
+ if (
2146
+ nextHistory.unmaskOnReload ??
2147
+ this.options.unmaskOnReload ??
2148
+ false
2149
+ ) {
2150
+ nextHistory.state.__tempKey = this.tempLocationKey
2151
+ }
2152
+ }
2153
+
2154
+ nextHistory.state.__hashScrollIntoViewOptions =
2155
+ hashScrollIntoView ?? this.options.defaultHashScrollIntoView ?? true
2156
+
2157
+ this.shouldViewTransition = viewTransition
2158
+
2159
+ this.history[next.replace ? 'replace' : 'push'](
2160
+ nextHistory.publicHref,
2161
+ nextHistory.state,
2162
+ { ignoreBlocker },
2163
+ )
2164
+ }
2165
+
2166
+ this.resetNextScroll = next.resetScroll ?? true
2167
+
2168
+ if (!this.history.subscribers.size) {
2169
+ this.load()
2170
+ }
2171
+
2172
+ return this.commitLocationPromise
2173
+ }
2174
+
2175
+ /** Convenience helper: build a location from options, then commit it. */
2176
+ buildAndCommitLocation = ({
2177
+ replace,
2178
+ resetScroll,
2179
+ hashScrollIntoView,
2180
+ viewTransition,
2181
+ ignoreBlocker,
2182
+ href,
2183
+ ...rest
2184
+ }: BuildNextOptions & CommitLocationOptions = {}) => {
2185
+ if (href) {
2186
+ const currentIndex = this.history.location.state.__TSR_index
2187
+
2188
+ const parsed = parseHref(href, {
2189
+ __TSR_index: replace ? currentIndex : currentIndex + 1,
2190
+ })
2191
+
2192
+ // If the href contains the basepath, we need to strip it before setting `to`
2193
+ // because `buildLocation` will add the basepath back when creating the final URL.
2194
+ // Without this, hrefs like '/app/about' would become '/app/app/about'.
2195
+ const hrefUrl = new URL(parsed.pathname, this.origin)
2196
+ const rewrittenUrl = executeRewriteInput(this.rewrite, hrefUrl)
2197
+
2198
+ rest.to = rewrittenUrl.pathname
2199
+ rest.search = this.options.parseSearch(parsed.search)
2200
+ // remove the leading `#` from the hash
2201
+ rest.hash = parsed.hash.slice(1)
2202
+ }
2203
+
2204
+ const location = this.buildLocation({
2205
+ ...(rest as any),
2206
+ _includeValidateSearch: true,
2207
+ })
2208
+
2209
+ this.pendingBuiltLocation = location as ParsedLocation<
2210
+ FullSearchSchema<TRouteTree>
2211
+ >
2212
+
2213
+ const commitPromise = this.commitLocation({
2214
+ ...location,
2215
+ viewTransition,
2216
+ replace,
2217
+ resetScroll,
2218
+ hashScrollIntoView,
2219
+ ignoreBlocker,
2220
+ })
2221
+
2222
+ // Clear pending location after commit starts
2223
+ // We do this on next microtask to allow synchronous navigate calls to chain
2224
+ Promise.resolve().then(() => {
2225
+ if (this.pendingBuiltLocation === location) {
2226
+ this.pendingBuiltLocation = undefined
2227
+ }
2228
+ })
2229
+
2230
+ return commitPromise
2231
+ }
2232
+
2233
+ /**
2234
+ * Imperatively navigate using standard `NavigateOptions`. When `reloadDocument`
2235
+ * or an absolute `href` is provided, performs a full document navigation.
2236
+ * Otherwise, builds and commits a client-side location.
2237
+ *
2238
+ * @link https://tanstack.com/router/latest/docs/framework/react/api/router/NavigateOptionsType
2239
+ */
2240
+ navigate: NavigateFn = async ({
2241
+ to,
2242
+ reloadDocument,
2243
+ href,
2244
+ publicHref,
2245
+ ...rest
2246
+ }) => {
2247
+ let hrefIsUrl = false
2248
+
2249
+ if (href) {
2250
+ try {
2251
+ new URL(`${href}`)
2252
+ hrefIsUrl = true
2253
+ } catch {}
2254
+ }
2255
+
2256
+ if (hrefIsUrl && !reloadDocument) {
2257
+ reloadDocument = true
2258
+ }
2259
+
2260
+ if (reloadDocument) {
2261
+ // When to is provided, always build a location to get the proper publicHref
2262
+ // (this handles redirects where href might be an internal path from resolveRedirect)
2263
+ // When only href is provided (no to), use it directly as it should already
2264
+ // be a complete path (possibly with basepath)
2265
+ if (to !== undefined || !href) {
2266
+ const location = this.buildLocation({ to, ...rest } as any)
2267
+ // Use publicHref which contains the path (origin-stripped is fine for reload)
2268
+ href = href ?? location.publicHref
2269
+ publicHref = publicHref ?? location.publicHref
2270
+ }
2271
+
2272
+ // Use publicHref when available and href is not a full URL,
2273
+ // otherwise use href directly (which may already include basepath)
2274
+ const reloadHref = !hrefIsUrl && publicHref ? publicHref : href
2275
+
2276
+ // Block dangerous protocols like javascript:, blob:, data:
2277
+ // These could execute arbitrary code if passed to window.location
2278
+ if (isDangerousProtocol(reloadHref, this.protocolAllowlist)) {
2279
+ if (process.env.NODE_ENV !== 'production') {
2280
+ console.warn(
2281
+ `Blocked navigation to dangerous protocol: ${reloadHref}`,
2282
+ )
2283
+ }
2284
+ return Promise.resolve()
2285
+ }
2286
+
2287
+ // Check blockers for external URLs unless ignoreBlocker is true
2288
+ if (!rest.ignoreBlocker) {
2289
+ // Cast to access internal getBlockers method
2290
+ const historyWithBlockers = this.history as any
2291
+ const blockers = historyWithBlockers.getBlockers?.() ?? []
2292
+ for (const blocker of blockers) {
2293
+ if (blocker?.blockerFn) {
2294
+ const shouldBlock = await blocker.blockerFn({
2295
+ currentLocation: this.latestLocation,
2296
+ nextLocation: this.latestLocation, // External URLs don't have a next location in our router
2297
+ action: 'PUSH',
2298
+ })
2299
+ if (shouldBlock) {
2300
+ return Promise.resolve()
2301
+ }
2302
+ }
2303
+ }
2304
+ }
2305
+
2306
+ if (rest.replace) {
2307
+ window.location.replace(reloadHref)
2308
+ } else {
2309
+ window.location.href = reloadHref
2310
+ }
2311
+ return Promise.resolve()
2312
+ }
2313
+
2314
+ return this.buildAndCommitLocation({
2315
+ ...rest,
2316
+ href,
2317
+ to: to as string,
2318
+ _isNavigate: true,
2319
+ })
2320
+ }
2321
+
2322
+ latestLoadPromise: undefined | Promise<void>
2323
+
2324
+ beforeLoad = () => {
2325
+ // Cancel any pending matches
2326
+ this.cancelMatches()
2327
+ this.updateLatestLocation()
2328
+
2329
+ if (isServer ?? this.isServer) {
2330
+ // for SPAs on the initial load, this is handled by the Transitioner
2331
+ const nextLocation = this.buildLocation({
2332
+ to: this.latestLocation.pathname,
2333
+ search: true,
2334
+ params: true,
2335
+ hash: true,
2336
+ state: true,
2337
+ _includeValidateSearch: true,
2338
+ })
2339
+
2340
+ // Check if location changed - origin check is unnecessary since buildLocation
2341
+ // always uses this.origin when constructing URLs
2342
+ if (this.latestLocation.publicHref !== nextLocation.publicHref) {
2343
+ const href = this.getParsedLocationHref(nextLocation)
2344
+ if (nextLocation.external) {
2345
+ throw redirect({ href })
2346
+ } else {
2347
+ throw redirect({ href, _builtLocation: nextLocation })
2348
+ }
2349
+ }
2350
+ }
2351
+
2352
+ // Match the routes
2353
+ const pendingMatches = this.matchRoutes(this.latestLocation)
2354
+
2355
+ const nextCachedMatches = this.stores.cachedMatchesSnapshot.state.filter(
2356
+ (d) => !pendingMatches.some((e) => e.id === d.id),
2357
+ )
2358
+
2359
+ // Ingest the new matches
2360
+ this.batch(() => {
2361
+ this.stores.status.setState(() => 'pending')
2362
+ this.stores.statusCode.setState(() => 200)
2363
+ this.stores.isLoading.setState(() => true)
2364
+ this.stores.location.setState(() => this.latestLocation)
2365
+ this.stores.setPendingMatches(pendingMatches)
2366
+ // If a cached match moved to pending matches, remove it from cached matches
2367
+ this.stores.setCachedMatches(nextCachedMatches)
2368
+ })
2369
+ }
2370
+
2371
+ load: LoadFn = async (opts?: { sync?: boolean }): Promise<void> => {
2372
+ let redirect: AnyRedirect | undefined
2373
+ let notFound: NotFoundError | undefined
2374
+ let loadPromise: Promise<void>
2375
+ const previousLocation =
2376
+ this.stores.resolvedLocation.state ?? this.stores.location.state
2377
+
2378
+ // eslint-disable-next-line prefer-const
2379
+ loadPromise = new Promise<void>((resolve) => {
2380
+ this.startTransition(async () => {
2381
+ try {
2382
+ this.beforeLoad()
2383
+ const next = this.latestLocation
2384
+ const prevLocation = this.stores.resolvedLocation.state
2385
+ const locationChangeInfo = getLocationChangeInfo(next, prevLocation)
2386
+
2387
+ if (!this.stores.redirect.state) {
2388
+ this.emit({
2389
+ type: 'onBeforeNavigate',
2390
+ ...locationChangeInfo,
2391
+ })
2392
+ }
2393
+
2394
+ this.emit({
2395
+ type: 'onBeforeLoad',
2396
+ ...locationChangeInfo,
2397
+ })
2398
+
2399
+ await loadMatches({
2400
+ router: this,
2401
+ sync: opts?.sync,
2402
+ forceStaleReload: previousLocation.href === next.href,
2403
+ matches: this.stores.pendingMatchesSnapshot.state,
2404
+ location: next,
2405
+ updateMatch: this.updateMatch,
2406
+ // eslint-disable-next-line @typescript-eslint/require-await
2407
+ onReady: async () => {
2408
+ // Wrap batch in framework-specific transition wrapper (e.g., Solid's startTransition)
2409
+ this.startTransition(() => {
2410
+ this.startViewTransition(async () => {
2411
+ // this.viewTransitionPromise = createControlledPromise<true>()
2412
+
2413
+ // Commit the pending matches. If a previous match was
2414
+ // removed, place it in the cachedMatches
2415
+ //
2416
+ // exitingMatches uses match.id (routeId + params + loaderDeps) so
2417
+ // navigating /foo?page=1 → /foo?page=2 correctly caches the page=1 entry.
2418
+ let exitingMatches: Array<AnyRouteMatch> | null = null
2419
+
2420
+ // Lifecycle-hook identity uses routeId only so that navigating between
2421
+ // different params/deps of the same route fires onStay (not onLeave+onEnter).
2422
+ let hookExitingMatches: Array<AnyRouteMatch> | null = null
2423
+ let hookEnteringMatches: Array<AnyRouteMatch> | null = null
2424
+ let hookStayingMatches: Array<AnyRouteMatch> | null = null
2425
+
2426
+ this.batch(() => {
2427
+ const pendingMatches =
2428
+ this.stores.pendingMatchesSnapshot.state
2429
+ const mountPending = pendingMatches.length
2430
+ const currentMatches =
2431
+ this.stores.activeMatchesSnapshot.state
2432
+
2433
+ exitingMatches = mountPending
2434
+ ? currentMatches.filter(
2435
+ (match) =>
2436
+ !this.stores.pendingMatchStoresById.has(match.id),
2437
+ )
2438
+ : null
2439
+
2440
+ // Lifecycle-hook identity: routeId only (route presence in tree)
2441
+ // Build routeId sets from pools to avoid derived stores.
2442
+ const pendingRouteIds = new Set<string>()
2443
+ for (const s of this.stores.pendingMatchStoresById.values()) {
2444
+ if (s.routeId) pendingRouteIds.add(s.routeId)
2445
+ }
2446
+ const activeRouteIds = new Set<string>()
2447
+ for (const s of this.stores.activeMatchStoresById.values()) {
2448
+ if (s.routeId) activeRouteIds.add(s.routeId)
2449
+ }
2450
+
2451
+ hookExitingMatches = mountPending
2452
+ ? currentMatches.filter(
2453
+ (match) => !pendingRouteIds.has(match.routeId),
2454
+ )
2455
+ : null
2456
+ hookEnteringMatches = mountPending
2457
+ ? pendingMatches.filter(
2458
+ (match) => !activeRouteIds.has(match.routeId),
2459
+ )
2460
+ : null
2461
+ hookStayingMatches = mountPending
2462
+ ? pendingMatches.filter((match) =>
2463
+ activeRouteIds.has(match.routeId),
2464
+ )
2465
+ : currentMatches
2466
+
2467
+ this.stores.isLoading.setState(() => false)
2468
+ this.stores.loadedAt.setState(() => Date.now())
2469
+ /**
2470
+ * When committing new matches, cache any exiting matches that are still usable.
2471
+ * Routes that resolved with `status: 'error'` or `status: 'notFound'` are
2472
+ * deliberately excluded from `cachedMatches` so that subsequent invalidations
2473
+ * or reloads re-run their loaders instead of reusing the failed/not-found data.
2474
+ */
2475
+ if (mountPending) {
2476
+ this.stores.setActiveMatches(pendingMatches)
2477
+ this.stores.setPendingMatches([])
2478
+ this.stores.setCachedMatches([
2479
+ ...this.stores.cachedMatchesSnapshot.state,
2480
+ ...exitingMatches!.filter(
2481
+ (d) =>
2482
+ d.status !== 'error' &&
2483
+ d.status !== 'notFound' &&
2484
+ d.status !== 'redirected',
2485
+ ),
2486
+ ])
2487
+ this.clearExpiredCache()
2488
+ }
2489
+ })
2490
+
2491
+ //
2492
+ for (const [matches, hook] of [
2493
+ [hookExitingMatches, 'onLeave'],
2494
+ [hookEnteringMatches, 'onEnter'],
2495
+ [hookStayingMatches, 'onStay'],
2496
+ ] as const) {
2497
+ if (!matches) continue
2498
+ for (const match of matches as Array<AnyRouteMatch>) {
2499
+ this.looseRoutesById[match.routeId]!.options[hook]?.(
2500
+ match,
2501
+ )
2502
+ }
2503
+ }
2504
+ })
2505
+ })
2506
+ },
2507
+ })
2508
+ } catch (err) {
2509
+ if (isRedirect(err)) {
2510
+ redirect = err
2511
+ if (!(isServer ?? this.isServer)) {
2512
+ this.navigate({
2513
+ ...redirect.options,
2514
+ replace: true,
2515
+ ignoreBlocker: true,
2516
+ })
2517
+ }
2518
+ } else if (isNotFound(err)) {
2519
+ notFound = err
2520
+ }
2521
+
2522
+ const nextStatusCode = redirect
2523
+ ? redirect.status
2524
+ : notFound
2525
+ ? 404
2526
+ : this.stores.activeMatchesSnapshot.state.some(
2527
+ (d) => d.status === 'error',
2528
+ )
2529
+ ? 500
2530
+ : 200
2531
+
2532
+ this.batch(() => {
2533
+ this.stores.statusCode.setState(() => nextStatusCode)
2534
+ this.stores.redirect.setState(() => redirect)
2535
+ })
2536
+ }
2537
+
2538
+ if (this.latestLoadPromise === loadPromise) {
2539
+ this.commitLocationPromise?.resolve()
2540
+ this.latestLoadPromise = undefined
2541
+ this.commitLocationPromise = undefined
2542
+ }
2543
+
2544
+ resolve()
2545
+ })
2546
+ })
2547
+
2548
+ this.latestLoadPromise = loadPromise
2549
+
2550
+ await loadPromise
2551
+
2552
+ while (
2553
+ (this.latestLoadPromise as any) &&
2554
+ loadPromise !== this.latestLoadPromise
2555
+ ) {
2556
+ await this.latestLoadPromise
2557
+ }
2558
+
2559
+ let newStatusCode: number | undefined = undefined
2560
+ if (this.hasNotFoundMatch()) {
2561
+ newStatusCode = 404
2562
+ } else if (
2563
+ this.stores.activeMatchesSnapshot.state.some((d) => d.status === 'error')
2564
+ ) {
2565
+ newStatusCode = 500
2566
+ }
2567
+ if (newStatusCode !== undefined) {
2568
+ this.stores.statusCode.setState(() => newStatusCode)
2569
+ }
2570
+ }
2571
+
2572
+ startViewTransition = (fn: () => Promise<void>) => {
2573
+ // Determine if we should start a view transition from the navigation
2574
+ // or from the router default
2575
+ const shouldViewTransition =
2576
+ this.shouldViewTransition ?? this.options.defaultViewTransition
2577
+
2578
+ // Reset the view transition flag
2579
+ this.shouldViewTransition = undefined
2580
+
2581
+ // Attempt to start a view transition (or just apply the changes if we can't)
2582
+ if (
2583
+ shouldViewTransition &&
2584
+ typeof document !== 'undefined' &&
2585
+ 'startViewTransition' in document &&
2586
+ typeof document.startViewTransition === 'function'
2587
+ ) {
2588
+ // lib.dom.ts doesn't support viewTransition types variant yet.
2589
+ // TODO: Fix this when dom types are updated
2590
+ let startViewTransitionParams: any
2591
+
2592
+ if (
2593
+ typeof shouldViewTransition === 'object' &&
2594
+ this.isViewTransitionTypesSupported
2595
+ ) {
2596
+ const next = this.latestLocation
2597
+ const prevLocation = this.stores.resolvedLocation.state
2598
+
2599
+ const resolvedViewTransitionTypes =
2600
+ typeof shouldViewTransition.types === 'function'
2601
+ ? shouldViewTransition.types(
2602
+ getLocationChangeInfo(next, prevLocation),
2603
+ )
2604
+ : shouldViewTransition.types
2605
+
2606
+ if (resolvedViewTransitionTypes === false) {
2607
+ fn()
2608
+ return
2609
+ }
2610
+
2611
+ startViewTransitionParams = {
2612
+ update: fn,
2613
+ types: resolvedViewTransitionTypes,
2614
+ }
2615
+ } else {
2616
+ startViewTransitionParams = fn
2617
+ }
2618
+
2619
+ document.startViewTransition(startViewTransitionParams)
2620
+ } else {
2621
+ fn()
2622
+ }
2623
+ }
2624
+
2625
+ updateMatch: UpdateMatchFn = (id, updater) => {
2626
+ this.startTransition(() => {
2627
+ const pendingMatch = this.stores.pendingMatchStoresById.get(id)
2628
+ if (pendingMatch) {
2629
+ pendingMatch.setState(updater)
2630
+ return
2631
+ }
2632
+
2633
+ const activeMatch = this.stores.activeMatchStoresById.get(id)
2634
+ if (activeMatch) {
2635
+ activeMatch.setState(updater)
2636
+ return
2637
+ }
2638
+
2639
+ const cachedMatch = this.stores.cachedMatchStoresById.get(id)
2640
+ if (cachedMatch) {
2641
+ const next = updater(cachedMatch.state)
2642
+ if (next.status === 'redirected') {
2643
+ const deleted = this.stores.cachedMatchStoresById.delete(id)
2644
+ if (deleted) {
2645
+ this.stores.cachedMatchesId.setState((prev) =>
2646
+ prev.filter((matchId) => matchId !== id),
2647
+ )
2648
+ }
2649
+ } else {
2650
+ cachedMatch.setState(() => next)
2651
+ }
2652
+ }
2653
+ })
2654
+ }
2655
+
2656
+ getMatch: GetMatchFn = (matchId: string): AnyRouteMatch | undefined => {
2657
+ return (
2658
+ this.stores.cachedMatchStoresById.get(matchId)?.state ??
2659
+ this.stores.pendingMatchStoresById.get(matchId)?.state ??
2660
+ this.stores.activeMatchStoresById.get(matchId)?.state
2661
+ )
2662
+ }
2663
+
2664
+ /**
2665
+ * Invalidate the current matches and optionally force them back into a pending state.
2666
+ *
2667
+ * - Marks all matches that pass the optional `filter` as `invalid: true`.
2668
+ * - If `forcePending` is true, or a match is currently in `'error'` or `'notFound'` status,
2669
+ * its status is reset to `'pending'` and its `error` cleared so that the loader is re-run
2670
+ * on the next `load()` call (eg. after HMR or a manual invalidation).
2671
+ */
2672
+ invalidate: InvalidateFn<
2673
+ RouterCore<
2674
+ TRouteTree,
2675
+ TTrailingSlashOption,
2676
+ TDefaultStructuralSharingOption,
2677
+ TRouterHistory,
2678
+ TDehydrated
2679
+ >
2680
+ > = (opts) => {
2681
+ const invalidate = (d: MakeRouteMatch<TRouteTree>) => {
2682
+ if (opts?.filter?.(d as MakeRouteMatchUnion<this>) ?? true) {
2683
+ return {
2684
+ ...d,
2685
+ invalid: true,
2686
+ ...(opts?.forcePending ||
2687
+ d.status === 'error' ||
2688
+ d.status === 'notFound'
2689
+ ? ({ status: 'pending', error: undefined } as const)
2690
+ : undefined),
2691
+ }
2692
+ }
2693
+ return d
2694
+ }
2695
+
2696
+ this.batch(() => {
2697
+ this.stores.setActiveMatches(
2698
+ this.stores.activeMatchesSnapshot.state.map(invalidate),
2699
+ )
2700
+ this.stores.setCachedMatches(
2701
+ this.stores.cachedMatchesSnapshot.state.map(invalidate),
2702
+ )
2703
+ this.stores.setPendingMatches(
2704
+ this.stores.pendingMatchesSnapshot.state.map(invalidate),
2705
+ )
2706
+ })
2707
+
2708
+ this.shouldViewTransition = false
2709
+ return this.load({ sync: opts?.sync })
2710
+ }
2711
+
2712
+ getParsedLocationHref = (location: ParsedLocation) => {
2713
+ // For redirects and external use, we need publicHref (with rewrite output applied)
2714
+ // href is the internal path after rewrite input, publicHref is user-facing
2715
+ return location.publicHref || '/'
2716
+ }
2717
+
2718
+ resolveRedirect = (redirect: AnyRedirect): AnyRedirect => {
2719
+ const locationHeader = redirect.headers.get('Location')
2720
+
2721
+ if (!redirect.options.href || redirect.options._builtLocation) {
2722
+ const location =
2723
+ redirect.options._builtLocation ?? this.buildLocation(redirect.options)
2724
+ const href = this.getParsedLocationHref(location)
2725
+ redirect.options.href = href
2726
+ redirect.headers.set('Location', href)
2727
+ } else if (locationHeader) {
2728
+ try {
2729
+ const url = new URL(locationHeader)
2730
+ if (this.origin && url.origin === this.origin) {
2731
+ const href = url.pathname + url.search + url.hash
2732
+ redirect.options.href = href
2733
+ redirect.headers.set('Location', href)
2734
+ }
2735
+ } catch {
2736
+ // ignore invalid URLs
2737
+ }
2738
+ }
2739
+
2740
+ if (
2741
+ redirect.options.href &&
2742
+ !redirect.options._builtLocation &&
2743
+ // Check for dangerous protocols before processing the redirect
2744
+ isDangerousProtocol(redirect.options.href, this.protocolAllowlist)
2745
+ ) {
2746
+ throw new Error(
2747
+ process.env.NODE_ENV !== 'production'
2748
+ ? `Redirect blocked: unsafe protocol in href "${redirect.options.href}". Allowed protocols: ${Array.from(this.protocolAllowlist).join(', ')}.`
2749
+ : 'Redirect blocked: unsafe protocol',
2750
+ )
2751
+ }
2752
+
2753
+ if (!redirect.headers.get('Location')) {
2754
+ redirect.headers.set('Location', redirect.options.href)
2755
+ }
2756
+
2757
+ return redirect
2758
+ }
2759
+
2760
+ clearCache: ClearCacheFn<this> = (opts) => {
2761
+ const filter = opts?.filter
2762
+ if (filter !== undefined) {
2763
+ this.stores.setCachedMatches(
2764
+ this.stores.cachedMatchesSnapshot.state.filter(
2765
+ (m) => !filter(m as MakeRouteMatchUnion<this>),
2766
+ ),
2767
+ )
2768
+ } else {
2769
+ this.stores.setCachedMatches([])
2770
+ }
2771
+ }
2772
+
2773
+ clearExpiredCache = () => {
2774
+ const now = Date.now()
2775
+ // This is where all of the garbage collection magic happens
2776
+ const filter = (d: MakeRouteMatch<TRouteTree>) => {
2777
+ const route = this.looseRoutesById[d.routeId]!
2778
+
2779
+ if (!route.options.loader) {
2780
+ return true
2781
+ }
2782
+
2783
+ // If the route was preloaded, use the preloadGcTime
2784
+ // otherwise, use the gcTime
2785
+ const gcTime =
2786
+ (d.preload
2787
+ ? (route.options.preloadGcTime ?? this.options.defaultPreloadGcTime)
2788
+ : (route.options.gcTime ?? this.options.defaultGcTime)) ??
2789
+ 5 * 60 * 1000
2790
+
2791
+ const isError = d.status === 'error'
2792
+ if (isError) return true
2793
+
2794
+ const gcEligible = now - d.updatedAt >= gcTime
2795
+ return gcEligible
2796
+ }
2797
+ this.clearCache({ filter })
2798
+ }
2799
+
2800
+ loadRouteChunk = loadRouteChunk
2801
+
2802
+ preloadRoute: PreloadRouteFn<
2803
+ TRouteTree,
2804
+ TTrailingSlashOption,
2805
+ TDefaultStructuralSharingOption,
2806
+ TRouterHistory
2807
+ > = async (opts) => {
2808
+ const next = opts._builtLocation ?? this.buildLocation(opts as any)
2809
+
2810
+ let matches = this.matchRoutes(next, {
2811
+ throwOnError: true,
2812
+ preload: true,
2813
+ dest: opts,
2814
+ })
2815
+
2816
+ const activeMatchIds = new Set([
2817
+ ...this.stores.matchesId.state,
2818
+ ...this.stores.pendingMatchesId.state,
2819
+ ])
2820
+
2821
+ const loadedMatchIds = new Set([
2822
+ ...activeMatchIds,
2823
+ ...this.stores.cachedMatchesId.state,
2824
+ ])
2825
+
2826
+ // If the matches are already loaded, we need to add them to the cached matches.
2827
+ const matchesToCache = matches.filter(
2828
+ (match) => !loadedMatchIds.has(match.id),
2829
+ )
2830
+ if (matchesToCache.length) {
2831
+ const cachedMatches = this.stores.cachedMatchesSnapshot.state
2832
+ this.stores.setCachedMatches([...cachedMatches, ...matchesToCache])
2833
+ }
2834
+
2835
+ try {
2836
+ matches = await loadMatches({
2837
+ router: this,
2838
+ matches,
2839
+ location: next,
2840
+ preload: true,
2841
+ updateMatch: (id, updater) => {
2842
+ // Don't update the match if it's currently loaded
2843
+ if (activeMatchIds.has(id)) {
2844
+ matches = matches.map((d) => (d.id === id ? updater(d) : d))
2845
+ } else {
2846
+ this.updateMatch(id, updater)
2847
+ }
2848
+ },
2849
+ })
2850
+
2851
+ return matches
2852
+ } catch (err) {
2853
+ if (isRedirect(err)) {
2854
+ if (err.options.reloadDocument) {
2855
+ return undefined
2856
+ }
2857
+
2858
+ return await this.preloadRoute({
2859
+ ...err.options,
2860
+ _fromLocation: next,
2861
+ })
2862
+ }
2863
+ if (!isNotFound(err)) {
2864
+ // Preload errors are not fatal, but we should still log them
2865
+ console.error(err)
2866
+ }
2867
+ return undefined
2868
+ }
2869
+ }
2870
+
2871
+ matchRoute: MatchRouteFn<
2872
+ TRouteTree,
2873
+ TTrailingSlashOption,
2874
+ TDefaultStructuralSharingOption,
2875
+ TRouterHistory
2876
+ > = (location, opts) => {
2877
+ const matchLocation = {
2878
+ ...location,
2879
+ to: location.to
2880
+ ? this.resolvePathWithBase(location.from || '', location.to as string)
2881
+ : undefined,
2882
+ params: location.params || {},
2883
+ leaveParams: true,
2884
+ }
2885
+ const next = this.buildLocation(matchLocation as any)
2886
+
2887
+ if (opts?.pending && this.stores.status.state !== 'pending') {
2888
+ return false
2889
+ }
2890
+
2891
+ const pending =
2892
+ opts?.pending === undefined ? !this.stores.isLoading.state : opts.pending
2893
+
2894
+ const baseLocation = pending
2895
+ ? this.latestLocation
2896
+ : this.stores.resolvedLocation.state || this.stores.location.state
2897
+
2898
+ const match = findSingleMatch(
2899
+ next.pathname,
2900
+ opts?.caseSensitive ?? false,
2901
+ opts?.fuzzy ?? false,
2902
+ baseLocation.pathname,
2903
+ this.processedTree,
2904
+ )
2905
+
2906
+ if (!match) {
2907
+ return false
2908
+ }
2909
+
2910
+ if (location.params) {
2911
+ if (!deepEqual(match.rawParams, location.params, { partial: true })) {
2912
+ return false
2913
+ }
2914
+ }
2915
+
2916
+ if (opts?.includeSearch ?? true) {
2917
+ return deepEqual(baseLocation.search, next.search, { partial: true })
2918
+ ? match.rawParams
2919
+ : false
2920
+ }
2921
+
2922
+ return match.rawParams
2923
+ }
2924
+
2925
+ ssr?: {
2926
+ manifest: Manifest | undefined
2927
+ }
2928
+
2929
+ serverSsr?: ServerSsr
2930
+
2931
+ hasNotFoundMatch = () => {
2932
+ return this.stores.activeMatchesSnapshot.state.some(
2933
+ (d) => d.status === 'notFound' || d.globalNotFound,
2934
+ )
2935
+ }
2936
+ }
2937
+
2938
+ /** Error thrown when search parameter validation fails. */
2939
+ export class SearchParamError extends Error {}
2940
+
2941
+ /** Error thrown when path parameter parsing/validation fails. */
2942
+ export class PathParamError extends Error {}
2943
+
2944
+ const normalize = (str: string) =>
2945
+ str.endsWith('/') && str.length > 1 ? str.slice(0, -1) : str
2946
+ function comparePaths(a: string, b: string) {
2947
+ return normalize(a) === normalize(b)
2948
+ }
2949
+
2950
+ /**
2951
+ * Lazily import a module function and forward arguments to it, retaining
2952
+ * parameter and return types for the selected export key.
2953
+ */
2954
+ export function lazyFn<
2955
+ T extends Record<string, (...args: Array<any>) => any>,
2956
+ TKey extends keyof T = 'default',
2957
+ >(fn: () => Promise<T>, key?: TKey) {
2958
+ return async (
2959
+ ...args: Parameters<T[TKey]>
2960
+ ): Promise<Awaited<ReturnType<T[TKey]>>> => {
2961
+ const imported = await fn()
2962
+ return imported[key || 'default'](...args)
2963
+ }
2964
+ }
2965
+
2966
+ /** Create an initial RouterState from a parsed location. */
2967
+ export function getInitialRouterState(
2968
+ location: ParsedLocation,
2969
+ ): RouterState<any> {
2970
+ return {
2971
+ loadedAt: 0,
2972
+ isLoading: false,
2973
+ isTransitioning: false,
2974
+ status: 'idle',
2975
+ resolvedLocation: undefined,
2976
+ location,
2977
+ matches: [],
2978
+ statusCode: 200,
2979
+ }
2980
+ }
2981
+
2982
+ function validateSearch(validateSearch: AnyValidator, input: unknown): unknown {
2983
+ if (validateSearch == null) return {}
2984
+
2985
+ if ('~standard' in validateSearch) {
2986
+ const result = validateSearch['~standard'].validate(input)
2987
+
2988
+ if (result instanceof Promise)
2989
+ throw new SearchParamError('Async validation not supported')
2990
+
2991
+ if (result.issues)
2992
+ throw new SearchParamError(JSON.stringify(result.issues, undefined, 2), {
2993
+ cause: result,
2994
+ })
2995
+
2996
+ return result.value
2997
+ }
2998
+
2999
+ if ('parse' in validateSearch) {
3000
+ return validateSearch.parse(input)
3001
+ }
3002
+
3003
+ if (typeof validateSearch === 'function') {
3004
+ return validateSearch(input)
3005
+ }
3006
+
3007
+ return {}
3008
+ }
3009
+
3010
+ /**
3011
+ * Build the matched route chain and extract params for a pathname.
3012
+ * Falls back to the root route if no specific route is found.
3013
+ */
3014
+ export function getMatchedRoutes<TRouteLike extends RouteLike>({
3015
+ pathname,
3016
+ routesById,
3017
+ processedTree,
3018
+ }: {
3019
+ pathname: string
3020
+ routesById: Record<string, TRouteLike>
3021
+ processedTree: ProcessedTree<any, any, any>
3022
+ }) {
3023
+ const routeParams: Record<string, string> = Object.create(null)
3024
+ const trimmedPath = trimPathRight(pathname)
3025
+
3026
+ let foundRoute: TRouteLike | undefined = undefined
3027
+ let parsedParams: Record<string, unknown> | undefined = undefined
3028
+ const match = findRouteMatch<TRouteLike>(trimmedPath, processedTree, true)
3029
+ if (match) {
3030
+ foundRoute = match.route
3031
+ Object.assign(routeParams, match.rawParams) // Copy params, because they're cached
3032
+ parsedParams = Object.assign(Object.create(null), match.parsedParams)
3033
+ }
3034
+
3035
+ const matchedRoutes = match?.branch || [routesById[rootRouteId]!]
3036
+
3037
+ return { matchedRoutes, routeParams, foundRoute, parsedParams }
3038
+ }
3039
+
3040
+ /**
3041
+ * TODO: once caches are persisted across requests on the server,
3042
+ * we can cache the built middleware chain using `last(destRoutes)` as the key
3043
+ */
3044
+ function applySearchMiddleware({
3045
+ search,
3046
+ dest,
3047
+ destRoutes,
3048
+ _includeValidateSearch,
3049
+ }: {
3050
+ search: any
3051
+ dest: { search?: unknown }
3052
+ destRoutes: ReadonlyArray<AnyRoute>
3053
+ _includeValidateSearch: boolean | undefined
3054
+ }) {
3055
+ const middleware = buildMiddlewareChain(destRoutes)
3056
+ return middleware(search, dest, _includeValidateSearch ?? false)
3057
+ }
3058
+
3059
+ function buildMiddlewareChain(destRoutes: ReadonlyArray<AnyRoute>) {
3060
+ const context = {
3061
+ dest: null as unknown as BuildNextOptions,
3062
+ _includeValidateSearch: false,
3063
+ middlewares: [] as Array<SearchMiddleware<any>>,
3064
+ }
3065
+
3066
+ for (const route of destRoutes) {
3067
+ if ('search' in route.options) {
3068
+ if (route.options.search?.middlewares) {
3069
+ context.middlewares.push(...route.options.search.middlewares)
3070
+ }
3071
+ }
3072
+ // TODO remove preSearchFilters and postSearchFilters in v2
3073
+ else if (
3074
+ route.options.preSearchFilters ||
3075
+ route.options.postSearchFilters
3076
+ ) {
3077
+ const legacyMiddleware: SearchMiddleware<any> = ({ search, next }) => {
3078
+ let nextSearch = search
3079
+
3080
+ if (
3081
+ 'preSearchFilters' in route.options &&
3082
+ route.options.preSearchFilters
3083
+ ) {
3084
+ nextSearch = route.options.preSearchFilters.reduce(
3085
+ (prev, next) => next(prev),
3086
+ search,
3087
+ )
3088
+ }
3089
+
3090
+ const result = next(nextSearch)
3091
+
3092
+ if (
3093
+ 'postSearchFilters' in route.options &&
3094
+ route.options.postSearchFilters
3095
+ ) {
3096
+ return route.options.postSearchFilters.reduce(
3097
+ (prev, next) => next(prev),
3098
+ result,
3099
+ )
3100
+ }
3101
+
3102
+ return result
3103
+ }
3104
+ context.middlewares.push(legacyMiddleware)
3105
+ }
3106
+
3107
+ if (route.options.validateSearch) {
3108
+ const validate: SearchMiddleware<any> = ({ search, next }) => {
3109
+ const result = next(search)
3110
+ if (!context._includeValidateSearch) return result
3111
+ try {
3112
+ const validatedSearch = {
3113
+ ...result,
3114
+ ...(validateSearch(route.options.validateSearch, result) ??
3115
+ undefined),
3116
+ }
3117
+ return validatedSearch
3118
+ } catch {
3119
+ // ignore errors here because they are already handled in matchRoutes
3120
+ return result
3121
+ }
3122
+ }
3123
+
3124
+ context.middlewares.push(validate)
3125
+ }
3126
+ }
3127
+
3128
+ // the chain ends here since `next` is not called
3129
+ const final: SearchMiddleware<any> = ({ search }) => {
3130
+ const dest = context.dest
3131
+ if (!dest.search) {
3132
+ return {}
3133
+ }
3134
+ if (dest.search === true) {
3135
+ return search
3136
+ }
3137
+ return functionalUpdate(dest.search, search)
3138
+ }
3139
+
3140
+ context.middlewares.push(final)
3141
+
3142
+ const applyNext = (
3143
+ index: number,
3144
+ currentSearch: any,
3145
+ middlewares: Array<SearchMiddleware<any>>,
3146
+ ): any => {
3147
+ // no more middlewares left, return the current search
3148
+ if (index >= middlewares.length) {
3149
+ return currentSearch
3150
+ }
3151
+
3152
+ const middleware = middlewares[index]!
3153
+
3154
+ const next = (newSearch: any): any => {
3155
+ return applyNext(index + 1, newSearch, middlewares)
3156
+ }
3157
+
3158
+ return middleware({ search: currentSearch, next })
3159
+ }
3160
+
3161
+ return function middleware(
3162
+ search: any,
3163
+ dest: BuildNextOptions,
3164
+ _includeValidateSearch: boolean,
3165
+ ) {
3166
+ context.dest = dest
3167
+ context._includeValidateSearch = _includeValidateSearch
3168
+ return applyNext(0, search, context.middlewares)
3169
+ }
3170
+ }
3171
+
3172
+ function findGlobalNotFoundRouteId(
3173
+ notFoundMode: 'root' | 'fuzzy' | undefined,
3174
+ routes: ReadonlyArray<AnyRoute>,
3175
+ ) {
3176
+ if (notFoundMode !== 'root') {
3177
+ for (let i = routes.length - 1; i >= 0; i--) {
3178
+ const route = routes[i]!
3179
+ if (route.children) {
3180
+ return route.id
3181
+ }
3182
+ }
3183
+ }
3184
+ return rootRouteId
3185
+ }
3186
+
3187
+ function extractStrictParams(
3188
+ route: AnyRoute,
3189
+ referenceParams: Record<string, unknown>,
3190
+ parsedParams: Record<string, unknown>,
3191
+ accumulatedParams: Record<string, unknown>,
3192
+ ) {
3193
+ const parseParams = route.options.params?.parse ?? route.options.parseParams
3194
+ if (parseParams) {
3195
+ if (route.options.skipRouteOnParseError) {
3196
+ // Use pre-parsed params from route matching for skipRouteOnParseError routes
3197
+ for (const key in referenceParams) {
3198
+ if (key in parsedParams) {
3199
+ accumulatedParams[key] = parsedParams[key]
3200
+ }
3201
+ }
3202
+ } else {
3203
+ const result = parseParams(accumulatedParams as Record<string, string>)
3204
+ Object.assign(accumulatedParams, result)
3205
+ }
3206
+ }
3207
+ }