@tanstack/react-router
Version:
Modern and scalable routing for React applications
1,221 lines (1,220 loc) • 66.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
const history = require("@tanstack/history");
const reactStore = require("@tanstack/react-store");
const invariant = require("tiny-invariant");
const routerCore = require("@tanstack/router-core");
const redirects = require("./redirects.cjs");
const notFound = require("./not-found.cjs");
const scrollRestoration = require("./scroll-restoration.cjs");
const componentTypes = [
"component",
"errorComponent",
"pendingComponent",
"notFoundComponent"
];
function routeNeedsPreload(route) {
var _a;
for (const componentType of componentTypes) {
if ((_a = route.options[componentType]) == null ? void 0 : _a.preload) {
return true;
}
}
return false;
}
function validateSearch(validateSearch2, input) {
if (validateSearch2 == null) return {};
if ("~standard" in validateSearch2) {
const result = validateSearch2["~standard"].validate(input);
if (result instanceof Promise)
throw new SearchParamError("Async validation not supported");
if (result.issues)
throw new SearchParamError(JSON.stringify(result.issues, void 0, 2), {
cause: result
});
return result.value;
}
if ("parse" in validateSearch2) {
return validateSearch2.parse(input);
}
if (typeof validateSearch2 === "function") {
return validateSearch2(input);
}
return {};
}
function createRouter(options) {
return new Router(options);
}
class Router {
/**
* @deprecated Use the `createRouter` function instead
*/
constructor(options) {
this.tempLocationKey = `${Math.round(
Math.random() * 1e7
)}`;
this.resetNextScroll = true;
this.shouldViewTransition = void 0;
this.isViewTransitionTypesSupported = void 0;
this.subscribers = /* @__PURE__ */ new Set();
this.isScrollRestoring = false;
this.isScrollRestorationSetup = false;
this.startTransition = (fn) => fn();
this.update = (newOptions) => {
var _a;
if (newOptions.notFoundRoute) {
console.warn(
"The notFoundRoute API is deprecated and will be removed in the next major version. See https://tanstack.com/router/v1/docs/framework/react/guide/not-found-errors#migrating-from-notfoundroute for more info."
);
}
const previousOptions = this.options;
this.options = {
...this.options,
...newOptions
};
this.isServer = this.options.isServer ?? typeof document === "undefined";
this.pathParamsDecodeCharMap = this.options.pathParamsAllowedCharacters ? new Map(
this.options.pathParamsAllowedCharacters.map((char) => [
encodeURIComponent(char),
char
])
) : void 0;
if (!this.basepath || newOptions.basepath && newOptions.basepath !== previousOptions.basepath) {
if (newOptions.basepath === void 0 || newOptions.basepath === "" || newOptions.basepath === "/") {
this.basepath = "/";
} else {
this.basepath = `/${routerCore.trimPath(newOptions.basepath)}`;
}
}
if (
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
!this.history || this.options.history && this.options.history !== this.history
) {
this.history = this.options.history ?? (this.isServer ? history.createMemoryHistory({
initialEntries: [this.basepath || "/"]
}) : history.createBrowserHistory());
this.latestLocation = this.parseLocation();
}
if (this.options.routeTree !== this.routeTree) {
this.routeTree = this.options.routeTree;
this.buildRouteTree();
}
if (!this.__store) {
this.__store = new reactStore.Store(getInitialRouterState(this.latestLocation), {
onUpdate: () => {
this.__store.state = {
...this.state,
cachedMatches: this.state.cachedMatches.filter(
(d) => !["redirected"].includes(d.status)
)
};
}
});
scrollRestoration.setupScrollRestoration(this);
}
if (typeof window !== "undefined" && "CSS" in window && // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
typeof ((_a = window.CSS) == null ? void 0 : _a.supports) === "function") {
this.isViewTransitionTypesSupported = window.CSS.supports(
"selector(:active-view-transition-type(a)"
);
}
};
this.buildRouteTree = () => {
this.routesById = {};
this.routesByPath = {};
const notFoundRoute = this.options.notFoundRoute;
if (notFoundRoute) {
notFoundRoute.init({
originalIndex: 99999999999,
defaultSsr: this.options.defaultSsr
});
this.routesById[notFoundRoute.id] = notFoundRoute;
}
const recurseRoutes = (childRoutes) => {
childRoutes.forEach((childRoute, i) => {
childRoute.init({
originalIndex: i,
defaultSsr: this.options.defaultSsr
});
const existingRoute = this.routesById[childRoute.id];
invariant(
!existingRoute,
`Duplicate routes found with id: ${String(childRoute.id)}`
);
this.routesById[childRoute.id] = childRoute;
if (!childRoute.isRoot && childRoute.path) {
const trimmedFullPath = routerCore.trimPathRight(childRoute.fullPath);
if (!this.routesByPath[trimmedFullPath] || childRoute.fullPath.endsWith("/")) {
this.routesByPath[trimmedFullPath] = childRoute;
}
}
const children = childRoute.children;
if (children == null ? void 0 : children.length) {
recurseRoutes(children);
}
});
};
recurseRoutes([this.routeTree]);
const scoredRoutes = [];
const routes = Object.values(this.routesById);
routes.forEach((d, i) => {
var _a;
if (d.isRoot || !d.path) {
return;
}
const trimmed = routerCore.trimPathLeft(d.fullPath);
const parsed = routerCore.parsePathname(trimmed);
while (parsed.length > 1 && ((_a = parsed[0]) == null ? void 0 : _a.value) === "/") {
parsed.shift();
}
const scores = parsed.map((segment) => {
if (segment.value === "/") {
return 0.75;
}
if (segment.type === "param") {
return 0.5;
}
if (segment.type === "wildcard") {
return 0.25;
}
return 1;
});
scoredRoutes.push({ child: d, trimmed, parsed, index: i, scores });
});
this.flatRoutes = scoredRoutes.sort((a, b) => {
const minLength = Math.min(a.scores.length, b.scores.length);
for (let i = 0; i < minLength; i++) {
if (a.scores[i] !== b.scores[i]) {
return b.scores[i] - a.scores[i];
}
}
if (a.scores.length !== b.scores.length) {
return b.scores.length - a.scores.length;
}
for (let i = 0; i < minLength; i++) {
if (a.parsed[i].value !== b.parsed[i].value) {
return a.parsed[i].value > b.parsed[i].value ? 1 : -1;
}
}
return a.index - b.index;
}).map((d, i) => {
d.child.rank = i;
return d.child;
});
};
this.subscribe = (eventType, fn) => {
const listener = {
eventType,
fn
};
this.subscribers.add(listener);
return () => {
this.subscribers.delete(listener);
};
};
this.emit = (routerEvent) => {
this.subscribers.forEach((listener) => {
if (listener.eventType === routerEvent.type) {
listener.fn(routerEvent);
}
});
};
this.parseLocation = (previousLocation, locationToParse) => {
const parse = ({
pathname,
search,
hash,
state
}) => {
const parsedSearch = this.options.parseSearch(search);
const searchStr = this.options.stringifySearch(parsedSearch);
return {
pathname,
searchStr,
search: routerCore.replaceEqualDeep(previousLocation == null ? void 0 : previousLocation.search, parsedSearch),
hash: hash.split("#").reverse()[0] ?? "",
href: `${pathname}${searchStr}${hash}`,
state: routerCore.replaceEqualDeep(previousLocation == null ? void 0 : previousLocation.state, state)
};
};
const location = parse(locationToParse ?? this.history.location);
const { __tempLocation, __tempKey } = location.state;
if (__tempLocation && (!__tempKey || __tempKey === this.tempLocationKey)) {
const parsedTempLocation = parse(__tempLocation);
parsedTempLocation.state.key = location.state.key;
delete parsedTempLocation.state.__tempLocation;
return {
...parsedTempLocation,
maskedLocation: location
};
}
return location;
};
this.resolvePathWithBase = (from, path) => {
const resolvedPath = routerCore.resolvePath({
basepath: this.basepath,
base: from,
to: routerCore.cleanPath(path),
trailingSlash: this.options.trailingSlash,
caseSensitive: this.options.caseSensitive
});
return resolvedPath;
};
this.matchRoutes = (pathnameOrNext, locationSearchOrOpts, opts) => {
if (typeof pathnameOrNext === "string") {
return this.matchRoutesInternal(
{
pathname: pathnameOrNext,
search: locationSearchOrOpts
},
opts
);
} else {
return this.matchRoutesInternal(pathnameOrNext, locationSearchOrOpts);
}
};
this.getMatchedRoutes = (next, dest) => {
let routeParams = {};
const trimmedPath = routerCore.trimPathRight(next.pathname);
const getMatchedParams = (route) => {
const result = routerCore.matchPathname(this.basepath, trimmedPath, {
to: route.fullPath,
caseSensitive: route.options.caseSensitive ?? this.options.caseSensitive,
fuzzy: true
});
return result;
};
let foundRoute = (dest == null ? void 0 : dest.to) !== void 0 ? this.routesByPath[dest.to] : void 0;
if (foundRoute) {
routeParams = getMatchedParams(foundRoute);
} else {
foundRoute = this.flatRoutes.find((route) => {
const matchedParams = getMatchedParams(route);
if (matchedParams) {
routeParams = matchedParams;
return true;
}
return false;
});
}
let routeCursor = foundRoute || this.routesById[routerCore.rootRouteId];
const matchedRoutes = [routeCursor];
while (routeCursor.parentRoute) {
routeCursor = routeCursor.parentRoute;
matchedRoutes.unshift(routeCursor);
}
return { matchedRoutes, routeParams, foundRoute };
};
this.cancelMatch = (id) => {
const match = this.getMatch(id);
if (!match) return;
match.abortController.abort();
clearTimeout(match.pendingTimeout);
};
this.cancelMatches = () => {
var _a;
(_a = this.state.pendingMatches) == null ? void 0 : _a.forEach((match) => {
this.cancelMatch(match.id);
});
};
this.buildLocation = (opts) => {
const build = (dest = {}, matchedRoutesResult) => {
var _a, _b, _c, _d, _e, _f, _g;
const fromMatches = dest._fromLocation ? this.matchRoutes(dest._fromLocation, { _buildLocation: true }) : this.state.matches;
const fromMatch = dest.from != null ? fromMatches.find(
(d) => routerCore.matchPathname(this.basepath, routerCore.trimPathRight(d.pathname), {
to: dest.from,
caseSensitive: false,
fuzzy: false
})
) : void 0;
const fromPath = (fromMatch == null ? void 0 : fromMatch.pathname) || this.latestLocation.pathname;
invariant(
dest.from == null || fromMatch != null,
"Could not find match for from: " + dest.from
);
const fromSearch = ((_a = this.state.pendingMatches) == null ? void 0 : _a.length) ? (_b = routerCore.last(this.state.pendingMatches)) == null ? void 0 : _b.search : ((_c = routerCore.last(fromMatches)) == null ? void 0 : _c.search) || this.latestLocation.search;
const stayingMatches = matchedRoutesResult == null ? void 0 : matchedRoutesResult.matchedRoutes.filter(
(d) => fromMatches.find((e) => e.routeId === d.id)
);
let pathname;
if (dest.to) {
const resolvePathTo = (fromMatch == null ? void 0 : fromMatch.fullPath) || ((_d = routerCore.last(fromMatches)) == null ? void 0 : _d.fullPath) || this.latestLocation.pathname;
pathname = this.resolvePathWithBase(resolvePathTo, `${dest.to}`);
} else {
const fromRouteByFromPathRouteId = this.routesById[(_e = stayingMatches == null ? void 0 : stayingMatches.find((route) => {
const interpolatedPath = routerCore.interpolatePath({
path: route.fullPath,
params: (matchedRoutesResult == null ? void 0 : matchedRoutesResult.routeParams) ?? {},
decodeCharMap: this.pathParamsDecodeCharMap
}).interpolatedPath;
const pathname2 = routerCore.joinPaths([this.basepath, interpolatedPath]);
return pathname2 === fromPath;
})) == null ? void 0 : _e.id];
pathname = this.resolvePathWithBase(
fromPath,
(fromRouteByFromPathRouteId == null ? void 0 : fromRouteByFromPathRouteId.to) ?? fromPath
);
}
const prevParams = { ...(_f = routerCore.last(fromMatches)) == null ? void 0 : _f.params };
let nextParams = (dest.params ?? true) === true ? prevParams : {
...prevParams,
...routerCore.functionalUpdate(dest.params, prevParams)
};
if (Object.keys(nextParams).length > 0) {
matchedRoutesResult == null ? void 0 : matchedRoutesResult.matchedRoutes.map((route) => {
var _a2;
return ((_a2 = route.options.params) == null ? void 0 : _a2.stringify) ?? route.options.stringifyParams;
}).filter(Boolean).forEach((fn) => {
nextParams = { ...nextParams, ...fn(nextParams) };
});
}
pathname = routerCore.interpolatePath({
path: pathname,
params: nextParams ?? {},
leaveWildcards: false,
leaveParams: opts.leaveParams,
decodeCharMap: this.pathParamsDecodeCharMap
}).interpolatedPath;
let search = fromSearch;
if (opts._includeValidateSearch && ((_g = this.options.search) == null ? void 0 : _g.strict)) {
let validatedSearch = {};
matchedRoutesResult == null ? void 0 : matchedRoutesResult.matchedRoutes.forEach((route) => {
try {
if (route.options.validateSearch) {
validatedSearch = {
...validatedSearch,
...validateSearch(route.options.validateSearch, {
...validatedSearch,
...search
}) ?? {}
};
}
} catch {
}
});
search = validatedSearch;
}
const applyMiddlewares = (search2) => {
const allMiddlewares = (matchedRoutesResult == null ? void 0 : matchedRoutesResult.matchedRoutes.reduce(
(acc, route) => {
var _a2;
const middlewares = [];
if ("search" in route.options) {
if ((_a2 = route.options.search) == null ? void 0 : _a2.middlewares) {
middlewares.push(...route.options.search.middlewares);
}
} else if (route.options.preSearchFilters || route.options.postSearchFilters) {
const legacyMiddleware = ({
search: search3,
next
}) => {
let nextSearch = search3;
if ("preSearchFilters" in route.options && route.options.preSearchFilters) {
nextSearch = route.options.preSearchFilters.reduce(
(prev, next2) => next2(prev),
search3
);
}
const result = next(nextSearch);
if ("postSearchFilters" in route.options && route.options.postSearchFilters) {
return route.options.postSearchFilters.reduce(
(prev, next2) => next2(prev),
result
);
}
return result;
};
middlewares.push(legacyMiddleware);
}
if (opts._includeValidateSearch && route.options.validateSearch) {
const validate = ({ search: search3, next }) => {
const result = next(search3);
try {
const validatedSearch = {
...result,
...validateSearch(
route.options.validateSearch,
result
) ?? {}
};
return validatedSearch;
} catch {
return result;
}
};
middlewares.push(validate);
}
return acc.concat(middlewares);
},
[]
)) ?? [];
const final = ({ search: search3 }) => {
if (!dest.search) {
return {};
}
if (dest.search === true) {
return search3;
}
return routerCore.functionalUpdate(dest.search, search3);
};
allMiddlewares.push(final);
const applyNext = (index, currentSearch) => {
if (index >= allMiddlewares.length) {
return currentSearch;
}
const middleware = allMiddlewares[index];
const next = (newSearch) => {
return applyNext(index + 1, newSearch);
};
return middleware({ search: currentSearch, next });
};
return applyNext(0, search2);
};
search = applyMiddlewares(search);
search = routerCore.replaceEqualDeep(fromSearch, search);
const searchStr = this.options.stringifySearch(search);
const hash = dest.hash === true ? this.latestLocation.hash : dest.hash ? routerCore.functionalUpdate(dest.hash, this.latestLocation.hash) : void 0;
const hashStr = hash ? `#${hash}` : "";
let nextState = dest.state === true ? this.latestLocation.state : dest.state ? routerCore.functionalUpdate(dest.state, this.latestLocation.state) : {};
nextState = routerCore.replaceEqualDeep(this.latestLocation.state, nextState);
return {
pathname,
search,
searchStr,
state: nextState,
hash: hash ?? "",
href: `${pathname}${searchStr}${hashStr}`,
unmaskOnReload: dest.unmaskOnReload
};
};
const buildWithMatches = (dest = {}, maskedDest) => {
var _a;
const next = build(dest);
let maskedNext = maskedDest ? build(maskedDest) : void 0;
if (!maskedNext) {
let params = {};
const foundMask = (_a = this.options.routeMasks) == null ? void 0 : _a.find((d) => {
const match = routerCore.matchPathname(this.basepath, next.pathname, {
to: d.from,
caseSensitive: false,
fuzzy: false
});
if (match) {
params = match;
return true;
}
return false;
});
if (foundMask) {
const { from: _from, ...maskProps } = foundMask;
maskedDest = {
...routerCore.pick(opts, ["from"]),
...maskProps,
params
};
maskedNext = build(maskedDest);
}
}
const nextMatches = this.getMatchedRoutes(next, dest);
const final = build(dest, nextMatches);
if (maskedNext) {
const maskedMatches = this.getMatchedRoutes(maskedNext, maskedDest);
const maskedFinal = build(maskedDest, maskedMatches);
final.maskedLocation = maskedFinal;
}
return final;
};
if (opts.mask) {
return buildWithMatches(opts, {
...routerCore.pick(opts, ["from"]),
...opts.mask
});
}
return buildWithMatches(opts);
};
this.commitLocation = ({
viewTransition,
ignoreBlocker,
...next
}) => {
const isSameState = () => {
const ignoredProps = [
"key",
"__TSR_index",
"__hashScrollIntoViewOptions"
];
ignoredProps.forEach((prop) => {
next.state[prop] = this.latestLocation.state[prop];
});
const isEqual = routerCore.deepEqual(next.state, this.latestLocation.state);
ignoredProps.forEach((prop) => {
delete next.state[prop];
});
return isEqual;
};
const isSameUrl = this.latestLocation.href === next.href;
const previousCommitPromise = this.commitLocationPromise;
this.commitLocationPromise = routerCore.createControlledPromise(() => {
previousCommitPromise == null ? void 0 : previousCommitPromise.resolve();
});
if (isSameUrl && isSameState()) {
this.load();
} else {
let { maskedLocation, hashScrollIntoView, ...nextHistory } = next;
if (maskedLocation) {
nextHistory = {
...maskedLocation,
state: {
...maskedLocation.state,
__tempKey: void 0,
__tempLocation: {
...nextHistory,
search: nextHistory.searchStr,
state: {
...nextHistory.state,
__tempKey: void 0,
__tempLocation: void 0,
key: void 0
}
}
}
};
if (nextHistory.unmaskOnReload ?? this.options.unmaskOnReload ?? false) {
nextHistory.state.__tempKey = this.tempLocationKey;
}
}
nextHistory.state.__hashScrollIntoViewOptions = hashScrollIntoView ?? this.options.defaultHashScrollIntoView ?? true;
this.shouldViewTransition = viewTransition;
this.history[next.replace ? "replace" : "push"](
nextHistory.href,
nextHistory.state,
{ ignoreBlocker }
);
}
this.resetNextScroll = next.resetScroll ?? true;
if (!this.history.subscribers.size) {
this.load();
}
return this.commitLocationPromise;
};
this.buildAndCommitLocation = ({
replace,
resetScroll,
hashScrollIntoView,
viewTransition,
ignoreBlocker,
href,
...rest
} = {}) => {
if (href) {
const currentIndex = this.history.location.state.__TSR_index;
const parsed = history.parseHref(href, {
__TSR_index: replace ? currentIndex : currentIndex + 1
});
rest.to = parsed.pathname;
rest.search = this.options.parseSearch(parsed.search);
rest.hash = parsed.hash.slice(1);
}
const location = this.buildLocation({
...rest,
_includeValidateSearch: true
});
return this.commitLocation({
...location,
viewTransition,
replace,
resetScroll,
hashScrollIntoView,
ignoreBlocker
});
};
this.navigate = ({ to, reloadDocument, href, ...rest }) => {
if (reloadDocument) {
if (!href) {
const location = this.buildLocation({ to, ...rest });
href = this.history.createHref(location.href);
}
if (rest.replace) {
window.location.replace(href);
} else {
window.location.href = href;
}
return;
}
return this.buildAndCommitLocation({
...rest,
href,
to
});
};
this.load = async (opts) => {
this.latestLocation = this.parseLocation(this.latestLocation);
let redirect;
let notFound$1;
let loadPromise;
loadPromise = new Promise((resolve) => {
this.startTransition(async () => {
var _a;
try {
const next = this.latestLocation;
const prevLocation = this.state.resolvedLocation;
this.cancelMatches();
let pendingMatches;
reactStore.batch(() => {
pendingMatches = this.matchRoutes(next);
this.__store.setState((s) => ({
...s,
status: "pending",
isLoading: true,
location: next,
pendingMatches,
// If a cached moved to pendingMatches, remove it from cachedMatches
cachedMatches: s.cachedMatches.filter((d) => {
return !pendingMatches.find((e) => e.id === d.id);
})
}));
});
if (!this.state.redirect) {
this.emit({
type: "onBeforeNavigate",
...routerCore.getLocationChangeInfo({
resolvedLocation: prevLocation,
location: next
})
});
}
this.emit({
type: "onBeforeLoad",
...routerCore.getLocationChangeInfo({
resolvedLocation: prevLocation,
location: next
})
});
await this.loadMatches({
sync: opts == null ? void 0 : opts.sync,
matches: pendingMatches,
location: next,
// eslint-disable-next-line @typescript-eslint/require-await
onReady: async () => {
this.startViewTransition(async () => {
let exitingMatches;
let enteringMatches;
let stayingMatches;
reactStore.batch(() => {
this.__store.setState((s) => {
const previousMatches = s.matches;
const newMatches = s.pendingMatches || s.matches;
exitingMatches = previousMatches.filter(
(match) => !newMatches.find((d) => d.id === match.id)
);
enteringMatches = newMatches.filter(
(match) => !previousMatches.find((d) => d.id === match.id)
);
stayingMatches = previousMatches.filter(
(match) => newMatches.find((d) => d.id === match.id)
);
return {
...s,
isLoading: false,
loadedAt: Date.now(),
matches: newMatches,
pendingMatches: void 0,
cachedMatches: [
...s.cachedMatches,
...exitingMatches.filter((d) => d.status !== "error")
]
};
});
this.clearExpiredCache();
});
[
[exitingMatches, "onLeave"],
[enteringMatches, "onEnter"],
[stayingMatches, "onStay"]
].forEach(([matches, hook]) => {
matches.forEach((match) => {
var _a2, _b;
(_b = (_a2 = this.looseRoutesById[match.routeId].options)[hook]) == null ? void 0 : _b.call(_a2, match);
});
});
});
}
});
} catch (err) {
if (redirects.isResolvedRedirect(err)) {
redirect = err;
if (!this.isServer) {
this.navigate({
...redirect,
replace: true,
ignoreBlocker: true
});
}
} else if (notFound.isNotFound(err)) {
notFound$1 = err;
}
this.__store.setState((s) => ({
...s,
statusCode: redirect ? redirect.statusCode : notFound$1 ? 404 : s.matches.some((d) => d.status === "error") ? 500 : 200,
redirect
}));
}
if (this.latestLoadPromise === loadPromise) {
(_a = this.commitLocationPromise) == null ? void 0 : _a.resolve();
this.latestLoadPromise = void 0;
this.commitLocationPromise = void 0;
}
resolve();
});
});
this.latestLoadPromise = loadPromise;
await loadPromise;
while (this.latestLoadPromise && loadPromise !== this.latestLoadPromise) {
await this.latestLoadPromise;
}
if (this.hasNotFoundMatch()) {
this.__store.setState((s) => ({
...s,
statusCode: 404
}));
}
};
this.startViewTransition = (fn) => {
const shouldViewTransition = this.shouldViewTransition ?? this.options.defaultViewTransition;
delete this.shouldViewTransition;
if (shouldViewTransition && typeof document !== "undefined" && "startViewTransition" in document && typeof document.startViewTransition === "function") {
let startViewTransitionParams;
if (typeof shouldViewTransition === "object" && this.isViewTransitionTypesSupported) {
startViewTransitionParams = {
update: fn,
types: shouldViewTransition.types
};
} else {
startViewTransitionParams = fn;
}
document.startViewTransition(startViewTransitionParams);
} else {
fn();
}
};
this.updateMatch = (id, updater) => {
var _a;
let updated;
const isPending = (_a = this.state.pendingMatches) == null ? void 0 : _a.find((d) => d.id === id);
const isMatched = this.state.matches.find((d) => d.id === id);
const isCached = this.state.cachedMatches.find((d) => d.id === id);
const matchesKey = isPending ? "pendingMatches" : isMatched ? "matches" : isCached ? "cachedMatches" : "";
if (matchesKey) {
this.__store.setState((s) => {
var _a2;
return {
...s,
[matchesKey]: (_a2 = s[matchesKey]) == null ? void 0 : _a2.map(
(d) => d.id === id ? updated = updater(d) : d
)
};
});
}
return updated;
};
this.getMatch = (matchId) => {
return [
...this.state.cachedMatches,
...this.state.pendingMatches ?? [],
...this.state.matches
].find((d) => d.id === matchId);
};
this.loadMatches = async ({
location,
matches,
preload: allPreload,
onReady,
updateMatch = this.updateMatch,
sync
}) => {
let firstBadMatchIndex;
let rendered = false;
const triggerOnReady = async () => {
if (!rendered) {
rendered = true;
await (onReady == null ? void 0 : onReady());
}
};
const resolvePreload = (matchId) => {
return !!(allPreload && !this.state.matches.find((d) => d.id === matchId));
};
if (!this.isServer && !this.state.matches.length) {
triggerOnReady();
}
const handleRedirectAndNotFound = (match, err) => {
var _a, _b, _c, _d;
if (redirects.isResolvedRedirect(err)) {
if (!err.reloadDocument) {
throw err;
}
}
if (redirects.isRedirect(err) || notFound.isNotFound(err)) {
updateMatch(match.id, (prev) => ({
...prev,
status: redirects.isRedirect(err) ? "redirected" : notFound.isNotFound(err) ? "notFound" : "error",
isFetching: false,
error: err,
beforeLoadPromise: void 0,
loaderPromise: void 0
}));
if (!err.routeId) {
err.routeId = match.routeId;
}
(_a = match.beforeLoadPromise) == null ? void 0 : _a.resolve();
(_b = match.loaderPromise) == null ? void 0 : _b.resolve();
(_c = match.loadPromise) == null ? void 0 : _c.resolve();
if (redirects.isRedirect(err)) {
rendered = true;
err = this.resolveRedirect({ ...err, _fromLocation: location });
throw err;
} else if (notFound.isNotFound(err)) {
this._handleNotFound(matches, err, {
updateMatch
});
(_d = this.serverSsr) == null ? void 0 : _d.onMatchSettled({
router: this,
match: this.getMatch(match.id)
});
throw err;
}
}
};
try {
await new Promise((resolveAll, rejectAll) => {
;
(async () => {
var _a, _b, _c;
try {
const handleSerialError = (index, err, routerCode) => {
var _a2, _b2;
const { id: matchId, routeId } = matches[index];
const route = this.looseRoutesById[routeId];
if (err instanceof Promise) {
throw err;
}
err.routerCode = routerCode;
firstBadMatchIndex = firstBadMatchIndex ?? index;
handleRedirectAndNotFound(this.getMatch(matchId), err);
try {
(_b2 = (_a2 = route.options).onError) == null ? void 0 : _b2.call(_a2, err);
} catch (errorHandlerErr) {
err = errorHandlerErr;
handleRedirectAndNotFound(this.getMatch(matchId), err);
}
updateMatch(matchId, (prev) => {
var _a3, _b3;
(_a3 = prev.beforeLoadPromise) == null ? void 0 : _a3.resolve();
(_b3 = prev.loadPromise) == null ? void 0 : _b3.resolve();
return {
...prev,
error: err,
status: "error",
isFetching: false,
updatedAt: Date.now(),
abortController: new AbortController(),
beforeLoadPromise: void 0
};
});
};
for (const [index, { id: matchId, routeId }] of matches.entries()) {
const existingMatch = this.getMatch(matchId);
const parentMatchId = (_a = matches[index - 1]) == null ? void 0 : _a.id;
const route = this.looseRoutesById[routeId];
const pendingMs = route.options.pendingMs ?? this.options.defaultPendingMs;
const shouldPending = !!(onReady && !this.isServer && !resolvePreload(matchId) && (route.options.loader || route.options.beforeLoad) && typeof pendingMs === "number" && pendingMs !== Infinity && (route.options.pendingComponent ?? this.options.defaultPendingComponent));
let executeBeforeLoad = true;
if (
// If we are in the middle of a load, either of these will be present
// (not to be confused with `loadPromise`, which is always defined)
existingMatch.beforeLoadPromise || existingMatch.loaderPromise
) {
if (shouldPending) {
setTimeout(() => {
try {
triggerOnReady();
} catch {
}
}, pendingMs);
}
await existingMatch.beforeLoadPromise;
executeBeforeLoad = this.getMatch(matchId).status !== "success";
}
if (executeBeforeLoad) {
try {
updateMatch(matchId, (prev) => {
const prevLoadPromise = prev.loadPromise;
return {
...prev,
loadPromise: routerCore.createControlledPromise(() => {
prevLoadPromise == null ? void 0 : prevLoadPromise.resolve();
}),
beforeLoadPromise: routerCore.createControlledPromise()
};
});
const abortController = new AbortController();
let pendingTimeout;
if (shouldPending) {
pendingTimeout = setTimeout(() => {
try {
triggerOnReady();
} catch {
}
}, pendingMs);
}
const { paramsError, searchError } = this.getMatch(matchId);
if (paramsError) {
handleSerialError(index, paramsError, "PARSE_PARAMS");
}
if (searchError) {
handleSerialError(index, searchError, "VALIDATE_SEARCH");
}
const getParentMatchContext = () => parentMatchId ? this.getMatch(parentMatchId).context : this.options.context ?? {};
updateMatch(matchId, (prev) => ({
...prev,
isFetching: "beforeLoad",
fetchCount: prev.fetchCount + 1,
abortController,
pendingTimeout,
context: {
...getParentMatchContext(),
...prev.__routeContext
}
}));
const { search, params, context, cause } = this.getMatch(matchId);
const preload = resolvePreload(matchId);
const beforeLoadFnContext = {
search,
abortController,
params,
preload,
context,
location,
navigate: (opts) => this.navigate({ ...opts, _fromLocation: location }),
buildLocation: this.buildLocation,
cause: preload ? "preload" : cause,
matches
};
const beforeLoadContext = await ((_c = (_b = route.options).beforeLoad) == null ? void 0 : _c.call(_b, beforeLoadFnContext)) ?? {};
if (redirects.isRedirect(beforeLoadContext) || notFound.isNotFound(beforeLoadContext)) {
handleSerialError(index, beforeLoadContext, "BEFORE_LOAD");
}
updateMatch(matchId, (prev) => {
return {
...prev,
__beforeLoadContext: beforeLoadContext,
context: {
...getParentMatchContext(),
...prev.__routeContext,
...beforeLoadContext
},
abortController
};
});
} catch (err) {
handleSerialError(index, err, "BEFORE_LOAD");
}
updateMatch(matchId, (prev) => {
var _a2;
(_a2 = prev.beforeLoadPromise) == null ? void 0 : _a2.resolve();
return {
...prev,
beforeLoadPromise: void 0,
isFetching: false
};
});
}
}
const validResolvedMatches = matches.slice(0, firstBadMatchIndex);
const matchPromises = [];
validResolvedMatches.forEach(({ id: matchId, routeId }, index) => {
matchPromises.push(
(async () => {
const { loaderPromise: prevLoaderPromise } = this.getMatch(matchId);
let loaderShouldRunAsync = false;
let loaderIsRunningAsync = false;
if (prevLoaderPromise) {
await prevLoaderPromise;
const match = this.getMatch(matchId);
if (match.error) {
handleRedirectAndNotFound(match, match.error);
}
} else {
const parentMatchPromise = matchPromises[index - 1];
const route = this.looseRoutesById[routeId];
const getLoaderContext = () => {
const {
params,
loaderDeps,
abortController,
context,
cause
} = this.getMatch(matchId);
const preload2 = resolvePreload(matchId);
return {
params,
deps: loaderDeps,
preload: !!preload2,
parentMatchPromise,
abortController,
context,
location,
navigate: (opts) => this.navigate({ ...opts, _fromLocation: location }),
cause: preload2 ? "preload" : cause,
route
};
};
const age = Date.now() - this.getMatch(matchId).updatedAt;
const preload = resolvePreload(matchId);
const staleAge = preload ? route.options.preloadStaleTime ?? this.options.defaultPreloadStaleTime ?? 3e4 : route.options.staleTime ?? this.options.defaultStaleTime ?? 0;
const shouldReloadOption = route.options.shouldReload;
const shouldReload = typeof shouldReloadOption === "function" ? shouldReloadOption(getLoaderContext()) : shouldReloadOption;
updateMatch(matchId, (prev) => ({
...prev,
loaderPromise: routerCore.createControlledPromise(),
preload: !!preload && !this.state.matches.find((d) => d.id === matchId)
}));
const runLoader = async () => {
var _a2, _b2, _c2, _d, _e, _f, _g, _h, _i, _j, _k;
try {
const potentialPendingMinPromise = async () => {
const latestMatch = this.getMatch(matchId);
if (latestMatch.minPendingPromise) {
await latestMatch.minPendingPromise;
}
};
try {
this.loadRouteChunk(route);
updateMatch(matchId, (prev) => ({
...prev,
isFetching: "loader"
}));
const loaderData = await ((_b2 = (_a2 = route.options).loader) == null ? void 0 : _b2.call(_a2, getLoaderContext()));
handleRedirectAndNotFound(
this.getMatch(matchId),
loaderData
);
await route._lazyPromise;
await potentialPendingMinPromise();
const assetContext = {
matches,
match: this.getMatch(matchId),
params: this.getMatch(matchId).params,
loaderData
};
const headFnContent = (_d = (_c2 = route.options).head) == null ? void 0 : _d.call(_c2, assetContext);
const meta = headFnContent == null ? void 0 : headFnContent.meta;
const links = headFnContent == null ? void 0 : headFnContent.links;
const headScripts = headFnContent == null ? void 0 : headFnContent.scripts;
const scripts = (_f = (_e = route.options).scripts) == null ? void 0 : _f.call(_e, assetContext);
const headers = (_h = (_g = route.options).headers) == null ? void 0 : _h.call(_g, {
loaderData
});
updateMatch(matchId, (prev) => ({
...prev,
error: void 0,
status: "success",
isFetching: false,
updatedAt: Date.now(),
loaderData,
meta,
links,
headScripts,
headers,
scripts
}));
} catch (e) {
let error = e;
await potentialPendingMinPromise();
handleRedirectAndNotFound(this.getMatch(matchId), e);
try {
(_j = (_i = route.options).onError) == null ? void 0 : _j.call(_i, e);
} catch (onErrorError) {
error = onErrorError;
handleRedirectAndNotFound(
this.getMatch(matchId),
onErrorError
);
}
updateMatch(matchId, (prev) => ({
...prev,
error,
status: "error",
isFetching: false
}));
}
(_k = this.serverSsr) == null ? void 0 : _k.onMatchSettled({
router: this,
match: this.getMatch(matchId)
});
await route._componentsPromise;
} catch (err) {
updateMatch(matchId, (prev) => ({
...prev,
loaderPromise: void 0
}));
handleRedirectAndNotFound(this.getMatch(matchId), err);
}
};
const { status, invalid } = this.getMatch(matchId);
loaderShouldRunAsync = status === "success" && (invalid || (shouldReload ?? age > staleAge));
if (preload && route.options.preload === false) {
} else if (loaderShouldRunAsync && !sync) {
loaderIsRunningAsync = true;
(async () => {
try {
await runLoader();
const { loaderPromise, loadPromise } = this.getMatch(matchId);
loaderPromise == null ? void 0 : loaderPromise.resolve();
loadPromise == null ? void 0 : loadPromise.resolve();
updateMatch(matchId, (prev) => ({
...prev,
loaderPromise: void 0
}));
} catch (err) {
if (redirects.isResolvedRedirect(err)) {
await this.navigate(err);
}
}
})();
} else if (status !== "success" || loaderShouldRunAsync && sync) {
await runLoader();
}
}
if (!loaderIsRunningAsync) {
const { loaderPromise, loadPromise } = this.getMatch(matchId);
loaderPromise == null ? void 0 : loaderPromise.resolve();
loadPromise == null ? void 0 : loadPromise.resolve();
}
updateMatch(matchId, (prev) => ({
...prev,
isFetching: loaderIsRunningAsync ? prev.isFetching : false,
loaderPromise: loaderIsRunningAsync ? prev.loaderPromise : void 0,
invalid: false
}));
return this.getMatch(matchId);
})()
);
});
await Promise.all(matchPromises);
resolveAll();
} catch (err) {
rejectAll(err);
}
})();
});
await triggerOnReady();
} catch (err) {
if (redirects.isRedirect(err) || notFound.isNotFound(err)) {
if (notFound.isNotFound(err) && !allPreload) {
await triggerOnReady();
}
throw err;
}
}
return matches;