@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
@@ -0,0 +1,1281 @@
1
+ import { isServer } from '@benjavicente/router-core/isServer'
2
+ import { invariant } from './invariant'
3
+ import { createControlledPromise, isPromise } from './utils'
4
+ import { isNotFound } from './not-found'
5
+ import { rootRouteId } from './root'
6
+ import { isRedirect } from './redirect'
7
+ import type { NotFoundError } from './not-found'
8
+ import type { ParsedLocation } from './location'
9
+ import type {
10
+ AnyRoute,
11
+ BeforeLoadContextOptions,
12
+ LoaderFnContext,
13
+ SsrContextOptions,
14
+ } from './route'
15
+ import type { AnyRouteMatch, MakeRouteMatch } from './Matches'
16
+ import type { AnyRouter, SSROption, UpdateMatchFn } from './router'
17
+
18
+ /**
19
+ * An object of this shape is created when calling `loadMatches`.
20
+ * It contains everything we need for all other functions in this file
21
+ * to work. (It's basically the function's argument, plus a few mutable states)
22
+ */
23
+ type InnerLoadContext = {
24
+ /** the calling router instance */
25
+ router: AnyRouter
26
+ location: ParsedLocation
27
+ /** mutable state, scoped to a `loadMatches` call */
28
+ firstBadMatchIndex?: number
29
+ /** mutable state, scoped to a `loadMatches` call */
30
+ rendered?: boolean
31
+ serialError?: unknown
32
+ updateMatch: UpdateMatchFn
33
+ matches: Array<AnyRouteMatch>
34
+ preload?: boolean
35
+ forceStaleReload?: boolean
36
+ onReady?: () => Promise<void>
37
+ sync?: boolean
38
+ }
39
+
40
+ const triggerOnReady = (inner: InnerLoadContext): void | Promise<void> => {
41
+ if (!inner.rendered) {
42
+ inner.rendered = true
43
+ return inner.onReady?.()
44
+ }
45
+ }
46
+
47
+ const hasForcePendingActiveMatch = (router: AnyRouter): boolean => {
48
+ return router.stores.matchesId.state.some((matchId) => {
49
+ return router.stores.activeMatchStoresById.get(matchId)?.state._forcePending
50
+ })
51
+ }
52
+
53
+ const resolvePreload = (inner: InnerLoadContext, matchId: string): boolean => {
54
+ return !!(
55
+ inner.preload && !inner.router.stores.activeMatchStoresById.has(matchId)
56
+ )
57
+ }
58
+
59
+ /**
60
+ * Builds the accumulated context from router options and all matches up to (and optionally including) the given index.
61
+ * Merges __routeContext and __beforeLoadContext from each match.
62
+ */
63
+ const buildMatchContext = (
64
+ inner: InnerLoadContext,
65
+ index: number,
66
+ includeCurrentMatch: boolean = true,
67
+ ): Record<string, unknown> => {
68
+ const context: Record<string, unknown> = {
69
+ ...(inner.router.options.context ?? {}),
70
+ }
71
+ const end = includeCurrentMatch ? index : index - 1
72
+ for (let i = 0; i <= end; i++) {
73
+ const innerMatch = inner.matches[i]
74
+ if (!innerMatch) continue
75
+ const m = inner.router.getMatch(innerMatch.id)
76
+ if (!m) continue
77
+ Object.assign(context, m.__routeContext, m.__beforeLoadContext)
78
+ }
79
+ return context
80
+ }
81
+
82
+ const getNotFoundBoundaryIndex = (
83
+ inner: InnerLoadContext,
84
+ err: NotFoundError,
85
+ ): number | undefined => {
86
+ if (!inner.matches.length) {
87
+ return undefined
88
+ }
89
+
90
+ const requestedRouteId = err.routeId
91
+ const matchedRootIndex = inner.matches.findIndex(
92
+ (m) => m.routeId === inner.router.routeTree.id,
93
+ )
94
+ const rootIndex = matchedRootIndex >= 0 ? matchedRootIndex : 0
95
+
96
+ let startIndex = requestedRouteId
97
+ ? inner.matches.findIndex((match) => match.routeId === requestedRouteId)
98
+ : (inner.firstBadMatchIndex ?? inner.matches.length - 1)
99
+
100
+ if (startIndex < 0) {
101
+ startIndex = rootIndex
102
+ }
103
+
104
+ for (let i = startIndex; i >= 0; i--) {
105
+ const match = inner.matches[i]!
106
+ const route = inner.router.looseRoutesById[match.routeId]!
107
+ if (route.options.notFoundComponent) {
108
+ return i
109
+ }
110
+ }
111
+
112
+ // If no boundary component is found, preserve explicit routeId targeting behavior,
113
+ // otherwise default to root for untargeted notFounds.
114
+ return requestedRouteId ? startIndex : rootIndex
115
+ }
116
+
117
+ const handleRedirectAndNotFound = (
118
+ inner: InnerLoadContext,
119
+ match: AnyRouteMatch | undefined,
120
+ err: unknown,
121
+ ): void => {
122
+ if (!isRedirect(err) && !isNotFound(err)) return
123
+
124
+ if (isRedirect(err) && err.redirectHandled && !err.options.reloadDocument) {
125
+ throw err
126
+ }
127
+
128
+ // in case of a redirecting match during preload, the match does not exist
129
+ if (match) {
130
+ match._nonReactive.beforeLoadPromise?.resolve()
131
+ match._nonReactive.loaderPromise?.resolve()
132
+ match._nonReactive.beforeLoadPromise = undefined
133
+ match._nonReactive.loaderPromise = undefined
134
+
135
+ match._nonReactive.error = err
136
+
137
+ inner.updateMatch(match.id, (prev) => ({
138
+ ...prev,
139
+ status: isRedirect(err)
140
+ ? 'redirected'
141
+ : prev.status === 'pending'
142
+ ? 'success'
143
+ : prev.status,
144
+ context: buildMatchContext(inner, match.index),
145
+ isFetching: false,
146
+ error: err,
147
+ }))
148
+
149
+ if (isNotFound(err) && !err.routeId) {
150
+ // Stamp the throwing match's routeId so that the finalization step in
151
+ // loadMatches knows where the notFound originated. The actual boundary
152
+ // resolution (walking up to the nearest notFoundComponent) is deferred to
153
+ // the finalization step, where firstBadMatchIndex is stable and
154
+ // headMaxIndex can be capped correctly.
155
+ err.routeId = match.routeId
156
+ }
157
+
158
+ match._nonReactive.loadPromise?.resolve()
159
+ }
160
+
161
+ if (isRedirect(err)) {
162
+ inner.rendered = true
163
+ err.options._fromLocation = inner.location
164
+ err.redirectHandled = true
165
+ err = inner.router.resolveRedirect(err)
166
+ }
167
+
168
+ throw err
169
+ }
170
+
171
+ const shouldSkipLoader = (
172
+ inner: InnerLoadContext,
173
+ matchId: string,
174
+ ): boolean => {
175
+ const match = inner.router.getMatch(matchId)
176
+ if (!match) {
177
+ return true
178
+ }
179
+ // upon hydration, we skip the loader if the match has been dehydrated on the server
180
+ if (!(isServer ?? inner.router.isServer) && match._nonReactive.dehydrated) {
181
+ return true
182
+ }
183
+
184
+ if ((isServer ?? inner.router.isServer) && match.ssr === false) {
185
+ return true
186
+ }
187
+
188
+ return false
189
+ }
190
+
191
+ const syncMatchContext = (
192
+ inner: InnerLoadContext,
193
+ matchId: string,
194
+ index: number,
195
+ ): void => {
196
+ const nextContext = buildMatchContext(inner, index)
197
+
198
+ inner.updateMatch(matchId, (prev) => {
199
+ return {
200
+ ...prev,
201
+ context: nextContext,
202
+ }
203
+ })
204
+ }
205
+
206
+ const handleSerialError = (
207
+ inner: InnerLoadContext,
208
+ index: number,
209
+ err: any,
210
+ routerCode: string,
211
+ ): void => {
212
+ const { id: matchId, routeId } = inner.matches[index]!
213
+ const route = inner.router.looseRoutesById[routeId]!
214
+
215
+ // Much like suspense, we use a promise here to know if
216
+ // we've been outdated by a new loadMatches call and
217
+ // should abort the current async operation
218
+ if (err instanceof Promise) {
219
+ throw err
220
+ }
221
+
222
+ err.routerCode = routerCode
223
+ inner.firstBadMatchIndex ??= index
224
+ handleRedirectAndNotFound(inner, inner.router.getMatch(matchId), err)
225
+
226
+ try {
227
+ route.options.onError?.(err)
228
+ } catch (errorHandlerErr) {
229
+ err = errorHandlerErr
230
+ handleRedirectAndNotFound(inner, inner.router.getMatch(matchId), err)
231
+ }
232
+
233
+ inner.updateMatch(matchId, (prev) => {
234
+ prev._nonReactive.beforeLoadPromise?.resolve()
235
+ prev._nonReactive.beforeLoadPromise = undefined
236
+ prev._nonReactive.loadPromise?.resolve()
237
+
238
+ return {
239
+ ...prev,
240
+ error: err,
241
+ status: 'error',
242
+ isFetching: false,
243
+ updatedAt: Date.now(),
244
+ abortController: new AbortController(),
245
+ }
246
+ })
247
+
248
+ if (!inner.preload && !isRedirect(err) && !isNotFound(err)) {
249
+ inner.serialError ??= err
250
+ }
251
+ }
252
+
253
+ const isBeforeLoadSsr = (
254
+ inner: InnerLoadContext,
255
+ matchId: string,
256
+ index: number,
257
+ route: AnyRoute,
258
+ ): void | Promise<void> => {
259
+ const existingMatch = inner.router.getMatch(matchId)!
260
+ const parentMatchId = inner.matches[index - 1]?.id
261
+ const parentMatch = parentMatchId
262
+ ? inner.router.getMatch(parentMatchId)!
263
+ : undefined
264
+
265
+ // in SPA mode, only SSR the root route
266
+ if (inner.router.isShell()) {
267
+ existingMatch.ssr = route.id === rootRouteId
268
+ return
269
+ }
270
+
271
+ if (parentMatch?.ssr === false) {
272
+ existingMatch.ssr = false
273
+ return
274
+ }
275
+
276
+ const parentOverride = (tempSsr: SSROption) => {
277
+ if (tempSsr === true && parentMatch?.ssr === 'data-only') {
278
+ return 'data-only'
279
+ }
280
+ return tempSsr
281
+ }
282
+
283
+ const defaultSsr = inner.router.options.defaultSsr ?? true
284
+
285
+ if (route.options.ssr === undefined) {
286
+ existingMatch.ssr = parentOverride(defaultSsr)
287
+ return
288
+ }
289
+
290
+ if (typeof route.options.ssr !== 'function') {
291
+ existingMatch.ssr = parentOverride(route.options.ssr)
292
+ return
293
+ }
294
+ const { search, params } = existingMatch
295
+
296
+ const ssrFnContext: SsrContextOptions<any, any, any> = {
297
+ search: makeMaybe(search, existingMatch.searchError),
298
+ params: makeMaybe(params, existingMatch.paramsError),
299
+ location: inner.location,
300
+ matches: inner.matches.map((match) => ({
301
+ index: match.index,
302
+ pathname: match.pathname,
303
+ fullPath: match.fullPath,
304
+ staticData: match.staticData,
305
+ id: match.id,
306
+ routeId: match.routeId,
307
+ search: makeMaybe(match.search, match.searchError),
308
+ params: makeMaybe(match.params, match.paramsError),
309
+ ssr: match.ssr,
310
+ })),
311
+ }
312
+
313
+ const tempSsr = route.options.ssr(ssrFnContext)
314
+ if (isPromise(tempSsr)) {
315
+ return tempSsr.then((ssr) => {
316
+ existingMatch.ssr = parentOverride(ssr ?? defaultSsr)
317
+ })
318
+ }
319
+
320
+ existingMatch.ssr = parentOverride(tempSsr ?? defaultSsr)
321
+ return
322
+ }
323
+
324
+ const setupPendingTimeout = (
325
+ inner: InnerLoadContext,
326
+ matchId: string,
327
+ route: AnyRoute,
328
+ match: AnyRouteMatch,
329
+ ): void => {
330
+ if (match._nonReactive.pendingTimeout !== undefined) return
331
+
332
+ const pendingMs =
333
+ route.options.pendingMs ?? inner.router.options.defaultPendingMs
334
+ const shouldPending = !!(
335
+ inner.onReady &&
336
+ !(isServer ?? inner.router.isServer) &&
337
+ !resolvePreload(inner, matchId) &&
338
+ (route.options.loader ||
339
+ route.options.beforeLoad ||
340
+ routeNeedsPreload(route)) &&
341
+ typeof pendingMs === 'number' &&
342
+ pendingMs !== Infinity &&
343
+ (route.options.pendingComponent ??
344
+ (inner.router.options as any)?.defaultPendingComponent)
345
+ )
346
+
347
+ if (shouldPending) {
348
+ const pendingTimeout = setTimeout(() => {
349
+ // Update the match and prematurely resolve the loadMatches promise so that
350
+ // the pending component can start rendering
351
+ triggerOnReady(inner)
352
+ }, pendingMs)
353
+ match._nonReactive.pendingTimeout = pendingTimeout
354
+ }
355
+ }
356
+
357
+ const preBeforeLoadSetup = (
358
+ inner: InnerLoadContext,
359
+ matchId: string,
360
+ route: AnyRoute,
361
+ ): void | Promise<void> => {
362
+ const existingMatch = inner.router.getMatch(matchId)!
363
+
364
+ // If we are in the middle of a load, either of these will be present
365
+ // (not to be confused with `loadPromise`, which is always defined)
366
+ if (
367
+ !existingMatch._nonReactive.beforeLoadPromise &&
368
+ !existingMatch._nonReactive.loaderPromise
369
+ )
370
+ return
371
+
372
+ setupPendingTimeout(inner, matchId, route, existingMatch)
373
+
374
+ const then = () => {
375
+ const match = inner.router.getMatch(matchId)!
376
+ if (
377
+ match.preload &&
378
+ (match.status === 'redirected' || match.status === 'notFound')
379
+ ) {
380
+ handleRedirectAndNotFound(inner, match, match.error)
381
+ }
382
+ }
383
+
384
+ // Wait for the previous beforeLoad to resolve before we continue
385
+ return existingMatch._nonReactive.beforeLoadPromise
386
+ ? existingMatch._nonReactive.beforeLoadPromise.then(then)
387
+ : then()
388
+ }
389
+
390
+ const executeBeforeLoad = (
391
+ inner: InnerLoadContext,
392
+ matchId: string,
393
+ index: number,
394
+ route: AnyRoute,
395
+ ): void | Promise<void> => {
396
+ const match = inner.router.getMatch(matchId)!
397
+
398
+ // explicitly capture the previous loadPromise
399
+ let prevLoadPromise = match._nonReactive.loadPromise
400
+ match._nonReactive.loadPromise = createControlledPromise<void>(() => {
401
+ prevLoadPromise?.resolve()
402
+ prevLoadPromise = undefined
403
+ })
404
+
405
+ const { paramsError, searchError } = match
406
+
407
+ if (paramsError) {
408
+ handleSerialError(inner, index, paramsError, 'PARSE_PARAMS')
409
+ }
410
+
411
+ if (searchError) {
412
+ handleSerialError(inner, index, searchError, 'VALIDATE_SEARCH')
413
+ }
414
+
415
+ setupPendingTimeout(inner, matchId, route, match)
416
+
417
+ const abortController = new AbortController()
418
+
419
+ let isPending = false
420
+ const pending = () => {
421
+ if (isPending) return
422
+ isPending = true
423
+ inner.updateMatch(matchId, (prev) => ({
424
+ ...prev,
425
+ isFetching: 'beforeLoad',
426
+ fetchCount: prev.fetchCount + 1,
427
+ abortController,
428
+ // Note: We intentionally don't update context here.
429
+ // Context should only be updated after beforeLoad resolves to avoid
430
+ // components seeing incomplete context during async beforeLoad execution.
431
+ }))
432
+ }
433
+
434
+ const resolve = () => {
435
+ match._nonReactive.beforeLoadPromise?.resolve()
436
+ match._nonReactive.beforeLoadPromise = undefined
437
+ inner.updateMatch(matchId, (prev) => ({
438
+ ...prev,
439
+ isFetching: false,
440
+ }))
441
+ }
442
+
443
+ // if there is no `beforeLoad` option, just mark as pending and resolve
444
+ // Context will be updated later in loadRouteMatch after loader completes
445
+ if (!route.options.beforeLoad) {
446
+ inner.router.batch(() => {
447
+ pending()
448
+ resolve()
449
+ })
450
+ return
451
+ }
452
+
453
+ match._nonReactive.beforeLoadPromise = createControlledPromise<void>()
454
+
455
+ // Build context from all parent matches, excluding current match's __beforeLoadContext
456
+ // (since we're about to execute beforeLoad for this match)
457
+ const context = {
458
+ ...buildMatchContext(inner, index, false),
459
+ ...match.__routeContext,
460
+ }
461
+ const { search, params, cause } = match
462
+ const preload = resolvePreload(inner, matchId)
463
+ const beforeLoadFnContext: BeforeLoadContextOptions<
464
+ any,
465
+ any,
466
+ any,
467
+ any,
468
+ any,
469
+ any,
470
+ any,
471
+ any,
472
+ any
473
+ > = {
474
+ search,
475
+ abortController,
476
+ params,
477
+ preload,
478
+ context,
479
+ location: inner.location,
480
+ navigate: (opts: any) =>
481
+ inner.router.navigate({
482
+ ...opts,
483
+ _fromLocation: inner.location,
484
+ }),
485
+ buildLocation: inner.router.buildLocation,
486
+ cause: preload ? 'preload' : cause,
487
+ matches: inner.matches,
488
+ routeId: route.id,
489
+ ...inner.router.options.additionalContext,
490
+ }
491
+
492
+ const updateContext = (beforeLoadContext: any) => {
493
+ if (beforeLoadContext === undefined) {
494
+ inner.router.batch(() => {
495
+ pending()
496
+ resolve()
497
+ })
498
+ return
499
+ }
500
+ if (isRedirect(beforeLoadContext) || isNotFound(beforeLoadContext)) {
501
+ pending()
502
+ handleSerialError(inner, index, beforeLoadContext, 'BEFORE_LOAD')
503
+ }
504
+
505
+ inner.router.batch(() => {
506
+ pending()
507
+ inner.updateMatch(matchId, (prev) => ({
508
+ ...prev,
509
+ __beforeLoadContext: beforeLoadContext,
510
+ }))
511
+ resolve()
512
+ })
513
+ }
514
+
515
+ let beforeLoadContext
516
+ try {
517
+ beforeLoadContext = route.options.beforeLoad(beforeLoadFnContext)
518
+ if (isPromise(beforeLoadContext)) {
519
+ pending()
520
+ return beforeLoadContext
521
+ .catch((err) => {
522
+ handleSerialError(inner, index, err, 'BEFORE_LOAD')
523
+ })
524
+ .then(updateContext)
525
+ }
526
+ } catch (err) {
527
+ pending()
528
+ handleSerialError(inner, index, err, 'BEFORE_LOAD')
529
+ }
530
+
531
+ updateContext(beforeLoadContext)
532
+ return
533
+ }
534
+
535
+ const handleBeforeLoad = (
536
+ inner: InnerLoadContext,
537
+ index: number,
538
+ ): void | Promise<void> => {
539
+ const { id: matchId, routeId } = inner.matches[index]!
540
+ const route = inner.router.looseRoutesById[routeId]!
541
+
542
+ const serverSsr = () => {
543
+ // on the server, determine whether SSR the current match or not
544
+ if (isServer ?? inner.router.isServer) {
545
+ const maybePromise = isBeforeLoadSsr(inner, matchId, index, route)
546
+ if (isPromise(maybePromise)) return maybePromise.then(queueExecution)
547
+ }
548
+ return queueExecution()
549
+ }
550
+
551
+ const execute = () => executeBeforeLoad(inner, matchId, index, route)
552
+
553
+ const queueExecution = () => {
554
+ if (shouldSkipLoader(inner, matchId)) return
555
+ const result = preBeforeLoadSetup(inner, matchId, route)
556
+ return isPromise(result) ? result.then(execute) : execute()
557
+ }
558
+
559
+ return serverSsr()
560
+ }
561
+
562
+ const executeHead = (
563
+ inner: InnerLoadContext,
564
+ matchId: string,
565
+ route: AnyRoute,
566
+ ): void | Promise<
567
+ Pick<
568
+ AnyRouteMatch,
569
+ 'meta' | 'links' | 'headScripts' | 'headers' | 'scripts' | 'styles'
570
+ >
571
+ > => {
572
+ const match = inner.router.getMatch(matchId)
573
+ // in case of a redirecting match during preload, the match does not exist
574
+ if (!match) {
575
+ return
576
+ }
577
+ if (!route.options.head && !route.options.scripts && !route.options.headers) {
578
+ return
579
+ }
580
+ const assetContext = {
581
+ ssr: inner.router.options.ssr,
582
+ matches: inner.matches,
583
+ match,
584
+ params: match.params,
585
+ loaderData: match.loaderData,
586
+ }
587
+
588
+ return Promise.all([
589
+ route.options.head?.(assetContext),
590
+ route.options.scripts?.(assetContext),
591
+ route.options.headers?.(assetContext),
592
+ ]).then(([headFnContent, scripts, headers]) => {
593
+ const meta = headFnContent?.meta
594
+ const links = headFnContent?.links
595
+ const headScripts = headFnContent?.scripts
596
+ const styles = headFnContent?.styles
597
+
598
+ return {
599
+ meta,
600
+ links,
601
+ headScripts,
602
+ headers,
603
+ scripts,
604
+ styles,
605
+ }
606
+ })
607
+ }
608
+
609
+ const getLoaderContext = (
610
+ inner: InnerLoadContext,
611
+ matchPromises: Array<Promise<AnyRouteMatch>>,
612
+ matchId: string,
613
+ index: number,
614
+ route: AnyRoute,
615
+ ): LoaderFnContext => {
616
+ const parentMatchPromise = matchPromises[index - 1] as any
617
+ const { params, loaderDeps, abortController, cause } =
618
+ inner.router.getMatch(matchId)!
619
+
620
+ const context = buildMatchContext(inner, index)
621
+
622
+ const preload = resolvePreload(inner, matchId)
623
+
624
+ return {
625
+ params,
626
+ deps: loaderDeps,
627
+ preload: !!preload,
628
+ parentMatchPromise,
629
+ abortController,
630
+ context,
631
+ location: inner.location,
632
+ navigate: (opts) =>
633
+ inner.router.navigate({
634
+ ...opts,
635
+ _fromLocation: inner.location,
636
+ }),
637
+ cause: preload ? 'preload' : cause,
638
+ route,
639
+ ...inner.router.options.additionalContext,
640
+ }
641
+ }
642
+
643
+ const runLoader = async (
644
+ inner: InnerLoadContext,
645
+ matchPromises: Array<Promise<AnyRouteMatch>>,
646
+ matchId: string,
647
+ index: number,
648
+ route: AnyRoute,
649
+ ): Promise<void> => {
650
+ try {
651
+ // If the Matches component rendered
652
+ // the pending component and needs to show it for
653
+ // a minimum duration, we''ll wait for it to resolve
654
+ // before committing to the match and resolving
655
+ // the loadPromise
656
+
657
+ const match = inner.router.getMatch(matchId)!
658
+
659
+ // Actually run the loader and handle the result
660
+ try {
661
+ if (!(isServer ?? inner.router.isServer) || match.ssr === true) {
662
+ loadRouteChunk(route)
663
+ }
664
+
665
+ // Kick off the loader!
666
+ const routeLoader = route.options.loader
667
+ const loader =
668
+ typeof routeLoader === 'function' ? routeLoader : routeLoader?.handler
669
+ const loaderResult = loader?.(
670
+ getLoaderContext(inner, matchPromises, matchId, index, route),
671
+ )
672
+ const loaderResultIsPromise = !!loader && isPromise(loaderResult)
673
+
674
+ const willLoadSomething = !!(
675
+ loaderResultIsPromise ||
676
+ route._lazyPromise ||
677
+ route._componentsPromise ||
678
+ route.options.head ||
679
+ route.options.scripts ||
680
+ route.options.headers ||
681
+ match._nonReactive.minPendingPromise
682
+ )
683
+
684
+ if (willLoadSomething) {
685
+ inner.updateMatch(matchId, (prev) => ({
686
+ ...prev,
687
+ isFetching: 'loader',
688
+ }))
689
+ }
690
+
691
+ if (loader) {
692
+ const loaderData = loaderResultIsPromise
693
+ ? await loaderResult
694
+ : loaderResult
695
+
696
+ handleRedirectAndNotFound(
697
+ inner,
698
+ inner.router.getMatch(matchId),
699
+ loaderData,
700
+ )
701
+ if (loaderData !== undefined) {
702
+ inner.updateMatch(matchId, (prev) => ({
703
+ ...prev,
704
+ loaderData,
705
+ }))
706
+ }
707
+ }
708
+
709
+ // Lazy option can modify the route options,
710
+ // so we need to wait for it to resolve before
711
+ // we can use the options
712
+ if (route._lazyPromise) await route._lazyPromise
713
+ const pendingPromise = match._nonReactive.minPendingPromise
714
+ if (pendingPromise) await pendingPromise
715
+
716
+ // Last but not least, wait for the the components
717
+ // to be preloaded before we resolve the match
718
+ if (route._componentsPromise) await route._componentsPromise
719
+ inner.updateMatch(matchId, (prev) => ({
720
+ ...prev,
721
+ error: undefined,
722
+ context: buildMatchContext(inner, index),
723
+ status: 'success',
724
+ isFetching: false,
725
+ updatedAt: Date.now(),
726
+ }))
727
+ } catch (e) {
728
+ let error = e
729
+
730
+ if ((error as any)?.name === 'AbortError') {
731
+ if (match.abortController.signal.aborted) {
732
+ match._nonReactive.loaderPromise?.resolve()
733
+ match._nonReactive.loaderPromise = undefined
734
+ return
735
+ }
736
+ inner.updateMatch(matchId, (prev) => ({
737
+ ...prev,
738
+ status: prev.status === 'pending' ? 'success' : prev.status,
739
+ isFetching: false,
740
+ context: buildMatchContext(inner, index),
741
+ }))
742
+ return
743
+ }
744
+
745
+ const pendingPromise = match._nonReactive.minPendingPromise
746
+ if (pendingPromise) await pendingPromise
747
+
748
+ if (isNotFound(e)) {
749
+ await (route.options.notFoundComponent as any)?.preload?.()
750
+ }
751
+
752
+ handleRedirectAndNotFound(inner, inner.router.getMatch(matchId), e)
753
+
754
+ try {
755
+ route.options.onError?.(e)
756
+ } catch (onErrorError) {
757
+ error = onErrorError
758
+ handleRedirectAndNotFound(
759
+ inner,
760
+ inner.router.getMatch(matchId),
761
+ onErrorError,
762
+ )
763
+ }
764
+ if (!isRedirect(error) && !isNotFound(error)) {
765
+ await loadRouteChunk(route, ['errorComponent'])
766
+ }
767
+
768
+ inner.updateMatch(matchId, (prev) => ({
769
+ ...prev,
770
+ error,
771
+ context: buildMatchContext(inner, index),
772
+ status: 'error',
773
+ isFetching: false,
774
+ }))
775
+ }
776
+ } catch (err) {
777
+ const match = inner.router.getMatch(matchId)
778
+ // in case of a redirecting match during preload, the match does not exist
779
+ if (match) {
780
+ match._nonReactive.loaderPromise = undefined
781
+ }
782
+ handleRedirectAndNotFound(inner, match, err)
783
+ }
784
+ }
785
+
786
+ const loadRouteMatch = async (
787
+ inner: InnerLoadContext,
788
+ matchPromises: Array<Promise<AnyRouteMatch>>,
789
+ index: number,
790
+ ): Promise<AnyRouteMatch> => {
791
+ async function handleLoader(
792
+ preload: boolean,
793
+ prevMatch: AnyRouteMatch,
794
+ previousRouteMatchId: string | undefined,
795
+ match: AnyRouteMatch,
796
+ route: AnyRoute,
797
+ ) {
798
+ const age = Date.now() - prevMatch.updatedAt
799
+
800
+ const staleAge = preload
801
+ ? (route.options.preloadStaleTime ??
802
+ inner.router.options.defaultPreloadStaleTime ??
803
+ 30_000) // 30 seconds for preloads by default
804
+ : (route.options.staleTime ?? inner.router.options.defaultStaleTime ?? 0)
805
+
806
+ const shouldReloadOption = route.options.shouldReload
807
+
808
+ // Default to reloading the route all the time
809
+ // Allow shouldReload to get the last say,
810
+ // if provided.
811
+ const shouldReload =
812
+ typeof shouldReloadOption === 'function'
813
+ ? shouldReloadOption(
814
+ getLoaderContext(inner, matchPromises, matchId, index, route),
815
+ )
816
+ : shouldReloadOption
817
+
818
+ // If the route is successful and still fresh, just resolve
819
+ const { status, invalid } = match
820
+ const staleMatchShouldReload =
821
+ age >= staleAge &&
822
+ (!!inner.forceStaleReload ||
823
+ match.cause === 'enter' ||
824
+ (previousRouteMatchId !== undefined &&
825
+ previousRouteMatchId !== match.id))
826
+ loaderShouldRunAsync =
827
+ status === 'success' &&
828
+ (invalid || (shouldReload ?? staleMatchShouldReload))
829
+ if (preload && route.options.preload === false) {
830
+ // Do nothing
831
+ } else if (
832
+ loaderShouldRunAsync &&
833
+ !inner.sync &&
834
+ shouldReloadInBackground
835
+ ) {
836
+ loaderIsRunningAsync = true
837
+ ;(async () => {
838
+ try {
839
+ await runLoader(inner, matchPromises, matchId, index, route)
840
+ const match = inner.router.getMatch(matchId)!
841
+ match._nonReactive.loaderPromise?.resolve()
842
+ match._nonReactive.loadPromise?.resolve()
843
+ match._nonReactive.loaderPromise = undefined
844
+ match._nonReactive.loadPromise = undefined
845
+ } catch (err) {
846
+ if (isRedirect(err)) {
847
+ await inner.router.navigate(err.options)
848
+ }
849
+ }
850
+ })()
851
+ } else if (status !== 'success' || loaderShouldRunAsync) {
852
+ await runLoader(inner, matchPromises, matchId, index, route)
853
+ } else {
854
+ syncMatchContext(inner, matchId, index)
855
+ }
856
+ }
857
+
858
+ const { id: matchId, routeId } = inner.matches[index]!
859
+ let loaderShouldRunAsync = false
860
+ let loaderIsRunningAsync = false
861
+ const route = inner.router.looseRoutesById[routeId]!
862
+ const routeLoader = route.options.loader
863
+ const shouldReloadInBackground =
864
+ ((typeof routeLoader === 'function'
865
+ ? undefined
866
+ : routeLoader?.staleReloadMode) ??
867
+ inner.router.options.defaultStaleReloadMode) !== 'blocking'
868
+
869
+ if (shouldSkipLoader(inner, matchId)) {
870
+ const match = inner.router.getMatch(matchId)
871
+ if (!match) {
872
+ return inner.matches[index]!
873
+ }
874
+
875
+ syncMatchContext(inner, matchId, index)
876
+
877
+ if (isServer ?? inner.router.isServer) {
878
+ return inner.router.getMatch(matchId)!
879
+ }
880
+ } else {
881
+ const prevMatch = inner.router.getMatch(matchId)! // This is where all of the stale-while-revalidate magic happens
882
+ const activeIdAtIndex = inner.router.stores.matchesId.state[index]
883
+ const activeAtIndex =
884
+ (activeIdAtIndex &&
885
+ inner.router.stores.activeMatchStoresById.get(activeIdAtIndex)) ||
886
+ null
887
+ const previousRouteMatchId =
888
+ activeAtIndex?.routeId === routeId
889
+ ? activeIdAtIndex
890
+ : inner.router.stores.activeMatchesSnapshot.state.find(
891
+ (d) => d.routeId === routeId,
892
+ )?.id
893
+ const preload = resolvePreload(inner, matchId)
894
+
895
+ // there is a loaderPromise, so we are in the middle of a load
896
+ if (prevMatch._nonReactive.loaderPromise) {
897
+ // do not block if we already have stale data we can show
898
+ // but only if the ongoing load is not a preload since error handling is different for preloads
899
+ // and we don't want to swallow errors
900
+ if (
901
+ prevMatch.status === 'success' &&
902
+ !inner.sync &&
903
+ !prevMatch.preload &&
904
+ shouldReloadInBackground
905
+ ) {
906
+ return prevMatch
907
+ }
908
+ await prevMatch._nonReactive.loaderPromise
909
+ const match = inner.router.getMatch(matchId)!
910
+ const error = match._nonReactive.error || match.error
911
+ if (error) {
912
+ handleRedirectAndNotFound(inner, match, error)
913
+ }
914
+
915
+ if (match.status === 'pending') {
916
+ await handleLoader(
917
+ preload,
918
+ prevMatch,
919
+ previousRouteMatchId,
920
+ match,
921
+ route,
922
+ )
923
+ }
924
+ } else {
925
+ const nextPreload =
926
+ preload && !inner.router.stores.activeMatchStoresById.has(matchId)
927
+ const match = inner.router.getMatch(matchId)!
928
+ match._nonReactive.loaderPromise = createControlledPromise<void>()
929
+ if (nextPreload !== match.preload) {
930
+ inner.updateMatch(matchId, (prev) => ({
931
+ ...prev,
932
+ preload: nextPreload,
933
+ }))
934
+ }
935
+
936
+ await handleLoader(preload, prevMatch, previousRouteMatchId, match, route)
937
+ }
938
+ }
939
+ const match = inner.router.getMatch(matchId)!
940
+ if (!loaderIsRunningAsync) {
941
+ match._nonReactive.loaderPromise?.resolve()
942
+ match._nonReactive.loadPromise?.resolve()
943
+ match._nonReactive.loadPromise = undefined
944
+ }
945
+
946
+ clearTimeout(match._nonReactive.pendingTimeout)
947
+ match._nonReactive.pendingTimeout = undefined
948
+ if (!loaderIsRunningAsync) match._nonReactive.loaderPromise = undefined
949
+ match._nonReactive.dehydrated = undefined
950
+
951
+ const nextIsFetching = loaderIsRunningAsync ? match.isFetching : false
952
+ if (nextIsFetching !== match.isFetching || match.invalid !== false) {
953
+ inner.updateMatch(matchId, (prev) => ({
954
+ ...prev,
955
+ isFetching: nextIsFetching,
956
+ invalid: false,
957
+ }))
958
+ return inner.router.getMatch(matchId)!
959
+ } else {
960
+ return match
961
+ }
962
+ }
963
+
964
+ export async function loadMatches(arg: {
965
+ router: AnyRouter
966
+ location: ParsedLocation
967
+ matches: Array<AnyRouteMatch>
968
+ preload?: boolean
969
+ forceStaleReload?: boolean
970
+ onReady?: () => Promise<void>
971
+ updateMatch: UpdateMatchFn
972
+ sync?: boolean
973
+ }): Promise<Array<MakeRouteMatch>> {
974
+ const inner: InnerLoadContext = arg
975
+ const matchPromises: Array<Promise<AnyRouteMatch>> = []
976
+
977
+ // make sure the pending component is immediately rendered when hydrating a match that is not SSRed
978
+ // the pending component was already rendered on the server and we want to keep it shown on the client until minPendingMs is reached
979
+ if (
980
+ !(isServer ?? inner.router.isServer) &&
981
+ hasForcePendingActiveMatch(inner.router)
982
+ ) {
983
+ triggerOnReady(inner)
984
+ }
985
+
986
+ let beforeLoadNotFound: NotFoundError | undefined
987
+
988
+ // Execute all beforeLoads one by one
989
+ for (let i = 0; i < inner.matches.length; i++) {
990
+ try {
991
+ const beforeLoad = handleBeforeLoad(inner, i)
992
+ if (isPromise(beforeLoad)) await beforeLoad
993
+ } catch (err) {
994
+ if (isRedirect(err)) {
995
+ throw err
996
+ }
997
+ if (isNotFound(err)) {
998
+ beforeLoadNotFound = err
999
+ } else {
1000
+ if (!inner.preload) throw err
1001
+ }
1002
+ break
1003
+ }
1004
+
1005
+ if (inner.serialError || inner.firstBadMatchIndex != null) {
1006
+ break
1007
+ }
1008
+ }
1009
+
1010
+ // Execute loaders once, with max index adapted for beforeLoad notFound handling.
1011
+ const baseMaxIndexExclusive = inner.firstBadMatchIndex ?? inner.matches.length
1012
+
1013
+ const boundaryIndex =
1014
+ beforeLoadNotFound && !inner.preload
1015
+ ? getNotFoundBoundaryIndex(inner, beforeLoadNotFound)
1016
+ : undefined
1017
+
1018
+ const maxIndexExclusive =
1019
+ beforeLoadNotFound && inner.preload
1020
+ ? 0
1021
+ : boundaryIndex !== undefined
1022
+ ? Math.min(boundaryIndex + 1, baseMaxIndexExclusive)
1023
+ : baseMaxIndexExclusive
1024
+
1025
+ let firstNotFound: NotFoundError | undefined
1026
+ let firstUnhandledRejection: unknown
1027
+
1028
+ for (let i = 0; i < maxIndexExclusive; i++) {
1029
+ matchPromises.push(loadRouteMatch(inner, matchPromises, i))
1030
+ }
1031
+
1032
+ try {
1033
+ await Promise.all(matchPromises)
1034
+ } catch {
1035
+ const settled = await Promise.allSettled(matchPromises)
1036
+
1037
+ for (const result of settled) {
1038
+ if (result.status !== 'rejected') continue
1039
+
1040
+ const reason = result.reason
1041
+ if (isRedirect(reason)) {
1042
+ throw reason
1043
+ }
1044
+ if (isNotFound(reason)) {
1045
+ firstNotFound ??= reason
1046
+ } else {
1047
+ firstUnhandledRejection ??= reason
1048
+ }
1049
+ }
1050
+
1051
+ if (firstUnhandledRejection !== undefined) {
1052
+ throw firstUnhandledRejection
1053
+ }
1054
+ }
1055
+
1056
+ const notFoundToThrow =
1057
+ firstNotFound ??
1058
+ (beforeLoadNotFound && !inner.preload ? beforeLoadNotFound : undefined)
1059
+
1060
+ let headMaxIndex =
1061
+ inner.firstBadMatchIndex !== undefined
1062
+ ? inner.firstBadMatchIndex
1063
+ : inner.matches.length - 1
1064
+
1065
+ if (!notFoundToThrow && beforeLoadNotFound && inner.preload) {
1066
+ return inner.matches
1067
+ }
1068
+
1069
+ if (notFoundToThrow) {
1070
+ // Determine once which matched route will actually render the
1071
+ // notFoundComponent, then pass this precomputed index through the remaining
1072
+ // finalization steps.
1073
+ // This can differ from the throwing route when routeId targets an ancestor
1074
+ // boundary (or when bubbling resolves to a parent/root boundary).
1075
+ const renderedBoundaryIndex = getNotFoundBoundaryIndex(
1076
+ inner,
1077
+ notFoundToThrow,
1078
+ )
1079
+
1080
+ if (renderedBoundaryIndex === undefined) {
1081
+ if (process.env.NODE_ENV !== 'production') {
1082
+ throw new Error(
1083
+ 'Invariant failed: Could not find match for notFound boundary',
1084
+ )
1085
+ }
1086
+
1087
+ invariant()
1088
+ }
1089
+ const boundaryMatch = inner.matches[renderedBoundaryIndex]!
1090
+
1091
+ const boundaryRoute = inner.router.looseRoutesById[boundaryMatch.routeId]!
1092
+ const defaultNotFoundComponent = (inner.router.options as any)
1093
+ ?.defaultNotFoundComponent
1094
+
1095
+ // Ensure a notFoundComponent exists on the boundary route
1096
+ if (!boundaryRoute.options.notFoundComponent && defaultNotFoundComponent) {
1097
+ boundaryRoute.options.notFoundComponent = defaultNotFoundComponent
1098
+ }
1099
+
1100
+ notFoundToThrow.routeId = boundaryMatch.routeId
1101
+
1102
+ const boundaryIsRoot = boundaryMatch.routeId === inner.router.routeTree.id
1103
+
1104
+ inner.updateMatch(boundaryMatch.id, (prev) => ({
1105
+ ...prev,
1106
+ ...(boundaryIsRoot
1107
+ ? // For root boundary, use globalNotFound so the root component's
1108
+ // shell still renders and <Outlet> handles the not-found display,
1109
+ // instead of replacing the entire root shell via status='notFound'.
1110
+ { status: 'success' as const, globalNotFound: true, error: undefined }
1111
+ : // For non-root boundaries, set status:'notFound' so MatchInner
1112
+ // renders the notFoundComponent directly.
1113
+ { status: 'notFound' as const, error: notFoundToThrow }),
1114
+ isFetching: false,
1115
+ }))
1116
+
1117
+ headMaxIndex = renderedBoundaryIndex
1118
+
1119
+ // Ensure the rendering boundary route chunk (and its lazy components, including
1120
+ // lazy notFoundComponent) is loaded before we continue to head execution/render.
1121
+ await loadRouteChunk(boundaryRoute, ['notFoundComponent'])
1122
+ } else if (!inner.preload) {
1123
+ // Clear stale root global-not-found state on normal navigations that do not
1124
+ // throw notFound. This must live here (instead of only in runLoader success)
1125
+ // because the root loader may be skipped when data is still fresh.
1126
+ const rootMatch = inner.matches[0]!
1127
+ // `rootMatch` is the next match for this navigation. If it is not global
1128
+ // not-found, then any currently stored root global-not-found is stale.
1129
+ if (!rootMatch.globalNotFound) {
1130
+ // `currentRootMatch` is the current store state (from the previous
1131
+ // navigation/load). Update only when a stale flag is actually present.
1132
+ const currentRootMatch = inner.router.getMatch(rootMatch.id)
1133
+ if (currentRootMatch?.globalNotFound) {
1134
+ inner.updateMatch(rootMatch.id, (prev) => ({
1135
+ ...prev,
1136
+ globalNotFound: false,
1137
+ error: undefined,
1138
+ }))
1139
+ }
1140
+ }
1141
+ }
1142
+
1143
+ // When a serial error occurred (e.g. beforeLoad threw a regular Error),
1144
+ // the erroring route's lazy chunk wasn't loaded because loaders were skipped.
1145
+ // We need to load it so the code-split errorComponent is available for rendering.
1146
+ if (inner.serialError && inner.firstBadMatchIndex !== undefined) {
1147
+ const errorRoute =
1148
+ inner.router.looseRoutesById[
1149
+ inner.matches[inner.firstBadMatchIndex]!.routeId
1150
+ ]!
1151
+ await loadRouteChunk(errorRoute, ['errorComponent'])
1152
+ }
1153
+
1154
+ // serially execute heads once after loaders/notFound handling, ensuring
1155
+ // all head functions get a chance even if one throws.
1156
+ for (let i = 0; i <= headMaxIndex; i++) {
1157
+ const match = inner.matches[i]!
1158
+ const { id: matchId, routeId } = match
1159
+ const route = inner.router.looseRoutesById[routeId]!
1160
+ try {
1161
+ const headResult = executeHead(inner, matchId, route)
1162
+ if (headResult) {
1163
+ const head = await headResult
1164
+ inner.updateMatch(matchId, (prev) => ({
1165
+ ...prev,
1166
+ ...head,
1167
+ }))
1168
+ }
1169
+ } catch (err) {
1170
+ console.error(`Error executing head for route ${routeId}:`, err)
1171
+ }
1172
+ }
1173
+
1174
+ const readyPromise = triggerOnReady(inner)
1175
+ if (isPromise(readyPromise)) {
1176
+ await readyPromise
1177
+ }
1178
+
1179
+ if (notFoundToThrow) {
1180
+ throw notFoundToThrow
1181
+ }
1182
+
1183
+ if (inner.serialError && !inner.preload && !inner.onReady) {
1184
+ throw inner.serialError
1185
+ }
1186
+
1187
+ return inner.matches
1188
+ }
1189
+
1190
+ export type RouteComponentType =
1191
+ | 'component'
1192
+ | 'errorComponent'
1193
+ | 'pendingComponent'
1194
+ | 'notFoundComponent'
1195
+
1196
+ function preloadRouteComponents(
1197
+ route: AnyRoute,
1198
+ componentTypesToLoad: Array<RouteComponentType>,
1199
+ ): Promise<void> | undefined {
1200
+ const preloads = componentTypesToLoad
1201
+ .map((type) => (route.options[type] as any)?.preload?.())
1202
+ .filter(Boolean)
1203
+
1204
+ if (preloads.length === 0) return undefined
1205
+
1206
+ return Promise.all(preloads) as any as Promise<void>
1207
+ }
1208
+
1209
+ export function loadRouteChunk(
1210
+ route: AnyRoute,
1211
+ componentTypesToLoad: Array<RouteComponentType> = componentTypes,
1212
+ ) {
1213
+ if (!route._lazyLoaded && route._lazyPromise === undefined) {
1214
+ if (route.lazyFn) {
1215
+ route._lazyPromise = route.lazyFn().then((lazyRoute) => {
1216
+ // explicitly don't copy over the lazy route's id
1217
+ const { id: _id, ...options } = lazyRoute.options
1218
+ Object.assign(route.options, options)
1219
+ route._lazyLoaded = true
1220
+ route._lazyPromise = undefined // gc promise, we won't need it anymore
1221
+ })
1222
+ } else {
1223
+ route._lazyLoaded = true
1224
+ }
1225
+ }
1226
+
1227
+ const runAfterLazy = () =>
1228
+ route._componentsLoaded
1229
+ ? undefined
1230
+ : componentTypesToLoad === componentTypes
1231
+ ? (() => {
1232
+ if (route._componentsPromise === undefined) {
1233
+ const componentsPromise = preloadRouteComponents(
1234
+ route,
1235
+ componentTypes,
1236
+ )
1237
+
1238
+ if (componentsPromise) {
1239
+ route._componentsPromise = componentsPromise.then(() => {
1240
+ route._componentsLoaded = true
1241
+ route._componentsPromise = undefined // gc promise, we won't need it anymore
1242
+ })
1243
+ } else {
1244
+ route._componentsLoaded = true
1245
+ }
1246
+ }
1247
+
1248
+ return route._componentsPromise
1249
+ })()
1250
+ : preloadRouteComponents(route, componentTypesToLoad)
1251
+
1252
+ return route._lazyPromise
1253
+ ? route._lazyPromise.then(runAfterLazy)
1254
+ : runAfterLazy()
1255
+ }
1256
+
1257
+ function makeMaybe<TValue, TError>(
1258
+ value: TValue,
1259
+ error: TError,
1260
+ ): { status: 'success'; value: TValue } | { status: 'error'; error: TError } {
1261
+ if (error) {
1262
+ return { status: 'error' as const, error }
1263
+ }
1264
+ return { status: 'success' as const, value }
1265
+ }
1266
+
1267
+ export function routeNeedsPreload(route: AnyRoute) {
1268
+ for (const componentType of componentTypes) {
1269
+ if ((route.options[componentType] as any)?.preload) {
1270
+ return true
1271
+ }
1272
+ }
1273
+ return false
1274
+ }
1275
+
1276
+ export const componentTypes: Array<RouteComponentType> = [
1277
+ 'component',
1278
+ 'errorComponent',
1279
+ 'pendingComponent',
1280
+ 'notFoundComponent',
1281
+ ] as const