@tanstack/router-core 0.0.1-beta.35 → 0.0.1-beta.39
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/index.js +2 -1
- package/build/cjs/index.js.map +1 -1
- package/build/cjs/path.js +5 -7
- package/build/cjs/path.js.map +1 -1
- package/build/cjs/route.js +112 -96
- package/build/cjs/route.js.map +1 -1
- package/build/cjs/routeConfig.js +2 -2
- package/build/cjs/routeConfig.js.map +1 -1
- package/build/cjs/routeMatch.js +107 -65
- package/build/cjs/routeMatch.js.map +1 -1
- package/build/cjs/router.js +352 -372
- package/build/cjs/router.js.map +1 -1
- package/build/cjs/searchParams.js +4 -3
- package/build/cjs/searchParams.js.map +1 -1
- package/build/cjs/sharedClone.js +122 -0
- package/build/cjs/sharedClone.js.map +1 -0
- package/build/cjs/utils.js +1 -59
- package/build/cjs/utils.js.map +1 -1
- package/build/esm/index.js +686 -614
- package/build/esm/index.js.map +1 -1
- package/build/stats-html.html +1 -1
- package/build/stats-react.json +183 -158
- package/build/types/index.d.ts +61 -78
- package/build/umd/index.development.js +1032 -617
- 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 +2 -1
- package/src/index.ts +1 -0
- package/src/link.ts +20 -12
- package/src/route.ts +160 -140
- package/src/routeConfig.ts +7 -2
- package/src/routeMatch.ts +146 -99
- package/src/router.ts +462 -523
- package/src/sharedClone.ts +118 -0
- package/src/utils.ts +0 -65
- package/build/cjs/_virtual/_rollupPluginBabelHelpers.js +0 -31
- package/build/cjs/_virtual/_rollupPluginBabelHelpers.js.map +0 -1
package/src/routeMatch.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { boolean } from 'zod'
|
|
1
2
|
import { GetFrameworkGeneric } from './frameworks'
|
|
2
3
|
import { Route } from './route'
|
|
3
4
|
import {
|
|
@@ -7,17 +8,15 @@ import {
|
|
|
7
8
|
RouteInfo,
|
|
8
9
|
} from './routeInfo'
|
|
9
10
|
import { Router } from './router'
|
|
10
|
-
import {
|
|
11
|
+
import { batch, createStore } from '@solidjs/reactivity'
|
|
12
|
+
import { Expand } from './utils'
|
|
13
|
+
import { sharedClone } from './sharedClone'
|
|
11
14
|
|
|
12
|
-
export interface
|
|
15
|
+
export interface RouteMatchStore<
|
|
13
16
|
TAllRouteInfo extends AnyAllRouteInfo = DefaultAllRouteInfo,
|
|
14
17
|
TRouteInfo extends AnyRouteInfo = RouteInfo,
|
|
15
|
-
>
|
|
16
|
-
matchId: string
|
|
17
|
-
pathname: string
|
|
18
|
-
params: TRouteInfo['allParams']
|
|
18
|
+
> {
|
|
19
19
|
parentMatch?: RouteMatch
|
|
20
|
-
childMatches: RouteMatch[]
|
|
21
20
|
routeSearch: TRouteInfo['searchSchema']
|
|
22
21
|
search: Expand<
|
|
23
22
|
TAllRouteInfo['fullSearchSchema'] & TRouteInfo['fullSearchSchema']
|
|
@@ -25,19 +24,39 @@ export interface RouteMatch<
|
|
|
25
24
|
status: 'idle' | 'loading' | 'success' | 'error'
|
|
26
25
|
updatedAt?: number
|
|
27
26
|
error?: unknown
|
|
27
|
+
invalid: boolean
|
|
28
28
|
isInvalid: boolean
|
|
29
|
-
getIsInvalid: () => boolean
|
|
30
29
|
loaderData: TRouteInfo['loaderData']
|
|
31
30
|
routeLoaderData: TRouteInfo['routeLoaderData']
|
|
32
31
|
isFetching: boolean
|
|
33
32
|
invalidAt: number
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface RouteMatch<
|
|
36
|
+
TAllRouteInfo extends AnyAllRouteInfo = DefaultAllRouteInfo,
|
|
37
|
+
TRouteInfo extends AnyRouteInfo = RouteInfo,
|
|
38
|
+
> extends Route<TAllRouteInfo, TRouteInfo> {
|
|
39
|
+
store: RouteMatchStore<TAllRouteInfo, TRouteInfo>
|
|
40
|
+
// setStore: WritableStore<RouteMatchStore<TAllRouteInfo, TRouteInfo>>
|
|
41
|
+
matchId: string
|
|
42
|
+
pathname: string
|
|
43
|
+
params: TRouteInfo['allParams']
|
|
44
|
+
childMatches: RouteMatch[]
|
|
45
|
+
cancel: () => void
|
|
46
|
+
load: (
|
|
47
|
+
loaderOpts?:
|
|
48
|
+
| { preload: true; maxAge: number; gcMaxAge: number }
|
|
49
|
+
| { preload?: false; maxAge?: never; gcMaxAge?: never },
|
|
50
|
+
) => Promise<TRouteInfo['routeLoaderData']>
|
|
51
|
+
fetch: (opts?: { maxAge?: number }) => Promise<TRouteInfo['routeLoaderData']>
|
|
52
|
+
invalidate: () => void
|
|
53
|
+
hasLoaders: () => boolean
|
|
34
54
|
__: {
|
|
55
|
+
setParentMatch: (parentMatch?: RouteMatch) => void
|
|
35
56
|
component?: GetFrameworkGeneric<'Component'>
|
|
36
57
|
errorComponent?: GetFrameworkGeneric<'ErrorComponent'>
|
|
37
58
|
pendingComponent?: GetFrameworkGeneric<'Component'>
|
|
38
59
|
loadPromise?: Promise<void>
|
|
39
|
-
componentsPromise?: Promise<void>
|
|
40
|
-
dataPromise?: Promise<TRouteInfo['routeLoaderData']>
|
|
41
60
|
onExit?:
|
|
42
61
|
| void
|
|
43
62
|
| ((matchContext: {
|
|
@@ -45,22 +64,8 @@ export interface RouteMatch<
|
|
|
45
64
|
search: TRouteInfo['fullSearchSchema']
|
|
46
65
|
}) => void)
|
|
47
66
|
abortController: AbortController
|
|
48
|
-
latestId: string
|
|
49
|
-
// setParentMatch: (parentMatch: RouteMatch) => void
|
|
50
|
-
// addChildMatch: (childMatch: RouteMatch) => void
|
|
51
67
|
validate: () => void
|
|
52
|
-
notify: () => void
|
|
53
|
-
resolve: () => void
|
|
54
68
|
}
|
|
55
|
-
cancel: () => void
|
|
56
|
-
load: (
|
|
57
|
-
loaderOpts?:
|
|
58
|
-
| { preload: true; maxAge: number; gcMaxAge: number }
|
|
59
|
-
| { preload?: false; maxAge?: never; gcMaxAge?: never },
|
|
60
|
-
) => Promise<TRouteInfo['routeLoaderData']>
|
|
61
|
-
fetch: (opts?: { maxAge?: number }) => Promise<TRouteInfo['routeLoaderData']>
|
|
62
|
-
invalidate: () => void
|
|
63
|
-
hasLoaders: () => boolean
|
|
64
69
|
}
|
|
65
70
|
|
|
66
71
|
const componentTypes = [
|
|
@@ -82,60 +87,96 @@ export function createRouteMatch<
|
|
|
82
87
|
pathname: string
|
|
83
88
|
},
|
|
84
89
|
): RouteMatch<TAllRouteInfo, TRouteInfo> {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
90
|
+
let componentsPromise: Promise<void>
|
|
91
|
+
let dataPromise: Promise<TRouteInfo['routeLoaderData']>
|
|
92
|
+
let latestId = ''
|
|
93
|
+
let resolve = () => {}
|
|
94
|
+
|
|
95
|
+
function setLoaderData(loaderData: TRouteInfo['routeLoaderData']) {
|
|
96
|
+
batch(() => {
|
|
97
|
+
setStore((s) => {
|
|
98
|
+
s.routeLoaderData = sharedClone(s.routeLoaderData, loaderData)
|
|
99
|
+
})
|
|
100
|
+
updateLoaderData()
|
|
101
|
+
})
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function updateLoaderData() {
|
|
105
|
+
setStore((s) => {
|
|
106
|
+
s.loaderData = sharedClone(s.loaderData, {
|
|
107
|
+
...store.parentMatch?.store.loaderData,
|
|
108
|
+
...s.routeLoaderData,
|
|
109
|
+
}) as TRouteInfo['loaderData']
|
|
110
|
+
})
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const [store, setStore] = createStore<
|
|
114
|
+
RouteMatchStore<TAllRouteInfo, TRouteInfo>
|
|
115
|
+
>({
|
|
89
116
|
routeSearch: {},
|
|
90
117
|
search: {} as any,
|
|
91
|
-
childMatches: [],
|
|
92
118
|
status: 'idle',
|
|
93
119
|
routeLoaderData: {} as TRouteInfo['routeLoaderData'],
|
|
94
120
|
loaderData: {} as TRouteInfo['loaderData'],
|
|
95
121
|
isFetching: false,
|
|
96
|
-
|
|
122
|
+
invalid: false,
|
|
97
123
|
invalidAt: Infinity,
|
|
98
|
-
|
|
99
|
-
getIsInvalid: () => {
|
|
124
|
+
get isInvalid(): boolean {
|
|
100
125
|
const now = Date.now()
|
|
101
|
-
return
|
|
126
|
+
return this.invalid || this.invalidAt < now
|
|
102
127
|
},
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
const routeMatch: RouteMatch<TAllRouteInfo, TRouteInfo> = {
|
|
131
|
+
...route,
|
|
132
|
+
...opts,
|
|
133
|
+
store,
|
|
134
|
+
// setStore,
|
|
135
|
+
router,
|
|
136
|
+
childMatches: [],
|
|
103
137
|
__: {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
138
|
+
setParentMatch: (parentMatch?: RouteMatch) => {
|
|
139
|
+
batch(() => {
|
|
140
|
+
setStore((s) => {
|
|
141
|
+
s.parentMatch = parentMatch
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
updateLoaderData()
|
|
145
|
+
})
|
|
110
146
|
},
|
|
147
|
+
abortController: new AbortController(),
|
|
111
148
|
validate: () => {
|
|
112
149
|
// Validate the search params and stabilize them
|
|
113
150
|
const parentSearch =
|
|
114
|
-
|
|
151
|
+
store.parentMatch?.store.search ?? router.store.currentLocation.search
|
|
115
152
|
|
|
116
153
|
try {
|
|
117
|
-
const prevSearch =
|
|
154
|
+
const prevSearch = store.routeSearch
|
|
118
155
|
|
|
119
156
|
const validator =
|
|
120
157
|
typeof routeMatch.options.validateSearch === 'object'
|
|
121
158
|
? routeMatch.options.validateSearch.parse
|
|
122
159
|
: routeMatch.options.validateSearch
|
|
123
160
|
|
|
124
|
-
let nextSearch =
|
|
161
|
+
let nextSearch = sharedClone(
|
|
125
162
|
prevSearch,
|
|
126
163
|
validator?.(parentSearch) ?? {},
|
|
127
164
|
)
|
|
128
165
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
routeMatch.routeSearch = nextSearch
|
|
166
|
+
batch(() => {
|
|
167
|
+
// Invalidate route matches when search param stability changes
|
|
168
|
+
if (prevSearch !== nextSearch) {
|
|
169
|
+
setStore((s) => (s.invalid = true))
|
|
170
|
+
}
|
|
135
171
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
172
|
+
// TODO: Alright, do we need batch() here?
|
|
173
|
+
setStore((s) => {
|
|
174
|
+
s.routeSearch = nextSearch
|
|
175
|
+
s.search = sharedClone(parentSearch, {
|
|
176
|
+
...parentSearch,
|
|
177
|
+
...nextSearch,
|
|
178
|
+
})
|
|
179
|
+
})
|
|
139
180
|
})
|
|
140
181
|
|
|
141
182
|
componentTypes.map(async (type) => {
|
|
@@ -151,8 +192,12 @@ export function createRouteMatch<
|
|
|
151
192
|
cause: err,
|
|
152
193
|
})
|
|
153
194
|
error.code = 'INVALID_SEARCH_PARAMS'
|
|
154
|
-
|
|
155
|
-
|
|
195
|
+
|
|
196
|
+
setStore((s) => {
|
|
197
|
+
s.status = 'error'
|
|
198
|
+
s.error = error
|
|
199
|
+
})
|
|
200
|
+
|
|
156
201
|
// Do not proceed with loading the route
|
|
157
202
|
return
|
|
158
203
|
}
|
|
@@ -162,7 +207,7 @@ export function createRouteMatch<
|
|
|
162
207
|
routeMatch.__.abortController?.abort()
|
|
163
208
|
},
|
|
164
209
|
invalidate: () => {
|
|
165
|
-
|
|
210
|
+
setStore((s) => (s.invalid = true))
|
|
166
211
|
},
|
|
167
212
|
hasLoaders: () => {
|
|
168
213
|
return !!(
|
|
@@ -180,12 +225,14 @@ export function createRouteMatch<
|
|
|
180
225
|
if (loaderOpts?.preload && minMaxAge > 0) {
|
|
181
226
|
// If the match is currently active, don't preload it
|
|
182
227
|
if (
|
|
183
|
-
router.
|
|
228
|
+
router.store.currentMatches.find(
|
|
229
|
+
(d) => d.matchId === routeMatch.matchId,
|
|
230
|
+
)
|
|
184
231
|
) {
|
|
185
232
|
return
|
|
186
233
|
}
|
|
187
234
|
|
|
188
|
-
router.matchCache[routeMatch.matchId] = {
|
|
235
|
+
router.store.matchCache[routeMatch.matchId] = {
|
|
189
236
|
gc: now + loaderOpts.gcMaxAge,
|
|
190
237
|
match: routeMatch as RouteMatch<any, any>,
|
|
191
238
|
}
|
|
@@ -193,9 +240,9 @@ export function createRouteMatch<
|
|
|
193
240
|
|
|
194
241
|
// If the match is invalid, errored or idle, trigger it to load
|
|
195
242
|
if (
|
|
196
|
-
(
|
|
197
|
-
|
|
198
|
-
|
|
243
|
+
(store.status === 'success' && store.isInvalid) ||
|
|
244
|
+
store.status === 'error' ||
|
|
245
|
+
store.status === 'idle'
|
|
199
246
|
) {
|
|
200
247
|
const maxAge = loaderOpts?.preload ? loaderOpts?.maxAge : undefined
|
|
201
248
|
|
|
@@ -204,31 +251,33 @@ export function createRouteMatch<
|
|
|
204
251
|
},
|
|
205
252
|
fetch: async (opts) => {
|
|
206
253
|
const loadId = '' + Date.now() + Math.random()
|
|
207
|
-
|
|
254
|
+
latestId = loadId
|
|
208
255
|
const checkLatest = async () => {
|
|
209
|
-
if (loadId !==
|
|
256
|
+
if (loadId !== latestId) {
|
|
210
257
|
// warning(true, 'Data loader is out of date!')
|
|
211
258
|
return new Promise(() => {})
|
|
212
259
|
}
|
|
213
260
|
}
|
|
214
261
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
262
|
+
batch(() => {
|
|
263
|
+
// If the match was in an error state, set it
|
|
264
|
+
// to a loading state again. Otherwise, keep it
|
|
265
|
+
// as loading or resolved
|
|
266
|
+
if (store.status === 'idle') {
|
|
267
|
+
setStore((s) => (s.status = 'loading'))
|
|
268
|
+
}
|
|
221
269
|
|
|
222
|
-
|
|
223
|
-
|
|
270
|
+
// We started loading the route, so it's no longer invalid
|
|
271
|
+
setStore((s) => (s.invalid = false))
|
|
272
|
+
})
|
|
224
273
|
|
|
225
|
-
routeMatch.__.loadPromise = new Promise(async (
|
|
274
|
+
routeMatch.__.loadPromise = new Promise(async (r) => {
|
|
226
275
|
// We are now fetching, even if it's in the background of a
|
|
227
276
|
// resolved state
|
|
228
|
-
|
|
229
|
-
|
|
277
|
+
setStore((s) => (s.isFetching = true))
|
|
278
|
+
resolve = r as () => void
|
|
230
279
|
|
|
231
|
-
|
|
280
|
+
componentsPromise = (async () => {
|
|
232
281
|
// then run all component and data loaders in parallel
|
|
233
282
|
// For each component type, potentially load it asynchronously
|
|
234
283
|
|
|
@@ -245,29 +294,28 @@ export function createRouteMatch<
|
|
|
245
294
|
)
|
|
246
295
|
})()
|
|
247
296
|
|
|
248
|
-
|
|
297
|
+
dataPromise = Promise.resolve().then(async () => {
|
|
249
298
|
try {
|
|
250
299
|
if (routeMatch.options.loader) {
|
|
251
300
|
const data = await router.loadMatchData(routeMatch)
|
|
252
301
|
await checkLatest()
|
|
253
302
|
|
|
254
|
-
|
|
255
|
-
routeMatch.routeLoaderData,
|
|
256
|
-
data,
|
|
257
|
-
)
|
|
303
|
+
setLoaderData(data)
|
|
258
304
|
}
|
|
259
305
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
306
|
+
setStore((s) => {
|
|
307
|
+
s.error = undefined
|
|
308
|
+
s.status = 'success'
|
|
309
|
+
s.updatedAt = Date.now()
|
|
310
|
+
s.invalidAt =
|
|
311
|
+
s.updatedAt +
|
|
312
|
+
(opts?.maxAge ??
|
|
313
|
+
routeMatch.options.loaderMaxAge ??
|
|
314
|
+
router.options.defaultLoaderMaxAge ??
|
|
315
|
+
0)
|
|
316
|
+
})
|
|
317
|
+
|
|
318
|
+
return store.routeLoaderData
|
|
271
319
|
} catch (err) {
|
|
272
320
|
await checkLatest()
|
|
273
321
|
|
|
@@ -275,9 +323,11 @@ export function createRouteMatch<
|
|
|
275
323
|
console.error(err)
|
|
276
324
|
}
|
|
277
325
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
326
|
+
setStore((s) => {
|
|
327
|
+
s.error = err
|
|
328
|
+
s.status = 'error'
|
|
329
|
+
s.updatedAt = Date.now()
|
|
330
|
+
})
|
|
281
331
|
|
|
282
332
|
throw err
|
|
283
333
|
}
|
|
@@ -285,16 +335,13 @@ export function createRouteMatch<
|
|
|
285
335
|
|
|
286
336
|
const after = async () => {
|
|
287
337
|
await checkLatest()
|
|
288
|
-
|
|
338
|
+
setStore((s) => (s.isFetching = false))
|
|
289
339
|
delete routeMatch.__.loadPromise
|
|
290
|
-
|
|
340
|
+
resolve()
|
|
291
341
|
}
|
|
292
342
|
|
|
293
343
|
try {
|
|
294
|
-
await Promise.all([
|
|
295
|
-
routeMatch.__.componentsPromise,
|
|
296
|
-
routeMatch.__.dataPromise.catch(() => {}),
|
|
297
|
-
])
|
|
344
|
+
await Promise.all([componentsPromise, dataPromise.catch(() => {})])
|
|
298
345
|
after()
|
|
299
346
|
} catch {
|
|
300
347
|
after()
|
|
@@ -307,7 +354,7 @@ export function createRouteMatch<
|
|
|
307
354
|
}
|
|
308
355
|
|
|
309
356
|
if (!routeMatch.hasLoaders()) {
|
|
310
|
-
|
|
357
|
+
setStore((s) => (s.status = 'success'))
|
|
311
358
|
}
|
|
312
359
|
|
|
313
360
|
return routeMatch
|