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 +129 -52
- package/dist/index.js +129 -52
- package/docs/architecture.md +10 -10
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -787,12 +787,50 @@ const LIVE_ROUTER_METHODS = [
|
|
|
787
787
|
"navigate",
|
|
788
788
|
"preloadRoute"
|
|
789
789
|
];
|
|
790
|
-
|
|
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: () =>
|
|
793
|
-
|
|
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
|
|
839
|
-
return
|
|
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
|
-
|
|
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
|
|
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(
|
|
883
|
-
const
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
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:
|
|
902
|
-
loadedAt:
|
|
903
|
-
isLoading:
|
|
904
|
-
isTransitioning:
|
|
905
|
-
location:
|
|
906
|
-
resolvedLocation:
|
|
907
|
-
statusCode:
|
|
908
|
-
redirect:
|
|
909
|
-
matchesId:
|
|
910
|
-
pendingIds:
|
|
911
|
-
cachedIds:
|
|
912
|
-
matches:
|
|
913
|
-
pendingMatches:
|
|
914
|
-
cachedMatches:
|
|
915
|
-
firstId:
|
|
916
|
-
hasPending:
|
|
917
|
-
matchRouteDeps:
|
|
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:
|
|
958
|
+
status: snapshotData.state.status
|
|
921
959
|
}),
|
|
922
|
-
__store:
|
|
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 =
|
|
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: {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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: () =>
|
|
792
|
-
|
|
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
|
|
838
|
-
return
|
|
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
|
-
|
|
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
|
|
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(
|
|
882
|
-
const
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
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:
|
|
901
|
-
loadedAt:
|
|
902
|
-
isLoading:
|
|
903
|
-
isTransitioning:
|
|
904
|
-
location:
|
|
905
|
-
resolvedLocation:
|
|
906
|
-
statusCode:
|
|
907
|
-
redirect:
|
|
908
|
-
matchesId:
|
|
909
|
-
pendingIds:
|
|
910
|
-
cachedIds:
|
|
911
|
-
matches:
|
|
912
|
-
pendingMatches:
|
|
913
|
-
cachedMatches:
|
|
914
|
-
firstId:
|
|
915
|
-
hasPending:
|
|
916
|
-
matchRouteDeps:
|
|
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:
|
|
957
|
+
status: snapshotData.state.status
|
|
920
958
|
}),
|
|
921
|
-
__store:
|
|
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 =
|
|
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: {
|
|
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
|
-
|
|
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
|
-
|
|
1189
|
+
previousRouteCacheModesRef.current ??= /* @__PURE__ */ new Map();
|
|
1113
1190
|
previousRouteCacheModesRef.current = syncCachedRouteActivityEvents({
|
|
1114
1191
|
cachedRoutes,
|
|
1115
1192
|
eventListener,
|
package/docs/architecture.md
CHANGED
|
@@ -131,8 +131,8 @@ classDiagram
|
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
class RouterSnapshot {
|
|
134
|
-
|
|
135
|
-
|
|
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` |
|
|
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
|
|
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
|
-
|
|
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 -->
|
|
175
|
-
location -->
|
|
174
|
+
matches --> snapshotStores
|
|
175
|
+
location --> snapshotStores
|
|
176
176
|
liveRouter --> liveMethods
|
|
177
|
-
|
|
177
|
+
snapshotStores --> routerSnapshot
|
|
178
178
|
liveMethods --> routerSnapshot
|
|
179
179
|
routerSnapshot --> cachedOutlet
|
|
180
180
|
cachedOutlet --> match
|
|
181
181
|
```
|
|
182
182
|
|
|
183
|
-
The snapshot
|
|
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
|
-
|
|
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
|
|