@tanstack/router-core 0.0.1-alpha.3 → 0.0.1-alpha.5

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/index.ts CHANGED
@@ -7,9 +7,10 @@ import {
7
7
  History,
8
8
  HashHistory,
9
9
  } from 'history'
10
- import React from 'react'
10
+ import invariant from 'tiny-invariant'
11
11
 
12
12
  export { createHashHistory, createBrowserHistory, createMemoryHistory }
13
+ export { invariant }
13
14
 
14
15
  import { decode, encode } from './qss'
15
16
 
@@ -62,6 +63,7 @@ export interface FrameworkGenerics {
62
63
 
63
64
  export interface RouteConfig<
64
65
  TId extends string = string,
66
+ TRouteId extends string = string,
65
67
  TPath extends string = string,
66
68
  TFullPath extends string = string,
67
69
  TRouteLoaderData extends AnyLoaderData = AnyLoaderData,
@@ -80,9 +82,11 @@ export interface RouteConfig<
80
82
  TKnownChildren = unknown,
81
83
  > {
82
84
  id: TId
85
+ routeId: TRouteId
83
86
  path: NoInfer<TPath>
84
87
  fullPath: TFullPath
85
88
  options: RouteOptions<
89
+ TRouteId,
86
90
  TPath,
87
91
  TRouteLoaderData,
88
92
  TLoaderData,
@@ -104,6 +108,7 @@ export interface RouteConfig<
104
108
  createChildRoute: CreateRouteConfigFn<
105
109
  false,
106
110
  TId,
111
+ TFullPath,
107
112
  TLoaderData,
108
113
  TFullSearchSchema,
109
114
  TAllParams
@@ -113,6 +118,7 @@ export interface RouteConfig<
113
118
  : { error: 'Invalid route detected'; route: TNewChildren },
114
119
  ) => RouteConfig<
115
120
  TId,
121
+ TRouteId,
116
122
  TPath,
117
123
  TFullPath,
118
124
  TRouteLoaderData,
@@ -133,10 +139,12 @@ export interface RouteConfig<
133
139
  type CreateRouteConfigFn<
134
140
  TIsRoot extends boolean = false,
135
141
  TParentId extends string = string,
142
+ TParentPath extends string = string,
136
143
  TParentAllLoaderData extends AnyLoaderData = {},
137
144
  TParentSearchSchema extends AnySearchSchema = {},
138
145
  TParentParams extends AnyPathParams = {},
139
146
  > = <
147
+ TRouteId extends string,
140
148
  TPath extends string,
141
149
  TRouteLoaderData extends AnyLoaderData,
142
150
  TActionPayload,
@@ -152,10 +160,16 @@ type CreateRouteConfigFn<
152
160
  ? Record<ParsePathParams<TPath>, string>
153
161
  : NoInfer<TParams>,
154
162
  TKnownChildren extends RouteConfig[] = RouteConfig[],
163
+ TResolvedId extends string = string extends TRouteId
164
+ ? string extends TPath
165
+ ? string
166
+ : TPath
167
+ : TRouteId,
155
168
  >(
156
169
  options?: TIsRoot extends true
157
170
  ? Omit<
158
171
  RouteOptions<
172
+ TRouteId,
159
173
  TPath,
160
174
  TRouteLoaderData,
161
175
  Expand<TParentAllLoaderData & DeepAwaited<NoInfer<TRouteLoaderData>>>,
@@ -171,6 +185,7 @@ type CreateRouteConfigFn<
171
185
  'path'
172
186
  > & { path?: never }
173
187
  : RouteOptions<
188
+ TRouteId,
174
189
  TPath,
175
190
  TRouteLoaderData,
176
191
  Expand<TParentAllLoaderData & DeepAwaited<NoInfer<TRouteLoaderData>>>,
@@ -186,10 +201,12 @@ type CreateRouteConfigFn<
186
201
  children?: TKnownChildren,
187
202
  isRoot?: boolean,
188
203
  parentId?: string,
204
+ parentPath?: string,
189
205
  ) => RouteConfig<
190
- RouteId<TParentId, TPath>,
206
+ RoutePrefix<TParentId, TResolvedId>,
207
+ TResolvedId,
191
208
  TPath,
192
- RouteIdToPath<RouteId<TParentId, TPath>>,
209
+ string extends TPath ? '' : RoutePath<RoutePrefix<TParentPath, TPath>>,
193
210
  TRouteLoaderData,
194
211
  Expand<TParentAllLoaderData & DeepAwaited<NoInfer<TRouteLoaderData>>>,
195
212
  TActionPayload,
@@ -208,11 +225,10 @@ export const createRouteConfig: CreateRouteConfigFn<true> = (
208
225
  children,
209
226
  isRoot = true,
210
227
  parentId,
228
+ parentPath,
211
229
  ) => {
212
230
  if (isRoot) {
213
231
  ;(options as any).path = rootRouteId
214
- } else {
215
- warning(!options.path, 'Routes must have a path property.')
216
232
  }
217
233
 
218
234
  // Strip the root from parentIds
@@ -220,14 +236,16 @@ export const createRouteConfig: CreateRouteConfigFn<true> = (
220
236
  parentId = ''
221
237
  }
222
238
 
223
- let path = String(isRoot ? rootRouteId : options.path)
239
+ let path: undefined | string = isRoot ? rootRouteId : options.path
224
240
 
225
241
  // If the path is anything other than an index path, trim it up
226
- if (path !== '/') {
242
+ if (path && path !== '/') {
227
243
  path = trimPath(path)
228
244
  }
229
245
 
230
- let id = joinPaths([parentId, path])
246
+ const routeId = path || (options as { id?: string }).id
247
+
248
+ let id = joinPaths([parentId, routeId])
231
249
 
232
250
  if (path === rootRouteId) {
233
251
  path = '/'
@@ -237,10 +255,12 @@ export const createRouteConfig: CreateRouteConfigFn<true> = (
237
255
  id = joinPaths(['/', id])
238
256
  }
239
257
 
240
- const fullPath = id === rootRouteId ? '/' : trimPathRight(id)
258
+ const fullPath =
259
+ id === rootRouteId ? '/' : trimPathRight(joinPaths([parentPath, path]))
241
260
 
242
261
  return {
243
262
  id: id as any,
263
+ routeId: routeId as any,
244
264
  path: path as any,
245
265
  fullPath: fullPath as any,
246
266
  options: options as any,
@@ -249,10 +269,11 @@ export const createRouteConfig: CreateRouteConfigFn<true> = (
249
269
  createRouteConfig(
250
270
  options,
251
271
  cb((childOptions: any) =>
252
- createRouteConfig(childOptions, undefined, false, id),
272
+ createRouteConfig(childOptions, undefined, false, id, fullPath),
253
273
  ),
254
274
  false,
255
275
  parentId,
276
+ parentPath,
256
277
  ),
257
278
  }
258
279
  }
@@ -271,6 +292,8 @@ export interface AnyRouteConfig
271
292
  any,
272
293
  any,
273
294
  any,
295
+ any,
296
+ any,
274
297
  any
275
298
  > {}
276
299
 
@@ -289,6 +312,7 @@ export interface AnyRouteConfigWithChildren<TChildren>
289
312
  any,
290
313
  any,
291
314
  any,
315
+ any,
292
316
  TChildren
293
317
  > {}
294
318
 
@@ -297,7 +321,8 @@ export interface AnyAllRouteInfo {
297
321
  routeInfo: AnyRouteInfo
298
322
  routeInfoById: Record<string, AnyRouteInfo>
299
323
  routeInfoByFullPath: Record<string, AnyRouteInfo>
300
- fullPath: string
324
+ routeIds: any
325
+ routePaths: any
301
326
  }
302
327
 
303
328
  export interface DefaultAllRouteInfo {
@@ -305,7 +330,8 @@ export interface DefaultAllRouteInfo {
305
330
  routeInfo: RouteInfo
306
331
  routeInfoById: Record<string, RouteInfo>
307
332
  routeInfoByFullPath: Record<string, RouteInfo>
308
- fullPath: string
333
+ routeIds: string
334
+ routePaths: string
309
335
  }
310
336
 
311
337
  export interface AllRouteInfo<TRouteConfig extends AnyRouteConfig = RouteConfig>
@@ -325,20 +351,27 @@ export interface RoutesInfoInner<
325
351
  any,
326
352
  any,
327
353
  any,
354
+ any,
355
+ any,
328
356
  any
329
357
  > = RouteInfo,
358
+ TRouteInfoById = {
359
+ [TInfo in TRouteInfo as TInfo['id']]: TInfo
360
+ },
361
+ TRouteInfoByFullPath = {
362
+ [TInfo in TRouteInfo as TInfo['fullPath'] extends RootRouteId
363
+ ? never
364
+ : string extends TInfo['fullPath']
365
+ ? never
366
+ : TInfo['fullPath']]: TInfo
367
+ },
330
368
  > {
331
369
  routeConfig: TRouteConfig
332
370
  routeInfo: TRouteInfo
333
- routeInfoById: {
334
- [TInfo in TRouteInfo as TInfo['id']]: TInfo
335
- }
336
- routeInfoByFullPath: {
337
- [TInfo in TRouteInfo as TInfo['id'] extends RootRouteId
338
- ? never
339
- : RouteIdToPath<TInfo['id']>]: TInfo
340
- }
341
- fullPath: RouteIdToPath<TRouteInfo['id']>
371
+ routeInfoById: TRouteInfoById
372
+ routeInfoByFullPath: TRouteInfoByFullPath
373
+ routeIds: keyof TRouteInfoById
374
+ routePaths: keyof TRouteInfoByFullPath
342
375
  }
343
376
 
344
377
  export interface AnyRoute extends Route<any, any> {}
@@ -356,6 +389,7 @@ export interface AnyRouteInfo
356
389
  any,
357
390
  any,
358
391
  any,
392
+ any,
359
393
  any
360
394
  > {}
361
395
 
@@ -363,7 +397,7 @@ export interface AnyRouteInfo
363
397
  // [E in T as E[TKey]]: E
364
398
  // }
365
399
 
366
- type RouteIdToPath<T extends string> = T extends RootRouteId
400
+ type RoutePath<T extends string> = T extends RootRouteId
367
401
  ? '/'
368
402
  : TrimPathRight<`${T}`>
369
403
 
@@ -397,6 +431,7 @@ export type ValueKeys<O> = Extract<keyof O, PropertyKey>
397
431
 
398
432
  export type RouteConfigRoute<TRouteConfig> = TRouteConfig extends RouteConfig<
399
433
  infer TId,
434
+ infer TRouteId,
400
435
  infer TPath,
401
436
  infer TFullPath,
402
437
  infer TRouteLoaderData,
@@ -411,10 +446,11 @@ export type RouteConfigRoute<TRouteConfig> = TRouteConfig extends RouteConfig<
411
446
  infer TAllParams,
412
447
  any
413
448
  >
414
- ? string extends TId
449
+ ? string extends TRouteId
415
450
  ? never
416
451
  : RouteInfo<
417
452
  TId,
453
+ TRouteId,
418
454
  TPath,
419
455
  TFullPath,
420
456
  TRouteLoaderData,
@@ -432,8 +468,9 @@ export type RouteConfigRoute<TRouteConfig> = TRouteConfig extends RouteConfig<
432
468
 
433
469
  export interface RouteInfo<
434
470
  TId extends string = string,
471
+ TRouteId extends string = string,
435
472
  TPath extends string = string,
436
- TFullPath extends {} = string,
473
+ TFullPath extends string = string,
437
474
  TRouteLoaderData extends AnyLoaderData = {},
438
475
  TLoaderData extends AnyLoaderData = {},
439
476
  TActionPayload = unknown,
@@ -449,6 +486,7 @@ export interface RouteInfo<
449
486
  TAllParams extends AnyPathParams = {},
450
487
  > {
451
488
  id: TId
489
+ routeId: TRouteId
452
490
  path: TPath
453
491
  fullPath: TFullPath
454
492
  routeLoaderData: TRouteLoaderData
@@ -461,6 +499,7 @@ export interface RouteInfo<
461
499
  params: TParams
462
500
  allParams: TAllParams
463
501
  options: RouteOptions<
502
+ TRouteId,
464
503
  TPath,
465
504
  TRouteLoaderData,
466
505
  TLoaderData,
@@ -484,14 +523,16 @@ type DeepAwaited<T> = T extends Promise<infer A>
484
523
  export const rootRouteId = '__root__' as const
485
524
  export type RootRouteId = typeof rootRouteId
486
525
 
487
- type RouteId<
526
+ type RoutePrefix<
488
527
  TPrefix extends string,
489
- TPath extends string,
490
- > = string extends TPath
528
+ TId extends string,
529
+ > = string extends TId
491
530
  ? RootRouteId
492
- : `${TPrefix}/${TPath}` extends '/'
493
- ? '/'
494
- : `/${TrimPathLeft<`${TrimPathRight<TPrefix>}/${TrimPath<TPath>}`>}`
531
+ : TId extends string
532
+ ? `${TPrefix}/${TId}` extends '/'
533
+ ? '/'
534
+ : `/${TrimPathLeft<`${TrimPathRight<TPrefix>}/${TrimPath<TId>}`>}`
535
+ : never
495
536
 
496
537
  type CleanPath<T extends string> = T extends `${infer L}//${infer R}`
497
538
  ? CleanPath<`${CleanPath<L>}/${CleanPath<R>}`>
@@ -621,6 +662,7 @@ export type ParentParams<TParentParams> = AnyPathParams extends TParentParams
621
662
  }
622
663
 
623
664
  export type RouteOptions<
665
+ TRouteId extends string = string,
624
666
  TPath extends string = string,
625
667
  TRouteLoaderData extends AnyLoaderData = {},
626
668
  TLoaderData extends AnyLoaderData = {},
@@ -635,9 +677,15 @@ export type RouteOptions<
635
677
  string
636
678
  >,
637
679
  TAllParams extends AnyPathParams = {},
638
- > = {
639
- // The path to match (relative to the nearest parent `Route` component or root basepath)
640
- path: TPath
680
+ > = (
681
+ | {
682
+ // The path to match (relative to the nearest parent `Route` component or root basepath)
683
+ path: TPath
684
+ }
685
+ | {
686
+ id: TRouteId
687
+ }
688
+ ) & {
641
689
  // If true, this route will be matched as case-sensitive
642
690
  caseSensitive?: boolean
643
691
  validateSearch?: SearchSchemaValidator<TSearchSchema, TParentSearchSchema>
@@ -654,20 +702,20 @@ export type RouteOptions<
654
702
  // // An array of child routes
655
703
  // children?: Route<any, any, any, any>[]
656
704
  } & (
657
- | {
658
- parseParams?: never
659
- stringifyParams?: never
660
- }
661
- | {
662
- // Parse params optionally receives path params as strings and returns them in a parsed format (like a number or boolean)
663
- parseParams: (
664
- rawParams: IsAny<TPath, any, Record<ParsePathParams<TPath>, string>>,
665
- ) => TParams
666
- stringifyParams: (
667
- params: TParams,
668
- ) => Record<ParsePathParams<TPath>, string>
669
- }
670
- ) &
705
+ | {
706
+ parseParams?: never
707
+ stringifyParams?: never
708
+ }
709
+ | {
710
+ // Parse params optionally receives path params as strings and returns them in a parsed format (like a number or boolean)
711
+ parseParams: (
712
+ rawParams: IsAny<TPath, any, Record<ParsePathParams<TPath>, string>>,
713
+ ) => TParams
714
+ stringifyParams: (
715
+ params: TParams,
716
+ ) => Record<ParsePathParams<TPath>, string>
717
+ }
718
+ ) &
671
719
  RouteLoaders<
672
720
  // Route Loaders (see below) can be inline on the route, or resolved async
673
721
  TRouteLoaderData,
@@ -779,7 +827,8 @@ export interface RouterState {
779
827
  matches: RouteMatch[]
780
828
  lastUpdated: number
781
829
  loaderData: unknown
782
- action?: ActionState
830
+ currentAction?: ActionState
831
+ latestAction?: ActionState
783
832
  actions: Record<string, Action>
784
833
  pending?: PendingState
785
834
  }
@@ -900,7 +949,8 @@ export interface Action<
900
949
  // TError = unknown,
901
950
  > {
902
951
  submit: (submission?: TPayload) => Promise<TResponse>
903
- latest?: ActionState
952
+ current?: ActionState<TPayload, TResponse>
953
+ latest?: ActionState<TPayload, TResponse>
904
954
  pending: ActionState<TPayload, TResponse>[]
905
955
  }
906
956
 
@@ -924,7 +974,7 @@ type RoutesById<TAllRouteInfo extends AnyAllRouteInfo> = {
924
974
  }
925
975
 
926
976
  // type RoutesByPath<TAllRouteInfo extends AnyAllRouteInfo> = {
927
- // [K in TAllRouteInfo['fullPath']]: Route<
977
+ // [K in TAllRouteInfo['routePaths']]: Route<
928
978
  // TAllRouteInfo,
929
979
  // TAllRouteInfo['routeInfoByFullPath'][K]
930
980
  // >
@@ -934,16 +984,16 @@ export type ValidFromPath<
934
984
  TAllRouteInfo extends AnyAllRouteInfo = DefaultAllRouteInfo,
935
985
  > =
936
986
  | undefined
937
- | (string extends TAllRouteInfo['fullPath']
987
+ | (string extends TAllRouteInfo['routePaths']
938
988
  ? string
939
- : TAllRouteInfo['fullPath'])
989
+ : TAllRouteInfo['routePaths'])
940
990
 
941
991
  // type ValidToPath<
942
992
  // TAllRouteInfo extends AnyAllRouteInfo = DefaultAllRouteInfo,
943
993
  // TFrom = undefined,
944
994
  // > = TFrom extends undefined
945
- // ? TAllRouteInfo['fullPath'] | `...unsafe-relative-path (cast "as any")`
946
- // : LooseAutocomplete<'.' | TAllRouteInfo['fullPath']>
995
+ // ? TAllRouteInfo['routePaths'] | `...unsafe-relative-path (cast "as any")`
996
+ // : LooseAutocomplete<'.' | TAllRouteInfo['routePaths']>
947
997
 
948
998
  export interface Router<
949
999
  TRouteConfig extends AnyRouteConfig = RouteConfig,
@@ -965,6 +1015,7 @@ export interface Router<
965
1015
  routeTree: Route<TAllRouteInfo, RouteInfo>
966
1016
  routesById: RoutesById<TAllRouteInfo>
967
1017
  navigationPromise: Promise<void>
1018
+ removeActionQueue: { action: Action; actionState: ActionState }[]
968
1019
  startedLoadingAt: number
969
1020
  destroy: () => void
970
1021
  resolveNavigation: () => void
@@ -1051,6 +1102,7 @@ export function createRouter<
1051
1102
  let router: Router<TRouteConfig, TAllRouteInfo> = {
1052
1103
  options: originalOptions,
1053
1104
  listeners: [],
1105
+ removeActionQueue: [],
1054
1106
  // Resolved after construction
1055
1107
  basepath: '',
1056
1108
  routeTree: undefined!,
@@ -1201,7 +1253,7 @@ export function createRouter<
1201
1253
 
1202
1254
  const toMatches = router.matchRoutes(pathname)
1203
1255
 
1204
- const prevParams = last(fromMatches)?.params
1256
+ const prevParams = { ...last(fromMatches)?.params }
1205
1257
 
1206
1258
  let nextParams =
1207
1259
  (dest.params ?? true) === true
@@ -1213,7 +1265,7 @@ export function createRouter<
1213
1265
  .map((d) => d.options.stringifyParams)
1214
1266
  .filter(Boolean)
1215
1267
  .forEach((fn) => {
1216
- Object.assign(nextParams!, fn!(nextParams!))
1268
+ Object.assign({}, nextParams!, fn!(nextParams!))
1217
1269
  })
1218
1270
  }
1219
1271
 
@@ -1358,10 +1410,23 @@ export function createRouter<
1358
1410
  router.startedLoadingAt = id
1359
1411
 
1360
1412
  if (next) {
1413
+ // If the location.href has changed
1414
+
1361
1415
  // Ingest the new location
1362
1416
  router.location = next
1363
1417
  }
1364
1418
 
1419
+ // Clear out old actions
1420
+ router.removeActionQueue.forEach(({ action, actionState }) => {
1421
+ if (router.state.currentAction === actionState) {
1422
+ router.state.currentAction = undefined
1423
+ }
1424
+ if (action.current === actionState) {
1425
+ action.current = undefined
1426
+ }
1427
+ })
1428
+ router.removeActionQueue = []
1429
+
1365
1430
  // Cancel any pending matches
1366
1431
  router.cancelMatches()
1367
1432
 
@@ -1370,14 +1435,6 @@ export function createRouter<
1370
1435
  strictParseParams: true,
1371
1436
  })
1372
1437
 
1373
- unloadedMatches.forEach((match, index) => {
1374
- const parent = unloadedMatches[index - 1]
1375
- const child = unloadedMatches[index + 1]
1376
-
1377
- if (parent) match.__.setParentMatch(parent)
1378
- if (child) match.__.addChildMatch(child)
1379
- })
1380
-
1381
1438
  router.state = {
1382
1439
  ...router.state,
1383
1440
  pending: {
@@ -1506,69 +1563,95 @@ export function createRouter<
1506
1563
  ...(router.state.pending?.matches ?? []),
1507
1564
  ]
1508
1565
 
1509
- const recurse = async (
1510
- routes: Route<any, any>[],
1511
- parentMatch?: RouteMatch,
1512
- ): Promise<void> => {
1566
+ const recurse = async (routes: Route<any, any>[]): Promise<void> => {
1567
+ const parentMatch = last(matches)
1513
1568
  let params = parentMatch?.params ?? {}
1514
1569
 
1515
1570
  const filteredRoutes = router.options.filterRoutes?.(routes) ?? routes
1516
1571
 
1517
- const route = filteredRoutes?.find((route) => {
1518
- const fuzzy = !!(route.routePath !== '/' || route.childRoutes?.length)
1519
- const matchParams = matchPathname(pathname, {
1520
- to: route.fullPath,
1521
- fuzzy,
1522
- caseSensitive:
1523
- route.options.caseSensitive ?? router.options.caseSensitive,
1524
- })
1572
+ let foundRoutes: Route[] = []
1525
1573
 
1526
- if (matchParams) {
1527
- let parsedParams
1574
+ const findMatchInRoutes = (parentRoutes: Route[], routes: Route[]) => {
1575
+ routes.some((route) => {
1576
+ if (!route.routePath && route.childRoutes?.length) {
1577
+ return findMatchInRoutes(
1578
+ [...foundRoutes, route],
1579
+ route.childRoutes,
1580
+ )
1581
+ }
1528
1582
 
1529
- try {
1530
- parsedParams =
1531
- route.options.parseParams?.(matchParams!) ?? matchParams
1532
- } catch (err) {
1533
- if (opts?.strictParseParams) {
1534
- throw err
1583
+ const fuzzy = !!(
1584
+ route.routePath !== '/' || route.childRoutes?.length
1585
+ )
1586
+
1587
+ const matchParams = matchPathname(pathname, {
1588
+ to: route.fullPath,
1589
+ fuzzy,
1590
+ caseSensitive:
1591
+ route.options.caseSensitive ?? router.options.caseSensitive,
1592
+ })
1593
+
1594
+ if (matchParams) {
1595
+ let parsedParams
1596
+
1597
+ try {
1598
+ parsedParams =
1599
+ route.options.parseParams?.(matchParams!) ?? matchParams
1600
+ } catch (err) {
1601
+ if (opts?.strictParseParams) {
1602
+ throw err
1603
+ }
1604
+ }
1605
+
1606
+ params = {
1607
+ ...params,
1608
+ ...parsedParams,
1535
1609
  }
1536
1610
  }
1537
1611
 
1538
- params = {
1539
- ...params,
1540
- ...parsedParams,
1612
+ if (!!matchParams) {
1613
+ foundRoutes = [...parentRoutes, route]
1541
1614
  }
1542
- }
1543
1615
 
1544
- return !!matchParams
1545
- })
1616
+ return !!foundRoutes.length
1617
+ })
1618
+
1619
+ return !!foundRoutes.length
1620
+ }
1621
+
1622
+ findMatchInRoutes([], filteredRoutes)
1546
1623
 
1547
- if (!route) {
1624
+ if (!foundRoutes.length) {
1548
1625
  return
1549
1626
  }
1550
1627
 
1551
- const interpolatedPath = interpolatePath(route.routePath, params)
1552
- const matchId = interpolatePath(route.routeId, params, true)
1628
+ foundRoutes.forEach((foundRoute) => {
1629
+ const interpolatedPath = interpolatePath(foundRoute.routePath, params)
1630
+ const matchId = interpolatePath(foundRoute.routeId, params, true)
1553
1631
 
1554
- const match =
1555
- existingMatches.find((d) => d.matchId === matchId) ||
1556
- router.preloadCache[matchId]?.match ||
1557
- createRouteMatch(router, route, {
1558
- matchId,
1559
- params,
1560
- pathname: joinPaths([pathname, interpolatedPath]),
1561
- })
1632
+ const match =
1633
+ existingMatches.find((d) => d.matchId === matchId) ||
1634
+ router.preloadCache[matchId]?.match ||
1635
+ createRouteMatch(router, foundRoute, {
1636
+ matchId,
1637
+ params,
1638
+ pathname: joinPaths([pathname, interpolatedPath]),
1639
+ })
1562
1640
 
1563
- matches.push(match)
1641
+ matches.push(match)
1642
+ })
1643
+
1644
+ const foundRoute = last(foundRoutes)!
1564
1645
 
1565
- if (route.childRoutes?.length) {
1566
- recurse(route.childRoutes, match)
1646
+ if (foundRoute.childRoutes?.length) {
1647
+ recurse(foundRoute.childRoutes)
1567
1648
  }
1568
1649
  }
1569
1650
 
1570
1651
  recurse([router.routeTree])
1571
1652
 
1653
+ cascadeLoaderData(matches)
1654
+
1572
1655
  return matches
1573
1656
  },
1574
1657
 
@@ -1690,14 +1773,10 @@ export function createRouter<
1690
1773
  isExternal = true
1691
1774
  } catch (e) {}
1692
1775
 
1693
- if (isExternal) {
1694
- if (process.env.NODE_ENV !== 'production') {
1695
- throw new Error(
1696
- 'Attempting to navigate to external url with router.navigate!',
1697
- )
1698
- }
1699
- return
1700
- }
1776
+ invariant(
1777
+ !isExternal,
1778
+ 'Attempting to navigate to external url with router.navigate!',
1779
+ )
1701
1780
 
1702
1781
  return router._navigate({
1703
1782
  from: fromString,
@@ -1867,6 +1946,7 @@ export interface Route<
1867
1946
  TRouteInfo extends AnyRouteInfo = RouteInfo,
1868
1947
  > {
1869
1948
  routeId: TRouteInfo['id']
1949
+ routeRouteId: TRouteInfo['routeId']
1870
1950
  routePath: TRouteInfo['path']
1871
1951
  fullPath: TRouteInfo['fullPath']
1872
1952
  parentRoute?: AnyRoute
@@ -1874,23 +1954,21 @@ export interface Route<
1874
1954
  options: RouteOptions
1875
1955
  router: Router<TAllRouteInfo['routeConfig'], TAllRouteInfo>
1876
1956
  buildLink: <TTo extends string = '.'>(
1877
- options: // CheckRelativePath<
1878
- // TAllRouteInfo,
1879
- // TRouteInfo['fullPath'],
1880
- // NoInfer<TTo>
1881
- // > &
1882
- Omit<LinkOptions<TAllRouteInfo, TRouteInfo['fullPath'], TTo>, 'from'>,
1957
+ options: Omit<
1958
+ LinkOptions<TAllRouteInfo, TRouteInfo['fullPath'], TTo>,
1959
+ 'from'
1960
+ >,
1883
1961
  ) => LinkInfo
1884
1962
  matchRoute: <
1885
1963
  TTo extends string = '.',
1886
1964
  TResolved extends string = ResolveRelativePath<TRouteInfo['id'], TTo>,
1887
1965
  >(
1888
- matchLocation: // CheckRelativePath<
1889
- // TAllRouteInfo,
1890
- // TRouteInfo['fullPath'],
1891
- // NoInfer<TTo>
1892
- // > &
1893
- Omit<ToOptions<TAllRouteInfo, TRouteInfo['fullPath'], TTo>, 'from'>,
1966
+ matchLocation: CheckRelativePath<
1967
+ TAllRouteInfo,
1968
+ TRouteInfo['fullPath'],
1969
+ NoInfer<TTo>
1970
+ > &
1971
+ Omit<ToOptions<TAllRouteInfo, TRouteInfo['fullPath'], TTo>, 'from'>,
1894
1972
  opts?: MatchRouteOptions,
1895
1973
  ) => RouteInfoByPath<TAllRouteInfo, TResolved>['allParams']
1896
1974
  navigate: <TTo extends string = '.'>(
@@ -1921,12 +1999,12 @@ export function createRoute<
1921
1999
  // ]).replace(new RegExp(`^${rootRouteId}`), '')
1922
2000
  // ) as TRouteInfo['id']
1923
2001
 
1924
- const { id: routeId, path: routePath, fullPath } = routeConfig
2002
+ const { id, routeId, path: routePath, fullPath } = routeConfig
1925
2003
 
1926
2004
  const action =
1927
- router.state.actions[routeId] ||
2005
+ router.state.actions[id] ||
1928
2006
  (() => {
1929
- router.state.actions[routeId] = {
2007
+ router.state.actions[id] = {
1930
2008
  pending: [],
1931
2009
  submit: async <T, U>(
1932
2010
  submission: T,
@@ -1944,12 +2022,14 @@ export function createRoute<
1944
2022
  submission,
1945
2023
  }
1946
2024
 
2025
+ action.current = actionState
1947
2026
  action.latest = actionState
1948
2027
  action.pending.push(actionState)
1949
2028
 
1950
2029
  router.state = {
1951
2030
  ...router.state,
1952
- action: actionState,
2031
+ currentAction: actionState,
2032
+ latestAction: actionState,
1953
2033
  }
1954
2034
 
1955
2035
  router.notify()
@@ -1969,18 +2049,17 @@ export function createRoute<
1969
2049
  actionState.status = 'error'
1970
2050
  } finally {
1971
2051
  action.pending = action.pending.filter((d) => d !== actionState)
1972
- if (actionState === router.state.action) {
1973
- router.state.action = undefined
1974
- }
2052
+ router.removeActionQueue.push({ action, actionState })
1975
2053
  router.notify()
1976
2054
  }
1977
2055
  },
1978
2056
  }
1979
- return router.state.actions[routeId]!
2057
+ return router.state.actions[id]!
1980
2058
  })()
1981
2059
 
1982
2060
  let route: Route<TAllRouteInfo, TRouteInfo> = {
1983
- routeId,
2061
+ routeId: id,
2062
+ routeRouteId: routeId,
1984
2063
  routePath,
1985
2064
  fullPath,
1986
2065
  options,
@@ -2099,7 +2178,8 @@ export type ToOptions<
2099
2178
  from?: TFrom
2100
2179
  // // When using relative route paths, this option forces resolution from the current path, instead of the route API's path or `from` path
2101
2180
  // fromCurrent?: boolean
2102
- } & SearchParamOptions<TAllRouteInfo, TFrom, TResolvedTo> &
2181
+ } & CheckPath<TAllRouteInfo, NoInfer<TResolvedTo>> &
2182
+ SearchParamOptions<TAllRouteInfo, TFrom, TResolvedTo> &
2103
2183
  PathParamOptions<TAllRouteInfo, TFrom, TResolvedTo>
2104
2184
 
2105
2185
  export type ToPathOption<
@@ -2109,7 +2189,7 @@ export type ToPathOption<
2109
2189
  > =
2110
2190
  | TTo
2111
2191
  | RelativeToPathAutoComplete<
2112
- TAllRouteInfo['fullPath'],
2192
+ TAllRouteInfo['routePaths'],
2113
2193
  NoInfer<TFrom> extends string ? NoInfer<TFrom> : '',
2114
2194
  NoInfer<TTo> & string
2115
2195
  >
@@ -2121,7 +2201,7 @@ export type ToIdOption<
2121
2201
  > =
2122
2202
  | TTo
2123
2203
  | RelativeToPathAutoComplete<
2124
- TAllRouteInfo['routeInfo']['id'],
2204
+ TAllRouteInfo['routeIds'],
2125
2205
  NoInfer<TFrom> extends string ? NoInfer<TFrom> : '',
2126
2206
  NoInfer<TTo> & string
2127
2207
  >
@@ -2151,23 +2231,33 @@ export type CheckRelativePath<
2151
2231
  TTo,
2152
2232
  > = TTo extends string
2153
2233
  ? TFrom extends string
2154
- ? ResolveRelativePath<TFrom, TTo> extends TAllRouteInfo['fullPath']
2234
+ ? ResolveRelativePath<TFrom, TTo> extends TAllRouteInfo['routePaths']
2155
2235
  ? {}
2156
2236
  : {
2157
2237
  Error: `${TFrom} + ${TTo} resolves to ${ResolveRelativePath<
2158
2238
  TFrom,
2159
2239
  TTo
2160
2240
  >}, which is not a valid route path.`
2161
- 'Valid Route Paths': TAllRouteInfo['fullPath']
2241
+ 'Valid Route Paths': TAllRouteInfo['routePaths']
2162
2242
  }
2163
2243
  : {}
2164
2244
  : {}
2165
2245
 
2166
- export type ResolveRelativePath<
2167
- TFrom,
2168
- TTo = '.',
2169
- TRooted = false,
2170
- > = TFrom extends string
2246
+ export type CheckPath<TAllRouteInfo extends AnyAllRouteInfo, TPath> = Exclude<
2247
+ TPath,
2248
+ TAllRouteInfo['routePaths']
2249
+ > extends never
2250
+ ? {}
2251
+ : CheckPathError<TAllRouteInfo, Exclude<TPath, TAllRouteInfo['routePaths']>>
2252
+
2253
+ export type CheckPathError<TAllRouteInfo extends AnyAllRouteInfo, TInvalids> = {
2254
+ Error: `${TInvalids extends string
2255
+ ? TInvalids
2256
+ : never} is not a valid route path.`
2257
+ 'Valid Route Paths': TAllRouteInfo['routePaths']
2258
+ }
2259
+
2260
+ export type ResolveRelativePath<TFrom, TTo = '.'> = TFrom extends string
2171
2261
  ? TTo extends string
2172
2262
  ? TTo extends '.'
2173
2263
  ? TFrom
@@ -2215,21 +2305,19 @@ type SearchParamOptions<
2215
2305
  TTo,
2216
2306
  TFromSchema = RouteInfoByPath<TAllRouteInfo, TFrom>['fullSearchSchema'],
2217
2307
  TToSchema = RouteInfoByPath<TAllRouteInfo, TTo>['fullSearchSchema'],
2218
- > =
2219
- // If the next route search extend or cover the from route, params will be optional
2220
- StartsWith<TFrom, TTo> extends true
2221
- ? {
2222
- search?: SearchReducer<TFromSchema, TToSchema>
2223
- }
2224
- : // Optional search params? Allow it
2225
- keyof PickRequired<TToSchema> extends never
2226
- ? {
2227
- search?: SearchReducer<TFromSchema, TToSchema>
2228
- }
2229
- : {
2230
- // Must have required search params, enforce it
2231
- search: SearchReducer<TFromSchema, TToSchema>
2232
- }
2308
+ > = StartsWith<TFrom, TTo> extends true // If the next route search extend or cover the from route, params will be optional
2309
+ ? {
2310
+ search?: SearchReducer<TFromSchema, TToSchema>
2311
+ }
2312
+ : // Optional search params? Allow it
2313
+ keyof PickRequired<TToSchema> extends never
2314
+ ? {
2315
+ search?: SearchReducer<TFromSchema, TToSchema>
2316
+ }
2317
+ : {
2318
+ // Must have required search params, enforce it
2319
+ search: SearchReducer<TFromSchema, TToSchema>
2320
+ }
2233
2321
 
2234
2322
  type SearchReducer<TFrom, TTo> = TTo | ((current: TFrom) => TTo)
2235
2323
 
@@ -2297,8 +2385,8 @@ export interface RouteMatch<
2297
2385
  }) => void)
2298
2386
  abortController: AbortController
2299
2387
  latestId: string
2300
- setParentMatch: (parentMatch: RouteMatch) => void
2301
- addChildMatch: (childMatch: RouteMatch) => void
2388
+ // setParentMatch: (parentMatch: RouteMatch) => void
2389
+ // addChildMatch: (childMatch: RouteMatch) => void
2302
2390
  validate: () => void
2303
2391
  startPending: () => void
2304
2392
  cancelPending: () => void
@@ -2373,18 +2461,18 @@ export function createRouteMatch<
2373
2461
  clearTimeout(routeMatch.__.pendingMinTimeout)
2374
2462
  delete routeMatch.__.pendingMinPromise
2375
2463
  },
2376
- setParentMatch: (parentMatch?: RouteMatch) => {
2377
- routeMatch.parentMatch = parentMatch
2378
- },
2379
- addChildMatch: (childMatch: RouteMatch) => {
2380
- if (
2381
- routeMatch.childMatches.find((d) => d.matchId === childMatch.matchId)
2382
- ) {
2383
- return
2384
- }
2385
-
2386
- routeMatch.childMatches.push(childMatch)
2387
- },
2464
+ // setParentMatch: (parentMatch?: RouteMatch) => {
2465
+ // routeMatch.parentMatch = parentMatch
2466
+ // },
2467
+ // addChildMatch: (childMatch: RouteMatch) => {
2468
+ // if (
2469
+ // routeMatch.childMatches.find((d) => d.matchId === childMatch.matchId)
2470
+ // ) {
2471
+ // return
2472
+ // }
2473
+
2474
+ // routeMatch.childMatches.push(childMatch)
2475
+ // },
2388
2476
  validate: () => {
2389
2477
  // Validate the search params and stabilize them
2390
2478
  const parentSearch =
@@ -2513,7 +2601,6 @@ export function createRouteMatch<
2513
2601
  data,
2514
2602
  )
2515
2603
 
2516
- cascadeLoaderData(routeMatch)
2517
2604
  routeMatch.error = undefined
2518
2605
  routeMatch.status = 'success'
2519
2606
  routeMatch.updatedAt = Date.now()
@@ -2571,19 +2658,17 @@ export function createRouteMatch<
2571
2658
  return routeMatch
2572
2659
  }
2573
2660
 
2574
- function cascadeLoaderData(routeMatch: RouteMatch<any, any>) {
2575
- if (routeMatch.parentMatch) {
2576
- routeMatch.loaderData = replaceEqualDeep(routeMatch.loaderData, {
2577
- ...routeMatch.parentMatch.loaderData,
2578
- ...routeMatch.routeLoaderData,
2579
- })
2580
- }
2661
+ function cascadeLoaderData(matches: RouteMatch<any, any>[]) {
2662
+ matches.forEach((match, index) => {
2663
+ const parent = matches[index - 1]
2581
2664
 
2582
- if (routeMatch.childMatches.length) {
2583
- routeMatch.childMatches.forEach((childMatch) => {
2584
- cascadeLoaderData(childMatch)
2585
- })
2586
- }
2665
+ if (parent) {
2666
+ match.loaderData = replaceEqualDeep(match.loaderData, {
2667
+ ...parent.loaderData,
2668
+ ...match.routeLoaderData,
2669
+ })
2670
+ }
2671
+ })
2587
2672
  }
2588
2673
 
2589
2674
  export function matchPathname(