@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,1387 @@
1
+ import { invariant } from './invariant'
2
+ import { createLRUCache } from './lru-cache'
3
+ import { last } from './utils'
4
+ import type { LRUCache } from './lru-cache'
5
+
6
+ export const SEGMENT_TYPE_PATHNAME = 0
7
+ export const SEGMENT_TYPE_PARAM = 1
8
+ export const SEGMENT_TYPE_WILDCARD = 2
9
+ export const SEGMENT_TYPE_OPTIONAL_PARAM = 3
10
+ const SEGMENT_TYPE_INDEX = 4
11
+ const SEGMENT_TYPE_PATHLESS = 5 // only used in matching to represent pathless routes that need to carry more information
12
+
13
+ /**
14
+ * All the kinds of segments that can be present in a route path.
15
+ */
16
+ export type SegmentKind =
17
+ | typeof SEGMENT_TYPE_PATHNAME
18
+ | typeof SEGMENT_TYPE_PARAM
19
+ | typeof SEGMENT_TYPE_WILDCARD
20
+ | typeof SEGMENT_TYPE_OPTIONAL_PARAM
21
+
22
+ /**
23
+ * All the kinds of segments that can be present in the segment tree.
24
+ */
25
+ type ExtendedSegmentKind =
26
+ | SegmentKind
27
+ | typeof SEGMENT_TYPE_INDEX
28
+ | typeof SEGMENT_TYPE_PATHLESS
29
+
30
+ function getOpenAndCloseBraces(
31
+ part: string,
32
+ ): [openBrace: number, closeBrace: number] | null {
33
+ const openBrace = part.indexOf('{')
34
+ if (openBrace === -1) return null
35
+ const closeBrace = part.indexOf('}', openBrace)
36
+ if (closeBrace === -1) return null
37
+ const afterOpen = openBrace + 1
38
+ if (afterOpen >= part.length) return null
39
+ return [openBrace, closeBrace]
40
+ }
41
+
42
+ type ParsedSegment = Uint16Array & {
43
+ /** segment type (0 = pathname, 1 = param, 2 = wildcard, 3 = optional param) */
44
+ 0: SegmentKind
45
+ /** index of the end of the prefix */
46
+ 1: number
47
+ /** index of the start of the value */
48
+ 2: number
49
+ /** index of the end of the value */
50
+ 3: number
51
+ /** index of the start of the suffix */
52
+ 4: number
53
+ /** index of the end of the segment */
54
+ 5: number
55
+ }
56
+
57
+ /**
58
+ * Populates the `output` array with the parsed representation of the given `segment` string.
59
+ *
60
+ * Usage:
61
+ * ```ts
62
+ * let output
63
+ * let cursor = 0
64
+ * while (cursor < path.length) {
65
+ * output = parseSegment(path, cursor, output)
66
+ * const end = output[5]
67
+ * cursor = end + 1
68
+ * ```
69
+ *
70
+ * `output` is stored outside to avoid allocations during repeated calls. It doesn't need to be typed
71
+ * or initialized, it will be done automatically.
72
+ */
73
+ export function parseSegment(
74
+ /** The full path string containing the segment. */
75
+ path: string,
76
+ /** The starting index of the segment within the path. */
77
+ start: number,
78
+ /** A Uint16Array (length: 6) to populate with the parsed segment data. */
79
+ output: Uint16Array = new Uint16Array(6),
80
+ ): ParsedSegment {
81
+ const next = path.indexOf('/', start)
82
+ const end = next === -1 ? path.length : next
83
+ const part = path.substring(start, end)
84
+
85
+ if (!part || !part.includes('$')) {
86
+ // early escape for static pathname
87
+ output[0] = SEGMENT_TYPE_PATHNAME
88
+ output[1] = start
89
+ output[2] = start
90
+ output[3] = end
91
+ output[4] = end
92
+ output[5] = end
93
+ return output as ParsedSegment
94
+ }
95
+
96
+ // $ (wildcard)
97
+ if (part === '$') {
98
+ const total = path.length
99
+ output[0] = SEGMENT_TYPE_WILDCARD
100
+ output[1] = start
101
+ output[2] = start
102
+ output[3] = total
103
+ output[4] = total
104
+ output[5] = total
105
+ return output as ParsedSegment
106
+ }
107
+
108
+ // $paramName
109
+ if (part.charCodeAt(0) === 36) {
110
+ output[0] = SEGMENT_TYPE_PARAM
111
+ output[1] = start
112
+ output[2] = start + 1 // skip '$'
113
+ output[3] = end
114
+ output[4] = end
115
+ output[5] = end
116
+ return output as ParsedSegment
117
+ }
118
+
119
+ const braces = getOpenAndCloseBraces(part)
120
+ if (braces) {
121
+ const [openBrace, closeBrace] = braces
122
+ const firstChar = part.charCodeAt(openBrace + 1)
123
+
124
+ // Check for {-$...} (optional param)
125
+ // prefix{-$paramName}suffix
126
+ // /^([^{]*)\{-\$([a-zA-Z_$][a-zA-Z0-9_$]*)\}([^}]*)$/
127
+ if (firstChar === 45) {
128
+ // '-'
129
+ if (
130
+ openBrace + 2 < part.length &&
131
+ part.charCodeAt(openBrace + 2) === 36 // '$'
132
+ ) {
133
+ const paramStart = openBrace + 3
134
+ const paramEnd = closeBrace
135
+ // Validate param name exists
136
+ if (paramStart < paramEnd) {
137
+ output[0] = SEGMENT_TYPE_OPTIONAL_PARAM
138
+ output[1] = start + openBrace
139
+ output[2] = start + paramStart
140
+ output[3] = start + paramEnd
141
+ output[4] = start + closeBrace + 1
142
+ output[5] = end
143
+ return output as ParsedSegment
144
+ }
145
+ }
146
+ } else if (firstChar === 36) {
147
+ // '$'
148
+ const dollarPos = openBrace + 1
149
+ const afterDollar = openBrace + 2
150
+ // Check for {$} (wildcard)
151
+ if (afterDollar === closeBrace) {
152
+ // For wildcard, value should be '$' (from dollarPos to afterDollar)
153
+ // prefix{$}suffix
154
+ // /^([^{]*)\{\$\}([^}]*)$/
155
+ output[0] = SEGMENT_TYPE_WILDCARD
156
+ output[1] = start + openBrace
157
+ output[2] = start + dollarPos
158
+ output[3] = start + afterDollar
159
+ output[4] = start + closeBrace + 1
160
+ output[5] = path.length
161
+ return output as ParsedSegment
162
+ }
163
+ // Regular param {$paramName} - value is the param name (after $)
164
+ // prefix{$paramName}suffix
165
+ // /^([^{]*)\{\$([a-zA-Z_$][a-zA-Z0-9_$]*)\}([^}]*)$/
166
+ output[0] = SEGMENT_TYPE_PARAM
167
+ output[1] = start + openBrace
168
+ output[2] = start + afterDollar
169
+ output[3] = start + closeBrace
170
+ output[4] = start + closeBrace + 1
171
+ output[5] = end
172
+ return output as ParsedSegment
173
+ }
174
+ }
175
+
176
+ // fallback to static pathname (should never happen)
177
+ output[0] = SEGMENT_TYPE_PATHNAME
178
+ output[1] = start
179
+ output[2] = start
180
+ output[3] = end
181
+ output[4] = end
182
+ output[5] = end
183
+ return output as ParsedSegment
184
+ }
185
+
186
+ /**
187
+ * Recursively parses the segments of the given route tree and populates a segment trie.
188
+ *
189
+ * @param data A reusable Uint16Array for parsing segments. (non important, we're just avoiding allocations)
190
+ * @param route The current route to parse.
191
+ * @param start The starting index for parsing within the route's full path.
192
+ * @param node The current segment node in the trie to populate.
193
+ * @param onRoute Callback invoked for each route processed.
194
+ */
195
+ function parseSegments<TRouteLike extends RouteLike>(
196
+ defaultCaseSensitive: boolean,
197
+ data: Uint16Array,
198
+ route: TRouteLike,
199
+ start: number,
200
+ node: AnySegmentNode<TRouteLike>,
201
+ depth: number,
202
+ onRoute?: (route: TRouteLike) => void,
203
+ ) {
204
+ onRoute?.(route)
205
+ let cursor = start
206
+ {
207
+ const path = route.fullPath ?? route.from
208
+ const length = path.length
209
+ const caseSensitive = route.options?.caseSensitive ?? defaultCaseSensitive
210
+ const skipOnParamError = !!(
211
+ route.options?.params?.parse &&
212
+ route.options?.skipRouteOnParseError?.params
213
+ )
214
+ while (cursor < length) {
215
+ const segment = parseSegment(path, cursor, data)
216
+ let nextNode: AnySegmentNode<TRouteLike>
217
+ const start = cursor
218
+ const end = segment[5]
219
+ cursor = end + 1
220
+ depth++
221
+ const kind = segment[0]
222
+ switch (kind) {
223
+ case SEGMENT_TYPE_PATHNAME: {
224
+ const value = path.substring(segment[2], segment[3])
225
+ if (caseSensitive) {
226
+ const existingNode = node.static?.get(value)
227
+ if (existingNode) {
228
+ nextNode = existingNode
229
+ } else {
230
+ node.static ??= new Map()
231
+ const next = createStaticNode<TRouteLike>(
232
+ route.fullPath ?? route.from,
233
+ )
234
+ next.parent = node
235
+ next.depth = depth
236
+ nextNode = next
237
+ node.static.set(value, next)
238
+ }
239
+ } else {
240
+ const name = value.toLowerCase()
241
+ const existingNode = node.staticInsensitive?.get(name)
242
+ if (existingNode) {
243
+ nextNode = existingNode
244
+ } else {
245
+ node.staticInsensitive ??= new Map()
246
+ const next = createStaticNode<TRouteLike>(
247
+ route.fullPath ?? route.from,
248
+ )
249
+ next.parent = node
250
+ next.depth = depth
251
+ nextNode = next
252
+ node.staticInsensitive.set(name, next)
253
+ }
254
+ }
255
+ break
256
+ }
257
+ case SEGMENT_TYPE_PARAM: {
258
+ const prefix_raw = path.substring(start, segment[1])
259
+ const suffix_raw = path.substring(segment[4], end)
260
+ const actuallyCaseSensitive =
261
+ caseSensitive && !!(prefix_raw || suffix_raw)
262
+ const prefix = !prefix_raw
263
+ ? undefined
264
+ : actuallyCaseSensitive
265
+ ? prefix_raw
266
+ : prefix_raw.toLowerCase()
267
+ const suffix = !suffix_raw
268
+ ? undefined
269
+ : actuallyCaseSensitive
270
+ ? suffix_raw
271
+ : suffix_raw.toLowerCase()
272
+ const existingNode =
273
+ !skipOnParamError &&
274
+ node.dynamic?.find(
275
+ (s) =>
276
+ !s.skipOnParamError &&
277
+ s.caseSensitive === actuallyCaseSensitive &&
278
+ s.prefix === prefix &&
279
+ s.suffix === suffix,
280
+ )
281
+ if (existingNode) {
282
+ nextNode = existingNode
283
+ } else {
284
+ const next = createDynamicNode<TRouteLike>(
285
+ SEGMENT_TYPE_PARAM,
286
+ route.fullPath ?? route.from,
287
+ actuallyCaseSensitive,
288
+ prefix,
289
+ suffix,
290
+ )
291
+ nextNode = next
292
+ next.depth = depth
293
+ next.parent = node
294
+ node.dynamic ??= []
295
+ node.dynamic.push(next)
296
+ }
297
+ break
298
+ }
299
+ case SEGMENT_TYPE_OPTIONAL_PARAM: {
300
+ const prefix_raw = path.substring(start, segment[1])
301
+ const suffix_raw = path.substring(segment[4], end)
302
+ const actuallyCaseSensitive =
303
+ caseSensitive && !!(prefix_raw || suffix_raw)
304
+ const prefix = !prefix_raw
305
+ ? undefined
306
+ : actuallyCaseSensitive
307
+ ? prefix_raw
308
+ : prefix_raw.toLowerCase()
309
+ const suffix = !suffix_raw
310
+ ? undefined
311
+ : actuallyCaseSensitive
312
+ ? suffix_raw
313
+ : suffix_raw.toLowerCase()
314
+ const existingNode =
315
+ !skipOnParamError &&
316
+ node.optional?.find(
317
+ (s) =>
318
+ !s.skipOnParamError &&
319
+ s.caseSensitive === actuallyCaseSensitive &&
320
+ s.prefix === prefix &&
321
+ s.suffix === suffix,
322
+ )
323
+ if (existingNode) {
324
+ nextNode = existingNode
325
+ } else {
326
+ const next = createDynamicNode<TRouteLike>(
327
+ SEGMENT_TYPE_OPTIONAL_PARAM,
328
+ route.fullPath ?? route.from,
329
+ actuallyCaseSensitive,
330
+ prefix,
331
+ suffix,
332
+ )
333
+ nextNode = next
334
+ next.parent = node
335
+ next.depth = depth
336
+ node.optional ??= []
337
+ node.optional.push(next)
338
+ }
339
+ break
340
+ }
341
+ case SEGMENT_TYPE_WILDCARD: {
342
+ const prefix_raw = path.substring(start, segment[1])
343
+ const suffix_raw = path.substring(segment[4], end)
344
+ const actuallyCaseSensitive =
345
+ caseSensitive && !!(prefix_raw || suffix_raw)
346
+ const prefix = !prefix_raw
347
+ ? undefined
348
+ : actuallyCaseSensitive
349
+ ? prefix_raw
350
+ : prefix_raw.toLowerCase()
351
+ const suffix = !suffix_raw
352
+ ? undefined
353
+ : actuallyCaseSensitive
354
+ ? suffix_raw
355
+ : suffix_raw.toLowerCase()
356
+ const next = createDynamicNode<TRouteLike>(
357
+ SEGMENT_TYPE_WILDCARD,
358
+ route.fullPath ?? route.from,
359
+ actuallyCaseSensitive,
360
+ prefix,
361
+ suffix,
362
+ )
363
+ nextNode = next
364
+ next.parent = node
365
+ next.depth = depth
366
+ node.wildcard ??= []
367
+ node.wildcard.push(next)
368
+ }
369
+ }
370
+ node = nextNode
371
+ }
372
+
373
+ // create pathless node
374
+ if (
375
+ skipOnParamError &&
376
+ route.children &&
377
+ !route.isRoot &&
378
+ route.id &&
379
+ route.id.charCodeAt(route.id.lastIndexOf('/') + 1) === 95 /* '_' */
380
+ ) {
381
+ const pathlessNode = createStaticNode<TRouteLike>(
382
+ route.fullPath ?? route.from,
383
+ )
384
+ pathlessNode.kind = SEGMENT_TYPE_PATHLESS
385
+ pathlessNode.parent = node
386
+ depth++
387
+ pathlessNode.depth = depth
388
+ node.pathless ??= []
389
+ node.pathless.push(pathlessNode)
390
+ node = pathlessNode
391
+ }
392
+
393
+ const isLeaf = (route.path || !route.children) && !route.isRoot
394
+ // create index node
395
+ if (isLeaf && path.endsWith('/')) {
396
+ const indexNode = createStaticNode<TRouteLike>(
397
+ route.fullPath ?? route.from,
398
+ )
399
+ indexNode.kind = SEGMENT_TYPE_INDEX
400
+ indexNode.parent = node
401
+ depth++
402
+ indexNode.depth = depth
403
+ node.index = indexNode
404
+ node = indexNode
405
+ }
406
+
407
+ node.parse = route.options?.params?.parse ?? null
408
+ node.skipOnParamError = skipOnParamError
409
+ node.parsingPriority = route.options?.skipRouteOnParseError?.priority ?? 0
410
+
411
+ // make node "matchable"
412
+ if (isLeaf && !node.route) {
413
+ node.route = route
414
+ node.fullPath = route.fullPath ?? route.from
415
+ }
416
+ }
417
+ if (route.children)
418
+ for (const child of route.children) {
419
+ parseSegments(
420
+ defaultCaseSensitive,
421
+ data,
422
+ child as TRouteLike,
423
+ cursor,
424
+ node,
425
+ depth,
426
+ onRoute,
427
+ )
428
+ }
429
+ }
430
+
431
+ function sortDynamic(
432
+ a: {
433
+ prefix?: string
434
+ suffix?: string
435
+ caseSensitive: boolean
436
+ skipOnParamError: boolean
437
+ parsingPriority: number
438
+ },
439
+ b: {
440
+ prefix?: string
441
+ suffix?: string
442
+ caseSensitive: boolean
443
+ skipOnParamError: boolean
444
+ parsingPriority: number
445
+ },
446
+ ) {
447
+ if (a.skipOnParamError && !b.skipOnParamError) return -1
448
+ if (!a.skipOnParamError && b.skipOnParamError) return 1
449
+ if (
450
+ a.skipOnParamError &&
451
+ b.skipOnParamError &&
452
+ (a.parsingPriority || b.parsingPriority)
453
+ )
454
+ return b.parsingPriority - a.parsingPriority
455
+ if (a.prefix && b.prefix && a.prefix !== b.prefix) {
456
+ if (a.prefix.startsWith(b.prefix)) return -1
457
+ if (b.prefix.startsWith(a.prefix)) return 1
458
+ }
459
+ if (a.suffix && b.suffix && a.suffix !== b.suffix) {
460
+ if (a.suffix.endsWith(b.suffix)) return -1
461
+ if (b.suffix.endsWith(a.suffix)) return 1
462
+ }
463
+ if (a.prefix && !b.prefix) return -1
464
+ if (!a.prefix && b.prefix) return 1
465
+ if (a.suffix && !b.suffix) return -1
466
+ if (!a.suffix && b.suffix) return 1
467
+ if (a.caseSensitive && !b.caseSensitive) return -1
468
+ if (!a.caseSensitive && b.caseSensitive) return 1
469
+
470
+ // we don't need a tiebreaker here
471
+ // at this point the 2 nodes cannot conflict during matching
472
+ return 0
473
+ }
474
+
475
+ function sortTreeNodes(node: SegmentNode<RouteLike>) {
476
+ if (node.pathless) {
477
+ for (const child of node.pathless) {
478
+ sortTreeNodes(child)
479
+ }
480
+ }
481
+ if (node.static) {
482
+ for (const child of node.static.values()) {
483
+ sortTreeNodes(child)
484
+ }
485
+ }
486
+ if (node.staticInsensitive) {
487
+ for (const child of node.staticInsensitive.values()) {
488
+ sortTreeNodes(child)
489
+ }
490
+ }
491
+ if (node.dynamic?.length) {
492
+ node.dynamic.sort(sortDynamic)
493
+ for (const child of node.dynamic) {
494
+ sortTreeNodes(child)
495
+ }
496
+ }
497
+ if (node.optional?.length) {
498
+ node.optional.sort(sortDynamic)
499
+ for (const child of node.optional) {
500
+ sortTreeNodes(child)
501
+ }
502
+ }
503
+ if (node.wildcard?.length) {
504
+ node.wildcard.sort(sortDynamic)
505
+ for (const child of node.wildcard) {
506
+ sortTreeNodes(child)
507
+ }
508
+ }
509
+ }
510
+
511
+ function createStaticNode<T extends RouteLike>(
512
+ fullPath: string,
513
+ ): StaticSegmentNode<T> {
514
+ return {
515
+ kind: SEGMENT_TYPE_PATHNAME,
516
+ depth: 0,
517
+ pathless: null,
518
+ index: null,
519
+ static: null,
520
+ staticInsensitive: null,
521
+ dynamic: null,
522
+ optional: null,
523
+ wildcard: null,
524
+ route: null,
525
+ fullPath,
526
+ parent: null,
527
+ parse: null,
528
+ skipOnParamError: false,
529
+ parsingPriority: 0,
530
+ }
531
+ }
532
+
533
+ /**
534
+ * Keys must be declared in the same order as in `SegmentNode` type,
535
+ * to ensure they are represented as the same object class in the engine.
536
+ */
537
+ function createDynamicNode<T extends RouteLike>(
538
+ kind:
539
+ | typeof SEGMENT_TYPE_PARAM
540
+ | typeof SEGMENT_TYPE_WILDCARD
541
+ | typeof SEGMENT_TYPE_OPTIONAL_PARAM,
542
+ fullPath: string,
543
+ caseSensitive: boolean,
544
+ prefix?: string,
545
+ suffix?: string,
546
+ ): DynamicSegmentNode<T> {
547
+ return {
548
+ kind,
549
+ depth: 0,
550
+ pathless: null,
551
+ index: null,
552
+ static: null,
553
+ staticInsensitive: null,
554
+ dynamic: null,
555
+ optional: null,
556
+ wildcard: null,
557
+ route: null,
558
+ fullPath,
559
+ parent: null,
560
+ parse: null,
561
+ skipOnParamError: false,
562
+ parsingPriority: 0,
563
+ caseSensitive,
564
+ prefix,
565
+ suffix,
566
+ }
567
+ }
568
+
569
+ type StaticSegmentNode<T extends RouteLike> = SegmentNode<T> & {
570
+ kind:
571
+ | typeof SEGMENT_TYPE_PATHNAME
572
+ | typeof SEGMENT_TYPE_PATHLESS
573
+ | typeof SEGMENT_TYPE_INDEX
574
+ }
575
+
576
+ type DynamicSegmentNode<T extends RouteLike> = SegmentNode<T> & {
577
+ kind:
578
+ | typeof SEGMENT_TYPE_PARAM
579
+ | typeof SEGMENT_TYPE_WILDCARD
580
+ | typeof SEGMENT_TYPE_OPTIONAL_PARAM
581
+ prefix?: string
582
+ suffix?: string
583
+ caseSensitive: boolean
584
+ }
585
+
586
+ type AnySegmentNode<T extends RouteLike> =
587
+ | StaticSegmentNode<T>
588
+ | DynamicSegmentNode<T>
589
+
590
+ type SegmentNode<T extends RouteLike> = {
591
+ kind: ExtendedSegmentKind
592
+
593
+ pathless: Array<StaticSegmentNode<T>> | null
594
+
595
+ /** Exact index segment (highest priority) */
596
+ index: StaticSegmentNode<T> | null
597
+
598
+ /** Static segments (2nd priority) */
599
+ static: Map<string, StaticSegmentNode<T>> | null
600
+
601
+ /** Case insensitive static segments (3rd highest priority) */
602
+ staticInsensitive: Map<string, StaticSegmentNode<T>> | null
603
+
604
+ /** Dynamic segments ($param) */
605
+ dynamic: Array<DynamicSegmentNode<T>> | null
606
+
607
+ /** Optional dynamic segments ({-$param}) */
608
+ optional: Array<DynamicSegmentNode<T>> | null
609
+
610
+ /** Wildcard segments ($ - lowest priority) */
611
+ wildcard: Array<DynamicSegmentNode<T>> | null
612
+
613
+ /** Terminal route (if this path can end here) */
614
+ route: T | null
615
+
616
+ /** The full path for this segment node (will only be valid on leaf nodes) */
617
+ fullPath: string
618
+
619
+ parent: AnySegmentNode<T> | null
620
+
621
+ depth: number
622
+
623
+ /** route.options.params.parse function, set on the last node of the route */
624
+ parse: null | ((params: Record<string, string>) => any)
625
+
626
+ /** options.skipRouteOnParseError.params ?? false */
627
+ skipOnParamError: boolean
628
+
629
+ /** options.skipRouteOnParseError.priority ?? 0 */
630
+ parsingPriority: number
631
+ }
632
+
633
+ type RouteLike = {
634
+ id?: string
635
+ path?: string // relative path from the parent,
636
+ children?: Array<RouteLike> // child routes,
637
+ parentRoute?: RouteLike // parent route,
638
+ isRoot?: boolean
639
+ options?: {
640
+ skipRouteOnParseError?: {
641
+ params?: boolean
642
+ priority?: number
643
+ }
644
+ caseSensitive?: boolean
645
+ params?: {
646
+ parse?: (params: Record<string, string>) => any
647
+ }
648
+ }
649
+ } &
650
+ // router tree
651
+ (| { fullPath: string; from?: never } // full path from the root
652
+ // flat route masks list
653
+ | { fullPath?: never; from: string } // full path from the root
654
+ )
655
+
656
+ export type ProcessedTree<
657
+ TTree extends Extract<RouteLike, { fullPath: string }>,
658
+ TFlat extends Extract<RouteLike, { from: string }>,
659
+ TSingle extends Extract<RouteLike, { from: string }>,
660
+ > = {
661
+ /** a representation of the `routeTree` as a segment tree */
662
+ segmentTree: AnySegmentNode<TTree>
663
+ /** a mini route tree generated from the flat `routeMasks` list */
664
+ masksTree: AnySegmentNode<TFlat> | null
665
+ /** @deprecated keep until v2 so that `router.matchRoute` can keep not caring about the actual route tree */
666
+ singleCache: LRUCache<string, AnySegmentNode<TSingle>>
667
+ /** a cache of route matches from the `segmentTree` */
668
+ matchCache: LRUCache<string, RouteMatch<TTree> | null>
669
+ /** a cache of route matches from the `masksTree` */
670
+ flatCache: LRUCache<string, ReturnType<typeof findMatch<TFlat>>> | null
671
+ }
672
+
673
+ export function processRouteMasks<
674
+ TRouteLike extends Extract<RouteLike, { from: string }>,
675
+ >(
676
+ routeList: Array<TRouteLike>,
677
+ processedTree: ProcessedTree<any, TRouteLike, any>,
678
+ ) {
679
+ const segmentTree = createStaticNode<TRouteLike>('/')
680
+ const data = new Uint16Array(6)
681
+ for (const route of routeList) {
682
+ parseSegments(false, data, route, 1, segmentTree, 0)
683
+ }
684
+ sortTreeNodes(segmentTree)
685
+ processedTree.masksTree = segmentTree
686
+ processedTree.flatCache = createLRUCache<
687
+ string,
688
+ ReturnType<typeof findMatch<TRouteLike>>
689
+ >(1000)
690
+ }
691
+
692
+ /**
693
+ * Take an arbitrary list of routes, create a tree from them (if it hasn't been created already), and match a path against it.
694
+ */
695
+ export function findFlatMatch<T extends Extract<RouteLike, { from: string }>>(
696
+ /** The path to match. */
697
+ path: string,
698
+ /** The `processedTree` returned by the initial `processRouteTree` call. */
699
+ processedTree: ProcessedTree<any, T, any>,
700
+ ) {
701
+ path ||= '/'
702
+ const cached = processedTree.flatCache!.get(path)
703
+ if (cached) return cached
704
+ const result = findMatch(path, processedTree.masksTree!)
705
+ processedTree.flatCache!.set(path, result)
706
+ return result
707
+ }
708
+
709
+ /**
710
+ * @deprecated keep until v2 so that `router.matchRoute` can keep not caring about the actual route tree
711
+ */
712
+ export function findSingleMatch(
713
+ from: string,
714
+ caseSensitive: boolean,
715
+ fuzzy: boolean,
716
+ path: string,
717
+ processedTree: ProcessedTree<any, any, { from: string }>,
718
+ ) {
719
+ from ||= '/'
720
+ path ||= '/'
721
+ const key = caseSensitive ? `case\0${from}` : from
722
+ let tree = processedTree.singleCache.get(key)
723
+ if (!tree) {
724
+ // single flat routes (router.matchRoute) are not eagerly processed,
725
+ // if we haven't seen this route before, process it now
726
+ tree = createStaticNode<{ from: string }>('/')
727
+ const data = new Uint16Array(6)
728
+ parseSegments(caseSensitive, data, { from }, 1, tree, 0)
729
+ processedTree.singleCache.set(key, tree)
730
+ }
731
+ return findMatch(path, tree, fuzzy)
732
+ }
733
+
734
+ type RouteMatch<T extends Extract<RouteLike, { fullPath: string }>> = {
735
+ route: T
736
+ rawParams: Record<string, string>
737
+ parsedParams?: Record<string, unknown>
738
+ branch: ReadonlyArray<T>
739
+ }
740
+
741
+ export function findRouteMatch<
742
+ T extends Extract<RouteLike, { fullPath: string }>,
743
+ >(
744
+ /** The path to match against the route tree. */
745
+ path: string,
746
+ /** The `processedTree` returned by the initial `processRouteTree` call. */
747
+ processedTree: ProcessedTree<T, any, any>,
748
+ /** If `true`, allows fuzzy matching (partial matches), i.e. which node in the tree would have been an exact match if the `path` had been shorter? */
749
+ fuzzy = false,
750
+ ): RouteMatch<T> | null {
751
+ const key = fuzzy ? path : `nofuzz\0${path}` // the main use for `findRouteMatch` is fuzzy:true, so we optimize for that case
752
+ const cached = processedTree.matchCache.get(key)
753
+ if (cached !== undefined) return cached
754
+ path ||= '/'
755
+ let result: RouteMatch<T> | null
756
+
757
+ try {
758
+ result = findMatch(
759
+ path,
760
+ processedTree.segmentTree,
761
+ fuzzy,
762
+ ) as RouteMatch<T> | null
763
+ } catch (err) {
764
+ if (err instanceof URIError) {
765
+ result = null
766
+ } else {
767
+ throw err
768
+ }
769
+ }
770
+
771
+ if (result) result.branch = buildRouteBranch(result.route)
772
+ processedTree.matchCache.set(key, result)
773
+ return result
774
+ }
775
+
776
+ /** Trim trailing slashes (except preserving root '/'). */
777
+ export function trimPathRight(path: string) {
778
+ return path === '/' ? path : path.replace(/\/{1,}$/, '')
779
+ }
780
+
781
+ export interface ProcessRouteTreeResult<
782
+ TRouteLike extends Extract<RouteLike, { fullPath: string }> & { id: string },
783
+ > {
784
+ /** Should be considered a black box, needs to be provided to all matching functions in this module. */
785
+ processedTree: ProcessedTree<TRouteLike, any, any>
786
+ /** A lookup map of routes by their unique IDs. */
787
+ routesById: Record<string, TRouteLike>
788
+ /** A lookup map of routes by their trimmed full paths. */
789
+ routesByPath: Record<string, TRouteLike>
790
+ }
791
+
792
+ /**
793
+ * Processes a route tree into a segment trie for efficient path matching.
794
+ * Also builds lookup maps for routes by ID and by trimmed full path.
795
+ */
796
+ export function processRouteTree<
797
+ TRouteLike extends Extract<RouteLike, { fullPath: string }> & { id: string },
798
+ >(
799
+ /** The root of the route tree to process. */
800
+ routeTree: TRouteLike,
801
+ /** Whether matching should be case sensitive by default (overridden by individual route options). */
802
+ caseSensitive: boolean = false,
803
+ /** Optional callback invoked for each route during processing. */
804
+ initRoute?: (route: TRouteLike, index: number) => void,
805
+ ): ProcessRouteTreeResult<TRouteLike> {
806
+ const segmentTree = createStaticNode<TRouteLike>(routeTree.fullPath)
807
+ const data = new Uint16Array(6)
808
+ const routesById = {} as Record<string, TRouteLike>
809
+ const routesByPath = {} as Record<string, TRouteLike>
810
+ let index = 0
811
+ parseSegments(caseSensitive, data, routeTree, 1, segmentTree, 0, (route) => {
812
+ initRoute?.(route, index)
813
+
814
+ if (route.id in routesById) {
815
+ if (process.env.NODE_ENV !== 'production') {
816
+ throw new Error(
817
+ `Invariant failed: Duplicate routes found with id: ${String(route.id)}`,
818
+ )
819
+ }
820
+
821
+ invariant()
822
+ }
823
+
824
+ routesById[route.id] = route
825
+
826
+ if (index !== 0 && route.path) {
827
+ const trimmedFullPath = trimPathRight(route.fullPath)
828
+ if (!routesByPath[trimmedFullPath] || route.fullPath.endsWith('/')) {
829
+ routesByPath[trimmedFullPath] = route
830
+ }
831
+ }
832
+
833
+ index++
834
+ })
835
+ sortTreeNodes(segmentTree)
836
+ const processedTree: ProcessedTree<TRouteLike, any, any> = {
837
+ segmentTree,
838
+ singleCache: createLRUCache<string, AnySegmentNode<any>>(1000),
839
+ matchCache: createLRUCache<string, RouteMatch<TRouteLike> | null>(1000),
840
+ flatCache: null,
841
+ masksTree: null,
842
+ }
843
+ return {
844
+ processedTree,
845
+ routesById,
846
+ routesByPath,
847
+ }
848
+ }
849
+
850
+ function findMatch<T extends RouteLike>(
851
+ path: string,
852
+ segmentTree: AnySegmentNode<T>,
853
+ fuzzy = false,
854
+ ): {
855
+ route: T
856
+ /**
857
+ * The raw (unparsed) params extracted from the path.
858
+ * This will be the exhaustive list of all params defined in the route's path.
859
+ */
860
+ rawParams: Record<string, string>
861
+ /**
862
+ * The accumlulated parsed params of each route in the branch that had `skipRouteOnParseError` enabled.
863
+ * Will not contain all params defined in the route's path. Those w/ a `params.parse` but no `skipRouteOnParseError` will need to be parsed separately.
864
+ */
865
+ parsedParams?: Record<string, unknown>
866
+ } | null {
867
+ const parts = path.split('/')
868
+ const leaf = getNodeMatch(path, parts, segmentTree, fuzzy)
869
+ if (!leaf) return null
870
+ const [rawParams] = extractParams(path, parts, leaf)
871
+ return {
872
+ route: leaf.node.route!,
873
+ rawParams,
874
+ parsedParams: leaf.parsedParams,
875
+ }
876
+ }
877
+
878
+ type ParamExtractionState = {
879
+ part: number
880
+ node: number
881
+ path: number
882
+ segment: number
883
+ }
884
+
885
+ /**
886
+ * This function is "resumable":
887
+ * - the `leaf` input can contain `extract` and `rawParams` properties from a previous `extractParams` call
888
+ * - the returned `state` can be passed back as `extract` in a future call to continue extracting params from where we left off
889
+ *
890
+ * Inputs are *not* mutated.
891
+ */
892
+ function extractParams<T extends RouteLike>(
893
+ path: string,
894
+ parts: Array<string>,
895
+ leaf: {
896
+ node: AnySegmentNode<T>
897
+ skipped: number
898
+ extract?: ParamExtractionState
899
+ rawParams?: Record<string, string>
900
+ },
901
+ ): [rawParams: Record<string, string>, state: ParamExtractionState] {
902
+ const list = buildBranch(leaf.node)
903
+ let nodeParts: Array<string> | null = null
904
+ const rawParams: Record<string, string> = Object.create(null)
905
+ /** which segment of the path we're currently processing */
906
+ let partIndex = leaf.extract?.part ?? 0
907
+ /** which node of the route tree branch we're currently processing */
908
+ let nodeIndex = leaf.extract?.node ?? 0
909
+ /** index of the 1st character of the segment we're processing in the path string */
910
+ let pathIndex = leaf.extract?.path ?? 0
911
+ /** which fullPath segment we're currently processing */
912
+ let segmentCount = leaf.extract?.segment ?? 0
913
+ for (
914
+ ;
915
+ nodeIndex < list.length;
916
+ partIndex++, nodeIndex++, pathIndex++, segmentCount++
917
+ ) {
918
+ const node = list[nodeIndex]!
919
+ // index nodes are terminating nodes, nothing to extract, just leave
920
+ if (node.kind === SEGMENT_TYPE_INDEX) break
921
+ // pathless nodes do not consume a path segment
922
+ if (node.kind === SEGMENT_TYPE_PATHLESS) {
923
+ segmentCount--
924
+ partIndex--
925
+ pathIndex--
926
+ continue
927
+ }
928
+ const part = parts[partIndex]
929
+ const currentPathIndex = pathIndex
930
+ if (part) pathIndex += part.length
931
+ if (node.kind === SEGMENT_TYPE_PARAM) {
932
+ nodeParts ??= leaf.node.fullPath.split('/')
933
+ const nodePart = nodeParts[segmentCount]!
934
+ const preLength = node.prefix?.length ?? 0
935
+ // we can't rely on the presence of prefix/suffix to know whether it's curly-braced or not, because `/{$param}/` is valid, but has no prefix/suffix
936
+ const isCurlyBraced = nodePart.charCodeAt(preLength) === 123 // '{'
937
+ // param name is extracted at match-time so that tree nodes that are identical except for param name can share the same node
938
+ if (isCurlyBraced) {
939
+ const sufLength = node.suffix?.length ?? 0
940
+ const name = nodePart.substring(
941
+ preLength + 2,
942
+ nodePart.length - sufLength - 1,
943
+ )
944
+ const value = part!.substring(preLength, part!.length - sufLength)
945
+ rawParams[name] = decodeURIComponent(value)
946
+ } else {
947
+ const name = nodePart.substring(1)
948
+ rawParams[name] = decodeURIComponent(part!)
949
+ }
950
+ } else if (node.kind === SEGMENT_TYPE_OPTIONAL_PARAM) {
951
+ if (leaf.skipped & (1 << nodeIndex)) {
952
+ partIndex-- // stay on the same part
953
+ pathIndex = currentPathIndex - 1 // undo pathIndex advancement; -1 to account for loop increment
954
+ continue
955
+ }
956
+ nodeParts ??= leaf.node.fullPath.split('/')
957
+ const nodePart = nodeParts[segmentCount]!
958
+ const preLength = node.prefix?.length ?? 0
959
+ const sufLength = node.suffix?.length ?? 0
960
+ const name = nodePart.substring(
961
+ preLength + 3,
962
+ nodePart.length - sufLength - 1,
963
+ )
964
+ const value =
965
+ node.suffix || node.prefix
966
+ ? part!.substring(preLength, part!.length - sufLength)
967
+ : part
968
+ if (value) rawParams[name] = decodeURIComponent(value)
969
+ } else if (node.kind === SEGMENT_TYPE_WILDCARD) {
970
+ const n = node
971
+ const value = path.substring(
972
+ currentPathIndex + (n.prefix?.length ?? 0),
973
+ path.length - (n.suffix?.length ?? 0),
974
+ )
975
+ const splat = decodeURIComponent(value)
976
+ // TODO: Deprecate *
977
+ rawParams['*'] = splat
978
+ rawParams._splat = splat
979
+ break
980
+ }
981
+ }
982
+ if (leaf.rawParams) Object.assign(rawParams, leaf.rawParams)
983
+ return [
984
+ rawParams,
985
+ {
986
+ part: partIndex,
987
+ node: nodeIndex,
988
+ path: pathIndex,
989
+ segment: segmentCount,
990
+ },
991
+ ]
992
+ }
993
+
994
+ function buildRouteBranch<T extends RouteLike>(route: T) {
995
+ const list = [route]
996
+ while (route.parentRoute) {
997
+ route = route.parentRoute as T
998
+ list.push(route)
999
+ }
1000
+ list.reverse()
1001
+ return list
1002
+ }
1003
+
1004
+ function buildBranch<T extends RouteLike>(node: AnySegmentNode<T>) {
1005
+ const list: Array<AnySegmentNode<T>> = Array(node.depth + 1)
1006
+ do {
1007
+ list[node.depth] = node
1008
+ node = node.parent!
1009
+ } while (node)
1010
+ return list
1011
+ }
1012
+
1013
+ type MatchStackFrame<T extends RouteLike> = {
1014
+ node: AnySegmentNode<T>
1015
+ /** index of the segment of path */
1016
+ index: number
1017
+ /** how many nodes between `node` and the root of the segment tree */
1018
+ depth: number
1019
+ /**
1020
+ * Bitmask of skipped optional segments.
1021
+ *
1022
+ * This is a very performant way of storing an "array of booleans", but it means beyond 32 segments we can't track skipped optionals.
1023
+ * If we really really need to support more than 32 segments we can switch to using a `BigInt` here. It's about 2x slower in worst case scenarios.
1024
+ */
1025
+ skipped: number
1026
+ statics: number
1027
+ dynamics: number
1028
+ optionals: number
1029
+ /** intermediary state for param extraction */
1030
+ extract?: ParamExtractionState
1031
+ /** intermediary params from param extraction */
1032
+ rawParams?: Record<string, string>
1033
+ parsedParams?: Record<string, unknown>
1034
+ }
1035
+
1036
+ function getNodeMatch<T extends RouteLike>(
1037
+ path: string,
1038
+ parts: Array<string>,
1039
+ segmentTree: AnySegmentNode<T>,
1040
+ fuzzy: boolean,
1041
+ ) {
1042
+ // quick check for root index
1043
+ // this is an optimization, algorithm should work correctly without this block
1044
+ if (path === '/' && segmentTree.index)
1045
+ return { node: segmentTree.index, skipped: 0 } as Pick<
1046
+ Frame,
1047
+ 'node' | 'skipped' | 'parsedParams'
1048
+ >
1049
+
1050
+ const trailingSlash = !last(parts)
1051
+ const pathIsIndex = trailingSlash && path !== '/'
1052
+ const partsLength = parts.length - (trailingSlash ? 1 : 0)
1053
+
1054
+ type Frame = MatchStackFrame<T>
1055
+
1056
+ // use a stack to explore all possible paths (params cause branching)
1057
+ // iterate "backwards" (low priority first) so that we can push() each candidate, and pop() the highest priority candidate first
1058
+ // - pros: it is depth-first, so we find full matches faster
1059
+ // - cons: we cannot short-circuit, because highest priority matches are at the end of the loop (for loop with i--) (but we have no good short-circuiting anyway)
1060
+ // other possible approaches:
1061
+ // - shift instead of pop (measure performance difference), this allows iterating "forwards" (effectively breadth-first)
1062
+ // - never remove from the stack, keep a cursor instead. Then we can push "forwards" and avoid reversing the order of candidates (effectively breadth-first)
1063
+ const stack: Array<Frame> = [
1064
+ {
1065
+ node: segmentTree,
1066
+ index: 1,
1067
+ skipped: 0,
1068
+ depth: 1,
1069
+ statics: 1,
1070
+ dynamics: 0,
1071
+ optionals: 0,
1072
+ },
1073
+ ]
1074
+
1075
+ let wildcardMatch: Frame | null = null
1076
+ let bestFuzzy: Frame | null = null
1077
+ let bestMatch: Frame | null = null
1078
+
1079
+ while (stack.length) {
1080
+ const frame = stack.pop()!
1081
+ const { node, index, skipped, depth, statics, dynamics, optionals } = frame
1082
+ let { extract, rawParams, parsedParams } = frame
1083
+
1084
+ if (node.skipOnParamError) {
1085
+ const result = validateMatchParams(path, parts, frame)
1086
+ if (!result) continue
1087
+ rawParams = frame.rawParams
1088
+ extract = frame.extract
1089
+ parsedParams = frame.parsedParams
1090
+ }
1091
+
1092
+ // In fuzzy mode, track the best partial match we've found so far
1093
+ if (
1094
+ fuzzy &&
1095
+ node.route &&
1096
+ node.kind !== SEGMENT_TYPE_INDEX &&
1097
+ isFrameMoreSpecific(bestFuzzy, frame)
1098
+ ) {
1099
+ bestFuzzy = frame
1100
+ }
1101
+
1102
+ const isBeyondPath = index === partsLength
1103
+ if (isBeyondPath) {
1104
+ if (node.route && !pathIsIndex && isFrameMoreSpecific(bestMatch, frame)) {
1105
+ bestMatch = frame
1106
+ }
1107
+ // beyond the length of the path parts, only some segment types can match
1108
+ if (!node.optional && !node.wildcard && !node.index && !node.pathless)
1109
+ continue
1110
+ }
1111
+
1112
+ const part = isBeyondPath ? undefined : parts[index]!
1113
+ let lowerPart: string
1114
+
1115
+ // 0. Try index match
1116
+ if (isBeyondPath && node.index) {
1117
+ const indexFrame = {
1118
+ node: node.index,
1119
+ index,
1120
+ skipped,
1121
+ depth: depth + 1,
1122
+ statics,
1123
+ dynamics,
1124
+ optionals,
1125
+ extract,
1126
+ rawParams,
1127
+ parsedParams,
1128
+ }
1129
+ let indexValid = true
1130
+ if (node.index.skipOnParamError) {
1131
+ const result = validateMatchParams(path, parts, indexFrame)
1132
+ if (!result) indexValid = false
1133
+ }
1134
+ if (indexValid) {
1135
+ // perfect match, no need to continue
1136
+ // this is an optimization, algorithm should work correctly without this block
1137
+ if (statics === partsLength && !dynamics && !optionals && !skipped) {
1138
+ return indexFrame
1139
+ }
1140
+ if (isFrameMoreSpecific(bestMatch, indexFrame)) {
1141
+ // index matches skip the stack because they cannot have children
1142
+ bestMatch = indexFrame
1143
+ }
1144
+ }
1145
+ }
1146
+
1147
+ // 5. Try wildcard match
1148
+ if (node.wildcard && isFrameMoreSpecific(wildcardMatch, frame)) {
1149
+ for (const segment of node.wildcard) {
1150
+ const { prefix, suffix } = segment
1151
+ if (prefix) {
1152
+ if (isBeyondPath) continue
1153
+ const casePart = segment.caseSensitive
1154
+ ? part
1155
+ : (lowerPart ??= part!.toLowerCase())
1156
+ if (!casePart!.startsWith(prefix)) continue
1157
+ }
1158
+ if (suffix) {
1159
+ if (isBeyondPath) continue
1160
+ const end = parts.slice(index).join('/').slice(-suffix.length)
1161
+ const casePart = segment.caseSensitive ? end : end.toLowerCase()
1162
+ if (casePart !== suffix) continue
1163
+ }
1164
+ // the first wildcard match is the highest priority one
1165
+ // wildcard matches skip the stack because they cannot have children
1166
+ const frame = {
1167
+ node: segment,
1168
+ index: partsLength,
1169
+ skipped,
1170
+ depth,
1171
+ statics,
1172
+ dynamics,
1173
+ optionals,
1174
+ extract,
1175
+ rawParams,
1176
+ parsedParams,
1177
+ }
1178
+ if (segment.skipOnParamError) {
1179
+ const result = validateMatchParams(path, parts, frame)
1180
+ if (!result) continue
1181
+ }
1182
+ wildcardMatch = frame
1183
+ break
1184
+ }
1185
+ }
1186
+
1187
+ // 4. Try optional match
1188
+ if (node.optional) {
1189
+ const nextSkipped = skipped | (1 << depth)
1190
+ const nextDepth = depth + 1
1191
+ for (let i = node.optional.length - 1; i >= 0; i--) {
1192
+ const segment = node.optional[i]!
1193
+ // when skipping, node and depth advance by 1, but index doesn't
1194
+ stack.push({
1195
+ node: segment,
1196
+ index,
1197
+ skipped: nextSkipped,
1198
+ depth: nextDepth,
1199
+ statics,
1200
+ dynamics,
1201
+ optionals,
1202
+ extract,
1203
+ rawParams,
1204
+ parsedParams,
1205
+ }) // enqueue skipping the optional
1206
+ }
1207
+ if (!isBeyondPath) {
1208
+ for (let i = node.optional.length - 1; i >= 0; i--) {
1209
+ const segment = node.optional[i]!
1210
+ const { prefix, suffix } = segment
1211
+ if (prefix || suffix) {
1212
+ const casePart = segment.caseSensitive
1213
+ ? part!
1214
+ : (lowerPart ??= part!.toLowerCase())
1215
+ if (prefix && !casePart.startsWith(prefix)) continue
1216
+ if (suffix && !casePart.endsWith(suffix)) continue
1217
+ }
1218
+ stack.push({
1219
+ node: segment,
1220
+ index: index + 1,
1221
+ skipped,
1222
+ depth: nextDepth,
1223
+ statics,
1224
+ dynamics,
1225
+ optionals: optionals + 1,
1226
+ extract,
1227
+ rawParams,
1228
+ parsedParams,
1229
+ })
1230
+ }
1231
+ }
1232
+ }
1233
+
1234
+ // 3. Try dynamic match
1235
+ if (!isBeyondPath && node.dynamic && part) {
1236
+ for (let i = node.dynamic.length - 1; i >= 0; i--) {
1237
+ const segment = node.dynamic[i]!
1238
+ const { prefix, suffix } = segment
1239
+ if (prefix || suffix) {
1240
+ const casePart = segment.caseSensitive
1241
+ ? part
1242
+ : (lowerPart ??= part.toLowerCase())
1243
+ if (prefix && !casePart.startsWith(prefix)) continue
1244
+ if (suffix && !casePart.endsWith(suffix)) continue
1245
+ }
1246
+ stack.push({
1247
+ node: segment,
1248
+ index: index + 1,
1249
+ skipped,
1250
+ depth: depth + 1,
1251
+ statics,
1252
+ dynamics: dynamics + 1,
1253
+ optionals,
1254
+ extract,
1255
+ rawParams,
1256
+ parsedParams,
1257
+ })
1258
+ }
1259
+ }
1260
+
1261
+ // 2. Try case insensitive static match
1262
+ if (!isBeyondPath && node.staticInsensitive) {
1263
+ const match = node.staticInsensitive.get(
1264
+ (lowerPart ??= part!.toLowerCase()),
1265
+ )
1266
+ if (match) {
1267
+ stack.push({
1268
+ node: match,
1269
+ index: index + 1,
1270
+ skipped,
1271
+ depth: depth + 1,
1272
+ statics: statics + 1,
1273
+ dynamics,
1274
+ optionals,
1275
+ extract,
1276
+ rawParams,
1277
+ parsedParams,
1278
+ })
1279
+ }
1280
+ }
1281
+
1282
+ // 1. Try static match
1283
+ if (!isBeyondPath && node.static) {
1284
+ const match = node.static.get(part!)
1285
+ if (match) {
1286
+ stack.push({
1287
+ node: match,
1288
+ index: index + 1,
1289
+ skipped,
1290
+ depth: depth + 1,
1291
+ statics: statics + 1,
1292
+ dynamics,
1293
+ optionals,
1294
+ extract,
1295
+ rawParams,
1296
+ parsedParams,
1297
+ })
1298
+ }
1299
+ }
1300
+
1301
+ // 0. Try pathless match
1302
+ if (node.pathless) {
1303
+ const nextDepth = depth + 1
1304
+ for (let i = node.pathless.length - 1; i >= 0; i--) {
1305
+ const segment = node.pathless[i]!
1306
+ stack.push({
1307
+ node: segment,
1308
+ index,
1309
+ skipped,
1310
+ depth: nextDepth,
1311
+ statics,
1312
+ dynamics,
1313
+ optionals,
1314
+ extract,
1315
+ rawParams,
1316
+ parsedParams,
1317
+ })
1318
+ }
1319
+ }
1320
+ }
1321
+
1322
+ if (bestMatch && wildcardMatch) {
1323
+ return isFrameMoreSpecific(wildcardMatch, bestMatch)
1324
+ ? bestMatch
1325
+ : wildcardMatch
1326
+ }
1327
+
1328
+ if (bestMatch) return bestMatch
1329
+
1330
+ if (wildcardMatch) return wildcardMatch
1331
+
1332
+ if (fuzzy && bestFuzzy) {
1333
+ let sliceIndex = bestFuzzy.index
1334
+ for (let i = 0; i < bestFuzzy.index; i++) {
1335
+ sliceIndex += parts[i]!.length
1336
+ }
1337
+ const splat = sliceIndex === path.length ? '/' : path.slice(sliceIndex)
1338
+ bestFuzzy.rawParams ??= Object.create(null)
1339
+ bestFuzzy.rawParams!['**'] = decodeURIComponent(splat)
1340
+ return bestFuzzy
1341
+ }
1342
+
1343
+ return null
1344
+ }
1345
+
1346
+ function validateMatchParams<T extends RouteLike>(
1347
+ path: string,
1348
+ parts: Array<string>,
1349
+ frame: MatchStackFrame<T>,
1350
+ ) {
1351
+ try {
1352
+ const [rawParams, state] = extractParams(path, parts, frame)
1353
+ frame.rawParams = rawParams
1354
+ frame.extract = state
1355
+ const parsed = frame.node.parse!(rawParams)
1356
+ frame.parsedParams = Object.assign(
1357
+ Object.create(null),
1358
+ frame.parsedParams,
1359
+ parsed,
1360
+ )
1361
+ return true
1362
+ } catch {
1363
+ return null
1364
+ }
1365
+ }
1366
+
1367
+ function isFrameMoreSpecific(
1368
+ // the stack frame previously saved as "best match"
1369
+ prev: MatchStackFrame<any> | null,
1370
+ // the candidate stack frame
1371
+ next: MatchStackFrame<any>,
1372
+ ): boolean {
1373
+ if (!prev) return true
1374
+ return (
1375
+ next.statics > prev.statics ||
1376
+ (next.statics === prev.statics &&
1377
+ (next.dynamics > prev.dynamics ||
1378
+ (next.dynamics === prev.dynamics &&
1379
+ (next.optionals > prev.optionals ||
1380
+ (next.optionals === prev.optionals &&
1381
+ ((next.node.kind === SEGMENT_TYPE_INDEX) >
1382
+ (prev.node.kind === SEGMENT_TYPE_INDEX) ||
1383
+ ((next.node.kind === SEGMENT_TYPE_INDEX) ===
1384
+ (prev.node.kind === SEGMENT_TYPE_INDEX) &&
1385
+ next.depth > prev.depth)))))))
1386
+ )
1387
+ }