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

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 (40) hide show
  1. package/build/cjs/packages/router-core/src/index.js +33 -1451
  2. package/build/cjs/packages/router-core/src/index.js.map +1 -1
  3. package/build/cjs/packages/router-core/src/path.js +222 -0
  4. package/build/cjs/packages/router-core/src/path.js.map +1 -0
  5. package/build/cjs/packages/router-core/src/qss.js +1 -1
  6. package/build/cjs/packages/router-core/src/qss.js.map +1 -1
  7. package/build/cjs/packages/router-core/src/route.js +126 -0
  8. package/build/cjs/packages/router-core/src/route.js.map +1 -0
  9. package/build/cjs/packages/router-core/src/routeConfig.js +69 -0
  10. package/build/cjs/packages/router-core/src/routeConfig.js.map +1 -0
  11. package/build/cjs/packages/router-core/src/routeMatch.js +260 -0
  12. package/build/cjs/packages/router-core/src/routeMatch.js.map +1 -0
  13. package/build/cjs/packages/router-core/src/router.js +787 -0
  14. package/build/cjs/packages/router-core/src/router.js.map +1 -0
  15. package/build/cjs/packages/router-core/src/searchParams.js +70 -0
  16. package/build/cjs/packages/router-core/src/searchParams.js.map +1 -0
  17. package/build/cjs/packages/router-core/src/utils.js +118 -0
  18. package/build/cjs/packages/router-core/src/utils.js.map +1 -0
  19. package/build/esm/index.js +1304 -1238
  20. package/build/esm/index.js.map +1 -1
  21. package/build/stats-html.html +1 -1
  22. package/build/stats-react.json +374 -57
  23. package/build/types/index.d.ts +361 -333
  24. package/build/umd/index.development.js +1313 -1238
  25. package/build/umd/index.development.js.map +1 -1
  26. package/build/umd/index.production.js +1 -1
  27. package/build/umd/index.production.js.map +1 -1
  28. package/package.json +2 -3
  29. package/src/frameworks.ts +13 -0
  30. package/src/index.ts +15 -3054
  31. package/src/link.ts +289 -0
  32. package/src/path.ts +236 -0
  33. package/src/qss.ts +1 -1
  34. package/src/route.ts +181 -0
  35. package/src/routeConfig.ts +523 -0
  36. package/src/routeInfo.ts +228 -0
  37. package/src/routeMatch.ts +357 -0
  38. package/src/router.ts +1182 -0
  39. package/src/searchParams.ts +54 -0
  40. package/src/utils.ts +157 -0
package/src/link.ts ADDED
@@ -0,0 +1,289 @@
1
+ import { AnyPathParams } from './routeConfig'
2
+ import {
3
+ AnyAllRouteInfo,
4
+ DefaultAllRouteInfo,
5
+ RouteInfoByPath,
6
+ } from './routeInfo'
7
+ import { Location } from './router'
8
+ import { NoInfer, PickAsRequired, PickRequired, Updater } from './utils'
9
+
10
+ export type LinkInfo =
11
+ | {
12
+ type: 'external'
13
+ href: string
14
+ }
15
+ | {
16
+ type: 'internal'
17
+ next: Location
18
+ handleFocus: (e: any) => void
19
+ handleClick: (e: any) => void
20
+ handleEnter: (e: any) => void
21
+ handleLeave: (e: any) => void
22
+ isActive: boolean
23
+ disabled?: boolean
24
+ }
25
+
26
+ type StartsWith<A, B> = A extends `${B extends string ? B : never}${infer _}`
27
+ ? true
28
+ : false
29
+
30
+ type CleanPath<T extends string> = T extends `${infer L}//${infer R}`
31
+ ? CleanPath<`${CleanPath<L>}/${CleanPath<R>}`>
32
+ : T extends `${infer L}//`
33
+ ? `${CleanPath<L>}/`
34
+ : T extends `//${infer L}`
35
+ ? `/${CleanPath<L>}`
36
+ : T
37
+
38
+ export type Split<S, TTrailing = true> = S extends unknown
39
+ ? string extends S
40
+ ? string[]
41
+ : S extends string
42
+ ? CleanPath<S> extends ''
43
+ ? []
44
+ : TTrailing extends true
45
+ ? CleanPath<S> extends `${infer T}/`
46
+ ? [T, '/']
47
+ : CleanPath<S> extends `/${infer U}`
48
+ ? ['/', U]
49
+ : CleanPath<S> extends `${infer T}/${infer U}`
50
+ ? [T, ...Split<U>]
51
+ : [S]
52
+ : CleanPath<S> extends `${infer T}/${infer U}`
53
+ ? [T, ...Split<U>]
54
+ : [S]
55
+ : never
56
+ : never
57
+
58
+ export type ParsePathParams<T extends string> = Split<T>[number] extends infer U
59
+ ? U extends `:${infer V}`
60
+ ? V
61
+ : never
62
+ : never
63
+
64
+ type Join<T> = T extends []
65
+ ? ''
66
+ : T extends [infer L extends string]
67
+ ? L
68
+ : T extends [infer L extends string, ...infer Tail extends [...string[]]]
69
+ ? CleanPath<`${L}/${Join<Tail>}`>
70
+ : never
71
+
72
+ export type RelativeToPathAutoComplete<
73
+ AllPaths extends string,
74
+ TFrom extends string,
75
+ TTo extends string,
76
+ SplitPaths extends string[] = Split<AllPaths, false>,
77
+ > = TTo extends `..${infer _}`
78
+ ? SplitPaths extends [
79
+ ...Split<ResolveRelativePath<TFrom, TTo>, false>,
80
+ ...infer TToRest,
81
+ ]
82
+ ? `${CleanPath<
83
+ Join<
84
+ [
85
+ ...Split<TTo, false>,
86
+ ...(
87
+ | TToRest
88
+ | (Split<
89
+ ResolveRelativePath<TFrom, TTo>,
90
+ false
91
+ >['length'] extends 1
92
+ ? never
93
+ : ['../'])
94
+ ),
95
+ ]
96
+ >
97
+ >}`
98
+ : never
99
+ : TTo extends `./${infer RestTTo}`
100
+ ? SplitPaths extends [
101
+ ...Split<TFrom, false>,
102
+ ...Split<RestTTo, false>,
103
+ ...infer RestPath,
104
+ ]
105
+ ? `${TTo}${Join<RestPath>}`
106
+ : never
107
+ : './' | '../' | AllPaths
108
+
109
+ export type NavigateOptionsAbsolute<
110
+ TAllRouteInfo extends AnyAllRouteInfo = DefaultAllRouteInfo,
111
+ TFrom extends ValidFromPath<TAllRouteInfo> = '/',
112
+ TTo extends string = '.',
113
+ > = ToOptions<TAllRouteInfo, TFrom, TTo> & {
114
+ // Whether to replace the current history stack instead of pushing a new one
115
+ replace?: boolean
116
+ }
117
+
118
+ export type ToOptions<
119
+ TAllRouteInfo extends AnyAllRouteInfo = DefaultAllRouteInfo,
120
+ TFrom extends ValidFromPath<TAllRouteInfo> = '/',
121
+ TTo extends string = '.',
122
+ TResolvedTo = ResolveRelativePath<TFrom, NoInfer<TTo>>,
123
+ > = {
124
+ to?: ToPathOption<TAllRouteInfo, TFrom, TTo>
125
+ // The new has string or a function to update it
126
+ hash?: Updater<string>
127
+ // The source route path. This is automatically set when using route-level APIs, but for type-safe relative routing on the router itself, this is required
128
+ from?: TFrom
129
+ // // When using relative route paths, this option forces resolution from the current path, instead of the route API's path or `from` path
130
+ // fromCurrent?: boolean
131
+ } & CheckPath<TAllRouteInfo, NoInfer<TResolvedTo>> &
132
+ SearchParamOptions<TAllRouteInfo, TFrom, TResolvedTo> &
133
+ PathParamOptions<TAllRouteInfo, TFrom, TResolvedTo>
134
+
135
+ type SearchParamOptions<
136
+ TAllRouteInfo extends AnyAllRouteInfo,
137
+ TFrom,
138
+ TTo,
139
+ TFromSchema = RouteInfoByPath<TAllRouteInfo, TFrom>['fullSearchSchema'],
140
+ TToSchema = RouteInfoByPath<TAllRouteInfo, TTo>['fullSearchSchema'],
141
+ > = StartsWith<TFrom, TTo> extends true // If the next route search extend or cover the from route, params will be optional
142
+ ? {
143
+ search?: SearchReducer<TFromSchema, TToSchema>
144
+ }
145
+ : // Optional search params? Allow it
146
+ keyof PickRequired<TToSchema> extends never
147
+ ? {
148
+ search?: SearchReducer<TFromSchema, TToSchema>
149
+ }
150
+ : {
151
+ // Must have required search params, enforce it
152
+ search: SearchReducer<TFromSchema, TToSchema>
153
+ }
154
+
155
+ type SearchReducer<TFrom, TTo> =
156
+ | { [TKey in keyof TTo]: TTo[TKey] }
157
+ | ((current: TFrom) => TTo)
158
+
159
+ type PathParamOptions<
160
+ TAllRouteInfo extends AnyAllRouteInfo,
161
+ TFrom,
162
+ TTo,
163
+ TFromParams = RouteInfoByPath<TAllRouteInfo, TFrom>['allParams'],
164
+ TToParams = RouteInfoByPath<TAllRouteInfo, TTo>['allParams'],
165
+ > =
166
+ // If the next routes params extend or cover the from route, params will be optional
167
+ StartsWith<TFrom, TTo> extends true
168
+ ? {
169
+ params?: ParamsReducer<TFromParams, TToParams>
170
+ }
171
+ : // If the next route doesn't have params, warn if any have been passed
172
+ AnyPathParams extends TToParams
173
+ ? {
174
+ params?: ParamsReducer<TFromParams, Record<string, never>>
175
+ }
176
+ : // If the next route has params, enforce them
177
+ {
178
+ params: ParamsReducer<TFromParams, TToParams>
179
+ }
180
+
181
+ type ParamsReducer<TFrom, TTo> = TTo | ((current: TFrom) => TTo)
182
+
183
+ export type ToPathOption<
184
+ TAllRouteInfo extends AnyAllRouteInfo = DefaultAllRouteInfo,
185
+ TFrom extends ValidFromPath<TAllRouteInfo> = '/',
186
+ TTo extends string = '.',
187
+ > =
188
+ | TTo
189
+ | RelativeToPathAutoComplete<
190
+ TAllRouteInfo['routePaths'],
191
+ NoInfer<TFrom> extends string ? NoInfer<TFrom> : '',
192
+ NoInfer<TTo> & string
193
+ >
194
+
195
+ export type ToIdOption<
196
+ TAllRouteInfo extends AnyAllRouteInfo = DefaultAllRouteInfo,
197
+ TFrom extends ValidFromPath<TAllRouteInfo> = '/',
198
+ TTo extends string = '.',
199
+ > =
200
+ | TTo
201
+ | RelativeToPathAutoComplete<
202
+ TAllRouteInfo['routeIds'],
203
+ NoInfer<TFrom> extends string ? NoInfer<TFrom> : '',
204
+ NoInfer<TTo> & string
205
+ >
206
+
207
+ interface ActiveOptions {
208
+ exact?: boolean
209
+ includeHash?: boolean
210
+ }
211
+
212
+ export type LinkOptions<
213
+ TAllRouteInfo extends AnyAllRouteInfo = DefaultAllRouteInfo,
214
+ TFrom extends ValidFromPath<TAllRouteInfo> = '/',
215
+ TTo extends string = '.',
216
+ > = NavigateOptionsAbsolute<TAllRouteInfo, TFrom, TTo> & {
217
+ // The standard anchor tag target attribute
218
+ target?: HTMLAnchorElement['target']
219
+ // Defaults to `{ exact: false, includeHash: false }`
220
+ activeOptions?: ActiveOptions
221
+ // If set, will preload the linked route on hover and cache it for this many milliseconds in hopes that the user will eventually navigate there.
222
+ preload?: false | 'intent'
223
+ // When preloaded and set, will cache the preloaded result for this duration in milliseconds
224
+ preloadMaxAge?: number
225
+ // Delay intent preloading by this many milliseconds. If the intent exits before this delay, the preload will be cancelled.
226
+ preloadDelay?: number
227
+ // If true, will render the link without the href attribute
228
+ disabled?: boolean
229
+ }
230
+
231
+ export type CheckRelativePath<
232
+ TAllRouteInfo extends AnyAllRouteInfo,
233
+ TFrom,
234
+ TTo,
235
+ > = TTo extends string
236
+ ? TFrom extends string
237
+ ? ResolveRelativePath<TFrom, TTo> extends TAllRouteInfo['routePaths']
238
+ ? {}
239
+ : {
240
+ Error: `${TFrom} + ${TTo} resolves to ${ResolveRelativePath<
241
+ TFrom,
242
+ TTo
243
+ >}, which is not a valid route path.`
244
+ 'Valid Route Paths': TAllRouteInfo['routePaths']
245
+ }
246
+ : {}
247
+ : {}
248
+
249
+ export type CheckPath<TAllRouteInfo extends AnyAllRouteInfo, TPath> = Exclude<
250
+ TPath,
251
+ TAllRouteInfo['routePaths']
252
+ > extends never
253
+ ? {}
254
+ : CheckPathError<TAllRouteInfo, Exclude<TPath, TAllRouteInfo['routePaths']>>
255
+
256
+ export type CheckPathError<TAllRouteInfo extends AnyAllRouteInfo, TInvalids> = {
257
+ Error: `${TInvalids extends string
258
+ ? TInvalids
259
+ : never} is not a valid route path.`
260
+ 'Valid Route Paths': TAllRouteInfo['routePaths']
261
+ }
262
+
263
+ export type ResolveRelativePath<TFrom, TTo = '.'> = TFrom extends string
264
+ ? TTo extends string
265
+ ? TTo extends '.'
266
+ ? TFrom
267
+ : TTo extends `./`
268
+ ? Join<[TFrom, '/']>
269
+ : TTo extends `./${infer TRest}`
270
+ ? ResolveRelativePath<TFrom, TRest>
271
+ : TTo extends `/${infer TRest}`
272
+ ? TTo
273
+ : Split<TTo> extends ['..', ...infer ToRest]
274
+ ? Split<TFrom> extends [...infer FromRest, infer FromTail]
275
+ ? ResolveRelativePath<Join<FromRest>, Join<ToRest>>
276
+ : never
277
+ : Split<TTo> extends ['.', ...infer ToRest]
278
+ ? ResolveRelativePath<TFrom, Join<ToRest>>
279
+ : CleanPath<Join<['/', ...Split<TFrom>, ...Split<TTo>]>>
280
+ : never
281
+ : never
282
+
283
+ export type ValidFromPath<
284
+ TAllRouteInfo extends AnyAllRouteInfo = DefaultAllRouteInfo,
285
+ > =
286
+ | undefined
287
+ | (string extends TAllRouteInfo['routePaths']
288
+ ? string
289
+ : TAllRouteInfo['routePaths'])
package/src/path.ts ADDED
@@ -0,0 +1,236 @@
1
+ import { AnyPathParams } from './routeConfig'
2
+ import { MatchLocation } from './router'
3
+ import { last } from './utils'
4
+
5
+ export interface Segment {
6
+ type: 'pathname' | 'param' | 'wildcard'
7
+ value: string
8
+ }
9
+
10
+ export function joinPaths(paths: (string | undefined)[]) {
11
+ return cleanPath(paths.filter(Boolean).join('/'))
12
+ }
13
+
14
+ export function cleanPath(path: string) {
15
+ // remove double slashes
16
+ return path.replace(/\/{2,}/g, '/')
17
+ }
18
+
19
+ export function trimPathLeft(path: string) {
20
+ return path === '/' ? path : path.replace(/^\/{1,}/, '')
21
+ }
22
+
23
+ export function trimPathRight(path: string) {
24
+ return path === '/' ? path : path.replace(/\/{1,}$/, '')
25
+ }
26
+
27
+ export function trimPath(path: string) {
28
+ return trimPathRight(trimPathLeft(path))
29
+ }
30
+
31
+ export function resolvePath(basepath: string, base: string, to: string) {
32
+ base = base.replace(new RegExp(`^${basepath}`), '/')
33
+ to = to.replace(new RegExp(`^${basepath}`), '/')
34
+
35
+ let baseSegments = parsePathname(base)
36
+ const toSegments = parsePathname(to)
37
+
38
+ toSegments.forEach((toSegment, index) => {
39
+ if (toSegment.value === '/') {
40
+ if (!index) {
41
+ // Leading slash
42
+ baseSegments = [toSegment]
43
+ } else if (index === toSegments.length - 1) {
44
+ // Trailing Slash
45
+ baseSegments.push(toSegment)
46
+ } else {
47
+ // ignore inter-slashes
48
+ }
49
+ } else if (toSegment.value === '..') {
50
+ // Extra trailing slash? pop it off
51
+ if (baseSegments.length > 1 && last(baseSegments)?.value === '/') {
52
+ baseSegments.pop()
53
+ }
54
+ baseSegments.pop()
55
+ } else if (toSegment.value === '.') {
56
+ return
57
+ } else {
58
+ baseSegments.push(toSegment)
59
+ }
60
+ })
61
+
62
+ const joined = joinPaths([basepath, ...baseSegments.map((d) => d.value)])
63
+
64
+ return cleanPath(joined)
65
+ }
66
+
67
+ export function parsePathname(pathname?: string): Segment[] {
68
+ if (!pathname) {
69
+ return []
70
+ }
71
+
72
+ pathname = cleanPath(pathname)
73
+
74
+ const segments: Segment[] = []
75
+
76
+ if (pathname.slice(0, 1) === '/') {
77
+ pathname = pathname.substring(1)
78
+ segments.push({
79
+ type: 'pathname',
80
+ value: '/',
81
+ })
82
+ }
83
+
84
+ if (!pathname) {
85
+ return segments
86
+ }
87
+
88
+ // Remove empty segments and '.' segments
89
+ const split = pathname.split('/').filter(Boolean)
90
+
91
+ segments.push(
92
+ ...split.map((part): Segment => {
93
+ if (part.startsWith('*')) {
94
+ return {
95
+ type: 'wildcard',
96
+ value: part,
97
+ }
98
+ }
99
+
100
+ if (part.charAt(0) === ':') {
101
+ return {
102
+ type: 'param',
103
+ value: part,
104
+ }
105
+ }
106
+
107
+ return {
108
+ type: 'pathname',
109
+ value: part,
110
+ }
111
+ }),
112
+ )
113
+
114
+ if (pathname.slice(-1) === '/') {
115
+ pathname = pathname.substring(1)
116
+ segments.push({
117
+ type: 'pathname',
118
+ value: '/',
119
+ })
120
+ }
121
+
122
+ return segments
123
+ }
124
+
125
+ export function interpolatePath(
126
+ path: string | undefined,
127
+ params: any,
128
+ leaveWildcard?: boolean,
129
+ ) {
130
+ const interpolatedPathSegments = parsePathname(path)
131
+
132
+ return joinPaths(
133
+ interpolatedPathSegments.map((segment) => {
134
+ if (segment.value === '*' && !leaveWildcard) {
135
+ return ''
136
+ }
137
+
138
+ if (segment.type === 'param') {
139
+ return params![segment.value.substring(1)] ?? ''
140
+ }
141
+
142
+ return segment.value
143
+ }),
144
+ )
145
+ }
146
+
147
+ export function matchPathname(
148
+ currentPathname: string,
149
+ matchLocation: Pick<MatchLocation, 'to' | 'fuzzy' | 'caseSensitive'>,
150
+ ): AnyPathParams | undefined {
151
+ const pathParams = matchByPath(currentPathname, matchLocation)
152
+ // const searchMatched = matchBySearch(currentLocation.search, matchLocation)
153
+
154
+ if (matchLocation.to && !pathParams) {
155
+ return
156
+ }
157
+
158
+ // if (matchLocation.search && !searchMatched) {
159
+ // return
160
+ // }
161
+
162
+ return pathParams ?? {}
163
+ }
164
+
165
+ export function matchByPath(
166
+ from: string,
167
+ matchLocation: Pick<MatchLocation, 'to' | 'caseSensitive' | 'fuzzy'>,
168
+ ): Record<string, string> | undefined {
169
+ const baseSegments = parsePathname(from)
170
+ const routeSegments = parsePathname(`${matchLocation.to ?? '*'}`)
171
+
172
+ const params: Record<string, string> = {}
173
+
174
+ let isMatch = (() => {
175
+ for (
176
+ let i = 0;
177
+ i < Math.max(baseSegments.length, routeSegments.length);
178
+ i++
179
+ ) {
180
+ const baseSegment = baseSegments[i]
181
+ const routeSegment = routeSegments[i]
182
+
183
+ const isLastRouteSegment = i === routeSegments.length - 1
184
+ const isLastBaseSegment = i === baseSegments.length - 1
185
+
186
+ if (routeSegment) {
187
+ if (routeSegment.type === 'wildcard') {
188
+ if (baseSegment?.value) {
189
+ params['*'] = joinPaths(baseSegments.slice(i).map((d) => d.value))
190
+ return true
191
+ }
192
+ return false
193
+ }
194
+
195
+ if (routeSegment.type === 'pathname') {
196
+ if (routeSegment.value === '/' && !baseSegment?.value) {
197
+ return true
198
+ }
199
+
200
+ if (baseSegment) {
201
+ if (matchLocation.caseSensitive) {
202
+ if (routeSegment.value !== baseSegment.value) {
203
+ return false
204
+ }
205
+ } else if (
206
+ routeSegment.value.toLowerCase() !==
207
+ baseSegment.value.toLowerCase()
208
+ ) {
209
+ return false
210
+ }
211
+ }
212
+ }
213
+
214
+ if (!baseSegment) {
215
+ return false
216
+ }
217
+
218
+ if (routeSegment.type === 'param') {
219
+ if (baseSegment?.value === '/') {
220
+ return false
221
+ }
222
+ if (!baseSegment.value.startsWith(':')) {
223
+ params[routeSegment.value.substring(1)] = baseSegment.value
224
+ }
225
+ }
226
+ }
227
+
228
+ if (isLastRouteSegment && !isLastBaseSegment) {
229
+ return !!matchLocation.fuzzy
230
+ }
231
+ }
232
+ return true
233
+ })()
234
+
235
+ return isMatch ? (params as Record<string, string>) : undefined
236
+ }
package/src/qss.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  // @ts-nocheck
2
2
 
3
- // We're inlining qss here for compression's sake, but we've included it as a hard dependency for the MIT license it requires.
3
+ // qss has been slightly modified and inlined here for our use cases (and compression's sake). We've included it as a hard dependency for MIT license attribution.
4
4
 
5
5
  export function encode(obj, pfx?: string) {
6
6
  var k,
package/src/route.ts ADDED
@@ -0,0 +1,181 @@
1
+ import {
2
+ CheckRelativePath,
3
+ LinkInfo,
4
+ LinkOptions,
5
+ ResolveRelativePath,
6
+ ToOptions,
7
+ } from './link'
8
+ import { RouteConfig, RouteOptions } from './routeConfig'
9
+ import {
10
+ AnyAllRouteInfo,
11
+ AnyRouteInfo,
12
+ DefaultAllRouteInfo,
13
+ RouteInfo,
14
+ RouteInfoByPath,
15
+ } from './routeInfo'
16
+ import { RouteMatch } from './routeMatch'
17
+ import { Action, ActionState, MatchRouteOptions, Router } from './router'
18
+ import { NoInfer, replaceEqualDeep } from './utils'
19
+
20
+ export interface AnyRoute extends Route<any, any> {}
21
+
22
+ export interface Route<
23
+ TAllRouteInfo extends AnyAllRouteInfo = DefaultAllRouteInfo,
24
+ TRouteInfo extends AnyRouteInfo = RouteInfo,
25
+ > {
26
+ routeId: TRouteInfo['id']
27
+ routeRouteId: TRouteInfo['routeId']
28
+ routePath: TRouteInfo['path']
29
+ fullPath: TRouteInfo['fullPath']
30
+ parentRoute?: AnyRoute
31
+ childRoutes?: AnyRoute[]
32
+ options: RouteOptions
33
+ router: Router<TAllRouteInfo['routeConfig'], TAllRouteInfo>
34
+ buildLink: <TTo extends string = '.'>(
35
+ options: Omit<
36
+ LinkOptions<TAllRouteInfo, TRouteInfo['fullPath'], TTo>,
37
+ 'from'
38
+ >,
39
+ ) => LinkInfo
40
+ matchRoute: <
41
+ TTo extends string = '.',
42
+ TResolved extends string = ResolveRelativePath<TRouteInfo['id'], TTo>,
43
+ >(
44
+ matchLocation: CheckRelativePath<
45
+ TAllRouteInfo,
46
+ TRouteInfo['fullPath'],
47
+ NoInfer<TTo>
48
+ > &
49
+ Omit<ToOptions<TAllRouteInfo, TRouteInfo['fullPath'], TTo>, 'from'>,
50
+ opts?: MatchRouteOptions,
51
+ ) => RouteInfoByPath<TAllRouteInfo, TResolved>['allParams']
52
+ navigate: <TTo extends string = '.'>(
53
+ options: Omit<LinkOptions<TAllRouteInfo, TRouteInfo['id'], TTo>, 'from'>,
54
+ ) => Promise<void>
55
+ action: unknown extends TRouteInfo['actionResponse']
56
+ ?
57
+ | Action<TRouteInfo['actionPayload'], TRouteInfo['actionResponse']>
58
+ | undefined
59
+ : Action<TRouteInfo['actionPayload'], TRouteInfo['actionResponse']>
60
+ }
61
+
62
+ export function createRoute<
63
+ TAllRouteInfo extends AnyAllRouteInfo = DefaultAllRouteInfo,
64
+ TRouteInfo extends AnyRouteInfo = RouteInfo,
65
+ >(
66
+ routeConfig: RouteConfig,
67
+ options: TRouteInfo['options'],
68
+ parent: undefined | Route<TAllRouteInfo, any>,
69
+ router: Router<TAllRouteInfo['routeConfig'], TAllRouteInfo>,
70
+ ): Route<TAllRouteInfo, TRouteInfo> {
71
+ const { id, routeId, path: routePath, fullPath } = routeConfig
72
+
73
+ const action =
74
+ router.state.actions[id] ||
75
+ (() => {
76
+ router.state.actions[id] = {
77
+ pending: [],
78
+ submit: async <T, U>(
79
+ submission: T,
80
+ actionOpts?: { invalidate?: boolean },
81
+ ) => {
82
+ if (!route) {
83
+ return
84
+ }
85
+
86
+ const invalidate = actionOpts?.invalidate ?? true
87
+
88
+ const actionState: ActionState<T, U> = {
89
+ submittedAt: Date.now(),
90
+ status: 'pending',
91
+ submission,
92
+ }
93
+
94
+ action.current = actionState
95
+ action.latest = actionState
96
+ action.pending.push(actionState)
97
+
98
+ router.state = {
99
+ ...router.state,
100
+ currentAction: actionState,
101
+ latestAction: actionState,
102
+ }
103
+
104
+ router.notify()
105
+
106
+ try {
107
+ const res = await route.options.action?.(submission)
108
+ actionState.data = res as U
109
+ if (invalidate) {
110
+ router.invalidateRoute({ to: '.', fromCurrent: true })
111
+ await router.reload()
112
+ }
113
+ actionState.status = 'success'
114
+ return res
115
+ } catch (err) {
116
+ console.error(err)
117
+ actionState.error = err
118
+ actionState.status = 'error'
119
+ } finally {
120
+ action.pending = action.pending.filter((d) => d !== actionState)
121
+ router.removeActionQueue.push({ action, actionState })
122
+ router.notify()
123
+ }
124
+ },
125
+ }
126
+ return router.state.actions[id]!
127
+ })()
128
+
129
+ let route: Route<TAllRouteInfo, TRouteInfo> = {
130
+ routeId: id,
131
+ routeRouteId: routeId,
132
+ routePath,
133
+ fullPath,
134
+ options,
135
+ router,
136
+ childRoutes: undefined!,
137
+ parentRoute: parent,
138
+ action,
139
+
140
+ buildLink: (options) => {
141
+ return router.buildLink({
142
+ ...options,
143
+ from: fullPath,
144
+ } as any) as any
145
+ },
146
+
147
+ navigate: (options) => {
148
+ return router.navigate({
149
+ ...options,
150
+ from: fullPath,
151
+ } as any) as any
152
+ },
153
+
154
+ matchRoute: (matchLocation, opts) => {
155
+ return router.matchRoute(
156
+ {
157
+ ...matchLocation,
158
+ from: fullPath,
159
+ } as any,
160
+ opts,
161
+ )
162
+ },
163
+ }
164
+
165
+ router.options.createRoute?.({ router, route })
166
+
167
+ return route
168
+ }
169
+
170
+ export function cascadeLoaderData(matches: RouteMatch<any, any>[]) {
171
+ matches.forEach((match, index) => {
172
+ const parent = matches[index - 1]
173
+
174
+ if (parent) {
175
+ match.loaderData = replaceEqualDeep(match.loaderData, {
176
+ ...parent.loaderData,
177
+ ...match.routeLoaderData,
178
+ })
179
+ }
180
+ })
181
+ }