@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.
Files changed (38) hide show
  1. package/build/cjs/index.js +2 -1
  2. package/build/cjs/index.js.map +1 -1
  3. package/build/cjs/path.js +5 -7
  4. package/build/cjs/path.js.map +1 -1
  5. package/build/cjs/route.js +112 -96
  6. package/build/cjs/route.js.map +1 -1
  7. package/build/cjs/routeConfig.js +2 -2
  8. package/build/cjs/routeConfig.js.map +1 -1
  9. package/build/cjs/routeMatch.js +107 -65
  10. package/build/cjs/routeMatch.js.map +1 -1
  11. package/build/cjs/router.js +352 -372
  12. package/build/cjs/router.js.map +1 -1
  13. package/build/cjs/searchParams.js +4 -3
  14. package/build/cjs/searchParams.js.map +1 -1
  15. package/build/cjs/sharedClone.js +122 -0
  16. package/build/cjs/sharedClone.js.map +1 -0
  17. package/build/cjs/utils.js +1 -59
  18. package/build/cjs/utils.js.map +1 -1
  19. package/build/esm/index.js +686 -614
  20. package/build/esm/index.js.map +1 -1
  21. package/build/stats-html.html +1 -1
  22. package/build/stats-react.json +183 -158
  23. package/build/types/index.d.ts +61 -78
  24. package/build/umd/index.development.js +1032 -617
  25. package/build/umd/index.development.js.map +1 -1
  26. package/build/umd/index.production.js +1 -1
  27. package/build/umd/index.production.js.map +1 -1
  28. package/package.json +2 -1
  29. package/src/index.ts +1 -0
  30. package/src/link.ts +20 -12
  31. package/src/route.ts +160 -140
  32. package/src/routeConfig.ts +7 -2
  33. package/src/routeMatch.ts +146 -99
  34. package/src/router.ts +462 -523
  35. package/src/sharedClone.ts +118 -0
  36. package/src/utils.ts +0 -65
  37. package/build/cjs/_virtual/_rollupPluginBabelHelpers.js +0 -31
  38. 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 { Expand, replaceEqualDeep } from './utils'
11
+ import { batch, createStore } from '@solidjs/reactivity'
12
+ import { Expand } from './utils'
13
+ import { sharedClone } from './sharedClone'
11
14
 
12
- export interface RouteMatch<
15
+ export interface RouteMatchStore<
13
16
  TAllRouteInfo extends AnyAllRouteInfo = DefaultAllRouteInfo,
14
17
  TRouteInfo extends AnyRouteInfo = RouteInfo,
15
- > extends Route<TAllRouteInfo, TRouteInfo> {
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
- const routeMatch: RouteMatch<TAllRouteInfo, TRouteInfo> = {
86
- ...route,
87
- ...opts,
88
- router,
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
- isInvalid: false,
122
+ invalid: false,
97
123
  invalidAt: Infinity,
98
- // pendingActions: [],
99
- getIsInvalid: () => {
124
+ get isInvalid(): boolean {
100
125
  const now = Date.now()
101
- return routeMatch.isInvalid || routeMatch.invalidAt < now
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
- abortController: new AbortController(),
105
- latestId: '',
106
- resolve: () => {},
107
- notify: () => {
108
- routeMatch.__.resolve()
109
- routeMatch.router.notify()
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
- routeMatch.parentMatch?.search ?? router.__location.search
151
+ store.parentMatch?.store.search ?? router.store.currentLocation.search
115
152
 
116
153
  try {
117
- const prevSearch = routeMatch.routeSearch
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 = replaceEqualDeep(
161
+ let nextSearch = sharedClone(
125
162
  prevSearch,
126
163
  validator?.(parentSearch) ?? {},
127
164
  )
128
165
 
129
- // Invalidate route matches when search param stability changes
130
- if (prevSearch !== nextSearch) {
131
- routeMatch.isInvalid = true
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
- routeMatch.search = replaceEqualDeep(parentSearch, {
137
- ...parentSearch,
138
- ...nextSearch,
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
- routeMatch.status = 'error'
155
- routeMatch.error = error
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
- routeMatch.isInvalid = true
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.state.matches.find((d) => d.matchId === routeMatch.matchId)
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
- (routeMatch.status === 'success' && routeMatch.getIsInvalid()) ||
197
- routeMatch.status === 'error' ||
198
- routeMatch.status === 'idle'
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
- routeMatch.__.latestId = loadId
254
+ latestId = loadId
208
255
  const checkLatest = async () => {
209
- if (loadId !== routeMatch.__.latestId) {
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
- // If the match was in an error state, set it
216
- // to a loading state again. Otherwise, keep it
217
- // as loading or resolved
218
- if (routeMatch.status === 'idle') {
219
- routeMatch.status = 'loading'
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
- // We started loading the route, so it's no longer invalid
223
- routeMatch.isInvalid = false
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 (resolve) => {
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
- routeMatch.isFetching = true
229
- routeMatch.__.resolve = resolve as () => void
277
+ setStore((s) => (s.isFetching = true))
278
+ resolve = r as () => void
230
279
 
231
- routeMatch.__.componentsPromise = (async () => {
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
- routeMatch.__.dataPromise = Promise.resolve().then(async () => {
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
- routeMatch.routeLoaderData = replaceEqualDeep(
255
- routeMatch.routeLoaderData,
256
- data,
257
- )
303
+ setLoaderData(data)
258
304
  }
259
305
 
260
- routeMatch.error = undefined
261
- routeMatch.status = 'success'
262
- routeMatch.updatedAt = Date.now()
263
- routeMatch.invalidAt =
264
- routeMatch.updatedAt +
265
- (opts?.maxAge ??
266
- routeMatch.options.loaderMaxAge ??
267
- router.options.defaultLoaderMaxAge ??
268
- 0)
269
-
270
- return routeMatch.routeLoaderData
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
- routeMatch.error = err
279
- routeMatch.status = 'error'
280
- routeMatch.updatedAt = Date.now()
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
- routeMatch.isFetching = false
338
+ setStore((s) => (s.isFetching = false))
289
339
  delete routeMatch.__.loadPromise
290
- routeMatch.__.notify()
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
- routeMatch.status = 'success'
357
+ setStore((s) => (s.status = 'success'))
311
358
  }
312
359
 
313
360
  return routeMatch