@tanstack/router-core 0.0.1-alpha.10 → 0.0.1-alpha.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cjs/packages/router-core/src/route.js +35 -0
- 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 +26 -7
- package/build/cjs/packages/router-core/src/routeMatch.js.map +1 -1
- package/build/cjs/packages/router-core/src/router.js +3 -23
- package/build/cjs/packages/router-core/src/router.js.map +1 -1
- package/build/esm/index.js +64 -30
- package/build/esm/index.js.map +1 -1
- package/build/stats-html.html +1 -1
- package/build/stats-react.json +136 -136
- package/build/types/index.d.ts +62 -25
- package/build/umd/index.development.js +64 -30
- 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/route.ts +64 -2
- package/src/routeConfig.ts +27 -55
- package/src/routeMatch.ts +44 -10
- package/src/router.ts +48 -29
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/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,
|
|
@@ -14,7 +14,14 @@ import {
|
|
|
14
14
|
RouteInfoByPath,
|
|
15
15
|
} from './routeInfo'
|
|
16
16
|
import { RouteMatch } from './routeMatch'
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
Action,
|
|
19
|
+
ActionState,
|
|
20
|
+
Loader,
|
|
21
|
+
LoaderState,
|
|
22
|
+
MatchRouteOptions,
|
|
23
|
+
Router,
|
|
24
|
+
} from './router'
|
|
18
25
|
import { NoInfer, replaceEqualDeep } from './utils'
|
|
19
26
|
|
|
20
27
|
export interface AnyRoute extends Route<any, any> {}
|
|
@@ -57,6 +64,21 @@ export interface Route<
|
|
|
57
64
|
| Action<TRouteInfo['actionPayload'], TRouteInfo['actionResponse']>
|
|
58
65
|
| undefined
|
|
59
66
|
: Action<TRouteInfo['actionPayload'], TRouteInfo['actionResponse']>
|
|
67
|
+
loader: unknown extends TRouteInfo['routeLoaderData']
|
|
68
|
+
?
|
|
69
|
+
| Action<
|
|
70
|
+
LoaderContext<
|
|
71
|
+
TRouteInfo['fullSearchSchema'],
|
|
72
|
+
TRouteInfo['allParams']
|
|
73
|
+
>,
|
|
74
|
+
TRouteInfo['routeLoaderData']
|
|
75
|
+
>
|
|
76
|
+
| undefined
|
|
77
|
+
: Loader<
|
|
78
|
+
TRouteInfo['fullSearchSchema'],
|
|
79
|
+
TRouteInfo['allParams'],
|
|
80
|
+
TRouteInfo['routeLoaderData']
|
|
81
|
+
>
|
|
60
82
|
}
|
|
61
83
|
|
|
62
84
|
export function createRoute<
|
|
@@ -126,6 +148,45 @@ export function createRoute<
|
|
|
126
148
|
return router.state.actions[id]!
|
|
127
149
|
})()
|
|
128
150
|
|
|
151
|
+
const loader =
|
|
152
|
+
router.state.loaders[id] ||
|
|
153
|
+
(() => {
|
|
154
|
+
router.state.loaders[id] = {
|
|
155
|
+
pending: [],
|
|
156
|
+
fetch: (async (loaderContext: LoaderContext<any, any>) => {
|
|
157
|
+
if (!route) {
|
|
158
|
+
return
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const loaderState: LoaderState<any, any> = {
|
|
162
|
+
loadedAt: Date.now(),
|
|
163
|
+
loaderContext,
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
loader.current = loaderState
|
|
167
|
+
loader.latest = loaderState
|
|
168
|
+
loader.pending.push(loaderState)
|
|
169
|
+
|
|
170
|
+
// router.state = {
|
|
171
|
+
// ...router.state,
|
|
172
|
+
// currentAction: loaderState,
|
|
173
|
+
// latestAction: loaderState,
|
|
174
|
+
// }
|
|
175
|
+
|
|
176
|
+
router.notify()
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
return await route.options.loader?.(loaderContext)
|
|
180
|
+
} finally {
|
|
181
|
+
loader.pending = loader.pending.filter((d) => d !== loaderState)
|
|
182
|
+
// router.removeActionQueue.push({ loader, loaderState })
|
|
183
|
+
router.notify()
|
|
184
|
+
}
|
|
185
|
+
}) as any,
|
|
186
|
+
}
|
|
187
|
+
return router.state.loaders[id]!
|
|
188
|
+
})()
|
|
189
|
+
|
|
129
190
|
let route: Route<TAllRouteInfo, TRouteInfo> = {
|
|
130
191
|
routeId: id,
|
|
131
192
|
routeRouteId: routeId,
|
|
@@ -136,6 +197,7 @@ export function createRoute<
|
|
|
136
197
|
childRoutes: undefined!,
|
|
137
198
|
parentRoute: parent,
|
|
138
199
|
action,
|
|
200
|
+
loader: loader as any,
|
|
139
201
|
|
|
140
202
|
buildLink: (options) => {
|
|
141
203
|
return router.buildLink({
|
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
|
@@ -16,7 +16,7 @@ export interface RouteMatch<
|
|
|
16
16
|
> extends Route<TAllRouteInfo, TRouteInfo> {
|
|
17
17
|
matchId: string
|
|
18
18
|
pathname: string
|
|
19
|
-
params:
|
|
19
|
+
params: TRouteInfo['params']
|
|
20
20
|
parentMatch?: RouteMatch
|
|
21
21
|
childMatches: RouteMatch[]
|
|
22
22
|
routeSearch: TRouteInfo['searchSchema']
|
|
@@ -60,7 +60,13 @@ export interface RouteMatch<
|
|
|
60
60
|
resolve: () => void
|
|
61
61
|
}
|
|
62
62
|
cancel: () => void
|
|
63
|
-
load: (
|
|
63
|
+
load: (
|
|
64
|
+
loaderOpts?: { withPending?: boolean } & (
|
|
65
|
+
| { preload: true; maxAge: number; gcMaxAge: number }
|
|
66
|
+
| { preload?: false; maxAge?: never; gcMaxAge?: never }
|
|
67
|
+
),
|
|
68
|
+
) => Promise<TRouteInfo['routeLoaderData']>
|
|
69
|
+
fetch: (opts?: { maxAge?: number }) => Promise<TRouteInfo['routeLoaderData']>
|
|
64
70
|
invalidate: () => void
|
|
65
71
|
hasLoaders: () => boolean
|
|
66
72
|
}
|
|
@@ -208,7 +214,39 @@ export function createRouteMatch<
|
|
|
208
214
|
elementTypes.some((d) => typeof route.options[d] === 'function')
|
|
209
215
|
)
|
|
210
216
|
},
|
|
211
|
-
load: async (
|
|
217
|
+
load: async (loaderOpts) => {
|
|
218
|
+
const now = Date.now()
|
|
219
|
+
const minMaxAge = loaderOpts?.preload
|
|
220
|
+
? Math.max(loaderOpts?.maxAge, loaderOpts?.gcMaxAge)
|
|
221
|
+
: 0
|
|
222
|
+
|
|
223
|
+
// If this is a preload, add it to the preload cache
|
|
224
|
+
if (loaderOpts?.preload && minMaxAge > 0) {
|
|
225
|
+
// If the match is currently active, don't preload it
|
|
226
|
+
if (
|
|
227
|
+
router.state.matches.find((d) => d.matchId === routeMatch.matchId)
|
|
228
|
+
) {
|
|
229
|
+
return
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
router.matchCache[routeMatch.matchId] = {
|
|
233
|
+
gc: now + loaderOpts.gcMaxAge,
|
|
234
|
+
match: routeMatch as RouteMatch<any, any>,
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// If the match is invalid, errored or idle, trigger it to load
|
|
239
|
+
if (
|
|
240
|
+
(routeMatch.status === 'success' && routeMatch.getIsInvalid()) ||
|
|
241
|
+
routeMatch.status === 'error' ||
|
|
242
|
+
routeMatch.status === 'idle'
|
|
243
|
+
) {
|
|
244
|
+
const maxAge = loaderOpts?.preload ? loaderOpts?.maxAge : undefined
|
|
245
|
+
|
|
246
|
+
routeMatch.fetch({ maxAge })
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
fetch: async (opts) => {
|
|
212
250
|
const id = '' + Date.now() + Math.random()
|
|
213
251
|
routeMatch.__.latestId = id
|
|
214
252
|
|
|
@@ -243,13 +281,9 @@ export function createRouteMatch<
|
|
|
243
281
|
return
|
|
244
282
|
}
|
|
245
283
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
routeMatch.__[type] = res
|
|
250
|
-
} else {
|
|
251
|
-
routeMatch.__[type] = routeMatch.options[type] as any
|
|
252
|
-
}
|
|
284
|
+
routeMatch.__[type] = await router.options.createElement!(
|
|
285
|
+
routeElement,
|
|
286
|
+
)
|
|
253
287
|
}),
|
|
254
288
|
)
|
|
255
289
|
})()
|
package/src/router.ts
CHANGED
|
@@ -25,8 +25,11 @@ import {
|
|
|
25
25
|
} from './path'
|
|
26
26
|
import { AnyRoute, cascadeLoaderData, createRoute, Route } from './route'
|
|
27
27
|
import {
|
|
28
|
+
AnyLoaderData,
|
|
29
|
+
AnyPathParams,
|
|
28
30
|
AnyRouteConfig,
|
|
29
31
|
AnySearchSchema,
|
|
32
|
+
LoaderContext,
|
|
30
33
|
RouteConfig,
|
|
31
34
|
SearchFilter,
|
|
32
35
|
} from './routeConfig'
|
|
@@ -43,6 +46,7 @@ import {
|
|
|
43
46
|
functionalUpdate,
|
|
44
47
|
last,
|
|
45
48
|
PickAsRequired,
|
|
49
|
+
PickRequired,
|
|
46
50
|
replaceEqualDeep,
|
|
47
51
|
Timeout,
|
|
48
52
|
Updater,
|
|
@@ -99,6 +103,11 @@ export interface RouterOptions<TRouteConfig extends AnyRouteConfig> {
|
|
|
99
103
|
basepath?: string
|
|
100
104
|
createRouter?: (router: Router<any, any>) => void
|
|
101
105
|
createRoute?: (opts: { route: AnyRoute; router: Router<any, any> }) => void
|
|
106
|
+
createElement?: (
|
|
107
|
+
element:
|
|
108
|
+
| GetFrameworkGeneric<'Element'>
|
|
109
|
+
| (() => Promise<GetFrameworkGeneric<'Element'>>),
|
|
110
|
+
) => Promise<GetFrameworkGeneric<'Element'>>
|
|
102
111
|
}
|
|
103
112
|
|
|
104
113
|
export interface Action<
|
|
@@ -124,6 +133,42 @@ export interface ActionState<
|
|
|
124
133
|
error?: unknown
|
|
125
134
|
}
|
|
126
135
|
|
|
136
|
+
export interface Loader<
|
|
137
|
+
TFullSearchSchema extends AnySearchSchema = {},
|
|
138
|
+
TAllParams extends AnyPathParams = {},
|
|
139
|
+
TRouteLoaderData = AnyLoaderData,
|
|
140
|
+
> {
|
|
141
|
+
fetch: keyof PickRequired<TFullSearchSchema> extends never
|
|
142
|
+
? keyof TAllParams extends never
|
|
143
|
+
? (loaderContext: { signal?: AbortSignal }) => Promise<TRouteLoaderData>
|
|
144
|
+
: (loaderContext: {
|
|
145
|
+
params: TAllParams
|
|
146
|
+
search?: TFullSearchSchema
|
|
147
|
+
signal?: AbortSignal
|
|
148
|
+
}) => Promise<TRouteLoaderData>
|
|
149
|
+
: keyof TAllParams extends never
|
|
150
|
+
? (loaderContext: {
|
|
151
|
+
search: TFullSearchSchema
|
|
152
|
+
params: TAllParams
|
|
153
|
+
signal?: AbortSignal
|
|
154
|
+
}) => Promise<TRouteLoaderData>
|
|
155
|
+
: (loaderContext: {
|
|
156
|
+
search: TFullSearchSchema
|
|
157
|
+
signal?: AbortSignal
|
|
158
|
+
}) => Promise<TRouteLoaderData>
|
|
159
|
+
current?: LoaderState<TFullSearchSchema, TAllParams>
|
|
160
|
+
latest?: LoaderState<TFullSearchSchema, TAllParams>
|
|
161
|
+
pending: LoaderState<TFullSearchSchema, TAllParams>[]
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export interface LoaderState<
|
|
165
|
+
TFullSearchSchema = unknown,
|
|
166
|
+
TAllParams = unknown,
|
|
167
|
+
> {
|
|
168
|
+
loadedAt: number
|
|
169
|
+
loaderContext: LoaderContext<TFullSearchSchema, TAllParams>
|
|
170
|
+
}
|
|
171
|
+
|
|
127
172
|
export interface RouterState {
|
|
128
173
|
status: 'idle' | 'loading'
|
|
129
174
|
location: Location
|
|
@@ -133,6 +178,7 @@ export interface RouterState {
|
|
|
133
178
|
currentAction?: ActionState
|
|
134
179
|
latestAction?: ActionState
|
|
135
180
|
actions: Record<string, Action>
|
|
181
|
+
loaders: Record<string, Loader>
|
|
136
182
|
pending?: PendingState
|
|
137
183
|
isFetching: boolean
|
|
138
184
|
isPreloading: boolean
|
|
@@ -322,6 +368,7 @@ export function createRouter<
|
|
|
322
368
|
location: null!,
|
|
323
369
|
matches: [],
|
|
324
370
|
actions: {},
|
|
371
|
+
loaders: {},
|
|
325
372
|
loaderData: {} as any,
|
|
326
373
|
lastUpdated: Date.now(),
|
|
327
374
|
isFetching: false,
|
|
@@ -705,38 +752,10 @@ export function createRouter<
|
|
|
705
752
|
},
|
|
706
753
|
|
|
707
754
|
loadMatches: async (resolvedMatches, loaderOpts) => {
|
|
708
|
-
const now = Date.now()
|
|
709
|
-
const minMaxAge = loaderOpts?.preload
|
|
710
|
-
? Math.max(loaderOpts?.maxAge, loaderOpts?.gcMaxAge)
|
|
711
|
-
: 0
|
|
712
|
-
|
|
713
755
|
const matchPromises = resolvedMatches.map(async (match) => {
|
|
714
756
|
// Validate the match (loads search params etc)
|
|
715
757
|
match.__.validate()
|
|
716
|
-
|
|
717
|
-
// If this is a preload, add it to the preload cache
|
|
718
|
-
if (loaderOpts?.preload && minMaxAge > 0) {
|
|
719
|
-
// If the match is currently active, don't preload it
|
|
720
|
-
if (router.state.matches.find((d) => d.matchId === match.matchId)) {
|
|
721
|
-
return
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
router.matchCache[match.matchId] = {
|
|
725
|
-
gc: now + loaderOpts.gcMaxAge,
|
|
726
|
-
match,
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
// If the match is invalid, errored or idle, trigger it to load
|
|
731
|
-
if (
|
|
732
|
-
(match.status === 'success' && match.getIsInvalid()) ||
|
|
733
|
-
match.status === 'error' ||
|
|
734
|
-
match.status === 'idle'
|
|
735
|
-
) {
|
|
736
|
-
const maxAge = loaderOpts?.preload ? loaderOpts?.maxAge : undefined
|
|
737
|
-
|
|
738
|
-
match.load({ maxAge })
|
|
739
|
-
}
|
|
758
|
+
match.load(loaderOpts)
|
|
740
759
|
|
|
741
760
|
if (match.status === 'loading') {
|
|
742
761
|
// If requested, start the pending timers
|