@benjavicente/router-core 1.168.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (380) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +5 -0
  3. package/bin/intent.js +25 -0
  4. package/dist/cjs/Matches.cjs +17 -0
  5. package/dist/cjs/Matches.cjs.map +1 -0
  6. package/dist/cjs/Matches.d.cts +139 -0
  7. package/dist/cjs/RouterProvider.d.cts +27 -0
  8. package/dist/cjs/config.cjs +11 -0
  9. package/dist/cjs/config.cjs.map +1 -0
  10. package/dist/cjs/config.d.cts +17 -0
  11. package/dist/cjs/defer.cjs +41 -0
  12. package/dist/cjs/defer.cjs.map +1 -0
  13. package/dist/cjs/defer.d.cts +37 -0
  14. package/dist/cjs/fileRoute.d.cts +24 -0
  15. package/dist/cjs/global.d.cts +7 -0
  16. package/dist/cjs/hash-scroll.cjs +20 -0
  17. package/dist/cjs/hash-scroll.cjs.map +1 -0
  18. package/dist/cjs/hash-scroll.d.cts +7 -0
  19. package/dist/cjs/history.d.cts +8 -0
  20. package/dist/cjs/index.cjs +96 -0
  21. package/dist/cjs/index.d.cts +53 -0
  22. package/dist/cjs/invariant.cjs +8 -0
  23. package/dist/cjs/invariant.cjs.map +1 -0
  24. package/dist/cjs/invariant.d.cts +1 -0
  25. package/dist/cjs/isServer/client.cjs +7 -0
  26. package/dist/cjs/isServer/client.cjs.map +1 -0
  27. package/dist/cjs/isServer/client.d.cts +1 -0
  28. package/dist/cjs/isServer/development.cjs +7 -0
  29. package/dist/cjs/isServer/development.cjs.map +1 -0
  30. package/dist/cjs/isServer/development.d.cts +1 -0
  31. package/dist/cjs/isServer/server.cjs +7 -0
  32. package/dist/cjs/isServer/server.cjs.map +1 -0
  33. package/dist/cjs/isServer/server.d.cts +1 -0
  34. package/dist/cjs/link.cjs +6 -0
  35. package/dist/cjs/link.cjs.map +1 -0
  36. package/dist/cjs/link.d.cts +221 -0
  37. package/dist/cjs/load-matches.cjs +659 -0
  38. package/dist/cjs/load-matches.cjs.map +1 -0
  39. package/dist/cjs/load-matches.d.cts +18 -0
  40. package/dist/cjs/location.d.cts +50 -0
  41. package/dist/cjs/lru-cache.cjs +70 -0
  42. package/dist/cjs/lru-cache.cjs.map +1 -0
  43. package/dist/cjs/lru-cache.d.cts +6 -0
  44. package/dist/cjs/manifest.cjs +18 -0
  45. package/dist/cjs/manifest.cjs.map +1 -0
  46. package/dist/cjs/manifest.d.cts +35 -0
  47. package/dist/cjs/new-process-route-tree.cjs +754 -0
  48. package/dist/cjs/new-process-route-tree.cjs.map +1 -0
  49. package/dist/cjs/new-process-route-tree.d.cts +236 -0
  50. package/dist/cjs/not-found.cjs +26 -0
  51. package/dist/cjs/not-found.cjs.map +1 -0
  52. package/dist/cjs/not-found.d.cts +32 -0
  53. package/dist/cjs/path.cjs +252 -0
  54. package/dist/cjs/path.cjs.map +1 -0
  55. package/dist/cjs/path.d.cts +56 -0
  56. package/dist/cjs/qss.cjs +70 -0
  57. package/dist/cjs/qss.cjs.map +1 -0
  58. package/dist/cjs/qss.d.cts +33 -0
  59. package/dist/cjs/redirect.cjs +56 -0
  60. package/dist/cjs/redirect.cjs.map +1 -0
  61. package/dist/cjs/redirect.d.cts +77 -0
  62. package/dist/cjs/rewrite.cjs +68 -0
  63. package/dist/cjs/rewrite.cjs.map +1 -0
  64. package/dist/cjs/rewrite.d.cts +30 -0
  65. package/dist/cjs/root.cjs +7 -0
  66. package/dist/cjs/root.cjs.map +1 -0
  67. package/dist/cjs/root.d.cts +3 -0
  68. package/dist/cjs/route.cjs +100 -0
  69. package/dist/cjs/route.cjs.map +1 -0
  70. package/dist/cjs/route.d.cts +552 -0
  71. package/dist/cjs/routeInfo.d.cts +54 -0
  72. package/dist/cjs/router.cjs +1173 -0
  73. package/dist/cjs/router.cjs.map +1 -0
  74. package/dist/cjs/router.d.cts +734 -0
  75. package/dist/cjs/scroll-restoration-inline.cjs +6 -0
  76. package/dist/cjs/scroll-restoration-inline.cjs.map +1 -0
  77. package/dist/cjs/scroll-restoration-inline.d.cts +6 -0
  78. package/dist/cjs/scroll-restoration-script/client.cjs +9 -0
  79. package/dist/cjs/scroll-restoration-script/client.cjs.map +1 -0
  80. package/dist/cjs/scroll-restoration-script/client.d.cts +2 -0
  81. package/dist/cjs/scroll-restoration-script/server.cjs +30 -0
  82. package/dist/cjs/scroll-restoration-script/server.cjs.map +1 -0
  83. package/dist/cjs/scroll-restoration-script/server.d.cts +2 -0
  84. package/dist/cjs/scroll-restoration.cjs +191 -0
  85. package/dist/cjs/scroll-restoration.cjs.map +1 -0
  86. package/dist/cjs/scroll-restoration.d.cts +38 -0
  87. package/dist/cjs/searchMiddleware.cjs +55 -0
  88. package/dist/cjs/searchMiddleware.cjs.map +1 -0
  89. package/dist/cjs/searchMiddleware.d.cts +25 -0
  90. package/dist/cjs/searchParams.cjs +65 -0
  91. package/dist/cjs/searchParams.cjs.map +1 -0
  92. package/dist/cjs/searchParams.d.cts +31 -0
  93. package/dist/cjs/ssr/client.cjs +7 -0
  94. package/dist/cjs/ssr/client.d.cts +6 -0
  95. package/dist/cjs/ssr/constants.cjs +8 -0
  96. package/dist/cjs/ssr/constants.cjs.map +1 -0
  97. package/dist/cjs/ssr/constants.d.cts +3 -0
  98. package/dist/cjs/ssr/createRequestHandler.cjs +44 -0
  99. package/dist/cjs/ssr/createRequestHandler.cjs.map +1 -0
  100. package/dist/cjs/ssr/createRequestHandler.d.cts +9 -0
  101. package/dist/cjs/ssr/handlerCallback.cjs +8 -0
  102. package/dist/cjs/ssr/handlerCallback.cjs.map +1 -0
  103. package/dist/cjs/ssr/handlerCallback.d.cts +9 -0
  104. package/dist/cjs/ssr/headers.cjs +21 -0
  105. package/dist/cjs/ssr/headers.cjs.map +1 -0
  106. package/dist/cjs/ssr/headers.d.cts +3 -0
  107. package/dist/cjs/ssr/json.cjs +11 -0
  108. package/dist/cjs/ssr/json.cjs.map +1 -0
  109. package/dist/cjs/ssr/json.d.cts +10 -0
  110. package/dist/cjs/ssr/serializer/RawStream.cjs +287 -0
  111. package/dist/cjs/ssr/serializer/RawStream.cjs.map +1 -0
  112. package/dist/cjs/ssr/serializer/RawStream.d.cts +64 -0
  113. package/dist/cjs/ssr/serializer/ShallowErrorPlugin.cjs +32 -0
  114. package/dist/cjs/ssr/serializer/ShallowErrorPlugin.cjs.map +1 -0
  115. package/dist/cjs/ssr/serializer/ShallowErrorPlugin.d.cts +9 -0
  116. package/dist/cjs/ssr/serializer/seroval-plugins.cjs +13 -0
  117. package/dist/cjs/ssr/serializer/seroval-plugins.cjs.map +1 -0
  118. package/dist/cjs/ssr/serializer/seroval-plugins.d.cts +2 -0
  119. package/dist/cjs/ssr/serializer/transformer.cjs +53 -0
  120. package/dist/cjs/ssr/serializer/transformer.cjs.map +1 -0
  121. package/dist/cjs/ssr/serializer/transformer.d.cts +91 -0
  122. package/dist/cjs/ssr/server.cjs +13 -0
  123. package/dist/cjs/ssr/server.d.cts +6 -0
  124. package/dist/cjs/ssr/ssr-client.cjs +183 -0
  125. package/dist/cjs/ssr/ssr-client.cjs.map +1 -0
  126. package/dist/cjs/ssr/ssr-client.d.cts +10 -0
  127. package/dist/cjs/ssr/ssr-match-id.cjs +12 -0
  128. package/dist/cjs/ssr/ssr-match-id.cjs.map +1 -0
  129. package/dist/cjs/ssr/ssr-match-id.d.cts +2 -0
  130. package/dist/cjs/ssr/ssr-server.cjs +279 -0
  131. package/dist/cjs/ssr/ssr-server.cjs.map +1 -0
  132. package/dist/cjs/ssr/ssr-server.d.cts +42 -0
  133. package/dist/cjs/ssr/transformStreamWithRouter.cjs +327 -0
  134. package/dist/cjs/ssr/transformStreamWithRouter.cjs.map +1 -0
  135. package/dist/cjs/ssr/transformStreamWithRouter.d.cts +11 -0
  136. package/dist/cjs/ssr/tsrScript.cjs +6 -0
  137. package/dist/cjs/ssr/tsrScript.cjs.map +1 -0
  138. package/dist/cjs/ssr/tsrScript.d.cts +1 -0
  139. package/dist/cjs/ssr/types.d.cts +30 -0
  140. package/dist/cjs/stores.cjs +148 -0
  141. package/dist/cjs/stores.cjs.map +1 -0
  142. package/dist/cjs/stores.d.cts +70 -0
  143. package/dist/cjs/structuralSharing.d.cts +4 -0
  144. package/dist/cjs/typePrimitives.d.cts +65 -0
  145. package/dist/cjs/useLoaderData.d.cts +5 -0
  146. package/dist/cjs/useLoaderDeps.d.cts +5 -0
  147. package/dist/cjs/useNavigate.d.cts +3 -0
  148. package/dist/cjs/useParams.d.cts +5 -0
  149. package/dist/cjs/useRouteContext.d.cts +9 -0
  150. package/dist/cjs/useSearch.d.cts +5 -0
  151. package/dist/cjs/utils.cjs +339 -0
  152. package/dist/cjs/utils.cjs.map +1 -0
  153. package/dist/cjs/utils.d.cts +178 -0
  154. package/dist/cjs/validators.d.cts +51 -0
  155. package/dist/esm/Matches.d.ts +139 -0
  156. package/dist/esm/Matches.js +17 -0
  157. package/dist/esm/Matches.js.map +1 -0
  158. package/dist/esm/RouterProvider.d.ts +27 -0
  159. package/dist/esm/config.d.ts +17 -0
  160. package/dist/esm/config.js +11 -0
  161. package/dist/esm/config.js.map +1 -0
  162. package/dist/esm/defer.d.ts +37 -0
  163. package/dist/esm/defer.js +40 -0
  164. package/dist/esm/defer.js.map +1 -0
  165. package/dist/esm/fileRoute.d.ts +24 -0
  166. package/dist/esm/global.d.ts +7 -0
  167. package/dist/esm/hash-scroll.d.ts +7 -0
  168. package/dist/esm/hash-scroll.js +20 -0
  169. package/dist/esm/hash-scroll.js.map +1 -0
  170. package/dist/esm/history.d.ts +8 -0
  171. package/dist/esm/index.d.ts +53 -0
  172. package/dist/esm/index.js +24 -0
  173. package/dist/esm/invariant.d.ts +1 -0
  174. package/dist/esm/invariant.js +8 -0
  175. package/dist/esm/invariant.js.map +1 -0
  176. package/dist/esm/isServer/client.d.ts +1 -0
  177. package/dist/esm/isServer/client.js +6 -0
  178. package/dist/esm/isServer/client.js.map +1 -0
  179. package/dist/esm/isServer/development.d.ts +1 -0
  180. package/dist/esm/isServer/development.js +6 -0
  181. package/dist/esm/isServer/development.js.map +1 -0
  182. package/dist/esm/isServer/server.d.ts +1 -0
  183. package/dist/esm/isServer/server.js +6 -0
  184. package/dist/esm/isServer/server.js.map +1 -0
  185. package/dist/esm/link.d.ts +221 -0
  186. package/dist/esm/link.js +6 -0
  187. package/dist/esm/link.js.map +1 -0
  188. package/dist/esm/load-matches.d.ts +18 -0
  189. package/dist/esm/load-matches.js +657 -0
  190. package/dist/esm/load-matches.js.map +1 -0
  191. package/dist/esm/location.d.ts +50 -0
  192. package/dist/esm/lru-cache.d.ts +6 -0
  193. package/dist/esm/lru-cache.js +70 -0
  194. package/dist/esm/lru-cache.js.map +1 -0
  195. package/dist/esm/manifest.d.ts +35 -0
  196. package/dist/esm/manifest.js +17 -0
  197. package/dist/esm/manifest.js.map +1 -0
  198. package/dist/esm/new-process-route-tree.d.ts +236 -0
  199. package/dist/esm/new-process-route-tree.js +749 -0
  200. package/dist/esm/new-process-route-tree.js.map +1 -0
  201. package/dist/esm/not-found.d.ts +32 -0
  202. package/dist/esm/not-found.js +25 -0
  203. package/dist/esm/not-found.js.map +1 -0
  204. package/dist/esm/path.d.ts +56 -0
  205. package/dist/esm/path.js +243 -0
  206. package/dist/esm/path.js.map +1 -0
  207. package/dist/esm/qss.d.ts +33 -0
  208. package/dist/esm/qss.js +69 -0
  209. package/dist/esm/qss.js.map +1 -0
  210. package/dist/esm/redirect.d.ts +77 -0
  211. package/dist/esm/redirect.js +53 -0
  212. package/dist/esm/redirect.js.map +1 -0
  213. package/dist/esm/rewrite.d.ts +30 -0
  214. package/dist/esm/rewrite.js +65 -0
  215. package/dist/esm/rewrite.js.map +1 -0
  216. package/dist/esm/root.d.ts +3 -0
  217. package/dist/esm/root.js +7 -0
  218. package/dist/esm/root.js.map +1 -0
  219. package/dist/esm/route.d.ts +552 -0
  220. package/dist/esm/route.js +98 -0
  221. package/dist/esm/route.js.map +1 -0
  222. package/dist/esm/routeInfo.d.ts +54 -0
  223. package/dist/esm/router.d.ts +734 -0
  224. package/dist/esm/router.js +1165 -0
  225. package/dist/esm/router.js.map +1 -0
  226. package/dist/esm/scroll-restoration-inline.d.ts +6 -0
  227. package/dist/esm/scroll-restoration-inline.js +6 -0
  228. package/dist/esm/scroll-restoration-inline.js.map +1 -0
  229. package/dist/esm/scroll-restoration-script/client.d.ts +2 -0
  230. package/dist/esm/scroll-restoration-script/client.js +8 -0
  231. package/dist/esm/scroll-restoration-script/client.js.map +1 -0
  232. package/dist/esm/scroll-restoration-script/server.d.ts +2 -0
  233. package/dist/esm/scroll-restoration-script/server.js +29 -0
  234. package/dist/esm/scroll-restoration-script/server.js.map +1 -0
  235. package/dist/esm/scroll-restoration.d.ts +38 -0
  236. package/dist/esm/scroll-restoration.js +187 -0
  237. package/dist/esm/scroll-restoration.js.map +1 -0
  238. package/dist/esm/searchMiddleware.d.ts +25 -0
  239. package/dist/esm/searchMiddleware.js +54 -0
  240. package/dist/esm/searchMiddleware.js.map +1 -0
  241. package/dist/esm/searchParams.d.ts +31 -0
  242. package/dist/esm/searchParams.js +62 -0
  243. package/dist/esm/searchParams.js.map +1 -0
  244. package/dist/esm/ssr/client.d.ts +6 -0
  245. package/dist/esm/ssr/client.js +4 -0
  246. package/dist/esm/ssr/constants.d.ts +3 -0
  247. package/dist/esm/ssr/constants.js +7 -0
  248. package/dist/esm/ssr/constants.js.map +1 -0
  249. package/dist/esm/ssr/createRequestHandler.d.ts +9 -0
  250. package/dist/esm/ssr/createRequestHandler.js +44 -0
  251. package/dist/esm/ssr/createRequestHandler.js.map +1 -0
  252. package/dist/esm/ssr/handlerCallback.d.ts +9 -0
  253. package/dist/esm/ssr/handlerCallback.js +8 -0
  254. package/dist/esm/ssr/handlerCallback.js.map +1 -0
  255. package/dist/esm/ssr/headers.d.ts +3 -0
  256. package/dist/esm/ssr/headers.js +21 -0
  257. package/dist/esm/ssr/headers.js.map +1 -0
  258. package/dist/esm/ssr/json.d.ts +10 -0
  259. package/dist/esm/ssr/json.js +11 -0
  260. package/dist/esm/ssr/json.js.map +1 -0
  261. package/dist/esm/ssr/serializer/RawStream.d.ts +64 -0
  262. package/dist/esm/ssr/serializer/RawStream.js +282 -0
  263. package/dist/esm/ssr/serializer/RawStream.js.map +1 -0
  264. package/dist/esm/ssr/serializer/ShallowErrorPlugin.d.ts +9 -0
  265. package/dist/esm/ssr/serializer/ShallowErrorPlugin.js +33 -0
  266. package/dist/esm/ssr/serializer/ShallowErrorPlugin.js.map +1 -0
  267. package/dist/esm/ssr/serializer/seroval-plugins.d.ts +2 -0
  268. package/dist/esm/ssr/serializer/seroval-plugins.js +13 -0
  269. package/dist/esm/ssr/serializer/seroval-plugins.js.map +1 -0
  270. package/dist/esm/ssr/serializer/transformer.d.ts +91 -0
  271. package/dist/esm/ssr/serializer/transformer.js +51 -0
  272. package/dist/esm/ssr/serializer/transformer.js.map +1 -0
  273. package/dist/esm/ssr/server.d.ts +6 -0
  274. package/dist/esm/ssr/server.js +5 -0
  275. package/dist/esm/ssr/ssr-client.d.ts +10 -0
  276. package/dist/esm/ssr/ssr-client.js +183 -0
  277. package/dist/esm/ssr/ssr-client.js.map +1 -0
  278. package/dist/esm/ssr/ssr-match-id.d.ts +2 -0
  279. package/dist/esm/ssr/ssr-match-id.js +11 -0
  280. package/dist/esm/ssr/ssr-match-id.js.map +1 -0
  281. package/dist/esm/ssr/ssr-server.d.ts +42 -0
  282. package/dist/esm/ssr/ssr-server.js +277 -0
  283. package/dist/esm/ssr/ssr-server.js.map +1 -0
  284. package/dist/esm/ssr/transformStreamWithRouter.d.ts +11 -0
  285. package/dist/esm/ssr/transformStreamWithRouter.js +325 -0
  286. package/dist/esm/ssr/transformStreamWithRouter.js.map +1 -0
  287. package/dist/esm/ssr/tsrScript.d.ts +0 -0
  288. package/dist/esm/ssr/tsrScript.js +6 -0
  289. package/dist/esm/ssr/tsrScript.js.map +1 -0
  290. package/dist/esm/ssr/types.d.ts +30 -0
  291. package/dist/esm/stores.d.ts +70 -0
  292. package/dist/esm/stores.js +146 -0
  293. package/dist/esm/stores.js.map +1 -0
  294. package/dist/esm/structuralSharing.d.ts +4 -0
  295. package/dist/esm/typePrimitives.d.ts +65 -0
  296. package/dist/esm/useLoaderData.d.ts +5 -0
  297. package/dist/esm/useLoaderDeps.d.ts +5 -0
  298. package/dist/esm/useNavigate.d.ts +3 -0
  299. package/dist/esm/useParams.d.ts +5 -0
  300. package/dist/esm/useRouteContext.d.ts +9 -0
  301. package/dist/esm/useSearch.d.ts +5 -0
  302. package/dist/esm/utils.d.ts +178 -0
  303. package/dist/esm/utils.js +322 -0
  304. package/dist/esm/utils.js.map +1 -0
  305. package/dist/esm/validators.d.ts +51 -0
  306. package/package.json +200 -0
  307. package/skills/router-core/SKILL.md +139 -0
  308. package/skills/router-core/auth-and-guards/SKILL.md +458 -0
  309. package/skills/router-core/code-splitting/SKILL.md +322 -0
  310. package/skills/router-core/data-loading/SKILL.md +485 -0
  311. package/skills/router-core/navigation/SKILL.md +448 -0
  312. package/skills/router-core/not-found-and-errors/SKILL.md +435 -0
  313. package/skills/router-core/path-params/SKILL.md +382 -0
  314. package/skills/router-core/search-params/SKILL.md +349 -0
  315. package/skills/router-core/search-params/references/validation-patterns.md +379 -0
  316. package/skills/router-core/ssr/SKILL.md +437 -0
  317. package/skills/router-core/type-safety/SKILL.md +497 -0
  318. package/src/Matches.ts +291 -0
  319. package/src/RouterProvider.ts +47 -0
  320. package/src/config.ts +42 -0
  321. package/src/defer.ts +69 -0
  322. package/src/fileRoute.ts +164 -0
  323. package/src/global.ts +9 -0
  324. package/src/hash-scroll.ts +21 -0
  325. package/src/history.ts +9 -0
  326. package/src/index.ts +471 -0
  327. package/src/invariant.ts +3 -0
  328. package/src/isServer/client.ts +1 -0
  329. package/src/isServer/development.ts +2 -0
  330. package/src/isServer/server.ts +1 -0
  331. package/src/link.ts +704 -0
  332. package/src/load-matches.ts +1281 -0
  333. package/src/location.ts +51 -0
  334. package/src/lru-cache.ts +74 -0
  335. package/src/manifest.ts +68 -0
  336. package/src/new-process-route-tree.ts +1387 -0
  337. package/src/not-found.ts +41 -0
  338. package/src/path.ts +436 -0
  339. package/src/qss.ts +81 -0
  340. package/src/redirect.ts +179 -0
  341. package/src/rewrite.ts +93 -0
  342. package/src/root.ts +3 -0
  343. package/src/route.ts +2235 -0
  344. package/src/routeInfo.ts +235 -0
  345. package/src/router.ts +3207 -0
  346. package/src/scroll-restoration-inline.ts +81 -0
  347. package/src/scroll-restoration-script/client.ts +5 -0
  348. package/src/scroll-restoration-script/server.ts +64 -0
  349. package/src/scroll-restoration.ts +357 -0
  350. package/src/searchMiddleware.ts +76 -0
  351. package/src/searchParams.ts +90 -0
  352. package/src/ssr/client.ts +6 -0
  353. package/src/ssr/constants.ts +3 -0
  354. package/src/ssr/createRequestHandler.ts +98 -0
  355. package/src/ssr/handlerCallback.ts +15 -0
  356. package/src/ssr/headers.ts +40 -0
  357. package/src/ssr/json.ts +16 -0
  358. package/src/ssr/serializer/RawStream.ts +464 -0
  359. package/src/ssr/serializer/ShallowErrorPlugin.ts +43 -0
  360. package/src/ssr/serializer/seroval-plugins.ts +12 -0
  361. package/src/ssr/serializer/transformer.ts +312 -0
  362. package/src/ssr/server.ts +14 -0
  363. package/src/ssr/ssr-client.ts +313 -0
  364. package/src/ssr/ssr-match-id.ts +7 -0
  365. package/src/ssr/ssr-server.ts +425 -0
  366. package/src/ssr/transformStreamWithRouter.ts +493 -0
  367. package/src/ssr/tsrScript.ts +20 -0
  368. package/src/ssr/types.ts +41 -0
  369. package/src/stores.ts +342 -0
  370. package/src/structuralSharing.ts +7 -0
  371. package/src/typePrimitives.ts +181 -0
  372. package/src/useLoaderData.ts +20 -0
  373. package/src/useLoaderDeps.ts +13 -0
  374. package/src/useNavigate.ts +13 -0
  375. package/src/useParams.ts +20 -0
  376. package/src/useRouteContext.ts +39 -0
  377. package/src/useSearch.ts +20 -0
  378. package/src/utils.ts +708 -0
  379. package/src/validators.ts +121 -0
  380. package/src/vite-env.d.ts +4 -0
package/src/utils.ts ADDED
@@ -0,0 +1,708 @@
1
+ import { isServer } from '@benjavicente/router-core/isServer'
2
+ import type { RouteIds } from './routeInfo'
3
+ import type { AnyRouter } from './router'
4
+
5
+ export type Awaitable<T> = T | Promise<T>
6
+ export type NoInfer<T> = [T][T extends any ? 0 : never]
7
+ export type IsAny<TValue, TYesResult, TNoResult = TValue> = 1 extends 0 & TValue
8
+ ? TYesResult
9
+ : TNoResult
10
+
11
+ export type PickAsRequired<TValue, TKey extends keyof TValue> = Omit<
12
+ TValue,
13
+ TKey
14
+ > &
15
+ Required<Pick<TValue, TKey>>
16
+
17
+ export type PickRequired<T> = {
18
+ [K in keyof T as undefined extends T[K] ? never : K]: T[K]
19
+ }
20
+
21
+ export type PickOptional<T> = {
22
+ [K in keyof T as undefined extends T[K] ? K : never]: T[K]
23
+ }
24
+
25
+ // from https://stackoverflow.com/a/76458160
26
+ export type WithoutEmpty<T> = T extends any ? ({} extends T ? never : T) : never
27
+
28
+ export type Expand<T> = T extends object
29
+ ? T extends infer O
30
+ ? O extends Function
31
+ ? O
32
+ : { [K in keyof O]: O[K] }
33
+ : never
34
+ : T
35
+
36
+ export type DeepPartial<T> = T extends object
37
+ ? {
38
+ [P in keyof T]?: DeepPartial<T[P]>
39
+ }
40
+ : T
41
+
42
+ export type MakeDifferenceOptional<TLeft, TRight> = keyof TLeft &
43
+ keyof TRight extends never
44
+ ? TRight
45
+ : Omit<TRight, keyof TLeft & keyof TRight> & {
46
+ [K in keyof TLeft & keyof TRight]?: TRight[K]
47
+ }
48
+
49
+ // from https://stackoverflow.com/a/53955431
50
+ // eslint-disable-next-line @typescript-eslint/naming-convention
51
+ export type IsUnion<T, U extends T = T> = (
52
+ T extends any ? (U extends T ? false : true) : never
53
+ ) extends false
54
+ ? false
55
+ : true
56
+
57
+ export type IsNonEmptyObject<T> = T extends object
58
+ ? keyof T extends never
59
+ ? false
60
+ : true
61
+ : false
62
+
63
+ export type Assign<TLeft, TRight> = TLeft extends any
64
+ ? TRight extends any
65
+ ? IsNonEmptyObject<TLeft> extends false
66
+ ? TRight
67
+ : IsNonEmptyObject<TRight> extends false
68
+ ? TLeft
69
+ : keyof TLeft & keyof TRight extends never
70
+ ? TLeft & TRight
71
+ : Omit<TLeft, keyof TRight> & TRight
72
+ : never
73
+ : never
74
+
75
+ export type IntersectAssign<TLeft, TRight> = TLeft extends any
76
+ ? TRight extends any
77
+ ? IsNonEmptyObject<TLeft> extends false
78
+ ? TRight
79
+ : IsNonEmptyObject<TRight> extends false
80
+ ? TLeft
81
+ : TRight & TLeft
82
+ : never
83
+ : never
84
+
85
+ export type Timeout = ReturnType<typeof setTimeout>
86
+
87
+ export type Updater<TPrevious, TResult = TPrevious> =
88
+ | TResult
89
+ | ((prev?: TPrevious) => TResult)
90
+
91
+ export type NonNullableUpdater<TPrevious, TResult = TPrevious> =
92
+ | TResult
93
+ | ((prev: TPrevious) => TResult)
94
+
95
+ export type ExtractObjects<TUnion> = TUnion extends MergeAllPrimitive
96
+ ? never
97
+ : TUnion
98
+
99
+ export type PartialMergeAllObject<TUnion> =
100
+ ExtractObjects<TUnion> extends infer TObj
101
+ ? [TObj] extends [never]
102
+ ? never
103
+ : {
104
+ [TKey in TObj extends any ? keyof TObj : never]?: TObj extends any
105
+ ? TKey extends keyof TObj
106
+ ? TObj[TKey]
107
+ : never
108
+ : never
109
+ }
110
+ : never
111
+
112
+ export type MergeAllPrimitive =
113
+ | ReadonlyArray<any>
114
+ | number
115
+ | string
116
+ | bigint
117
+ | boolean
118
+ | symbol
119
+ | undefined
120
+ | null
121
+
122
+ export type ExtractPrimitives<TUnion> = TUnion extends MergeAllPrimitive
123
+ ? TUnion
124
+ : TUnion extends object
125
+ ? never
126
+ : TUnion
127
+
128
+ export type PartialMergeAll<TUnion> =
129
+ | ExtractPrimitives<TUnion>
130
+ | PartialMergeAllObject<TUnion>
131
+
132
+ export type Constrain<T, TConstraint, TDefault = TConstraint> =
133
+ | (T extends TConstraint ? T : never)
134
+ | TDefault
135
+
136
+ export type ConstrainLiteral<T, TConstraint, TDefault = TConstraint> =
137
+ | (T & TConstraint)
138
+ | TDefault
139
+
140
+ /**
141
+ * To be added to router types
142
+ */
143
+ export type UnionToIntersection<T> = (
144
+ T extends any ? (arg: T) => any : never
145
+ ) extends (arg: infer T) => any
146
+ ? T
147
+ : never
148
+
149
+ /**
150
+ * Merges everything in a union into one object.
151
+ * This mapped type is homomorphic which means it preserves stuff! :)
152
+ */
153
+ export type MergeAllObjects<
154
+ TUnion,
155
+ TIntersected = UnionToIntersection<ExtractObjects<TUnion>>,
156
+ > = [keyof TIntersected] extends [never]
157
+ ? never
158
+ : {
159
+ [TKey in keyof TIntersected]: TUnion extends any
160
+ ? TUnion[TKey & keyof TUnion]
161
+ : never
162
+ }
163
+
164
+ export type MergeAll<TUnion> =
165
+ | MergeAllObjects<TUnion>
166
+ | ExtractPrimitives<TUnion>
167
+
168
+ export type ValidateJSON<T> = ((...args: Array<any>) => any) extends T
169
+ ? unknown extends T
170
+ ? never
171
+ : 'Function is not serializable'
172
+ : { [K in keyof T]: ValidateJSON<T[K]> }
173
+
174
+ export type LooseReturnType<T> = T extends (
175
+ ...args: Array<any>
176
+ ) => infer TReturn
177
+ ? TReturn
178
+ : never
179
+
180
+ export type LooseAsyncReturnType<T> = T extends (
181
+ ...args: Array<any>
182
+ ) => infer TReturn
183
+ ? TReturn extends Promise<infer TReturn>
184
+ ? TReturn
185
+ : TReturn
186
+ : never
187
+
188
+ /**
189
+ * Return the last element of an array.
190
+ * Intended for non-empty arrays used within router internals.
191
+ */
192
+ export function last<T>(arr: ReadonlyArray<T>) {
193
+ return arr[arr.length - 1]
194
+ }
195
+
196
+ function isFunction(d: any): d is Function {
197
+ return typeof d === 'function'
198
+ }
199
+
200
+ /**
201
+ * Apply a value-or-updater to a previous value.
202
+ * Accepts either a literal value or a function of the previous value.
203
+ */
204
+ export function functionalUpdate<TPrevious, TResult = TPrevious>(
205
+ updater: Updater<TPrevious, TResult> | NonNullableUpdater<TPrevious, TResult>,
206
+ previous: TPrevious,
207
+ ): TResult {
208
+ if (isFunction(updater)) {
209
+ return updater(previous)
210
+ }
211
+
212
+ return updater
213
+ }
214
+
215
+ const hasOwn = Object.prototype.hasOwnProperty
216
+ const isEnumerable = Object.prototype.propertyIsEnumerable
217
+
218
+ const createNull = () => Object.create(null)
219
+ export const nullReplaceEqualDeep: typeof replaceEqualDeep = (prev, next) =>
220
+ replaceEqualDeep(prev, next, createNull)
221
+
222
+ /**
223
+ * This function returns `prev` if `_next` is deeply equal.
224
+ * If not, it will replace any deeply equal children of `b` with those of `a`.
225
+ * This can be used for structural sharing between immutable JSON values for example.
226
+ * Do not use this with signals
227
+ */
228
+ export function replaceEqualDeep<T>(
229
+ prev: any,
230
+ _next: T,
231
+ _makeObj = () => ({}),
232
+ _depth = 0,
233
+ ): T {
234
+ if (isServer) {
235
+ return _next
236
+ }
237
+ if (prev === _next) {
238
+ return prev
239
+ }
240
+
241
+ if (_depth > 500) return _next
242
+
243
+ const next = _next as any
244
+
245
+ const array = isPlainArray(prev) && isPlainArray(next)
246
+
247
+ if (!array && !(isPlainObject(prev) && isPlainObject(next))) return next
248
+
249
+ const prevItems = array ? prev : getEnumerableOwnKeys(prev)
250
+ if (!prevItems) return next
251
+ const nextItems = array ? next : getEnumerableOwnKeys(next)
252
+ if (!nextItems) return next
253
+ const prevSize = prevItems.length
254
+ const nextSize = nextItems.length
255
+ const copy: any = array ? new Array(nextSize) : _makeObj()
256
+
257
+ let equalItems = 0
258
+
259
+ for (let i = 0; i < nextSize; i++) {
260
+ const key = array ? i : (nextItems[i] as any)
261
+ const p = prev[key]
262
+ const n = next[key]
263
+
264
+ if (p === n) {
265
+ copy[key] = p
266
+ if (array ? i < prevSize : hasOwn.call(prev, key)) equalItems++
267
+ continue
268
+ }
269
+
270
+ if (
271
+ p === null ||
272
+ n === null ||
273
+ typeof p !== 'object' ||
274
+ typeof n !== 'object'
275
+ ) {
276
+ copy[key] = n
277
+ continue
278
+ }
279
+
280
+ const v = replaceEqualDeep(p, n, _makeObj, _depth + 1)
281
+ copy[key] = v
282
+ if (v === p) equalItems++
283
+ }
284
+
285
+ return prevSize === nextSize && equalItems === prevSize ? prev : copy
286
+ }
287
+
288
+ /**
289
+ * Equivalent to `Reflect.ownKeys`, but ensures that objects are "clone-friendly":
290
+ * will return false if object has any non-enumerable properties.
291
+ *
292
+ * Optimized for the common case where objects have no symbol properties.
293
+ */
294
+ function getEnumerableOwnKeys(o: object) {
295
+ const names = Object.getOwnPropertyNames(o)
296
+
297
+ // Fast path: check all string property names are enumerable
298
+ for (const name of names) {
299
+ if (!isEnumerable.call(o, name)) return false
300
+ }
301
+
302
+ // Only check symbols if the object has any (most plain objects don't)
303
+ const symbols = Object.getOwnPropertySymbols(o)
304
+
305
+ // Fast path: no symbols, return names directly (avoids array allocation/concat)
306
+ if (symbols.length === 0) return names
307
+
308
+ // Slow path: has symbols, need to check and merge
309
+ const keys: Array<string | symbol> = names
310
+ for (const symbol of symbols) {
311
+ if (!isEnumerable.call(o, symbol)) return false
312
+ keys.push(symbol)
313
+ }
314
+ return keys
315
+ }
316
+
317
+ // Copied from: https://github.com/jonschlinkert/is-plain-object
318
+ export function isPlainObject(o: any) {
319
+ if (!hasObjectPrototype(o)) {
320
+ return false
321
+ }
322
+
323
+ // If has modified constructor
324
+ const ctor = o.constructor
325
+ if (typeof ctor === 'undefined') {
326
+ return true
327
+ }
328
+
329
+ // If has modified prototype
330
+ const prot = ctor.prototype
331
+ if (!hasObjectPrototype(prot)) {
332
+ return false
333
+ }
334
+
335
+ // If constructor does not have an Object-specific method
336
+ if (!prot.hasOwnProperty('isPrototypeOf')) {
337
+ return false
338
+ }
339
+
340
+ // Most likely a plain Object
341
+ return true
342
+ }
343
+
344
+ function hasObjectPrototype(o: any) {
345
+ return Object.prototype.toString.call(o) === '[object Object]'
346
+ }
347
+
348
+ /**
349
+ * Check if a value is a "plain" array (no extra enumerable keys).
350
+ */
351
+ export function isPlainArray(value: unknown): value is Array<unknown> {
352
+ return Array.isArray(value) && value.length === Object.keys(value).length
353
+ }
354
+
355
+ /**
356
+ * Perform a deep equality check with options for partial comparison and
357
+ * ignoring `undefined` values. Optimized for router state comparisons.
358
+ */
359
+ export function deepEqual(
360
+ a: any,
361
+ b: any,
362
+ opts?: { partial?: boolean; ignoreUndefined?: boolean },
363
+ ): boolean {
364
+ if (a === b) {
365
+ return true
366
+ }
367
+
368
+ if (typeof a !== typeof b) {
369
+ return false
370
+ }
371
+
372
+ if (Array.isArray(a) && Array.isArray(b)) {
373
+ if (a.length !== b.length) return false
374
+ for (let i = 0, l = a.length; i < l; i++) {
375
+ if (!deepEqual(a[i], b[i], opts)) return false
376
+ }
377
+ return true
378
+ }
379
+
380
+ if (isPlainObject(a) && isPlainObject(b)) {
381
+ const ignoreUndefined = opts?.ignoreUndefined ?? true
382
+
383
+ if (opts?.partial) {
384
+ for (const k in b) {
385
+ if (!ignoreUndefined || b[k] !== undefined) {
386
+ if (!deepEqual(a[k], b[k], opts)) return false
387
+ }
388
+ }
389
+ return true
390
+ }
391
+
392
+ let aCount = 0
393
+ if (!ignoreUndefined) {
394
+ aCount = Object.keys(a).length
395
+ } else {
396
+ for (const k in a) {
397
+ if (a[k] !== undefined) aCount++
398
+ }
399
+ }
400
+
401
+ let bCount = 0
402
+ for (const k in b) {
403
+ if (!ignoreUndefined || b[k] !== undefined) {
404
+ bCount++
405
+ if (bCount > aCount || !deepEqual(a[k], b[k], opts)) return false
406
+ }
407
+ }
408
+
409
+ return aCount === bCount
410
+ }
411
+
412
+ return false
413
+ }
414
+
415
+ export type StringLiteral<T> = T extends string
416
+ ? string extends T
417
+ ? string
418
+ : T
419
+ : never
420
+
421
+ export type ThrowOrOptional<T, TThrow extends boolean> = TThrow extends true
422
+ ? T
423
+ : T | undefined
424
+
425
+ export type StrictOrFrom<
426
+ TRouter extends AnyRouter,
427
+ TFrom,
428
+ TStrict extends boolean = true,
429
+ > = TStrict extends false
430
+ ? {
431
+ from?: never
432
+ strict: TStrict
433
+ }
434
+ : {
435
+ from: ConstrainLiteral<TFrom, RouteIds<TRouter['routeTree']>>
436
+ strict?: TStrict
437
+ }
438
+
439
+ export type ThrowConstraint<
440
+ TStrict extends boolean,
441
+ TThrow extends boolean,
442
+ > = TStrict extends false ? (TThrow extends true ? never : TThrow) : TThrow
443
+
444
+ export type ControlledPromise<T> = Promise<T> & {
445
+ resolve: (value: T) => void
446
+ reject: (value: any) => void
447
+ status: 'pending' | 'resolved' | 'rejected'
448
+ value?: T
449
+ }
450
+
451
+ /**
452
+ * Create a promise with exposed resolve/reject and status fields.
453
+ * Useful for coordinating async router lifecycle operations.
454
+ */
455
+ export function createControlledPromise<T>(onResolve?: (value: T) => void) {
456
+ let resolveLoadPromise!: (value: T) => void
457
+ let rejectLoadPromise!: (value: any) => void
458
+
459
+ const controlledPromise = new Promise<T>((resolve, reject) => {
460
+ resolveLoadPromise = resolve
461
+ rejectLoadPromise = reject
462
+ }) as ControlledPromise<T>
463
+
464
+ controlledPromise.status = 'pending'
465
+
466
+ controlledPromise.resolve = (value: T) => {
467
+ controlledPromise.status = 'resolved'
468
+ controlledPromise.value = value
469
+ resolveLoadPromise(value)
470
+ onResolve?.(value)
471
+ }
472
+
473
+ controlledPromise.reject = (e) => {
474
+ controlledPromise.status = 'rejected'
475
+ rejectLoadPromise(e)
476
+ }
477
+
478
+ return controlledPromise
479
+ }
480
+
481
+ /**
482
+ * Heuristically detect dynamic import "module not found" errors
483
+ * across major browsers for lazy route component handling.
484
+ */
485
+ export function isModuleNotFoundError(error: any): boolean {
486
+ // chrome: "Failed to fetch dynamically imported module: http://localhost:5173/src/routes/posts.index.tsx?tsr-split"
487
+ // firefox: "error loading dynamically imported module: http://localhost:5173/src/routes/posts.index.tsx?tsr-split"
488
+ // safari: "Importing a module script failed."
489
+ if (typeof error?.message !== 'string') return false
490
+ return (
491
+ error.message.startsWith('Failed to fetch dynamically imported module') ||
492
+ error.message.startsWith('error loading dynamically imported module') ||
493
+ error.message.startsWith('Importing a module script failed')
494
+ )
495
+ }
496
+
497
+ export function isPromise<T>(
498
+ value: Promise<Awaited<T>> | T,
499
+ ): value is Promise<Awaited<T>> {
500
+ return Boolean(
501
+ value &&
502
+ typeof value === 'object' &&
503
+ typeof (value as Promise<T>).then === 'function',
504
+ )
505
+ }
506
+
507
+ export function findLast<T>(
508
+ array: ReadonlyArray<T>,
509
+ predicate: (item: T) => boolean,
510
+ ): T | undefined {
511
+ for (let i = array.length - 1; i >= 0; i--) {
512
+ const item = array[i]!
513
+ if (predicate(item)) return item
514
+ }
515
+ return undefined
516
+ }
517
+
518
+ /**
519
+ * Remove control characters that can cause open redirect vulnerabilities.
520
+ * Characters like \r (CR) and \n (LF) can trick URL parsers into interpreting
521
+ * paths like "/\r/evil.com" as "http://evil.com".
522
+ */
523
+ function sanitizePathSegment(segment: string): string {
524
+ // Remove ASCII control characters (0x00-0x1F) and DEL (0x7F)
525
+ // These include CR (\r = 0x0D), LF (\n = 0x0A), and other potentially dangerous characters
526
+ // eslint-disable-next-line no-control-regex
527
+ return segment.replace(/[\x00-\x1f\x7f]/g, '')
528
+ }
529
+
530
+ function decodeSegment(segment: string): string {
531
+ let decoded: string
532
+ try {
533
+ decoded = decodeURI(segment)
534
+ } catch {
535
+ // if the decoding fails, try to decode the various parts leaving the malformed tags in place
536
+ decoded = segment.replaceAll(/%[0-9A-F]{2}/gi, (match) => {
537
+ try {
538
+ return decodeURI(match)
539
+ } catch {
540
+ return match
541
+ }
542
+ })
543
+ }
544
+ return sanitizePathSegment(decoded)
545
+ }
546
+
547
+ /**
548
+ * Default list of URL protocols to allow in links, redirects, and navigation.
549
+ * Any absolute URL protocol not in this list is treated as dangerous by default.
550
+ */
551
+ export const DEFAULT_PROTOCOL_ALLOWLIST = [
552
+ // Standard web navigation
553
+ 'http:',
554
+ 'https:',
555
+
556
+ // Common browser-safe actions
557
+ 'mailto:',
558
+ 'tel:',
559
+ ]
560
+
561
+ /**
562
+ * Check if a URL string uses a protocol that is not in the allowlist.
563
+ * Returns true for blocked protocols like javascript:, blob:, data:, etc.
564
+ *
565
+ * The URL constructor correctly normalizes:
566
+ * - Mixed case (JavaScript: → javascript:)
567
+ * - Whitespace/control characters (java\nscript: → javascript:)
568
+ * - Leading whitespace
569
+ *
570
+ * For relative URLs (no protocol), returns false (safe).
571
+ *
572
+ * @param url - The URL string to check
573
+ * @param allowlist - Set of protocols to allow
574
+ * @returns true if the URL uses a protocol that is not allowed
575
+ */
576
+ export function isDangerousProtocol(
577
+ url: string,
578
+ allowlist: Set<string>,
579
+ ): boolean {
580
+ if (!url) return false
581
+
582
+ try {
583
+ // Use the URL constructor - it correctly normalizes protocols
584
+ // per WHATWG URL spec, handling all bypass attempts automatically
585
+ const parsed = new URL(url)
586
+ return !allowlist.has(parsed.protocol)
587
+ } catch {
588
+ // URL constructor throws for relative URLs (no protocol)
589
+ // These are safe - they can't execute scripts
590
+ return false
591
+ }
592
+ }
593
+
594
+ // This utility is based on https://github.com/zertosh/htmlescape
595
+ // License: https://github.com/zertosh/htmlescape/blob/0527ca7156a524d256101bb310a9f970f63078ad/LICENSE
596
+ const HTML_ESCAPE_LOOKUP: { [match: string]: string } = {
597
+ '&': '\\u0026',
598
+ '>': '\\u003e',
599
+ '<': '\\u003c',
600
+ '\u2028': '\\u2028',
601
+ '\u2029': '\\u2029',
602
+ }
603
+
604
+ const HTML_ESCAPE_REGEX = /[&><\u2028\u2029]/g
605
+
606
+ /**
607
+ * Escape HTML special characters in a string to prevent XSS attacks
608
+ * when embedding strings in script tags during SSR.
609
+ *
610
+ * This is essential for preventing XSS vulnerabilities when user-controlled
611
+ * content is embedded in inline scripts.
612
+ */
613
+ export function escapeHtml(str: string): string {
614
+ return str.replace(HTML_ESCAPE_REGEX, (match) => HTML_ESCAPE_LOOKUP[match]!)
615
+ }
616
+
617
+ export function decodePath(path: string) {
618
+ if (!path) return { path, handledProtocolRelativeURL: false }
619
+
620
+ // Fast path: most paths are already decoded and safe.
621
+ // Only fall back to the slower scan/regex path when we see a '%' (encoded),
622
+ // a backslash (explicitly handled), a control character, or a protocol-relative
623
+ // prefix which needs collapsing.
624
+ // eslint-disable-next-line no-control-regex
625
+ if (!/[%\\\x00-\x1f\x7f]/.test(path) && !path.startsWith('//')) {
626
+ return { path, handledProtocolRelativeURL: false }
627
+ }
628
+
629
+ const re = /%25|%5C/gi
630
+ let cursor = 0
631
+ let result = ''
632
+ let match
633
+ while (null !== (match = re.exec(path))) {
634
+ result += decodeSegment(path.slice(cursor, match.index)) + match[0]
635
+ cursor = re.lastIndex
636
+ }
637
+ result = result + decodeSegment(cursor ? path.slice(cursor) : path)
638
+
639
+ // Prevent open redirect via protocol-relative URLs (e.g. "//evil.com")
640
+ // After sanitizing control characters, paths like "/\r/evil.com" become "//evil.com"
641
+ // Collapse leading double slashes to a single slash
642
+ let handledProtocolRelativeURL = false
643
+ if (result.startsWith('//')) {
644
+ handledProtocolRelativeURL = true
645
+ result = '/' + result.replace(/^\/+/, '')
646
+ }
647
+
648
+ return { path: result, handledProtocolRelativeURL }
649
+ }
650
+
651
+ /**
652
+ * Encodes a path the same way `new URL()` would, but without the overhead of full URL parsing.
653
+ *
654
+ * This function encodes:
655
+ * - Whitespace characters (spaces → %20, tabs → %09, etc.)
656
+ * - Non-ASCII/Unicode characters (emojis, accented characters, etc.)
657
+ *
658
+ * It preserves:
659
+ * - Already percent-encoded sequences (won't double-encode %2F, %25, etc.)
660
+ * - ASCII special characters valid in URL paths (@, $, &, +, etc.)
661
+ * - Forward slashes as path separators
662
+ *
663
+ * Used to generate proper href values for SSR without constructing URL objects.
664
+ *
665
+ * @example
666
+ * encodePathLikeUrl('/path/file name.pdf') // '/path/file%20name.pdf'
667
+ * encodePathLikeUrl('/path/日本語') // '/path/%E6%97%A5%E6%9C%AC%E8%AA%9E'
668
+ * encodePathLikeUrl('/path/already%20encoded') // '/path/already%20encoded' (preserved)
669
+ */
670
+ export function encodePathLikeUrl(path: string): string {
671
+ // Encode whitespace and non-ASCII characters that browsers encode in URLs
672
+
673
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: intentional ASCII range check
674
+ // eslint-disable-next-line no-control-regex
675
+ if (!/\s|[^\u0000-\u007F]/.test(path)) return path
676
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: intentional ASCII range check
677
+ // eslint-disable-next-line no-control-regex
678
+ return path.replace(/\s|[^\u0000-\u007F]/gu, encodeURIComponent)
679
+ }
680
+
681
+ /**
682
+ * Builds the dev-mode CSS styles URL for route-scoped CSS collection.
683
+ * Used by HeadContent components in all framework implementations to construct
684
+ * the URL for the `/@tanstack-start/styles.css` endpoint.
685
+ *
686
+ * @param basepath - The router's basepath (may or may not have leading slash)
687
+ * @param routeIds - Array of matched route IDs to include in the CSS collection
688
+ * @returns The full URL path for the dev styles CSS endpoint
689
+ */
690
+ export function buildDevStylesUrl(
691
+ basepath: string,
692
+ routeIds: Array<string>,
693
+ ): string {
694
+ // Trim all leading and trailing slashes from basepath
695
+ const trimmedBasepath = basepath.replace(/^\/+|\/+$/g, '')
696
+ // Build normalized basepath: empty string for root, or '/path' for non-root
697
+ const normalizedBasepath = trimmedBasepath === '' ? '' : `/${trimmedBasepath}`
698
+ return `${normalizedBasepath}/@tanstack-start/styles.css?routes=${encodeURIComponent(routeIds.join(','))}`
699
+ }
700
+
701
+ export function arraysEqual<T>(a: Array<T>, b: Array<T>) {
702
+ if (a === b) return true
703
+ if (a.length !== b.length) return false
704
+ for (let i = 0; i < a.length; i++) {
705
+ if (a[i] !== b[i]) return false
706
+ }
707
+ return true
708
+ }