@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
@@ -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") {
@@ -390,8 +378,10 @@ class Router {
390
378
  return matches;
391
379
  };
392
380
  this.cancelMatch = (id) => {
393
- var _a;
394
- (_a = getRouteMatch(this.state, id)) == null ? void 0 : _a.abortController.abort();
381
+ const match = this.getMatch(id);
382
+ if (!match) return;
383
+ match.abortController.abort();
384
+ clearTimeout(match.pendingTimeout);
395
385
  };
396
386
  this.cancelMatches = () => {
397
387
  var _a;
@@ -515,8 +505,7 @@ class Router {
515
505
  }
516
506
  return buildWithMatches(opts);
517
507
  };
518
- this.commitLocation = async ({
519
- startTransition,
508
+ this.commitLocation = ({
520
509
  viewTransition,
521
510
  ignoreBlocker,
522
511
  ...next
@@ -528,6 +517,10 @@ class Router {
528
517
  return isEqual;
529
518
  };
530
519
  const isSameUrl = this.latestLocation.href === next.href;
520
+ const previousCommitPromise = this.commitLocationPromise;
521
+ this.commitLocationPromise = createControlledPromise(() => {
522
+ previousCommitPromise == null ? void 0 : previousCommitPromise.resolve();
523
+ });
531
524
  if (isSameUrl && isSameState()) {
532
525
  this.load();
533
526
  } else {
@@ -562,12 +555,14 @@ class Router {
562
555
  );
563
556
  }
564
557
  this.resetNextScroll = next.resetScroll ?? true;
565
- return this.latestLoadPromise;
558
+ if (!this.history.subscribers.size) {
559
+ this.load();
560
+ }
561
+ return this.commitLocationPromise;
566
562
  };
567
563
  this.buildAndCommitLocation = ({
568
564
  replace,
569
565
  resetScroll,
570
- startTransition,
571
566
  viewTransition,
572
567
  ignoreBlocker,
573
568
  ...rest
@@ -575,7 +570,6 @@ class Router {
575
570
  const location = this.buildLocation(rest);
576
571
  return this.commitLocation({
577
572
  ...location,
578
- startTransition,
579
573
  viewTransition,
580
574
  replace,
581
575
  resetScroll,
@@ -592,7 +586,7 @@ class Router {
592
586
  }
593
587
  invariant(
594
588
  !isExternal,
595
- "Attempting to navigate to external url with this.navigate!"
589
+ "Attempting to navigate to external url with router.navigate!"
596
590
  );
597
591
  return this.buildAndCommitLocation({
598
592
  ...rest,
@@ -603,127 +597,162 @@ class Router {
603
597
  };
604
598
  this.load = async () => {
605
599
  this.latestLocation = this.parseLocation(this.latestLocation);
606
- if (this.state.location === this.latestLocation) {
607
- return;
608
- }
609
- const promise = createControlledPromise();
610
- this.latestLoadPromise = promise;
600
+ this.__store.setState((s) => ({
601
+ ...s,
602
+ loadedAt: Date.now()
603
+ }));
611
604
  let redirect;
612
605
  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) {
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
+ }
635
637
  this.emit({
636
- type: "onBeforeNavigate",
638
+ type: "onBeforeLoad",
637
639
  fromLocation: prevLocation,
638
640
  toLocation: next,
639
641
  pathChanged: pathDidChange
640
642
  });
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
- };
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();
680
677
  });
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);
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
+ });
691
687
  });
692
688
  });
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 });
689
+ }
690
+ });
691
+ } catch (err) {
692
+ if (isResolvedRedirect(err)) {
693
+ redirect = err;
694
+ if (!this.isServer) {
695
+ this.navigate({ ...err, replace: true, __isRedirect: true });
696
+ }
697
+ } else if (isNotFound(err)) {
698
+ notFound = err;
701
699
  }
702
- } else if (isNotFound(err)) {
703
- notFound = err;
700
+ this.__store.setState((s) => ({
701
+ ...s,
702
+ statusCode: redirect ? redirect.statusCode : notFound ? 404 : s.matches.some((d) => d.status === "error") ? 500 : 200,
703
+ redirect
704
+ }));
704
705
  }
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();
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
+ });
712
713
  });
713
- return this.latestLoadPromise;
714
+ this.latestLoadPromise = loadPromise;
715
+ await loadPromise;
716
+ while (this.latestLoadPromise && loadPromise !== this.latestLoadPromise) {
717
+ await this.latestLoadPromise;
718
+ }
714
719
  };
715
- this.startViewTransition = async (fn) => {
720
+ this.startViewTransition = (fn) => {
716
721
  var _a, _b;
717
722
  const shouldViewTransition = this.shouldViewTransition ?? this.options.defaultViewTransition;
718
723
  delete this.shouldViewTransition;
719
724
  ((_b = (_a = shouldViewTransition && typeof document !== "undefined" ? document : void 0) == null ? void 0 : _a.startViewTransition) == null ? void 0 : _b.call(_a, fn)) || fn();
720
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
+ };
721
750
  this.loadMatches = async ({
722
- checkLatest,
723
751
  location,
724
752
  matches,
725
753
  preload,
726
- onReady
754
+ onReady,
755
+ updateMatch = this.updateMatch
727
756
  }) => {
728
757
  let firstBadMatchIndex;
729
758
  let rendered = false;
@@ -736,41 +765,32 @@ class Router {
736
765
  if (!this.isServer && !this.state.matches.length) {
737
766
  triggerOnReady();
738
767
  }
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
768
  const handleRedirectAndNotFound = (match, err) => {
769
+ var _a, _b, _c;
757
770
  if (isResolvedRedirect(err)) throw err;
758
771
  if (isRedirect(err) || isNotFound(err)) {
759
772
  updateMatch(match.id, (prev) => ({
760
773
  ...prev,
761
774
  status: isRedirect(err) ? "redirected" : isNotFound(err) ? "notFound" : "error",
762
775
  isFetching: false,
763
- error: err
776
+ error: err,
777
+ beforeLoadPromise: void 0,
778
+ loaderPromise: void 0
764
779
  }));
765
780
  if (!err.routeId) {
766
781
  err.routeId = match.routeId;
767
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();
768
786
  if (isRedirect(err)) {
769
787
  rendered = true;
770
788
  err = this.resolveRedirect({ ...err, _fromLocation: location });
771
789
  throw err;
772
790
  } else if (isNotFound(err)) {
773
- this.handleNotFound(matches, err);
791
+ this._handleNotFound(matches, err, {
792
+ updateMatch
793
+ });
774
794
  throw err;
775
795
  }
776
796
  }
@@ -779,268 +799,295 @@ class Router {
779
799
  await new Promise((resolveAll, rejectAll) => {
780
800
  ;
781
801
  (async () => {
802
+ var _a, _b, _c;
782
803
  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);
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;
798
810
  }
799
- if (match.isFetching) {
800
- 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);
801
819
  }
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,
820
+ updateMatch(matchId, (prev) => {
821
+ var _a3;
822
+ (_a3 = prev.beforeLoadPromise) == null ? void 0 : _a3.resolve();
823
+ return {
824
+ ...prev,
828
825
  error: err,
829
826
  status: "error",
827
+ isFetching: false,
830
828
  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
850
- };
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
829
+ abortController: new AbortController(),
830
+ beforeLoadPromise: void 0
861
831
  };
862
- const beforeLoadContext = route.options.beforeLoad ? await route.options.beforeLoad(beforeLoadFnContext) ?? {} : {};
863
- checkLatest();
864
- if (isRedirect(beforeLoadContext) || isNotFound(beforeLoadContext)) {
865
- 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: createControlledPromise(() => {
847
+ var _a2;
848
+ (_a2 = prev.loadPromise) == null ? void 0 : _a2.resolve();
849
+ }),
850
+ beforeLoadPromise: 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
+ let pendingTimeout;
864
+ if (shouldPending) {
865
+ pendingTimeout = setTimeout(() => {
866
+ try {
867
+ triggerOnReady();
868
+ } catch {
869
+ }
870
+ }, pendingMs);
871
+ }
872
+ const { paramsError, searchError } = this.getMatch(matchId);
873
+ if (paramsError) {
874
+ handleSerialError(index, paramsError, "PARSE_PARAMS");
875
+ }
876
+ if (searchError) {
877
+ handleSerialError(index, searchError, "VALIDATE_SEARCH");
878
+ }
879
+ const parentContext = getParentContext();
880
+ updateMatch(matchId, (prev) => ({
881
+ ...prev,
882
+ isFetching: "beforeLoad",
883
+ fetchCount: prev.fetchCount + 1,
884
+ routeContext: replaceEqualDeep(
885
+ prev.routeContext,
886
+ parentContext
887
+ ),
888
+ context: replaceEqualDeep(prev.context, parentContext),
889
+ abortController,
890
+ pendingTimeout
891
+ }));
892
+ const { search, params, routeContext, cause } = this.getMatch(matchId);
893
+ const beforeLoadFnContext = {
894
+ search,
895
+ abortController,
896
+ params,
897
+ preload: !!preload,
898
+ context: routeContext,
899
+ location,
900
+ navigate: (opts) => this.navigate({ ...opts, _fromLocation: location }),
901
+ buildLocation: this.buildLocation,
902
+ cause: preload ? "preload" : cause
903
+ };
904
+ const beforeLoadContext = await ((_c = (_b = route.options).beforeLoad) == null ? void 0 : _c.call(_b, beforeLoadFnContext)) ?? {};
905
+ if (isRedirect(beforeLoadContext) || isNotFound(beforeLoadContext)) {
906
+ handleSerialError(index, beforeLoadContext, "BEFORE_LOAD");
907
+ }
908
+ updateMatch(matchId, (prev) => {
909
+ const routeContext2 = {
910
+ ...prev.routeContext,
911
+ ...beforeLoadContext
912
+ };
913
+ return {
914
+ ...prev,
915
+ routeContext: replaceEqualDeep(
916
+ prev.routeContext,
917
+ routeContext2
918
+ ),
919
+ context: replaceEqualDeep(prev.context, routeContext2),
920
+ abortController
921
+ };
922
+ });
923
+ } catch (err) {
924
+ handleSerialError(index, err, "BEFORE_LOAD");
866
925
  }
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;
926
+ updateMatch(matchId, (prev) => {
927
+ var _a2;
928
+ (_a2 = prev.beforeLoadPromise) == null ? void 0 : _a2.resolve();
929
+ return {
930
+ ...prev,
931
+ beforeLoadPromise: void 0,
932
+ isFetching: false
933
+ };
934
+ });
884
935
  }
885
936
  }
886
- checkLatest();
887
937
  const validResolvedMatches = matches.slice(0, firstBadMatchIndex);
888
938
  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) => ({
939
+ validResolvedMatches.forEach(({ id: matchId, routeId }, index) => {
940
+ matchPromises.push(
941
+ (async () => {
942
+ const { loaderPromise: prevLoaderPromise } = this.getMatch(matchId);
943
+ if (prevLoaderPromise) {
944
+ await prevLoaderPromise;
945
+ } else {
946
+ const parentMatchPromise = matchPromises[index - 1];
947
+ const route = this.looseRoutesById[routeId];
948
+ const getLoaderContext = () => {
949
+ const {
950
+ params,
951
+ loaderDeps,
952
+ abortController,
953
+ context,
954
+ cause
955
+ } = this.getMatch(matchId);
956
+ return {
957
+ params,
958
+ deps: loaderDeps,
959
+ preload: !!preload,
960
+ parentMatchPromise,
961
+ abortController,
962
+ context,
963
+ location,
964
+ navigate: (opts) => this.navigate({ ...opts, _fromLocation: location }),
965
+ cause: preload ? "preload" : cause,
966
+ route
967
+ };
968
+ };
969
+ const age = Date.now() - this.getMatch(matchId).updatedAt;
970
+ const staleAge = preload ? route.options.preloadStaleTime ?? this.options.defaultPreloadStaleTime ?? 3e4 : route.options.staleTime ?? this.options.defaultStaleTime ?? 0;
971
+ const shouldReloadOption = route.options.shouldReload;
972
+ const shouldReload = typeof shouldReloadOption === "function" ? shouldReloadOption(getLoaderContext()) : shouldReloadOption;
973
+ updateMatch(matchId, (prev) => ({
977
974
  ...prev,
978
- error: void 0,
979
- status: "success",
980
- isFetching: false,
981
- updatedAt: Date.now(),
982
- loaderData,
983
- meta,
984
- headers
975
+ loaderPromise: createControlledPromise(),
976
+ preload: !!preload && !this.state.matches.find((d) => d.id === matchId)
985
977
  }));
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);
978
+ const runLoader = async () => {
979
+ var _a2, _b2, _c2, _d, _e, _f, _g, _h;
980
+ try {
981
+ const potentialPendingMinPromise = async () => {
982
+ const latestMatch = this.getMatch(matchId);
983
+ if (latestMatch.minPendingPromise) {
984
+ await latestMatch.minPendingPromise;
985
+ }
986
+ };
987
+ try {
988
+ route._lazyPromise = route._lazyPromise || (route.lazyFn ? route.lazyFn().then((lazyRoute) => {
989
+ Object.assign(
990
+ route.options,
991
+ lazyRoute.options
992
+ );
993
+ }) : Promise.resolve());
994
+ const componentsPromise = this.getMatch(matchId).componentsPromise || route._lazyPromise.then(
995
+ () => Promise.all(
996
+ componentTypes.map(async (type) => {
997
+ const component = route.options[type];
998
+ if (component == null ? void 0 : component.preload) {
999
+ await component.preload();
1000
+ }
1001
+ })
1002
+ )
1003
+ );
1004
+ updateMatch(matchId, (prev) => ({
1005
+ ...prev,
1006
+ isFetching: "loader",
1007
+ componentsPromise
1008
+ }));
1009
+ await route._lazyPromise;
1010
+ let loaderData = await ((_b2 = (_a2 = route.options).loader) == null ? void 0 : _b2.call(_a2, getLoaderContext()));
1011
+ if (this.serializeLoaderData) {
1012
+ loaderData = this.serializeLoaderData(loaderData, {
1013
+ router: this,
1014
+ match: this.getMatch(matchId)
1015
+ });
1016
+ }
1017
+ handleRedirectAndNotFound(
1018
+ this.getMatch(matchId),
1019
+ loaderData
1020
+ );
1021
+ await potentialPendingMinPromise();
1022
+ const meta = (_d = (_c2 = route.options).meta) == null ? void 0 : _d.call(_c2, {
1023
+ matches,
1024
+ match: this.getMatch(matchId),
1025
+ params: this.getMatch(matchId).params,
1026
+ loaderData
1027
+ });
1028
+ const headers = (_f = (_e = route.options).headers) == null ? void 0 : _f.call(_e, {
1029
+ loaderData
1030
+ });
1031
+ updateMatch(matchId, (prev) => ({
1032
+ ...prev,
1033
+ error: void 0,
1034
+ status: "success",
1035
+ isFetching: false,
1036
+ updatedAt: Date.now(),
1037
+ loaderData,
1038
+ meta,
1039
+ headers
1040
+ }));
1041
+ } catch (e) {
1042
+ let error = e;
1043
+ await potentialPendingMinPromise();
1044
+ handleRedirectAndNotFound(this.getMatch(matchId), e);
1045
+ try {
1046
+ (_h = (_g = route.options).onError) == null ? void 0 : _h.call(_g, e);
1047
+ } catch (onErrorError) {
1048
+ error = onErrorError;
1049
+ handleRedirectAndNotFound(
1050
+ this.getMatch(matchId),
1051
+ onErrorError
1052
+ );
1053
+ }
1054
+ updateMatch(matchId, (prev) => ({
1055
+ ...prev,
1056
+ error,
1057
+ status: "error",
1058
+ isFetching: false
1059
+ }));
1060
+ }
1061
+ await this.getMatch(matchId).componentsPromise;
1062
+ } catch (err) {
1063
+ handleRedirectAndNotFound(this.getMatch(matchId), err);
1064
+ }
1065
+ };
1066
+ const { status, invalid } = this.getMatch(matchId);
1067
+ if (status === "success" && (invalid || (shouldReload ?? age > staleAge))) {
1068
+ ;
1069
+ (async () => {
1070
+ try {
1071
+ await runLoader();
1072
+ } catch (err) {
1073
+ }
1074
+ })();
1075
+ } else if (status !== "success") {
1076
+ await runLoader();
997
1077
  }
998
- matches[index] = match = updateMatch(match.id, (prev) => ({
999
- ...prev,
1000
- error,
1001
- status: "error",
1002
- isFetching: false
1003
- }));
1004
- }
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);
1078
+ const { loaderPromise, loadPromise } = this.getMatch(matchId);
1079
+ loaderPromise == null ? void 0 : loaderPromise.resolve();
1080
+ loadPromise == null ? void 0 : loadPromise.resolve();
1023
1081
  }
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());
1082
+ updateMatch(matchId, (prev) => ({
1083
+ ...prev,
1084
+ isFetching: false,
1085
+ loaderPromise: void 0
1086
+ }));
1087
+ })()
1088
+ );
1041
1089
  });
1042
1090
  await Promise.all(matchPromises);
1043
- checkLatest();
1044
1091
  resolveAll();
1045
1092
  } catch (err) {
1046
1093
  rejectAll(err);
@@ -1062,7 +1109,7 @@ class Router {
1062
1109
  const invalidate = (d) => ({
1063
1110
  ...d,
1064
1111
  invalid: true,
1065
- ...d.status === "error" ? { status: "pending" } : {}
1112
+ ...d.status === "error" ? { status: "pending", error: void 0 } : {}
1066
1113
  });
1067
1114
  this.__store.setState((s) => {
1068
1115
  var _a;
@@ -1120,18 +1167,23 @@ class Router {
1120
1167
  }
1121
1168
  });
1122
1169
  });
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
- }
1170
+ const activeMatchIds = new Set(
1171
+ [...this.state.matches, ...this.state.pendingMatches ?? []].map(
1172
+ (d) => d.id
1173
+ )
1174
+ );
1129
1175
  try {
1130
1176
  matches = await this.loadMatches({
1131
1177
  matches,
1132
1178
  location: next,
1133
1179
  preload: true,
1134
- checkLatest: () => void 0
1180
+ updateMatch: (id, updater) => {
1181
+ if (activeMatchIds.has(id)) {
1182
+ matches = matches.map((d) => d.id === id ? updater(d) : d);
1183
+ } else {
1184
+ this.updateMatch(id, updater);
1185
+ }
1186
+ }
1135
1187
  });
1136
1188
  return matches;
1137
1189
  } catch (err) {
@@ -1199,7 +1251,7 @@ class Router {
1199
1251
  manifest: this.manifest
1200
1252
  };
1201
1253
  };
1202
- this.hydrate = async () => {
1254
+ this.hydrate = () => {
1203
1255
  var _a, _b, _c;
1204
1256
  let ctx;
1205
1257
  if (typeof document !== "undefined") {
@@ -1272,7 +1324,9 @@ class Router {
1272
1324
  ${children}\`)` : ""}; __TSR__.cleanScripts()<\/script>`
1273
1325
  );
1274
1326
  };
1275
- this.handleNotFound = (matches, err) => {
1327
+ this._handleNotFound = (matches, err, {
1328
+ updateMatch = this.updateMatch
1329
+ } = {}) => {
1276
1330
  const matchesByRouteId = Object.fromEntries(
1277
1331
  matches.map((match2) => [match2.routeId, match2])
1278
1332
  );
@@ -1286,11 +1340,18 @@ class Router {
1286
1340
  }
1287
1341
  const match = matchesByRouteId[routeCursor.id];
1288
1342
  invariant(match, "Could not find match for route: " + routeCursor.id);
1289
- Object.assign(match, {
1343
+ updateMatch(match.id, (prev) => ({
1344
+ ...prev,
1290
1345
  status: "notFound",
1291
1346
  error: err,
1292
1347
  isFetching: false
1293
- });
1348
+ }));
1349
+ if (err.routerCode === "BEFORE_LOAD" && routeCursor.parentRoute) {
1350
+ err.routeId = routeCursor.parentRoute.id;
1351
+ this._handleNotFound(matches, err, {
1352
+ updateMatch
1353
+ });
1354
+ }
1294
1355
  };
1295
1356
  this.hasNotFoundMatch = () => {
1296
1357
  return this.__store.state.matches.some(
@@ -1316,11 +1377,6 @@ class Router {
1316
1377
  get looseRoutesById() {
1317
1378
  return this.routesById;
1318
1379
  }
1319
- // resolveMatchPromise = (matchId: string, key: string, value: any) => {
1320
- // state.matches
1321
- // .find((d) => d.id === matchId)
1322
- // ?.__promisesByKey[key]?.resolve(value)
1323
- // }
1324
1380
  }
1325
1381
  function lazyFn(fn, key) {
1326
1382
  return async (...args) => {
@@ -1334,6 +1390,7 @@ class PathParamError extends Error {
1334
1390
  }
1335
1391
  function getInitialRouterState(location) {
1336
1392
  return {
1393
+ loadedAt: 0,
1337
1394
  isLoading: false,
1338
1395
  isTransitioning: false,
1339
1396
  status: "idle",