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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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 +15 -14
  16. package/dist/cjs/router.cjs +231 -155
  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 +2 -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 +15 -14
  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 +234 -158
  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 +2 -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 +104 -35
  49. package/src/router.ts +332 -209
  50. package/src/typePrimitives.ts +2 -2
  51. package/src/utils.ts +14 -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'
@@ -20,6 +21,8 @@ import type {
20
21
  Constrain,
21
22
  Expand,
22
23
  IntersectAssign,
24
+ LooseAsyncReturnType,
25
+ LooseReturnType,
23
26
  NoInfer,
24
27
  } from './utils'
25
28
  import type {
@@ -150,27 +153,24 @@ export type ResolveSearchSchema<TSearchValidator> =
150
153
  ? ResolveSearchSchemaFn<TSearchValidator['parse']>
151
154
  : ResolveSearchSchemaFn<TSearchValidator>
152
155
 
153
- export type ParseSplatParams<TPath extends string> = TPath &
154
- `${string}$` extends never
155
- ? TPath & `${string}$/${string}` extends never
156
- ? never
157
- : '_splat'
158
- : '_splat'
156
+ export type ResolveRequiredParams<TPath extends string, T> = {
157
+ [K in ParsePathParams<TPath>['required']]: T
158
+ }
159
159
 
160
- export interface SplatParams {
161
- _splat?: string
160
+ export type ResolveOptionalParams<TPath extends string, T> = {
161
+ [K in ParsePathParams<TPath>['optional']]?: T
162
162
  }
163
163
 
164
- export type ResolveParams<TPath extends string> =
165
- ParseSplatParams<TPath> extends never
166
- ? Record<ParsePathParams<TPath>, string>
167
- : Record<ParsePathParams<TPath>, string> & SplatParams
164
+ export type ResolveParams<
165
+ TPath extends string,
166
+ T = string,
167
+ > = ResolveRequiredParams<TPath, T> & ResolveOptionalParams<TPath, T>
168
168
 
169
169
  export type ParseParamsFn<in out TPath extends string, in out TParams> = (
170
- rawParams: ResolveParams<TPath>,
171
- ) => TParams extends Record<ParsePathParams<TPath>, any>
170
+ rawParams: Expand<ResolveParams<TPath>>,
171
+ ) => TParams extends ResolveParams<TPath, any>
172
172
  ? TParams
173
- : Record<ParsePathParams<TPath>, any>
173
+ : ResolveParams<TPath, any>
174
174
 
175
175
  export type StringifyParamsFn<in out TPath extends string, in out TParams> = (
176
176
  params: TParams,
@@ -270,20 +270,6 @@ export type TrimPathRight<T extends string> = T extends '/'
270
270
  ? TrimPathRight<U>
271
271
  : T
272
272
 
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
273
  export type ContextReturnType<TContextFn> = unknown extends TContextFn
288
274
  ? TContextFn
289
275
  : LooseReturnType<TContextFn> extends never
@@ -448,7 +434,7 @@ export interface RouteExtensions<in out TId, in out TFullPath> {
448
434
  }
449
435
 
450
436
  export type RouteLazyFn<TRoute extends AnyRoute> = (
451
- lazyFn: () => Promise<LazyRoute>,
437
+ lazyFn: () => Promise<LazyRoute<TRoute>>,
452
438
  ) => TRoute
453
439
 
454
440
  export type RouteAddChildrenFn<
@@ -602,7 +588,26 @@ export interface Route<
602
588
  >
603
589
  isRoot: TParentRoute extends AnyRoute ? true : false
604
590
  _componentsPromise?: Promise<Array<void>>
605
- lazyFn?: () => Promise<LazyRoute>
591
+ lazyFn?: () => Promise<
592
+ LazyRoute<
593
+ Route<
594
+ TParentRoute,
595
+ TPath,
596
+ TFullPath,
597
+ TCustomId,
598
+ TId,
599
+ TSearchValidator,
600
+ TParams,
601
+ TRouterContext,
602
+ TRouteContextFn,
603
+ TBeforeLoadFn,
604
+ TLoaderDeps,
605
+ TLoaderFn,
606
+ TChildren,
607
+ TFileRouteTypes
608
+ >
609
+ >
610
+ >
606
611
  _lazyPromise?: Promise<void>
607
612
  rank: number
608
613
  to: TrimPathRight<TFullPath>
@@ -621,7 +626,24 @@ export interface Route<
621
626
  TBeforeLoadFn
622
627
  >,
623
628
  ) => this
624
- lazy: RouteLazyFn<this>
629
+ lazy: RouteLazyFn<
630
+ Route<
631
+ TParentRoute,
632
+ TPath,
633
+ TFullPath,
634
+ TCustomId,
635
+ TId,
636
+ TSearchValidator,
637
+ TParams,
638
+ TRouterContext,
639
+ TRouteContextFn,
640
+ TBeforeLoadFn,
641
+ TLoaderDeps,
642
+ TLoaderFn,
643
+ TChildren,
644
+ TFileRouteTypes
645
+ >
646
+ >
625
647
  addChildren: RouteAddChildrenFn<
626
648
  TParentRoute,
627
649
  TPath,
@@ -1347,7 +1369,26 @@ export class BaseRoute<
1347
1369
  children?: TChildren
1348
1370
  originalIndex?: number
1349
1371
  rank!: number
1350
- lazyFn?: () => Promise<LazyRoute>
1372
+ lazyFn?: () => Promise<
1373
+ LazyRoute<
1374
+ Route<
1375
+ TParentRoute,
1376
+ TPath,
1377
+ TFullPath,
1378
+ TCustomId,
1379
+ TId,
1380
+ TSearchValidator,
1381
+ TParams,
1382
+ TRouterContext,
1383
+ TRouteContextFn,
1384
+ TBeforeLoadFn,
1385
+ TLoaderDeps,
1386
+ TLoaderFn,
1387
+ TChildren,
1388
+ TFileRouteTypes
1389
+ >
1390
+ >
1391
+ >
1351
1392
  _lazyPromise?: Promise<void>
1352
1393
  _componentsPromise?: Promise<Array<void>>
1353
1394
 
@@ -1420,7 +1461,8 @@ export class BaseRoute<
1420
1461
  if (isRoot) {
1421
1462
  this._path = rootRouteId as TPath
1422
1463
  } else if (!this.parentRoute) {
1423
- throw new Error(
1464
+ invariant(
1465
+ false,
1424
1466
  `Child Route instances must pass a 'getParentRoute: () => ParentRoute' option that returns a Route instance.`,
1425
1467
  )
1426
1468
  }
@@ -1460,6 +1502,16 @@ export class BaseRoute<
1460
1502
  this._ssr = options?.ssr ?? opts.defaultSsr ?? true
1461
1503
  }
1462
1504
 
1505
+ clone = (other: typeof this) => {
1506
+ this._path = other._path
1507
+ this._id = other._id
1508
+ this._fullPath = other._fullPath
1509
+ this._to = other._to
1510
+ this._ssr = other._ssr
1511
+ this.options.getParentRoute = other.options.getParentRoute
1512
+ this.children = other.children
1513
+ }
1514
+
1463
1515
  addChildren: RouteAddChildrenFn<
1464
1516
  TParentRoute,
1465
1517
  TPath,
@@ -1573,7 +1625,24 @@ export class BaseRoute<
1573
1625
  return this
1574
1626
  }
1575
1627
 
1576
- lazy: RouteLazyFn<this> = (lazyFn) => {
1628
+ lazy: RouteLazyFn<
1629
+ Route<
1630
+ TParentRoute,
1631
+ TPath,
1632
+ TFullPath,
1633
+ TCustomId,
1634
+ TId,
1635
+ TSearchValidator,
1636
+ TParams,
1637
+ TRouterContext,
1638
+ TRouteContextFn,
1639
+ TBeforeLoadFn,
1640
+ TLoaderDeps,
1641
+ TLoaderFn,
1642
+ TChildren,
1643
+ TFileRouteTypes
1644
+ >
1645
+ > = (lazyFn) => {
1577
1646
  this.lazyFn = lazyFn
1578
1647
  return this
1579
1648
  }