@tanstack/router-core 0.0.1-alpha.9 → 0.0.1-beta.10
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/build/cjs/packages/router-core/src/index.js +1 -1
- package/build/cjs/packages/router-core/src/qss.js +1 -0
- package/build/cjs/packages/router-core/src/qss.js.map +1 -1
- package/build/cjs/packages/router-core/src/route.js +44 -23
- package/build/cjs/packages/router-core/src/route.js.map +1 -1
- package/build/cjs/packages/router-core/src/routeConfig.js.map +1 -1
- package/build/cjs/packages/router-core/src/routeMatch.js +50 -48
- package/build/cjs/packages/router-core/src/routeMatch.js.map +1 -1
- package/build/cjs/packages/router-core/src/router.js +280 -257
- package/build/cjs/packages/router-core/src/router.js.map +1 -1
- package/build/cjs/packages/router-core/src/utils.js +7 -0
- package/build/cjs/packages/router-core/src/utils.js.map +1 -1
- package/build/esm/index.js +379 -324
- package/build/esm/index.js.map +1 -1
- package/build/stats-html.html +1 -1
- package/build/stats-react.json +138 -144
- package/build/types/index.d.ts +140 -96
- package/build/umd/index.development.js +379 -324
- package/build/umd/index.development.js.map +1 -1
- package/build/umd/index.production.js +1 -1
- package/build/umd/index.production.js.map +1 -1
- package/package.json +1 -1
- package/src/frameworks.ts +0 -1
- package/src/link.ts +45 -17
- package/src/qss.ts +1 -0
- package/src/route.ts +74 -28
- package/src/routeConfig.ts +27 -55
- package/src/routeMatch.ts +70 -61
- package/src/router.ts +435 -331
- package/src/utils.ts +7 -0
package/package.json
CHANGED
package/src/frameworks.ts
CHANGED
package/src/link.ts
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
RouteInfoByPath,
|
|
6
6
|
} from './routeInfo'
|
|
7
7
|
import { Location } from './router'
|
|
8
|
-
import { NoInfer, PickAsRequired, PickRequired, Updater } from './utils'
|
|
8
|
+
import { Expand, NoInfer, PickAsRequired, PickRequired, Updater } from './utils'
|
|
9
9
|
|
|
10
10
|
export type LinkInfo =
|
|
11
11
|
| {
|
|
@@ -35,23 +35,25 @@ type CleanPath<T extends string> = T extends `${infer L}//${infer R}`
|
|
|
35
35
|
? `/${CleanPath<L>}`
|
|
36
36
|
: T
|
|
37
37
|
|
|
38
|
-
export type Split<S,
|
|
38
|
+
export type Split<S, TIncludeTrailingSlash = true> = S extends unknown
|
|
39
39
|
? string extends S
|
|
40
40
|
? string[]
|
|
41
41
|
: S extends string
|
|
42
42
|
? CleanPath<S> extends ''
|
|
43
43
|
? []
|
|
44
|
-
:
|
|
44
|
+
: TIncludeTrailingSlash extends true
|
|
45
45
|
? CleanPath<S> extends `${infer T}/`
|
|
46
|
-
? [T
|
|
46
|
+
? [...Split<T>, '/']
|
|
47
47
|
: CleanPath<S> extends `/${infer U}`
|
|
48
|
-
?
|
|
48
|
+
? Split<U>
|
|
49
49
|
: CleanPath<S> extends `${infer T}/${infer U}`
|
|
50
|
-
? [T
|
|
50
|
+
? [...Split<T>, ...Split<U>]
|
|
51
51
|
: [S]
|
|
52
52
|
: CleanPath<S> extends `${infer T}/${infer U}`
|
|
53
|
-
? [T
|
|
54
|
-
:
|
|
53
|
+
? [...Split<T>, ...Split<U>]
|
|
54
|
+
: S extends string
|
|
55
|
+
? [S]
|
|
56
|
+
: never
|
|
55
57
|
: never
|
|
56
58
|
: never
|
|
57
59
|
|
|
@@ -128,7 +130,7 @@ export type ToOptions<
|
|
|
128
130
|
from?: TFrom
|
|
129
131
|
// // When using relative route paths, this option forces resolution from the current path, instead of the route API's path or `from` path
|
|
130
132
|
// fromCurrent?: boolean
|
|
131
|
-
} & CheckPath<TAllRouteInfo, NoInfer<TResolvedTo
|
|
133
|
+
} & CheckPath<TAllRouteInfo, NoInfer<TResolvedTo>, {}> &
|
|
132
134
|
SearchParamOptions<TAllRouteInfo, TFrom, TResolvedTo> &
|
|
133
135
|
PathParamOptions<TAllRouteInfo, TFrom, TResolvedTo>
|
|
134
136
|
|
|
@@ -248,19 +250,41 @@ export type CheckRelativePath<
|
|
|
248
250
|
: {}
|
|
249
251
|
: {}
|
|
250
252
|
|
|
251
|
-
export type CheckPath<
|
|
253
|
+
export type CheckPath<
|
|
254
|
+
TAllRouteInfo extends AnyAllRouteInfo,
|
|
252
255
|
TPath,
|
|
253
|
-
|
|
254
|
-
> extends never
|
|
255
|
-
?
|
|
256
|
+
TPass,
|
|
257
|
+
> = Exclude<TPath, TAllRouteInfo['routePaths']> extends never
|
|
258
|
+
? TPass
|
|
256
259
|
: CheckPathError<TAllRouteInfo, Exclude<TPath, TAllRouteInfo['routePaths']>>
|
|
257
260
|
|
|
258
|
-
export type CheckPathError<
|
|
261
|
+
export type CheckPathError<
|
|
262
|
+
TAllRouteInfo extends AnyAllRouteInfo,
|
|
263
|
+
TInvalids,
|
|
264
|
+
> = Expand<{
|
|
259
265
|
Error: `${TInvalids extends string
|
|
260
266
|
? TInvalids
|
|
261
267
|
: never} is not a valid route path.`
|
|
262
268
|
'Valid Route Paths': TAllRouteInfo['routePaths']
|
|
263
|
-
}
|
|
269
|
+
}>
|
|
270
|
+
|
|
271
|
+
export type CheckId<
|
|
272
|
+
TAllRouteInfo extends AnyAllRouteInfo,
|
|
273
|
+
TPath,
|
|
274
|
+
TPass,
|
|
275
|
+
> = Exclude<TPath, TAllRouteInfo['routeIds']> extends never
|
|
276
|
+
? TPass
|
|
277
|
+
: CheckIdError<TAllRouteInfo, Exclude<TPath, TAllRouteInfo['routeIds']>>
|
|
278
|
+
|
|
279
|
+
export type CheckIdError<
|
|
280
|
+
TAllRouteInfo extends AnyAllRouteInfo,
|
|
281
|
+
TInvalids,
|
|
282
|
+
> = Expand<{
|
|
283
|
+
Error: `${TInvalids extends string
|
|
284
|
+
? TInvalids
|
|
285
|
+
: never} is not a valid route ID.`
|
|
286
|
+
'Valid Route IDs': TAllRouteInfo['routeIds']
|
|
287
|
+
}>
|
|
264
288
|
|
|
265
289
|
export type ResolveRelativePath<TFrom, TTo = '.'> = TFrom extends string
|
|
266
290
|
? TTo extends string
|
|
@@ -274,10 +298,14 @@ export type ResolveRelativePath<TFrom, TTo = '.'> = TFrom extends string
|
|
|
274
298
|
? TTo
|
|
275
299
|
: Split<TTo> extends ['..', ...infer ToRest]
|
|
276
300
|
? Split<TFrom> extends [...infer FromRest, infer FromTail]
|
|
277
|
-
?
|
|
301
|
+
? ToRest extends ['/']
|
|
302
|
+
? Join<[...FromRest, '/']>
|
|
303
|
+
: ResolveRelativePath<Join<FromRest>, Join<ToRest>>
|
|
278
304
|
: never
|
|
279
305
|
: Split<TTo> extends ['.', ...infer ToRest]
|
|
280
|
-
?
|
|
306
|
+
? ToRest extends ['/']
|
|
307
|
+
? Join<[TFrom, '/']>
|
|
308
|
+
: ResolveRelativePath<TFrom, Join<ToRest>>
|
|
281
309
|
: CleanPath<Join<['/', ...Split<TFrom>, ...Split<TTo>]>>
|
|
282
310
|
: never
|
|
283
311
|
: never
|
package/src/qss.ts
CHANGED
package/src/route.ts
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
ResolveRelativePath,
|
|
6
6
|
ToOptions,
|
|
7
7
|
} from './link'
|
|
8
|
-
import { RouteConfig, RouteOptions } from './routeConfig'
|
|
8
|
+
import { LoaderContext, RouteConfig, RouteOptions } from './routeConfig'
|
|
9
9
|
import {
|
|
10
10
|
AnyAllRouteInfo,
|
|
11
11
|
AnyRouteInfo,
|
|
@@ -13,9 +13,15 @@ import {
|
|
|
13
13
|
RouteInfo,
|
|
14
14
|
RouteInfoByPath,
|
|
15
15
|
} from './routeInfo'
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
import {
|
|
17
|
+
Action,
|
|
18
|
+
ActionState,
|
|
19
|
+
Loader,
|
|
20
|
+
LoaderState,
|
|
21
|
+
MatchRouteOptions,
|
|
22
|
+
Router,
|
|
23
|
+
} from './router'
|
|
24
|
+
import { NoInfer } from './utils'
|
|
19
25
|
|
|
20
26
|
export interface AnyRoute extends Route<any, any> {}
|
|
21
27
|
|
|
@@ -57,6 +63,21 @@ export interface Route<
|
|
|
57
63
|
| Action<TRouteInfo['actionPayload'], TRouteInfo['actionResponse']>
|
|
58
64
|
| undefined
|
|
59
65
|
: Action<TRouteInfo['actionPayload'], TRouteInfo['actionResponse']>
|
|
66
|
+
loader: unknown extends TRouteInfo['routeLoaderData']
|
|
67
|
+
?
|
|
68
|
+
| Action<
|
|
69
|
+
LoaderContext<
|
|
70
|
+
TRouteInfo['fullSearchSchema'],
|
|
71
|
+
TRouteInfo['allParams']
|
|
72
|
+
>,
|
|
73
|
+
TRouteInfo['routeLoaderData']
|
|
74
|
+
>
|
|
75
|
+
| undefined
|
|
76
|
+
: Loader<
|
|
77
|
+
TRouteInfo['fullSearchSchema'],
|
|
78
|
+
TRouteInfo['allParams'],
|
|
79
|
+
TRouteInfo['routeLoaderData']
|
|
80
|
+
>
|
|
60
81
|
}
|
|
61
82
|
|
|
62
83
|
export function createRoute<
|
|
@@ -74,10 +95,10 @@ export function createRoute<
|
|
|
74
95
|
router.state.actions[id] ||
|
|
75
96
|
(() => {
|
|
76
97
|
router.state.actions[id] = {
|
|
77
|
-
|
|
98
|
+
submissions: [],
|
|
78
99
|
submit: async <T, U>(
|
|
79
100
|
submission: T,
|
|
80
|
-
actionOpts?: { invalidate?: boolean },
|
|
101
|
+
actionOpts?: { invalidate?: boolean; multi?: boolean },
|
|
81
102
|
) => {
|
|
82
103
|
if (!route) {
|
|
83
104
|
return
|
|
@@ -85,27 +106,27 @@ export function createRoute<
|
|
|
85
106
|
|
|
86
107
|
const invalidate = actionOpts?.invalidate ?? true
|
|
87
108
|
|
|
109
|
+
if (!actionOpts?.multi) {
|
|
110
|
+
action.submissions = action.submissions.filter((d) => d.isMulti)
|
|
111
|
+
}
|
|
112
|
+
|
|
88
113
|
const actionState: ActionState<T, U> = {
|
|
89
114
|
submittedAt: Date.now(),
|
|
90
115
|
status: 'pending',
|
|
91
116
|
submission,
|
|
117
|
+
isMulti: !!actionOpts?.multi,
|
|
92
118
|
}
|
|
93
119
|
|
|
94
120
|
action.current = actionState
|
|
95
121
|
action.latest = actionState
|
|
96
|
-
action.
|
|
97
|
-
|
|
98
|
-
router.state = {
|
|
99
|
-
...router.state,
|
|
100
|
-
currentAction: actionState,
|
|
101
|
-
latestAction: actionState,
|
|
102
|
-
}
|
|
122
|
+
action.submissions.push(actionState)
|
|
103
123
|
|
|
104
124
|
router.notify()
|
|
105
125
|
|
|
106
126
|
try {
|
|
107
127
|
const res = await route.options.action?.(submission)
|
|
108
128
|
actionState.data = res as U
|
|
129
|
+
|
|
109
130
|
if (invalidate) {
|
|
110
131
|
router.invalidateRoute({ to: '.', fromCurrent: true })
|
|
111
132
|
await router.reload()
|
|
@@ -117,8 +138,6 @@ export function createRoute<
|
|
|
117
138
|
actionState.error = err
|
|
118
139
|
actionState.status = 'error'
|
|
119
140
|
} finally {
|
|
120
|
-
action.pending = action.pending.filter((d) => d !== actionState)
|
|
121
|
-
router.removeActionQueue.push({ action, actionState })
|
|
122
141
|
router.notify()
|
|
123
142
|
}
|
|
124
143
|
},
|
|
@@ -126,6 +145,45 @@ export function createRoute<
|
|
|
126
145
|
return router.state.actions[id]!
|
|
127
146
|
})()
|
|
128
147
|
|
|
148
|
+
const loader =
|
|
149
|
+
router.state.loaders[id] ||
|
|
150
|
+
(() => {
|
|
151
|
+
router.state.loaders[id] = {
|
|
152
|
+
pending: [],
|
|
153
|
+
fetch: (async (loaderContext: LoaderContext<any, any>) => {
|
|
154
|
+
if (!route) {
|
|
155
|
+
return
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const loaderState: LoaderState<any, any> = {
|
|
159
|
+
loadedAt: Date.now(),
|
|
160
|
+
loaderContext,
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
loader.current = loaderState
|
|
164
|
+
loader.latest = loaderState
|
|
165
|
+
loader.pending.push(loaderState)
|
|
166
|
+
|
|
167
|
+
// router.state = {
|
|
168
|
+
// ...router.state,
|
|
169
|
+
// currentAction: loaderState,
|
|
170
|
+
// latestAction: loaderState,
|
|
171
|
+
// }
|
|
172
|
+
|
|
173
|
+
router.notify()
|
|
174
|
+
|
|
175
|
+
try {
|
|
176
|
+
return await route.options.loader?.(loaderContext)
|
|
177
|
+
} finally {
|
|
178
|
+
loader.pending = loader.pending.filter((d) => d !== loaderState)
|
|
179
|
+
// router.removeActionQueue.push({ loader, loaderState })
|
|
180
|
+
router.notify()
|
|
181
|
+
}
|
|
182
|
+
}) as any,
|
|
183
|
+
}
|
|
184
|
+
return router.state.loaders[id]!
|
|
185
|
+
})()
|
|
186
|
+
|
|
129
187
|
let route: Route<TAllRouteInfo, TRouteInfo> = {
|
|
130
188
|
routeId: id,
|
|
131
189
|
routeRouteId: routeId,
|
|
@@ -136,6 +194,7 @@ export function createRoute<
|
|
|
136
194
|
childRoutes: undefined!,
|
|
137
195
|
parentRoute: parent,
|
|
138
196
|
action,
|
|
197
|
+
loader: loader as any,
|
|
139
198
|
|
|
140
199
|
buildLink: (options) => {
|
|
141
200
|
return router.buildLink({
|
|
@@ -166,16 +225,3 @@ export function createRoute<
|
|
|
166
225
|
|
|
167
226
|
return route
|
|
168
227
|
}
|
|
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
|
-
}
|
package/src/routeConfig.ts
CHANGED
|
@@ -53,11 +53,18 @@ export type LoaderFn<
|
|
|
53
53
|
TRouteLoaderData extends AnyLoaderData,
|
|
54
54
|
TFullSearchSchema extends AnySearchSchema = {},
|
|
55
55
|
TAllParams extends AnyPathParams = {},
|
|
56
|
-
> = (
|
|
56
|
+
> = (
|
|
57
|
+
loaderContext: LoaderContext<TFullSearchSchema, TAllParams>,
|
|
58
|
+
) => Promise<TRouteLoaderData>
|
|
59
|
+
|
|
60
|
+
export interface LoaderContext<
|
|
61
|
+
TFullSearchSchema extends AnySearchSchema = {},
|
|
62
|
+
TAllParams extends AnyPathParams = {},
|
|
63
|
+
> {
|
|
57
64
|
params: TAllParams
|
|
58
65
|
search: TFullSearchSchema
|
|
59
66
|
signal?: AbortSignal
|
|
60
|
-
}
|
|
67
|
+
}
|
|
61
68
|
|
|
62
69
|
export type ActionFn<TActionPayload = unknown, TActionResponse = unknown> = (
|
|
63
70
|
submission: TActionPayload,
|
|
@@ -105,58 +112,6 @@ export type RouteOptions<
|
|
|
105
112
|
pendingMs?: number
|
|
106
113
|
// _If the `pendingElement` is shown_, the minimum duration for which it will be visible.
|
|
107
114
|
pendingMinMs?: number
|
|
108
|
-
// // An array of child routes
|
|
109
|
-
// children?: Route<any, any, any, any>[]
|
|
110
|
-
} & (
|
|
111
|
-
| {
|
|
112
|
-
parseParams?: never
|
|
113
|
-
stringifyParams?: never
|
|
114
|
-
}
|
|
115
|
-
| {
|
|
116
|
-
// Parse params optionally receives path params as strings and returns them in a parsed format (like a number or boolean)
|
|
117
|
-
parseParams: (
|
|
118
|
-
rawParams: IsAny<TPath, any, Record<ParsePathParams<TPath>, string>>,
|
|
119
|
-
) => TParams
|
|
120
|
-
stringifyParams: (
|
|
121
|
-
params: TParams,
|
|
122
|
-
) => Record<ParsePathParams<TPath>, string>
|
|
123
|
-
}
|
|
124
|
-
) &
|
|
125
|
-
RouteLoaders<
|
|
126
|
-
// Route Loaders (see below) can be inline on the route, or resolved async
|
|
127
|
-
TRouteLoaderData,
|
|
128
|
-
TLoaderData,
|
|
129
|
-
TActionPayload,
|
|
130
|
-
TActionResponse,
|
|
131
|
-
TFullSearchSchema,
|
|
132
|
-
TAllParams
|
|
133
|
-
> & {
|
|
134
|
-
// If `import` is defined, this route can resolve its elements and loaders in a single asynchronous call
|
|
135
|
-
// This is particularly useful for code-splitting or module federation
|
|
136
|
-
import?: (opts: {
|
|
137
|
-
params: AnyPathParams
|
|
138
|
-
}) => Promise<
|
|
139
|
-
RouteLoaders<
|
|
140
|
-
TRouteLoaderData,
|
|
141
|
-
TLoaderData,
|
|
142
|
-
TActionPayload,
|
|
143
|
-
TActionResponse,
|
|
144
|
-
TFullSearchSchema,
|
|
145
|
-
TAllParams
|
|
146
|
-
>
|
|
147
|
-
>
|
|
148
|
-
} & (PickUnsafe<TParentParams, ParsePathParams<TPath>> extends never // Detect if an existing path param is being redefined
|
|
149
|
-
? {}
|
|
150
|
-
: 'Cannot redefined path params in child routes!')
|
|
151
|
-
|
|
152
|
-
export interface RouteLoaders<
|
|
153
|
-
TRouteLoaderData extends AnyLoaderData = {},
|
|
154
|
-
TLoaderData extends AnyLoaderData = {},
|
|
155
|
-
TActionPayload = unknown,
|
|
156
|
-
TActionResponse = unknown,
|
|
157
|
-
TFullSearchSchema extends AnySearchSchema = {},
|
|
158
|
-
TAllParams extends AnyPathParams = {},
|
|
159
|
-
> {
|
|
160
115
|
// The content to be rendered when the route is matched. If no element is provided, defaults to `<Outlet />`
|
|
161
116
|
element?: GetFrameworkGeneric<'SyncOrAsyncElement'> // , NoInfer<TLoaderData>>
|
|
162
117
|
// The content to be rendered when `loader` encounters an error
|
|
@@ -196,7 +151,24 @@ export interface RouteLoaders<
|
|
|
196
151
|
}) => void
|
|
197
152
|
// An object of whatever you want! This object is accessible anywhere matches are.
|
|
198
153
|
meta?: RouteMeta // TODO: Make this nested and mergeable
|
|
199
|
-
}
|
|
154
|
+
} & (
|
|
155
|
+
| {
|
|
156
|
+
parseParams?: never
|
|
157
|
+
stringifyParams?: never
|
|
158
|
+
}
|
|
159
|
+
| {
|
|
160
|
+
// Parse params optionally receives path params as strings and returns them in a parsed format (like a number or boolean)
|
|
161
|
+
parseParams: (
|
|
162
|
+
rawParams: IsAny<TPath, any, Record<ParsePathParams<TPath>, string>>,
|
|
163
|
+
) => TParams
|
|
164
|
+
stringifyParams: (
|
|
165
|
+
params: TParams,
|
|
166
|
+
) => Record<ParsePathParams<TPath>, string>
|
|
167
|
+
}
|
|
168
|
+
) &
|
|
169
|
+
(PickUnsafe<TParentParams, ParsePathParams<TPath>> extends never // Detect if an existing path param is being redefined
|
|
170
|
+
? {}
|
|
171
|
+
: 'Cannot redefined path params in child routes!')
|
|
200
172
|
|
|
201
173
|
export type SearchFilter<T, U = T> = (prev: T) => U
|
|
202
174
|
|
package/src/routeMatch.ts
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { GetFrameworkGeneric } from './frameworks'
|
|
2
2
|
import { Route } from './route'
|
|
3
|
-
import { AnyPathParams } from './routeConfig'
|
|
4
3
|
import {
|
|
5
4
|
AnyAllRouteInfo,
|
|
6
5
|
AnyRouteInfo,
|
|
7
6
|
DefaultAllRouteInfo,
|
|
8
7
|
RouteInfo,
|
|
9
8
|
} from './routeInfo'
|
|
10
|
-
import { Router } from './router'
|
|
9
|
+
import { ActionState, Router } from './router'
|
|
11
10
|
import { replaceEqualDeep, Timeout } from './utils'
|
|
12
11
|
|
|
13
12
|
export interface RouteMatch<
|
|
@@ -16,7 +15,7 @@ export interface RouteMatch<
|
|
|
16
15
|
> extends Route<TAllRouteInfo, TRouteInfo> {
|
|
17
16
|
matchId: string
|
|
18
17
|
pathname: string
|
|
19
|
-
params:
|
|
18
|
+
params: TRouteInfo['params']
|
|
20
19
|
parentMatch?: RouteMatch
|
|
21
20
|
childMatches: RouteMatch[]
|
|
22
21
|
routeSearch: TRouteInfo['searchSchema']
|
|
@@ -37,8 +36,7 @@ export interface RouteMatch<
|
|
|
37
36
|
catchElement?: GetFrameworkGeneric<'Element'> // , TRouteInfo['loaderData']>
|
|
38
37
|
pendingElement?: GetFrameworkGeneric<'Element'> // , TRouteInfo['loaderData']>
|
|
39
38
|
loadPromise?: Promise<void>
|
|
40
|
-
|
|
41
|
-
importPromise?: Promise<void>
|
|
39
|
+
loaderDataPromise?: Promise<void>
|
|
42
40
|
elementsPromise?: Promise<void>
|
|
43
41
|
dataPromise?: Promise<void>
|
|
44
42
|
pendingTimeout?: Timeout
|
|
@@ -61,7 +59,13 @@ export interface RouteMatch<
|
|
|
61
59
|
resolve: () => void
|
|
62
60
|
}
|
|
63
61
|
cancel: () => void
|
|
64
|
-
load: (
|
|
62
|
+
load: (
|
|
63
|
+
loaderOpts?: { withPending?: boolean } & (
|
|
64
|
+
| { preload: true; maxAge: number; gcMaxAge: number }
|
|
65
|
+
| { preload?: false; maxAge?: never; gcMaxAge?: never }
|
|
66
|
+
),
|
|
67
|
+
) => Promise<TRouteInfo['routeLoaderData']>
|
|
68
|
+
fetch: (opts?: { maxAge?: number }) => Promise<TRouteInfo['routeLoaderData']>
|
|
65
69
|
invalidate: () => void
|
|
66
70
|
hasLoaders: () => boolean
|
|
67
71
|
}
|
|
@@ -99,6 +103,7 @@ export function createRouteMatch<
|
|
|
99
103
|
isFetching: false,
|
|
100
104
|
isInvalid: false,
|
|
101
105
|
invalidAt: Infinity,
|
|
106
|
+
// pendingActions: [],
|
|
102
107
|
getIsInvalid: () => {
|
|
103
108
|
const now = Date.now()
|
|
104
109
|
return routeMatch.isInvalid || routeMatch.invalidAt < now
|
|
@@ -142,18 +147,6 @@ export function createRouteMatch<
|
|
|
142
147
|
clearTimeout(routeMatch.__.pendingMinTimeout)
|
|
143
148
|
delete routeMatch.__.pendingMinPromise
|
|
144
149
|
},
|
|
145
|
-
// setParentMatch: (parentMatch?: RouteMatch) => {
|
|
146
|
-
// routeMatch.parentMatch = parentMatch
|
|
147
|
-
// },
|
|
148
|
-
// addChildMatch: (childMatch: RouteMatch) => {
|
|
149
|
-
// if (
|
|
150
|
-
// routeMatch.childMatches.find((d) => d.matchId === childMatch.matchId)
|
|
151
|
-
// ) {
|
|
152
|
-
// return
|
|
153
|
-
// }
|
|
154
|
-
|
|
155
|
-
// routeMatch.childMatches.push(childMatch)
|
|
156
|
-
// },
|
|
157
150
|
validate: () => {
|
|
158
151
|
// Validate the search params and stabilize them
|
|
159
152
|
const parentSearch =
|
|
@@ -169,7 +162,7 @@ export function createRouteMatch<
|
|
|
169
162
|
|
|
170
163
|
let nextSearch = replaceEqualDeep(
|
|
171
164
|
prevSearch,
|
|
172
|
-
validator?.(parentSearch),
|
|
165
|
+
validator?.(parentSearch) ?? {},
|
|
173
166
|
)
|
|
174
167
|
|
|
175
168
|
// Invalidate route matches when search param stability changes
|
|
@@ -183,6 +176,14 @@ export function createRouteMatch<
|
|
|
183
176
|
...parentSearch,
|
|
184
177
|
...nextSearch,
|
|
185
178
|
})
|
|
179
|
+
|
|
180
|
+
elementTypes.map(async (type) => {
|
|
181
|
+
const routeElement = routeMatch.options[type]
|
|
182
|
+
|
|
183
|
+
if (typeof routeMatch.__[type] !== 'function') {
|
|
184
|
+
routeMatch.__[type] = routeElement
|
|
185
|
+
}
|
|
186
|
+
})
|
|
186
187
|
} catch (err: any) {
|
|
187
188
|
console.error(err)
|
|
188
189
|
const error = new (Error as any)('Invalid search params found', {
|
|
@@ -206,11 +207,42 @@ export function createRouteMatch<
|
|
|
206
207
|
hasLoaders: () => {
|
|
207
208
|
return !!(
|
|
208
209
|
route.options.loader ||
|
|
209
|
-
route.options.import ||
|
|
210
210
|
elementTypes.some((d) => typeof route.options[d] === 'function')
|
|
211
211
|
)
|
|
212
212
|
},
|
|
213
|
-
load: async (
|
|
213
|
+
load: async (loaderOpts) => {
|
|
214
|
+
const now = Date.now()
|
|
215
|
+
const minMaxAge = loaderOpts?.preload
|
|
216
|
+
? Math.max(loaderOpts?.maxAge, loaderOpts?.gcMaxAge)
|
|
217
|
+
: 0
|
|
218
|
+
|
|
219
|
+
// If this is a preload, add it to the preload cache
|
|
220
|
+
if (loaderOpts?.preload && minMaxAge > 0) {
|
|
221
|
+
// If the match is currently active, don't preload it
|
|
222
|
+
if (
|
|
223
|
+
router.state.matches.find((d) => d.matchId === routeMatch.matchId)
|
|
224
|
+
) {
|
|
225
|
+
return
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
router.matchCache[routeMatch.matchId] = {
|
|
229
|
+
gc: now + loaderOpts.gcMaxAge,
|
|
230
|
+
match: routeMatch as RouteMatch<any, any>,
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// If the match is invalid, errored or idle, trigger it to load
|
|
235
|
+
if (
|
|
236
|
+
(routeMatch.status === 'success' && routeMatch.getIsInvalid()) ||
|
|
237
|
+
routeMatch.status === 'error' ||
|
|
238
|
+
routeMatch.status === 'idle'
|
|
239
|
+
) {
|
|
240
|
+
const maxAge = loaderOpts?.preload ? loaderOpts?.maxAge : undefined
|
|
241
|
+
|
|
242
|
+
await routeMatch.fetch({ maxAge })
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
fetch: async (opts) => {
|
|
214
246
|
const id = '' + Date.now() + Math.random()
|
|
215
247
|
routeMatch.__.latestId = id
|
|
216
248
|
|
|
@@ -230,27 +262,8 @@ export function createRouteMatch<
|
|
|
230
262
|
routeMatch.isFetching = true
|
|
231
263
|
routeMatch.__.resolve = resolve as () => void
|
|
232
264
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
// First, run any importers
|
|
237
|
-
if (importer) {
|
|
238
|
-
routeMatch.__.importPromise = importer({
|
|
239
|
-
params: routeMatch.params,
|
|
240
|
-
// search: routeMatch.search,
|
|
241
|
-
}).then((imported) => {
|
|
242
|
-
routeMatch.__ = {
|
|
243
|
-
...routeMatch.__,
|
|
244
|
-
...imported,
|
|
245
|
-
}
|
|
246
|
-
})
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// Wait for the importer to finish before
|
|
250
|
-
// attempting to load elements and data
|
|
251
|
-
await routeMatch.__.importPromise
|
|
252
|
-
|
|
253
|
-
// Next, load the elements and data in parallel
|
|
265
|
+
routeMatch.__.loaderDataPromise = (async () => {
|
|
266
|
+
// Load the elements and data in parallel
|
|
254
267
|
|
|
255
268
|
routeMatch.__.elementsPromise = (async () => {
|
|
256
269
|
// then run all element and data loaders in parallel
|
|
@@ -260,16 +273,10 @@ export function createRouteMatch<
|
|
|
260
273
|
elementTypes.map(async (type) => {
|
|
261
274
|
const routeElement = routeMatch.options[type]
|
|
262
275
|
|
|
263
|
-
if (routeMatch.__[type]) {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
if (typeof routeElement === 'function') {
|
|
268
|
-
const res = await (routeElement as any)(routeMatch)
|
|
269
|
-
|
|
270
|
-
routeMatch.__[type] = res
|
|
271
|
-
} else {
|
|
272
|
-
routeMatch.__[type] = routeMatch.options[type] as any
|
|
276
|
+
if (typeof routeMatch.__[type] === 'function') {
|
|
277
|
+
routeMatch.__[type] = await router.options.createElement!(
|
|
278
|
+
routeElement,
|
|
279
|
+
)
|
|
273
280
|
}
|
|
274
281
|
}),
|
|
275
282
|
)
|
|
@@ -284,7 +291,7 @@ export function createRouteMatch<
|
|
|
284
291
|
signal: routeMatch.__.abortController.signal,
|
|
285
292
|
})
|
|
286
293
|
if (id !== routeMatch.__.latestId) {
|
|
287
|
-
return routeMatch.__.
|
|
294
|
+
return routeMatch.__.loadPromise
|
|
288
295
|
}
|
|
289
296
|
|
|
290
297
|
routeMatch.routeLoaderData = replaceEqualDeep(
|
|
@@ -304,7 +311,7 @@ export function createRouteMatch<
|
|
|
304
311
|
0)
|
|
305
312
|
} catch (err) {
|
|
306
313
|
if (id !== routeMatch.__.latestId) {
|
|
307
|
-
return routeMatch.__.
|
|
314
|
+
return routeMatch.__.loadPromise
|
|
308
315
|
}
|
|
309
316
|
|
|
310
317
|
if (process.env.NODE_ENV !== 'production') {
|
|
@@ -322,7 +329,7 @@ export function createRouteMatch<
|
|
|
322
329
|
routeMatch.__.dataPromise,
|
|
323
330
|
])
|
|
324
331
|
if (id !== routeMatch.__.latestId) {
|
|
325
|
-
return routeMatch.__.
|
|
332
|
+
return routeMatch.__.loadPromise
|
|
326
333
|
}
|
|
327
334
|
|
|
328
335
|
if (routeMatch.__.pendingMinPromise) {
|
|
@@ -331,7 +338,7 @@ export function createRouteMatch<
|
|
|
331
338
|
}
|
|
332
339
|
} finally {
|
|
333
340
|
if (id !== routeMatch.__.latestId) {
|
|
334
|
-
return routeMatch.__.
|
|
341
|
+
return routeMatch.__.loadPromise
|
|
335
342
|
}
|
|
336
343
|
routeMatch.__.cancelPending()
|
|
337
344
|
routeMatch.isPending = false
|
|
@@ -340,16 +347,18 @@ export function createRouteMatch<
|
|
|
340
347
|
}
|
|
341
348
|
})()
|
|
342
349
|
|
|
343
|
-
routeMatch.__.
|
|
344
|
-
await loaderPromise
|
|
350
|
+
await routeMatch.__.loaderDataPromise
|
|
345
351
|
|
|
346
352
|
if (id !== routeMatch.__.latestId) {
|
|
347
|
-
return routeMatch.__.
|
|
353
|
+
return routeMatch.__.loadPromise
|
|
348
354
|
}
|
|
349
|
-
|
|
355
|
+
|
|
356
|
+
delete routeMatch.__.loaderDataPromise
|
|
350
357
|
})
|
|
351
358
|
|
|
352
|
-
|
|
359
|
+
await routeMatch.__.loadPromise
|
|
360
|
+
|
|
361
|
+
delete routeMatch.__.loadPromise
|
|
353
362
|
},
|
|
354
363
|
}
|
|
355
364
|
|