@tanstack/router-core 1.120.5 → 1.121.0-alpha.3

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 +6 -6
  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 -14
  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 +19 -18
  16. package/dist/cjs/router.cjs +268 -195
  17. package/dist/cjs/router.cjs.map +1 -1
  18. package/dist/cjs/router.d.cts +46 -3
  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 +6 -6
  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 -14
  32. package/dist/esm/redirect.js.map +1 -1
  33. package/dist/esm/route.d.ts +19 -18
  34. package/dist/esm/route.js +12 -1
  35. package/dist/esm/route.js.map +1 -1
  36. package/dist/esm/router.d.ts +46 -3
  37. package/dist/esm/router.js +271 -198
  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 -6
  45. package/src/link.ts +97 -11
  46. package/src/path.ts +181 -16
  47. package/src/redirect.ts +37 -22
  48. package/src/route.ts +109 -39
  49. package/src/router.ts +373 -250
  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,40 @@ 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 || {}
64
- if (!opts.reloadDocument) {
65
- opts.reloadDocument = false
66
- try {
67
- new URL(`${opts.href}`)
68
- opts.reloadDocument = true
69
- } catch {}
70
- }
67
+ const headers = new Headers(opts.headers || {})
68
+
69
+ const response = new Response(null, {
70
+ status: opts.statusCode,
71
+ headers,
72
+ })
73
+
74
+ ;(response as Redirect<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>).options =
75
+ opts
71
76
 
72
77
  if (opts.throw) {
73
- throw opts
78
+ throw response
74
79
  }
75
80
 
76
- return opts
81
+ return response as Redirect<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>
77
82
  }
78
83
 
79
84
  export function isRedirect(obj: any): obj is AnyRedirect {
80
- return !!obj?.isRedirect
85
+ return obj instanceof Response && !!(obj as any).options
81
86
  }
82
87
 
83
- export function isResolvedRedirect(obj: any): obj is ResolvedRedirect {
84
- return !!obj?.isRedirect && obj.href
88
+ export function isResolvedRedirect(
89
+ obj: any,
90
+ ): obj is AnyRedirect & { options: { href: string } } {
91
+ return isRedirect(obj) && !!obj.options.href
92
+ }
93
+
94
+ export function parseRedirect(obj: any) {
95
+ if (typeof obj === 'object' && obj.isSerializedRedirect) {
96
+ return redirect(obj)
97
+ }
98
+
99
+ return undefined
85
100
  }
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>
@@ -621,7 +627,24 @@ export interface Route<
621
627
  TBeforeLoadFn
622
628
  >,
623
629
  ) => this
624
- lazy: RouteLazyFn<this>
630
+ lazy: RouteLazyFn<
631
+ Route<
632
+ TParentRoute,
633
+ TPath,
634
+ TFullPath,
635
+ TCustomId,
636
+ TId,
637
+ TSearchValidator,
638
+ TParams,
639
+ TRouterContext,
640
+ TRouteContextFn,
641
+ TBeforeLoadFn,
642
+ TLoaderDeps,
643
+ TLoaderFn,
644
+ TChildren,
645
+ TFileRouteTypes
646
+ >
647
+ >
625
648
  addChildren: RouteAddChildrenFn<
626
649
  TParentRoute,
627
650
  TPath,
@@ -1083,7 +1106,7 @@ export interface UpdatableRouteOptions<
1083
1106
  TBeforeLoadFn,
1084
1107
  TLoaderDeps
1085
1108
  >,
1086
- ) => Record<string, string>
1109
+ ) => Awaitable<Record<string, string>>
1087
1110
  head?: (
1088
1111
  ctx: AssetFnContextOptions<
1089
1112
  TRouteId,
@@ -1097,11 +1120,11 @@ export interface UpdatableRouteOptions<
1097
1120
  TBeforeLoadFn,
1098
1121
  TLoaderDeps
1099
1122
  >,
1100
- ) => {
1123
+ ) => Awaitable<{
1101
1124
  links?: AnyRouteMatch['links']
1102
1125
  scripts?: AnyRouteMatch['headScripts']
1103
1126
  meta?: AnyRouteMatch['meta']
1104
- }
1127
+ }>
1105
1128
  scripts?: (
1106
1129
  ctx: AssetFnContextOptions<
1107
1130
  TRouteId,
@@ -1115,7 +1138,7 @@ export interface UpdatableRouteOptions<
1115
1138
  TBeforeLoadFn,
1116
1139
  TLoaderDeps
1117
1140
  >,
1118
- ) => AnyRouteMatch['scripts']
1141
+ ) => Awaitable<AnyRouteMatch['scripts']>
1119
1142
  ssr?: boolean
1120
1143
  codeSplitGroupings?: Array<
1121
1144
  Array<
@@ -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,
@@ -1573,7 +1626,24 @@ export class BaseRoute<
1573
1626
  return this
1574
1627
  }
1575
1628
 
1576
- lazy: RouteLazyFn<this> = (lazyFn) => {
1629
+ lazy: RouteLazyFn<
1630
+ Route<
1631
+ TParentRoute,
1632
+ TPath,
1633
+ TFullPath,
1634
+ TCustomId,
1635
+ TId,
1636
+ TSearchValidator,
1637
+ TParams,
1638
+ TRouterContext,
1639
+ TRouteContextFn,
1640
+ TBeforeLoadFn,
1641
+ TLoaderDeps,
1642
+ TLoaderFn,
1643
+ TChildren,
1644
+ TFileRouteTypes
1645
+ >
1646
+ > = (lazyFn) => {
1577
1647
  this.lazyFn = lazyFn
1578
1648
  return this
1579
1649
  }