@tanstack/router-core 0.0.1-beta.3 → 0.0.1-beta.31

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 (56) hide show
  1. package/build/cjs/_virtual/_rollupPluginBabelHelpers.js +0 -2
  2. package/build/cjs/_virtual/_rollupPluginBabelHelpers.js.map +1 -1
  3. package/build/cjs/{packages/router-core/src/index.js → index.js} +23 -7
  4. package/build/cjs/{packages/router-core/src/index.js.map → index.js.map} +1 -1
  5. package/build/cjs/{packages/router-core/src/path.js → path.js} +7 -34
  6. package/build/cjs/path.js.map +1 -0
  7. package/build/cjs/{packages/router-core/src/qss.js → qss.js} +9 -13
  8. package/build/cjs/qss.js.map +1 -0
  9. package/build/cjs/{packages/router-core/src/route.js → route.js} +15 -37
  10. package/build/cjs/route.js.map +1 -0
  11. package/build/cjs/{packages/router-core/src/routeConfig.js → routeConfig.js} +13 -12
  12. package/build/cjs/routeConfig.js.map +1 -0
  13. package/build/cjs/routeMatch.js +200 -0
  14. package/build/cjs/routeMatch.js.map +1 -0
  15. package/build/cjs/{packages/router-core/src/router.js → router.js} +257 -221
  16. package/build/cjs/router.js.map +1 -0
  17. package/build/cjs/{packages/router-core/src/searchParams.js → searchParams.js} +7 -10
  18. package/build/cjs/searchParams.js.map +1 -0
  19. package/build/cjs/{packages/router-core/src/utils.js → utils.js} +17 -30
  20. package/build/cjs/utils.js.map +1 -0
  21. package/build/esm/index.js +395 -1322
  22. package/build/esm/index.js.map +1 -1
  23. package/build/stats-html.html +59 -49
  24. package/build/stats-react.json +161 -168
  25. package/build/types/index.d.ts +247 -227
  26. package/build/umd/index.development.js +385 -495
  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 +3 -2
  31. package/src/frameworks.ts +2 -2
  32. package/src/index.ts +0 -1
  33. package/src/link.ts +66 -31
  34. package/src/path.ts +2 -6
  35. package/src/qss.ts +1 -0
  36. package/src/route.ts +25 -33
  37. package/src/routeConfig.ts +100 -77
  38. package/src/routeInfo.ts +26 -8
  39. package/src/routeMatch.ts +100 -160
  40. package/src/router.ts +363 -141
  41. package/src/utils.ts +14 -7
  42. package/build/cjs/node_modules/@babel/runtime/helpers/esm/extends.js +0 -33
  43. package/build/cjs/node_modules/@babel/runtime/helpers/esm/extends.js.map +0 -1
  44. package/build/cjs/node_modules/history/index.js +0 -815
  45. package/build/cjs/node_modules/history/index.js.map +0 -1
  46. package/build/cjs/node_modules/tiny-invariant/dist/esm/tiny-invariant.js +0 -30
  47. package/build/cjs/node_modules/tiny-invariant/dist/esm/tiny-invariant.js.map +0 -1
  48. package/build/cjs/packages/router-core/src/path.js.map +0 -1
  49. package/build/cjs/packages/router-core/src/qss.js.map +0 -1
  50. package/build/cjs/packages/router-core/src/route.js.map +0 -1
  51. package/build/cjs/packages/router-core/src/routeConfig.js.map +0 -1
  52. package/build/cjs/packages/router-core/src/routeMatch.js +0 -266
  53. package/build/cjs/packages/router-core/src/routeMatch.js.map +0 -1
  54. package/build/cjs/packages/router-core/src/router.js.map +0 -1
  55. package/build/cjs/packages/router-core/src/searchParams.js.map +0 -1
  56. package/build/cjs/packages/router-core/src/utils.js.map +0 -1
package/src/router.ts CHANGED
@@ -23,7 +23,7 @@ import {
23
23
  matchPathname,
24
24
  resolvePath,
25
25
  } from './path'
26
- import { AnyRoute, cascadeLoaderData, createRoute, Route } from './route'
26
+ import { AnyRoute, createRoute, Route } from './route'
27
27
  import {
28
28
  AnyLoaderData,
29
29
  AnyPathParams,
@@ -45,6 +45,7 @@ import { defaultParseSearch, defaultStringifySearch } from './searchParams'
45
45
  import {
46
46
  functionalUpdate,
47
47
  last,
48
+ pick,
48
49
  PickAsRequired,
49
50
  PickRequired,
50
51
  replaceEqualDeep,
@@ -52,6 +53,22 @@ import {
52
53
  Updater,
53
54
  } from './utils'
54
55
 
56
+ export interface RegisterRouter {
57
+ // router: Router
58
+ }
59
+
60
+ export type RegisteredRouter = RegisterRouter extends {
61
+ router: Router<infer TRouteConfig, infer TAllRouteInfo, infer TRouterContext>
62
+ }
63
+ ? Router<TRouteConfig, TAllRouteInfo, TRouterContext>
64
+ : Router
65
+
66
+ export type RegisteredAllRouteInfo = RegisterRouter extends {
67
+ router: Router<infer TRouteConfig, infer TAllRouteInfo, infer TRouterContext>
68
+ }
69
+ ? TAllRouteInfo
70
+ : AnyAllRouteInfo
71
+
55
72
  export interface LocationState {}
56
73
 
57
74
  export interface Location<
@@ -80,7 +97,10 @@ export type FilterRoutesFn = <TRoute extends Route<any, RouteInfo>>(
80
97
  routeConfigs: TRoute[],
81
98
  ) => TRoute[]
82
99
 
83
- export interface RouterOptions<TRouteConfig extends AnyRouteConfig> {
100
+ export interface RouterOptions<
101
+ TRouteConfig extends AnyRouteConfig,
102
+ TRouterContext,
103
+ > {
84
104
  history?: BrowserHistory | MemoryHistory | HashHistory
85
105
  stringifySearch?: SearchSerializer
86
106
  parseSearch?: SearchParser
@@ -89,23 +109,27 @@ export interface RouterOptions<TRouteConfig extends AnyRouteConfig> {
89
109
  defaultPreloadMaxAge?: number
90
110
  defaultPreloadGcMaxAge?: number
91
111
  defaultPreloadDelay?: number
92
- useErrorBoundary?: boolean
93
- defaultElement?: GetFrameworkGeneric<'Element'>
94
- defaultErrorElement?: GetFrameworkGeneric<'Element'>
95
- defaultCatchElement?: GetFrameworkGeneric<'Element'>
96
- defaultPendingElement?: GetFrameworkGeneric<'Element'>
97
- defaultPendingMs?: number
98
- defaultPendingMinMs?: number
112
+ defaultComponent?: GetFrameworkGeneric<'Component'>
113
+ defaultErrorComponent?: GetFrameworkGeneric<'ErrorComponent'>
114
+ defaultPendingComponent?: GetFrameworkGeneric<'Component'>
99
115
  defaultLoaderMaxAge?: number
100
116
  defaultLoaderGcMaxAge?: number
101
117
  caseSensitive?: boolean
102
118
  routeConfig?: TRouteConfig
103
119
  basepath?: string
104
- createRouter?: (router: Router<any, any>) => void
105
- createRoute?: (opts: { route: AnyRoute; router: Router<any, any> }) => void
106
- createElement?: (
107
- element: GetFrameworkGeneric<'SyncOrAsyncElement'>,
108
- ) => Promise<GetFrameworkGeneric<'Element'>>
120
+ useServerData?: boolean
121
+ createRouter?: (router: Router<any, any, any>) => void
122
+ createRoute?: (opts: {
123
+ route: AnyRoute
124
+ router: Router<any, any, any>
125
+ }) => void
126
+ context?: TRouterContext
127
+ loadComponent?: (
128
+ component: GetFrameworkGeneric<'Component'>,
129
+ ) => Promise<GetFrameworkGeneric<'Component'>>
130
+ // renderComponent?: (
131
+ // component: GetFrameworkGeneric<'Component'>,
132
+ // ) => GetFrameworkGeneric<'Element'>
109
133
  }
110
134
 
111
135
  export interface Action<
@@ -113,10 +137,13 @@ export interface Action<
113
137
  TResponse = unknown,
114
138
  // TError = unknown,
115
139
  > {
116
- submit: (submission?: TPayload) => Promise<TResponse>
140
+ submit: (
141
+ submission?: TPayload,
142
+ actionOpts?: { invalidate?: boolean; multi?: boolean },
143
+ ) => Promise<TResponse>
117
144
  current?: ActionState<TPayload, TResponse>
118
145
  latest?: ActionState<TPayload, TResponse>
119
- pending: ActionState<TPayload, TResponse>[]
146
+ submissions: ActionState<TPayload, TResponse>[]
120
147
  }
121
148
 
122
149
  export interface ActionState<
@@ -127,6 +154,7 @@ export interface ActionState<
127
154
  submittedAt: number
128
155
  status: 'idle' | 'pending' | 'success' | 'error'
129
156
  submission: TPayload
157
+ isMulti: boolean
130
158
  data?: TResponse
131
159
  error?: unknown
132
160
  }
@@ -160,21 +188,21 @@ export interface Loader<
160
188
  }
161
189
 
162
190
  export interface LoaderState<
163
- TFullSearchSchema = unknown,
164
- TAllParams = unknown,
191
+ TFullSearchSchema extends AnySearchSchema = {},
192
+ TAllParams extends AnyPathParams = {},
165
193
  > {
166
194
  loadedAt: number
167
195
  loaderContext: LoaderContext<TFullSearchSchema, TAllParams>
168
196
  }
169
197
 
170
- export interface RouterState {
198
+ export interface RouterState<
199
+ TSearchObj extends AnySearchSchema = {},
200
+ TState extends LocationState = LocationState,
201
+ > {
171
202
  status: 'idle' | 'loading'
172
- location: Location
203
+ location: Location<TSearchObj, TState>
173
204
  matches: RouteMatch[]
174
205
  lastUpdated: number
175
- loaderData: unknown
176
- currentAction?: ActionState
177
- latestAction?: ActionState
178
206
  actions: Record<string, Action>
179
207
  loaders: Record<string, Loader>
180
208
  pending?: PendingState
@@ -187,15 +215,16 @@ export interface PendingState {
187
215
  matches: RouteMatch[]
188
216
  }
189
217
 
190
- type Listener = (router: Router<any, any>) => void
218
+ type Listener = (router: Router<any, any, any>) => void
191
219
 
192
220
  export type ListenerFn = () => void
193
221
 
194
222
  export interface BuildNextOptions {
195
223
  to?: string | number | null
196
- params?: true | Updater<Record<string, any>>
224
+ params?: true | Updater<unknown>
197
225
  search?: true | Updater<unknown>
198
226
  hash?: true | Updater<string>
227
+ state?: LocationState
199
228
  key?: string
200
229
  from?: string
201
230
  fromCurrent?: boolean
@@ -225,41 +254,79 @@ type LinkCurrentTargetElement = {
225
254
  preloadTimeout?: null | ReturnType<typeof setTimeout>
226
255
  }
227
256
 
257
+ export interface DehydratedRouterState
258
+ extends Pick<
259
+ RouterState,
260
+ 'status' | 'location' | 'lastUpdated' | 'location'
261
+ > {
262
+ matches: DehydratedRouteMatch[]
263
+ }
264
+
265
+ export interface DehydratedRouter<TRouterContext = unknown> {
266
+ location: Router['__location']
267
+ state: DehydratedRouterState
268
+ context: TRouterContext
269
+ }
270
+
271
+ interface DehydratedRouteMatch
272
+ extends Pick<
273
+ RouteMatch<any, any>,
274
+ | 'matchId'
275
+ | 'status'
276
+ | 'routeLoaderData'
277
+ | 'loaderData'
278
+ | 'isInvalid'
279
+ | 'invalidAt'
280
+ > {}
281
+
282
+ export interface RouterContext {}
283
+
228
284
  export interface Router<
229
285
  TRouteConfig extends AnyRouteConfig = RouteConfig,
230
286
  TAllRouteInfo extends AnyAllRouteInfo = AllRouteInfo<TRouteConfig>,
287
+ TRouterContext = unknown,
231
288
  > {
289
+ types: {
290
+ // Super secret internal stuff
291
+ RouteConfig: TRouteConfig
292
+ AllRouteInfo: TAllRouteInfo
293
+ }
294
+
295
+ // Public API
232
296
  history: BrowserHistory | MemoryHistory | HashHistory
233
297
  options: PickAsRequired<
234
- RouterOptions<TRouteConfig>,
235
- 'stringifySearch' | 'parseSearch'
298
+ RouterOptions<TRouteConfig, TRouterContext>,
299
+ 'stringifySearch' | 'parseSearch' | 'context'
236
300
  >
237
301
  // Computed in this.update()
238
302
  basepath: string
239
303
  // Internal:
240
- allRouteInfo: TAllRouteInfo
241
304
  listeners: Listener[]
242
- location: Location
305
+ __location: Location<TAllRouteInfo['fullSearchSchema']>
243
306
  navigateTimeout?: Timeout
244
307
  nextAction?: 'push' | 'replace'
245
- state: RouterState
308
+ state: RouterState<TAllRouteInfo['fullSearchSchema']>
246
309
  routeTree: Route<TAllRouteInfo, RouteInfo>
247
310
  routesById: RoutesById<TAllRouteInfo>
248
- navigationPromise: Promise<void>
249
- removeActionQueue: { action: Action; actionState: ActionState }[]
311
+ navigationPromise?: Promise<void>
250
312
  startedLoadingAt: number
251
313
  resolveNavigation: () => void
252
314
  subscribe: (listener: Listener) => () => void
315
+ reset: () => void
253
316
  notify: () => void
254
317
  mount: () => () => void
255
318
  onFocus: () => void
256
- update: <TRouteConfig extends RouteConfig = RouteConfig>(
257
- opts?: RouterOptions<TRouteConfig>,
258
- ) => Router<TRouteConfig>
319
+ update: <
320
+ TRouteConfig extends RouteConfig = RouteConfig,
321
+ TAllRouteInfo extends AnyAllRouteInfo = AllRouteInfo<TRouteConfig>,
322
+ TRouterContext = unknown,
323
+ >(
324
+ opts?: RouterOptions<TRouteConfig, TRouterContext>,
325
+ ) => Router<TRouteConfig, TAllRouteInfo, TRouterContext>
259
326
 
260
327
  buildNext: (opts: BuildNextOptions) => Location
261
328
  cancelMatches: () => void
262
- loadLocation: (next?: Location) => Promise<void>
329
+ load: (next?: Location) => Promise<void>
263
330
  matchCache: Record<string, MatchCacheEntry>
264
331
  cleanMatchCache: () => void
265
332
  getRoute: <TId extends keyof TAllRouteInfo['routeInfoById']>(
@@ -276,11 +343,13 @@ export interface Router<
276
343
  ) => RouteMatch[]
277
344
  loadMatches: (
278
345
  resolvedMatches: RouteMatch[],
279
- loaderOpts?: { withPending?: boolean } & (
346
+ loaderOpts?:
280
347
  | { preload: true; maxAge: number; gcMaxAge: number }
281
- | { preload?: false; maxAge?: never; gcMaxAge?: never }
282
- ),
348
+ | { preload?: false; maxAge?: never; gcMaxAge?: never },
283
349
  ) => Promise<void>
350
+ loadMatchData: (
351
+ routeMatch: RouteMatch<any, any>,
352
+ ) => Promise<Record<string, unknown>>
284
353
  invalidateRoute: (opts: MatchLocation) => void
285
354
  reload: () => Promise<void>
286
355
  resolvePath: (from: string, path: string) => string
@@ -303,6 +372,8 @@ export interface Router<
303
372
  >(
304
373
  opts: LinkOptions<TAllRouteInfo, TFrom, TTo>,
305
374
  ) => LinkInfo
375
+ dehydrate: () => DehydratedRouter<TRouterContext>
376
+ hydrate: (dehydratedRouter: DehydratedRouter<TRouterContext>) => void
306
377
  __: {
307
378
  buildRouteTree: (
308
379
  routeConfig: RouteConfig,
@@ -327,12 +398,26 @@ const isServer =
327
398
  const createDefaultHistory = () =>
328
399
  isServer ? createMemoryHistory() : createBrowserHistory()
329
400
 
401
+ function getInitialRouterState(): RouterState {
402
+ return {
403
+ status: 'idle',
404
+ location: null!,
405
+ matches: [],
406
+ actions: {},
407
+ loaders: {},
408
+ lastUpdated: Date.now(),
409
+ isFetching: false,
410
+ isPreloading: false,
411
+ }
412
+ }
413
+
330
414
  export function createRouter<
331
415
  TRouteConfig extends AnyRouteConfig = RouteConfig,
332
416
  TAllRouteInfo extends AnyAllRouteInfo = AllRouteInfo<TRouteConfig>,
417
+ TRouterContext = unknown,
333
418
  >(
334
- userOptions?: RouterOptions<TRouteConfig>,
335
- ): Router<TRouteConfig, TAllRouteInfo> {
419
+ userOptions?: RouterOptions<TRouteConfig, TRouterContext>,
420
+ ): Router<TRouteConfig, TAllRouteInfo, TRouterContext> {
336
421
  const history = userOptions?.history || createDefaultHistory()
337
422
 
338
423
  const originalOptions = {
@@ -340,36 +425,31 @@ export function createRouter<
340
425
  defaultLoaderMaxAge: 0,
341
426
  defaultPreloadMaxAge: 2000,
342
427
  defaultPreloadDelay: 50,
428
+ context: undefined!,
343
429
  ...userOptions,
344
430
  stringifySearch: userOptions?.stringifySearch ?? defaultStringifySearch,
345
431
  parseSearch: userOptions?.parseSearch ?? defaultParseSearch,
346
432
  }
347
433
 
348
- let router: Router<TRouteConfig, TAllRouteInfo> = {
434
+ let router: Router<TRouteConfig, TAllRouteInfo, TRouterContext> = {
435
+ types: undefined!,
436
+
437
+ // public api
349
438
  history,
350
439
  options: originalOptions,
351
440
  listeners: [],
352
- removeActionQueue: [],
353
441
  // Resolved after construction
354
442
  basepath: '',
355
443
  routeTree: undefined!,
356
444
  routesById: {} as any,
357
- location: undefined!,
358
- allRouteInfo: undefined!,
445
+ __location: undefined!,
359
446
  //
360
- navigationPromise: Promise.resolve(),
361
447
  resolveNavigation: () => {},
362
448
  matchCache: {},
363
- state: {
364
- status: 'idle',
365
- location: null!,
366
- matches: [],
367
- actions: {},
368
- loaders: {},
369
- loaderData: {} as any,
370
- lastUpdated: Date.now(),
371
- isFetching: false,
372
- isPreloading: false,
449
+ state: getInitialRouterState(),
450
+ reset: () => {
451
+ router.state = getInitialRouterState()
452
+ router.notify()
373
453
  },
374
454
  startedLoadingAt: Date.now(),
375
455
  subscribe: (listener: Listener): (() => void) => {
@@ -382,22 +462,86 @@ export function createRouter<
382
462
  return router.routesById[id]
383
463
  },
384
464
  notify: (): void => {
385
- router.state = {
386
- ...router.state,
387
- isFetching:
388
- router.state.status === 'loading' ||
389
- router.state.matches.some((d) => d.isFetching),
390
- isPreloading: Object.values(router.matchCache).some(
391
- (d) =>
392
- d.match.isFetching &&
393
- !router.state.matches.find((dd) => dd.matchId === d.match.matchId),
394
- ),
465
+ const isFetching =
466
+ router.state.status === 'loading' ||
467
+ router.state.matches.some((d) => d.isFetching)
468
+
469
+ const isPreloading = Object.values(router.matchCache).some(
470
+ (d) =>
471
+ d.match.isFetching &&
472
+ !router.state.matches.find((dd) => dd.matchId === d.match.matchId),
473
+ )
474
+
475
+ if (
476
+ router.state.isFetching !== isFetching ||
477
+ router.state.isPreloading !== isPreloading
478
+ ) {
479
+ router.state = {
480
+ ...router.state,
481
+ isFetching,
482
+ isPreloading,
483
+ }
395
484
  }
396
485
 
397
486
  cascadeLoaderData(router.state.matches)
398
487
  router.listeners.forEach((listener) => listener(router))
399
488
  },
400
489
 
490
+ dehydrate: () => {
491
+ return {
492
+ location: router.__location,
493
+ state: {
494
+ ...pick(router.state, [
495
+ 'status',
496
+ 'location',
497
+ 'lastUpdated',
498
+ 'location',
499
+ ]),
500
+ matches: router.state.matches.map((match) =>
501
+ pick(match, [
502
+ 'matchId',
503
+ 'status',
504
+ 'routeLoaderData',
505
+ 'loaderData',
506
+ 'isInvalid',
507
+ 'invalidAt',
508
+ ]),
509
+ ),
510
+ },
511
+ context: router.options.context as TRouterContext,
512
+ }
513
+ },
514
+
515
+ hydrate: (dehydratedState) => {
516
+ // Update the location
517
+ router.__location = dehydratedState.location
518
+
519
+ // Update the context
520
+ router.options.context = dehydratedState.context
521
+
522
+ // Match the routes
523
+ const matches = router.matchRoutes(router.__location.pathname, {
524
+ strictParseParams: true,
525
+ })
526
+
527
+ matches.forEach((match, index) => {
528
+ const dehydratedMatch = dehydratedState.state.matches[index]
529
+ invariant(
530
+ dehydratedMatch,
531
+ 'Oh no! Dehydrated route matches did not match the active state of the router 😬',
532
+ )
533
+ Object.assign(match, dehydratedMatch)
534
+ })
535
+
536
+ matches.forEach((match) => match.__.validate())
537
+
538
+ router.state = {
539
+ ...router.state,
540
+ ...dehydratedState,
541
+ matches,
542
+ }
543
+ },
544
+
401
545
  mount: () => {
402
546
  const next = router.__.buildLocation({
403
547
  to: '.',
@@ -407,16 +551,16 @@ export function createRouter<
407
551
 
408
552
  // If the current location isn't updated, trigger a navigation
409
553
  // to the current location. Otherwise, load the current location.
410
- if (next.href !== router.location.href) {
554
+ if (next.href !== router.__location.href) {
411
555
  router.__.commitLocation(next, true)
412
- } else {
413
- router.loadLocation()
414
556
  }
415
557
 
416
- const unsub = history.listen((event) => {
417
- router.loadLocation(
418
- router.__.parseLocation(event.location, router.location),
419
- )
558
+ if (!router.state.matches.length) {
559
+ router.load()
560
+ }
561
+
562
+ const unsub = router.history.listen((event) => {
563
+ router.load(router.__.parseLocation(event.location, router.__location))
420
564
  })
421
565
 
422
566
  // addEventListener does not exist in React Native, but window does
@@ -429,17 +573,28 @@ export function createRouter<
429
573
 
430
574
  return () => {
431
575
  unsub()
432
- // Be sure to unsubscribe if a new handler is set
433
- window.removeEventListener('visibilitychange', router.onFocus)
434
- window.removeEventListener('focus', router.onFocus)
576
+ if (!isServer && window.removeEventListener) {
577
+ // Be sure to unsubscribe if a new handler is set
578
+ window.removeEventListener('visibilitychange', router.onFocus)
579
+ window.removeEventListener('focus', router.onFocus)
580
+ }
435
581
  }
436
582
  },
437
583
 
438
584
  onFocus: () => {
439
- router.loadLocation()
585
+ router.load()
440
586
  },
441
587
 
442
588
  update: (opts) => {
589
+ const newHistory = opts?.history !== router.history
590
+ if (!router.__location || newHistory) {
591
+ if (opts?.history) {
592
+ router.history = opts.history
593
+ }
594
+ router.__location = router.__.parseLocation(router.history.location)
595
+ router.state.location = router.__location
596
+ }
597
+
443
598
  Object.assign(router.options, opts)
444
599
 
445
600
  const { basepath, routeConfig } = router.options
@@ -463,49 +618,62 @@ export function createRouter<
463
618
  })
464
619
  },
465
620
 
466
- loadLocation: async (next?: Location) => {
621
+ load: async (next?: Location) => {
467
622
  const id = Math.random()
468
623
  router.startedLoadingAt = id
469
624
 
470
625
  if (next) {
471
626
  // Ingest the new location
472
- router.location = next
627
+ router.__location = next
473
628
  }
474
629
 
475
- // Clear out old actions
476
- router.removeActionQueue.forEach(({ action, actionState }) => {
477
- if (router.state.currentAction === actionState) {
478
- router.state.currentAction = undefined
479
- }
480
- if (action.current === actionState) {
481
- action.current = undefined
482
- }
483
- })
484
- router.removeActionQueue = []
485
-
486
630
  // Cancel any pending matches
487
631
  router.cancelMatches()
488
632
 
489
633
  // Match the routes
490
- const matches = router.matchRoutes(location.pathname, {
634
+ const matches = router.matchRoutes(router.__location.pathname, {
491
635
  strictParseParams: true,
492
636
  })
493
637
 
494
- router.state = {
495
- ...router.state,
496
- pending: {
638
+ if (typeof document !== 'undefined') {
639
+ router.state = {
640
+ ...router.state,
641
+ pending: {
642
+ matches: matches,
643
+ location: router.__location,
644
+ },
645
+ status: 'loading',
646
+ }
647
+ } else {
648
+ router.state = {
649
+ ...router.state,
497
650
  matches: matches,
498
- location: router.location,
499
- },
500
- status: 'loading',
651
+ location: router.__location,
652
+ status: 'loading',
653
+ }
654
+ }
655
+
656
+ // Check if each match middleware to see if the route can be accessed
657
+ try {
658
+ await Promise.all(
659
+ matches.map((match) =>
660
+ match.options.beforeLoad?.({
661
+ router: router as any,
662
+ match,
663
+ }),
664
+ ),
665
+ )
666
+ } catch (err: any) {
667
+ if (err?.then) {
668
+ await new Promise(() => {})
669
+ }
670
+ throw err
501
671
  }
502
672
 
503
673
  router.notify()
504
674
 
505
675
  // Load the matches
506
- await router.loadMatches(matches, {
507
- withPending: true,
508
- })
676
+ await router.loadMatches(matches)
509
677
 
510
678
  if (router.startedLoadingAt !== id) {
511
679
  // Ignore side-effects of match loading
@@ -525,6 +693,10 @@ export function createRouter<
525
693
  }
526
694
  })
527
695
 
696
+ const entering = matches.filter((d) => {
697
+ return !previousMatches.find((dd) => dd.matchId === d.matchId)
698
+ })
699
+
528
700
  const now = Date.now()
529
701
 
530
702
  exiting.forEach((d) => {
@@ -532,6 +704,7 @@ export function createRouter<
532
704
  params: d.params,
533
705
  search: d.routeSearch,
534
706
  })
707
+
535
708
  // Clear idle error states when match leaves
536
709
  if (d.status === 'error' && !d.isFetching) {
537
710
  d.status = 'idle'
@@ -556,32 +729,30 @@ export function createRouter<
556
729
  })
557
730
  })
558
731
 
559
- const entering = matches.filter((d) => {
560
- return !previousMatches.find((dd) => dd.matchId === d.matchId)
561
- })
562
-
563
732
  entering.forEach((d) => {
564
- d.__.onExit = d.options.onMatch?.({
733
+ d.__.onExit = d.options.onLoaded?.({
565
734
  params: d.params,
566
735
  search: d.search,
567
736
  })
568
737
  delete router.matchCache[d.matchId]
569
738
  })
570
739
 
571
- if (matches.some((d) => d.status === 'loading')) {
572
- router.notify()
573
- await Promise.all(
574
- matches.map((d) => d.__.loaderPromise || Promise.resolve()),
575
- )
576
- }
577
740
  if (router.startedLoadingAt !== id) {
578
741
  // Ignore side-effects of match loading
579
742
  return
580
743
  }
581
744
 
745
+ matches.forEach((match) => {
746
+ // Clear actions
747
+ if (match.action) {
748
+ match.action.current = undefined
749
+ match.action.submissions = []
750
+ }
751
+ })
752
+
582
753
  router.state = {
583
754
  ...router.state,
584
- location: router.location,
755
+ location: router.__location,
585
756
  matches,
586
757
  pending: undefined,
587
758
  status: 'idle',
@@ -612,7 +783,7 @@ export function createRouter<
612
783
  })
613
784
  },
614
785
 
615
- loadRoute: async (navigateOpts = router.location) => {
786
+ loadRoute: async (navigateOpts = router.__location) => {
616
787
  const next = router.buildNext(navigateOpts)
617
788
  const matches = router.matchRoutes(next.pathname, {
618
789
  strictParseParams: true,
@@ -621,7 +792,7 @@ export function createRouter<
621
792
  return matches
622
793
  },
623
794
 
624
- preloadRoute: async (navigateOpts = router.location, loaderOpts) => {
795
+ preloadRoute: async (navigateOpts = router.__location, loaderOpts) => {
625
796
  const next = router.buildNext(navigateOpts)
626
797
  const matches = router.matchRoutes(next.pathname, {
627
798
  strictParseParams: true,
@@ -726,9 +897,10 @@ export function createRouter<
726
897
  existingMatches.find((d) => d.matchId === matchId) ||
727
898
  router.matchCache[matchId]?.match ||
728
899
  createRouteMatch(router, foundRoute, {
900
+ parentMatch,
729
901
  matchId,
730
902
  params,
731
- pathname: joinPaths([pathname, interpolatedPath]),
903
+ pathname: joinPaths([router.basepath, interpolatedPath]),
732
904
  })
733
905
 
734
906
  matches.push(match)
@@ -754,12 +926,14 @@ export function createRouter<
754
926
  match.__.validate()
755
927
  match.load(loaderOpts)
756
928
 
757
- if (match.status === 'loading') {
758
- // If requested, start the pending timers
759
- if (loaderOpts?.withPending) match.__.startPending()
929
+ const search = match.search as { __data?: any }
930
+
931
+ if (search.__data && search.__data.matchId !== match.matchId) {
932
+ return
933
+ }
760
934
 
935
+ if (match.__.loadPromise) {
761
936
  // Wait for the first sign of activity from the match
762
- // This might be completion, error, or a pending state
763
937
  await match.__.loadPromise
764
938
  }
765
939
  })
@@ -769,6 +943,50 @@ export function createRouter<
769
943
  await Promise.all(matchPromises)
770
944
  },
771
945
 
946
+ loadMatchData: async (routeMatch) => {
947
+ if (isServer || !router.options.useServerData) {
948
+ return (
949
+ (await routeMatch.options.loader?.({
950
+ // parentLoaderPromise: routeMatch.parentMatch?.__.dataPromise,
951
+ params: routeMatch.params,
952
+ search: routeMatch.routeSearch,
953
+ signal: routeMatch.__.abortController.signal,
954
+ })) ?? {}
955
+ )
956
+ } else {
957
+ const next = router.buildNext({
958
+ to: '.',
959
+ search: (d: any) => ({
960
+ ...(d ?? {}),
961
+ __data: {
962
+ matchId: routeMatch.matchId,
963
+ },
964
+ }),
965
+ })
966
+
967
+ // Refresh:
968
+ // '/dashboard'
969
+ // '/dashboard/invoices/'
970
+ // '/dashboard/invoices/123'
971
+
972
+ // New:
973
+ // '/dashboard/invoices/456'
974
+
975
+ // TODO: batch requests when possible
976
+
977
+ const res = await fetch(next.href, {
978
+ method: 'GET',
979
+ // signal: routeMatch.__.abortController.signal,
980
+ })
981
+
982
+ if (res.ok) {
983
+ return res.json()
984
+ }
985
+
986
+ throw new Error('Failed to fetch match data')
987
+ }
988
+ },
989
+
772
990
  invalidateRoute: (opts: MatchLocation) => {
773
991
  const next = router.buildNext(opts)
774
992
  const unloadedMatchIds = router
@@ -1006,17 +1224,11 @@ export function createRouter<
1006
1224
  buildRouteTree: (rootRouteConfig: RouteConfig) => {
1007
1225
  const recurseRoutes = (
1008
1226
  routeConfigs: RouteConfig[],
1009
- parent?: Route<TAllRouteInfo, any>,
1010
- ): Route<TAllRouteInfo, any>[] => {
1227
+ parent?: Route<TAllRouteInfo, any, any>,
1228
+ ): Route<TAllRouteInfo, any, any>[] => {
1011
1229
  return routeConfigs.map((routeConfig) => {
1012
1230
  const routeOptions = routeConfig.options
1013
1231
  const route = createRoute(routeConfig, routeOptions, parent, router)
1014
-
1015
- // {
1016
- // pendingMs: routeOptions.pendingMs ?? router.defaultPendingMs,
1017
- // pendingMinMs: routeOptions.pendingMinMs ?? router.defaultPendingMinMs,
1018
- // }
1019
-
1020
1232
  const existingRoute = (router.routesById as any)[route.routeId]
1021
1233
 
1022
1234
  if (existingRoute) {
@@ -1070,11 +1282,9 @@ export function createRouter<
1070
1282
  },
1071
1283
 
1072
1284
  buildLocation: (dest: BuildNextOptions = {}): Location => {
1073
- // const resolvedFrom: Location = {
1074
- // ...router.location,
1075
1285
  const fromPathname = dest.fromCurrent
1076
- ? router.location.pathname
1077
- : dest.from ?? router.location.pathname
1286
+ ? router.__location.pathname
1287
+ : dest.from ?? router.__location.pathname
1078
1288
 
1079
1289
  let pathname = resolvePath(
1080
1290
  router.basepath ?? '/',
@@ -1082,7 +1292,7 @@ export function createRouter<
1082
1292
  `${dest.to ?? '.'}`,
1083
1293
  )
1084
1294
 
1085
- const fromMatches = router.matchRoutes(router.location.pathname, {
1295
+ const fromMatches = router.matchRoutes(router.__location.pathname, {
1086
1296
  strictParseParams: true,
1087
1297
  })
1088
1298
 
@@ -1110,9 +1320,9 @@ export function createRouter<
1110
1320
  const preFilteredSearch = dest.__preSearchFilters?.length
1111
1321
  ? dest.__preSearchFilters.reduce(
1112
1322
  (prev, next) => next(prev),
1113
- router.location.search,
1323
+ router.__location.search,
1114
1324
  )
1115
- : router.location.search
1325
+ : router.__location.search
1116
1326
 
1117
1327
  // Then the link/navigate function
1118
1328
  const destSearch =
@@ -1133,22 +1343,22 @@ export function createRouter<
1133
1343
  : destSearch
1134
1344
 
1135
1345
  const search = replaceEqualDeep(
1136
- router.location.search,
1346
+ router.__location.search,
1137
1347
  postFilteredSearch,
1138
1348
  )
1139
1349
 
1140
1350
  const searchStr = router.options.stringifySearch(search)
1141
1351
  let hash =
1142
1352
  dest.hash === true
1143
- ? router.location.hash
1144
- : functionalUpdate(dest.hash!, router.location.hash)
1353
+ ? router.__location.hash
1354
+ : functionalUpdate(dest.hash!, router.__location.hash)
1145
1355
  hash = hash ? `#${hash}` : ''
1146
1356
 
1147
1357
  return {
1148
1358
  pathname,
1149
1359
  search,
1150
1360
  searchStr,
1151
- state: router.location.state,
1361
+ state: router.__location.state,
1152
1362
  hash,
1153
1363
  href: `${pathname}${searchStr}${hash}`,
1154
1364
  key: dest.key,
@@ -1182,6 +1392,7 @@ export function createRouter<
1182
1392
  },
1183
1393
  {
1184
1394
  id,
1395
+ ...next.state,
1185
1396
  },
1186
1397
  )
1187
1398
  } else {
@@ -1203,6 +1414,7 @@ export function createRouter<
1203
1414
  router.resolveNavigation = () => {
1204
1415
  previousNavigationResolve()
1205
1416
  resolve()
1417
+ delete router.navigationPromise
1206
1418
  }
1207
1419
  })
1208
1420
 
@@ -1211,9 +1423,6 @@ export function createRouter<
1211
1423
  },
1212
1424
  }
1213
1425
 
1214
- router.location = router.__.parseLocation(history.location)
1215
- router.state.location = router.location
1216
-
1217
1426
  router.update(userOptions)
1218
1427
 
1219
1428
  // Allow frameworks to hook into the router creation
@@ -1225,3 +1434,16 @@ export function createRouter<
1225
1434
  function isCtrlEvent(e: MouseEvent) {
1226
1435
  return !!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey)
1227
1436
  }
1437
+
1438
+ function cascadeLoaderData(matches: RouteMatch<any, any>[]) {
1439
+ matches.forEach((match, index) => {
1440
+ const parent = matches[index - 1]
1441
+
1442
+ if (parent) {
1443
+ match.loaderData = replaceEqualDeep(match.loaderData, {
1444
+ ...parent.loaderData,
1445
+ ...match.routeLoaderData,
1446
+ })
1447
+ }
1448
+ })
1449
+ }