tanstack-router-cache 0.1.3 → 0.1.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.
package/dist/index.cjs CHANGED
@@ -787,12 +787,50 @@ const LIVE_ROUTER_METHODS = [
787
787
  "navigate",
788
788
  "preloadRoute"
789
789
  ];
790
- function createStaticStore(value) {
790
+ const routerSnapshotUpdaters = /* @__PURE__ */ new WeakMap();
791
+ function createSnapshotStore(value) {
792
+ let currentValue = value;
793
+ const listeners = /* @__PURE__ */ new Set();
791
794
  return {
792
- get: () => value,
793
- subscribe: () => ({ unsubscribe: () => void 0 })
795
+ get: () => currentValue,
796
+ set: (nextValue) => {
797
+ if (Object.is(currentValue, nextValue)) return;
798
+ currentValue = nextValue;
799
+ for (const listener of listeners) listener(currentValue);
800
+ },
801
+ subscribe: (listener) => {
802
+ if (listener) listeners.add(listener);
803
+ return { unsubscribe: listener ? () => {
804
+ listeners.delete(listener);
805
+ } : () => void 0 };
806
+ }
794
807
  };
795
808
  }
809
+ function createRouterSnapshotData({ matches, router, routerLocation, routerResolvedLocation }) {
810
+ const snapshotMatches = matches.reduce((snapshot, match) => {
811
+ const nextMatch = snapshotMatch(match, routerLocation);
812
+ if (isRouterMatch(nextMatch)) snapshot.push(nextMatch);
813
+ return snapshot;
814
+ }, []);
815
+ return {
816
+ matches: snapshotMatches,
817
+ state: {
818
+ ...router.stores.__store.get(),
819
+ matches: snapshotMatches,
820
+ location: routerLocation,
821
+ resolvedLocation: routerResolvedLocation
822
+ }
823
+ };
824
+ }
825
+ function createMatchStore(match) {
826
+ return Object.assign(createSnapshotStore(match), { routeId: match.routeId });
827
+ }
828
+ function syncRouterSnapshot(routerSnapshot, input) {
829
+ const update = routerSnapshotUpdaters.get(routerSnapshot);
830
+ if (!update) return false;
831
+ update(input);
832
+ return true;
833
+ }
796
834
  function getLiveRouterMethodDescriptors(router) {
797
835
  return Object.fromEntries(LIVE_ROUTER_METHODS.flatMap((methodName) => {
798
836
  const method = router[methodName];
@@ -828,29 +866,40 @@ function hasCurrentRouteError({ erroredRouteCounts, matches, resolvedPathname, r
828
866
  function isRouterMatch(match) {
829
867
  return Boolean(match?.id && match.routeId);
830
868
  }
831
- function snapshotMatch(match) {
869
+ function snapshotMatch(match, routerLocation) {
832
870
  if (!isRouterMatch(match)) return;
871
+ const isLocationMatch = match.pathname ? normalizeCachedRoutePathname(match.pathname) === normalizeCachedRoutePathname(routerLocation.pathname) : false;
833
872
  return {
834
873
  ...match,
874
+ ...isLocationMatch ? {
875
+ _strictSearch: routerLocation.search,
876
+ search: routerLocation.search
877
+ } : {},
835
878
  _nonReactive: { ...match._nonReactive }
836
879
  };
837
880
  }
838
- function shouldRefreshCachedRoute(route, routerHref) {
839
- return !(isRouteCacheEnabled(route?.staticData) && route.ready) || route.href !== routerHref;
881
+ function isCurrentUnmanagedCachedRoute(route, routerHref) {
882
+ return Boolean(route && isRouteCacheEnabled(route.staticData) && route.ready && route.routerSnapshot && route.href === routerHref);
840
883
  }
841
884
  function syncReadyCachedRoute({ matchId, matches, routeId, route, routerLocation, router, routerResolvedLocation, routerHref, routerPathname, setCachedRoutes, staticData }) {
842
- if (!shouldRefreshCachedRoute(route, routerHref)) return;
885
+ const routerSnapshotInput = {
886
+ matches,
887
+ router,
888
+ routerLocation,
889
+ routerResolvedLocation
890
+ };
891
+ let routerSnapshot = matchId ? route?.routerSnapshot : void 0;
892
+ if (routerSnapshot && !syncRouterSnapshot(routerSnapshot, routerSnapshotInput)) {
893
+ if (isCurrentUnmanagedCachedRoute(route, routerHref)) return;
894
+ routerSnapshot = void 0;
895
+ }
896
+ if (matchId && !routerSnapshot) routerSnapshot = createRouterSnapshot(routerSnapshotInput);
843
897
  setCachedRoutes(routerPathname, {
844
898
  href: routerHref,
845
899
  matchId,
846
900
  routeId,
847
901
  ready: true,
848
- routerSnapshot: matchId ? createRouterSnapshot({
849
- matches,
850
- router,
851
- routerLocation,
852
- routerResolvedLocation
853
- }) : void 0,
902
+ routerSnapshot,
854
903
  staticData
855
904
  });
856
905
  }
@@ -879,54 +928,43 @@ function syncCachedRouteState({ isCurrentMatchReady, isCurrentMatchResolved, del
879
928
  }
880
929
  if (route) deleteCachedRoutes([routerPathname]);
881
930
  }
882
- function createRouterSnapshot({ matches, router, routerLocation, routerResolvedLocation }) {
883
- const snapshotMatches = matches.reduce((snapshot, match) => {
884
- const nextMatch = snapshotMatch(match);
885
- if (isRouterMatch(nextMatch)) snapshot.push(nextMatch);
886
- return snapshot;
887
- }, []);
888
- const matchStores = new Map(snapshotMatches.map((match) => {
889
- const store = Object.assign(createStaticStore(match), { routeId: match.routeId });
890
- return [match.id, store];
891
- }));
892
- const snapshotState = {
893
- ...router.stores.__store.get(),
894
- matches: snapshotMatches,
895
- location: routerLocation,
896
- resolvedLocation: routerResolvedLocation
897
- };
931
+ function createRouterSnapshot(input) {
932
+ const { router, routerLocation, routerResolvedLocation } = input;
933
+ const snapshotData = createRouterSnapshotData(input);
934
+ const snapshotMatches = snapshotData.matches;
935
+ const matchStores = new Map(snapshotMatches.map((match) => [match.id, createMatchStore(match)]));
898
936
  const routeMatchStoreCache = /* @__PURE__ */ new Map();
899
937
  const stores = {
900
938
  ...router.stores,
901
- status: createStaticStore(router.stores.status.get()),
902
- loadedAt: createStaticStore(router.stores.loadedAt.get()),
903
- isLoading: createStaticStore(router.stores.isLoading.get()),
904
- isTransitioning: createStaticStore(router.stores.isTransitioning.get()),
905
- location: createStaticStore(routerLocation),
906
- resolvedLocation: createStaticStore(routerResolvedLocation),
907
- statusCode: createStaticStore(router.stores.statusCode.get()),
908
- redirect: createStaticStore(router.stores.redirect.get()),
909
- matchesId: createStaticStore(snapshotMatches.map((match) => match.id)),
910
- pendingIds: createStaticStore([]),
911
- cachedIds: createStaticStore([]),
912
- matches: createStaticStore(snapshotMatches),
913
- pendingMatches: createStaticStore([]),
914
- cachedMatches: createStaticStore([]),
915
- firstId: createStaticStore(snapshotMatches[0]?.id),
916
- hasPending: createStaticStore(snapshotMatches.some((match) => match.status === "pending")),
917
- matchRouteDeps: createStaticStore({
939
+ status: createSnapshotStore(router.stores.status.get()),
940
+ loadedAt: createSnapshotStore(router.stores.loadedAt.get()),
941
+ isLoading: createSnapshotStore(router.stores.isLoading.get()),
942
+ isTransitioning: createSnapshotStore(router.stores.isTransitioning.get()),
943
+ location: createSnapshotStore(routerLocation),
944
+ resolvedLocation: createSnapshotStore(routerResolvedLocation),
945
+ statusCode: createSnapshotStore(router.stores.statusCode.get()),
946
+ redirect: createSnapshotStore(router.stores.redirect.get()),
947
+ matchesId: createSnapshotStore(snapshotMatches.map((match) => match.id)),
948
+ pendingIds: createSnapshotStore([]),
949
+ cachedIds: createSnapshotStore([]),
950
+ matches: createSnapshotStore(snapshotMatches),
951
+ pendingMatches: createSnapshotStore([]),
952
+ cachedMatches: createSnapshotStore([]),
953
+ firstId: createSnapshotStore(snapshotMatches[0]?.id),
954
+ hasPending: createSnapshotStore(snapshotMatches.some((match) => match.status === "pending")),
955
+ matchRouteDeps: createSnapshotStore({
918
956
  locationHref: routerLocation.href,
919
957
  resolvedLocationHref: routerResolvedLocation?.href,
920
- status: snapshotState.status
958
+ status: snapshotData.state.status
921
959
  }),
922
- __store: createStaticStore(snapshotState),
960
+ __store: createSnapshotStore(snapshotData.state),
923
961
  matchStores,
924
962
  pendingMatchStores: /* @__PURE__ */ new Map(),
925
963
  cachedMatchStores: /* @__PURE__ */ new Map(),
926
964
  getRouteMatchStore: (routeId) => {
927
965
  let cached = routeMatchStoreCache.get(routeId);
928
966
  if (!cached) {
929
- cached = createStaticStore(snapshotMatches.find((match) => match.routeId === routeId));
967
+ cached = createSnapshotStore(snapshotMatches.find((match) => match.routeId === routeId));
930
968
  routeMatchStoreCache.set(routeId, cached);
931
969
  }
932
970
  return cached;
@@ -935,14 +973,53 @@ function createRouterSnapshot({ matches, router, routerLocation, routerResolvedL
935
973
  setPending: () => void 0,
936
974
  setCached: () => void 0
937
975
  };
976
+ const updateSnapshot = (nextInput) => {
977
+ const nextData = createRouterSnapshotData(nextInput);
978
+ const nextMatches = nextData.matches;
979
+ const nextMatchIds = new Set(nextMatches.map((match) => match.id));
980
+ const nextMatchesByRouteId = new Map(nextMatches.map((match) => [match.routeId, match]));
981
+ for (const match of nextMatches) {
982
+ const store = matchStores.get(match.id);
983
+ if (store) {
984
+ store.set(match);
985
+ continue;
986
+ }
987
+ matchStores.set(match.id, createMatchStore(match));
988
+ }
989
+ for (const [matchId] of matchStores) if (!nextMatchIds.has(matchId)) matchStores.delete(matchId);
990
+ stores.status.set(nextInput.router.stores.status.get());
991
+ stores.loadedAt.set(nextInput.router.stores.loadedAt.get());
992
+ stores.isLoading.set(nextInput.router.stores.isLoading.get());
993
+ stores.isTransitioning.set(nextInput.router.stores.isTransitioning.get());
994
+ stores.location.set(nextInput.routerLocation);
995
+ stores.resolvedLocation.set(nextInput.routerResolvedLocation);
996
+ stores.statusCode.set(nextInput.router.stores.statusCode.get());
997
+ stores.redirect.set(nextInput.router.stores.redirect.get());
998
+ stores.matchesId.set(nextMatches.map((match) => match.id));
999
+ stores.pendingIds.set([]);
1000
+ stores.cachedIds.set([]);
1001
+ stores.matches.set(nextMatches);
1002
+ stores.pendingMatches.set([]);
1003
+ stores.cachedMatches.set([]);
1004
+ stores.firstId.set(nextMatches[0]?.id);
1005
+ stores.hasPending.set(nextMatches.some((match) => match.status === "pending"));
1006
+ stores.matchRouteDeps.set({
1007
+ locationHref: nextInput.routerLocation.href,
1008
+ resolvedLocationHref: nextInput.routerResolvedLocation?.href,
1009
+ status: nextData.state.status
1010
+ });
1011
+ stores.__store.set(nextData.state);
1012
+ for (const [routeId, store] of routeMatchStoreCache) store.set(nextMatchesByRouteId.get(routeId));
1013
+ };
938
1014
  const routerSnapshot = Object.create(router);
939
1015
  Object.defineProperties(routerSnapshot, {
940
1016
  stores: { value: stores },
941
- latestLocation: { value: routerLocation },
1017
+ latestLocation: { get: () => stores.location.get() },
942
1018
  getMatch: { value: (matchId) => matchStores.get(matchId)?.get() },
943
1019
  updateMatch: { value: () => void 0 },
944
1020
  ...getLiveRouterMethodDescriptors(router)
945
1021
  });
1022
+ routerSnapshotUpdaters.set(routerSnapshot, updateSnapshot);
946
1023
  return routerSnapshot;
947
1024
  }
948
1025
  function getRouterCacheStaticData(childMatches, isCurrentMatchResolved) {
@@ -1014,7 +1091,7 @@ function RouteCacheManager() {
1014
1091
  const previousPathnameRef = (0, react.useRef)(void 0);
1015
1092
  const previousHrefRef = (0, react.useRef)(void 0);
1016
1093
  const previousRouteCacheModesRef = (0, react.useRef)(null);
1017
- if (previousRouteCacheModesRef.current === null) previousRouteCacheModesRef.current = /* @__PURE__ */ new Map();
1094
+ previousRouteCacheModesRef.current ??= /* @__PURE__ */ new Map();
1018
1095
  const previousVisiblePathnameRef = (0, react.useRef)(void 0);
1019
1096
  const routerLocation = (0, _tanstack_react_router.useRouterState)({ select: (state) => toRouterLocation(state.location) });
1020
1097
  const routerHref = routerLocation.href;
@@ -1110,7 +1187,7 @@ function RouteCacheManager() {
1110
1187
  visiblePathname
1111
1188
  ]);
1112
1189
  (0, react.useLayoutEffect)(() => {
1113
- if (previousRouteCacheModesRef.current === null) previousRouteCacheModesRef.current = /* @__PURE__ */ new Map();
1190
+ previousRouteCacheModesRef.current ??= /* @__PURE__ */ new Map();
1114
1191
  previousRouteCacheModesRef.current = syncCachedRouteActivityEvents({
1115
1192
  cachedRoutes,
1116
1193
  eventListener,
package/dist/index.js CHANGED
@@ -786,12 +786,50 @@ const LIVE_ROUTER_METHODS = [
786
786
  "navigate",
787
787
  "preloadRoute"
788
788
  ];
789
- function createStaticStore(value) {
789
+ const routerSnapshotUpdaters = /* @__PURE__ */ new WeakMap();
790
+ function createSnapshotStore(value) {
791
+ let currentValue = value;
792
+ const listeners = /* @__PURE__ */ new Set();
790
793
  return {
791
- get: () => value,
792
- subscribe: () => ({ unsubscribe: () => void 0 })
794
+ get: () => currentValue,
795
+ set: (nextValue) => {
796
+ if (Object.is(currentValue, nextValue)) return;
797
+ currentValue = nextValue;
798
+ for (const listener of listeners) listener(currentValue);
799
+ },
800
+ subscribe: (listener) => {
801
+ if (listener) listeners.add(listener);
802
+ return { unsubscribe: listener ? () => {
803
+ listeners.delete(listener);
804
+ } : () => void 0 };
805
+ }
793
806
  };
794
807
  }
808
+ function createRouterSnapshotData({ matches, router, routerLocation, routerResolvedLocation }) {
809
+ const snapshotMatches = matches.reduce((snapshot, match) => {
810
+ const nextMatch = snapshotMatch(match, routerLocation);
811
+ if (isRouterMatch(nextMatch)) snapshot.push(nextMatch);
812
+ return snapshot;
813
+ }, []);
814
+ return {
815
+ matches: snapshotMatches,
816
+ state: {
817
+ ...router.stores.__store.get(),
818
+ matches: snapshotMatches,
819
+ location: routerLocation,
820
+ resolvedLocation: routerResolvedLocation
821
+ }
822
+ };
823
+ }
824
+ function createMatchStore(match) {
825
+ return Object.assign(createSnapshotStore(match), { routeId: match.routeId });
826
+ }
827
+ function syncRouterSnapshot(routerSnapshot, input) {
828
+ const update = routerSnapshotUpdaters.get(routerSnapshot);
829
+ if (!update) return false;
830
+ update(input);
831
+ return true;
832
+ }
795
833
  function getLiveRouterMethodDescriptors(router) {
796
834
  return Object.fromEntries(LIVE_ROUTER_METHODS.flatMap((methodName) => {
797
835
  const method = router[methodName];
@@ -827,29 +865,40 @@ function hasCurrentRouteError({ erroredRouteCounts, matches, resolvedPathname, r
827
865
  function isRouterMatch(match) {
828
866
  return Boolean(match?.id && match.routeId);
829
867
  }
830
- function snapshotMatch(match) {
868
+ function snapshotMatch(match, routerLocation) {
831
869
  if (!isRouterMatch(match)) return;
870
+ const isLocationMatch = match.pathname ? normalizeCachedRoutePathname(match.pathname) === normalizeCachedRoutePathname(routerLocation.pathname) : false;
832
871
  return {
833
872
  ...match,
873
+ ...isLocationMatch ? {
874
+ _strictSearch: routerLocation.search,
875
+ search: routerLocation.search
876
+ } : {},
834
877
  _nonReactive: { ...match._nonReactive }
835
878
  };
836
879
  }
837
- function shouldRefreshCachedRoute(route, routerHref) {
838
- return !(isRouteCacheEnabled(route?.staticData) && route.ready) || route.href !== routerHref;
880
+ function isCurrentUnmanagedCachedRoute(route, routerHref) {
881
+ return Boolean(route && isRouteCacheEnabled(route.staticData) && route.ready && route.routerSnapshot && route.href === routerHref);
839
882
  }
840
883
  function syncReadyCachedRoute({ matchId, matches, routeId, route, routerLocation, router, routerResolvedLocation, routerHref, routerPathname, setCachedRoutes, staticData }) {
841
- if (!shouldRefreshCachedRoute(route, routerHref)) return;
884
+ const routerSnapshotInput = {
885
+ matches,
886
+ router,
887
+ routerLocation,
888
+ routerResolvedLocation
889
+ };
890
+ let routerSnapshot = matchId ? route?.routerSnapshot : void 0;
891
+ if (routerSnapshot && !syncRouterSnapshot(routerSnapshot, routerSnapshotInput)) {
892
+ if (isCurrentUnmanagedCachedRoute(route, routerHref)) return;
893
+ routerSnapshot = void 0;
894
+ }
895
+ if (matchId && !routerSnapshot) routerSnapshot = createRouterSnapshot(routerSnapshotInput);
842
896
  setCachedRoutes(routerPathname, {
843
897
  href: routerHref,
844
898
  matchId,
845
899
  routeId,
846
900
  ready: true,
847
- routerSnapshot: matchId ? createRouterSnapshot({
848
- matches,
849
- router,
850
- routerLocation,
851
- routerResolvedLocation
852
- }) : void 0,
901
+ routerSnapshot,
853
902
  staticData
854
903
  });
855
904
  }
@@ -878,54 +927,43 @@ function syncCachedRouteState({ isCurrentMatchReady, isCurrentMatchResolved, del
878
927
  }
879
928
  if (route) deleteCachedRoutes([routerPathname]);
880
929
  }
881
- function createRouterSnapshot({ matches, router, routerLocation, routerResolvedLocation }) {
882
- const snapshotMatches = matches.reduce((snapshot, match) => {
883
- const nextMatch = snapshotMatch(match);
884
- if (isRouterMatch(nextMatch)) snapshot.push(nextMatch);
885
- return snapshot;
886
- }, []);
887
- const matchStores = new Map(snapshotMatches.map((match) => {
888
- const store = Object.assign(createStaticStore(match), { routeId: match.routeId });
889
- return [match.id, store];
890
- }));
891
- const snapshotState = {
892
- ...router.stores.__store.get(),
893
- matches: snapshotMatches,
894
- location: routerLocation,
895
- resolvedLocation: routerResolvedLocation
896
- };
930
+ function createRouterSnapshot(input) {
931
+ const { router, routerLocation, routerResolvedLocation } = input;
932
+ const snapshotData = createRouterSnapshotData(input);
933
+ const snapshotMatches = snapshotData.matches;
934
+ const matchStores = new Map(snapshotMatches.map((match) => [match.id, createMatchStore(match)]));
897
935
  const routeMatchStoreCache = /* @__PURE__ */ new Map();
898
936
  const stores = {
899
937
  ...router.stores,
900
- status: createStaticStore(router.stores.status.get()),
901
- loadedAt: createStaticStore(router.stores.loadedAt.get()),
902
- isLoading: createStaticStore(router.stores.isLoading.get()),
903
- isTransitioning: createStaticStore(router.stores.isTransitioning.get()),
904
- location: createStaticStore(routerLocation),
905
- resolvedLocation: createStaticStore(routerResolvedLocation),
906
- statusCode: createStaticStore(router.stores.statusCode.get()),
907
- redirect: createStaticStore(router.stores.redirect.get()),
908
- matchesId: createStaticStore(snapshotMatches.map((match) => match.id)),
909
- pendingIds: createStaticStore([]),
910
- cachedIds: createStaticStore([]),
911
- matches: createStaticStore(snapshotMatches),
912
- pendingMatches: createStaticStore([]),
913
- cachedMatches: createStaticStore([]),
914
- firstId: createStaticStore(snapshotMatches[0]?.id),
915
- hasPending: createStaticStore(snapshotMatches.some((match) => match.status === "pending")),
916
- matchRouteDeps: createStaticStore({
938
+ status: createSnapshotStore(router.stores.status.get()),
939
+ loadedAt: createSnapshotStore(router.stores.loadedAt.get()),
940
+ isLoading: createSnapshotStore(router.stores.isLoading.get()),
941
+ isTransitioning: createSnapshotStore(router.stores.isTransitioning.get()),
942
+ location: createSnapshotStore(routerLocation),
943
+ resolvedLocation: createSnapshotStore(routerResolvedLocation),
944
+ statusCode: createSnapshotStore(router.stores.statusCode.get()),
945
+ redirect: createSnapshotStore(router.stores.redirect.get()),
946
+ matchesId: createSnapshotStore(snapshotMatches.map((match) => match.id)),
947
+ pendingIds: createSnapshotStore([]),
948
+ cachedIds: createSnapshotStore([]),
949
+ matches: createSnapshotStore(snapshotMatches),
950
+ pendingMatches: createSnapshotStore([]),
951
+ cachedMatches: createSnapshotStore([]),
952
+ firstId: createSnapshotStore(snapshotMatches[0]?.id),
953
+ hasPending: createSnapshotStore(snapshotMatches.some((match) => match.status === "pending")),
954
+ matchRouteDeps: createSnapshotStore({
917
955
  locationHref: routerLocation.href,
918
956
  resolvedLocationHref: routerResolvedLocation?.href,
919
- status: snapshotState.status
957
+ status: snapshotData.state.status
920
958
  }),
921
- __store: createStaticStore(snapshotState),
959
+ __store: createSnapshotStore(snapshotData.state),
922
960
  matchStores,
923
961
  pendingMatchStores: /* @__PURE__ */ new Map(),
924
962
  cachedMatchStores: /* @__PURE__ */ new Map(),
925
963
  getRouteMatchStore: (routeId) => {
926
964
  let cached = routeMatchStoreCache.get(routeId);
927
965
  if (!cached) {
928
- cached = createStaticStore(snapshotMatches.find((match) => match.routeId === routeId));
966
+ cached = createSnapshotStore(snapshotMatches.find((match) => match.routeId === routeId));
929
967
  routeMatchStoreCache.set(routeId, cached);
930
968
  }
931
969
  return cached;
@@ -934,14 +972,53 @@ function createRouterSnapshot({ matches, router, routerLocation, routerResolvedL
934
972
  setPending: () => void 0,
935
973
  setCached: () => void 0
936
974
  };
975
+ const updateSnapshot = (nextInput) => {
976
+ const nextData = createRouterSnapshotData(nextInput);
977
+ const nextMatches = nextData.matches;
978
+ const nextMatchIds = new Set(nextMatches.map((match) => match.id));
979
+ const nextMatchesByRouteId = new Map(nextMatches.map((match) => [match.routeId, match]));
980
+ for (const match of nextMatches) {
981
+ const store = matchStores.get(match.id);
982
+ if (store) {
983
+ store.set(match);
984
+ continue;
985
+ }
986
+ matchStores.set(match.id, createMatchStore(match));
987
+ }
988
+ for (const [matchId] of matchStores) if (!nextMatchIds.has(matchId)) matchStores.delete(matchId);
989
+ stores.status.set(nextInput.router.stores.status.get());
990
+ stores.loadedAt.set(nextInput.router.stores.loadedAt.get());
991
+ stores.isLoading.set(nextInput.router.stores.isLoading.get());
992
+ stores.isTransitioning.set(nextInput.router.stores.isTransitioning.get());
993
+ stores.location.set(nextInput.routerLocation);
994
+ stores.resolvedLocation.set(nextInput.routerResolvedLocation);
995
+ stores.statusCode.set(nextInput.router.stores.statusCode.get());
996
+ stores.redirect.set(nextInput.router.stores.redirect.get());
997
+ stores.matchesId.set(nextMatches.map((match) => match.id));
998
+ stores.pendingIds.set([]);
999
+ stores.cachedIds.set([]);
1000
+ stores.matches.set(nextMatches);
1001
+ stores.pendingMatches.set([]);
1002
+ stores.cachedMatches.set([]);
1003
+ stores.firstId.set(nextMatches[0]?.id);
1004
+ stores.hasPending.set(nextMatches.some((match) => match.status === "pending"));
1005
+ stores.matchRouteDeps.set({
1006
+ locationHref: nextInput.routerLocation.href,
1007
+ resolvedLocationHref: nextInput.routerResolvedLocation?.href,
1008
+ status: nextData.state.status
1009
+ });
1010
+ stores.__store.set(nextData.state);
1011
+ for (const [routeId, store] of routeMatchStoreCache) store.set(nextMatchesByRouteId.get(routeId));
1012
+ };
937
1013
  const routerSnapshot = Object.create(router);
938
1014
  Object.defineProperties(routerSnapshot, {
939
1015
  stores: { value: stores },
940
- latestLocation: { value: routerLocation },
1016
+ latestLocation: { get: () => stores.location.get() },
941
1017
  getMatch: { value: (matchId) => matchStores.get(matchId)?.get() },
942
1018
  updateMatch: { value: () => void 0 },
943
1019
  ...getLiveRouterMethodDescriptors(router)
944
1020
  });
1021
+ routerSnapshotUpdaters.set(routerSnapshot, updateSnapshot);
945
1022
  return routerSnapshot;
946
1023
  }
947
1024
  function getRouterCacheStaticData(childMatches, isCurrentMatchResolved) {
@@ -1013,7 +1090,7 @@ function RouteCacheManager() {
1013
1090
  const previousPathnameRef = useRef(void 0);
1014
1091
  const previousHrefRef = useRef(void 0);
1015
1092
  const previousRouteCacheModesRef = useRef(null);
1016
- if (previousRouteCacheModesRef.current === null) previousRouteCacheModesRef.current = /* @__PURE__ */ new Map();
1093
+ previousRouteCacheModesRef.current ??= /* @__PURE__ */ new Map();
1017
1094
  const previousVisiblePathnameRef = useRef(void 0);
1018
1095
  const routerLocation = useRouterState({ select: (state) => toRouterLocation(state.location) });
1019
1096
  const routerHref = routerLocation.href;
@@ -1109,7 +1186,7 @@ function RouteCacheManager() {
1109
1186
  visiblePathname
1110
1187
  ]);
1111
1188
  useLayoutEffect(() => {
1112
- if (previousRouteCacheModesRef.current === null) previousRouteCacheModesRef.current = /* @__PURE__ */ new Map();
1189
+ previousRouteCacheModesRef.current ??= /* @__PURE__ */ new Map();
1113
1190
  previousRouteCacheModesRef.current = syncCachedRouteActivityEvents({
1114
1191
  cachedRoutes,
1115
1192
  eventListener,
@@ -131,8 +131,8 @@ classDiagram
131
131
  }
132
132
 
133
133
  class RouterSnapshot {
134
- static stores
135
- static matches
134
+ snapshot stores
135
+ snapshot matches
136
136
  live navigate
137
137
  live invalidate
138
138
  live preloadRoute
@@ -149,21 +149,21 @@ classDiagram
149
149
  | `routeId` | TanStack route id. Used by `maxEntriesPerRouteId`. |
150
150
  | `staticData` | Route static data. The route is cacheable when `routeCache` is `true`. |
151
151
  | `matchId` | Match id used to render the cached route with TanStack Router's `Match`. |
152
- | `routerSnapshot` | Frozen router-like object used by the cached route tree. |
152
+ | `routerSnapshot` | Router-like object with isolated snapshot stores used by the cached route tree. |
153
153
  | `ready` | Marks that the route has a complete snapshot and can be rendered from cache. |
154
154
 
155
155
  Pathnames are normalized by removing trailing slashes except for `/`, so `/customers/` and `/customers` share one cache key.
156
156
 
157
157
  ## Router snapshot
158
158
 
159
- Cached route trees still expect TanStack Router context. Instead of keeping every cached tree connected to the live router stores, the package creates a router snapshot when a route becomes ready.
159
+ Cached route trees still expect TanStack Router context. Instead of connecting every cached tree directly to the live router stores, the package creates a router-like snapshot when a route becomes ready.
160
160
 
161
161
  ```mermaid
162
162
  flowchart TD
163
163
  liveRouter["Live router"]
164
164
  matches["Current matches"]
165
165
  location["Current location"]
166
- staticStores["Static stores"]
166
+ snapshotStores["Snapshot stores"]
167
167
  liveMethods["Bound live methods"]
168
168
  routerSnapshot["Router snapshot"]
169
169
  cachedOutlet["CachedOutlet"]
@@ -171,18 +171,18 @@ flowchart TD
171
171
 
172
172
  liveRouter --> matches
173
173
  liveRouter --> location
174
- matches --> staticStores
175
- location --> staticStores
174
+ matches --> snapshotStores
175
+ location --> snapshotStores
176
176
  liveRouter --> liveMethods
177
- staticStores --> routerSnapshot
177
+ snapshotStores --> routerSnapshot
178
178
  liveMethods --> routerSnapshot
179
179
  routerSnapshot --> cachedOutlet
180
180
  cachedOutlet --> match
181
181
  ```
182
182
 
183
- The snapshot copies the current matches, location, resolved location, and match stores. It also keeps selected live router methods bound to the real router, including `navigate`, `invalidate`, `preloadRoute`, and location builders.
183
+ The snapshot owns isolated stores for the current matches, location, resolved location, and match stores. It also keeps selected live router methods bound to the real router, including `navigate`, `invalidate`, `preloadRoute`, and location builders.
184
184
 
185
- This gives hidden cached routes a stable route view while still allowing imperative router actions to call through to the real router.
185
+ When the cached entry is refreshed, the cache manager updates those snapshot stores in place. This lets route hooks read current match and search data without remounting the retained route tree. Imperative router actions still call through to the real router.
186
186
 
187
187
  `CachedOutlet` renders that snapshot like this:
188
188
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tanstack-router-cache",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Route view caching for TanStack Router.",
5
5
  "type": "module",
6
6
  "license": "MIT",