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