@tanstack/react-router 1.20.1 → 1.20.3-alpha.1

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 (320) hide show
  1. package/README.md +31 -0
  2. package/dist/cjs/Asset.cjs +41 -0
  3. package/dist/cjs/Asset.cjs.map +1 -0
  4. package/dist/cjs/Asset.d.cts +2 -0
  5. package/dist/cjs/CatchBoundary.cjs +16 -12
  6. package/dist/cjs/CatchBoundary.cjs.map +1 -1
  7. package/dist/cjs/CatchBoundary.d.cts +8 -32
  8. package/dist/cjs/ClientOnly.cjs +20 -0
  9. package/dist/cjs/ClientOnly.cjs.map +1 -0
  10. package/dist/cjs/ClientOnly.d.cts +29 -0
  11. package/dist/cjs/HeadContent.cjs +155 -0
  12. package/dist/cjs/HeadContent.cjs.map +1 -0
  13. package/dist/cjs/HeadContent.d.cts +7 -0
  14. package/dist/cjs/Match.cjs +252 -0
  15. package/dist/cjs/Match.cjs.map +1 -0
  16. package/dist/cjs/Match.d.cts +8 -0
  17. package/dist/cjs/Matches.cjs +39 -287
  18. package/dist/cjs/Matches.cjs.map +1 -1
  19. package/dist/cjs/Matches.d.cts +23 -83
  20. package/dist/cjs/RouterProvider.cjs +17 -140
  21. package/dist/cjs/RouterProvider.cjs.map +1 -1
  22. package/dist/cjs/RouterProvider.d.cts +8 -27
  23. package/dist/cjs/SafeFragment.cjs +8 -0
  24. package/dist/cjs/SafeFragment.cjs.map +1 -0
  25. package/dist/cjs/SafeFragment.d.cts +1 -0
  26. package/dist/cjs/ScriptOnce.cjs +28 -0
  27. package/dist/cjs/ScriptOnce.cjs.map +1 -0
  28. package/dist/cjs/ScriptOnce.d.cts +5 -0
  29. package/dist/cjs/Scripts.cjs +51 -0
  30. package/dist/cjs/Scripts.cjs.map +1 -0
  31. package/dist/cjs/Scripts.d.cts +1 -0
  32. package/dist/cjs/ScrollRestoration.cjs +39 -0
  33. package/dist/cjs/ScrollRestoration.cjs.map +1 -0
  34. package/dist/cjs/ScrollRestoration.d.cts +14 -0
  35. package/dist/cjs/Transitioner.cjs +115 -0
  36. package/dist/cjs/Transitioner.cjs.map +1 -0
  37. package/dist/cjs/Transitioner.d.cts +1 -0
  38. package/dist/cjs/awaited.cjs +12 -65
  39. package/dist/cjs/awaited.cjs.map +1 -1
  40. package/dist/cjs/awaited.d.cts +4 -4
  41. package/dist/cjs/fileRoute.cjs +41 -15
  42. package/dist/cjs/fileRoute.cjs.map +1 -1
  43. package/dist/cjs/fileRoute.d.cts +33 -108
  44. package/dist/cjs/history.d.cts +1 -0
  45. package/dist/cjs/index.cjs +216 -73
  46. package/dist/cjs/index.cjs.map +1 -1
  47. package/dist/cjs/index.d.cts +52 -29
  48. package/dist/cjs/lazyRouteComponent.cjs +40 -29
  49. package/dist/cjs/lazyRouteComponent.cjs.map +1 -1
  50. package/dist/cjs/lazyRouteComponent.d.cts +1 -1
  51. package/dist/cjs/link.cjs +212 -106
  52. package/dist/cjs/link.cjs.map +1 -1
  53. package/dist/cjs/link.d.cts +41 -86
  54. package/dist/cjs/matchContext.cjs +27 -0
  55. package/dist/cjs/matchContext.cjs.map +1 -0
  56. package/dist/cjs/matchContext.d.cts +3 -0
  57. package/dist/cjs/not-found.cjs +9 -15
  58. package/dist/cjs/not-found.cjs.map +1 -1
  59. package/dist/cjs/not-found.d.cts +5 -22
  60. package/dist/cjs/renderRouteNotFound.cjs +22 -0
  61. package/dist/cjs/renderRouteNotFound.cjs.map +1 -0
  62. package/dist/cjs/renderRouteNotFound.d.cts +2 -0
  63. package/dist/cjs/route.cjs +110 -79
  64. package/dist/cjs/route.cjs.map +1 -1
  65. package/dist/cjs/route.d.cts +64 -361
  66. package/dist/cjs/router.cjs +12 -1237
  67. package/dist/cjs/router.cjs.map +1 -1
  68. package/dist/cjs/router.d.cts +69 -237
  69. package/dist/cjs/routerContext.cjs +1 -1
  70. package/dist/cjs/routerContext.cjs.map +1 -1
  71. package/dist/cjs/routerContext.d.cts +7 -2
  72. package/dist/cjs/scroll-restoration.cjs +16 -177
  73. package/dist/cjs/scroll-restoration.cjs.map +1 -1
  74. package/dist/cjs/scroll-restoration.d.cts +1 -18
  75. package/dist/cjs/serializer.d.cts +6 -0
  76. package/dist/cjs/structuralSharing.d.cts +8 -0
  77. package/dist/cjs/typePrimitives.d.cts +16 -0
  78. package/dist/cjs/useBlocker.cjs +138 -9
  79. package/dist/cjs/useBlocker.cjs.map +1 -1
  80. package/dist/cjs/useBlocker.d.cts +64 -7
  81. package/dist/cjs/useCanGoBack.cjs +8 -0
  82. package/dist/cjs/useCanGoBack.cjs.map +1 -0
  83. package/dist/cjs/useCanGoBack.d.cts +1 -0
  84. package/dist/cjs/useLoaderData.cjs +15 -0
  85. package/dist/cjs/useLoaderData.cjs.map +1 -0
  86. package/dist/cjs/useLoaderData.d.cts +8 -0
  87. package/dist/cjs/useLoaderDeps.cjs +14 -0
  88. package/dist/cjs/useLoaderDeps.cjs.map +1 -0
  89. package/dist/cjs/useLoaderDeps.d.cts +8 -0
  90. package/dist/cjs/useLocation.cjs +10 -0
  91. package/dist/cjs/useLocation.cjs.map +1 -0
  92. package/dist/cjs/useLocation.d.cts +7 -0
  93. package/dist/cjs/useMatch.cjs +47 -0
  94. package/dist/cjs/useMatch.cjs.map +1 -0
  95. package/dist/cjs/useMatch.d.cts +10 -0
  96. package/dist/cjs/useNavigate.cjs +18 -19
  97. package/dist/cjs/useNavigate.cjs.map +1 -1
  98. package/dist/cjs/useNavigate.d.cts +4 -8
  99. package/dist/cjs/useParams.cjs +8 -8
  100. package/dist/cjs/useParams.cjs.map +1 -1
  101. package/dist/cjs/useParams.d.cts +9 -8
  102. package/dist/cjs/useRouteContext.cjs +3 -3
  103. package/dist/cjs/useRouteContext.cjs.map +1 -1
  104. package/dist/cjs/useRouteContext.d.cts +3 -7
  105. package/dist/cjs/useRouter.cjs.map +1 -1
  106. package/dist/cjs/useRouter.d.cts +3 -4
  107. package/dist/cjs/useRouterState.cjs +18 -1
  108. package/dist/cjs/useRouterState.cjs.map +1 -1
  109. package/dist/cjs/useRouterState.d.cts +8 -6
  110. package/dist/cjs/useSearch.cjs +7 -4
  111. package/dist/cjs/useSearch.cjs.map +1 -1
  112. package/dist/cjs/useSearch.d.cts +9 -7
  113. package/dist/cjs/utils.cjs +40 -122
  114. package/dist/cjs/utils.cjs.map +1 -1
  115. package/dist/cjs/utils.d.cts +46 -50
  116. package/dist/esm/Asset.d.ts +2 -0
  117. package/dist/esm/Asset.js +41 -0
  118. package/dist/esm/Asset.js.map +1 -0
  119. package/dist/esm/CatchBoundary.d.ts +8 -32
  120. package/dist/esm/CatchBoundary.js +16 -12
  121. package/dist/esm/CatchBoundary.js.map +1 -1
  122. package/dist/esm/ClientOnly.d.ts +29 -0
  123. package/dist/esm/ClientOnly.js +20 -0
  124. package/dist/esm/ClientOnly.js.map +1 -0
  125. package/dist/esm/HeadContent.d.ts +7 -0
  126. package/dist/esm/HeadContent.js +139 -0
  127. package/dist/esm/HeadContent.js.map +1 -0
  128. package/dist/esm/Match.d.ts +8 -0
  129. package/dist/esm/Match.js +235 -0
  130. package/dist/esm/Match.js.map +1 -0
  131. package/dist/esm/Matches.d.ts +23 -83
  132. package/dist/esm/Matches.js +36 -284
  133. package/dist/esm/Matches.js.map +1 -1
  134. package/dist/esm/RouterProvider.d.ts +8 -27
  135. package/dist/esm/RouterProvider.js +20 -126
  136. package/dist/esm/RouterProvider.js.map +1 -1
  137. package/dist/esm/SafeFragment.d.ts +1 -0
  138. package/dist/esm/SafeFragment.js +8 -0
  139. package/dist/esm/SafeFragment.js.map +1 -0
  140. package/dist/esm/ScriptOnce.d.ts +5 -0
  141. package/dist/esm/ScriptOnce.js +28 -0
  142. package/dist/esm/ScriptOnce.js.map +1 -0
  143. package/dist/esm/Scripts.d.ts +1 -0
  144. package/dist/esm/Scripts.js +51 -0
  145. package/dist/esm/Scripts.js.map +1 -0
  146. package/dist/esm/ScrollRestoration.d.ts +14 -0
  147. package/dist/esm/ScrollRestoration.js +39 -0
  148. package/dist/esm/ScrollRestoration.js.map +1 -0
  149. package/dist/esm/Transitioner.d.ts +1 -0
  150. package/dist/esm/Transitioner.js +98 -0
  151. package/dist/esm/Transitioner.js.map +1 -0
  152. package/dist/esm/awaited.d.ts +4 -4
  153. package/dist/esm/awaited.js +12 -65
  154. package/dist/esm/awaited.js.map +1 -1
  155. package/dist/esm/fileRoute.d.ts +33 -108
  156. package/dist/esm/fileRoute.js +38 -12
  157. package/dist/esm/fileRoute.js.map +1 -1
  158. package/dist/esm/history.d.ts +1 -0
  159. package/dist/esm/index.d.ts +52 -29
  160. package/dist/esm/index.js +41 -29
  161. package/dist/esm/index.js.map +1 -1
  162. package/dist/esm/lazyRouteComponent.d.ts +1 -1
  163. package/dist/esm/lazyRouteComponent.js +40 -29
  164. package/dist/esm/lazyRouteComponent.js.map +1 -1
  165. package/dist/esm/link.d.ts +41 -86
  166. package/dist/esm/link.js +212 -106
  167. package/dist/esm/link.js.map +1 -1
  168. package/dist/esm/matchContext.d.ts +3 -0
  169. package/dist/esm/matchContext.js +10 -0
  170. package/dist/esm/matchContext.js.map +1 -0
  171. package/dist/esm/not-found.d.ts +5 -22
  172. package/dist/esm/not-found.js +9 -15
  173. package/dist/esm/not-found.js.map +1 -1
  174. package/dist/esm/renderRouteNotFound.d.ts +2 -0
  175. package/dist/esm/renderRouteNotFound.js +22 -0
  176. package/dist/esm/renderRouteNotFound.js.map +1 -0
  177. package/dist/esm/route.d.ts +64 -361
  178. package/dist/esm/route.js +103 -72
  179. package/dist/esm/route.js.map +1 -1
  180. package/dist/esm/router.d.ts +69 -237
  181. package/dist/esm/router.js +13 -1238
  182. package/dist/esm/router.js.map +1 -1
  183. package/dist/esm/routerContext.d.ts +7 -2
  184. package/dist/esm/routerContext.js +1 -1
  185. package/dist/esm/routerContext.js.map +1 -1
  186. package/dist/esm/scroll-restoration.d.ts +1 -18
  187. package/dist/esm/scroll-restoration.js +17 -161
  188. package/dist/esm/scroll-restoration.js.map +1 -1
  189. package/dist/esm/serializer.d.ts +6 -0
  190. package/dist/esm/structuralSharing.d.ts +8 -0
  191. package/dist/esm/typePrimitives.d.ts +16 -0
  192. package/dist/esm/useBlocker.d.ts +64 -7
  193. package/dist/esm/useBlocker.js +138 -9
  194. package/dist/esm/useBlocker.js.map +1 -1
  195. package/dist/esm/useCanGoBack.d.ts +1 -0
  196. package/dist/esm/useCanGoBack.js +8 -0
  197. package/dist/esm/useCanGoBack.js.map +1 -0
  198. package/dist/esm/useLoaderData.d.ts +8 -0
  199. package/dist/esm/useLoaderData.js +15 -0
  200. package/dist/esm/useLoaderData.js.map +1 -0
  201. package/dist/esm/useLoaderDeps.d.ts +8 -0
  202. package/dist/esm/useLoaderDeps.js +14 -0
  203. package/dist/esm/useLoaderDeps.js.map +1 -0
  204. package/dist/esm/useLocation.d.ts +7 -0
  205. package/dist/esm/useLocation.js +10 -0
  206. package/dist/esm/useLocation.js.map +1 -0
  207. package/dist/esm/useMatch.d.ts +10 -0
  208. package/dist/esm/useMatch.js +30 -0
  209. package/dist/esm/useMatch.js.map +1 -0
  210. package/dist/esm/useNavigate.d.ts +4 -8
  211. package/dist/esm/useNavigate.js +18 -19
  212. package/dist/esm/useNavigate.js.map +1 -1
  213. package/dist/esm/useParams.d.ts +9 -8
  214. package/dist/esm/useParams.js +8 -8
  215. package/dist/esm/useParams.js.map +1 -1
  216. package/dist/esm/useRouteContext.d.ts +3 -7
  217. package/dist/esm/useRouteContext.js +2 -2
  218. package/dist/esm/useRouteContext.js.map +1 -1
  219. package/dist/esm/useRouter.d.ts +3 -4
  220. package/dist/esm/useRouter.js.map +1 -1
  221. package/dist/esm/useRouterState.d.ts +8 -6
  222. package/dist/esm/useRouterState.js +18 -1
  223. package/dist/esm/useRouterState.js.map +1 -1
  224. package/dist/esm/useSearch.d.ts +9 -7
  225. package/dist/esm/useSearch.js +6 -3
  226. package/dist/esm/useSearch.js.map +1 -1
  227. package/dist/esm/utils.d.ts +46 -50
  228. package/dist/esm/utils.js +41 -123
  229. package/dist/esm/utils.js.map +1 -1
  230. package/package.json +30 -31
  231. package/src/Asset.tsx +40 -0
  232. package/src/CatchBoundary.tsx +35 -19
  233. package/src/ClientOnly.tsx +68 -0
  234. package/src/HeadContent.tsx +174 -0
  235. package/src/Match.tsx +330 -0
  236. package/src/Matches.tsx +149 -558
  237. package/src/RouterProvider.tsx +58 -212
  238. package/src/SafeFragment.tsx +5 -0
  239. package/src/ScriptOnce.tsx +32 -0
  240. package/src/Scripts.tsx +65 -0
  241. package/src/ScrollRestoration.tsx +69 -0
  242. package/src/Transitioner.tsx +130 -0
  243. package/src/awaited.tsx +16 -87
  244. package/src/fileRoute.ts +145 -248
  245. package/src/history.ts +2 -1
  246. package/src/index.tsx +368 -30
  247. package/src/lazyRouteComponent.tsx +68 -54
  248. package/src/link.tsx +397 -522
  249. package/src/matchContext.tsx +8 -0
  250. package/src/not-found.tsx +13 -34
  251. package/src/renderRouteNotFound.tsx +27 -0
  252. package/src/route.tsx +572 -0
  253. package/src/router.ts +99 -2067
  254. package/src/routerContext.tsx +8 -2
  255. package/src/scroll-restoration.tsx +23 -224
  256. package/src/serializer.ts +7 -0
  257. package/src/structuralSharing.ts +47 -0
  258. package/src/typePrimitives.ts +84 -0
  259. package/src/useBlocker.tsx +297 -15
  260. package/src/useCanGoBack.ts +5 -0
  261. package/src/useLoaderData.tsx +80 -0
  262. package/src/useLoaderDeps.tsx +58 -0
  263. package/src/useLocation.tsx +41 -0
  264. package/src/useMatch.tsx +119 -0
  265. package/src/useNavigate.tsx +41 -61
  266. package/src/useParams.tsx +88 -23
  267. package/src/useRouteContext.ts +24 -18
  268. package/src/useRouter.tsx +4 -5
  269. package/src/useRouterState.tsx +52 -10
  270. package/src/useSearch.tsx +87 -24
  271. package/src/utils.ts +97 -312
  272. package/dist/cjs/createServerFn.cjs +0 -40
  273. package/dist/cjs/createServerFn.cjs.map +0 -1
  274. package/dist/cjs/createServerFn.d.cts +0 -44
  275. package/dist/cjs/defer.cjs +0 -30
  276. package/dist/cjs/defer.cjs.map +0 -1
  277. package/dist/cjs/defer.d.cts +0 -25
  278. package/dist/cjs/location.d.cts +0 -12
  279. package/dist/cjs/path.cjs +0 -213
  280. package/dist/cjs/path.cjs.map +0 -1
  281. package/dist/cjs/path.d.cts +0 -24
  282. package/dist/cjs/qss.cjs +0 -45
  283. package/dist/cjs/qss.cjs.map +0 -1
  284. package/dist/cjs/qss.d.cts +0 -2
  285. package/dist/cjs/redirects.cjs +0 -16
  286. package/dist/cjs/redirects.cjs.map +0 -1
  287. package/dist/cjs/redirects.d.cts +0 -18
  288. package/dist/cjs/routeInfo.d.cts +0 -31
  289. package/dist/cjs/searchParams.cjs +0 -63
  290. package/dist/cjs/searchParams.cjs.map +0 -1
  291. package/dist/cjs/searchParams.d.cts +0 -7
  292. package/dist/esm/createServerFn.d.ts +0 -44
  293. package/dist/esm/createServerFn.js +0 -40
  294. package/dist/esm/createServerFn.js.map +0 -1
  295. package/dist/esm/defer.d.ts +0 -25
  296. package/dist/esm/defer.js +0 -30
  297. package/dist/esm/defer.js.map +0 -1
  298. package/dist/esm/location.d.ts +0 -12
  299. package/dist/esm/path.d.ts +0 -24
  300. package/dist/esm/path.js +0 -213
  301. package/dist/esm/path.js.map +0 -1
  302. package/dist/esm/qss.d.ts +0 -2
  303. package/dist/esm/qss.js +0 -45
  304. package/dist/esm/qss.js.map +0 -1
  305. package/dist/esm/redirects.d.ts +0 -18
  306. package/dist/esm/redirects.js +0 -16
  307. package/dist/esm/redirects.js.map +0 -1
  308. package/dist/esm/routeInfo.d.ts +0 -31
  309. package/dist/esm/searchParams.d.ts +0 -7
  310. package/dist/esm/searchParams.js +0 -63
  311. package/dist/esm/searchParams.js.map +0 -1
  312. package/src/createServerFn.ts +0 -107
  313. package/src/defer.ts +0 -70
  314. package/src/location.ts +0 -13
  315. package/src/path.ts +0 -280
  316. package/src/qss.ts +0 -53
  317. package/src/redirects.ts +0 -56
  318. package/src/route.ts +0 -1356
  319. package/src/routeInfo.ts +0 -63
  320. package/src/searchParams.ts +0 -79
package/src/router.ts CHANGED
@@ -1,2084 +1,116 @@
1
- import * as React from 'react'
2
- import {
3
- HistoryLocation,
4
- HistoryState,
5
- RouterHistory,
6
- createBrowserHistory,
7
- createMemoryHistory,
8
- } from '@tanstack/history'
9
- import { Store } from '@tanstack/react-store'
10
-
11
- //
12
-
13
- import {
14
- AnySearchSchema,
1
+ import { RouterCore } from '@tanstack/router-core'
2
+ import { createFileRoute, createLazyFileRoute } from './fileRoute'
3
+ import type { RouterHistory } from '@tanstack/history'
4
+ import type {
15
5
  AnyRoute,
16
- AnyContext,
17
- RouteMask,
18
- Route,
19
- LoaderFnContext,
20
- rootRouteId,
6
+ CreateRouterFn,
7
+ RouterConstructorOptions,
8
+ TrailingSlashOption,
9
+ } from '@tanstack/router-core'
10
+
11
+ import type {
12
+ ErrorRouteComponent,
21
13
  NotFoundRouteComponent,
14
+ RouteComponent,
22
15
  } from './route'
23
- import {
24
- FullSearchSchema,
25
- RouteById,
26
- RoutePaths,
27
- RoutesById,
28
- RoutesByPath,
29
- } from './routeInfo'
30
- import { defaultParseSearch, defaultStringifySearch } from './searchParams'
31
- import {
32
- PickAsRequired,
33
- Updater,
34
- NonNullableUpdater,
35
- replaceEqualDeep,
36
- deepEqual,
37
- escapeJSON,
38
- functionalUpdate,
39
- last,
40
- pick,
41
- Timeout,
42
- isServer,
43
- } from './utils'
44
- import { RouteComponent } from './route'
45
- import { AnyRouteMatch, MatchRouteOptions, RouteMatch } from './Matches'
46
- import { ParsedLocation } from './location'
47
- import { SearchSerializer, SearchParser } from './searchParams'
48
- import {
49
- BuildLocationFn,
50
- CommitLocationOptions,
51
- InjectedHtmlEntry,
52
- NavigateFn,
53
- getRouteMatch,
54
- } from './RouterProvider'
55
-
56
- import {
57
- cleanPath,
58
- interpolatePath,
59
- joinPaths,
60
- matchPathname,
61
- parsePathname,
62
- resolvePath,
63
- trimPath,
64
- trimPathLeft,
65
- trimPathRight,
66
- } from './path'
67
- import invariant from 'tiny-invariant'
68
- import { AnyRedirect, ResolvedRedirect, isRedirect } from './redirects'
69
- import { NotFoundError, isNotFound } from './not-found'
70
- import { NavigateOptions, ResolveRelativePath, ToOptions } from './link'
71
- import { NoInfer } from '@tanstack/react-store'
72
- import warning from 'tiny-warning'
73
- import { DeferredPromiseState } from './defer'
74
-
75
- //
76
-
77
- declare global {
78
- interface Window {
79
- __TSR_DEHYDRATED__?: { data: string }
80
- __TSR_ROUTER_CONTEXT__?: React.Context<Router<any>>
81
- }
82
- }
83
-
84
- export interface Register {
85
- // router: Router
86
- }
87
-
88
- export type AnyRouter = Router<AnyRoute, any, any>
89
-
90
- export type RegisteredRouter = Register extends {
91
- router: infer TRouter extends AnyRouter
92
- }
93
- ? TRouter
94
- : AnyRouter
95
-
96
- export type HydrationCtx = {
97
- router: DehydratedRouter
98
- payload: Record<string, any>
99
- }
100
-
101
- export type RouterContextOptions<TRouteTree extends AnyRoute> =
102
- AnyContext extends TRouteTree['types']['routerContext']
103
- ? {
104
- context?: TRouteTree['types']['routerContext']
105
- }
106
- : {
107
- context: TRouteTree['types']['routerContext']
108
- }
109
-
110
- export interface RouterOptions<
111
- TRouteTree extends AnyRoute,
112
- TDehydrated extends Record<string, any> = Record<string, any>,
113
- TSerializedError extends Record<string, any> = Record<string, any>,
114
- > {
115
- history?: RouterHistory
116
- stringifySearch?: SearchSerializer
117
- parseSearch?: SearchParser
118
- defaultPreload?: false | 'intent'
119
- defaultPreloadDelay?: number
120
- defaultComponent?: RouteComponent
121
- defaultErrorComponent?: RouteComponent
122
- defaultPendingComponent?: RouteComponent
123
- defaultPendingMs?: number
124
- defaultPendingMinMs?: number
125
- defaultStaleTime?: number
126
- defaultPreloadStaleTime?: number
127
- defaultPreloadGcTime?: number
128
- notFoundMode?: 'root' | 'fuzzy'
129
- defaultGcTime?: number
130
- caseSensitive?: boolean
131
- routeTree?: TRouteTree
132
- basepath?: string
133
- context?: TRouteTree['types']['routerContext']
134
- dehydrate?: () => TDehydrated
135
- hydrate?: (dehydrated: TDehydrated) => void
136
- routeMasks?: RouteMask<TRouteTree>[]
137
- unmaskOnReload?: boolean
138
- Wrap?: (props: { children: any }) => React.ReactNode
139
- InnerWrap?: (props: { children: any }) => React.ReactNode
140
- /**
141
- * @deprecated
142
- * Use `notFoundComponent` instead.
143
- * See https://tanstack.com/router/v1/docs/guide/not-found-errors#migrating-from-notfoundroute for more info.
144
- */
145
- notFoundRoute?: AnyRoute
146
- defaultNotFoundComponent?: NotFoundRouteComponent
147
- transformer?: RouterTransformer
148
- errorSerializer?: RouterErrorSerializer<TSerializedError>
149
- }
150
-
151
- export interface RouterTransformer {
152
- stringify: (obj: unknown) => string
153
- parse: (str: string) => unknown
154
- }
155
- export interface RouterErrorSerializer<TSerializedError> {
156
- serialize: (err: unknown) => TSerializedError
157
- deserialize: (err: TSerializedError) => unknown
158
- }
159
16
 
160
- export interface RouterState<TRouteTree extends AnyRoute = AnyRoute> {
161
- status: 'pending' | 'idle'
162
- isLoading: boolean
163
- isTransitioning: boolean
164
- matches: RouteMatch<TRouteTree>[]
165
- pendingMatches?: RouteMatch<TRouteTree>[]
166
- cachedMatches: RouteMatch<TRouteTree>[]
167
- location: ParsedLocation<FullSearchSchema<TRouteTree>>
168
- resolvedLocation: ParsedLocation<FullSearchSchema<TRouteTree>>
169
- lastUpdated: number
170
- statusCode: number
171
- redirect?: ResolvedRedirect
172
- }
173
-
174
- export type ListenerFn<TEvent extends RouterEvent> = (event: TEvent) => void
175
-
176
- export interface BuildNextOptions {
177
- to?: string | number | null
178
- params?: true | Updater<unknown>
179
- search?: true | Updater<unknown>
180
- hash?: true | Updater<string>
181
- state?: true | NonNullableUpdater<HistoryState>
182
- mask?: {
183
- to?: string | number | null
184
- params?: true | Updater<unknown>
185
- search?: true | Updater<unknown>
186
- hash?: true | Updater<string>
187
- state?: true | NonNullableUpdater<HistoryState>
188
- unmaskOnReload?: boolean
189
- }
190
- from?: string
191
- }
192
-
193
- export interface DehydratedRouterState {
194
- dehydratedMatches: DehydratedRouteMatch[]
195
- }
196
-
197
- export type DehydratedRouteMatch = Pick<
198
- RouteMatch,
199
- 'id' | 'status' | 'updatedAt' | 'loaderData'
200
- >
201
-
202
- export interface DehydratedRouter {
203
- state: DehydratedRouterState
204
- }
205
-
206
- export type RouterConstructorOptions<
207
- TRouteTree extends AnyRoute,
208
- TDehydrated extends Record<string, any>,
209
- TSerializedError extends Record<string, any>,
210
- > = Omit<RouterOptions<TRouteTree, TDehydrated, TSerializedError>, 'context'> &
211
- RouterContextOptions<TRouteTree>
212
-
213
- export const componentTypes = [
214
- 'component',
215
- 'errorComponent',
216
- 'pendingComponent',
217
- 'notFoundComponent',
218
- ] as const
219
-
220
- export type RouterEvents = {
221
- onBeforeLoad: {
222
- type: 'onBeforeLoad'
223
- fromLocation: ParsedLocation
224
- toLocation: ParsedLocation
225
- pathChanged: boolean
226
- }
227
- onLoad: {
228
- type: 'onLoad'
229
- fromLocation: ParsedLocation
230
- toLocation: ParsedLocation
231
- pathChanged: boolean
232
- }
233
- onResolved: {
234
- type: 'onResolved'
235
- fromLocation: ParsedLocation
236
- toLocation: ParsedLocation
237
- pathChanged: boolean
238
- }
239
- }
240
-
241
- export type RouterEvent = RouterEvents[keyof RouterEvents]
242
-
243
- export type RouterListener<TRouterEvent extends RouterEvent> = {
244
- eventType: TRouterEvent['type']
245
- fn: ListenerFn<TRouterEvent>
246
- }
247
-
248
- export function createRouter<
249
- TRouteTree extends AnyRoute = AnyRoute,
250
- TDehydrated extends Record<string, any> = Record<string, any>,
251
- TSerializedError extends Record<string, any> = Record<string, any>,
252
- >(
253
- options: RouterConstructorOptions<TRouteTree, TDehydrated, TSerializedError>,
254
- ) {
255
- return new Router<TRouteTree, TDehydrated, TSerializedError>(options)
17
+ declare module '@tanstack/router-core' {
18
+ export interface RouterOptionsExtensions {
19
+ /**
20
+ * The default `component` a route should use if no component is provided.
21
+ *
22
+ * @default Outlet
23
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#defaultcomponent-property)
24
+ */
25
+ defaultComponent?: RouteComponent
26
+ /**
27
+ * The default `errorComponent` a route should use if no error component is provided.
28
+ *
29
+ * @default ErrorComponent
30
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#defaulterrorcomponent-property)
31
+ * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/data-loading#handling-errors-with-routeoptionserrorcomponent)
32
+ */
33
+ defaultErrorComponent?: ErrorRouteComponent
34
+ /**
35
+ * The default `pendingComponent` a route should use if no pending component is provided.
36
+ *
37
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#defaultpendingcomponent-property)
38
+ * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/data-loading#showing-a-pending-component)
39
+ */
40
+ defaultPendingComponent?: RouteComponent
41
+ /**
42
+ * The default `notFoundComponent` a route should use if no notFound component is provided.
43
+ *
44
+ * @default NotFound
45
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#defaultnotfoundcomponent-property)
46
+ * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/not-found-errors#default-router-wide-not-found-handling)
47
+ */
48
+ defaultNotFoundComponent?: NotFoundRouteComponent
49
+ /**
50
+ * A component that will be used to wrap the entire router.
51
+ *
52
+ * This is useful for providing a context to the entire router.
53
+ *
54
+ * Only non-DOM-rendering components like providers should be used, anything else will cause a hydration error.
55
+ *
56
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#wrap-property)
57
+ */
58
+ Wrap?: (props: { children: any }) => React.JSX.Element
59
+ /**
60
+ * A component that will be used to wrap the inner contents of the router.
61
+ *
62
+ * This is useful for providing a context to the inner contents of the router where you also need access to the router context and hooks.
63
+ *
64
+ * Only non-DOM-rendering components like providers should be used, anything else will cause a hydration error.
65
+ *
66
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#innerwrap-property)
67
+ */
68
+ InnerWrap?: (props: { children: any }) => React.JSX.Element
69
+
70
+ /**
71
+ * The default `onCatch` handler for errors caught by the Router ErrorBoundary
72
+ *
73
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#defaultoncatch-property)
74
+ * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/data-loading#handling-errors-with-routeoptionsoncatch)
75
+ */
76
+ defaultOnCatch?: (error: Error, errorInfo: React.ErrorInfo) => void
77
+ }
78
+ }
79
+
80
+ export const createRouter: CreateRouterFn = (options) => {
81
+ return new Router(options)
256
82
  }
257
83
 
258
84
  export class Router<
259
- TRouteTree extends AnyRoute = AnyRoute,
260
- TDehydrated extends Record<string, any> = Record<string, any>,
261
- TSerializedError extends Record<string, any> = Record<string, any>,
85
+ in out TRouteTree extends AnyRoute,
86
+ in out TTrailingSlashOption extends TrailingSlashOption = 'never',
87
+ in out TDefaultStructuralSharingOption extends boolean = false,
88
+ in out TRouterHistory extends RouterHistory = RouterHistory,
89
+ in out TDehydrated extends Record<string, any> = Record<string, any>,
90
+ > extends RouterCore<
91
+ TRouteTree,
92
+ TTrailingSlashOption,
93
+ TDefaultStructuralSharingOption,
94
+ TRouterHistory,
95
+ TDehydrated
262
96
  > {
263
- // Option-independent properties
264
- tempLocationKey: string | undefined = `${Math.round(
265
- Math.random() * 10000000,
266
- )}`
267
- resetNextScroll: boolean = true
268
- navigateTimeout: Timeout | null = null
269
- latestLoadPromise: Promise<void> = Promise.resolve()
270
- subscribers = new Set<RouterListener<RouterEvent>>()
271
- injectedHtml: InjectedHtmlEntry[] = []
272
- dehydratedData?: TDehydrated
273
-
274
- // Must build in constructor
275
- __store!: Store<RouterState<TRouteTree>>
276
- options!: PickAsRequired<
277
- Omit<
278
- RouterOptions<TRouteTree, TDehydrated, TSerializedError>,
279
- 'transformer'
280
- > & {
281
- transformer: RouterTransformer
282
- },
283
- 'stringifySearch' | 'parseSearch' | 'context'
284
- >
285
- history!: RouterHistory
286
- latestLocation!: ParsedLocation
287
- basepath!: string
288
- routeTree!: TRouteTree
289
- routesById!: RoutesById<TRouteTree>
290
- routesByPath!: RoutesByPath<TRouteTree>
291
- flatRoutes!: AnyRoute[]
292
-
293
- /**
294
- * @deprecated Use the `createRouter` function instead
295
- */
296
97
  constructor(
297
98
  options: RouterConstructorOptions<
298
99
  TRouteTree,
299
- TDehydrated,
300
- TSerializedError
100
+ TTrailingSlashOption,
101
+ TDefaultStructuralSharingOption,
102
+ TRouterHistory,
103
+ TDehydrated
301
104
  >,
302
105
  ) {
303
- this.update({
304
- defaultPreloadDelay: 50,
305
- defaultPendingMs: 1000,
306
- defaultPendingMinMs: 500,
307
- context: undefined!,
308
- ...options,
309
- stringifySearch: options?.stringifySearch ?? defaultStringifySearch,
310
- parseSearch: options?.parseSearch ?? defaultParseSearch,
311
- transformer: options?.transformer ?? JSON,
312
- })
313
-
314
- if (typeof document !== 'undefined') {
315
- ;(window as any).__TSR__ROUTER__ = this
316
- }
317
- }
318
-
319
- // These are default implementations that can optionally be overridden
320
- // by the router provider once rendered. We provide these so that the
321
- // router can be used in a non-react environment if necessary
322
- startReactTransition: (fn: () => void) => void = (fn) => fn()
323
-
324
- update = (
325
- newOptions: RouterConstructorOptions<
326
- TRouteTree,
327
- TDehydrated,
328
- TSerializedError
329
- >,
330
- ) => {
331
- if (newOptions.notFoundRoute) {
332
- console.warn(
333
- 'The notFoundRoute API is deprecated and will be removed in the next major version. See https://tanstack.com/router/v1/docs/guide/not-found-errors#migrating-from-notfoundroute for more info.',
334
- )
335
- }
336
-
337
- const previousOptions = this.options
338
- this.options = {
339
- ...this.options,
340
- ...newOptions,
341
- }
342
-
343
- if (
344
- !this.basepath ||
345
- (newOptions.basepath && newOptions.basepath !== previousOptions.basepath)
346
- ) {
347
- if (
348
- newOptions.basepath === undefined ||
349
- newOptions.basepath === '' ||
350
- newOptions.basepath === '/'
351
- ) {
352
- this.basepath = '/'
353
- } else {
354
- this.basepath = `/${trimPath(newOptions.basepath)}`
355
- }
356
- }
357
-
358
- if (
359
- !this.history ||
360
- (this.options.history && this.options.history !== this.history)
361
- ) {
362
- this.history =
363
- this.options.history ??
364
- (typeof document !== 'undefined'
365
- ? createBrowserHistory()
366
- : createMemoryHistory({
367
- initialEntries: [this.options.basepath || '/'],
368
- }))
369
- this.latestLocation = this.parseLocation()
370
- }
371
-
372
- if (this.options.routeTree !== this.routeTree) {
373
- this.routeTree = this.options.routeTree as TRouteTree
374
- this.buildRouteTree()
375
- }
376
-
377
- if (!this.__store) {
378
- this.__store = new Store(getInitialRouterState(this.latestLocation), {
379
- onUpdate: () => {
380
- this.__store.state = {
381
- ...this.state,
382
- status:
383
- this.state.isTransitioning || this.state.isLoading
384
- ? 'pending'
385
- : 'idle',
386
- cachedMatches: this.state.cachedMatches.filter(
387
- (d) => !['redirected'].includes(d.status),
388
- ),
389
- }
390
- },
391
- })
392
- }
393
- }
394
-
395
- get state() {
396
- return this.__store.state
397
- }
398
-
399
- buildRouteTree = () => {
400
- this.routesById = {} as RoutesById<TRouteTree>
401
- this.routesByPath = {} as RoutesByPath<TRouteTree>
402
-
403
- const notFoundRoute = this.options.notFoundRoute
404
- if (notFoundRoute) {
405
- notFoundRoute.init({ originalIndex: 99999999999 })
406
- ;(this.routesById as any)[notFoundRoute.id] = notFoundRoute
407
- }
408
-
409
- const recurseRoutes = (childRoutes: AnyRoute[]) => {
410
- childRoutes.forEach((childRoute, i) => {
411
- childRoute.init({ originalIndex: i })
412
-
413
- const existingRoute = (this.routesById as any)[childRoute.id]
414
-
415
- invariant(
416
- !existingRoute,
417
- `Duplicate routes found with id: ${String(childRoute.id)}`,
418
- )
419
- ;(this.routesById as any)[childRoute.id] = childRoute
420
-
421
- if (!childRoute.isRoot && childRoute.path) {
422
- const trimmedFullPath = trimPathRight(childRoute.fullPath)
423
- if (
424
- !(this.routesByPath as any)[trimmedFullPath] ||
425
- childRoute.fullPath.endsWith('/')
426
- ) {
427
- ;(this.routesByPath as any)[trimmedFullPath] = childRoute
428
- }
429
- }
430
-
431
- const children = childRoute.children as Route[]
432
-
433
- if (children?.length) {
434
- recurseRoutes(children)
435
- }
436
- })
437
- }
438
-
439
- recurseRoutes([this.routeTree])
440
-
441
- const scoredRoutes: {
442
- child: AnyRoute
443
- trimmed: string
444
- parsed: ReturnType<typeof parsePathname>
445
- index: number
446
- scores: number[]
447
- }[] = []
448
-
449
- ;(Object.values(this.routesById) as AnyRoute[]).forEach((d, i) => {
450
- if (d.isRoot || !d.path) {
451
- return
452
- }
453
-
454
- const trimmed = trimPathLeft(d.fullPath)
455
- const parsed = parsePathname(trimmed)
456
-
457
- while (parsed.length > 1 && parsed[0]?.value === '/') {
458
- parsed.shift()
459
- }
460
-
461
- const scores = parsed.map((d) => {
462
- if (d.value === '/') {
463
- return 0.75
464
- }
465
-
466
- if (d.type === 'param') {
467
- return 0.5
468
- }
469
-
470
- if (d.type === 'wildcard') {
471
- return 0.25
472
- }
473
-
474
- return 1
475
- })
476
-
477
- scoredRoutes.push({ child: d, trimmed, parsed, index: i, scores })
478
- })
479
-
480
- this.flatRoutes = scoredRoutes
481
- .sort((a, b) => {
482
- const minLength = Math.min(a.scores.length, b.scores.length)
483
-
484
- // Sort by min available score
485
- for (let i = 0; i < minLength; i++) {
486
- if (a.scores[i] !== b.scores[i]) {
487
- return b.scores[i]! - a.scores[i]!
488
- }
489
- }
490
-
491
- // Sort by length of score
492
- if (a.scores.length !== b.scores.length) {
493
- return b.scores.length - a.scores.length
494
- }
495
-
496
- // Sort by min available parsed value
497
- for (let i = 0; i < minLength; i++) {
498
- if (a.parsed[i]!.value !== b.parsed[i]!.value) {
499
- return a.parsed[i]!.value! > b.parsed[i]!.value! ? 1 : -1
500
- }
501
- }
502
-
503
- // Sort by original index
504
- return a.index - b.index
505
- })
506
- .map((d, i) => {
507
- d.child.rank = i
508
- return d.child
509
- })
510
- }
511
-
512
- subscribe = <TType extends keyof RouterEvents>(
513
- eventType: TType,
514
- fn: ListenerFn<RouterEvents[TType]>,
515
- ) => {
516
- const listener: RouterListener<any> = {
517
- eventType,
518
- fn,
519
- }
520
-
521
- this.subscribers.add(listener)
522
-
523
- return () => {
524
- this.subscribers.delete(listener)
525
- }
526
- }
527
-
528
- emit = (routerEvent: RouterEvent) => {
529
- this.subscribers.forEach((listener) => {
530
- if (listener.eventType === routerEvent.type) {
531
- listener.fn(routerEvent)
532
- }
533
- })
534
- }
535
-
536
- checkLatest = (promise: Promise<void>): undefined | Promise<void> => {
537
- return this.latestLoadPromise !== promise
538
- ? this.latestLoadPromise
539
- : undefined
540
- }
541
-
542
- parseLocation = (
543
- previousLocation?: ParsedLocation,
544
- ): ParsedLocation<FullSearchSchema<TRouteTree>> => {
545
- const parse = ({
546
- pathname,
547
- search,
548
- hash,
549
- state,
550
- }: HistoryLocation): ParsedLocation<FullSearchSchema<TRouteTree>> => {
551
- const parsedSearch = this.options.parseSearch(search)
552
- const searchStr = this.options.stringifySearch(parsedSearch)
553
-
554
- return {
555
- pathname: pathname,
556
- searchStr,
557
- search: replaceEqualDeep(previousLocation?.search, parsedSearch) as any,
558
- hash: hash.split('#').reverse()[0] ?? '',
559
- href: `${pathname}${searchStr}${hash}`,
560
- state: replaceEqualDeep(previousLocation?.state, state) as HistoryState,
561
- }
562
- }
563
-
564
- const location = parse(this.history.location)
565
-
566
- let { __tempLocation, __tempKey } = location.state
567
-
568
- if (__tempLocation && (!__tempKey || __tempKey === this.tempLocationKey)) {
569
- // Sync up the location keys
570
- const parsedTempLocation = parse(__tempLocation) as any
571
- parsedTempLocation.state.key = location.state.key
572
-
573
- delete parsedTempLocation.state.__tempLocation
574
-
575
- return {
576
- ...parsedTempLocation,
577
- maskedLocation: location,
578
- }
579
- }
580
-
581
- return location
582
- }
583
-
584
- resolvePathWithBase = (from: string, path: string) => {
585
- return resolvePath(this.basepath!, from, cleanPath(path))
586
- }
587
-
588
- get looseRoutesById() {
589
- return this.routesById as Record<string, AnyRoute>
590
- }
591
-
592
- matchRoutes = <TRouteTree extends AnyRoute>(
593
- pathname: string,
594
- locationSearch: AnySearchSchema,
595
- opts?: { preload?: boolean; throwOnError?: boolean; debug?: boolean },
596
- ): RouteMatch<TRouteTree>[] => {
597
- let routeParams: Record<string, string> = {}
598
-
599
- let foundRoute = this.flatRoutes.find((route) => {
600
- const matchedParams = matchPathname(
601
- this.basepath,
602
- trimPathRight(pathname),
603
- {
604
- to: route.fullPath,
605
- caseSensitive:
606
- route.options.caseSensitive ?? this.options.caseSensitive,
607
- fuzzy: true,
608
- },
609
- )
610
-
611
- if (matchedParams) {
612
- routeParams = matchedParams
613
- return true
614
- }
615
-
616
- return false
617
- })
618
-
619
- let routeCursor: AnyRoute =
620
- foundRoute || (this.routesById as any)[rootRouteId]
621
-
622
- let matchedRoutes: AnyRoute[] = [routeCursor]
623
-
624
- let isGlobalNotFound = false
625
-
626
- // Check to see if the route needs a 404 entry
627
- if (
628
- // If we found a route, and it's not an index route and we have left over path
629
- foundRoute
630
- ? foundRoute.path !== '/' && routeParams['**']
631
- : // Or if we didn't find a route and we have left over path
632
- trimPathRight(pathname)
633
- ) {
634
- // If the user has defined an (old) 404 route, use it
635
- if (this.options.notFoundRoute) {
636
- matchedRoutes.push(this.options.notFoundRoute)
637
- } else {
638
- // If there is no routes found during path matching
639
- isGlobalNotFound = true
640
- }
641
- }
642
-
643
- while (routeCursor?.parentRoute) {
644
- routeCursor = routeCursor.parentRoute
645
- if (routeCursor) matchedRoutes.unshift(routeCursor)
646
- }
647
-
648
- const globalNotFoundRouteId = (() => {
649
- if (!isGlobalNotFound) {
650
- return undefined
651
- }
652
-
653
- if (this.options.notFoundMode !== 'root') {
654
- for (let i = matchedRoutes.length - 1; i >= 0; i--) {
655
- const route = matchedRoutes[i]!
656
- if (route.children) {
657
- return route.id
658
- }
659
- }
660
- }
661
-
662
- return rootRouteId
663
- })()
664
-
665
- // Existing matches are matches that are already loaded along with
666
- // pending matches that are still loading
667
-
668
- const parseErrors = matchedRoutes.map((route) => {
669
- let parsedParamsError
670
-
671
- if (route.options.parseParams) {
672
- try {
673
- const parsedParams = route.options.parseParams(routeParams)
674
- // Add the parsed params to the accumulated params bag
675
- Object.assign(routeParams, parsedParams)
676
- } catch (err: any) {
677
- parsedParamsError = new PathParamError(err.message, {
678
- cause: err,
679
- })
680
-
681
- if (opts?.throwOnError) {
682
- throw parsedParamsError
683
- }
684
-
685
- return parsedParamsError
686
- }
687
- }
688
-
689
- return
690
- })
691
-
692
- const matches: AnyRouteMatch[] = []
693
-
694
- matchedRoutes.forEach((route, index) => {
695
- // Take each matched route and resolve + validate its search params
696
- // This has to happen serially because each route's search params
697
- // can depend on the parent route's search params
698
- // It must also happen before we create the match so that we can
699
- // pass the search params to the route's potential key function
700
- // which is used to uniquely identify the route match in state
701
-
702
- const parentMatch = matches[index - 1]
703
- const isLast = index === matchedRoutes.length - 1
704
-
705
- const [preMatchSearch, searchError]: [Record<string, any>, any] = (() => {
706
- // Validate the search params and stabilize them
707
- const parentSearch = parentMatch?.search ?? locationSearch
708
-
709
- try {
710
- const validator =
711
- typeof route.options.validateSearch === 'object'
712
- ? route.options.validateSearch.parse
713
- : route.options.validateSearch
714
-
715
- let search = validator?.(parentSearch) ?? {}
716
-
717
- return [
718
- {
719
- ...parentSearch,
720
- ...search,
721
- },
722
- undefined,
723
- ]
724
- } catch (err: any) {
725
- const searchError = new SearchParamError(err.message, {
726
- cause: err,
727
- })
728
-
729
- if (opts?.throwOnError) {
730
- throw searchError
731
- }
732
-
733
- return [parentSearch, searchError]
734
- }
735
- })()
736
-
737
- // This is where we need to call route.options.loaderDeps() to get any additional
738
- // deps that the route's loader function might need to run. We need to do this
739
- // before we create the match so that we can pass the deps to the route's
740
- // potential key function which is used to uniquely identify the route match in state
741
-
742
- const loaderDeps =
743
- route.options.loaderDeps?.({
744
- search: preMatchSearch,
745
- }) ?? ''
746
-
747
- const loaderDepsHash = loaderDeps ? JSON.stringify(loaderDeps) : ''
748
-
749
- const interpolatedPath = interpolatePath({
750
- path: route.fullPath,
751
- params: routeParams,
752
- })
753
-
754
- const matchId =
755
- interpolatePath({
756
- path: route.id,
757
- params: routeParams,
758
- leaveWildcards: true,
759
- }) + loaderDepsHash
760
-
761
- // Waste not, want not. If we already have a match for this route,
762
- // reuse it. This is important for layout routes, which might stick
763
- // around between navigation actions that only change leaf routes.
764
- let existingMatch = getRouteMatch(this.state, matchId)
765
-
766
- const cause = this.state.matches.find((d) => d.id === matchId)
767
- ? 'stay'
768
- : 'enter'
769
-
770
- const match: AnyRouteMatch = existingMatch
771
- ? {
772
- ...existingMatch,
773
- cause,
774
- params: routeParams,
775
- }
776
- : {
777
- id: matchId,
778
- routeId: route.id,
779
- params: routeParams,
780
- pathname: joinPaths([this.basepath, interpolatedPath]),
781
- updatedAt: Date.now(),
782
- search: {} as any,
783
- searchError: undefined,
784
- status: 'pending',
785
- showPending: false,
786
- isFetching: false,
787
- error: undefined,
788
- paramsError: parseErrors[index],
789
- loadPromise: Promise.resolve(),
790
- routeContext: undefined!,
791
- context: undefined!,
792
- abortController: new AbortController(),
793
- fetchCount: 0,
794
- cause,
795
- loaderDeps,
796
- invalid: false,
797
- preload: false,
798
- links: route.options.links?.(),
799
- scripts: route.options.scripts?.(),
800
- staticData: route.options.staticData || {},
801
- }
802
-
803
- if (!opts?.preload) {
804
- // If we have a global not found, mark the right match as global not found
805
- match.globalNotFound = globalNotFoundRouteId === route.id
806
- }
807
-
808
- // Regardless of whether we're reusing an existing match or creating
809
- // a new one, we need to update the match's search params
810
- match.search = replaceEqualDeep(match.search, preMatchSearch)
811
- // And also update the searchError if there is one
812
- match.searchError = searchError
813
-
814
- matches.push(match)
815
- })
816
-
817
- return matches as any
818
- }
819
-
820
- cancelMatch = (id: string) => {}
821
-
822
- cancelMatches = () => {
823
- this.state.pendingMatches?.forEach((match) => {
824
- this.cancelMatch(match.id)
825
- })
826
- }
827
-
828
- buildLocation: BuildLocationFn<TRouteTree> = (opts) => {
829
- const build = (
830
- dest: BuildNextOptions & {
831
- unmaskOnReload?: boolean
832
- } = {},
833
- matches?: AnyRouteMatch[],
834
- ): ParsedLocation => {
835
- // if (dest.href) {
836
- // return {
837
- // pathname: dest.href,
838
- // search: {},
839
- // searchStr: '',
840
- // state: {},
841
- // hash: '',
842
- // href: dest.href,
843
- // unmaskOnReload: dest.unmaskOnReload,
844
- // }
845
- // }
846
-
847
- const relevantMatches = this.state.pendingMatches || this.state.matches
848
- const fromSearch =
849
- relevantMatches[relevantMatches.length - 1]?.search ||
850
- this.latestLocation.search
851
-
852
- const fromMatches = this.matchRoutes(
853
- this.latestLocation.pathname,
854
- fromSearch,
855
- )
856
- const stayingMatches = matches?.filter((d) =>
857
- fromMatches?.find((e) => e.routeId === d.routeId),
858
- )
859
-
860
- const fromRoute = this.looseRoutesById[last(fromMatches)?.routeId]
861
-
862
- let pathname = dest.to
863
- ? this.resolvePathWithBase(
864
- dest.from ?? this.latestLocation.pathname,
865
- `${dest.to}`,
866
- )
867
- : this.resolvePathWithBase(fromRoute?.fullPath, fromRoute?.fullPath)
868
-
869
- const prevParams = { ...last(fromMatches)?.params }
870
-
871
- let nextParams =
872
- (dest.params ?? true) === true
873
- ? prevParams
874
- : { ...prevParams, ...functionalUpdate(dest.params!, prevParams) }
875
-
876
- if (Object.keys(nextParams).length > 0) {
877
- matches
878
- ?.map((d) => this.looseRoutesById[d.routeId]!.options.stringifyParams)
879
- .filter(Boolean)
880
- .forEach((fn) => {
881
- nextParams = { ...nextParams!, ...fn!(nextParams!) }
882
- })
883
- }
884
-
885
- pathname = interpolatePath({
886
- path: pathname,
887
- params: nextParams ?? {},
888
- leaveWildcards: false,
889
- leaveParams: opts.leaveParams,
890
- })
891
-
892
- const preSearchFilters =
893
- stayingMatches
894
- ?.map(
895
- (match) =>
896
- this.looseRoutesById[match.routeId]!.options.preSearchFilters ??
897
- [],
898
- )
899
- .flat()
900
- .filter(Boolean) ?? []
901
-
902
- const postSearchFilters =
903
- stayingMatches
904
- ?.map(
905
- (match) =>
906
- this.looseRoutesById[match.routeId]!.options.postSearchFilters ??
907
- [],
908
- )
909
- .flat()
910
- .filter(Boolean) ?? []
911
-
912
- // Pre filters first
913
- const preFilteredSearch = preSearchFilters?.length
914
- ? preSearchFilters?.reduce(
915
- (prev, next) => next(prev) as any,
916
- fromSearch,
917
- )
918
- : fromSearch
919
-
920
- // Then the link/navigate function
921
- const destSearch =
922
- dest.search === true
923
- ? preFilteredSearch // Preserve resolvedFrom true
924
- : dest.search
925
- ? functionalUpdate(dest.search, preFilteredSearch) ?? {} // Updater
926
- : preSearchFilters?.length
927
- ? preFilteredSearch // Preserve resolvedFrom filters
928
- : {}
929
-
930
- // Then post filters
931
- const postFilteredSearch = postSearchFilters?.length
932
- ? postSearchFilters.reduce((prev, next) => next(prev), destSearch)
933
- : destSearch
934
-
935
- const search = replaceEqualDeep(fromSearch, postFilteredSearch)
936
-
937
- const searchStr = this.options.stringifySearch(search)
938
-
939
- const hash =
940
- dest.hash === true
941
- ? this.latestLocation.hash
942
- : dest.hash
943
- ? functionalUpdate(dest.hash!, this.latestLocation.hash)
944
- : undefined
945
-
946
- const hashStr = hash ? `#${hash}` : ''
947
-
948
- let nextState =
949
- dest.state === true
950
- ? this.latestLocation.state
951
- : dest.state
952
- ? functionalUpdate(dest.state, this.latestLocation.state)
953
- : {}
954
-
955
- nextState = replaceEqualDeep(this.latestLocation.state, nextState)
956
-
957
- return {
958
- pathname,
959
- search,
960
- searchStr,
961
- state: nextState as any,
962
- hash: hash ?? '',
963
- href: `${pathname}${searchStr}${hashStr}`,
964
- unmaskOnReload: dest.unmaskOnReload,
965
- }
966
- }
967
-
968
- const buildWithMatches = (
969
- dest: BuildNextOptions = {},
970
- maskedDest?: BuildNextOptions,
971
- ) => {
972
- let next = build(dest)
973
- let maskedNext = maskedDest ? build(maskedDest) : undefined
974
-
975
- if (!maskedNext) {
976
- let params = {}
977
-
978
- let foundMask = this.options.routeMasks?.find((d) => {
979
- const match = matchPathname(this.basepath, next.pathname, {
980
- to: d.from,
981
- caseSensitive: false,
982
- fuzzy: false,
983
- })
984
-
985
- if (match) {
986
- params = match
987
- return true
988
- }
989
-
990
- return false
991
- })
992
-
993
- if (foundMask) {
994
- maskedDest = {
995
- ...pick(opts, ['from']),
996
- ...foundMask,
997
- params,
998
- }
999
- maskedNext = build(maskedDest)
1000
- }
1001
- }
1002
-
1003
- const nextMatches = this.matchRoutes(next.pathname, next.search)
1004
- const maskedMatches = maskedNext
1005
- ? this.matchRoutes(maskedNext.pathname, maskedNext.search)
1006
- : undefined
1007
- const maskedFinal = maskedNext
1008
- ? build(maskedDest, maskedMatches)
1009
- : undefined
1010
-
1011
- const final = build(dest, nextMatches)
1012
-
1013
- if (maskedFinal) {
1014
- final.maskedLocation = maskedFinal
1015
- }
1016
-
1017
- return final
1018
- }
1019
-
1020
- if (opts.mask) {
1021
- return buildWithMatches(opts, {
1022
- ...pick(opts, ['from']),
1023
- ...opts.mask,
1024
- })
1025
- }
1026
-
1027
- return buildWithMatches(opts)
1028
- }
1029
-
1030
- commitLocation = async ({
1031
- startTransition,
1032
- ...next
1033
- }: ParsedLocation & CommitLocationOptions) => {
1034
- if (this.navigateTimeout) clearTimeout(this.navigateTimeout)
1035
-
1036
- const isSameUrl = this.latestLocation.href === next.href
1037
-
1038
- // If the next urls are the same and we're not replacing,
1039
- // do nothing
1040
- if (!isSameUrl) {
1041
- let { maskedLocation, ...nextHistory } = next
1042
-
1043
- if (maskedLocation) {
1044
- nextHistory = {
1045
- ...maskedLocation,
1046
- state: {
1047
- ...maskedLocation.state,
1048
- __tempKey: undefined,
1049
- __tempLocation: {
1050
- ...nextHistory,
1051
- search: nextHistory.searchStr,
1052
- state: {
1053
- ...nextHistory.state,
1054
- __tempKey: undefined!,
1055
- __tempLocation: undefined!,
1056
- key: undefined!,
1057
- },
1058
- },
1059
- },
1060
- }
1061
-
1062
- if (
1063
- nextHistory.unmaskOnReload ??
1064
- this.options.unmaskOnReload ??
1065
- false
1066
- ) {
1067
- nextHistory.state.__tempKey = this.tempLocationKey
1068
- }
1069
- }
1070
-
1071
- const apply = () => {
1072
- this.history[next.replace ? 'replace' : 'push'](
1073
- nextHistory.href,
1074
- nextHistory.state,
1075
- )
1076
- }
1077
-
1078
- if (startTransition ?? true) {
1079
- this.startReactTransition(apply)
1080
- } else {
1081
- apply()
1082
- }
1083
- }
1084
-
1085
- this.resetNextScroll = next.resetScroll ?? true
1086
-
1087
- return this.latestLoadPromise
1088
- }
1089
-
1090
- buildAndCommitLocation = ({
1091
- replace,
1092
- resetScroll,
1093
- startTransition,
1094
- ...rest
1095
- }: BuildNextOptions & CommitLocationOptions = {}) => {
1096
- const location = this.buildLocation(rest as any)
1097
- return this.commitLocation({
1098
- ...location,
1099
- startTransition,
1100
- replace,
1101
- resetScroll,
1102
- })
1103
- }
1104
-
1105
- navigate: NavigateFn = ({ from, to, ...rest }) => {
1106
- // If this link simply reloads the current route,
1107
- // make sure it has a new key so it will trigger a data refresh
1108
-
1109
- // If this `to` is a valid external URL, return
1110
- // null for LinkUtils
1111
- const toString = String(to)
1112
- // const fromString = from !== undefined ? String(from) : from
1113
- let isExternal
1114
-
1115
- try {
1116
- new URL(`${toString}`)
1117
- isExternal = true
1118
- } catch (e) {}
1119
-
1120
- invariant(
1121
- !isExternal,
1122
- 'Attempting to navigate to external url with this.navigate!',
1123
- )
1124
-
1125
- return this.buildAndCommitLocation({
1126
- ...rest,
1127
- from,
1128
- to,
1129
- // to: toString,
1130
- })
1131
- }
1132
-
1133
- loadMatches = async ({
1134
- checkLatest,
1135
- location,
1136
- matches,
1137
- preload,
1138
- }: {
1139
- checkLatest: () => Promise<void> | undefined
1140
- location: ParsedLocation
1141
- matches: AnyRouteMatch[]
1142
- preload?: boolean
1143
- }): Promise<RouteMatch[]> => {
1144
- let latestPromise
1145
- let firstBadMatchIndex: number | undefined
1146
-
1147
- const updateMatch = (match: AnyRouteMatch, opts?: { remove?: boolean }) => {
1148
- const isPending = this.state.pendingMatches?.find(
1149
- (d) => d.id === match.id,
1150
- )
1151
-
1152
- const isMatched = this.state.matches.find((d) => d.id === match.id)
1153
-
1154
- const matchesKey = isPending
1155
- ? 'pendingMatches'
1156
- : isMatched
1157
- ? 'matches'
1158
- : 'cachedMatches'
1159
-
1160
- this.__store.setState((s) => ({
1161
- ...s,
1162
- [matchesKey]: opts?.remove
1163
- ? s[matchesKey]?.filter((d) => d.id !== match.id)
1164
- : s[matchesKey]?.map((d) => (d.id === match.id ? match : d)),
1165
- }))
1166
- }
1167
-
1168
- const handleMatchSpecialError = (match: AnyRouteMatch, err: any) => {
1169
- match = {
1170
- ...match,
1171
- status: isRedirect(err)
1172
- ? 'redirected'
1173
- : isNotFound(err)
1174
- ? 'notFound'
1175
- : 'error',
1176
- isFetching: false,
1177
- error: err,
1178
- }
1179
-
1180
- updateMatch(match)
1181
-
1182
- if (!err.routeId) {
1183
- err.routeId = match.routeId
1184
- }
1185
-
1186
- throw err
1187
- }
1188
-
1189
- // Check each match middleware to see if the route can be accessed
1190
- for (let [index, match] of matches.entries()) {
1191
- const parentMatch = matches[index - 1]
1192
- const route = this.looseRoutesById[match.routeId]!
1193
- const abortController = new AbortController()
1194
-
1195
- const handleSerialError = (err: any, code: string) => {
1196
- err.routerCode = code
1197
- firstBadMatchIndex = firstBadMatchIndex ?? index
1198
-
1199
- if (isRedirect(err) || isNotFound(err)) {
1200
- handleMatchSpecialError(match, err)
1201
- }
1202
-
1203
- try {
1204
- route.options.onError?.(err)
1205
- } catch (errorHandlerErr) {
1206
- err = errorHandlerErr
1207
-
1208
- if (isRedirect(err) || isNotFound(err)) {
1209
- handleMatchSpecialError(match, errorHandlerErr)
1210
- }
1211
- }
1212
-
1213
- matches[index] = match = {
1214
- ...match,
1215
- error: err,
1216
- status: 'error',
1217
- updatedAt: Date.now(),
1218
- abortController: new AbortController(),
1219
- }
1220
- }
1221
-
1222
- if (match.paramsError) {
1223
- handleSerialError(match.paramsError, 'PARSE_PARAMS')
1224
- }
1225
-
1226
- if (match.searchError) {
1227
- handleSerialError(match.searchError, 'VALIDATE_SEARCH')
1228
- }
1229
-
1230
- // if (match.globalNotFound && !preload) {
1231
- // handleSerialError(notFound({ _global: true }), 'NOT_FOUND')
1232
- // }
1233
-
1234
- try {
1235
- const parentContext = parentMatch?.context ?? this.options.context ?? {}
1236
-
1237
- const pendingMs =
1238
- route.options.pendingMs ?? this.options.defaultPendingMs
1239
- const pendingPromise =
1240
- typeof pendingMs === 'number' && pendingMs <= 0
1241
- ? Promise.resolve()
1242
- : new Promise<void>((r) => setTimeout(r, pendingMs))
1243
-
1244
- const beforeLoadContext =
1245
- (await route.options.beforeLoad?.({
1246
- search: match.search,
1247
- abortController,
1248
- params: match.params,
1249
- preload: !!preload,
1250
- context: parentContext,
1251
- location,
1252
- navigate: (opts) =>
1253
- this.navigate({ ...opts, from: match.pathname } as any),
1254
- buildLocation: this.buildLocation,
1255
- cause: preload ? 'preload' : match.cause,
1256
- })) ?? ({} as any)
1257
-
1258
- if (isRedirect(beforeLoadContext) || isNotFound(beforeLoadContext)) {
1259
- handleSerialError(beforeLoadContext, 'BEFORE_LOAD')
1260
- }
1261
-
1262
- const context = {
1263
- ...parentContext,
1264
- ...beforeLoadContext,
1265
- }
1266
-
1267
- matches[index] = match = {
1268
- ...match,
1269
- routeContext: replaceEqualDeep(match.routeContext, beforeLoadContext),
1270
- context: replaceEqualDeep(match.context, context),
1271
- abortController,
1272
- pendingPromise,
1273
- }
1274
- } catch (err) {
1275
- handleSerialError(err, 'BEFORE_LOAD')
1276
- break
1277
- }
1278
- }
1279
-
1280
- const validResolvedMatches = matches.slice(0, firstBadMatchIndex)
1281
- const matchPromises: Promise<any>[] = []
1282
-
1283
- validResolvedMatches.forEach((match, index) => {
1284
- matchPromises.push(
1285
- new Promise<void>(async (resolve, reject) => {
1286
- const parentMatchPromise = matchPromises[index - 1]
1287
- const route = this.looseRoutesById[match.routeId]!
1288
-
1289
- const handleError = (err: any) => {
1290
- if (isRedirect(err) || isNotFound(err)) {
1291
- handleMatchSpecialError(match, err)
1292
- }
1293
- }
1294
-
1295
- let loadPromise: Promise<void> | undefined
1296
-
1297
- matches[index] = match = {
1298
- ...match,
1299
- showPending: false,
1300
- }
1301
-
1302
- let didShowPending = false
1303
- const pendingMs =
1304
- route.options.pendingMs ?? this.options.defaultPendingMs
1305
- const pendingMinMs =
1306
- route.options.pendingMinMs ?? this.options.defaultPendingMinMs
1307
-
1308
- const loaderContext: LoaderFnContext = {
1309
- params: match.params,
1310
- deps: match.loaderDeps,
1311
- preload: !!preload,
1312
- parentMatchPromise,
1313
- abortController: match.abortController,
1314
- context: match.context,
1315
- location,
1316
- navigate: (opts) =>
1317
- this.navigate({ ...opts, from: match.pathname } as any),
1318
- cause: preload ? 'preload' : match.cause,
1319
- route,
1320
- }
1321
-
1322
- const fetch = async () => {
1323
- try {
1324
- if (match.isFetching) {
1325
- loadPromise = getRouteMatch(this.state, match.id)?.loadPromise
1326
- } else {
1327
- // If the user doesn't want the route to reload, just
1328
- // resolve with the existing loader data
1329
-
1330
- // if (match.fetchCount && match.status === 'success') {
1331
- // resolve()
1332
- // }
1333
-
1334
- // Otherwise, load the route
1335
- matches[index] = match = {
1336
- ...match,
1337
- isFetching: true,
1338
- fetchCount: match.fetchCount + 1,
1339
- }
1340
-
1341
- const lazyPromise =
1342
- route.lazyFn?.().then((lazyRoute) => {
1343
- Object.assign(route.options, lazyRoute.options)
1344
- }) || Promise.resolve()
1345
-
1346
- // If for some reason lazy resolves more lazy components...
1347
- // We'll wait for that before pre attempt to preload any
1348
- // components themselves.
1349
- const componentsPromise = lazyPromise.then(() =>
1350
- Promise.all(
1351
- componentTypes.map(async (type) => {
1352
- const component = route.options[type]
1353
-
1354
- if ((component as any)?.preload) {
1355
- await (component as any).preload()
1356
- }
1357
- }),
1358
- ),
1359
- )
1360
-
1361
- // Kick off the loader!
1362
- const loaderPromise = route.options.loader?.(loaderContext)
1363
-
1364
- loadPromise = Promise.all([
1365
- componentsPromise,
1366
- loaderPromise,
1367
- lazyPromise,
1368
- ]).then((d) => d[1])
1369
- }
1370
-
1371
- matches[index] = match = {
1372
- ...match,
1373
- loadPromise,
1374
- }
1375
-
1376
- updateMatch(match)
1377
-
1378
- const loaderData = await loadPromise
1379
- if ((latestPromise = checkLatest())) return await latestPromise
1380
-
1381
- handleError(loaderData)
1382
-
1383
- if (didShowPending && pendingMinMs) {
1384
- await new Promise((r) => setTimeout(r, pendingMinMs))
1385
- }
1386
-
1387
- if ((latestPromise = checkLatest())) return await latestPromise
1388
-
1389
- const [meta, headers] = await Promise.all([
1390
- route.options.meta?.({
1391
- params: match.params,
1392
- loaderData,
1393
- }),
1394
- route.options.headers?.({
1395
- loaderData,
1396
- }),
1397
- ])
1398
-
1399
- matches[index] = match = {
1400
- ...match,
1401
- error: undefined,
1402
- status: 'success',
1403
- isFetching: false,
1404
- updatedAt: Date.now(),
1405
- loaderData,
1406
- loadPromise: undefined,
1407
- meta,
1408
- headers,
1409
- }
1410
- } catch (error) {
1411
- if ((latestPromise = checkLatest())) return await latestPromise
1412
-
1413
- handleError(error)
1414
-
1415
- try {
1416
- route.options.onError?.(error)
1417
- } catch (onErrorError) {
1418
- error = onErrorError
1419
- handleError(onErrorError)
1420
- }
1421
-
1422
- matches[index] = match = {
1423
- ...match,
1424
- error,
1425
- status: 'error',
1426
- isFetching: false,
1427
- }
1428
- }
1429
-
1430
- updateMatch(match)
1431
- }
1432
-
1433
- // This is where all of the stale-while-revalidate magic happens
1434
- const age = Date.now() - match.updatedAt
1435
-
1436
- let staleAge = preload
1437
- ? route.options.preloadStaleTime ??
1438
- this.options.defaultPreloadStaleTime ??
1439
- 30_000 // 30 seconds for preloads by default
1440
- : route.options.staleTime ?? this.options.defaultStaleTime ?? 0
1441
-
1442
- // Default to reloading the route all the time
1443
- let shouldReload
1444
-
1445
- const shouldReloadOption = route.options.shouldReload
1446
-
1447
- // Allow shouldReload to get the last say,
1448
- // if provided.
1449
- shouldReload =
1450
- typeof shouldReloadOption === 'function'
1451
- ? shouldReloadOption(loaderContext)
1452
- : shouldReloadOption
1453
-
1454
- matches[index] = match = {
1455
- ...match,
1456
- preload:
1457
- !!preload && !this.state.matches.find((d) => d.id === match.id),
1458
- }
1459
-
1460
- // If the route is successful and still fresh, just resolve
1461
- if (
1462
- match.status === 'success' &&
1463
- (match.invalid || (shouldReload ?? age > staleAge))
1464
- ) {
1465
- ;(async () => {
1466
- try {
1467
- await fetch()
1468
- } catch (err) {
1469
- console.info('Background Fetching Error', err)
1470
-
1471
- if (isRedirect(err)) {
1472
- const isActive = (
1473
- this.state.pendingMatches || this.state.matches
1474
- ).find((d) => d.id === match.id)
1475
-
1476
- // Redirects should not be persisted
1477
- handleError(err)
1478
-
1479
- // If the route is still active, redirect
1480
- // TODO: Do we really need this?
1481
- invariant(
1482
- false,
1483
- 'You need to redirect from a background fetch? This is not supported yet. File an issue.',
1484
- )
1485
- // if (isActive) {
1486
- // this.handleRedirect(err)
1487
- // }
1488
- }
1489
- }
1490
- })()
1491
-
1492
- return resolve()
1493
- }
1494
-
1495
- const shouldPending =
1496
- !preload &&
1497
- typeof pendingMs === 'number' &&
1498
- (route.options.pendingComponent ??
1499
- this.options.defaultPendingComponent)
1500
-
1501
- if (match.status !== 'success') {
1502
- try {
1503
- if (shouldPending) {
1504
- match.pendingPromise?.then(async () => {
1505
- if ((latestPromise = checkLatest())) return latestPromise
1506
-
1507
- didShowPending = true
1508
- matches[index] = match = {
1509
- ...match,
1510
- showPending: true,
1511
- }
1512
-
1513
- updateMatch(match)
1514
- resolve()
1515
- })
1516
- }
1517
-
1518
- await fetch()
1519
- } catch (err) {
1520
- reject(err)
1521
- }
1522
- }
1523
-
1524
- resolve()
1525
- }),
1526
- )
1527
- })
1528
-
1529
- await Promise.all(matchPromises)
1530
-
1531
- return matches
1532
- }
1533
-
1534
- invalidate = () => {
1535
- const invalidate = (d: any) => ({
1536
- ...d,
1537
- invalid: true,
1538
- })
1539
-
1540
- this.__store.setState((s) => ({
1541
- ...s,
1542
- matches: s.matches.map(invalidate),
1543
- cachedMatches: s.cachedMatches.map(invalidate),
1544
- pendingMatches: s.pendingMatches?.map(invalidate),
1545
- }))
1546
-
1547
- this.load()
1548
- }
1549
-
1550
- load = async (): Promise<void> => {
1551
- const promise = new Promise<void>(async (resolve, reject) => {
1552
- const next = this.latestLocation
1553
- const prevLocation = this.state.resolvedLocation
1554
- const pathDidChange = prevLocation!.href !== next.href
1555
- let latestPromise: Promise<void> | undefined | null
1556
-
1557
- // Cancel any pending matches
1558
- this.cancelMatches()
1559
-
1560
- this.emit({
1561
- type: 'onBeforeLoad',
1562
- fromLocation: prevLocation,
1563
- toLocation: next,
1564
- pathChanged: pathDidChange,
1565
- })
1566
-
1567
- let pendingMatches!: RouteMatch<any, any>[]
1568
- const previousMatches = this.state.matches
1569
-
1570
- this.__store.batch(() => {
1571
- this.cleanCache()
1572
-
1573
- // Match the routes
1574
- pendingMatches = this.matchRoutes(next.pathname, next.search, {
1575
- debug: true,
1576
- })
1577
-
1578
- // Ingest the new matches
1579
- // If a cached moved to pendingMatches, remove it from cachedMatches
1580
- this.__store.setState((s) => ({
1581
- ...s,
1582
- isLoading: true,
1583
- location: next,
1584
- pendingMatches,
1585
- cachedMatches: s.cachedMatches.filter((d) => {
1586
- return !pendingMatches.find((e) => e.id === d.id)
1587
- }),
1588
- }))
1589
- })
1590
-
1591
- try {
1592
- let redirect: ResolvedRedirect
1593
- let notFound: NotFoundError
1594
-
1595
- try {
1596
- // Load the matches
1597
- await this.loadMatches({
1598
- matches: pendingMatches,
1599
- location: next,
1600
- checkLatest: () => this.checkLatest(promise),
1601
- })
1602
- } catch (err) {
1603
- if (isRedirect(err)) {
1604
- redirect = this.resolveRedirect(err)
1605
-
1606
- if (!isServer) {
1607
- this.navigate({ ...(redirect as any), replace: true })
1608
- }
1609
- } else if (isNotFound(err)) {
1610
- notFound = err
1611
- this.handleNotFound(pendingMatches, err)
1612
- }
1613
-
1614
- // Swallow all other errors that happen inside
1615
- // of loadMatches. These errors will be handled
1616
- // as state on each match.
1617
- }
1618
-
1619
- // Only apply the latest transition
1620
- if ((latestPromise = this.checkLatest(promise))) {
1621
- return latestPromise
1622
- }
1623
-
1624
- const exitingMatches = previousMatches.filter(
1625
- (match) => !pendingMatches.find((d) => d.id === match.id),
1626
- )
1627
- const enteringMatches = pendingMatches.filter(
1628
- (match) => !previousMatches.find((d) => d.id === match.id),
1629
- )
1630
- const stayingMatches = previousMatches.filter((match) =>
1631
- pendingMatches.find((d) => d.id === match.id),
1632
- )
1633
-
1634
- // Commit the pending matches. If a previous match was
1635
- // removed, place it in the cachedMatches
1636
- this.__store.batch(() => {
1637
- this.__store.setState((s) => ({
1638
- ...s,
1639
- isLoading: false,
1640
- matches: s.pendingMatches!,
1641
- pendingMatches: undefined,
1642
- cachedMatches: [
1643
- ...s.cachedMatches,
1644
- ...exitingMatches.filter((d) => d.status !== 'error'),
1645
- ],
1646
- statusCode:
1647
- redirect?.statusCode || notFound
1648
- ? 404
1649
- : s.matches.some((d) => d.status === 'error')
1650
- ? 500
1651
- : 200,
1652
- redirect,
1653
- }))
1654
- this.cleanCache()
1655
- })
1656
-
1657
- //
1658
- ;(
1659
- [
1660
- [exitingMatches, 'onLeave'],
1661
- [enteringMatches, 'onEnter'],
1662
- [stayingMatches, 'onStay'],
1663
- ] as const
1664
- ).forEach(([matches, hook]) => {
1665
- matches.forEach((match) => {
1666
- this.looseRoutesById[match.routeId]!.options[hook]?.(match)
1667
- })
1668
- })
1669
-
1670
- this.emit({
1671
- type: 'onLoad',
1672
- fromLocation: prevLocation,
1673
- toLocation: next,
1674
- pathChanged: pathDidChange,
1675
- })
1676
-
1677
- resolve()
1678
- } catch (err) {
1679
- // Only apply the latest transition
1680
- if ((latestPromise = this.checkLatest(promise))) {
1681
- return latestPromise
1682
- }
1683
-
1684
- console.log('Load Error', err)
1685
-
1686
- reject(err)
1687
- }
1688
- })
1689
-
1690
- this.latestLoadPromise = promise
1691
-
1692
- return this.latestLoadPromise
1693
- }
1694
-
1695
- resolveRedirect = (err: AnyRedirect): ResolvedRedirect => {
1696
- let redirect = err as ResolvedRedirect
1697
-
1698
- if (!redirect.href) {
1699
- redirect.href = this.buildLocation(redirect as any).href
1700
- }
1701
-
1702
- return redirect
1703
- }
1704
-
1705
- cleanCache = () => {
1706
- // This is where all of the garbage collection magic happens
1707
- this.__store.setState((s) => {
1708
- return {
1709
- ...s,
1710
- cachedMatches: s.cachedMatches.filter((d) => {
1711
- const route = this.looseRoutesById[d.routeId]!
1712
-
1713
- if (!route.options.loader) {
1714
- return false
1715
- }
1716
-
1717
- // If the route was preloaded, use the preloadGcTime
1718
- // otherwise, use the gcTime
1719
- const gcTime =
1720
- (d.preload
1721
- ? route.options.preloadGcTime ?? this.options.defaultPreloadGcTime
1722
- : route.options.gcTime ?? this.options.defaultGcTime) ??
1723
- 5 * 60 * 1000
1724
-
1725
- return d.status !== 'error' && Date.now() - d.updatedAt < gcTime
1726
- }),
1727
- }
1728
- })
1729
- }
1730
-
1731
- preloadRoute = async <
1732
- TFrom extends RoutePaths<TRouteTree> | string = string,
1733
- TTo extends string = '',
1734
- TMaskFrom extends RoutePaths<TRouteTree> | string = TFrom,
1735
- TMaskTo extends string = '',
1736
- >(
1737
- opts: NavigateOptions<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo>,
1738
- ): Promise<AnyRouteMatch[] | undefined> => {
1739
- let next = this.buildLocation(opts as any)
1740
-
1741
- let matches = this.matchRoutes(next.pathname, next.search, {
1742
- throwOnError: true,
1743
- preload: true,
1744
- })
1745
-
1746
- const loadedMatchIds = Object.fromEntries(
1747
- [
1748
- ...this.state.matches,
1749
- ...(this.state.pendingMatches ?? []),
1750
- ...this.state.cachedMatches,
1751
- ]?.map((d) => [d.id, true]),
1752
- )
1753
-
1754
- this.__store.batch(() => {
1755
- matches.forEach((match) => {
1756
- if (!loadedMatchIds[match.id]) {
1757
- this.__store.setState((s) => ({
1758
- ...s,
1759
- cachedMatches: [...(s.cachedMatches as any), match],
1760
- }))
1761
- }
1762
- })
1763
- })
1764
-
1765
- try {
1766
- matches = await this.loadMatches({
1767
- matches,
1768
- location: next,
1769
- preload: true,
1770
- checkLatest: () => undefined,
1771
- })
1772
-
1773
- return matches
1774
- } catch (err) {
1775
- if (isRedirect(err)) {
1776
- return await this.preloadRoute(err as any)
1777
- }
1778
- // Preload errors are not fatal, but we should still log them
1779
- console.error(err)
1780
- return undefined
1781
- }
1782
- }
1783
-
1784
- matchRoute = <
1785
- TFrom extends RoutePaths<TRouteTree> = '/',
1786
- TTo extends string = '',
1787
- TResolved = ResolveRelativePath<TFrom, NoInfer<TTo>>,
1788
- >(
1789
- location: ToOptions<TRouteTree, TFrom, TTo>,
1790
- opts?: MatchRouteOptions,
1791
- ): false | RouteById<TRouteTree, TResolved>['types']['allParams'] => {
1792
- const matchLocation = {
1793
- ...location,
1794
- to: location.to
1795
- ? this.resolvePathWithBase((location.from || '') as string, location.to)
1796
- : undefined,
1797
- params: location.params || {},
1798
- leaveParams: true,
1799
- }
1800
- const next = this.buildLocation(matchLocation as any)
1801
-
1802
- if (opts?.pending && this.state.status !== 'pending') {
1803
- return false
1804
- }
1805
-
1806
- const baseLocation = opts?.pending
1807
- ? this.latestLocation
1808
- : this.state.resolvedLocation
1809
-
1810
- if (!baseLocation) {
1811
- return false
1812
- }
1813
- const match = matchPathname(this.basepath, baseLocation.pathname, {
1814
- ...opts,
1815
- to: next.pathname,
1816
- }) as any
1817
-
1818
- if (!match) {
1819
- return false
1820
- }
1821
- if (location.params) {
1822
- if (!deepEqual(match, location.params, true)) {
1823
- return false
1824
- }
1825
- }
1826
-
1827
- if (match && (opts?.includeSearch ?? true)) {
1828
- return deepEqual(baseLocation.search, next.search, true) ? match : false
1829
- }
1830
-
1831
- return match
1832
- }
1833
-
1834
- injectHtml = async (html: string | (() => Promise<string> | string)) => {
1835
- this.injectedHtml.push(html)
1836
- }
1837
-
1838
- // We use a token -> weak map to keep track of deferred promises
1839
- // that are registered on the server and need to be resolved
1840
- registeredDeferredsIds = new Map<string, {}>()
1841
- registeredDeferreds = new WeakMap<{}, DeferredPromiseState<any>>()
1842
-
1843
- getDeferred = (uid: string) => {
1844
- const token = this.registeredDeferredsIds.get(uid)
1845
-
1846
- if (!token) {
1847
- return undefined
1848
- }
1849
-
1850
- return this.registeredDeferreds.get(token)
1851
- }
1852
-
1853
- /**
1854
- * @deprecated Please inject your own html using the `injectHtml` method
1855
- */
1856
- dehydrateData = <T>(key: any, getData: T | (() => Promise<T> | T)) => {
1857
- warning(
1858
- false,
1859
- `The dehydrateData method is deprecated. Please use the injectHtml method to inject your own data.`,
1860
- )
1861
-
1862
- if (typeof document === 'undefined') {
1863
- const strKey = typeof key === 'string' ? key : JSON.stringify(key)
1864
-
1865
- this.injectHtml(async () => {
1866
- const id = `__TSR_DEHYDRATED__${strKey}`
1867
- const data =
1868
- typeof getData === 'function' ? await (getData as any)() : getData
1869
- return `<script id='${id}' suppressHydrationWarning>
1870
- window["__TSR_DEHYDRATED__${escapeJSON(
1871
- strKey,
1872
- )}"] = ${JSON.stringify(this.options.transformer.stringify(data))}
1873
- </script>`
1874
- })
1875
-
1876
- return () => this.hydrateData<T>(key)
1877
- }
1878
-
1879
- return () => undefined
1880
- }
1881
-
1882
- /**
1883
- * @deprecated Please extract your own data from scripts injected using the `injectHtml` method
1884
- */
1885
- hydrateData = <T extends any = unknown>(key: any) => {
1886
- warning(
1887
- false,
1888
- `The hydrateData method is deprecated. Please use the extractHtml method to extract your own data.`,
1889
- )
1890
-
1891
- if (typeof document !== 'undefined') {
1892
- const strKey = typeof key === 'string' ? key : JSON.stringify(key)
1893
-
1894
- return this.options.transformer.parse(
1895
- window[`__TSR_DEHYDRATED__${strKey}` as any] as unknown as string,
1896
- ) as T
1897
- }
1898
-
1899
- return undefined
1900
- }
1901
-
1902
- dehydrate = (): DehydratedRouter => {
1903
- const pickError =
1904
- this.options.errorSerializer?.serialize ?? defaultSerializeError
1905
-
1906
- return {
1907
- state: {
1908
- dehydratedMatches: this.state.matches.map((d) => ({
1909
- ...pick(d, ['id', 'status', 'updatedAt', 'loaderData']),
1910
- // If an error occurs server-side during SSRing,
1911
- // send a small subset of the error to the client
1912
- error: d.error
1913
- ? {
1914
- data: pickError(d.error),
1915
- __isServerError: true,
1916
- }
1917
- : undefined,
1918
- })),
1919
- },
1920
- }
1921
- }
1922
-
1923
- hydrate = async (__do_not_use_server_ctx?: string) => {
1924
- let _ctx = __do_not_use_server_ctx
1925
- // Client hydrates from window
1926
- if (typeof document !== 'undefined') {
1927
- _ctx = window.__TSR_DEHYDRATED__?.data
1928
- }
1929
-
1930
- invariant(
1931
- _ctx,
1932
- 'Expected to find a __TSR_DEHYDRATED__ property on window... but we did not. Did you forget to render <DehydrateRouter /> in your app?',
1933
- )
1934
-
1935
- const ctx = this.options.transformer.parse(_ctx) as HydrationCtx
1936
- this.dehydratedData = ctx.payload as any
1937
- this.options.hydrate?.(ctx.payload as any)
1938
- const dehydratedState = ctx.router.state
1939
-
1940
- let matches = this.matchRoutes(
1941
- this.state.location.pathname,
1942
- this.state.location.search,
1943
- ).map((match) => {
1944
- const dehydratedMatch = dehydratedState.dehydratedMatches.find(
1945
- (d) => d.id === match.id,
1946
- )
1947
-
1948
- invariant(
1949
- dehydratedMatch,
1950
- `Could not find a client-side match for dehydrated match with id: ${match.id}!`,
1951
- )
1952
-
1953
- if (dehydratedMatch) {
1954
- const route = this.looseRoutesById[match.routeId]!
1955
-
1956
- const assets =
1957
- dehydratedMatch.status === 'notFound' ||
1958
- dehydratedMatch.status === 'redirected'
1959
- ? {}
1960
- : {
1961
- meta: route.options.meta?.({
1962
- params: match.params,
1963
- loaderData: dehydratedMatch.loaderData,
1964
- }),
1965
- links: route.options.links?.(),
1966
- scripts: route.options.scripts?.(),
1967
- }
1968
-
1969
- return {
1970
- ...match,
1971
- ...dehydratedMatch,
1972
- ...assets,
1973
- }
1974
- }
1975
- return match
1976
- })
1977
-
1978
- this.__store.setState((s) => {
1979
- return {
1980
- ...s,
1981
- matches: matches as any,
1982
- lastUpdated: Date.now(),
1983
- }
1984
- })
1985
- }
1986
-
1987
- handleNotFound = (matches: AnyRouteMatch[], err: NotFoundError) => {
1988
- const matchesByRouteId = Object.fromEntries(
1989
- matches.map((match) => [match.routeId, match]),
1990
- ) as Record<string, AnyRouteMatch>
1991
-
1992
- // Start at the route that errored or default to the root route
1993
- let routeCursor =
1994
- (err.global
1995
- ? this.looseRoutesById[rootRouteId]
1996
- : this.looseRoutesById[err.routeId]) ||
1997
- this.looseRoutesById[rootRouteId]!
1998
-
1999
- // Go up the tree until we find a route with a notFoundComponent or we hit the root
2000
- while (
2001
- !routeCursor.options.notFoundComponent &&
2002
- !this.options.defaultNotFoundComponent &&
2003
- routeCursor.id !== rootRouteId
2004
- ) {
2005
- routeCursor = routeCursor?.parentRoute
2006
-
2007
- invariant(
2008
- routeCursor,
2009
- 'Found invalid route tree while trying to find not-found handler.',
2010
- )
2011
- }
2012
-
2013
- let match = matchesByRouteId[routeCursor.id]
2014
-
2015
- invariant(match, 'Could not find match for route: ' + routeCursor.id)
2016
-
2017
- // Assign the error to the match
2018
- Object.assign(match, {
2019
- status: 'notFound',
2020
- error: err,
2021
- isFetching: false,
2022
- } as AnyRouteMatch)
2023
- }
2024
-
2025
- hasNotFoundMatch = () => {
2026
- return this.__store.state.matches.some(
2027
- (d) => d.status === 'notFound' || d.globalNotFound,
2028
- )
2029
- }
2030
-
2031
- // resolveMatchPromise = (matchId: string, key: string, value: any) => {
2032
- // state.matches
2033
- // .find((d) => d.id === matchId)
2034
- // ?.__promisesByKey[key]?.resolve(value)
2035
- // }
2036
- }
2037
-
2038
- // A function that takes an import() argument which is a function and returns a new function that will
2039
- // proxy arguments from the caller to the imported function, retaining all type
2040
- // information along the way
2041
- export function lazyFn<
2042
- T extends Record<string, (...args: any[]) => any>,
2043
- TKey extends keyof T = 'default',
2044
- >(fn: () => Promise<T>, key?: TKey) {
2045
- return async (
2046
- ...args: Parameters<T[TKey]>
2047
- ): Promise<Awaited<ReturnType<T[TKey]>>> => {
2048
- const imported = await fn()
2049
- return imported[key || 'default'](...args)
106
+ super(options)
2050
107
  }
2051
108
  }
2052
109
 
2053
- export class SearchParamError extends Error {}
2054
-
2055
- export class PathParamError extends Error {}
2056
-
2057
- export function getInitialRouterState(
2058
- location: ParsedLocation,
2059
- ): RouterState<any> {
2060
- return {
2061
- isLoading: false,
2062
- isTransitioning: false,
2063
- status: 'idle',
2064
- resolvedLocation: { ...location },
2065
- location,
2066
- matches: [],
2067
- pendingMatches: [],
2068
- cachedMatches: [],
2069
- lastUpdated: 0,
2070
- statusCode: 200,
2071
- }
2072
- }
2073
-
2074
- export function defaultSerializeError(err: unknown) {
2075
- if (err instanceof Error)
2076
- return {
2077
- name: err.name,
2078
- message: err.message,
2079
- }
2080
-
2081
- return {
2082
- data: err,
2083
- }
110
+ if (typeof globalThis !== 'undefined') {
111
+ ;(globalThis as any).createFileRoute = createFileRoute
112
+ ;(globalThis as any).createLazyFileRoute = createLazyFileRoute
113
+ } else if (typeof window !== 'undefined') {
114
+ ;(window as any).createFileRoute = createFileRoute
115
+ ;(window as any).createFileRoute = createLazyFileRoute
2084
116
  }