@tanstack/router-core 0.0.1-beta.161 → 0.0.1-beta.163

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tanstack/router-core",
3
3
  "author": "Tanner Linsley",
4
- "version": "0.0.1-beta.161",
4
+ "version": "0.0.1-beta.163",
5
5
  "license": "MIT",
6
6
  "repository": "tanstack/router",
7
7
  "homepage": "https://tanstack.com/router",
@@ -43,7 +43,7 @@
43
43
  "tiny-invariant": "^1.3.1",
44
44
  "tiny-warning": "^1.0.3",
45
45
  "@gisatcz/cross-package-react-context": "^0.2.0",
46
- "@tanstack/react-store": "0.0.1-beta.161"
46
+ "@tanstack/react-store": "0.0.1-beta.163"
47
47
  },
48
48
  "scripts": {
49
49
  "build": "rollup --config rollup.config.js",
package/src/fileRoute.ts CHANGED
@@ -59,7 +59,7 @@ export type ResolveFilePath<
59
59
  ? TrimPathLeft<TFilePath>
60
60
  : Replace<
61
61
  TrimPathLeft<TFilePath>,
62
- TrimPathLeft<TParentRoute['__types']['customId']>,
62
+ TrimPathLeft<TParentRoute['types']['customId']>,
63
63
  ''
64
64
  >
65
65
 
@@ -96,14 +96,14 @@ export class FileRoute<
96
96
  ? AnyPathParams
97
97
  : Record<ParsePathParams<TPath>, RouteConstraints['TPath']>,
98
98
  TAllParams extends RouteConstraints['TAllParams'] = MergeParamsFromParent<
99
- TParentRoute['__types']['allParams'],
99
+ TParentRoute['types']['allParams'],
100
100
  TParams
101
101
  >,
102
- TParentContext extends RouteConstraints['TParentContext'] = TParentRoute['__types']['routeContext'],
103
- TAllParentContext extends RouteConstraints['TId'] = TParentRoute['__types']['context'],
102
+ TParentContext extends RouteConstraints['TParentContext'] = TParentRoute['types']['routeContext'],
103
+ TAllParentContext extends RouteConstraints['TId'] = TParentRoute['types']['context'],
104
104
  TRouteContext extends RouteConstraints['TRouteContext'] = RouteContext,
105
105
  TContext extends RouteConstraints['TAllContext'] = MergeParamsFromParent<
106
- TParentRoute['__types']['context'],
106
+ TParentRoute['types']['context'],
107
107
  TRouteContext
108
108
  >,
109
109
  TRouterContext extends RouteConstraints['TRouterContext'] = AnyContext,
package/src/history.ts CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  export interface RouterHistory {
6
6
  location: RouterLocation
7
- listen: (cb: () => void) => () => void
7
+ subscribe: (cb: () => void) => () => void
8
8
  push: (path: string, state?: any) => void
9
9
  replace: (path: string, state?: any) => void
10
10
  go: (index: number) => void
@@ -45,7 +45,7 @@ const stopBlocking = () => {
45
45
 
46
46
  function createHistory(opts: {
47
47
  getLocation: () => RouterLocation
48
- listener: false | ((onUpdate: () => void) => () => void)
48
+ subscriber: false | ((onUpdate: () => void) => () => void)
49
49
  pushState: (path: string, state: any) => void
50
50
  replaceState: (path: string, state: any) => void
51
51
  go: (n: number) => void
@@ -55,7 +55,7 @@ function createHistory(opts: {
55
55
  }): RouterHistory {
56
56
  let location = opts.getLocation()
57
57
  let unsub = () => {}
58
- let listeners = new Set<() => void>()
58
+ let subscribers = new Set<() => void>()
59
59
  let blockers: BlockerFn[] = []
60
60
  let queue: (() => void)[] = []
61
61
 
@@ -72,7 +72,7 @@ function createHistory(opts: {
72
72
  queue.shift()?.()
73
73
  }
74
74
 
75
- if (!opts.listener) {
75
+ if (!opts.subscriber) {
76
76
  onUpdate()
77
77
  }
78
78
  }
@@ -84,25 +84,25 @@ function createHistory(opts: {
84
84
 
85
85
  const onUpdate = () => {
86
86
  location = opts.getLocation()
87
- listeners.forEach((listener) => listener())
87
+ subscribers.forEach((subscriber) => subscriber())
88
88
  }
89
89
 
90
90
  return {
91
91
  get location() {
92
92
  return location
93
93
  },
94
- listen: (cb: () => void) => {
95
- if (listeners.size === 0) {
94
+ subscribe: (cb: () => void) => {
95
+ if (subscribers.size === 0) {
96
96
  unsub =
97
- typeof opts.listener === 'function'
98
- ? opts.listener(onUpdate)
97
+ typeof opts.subscriber === 'function'
98
+ ? opts.subscriber(onUpdate)
99
99
  : () => {}
100
100
  }
101
- listeners.add(cb)
101
+ subscribers.add(cb)
102
102
 
103
103
  return () => {
104
- listeners.delete(cb)
105
- if (listeners.size === 0) {
104
+ subscribers.delete(cb)
105
+ if (subscribers.size === 0) {
106
106
  unsub()
107
107
  }
108
108
  }
@@ -166,7 +166,7 @@ export function createBrowserHistory(opts?: {
166
166
 
167
167
  return createHistory({
168
168
  getLocation,
169
- listener: (onUpdate) => {
169
+ subscriber: (onUpdate) => {
170
170
  window.addEventListener(pushStateEvent, onUpdate)
171
171
  window.addEventListener(popStateEvent, onUpdate)
172
172
 
@@ -234,7 +234,7 @@ export function createMemoryHistory(
234
234
 
235
235
  return createHistory({
236
236
  getLocation,
237
- listener: false,
237
+ subscriber: false,
238
238
  pushState: (path, state) => {
239
239
  currentState = {
240
240
  ...state,
package/src/link.ts CHANGED
@@ -152,17 +152,17 @@ export type SearchParamOptions<
152
152
  TFromSchema = UnionToIntersection<
153
153
  FullSearchSchema<TRouteTree> & RouteByPath<TRouteTree, TFrom> extends never
154
154
  ? {}
155
- : RouteByPath<TRouteTree, TFrom>['__types']['fullSearchSchema']
155
+ : RouteByPath<TRouteTree, TFrom>['types']['fullSearchSchema']
156
156
  >,
157
157
  // Find the schema for the new path, and make optional any keys
158
158
  // that are already defined in the current schema
159
159
  TToSchema = Partial<
160
- RouteByPath<TRouteTree, TFrom>['__types']['fullSearchSchema']
160
+ RouteByPath<TRouteTree, TFrom>['types']['fullSearchSchema']
161
161
  > &
162
162
  Omit<
163
- RouteByPath<TRouteTree, TTo>['__types']['fullSearchSchema'],
163
+ RouteByPath<TRouteTree, TTo>['types']['fullSearchSchema'],
164
164
  keyof PickRequired<
165
- RouteByPath<TRouteTree, TFrom>['__types']['fullSearchSchema']
165
+ RouteByPath<TRouteTree, TFrom>['types']['fullSearchSchema']
166
166
  >
167
167
  >,
168
168
  TFromFullSchema = UnionToIntersection<
@@ -188,14 +188,14 @@ export type PathParamOptions<
188
188
  TFromSchema = UnionToIntersection<
189
189
  RouteByPath<TRouteTree, TFrom> extends never
190
190
  ? {}
191
- : RouteByPath<TRouteTree, TFrom>['__types']['allParams']
191
+ : RouteByPath<TRouteTree, TFrom>['types']['allParams']
192
192
  >,
193
193
  // Find the schema for the new path, and make optional any keys
194
194
  // that are already defined in the current schema
195
- TToSchema = Partial<RouteByPath<TRouteTree, TFrom>['__types']['allParams']> &
195
+ TToSchema = Partial<RouteByPath<TRouteTree, TFrom>['types']['allParams']> &
196
196
  Omit<
197
- RouteByPath<TRouteTree, TTo>['__types']['allParams'],
198
- keyof PickRequired<RouteByPath<TRouteTree, TFrom>['__types']['allParams']>
197
+ RouteByPath<TRouteTree, TTo>['types']['allParams'],
198
+ keyof PickRequired<RouteByPath<TRouteTree, TFrom>['types']['allParams']>
199
199
  >,
200
200
  TFromFullParams = UnionToIntersection<AllParams<TRouteTree> & TFromSchema>,
201
201
  TToFullParams = UnionToIntersection<AllParams<TRouteTree> & TToSchema>,
package/src/route.ts CHANGED
@@ -81,12 +81,12 @@ export type ComponentFromRoute<TRoute> = RegisteredRouteComponent<
81
81
  >
82
82
 
83
83
  export type RouteLoaderFromRoute<TRoute extends AnyRoute> = LoaderFn<
84
- TRoute['__types']['loader'],
85
- TRoute['__types']['searchSchema'],
86
- TRoute['__types']['fullSearchSchema'],
87
- TRoute['__types']['allParams'],
88
- TRoute['__types']['routeContext'],
89
- TRoute['__types']['context']
84
+ TRoute['types']['loader'],
85
+ TRoute['types']['searchSchema'],
86
+ TRoute['types']['fullSearchSchema'],
87
+ TRoute['types']['allParams'],
88
+ TRoute['types']['routeContext'],
89
+ TRoute['types']['context']
90
90
  >
91
91
 
92
92
  export type RouteProps<
@@ -312,7 +312,7 @@ export type UpdatableRouteOptions<
312
312
  gcMaxAge?: number
313
313
  // This async function is called before a route is loaded.
314
314
  // If an error is thrown here, the route's loader will not be called.
315
- // If thrown during a navigation, the navigation will be cancelled and the error will be passed to the `onLoadError` function.
315
+ // If thrown during a navigation, the navigation will be cancelled and the error will be passed to the `onError` function.
316
316
  // If thrown during a preload event, the error will be logged to the console.
317
317
  beforeLoad?: (
318
318
  opts: LoaderContext<
@@ -323,15 +323,6 @@ export type UpdatableRouteOptions<
323
323
  TContext
324
324
  >,
325
325
  ) => Promise<void> | void
326
- // This function will be called if the route's loader throws an error **during an attempted navigation**.
327
- // If you want to redirect due to an error, call `router.navigate()` from within this function.
328
- onBeforeLoadError?: (err: any) => void
329
- // This function will be called if the route's validateSearch option throws an error **during an attempted validation**.
330
- // If you want to redirect due to an error, call `router.navigate()` from within this function.
331
- // If you want to display the errorComponent, rethrow the error
332
- onValidateSearchError?: (err: any) => void
333
- onParseParamsError?: (err: any) => void
334
- onLoadError?: (err: any) => void
335
326
  onError?: (err: any) => void
336
327
  // This function is called
337
328
  // when moving from an inactive state to an active one. Likewise, when moving from
@@ -443,13 +434,13 @@ export type ResolveId<
443
434
 
444
435
  export type InferFullSearchSchema<TRoute> = TRoute extends {
445
436
  isRoot: true
446
- __types: {
437
+ types: {
447
438
  searchSchema: infer TSearchSchema
448
439
  }
449
440
  }
450
441
  ? TSearchSchema
451
442
  : TRoute extends {
452
- __types: {
443
+ types: {
453
444
  fullSearchSchema: infer TFullSearchSchema
454
445
  }
455
446
  }
@@ -542,21 +533,21 @@ export class Route<
542
533
  string
543
534
  >,
544
535
  TAllParams extends RouteConstraints['TAllParams'] = MergeParamsFromParent<
545
- TParentRoute['__types']['allParams'],
536
+ TParentRoute['types']['allParams'],
546
537
  TParams
547
538
  >,
548
- TParentContext extends RouteConstraints['TParentContext'] = TParentRoute['__types']['routeContext'],
549
- TAllParentContext extends RouteConstraints['TAllParentContext'] = TParentRoute['__types']['context'],
539
+ TParentContext extends RouteConstraints['TParentContext'] = TParentRoute['types']['routeContext'],
540
+ TAllParentContext extends RouteConstraints['TAllParentContext'] = TParentRoute['types']['context'],
550
541
  TRouteContext extends RouteConstraints['TRouteContext'] = RouteContext,
551
542
  TAllContext extends RouteConstraints['TAllContext'] = MergeParamsFromParent<
552
- TParentRoute['__types']['context'],
543
+ TParentRoute['types']['context'],
553
544
  TRouteContext
554
545
  >,
555
546
  TRouterContext extends RouteConstraints['TRouterContext'] = AnyContext,
556
547
  TChildren extends RouteConstraints['TChildren'] = unknown,
557
548
  TRouteTree extends RouteConstraints['TRouteTree'] = AnyRoute,
558
549
  > {
559
- __types!: {
550
+ types!: {
560
551
  parentRoute: TParentRoute
561
552
  path: TPath
562
553
  to: TrimPathRight<TFullPath>
package/src/routeInfo.ts CHANGED
@@ -63,9 +63,9 @@ export type RoutePaths<TRouteTree extends AnyRoute> =
63
63
  | '/'
64
64
 
65
65
  export type FullSearchSchema<TRouteTree extends AnyRoute> = MergeUnion<
66
- ParseRoute<TRouteTree>['__types']['fullSearchSchema']
66
+ ParseRoute<TRouteTree>['types']['fullSearchSchema']
67
67
  > & {}
68
68
 
69
69
  export type AllParams<TRouteTree extends AnyRoute> = MergeUnion<
70
- ParseRoute<TRouteTree>['__types']['allParams']
70
+ ParseRoute<TRouteTree>['types']['allParams']
71
71
  >
package/src/router.ts CHANGED
@@ -114,7 +114,7 @@ export interface RouteMatch<
114
114
  key?: string
115
115
  routeId: string
116
116
  pathname: string
117
- params: TRoute['__types']['allParams']
117
+ params: TRoute['types']['allParams']
118
118
  status: 'pending' | 'success' | 'error'
119
119
  isFetching: boolean
120
120
  invalid: boolean
@@ -124,13 +124,13 @@ export interface RouteMatch<
124
124
  updatedAt: number
125
125
  invalidAt: number
126
126
  preloadInvalidAt: number
127
- loaderData: TRoute['__types']['loader']
127
+ loaderData: TRoute['types']['loader']
128
128
  loadPromise?: Promise<void>
129
129
  __resolveLoadPromise?: () => void
130
- routeContext: TRoute['__types']['routeContext']
131
- context: TRoute['__types']['context']
132
- routeSearch: TRoute['__types']['searchSchema']
133
- search: FullSearchSchema<TRouteTree> & TRoute['__types']['fullSearchSchema']
130
+ routeContext: TRoute['types']['routeContext']
131
+ context: TRoute['types']['context']
132
+ routeSearch: TRoute['types']['searchSchema']
133
+ search: FullSearchSchema<TRouteTree> & TRoute['types']['fullSearchSchema']
134
134
  fetchedAt: number
135
135
  abortController: AbortController
136
136
  }
@@ -138,12 +138,12 @@ export interface RouteMatch<
138
138
  export type AnyRouteMatch = RouteMatch<AnyRoute, AnyRoute>
139
139
 
140
140
  export type RouterContextOptions<TRouteTree extends AnyRoute> =
141
- AnyContext extends TRouteTree['__types']['routerContext']
141
+ AnyContext extends TRouteTree['types']['routerContext']
142
142
  ? {
143
- context?: TRouteTree['__types']['routerContext']
143
+ context?: TRouteTree['types']['routerContext']
144
144
  }
145
145
  : {
146
- context: TRouteTree['__types']['routerContext']
146
+ context: TRouteTree['types']['routerContext']
147
147
  }
148
148
 
149
149
  export interface RouterOptions<
@@ -171,8 +171,7 @@ export interface RouterOptions<
171
171
  routeTree?: TRouteTree
172
172
  basepath?: string
173
173
  createRoute?: (opts: { route: AnyRoute; router: AnyRouter }) => void
174
- onRouteChange?: () => void
175
- context?: TRouteTree['__types']['routerContext']
174
+ context?: TRouteTree['types']['routerContext']
176
175
  Wrap?: React.ComponentType<{
177
176
  children: React.ReactNode
178
177
  dehydratedState?: TDehydrated
@@ -197,7 +196,7 @@ export interface RouterState<
197
196
  lastUpdated: number
198
197
  }
199
198
 
200
- export type ListenerFn = () => void
199
+ export type ListenerFn<TEvent extends RouterEvent> = (event: TEvent) => void
201
200
 
202
201
  export interface BuildNextOptions {
203
202
  to?: string | number | null
@@ -249,6 +248,28 @@ export const componentTypes = [
249
248
  'pendingComponent',
250
249
  ] as const
251
250
 
251
+ export type RouterEvents = {
252
+ onBeforeLoad: {
253
+ type: 'onBeforeLoad'
254
+ from: ParsedLocation
255
+ to: ParsedLocation
256
+ pathChanged: boolean
257
+ }
258
+ onLoad: {
259
+ type: 'onLoad'
260
+ from: ParsedLocation
261
+ to: ParsedLocation
262
+ pathChanged: boolean
263
+ }
264
+ }
265
+
266
+ export type RouterEvent = RouterEvents[keyof RouterEvents]
267
+
268
+ export type RouterListener<TRouterEvent extends RouterEvent> = {
269
+ eventType: TRouterEvent['type']
270
+ fn: ListenerFn<TRouterEvent>
271
+ }
272
+
252
273
  export class Router<
253
274
  TRouteTree extends AnyRoute = AnyRoute,
254
275
  TDehydrated extends Record<string, any> = Record<string, any>,
@@ -343,6 +364,32 @@ export class Router<
343
364
  }
344
365
  }
345
366
 
367
+ subscribers = new Set<RouterListener<RouterEvent>>()
368
+
369
+ subscribe = <TType extends keyof RouterEvents>(
370
+ eventType: TType,
371
+ fn: ListenerFn<RouterEvents[TType]>,
372
+ ) => {
373
+ const listener: RouterListener<any> = {
374
+ eventType,
375
+ fn,
376
+ }
377
+
378
+ this.subscribers.add(listener)
379
+
380
+ return () => {
381
+ this.subscribers.delete(listener)
382
+ }
383
+ }
384
+
385
+ #emit = (routerEvent: RouterEvent) => {
386
+ this.subscribers.forEach((listener) => {
387
+ if (listener.eventType === routerEvent.type) {
388
+ listener.fn(routerEvent)
389
+ }
390
+ })
391
+ }
392
+
346
393
  reset = () => {
347
394
  this.__store.setState((s) => Object.assign(s, getInitialRouterState()))
348
395
  }
@@ -384,7 +431,7 @@ export class Router<
384
431
  location: parsedLocation as any,
385
432
  }))
386
433
 
387
- this.#unsubHistory = this.history.listen(() => {
434
+ this.#unsubHistory = this.history.subscribe(() => {
388
435
  this.safeLoad({
389
436
  next: this.#parseLocation(this.state.location),
390
437
  })
@@ -435,6 +482,11 @@ export class Router<
435
482
 
436
483
  load = async (opts?: { next?: ParsedLocation; throwOnError?: boolean }) => {
437
484
  const promise = new Promise<void>(async (resolve, reject) => {
485
+ const prevLocation = this.state.resolvedLocation
486
+ const pathDidChange = !!(
487
+ opts?.next && prevLocation!.href !== opts.next.href
488
+ )
489
+
438
490
  let latestPromise: Promise<void> | undefined | null
439
491
 
440
492
  const checkLatest = (): undefined | Promise<void> | null => {
@@ -448,6 +500,13 @@ export class Router<
448
500
 
449
501
  let pendingMatches!: RouteMatch<any, any>[]
450
502
 
503
+ this.#emit({
504
+ type: 'onBeforeLoad',
505
+ from: prevLocation,
506
+ to: opts?.next ?? this.state.location,
507
+ pathChanged: pathDidChange,
508
+ })
509
+
451
510
  this.__store.batch(() => {
452
511
  if (opts?.next) {
453
512
  // Ingest the new location
@@ -489,8 +548,6 @@ export class Router<
489
548
  return latestPromise
490
549
  }
491
550
 
492
- const prevLocation = this.state.resolvedLocation
493
-
494
551
  this.__store.setState((s) => ({
495
552
  ...s,
496
553
  status: 'idle',
@@ -499,9 +556,12 @@ export class Router<
499
556
  pendingMatchIds: [],
500
557
  }))
501
558
 
502
- if (prevLocation!.href !== this.state.location.href) {
503
- this.options.onRouteChange?.()
504
- }
559
+ this.#emit({
560
+ type: 'onLoad',
561
+ from: prevLocation,
562
+ to: this.state.location,
563
+ pathChanged: pathDidChange,
564
+ })
505
565
 
506
566
  resolve()
507
567
  } catch (err) {
@@ -844,19 +904,16 @@ export class Router<
844
904
  for (const [index, match] of resolvedMatches.entries()) {
845
905
  const route = this.getRoute(match.routeId)
846
906
 
847
- const handleError = (
848
- err: any,
849
- handler: undefined | ((err: any) => void),
850
- ) => {
907
+ const handleError = (err: any, code: string) => {
908
+ err.routerCode = code
851
909
  firstBadMatchIndex = firstBadMatchIndex ?? index
852
- handler = handler || route.options.onError
853
910
 
854
911
  if (isRedirect(err)) {
855
912
  throw err
856
913
  }
857
914
 
858
915
  try {
859
- handler?.(err)
916
+ route.options.onError?.(err)
860
917
  } catch (errorHandlerErr) {
861
918
  err = errorHandlerErr
862
919
 
@@ -874,11 +931,11 @@ export class Router<
874
931
  }
875
932
 
876
933
  if (match.paramsError) {
877
- handleError(match.paramsError, route.options.onParseParamsError)
934
+ handleError(match.paramsError, 'PARSE_PARAMS')
878
935
  }
879
936
 
880
937
  if (match.searchError) {
881
- handleError(match.searchError, route.options.onValidateSearchError)
938
+ handleError(match.searchError, 'VALIDATE_SEARCH')
882
939
  }
883
940
 
884
941
  let didError = false
@@ -889,7 +946,7 @@ export class Router<
889
946
  preload: !!opts?.preload,
890
947
  })
891
948
  } catch (err) {
892
- handleError(err, route.options.onBeforeLoadError)
949
+ handleError(err, 'BEFORE_LOAD')
893
950
  didError = true
894
951
  }
895
952
 
@@ -968,34 +1025,20 @@ export class Router<
968
1025
  if ((latestPromise = checkLatest())) return await latestPromise
969
1026
 
970
1027
  this.setRouteMatchData(match.id, () => loader, opts)
971
- } catch (loaderError) {
972
- let latestError = loaderError
1028
+ } catch (error) {
973
1029
  if ((latestPromise = checkLatest())) return await latestPromise
974
- if (handleIfRedirect(loaderError)) return
975
-
976
- if (route.options.onLoadError) {
977
- try {
978
- route.options.onLoadError(loaderError)
979
- } catch (onLoadError) {
980
- latestError = onLoadError
981
- if (handleIfRedirect(onLoadError)) return
982
- }
983
- }
1030
+ if (handleIfRedirect(error)) return
984
1031
 
985
- if (
986
- (!route.options.onLoadError || latestError !== loaderError) &&
987
- route.options.onError
988
- ) {
989
- try {
990
- route.options.onError(latestError)
991
- } catch (onErrorError) {
992
- if (handleIfRedirect(onErrorError)) return
993
- }
1032
+ try {
1033
+ route.options.onError?.(error)
1034
+ } catch (onErrorError) {
1035
+ error = onErrorError
1036
+ if (handleIfRedirect(onErrorError)) return
994
1037
  }
995
1038
 
996
1039
  this.setRouteMatch(match.id, (s) => ({
997
1040
  ...s,
998
- error: loaderError,
1041
+ error,
999
1042
  status: 'error',
1000
1043
  isFetching: false,
1001
1044
  updatedAt: Date.now(),
@@ -1089,7 +1132,7 @@ export class Router<
1089
1132
  >(
1090
1133
  location: ToOptions<TRouteTree, TFrom, TTo>,
1091
1134
  opts?: MatchRouteOptions,
1092
- ): false | RouteById<TRouteTree, TResolved>['__types']['allParams'] => {
1135
+ ): false | RouteById<TRouteTree, TResolved>['types']['allParams'] => {
1093
1136
  location = {
1094
1137
  ...location,
1095
1138
  to: location.to