@tanstack/router-core 0.0.1-alpha.5 → 0.0.1-alpha.7

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 (40) hide show
  1. package/build/cjs/packages/router-core/src/index.js +33 -1451
  2. package/build/cjs/packages/router-core/src/index.js.map +1 -1
  3. package/build/cjs/packages/router-core/src/path.js +222 -0
  4. package/build/cjs/packages/router-core/src/path.js.map +1 -0
  5. package/build/cjs/packages/router-core/src/qss.js +1 -1
  6. package/build/cjs/packages/router-core/src/qss.js.map +1 -1
  7. package/build/cjs/packages/router-core/src/route.js +126 -0
  8. package/build/cjs/packages/router-core/src/route.js.map +1 -0
  9. package/build/cjs/packages/router-core/src/routeConfig.js +69 -0
  10. package/build/cjs/packages/router-core/src/routeConfig.js.map +1 -0
  11. package/build/cjs/packages/router-core/src/routeMatch.js +260 -0
  12. package/build/cjs/packages/router-core/src/routeMatch.js.map +1 -0
  13. package/build/cjs/packages/router-core/src/router.js +787 -0
  14. package/build/cjs/packages/router-core/src/router.js.map +1 -0
  15. package/build/cjs/packages/router-core/src/searchParams.js +70 -0
  16. package/build/cjs/packages/router-core/src/searchParams.js.map +1 -0
  17. package/build/cjs/packages/router-core/src/utils.js +118 -0
  18. package/build/cjs/packages/router-core/src/utils.js.map +1 -0
  19. package/build/esm/index.js +1304 -1238
  20. package/build/esm/index.js.map +1 -1
  21. package/build/stats-html.html +1 -1
  22. package/build/stats-react.json +374 -57
  23. package/build/types/index.d.ts +361 -333
  24. package/build/umd/index.development.js +1313 -1238
  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 -3
  29. package/src/frameworks.ts +13 -0
  30. package/src/index.ts +15 -3054
  31. package/src/link.ts +289 -0
  32. package/src/path.ts +236 -0
  33. package/src/qss.ts +1 -1
  34. package/src/route.ts +181 -0
  35. package/src/routeConfig.ts +523 -0
  36. package/src/routeInfo.ts +228 -0
  37. package/src/routeMatch.ts +357 -0
  38. package/src/router.ts +1182 -0
  39. package/src/searchParams.ts +54 -0
  40. package/src/utils.ts +157 -0
@@ -0,0 +1,228 @@
1
+ import { ParsePathParams } from './link'
2
+ import { Route } from './route'
3
+ import {
4
+ AnyLoaderData,
5
+ AnyPathParams,
6
+ AnyRouteConfig,
7
+ AnyRouteConfigWithChildren,
8
+ AnySearchSchema,
9
+ RootRouteId,
10
+ RouteConfig,
11
+ RouteOptions,
12
+ } from './routeConfig'
13
+ import { IsAny, Values } from './utils'
14
+
15
+ export interface AnyAllRouteInfo {
16
+ routeConfig: AnyRouteConfig
17
+ routeInfo: AnyRouteInfo
18
+ routeInfoById: Record<string, AnyRouteInfo>
19
+ routeInfoByFullPath: Record<string, AnyRouteInfo>
20
+ routeIds: any
21
+ routePaths: any
22
+ }
23
+
24
+ export interface DefaultAllRouteInfo {
25
+ routeConfig: RouteConfig
26
+ routeInfo: RouteInfo
27
+ routeInfoById: Record<string, RouteInfo>
28
+ routeInfoByFullPath: Record<string, RouteInfo>
29
+ routeIds: string
30
+ routePaths: string
31
+ }
32
+
33
+ export interface AllRouteInfo<TRouteConfig extends AnyRouteConfig = RouteConfig>
34
+ extends RoutesInfoInner<TRouteConfig, ParseRouteConfig<TRouteConfig>> {}
35
+
36
+ export type ParseRouteConfig<TRouteConfig = AnyRouteConfig> =
37
+ TRouteConfig extends AnyRouteConfig
38
+ ? RouteConfigRoute<TRouteConfig> | ParseRouteChildren<TRouteConfig>
39
+ : never
40
+
41
+ type ParseRouteChildren<TRouteConfig> =
42
+ TRouteConfig extends AnyRouteConfigWithChildren<infer TChildren>
43
+ ? unknown extends TChildren
44
+ ? never
45
+ : TChildren extends AnyRouteConfig[]
46
+ ? Values<{
47
+ [TId in TChildren[number]['id']]: ParseRouteChild<
48
+ TChildren[number],
49
+ TId
50
+ >
51
+ }>
52
+ : never // Children are not routes
53
+ : never // No children
54
+
55
+ type ParseRouteChild<TRouteConfig, TId> = TRouteConfig & {
56
+ id: TId
57
+ } extends AnyRouteConfig
58
+ ? ParseRouteConfig<TRouteConfig>
59
+ : never
60
+
61
+ export type RouteConfigRoute<TRouteConfig> = TRouteConfig extends RouteConfig<
62
+ infer TId,
63
+ infer TRouteId,
64
+ infer TPath,
65
+ infer TFullPath,
66
+ infer TRouteLoaderData,
67
+ infer TLoaderData,
68
+ infer TActionPayload,
69
+ infer TActionResponse,
70
+ infer TParentSearchSchema,
71
+ infer TSearchSchema,
72
+ infer TFullSearchSchema,
73
+ infer TParentParams,
74
+ infer TParams,
75
+ infer TAllParams,
76
+ any
77
+ >
78
+ ? string extends TRouteId
79
+ ? never
80
+ : RouteInfo<
81
+ TId,
82
+ TRouteId,
83
+ TPath,
84
+ TFullPath,
85
+ TRouteLoaderData,
86
+ TLoaderData,
87
+ TActionPayload,
88
+ TActionResponse,
89
+ TParentSearchSchema,
90
+ TSearchSchema,
91
+ TFullSearchSchema,
92
+ TParentParams,
93
+ TParams,
94
+ TAllParams
95
+ >
96
+ : never
97
+
98
+ export interface RoutesInfoInner<
99
+ TRouteConfig extends AnyRouteConfig,
100
+ TRouteInfo extends RouteInfo<
101
+ string,
102
+ string,
103
+ any,
104
+ any,
105
+ any,
106
+ any,
107
+ any,
108
+ any,
109
+ any,
110
+ any,
111
+ any,
112
+ any,
113
+ any,
114
+ any
115
+ > = RouteInfo,
116
+ TRouteInfoById = {
117
+ [TInfo in TRouteInfo as TInfo['id']]: TInfo
118
+ },
119
+ TRouteInfoByFullPath = {
120
+ [TInfo in TRouteInfo as TInfo['fullPath'] extends RootRouteId
121
+ ? never
122
+ : string extends TInfo['fullPath']
123
+ ? never
124
+ : TInfo['fullPath']]: TInfo
125
+ },
126
+ > {
127
+ routeConfig: TRouteConfig
128
+ routeInfo: TRouteInfo
129
+ routeInfoById: TRouteInfoById
130
+ routeInfoByFullPath: TRouteInfoByFullPath
131
+ routeIds: keyof TRouteInfoById
132
+ routePaths: keyof TRouteInfoByFullPath
133
+ }
134
+
135
+ export interface AnyRouteInfo
136
+ extends RouteInfo<
137
+ any,
138
+ any,
139
+ any,
140
+ any,
141
+ any,
142
+ any,
143
+ any,
144
+ any,
145
+ any,
146
+ any,
147
+ any,
148
+ any,
149
+ any,
150
+ any
151
+ > {}
152
+
153
+ export interface RouteInfo<
154
+ TId extends string = string,
155
+ TRouteId extends string = string,
156
+ TPath extends string = string,
157
+ TFullPath extends string = string,
158
+ TRouteLoaderData extends AnyLoaderData = {},
159
+ TLoaderData extends AnyLoaderData = {},
160
+ TActionPayload = unknown,
161
+ TActionResponse = unknown,
162
+ TParentSearchSchema extends {} = {},
163
+ TSearchSchema extends AnySearchSchema = {},
164
+ TFullSearchSchema extends AnySearchSchema = {},
165
+ TParentParams extends AnyPathParams = {},
166
+ TParams extends Record<ParsePathParams<TPath>, unknown> = Record<
167
+ ParsePathParams<TPath>,
168
+ string
169
+ >,
170
+ TAllParams extends AnyPathParams = {},
171
+ > {
172
+ id: TId
173
+ routeId: TRouteId
174
+ path: TPath
175
+ fullPath: TFullPath
176
+ routeLoaderData: TRouteLoaderData
177
+ loaderData: TLoaderData
178
+ actionPayload: TActionPayload
179
+ actionResponse: TActionResponse
180
+ searchSchema: TSearchSchema
181
+ fullSearchSchema: TFullSearchSchema
182
+ parentParams: TParentParams
183
+ params: TParams
184
+ allParams: TAllParams
185
+ options: RouteOptions<
186
+ TRouteId,
187
+ TPath,
188
+ TRouteLoaderData,
189
+ TLoaderData,
190
+ TActionPayload,
191
+ TActionResponse,
192
+ TParentSearchSchema,
193
+ TSearchSchema,
194
+ TFullSearchSchema,
195
+ TParentParams,
196
+ TParams,
197
+ TAllParams
198
+ >
199
+ }
200
+
201
+ export type RoutesById<TAllRouteInfo extends AnyAllRouteInfo> = {
202
+ [K in keyof TAllRouteInfo['routeInfoById']]: Route<
203
+ TAllRouteInfo,
204
+ TAllRouteInfo['routeInfoById'][K]
205
+ >
206
+ }
207
+
208
+ export type RouteInfoById<
209
+ TAllRouteInfo extends AnyAllRouteInfo,
210
+ TId,
211
+ > = TId extends keyof TAllRouteInfo['routeInfoById']
212
+ ? IsAny<
213
+ TAllRouteInfo['routeInfoById'][TId]['id'],
214
+ RouteInfo,
215
+ TAllRouteInfo['routeInfoById'][TId]
216
+ >
217
+ : never
218
+
219
+ export type RouteInfoByPath<
220
+ TAllRouteInfo extends AnyAllRouteInfo,
221
+ TPath,
222
+ > = TPath extends keyof TAllRouteInfo['routeInfoByFullPath']
223
+ ? IsAny<
224
+ TAllRouteInfo['routeInfoByFullPath'][TPath]['id'],
225
+ RouteInfo,
226
+ TAllRouteInfo['routeInfoByFullPath'][TPath]
227
+ >
228
+ : never
@@ -0,0 +1,357 @@
1
+ import { GetFrameworkGeneric } from './frameworks'
2
+ import { Route } from './route'
3
+ import { AnyPathParams } from './routeConfig'
4
+ import {
5
+ AnyAllRouteInfo,
6
+ AnyRouteInfo,
7
+ DefaultAllRouteInfo,
8
+ RouteInfo,
9
+ } from './routeInfo'
10
+ import { Router } from './router'
11
+ import { replaceEqualDeep, Timeout } from './utils'
12
+
13
+ export interface RouteMatch<
14
+ TAllRouteInfo extends AnyAllRouteInfo = DefaultAllRouteInfo,
15
+ TRouteInfo extends AnyRouteInfo = RouteInfo,
16
+ > extends Route<TAllRouteInfo, TRouteInfo> {
17
+ matchId: string
18
+ pathname: string
19
+ params: AnyPathParams
20
+ parentMatch?: RouteMatch
21
+ childMatches: RouteMatch[]
22
+ routeSearch: TRouteInfo['searchSchema']
23
+ search: TRouteInfo['fullSearchSchema']
24
+ status: 'idle' | 'loading' | 'success' | 'error'
25
+ updatedAt?: number
26
+ error?: unknown
27
+ isInvalid: boolean
28
+ getIsInvalid: () => boolean
29
+ loaderData: TRouteInfo['loaderData']
30
+ routeLoaderData: TRouteInfo['routeLoaderData']
31
+ isFetching: boolean
32
+ isPending: boolean
33
+ __: {
34
+ element?: GetFrameworkGeneric<'Element'> // , TRouteInfo['loaderData']>
35
+ errorElement?: GetFrameworkGeneric<'Element'> // , TRouteInfo['loaderData']>
36
+ catchElement?: GetFrameworkGeneric<'Element'> // , TRouteInfo['loaderData']>
37
+ pendingElement?: GetFrameworkGeneric<'Element'> // , TRouteInfo['loaderData']>
38
+ loadPromise?: Promise<void>
39
+ loaderPromise?: Promise<void>
40
+ importPromise?: Promise<void>
41
+ elementsPromise?: Promise<void>
42
+ dataPromise?: Promise<void>
43
+ pendingTimeout?: Timeout
44
+ pendingMinTimeout?: Timeout
45
+ pendingMinPromise?: Promise<void>
46
+ onExit?:
47
+ | void
48
+ | ((matchContext: {
49
+ params: TRouteInfo['allParams']
50
+ search: TRouteInfo['fullSearchSchema']
51
+ }) => void)
52
+ abortController: AbortController
53
+ latestId: string
54
+ // setParentMatch: (parentMatch: RouteMatch) => void
55
+ // addChildMatch: (childMatch: RouteMatch) => void
56
+ validate: () => void
57
+ startPending: () => void
58
+ cancelPending: () => void
59
+ notify: () => void
60
+ resolve: () => void
61
+ }
62
+ cancel: () => void
63
+ load: () => Promise<void>
64
+ invalidate: () => void
65
+ hasLoaders: () => boolean
66
+ }
67
+
68
+ const elementTypes = [
69
+ 'element',
70
+ 'errorElement',
71
+ 'catchElement',
72
+ 'pendingElement',
73
+ ] as const
74
+
75
+ export function createRouteMatch<
76
+ TAllRouteInfo extends AnyAllRouteInfo = DefaultAllRouteInfo,
77
+ TRouteInfo extends AnyRouteInfo = RouteInfo,
78
+ >(
79
+ router: Router<any, any>,
80
+ route: Route<TAllRouteInfo, TRouteInfo>,
81
+ opts: {
82
+ matchId: string
83
+ params: TRouteInfo['allParams']
84
+ pathname: string
85
+ },
86
+ ): RouteMatch<TAllRouteInfo, TRouteInfo> {
87
+ const routeMatch: RouteMatch<TAllRouteInfo, TRouteInfo> = {
88
+ ...route,
89
+ ...opts,
90
+ router,
91
+ routeSearch: {},
92
+ search: {},
93
+ childMatches: [],
94
+ status: 'idle',
95
+ routeLoaderData: {} as TRouteInfo['routeLoaderData'],
96
+ loaderData: {} as TRouteInfo['loaderData'],
97
+ isPending: false,
98
+ isFetching: false,
99
+ isInvalid: false,
100
+ getIsInvalid: () => {
101
+ const now = Date.now()
102
+ const maxAge =
103
+ routeMatch.options.loaderMaxAge ??
104
+ router.options.defaultLoaderMaxAge ??
105
+ 0
106
+ return routeMatch.isInvalid || routeMatch.updatedAt! + maxAge < now
107
+ },
108
+ __: {
109
+ abortController: new AbortController(),
110
+ latestId: '',
111
+ resolve: () => {},
112
+ notify: () => {
113
+ routeMatch.__.resolve()
114
+ routeMatch.router.notify()
115
+ },
116
+ startPending: () => {
117
+ const pendingMs =
118
+ routeMatch.options.pendingMs ?? router.options.defaultPendingMs
119
+ const pendingMinMs =
120
+ routeMatch.options.pendingMinMs ?? router.options.defaultPendingMinMs
121
+
122
+ if (
123
+ routeMatch.__.pendingTimeout ||
124
+ routeMatch.status !== 'loading' ||
125
+ typeof pendingMs === 'undefined'
126
+ ) {
127
+ return
128
+ }
129
+
130
+ routeMatch.__.pendingTimeout = setTimeout(() => {
131
+ routeMatch.isPending = true
132
+ routeMatch.__.resolve()
133
+ if (typeof pendingMinMs !== 'undefined') {
134
+ routeMatch.__.pendingMinPromise = new Promise(
135
+ (r) =>
136
+ (routeMatch.__.pendingMinTimeout = setTimeout(r, pendingMinMs)),
137
+ )
138
+ }
139
+ }, pendingMs)
140
+ },
141
+ cancelPending: () => {
142
+ routeMatch.isPending = false
143
+ clearTimeout(routeMatch.__.pendingTimeout)
144
+ clearTimeout(routeMatch.__.pendingMinTimeout)
145
+ delete routeMatch.__.pendingMinPromise
146
+ },
147
+ // setParentMatch: (parentMatch?: RouteMatch) => {
148
+ // routeMatch.parentMatch = parentMatch
149
+ // },
150
+ // addChildMatch: (childMatch: RouteMatch) => {
151
+ // if (
152
+ // routeMatch.childMatches.find((d) => d.matchId === childMatch.matchId)
153
+ // ) {
154
+ // return
155
+ // }
156
+
157
+ // routeMatch.childMatches.push(childMatch)
158
+ // },
159
+ validate: () => {
160
+ // Validate the search params and stabilize them
161
+ const parentSearch =
162
+ routeMatch.parentMatch?.search ?? router.location.search
163
+
164
+ try {
165
+ const prevSearch = routeMatch.routeSearch
166
+
167
+ const validator =
168
+ typeof routeMatch.options.validateSearch === 'object'
169
+ ? routeMatch.options.validateSearch.parse
170
+ : routeMatch.options.validateSearch
171
+
172
+ let nextSearch = replaceEqualDeep(
173
+ prevSearch,
174
+ validator?.(parentSearch),
175
+ )
176
+
177
+ // Invalidate route matches when search param stability changes
178
+ if (prevSearch !== nextSearch) {
179
+ routeMatch.isInvalid = true
180
+ }
181
+
182
+ routeMatch.routeSearch = nextSearch
183
+
184
+ routeMatch.search = replaceEqualDeep(parentSearch, {
185
+ ...parentSearch,
186
+ ...nextSearch,
187
+ })
188
+ } catch (err: any) {
189
+ console.error(err)
190
+ const error = new (Error as any)('Invalid search params found', {
191
+ cause: err,
192
+ })
193
+ error.code = 'INVALID_SEARCH_PARAMS'
194
+ routeMatch.status = 'error'
195
+ routeMatch.error = error
196
+ // Do not proceed with loading the route
197
+ return
198
+ }
199
+ },
200
+ },
201
+ cancel: () => {
202
+ routeMatch.__.abortController?.abort()
203
+ routeMatch.__.cancelPending()
204
+ },
205
+ invalidate: () => {
206
+ routeMatch.isInvalid = true
207
+ },
208
+ hasLoaders: () => {
209
+ return !!(
210
+ route.options.loader ||
211
+ route.options.import ||
212
+ elementTypes.some((d) => typeof route.options[d] === 'function')
213
+ )
214
+ },
215
+ load: async () => {
216
+ const id = '' + Date.now() + Math.random()
217
+ routeMatch.__.latestId = id
218
+
219
+ // If the match was in an error state, set it
220
+ // to a loading state again. Otherwise, keep it
221
+ // as loading or resolved
222
+ if (routeMatch.status === 'idle') {
223
+ routeMatch.status = 'loading'
224
+ }
225
+
226
+ // We started loading the route, so it's no longer invalid
227
+ routeMatch.isInvalid = false
228
+
229
+ routeMatch.__.loadPromise = new Promise(async (resolve) => {
230
+ // We are now fetching, even if it's in the background of a
231
+ // resolved state
232
+ routeMatch.isFetching = true
233
+ routeMatch.__.resolve = resolve as () => void
234
+
235
+ const loaderPromise = (async () => {
236
+ const importer = routeMatch.options.import
237
+
238
+ // First, run any importers
239
+ if (importer) {
240
+ routeMatch.__.importPromise = importer({
241
+ params: routeMatch.params,
242
+ // search: routeMatch.search,
243
+ }).then((imported) => {
244
+ routeMatch.__ = {
245
+ ...routeMatch.__,
246
+ ...imported,
247
+ }
248
+ })
249
+ }
250
+
251
+ // Wait for the importer to finish before
252
+ // attempting to load elements and data
253
+ await routeMatch.__.importPromise
254
+
255
+ // Next, load the elements and data in parallel
256
+
257
+ routeMatch.__.elementsPromise = (async () => {
258
+ // then run all element and data loaders in parallel
259
+ // For each element type, potentially load it asynchronously
260
+
261
+ await Promise.all(
262
+ elementTypes.map(async (type) => {
263
+ const routeElement = routeMatch.options[type]
264
+
265
+ if (routeMatch.__[type]) {
266
+ return
267
+ }
268
+
269
+ if (typeof routeElement === 'function') {
270
+ const res = await (routeElement as any)(routeMatch)
271
+
272
+ routeMatch.__[type] = res
273
+ } else {
274
+ routeMatch.__[type] = routeMatch.options[type] as any
275
+ }
276
+ }),
277
+ )
278
+ })()
279
+
280
+ routeMatch.__.dataPromise = Promise.resolve().then(async () => {
281
+ try {
282
+ if (routeMatch.options.loader) {
283
+ const data = await routeMatch.options.loader({
284
+ params: routeMatch.params,
285
+ search: routeMatch.routeSearch,
286
+ signal: routeMatch.__.abortController.signal,
287
+ })
288
+ if (id !== routeMatch.__.latestId) {
289
+ return routeMatch.__.loaderPromise
290
+ }
291
+
292
+ routeMatch.routeLoaderData = replaceEqualDeep(
293
+ routeMatch.routeLoaderData,
294
+ data,
295
+ )
296
+ }
297
+
298
+ routeMatch.error = undefined
299
+ routeMatch.status = 'success'
300
+ routeMatch.updatedAt = Date.now()
301
+ } catch (err) {
302
+ if (id !== routeMatch.__.latestId) {
303
+ return routeMatch.__.loaderPromise
304
+ }
305
+
306
+ if (process.env.NODE_ENV !== 'production') {
307
+ console.error(err)
308
+ }
309
+ routeMatch.error = err
310
+ routeMatch.status = 'error'
311
+ routeMatch.updatedAt = Date.now()
312
+ }
313
+ })
314
+
315
+ try {
316
+ await Promise.all([
317
+ routeMatch.__.elementsPromise,
318
+ routeMatch.__.dataPromise,
319
+ ])
320
+ if (id !== routeMatch.__.latestId) {
321
+ return routeMatch.__.loaderPromise
322
+ }
323
+
324
+ if (routeMatch.__.pendingMinPromise) {
325
+ await routeMatch.__.pendingMinPromise
326
+ delete routeMatch.__.pendingMinPromise
327
+ }
328
+ } finally {
329
+ if (id !== routeMatch.__.latestId) {
330
+ return routeMatch.__.loaderPromise
331
+ }
332
+ routeMatch.__.cancelPending()
333
+ routeMatch.isPending = false
334
+ routeMatch.isFetching = false
335
+ routeMatch.__.notify()
336
+ }
337
+ })()
338
+
339
+ routeMatch.__.loaderPromise = loaderPromise
340
+ await loaderPromise
341
+
342
+ if (id !== routeMatch.__.latestId) {
343
+ return routeMatch.__.loaderPromise
344
+ }
345
+ delete routeMatch.__.loaderPromise
346
+ })
347
+
348
+ return await routeMatch.__.loadPromise
349
+ },
350
+ }
351
+
352
+ if (!routeMatch.hasLoaders()) {
353
+ routeMatch.status = 'success'
354
+ }
355
+
356
+ return routeMatch
357
+ }