@tanstack/router-core 0.0.1-alpha.1 → 0.0.1-alpha.10

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 (43) hide show
  1. package/build/cjs/node_modules/tiny-invariant/dist/esm/tiny-invariant.js +30 -0
  2. package/build/cjs/node_modules/tiny-invariant/dist/esm/tiny-invariant.js.map +1 -0
  3. package/build/cjs/packages/router-core/src/index.js +35 -1421
  4. package/build/cjs/packages/router-core/src/index.js.map +1 -1
  5. package/build/cjs/packages/router-core/src/path.js +222 -0
  6. package/build/cjs/packages/router-core/src/path.js.map +1 -0
  7. package/build/cjs/packages/router-core/src/qss.js +1 -1
  8. package/build/cjs/packages/router-core/src/qss.js.map +1 -1
  9. package/build/cjs/packages/router-core/src/route.js +126 -0
  10. package/build/cjs/packages/router-core/src/route.js.map +1 -0
  11. package/build/cjs/packages/router-core/src/routeConfig.js +69 -0
  12. package/build/cjs/packages/router-core/src/routeConfig.js.map +1 -0
  13. package/build/cjs/packages/router-core/src/routeMatch.js +247 -0
  14. package/build/cjs/packages/router-core/src/routeMatch.js.map +1 -0
  15. package/build/cjs/packages/router-core/src/router.js +809 -0
  16. package/build/cjs/packages/router-core/src/router.js.map +1 -0
  17. package/build/cjs/packages/router-core/src/searchParams.js +70 -0
  18. package/build/cjs/packages/router-core/src/searchParams.js.map +1 -0
  19. package/build/cjs/packages/router-core/src/utils.js +118 -0
  20. package/build/cjs/packages/router-core/src/utils.js.map +1 -0
  21. package/build/esm/index.js +1350 -1231
  22. package/build/esm/index.js.map +1 -1
  23. package/build/stats-html.html +1 -1
  24. package/build/stats-react.json +388 -46
  25. package/build/types/index.d.ts +401 -343
  26. package/build/umd/index.development.js +1218 -1091
  27. package/build/umd/index.development.js.map +1 -1
  28. package/build/umd/index.production.js +1 -1
  29. package/build/umd/index.production.js.map +1 -1
  30. package/package.json +2 -2
  31. package/src/frameworks.ts +13 -0
  32. package/src/index.ts +15 -2969
  33. package/src/link.ts +291 -0
  34. package/src/path.ts +236 -0
  35. package/src/qss.ts +1 -1
  36. package/src/route.ts +181 -0
  37. package/src/routeConfig.ts +523 -0
  38. package/src/routeInfo.ts +228 -0
  39. package/src/routeMatch.ts +340 -0
  40. package/src/router.ts +1211 -0
  41. package/src/searchParams.ts +54 -0
  42. package/src/utils.ts +157 -0
  43. package/src/createRoutes.test.ts +0 -328
@@ -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,340 @@
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
+ invalidAt: number
34
+ __: {
35
+ element?: GetFrameworkGeneric<'Element'> // , TRouteInfo['loaderData']>
36
+ errorElement?: GetFrameworkGeneric<'Element'> // , TRouteInfo['loaderData']>
37
+ catchElement?: GetFrameworkGeneric<'Element'> // , TRouteInfo['loaderData']>
38
+ pendingElement?: GetFrameworkGeneric<'Element'> // , TRouteInfo['loaderData']>
39
+ loadPromise?: Promise<void>
40
+ loaderPromise?: 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: (opts?: { maxAge?: number }) => 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
+ invalidAt: Infinity,
101
+ getIsInvalid: () => {
102
+ const now = Date.now()
103
+ return routeMatch.isInvalid || routeMatch.invalidAt < now
104
+ },
105
+ __: {
106
+ abortController: new AbortController(),
107
+ latestId: '',
108
+ resolve: () => {},
109
+ notify: () => {
110
+ routeMatch.__.resolve()
111
+ routeMatch.router.notify()
112
+ },
113
+ startPending: () => {
114
+ const pendingMs =
115
+ routeMatch.options.pendingMs ?? router.options.defaultPendingMs
116
+ const pendingMinMs =
117
+ routeMatch.options.pendingMinMs ?? router.options.defaultPendingMinMs
118
+
119
+ if (
120
+ routeMatch.__.pendingTimeout ||
121
+ routeMatch.status !== 'loading' ||
122
+ typeof pendingMs === 'undefined'
123
+ ) {
124
+ return
125
+ }
126
+
127
+ routeMatch.__.pendingTimeout = setTimeout(() => {
128
+ routeMatch.isPending = true
129
+ routeMatch.__.resolve()
130
+ if (typeof pendingMinMs !== 'undefined') {
131
+ routeMatch.__.pendingMinPromise = new Promise(
132
+ (r) =>
133
+ (routeMatch.__.pendingMinTimeout = setTimeout(r, pendingMinMs)),
134
+ )
135
+ }
136
+ }, pendingMs)
137
+ },
138
+ cancelPending: () => {
139
+ routeMatch.isPending = false
140
+ clearTimeout(routeMatch.__.pendingTimeout)
141
+ clearTimeout(routeMatch.__.pendingMinTimeout)
142
+ delete routeMatch.__.pendingMinPromise
143
+ },
144
+ // setParentMatch: (parentMatch?: RouteMatch) => {
145
+ // routeMatch.parentMatch = parentMatch
146
+ // },
147
+ // addChildMatch: (childMatch: RouteMatch) => {
148
+ // if (
149
+ // routeMatch.childMatches.find((d) => d.matchId === childMatch.matchId)
150
+ // ) {
151
+ // return
152
+ // }
153
+
154
+ // routeMatch.childMatches.push(childMatch)
155
+ // },
156
+ validate: () => {
157
+ // Validate the search params and stabilize them
158
+ const parentSearch =
159
+ routeMatch.parentMatch?.search ?? router.location.search
160
+
161
+ try {
162
+ const prevSearch = routeMatch.routeSearch
163
+
164
+ const validator =
165
+ typeof routeMatch.options.validateSearch === 'object'
166
+ ? routeMatch.options.validateSearch.parse
167
+ : routeMatch.options.validateSearch
168
+
169
+ let nextSearch = replaceEqualDeep(
170
+ prevSearch,
171
+ validator?.(parentSearch),
172
+ )
173
+
174
+ // Invalidate route matches when search param stability changes
175
+ if (prevSearch !== nextSearch) {
176
+ routeMatch.isInvalid = true
177
+ }
178
+
179
+ routeMatch.routeSearch = nextSearch
180
+
181
+ routeMatch.search = replaceEqualDeep(parentSearch, {
182
+ ...parentSearch,
183
+ ...nextSearch,
184
+ })
185
+ } catch (err: any) {
186
+ console.error(err)
187
+ const error = new (Error as any)('Invalid search params found', {
188
+ cause: err,
189
+ })
190
+ error.code = 'INVALID_SEARCH_PARAMS'
191
+ routeMatch.status = 'error'
192
+ routeMatch.error = error
193
+ // Do not proceed with loading the route
194
+ return
195
+ }
196
+ },
197
+ },
198
+ cancel: () => {
199
+ routeMatch.__.abortController?.abort()
200
+ routeMatch.__.cancelPending()
201
+ },
202
+ invalidate: () => {
203
+ routeMatch.isInvalid = true
204
+ },
205
+ hasLoaders: () => {
206
+ return !!(
207
+ route.options.loader ||
208
+ elementTypes.some((d) => typeof route.options[d] === 'function')
209
+ )
210
+ },
211
+ load: async (opts) => {
212
+ const id = '' + Date.now() + Math.random()
213
+ routeMatch.__.latestId = id
214
+
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
+ }
221
+
222
+ // We started loading the route, so it's no longer invalid
223
+ routeMatch.isInvalid = false
224
+
225
+ routeMatch.__.loadPromise = new Promise(async (resolve) => {
226
+ // We are now fetching, even if it's in the background of a
227
+ // resolved state
228
+ routeMatch.isFetching = true
229
+ routeMatch.__.resolve = resolve as () => void
230
+
231
+ const loaderPromise = (async () => {
232
+ // Load the elements and data in parallel
233
+
234
+ routeMatch.__.elementsPromise = (async () => {
235
+ // then run all element and data loaders in parallel
236
+ // For each element type, potentially load it asynchronously
237
+
238
+ await Promise.all(
239
+ elementTypes.map(async (type) => {
240
+ const routeElement = routeMatch.options[type]
241
+
242
+ if (routeMatch.__[type]) {
243
+ return
244
+ }
245
+
246
+ if (typeof routeElement === 'function') {
247
+ const res = await (routeElement as any)(routeMatch)
248
+
249
+ routeMatch.__[type] = res
250
+ } else {
251
+ routeMatch.__[type] = routeMatch.options[type] as any
252
+ }
253
+ }),
254
+ )
255
+ })()
256
+
257
+ routeMatch.__.dataPromise = Promise.resolve().then(async () => {
258
+ try {
259
+ if (routeMatch.options.loader) {
260
+ const data = await routeMatch.options.loader({
261
+ params: routeMatch.params,
262
+ search: routeMatch.routeSearch,
263
+ signal: routeMatch.__.abortController.signal,
264
+ })
265
+ if (id !== routeMatch.__.latestId) {
266
+ return routeMatch.__.loaderPromise
267
+ }
268
+
269
+ routeMatch.routeLoaderData = replaceEqualDeep(
270
+ routeMatch.routeLoaderData,
271
+ data,
272
+ )
273
+ }
274
+
275
+ routeMatch.error = undefined
276
+ routeMatch.status = 'success'
277
+ routeMatch.updatedAt = Date.now()
278
+ routeMatch.invalidAt =
279
+ routeMatch.updatedAt +
280
+ (opts?.maxAge ??
281
+ routeMatch.options.loaderMaxAge ??
282
+ router.options.defaultLoaderMaxAge ??
283
+ 0)
284
+ } catch (err) {
285
+ if (id !== routeMatch.__.latestId) {
286
+ return routeMatch.__.loaderPromise
287
+ }
288
+
289
+ if (process.env.NODE_ENV !== 'production') {
290
+ console.error(err)
291
+ }
292
+ routeMatch.error = err
293
+ routeMatch.status = 'error'
294
+ routeMatch.updatedAt = Date.now()
295
+ }
296
+ })
297
+
298
+ try {
299
+ await Promise.all([
300
+ routeMatch.__.elementsPromise,
301
+ routeMatch.__.dataPromise,
302
+ ])
303
+ if (id !== routeMatch.__.latestId) {
304
+ return routeMatch.__.loaderPromise
305
+ }
306
+
307
+ if (routeMatch.__.pendingMinPromise) {
308
+ await routeMatch.__.pendingMinPromise
309
+ delete routeMatch.__.pendingMinPromise
310
+ }
311
+ } finally {
312
+ if (id !== routeMatch.__.latestId) {
313
+ return routeMatch.__.loaderPromise
314
+ }
315
+ routeMatch.__.cancelPending()
316
+ routeMatch.isPending = false
317
+ routeMatch.isFetching = false
318
+ routeMatch.__.notify()
319
+ }
320
+ })()
321
+
322
+ routeMatch.__.loaderPromise = loaderPromise
323
+ await loaderPromise
324
+
325
+ if (id !== routeMatch.__.latestId) {
326
+ return routeMatch.__.loaderPromise
327
+ }
328
+ delete routeMatch.__.loaderPromise
329
+ })
330
+
331
+ return await routeMatch.__.loadPromise
332
+ },
333
+ }
334
+
335
+ if (!routeMatch.hasLoaders()) {
336
+ routeMatch.status = 'success'
337
+ }
338
+
339
+ return routeMatch
340
+ }