@tanstack/router-core 0.0.1-alpha.8 → 0.0.1-beta.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.
package/src/router.ts CHANGED
@@ -25,8 +25,11 @@ import {
25
25
  } from './path'
26
26
  import { AnyRoute, cascadeLoaderData, createRoute, Route } from './route'
27
27
  import {
28
+ AnyLoaderData,
29
+ AnyPathParams,
28
30
  AnyRouteConfig,
29
31
  AnySearchSchema,
32
+ LoaderContext,
30
33
  RouteConfig,
31
34
  SearchFilter,
32
35
  } from './routeConfig'
@@ -43,6 +46,7 @@ import {
43
46
  functionalUpdate,
44
47
  last,
45
48
  PickAsRequired,
49
+ PickRequired,
46
50
  replaceEqualDeep,
47
51
  Timeout,
48
52
  Updater,
@@ -81,9 +85,10 @@ export interface RouterOptions<TRouteConfig extends AnyRouteConfig> {
81
85
  stringifySearch?: SearchSerializer
82
86
  parseSearch?: SearchParser
83
87
  filterRoutes?: FilterRoutesFn
84
- defaultLinkPreload?: false | 'intent'
85
- defaultLinkPreloadMaxAge?: number
86
- defaultLinkPreloadDelay?: number
88
+ defaultPreload?: false | 'intent'
89
+ defaultPreloadMaxAge?: number
90
+ defaultPreloadGcMaxAge?: number
91
+ defaultPreloadDelay?: number
87
92
  useErrorBoundary?: boolean
88
93
  defaultElement?: GetFrameworkGeneric<'Element'>
89
94
  defaultErrorElement?: GetFrameworkGeneric<'Element'>
@@ -98,6 +103,11 @@ export interface RouterOptions<TRouteConfig extends AnyRouteConfig> {
98
103
  basepath?: string
99
104
  createRouter?: (router: Router<any, any>) => void
100
105
  createRoute?: (opts: { route: AnyRoute; router: Router<any, any> }) => void
106
+ createElement?: (
107
+ element:
108
+ | GetFrameworkGeneric<'Element'>
109
+ | (() => Promise<GetFrameworkGeneric<'Element'>>),
110
+ ) => Promise<GetFrameworkGeneric<'Element'>>
101
111
  }
102
112
 
103
113
  export interface Action<
@@ -123,6 +133,42 @@ export interface ActionState<
123
133
  error?: unknown
124
134
  }
125
135
 
136
+ export interface Loader<
137
+ TFullSearchSchema extends AnySearchSchema = {},
138
+ TAllParams extends AnyPathParams = {},
139
+ TRouteLoaderData = AnyLoaderData,
140
+ > {
141
+ fetch: keyof PickRequired<TFullSearchSchema> extends never
142
+ ? keyof TAllParams extends never
143
+ ? (loaderContext: { signal?: AbortSignal }) => Promise<TRouteLoaderData>
144
+ : (loaderContext: {
145
+ params: TAllParams
146
+ search?: TFullSearchSchema
147
+ signal?: AbortSignal
148
+ }) => Promise<TRouteLoaderData>
149
+ : keyof TAllParams extends never
150
+ ? (loaderContext: {
151
+ search: TFullSearchSchema
152
+ params: TAllParams
153
+ signal?: AbortSignal
154
+ }) => Promise<TRouteLoaderData>
155
+ : (loaderContext: {
156
+ search: TFullSearchSchema
157
+ signal?: AbortSignal
158
+ }) => Promise<TRouteLoaderData>
159
+ current?: LoaderState<TFullSearchSchema, TAllParams>
160
+ latest?: LoaderState<TFullSearchSchema, TAllParams>
161
+ pending: LoaderState<TFullSearchSchema, TAllParams>[]
162
+ }
163
+
164
+ export interface LoaderState<
165
+ TFullSearchSchema = unknown,
166
+ TAllParams = unknown,
167
+ > {
168
+ loadedAt: number
169
+ loaderContext: LoaderContext<TFullSearchSchema, TAllParams>
170
+ }
171
+
126
172
  export interface RouterState {
127
173
  status: 'idle' | 'loading'
128
174
  location: Location
@@ -132,6 +178,7 @@ export interface RouterState {
132
178
  currentAction?: ActionState
133
179
  latestAction?: ActionState
134
180
  actions: Record<string, Action>
181
+ loaders: Record<string, Loader>
135
182
  pending?: PendingState
136
183
  isFetching: boolean
137
184
  isPreloading: boolean
@@ -184,6 +231,7 @@ export interface Router<
184
231
  TRouteConfig extends AnyRouteConfig = RouteConfig,
185
232
  TAllRouteInfo extends AnyAllRouteInfo = AllRouteInfo<TRouteConfig>,
186
233
  > {
234
+ history: BrowserHistory | MemoryHistory | HashHistory
187
235
  options: PickAsRequired<
188
236
  RouterOptions<TRouteConfig>,
189
237
  'stringifySearch' | 'parseSearch'
@@ -210,15 +258,7 @@ export interface Router<
210
258
  update: <TRouteConfig extends RouteConfig = RouteConfig>(
211
259
  opts?: RouterOptions<TRouteConfig>,
212
260
  ) => Router<TRouteConfig>
213
- buildRouteTree: (
214
- routeConfig: RouteConfig,
215
- ) => Route<TAllRouteInfo, AnyRouteInfo>
216
- parseLocation: (
217
- location: History['location'],
218
- previousLocation?: Location,
219
- ) => Location
220
- buildLocation: (dest: BuildNextOptions) => Location
221
- commitLocation: (next: Location, replace?: boolean) => Promise<void>
261
+
222
262
  buildNext: (opts: BuildNextOptions) => Location
223
263
  cancelMatches: () => void
224
264
  loadLocation: (next?: Location) => Promise<void>
@@ -227,9 +267,10 @@ export interface Router<
227
267
  getRoute: <TId extends keyof TAllRouteInfo['routeInfoById']>(
228
268
  id: TId,
229
269
  ) => Route<TAllRouteInfo, TAllRouteInfo['routeInfoById'][TId]>
230
- loadRoute: (
270
+ loadRoute: (navigateOpts: BuildNextOptions) => Promise<RouteMatch[]>
271
+ preloadRoute: (
231
272
  navigateOpts: BuildNextOptions,
232
- loaderOpts: { maxAge: number },
273
+ loaderOpts: { maxAge?: number; gcMaxAge?: number },
233
274
  ) => Promise<RouteMatch[]>
234
275
  matchRoutes: (
235
276
  pathname: string,
@@ -238,16 +279,13 @@ export interface Router<
238
279
  loadMatches: (
239
280
  resolvedMatches: RouteMatch[],
240
281
  loaderOpts?: { withPending?: boolean } & (
241
- | { preload: true; maxAge: number }
242
- | { preload?: false; maxAge?: never }
282
+ | { preload: true; maxAge: number; gcMaxAge: number }
283
+ | { preload?: false; maxAge?: never; gcMaxAge?: never }
243
284
  ),
244
285
  ) => Promise<void>
245
286
  invalidateRoute: (opts: MatchLocation) => void
246
287
  reload: () => Promise<void>
247
288
  resolvePath: (from: string, path: string) => string
248
- _navigate: (
249
- location: BuildNextOptions & { replace?: boolean },
250
- ) => Promise<void>
251
289
  navigate: <
252
290
  TFrom extends ValidFromPath<TAllRouteInfo> = '/',
253
291
  TTo extends string = '.',
@@ -267,6 +305,20 @@ export interface Router<
267
305
  >(
268
306
  opts: LinkOptions<TAllRouteInfo, TFrom, TTo>,
269
307
  ) => LinkInfo
308
+ __: {
309
+ buildRouteTree: (
310
+ routeConfig: RouteConfig,
311
+ ) => Route<TAllRouteInfo, AnyRouteInfo>
312
+ parseLocation: (
313
+ location: History['location'],
314
+ previousLocation?: Location,
315
+ ) => Location
316
+ buildLocation: (dest: BuildNextOptions) => Location
317
+ commitLocation: (next: Location, replace?: boolean) => Promise<void>
318
+ navigate: (
319
+ location: BuildNextOptions & { replace?: boolean },
320
+ ) => Promise<void>
321
+ }
270
322
  }
271
323
 
272
324
  // Detect if we're in the DOM
@@ -289,13 +341,15 @@ export function createRouter<
289
341
  const originalOptions = {
290
342
  defaultLoaderGcMaxAge: 5 * 60 * 1000,
291
343
  defaultLoaderMaxAge: 0,
292
- defaultLinkPreloadDelay: 50,
344
+ defaultPreloadMaxAge: 2000,
345
+ defaultPreloadDelay: 50,
293
346
  ...userOptions,
294
347
  stringifySearch: userOptions?.stringifySearch ?? defaultStringifySearch,
295
348
  parseSearch: userOptions?.parseSearch ?? defaultParseSearch,
296
349
  }
297
350
 
298
351
  let router: Router<TRouteConfig, TAllRouteInfo> = {
352
+ history,
299
353
  options: originalOptions,
300
354
  listeners: [],
301
355
  removeActionQueue: [],
@@ -314,6 +368,7 @@ export function createRouter<
314
368
  location: null!,
315
369
  matches: [],
316
370
  actions: {},
371
+ loaders: {},
317
372
  loaderData: {} as any,
318
373
  lastUpdated: Date.now(),
319
374
  isFetching: false,
@@ -347,7 +402,7 @@ export function createRouter<
347
402
  },
348
403
 
349
404
  mount: () => {
350
- const next = router.buildLocation({
405
+ const next = router.__.buildLocation({
351
406
  to: '.',
352
407
  search: true,
353
408
  hash: true,
@@ -356,14 +411,14 @@ export function createRouter<
356
411
  // If the current location isn't updated, trigger a navigation
357
412
  // to the current location. Otherwise, load the current location.
358
413
  if (next.href !== router.location.href) {
359
- router.commitLocation(next, true)
414
+ router.__.commitLocation(next, true)
360
415
  } else {
361
416
  router.loadLocation()
362
417
  }
363
418
 
364
419
  const unsub = history.listen((event) => {
365
420
  router.loadLocation(
366
- router.parseLocation(event.location, router.location),
421
+ router.__.parseLocation(event.location, router.location),
367
422
  )
368
423
  })
369
424
 
@@ -396,235 +451,12 @@ export function createRouter<
396
451
 
397
452
  if (routeConfig) {
398
453
  router.routesById = {} as any
399
- router.routeTree = router.buildRouteTree(routeConfig)
454
+ router.routeTree = router.__.buildRouteTree(routeConfig)
400
455
  }
401
456
 
402
457
  return router as any
403
458
  },
404
459
 
405
- buildRouteTree: (rootRouteConfig: RouteConfig) => {
406
- const recurseRoutes = (
407
- routeConfigs: RouteConfig[],
408
- parent?: Route<TAllRouteInfo, any>,
409
- ): Route<TAllRouteInfo, any>[] => {
410
- return routeConfigs.map((routeConfig) => {
411
- const routeOptions = routeConfig.options
412
- const route = createRoute(routeConfig, routeOptions, parent, router)
413
-
414
- // {
415
- // pendingMs: routeOptions.pendingMs ?? router.defaultPendingMs,
416
- // pendingMinMs: routeOptions.pendingMinMs ?? router.defaultPendingMinMs,
417
- // }
418
-
419
- const existingRoute = (router.routesById as any)[route.routeId]
420
-
421
- if (existingRoute) {
422
- if (process.env.NODE_ENV !== 'production') {
423
- console.warn(
424
- `Duplicate routes found with id: ${String(route.routeId)}`,
425
- router.routesById,
426
- route,
427
- )
428
- }
429
- throw new Error()
430
- }
431
-
432
- ;(router.routesById as any)[route.routeId] = route
433
-
434
- const children = routeConfig.children as RouteConfig[]
435
-
436
- route.childRoutes = children?.length
437
- ? recurseRoutes(children, route)
438
- : undefined
439
-
440
- return route
441
- })
442
- }
443
-
444
- const routes = recurseRoutes([rootRouteConfig])
445
-
446
- return routes[0]!
447
- },
448
-
449
- parseLocation: (
450
- location: History['location'],
451
- previousLocation?: Location,
452
- ): Location => {
453
- const parsedSearch = router.options.parseSearch(location.search)
454
-
455
- return {
456
- pathname: location.pathname,
457
- searchStr: location.search,
458
- search: replaceEqualDeep(previousLocation?.search, parsedSearch),
459
- hash: location.hash.split('#').reverse()[0] ?? '',
460
- href: `${location.pathname}${location.search}${location.hash}`,
461
- state: location.state as LocationState,
462
- key: location.key,
463
- }
464
- },
465
-
466
- buildLocation: (dest: BuildNextOptions = {}): Location => {
467
- // const resolvedFrom: Location = {
468
- // ...router.location,
469
- const fromPathname = dest.fromCurrent
470
- ? router.location.pathname
471
- : dest.from ?? router.location.pathname
472
-
473
- let pathname = resolvePath(
474
- router.basepath ?? '/',
475
- fromPathname,
476
- `${dest.to ?? '.'}`,
477
- )
478
-
479
- const fromMatches = router.matchRoutes(router.location.pathname, {
480
- strictParseParams: true,
481
- })
482
-
483
- const toMatches = router.matchRoutes(pathname)
484
-
485
- const prevParams = { ...last(fromMatches)?.params }
486
-
487
- let nextParams =
488
- (dest.params ?? true) === true
489
- ? prevParams
490
- : functionalUpdate(dest.params!, prevParams)
491
-
492
- if (nextParams) {
493
- toMatches
494
- .map((d) => d.options.stringifyParams)
495
- .filter(Boolean)
496
- .forEach((fn) => {
497
- Object.assign({}, nextParams!, fn!(nextParams!))
498
- })
499
- }
500
-
501
- pathname = interpolatePath(pathname, nextParams ?? {})
502
-
503
- // Pre filters first
504
- const preFilteredSearch = dest.__preSearchFilters?.length
505
- ? dest.__preSearchFilters.reduce(
506
- (prev, next) => next(prev),
507
- router.location.search,
508
- )
509
- : router.location.search
510
-
511
- // Then the link/navigate function
512
- const destSearch =
513
- dest.search === true
514
- ? preFilteredSearch // Preserve resolvedFrom true
515
- : dest.search
516
- ? functionalUpdate(dest.search, preFilteredSearch) ?? {} // Updater
517
- : dest.__preSearchFilters?.length
518
- ? preFilteredSearch // Preserve resolvedFrom filters
519
- : {}
520
-
521
- // Then post filters
522
- const postFilteredSearch = dest.__postSearchFilters?.length
523
- ? dest.__postSearchFilters.reduce(
524
- (prev, next) => next(prev),
525
- destSearch,
526
- )
527
- : destSearch
528
-
529
- const search = replaceEqualDeep(
530
- router.location.search,
531
- postFilteredSearch,
532
- )
533
-
534
- const searchStr = router.options.stringifySearch(search)
535
- let hash =
536
- dest.hash === true
537
- ? router.location.hash
538
- : functionalUpdate(dest.hash!, router.location.hash)
539
- hash = hash ? `#${hash}` : ''
540
-
541
- return {
542
- pathname,
543
- search,
544
- searchStr,
545
- state: router.location.state,
546
- hash,
547
- href: `${pathname}${searchStr}${hash}`,
548
- key: dest.key,
549
- }
550
- },
551
-
552
- commitLocation: (next: Location, replace?: boolean): Promise<void> => {
553
- const id = '' + Date.now() + Math.random()
554
-
555
- if (router.navigateTimeout) clearTimeout(router.navigateTimeout)
556
-
557
- let nextAction: 'push' | 'replace' = 'replace'
558
-
559
- if (!replace) {
560
- nextAction = 'push'
561
- }
562
-
563
- const isSameUrl =
564
- router.parseLocation(history.location).href === next.href
565
-
566
- if (isSameUrl && !next.key) {
567
- nextAction = 'replace'
568
- }
569
-
570
- if (nextAction === 'replace') {
571
- history.replace(
572
- {
573
- pathname: next.pathname,
574
- hash: next.hash,
575
- search: next.searchStr,
576
- },
577
- {
578
- id,
579
- },
580
- )
581
- } else {
582
- history.push(
583
- {
584
- pathname: next.pathname,
585
- hash: next.hash,
586
- search: next.searchStr,
587
- },
588
- {
589
- id,
590
- },
591
- )
592
- }
593
-
594
- router.navigationPromise = new Promise((resolve) => {
595
- const previousNavigationResolve = router.resolveNavigation
596
-
597
- router.resolveNavigation = () => {
598
- previousNavigationResolve()
599
- resolve()
600
- }
601
- })
602
-
603
- return router.navigationPromise
604
- },
605
-
606
- buildNext: (opts: BuildNextOptions) => {
607
- const next = router.buildLocation(opts)
608
-
609
- const matches = router.matchRoutes(next.pathname)
610
-
611
- const __preSearchFilters = matches
612
- .map((match) => match.options.preSearchFilters ?? [])
613
- .flat()
614
- .filter(Boolean)
615
-
616
- const __postSearchFilters = matches
617
- .map((match) => match.options.postSearchFilters ?? [])
618
- .flat()
619
- .filter(Boolean)
620
-
621
- return router.buildLocation({
622
- ...opts,
623
- __preSearchFilters,
624
- __postSearchFilters,
625
- })
626
- },
627
-
628
460
  cancelMatches: () => {
629
461
  ;[
630
462
  ...router.state.matches,
@@ -736,6 +568,7 @@ export function createRouter<
736
568
  params: d.params,
737
569
  search: d.search,
738
570
  })
571
+ delete router.matchCache[d.matchId]
739
572
  })
740
573
 
741
574
  if (matches.some((d) => d.status === 'loading')) {
@@ -782,17 +615,32 @@ export function createRouter<
782
615
  })
783
616
  },
784
617
 
785
- loadRoute: async (
786
- navigateOpts: BuildNextOptions = router.location,
787
- loaderOpts: { maxAge: number },
788
- ) => {
618
+ loadRoute: async (navigateOpts = router.location) => {
619
+ const next = router.buildNext(navigateOpts)
620
+ const matches = router.matchRoutes(next.pathname, {
621
+ strictParseParams: true,
622
+ })
623
+ await router.loadMatches(matches)
624
+ return matches
625
+ },
626
+
627
+ preloadRoute: async (navigateOpts = router.location, loaderOpts) => {
789
628
  const next = router.buildNext(navigateOpts)
790
629
  const matches = router.matchRoutes(next.pathname, {
791
630
  strictParseParams: true,
792
631
  })
793
632
  await router.loadMatches(matches, {
794
633
  preload: true,
795
- maxAge: loaderOpts.maxAge,
634
+ maxAge:
635
+ loaderOpts.maxAge ??
636
+ router.options.defaultPreloadMaxAge ??
637
+ router.options.defaultLoaderMaxAge ??
638
+ 0,
639
+ gcMaxAge:
640
+ loaderOpts.gcMaxAge ??
641
+ router.options.defaultPreloadGcMaxAge ??
642
+ router.options.defaultLoaderGcMaxAge ??
643
+ 0,
796
644
  })
797
645
  return matches
798
646
  },
@@ -904,38 +752,10 @@ export function createRouter<
904
752
  },
905
753
 
906
754
  loadMatches: async (resolvedMatches, loaderOpts) => {
907
- const now = Date.now()
908
-
909
755
  const matchPromises = resolvedMatches.map(async (match) => {
910
756
  // Validate the match (loads search params etc)
911
757
  match.__.validate()
912
-
913
- // // If the match doesn't have a loader, don't attempt to load it
914
- // if (!match.hasLoaders()) {
915
- // return
916
- // }
917
-
918
- // If this is a preload, add it to the preload cache
919
- if (loaderOpts?.preload && loaderOpts?.maxAge > 0) {
920
- // If the match is currently active, don't preload it
921
- if (router.state.matches.find((d) => d.matchId === match.matchId)) {
922
- return
923
- }
924
-
925
- router.matchCache[match.matchId] = {
926
- gc: now + loaderOpts.maxAge, // TODO: Should this use the route's maxAge?
927
- match,
928
- }
929
- }
930
-
931
- // If the match is invalid, errored or idle, trigger it to load
932
- if (
933
- (match.status === 'success' && match.getIsInvalid()) ||
934
- match.status === 'error' ||
935
- match.status === 'idle'
936
- ) {
937
- match.load()
938
- }
758
+ match.load(loaderOpts)
939
759
 
940
760
  if (match.status === 'loading') {
941
761
  // If requested, start the pending timers
@@ -968,7 +788,7 @@ export function createRouter<
968
788
  },
969
789
 
970
790
  reload: () =>
971
- router._navigate({
791
+ router.__.navigate({
972
792
  fromCurrent: true,
973
793
  replace: true,
974
794
  search: true,
@@ -1006,11 +826,6 @@ export function createRouter<
1006
826
  })
1007
827
  },
1008
828
 
1009
- _navigate: (location: BuildNextOptions & { replace?: boolean }) => {
1010
- const next = router.buildNext(location)
1011
- return router.commitLocation(next, location.replace)
1012
- },
1013
-
1014
829
  navigate: async ({ from, to = '.', search, hash, replace, params }) => {
1015
830
  // If this link simply reloads the current route,
1016
831
  // make sure it has a new key so it will trigger a data refresh
@@ -1032,7 +847,7 @@ export function createRouter<
1032
847
  'Attempting to navigate to external url with router.navigate!',
1033
848
  )
1034
849
 
1035
- return router._navigate({
850
+ return router.__.navigate({
1036
851
  from: fromString,
1037
852
  to: toString,
1038
853
  search,
@@ -1053,6 +868,7 @@ export function createRouter<
1053
868
  activeOptions,
1054
869
  preload,
1055
870
  preloadMaxAge: userPreloadMaxAge,
871
+ preloadGcMaxAge: userPreloadGcMaxAge,
1056
872
  preloadDelay: userPreloadDelay,
1057
873
  disabled,
1058
874
  }) => {
@@ -1081,14 +897,9 @@ export function createRouter<
1081
897
 
1082
898
  const next = router.buildNext(nextOpts)
1083
899
 
1084
- preload = preload ?? router.options.defaultLinkPreload
1085
- const preloadMaxAge =
1086
- userPreloadMaxAge ??
1087
- router.options.defaultLinkPreloadMaxAge ??
1088
- router.options.defaultLoaderGcMaxAge ??
1089
- 0
900
+ preload = preload ?? router.options.defaultPreload
1090
901
  const preloadDelay =
1091
- userPreloadDelay ?? router.options.defaultLinkPreloadDelay ?? 0
902
+ userPreloadDelay ?? router.options.defaultPreloadDelay ?? 0
1092
903
 
1093
904
  // Compare path/hash for matches
1094
905
  const pathIsEqual = router.state.location.pathname === next.pathname
@@ -1120,28 +931,34 @@ export function createRouter<
1120
931
  }
1121
932
 
1122
933
  // All is well? Navigate!)
1123
- router._navigate(nextOpts)
934
+ router.__.navigate(nextOpts)
1124
935
  }
1125
936
  }
1126
937
 
1127
938
  // The click handler
1128
939
  const handleFocus = (e: MouseEvent) => {
1129
- if (preload && preloadMaxAge > 0) {
1130
- router.loadRoute(nextOpts, { maxAge: preloadMaxAge })
940
+ if (preload) {
941
+ router.preloadRoute(nextOpts, {
942
+ maxAge: userPreloadMaxAge,
943
+ gcMaxAge: userPreloadGcMaxAge,
944
+ })
1131
945
  }
1132
946
  }
1133
947
 
1134
948
  const handleEnter = (e: MouseEvent) => {
1135
949
  const target = (e.target || {}) as LinkCurrentTargetElement
1136
950
 
1137
- if (preload && preloadMaxAge > 0) {
951
+ if (preload) {
1138
952
  if (target.preloadTimeout) {
1139
953
  return
1140
954
  }
1141
955
 
1142
956
  target.preloadTimeout = setTimeout(() => {
1143
957
  target.preloadTimeout = null
1144
- router.loadRoute(nextOpts, { maxAge: preloadMaxAge })
958
+ router.preloadRoute(nextOpts, {
959
+ maxAge: userPreloadMaxAge,
960
+ gcMaxAge: userPreloadGcMaxAge,
961
+ })
1145
962
  }, preloadDelay)
1146
963
  }
1147
964
  }
@@ -1166,9 +983,238 @@ export function createRouter<
1166
983
  disabled,
1167
984
  }
1168
985
  },
986
+ buildNext: (opts: BuildNextOptions) => {
987
+ const next = router.__.buildLocation(opts)
988
+
989
+ const matches = router.matchRoutes(next.pathname)
990
+
991
+ const __preSearchFilters = matches
992
+ .map((match) => match.options.preSearchFilters ?? [])
993
+ .flat()
994
+ .filter(Boolean)
995
+
996
+ const __postSearchFilters = matches
997
+ .map((match) => match.options.postSearchFilters ?? [])
998
+ .flat()
999
+ .filter(Boolean)
1000
+
1001
+ return router.__.buildLocation({
1002
+ ...opts,
1003
+ __preSearchFilters,
1004
+ __postSearchFilters,
1005
+ })
1006
+ },
1007
+
1008
+ __: {
1009
+ buildRouteTree: (rootRouteConfig: RouteConfig) => {
1010
+ const recurseRoutes = (
1011
+ routeConfigs: RouteConfig[],
1012
+ parent?: Route<TAllRouteInfo, any>,
1013
+ ): Route<TAllRouteInfo, any>[] => {
1014
+ return routeConfigs.map((routeConfig) => {
1015
+ const routeOptions = routeConfig.options
1016
+ const route = createRoute(routeConfig, routeOptions, parent, router)
1017
+
1018
+ // {
1019
+ // pendingMs: routeOptions.pendingMs ?? router.defaultPendingMs,
1020
+ // pendingMinMs: routeOptions.pendingMinMs ?? router.defaultPendingMinMs,
1021
+ // }
1022
+
1023
+ const existingRoute = (router.routesById as any)[route.routeId]
1024
+
1025
+ if (existingRoute) {
1026
+ if (process.env.NODE_ENV !== 'production') {
1027
+ console.warn(
1028
+ `Duplicate routes found with id: ${String(route.routeId)}`,
1029
+ router.routesById,
1030
+ route,
1031
+ )
1032
+ }
1033
+ throw new Error()
1034
+ }
1035
+
1036
+ ;(router.routesById as any)[route.routeId] = route
1037
+
1038
+ const children = routeConfig.children as RouteConfig[]
1039
+
1040
+ route.childRoutes = children?.length
1041
+ ? recurseRoutes(children, route)
1042
+ : undefined
1043
+
1044
+ return route
1045
+ })
1046
+ }
1047
+
1048
+ const routes = recurseRoutes([rootRouteConfig])
1049
+
1050
+ return routes[0]!
1051
+ },
1052
+
1053
+ parseLocation: (
1054
+ location: History['location'],
1055
+ previousLocation?: Location,
1056
+ ): Location => {
1057
+ const parsedSearch = router.options.parseSearch(location.search)
1058
+
1059
+ return {
1060
+ pathname: location.pathname,
1061
+ searchStr: location.search,
1062
+ search: replaceEqualDeep(previousLocation?.search, parsedSearch),
1063
+ hash: location.hash.split('#').reverse()[0] ?? '',
1064
+ href: `${location.pathname}${location.search}${location.hash}`,
1065
+ state: location.state as LocationState,
1066
+ key: location.key,
1067
+ }
1068
+ },
1069
+
1070
+ navigate: (location: BuildNextOptions & { replace?: boolean }) => {
1071
+ const next = router.buildNext(location)
1072
+ return router.__.commitLocation(next, location.replace)
1073
+ },
1074
+
1075
+ buildLocation: (dest: BuildNextOptions = {}): Location => {
1076
+ // const resolvedFrom: Location = {
1077
+ // ...router.location,
1078
+ const fromPathname = dest.fromCurrent
1079
+ ? router.location.pathname
1080
+ : dest.from ?? router.location.pathname
1081
+
1082
+ let pathname = resolvePath(
1083
+ router.basepath ?? '/',
1084
+ fromPathname,
1085
+ `${dest.to ?? '.'}`,
1086
+ )
1087
+
1088
+ const fromMatches = router.matchRoutes(router.location.pathname, {
1089
+ strictParseParams: true,
1090
+ })
1091
+
1092
+ const toMatches = router.matchRoutes(pathname)
1093
+
1094
+ const prevParams = { ...last(fromMatches)?.params }
1095
+
1096
+ let nextParams =
1097
+ (dest.params ?? true) === true
1098
+ ? prevParams
1099
+ : functionalUpdate(dest.params!, prevParams)
1100
+
1101
+ if (nextParams) {
1102
+ toMatches
1103
+ .map((d) => d.options.stringifyParams)
1104
+ .filter(Boolean)
1105
+ .forEach((fn) => {
1106
+ Object.assign({}, nextParams!, fn!(nextParams!))
1107
+ })
1108
+ }
1109
+
1110
+ pathname = interpolatePath(pathname, nextParams ?? {})
1111
+
1112
+ // Pre filters first
1113
+ const preFilteredSearch = dest.__preSearchFilters?.length
1114
+ ? dest.__preSearchFilters.reduce(
1115
+ (prev, next) => next(prev),
1116
+ router.location.search,
1117
+ )
1118
+ : router.location.search
1119
+
1120
+ // Then the link/navigate function
1121
+ const destSearch =
1122
+ dest.search === true
1123
+ ? preFilteredSearch // Preserve resolvedFrom true
1124
+ : dest.search
1125
+ ? functionalUpdate(dest.search, preFilteredSearch) ?? {} // Updater
1126
+ : dest.__preSearchFilters?.length
1127
+ ? preFilteredSearch // Preserve resolvedFrom filters
1128
+ : {}
1129
+
1130
+ // Then post filters
1131
+ const postFilteredSearch = dest.__postSearchFilters?.length
1132
+ ? dest.__postSearchFilters.reduce(
1133
+ (prev, next) => next(prev),
1134
+ destSearch,
1135
+ )
1136
+ : destSearch
1137
+
1138
+ const search = replaceEqualDeep(
1139
+ router.location.search,
1140
+ postFilteredSearch,
1141
+ )
1142
+
1143
+ const searchStr = router.options.stringifySearch(search)
1144
+ let hash =
1145
+ dest.hash === true
1146
+ ? router.location.hash
1147
+ : functionalUpdate(dest.hash!, router.location.hash)
1148
+ hash = hash ? `#${hash}` : ''
1149
+
1150
+ return {
1151
+ pathname,
1152
+ search,
1153
+ searchStr,
1154
+ state: router.location.state,
1155
+ hash,
1156
+ href: `${pathname}${searchStr}${hash}`,
1157
+ key: dest.key,
1158
+ }
1159
+ },
1160
+
1161
+ commitLocation: (next: Location, replace?: boolean): Promise<void> => {
1162
+ const id = '' + Date.now() + Math.random()
1163
+
1164
+ if (router.navigateTimeout) clearTimeout(router.navigateTimeout)
1165
+
1166
+ let nextAction: 'push' | 'replace' = 'replace'
1167
+
1168
+ if (!replace) {
1169
+ nextAction = 'push'
1170
+ }
1171
+
1172
+ const isSameUrl =
1173
+ router.__.parseLocation(history.location).href === next.href
1174
+
1175
+ if (isSameUrl && !next.key) {
1176
+ nextAction = 'replace'
1177
+ }
1178
+
1179
+ if (nextAction === 'replace') {
1180
+ history.replace(
1181
+ {
1182
+ pathname: next.pathname,
1183
+ hash: next.hash,
1184
+ search: next.searchStr,
1185
+ },
1186
+ {
1187
+ id,
1188
+ },
1189
+ )
1190
+ } else {
1191
+ history.push(
1192
+ {
1193
+ pathname: next.pathname,
1194
+ hash: next.hash,
1195
+ search: next.searchStr,
1196
+ },
1197
+ {
1198
+ id,
1199
+ },
1200
+ )
1201
+ }
1202
+
1203
+ router.navigationPromise = new Promise((resolve) => {
1204
+ const previousNavigationResolve = router.resolveNavigation
1205
+
1206
+ router.resolveNavigation = () => {
1207
+ previousNavigationResolve()
1208
+ resolve()
1209
+ }
1210
+ })
1211
+
1212
+ return router.navigationPromise
1213
+ },
1214
+ },
1169
1215
  }
1170
1216
 
1171
- router.location = router.parseLocation(history.location)
1217
+ router.location = router.__.parseLocation(history.location)
1172
1218
  router.state.location = router.location
1173
1219
 
1174
1220
  router.update(userOptions)