@tanstack/react-router 1.45.0 → 1.45.3

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 (58) hide show
  1. package/dist/cjs/CatchBoundary.cjs +1 -1
  2. package/dist/cjs/CatchBoundary.cjs.map +1 -1
  3. package/dist/cjs/CatchBoundary.d.cts +1 -1
  4. package/dist/cjs/Match.cjs +24 -34
  5. package/dist/cjs/Match.cjs.map +1 -1
  6. package/dist/cjs/Matches.cjs +1 -1
  7. package/dist/cjs/Matches.cjs.map +1 -1
  8. package/dist/cjs/Matches.d.cts +4 -2
  9. package/dist/cjs/RouterProvider.cjs +0 -8
  10. package/dist/cjs/RouterProvider.cjs.map +1 -1
  11. package/dist/cjs/RouterProvider.d.cts +1 -4
  12. package/dist/cjs/Transitioner.cjs +5 -1
  13. package/dist/cjs/Transitioner.cjs.map +1 -1
  14. package/dist/cjs/index.cjs +0 -1
  15. package/dist/cjs/index.cjs.map +1 -1
  16. package/dist/cjs/index.d.cts +1 -1
  17. package/dist/cjs/link.cjs.map +1 -1
  18. package/dist/cjs/path.cjs +1 -1
  19. package/dist/cjs/path.cjs.map +1 -1
  20. package/dist/cjs/route.cjs.map +1 -1
  21. package/dist/cjs/route.d.cts +1 -0
  22. package/dist/cjs/router.cjs +459 -406
  23. package/dist/cjs/router.cjs.map +1 -1
  24. package/dist/cjs/router.d.cts +22 -10
  25. package/dist/esm/CatchBoundary.d.ts +1 -1
  26. package/dist/esm/CatchBoundary.js +1 -1
  27. package/dist/esm/CatchBoundary.js.map +1 -1
  28. package/dist/esm/Match.js +24 -34
  29. package/dist/esm/Match.js.map +1 -1
  30. package/dist/esm/Matches.d.ts +4 -2
  31. package/dist/esm/Matches.js +1 -1
  32. package/dist/esm/Matches.js.map +1 -1
  33. package/dist/esm/RouterProvider.d.ts +1 -4
  34. package/dist/esm/RouterProvider.js +1 -9
  35. package/dist/esm/RouterProvider.js.map +1 -1
  36. package/dist/esm/Transitioner.js +5 -1
  37. package/dist/esm/Transitioner.js.map +1 -1
  38. package/dist/esm/index.d.ts +1 -1
  39. package/dist/esm/index.js +1 -2
  40. package/dist/esm/link.js.map +1 -1
  41. package/dist/esm/path.js +1 -1
  42. package/dist/esm/path.js.map +1 -1
  43. package/dist/esm/route.d.ts +1 -0
  44. package/dist/esm/route.js.map +1 -1
  45. package/dist/esm/router.d.ts +22 -10
  46. package/dist/esm/router.js +461 -408
  47. package/dist/esm/router.js.map +1 -1
  48. package/package.json +2 -2
  49. package/src/CatchBoundary.tsx +7 -3
  50. package/src/Match.tsx +47 -38
  51. package/src/Matches.tsx +5 -3
  52. package/src/RouterProvider.tsx +0 -11
  53. package/src/Transitioner.tsx +5 -1
  54. package/src/index.tsx +0 -1
  55. package/src/link.tsx +2 -2
  56. package/src/path.ts +1 -1
  57. package/src/route.ts +1 -0
  58. package/src/router.ts +642 -566
@@ -1,11 +1,10 @@
1
- import { createBrowserHistory, createMemoryHistory } from "@tanstack/history";
1
+ import { createMemoryHistory, createBrowserHistory } from "@tanstack/history";
2
2
  import { Store } from "@tanstack/react-store";
3
3
  import invariant from "tiny-invariant";
4
4
  import warning from "tiny-warning";
5
5
  import { rootRouteId } from "./root.js";
6
6
  import { defaultStringifySearch, defaultParseSearch } from "./searchParams.js";
7
- import { createControlledPromise, replaceEqualDeep, pick, last, deepEqual, functionalUpdate } from "./utils.js";
8
- import { getRouteMatch } from "./RouterProvider.js";
7
+ import { createControlledPromise, replaceEqualDeep, pick, deepEqual, last, functionalUpdate } from "./utils.js";
9
8
  import { trimPath, trimPathLeft, parsePathname, resolvePath, cleanPath, matchPathname, trimPathRight, interpolatePath, joinPaths } from "./path.js";
10
9
  import { isResolvedRedirect, isRedirect } from "./redirects.js";
11
10
  import { isNotFound } from "./not-found.js";
@@ -28,9 +27,7 @@ class Router {
28
27
  )}`;
29
28
  this.resetNextScroll = true;
30
29
  this.shouldViewTransition = void 0;
31
- this.latestLoadPromise = Promise.resolve();
32
30
  this.subscribers = /* @__PURE__ */ new Set();
33
- this.isServer = typeof document === "undefined";
34
31
  this.startReactTransition = (fn) => fn();
35
32
  this.update = (newOptions) => {
36
33
  if (newOptions.notFoundRoute) {
@@ -43,6 +40,7 @@ class Router {
43
40
  ...this.options,
44
41
  ...newOptions
45
42
  };
43
+ this.isServer = this.options.isServer ?? typeof document === "undefined";
46
44
  if (!this.basepath || newOptions.basepath && newOptions.basepath !== previousOptions.basepath) {
47
45
  if (newOptions.basepath === void 0 || newOptions.basepath === "" || newOptions.basepath === "/") {
48
46
  this.basepath = "/";
@@ -54,9 +52,9 @@ class Router {
54
52
  // eslint-disable-next-line ts/no-unnecessary-condition
55
53
  !this.history || this.options.history && this.options.history !== this.history
56
54
  ) {
57
- this.history = this.options.history ?? (typeof document !== "undefined" ? createBrowserHistory() : createMemoryHistory({
58
- initialEntries: [this.options.basepath || "/"]
59
- }));
55
+ this.history = this.options.history ?? (this.isServer ? createMemoryHistory({
56
+ initialEntries: [this.basepath || "/"]
57
+ }) : createBrowserHistory());
60
58
  this.latestLocation = this.parseLocation();
61
59
  }
62
60
  if (this.options.routeTree !== this.routeTree) {
@@ -170,11 +168,6 @@ class Router {
170
168
  }
171
169
  });
172
170
  };
173
- this.checkLatest = (promise) => {
174
- if (this.latestLoadPromise !== promise) {
175
- throw this.latestLoadPromise;
176
- }
177
- };
178
171
  this.parseLocation = (previousLocation) => {
179
172
  const parse = ({
180
173
  pathname,
@@ -326,7 +319,7 @@ class Router {
326
319
  params: routeParams,
327
320
  leaveWildcards: true
328
321
  }) + loaderDepsHash;
329
- const existingMatch = getRouteMatch(this.state, matchId);
322
+ const existingMatch = this.getMatch(matchId);
330
323
  const cause = this.state.matches.find((d) => d.id === matchId) ? "stay" : "enter";
331
324
  let match;
332
325
  if (existingMatch) {
@@ -336,11 +329,7 @@ class Router {
336
329
  params: routeParams
337
330
  };
338
331
  } else {
339
- const status = route.options.loader || route.options.beforeLoad ? "pending" : "success";
340
- const loadPromise = createControlledPromise();
341
- if (status === "success") {
342
- loadPromise.resolve();
343
- }
332
+ const status = route.options.loader || route.options.beforeLoad || route.lazyFn ? "pending" : "success";
344
333
  match = {
345
334
  id: matchId,
346
335
  index,
@@ -350,12 +339,10 @@ class Router {
350
339
  updatedAt: Date.now(),
351
340
  search: {},
352
341
  searchError: void 0,
353
- status: "pending",
342
+ status,
354
343
  isFetching: false,
355
344
  error: void 0,
356
345
  paramsError: parseErrors[index],
357
- loaderPromise: Promise.resolve(),
358
- loadPromise,
359
346
  routeContext: void 0,
360
347
  context: void 0,
361
348
  abortController: new AbortController(),
@@ -366,7 +353,8 @@ class Router {
366
353
  preload: false,
367
354
  links: (_d = (_c = route.options).links) == null ? void 0 : _d.call(_c),
368
355
  scripts: (_f = (_e = route.options).scripts) == null ? void 0 : _f.call(_e),
369
- staticData: route.options.staticData || {}
356
+ staticData: route.options.staticData || {},
357
+ loadPromise: createControlledPromise()
370
358
  };
371
359
  }
372
360
  if (match.status === "success") {
@@ -391,7 +379,7 @@ class Router {
391
379
  };
392
380
  this.cancelMatch = (id) => {
393
381
  var _a;
394
- (_a = getRouteMatch(this.state, id)) == null ? void 0 : _a.abortController.abort();
382
+ (_a = this.getMatch(id)) == null ? void 0 : _a.abortController.abort();
395
383
  };
396
384
  this.cancelMatches = () => {
397
385
  var _a;
@@ -515,8 +503,7 @@ class Router {
515
503
  }
516
504
  return buildWithMatches(opts);
517
505
  };
518
- this.commitLocation = async ({
519
- startTransition,
506
+ this.commitLocation = ({
520
507
  viewTransition,
521
508
  ignoreBlocker,
522
509
  ...next
@@ -528,6 +515,10 @@ class Router {
528
515
  return isEqual;
529
516
  };
530
517
  const isSameUrl = this.latestLocation.href === next.href;
518
+ const previousCommitPromise = this.commitLocationPromise;
519
+ this.commitLocationPromise = createControlledPromise(() => {
520
+ previousCommitPromise == null ? void 0 : previousCommitPromise.resolve();
521
+ });
531
522
  if (isSameUrl && isSameState()) {
532
523
  this.load();
533
524
  } else {
@@ -562,12 +553,14 @@ class Router {
562
553
  );
563
554
  }
564
555
  this.resetNextScroll = next.resetScroll ?? true;
565
- return this.latestLoadPromise;
556
+ if (!this.history.subscribers.size) {
557
+ this.load();
558
+ }
559
+ return this.commitLocationPromise;
566
560
  };
567
561
  this.buildAndCommitLocation = ({
568
562
  replace,
569
563
  resetScroll,
570
- startTransition,
571
564
  viewTransition,
572
565
  ignoreBlocker,
573
566
  ...rest
@@ -575,7 +568,6 @@ class Router {
575
568
  const location = this.buildLocation(rest);
576
569
  return this.commitLocation({
577
570
  ...location,
578
- startTransition,
579
571
  viewTransition,
580
572
  replace,
581
573
  resetScroll,
@@ -592,7 +584,7 @@ class Router {
592
584
  }
593
585
  invariant(
594
586
  !isExternal,
595
- "Attempting to navigate to external url with this.navigate!"
587
+ "Attempting to navigate to external url with router.navigate!"
596
588
  );
597
589
  return this.buildAndCommitLocation({
598
590
  ...rest,
@@ -603,127 +595,162 @@ class Router {
603
595
  };
604
596
  this.load = async () => {
605
597
  this.latestLocation = this.parseLocation(this.latestLocation);
606
- if (this.state.location === this.latestLocation) {
607
- return;
608
- }
609
- const promise = createControlledPromise();
610
- this.latestLoadPromise = promise;
598
+ this.__store.setState((s) => ({
599
+ ...s,
600
+ loadedAt: Date.now()
601
+ }));
611
602
  let redirect;
612
603
  let notFound;
613
- this.startReactTransition(async () => {
614
- try {
615
- const next = this.latestLocation;
616
- const prevLocation = this.state.resolvedLocation;
617
- const pathDidChange = prevLocation.href !== next.href;
618
- this.cancelMatches();
619
- let pendingMatches;
620
- this.__store.batch(() => {
621
- pendingMatches = this.matchRoutes(next.pathname, next.search);
622
- this.__store.setState((s) => ({
623
- ...s,
624
- status: "pending",
625
- isLoading: true,
626
- location: next,
627
- pendingMatches,
628
- // If a cached moved to pendingMatches, remove it from cachedMatches
629
- cachedMatches: s.cachedMatches.filter((d) => {
630
- return !pendingMatches.find((e) => e.id === d.id);
631
- })
632
- }));
633
- });
634
- if (!this.state.redirect) {
604
+ const loadPromise = new Promise((resolve) => {
605
+ this.startReactTransition(async () => {
606
+ var _a;
607
+ try {
608
+ const next = this.latestLocation;
609
+ const prevLocation = this.state.resolvedLocation;
610
+ const pathDidChange = prevLocation.href !== next.href;
611
+ this.cancelMatches();
612
+ let pendingMatches;
613
+ this.__store.batch(() => {
614
+ pendingMatches = this.matchRoutes(next.pathname, next.search);
615
+ this.__store.setState((s) => ({
616
+ ...s,
617
+ status: "pending",
618
+ isLoading: true,
619
+ location: next,
620
+ pendingMatches,
621
+ // If a cached moved to pendingMatches, remove it from cachedMatches
622
+ cachedMatches: s.cachedMatches.filter((d) => {
623
+ return !pendingMatches.find((e) => e.id === d.id);
624
+ })
625
+ }));
626
+ });
627
+ if (!this.state.redirect) {
628
+ this.emit({
629
+ type: "onBeforeNavigate",
630
+ fromLocation: prevLocation,
631
+ toLocation: next,
632
+ pathChanged: pathDidChange
633
+ });
634
+ }
635
635
  this.emit({
636
- type: "onBeforeNavigate",
636
+ type: "onBeforeLoad",
637
637
  fromLocation: prevLocation,
638
638
  toLocation: next,
639
639
  pathChanged: pathDidChange
640
640
  });
641
- }
642
- this.emit({
643
- type: "onBeforeLoad",
644
- fromLocation: prevLocation,
645
- toLocation: next,
646
- pathChanged: pathDidChange
647
- });
648
- await this.loadMatches({
649
- matches: pendingMatches,
650
- location: next,
651
- checkLatest: () => this.checkLatest(promise),
652
- onReady: async () => {
653
- await this.startViewTransition(async () => {
654
- let exitingMatches;
655
- let enteringMatches;
656
- let stayingMatches;
657
- this.__store.batch(() => {
658
- this.__store.setState((s) => {
659
- const previousMatches = s.matches;
660
- const newMatches = s.pendingMatches || s.matches;
661
- exitingMatches = previousMatches.filter(
662
- (match) => !newMatches.find((d) => d.id === match.id)
663
- );
664
- enteringMatches = newMatches.filter(
665
- (match) => !previousMatches.find((d) => d.id === match.id)
666
- );
667
- stayingMatches = previousMatches.filter(
668
- (match) => newMatches.find((d) => d.id === match.id)
669
- );
670
- return {
671
- ...s,
672
- isLoading: false,
673
- matches: newMatches,
674
- pendingMatches: void 0,
675
- cachedMatches: [
676
- ...s.cachedMatches,
677
- ...exitingMatches.filter((d) => d.status !== "error")
678
- ]
679
- };
641
+ await this.loadMatches({
642
+ matches: pendingMatches,
643
+ location: next,
644
+ // eslint-disable-next-line ts/require-await
645
+ onReady: async () => {
646
+ this.startViewTransition(async () => {
647
+ let exitingMatches;
648
+ let enteringMatches;
649
+ let stayingMatches;
650
+ this.__store.batch(() => {
651
+ this.__store.setState((s) => {
652
+ const previousMatches = s.matches;
653
+ const newMatches = s.pendingMatches || s.matches;
654
+ exitingMatches = previousMatches.filter(
655
+ (match) => !newMatches.find((d) => d.id === match.id)
656
+ );
657
+ enteringMatches = newMatches.filter(
658
+ (match) => !previousMatches.find((d) => d.id === match.id)
659
+ );
660
+ stayingMatches = previousMatches.filter(
661
+ (match) => newMatches.find((d) => d.id === match.id)
662
+ );
663
+ return {
664
+ ...s,
665
+ isLoading: false,
666
+ matches: newMatches,
667
+ pendingMatches: void 0,
668
+ cachedMatches: [
669
+ ...s.cachedMatches,
670
+ ...exitingMatches.filter((d) => d.status !== "error")
671
+ ]
672
+ };
673
+ });
674
+ this.cleanCache();
680
675
  });
681
- this.cleanCache();
682
- });
683
- [
684
- [exitingMatches, "onLeave"],
685
- [enteringMatches, "onEnter"],
686
- [stayingMatches, "onStay"]
687
- ].forEach(([matches, hook]) => {
688
- matches.forEach((match) => {
689
- var _a, _b;
690
- (_b = (_a = this.looseRoutesById[match.routeId].options)[hook]) == null ? void 0 : _b.call(_a, match);
676
+ [
677
+ [exitingMatches, "onLeave"],
678
+ [enteringMatches, "onEnter"],
679
+ [stayingMatches, "onStay"]
680
+ ].forEach(([matches, hook]) => {
681
+ matches.forEach((match) => {
682
+ var _a2, _b;
683
+ (_b = (_a2 = this.looseRoutesById[match.routeId].options)[hook]) == null ? void 0 : _b.call(_a2, match);
684
+ });
691
685
  });
692
686
  });
693
- });
694
- }
695
- });
696
- } catch (err) {
697
- if (isResolvedRedirect(err)) {
698
- redirect = err;
699
- if (!this.isServer) {
700
- this.navigate({ ...err, replace: true, __isRedirect: true });
687
+ }
688
+ });
689
+ } catch (err) {
690
+ if (isResolvedRedirect(err)) {
691
+ redirect = err;
692
+ if (!this.isServer) {
693
+ this.navigate({ ...err, replace: true, __isRedirect: true });
694
+ }
695
+ } else if (isNotFound(err)) {
696
+ notFound = err;
701
697
  }
702
- } else if (isNotFound(err)) {
703
- notFound = err;
698
+ this.__store.setState((s) => ({
699
+ ...s,
700
+ statusCode: redirect ? redirect.statusCode : notFound ? 404 : s.matches.some((d) => d.status === "error") ? 500 : 200,
701
+ redirect
702
+ }));
704
703
  }
705
- this.__store.setState((s) => ({
706
- ...s,
707
- statusCode: redirect ? redirect.statusCode : notFound ? 404 : s.matches.some((d) => d.status === "error") ? 500 : 200,
708
- redirect
709
- }));
710
- }
711
- promise.resolve();
704
+ if (this.latestLoadPromise === loadPromise) {
705
+ (_a = this.commitLocationPromise) == null ? void 0 : _a.resolve();
706
+ this.latestLoadPromise = void 0;
707
+ this.commitLocationPromise = void 0;
708
+ }
709
+ resolve();
710
+ });
712
711
  });
713
- return this.latestLoadPromise;
712
+ this.latestLoadPromise = loadPromise;
713
+ await loadPromise;
714
+ while (this.latestLoadPromise && loadPromise !== this.latestLoadPromise) {
715
+ await this.latestLoadPromise;
716
+ }
714
717
  };
715
- this.startViewTransition = async (fn) => {
718
+ this.startViewTransition = (fn) => {
716
719
  var _a, _b;
717
720
  const shouldViewTransition = this.shouldViewTransition ?? this.options.defaultViewTransition;
718
721
  delete this.shouldViewTransition;
719
722
  ((_b = (_a = shouldViewTransition && typeof document !== "undefined" ? document : void 0) == null ? void 0 : _a.startViewTransition) == null ? void 0 : _b.call(_a, fn)) || fn();
720
723
  };
724
+ this.updateMatch = (id, updater) => {
725
+ var _a;
726
+ let updated;
727
+ const isPending = (_a = this.state.pendingMatches) == null ? void 0 : _a.find((d) => d.id === id);
728
+ const isMatched = this.state.matches.find((d) => d.id === id);
729
+ const matchesKey = isPending ? "pendingMatches" : isMatched ? "matches" : "cachedMatches";
730
+ this.__store.setState((s) => {
731
+ var _a2;
732
+ return {
733
+ ...s,
734
+ [matchesKey]: (_a2 = s[matchesKey]) == null ? void 0 : _a2.map(
735
+ (d) => d.id === id ? updated = updater(d) : d
736
+ )
737
+ };
738
+ });
739
+ return updated;
740
+ };
741
+ this.getMatch = (matchId) => {
742
+ return [
743
+ ...this.state.cachedMatches,
744
+ ...this.state.pendingMatches ?? [],
745
+ ...this.state.matches
746
+ ].find((d) => d.id === matchId);
747
+ };
721
748
  this.loadMatches = async ({
722
- checkLatest,
723
749
  location,
724
750
  matches,
725
751
  preload,
726
- onReady
752
+ onReady,
753
+ updateMatch = this.updateMatch
727
754
  }) => {
728
755
  let firstBadMatchIndex;
729
756
  let rendered = false;
@@ -736,41 +763,32 @@ class Router {
736
763
  if (!this.isServer && !this.state.matches.length) {
737
764
  triggerOnReady();
738
765
  }
739
- const updateMatch = (id, updater, opts) => {
740
- var _a;
741
- let updated;
742
- const isPending = (_a = this.state.pendingMatches) == null ? void 0 : _a.find((d) => d.id === id);
743
- const isMatched = this.state.matches.find((d) => d.id === id);
744
- const matchesKey = isPending ? "pendingMatches" : isMatched ? "matches" : "cachedMatches";
745
- this.__store.setState((s) => {
746
- var _a2;
747
- return {
748
- ...s,
749
- [matchesKey]: (_a2 = s[matchesKey]) == null ? void 0 : _a2.map(
750
- (d) => d.id === id ? updated = updater(d) : d
751
- )
752
- };
753
- });
754
- return updated;
755
- };
756
766
  const handleRedirectAndNotFound = (match, err) => {
767
+ var _a, _b, _c;
757
768
  if (isResolvedRedirect(err)) throw err;
758
769
  if (isRedirect(err) || isNotFound(err)) {
759
770
  updateMatch(match.id, (prev) => ({
760
771
  ...prev,
761
772
  status: isRedirect(err) ? "redirected" : isNotFound(err) ? "notFound" : "error",
762
773
  isFetching: false,
763
- error: err
774
+ error: err,
775
+ beforeLoadPromise: void 0,
776
+ loaderPromise: void 0
764
777
  }));
765
778
  if (!err.routeId) {
766
779
  err.routeId = match.routeId;
767
780
  }
781
+ (_a = match.beforeLoadPromise) == null ? void 0 : _a.resolve();
782
+ (_b = match.loaderPromise) == null ? void 0 : _b.resolve();
783
+ (_c = match.loadPromise) == null ? void 0 : _c.resolve();
768
784
  if (isRedirect(err)) {
769
785
  rendered = true;
770
786
  err = this.resolveRedirect({ ...err, _fromLocation: location });
771
787
  throw err;
772
788
  } else if (isNotFound(err)) {
773
- this.handleNotFound(matches, err);
789
+ this._handleNotFound(matches, err, {
790
+ updateMatch
791
+ });
774
792
  throw err;
775
793
  }
776
794
  }
@@ -779,268 +797,293 @@ class Router {
779
797
  await new Promise((resolveAll, rejectAll) => {
780
798
  ;
781
799
  (async () => {
800
+ var _a, _b, _c;
782
801
  try {
783
- for (let [index, match] of matches.entries()) {
784
- const parentMatch = matches[index - 1];
785
- const route = this.looseRoutesById[match.routeId];
786
- const abortController = new AbortController();
787
- let loadPromise = match.loadPromise;
788
- const pendingMs = route.options.pendingMs ?? this.options.defaultPendingMs;
789
- const shouldPending = !!(onReady && !this.isServer && !preload && (route.options.loader || route.options.beforeLoad) && typeof pendingMs === "number" && pendingMs !== Infinity && (route.options.pendingComponent ?? this.options.defaultPendingComponent));
790
- if (shouldPending) {
791
- setTimeout(() => {
792
- try {
793
- checkLatest();
794
- triggerOnReady();
795
- } catch {
796
- }
797
- }, pendingMs);
802
+ const handleSerialError = (index, err, routerCode) => {
803
+ var _a2, _b2;
804
+ const { id: matchId, routeId } = matches[index];
805
+ const route = this.looseRoutesById[routeId];
806
+ if (err instanceof Promise) {
807
+ throw err;
798
808
  }
799
- if (match.isFetching) {
800
- continue;
809
+ err.routerCode = routerCode;
810
+ firstBadMatchIndex = firstBadMatchIndex ?? index;
811
+ handleRedirectAndNotFound(this.getMatch(matchId), err);
812
+ try {
813
+ (_b2 = (_a2 = route.options).onError) == null ? void 0 : _b2.call(_a2, err);
814
+ } catch (errorHandlerErr) {
815
+ err = errorHandlerErr;
816
+ handleRedirectAndNotFound(this.getMatch(matchId), err);
801
817
  }
802
- const previousResolve = loadPromise.resolve;
803
- loadPromise = createControlledPromise(
804
- // Resolve the old when we we resolve the new one
805
- previousResolve
806
- );
807
- matches[index] = match = updateMatch(match.id, (prev) => ({
808
- ...prev,
809
- isFetching: "beforeLoad",
810
- loadPromise
811
- }));
812
- const handleSerialError = (err, routerCode) => {
813
- var _a, _b;
814
- if (err instanceof Promise) {
815
- throw err;
816
- }
817
- err.routerCode = routerCode;
818
- firstBadMatchIndex = firstBadMatchIndex ?? index;
819
- handleRedirectAndNotFound(match, err);
820
- try {
821
- (_b = (_a = route.options).onError) == null ? void 0 : _b.call(_a, err);
822
- } catch (errorHandlerErr) {
823
- err = errorHandlerErr;
824
- handleRedirectAndNotFound(match, err);
825
- }
826
- matches[index] = match = updateMatch(match.id, () => ({
827
- ...match,
818
+ updateMatch(matchId, (prev) => {
819
+ var _a3;
820
+ (_a3 = prev.beforeLoadPromise) == null ? void 0 : _a3.resolve();
821
+ return {
822
+ ...prev,
828
823
  error: err,
829
824
  status: "error",
825
+ isFetching: false,
830
826
  updatedAt: Date.now(),
831
- abortController: new AbortController()
832
- }));
833
- };
834
- if (match.paramsError) {
835
- handleSerialError(match.paramsError, "PARSE_PARAMS");
836
- }
837
- if (match.searchError) {
838
- handleSerialError(match.searchError, "VALIDATE_SEARCH");
839
- }
840
- try {
841
- const parentContext = (parentMatch == null ? void 0 : parentMatch.context) ?? this.options.context ?? {};
842
- matches[index] = match = {
843
- ...match,
844
- routeContext: replaceEqualDeep(
845
- match.routeContext,
846
- parentContext
847
- ),
848
- context: replaceEqualDeep(match.context, parentContext),
849
- abortController
827
+ abortController: new AbortController(),
828
+ beforeLoadPromise: void 0
850
829
  };
851
- const beforeLoadFnContext = {
852
- search: match.search,
853
- abortController,
854
- params: match.params,
855
- preload: !!preload,
856
- context: match.routeContext,
857
- location,
858
- navigate: (opts) => this.navigate({ ...opts, _fromLocation: location }),
859
- buildLocation: this.buildLocation,
860
- cause: preload ? "preload" : match.cause
861
- };
862
- const beforeLoadContext = route.options.beforeLoad ? await route.options.beforeLoad(beforeLoadFnContext) ?? {} : {};
863
- checkLatest();
864
- if (isRedirect(beforeLoadContext) || isNotFound(beforeLoadContext)) {
865
- handleSerialError(beforeLoadContext, "BEFORE_LOAD");
830
+ });
831
+ };
832
+ for (const [index, { id: matchId, routeId }] of matches.entries()) {
833
+ const existingMatch = this.getMatch(matchId);
834
+ if (
835
+ // If we are in the middle of a load, either of these will be present
836
+ // (not to be confused with `loadPromise`, which is always defined)
837
+ existingMatch.beforeLoadPromise || existingMatch.loaderPromise
838
+ ) {
839
+ await existingMatch.beforeLoadPromise;
840
+ } else {
841
+ try {
842
+ updateMatch(matchId, (prev) => ({
843
+ ...prev,
844
+ loadPromise: createControlledPromise(() => {
845
+ var _a2;
846
+ (_a2 = prev.loadPromise) == null ? void 0 : _a2.resolve();
847
+ }),
848
+ beforeLoadPromise: createControlledPromise()
849
+ }));
850
+ const route = this.looseRoutesById[routeId];
851
+ const abortController = new AbortController();
852
+ const parentMatchId = (_a = matches[index - 1]) == null ? void 0 : _a.id;
853
+ const getParentContext = () => {
854
+ if (!parentMatchId) {
855
+ return this.options.context ?? {};
856
+ }
857
+ return this.getMatch(parentMatchId).context ?? this.options.context ?? {};
858
+ };
859
+ const pendingMs = route.options.pendingMs ?? this.options.defaultPendingMs;
860
+ const shouldPending = !!(onReady && !this.isServer && !preload && (route.options.loader || route.options.beforeLoad) && typeof pendingMs === "number" && pendingMs !== Infinity && (route.options.pendingComponent ?? this.options.defaultPendingComponent));
861
+ if (shouldPending) {
862
+ setTimeout(() => {
863
+ try {
864
+ triggerOnReady();
865
+ } catch {
866
+ }
867
+ }, pendingMs);
868
+ }
869
+ const { paramsError, searchError } = this.getMatch(matchId);
870
+ if (paramsError) {
871
+ handleSerialError(index, paramsError, "PARSE_PARAMS");
872
+ }
873
+ if (searchError) {
874
+ handleSerialError(index, searchError, "VALIDATE_SEARCH");
875
+ }
876
+ const parentContext = getParentContext();
877
+ updateMatch(matchId, (prev) => ({
878
+ ...prev,
879
+ isFetching: "beforeLoad",
880
+ fetchCount: prev.fetchCount + 1,
881
+ routeContext: replaceEqualDeep(
882
+ prev.routeContext,
883
+ parentContext
884
+ ),
885
+ context: replaceEqualDeep(prev.context, parentContext),
886
+ abortController
887
+ }));
888
+ const { search, params, routeContext, cause } = this.getMatch(matchId);
889
+ const beforeLoadFnContext = {
890
+ search,
891
+ abortController,
892
+ params,
893
+ preload: !!preload,
894
+ context: routeContext,
895
+ location,
896
+ navigate: (opts) => this.navigate({ ...opts, _fromLocation: location }),
897
+ buildLocation: this.buildLocation,
898
+ cause: preload ? "preload" : cause
899
+ };
900
+ const beforeLoadContext = await ((_c = (_b = route.options).beforeLoad) == null ? void 0 : _c.call(_b, beforeLoadFnContext)) ?? {};
901
+ if (isRedirect(beforeLoadContext) || isNotFound(beforeLoadContext)) {
902
+ handleSerialError(index, beforeLoadContext, "BEFORE_LOAD");
903
+ }
904
+ updateMatch(matchId, (prev) => {
905
+ const routeContext2 = {
906
+ ...prev.routeContext,
907
+ ...beforeLoadContext
908
+ };
909
+ return {
910
+ ...prev,
911
+ routeContext: replaceEqualDeep(
912
+ prev.routeContext,
913
+ routeContext2
914
+ ),
915
+ context: replaceEqualDeep(prev.context, routeContext2),
916
+ abortController
917
+ };
918
+ });
919
+ } catch (err) {
920
+ handleSerialError(index, err, "BEFORE_LOAD");
866
921
  }
867
- const context = {
868
- ...parentContext,
869
- ...beforeLoadContext
870
- };
871
- matches[index] = match = {
872
- ...match,
873
- routeContext: replaceEqualDeep(
874
- match.routeContext,
875
- beforeLoadContext
876
- ),
877
- context: replaceEqualDeep(match.context, context),
878
- abortController
879
- };
880
- updateMatch(match.id, () => match);
881
- } catch (err) {
882
- handleSerialError(err, "BEFORE_LOAD");
883
- break;
922
+ updateMatch(matchId, (prev) => {
923
+ var _a2;
924
+ (_a2 = prev.beforeLoadPromise) == null ? void 0 : _a2.resolve();
925
+ return {
926
+ ...prev,
927
+ beforeLoadPromise: void 0,
928
+ isFetching: false
929
+ };
930
+ });
884
931
  }
885
932
  }
886
- checkLatest();
887
933
  const validResolvedMatches = matches.slice(0, firstBadMatchIndex);
888
934
  const matchPromises = [];
889
- validResolvedMatches.forEach((match, index) => {
890
- const createValidateResolvedMatchPromise = async () => {
891
- const parentMatchPromise = matchPromises[index - 1];
892
- const route = this.looseRoutesById[match.routeId];
893
- const loaderContext = {
894
- params: match.params,
895
- deps: match.loaderDeps,
896
- preload: !!preload,
897
- parentMatchPromise,
898
- abortController: match.abortController,
899
- context: match.context,
900
- location,
901
- navigate: (opts) => this.navigate({ ...opts, _fromLocation: location }),
902
- cause: preload ? "preload" : match.cause,
903
- route
904
- };
905
- const fetchAndResolveInLoaderLifetime = async () => {
906
- var _a, _b, _c, _d, _e, _f, _g, _h, _i;
907
- const existing = getRouteMatch(this.state, match.id);
908
- let lazyPromise = Promise.resolve();
909
- let componentsPromise = Promise.resolve();
910
- let loaderPromise = existing.loaderPromise;
911
- const potentialPendingMinPromise = async () => {
912
- const latestMatch = getRouteMatch(this.state, match.id);
913
- if (latestMatch == null ? void 0 : latestMatch.minPendingPromise) {
914
- await latestMatch.minPendingPromise;
915
- checkLatest();
916
- updateMatch(latestMatch.id, (prev) => ({
917
- ...prev,
918
- minPendingPromise: void 0
919
- }));
920
- }
921
- };
922
- try {
923
- if (match.isFetching === "beforeLoad") {
924
- matches[index] = match = updateMatch(
925
- match.id,
926
- (prev) => ({
927
- ...prev,
928
- isFetching: "loader",
929
- fetchCount: match.fetchCount + 1
930
- })
931
- );
932
- lazyPromise = ((_a = route.lazyFn) == null ? void 0 : _a.call(route).then((lazyRoute) => {
933
- Object.assign(route.options, lazyRoute.options);
934
- })) || Promise.resolve();
935
- componentsPromise = lazyPromise.then(
936
- () => Promise.all(
937
- componentTypes.map(async (type) => {
938
- const component = route.options[type];
939
- if (component == null ? void 0 : component.preload) {
940
- await component.preload();
941
- }
942
- })
943
- )
944
- );
945
- await lazyPromise;
946
- checkLatest();
947
- loaderPromise = (_c = (_b = route.options).loader) == null ? void 0 : _c.call(_b, loaderContext);
948
- matches[index] = match = updateMatch(
949
- match.id,
950
- (prev) => ({
951
- ...prev,
952
- loaderPromise
953
- })
954
- );
955
- }
956
- let loaderData = await loaderPromise;
957
- if (this.serializeLoaderData) {
958
- loaderData = this.serializeLoaderData(loaderData, {
959
- router: this,
960
- match
961
- });
962
- }
963
- checkLatest();
964
- handleRedirectAndNotFound(match, loaderData);
965
- await potentialPendingMinPromise();
966
- checkLatest();
967
- const meta = (_e = (_d = route.options).meta) == null ? void 0 : _e.call(_d, {
968
- matches,
969
- match,
970
- params: match.params,
971
- loaderData
972
- });
973
- const headers = (_g = (_f = route.options).headers) == null ? void 0 : _g.call(_f, {
974
- loaderData
975
- });
976
- matches[index] = match = updateMatch(match.id, (prev) => ({
935
+ validResolvedMatches.forEach(({ id: matchId, routeId }, index) => {
936
+ matchPromises.push(
937
+ (async () => {
938
+ const { loaderPromise: prevLoaderPromise } = this.getMatch(matchId);
939
+ if (prevLoaderPromise) {
940
+ await prevLoaderPromise;
941
+ } else {
942
+ const parentMatchPromise = matchPromises[index - 1];
943
+ const route = this.looseRoutesById[routeId];
944
+ const getLoaderContext = () => {
945
+ const {
946
+ params,
947
+ loaderDeps,
948
+ abortController,
949
+ context,
950
+ cause
951
+ } = this.getMatch(matchId);
952
+ return {
953
+ params,
954
+ deps: loaderDeps,
955
+ preload: !!preload,
956
+ parentMatchPromise,
957
+ abortController,
958
+ context,
959
+ location,
960
+ navigate: (opts) => this.navigate({ ...opts, _fromLocation: location }),
961
+ cause: preload ? "preload" : cause,
962
+ route
963
+ };
964
+ };
965
+ const age = Date.now() - this.getMatch(matchId).updatedAt;
966
+ const staleAge = preload ? route.options.preloadStaleTime ?? this.options.defaultPreloadStaleTime ?? 3e4 : route.options.staleTime ?? this.options.defaultStaleTime ?? 0;
967
+ const shouldReloadOption = route.options.shouldReload;
968
+ const shouldReload = typeof shouldReloadOption === "function" ? shouldReloadOption(getLoaderContext()) : shouldReloadOption;
969
+ updateMatch(matchId, (prev) => ({
977
970
  ...prev,
978
- error: void 0,
979
- status: "success",
980
- isFetching: false,
981
- updatedAt: Date.now(),
982
- loaderData,
983
- meta,
984
- headers
971
+ loaderPromise: createControlledPromise(),
972
+ preload: !!preload && !this.state.matches.find((d) => d.id === matchId)
985
973
  }));
986
- } catch (e) {
987
- checkLatest();
988
- let error = e;
989
- await potentialPendingMinPromise();
990
- checkLatest();
991
- handleRedirectAndNotFound(match, e);
992
- try {
993
- (_i = (_h = route.options).onError) == null ? void 0 : _i.call(_h, e);
994
- } catch (onErrorError) {
995
- error = onErrorError;
996
- handleRedirectAndNotFound(match, onErrorError);
974
+ const runLoader = async () => {
975
+ var _a2, _b2, _c2, _d, _e, _f, _g, _h;
976
+ try {
977
+ const potentialPendingMinPromise = async () => {
978
+ const latestMatch = this.getMatch(matchId);
979
+ if (latestMatch.minPendingPromise) {
980
+ await latestMatch.minPendingPromise;
981
+ }
982
+ };
983
+ try {
984
+ route._lazyPromise = route._lazyPromise || (route.lazyFn ? route.lazyFn().then((lazyRoute) => {
985
+ Object.assign(
986
+ route.options,
987
+ lazyRoute.options
988
+ );
989
+ }) : Promise.resolve());
990
+ const componentsPromise = this.getMatch(matchId).componentsPromise || route._lazyPromise.then(
991
+ () => Promise.all(
992
+ componentTypes.map(async (type) => {
993
+ const component = route.options[type];
994
+ if (component == null ? void 0 : component.preload) {
995
+ await component.preload();
996
+ }
997
+ })
998
+ )
999
+ );
1000
+ updateMatch(matchId, (prev) => ({
1001
+ ...prev,
1002
+ isFetching: "loader",
1003
+ componentsPromise
1004
+ }));
1005
+ await route._lazyPromise;
1006
+ let loaderData = await ((_b2 = (_a2 = route.options).loader) == null ? void 0 : _b2.call(_a2, getLoaderContext()));
1007
+ if (this.serializeLoaderData) {
1008
+ loaderData = this.serializeLoaderData(loaderData, {
1009
+ router: this,
1010
+ match: this.getMatch(matchId)
1011
+ });
1012
+ }
1013
+ handleRedirectAndNotFound(
1014
+ this.getMatch(matchId),
1015
+ loaderData
1016
+ );
1017
+ await potentialPendingMinPromise();
1018
+ const meta = (_d = (_c2 = route.options).meta) == null ? void 0 : _d.call(_c2, {
1019
+ matches,
1020
+ match: this.getMatch(matchId),
1021
+ params: this.getMatch(matchId).params,
1022
+ loaderData
1023
+ });
1024
+ const headers = (_f = (_e = route.options).headers) == null ? void 0 : _f.call(_e, {
1025
+ loaderData
1026
+ });
1027
+ updateMatch(matchId, (prev) => ({
1028
+ ...prev,
1029
+ error: void 0,
1030
+ status: "success",
1031
+ isFetching: false,
1032
+ updatedAt: Date.now(),
1033
+ loaderData,
1034
+ meta,
1035
+ headers
1036
+ }));
1037
+ } catch (e) {
1038
+ let error = e;
1039
+ await potentialPendingMinPromise();
1040
+ handleRedirectAndNotFound(this.getMatch(matchId), e);
1041
+ try {
1042
+ (_h = (_g = route.options).onError) == null ? void 0 : _h.call(_g, e);
1043
+ } catch (onErrorError) {
1044
+ error = onErrorError;
1045
+ handleRedirectAndNotFound(
1046
+ this.getMatch(matchId),
1047
+ onErrorError
1048
+ );
1049
+ }
1050
+ updateMatch(matchId, (prev) => ({
1051
+ ...prev,
1052
+ error,
1053
+ status: "error",
1054
+ isFetching: false
1055
+ }));
1056
+ }
1057
+ await this.getMatch(matchId).componentsPromise;
1058
+ } catch (err) {
1059
+ handleRedirectAndNotFound(this.getMatch(matchId), err);
1060
+ }
1061
+ };
1062
+ const { status, invalid } = this.getMatch(matchId);
1063
+ if (status === "success" && (invalid || (shouldReload ?? age > staleAge))) {
1064
+ ;
1065
+ (async () => {
1066
+ try {
1067
+ await runLoader();
1068
+ } catch (err) {
1069
+ }
1070
+ })();
1071
+ } else if (status !== "success") {
1072
+ await runLoader();
997
1073
  }
998
- matches[index] = match = updateMatch(match.id, (prev) => ({
999
- ...prev,
1000
- error,
1001
- status: "error",
1002
- isFetching: false
1003
- }));
1074
+ const { loaderPromise, loadPromise } = this.getMatch(matchId);
1075
+ loaderPromise == null ? void 0 : loaderPromise.resolve();
1076
+ loadPromise == null ? void 0 : loadPromise.resolve();
1004
1077
  }
1005
- await componentsPromise;
1006
- checkLatest();
1007
- match.loadPromise.resolve();
1008
- };
1009
- const age = Date.now() - match.updatedAt;
1010
- const staleAge = preload ? route.options.preloadStaleTime ?? this.options.defaultPreloadStaleTime ?? 3e4 : route.options.staleTime ?? this.options.defaultStaleTime ?? 0;
1011
- const shouldReloadOption = route.options.shouldReload;
1012
- const shouldReload = typeof shouldReloadOption === "function" ? shouldReloadOption(loaderContext) : shouldReloadOption;
1013
- matches[index] = match = {
1014
- ...match,
1015
- preload: !!preload && !this.state.matches.find((d) => d.id === match.id)
1016
- };
1017
- const fetchWithRedirectAndNotFound = async () => {
1018
- try {
1019
- await fetchAndResolveInLoaderLifetime();
1020
- } catch (err) {
1021
- checkLatest();
1022
- handleRedirectAndNotFound(match, err);
1023
- }
1024
- };
1025
- if (match.status === "success" && (match.invalid || (shouldReload ?? age > staleAge))) {
1026
- ;
1027
- (async () => {
1028
- try {
1029
- await fetchWithRedirectAndNotFound();
1030
- } catch (err) {
1031
- }
1032
- })();
1033
- return;
1034
- }
1035
- if (match.status !== "success") {
1036
- await fetchWithRedirectAndNotFound();
1037
- }
1038
- return;
1039
- };
1040
- matchPromises.push(createValidateResolvedMatchPromise());
1078
+ updateMatch(matchId, (prev) => ({
1079
+ ...prev,
1080
+ isFetching: false,
1081
+ loaderPromise: void 0
1082
+ }));
1083
+ })()
1084
+ );
1041
1085
  });
1042
1086
  await Promise.all(matchPromises);
1043
- checkLatest();
1044
1087
  resolveAll();
1045
1088
  } catch (err) {
1046
1089
  rejectAll(err);
@@ -1062,7 +1105,7 @@ class Router {
1062
1105
  const invalidate = (d) => ({
1063
1106
  ...d,
1064
1107
  invalid: true,
1065
- ...d.status === "error" ? { status: "pending" } : {}
1108
+ ...d.status === "error" ? { status: "pending", error: void 0 } : {}
1066
1109
  });
1067
1110
  this.__store.setState((s) => {
1068
1111
  var _a;
@@ -1120,18 +1163,23 @@ class Router {
1120
1163
  }
1121
1164
  });
1122
1165
  });
1123
- const leafMatch = last(matches);
1124
- const currentLeafMatch = last(this.state.matches);
1125
- const pendingLeafMatch = last(this.state.pendingMatches ?? []);
1126
- if (leafMatch && ((currentLeafMatch == null ? void 0 : currentLeafMatch.id) === leafMatch.id || (pendingLeafMatch == null ? void 0 : pendingLeafMatch.id) === leafMatch.id)) {
1127
- return void 0;
1128
- }
1166
+ const activeMatchIds = new Set(
1167
+ [...this.state.matches, ...this.state.pendingMatches ?? []].map(
1168
+ (d) => d.id
1169
+ )
1170
+ );
1129
1171
  try {
1130
1172
  matches = await this.loadMatches({
1131
1173
  matches,
1132
1174
  location: next,
1133
1175
  preload: true,
1134
- checkLatest: () => void 0
1176
+ updateMatch: (id, updater) => {
1177
+ if (activeMatchIds.has(id)) {
1178
+ matches = matches.map((d) => d.id === id ? updater(d) : d);
1179
+ } else {
1180
+ this.updateMatch(id, updater);
1181
+ }
1182
+ }
1135
1183
  });
1136
1184
  return matches;
1137
1185
  } catch (err) {
@@ -1199,7 +1247,7 @@ class Router {
1199
1247
  manifest: this.manifest
1200
1248
  };
1201
1249
  };
1202
- this.hydrate = async () => {
1250
+ this.hydrate = () => {
1203
1251
  var _a, _b, _c;
1204
1252
  let ctx;
1205
1253
  if (typeof document !== "undefined") {
@@ -1272,7 +1320,9 @@ class Router {
1272
1320
  ${children}\`)` : ""}; __TSR__.cleanScripts()<\/script>`
1273
1321
  );
1274
1322
  };
1275
- this.handleNotFound = (matches, err) => {
1323
+ this._handleNotFound = (matches, err, {
1324
+ updateMatch = this.updateMatch
1325
+ } = {}) => {
1276
1326
  const matchesByRouteId = Object.fromEntries(
1277
1327
  matches.map((match2) => [match2.routeId, match2])
1278
1328
  );
@@ -1286,11 +1336,18 @@ class Router {
1286
1336
  }
1287
1337
  const match = matchesByRouteId[routeCursor.id];
1288
1338
  invariant(match, "Could not find match for route: " + routeCursor.id);
1289
- Object.assign(match, {
1339
+ updateMatch(match.id, (prev) => ({
1340
+ ...prev,
1290
1341
  status: "notFound",
1291
1342
  error: err,
1292
1343
  isFetching: false
1293
- });
1344
+ }));
1345
+ if (err.routerCode === "BEFORE_LOAD" && routeCursor.parentRoute) {
1346
+ err.routeId = routeCursor.parentRoute.id;
1347
+ this._handleNotFound(matches, err, {
1348
+ updateMatch
1349
+ });
1350
+ }
1294
1351
  };
1295
1352
  this.hasNotFoundMatch = () => {
1296
1353
  return this.__store.state.matches.some(
@@ -1316,11 +1373,6 @@ class Router {
1316
1373
  get looseRoutesById() {
1317
1374
  return this.routesById;
1318
1375
  }
1319
- // resolveMatchPromise = (matchId: string, key: string, value: any) => {
1320
- // state.matches
1321
- // .find((d) => d.id === matchId)
1322
- // ?.__promisesByKey[key]?.resolve(value)
1323
- // }
1324
1376
  }
1325
1377
  function lazyFn(fn, key) {
1326
1378
  return async (...args) => {
@@ -1334,6 +1386,7 @@ class PathParamError extends Error {
1334
1386
  }
1335
1387
  function getInitialRouterState(location) {
1336
1388
  return {
1389
+ loadedAt: 0,
1337
1390
  isLoading: false,
1338
1391
  isTransitioning: false,
1339
1392
  status: "idle",