@tanstack/router-core 0.0.1-beta.36 → 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 -95
- 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 +320 -351
- 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 +654 -592
- 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 +54 -68
- package/build/umd/index.development.js +1000 -595
- 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 -139
- package/src/routeMatch.ts +144 -99
- package/src/router.ts +402 -491
- 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,14 +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(
|
|
184
229
|
(d) => d.matchId === routeMatch.matchId,
|
|
185
230
|
)
|
|
186
231
|
) {
|
|
187
232
|
return
|
|
188
233
|
}
|
|
189
234
|
|
|
190
|
-
router.matchCache[routeMatch.matchId] = {
|
|
235
|
+
router.store.matchCache[routeMatch.matchId] = {
|
|
191
236
|
gc: now + loaderOpts.gcMaxAge,
|
|
192
237
|
match: routeMatch as RouteMatch<any, any>,
|
|
193
238
|
}
|
|
@@ -195,9 +240,9 @@ export function createRouteMatch<
|
|
|
195
240
|
|
|
196
241
|
// If the match is invalid, errored or idle, trigger it to load
|
|
197
242
|
if (
|
|
198
|
-
(
|
|
199
|
-
|
|
200
|
-
|
|
243
|
+
(store.status === 'success' && store.isInvalid) ||
|
|
244
|
+
store.status === 'error' ||
|
|
245
|
+
store.status === 'idle'
|
|
201
246
|
) {
|
|
202
247
|
const maxAge = loaderOpts?.preload ? loaderOpts?.maxAge : undefined
|
|
203
248
|
|
|
@@ -206,31 +251,33 @@ export function createRouteMatch<
|
|
|
206
251
|
},
|
|
207
252
|
fetch: async (opts) => {
|
|
208
253
|
const loadId = '' + Date.now() + Math.random()
|
|
209
|
-
|
|
254
|
+
latestId = loadId
|
|
210
255
|
const checkLatest = async () => {
|
|
211
|
-
if (loadId !==
|
|
256
|
+
if (loadId !== latestId) {
|
|
212
257
|
// warning(true, 'Data loader is out of date!')
|
|
213
258
|
return new Promise(() => {})
|
|
214
259
|
}
|
|
215
260
|
}
|
|
216
261
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
+
}
|
|
223
269
|
|
|
224
|
-
|
|
225
|
-
|
|
270
|
+
// We started loading the route, so it's no longer invalid
|
|
271
|
+
setStore((s) => (s.invalid = false))
|
|
272
|
+
})
|
|
226
273
|
|
|
227
|
-
routeMatch.__.loadPromise = new Promise(async (
|
|
274
|
+
routeMatch.__.loadPromise = new Promise(async (r) => {
|
|
228
275
|
// We are now fetching, even if it's in the background of a
|
|
229
276
|
// resolved state
|
|
230
|
-
|
|
231
|
-
|
|
277
|
+
setStore((s) => (s.isFetching = true))
|
|
278
|
+
resolve = r as () => void
|
|
232
279
|
|
|
233
|
-
|
|
280
|
+
componentsPromise = (async () => {
|
|
234
281
|
// then run all component and data loaders in parallel
|
|
235
282
|
// For each component type, potentially load it asynchronously
|
|
236
283
|
|
|
@@ -247,29 +294,28 @@ export function createRouteMatch<
|
|
|
247
294
|
)
|
|
248
295
|
})()
|
|
249
296
|
|
|
250
|
-
|
|
297
|
+
dataPromise = Promise.resolve().then(async () => {
|
|
251
298
|
try {
|
|
252
299
|
if (routeMatch.options.loader) {
|
|
253
300
|
const data = await router.loadMatchData(routeMatch)
|
|
254
301
|
await checkLatest()
|
|
255
302
|
|
|
256
|
-
|
|
257
|
-
routeMatch.routeLoaderData,
|
|
258
|
-
data,
|
|
259
|
-
)
|
|
303
|
+
setLoaderData(data)
|
|
260
304
|
}
|
|
261
305
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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
|
|
273
319
|
} catch (err) {
|
|
274
320
|
await checkLatest()
|
|
275
321
|
|
|
@@ -277,9 +323,11 @@ export function createRouteMatch<
|
|
|
277
323
|
console.error(err)
|
|
278
324
|
}
|
|
279
325
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
326
|
+
setStore((s) => {
|
|
327
|
+
s.error = err
|
|
328
|
+
s.status = 'error'
|
|
329
|
+
s.updatedAt = Date.now()
|
|
330
|
+
})
|
|
283
331
|
|
|
284
332
|
throw err
|
|
285
333
|
}
|
|
@@ -287,16 +335,13 @@ export function createRouteMatch<
|
|
|
287
335
|
|
|
288
336
|
const after = async () => {
|
|
289
337
|
await checkLatest()
|
|
290
|
-
|
|
338
|
+
setStore((s) => (s.isFetching = false))
|
|
291
339
|
delete routeMatch.__.loadPromise
|
|
292
|
-
|
|
340
|
+
resolve()
|
|
293
341
|
}
|
|
294
342
|
|
|
295
343
|
try {
|
|
296
|
-
await Promise.all([
|
|
297
|
-
routeMatch.__.componentsPromise,
|
|
298
|
-
routeMatch.__.dataPromise.catch(() => {}),
|
|
299
|
-
])
|
|
344
|
+
await Promise.all([componentsPromise, dataPromise.catch(() => {})])
|
|
300
345
|
after()
|
|
301
346
|
} catch {
|
|
302
347
|
after()
|
|
@@ -309,7 +354,7 @@ export function createRouteMatch<
|
|
|
309
354
|
}
|
|
310
355
|
|
|
311
356
|
if (!routeMatch.hasLoaders()) {
|
|
312
|
-
|
|
357
|
+
setStore((s) => (s.status = 'success'))
|
|
313
358
|
}
|
|
314
359
|
|
|
315
360
|
return routeMatch
|