@tanstack/router-core 1.120.7 → 1.121.0-alpha.11

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 (51) hide show
  1. package/dist/cjs/fileRoute.d.cts +6 -2
  2. package/dist/cjs/index.cjs +3 -0
  3. package/dist/cjs/index.cjs.map +1 -1
  4. package/dist/cjs/index.d.cts +7 -7
  5. package/dist/cjs/link.cjs.map +1 -1
  6. package/dist/cjs/link.d.cts +18 -1
  7. package/dist/cjs/path.cjs +130 -16
  8. package/dist/cjs/path.cjs.map +1 -1
  9. package/dist/cjs/path.d.cts +17 -0
  10. package/dist/cjs/redirect.cjs +17 -7
  11. package/dist/cjs/redirect.cjs.map +1 -1
  12. package/dist/cjs/redirect.d.cts +13 -7
  13. package/dist/cjs/route.cjs +12 -1
  14. package/dist/cjs/route.cjs.map +1 -1
  15. package/dist/cjs/route.d.cts +18 -27
  16. package/dist/cjs/router.cjs +395 -335
  17. package/dist/cjs/router.cjs.map +1 -1
  18. package/dist/cjs/router.d.cts +48 -8
  19. package/dist/cjs/typePrimitives.d.cts +2 -2
  20. package/dist/cjs/utils.cjs.map +1 -1
  21. package/dist/cjs/utils.d.cts +3 -0
  22. package/dist/esm/fileRoute.d.ts +6 -2
  23. package/dist/esm/index.d.ts +7 -7
  24. package/dist/esm/index.js +5 -2
  25. package/dist/esm/link.d.ts +18 -1
  26. package/dist/esm/link.js.map +1 -1
  27. package/dist/esm/path.d.ts +17 -0
  28. package/dist/esm/path.js +130 -16
  29. package/dist/esm/path.js.map +1 -1
  30. package/dist/esm/redirect.d.ts +13 -7
  31. package/dist/esm/redirect.js +17 -7
  32. package/dist/esm/redirect.js.map +1 -1
  33. package/dist/esm/route.d.ts +18 -27
  34. package/dist/esm/route.js +12 -1
  35. package/dist/esm/route.js.map +1 -1
  36. package/dist/esm/router.d.ts +48 -8
  37. package/dist/esm/router.js +398 -338
  38. package/dist/esm/router.js.map +1 -1
  39. package/dist/esm/typePrimitives.d.ts +2 -2
  40. package/dist/esm/utils.d.ts +3 -0
  41. package/dist/esm/utils.js.map +1 -1
  42. package/package.json +2 -2
  43. package/src/fileRoute.ts +90 -1
  44. package/src/index.ts +14 -8
  45. package/src/link.ts +97 -11
  46. package/src/path.ts +181 -16
  47. package/src/redirect.ts +39 -16
  48. package/src/route.ts +91 -64
  49. package/src/router.ts +569 -434
  50. package/src/typePrimitives.ts +2 -2
  51. package/src/utils.ts +15 -0
package/src/path.ts CHANGED
@@ -5,6 +5,9 @@ import type { AnyPathParams } from './route'
5
5
  export interface Segment {
6
6
  type: 'pathname' | 'param' | 'wildcard'
7
7
  value: string
8
+ // Add a new property to store the static segment if present
9
+ prefixSegment?: string
10
+ suffixSegment?: string
8
11
  }
9
12
 
10
13
  export function joinPaths(paths: Array<string | undefined>) {
@@ -137,10 +140,52 @@ export function resolvePath({
137
140
  }
138
141
  }
139
142
 
140
- const joined = joinPaths([basepath, ...baseSegments.map((d) => d.value)])
143
+ const segmentValues = baseSegments.map((segment) => {
144
+ if (segment.type === 'param') {
145
+ const param = segment.value.substring(1)
146
+ if (segment.prefixSegment && segment.suffixSegment) {
147
+ return `${segment.prefixSegment}{$${param}}${segment.suffixSegment}`
148
+ } else if (segment.prefixSegment) {
149
+ return `${segment.prefixSegment}{$${param}}`
150
+ } else if (segment.suffixSegment) {
151
+ return `{$${param}}${segment.suffixSegment}`
152
+ }
153
+ }
154
+ if (segment.type === 'wildcard') {
155
+ if (segment.prefixSegment && segment.suffixSegment) {
156
+ return `${segment.prefixSegment}{$}${segment.suffixSegment}`
157
+ } else if (segment.prefixSegment) {
158
+ return `${segment.prefixSegment}{$}`
159
+ } else if (segment.suffixSegment) {
160
+ return `{$}${segment.suffixSegment}`
161
+ }
162
+ }
163
+ return segment.value
164
+ })
165
+ const joined = joinPaths([basepath, ...segmentValues])
141
166
  return cleanPath(joined)
142
167
  }
143
168
 
169
+ const PARAM_RE = /^\$.{1,}$/ // $paramName
170
+ const PARAM_W_CURLY_BRACES_RE = /^(.*?)\{(\$[a-zA-Z_$][a-zA-Z0-9_$]*)\}(.*)$/ // prefix{$paramName}suffix
171
+ const WILDCARD_RE = /^\$$/ // $
172
+ const WILDCARD_W_CURLY_BRACES_RE = /^(.*?)\{\$\}(.*)$/ // prefix{$}suffix
173
+
174
+ /**
175
+ * Required: `/foo/$bar` ✅
176
+ * Prefix and Suffix: `/foo/prefix${bar}suffix` ✅
177
+ * Wildcard: `/foo/$` ✅
178
+ * Wildcard with Prefix and Suffix: `/foo/prefix{$}suffix` ✅
179
+ *
180
+ * Future:
181
+ * Optional: `/foo/{-bar}`
182
+ * Optional named segment: `/foo/{bar}`
183
+ * Optional named segment with Prefix and Suffix: `/foo/prefix{-bar}suffix`
184
+ * Escape special characters:
185
+ * - `/foo/[$]` - Static route
186
+ * - `/foo/[$]{$foo} - Dynamic route with a static prefix of `$`
187
+ * - `/foo/{$foo}[$]` - Dynamic route with a static suffix of `$`
188
+ */
144
189
  export function parsePathname(pathname?: string): Array<Segment> {
145
190
  if (!pathname) {
146
191
  return []
@@ -167,20 +212,55 @@ export function parsePathname(pathname?: string): Array<Segment> {
167
212
 
168
213
  segments.push(
169
214
  ...split.map((part): Segment => {
170
- if (part === '$' || part === '*') {
215
+ // Check for wildcard with curly braces: prefix{$}suffix
216
+ const wildcardBracesMatch = part.match(WILDCARD_W_CURLY_BRACES_RE)
217
+ if (wildcardBracesMatch) {
218
+ const prefix = wildcardBracesMatch[1]
219
+ const suffix = wildcardBracesMatch[2]
171
220
  return {
172
221
  type: 'wildcard',
173
- value: part,
222
+ value: '$',
223
+ prefixSegment: prefix || undefined,
224
+ suffixSegment: suffix || undefined,
174
225
  }
175
226
  }
176
227
 
177
- if (part.charAt(0) === '$') {
228
+ // Check for the new parameter format: prefix{$paramName}suffix
229
+ const paramBracesMatch = part.match(PARAM_W_CURLY_BRACES_RE)
230
+ if (paramBracesMatch) {
231
+ const prefix = paramBracesMatch[1]
232
+ const paramName = paramBracesMatch[2]
233
+ const suffix = paramBracesMatch[3]
178
234
  return {
179
235
  type: 'param',
180
- value: part,
236
+ value: '' + paramName,
237
+ prefixSegment: prefix || undefined,
238
+ suffixSegment: suffix || undefined,
181
239
  }
182
240
  }
183
241
 
242
+ // Check for bare parameter format: $paramName (without curly braces)
243
+ if (PARAM_RE.test(part)) {
244
+ const paramName = part.substring(1)
245
+ return {
246
+ type: 'param',
247
+ value: '$' + paramName,
248
+ prefixSegment: undefined,
249
+ suffixSegment: undefined,
250
+ }
251
+ }
252
+
253
+ // Check for bare wildcard: $ (without curly braces)
254
+ if (WILDCARD_RE.test(part)) {
255
+ return {
256
+ type: 'wildcard',
257
+ value: '$',
258
+ prefixSegment: undefined,
259
+ suffixSegment: undefined,
260
+ }
261
+ }
262
+
263
+ // Handle regular pathname segment
184
264
  return {
185
265
  type: 'pathname',
186
266
  value: part.includes('%25')
@@ -248,9 +328,13 @@ export function interpolatePath({
248
328
  interpolatedPathSegments.map((segment) => {
249
329
  if (segment.type === 'wildcard') {
250
330
  usedParams._splat = params._splat
331
+ const segmentPrefix = segment.prefixSegment || ''
332
+ const segmentSuffix = segment.suffixSegment || ''
251
333
  const value = encodeParam('_splat')
252
- if (leaveWildcards) return `${segment.value}${value ?? ''}`
253
- return value
334
+ if (leaveWildcards) {
335
+ return `${segmentPrefix}${segment.value}${value ?? ''}${segmentSuffix}`
336
+ }
337
+ return `${segmentPrefix}${value}${segmentSuffix}`
254
338
  }
255
339
 
256
340
  if (segment.type === 'param') {
@@ -259,11 +343,14 @@ export function interpolatePath({
259
343
  isMissingParams = true
260
344
  }
261
345
  usedParams[key] = params[key]
346
+
347
+ const segmentPrefix = segment.prefixSegment || ''
348
+ const segmentSuffix = segment.suffixSegment || ''
262
349
  if (leaveParams) {
263
350
  const value = encodeParam(segment.value)
264
- return `${segment.value}${value ?? ''}`
351
+ return `${segmentPrefix}${segment.value}${value ?? ''}${segmentSuffix}`
265
352
  }
266
- return encodeParam(key) ?? 'undefined'
353
+ return `${segmentPrefix}${encodeParam(key) ?? 'undefined'}${segmentSuffix}`
267
354
  }
268
355
 
269
356
  return segment.value
@@ -390,9 +477,57 @@ export function matchByPath(
390
477
 
391
478
  if (routeSegment) {
392
479
  if (routeSegment.type === 'wildcard') {
393
- const _splat = decodeURI(
394
- joinPaths(baseSegments.slice(i).map((d) => d.value)),
395
- )
480
+ // Capture all remaining segments for a wildcard
481
+ const remainingBaseSegments = baseSegments.slice(i)
482
+
483
+ let _splat: string
484
+
485
+ // If this is a wildcard with prefix/suffix, we need to handle the first segment specially
486
+ if (routeSegment.prefixSegment || routeSegment.suffixSegment) {
487
+ if (!baseSegment) return false
488
+
489
+ const prefix = routeSegment.prefixSegment || ''
490
+ const suffix = routeSegment.suffixSegment || ''
491
+
492
+ // Check if the base segment starts with prefix and ends with suffix
493
+ const baseValue = baseSegment.value
494
+ if ('prefixSegment' in routeSegment) {
495
+ if (!baseValue.startsWith(prefix)) {
496
+ return false
497
+ }
498
+ }
499
+ if ('suffixSegment' in routeSegment) {
500
+ if (
501
+ !baseSegments[baseSegments.length - 1]?.value.endsWith(suffix)
502
+ ) {
503
+ return false
504
+ }
505
+ }
506
+
507
+ let rejoinedSplat = decodeURI(
508
+ joinPaths(remainingBaseSegments.map((d) => d.value)),
509
+ )
510
+
511
+ // Remove the prefix and suffix from the rejoined splat
512
+ if (prefix && rejoinedSplat.startsWith(prefix)) {
513
+ rejoinedSplat = rejoinedSplat.slice(prefix.length)
514
+ }
515
+
516
+ if (suffix && rejoinedSplat.endsWith(suffix)) {
517
+ rejoinedSplat = rejoinedSplat.slice(
518
+ 0,
519
+ rejoinedSplat.length - suffix.length,
520
+ )
521
+ }
522
+
523
+ _splat = rejoinedSplat
524
+ } else {
525
+ // If no prefix/suffix, just rejoin the remaining segments
526
+ _splat = decodeURI(
527
+ joinPaths(remainingBaseSegments.map((d) => d.value)),
528
+ )
529
+ }
530
+
396
531
  // TODO: Deprecate *
397
532
  params['*'] = _splat
398
533
  params['_splat'] = _splat
@@ -426,11 +561,41 @@ export function matchByPath(
426
561
  if (baseSegment.value === '/') {
427
562
  return false
428
563
  }
429
- if (baseSegment.value.charAt(0) !== '$') {
430
- params[routeSegment.value.substring(1)] = decodeURIComponent(
431
- baseSegment.value,
432
- )
564
+
565
+ let _paramValue: string
566
+
567
+ // If this param has prefix/suffix, we need to extract the actual parameter value
568
+ if (routeSegment.prefixSegment || routeSegment.suffixSegment) {
569
+ const prefix = routeSegment.prefixSegment || ''
570
+ const suffix = routeSegment.suffixSegment || ''
571
+
572
+ // Check if the base segment starts with prefix and ends with suffix
573
+ const baseValue = baseSegment.value
574
+ if (prefix && !baseValue.startsWith(prefix)) {
575
+ return false
576
+ }
577
+ if (suffix && !baseValue.endsWith(suffix)) {
578
+ return false
579
+ }
580
+
581
+ let paramValue = baseValue
582
+ if (prefix && paramValue.startsWith(prefix)) {
583
+ paramValue = paramValue.slice(prefix.length)
584
+ }
585
+ if (suffix && paramValue.endsWith(suffix)) {
586
+ paramValue = paramValue.slice(
587
+ 0,
588
+ paramValue.length - suffix.length,
589
+ )
590
+ }
591
+
592
+ _paramValue = decodeURIComponent(paramValue)
593
+ } else {
594
+ // If no prefix/suffix, just decode the base segment value
595
+ _paramValue = decodeURIComponent(baseSegment.value)
433
596
  }
597
+
598
+ params[routeSegment.value.substring(1)] = _paramValue
434
599
  }
435
600
  }
436
601
 
package/src/redirect.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import type { NavigateOptions } from './link'
2
2
  import type { AnyRouter, RegisteredRouter } from './router'
3
- import type { PickAsRequired } from './utils'
4
3
 
5
4
  export type AnyRedirect = Redirect<any, any, any, any, any>
6
5
 
@@ -13,6 +12,17 @@ export type Redirect<
13
12
  TTo extends string | undefined = undefined,
14
13
  TMaskFrom extends string = TFrom,
15
14
  TMaskTo extends string = '.',
15
+ > = Response & {
16
+ options: NavigateOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>
17
+ redirectHandled?: boolean
18
+ }
19
+
20
+ export type RedirectOptions<
21
+ TRouter extends AnyRouter = RegisteredRouter,
22
+ TFrom extends string = string,
23
+ TTo extends string | undefined = undefined,
24
+ TMaskFrom extends string = TFrom,
25
+ TMaskTo extends string = '.',
16
26
  > = {
17
27
  href?: string
18
28
  /**
@@ -42,12 +52,7 @@ export type ResolvedRedirect<
42
52
  TTo extends string = '',
43
53
  TMaskFrom extends string = TFrom,
44
54
  TMaskTo extends string = '',
45
- > = PickAsRequired<
46
- Redirect<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>,
47
- 'code' | 'statusCode' | 'headers'
48
- > & {
49
- href: string
50
- }
55
+ > = Redirect<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>
51
56
 
52
57
  export function redirect<
53
58
  TRouter extends AnyRouter = RegisteredRouter,
@@ -56,30 +61,48 @@ export function redirect<
56
61
  const TMaskFrom extends string = TFrom,
57
62
  const TMaskTo extends string = '',
58
63
  >(
59
- opts: Redirect<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>,
64
+ opts: RedirectOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>,
60
65
  ): Redirect<TRouter, TFrom, TTo, TMaskFrom, TMaskTo> {
61
- ;(opts as any).isRedirect = true
62
66
  opts.statusCode = opts.statusCode || opts.code || 307
63
- opts.headers = opts.headers || {}
67
+
64
68
  if (!opts.reloadDocument) {
65
- opts.reloadDocument = false
66
69
  try {
67
70
  new URL(`${opts.href}`)
68
71
  opts.reloadDocument = true
69
72
  } catch {}
70
73
  }
71
74
 
75
+ const headers = new Headers(opts.headers || {})
76
+
77
+ const response = new Response(null, {
78
+ status: opts.statusCode,
79
+ headers,
80
+ })
81
+
82
+ ;(response as Redirect<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>).options =
83
+ opts
84
+
72
85
  if (opts.throw) {
73
- throw opts
86
+ throw response
74
87
  }
75
88
 
76
- return opts
89
+ return response as Redirect<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>
77
90
  }
78
91
 
79
92
  export function isRedirect(obj: any): obj is AnyRedirect {
80
- return !!obj?.isRedirect
93
+ return obj instanceof Response && !!(obj as any).options
81
94
  }
82
95
 
83
- export function isResolvedRedirect(obj: any): obj is ResolvedRedirect {
84
- return !!obj?.isRedirect && obj.href
96
+ export function isResolvedRedirect(
97
+ obj: any,
98
+ ): obj is AnyRedirect & { options: { href: string } } {
99
+ return isRedirect(obj) && !!obj.options.href
100
+ }
101
+
102
+ export function parseRedirect(obj: any) {
103
+ if (typeof obj === 'object' && obj.isSerializedRedirect) {
104
+ return redirect(obj)
105
+ }
106
+
107
+ return undefined
85
108
  }
package/src/route.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import invariant from 'tiny-invariant'
1
2
  import { joinPaths, trimPathLeft } from './path'
2
3
  import { notFound } from './not-found'
3
4
  import { rootRouteId } from './root'
@@ -17,9 +18,12 @@ import type { AnyRouter, RegisteredRouter } from './router'
17
18
  import type { BuildLocationFn, NavigateFn } from './RouterProvider'
18
19
  import type {
19
20
  Assign,
21
+ Awaitable,
20
22
  Constrain,
21
23
  Expand,
22
24
  IntersectAssign,
25
+ LooseAsyncReturnType,
26
+ LooseReturnType,
23
27
  NoInfer,
24
28
  } from './utils'
25
29
  import type {
@@ -150,27 +154,24 @@ export type ResolveSearchSchema<TSearchValidator> =
150
154
  ? ResolveSearchSchemaFn<TSearchValidator['parse']>
151
155
  : ResolveSearchSchemaFn<TSearchValidator>
152
156
 
153
- export type ParseSplatParams<TPath extends string> = TPath &
154
- `${string}$` extends never
155
- ? TPath & `${string}$/${string}` extends never
156
- ? never
157
- : '_splat'
158
- : '_splat'
157
+ export type ResolveRequiredParams<TPath extends string, T> = {
158
+ [K in ParsePathParams<TPath>['required']]: T
159
+ }
159
160
 
160
- export interface SplatParams {
161
- _splat?: string
161
+ export type ResolveOptionalParams<TPath extends string, T> = {
162
+ [K in ParsePathParams<TPath>['optional']]?: T
162
163
  }
163
164
 
164
- export type ResolveParams<TPath extends string> =
165
- ParseSplatParams<TPath> extends never
166
- ? Record<ParsePathParams<TPath>, string>
167
- : Record<ParsePathParams<TPath>, string> & SplatParams
165
+ export type ResolveParams<
166
+ TPath extends string,
167
+ T = string,
168
+ > = ResolveRequiredParams<TPath, T> & ResolveOptionalParams<TPath, T>
168
169
 
169
170
  export type ParseParamsFn<in out TPath extends string, in out TParams> = (
170
- rawParams: ResolveParams<TPath>,
171
- ) => TParams extends Record<ParsePathParams<TPath>, any>
171
+ rawParams: Expand<ResolveParams<TPath>>,
172
+ ) => TParams extends ResolveParams<TPath, any>
172
173
  ? TParams
173
- : Record<ParsePathParams<TPath>, any>
174
+ : ResolveParams<TPath, any>
174
175
 
175
176
  export type StringifyParamsFn<in out TPath extends string, in out TParams> = (
176
177
  params: TParams,
@@ -270,20 +271,6 @@ export type TrimPathRight<T extends string> = T extends '/'
270
271
  ? TrimPathRight<U>
271
272
  : T
272
273
 
273
- export type LooseReturnType<T> = T extends (
274
- ...args: Array<any>
275
- ) => infer TReturn
276
- ? TReturn
277
- : never
278
-
279
- export type LooseAsyncReturnType<T> = T extends (
280
- ...args: Array<any>
281
- ) => infer TReturn
282
- ? TReturn extends Promise<infer TReturn>
283
- ? TReturn
284
- : TReturn
285
- : never
286
-
287
274
  export type ContextReturnType<TContextFn> = unknown extends TContextFn
288
275
  ? TContextFn
289
276
  : LooseReturnType<TContextFn> extends never
@@ -448,7 +435,7 @@ export interface RouteExtensions<in out TId, in out TFullPath> {
448
435
  }
449
436
 
450
437
  export type RouteLazyFn<TRoute extends AnyRoute> = (
451
- lazyFn: () => Promise<LazyRoute>,
438
+ lazyFn: () => Promise<LazyRoute<TRoute>>,
452
439
  ) => TRoute
453
440
 
454
441
  export type RouteAddChildrenFn<
@@ -602,7 +589,26 @@ export interface Route<
602
589
  >
603
590
  isRoot: TParentRoute extends AnyRoute ? true : false
604
591
  _componentsPromise?: Promise<Array<void>>
605
- lazyFn?: () => Promise<LazyRoute>
592
+ lazyFn?: () => Promise<
593
+ LazyRoute<
594
+ Route<
595
+ TParentRoute,
596
+ TPath,
597
+ TFullPath,
598
+ TCustomId,
599
+ TId,
600
+ TSearchValidator,
601
+ TParams,
602
+ TRouterContext,
603
+ TRouteContextFn,
604
+ TBeforeLoadFn,
605
+ TLoaderDeps,
606
+ TLoaderFn,
607
+ TChildren,
608
+ TFileRouteTypes
609
+ >
610
+ >
611
+ >
606
612
  _lazyPromise?: Promise<void>
607
613
  rank: number
608
614
  to: TrimPathRight<TFullPath>
@@ -1100,7 +1106,7 @@ export interface UpdatableRouteOptions<
1100
1106
  TBeforeLoadFn,
1101
1107
  TLoaderDeps
1102
1108
  >,
1103
- ) => Record<string, string>
1109
+ ) => Awaitable<Record<string, string>>
1104
1110
  head?: (
1105
1111
  ctx: AssetFnContextOptions<
1106
1112
  TRouteId,
@@ -1114,11 +1120,11 @@ export interface UpdatableRouteOptions<
1114
1120
  TBeforeLoadFn,
1115
1121
  TLoaderDeps
1116
1122
  >,
1117
- ) => {
1123
+ ) => Awaitable<{
1118
1124
  links?: AnyRouteMatch['links']
1119
1125
  scripts?: AnyRouteMatch['headScripts']
1120
1126
  meta?: AnyRouteMatch['meta']
1121
- }
1127
+ }>
1122
1128
  scripts?: (
1123
1129
  ctx: AssetFnContextOptions<
1124
1130
  TRouteId,
@@ -1132,7 +1138,7 @@ export interface UpdatableRouteOptions<
1132
1138
  TBeforeLoadFn,
1133
1139
  TLoaderDeps
1134
1140
  >,
1135
- ) => AnyRouteMatch['scripts']
1141
+ ) => Awaitable<AnyRouteMatch['scripts']>
1136
1142
  ssr?: boolean
1137
1143
  codeSplitGroupings?: Array<
1138
1144
  Array<
@@ -1298,7 +1304,24 @@ export class BaseRoute<
1298
1304
  in out TLoaderFn = undefined,
1299
1305
  in out TChildren = unknown,
1300
1306
  in out TFileRouteTypes = unknown,
1301
- > {
1307
+ > implements
1308
+ Route<
1309
+ TParentRoute,
1310
+ TPath,
1311
+ TFullPath,
1312
+ TCustomId,
1313
+ TId,
1314
+ TSearchValidator,
1315
+ TParams,
1316
+ TRouterContext,
1317
+ TRouteContextFn,
1318
+ TBeforeLoadFn,
1319
+ TLoaderDeps,
1320
+ TLoaderFn,
1321
+ TChildren,
1322
+ TFileRouteTypes
1323
+ >
1324
+ {
1302
1325
  isRoot: TParentRoute extends AnyRoute ? true : false
1303
1326
  options: RouteOptions<
1304
1327
  TParentRoute,
@@ -1347,7 +1370,26 @@ export class BaseRoute<
1347
1370
  children?: TChildren
1348
1371
  originalIndex?: number
1349
1372
  rank!: number
1350
- lazyFn?: () => Promise<LazyRoute>
1373
+ lazyFn?: () => Promise<
1374
+ LazyRoute<
1375
+ Route<
1376
+ TParentRoute,
1377
+ TPath,
1378
+ TFullPath,
1379
+ TCustomId,
1380
+ TId,
1381
+ TSearchValidator,
1382
+ TParams,
1383
+ TRouterContext,
1384
+ TRouteContextFn,
1385
+ TBeforeLoadFn,
1386
+ TLoaderDeps,
1387
+ TLoaderFn,
1388
+ TChildren,
1389
+ TFileRouteTypes
1390
+ >
1391
+ >
1392
+ >
1351
1393
  _lazyPromise?: Promise<void>
1352
1394
  _componentsPromise?: Promise<Array<void>>
1353
1395
 
@@ -1420,7 +1462,8 @@ export class BaseRoute<
1420
1462
  if (isRoot) {
1421
1463
  this._path = rootRouteId as TPath
1422
1464
  } else if (!this.parentRoute) {
1423
- throw new Error(
1465
+ invariant(
1466
+ false,
1424
1467
  `Child Route instances must pass a 'getParentRoute: () => ParentRoute' option that returns a Route instance.`,
1425
1468
  )
1426
1469
  }
@@ -1460,6 +1503,16 @@ export class BaseRoute<
1460
1503
  this._ssr = options?.ssr ?? opts.defaultSsr ?? true
1461
1504
  }
1462
1505
 
1506
+ clone = (other: typeof this) => {
1507
+ this._path = other._path
1508
+ this._id = other._id
1509
+ this._fullPath = other._fullPath
1510
+ this._to = other._to
1511
+ this._ssr = other._ssr
1512
+ this.options.getParentRoute = other.options.getParentRoute
1513
+ this.children = other.children
1514
+ }
1515
+
1463
1516
  addChildren: RouteAddChildrenFn<
1464
1517
  TParentRoute,
1465
1518
  TPath,
@@ -1608,32 +1661,6 @@ export class BaseRouteApi<TId, TRouter extends AnyRouter = RegisteredRouter> {
1608
1661
  }
1609
1662
  }
1610
1663
 
1611
- export interface RootRoute<
1612
- in out TSearchValidator = undefined,
1613
- in out TRouterContext = {},
1614
- in out TRouteContextFn = AnyContext,
1615
- in out TBeforeLoadFn = AnyContext,
1616
- in out TLoaderDeps extends Record<string, any> = {},
1617
- in out TLoaderFn = undefined,
1618
- in out TChildren = unknown,
1619
- in out TFileRouteTypes = unknown,
1620
- > extends Route<
1621
- any, // TParentRoute
1622
- '/', // TPath
1623
- '/', // TFullPath
1624
- string, // TCustomId
1625
- RootRouteId, // TId
1626
- TSearchValidator, // TSearchValidator
1627
- {}, // TParams
1628
- TRouterContext,
1629
- TRouteContextFn,
1630
- TBeforeLoadFn,
1631
- TLoaderDeps,
1632
- TLoaderFn,
1633
- TChildren, // TChildren
1634
- TFileRouteTypes
1635
- > {}
1636
-
1637
1664
  export class BaseRootRoute<
1638
1665
  in out TSearchValidator = undefined,
1639
1666
  in out TRouterContext = {},