@tanstack/react-router 1.45.2 → 1.45.4

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