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