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

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} +246 -208
  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 +382 -1307
  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 +228 -206
  26. package/build/umd/index.development.js +373 -481
  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 +8 -5
  34. package/src/path.ts +2 -6
  35. package/src/qss.ts +1 -0
  36. package/src/route.ts +24 -32
  37. package/src/routeConfig.ts +100 -77
  38. package/src/routeInfo.ts +22 -7
  39. package/src/routeMatch.ts +95 -157
  40. package/src/router.ts +346 -122
  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,7 +215,7 @@ 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
 
@@ -196,6 +224,7 @@ export interface BuildNextOptions {
196
224
  params?: true | Updater<Record<string, any>>
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
445
  location: undefined!,
358
- allRouteInfo: 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: '.',
@@ -409,14 +553,14 @@ export function createRouter<
409
553
  // to the current location. Otherwise, load the current location.
410
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,7 +618,7 @@ 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
 
@@ -472,40 +627,53 @@ export function createRouter<
472
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
651
  location: router.location,
499
- },
500
- status: 'loading',
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,29 +729,27 @@ 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
755
  location: router.location,
@@ -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) {
@@ -1182,6 +1394,7 @@ export function createRouter<
1182
1394
  },
1183
1395
  {
1184
1396
  id,
1397
+ ...next.state,
1185
1398
  },
1186
1399
  )
1187
1400
  } else {
@@ -1203,6 +1416,7 @@ export function createRouter<
1203
1416
  router.resolveNavigation = () => {
1204
1417
  previousNavigationResolve()
1205
1418
  resolve()
1419
+ delete router.navigationPromise
1206
1420
  }
1207
1421
  })
1208
1422
 
@@ -1211,9 +1425,6 @@ export function createRouter<
1211
1425
  },
1212
1426
  }
1213
1427
 
1214
- router.location = router.__.parseLocation(history.location)
1215
- router.state.location = router.location
1216
-
1217
1428
  router.update(userOptions)
1218
1429
 
1219
1430
  // Allow frameworks to hook into the router creation
@@ -1225,3 +1436,16 @@ export function createRouter<
1225
1436
  function isCtrlEvent(e: MouseEvent) {
1226
1437
  return !!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey)
1227
1438
  }
1439
+
1440
+ function cascadeLoaderData(matches: RouteMatch<any, any>[]) {
1441
+ matches.forEach((match, index) => {
1442
+ const parent = matches[index - 1]
1443
+
1444
+ if (parent) {
1445
+ match.loaderData = replaceEqualDeep(match.loaderData, {
1446
+ ...parent.loaderData,
1447
+ ...match.routeLoaderData,
1448
+ })
1449
+ }
1450
+ })
1451
+ }