 85bf1341f3
			
		
	
	85bf1341f3
	
	
	
		
			
			Frontend Enhancements: - Complete React TypeScript frontend with modern UI components - Distributed workflows management interface with real-time updates - Socket.IO integration for live agent status monitoring - Agent management dashboard with cluster visualization - Project management interface with metrics and task tracking - Responsive design with proper error handling and loading states Backend Infrastructure: - Distributed coordinator for multi-agent workflow orchestration - Cluster management API with comprehensive agent operations - Enhanced database models for agents and projects - Project service for filesystem-based project discovery - Performance monitoring and metrics collection - Comprehensive API documentation and error handling Documentation: - Complete distributed development guide (README_DISTRIBUTED.md) - Comprehensive development report with architecture insights - System configuration templates and deployment guides The platform now provides a complete web interface for managing the distributed AI cluster with real-time monitoring, workflow orchestration, and agent coordination capabilities. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
		
			
				
	
	
		
			5608 lines
		
	
	
		
			202 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			5608 lines
		
	
	
		
			202 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /**
 | |
|  * @remix-run/router v1.23.0
 | |
|  *
 | |
|  * Copyright (c) Remix Software Inc.
 | |
|  *
 | |
|  * This source code is licensed under the MIT license found in the
 | |
|  * LICENSE.md file in the root directory of this source tree.
 | |
|  *
 | |
|  * @license MIT
 | |
|  */
 | |
| 'use strict';
 | |
| 
 | |
| Object.defineProperty(exports, '__esModule', { value: true });
 | |
| 
 | |
| function _extends() {
 | |
|   _extends = Object.assign ? Object.assign.bind() : function (target) {
 | |
|     for (var i = 1; i < arguments.length; i++) {
 | |
|       var source = arguments[i];
 | |
|       for (var key in source) {
 | |
|         if (Object.prototype.hasOwnProperty.call(source, key)) {
 | |
|           target[key] = source[key];
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     return target;
 | |
|   };
 | |
|   return _extends.apply(this, arguments);
 | |
| }
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| //#region Types and Constants
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /**
 | |
|  * Actions represent the type of change to a location value.
 | |
|  */
 | |
| let Action = /*#__PURE__*/function (Action) {
 | |
|   Action["Pop"] = "POP";
 | |
|   Action["Push"] = "PUSH";
 | |
|   Action["Replace"] = "REPLACE";
 | |
|   return Action;
 | |
| }({});
 | |
| 
 | |
| /**
 | |
|  * The pathname, search, and hash values of a URL.
 | |
|  */
 | |
| 
 | |
| // TODO: (v7) Change the Location generic default from `any` to `unknown` and
 | |
| // remove Remix `useLocation` wrapper.
 | |
| /**
 | |
|  * An entry in a history stack. A location contains information about the
 | |
|  * URL path, as well as possibly some arbitrary state and a key.
 | |
|  */
 | |
| /**
 | |
|  * A change to the current location.
 | |
|  */
 | |
| /**
 | |
|  * A function that receives notifications about location changes.
 | |
|  */
 | |
| /**
 | |
|  * Describes a location that is the destination of some navigation, either via
 | |
|  * `history.push` or `history.replace`. This may be either a URL or the pieces
 | |
|  * of a URL path.
 | |
|  */
 | |
| /**
 | |
|  * A history is an interface to the navigation stack. The history serves as the
 | |
|  * source of truth for the current location, as well as provides a set of
 | |
|  * methods that may be used to change it.
 | |
|  *
 | |
|  * It is similar to the DOM's `window.history` object, but with a smaller, more
 | |
|  * focused API.
 | |
|  */
 | |
| const PopStateEventType = "popstate";
 | |
| //#endregion
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| //#region Memory History
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /**
 | |
|  * A user-supplied object that describes a location. Used when providing
 | |
|  * entries to `createMemoryHistory` via its `initialEntries` option.
 | |
|  */
 | |
| /**
 | |
|  * A memory history stores locations in memory. This is useful in stateful
 | |
|  * environments where there is no web browser, such as node tests or React
 | |
|  * Native.
 | |
|  */
 | |
| /**
 | |
|  * Memory history stores the current location in memory. It is designed for use
 | |
|  * in stateful non-browser environments like tests and React Native.
 | |
|  */
 | |
| function createMemoryHistory(options) {
 | |
|   if (options === void 0) {
 | |
|     options = {};
 | |
|   }
 | |
|   let {
 | |
|     initialEntries = ["/"],
 | |
|     initialIndex,
 | |
|     v5Compat = false
 | |
|   } = options;
 | |
|   let entries; // Declare so we can access from createMemoryLocation
 | |
|   entries = initialEntries.map((entry, index) => createMemoryLocation(entry, typeof entry === "string" ? null : entry.state, index === 0 ? "default" : undefined));
 | |
|   let index = clampIndex(initialIndex == null ? entries.length - 1 : initialIndex);
 | |
|   let action = Action.Pop;
 | |
|   let listener = null;
 | |
|   function clampIndex(n) {
 | |
|     return Math.min(Math.max(n, 0), entries.length - 1);
 | |
|   }
 | |
|   function getCurrentLocation() {
 | |
|     return entries[index];
 | |
|   }
 | |
|   function createMemoryLocation(to, state, key) {
 | |
|     if (state === void 0) {
 | |
|       state = null;
 | |
|     }
 | |
|     let location = createLocation(entries ? getCurrentLocation().pathname : "/", to, state, key);
 | |
|     warning(location.pathname.charAt(0) === "/", "relative pathnames are not supported in memory history: " + JSON.stringify(to));
 | |
|     return location;
 | |
|   }
 | |
|   function createHref(to) {
 | |
|     return typeof to === "string" ? to : createPath(to);
 | |
|   }
 | |
|   let history = {
 | |
|     get index() {
 | |
|       return index;
 | |
|     },
 | |
|     get action() {
 | |
|       return action;
 | |
|     },
 | |
|     get location() {
 | |
|       return getCurrentLocation();
 | |
|     },
 | |
|     createHref,
 | |
|     createURL(to) {
 | |
|       return new URL(createHref(to), "http://localhost");
 | |
|     },
 | |
|     encodeLocation(to) {
 | |
|       let path = typeof to === "string" ? parsePath(to) : to;
 | |
|       return {
 | |
|         pathname: path.pathname || "",
 | |
|         search: path.search || "",
 | |
|         hash: path.hash || ""
 | |
|       };
 | |
|     },
 | |
|     push(to, state) {
 | |
|       action = Action.Push;
 | |
|       let nextLocation = createMemoryLocation(to, state);
 | |
|       index += 1;
 | |
|       entries.splice(index, entries.length, nextLocation);
 | |
|       if (v5Compat && listener) {
 | |
|         listener({
 | |
|           action,
 | |
|           location: nextLocation,
 | |
|           delta: 1
 | |
|         });
 | |
|       }
 | |
|     },
 | |
|     replace(to, state) {
 | |
|       action = Action.Replace;
 | |
|       let nextLocation = createMemoryLocation(to, state);
 | |
|       entries[index] = nextLocation;
 | |
|       if (v5Compat && listener) {
 | |
|         listener({
 | |
|           action,
 | |
|           location: nextLocation,
 | |
|           delta: 0
 | |
|         });
 | |
|       }
 | |
|     },
 | |
|     go(delta) {
 | |
|       action = Action.Pop;
 | |
|       let nextIndex = clampIndex(index + delta);
 | |
|       let nextLocation = entries[nextIndex];
 | |
|       index = nextIndex;
 | |
|       if (listener) {
 | |
|         listener({
 | |
|           action,
 | |
|           location: nextLocation,
 | |
|           delta
 | |
|         });
 | |
|       }
 | |
|     },
 | |
|     listen(fn) {
 | |
|       listener = fn;
 | |
|       return () => {
 | |
|         listener = null;
 | |
|       };
 | |
|     }
 | |
|   };
 | |
|   return history;
 | |
| }
 | |
| //#endregion
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| //#region Browser History
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /**
 | |
|  * A browser history stores the current location in regular URLs in a web
 | |
|  * browser environment. This is the standard for most web apps and provides the
 | |
|  * cleanest URLs the browser's address bar.
 | |
|  *
 | |
|  * @see https://github.com/remix-run/history/tree/main/docs/api-reference.md#browserhistory
 | |
|  */
 | |
| /**
 | |
|  * Browser history stores the location in regular URLs. This is the standard for
 | |
|  * most web apps, but it requires some configuration on the server to ensure you
 | |
|  * serve the same app at multiple URLs.
 | |
|  *
 | |
|  * @see https://github.com/remix-run/history/tree/main/docs/api-reference.md#createbrowserhistory
 | |
|  */
 | |
| function createBrowserHistory(options) {
 | |
|   if (options === void 0) {
 | |
|     options = {};
 | |
|   }
 | |
|   function createBrowserLocation(window, globalHistory) {
 | |
|     let {
 | |
|       pathname,
 | |
|       search,
 | |
|       hash
 | |
|     } = window.location;
 | |
|     return createLocation("", {
 | |
|       pathname,
 | |
|       search,
 | |
|       hash
 | |
|     },
 | |
|     // state defaults to `null` because `window.history.state` does
 | |
|     globalHistory.state && globalHistory.state.usr || null, globalHistory.state && globalHistory.state.key || "default");
 | |
|   }
 | |
|   function createBrowserHref(window, to) {
 | |
|     return typeof to === "string" ? to : createPath(to);
 | |
|   }
 | |
|   return getUrlBasedHistory(createBrowserLocation, createBrowserHref, null, options);
 | |
| }
 | |
| //#endregion
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| //#region Hash History
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /**
 | |
|  * A hash history stores the current location in the fragment identifier portion
 | |
|  * of the URL in a web browser environment.
 | |
|  *
 | |
|  * This is ideal for apps that do not control the server for some reason
 | |
|  * (because the fragment identifier is never sent to the server), including some
 | |
|  * shared hosting environments that do not provide fine-grained controls over
 | |
|  * which pages are served at which URLs.
 | |
|  *
 | |
|  * @see https://github.com/remix-run/history/tree/main/docs/api-reference.md#hashhistory
 | |
|  */
 | |
| /**
 | |
|  * Hash history stores the location in window.location.hash. This makes it ideal
 | |
|  * for situations where you don't want to send the location to the server for
 | |
|  * some reason, either because you do cannot configure it or the URL space is
 | |
|  * reserved for something else.
 | |
|  *
 | |
|  * @see https://github.com/remix-run/history/tree/main/docs/api-reference.md#createhashhistory
 | |
|  */
 | |
| function createHashHistory(options) {
 | |
|   if (options === void 0) {
 | |
|     options = {};
 | |
|   }
 | |
|   function createHashLocation(window, globalHistory) {
 | |
|     let {
 | |
|       pathname = "/",
 | |
|       search = "",
 | |
|       hash = ""
 | |
|     } = parsePath(window.location.hash.substr(1));
 | |
| 
 | |
|     // Hash URL should always have a leading / just like window.location.pathname
 | |
|     // does, so if an app ends up at a route like /#something then we add a
 | |
|     // leading slash so all of our path-matching behaves the same as if it would
 | |
|     // in a browser router.  This is particularly important when there exists a
 | |
|     // root splat route (<Route path="*">) since that matches internally against
 | |
|     // "/*" and we'd expect /#something to 404 in a hash router app.
 | |
|     if (!pathname.startsWith("/") && !pathname.startsWith(".")) {
 | |
|       pathname = "/" + pathname;
 | |
|     }
 | |
|     return createLocation("", {
 | |
|       pathname,
 | |
|       search,
 | |
|       hash
 | |
|     },
 | |
|     // state defaults to `null` because `window.history.state` does
 | |
|     globalHistory.state && globalHistory.state.usr || null, globalHistory.state && globalHistory.state.key || "default");
 | |
|   }
 | |
|   function createHashHref(window, to) {
 | |
|     let base = window.document.querySelector("base");
 | |
|     let href = "";
 | |
|     if (base && base.getAttribute("href")) {
 | |
|       let url = window.location.href;
 | |
|       let hashIndex = url.indexOf("#");
 | |
|       href = hashIndex === -1 ? url : url.slice(0, hashIndex);
 | |
|     }
 | |
|     return href + "#" + (typeof to === "string" ? to : createPath(to));
 | |
|   }
 | |
|   function validateHashLocation(location, to) {
 | |
|     warning(location.pathname.charAt(0) === "/", "relative pathnames are not supported in hash history.push(" + JSON.stringify(to) + ")");
 | |
|   }
 | |
|   return getUrlBasedHistory(createHashLocation, createHashHref, validateHashLocation, options);
 | |
| }
 | |
| //#endregion
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| //#region UTILS
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /**
 | |
|  * @private
 | |
|  */
 | |
| function invariant(value, message) {
 | |
|   if (value === false || value === null || typeof value === "undefined") {
 | |
|     throw new Error(message);
 | |
|   }
 | |
| }
 | |
| function warning(cond, message) {
 | |
|   if (!cond) {
 | |
|     // eslint-disable-next-line no-console
 | |
|     if (typeof console !== "undefined") console.warn(message);
 | |
|     try {
 | |
|       // Welcome to debugging history!
 | |
|       //
 | |
|       // This error is thrown as a convenience, so you can more easily
 | |
|       // find the source for a warning that appears in the console by
 | |
|       // enabling "pause on exceptions" in your JavaScript debugger.
 | |
|       throw new Error(message);
 | |
|       // eslint-disable-next-line no-empty
 | |
|     } catch (e) {}
 | |
|   }
 | |
| }
 | |
| function createKey() {
 | |
|   return Math.random().toString(36).substr(2, 8);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * For browser-based histories, we combine the state and key into an object
 | |
|  */
 | |
| function getHistoryState(location, index) {
 | |
|   return {
 | |
|     usr: location.state,
 | |
|     key: location.key,
 | |
|     idx: index
 | |
|   };
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Creates a Location object with a unique key from the given Path
 | |
|  */
 | |
| function createLocation(current, to, state, key) {
 | |
|   if (state === void 0) {
 | |
|     state = null;
 | |
|   }
 | |
|   let location = _extends({
 | |
|     pathname: typeof current === "string" ? current : current.pathname,
 | |
|     search: "",
 | |
|     hash: ""
 | |
|   }, typeof to === "string" ? parsePath(to) : to, {
 | |
|     state,
 | |
|     // TODO: This could be cleaned up.  push/replace should probably just take
 | |
|     // full Locations now and avoid the need to run through this flow at all
 | |
|     // But that's a pretty big refactor to the current test suite so going to
 | |
|     // keep as is for the time being and just let any incoming keys take precedence
 | |
|     key: to && to.key || key || createKey()
 | |
|   });
 | |
|   return location;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Creates a string URL path from the given pathname, search, and hash components.
 | |
|  */
 | |
| function createPath(_ref) {
 | |
|   let {
 | |
|     pathname = "/",
 | |
|     search = "",
 | |
|     hash = ""
 | |
|   } = _ref;
 | |
|   if (search && search !== "?") pathname += search.charAt(0) === "?" ? search : "?" + search;
 | |
|   if (hash && hash !== "#") pathname += hash.charAt(0) === "#" ? hash : "#" + hash;
 | |
|   return pathname;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Parses a string URL path into its separate pathname, search, and hash components.
 | |
|  */
 | |
| function parsePath(path) {
 | |
|   let parsedPath = {};
 | |
|   if (path) {
 | |
|     let hashIndex = path.indexOf("#");
 | |
|     if (hashIndex >= 0) {
 | |
|       parsedPath.hash = path.substr(hashIndex);
 | |
|       path = path.substr(0, hashIndex);
 | |
|     }
 | |
|     let searchIndex = path.indexOf("?");
 | |
|     if (searchIndex >= 0) {
 | |
|       parsedPath.search = path.substr(searchIndex);
 | |
|       path = path.substr(0, searchIndex);
 | |
|     }
 | |
|     if (path) {
 | |
|       parsedPath.pathname = path;
 | |
|     }
 | |
|   }
 | |
|   return parsedPath;
 | |
| }
 | |
| function getUrlBasedHistory(getLocation, createHref, validateLocation, options) {
 | |
|   if (options === void 0) {
 | |
|     options = {};
 | |
|   }
 | |
|   let {
 | |
|     window = document.defaultView,
 | |
|     v5Compat = false
 | |
|   } = options;
 | |
|   let globalHistory = window.history;
 | |
|   let action = Action.Pop;
 | |
|   let listener = null;
 | |
|   let index = getIndex();
 | |
|   // Index should only be null when we initialize. If not, it's because the
 | |
|   // user called history.pushState or history.replaceState directly, in which
 | |
|   // case we should log a warning as it will result in bugs.
 | |
|   if (index == null) {
 | |
|     index = 0;
 | |
|     globalHistory.replaceState(_extends({}, globalHistory.state, {
 | |
|       idx: index
 | |
|     }), "");
 | |
|   }
 | |
|   function getIndex() {
 | |
|     let state = globalHistory.state || {
 | |
|       idx: null
 | |
|     };
 | |
|     return state.idx;
 | |
|   }
 | |
|   function handlePop() {
 | |
|     action = Action.Pop;
 | |
|     let nextIndex = getIndex();
 | |
|     let delta = nextIndex == null ? null : nextIndex - index;
 | |
|     index = nextIndex;
 | |
|     if (listener) {
 | |
|       listener({
 | |
|         action,
 | |
|         location: history.location,
 | |
|         delta
 | |
|       });
 | |
|     }
 | |
|   }
 | |
|   function push(to, state) {
 | |
|     action = Action.Push;
 | |
|     let location = createLocation(history.location, to, state);
 | |
|     if (validateLocation) validateLocation(location, to);
 | |
|     index = getIndex() + 1;
 | |
|     let historyState = getHistoryState(location, index);
 | |
|     let url = history.createHref(location);
 | |
| 
 | |
|     // try...catch because iOS limits us to 100 pushState calls :/
 | |
|     try {
 | |
|       globalHistory.pushState(historyState, "", url);
 | |
|     } catch (error) {
 | |
|       // If the exception is because `state` can't be serialized, let that throw
 | |
|       // outwards just like a replace call would so the dev knows the cause
 | |
|       // https://html.spec.whatwg.org/multipage/nav-history-apis.html#shared-history-push/replace-state-steps
 | |
|       // https://html.spec.whatwg.org/multipage/structured-data.html#structuredserializeinternal
 | |
|       if (error instanceof DOMException && error.name === "DataCloneError") {
 | |
|         throw error;
 | |
|       }
 | |
|       // They are going to lose state here, but there is no real
 | |
|       // way to warn them about it since the page will refresh...
 | |
|       window.location.assign(url);
 | |
|     }
 | |
|     if (v5Compat && listener) {
 | |
|       listener({
 | |
|         action,
 | |
|         location: history.location,
 | |
|         delta: 1
 | |
|       });
 | |
|     }
 | |
|   }
 | |
|   function replace(to, state) {
 | |
|     action = Action.Replace;
 | |
|     let location = createLocation(history.location, to, state);
 | |
|     if (validateLocation) validateLocation(location, to);
 | |
|     index = getIndex();
 | |
|     let historyState = getHistoryState(location, index);
 | |
|     let url = history.createHref(location);
 | |
|     globalHistory.replaceState(historyState, "", url);
 | |
|     if (v5Compat && listener) {
 | |
|       listener({
 | |
|         action,
 | |
|         location: history.location,
 | |
|         delta: 0
 | |
|       });
 | |
|     }
 | |
|   }
 | |
|   function createURL(to) {
 | |
|     // window.location.origin is "null" (the literal string value) in Firefox
 | |
|     // under certain conditions, notably when serving from a local HTML file
 | |
|     // See https://bugzilla.mozilla.org/show_bug.cgi?id=878297
 | |
|     let base = window.location.origin !== "null" ? window.location.origin : window.location.href;
 | |
|     let href = typeof to === "string" ? to : createPath(to);
 | |
|     // Treating this as a full URL will strip any trailing spaces so we need to
 | |
|     // pre-encode them since they might be part of a matching splat param from
 | |
|     // an ancestor route
 | |
|     href = href.replace(/ $/, "%20");
 | |
|     invariant(base, "No window.location.(origin|href) available to create URL for href: " + href);
 | |
|     return new URL(href, base);
 | |
|   }
 | |
|   let history = {
 | |
|     get action() {
 | |
|       return action;
 | |
|     },
 | |
|     get location() {
 | |
|       return getLocation(window, globalHistory);
 | |
|     },
 | |
|     listen(fn) {
 | |
|       if (listener) {
 | |
|         throw new Error("A history only accepts one active listener");
 | |
|       }
 | |
|       window.addEventListener(PopStateEventType, handlePop);
 | |
|       listener = fn;
 | |
|       return () => {
 | |
|         window.removeEventListener(PopStateEventType, handlePop);
 | |
|         listener = null;
 | |
|       };
 | |
|     },
 | |
|     createHref(to) {
 | |
|       return createHref(window, to);
 | |
|     },
 | |
|     createURL,
 | |
|     encodeLocation(to) {
 | |
|       // Encode a Location the same way window.location would
 | |
|       let url = createURL(to);
 | |
|       return {
 | |
|         pathname: url.pathname,
 | |
|         search: url.search,
 | |
|         hash: url.hash
 | |
|       };
 | |
|     },
 | |
|     push,
 | |
|     replace,
 | |
|     go(n) {
 | |
|       return globalHistory.go(n);
 | |
|     }
 | |
|   };
 | |
|   return history;
 | |
| }
 | |
| 
 | |
| //#endregion
 | |
| 
 | |
| /**
 | |
|  * Map of routeId -> data returned from a loader/action/error
 | |
|  */
 | |
| 
 | |
| let ResultType = /*#__PURE__*/function (ResultType) {
 | |
|   ResultType["data"] = "data";
 | |
|   ResultType["deferred"] = "deferred";
 | |
|   ResultType["redirect"] = "redirect";
 | |
|   ResultType["error"] = "error";
 | |
|   return ResultType;
 | |
| }({});
 | |
| 
 | |
| /**
 | |
|  * Successful result from a loader or action
 | |
|  */
 | |
| 
 | |
| /**
 | |
|  * Successful defer() result from a loader or action
 | |
|  */
 | |
| 
 | |
| /**
 | |
|  * Redirect result from a loader or action
 | |
|  */
 | |
| 
 | |
| /**
 | |
|  * Unsuccessful result from a loader or action
 | |
|  */
 | |
| 
 | |
| /**
 | |
|  * Result from a loader or action - potentially successful or unsuccessful
 | |
|  */
 | |
| 
 | |
| /**
 | |
|  * Users can specify either lowercase or uppercase form methods on `<Form>`,
 | |
|  * useSubmit(), `<fetcher.Form>`, etc.
 | |
|  */
 | |
| 
 | |
| /**
 | |
|  * Active navigation/fetcher form methods are exposed in lowercase on the
 | |
|  * RouterState
 | |
|  */
 | |
| 
 | |
| /**
 | |
|  * In v7, active navigation/fetcher form methods are exposed in uppercase on the
 | |
|  * RouterState.  This is to align with the normalization done via fetch().
 | |
|  */
 | |
| 
 | |
| // Thanks https://github.com/sindresorhus/type-fest!
 | |
| 
 | |
| /**
 | |
|  * @private
 | |
|  * Internal interface to pass around for action submissions, not intended for
 | |
|  * external consumption
 | |
|  */
 | |
| 
 | |
| /**
 | |
|  * @private
 | |
|  * Arguments passed to route loader/action functions.  Same for now but we keep
 | |
|  * this as a private implementation detail in case they diverge in the future.
 | |
|  */
 | |
| 
 | |
| // TODO: (v7) Change the defaults from any to unknown in and remove Remix wrappers:
 | |
| //   ActionFunction, ActionFunctionArgs, LoaderFunction, LoaderFunctionArgs
 | |
| //   Also, make them a type alias instead of an interface
 | |
| /**
 | |
|  * Arguments passed to loader functions
 | |
|  */
 | |
| /**
 | |
|  * Arguments passed to action functions
 | |
|  */
 | |
| /**
 | |
|  * Loaders and actions can return anything except `undefined` (`null` is a
 | |
|  * valid return value if there is no data to return).  Responses are preferred
 | |
|  * and will ease any future migration to Remix
 | |
|  */
 | |
| /**
 | |
|  * Route loader function signature
 | |
|  */
 | |
| /**
 | |
|  * Route action function signature
 | |
|  */
 | |
| /**
 | |
|  * Arguments passed to shouldRevalidate function
 | |
|  */
 | |
| /**
 | |
|  * Route shouldRevalidate function signature.  This runs after any submission
 | |
|  * (navigation or fetcher), so we flatten the navigation/fetcher submission
 | |
|  * onto the arguments.  It shouldn't matter whether it came from a navigation
 | |
|  * or a fetcher, what really matters is the URLs and the formData since loaders
 | |
|  * have to re-run based on the data models that were potentially mutated.
 | |
|  */
 | |
| /**
 | |
|  * Function provided by the framework-aware layers to set `hasErrorBoundary`
 | |
|  * from the framework-aware `errorElement` prop
 | |
|  *
 | |
|  * @deprecated Use `mapRouteProperties` instead
 | |
|  */
 | |
| /**
 | |
|  * Result from a loader or action called via dataStrategy
 | |
|  */
 | |
| /**
 | |
|  * Function provided by the framework-aware layers to set any framework-specific
 | |
|  * properties from framework-agnostic properties
 | |
|  */
 | |
| /**
 | |
|  * Keys we cannot change from within a lazy() function. We spread all other keys
 | |
|  * onto the route. Either they're meaningful to the router, or they'll get
 | |
|  * ignored.
 | |
|  */
 | |
| const immutableRouteKeys = new Set(["lazy", "caseSensitive", "path", "id", "index", "children"]);
 | |
| 
 | |
| /**
 | |
|  * lazy() function to load a route definition, which can add non-matching
 | |
|  * related properties to a route
 | |
|  */
 | |
| 
 | |
| /**
 | |
|  * Base RouteObject with common props shared by all types of routes
 | |
|  */
 | |
| 
 | |
| /**
 | |
|  * Index routes must not have children
 | |
|  */
 | |
| 
 | |
| /**
 | |
|  * Non-index routes may have children, but cannot have index
 | |
|  */
 | |
| 
 | |
| /**
 | |
|  * A route object represents a logical route, with (optionally) its child
 | |
|  * routes organized in a tree-like structure.
 | |
|  */
 | |
| 
 | |
| /**
 | |
|  * A data route object, which is just a RouteObject with a required unique ID
 | |
|  */
 | |
| 
 | |
| // Recursive helper for finding path parameters in the absence of wildcards
 | |
| 
 | |
| /**
 | |
|  * Examples:
 | |
|  * "/a/b/*" -> "*"
 | |
|  * ":a" -> "a"
 | |
|  * "/a/:b" -> "b"
 | |
|  * "/a/blahblahblah:b" -> "b"
 | |
|  * "/:a/:b" -> "a" | "b"
 | |
|  * "/:a/b/:c/*" -> "a" | "c" | "*"
 | |
|  */
 | |
| 
 | |
| // Attempt to parse the given string segment. If it fails, then just return the
 | |
| // plain string type as a default fallback. Otherwise, return the union of the
 | |
| // parsed string literals that were referenced as dynamic segments in the route.
 | |
| /**
 | |
|  * The parameters that were parsed from the URL path.
 | |
|  */
 | |
| /**
 | |
|  * A RouteMatch contains info about how a route matched a URL.
 | |
|  */
 | |
| function isIndexRoute(route) {
 | |
|   return route.index === true;
 | |
| }
 | |
| 
 | |
| // Walk the route tree generating unique IDs where necessary, so we are working
 | |
| // solely with AgnosticDataRouteObject's within the Router
 | |
| function convertRoutesToDataRoutes(routes, mapRouteProperties, parentPath, manifest) {
 | |
|   if (parentPath === void 0) {
 | |
|     parentPath = [];
 | |
|   }
 | |
|   if (manifest === void 0) {
 | |
|     manifest = {};
 | |
|   }
 | |
|   return routes.map((route, index) => {
 | |
|     let treePath = [...parentPath, String(index)];
 | |
|     let id = typeof route.id === "string" ? route.id : treePath.join("-");
 | |
|     invariant(route.index !== true || !route.children, "Cannot specify children on an index route");
 | |
|     invariant(!manifest[id], "Found a route id collision on id \"" + id + "\".  Route " + "id's must be globally unique within Data Router usages");
 | |
|     if (isIndexRoute(route)) {
 | |
|       let indexRoute = _extends({}, route, mapRouteProperties(route), {
 | |
|         id
 | |
|       });
 | |
|       manifest[id] = indexRoute;
 | |
|       return indexRoute;
 | |
|     } else {
 | |
|       let pathOrLayoutRoute = _extends({}, route, mapRouteProperties(route), {
 | |
|         id,
 | |
|         children: undefined
 | |
|       });
 | |
|       manifest[id] = pathOrLayoutRoute;
 | |
|       if (route.children) {
 | |
|         pathOrLayoutRoute.children = convertRoutesToDataRoutes(route.children, mapRouteProperties, treePath, manifest);
 | |
|       }
 | |
|       return pathOrLayoutRoute;
 | |
|     }
 | |
|   });
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Matches the given routes to a location and returns the match data.
 | |
|  *
 | |
|  * @see https://reactrouter.com/v6/utils/match-routes
 | |
|  */
 | |
| function matchRoutes(routes, locationArg, basename) {
 | |
|   if (basename === void 0) {
 | |
|     basename = "/";
 | |
|   }
 | |
|   return matchRoutesImpl(routes, locationArg, basename, false);
 | |
| }
 | |
| function matchRoutesImpl(routes, locationArg, basename, allowPartial) {
 | |
|   let location = typeof locationArg === "string" ? parsePath(locationArg) : locationArg;
 | |
|   let pathname = stripBasename(location.pathname || "/", basename);
 | |
|   if (pathname == null) {
 | |
|     return null;
 | |
|   }
 | |
|   let branches = flattenRoutes(routes);
 | |
|   rankRouteBranches(branches);
 | |
|   let matches = null;
 | |
|   for (let i = 0; matches == null && i < branches.length; ++i) {
 | |
|     // Incoming pathnames are generally encoded from either window.location
 | |
|     // or from router.navigate, but we want to match against the unencoded
 | |
|     // paths in the route definitions.  Memory router locations won't be
 | |
|     // encoded here but there also shouldn't be anything to decode so this
 | |
|     // should be a safe operation.  This avoids needing matchRoutes to be
 | |
|     // history-aware.
 | |
|     let decoded = decodePath(pathname);
 | |
|     matches = matchRouteBranch(branches[i], decoded, allowPartial);
 | |
|   }
 | |
|   return matches;
 | |
| }
 | |
| function convertRouteMatchToUiMatch(match, loaderData) {
 | |
|   let {
 | |
|     route,
 | |
|     pathname,
 | |
|     params
 | |
|   } = match;
 | |
|   return {
 | |
|     id: route.id,
 | |
|     pathname,
 | |
|     params,
 | |
|     data: loaderData[route.id],
 | |
|     handle: route.handle
 | |
|   };
 | |
| }
 | |
| function flattenRoutes(routes, branches, parentsMeta, parentPath) {
 | |
|   if (branches === void 0) {
 | |
|     branches = [];
 | |
|   }
 | |
|   if (parentsMeta === void 0) {
 | |
|     parentsMeta = [];
 | |
|   }
 | |
|   if (parentPath === void 0) {
 | |
|     parentPath = "";
 | |
|   }
 | |
|   let flattenRoute = (route, index, relativePath) => {
 | |
|     let meta = {
 | |
|       relativePath: relativePath === undefined ? route.path || "" : relativePath,
 | |
|       caseSensitive: route.caseSensitive === true,
 | |
|       childrenIndex: index,
 | |
|       route
 | |
|     };
 | |
|     if (meta.relativePath.startsWith("/")) {
 | |
|       invariant(meta.relativePath.startsWith(parentPath), "Absolute route path \"" + meta.relativePath + "\" nested under path " + ("\"" + parentPath + "\" is not valid. An absolute child route path ") + "must start with the combined path of all its parent routes.");
 | |
|       meta.relativePath = meta.relativePath.slice(parentPath.length);
 | |
|     }
 | |
|     let path = joinPaths([parentPath, meta.relativePath]);
 | |
|     let routesMeta = parentsMeta.concat(meta);
 | |
| 
 | |
|     // Add the children before adding this route to the array, so we traverse the
 | |
|     // route tree depth-first and child routes appear before their parents in
 | |
|     // the "flattened" version.
 | |
|     if (route.children && route.children.length > 0) {
 | |
|       invariant(
 | |
|       // Our types know better, but runtime JS may not!
 | |
|       // @ts-expect-error
 | |
|       route.index !== true, "Index routes must not have child routes. Please remove " + ("all child routes from route path \"" + path + "\"."));
 | |
|       flattenRoutes(route.children, branches, routesMeta, path);
 | |
|     }
 | |
| 
 | |
|     // Routes without a path shouldn't ever match by themselves unless they are
 | |
|     // index routes, so don't add them to the list of possible branches.
 | |
|     if (route.path == null && !route.index) {
 | |
|       return;
 | |
|     }
 | |
|     branches.push({
 | |
|       path,
 | |
|       score: computeScore(path, route.index),
 | |
|       routesMeta
 | |
|     });
 | |
|   };
 | |
|   routes.forEach((route, index) => {
 | |
|     var _route$path;
 | |
|     // coarse-grain check for optional params
 | |
|     if (route.path === "" || !((_route$path = route.path) != null && _route$path.includes("?"))) {
 | |
|       flattenRoute(route, index);
 | |
|     } else {
 | |
|       for (let exploded of explodeOptionalSegments(route.path)) {
 | |
|         flattenRoute(route, index, exploded);
 | |
|       }
 | |
|     }
 | |
|   });
 | |
|   return branches;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Computes all combinations of optional path segments for a given path,
 | |
|  * excluding combinations that are ambiguous and of lower priority.
 | |
|  *
 | |
|  * For example, `/one/:two?/three/:four?/:five?` explodes to:
 | |
|  * - `/one/three`
 | |
|  * - `/one/:two/three`
 | |
|  * - `/one/three/:four`
 | |
|  * - `/one/three/:five`
 | |
|  * - `/one/:two/three/:four`
 | |
|  * - `/one/:two/three/:five`
 | |
|  * - `/one/three/:four/:five`
 | |
|  * - `/one/:two/three/:four/:five`
 | |
|  */
 | |
| function explodeOptionalSegments(path) {
 | |
|   let segments = path.split("/");
 | |
|   if (segments.length === 0) return [];
 | |
|   let [first, ...rest] = segments;
 | |
| 
 | |
|   // Optional path segments are denoted by a trailing `?`
 | |
|   let isOptional = first.endsWith("?");
 | |
|   // Compute the corresponding required segment: `foo?` -> `foo`
 | |
|   let required = first.replace(/\?$/, "");
 | |
|   if (rest.length === 0) {
 | |
|     // Intepret empty string as omitting an optional segment
 | |
|     // `["one", "", "three"]` corresponds to omitting `:two` from `/one/:two?/three` -> `/one/three`
 | |
|     return isOptional ? [required, ""] : [required];
 | |
|   }
 | |
|   let restExploded = explodeOptionalSegments(rest.join("/"));
 | |
|   let result = [];
 | |
| 
 | |
|   // All child paths with the prefix.  Do this for all children before the
 | |
|   // optional version for all children, so we get consistent ordering where the
 | |
|   // parent optional aspect is preferred as required.  Otherwise, we can get
 | |
|   // child sections interspersed where deeper optional segments are higher than
 | |
|   // parent optional segments, where for example, /:two would explode _earlier_
 | |
|   // then /:one.  By always including the parent as required _for all children_
 | |
|   // first, we avoid this issue
 | |
|   result.push(...restExploded.map(subpath => subpath === "" ? required : [required, subpath].join("/")));
 | |
| 
 | |
|   // Then, if this is an optional value, add all child versions without
 | |
|   if (isOptional) {
 | |
|     result.push(...restExploded);
 | |
|   }
 | |
| 
 | |
|   // for absolute paths, ensure `/` instead of empty segment
 | |
|   return result.map(exploded => path.startsWith("/") && exploded === "" ? "/" : exploded);
 | |
| }
 | |
| function rankRouteBranches(branches) {
 | |
|   branches.sort((a, b) => a.score !== b.score ? b.score - a.score // Higher score first
 | |
|   : compareIndexes(a.routesMeta.map(meta => meta.childrenIndex), b.routesMeta.map(meta => meta.childrenIndex)));
 | |
| }
 | |
| const paramRe = /^:[\w-]+$/;
 | |
| const dynamicSegmentValue = 3;
 | |
| const indexRouteValue = 2;
 | |
| const emptySegmentValue = 1;
 | |
| const staticSegmentValue = 10;
 | |
| const splatPenalty = -2;
 | |
| const isSplat = s => s === "*";
 | |
| function computeScore(path, index) {
 | |
|   let segments = path.split("/");
 | |
|   let initialScore = segments.length;
 | |
|   if (segments.some(isSplat)) {
 | |
|     initialScore += splatPenalty;
 | |
|   }
 | |
|   if (index) {
 | |
|     initialScore += indexRouteValue;
 | |
|   }
 | |
|   return segments.filter(s => !isSplat(s)).reduce((score, segment) => score + (paramRe.test(segment) ? dynamicSegmentValue : segment === "" ? emptySegmentValue : staticSegmentValue), initialScore);
 | |
| }
 | |
| function compareIndexes(a, b) {
 | |
|   let siblings = a.length === b.length && a.slice(0, -1).every((n, i) => n === b[i]);
 | |
|   return siblings ?
 | |
|   // If two routes are siblings, we should try to match the earlier sibling
 | |
|   // first. This allows people to have fine-grained control over the matching
 | |
|   // behavior by simply putting routes with identical paths in the order they
 | |
|   // want them tried.
 | |
|   a[a.length - 1] - b[b.length - 1] :
 | |
|   // Otherwise, it doesn't really make sense to rank non-siblings by index,
 | |
|   // so they sort equally.
 | |
|   0;
 | |
| }
 | |
| function matchRouteBranch(branch, pathname, allowPartial) {
 | |
|   if (allowPartial === void 0) {
 | |
|     allowPartial = false;
 | |
|   }
 | |
|   let {
 | |
|     routesMeta
 | |
|   } = branch;
 | |
|   let matchedParams = {};
 | |
|   let matchedPathname = "/";
 | |
|   let matches = [];
 | |
|   for (let i = 0; i < routesMeta.length; ++i) {
 | |
|     let meta = routesMeta[i];
 | |
|     let end = i === routesMeta.length - 1;
 | |
|     let remainingPathname = matchedPathname === "/" ? pathname : pathname.slice(matchedPathname.length) || "/";
 | |
|     let match = matchPath({
 | |
|       path: meta.relativePath,
 | |
|       caseSensitive: meta.caseSensitive,
 | |
|       end
 | |
|     }, remainingPathname);
 | |
|     let route = meta.route;
 | |
|     if (!match && end && allowPartial && !routesMeta[routesMeta.length - 1].route.index) {
 | |
|       match = matchPath({
 | |
|         path: meta.relativePath,
 | |
|         caseSensitive: meta.caseSensitive,
 | |
|         end: false
 | |
|       }, remainingPathname);
 | |
|     }
 | |
|     if (!match) {
 | |
|       return null;
 | |
|     }
 | |
|     Object.assign(matchedParams, match.params);
 | |
|     matches.push({
 | |
|       // TODO: Can this as be avoided?
 | |
|       params: matchedParams,
 | |
|       pathname: joinPaths([matchedPathname, match.pathname]),
 | |
|       pathnameBase: normalizePathname(joinPaths([matchedPathname, match.pathnameBase])),
 | |
|       route
 | |
|     });
 | |
|     if (match.pathnameBase !== "/") {
 | |
|       matchedPathname = joinPaths([matchedPathname, match.pathnameBase]);
 | |
|     }
 | |
|   }
 | |
|   return matches;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Returns a path with params interpolated.
 | |
|  *
 | |
|  * @see https://reactrouter.com/v6/utils/generate-path
 | |
|  */
 | |
| function generatePath(originalPath, params) {
 | |
|   if (params === void 0) {
 | |
|     params = {};
 | |
|   }
 | |
|   let path = originalPath;
 | |
|   if (path.endsWith("*") && path !== "*" && !path.endsWith("/*")) {
 | |
|     warning(false, "Route path \"" + path + "\" will be treated as if it were " + ("\"" + path.replace(/\*$/, "/*") + "\" because the `*` character must ") + "always follow a `/` in the pattern. To get rid of this warning, " + ("please change the route path to \"" + path.replace(/\*$/, "/*") + "\"."));
 | |
|     path = path.replace(/\*$/, "/*");
 | |
|   }
 | |
| 
 | |
|   // ensure `/` is added at the beginning if the path is absolute
 | |
|   const prefix = path.startsWith("/") ? "/" : "";
 | |
|   const stringify = p => p == null ? "" : typeof p === "string" ? p : String(p);
 | |
|   const segments = path.split(/\/+/).map((segment, index, array) => {
 | |
|     const isLastSegment = index === array.length - 1;
 | |
| 
 | |
|     // only apply the splat if it's the last segment
 | |
|     if (isLastSegment && segment === "*") {
 | |
|       const star = "*";
 | |
|       // Apply the splat
 | |
|       return stringify(params[star]);
 | |
|     }
 | |
|     const keyMatch = segment.match(/^:([\w-]+)(\??)$/);
 | |
|     if (keyMatch) {
 | |
|       const [, key, optional] = keyMatch;
 | |
|       let param = params[key];
 | |
|       invariant(optional === "?" || param != null, "Missing \":" + key + "\" param");
 | |
|       return stringify(param);
 | |
|     }
 | |
| 
 | |
|     // Remove any optional markers from optional static segments
 | |
|     return segment.replace(/\?$/g, "");
 | |
|   })
 | |
|   // Remove empty segments
 | |
|   .filter(segment => !!segment);
 | |
|   return prefix + segments.join("/");
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * A PathPattern is used to match on some portion of a URL pathname.
 | |
|  */
 | |
| 
 | |
| /**
 | |
|  * A PathMatch contains info about how a PathPattern matched on a URL pathname.
 | |
|  */
 | |
| 
 | |
| /**
 | |
|  * Performs pattern matching on a URL pathname and returns information about
 | |
|  * the match.
 | |
|  *
 | |
|  * @see https://reactrouter.com/v6/utils/match-path
 | |
|  */
 | |
| function matchPath(pattern, pathname) {
 | |
|   if (typeof pattern === "string") {
 | |
|     pattern = {
 | |
|       path: pattern,
 | |
|       caseSensitive: false,
 | |
|       end: true
 | |
|     };
 | |
|   }
 | |
|   let [matcher, compiledParams] = compilePath(pattern.path, pattern.caseSensitive, pattern.end);
 | |
|   let match = pathname.match(matcher);
 | |
|   if (!match) return null;
 | |
|   let matchedPathname = match[0];
 | |
|   let pathnameBase = matchedPathname.replace(/(.)\/+$/, "$1");
 | |
|   let captureGroups = match.slice(1);
 | |
|   let params = compiledParams.reduce((memo, _ref, index) => {
 | |
|     let {
 | |
|       paramName,
 | |
|       isOptional
 | |
|     } = _ref;
 | |
|     // We need to compute the pathnameBase here using the raw splat value
 | |
|     // instead of using params["*"] later because it will be decoded then
 | |
|     if (paramName === "*") {
 | |
|       let splatValue = captureGroups[index] || "";
 | |
|       pathnameBase = matchedPathname.slice(0, matchedPathname.length - splatValue.length).replace(/(.)\/+$/, "$1");
 | |
|     }
 | |
|     const value = captureGroups[index];
 | |
|     if (isOptional && !value) {
 | |
|       memo[paramName] = undefined;
 | |
|     } else {
 | |
|       memo[paramName] = (value || "").replace(/%2F/g, "/");
 | |
|     }
 | |
|     return memo;
 | |
|   }, {});
 | |
|   return {
 | |
|     params,
 | |
|     pathname: matchedPathname,
 | |
|     pathnameBase,
 | |
|     pattern
 | |
|   };
 | |
| }
 | |
| function compilePath(path, caseSensitive, end) {
 | |
|   if (caseSensitive === void 0) {
 | |
|     caseSensitive = false;
 | |
|   }
 | |
|   if (end === void 0) {
 | |
|     end = true;
 | |
|   }
 | |
|   warning(path === "*" || !path.endsWith("*") || path.endsWith("/*"), "Route path \"" + path + "\" will be treated as if it were " + ("\"" + path.replace(/\*$/, "/*") + "\" because the `*` character must ") + "always follow a `/` in the pattern. To get rid of this warning, " + ("please change the route path to \"" + path.replace(/\*$/, "/*") + "\"."));
 | |
|   let params = [];
 | |
|   let regexpSource = "^" + path.replace(/\/*\*?$/, "") // Ignore trailing / and /*, we'll handle it below
 | |
|   .replace(/^\/*/, "/") // Make sure it has a leading /
 | |
|   .replace(/[\\.*+^${}|()[\]]/g, "\\$&") // Escape special regex chars
 | |
|   .replace(/\/:([\w-]+)(\?)?/g, (_, paramName, isOptional) => {
 | |
|     params.push({
 | |
|       paramName,
 | |
|       isOptional: isOptional != null
 | |
|     });
 | |
|     return isOptional ? "/?([^\\/]+)?" : "/([^\\/]+)";
 | |
|   });
 | |
|   if (path.endsWith("*")) {
 | |
|     params.push({
 | |
|       paramName: "*"
 | |
|     });
 | |
|     regexpSource += path === "*" || path === "/*" ? "(.*)$" // Already matched the initial /, just match the rest
 | |
|     : "(?:\\/(.+)|\\/*)$"; // Don't include the / in params["*"]
 | |
|   } else if (end) {
 | |
|     // When matching to the end, ignore trailing slashes
 | |
|     regexpSource += "\\/*$";
 | |
|   } else if (path !== "" && path !== "/") {
 | |
|     // If our path is non-empty and contains anything beyond an initial slash,
 | |
|     // then we have _some_ form of path in our regex, so we should expect to
 | |
|     // match only if we find the end of this path segment.  Look for an optional
 | |
|     // non-captured trailing slash (to match a portion of the URL) or the end
 | |
|     // of the path (if we've matched to the end).  We used to do this with a
 | |
|     // word boundary but that gives false positives on routes like
 | |
|     // /user-preferences since `-` counts as a word boundary.
 | |
|     regexpSource += "(?:(?=\\/|$))";
 | |
|   } else ;
 | |
|   let matcher = new RegExp(regexpSource, caseSensitive ? undefined : "i");
 | |
|   return [matcher, params];
 | |
| }
 | |
| function decodePath(value) {
 | |
|   try {
 | |
|     return value.split("/").map(v => decodeURIComponent(v).replace(/\//g, "%2F")).join("/");
 | |
|   } catch (error) {
 | |
|     warning(false, "The URL path \"" + value + "\" could not be decoded because it is is a " + "malformed URL segment. This is probably due to a bad percent " + ("encoding (" + error + ")."));
 | |
|     return value;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @private
 | |
|  */
 | |
| function stripBasename(pathname, basename) {
 | |
|   if (basename === "/") return pathname;
 | |
|   if (!pathname.toLowerCase().startsWith(basename.toLowerCase())) {
 | |
|     return null;
 | |
|   }
 | |
| 
 | |
|   // We want to leave trailing slash behavior in the user's control, so if they
 | |
|   // specify a basename with a trailing slash, we should support it
 | |
|   let startIndex = basename.endsWith("/") ? basename.length - 1 : basename.length;
 | |
|   let nextChar = pathname.charAt(startIndex);
 | |
|   if (nextChar && nextChar !== "/") {
 | |
|     // pathname does not start with basename/
 | |
|     return null;
 | |
|   }
 | |
|   return pathname.slice(startIndex) || "/";
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Returns a resolved path object relative to the given pathname.
 | |
|  *
 | |
|  * @see https://reactrouter.com/v6/utils/resolve-path
 | |
|  */
 | |
| function resolvePath(to, fromPathname) {
 | |
|   if (fromPathname === void 0) {
 | |
|     fromPathname = "/";
 | |
|   }
 | |
|   let {
 | |
|     pathname: toPathname,
 | |
|     search = "",
 | |
|     hash = ""
 | |
|   } = typeof to === "string" ? parsePath(to) : to;
 | |
|   let pathname = toPathname ? toPathname.startsWith("/") ? toPathname : resolvePathname(toPathname, fromPathname) : fromPathname;
 | |
|   return {
 | |
|     pathname,
 | |
|     search: normalizeSearch(search),
 | |
|     hash: normalizeHash(hash)
 | |
|   };
 | |
| }
 | |
| function resolvePathname(relativePath, fromPathname) {
 | |
|   let segments = fromPathname.replace(/\/+$/, "").split("/");
 | |
|   let relativeSegments = relativePath.split("/");
 | |
|   relativeSegments.forEach(segment => {
 | |
|     if (segment === "..") {
 | |
|       // Keep the root "" segment so the pathname starts at /
 | |
|       if (segments.length > 1) segments.pop();
 | |
|     } else if (segment !== ".") {
 | |
|       segments.push(segment);
 | |
|     }
 | |
|   });
 | |
|   return segments.length > 1 ? segments.join("/") : "/";
 | |
| }
 | |
| function getInvalidPathError(char, field, dest, path) {
 | |
|   return "Cannot include a '" + char + "' character in a manually specified " + ("`to." + field + "` field [" + JSON.stringify(path) + "].  Please separate it out to the ") + ("`to." + dest + "` field. Alternatively you may provide the full path as ") + "a string in <Link to=\"...\"> and the router will parse it for you.";
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @private
 | |
|  *
 | |
|  * When processing relative navigation we want to ignore ancestor routes that
 | |
|  * do not contribute to the path, such that index/pathless layout routes don't
 | |
|  * interfere.
 | |
|  *
 | |
|  * For example, when moving a route element into an index route and/or a
 | |
|  * pathless layout route, relative link behavior contained within should stay
 | |
|  * the same.  Both of the following examples should link back to the root:
 | |
|  *
 | |
|  *   <Route path="/">
 | |
|  *     <Route path="accounts" element={<Link to=".."}>
 | |
|  *   </Route>
 | |
|  *
 | |
|  *   <Route path="/">
 | |
|  *     <Route path="accounts">
 | |
|  *       <Route element={<AccountsLayout />}>       // <-- Does not contribute
 | |
|  *         <Route index element={<Link to=".."} />  // <-- Does not contribute
 | |
|  *       </Route
 | |
|  *     </Route>
 | |
|  *   </Route>
 | |
|  */
 | |
| function getPathContributingMatches(matches) {
 | |
|   return matches.filter((match, index) => index === 0 || match.route.path && match.route.path.length > 0);
 | |
| }
 | |
| 
 | |
| // Return the array of pathnames for the current route matches - used to
 | |
| // generate the routePathnames input for resolveTo()
 | |
| function getResolveToMatches(matches, v7_relativeSplatPath) {
 | |
|   let pathMatches = getPathContributingMatches(matches);
 | |
| 
 | |
|   // When v7_relativeSplatPath is enabled, use the full pathname for the leaf
 | |
|   // match so we include splat values for "." links.  See:
 | |
|   // https://github.com/remix-run/react-router/issues/11052#issuecomment-1836589329
 | |
|   if (v7_relativeSplatPath) {
 | |
|     return pathMatches.map((match, idx) => idx === pathMatches.length - 1 ? match.pathname : match.pathnameBase);
 | |
|   }
 | |
|   return pathMatches.map(match => match.pathnameBase);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @private
 | |
|  */
 | |
| function resolveTo(toArg, routePathnames, locationPathname, isPathRelative) {
 | |
|   if (isPathRelative === void 0) {
 | |
|     isPathRelative = false;
 | |
|   }
 | |
|   let to;
 | |
|   if (typeof toArg === "string") {
 | |
|     to = parsePath(toArg);
 | |
|   } else {
 | |
|     to = _extends({}, toArg);
 | |
|     invariant(!to.pathname || !to.pathname.includes("?"), getInvalidPathError("?", "pathname", "search", to));
 | |
|     invariant(!to.pathname || !to.pathname.includes("#"), getInvalidPathError("#", "pathname", "hash", to));
 | |
|     invariant(!to.search || !to.search.includes("#"), getInvalidPathError("#", "search", "hash", to));
 | |
|   }
 | |
|   let isEmptyPath = toArg === "" || to.pathname === "";
 | |
|   let toPathname = isEmptyPath ? "/" : to.pathname;
 | |
|   let from;
 | |
| 
 | |
|   // Routing is relative to the current pathname if explicitly requested.
 | |
|   //
 | |
|   // If a pathname is explicitly provided in `to`, it should be relative to the
 | |
|   // route context. This is explained in `Note on `<Link to>` values` in our
 | |
|   // migration guide from v5 as a means of disambiguation between `to` values
 | |
|   // that begin with `/` and those that do not. However, this is problematic for
 | |
|   // `to` values that do not provide a pathname. `to` can simply be a search or
 | |
|   // hash string, in which case we should assume that the navigation is relative
 | |
|   // to the current location's pathname and *not* the route pathname.
 | |
|   if (toPathname == null) {
 | |
|     from = locationPathname;
 | |
|   } else {
 | |
|     let routePathnameIndex = routePathnames.length - 1;
 | |
| 
 | |
|     // With relative="route" (the default), each leading .. segment means
 | |
|     // "go up one route" instead of "go up one URL segment".  This is a key
 | |
|     // difference from how <a href> works and a major reason we call this a
 | |
|     // "to" value instead of a "href".
 | |
|     if (!isPathRelative && toPathname.startsWith("..")) {
 | |
|       let toSegments = toPathname.split("/");
 | |
|       while (toSegments[0] === "..") {
 | |
|         toSegments.shift();
 | |
|         routePathnameIndex -= 1;
 | |
|       }
 | |
|       to.pathname = toSegments.join("/");
 | |
|     }
 | |
|     from = routePathnameIndex >= 0 ? routePathnames[routePathnameIndex] : "/";
 | |
|   }
 | |
|   let path = resolvePath(to, from);
 | |
| 
 | |
|   // Ensure the pathname has a trailing slash if the original "to" had one
 | |
|   let hasExplicitTrailingSlash = toPathname && toPathname !== "/" && toPathname.endsWith("/");
 | |
|   // Or if this was a link to the current path which has a trailing slash
 | |
|   let hasCurrentTrailingSlash = (isEmptyPath || toPathname === ".") && locationPathname.endsWith("/");
 | |
|   if (!path.pathname.endsWith("/") && (hasExplicitTrailingSlash || hasCurrentTrailingSlash)) {
 | |
|     path.pathname += "/";
 | |
|   }
 | |
|   return path;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @private
 | |
|  */
 | |
| function getToPathname(to) {
 | |
|   // Empty strings should be treated the same as / paths
 | |
|   return to === "" || to.pathname === "" ? "/" : typeof to === "string" ? parsePath(to).pathname : to.pathname;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @private
 | |
|  */
 | |
| const joinPaths = paths => paths.join("/").replace(/\/\/+/g, "/");
 | |
| 
 | |
| /**
 | |
|  * @private
 | |
|  */
 | |
| const normalizePathname = pathname => pathname.replace(/\/+$/, "").replace(/^\/*/, "/");
 | |
| 
 | |
| /**
 | |
|  * @private
 | |
|  */
 | |
| const normalizeSearch = search => !search || search === "?" ? "" : search.startsWith("?") ? search : "?" + search;
 | |
| 
 | |
| /**
 | |
|  * @private
 | |
|  */
 | |
| const normalizeHash = hash => !hash || hash === "#" ? "" : hash.startsWith("#") ? hash : "#" + hash;
 | |
| /**
 | |
|  * This is a shortcut for creating `application/json` responses. Converts `data`
 | |
|  * to JSON and sets the `Content-Type` header.
 | |
|  *
 | |
|  * @deprecated The `json` method is deprecated in favor of returning raw objects.
 | |
|  * This method will be removed in v7.
 | |
|  */
 | |
| const json = function json(data, init) {
 | |
|   if (init === void 0) {
 | |
|     init = {};
 | |
|   }
 | |
|   let responseInit = typeof init === "number" ? {
 | |
|     status: init
 | |
|   } : init;
 | |
|   let headers = new Headers(responseInit.headers);
 | |
|   if (!headers.has("Content-Type")) {
 | |
|     headers.set("Content-Type", "application/json; charset=utf-8");
 | |
|   }
 | |
|   return new Response(JSON.stringify(data), _extends({}, responseInit, {
 | |
|     headers
 | |
|   }));
 | |
| };
 | |
| class DataWithResponseInit {
 | |
|   constructor(data, init) {
 | |
|     this.type = "DataWithResponseInit";
 | |
|     this.data = data;
 | |
|     this.init = init || null;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Create "responses" that contain `status`/`headers` without forcing
 | |
|  * serialization into an actual `Response` - used by Remix single fetch
 | |
|  */
 | |
| function data(data, init) {
 | |
|   return new DataWithResponseInit(data, typeof init === "number" ? {
 | |
|     status: init
 | |
|   } : init);
 | |
| }
 | |
| class AbortedDeferredError extends Error {}
 | |
| class DeferredData {
 | |
|   constructor(data, responseInit) {
 | |
|     this.pendingKeysSet = new Set();
 | |
|     this.subscribers = new Set();
 | |
|     this.deferredKeys = [];
 | |
|     invariant(data && typeof data === "object" && !Array.isArray(data), "defer() only accepts plain objects");
 | |
| 
 | |
|     // Set up an AbortController + Promise we can race against to exit early
 | |
|     // cancellation
 | |
|     let reject;
 | |
|     this.abortPromise = new Promise((_, r) => reject = r);
 | |
|     this.controller = new AbortController();
 | |
|     let onAbort = () => reject(new AbortedDeferredError("Deferred data aborted"));
 | |
|     this.unlistenAbortSignal = () => this.controller.signal.removeEventListener("abort", onAbort);
 | |
|     this.controller.signal.addEventListener("abort", onAbort);
 | |
|     this.data = Object.entries(data).reduce((acc, _ref2) => {
 | |
|       let [key, value] = _ref2;
 | |
|       return Object.assign(acc, {
 | |
|         [key]: this.trackPromise(key, value)
 | |
|       });
 | |
|     }, {});
 | |
|     if (this.done) {
 | |
|       // All incoming values were resolved
 | |
|       this.unlistenAbortSignal();
 | |
|     }
 | |
|     this.init = responseInit;
 | |
|   }
 | |
|   trackPromise(key, value) {
 | |
|     if (!(value instanceof Promise)) {
 | |
|       return value;
 | |
|     }
 | |
|     this.deferredKeys.push(key);
 | |
|     this.pendingKeysSet.add(key);
 | |
| 
 | |
|     // We store a little wrapper promise that will be extended with
 | |
|     // _data/_error props upon resolve/reject
 | |
|     let promise = Promise.race([value, this.abortPromise]).then(data => this.onSettle(promise, key, undefined, data), error => this.onSettle(promise, key, error));
 | |
| 
 | |
|     // Register rejection listeners to avoid uncaught promise rejections on
 | |
|     // errors or aborted deferred values
 | |
|     promise.catch(() => {});
 | |
|     Object.defineProperty(promise, "_tracked", {
 | |
|       get: () => true
 | |
|     });
 | |
|     return promise;
 | |
|   }
 | |
|   onSettle(promise, key, error, data) {
 | |
|     if (this.controller.signal.aborted && error instanceof AbortedDeferredError) {
 | |
|       this.unlistenAbortSignal();
 | |
|       Object.defineProperty(promise, "_error", {
 | |
|         get: () => error
 | |
|       });
 | |
|       return Promise.reject(error);
 | |
|     }
 | |
|     this.pendingKeysSet.delete(key);
 | |
|     if (this.done) {
 | |
|       // Nothing left to abort!
 | |
|       this.unlistenAbortSignal();
 | |
|     }
 | |
| 
 | |
|     // If the promise was resolved/rejected with undefined, we'll throw an error as you
 | |
|     // should always resolve with a value or null
 | |
|     if (error === undefined && data === undefined) {
 | |
|       let undefinedError = new Error("Deferred data for key \"" + key + "\" resolved/rejected with `undefined`, " + "you must resolve/reject with a value or `null`.");
 | |
|       Object.defineProperty(promise, "_error", {
 | |
|         get: () => undefinedError
 | |
|       });
 | |
|       this.emit(false, key);
 | |
|       return Promise.reject(undefinedError);
 | |
|     }
 | |
|     if (data === undefined) {
 | |
|       Object.defineProperty(promise, "_error", {
 | |
|         get: () => error
 | |
|       });
 | |
|       this.emit(false, key);
 | |
|       return Promise.reject(error);
 | |
|     }
 | |
|     Object.defineProperty(promise, "_data", {
 | |
|       get: () => data
 | |
|     });
 | |
|     this.emit(false, key);
 | |
|     return data;
 | |
|   }
 | |
|   emit(aborted, settledKey) {
 | |
|     this.subscribers.forEach(subscriber => subscriber(aborted, settledKey));
 | |
|   }
 | |
|   subscribe(fn) {
 | |
|     this.subscribers.add(fn);
 | |
|     return () => this.subscribers.delete(fn);
 | |
|   }
 | |
|   cancel() {
 | |
|     this.controller.abort();
 | |
|     this.pendingKeysSet.forEach((v, k) => this.pendingKeysSet.delete(k));
 | |
|     this.emit(true);
 | |
|   }
 | |
|   async resolveData(signal) {
 | |
|     let aborted = false;
 | |
|     if (!this.done) {
 | |
|       let onAbort = () => this.cancel();
 | |
|       signal.addEventListener("abort", onAbort);
 | |
|       aborted = await new Promise(resolve => {
 | |
|         this.subscribe(aborted => {
 | |
|           signal.removeEventListener("abort", onAbort);
 | |
|           if (aborted || this.done) {
 | |
|             resolve(aborted);
 | |
|           }
 | |
|         });
 | |
|       });
 | |
|     }
 | |
|     return aborted;
 | |
|   }
 | |
|   get done() {
 | |
|     return this.pendingKeysSet.size === 0;
 | |
|   }
 | |
|   get unwrappedData() {
 | |
|     invariant(this.data !== null && this.done, "Can only unwrap data on initialized and settled deferreds");
 | |
|     return Object.entries(this.data).reduce((acc, _ref3) => {
 | |
|       let [key, value] = _ref3;
 | |
|       return Object.assign(acc, {
 | |
|         [key]: unwrapTrackedPromise(value)
 | |
|       });
 | |
|     }, {});
 | |
|   }
 | |
|   get pendingKeys() {
 | |
|     return Array.from(this.pendingKeysSet);
 | |
|   }
 | |
| }
 | |
| function isTrackedPromise(value) {
 | |
|   return value instanceof Promise && value._tracked === true;
 | |
| }
 | |
| function unwrapTrackedPromise(value) {
 | |
|   if (!isTrackedPromise(value)) {
 | |
|     return value;
 | |
|   }
 | |
|   if (value._error) {
 | |
|     throw value._error;
 | |
|   }
 | |
|   return value._data;
 | |
| }
 | |
| /**
 | |
|  * @deprecated The `defer` method is deprecated in favor of returning raw
 | |
|  * objects. This method will be removed in v7.
 | |
|  */
 | |
| const defer = function defer(data, init) {
 | |
|   if (init === void 0) {
 | |
|     init = {};
 | |
|   }
 | |
|   let responseInit = typeof init === "number" ? {
 | |
|     status: init
 | |
|   } : init;
 | |
|   return new DeferredData(data, responseInit);
 | |
| };
 | |
| /**
 | |
|  * A redirect response. Sets the status code and the `Location` header.
 | |
|  * Defaults to "302 Found".
 | |
|  */
 | |
| const redirect = function redirect(url, init) {
 | |
|   if (init === void 0) {
 | |
|     init = 302;
 | |
|   }
 | |
|   let responseInit = init;
 | |
|   if (typeof responseInit === "number") {
 | |
|     responseInit = {
 | |
|       status: responseInit
 | |
|     };
 | |
|   } else if (typeof responseInit.status === "undefined") {
 | |
|     responseInit.status = 302;
 | |
|   }
 | |
|   let headers = new Headers(responseInit.headers);
 | |
|   headers.set("Location", url);
 | |
|   return new Response(null, _extends({}, responseInit, {
 | |
|     headers
 | |
|   }));
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * A redirect response that will force a document reload to the new location.
 | |
|  * Sets the status code and the `Location` header.
 | |
|  * Defaults to "302 Found".
 | |
|  */
 | |
| const redirectDocument = (url, init) => {
 | |
|   let response = redirect(url, init);
 | |
|   response.headers.set("X-Remix-Reload-Document", "true");
 | |
|   return response;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * A redirect response that will perform a `history.replaceState` instead of a
 | |
|  * `history.pushState` for client-side navigation redirects.
 | |
|  * Sets the status code and the `Location` header.
 | |
|  * Defaults to "302 Found".
 | |
|  */
 | |
| const replace = (url, init) => {
 | |
|   let response = redirect(url, init);
 | |
|   response.headers.set("X-Remix-Replace", "true");
 | |
|   return response;
 | |
| };
 | |
| /**
 | |
|  * @private
 | |
|  * Utility class we use to hold auto-unwrapped 4xx/5xx Response bodies
 | |
|  *
 | |
|  * We don't export the class for public use since it's an implementation
 | |
|  * detail, but we export the interface above so folks can build their own
 | |
|  * abstractions around instances via isRouteErrorResponse()
 | |
|  */
 | |
| class ErrorResponseImpl {
 | |
|   constructor(status, statusText, data, internal) {
 | |
|     if (internal === void 0) {
 | |
|       internal = false;
 | |
|     }
 | |
|     this.status = status;
 | |
|     this.statusText = statusText || "";
 | |
|     this.internal = internal;
 | |
|     if (data instanceof Error) {
 | |
|       this.data = data.toString();
 | |
|       this.error = data;
 | |
|     } else {
 | |
|       this.data = data;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Check if the given error is an ErrorResponse generated from a 4xx/5xx
 | |
|  * Response thrown from an action/loader
 | |
|  */
 | |
| function isRouteErrorResponse(error) {
 | |
|   return error != null && typeof error.status === "number" && typeof error.statusText === "string" && typeof error.internal === "boolean" && "data" in error;
 | |
| }
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| //#region Types and Constants
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /**
 | |
|  * A Router instance manages all navigation and data loading/mutations
 | |
|  */
 | |
| /**
 | |
|  * State maintained internally by the router.  During a navigation, all states
 | |
|  * reflect the the "old" location unless otherwise noted.
 | |
|  */
 | |
| /**
 | |
|  * Data that can be passed into hydrate a Router from SSR
 | |
|  */
 | |
| /**
 | |
|  * Future flags to toggle new feature behavior
 | |
|  */
 | |
| /**
 | |
|  * Initialization options for createRouter
 | |
|  */
 | |
| /**
 | |
|  * State returned from a server-side query() call
 | |
|  */
 | |
| /**
 | |
|  * A StaticHandler instance manages a singular SSR navigation/fetch event
 | |
|  */
 | |
| /**
 | |
|  * Subscriber function signature for changes to router state
 | |
|  */
 | |
| /**
 | |
|  * Function signature for determining the key to be used in scroll restoration
 | |
|  * for a given location
 | |
|  */
 | |
| /**
 | |
|  * Function signature for determining the current scroll position
 | |
|  */
 | |
| // Allowed for any navigation or fetch
 | |
| // Only allowed for navigations
 | |
| // Only allowed for submission navigations
 | |
| /**
 | |
|  * Options for a navigate() call for a normal (non-submission) navigation
 | |
|  */
 | |
| /**
 | |
|  * Options for a navigate() call for a submission navigation
 | |
|  */
 | |
| /**
 | |
|  * Options to pass to navigate() for a navigation
 | |
|  */
 | |
| /**
 | |
|  * Options for a fetch() load
 | |
|  */
 | |
| /**
 | |
|  * Options for a fetch() submission
 | |
|  */
 | |
| /**
 | |
|  * Options to pass to fetch()
 | |
|  */
 | |
| /**
 | |
|  * Potential states for state.navigation
 | |
|  */
 | |
| /**
 | |
|  * Potential states for fetchers
 | |
|  */
 | |
| /**
 | |
|  * Cached info for active fetcher.load() instances so they can participate
 | |
|  * in revalidation
 | |
|  */
 | |
| /**
 | |
|  * Identified fetcher.load() calls that need to be revalidated
 | |
|  */
 | |
| const validMutationMethodsArr = ["post", "put", "patch", "delete"];
 | |
| const validMutationMethods = new Set(validMutationMethodsArr);
 | |
| const validRequestMethodsArr = ["get", ...validMutationMethodsArr];
 | |
| const validRequestMethods = new Set(validRequestMethodsArr);
 | |
| const redirectStatusCodes = new Set([301, 302, 303, 307, 308]);
 | |
| const redirectPreserveMethodStatusCodes = new Set([307, 308]);
 | |
| const IDLE_NAVIGATION = {
 | |
|   state: "idle",
 | |
|   location: undefined,
 | |
|   formMethod: undefined,
 | |
|   formAction: undefined,
 | |
|   formEncType: undefined,
 | |
|   formData: undefined,
 | |
|   json: undefined,
 | |
|   text: undefined
 | |
| };
 | |
| const IDLE_FETCHER = {
 | |
|   state: "idle",
 | |
|   data: undefined,
 | |
|   formMethod: undefined,
 | |
|   formAction: undefined,
 | |
|   formEncType: undefined,
 | |
|   formData: undefined,
 | |
|   json: undefined,
 | |
|   text: undefined
 | |
| };
 | |
| const IDLE_BLOCKER = {
 | |
|   state: "unblocked",
 | |
|   proceed: undefined,
 | |
|   reset: undefined,
 | |
|   location: undefined
 | |
| };
 | |
| const ABSOLUTE_URL_REGEX = /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i;
 | |
| const defaultMapRouteProperties = route => ({
 | |
|   hasErrorBoundary: Boolean(route.hasErrorBoundary)
 | |
| });
 | |
| const TRANSITIONS_STORAGE_KEY = "remix-router-transitions";
 | |
| 
 | |
| //#endregion
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| //#region createRouter
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /**
 | |
|  * Create a router and listen to history POP navigations
 | |
|  */
 | |
| function createRouter(init) {
 | |
|   const routerWindow = init.window ? init.window : typeof window !== "undefined" ? window : undefined;
 | |
|   const isBrowser = typeof routerWindow !== "undefined" && typeof routerWindow.document !== "undefined" && typeof routerWindow.document.createElement !== "undefined";
 | |
|   const isServer = !isBrowser;
 | |
|   invariant(init.routes.length > 0, "You must provide a non-empty routes array to createRouter");
 | |
|   let mapRouteProperties;
 | |
|   if (init.mapRouteProperties) {
 | |
|     mapRouteProperties = init.mapRouteProperties;
 | |
|   } else if (init.detectErrorBoundary) {
 | |
|     // If they are still using the deprecated version, wrap it with the new API
 | |
|     let detectErrorBoundary = init.detectErrorBoundary;
 | |
|     mapRouteProperties = route => ({
 | |
|       hasErrorBoundary: detectErrorBoundary(route)
 | |
|     });
 | |
|   } else {
 | |
|     mapRouteProperties = defaultMapRouteProperties;
 | |
|   }
 | |
| 
 | |
|   // Routes keyed by ID
 | |
|   let manifest = {};
 | |
|   // Routes in tree format for matching
 | |
|   let dataRoutes = convertRoutesToDataRoutes(init.routes, mapRouteProperties, undefined, manifest);
 | |
|   let inFlightDataRoutes;
 | |
|   let basename = init.basename || "/";
 | |
|   let dataStrategyImpl = init.dataStrategy || defaultDataStrategy;
 | |
|   let patchRoutesOnNavigationImpl = init.patchRoutesOnNavigation;
 | |
| 
 | |
|   // Config driven behavior flags
 | |
|   let future = _extends({
 | |
|     v7_fetcherPersist: false,
 | |
|     v7_normalizeFormMethod: false,
 | |
|     v7_partialHydration: false,
 | |
|     v7_prependBasename: false,
 | |
|     v7_relativeSplatPath: false,
 | |
|     v7_skipActionErrorRevalidation: false
 | |
|   }, init.future);
 | |
|   // Cleanup function for history
 | |
|   let unlistenHistory = null;
 | |
|   // Externally-provided functions to call on all state changes
 | |
|   let subscribers = new Set();
 | |
|   // Externally-provided object to hold scroll restoration locations during routing
 | |
|   let savedScrollPositions = null;
 | |
|   // Externally-provided function to get scroll restoration keys
 | |
|   let getScrollRestorationKey = null;
 | |
|   // Externally-provided function to get current scroll position
 | |
|   let getScrollPosition = null;
 | |
|   // One-time flag to control the initial hydration scroll restoration.  Because
 | |
|   // we don't get the saved positions from <ScrollRestoration /> until _after_
 | |
|   // the initial render, we need to manually trigger a separate updateState to
 | |
|   // send along the restoreScrollPosition
 | |
|   // Set to true if we have `hydrationData` since we assume we were SSR'd and that
 | |
|   // SSR did the initial scroll restoration.
 | |
|   let initialScrollRestored = init.hydrationData != null;
 | |
|   let initialMatches = matchRoutes(dataRoutes, init.history.location, basename);
 | |
|   let initialMatchesIsFOW = false;
 | |
|   let initialErrors = null;
 | |
|   if (initialMatches == null && !patchRoutesOnNavigationImpl) {
 | |
|     // If we do not match a user-provided-route, fall back to the root
 | |
|     // to allow the error boundary to take over
 | |
|     let error = getInternalRouterError(404, {
 | |
|       pathname: init.history.location.pathname
 | |
|     });
 | |
|     let {
 | |
|       matches,
 | |
|       route
 | |
|     } = getShortCircuitMatches(dataRoutes);
 | |
|     initialMatches = matches;
 | |
|     initialErrors = {
 | |
|       [route.id]: error
 | |
|     };
 | |
|   }
 | |
| 
 | |
|   // In SPA apps, if the user provided a patchRoutesOnNavigation implementation and
 | |
|   // our initial match is a splat route, clear them out so we run through lazy
 | |
|   // discovery on hydration in case there's a more accurate lazy route match.
 | |
|   // In SSR apps (with `hydrationData`), we expect that the server will send
 | |
|   // up the proper matched routes so we don't want to run lazy discovery on
 | |
|   // initial hydration and want to hydrate into the splat route.
 | |
|   if (initialMatches && !init.hydrationData) {
 | |
|     let fogOfWar = checkFogOfWar(initialMatches, dataRoutes, init.history.location.pathname);
 | |
|     if (fogOfWar.active) {
 | |
|       initialMatches = null;
 | |
|     }
 | |
|   }
 | |
|   let initialized;
 | |
|   if (!initialMatches) {
 | |
|     initialized = false;
 | |
|     initialMatches = [];
 | |
| 
 | |
|     // If partial hydration and fog of war is enabled, we will be running
 | |
|     // `patchRoutesOnNavigation` during hydration so include any partial matches as
 | |
|     // the initial matches so we can properly render `HydrateFallback`'s
 | |
|     if (future.v7_partialHydration) {
 | |
|       let fogOfWar = checkFogOfWar(null, dataRoutes, init.history.location.pathname);
 | |
|       if (fogOfWar.active && fogOfWar.matches) {
 | |
|         initialMatchesIsFOW = true;
 | |
|         initialMatches = fogOfWar.matches;
 | |
|       }
 | |
|     }
 | |
|   } else if (initialMatches.some(m => m.route.lazy)) {
 | |
|     // All initialMatches need to be loaded before we're ready.  If we have lazy
 | |
|     // functions around still then we'll need to run them in initialize()
 | |
|     initialized = false;
 | |
|   } else if (!initialMatches.some(m => m.route.loader)) {
 | |
|     // If we've got no loaders to run, then we're good to go
 | |
|     initialized = true;
 | |
|   } else if (future.v7_partialHydration) {
 | |
|     // If partial hydration is enabled, we're initialized so long as we were
 | |
|     // provided with hydrationData for every route with a loader, and no loaders
 | |
|     // were marked for explicit hydration
 | |
|     let loaderData = init.hydrationData ? init.hydrationData.loaderData : null;
 | |
|     let errors = init.hydrationData ? init.hydrationData.errors : null;
 | |
|     // If errors exist, don't consider routes below the boundary
 | |
|     if (errors) {
 | |
|       let idx = initialMatches.findIndex(m => errors[m.route.id] !== undefined);
 | |
|       initialized = initialMatches.slice(0, idx + 1).every(m => !shouldLoadRouteOnHydration(m.route, loaderData, errors));
 | |
|     } else {
 | |
|       initialized = initialMatches.every(m => !shouldLoadRouteOnHydration(m.route, loaderData, errors));
 | |
|     }
 | |
|   } else {
 | |
|     // Without partial hydration - we're initialized if we were provided any
 | |
|     // hydrationData - which is expected to be complete
 | |
|     initialized = init.hydrationData != null;
 | |
|   }
 | |
|   let router;
 | |
|   let state = {
 | |
|     historyAction: init.history.action,
 | |
|     location: init.history.location,
 | |
|     matches: initialMatches,
 | |
|     initialized,
 | |
|     navigation: IDLE_NAVIGATION,
 | |
|     // Don't restore on initial updateState() if we were SSR'd
 | |
|     restoreScrollPosition: init.hydrationData != null ? false : null,
 | |
|     preventScrollReset: false,
 | |
|     revalidation: "idle",
 | |
|     loaderData: init.hydrationData && init.hydrationData.loaderData || {},
 | |
|     actionData: init.hydrationData && init.hydrationData.actionData || null,
 | |
|     errors: init.hydrationData && init.hydrationData.errors || initialErrors,
 | |
|     fetchers: new Map(),
 | |
|     blockers: new Map()
 | |
|   };
 | |
| 
 | |
|   // -- Stateful internal variables to manage navigations --
 | |
|   // Current navigation in progress (to be committed in completeNavigation)
 | |
|   let pendingAction = Action.Pop;
 | |
| 
 | |
|   // Should the current navigation prevent the scroll reset if scroll cannot
 | |
|   // be restored?
 | |
|   let pendingPreventScrollReset = false;
 | |
| 
 | |
|   // AbortController for the active navigation
 | |
|   let pendingNavigationController;
 | |
| 
 | |
|   // Should the current navigation enable document.startViewTransition?
 | |
|   let pendingViewTransitionEnabled = false;
 | |
| 
 | |
|   // Store applied view transitions so we can apply them on POP
 | |
|   let appliedViewTransitions = new Map();
 | |
| 
 | |
|   // Cleanup function for persisting applied transitions to sessionStorage
 | |
|   let removePageHideEventListener = null;
 | |
| 
 | |
|   // We use this to avoid touching history in completeNavigation if a
 | |
|   // revalidation is entirely uninterrupted
 | |
|   let isUninterruptedRevalidation = false;
 | |
| 
 | |
|   // Use this internal flag to force revalidation of all loaders:
 | |
|   //  - submissions (completed or interrupted)
 | |
|   //  - useRevalidator()
 | |
|   //  - X-Remix-Revalidate (from redirect)
 | |
|   let isRevalidationRequired = false;
 | |
| 
 | |
|   // Use this internal array to capture routes that require revalidation due
 | |
|   // to a cancelled deferred on action submission
 | |
|   let cancelledDeferredRoutes = [];
 | |
| 
 | |
|   // Use this internal array to capture fetcher loads that were cancelled by an
 | |
|   // action navigation and require revalidation
 | |
|   let cancelledFetcherLoads = new Set();
 | |
| 
 | |
|   // AbortControllers for any in-flight fetchers
 | |
|   let fetchControllers = new Map();
 | |
| 
 | |
|   // Track loads based on the order in which they started
 | |
|   let incrementingLoadId = 0;
 | |
| 
 | |
|   // Track the outstanding pending navigation data load to be compared against
 | |
|   // the globally incrementing load when a fetcher load lands after a completed
 | |
|   // navigation
 | |
|   let pendingNavigationLoadId = -1;
 | |
| 
 | |
|   // Fetchers that triggered data reloads as a result of their actions
 | |
|   let fetchReloadIds = new Map();
 | |
| 
 | |
|   // Fetchers that triggered redirect navigations
 | |
|   let fetchRedirectIds = new Set();
 | |
| 
 | |
|   // Most recent href/match for fetcher.load calls for fetchers
 | |
|   let fetchLoadMatches = new Map();
 | |
| 
 | |
|   // Ref-count mounted fetchers so we know when it's ok to clean them up
 | |
|   let activeFetchers = new Map();
 | |
| 
 | |
|   // Fetchers that have requested a delete when using v7_fetcherPersist,
 | |
|   // they'll be officially removed after they return to idle
 | |
|   let deletedFetchers = new Set();
 | |
| 
 | |
|   // Store DeferredData instances for active route matches.  When a
 | |
|   // route loader returns defer() we stick one in here.  Then, when a nested
 | |
|   // promise resolves we update loaderData.  If a new navigation starts we
 | |
|   // cancel active deferreds for eliminated routes.
 | |
|   let activeDeferreds = new Map();
 | |
| 
 | |
|   // Store blocker functions in a separate Map outside of router state since
 | |
|   // we don't need to update UI state if they change
 | |
|   let blockerFunctions = new Map();
 | |
| 
 | |
|   // Flag to ignore the next history update, so we can revert the URL change on
 | |
|   // a POP navigation that was blocked by the user without touching router state
 | |
|   let unblockBlockerHistoryUpdate = undefined;
 | |
| 
 | |
|   // Initialize the router, all side effects should be kicked off from here.
 | |
|   // Implemented as a Fluent API for ease of:
 | |
|   //   let router = createRouter(init).initialize();
 | |
|   function initialize() {
 | |
|     // If history informs us of a POP navigation, start the navigation but do not update
 | |
|     // state.  We'll update our own state once the navigation completes
 | |
|     unlistenHistory = init.history.listen(_ref => {
 | |
|       let {
 | |
|         action: historyAction,
 | |
|         location,
 | |
|         delta
 | |
|       } = _ref;
 | |
|       // Ignore this event if it was just us resetting the URL from a
 | |
|       // blocked POP navigation
 | |
|       if (unblockBlockerHistoryUpdate) {
 | |
|         unblockBlockerHistoryUpdate();
 | |
|         unblockBlockerHistoryUpdate = undefined;
 | |
|         return;
 | |
|       }
 | |
|       warning(blockerFunctions.size === 0 || delta != null, "You are trying to use a blocker on a POP navigation to a location " + "that was not created by @remix-run/router. This will fail silently in " + "production. This can happen if you are navigating outside the router " + "via `window.history.pushState`/`window.location.hash` instead of using " + "router navigation APIs.  This can also happen if you are using " + "createHashRouter and the user manually changes the URL.");
 | |
|       let blockerKey = shouldBlockNavigation({
 | |
|         currentLocation: state.location,
 | |
|         nextLocation: location,
 | |
|         historyAction
 | |
|       });
 | |
|       if (blockerKey && delta != null) {
 | |
|         // Restore the URL to match the current UI, but don't update router state
 | |
|         let nextHistoryUpdatePromise = new Promise(resolve => {
 | |
|           unblockBlockerHistoryUpdate = resolve;
 | |
|         });
 | |
|         init.history.go(delta * -1);
 | |
| 
 | |
|         // Put the blocker into a blocked state
 | |
|         updateBlocker(blockerKey, {
 | |
|           state: "blocked",
 | |
|           location,
 | |
|           proceed() {
 | |
|             updateBlocker(blockerKey, {
 | |
|               state: "proceeding",
 | |
|               proceed: undefined,
 | |
|               reset: undefined,
 | |
|               location
 | |
|             });
 | |
|             // Re-do the same POP navigation we just blocked, after the url
 | |
|             // restoration is also complete.  See:
 | |
|             // https://github.com/remix-run/react-router/issues/11613
 | |
|             nextHistoryUpdatePromise.then(() => init.history.go(delta));
 | |
|           },
 | |
|           reset() {
 | |
|             let blockers = new Map(state.blockers);
 | |
|             blockers.set(blockerKey, IDLE_BLOCKER);
 | |
|             updateState({
 | |
|               blockers
 | |
|             });
 | |
|           }
 | |
|         });
 | |
|         return;
 | |
|       }
 | |
|       return startNavigation(historyAction, location);
 | |
|     });
 | |
|     if (isBrowser) {
 | |
|       // FIXME: This feels gross.  How can we cleanup the lines between
 | |
|       // scrollRestoration/appliedTransitions persistance?
 | |
|       restoreAppliedTransitions(routerWindow, appliedViewTransitions);
 | |
|       let _saveAppliedTransitions = () => persistAppliedTransitions(routerWindow, appliedViewTransitions);
 | |
|       routerWindow.addEventListener("pagehide", _saveAppliedTransitions);
 | |
|       removePageHideEventListener = () => routerWindow.removeEventListener("pagehide", _saveAppliedTransitions);
 | |
|     }
 | |
| 
 | |
|     // Kick off initial data load if needed.  Use Pop to avoid modifying history
 | |
|     // Note we don't do any handling of lazy here.  For SPA's it'll get handled
 | |
|     // in the normal navigation flow.  For SSR it's expected that lazy modules are
 | |
|     // resolved prior to router creation since we can't go into a fallbackElement
 | |
|     // UI for SSR'd apps
 | |
|     if (!state.initialized) {
 | |
|       startNavigation(Action.Pop, state.location, {
 | |
|         initialHydration: true
 | |
|       });
 | |
|     }
 | |
|     return router;
 | |
|   }
 | |
| 
 | |
|   // Clean up a router and it's side effects
 | |
|   function dispose() {
 | |
|     if (unlistenHistory) {
 | |
|       unlistenHistory();
 | |
|     }
 | |
|     if (removePageHideEventListener) {
 | |
|       removePageHideEventListener();
 | |
|     }
 | |
|     subscribers.clear();
 | |
|     pendingNavigationController && pendingNavigationController.abort();
 | |
|     state.fetchers.forEach((_, key) => deleteFetcher(key));
 | |
|     state.blockers.forEach((_, key) => deleteBlocker(key));
 | |
|   }
 | |
| 
 | |
|   // Subscribe to state updates for the router
 | |
|   function subscribe(fn) {
 | |
|     subscribers.add(fn);
 | |
|     return () => subscribers.delete(fn);
 | |
|   }
 | |
| 
 | |
|   // Update our state and notify the calling context of the change
 | |
|   function updateState(newState, opts) {
 | |
|     if (opts === void 0) {
 | |
|       opts = {};
 | |
|     }
 | |
|     state = _extends({}, state, newState);
 | |
| 
 | |
|     // Prep fetcher cleanup so we can tell the UI which fetcher data entries
 | |
|     // can be removed
 | |
|     let completedFetchers = [];
 | |
|     let deletedFetchersKeys = [];
 | |
|     if (future.v7_fetcherPersist) {
 | |
|       state.fetchers.forEach((fetcher, key) => {
 | |
|         if (fetcher.state === "idle") {
 | |
|           if (deletedFetchers.has(key)) {
 | |
|             // Unmounted from the UI and can be totally removed
 | |
|             deletedFetchersKeys.push(key);
 | |
|           } else {
 | |
|             // Returned to idle but still mounted in the UI, so semi-remains for
 | |
|             // revalidations and such
 | |
|             completedFetchers.push(key);
 | |
|           }
 | |
|         }
 | |
|       });
 | |
|     }
 | |
| 
 | |
|     // Remove any lingering deleted fetchers that have already been removed
 | |
|     // from state.fetchers
 | |
|     deletedFetchers.forEach(key => {
 | |
|       if (!state.fetchers.has(key) && !fetchControllers.has(key)) {
 | |
|         deletedFetchersKeys.push(key);
 | |
|       }
 | |
|     });
 | |
| 
 | |
|     // Iterate over a local copy so that if flushSync is used and we end up
 | |
|     // removing and adding a new subscriber due to the useCallback dependencies,
 | |
|     // we don't get ourselves into a loop calling the new subscriber immediately
 | |
|     [...subscribers].forEach(subscriber => subscriber(state, {
 | |
|       deletedFetchers: deletedFetchersKeys,
 | |
|       viewTransitionOpts: opts.viewTransitionOpts,
 | |
|       flushSync: opts.flushSync === true
 | |
|     }));
 | |
| 
 | |
|     // Remove idle fetchers from state since we only care about in-flight fetchers.
 | |
|     if (future.v7_fetcherPersist) {
 | |
|       completedFetchers.forEach(key => state.fetchers.delete(key));
 | |
|       deletedFetchersKeys.forEach(key => deleteFetcher(key));
 | |
|     } else {
 | |
|       // We already called deleteFetcher() on these, can remove them from this
 | |
|       // Set now that we've handed the keys off to the data layer
 | |
|       deletedFetchersKeys.forEach(key => deletedFetchers.delete(key));
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Complete a navigation returning the state.navigation back to the IDLE_NAVIGATION
 | |
|   // and setting state.[historyAction/location/matches] to the new route.
 | |
|   // - Location is a required param
 | |
|   // - Navigation will always be set to IDLE_NAVIGATION
 | |
|   // - Can pass any other state in newState
 | |
|   function completeNavigation(location, newState, _temp) {
 | |
|     var _location$state, _location$state2;
 | |
|     let {
 | |
|       flushSync
 | |
|     } = _temp === void 0 ? {} : _temp;
 | |
|     // Deduce if we're in a loading/actionReload state:
 | |
|     // - We have committed actionData in the store
 | |
|     // - The current navigation was a mutation submission
 | |
|     // - We're past the submitting state and into the loading state
 | |
|     // - The location being loaded is not the result of a redirect
 | |
|     let isActionReload = state.actionData != null && state.navigation.formMethod != null && isMutationMethod(state.navigation.formMethod) && state.navigation.state === "loading" && ((_location$state = location.state) == null ? void 0 : _location$state._isRedirect) !== true;
 | |
|     let actionData;
 | |
|     if (newState.actionData) {
 | |
|       if (Object.keys(newState.actionData).length > 0) {
 | |
|         actionData = newState.actionData;
 | |
|       } else {
 | |
|         // Empty actionData -> clear prior actionData due to an action error
 | |
|         actionData = null;
 | |
|       }
 | |
|     } else if (isActionReload) {
 | |
|       // Keep the current data if we're wrapping up the action reload
 | |
|       actionData = state.actionData;
 | |
|     } else {
 | |
|       // Clear actionData on any other completed navigations
 | |
|       actionData = null;
 | |
|     }
 | |
| 
 | |
|     // Always preserve any existing loaderData from re-used routes
 | |
|     let loaderData = newState.loaderData ? mergeLoaderData(state.loaderData, newState.loaderData, newState.matches || [], newState.errors) : state.loaderData;
 | |
| 
 | |
|     // On a successful navigation we can assume we got through all blockers
 | |
|     // so we can start fresh
 | |
|     let blockers = state.blockers;
 | |
|     if (blockers.size > 0) {
 | |
|       blockers = new Map(blockers);
 | |
|       blockers.forEach((_, k) => blockers.set(k, IDLE_BLOCKER));
 | |
|     }
 | |
| 
 | |
|     // Always respect the user flag.  Otherwise don't reset on mutation
 | |
|     // submission navigations unless they redirect
 | |
|     let preventScrollReset = pendingPreventScrollReset === true || state.navigation.formMethod != null && isMutationMethod(state.navigation.formMethod) && ((_location$state2 = location.state) == null ? void 0 : _location$state2._isRedirect) !== true;
 | |
| 
 | |
|     // Commit any in-flight routes at the end of the HMR revalidation "navigation"
 | |
|     if (inFlightDataRoutes) {
 | |
|       dataRoutes = inFlightDataRoutes;
 | |
|       inFlightDataRoutes = undefined;
 | |
|     }
 | |
|     if (isUninterruptedRevalidation) ; else if (pendingAction === Action.Pop) ; else if (pendingAction === Action.Push) {
 | |
|       init.history.push(location, location.state);
 | |
|     } else if (pendingAction === Action.Replace) {
 | |
|       init.history.replace(location, location.state);
 | |
|     }
 | |
|     let viewTransitionOpts;
 | |
| 
 | |
|     // On POP, enable transitions if they were enabled on the original navigation
 | |
|     if (pendingAction === Action.Pop) {
 | |
|       // Forward takes precedence so they behave like the original navigation
 | |
|       let priorPaths = appliedViewTransitions.get(state.location.pathname);
 | |
|       if (priorPaths && priorPaths.has(location.pathname)) {
 | |
|         viewTransitionOpts = {
 | |
|           currentLocation: state.location,
 | |
|           nextLocation: location
 | |
|         };
 | |
|       } else if (appliedViewTransitions.has(location.pathname)) {
 | |
|         // If we don't have a previous forward nav, assume we're popping back to
 | |
|         // the new location and enable if that location previously enabled
 | |
|         viewTransitionOpts = {
 | |
|           currentLocation: location,
 | |
|           nextLocation: state.location
 | |
|         };
 | |
|       }
 | |
|     } else if (pendingViewTransitionEnabled) {
 | |
|       // Store the applied transition on PUSH/REPLACE
 | |
|       let toPaths = appliedViewTransitions.get(state.location.pathname);
 | |
|       if (toPaths) {
 | |
|         toPaths.add(location.pathname);
 | |
|       } else {
 | |
|         toPaths = new Set([location.pathname]);
 | |
|         appliedViewTransitions.set(state.location.pathname, toPaths);
 | |
|       }
 | |
|       viewTransitionOpts = {
 | |
|         currentLocation: state.location,
 | |
|         nextLocation: location
 | |
|       };
 | |
|     }
 | |
|     updateState(_extends({}, newState, {
 | |
|       // matches, errors, fetchers go through as-is
 | |
|       actionData,
 | |
|       loaderData,
 | |
|       historyAction: pendingAction,
 | |
|       location,
 | |
|       initialized: true,
 | |
|       navigation: IDLE_NAVIGATION,
 | |
|       revalidation: "idle",
 | |
|       restoreScrollPosition: getSavedScrollPosition(location, newState.matches || state.matches),
 | |
|       preventScrollReset,
 | |
|       blockers
 | |
|     }), {
 | |
|       viewTransitionOpts,
 | |
|       flushSync: flushSync === true
 | |
|     });
 | |
| 
 | |
|     // Reset stateful navigation vars
 | |
|     pendingAction = Action.Pop;
 | |
|     pendingPreventScrollReset = false;
 | |
|     pendingViewTransitionEnabled = false;
 | |
|     isUninterruptedRevalidation = false;
 | |
|     isRevalidationRequired = false;
 | |
|     cancelledDeferredRoutes = [];
 | |
|   }
 | |
| 
 | |
|   // Trigger a navigation event, which can either be a numerical POP or a PUSH
 | |
|   // replace with an optional submission
 | |
|   async function navigate(to, opts) {
 | |
|     if (typeof to === "number") {
 | |
|       init.history.go(to);
 | |
|       return;
 | |
|     }
 | |
|     let normalizedPath = normalizeTo(state.location, state.matches, basename, future.v7_prependBasename, to, future.v7_relativeSplatPath, opts == null ? void 0 : opts.fromRouteId, opts == null ? void 0 : opts.relative);
 | |
|     let {
 | |
|       path,
 | |
|       submission,
 | |
|       error
 | |
|     } = normalizeNavigateOptions(future.v7_normalizeFormMethod, false, normalizedPath, opts);
 | |
|     let currentLocation = state.location;
 | |
|     let nextLocation = createLocation(state.location, path, opts && opts.state);
 | |
| 
 | |
|     // When using navigate as a PUSH/REPLACE we aren't reading an already-encoded
 | |
|     // URL from window.location, so we need to encode it here so the behavior
 | |
|     // remains the same as POP and non-data-router usages.  new URL() does all
 | |
|     // the same encoding we'd get from a history.pushState/window.location read
 | |
|     // without having to touch history
 | |
|     nextLocation = _extends({}, nextLocation, init.history.encodeLocation(nextLocation));
 | |
|     let userReplace = opts && opts.replace != null ? opts.replace : undefined;
 | |
|     let historyAction = Action.Push;
 | |
|     if (userReplace === true) {
 | |
|       historyAction = Action.Replace;
 | |
|     } else if (userReplace === false) ; else if (submission != null && isMutationMethod(submission.formMethod) && submission.formAction === state.location.pathname + state.location.search) {
 | |
|       // By default on submissions to the current location we REPLACE so that
 | |
|       // users don't have to double-click the back button to get to the prior
 | |
|       // location.  If the user redirects to a different location from the
 | |
|       // action/loader this will be ignored and the redirect will be a PUSH
 | |
|       historyAction = Action.Replace;
 | |
|     }
 | |
|     let preventScrollReset = opts && "preventScrollReset" in opts ? opts.preventScrollReset === true : undefined;
 | |
|     let flushSync = (opts && opts.flushSync) === true;
 | |
|     let blockerKey = shouldBlockNavigation({
 | |
|       currentLocation,
 | |
|       nextLocation,
 | |
|       historyAction
 | |
|     });
 | |
|     if (blockerKey) {
 | |
|       // Put the blocker into a blocked state
 | |
|       updateBlocker(blockerKey, {
 | |
|         state: "blocked",
 | |
|         location: nextLocation,
 | |
|         proceed() {
 | |
|           updateBlocker(blockerKey, {
 | |
|             state: "proceeding",
 | |
|             proceed: undefined,
 | |
|             reset: undefined,
 | |
|             location: nextLocation
 | |
|           });
 | |
|           // Send the same navigation through
 | |
|           navigate(to, opts);
 | |
|         },
 | |
|         reset() {
 | |
|           let blockers = new Map(state.blockers);
 | |
|           blockers.set(blockerKey, IDLE_BLOCKER);
 | |
|           updateState({
 | |
|             blockers
 | |
|           });
 | |
|         }
 | |
|       });
 | |
|       return;
 | |
|     }
 | |
|     return await startNavigation(historyAction, nextLocation, {
 | |
|       submission,
 | |
|       // Send through the formData serialization error if we have one so we can
 | |
|       // render at the right error boundary after we match routes
 | |
|       pendingError: error,
 | |
|       preventScrollReset,
 | |
|       replace: opts && opts.replace,
 | |
|       enableViewTransition: opts && opts.viewTransition,
 | |
|       flushSync
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   // Revalidate all current loaders.  If a navigation is in progress or if this
 | |
|   // is interrupted by a navigation, allow this to "succeed" by calling all
 | |
|   // loaders during the next loader round
 | |
|   function revalidate() {
 | |
|     interruptActiveLoads();
 | |
|     updateState({
 | |
|       revalidation: "loading"
 | |
|     });
 | |
| 
 | |
|     // If we're currently submitting an action, we don't need to start a new
 | |
|     // navigation, we'll just let the follow up loader execution call all loaders
 | |
|     if (state.navigation.state === "submitting") {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // If we're currently in an idle state, start a new navigation for the current
 | |
|     // action/location and mark it as uninterrupted, which will skip the history
 | |
|     // update in completeNavigation
 | |
|     if (state.navigation.state === "idle") {
 | |
|       startNavigation(state.historyAction, state.location, {
 | |
|         startUninterruptedRevalidation: true
 | |
|       });
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // Otherwise, if we're currently in a loading state, just start a new
 | |
|     // navigation to the navigation.location but do not trigger an uninterrupted
 | |
|     // revalidation so that history correctly updates once the navigation completes
 | |
|     startNavigation(pendingAction || state.historyAction, state.navigation.location, {
 | |
|       overrideNavigation: state.navigation,
 | |
|       // Proxy through any rending view transition
 | |
|       enableViewTransition: pendingViewTransitionEnabled === true
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   // Start a navigation to the given action/location.  Can optionally provide a
 | |
|   // overrideNavigation which will override the normalLoad in the case of a redirect
 | |
|   // navigation
 | |
|   async function startNavigation(historyAction, location, opts) {
 | |
|     // Abort any in-progress navigations and start a new one. Unset any ongoing
 | |
|     // uninterrupted revalidations unless told otherwise, since we want this
 | |
|     // new navigation to update history normally
 | |
|     pendingNavigationController && pendingNavigationController.abort();
 | |
|     pendingNavigationController = null;
 | |
|     pendingAction = historyAction;
 | |
|     isUninterruptedRevalidation = (opts && opts.startUninterruptedRevalidation) === true;
 | |
| 
 | |
|     // Save the current scroll position every time we start a new navigation,
 | |
|     // and track whether we should reset scroll on completion
 | |
|     saveScrollPosition(state.location, state.matches);
 | |
|     pendingPreventScrollReset = (opts && opts.preventScrollReset) === true;
 | |
|     pendingViewTransitionEnabled = (opts && opts.enableViewTransition) === true;
 | |
|     let routesToUse = inFlightDataRoutes || dataRoutes;
 | |
|     let loadingNavigation = opts && opts.overrideNavigation;
 | |
|     let matches = opts != null && opts.initialHydration && state.matches && state.matches.length > 0 && !initialMatchesIsFOW ?
 | |
|     // `matchRoutes()` has already been called if we're in here via `router.initialize()`
 | |
|     state.matches : matchRoutes(routesToUse, location, basename);
 | |
|     let flushSync = (opts && opts.flushSync) === true;
 | |
| 
 | |
|     // Short circuit if it's only a hash change and not a revalidation or
 | |
|     // mutation submission.
 | |
|     //
 | |
|     // Ignore on initial page loads because since the initial hydration will always
 | |
|     // be "same hash".  For example, on /page#hash and submit a <Form method="post">
 | |
|     // which will default to a navigation to /page
 | |
|     if (matches && state.initialized && !isRevalidationRequired && isHashChangeOnly(state.location, location) && !(opts && opts.submission && isMutationMethod(opts.submission.formMethod))) {
 | |
|       completeNavigation(location, {
 | |
|         matches
 | |
|       }, {
 | |
|         flushSync
 | |
|       });
 | |
|       return;
 | |
|     }
 | |
|     let fogOfWar = checkFogOfWar(matches, routesToUse, location.pathname);
 | |
|     if (fogOfWar.active && fogOfWar.matches) {
 | |
|       matches = fogOfWar.matches;
 | |
|     }
 | |
| 
 | |
|     // Short circuit with a 404 on the root error boundary if we match nothing
 | |
|     if (!matches) {
 | |
|       let {
 | |
|         error,
 | |
|         notFoundMatches,
 | |
|         route
 | |
|       } = handleNavigational404(location.pathname);
 | |
|       completeNavigation(location, {
 | |
|         matches: notFoundMatches,
 | |
|         loaderData: {},
 | |
|         errors: {
 | |
|           [route.id]: error
 | |
|         }
 | |
|       }, {
 | |
|         flushSync
 | |
|       });
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // Create a controller/Request for this navigation
 | |
|     pendingNavigationController = new AbortController();
 | |
|     let request = createClientSideRequest(init.history, location, pendingNavigationController.signal, opts && opts.submission);
 | |
|     let pendingActionResult;
 | |
|     if (opts && opts.pendingError) {
 | |
|       // If we have a pendingError, it means the user attempted a GET submission
 | |
|       // with binary FormData so assign here and skip to handleLoaders.  That
 | |
|       // way we handle calling loaders above the boundary etc.  It's not really
 | |
|       // different from an actionError in that sense.
 | |
|       pendingActionResult = [findNearestBoundary(matches).route.id, {
 | |
|         type: ResultType.error,
 | |
|         error: opts.pendingError
 | |
|       }];
 | |
|     } else if (opts && opts.submission && isMutationMethod(opts.submission.formMethod)) {
 | |
|       // Call action if we received an action submission
 | |
|       let actionResult = await handleAction(request, location, opts.submission, matches, fogOfWar.active, {
 | |
|         replace: opts.replace,
 | |
|         flushSync
 | |
|       });
 | |
|       if (actionResult.shortCircuited) {
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       // If we received a 404 from handleAction, it's because we couldn't lazily
 | |
|       // discover the destination route so we don't want to call loaders
 | |
|       if (actionResult.pendingActionResult) {
 | |
|         let [routeId, result] = actionResult.pendingActionResult;
 | |
|         if (isErrorResult(result) && isRouteErrorResponse(result.error) && result.error.status === 404) {
 | |
|           pendingNavigationController = null;
 | |
|           completeNavigation(location, {
 | |
|             matches: actionResult.matches,
 | |
|             loaderData: {},
 | |
|             errors: {
 | |
|               [routeId]: result.error
 | |
|             }
 | |
|           });
 | |
|           return;
 | |
|         }
 | |
|       }
 | |
|       matches = actionResult.matches || matches;
 | |
|       pendingActionResult = actionResult.pendingActionResult;
 | |
|       loadingNavigation = getLoadingNavigation(location, opts.submission);
 | |
|       flushSync = false;
 | |
|       // No need to do fog of war matching again on loader execution
 | |
|       fogOfWar.active = false;
 | |
| 
 | |
|       // Create a GET request for the loaders
 | |
|       request = createClientSideRequest(init.history, request.url, request.signal);
 | |
|     }
 | |
| 
 | |
|     // Call loaders
 | |
|     let {
 | |
|       shortCircuited,
 | |
|       matches: updatedMatches,
 | |
|       loaderData,
 | |
|       errors
 | |
|     } = await handleLoaders(request, location, matches, fogOfWar.active, loadingNavigation, opts && opts.submission, opts && opts.fetcherSubmission, opts && opts.replace, opts && opts.initialHydration === true, flushSync, pendingActionResult);
 | |
|     if (shortCircuited) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // Clean up now that the action/loaders have completed.  Don't clean up if
 | |
|     // we short circuited because pendingNavigationController will have already
 | |
|     // been assigned to a new controller for the next navigation
 | |
|     pendingNavigationController = null;
 | |
|     completeNavigation(location, _extends({
 | |
|       matches: updatedMatches || matches
 | |
|     }, getActionDataForCommit(pendingActionResult), {
 | |
|       loaderData,
 | |
|       errors
 | |
|     }));
 | |
|   }
 | |
| 
 | |
|   // Call the action matched by the leaf route for this navigation and handle
 | |
|   // redirects/errors
 | |
|   async function handleAction(request, location, submission, matches, isFogOfWar, opts) {
 | |
|     if (opts === void 0) {
 | |
|       opts = {};
 | |
|     }
 | |
|     interruptActiveLoads();
 | |
| 
 | |
|     // Put us in a submitting state
 | |
|     let navigation = getSubmittingNavigation(location, submission);
 | |
|     updateState({
 | |
|       navigation
 | |
|     }, {
 | |
|       flushSync: opts.flushSync === true
 | |
|     });
 | |
|     if (isFogOfWar) {
 | |
|       let discoverResult = await discoverRoutes(matches, location.pathname, request.signal);
 | |
|       if (discoverResult.type === "aborted") {
 | |
|         return {
 | |
|           shortCircuited: true
 | |
|         };
 | |
|       } else if (discoverResult.type === "error") {
 | |
|         let boundaryId = findNearestBoundary(discoverResult.partialMatches).route.id;
 | |
|         return {
 | |
|           matches: discoverResult.partialMatches,
 | |
|           pendingActionResult: [boundaryId, {
 | |
|             type: ResultType.error,
 | |
|             error: discoverResult.error
 | |
|           }]
 | |
|         };
 | |
|       } else if (!discoverResult.matches) {
 | |
|         let {
 | |
|           notFoundMatches,
 | |
|           error,
 | |
|           route
 | |
|         } = handleNavigational404(location.pathname);
 | |
|         return {
 | |
|           matches: notFoundMatches,
 | |
|           pendingActionResult: [route.id, {
 | |
|             type: ResultType.error,
 | |
|             error
 | |
|           }]
 | |
|         };
 | |
|       } else {
 | |
|         matches = discoverResult.matches;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // Call our action and get the result
 | |
|     let result;
 | |
|     let actionMatch = getTargetMatch(matches, location);
 | |
|     if (!actionMatch.route.action && !actionMatch.route.lazy) {
 | |
|       result = {
 | |
|         type: ResultType.error,
 | |
|         error: getInternalRouterError(405, {
 | |
|           method: request.method,
 | |
|           pathname: location.pathname,
 | |
|           routeId: actionMatch.route.id
 | |
|         })
 | |
|       };
 | |
|     } else {
 | |
|       let results = await callDataStrategy("action", state, request, [actionMatch], matches, null);
 | |
|       result = results[actionMatch.route.id];
 | |
|       if (request.signal.aborted) {
 | |
|         return {
 | |
|           shortCircuited: true
 | |
|         };
 | |
|       }
 | |
|     }
 | |
|     if (isRedirectResult(result)) {
 | |
|       let replace;
 | |
|       if (opts && opts.replace != null) {
 | |
|         replace = opts.replace;
 | |
|       } else {
 | |
|         // If the user didn't explicity indicate replace behavior, replace if
 | |
|         // we redirected to the exact same location we're currently at to avoid
 | |
|         // double back-buttons
 | |
|         let location = normalizeRedirectLocation(result.response.headers.get("Location"), new URL(request.url), basename);
 | |
|         replace = location === state.location.pathname + state.location.search;
 | |
|       }
 | |
|       await startRedirectNavigation(request, result, true, {
 | |
|         submission,
 | |
|         replace
 | |
|       });
 | |
|       return {
 | |
|         shortCircuited: true
 | |
|       };
 | |
|     }
 | |
|     if (isDeferredResult(result)) {
 | |
|       throw getInternalRouterError(400, {
 | |
|         type: "defer-action"
 | |
|       });
 | |
|     }
 | |
|     if (isErrorResult(result)) {
 | |
|       // Store off the pending error - we use it to determine which loaders
 | |
|       // to call and will commit it when we complete the navigation
 | |
|       let boundaryMatch = findNearestBoundary(matches, actionMatch.route.id);
 | |
| 
 | |
|       // By default, all submissions to the current location are REPLACE
 | |
|       // navigations, but if the action threw an error that'll be rendered in
 | |
|       // an errorElement, we fall back to PUSH so that the user can use the
 | |
|       // back button to get back to the pre-submission form location to try
 | |
|       // again
 | |
|       if ((opts && opts.replace) !== true) {
 | |
|         pendingAction = Action.Push;
 | |
|       }
 | |
|       return {
 | |
|         matches,
 | |
|         pendingActionResult: [boundaryMatch.route.id, result]
 | |
|       };
 | |
|     }
 | |
|     return {
 | |
|       matches,
 | |
|       pendingActionResult: [actionMatch.route.id, result]
 | |
|     };
 | |
|   }
 | |
| 
 | |
|   // Call all applicable loaders for the given matches, handling redirects,
 | |
|   // errors, etc.
 | |
|   async function handleLoaders(request, location, matches, isFogOfWar, overrideNavigation, submission, fetcherSubmission, replace, initialHydration, flushSync, pendingActionResult) {
 | |
|     // Figure out the right navigation we want to use for data loading
 | |
|     let loadingNavigation = overrideNavigation || getLoadingNavigation(location, submission);
 | |
| 
 | |
|     // If this was a redirect from an action we don't have a "submission" but
 | |
|     // we have it on the loading navigation so use that if available
 | |
|     let activeSubmission = submission || fetcherSubmission || getSubmissionFromNavigation(loadingNavigation);
 | |
| 
 | |
|     // If this is an uninterrupted revalidation, we remain in our current idle
 | |
|     // state.  If not, we need to switch to our loading state and load data,
 | |
|     // preserving any new action data or existing action data (in the case of
 | |
|     // a revalidation interrupting an actionReload)
 | |
|     // If we have partialHydration enabled, then don't update the state for the
 | |
|     // initial data load since it's not a "navigation"
 | |
|     let shouldUpdateNavigationState = !isUninterruptedRevalidation && (!future.v7_partialHydration || !initialHydration);
 | |
| 
 | |
|     // When fog of war is enabled, we enter our `loading` state earlier so we
 | |
|     // can discover new routes during the `loading` state.  We skip this if
 | |
|     // we've already run actions since we would have done our matching already.
 | |
|     // If the children() function threw then, we want to proceed with the
 | |
|     // partial matches it discovered.
 | |
|     if (isFogOfWar) {
 | |
|       if (shouldUpdateNavigationState) {
 | |
|         let actionData = getUpdatedActionData(pendingActionResult);
 | |
|         updateState(_extends({
 | |
|           navigation: loadingNavigation
 | |
|         }, actionData !== undefined ? {
 | |
|           actionData
 | |
|         } : {}), {
 | |
|           flushSync
 | |
|         });
 | |
|       }
 | |
|       let discoverResult = await discoverRoutes(matches, location.pathname, request.signal);
 | |
|       if (discoverResult.type === "aborted") {
 | |
|         return {
 | |
|           shortCircuited: true
 | |
|         };
 | |
|       } else if (discoverResult.type === "error") {
 | |
|         let boundaryId = findNearestBoundary(discoverResult.partialMatches).route.id;
 | |
|         return {
 | |
|           matches: discoverResult.partialMatches,
 | |
|           loaderData: {},
 | |
|           errors: {
 | |
|             [boundaryId]: discoverResult.error
 | |
|           }
 | |
|         };
 | |
|       } else if (!discoverResult.matches) {
 | |
|         let {
 | |
|           error,
 | |
|           notFoundMatches,
 | |
|           route
 | |
|         } = handleNavigational404(location.pathname);
 | |
|         return {
 | |
|           matches: notFoundMatches,
 | |
|           loaderData: {},
 | |
|           errors: {
 | |
|             [route.id]: error
 | |
|           }
 | |
|         };
 | |
|       } else {
 | |
|         matches = discoverResult.matches;
 | |
|       }
 | |
|     }
 | |
|     let routesToUse = inFlightDataRoutes || dataRoutes;
 | |
|     let [matchesToLoad, revalidatingFetchers] = getMatchesToLoad(init.history, state, matches, activeSubmission, location, future.v7_partialHydration && initialHydration === true, future.v7_skipActionErrorRevalidation, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, deletedFetchers, fetchLoadMatches, fetchRedirectIds, routesToUse, basename, pendingActionResult);
 | |
| 
 | |
|     // Cancel pending deferreds for no-longer-matched routes or routes we're
 | |
|     // about to reload.  Note that if this is an action reload we would have
 | |
|     // already cancelled all pending deferreds so this would be a no-op
 | |
|     cancelActiveDeferreds(routeId => !(matches && matches.some(m => m.route.id === routeId)) || matchesToLoad && matchesToLoad.some(m => m.route.id === routeId));
 | |
|     pendingNavigationLoadId = ++incrementingLoadId;
 | |
| 
 | |
|     // Short circuit if we have no loaders to run
 | |
|     if (matchesToLoad.length === 0 && revalidatingFetchers.length === 0) {
 | |
|       let updatedFetchers = markFetchRedirectsDone();
 | |
|       completeNavigation(location, _extends({
 | |
|         matches,
 | |
|         loaderData: {},
 | |
|         // Commit pending error if we're short circuiting
 | |
|         errors: pendingActionResult && isErrorResult(pendingActionResult[1]) ? {
 | |
|           [pendingActionResult[0]]: pendingActionResult[1].error
 | |
|         } : null
 | |
|       }, getActionDataForCommit(pendingActionResult), updatedFetchers ? {
 | |
|         fetchers: new Map(state.fetchers)
 | |
|       } : {}), {
 | |
|         flushSync
 | |
|       });
 | |
|       return {
 | |
|         shortCircuited: true
 | |
|       };
 | |
|     }
 | |
|     if (shouldUpdateNavigationState) {
 | |
|       let updates = {};
 | |
|       if (!isFogOfWar) {
 | |
|         // Only update navigation/actionNData if we didn't already do it above
 | |
|         updates.navigation = loadingNavigation;
 | |
|         let actionData = getUpdatedActionData(pendingActionResult);
 | |
|         if (actionData !== undefined) {
 | |
|           updates.actionData = actionData;
 | |
|         }
 | |
|       }
 | |
|       if (revalidatingFetchers.length > 0) {
 | |
|         updates.fetchers = getUpdatedRevalidatingFetchers(revalidatingFetchers);
 | |
|       }
 | |
|       updateState(updates, {
 | |
|         flushSync
 | |
|       });
 | |
|     }
 | |
|     revalidatingFetchers.forEach(rf => {
 | |
|       abortFetcher(rf.key);
 | |
|       if (rf.controller) {
 | |
|         // Fetchers use an independent AbortController so that aborting a fetcher
 | |
|         // (via deleteFetcher) does not abort the triggering navigation that
 | |
|         // triggered the revalidation
 | |
|         fetchControllers.set(rf.key, rf.controller);
 | |
|       }
 | |
|     });
 | |
| 
 | |
|     // Proxy navigation abort through to revalidation fetchers
 | |
|     let abortPendingFetchRevalidations = () => revalidatingFetchers.forEach(f => abortFetcher(f.key));
 | |
|     if (pendingNavigationController) {
 | |
|       pendingNavigationController.signal.addEventListener("abort", abortPendingFetchRevalidations);
 | |
|     }
 | |
|     let {
 | |
|       loaderResults,
 | |
|       fetcherResults
 | |
|     } = await callLoadersAndMaybeResolveData(state, matches, matchesToLoad, revalidatingFetchers, request);
 | |
|     if (request.signal.aborted) {
 | |
|       return {
 | |
|         shortCircuited: true
 | |
|       };
 | |
|     }
 | |
| 
 | |
|     // Clean up _after_ loaders have completed.  Don't clean up if we short
 | |
|     // circuited because fetchControllers would have been aborted and
 | |
|     // reassigned to new controllers for the next navigation
 | |
|     if (pendingNavigationController) {
 | |
|       pendingNavigationController.signal.removeEventListener("abort", abortPendingFetchRevalidations);
 | |
|     }
 | |
|     revalidatingFetchers.forEach(rf => fetchControllers.delete(rf.key));
 | |
| 
 | |
|     // If any loaders returned a redirect Response, start a new REPLACE navigation
 | |
|     let redirect = findRedirect(loaderResults);
 | |
|     if (redirect) {
 | |
|       await startRedirectNavigation(request, redirect.result, true, {
 | |
|         replace
 | |
|       });
 | |
|       return {
 | |
|         shortCircuited: true
 | |
|       };
 | |
|     }
 | |
|     redirect = findRedirect(fetcherResults);
 | |
|     if (redirect) {
 | |
|       // If this redirect came from a fetcher make sure we mark it in
 | |
|       // fetchRedirectIds so it doesn't get revalidated on the next set of
 | |
|       // loader executions
 | |
|       fetchRedirectIds.add(redirect.key);
 | |
|       await startRedirectNavigation(request, redirect.result, true, {
 | |
|         replace
 | |
|       });
 | |
|       return {
 | |
|         shortCircuited: true
 | |
|       };
 | |
|     }
 | |
| 
 | |
|     // Process and commit output from loaders
 | |
|     let {
 | |
|       loaderData,
 | |
|       errors
 | |
|     } = processLoaderData(state, matches, loaderResults, pendingActionResult, revalidatingFetchers, fetcherResults, activeDeferreds);
 | |
| 
 | |
|     // Wire up subscribers to update loaderData as promises settle
 | |
|     activeDeferreds.forEach((deferredData, routeId) => {
 | |
|       deferredData.subscribe(aborted => {
 | |
|         // Note: No need to updateState here since the TrackedPromise on
 | |
|         // loaderData is stable across resolve/reject
 | |
|         // Remove this instance if we were aborted or if promises have settled
 | |
|         if (aborted || deferredData.done) {
 | |
|           activeDeferreds.delete(routeId);
 | |
|         }
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     // Preserve SSR errors during partial hydration
 | |
|     if (future.v7_partialHydration && initialHydration && state.errors) {
 | |
|       errors = _extends({}, state.errors, errors);
 | |
|     }
 | |
|     let updatedFetchers = markFetchRedirectsDone();
 | |
|     let didAbortFetchLoads = abortStaleFetchLoads(pendingNavigationLoadId);
 | |
|     let shouldUpdateFetchers = updatedFetchers || didAbortFetchLoads || revalidatingFetchers.length > 0;
 | |
|     return _extends({
 | |
|       matches,
 | |
|       loaderData,
 | |
|       errors
 | |
|     }, shouldUpdateFetchers ? {
 | |
|       fetchers: new Map(state.fetchers)
 | |
|     } : {});
 | |
|   }
 | |
|   function getUpdatedActionData(pendingActionResult) {
 | |
|     if (pendingActionResult && !isErrorResult(pendingActionResult[1])) {
 | |
|       // This is cast to `any` currently because `RouteData`uses any and it
 | |
|       // would be a breaking change to use any.
 | |
|       // TODO: v7 - change `RouteData` to use `unknown` instead of `any`
 | |
|       return {
 | |
|         [pendingActionResult[0]]: pendingActionResult[1].data
 | |
|       };
 | |
|     } else if (state.actionData) {
 | |
|       if (Object.keys(state.actionData).length === 0) {
 | |
|         return null;
 | |
|       } else {
 | |
|         return state.actionData;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   function getUpdatedRevalidatingFetchers(revalidatingFetchers) {
 | |
|     revalidatingFetchers.forEach(rf => {
 | |
|       let fetcher = state.fetchers.get(rf.key);
 | |
|       let revalidatingFetcher = getLoadingFetcher(undefined, fetcher ? fetcher.data : undefined);
 | |
|       state.fetchers.set(rf.key, revalidatingFetcher);
 | |
|     });
 | |
|     return new Map(state.fetchers);
 | |
|   }
 | |
| 
 | |
|   // Trigger a fetcher load/submit for the given fetcher key
 | |
|   function fetch(key, routeId, href, opts) {
 | |
|     if (isServer) {
 | |
|       throw new Error("router.fetch() was called during the server render, but it shouldn't be. " + "You are likely calling a useFetcher() method in the body of your component. " + "Try moving it to a useEffect or a callback.");
 | |
|     }
 | |
|     abortFetcher(key);
 | |
|     let flushSync = (opts && opts.flushSync) === true;
 | |
|     let routesToUse = inFlightDataRoutes || dataRoutes;
 | |
|     let normalizedPath = normalizeTo(state.location, state.matches, basename, future.v7_prependBasename, href, future.v7_relativeSplatPath, routeId, opts == null ? void 0 : opts.relative);
 | |
|     let matches = matchRoutes(routesToUse, normalizedPath, basename);
 | |
|     let fogOfWar = checkFogOfWar(matches, routesToUse, normalizedPath);
 | |
|     if (fogOfWar.active && fogOfWar.matches) {
 | |
|       matches = fogOfWar.matches;
 | |
|     }
 | |
|     if (!matches) {
 | |
|       setFetcherError(key, routeId, getInternalRouterError(404, {
 | |
|         pathname: normalizedPath
 | |
|       }), {
 | |
|         flushSync
 | |
|       });
 | |
|       return;
 | |
|     }
 | |
|     let {
 | |
|       path,
 | |
|       submission,
 | |
|       error
 | |
|     } = normalizeNavigateOptions(future.v7_normalizeFormMethod, true, normalizedPath, opts);
 | |
|     if (error) {
 | |
|       setFetcherError(key, routeId, error, {
 | |
|         flushSync
 | |
|       });
 | |
|       return;
 | |
|     }
 | |
|     let match = getTargetMatch(matches, path);
 | |
|     let preventScrollReset = (opts && opts.preventScrollReset) === true;
 | |
|     if (submission && isMutationMethod(submission.formMethod)) {
 | |
|       handleFetcherAction(key, routeId, path, match, matches, fogOfWar.active, flushSync, preventScrollReset, submission);
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // Store off the match so we can call it's shouldRevalidate on subsequent
 | |
|     // revalidations
 | |
|     fetchLoadMatches.set(key, {
 | |
|       routeId,
 | |
|       path
 | |
|     });
 | |
|     handleFetcherLoader(key, routeId, path, match, matches, fogOfWar.active, flushSync, preventScrollReset, submission);
 | |
|   }
 | |
| 
 | |
|   // Call the action for the matched fetcher.submit(), and then handle redirects,
 | |
|   // errors, and revalidation
 | |
|   async function handleFetcherAction(key, routeId, path, match, requestMatches, isFogOfWar, flushSync, preventScrollReset, submission) {
 | |
|     interruptActiveLoads();
 | |
|     fetchLoadMatches.delete(key);
 | |
|     function detectAndHandle405Error(m) {
 | |
|       if (!m.route.action && !m.route.lazy) {
 | |
|         let error = getInternalRouterError(405, {
 | |
|           method: submission.formMethod,
 | |
|           pathname: path,
 | |
|           routeId: routeId
 | |
|         });
 | |
|         setFetcherError(key, routeId, error, {
 | |
|           flushSync
 | |
|         });
 | |
|         return true;
 | |
|       }
 | |
|       return false;
 | |
|     }
 | |
|     if (!isFogOfWar && detectAndHandle405Error(match)) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // Put this fetcher into it's submitting state
 | |
|     let existingFetcher = state.fetchers.get(key);
 | |
|     updateFetcherState(key, getSubmittingFetcher(submission, existingFetcher), {
 | |
|       flushSync
 | |
|     });
 | |
|     let abortController = new AbortController();
 | |
|     let fetchRequest = createClientSideRequest(init.history, path, abortController.signal, submission);
 | |
|     if (isFogOfWar) {
 | |
|       let discoverResult = await discoverRoutes(requestMatches, new URL(fetchRequest.url).pathname, fetchRequest.signal, key);
 | |
|       if (discoverResult.type === "aborted") {
 | |
|         return;
 | |
|       } else if (discoverResult.type === "error") {
 | |
|         setFetcherError(key, routeId, discoverResult.error, {
 | |
|           flushSync
 | |
|         });
 | |
|         return;
 | |
|       } else if (!discoverResult.matches) {
 | |
|         setFetcherError(key, routeId, getInternalRouterError(404, {
 | |
|           pathname: path
 | |
|         }), {
 | |
|           flushSync
 | |
|         });
 | |
|         return;
 | |
|       } else {
 | |
|         requestMatches = discoverResult.matches;
 | |
|         match = getTargetMatch(requestMatches, path);
 | |
|         if (detectAndHandle405Error(match)) {
 | |
|           return;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // Call the action for the fetcher
 | |
|     fetchControllers.set(key, abortController);
 | |
|     let originatingLoadId = incrementingLoadId;
 | |
|     let actionResults = await callDataStrategy("action", state, fetchRequest, [match], requestMatches, key);
 | |
|     let actionResult = actionResults[match.route.id];
 | |
|     if (fetchRequest.signal.aborted) {
 | |
|       // We can delete this so long as we weren't aborted by our own fetcher
 | |
|       // re-submit which would have put _new_ controller is in fetchControllers
 | |
|       if (fetchControllers.get(key) === abortController) {
 | |
|         fetchControllers.delete(key);
 | |
|       }
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // When using v7_fetcherPersist, we don't want errors bubbling up to the UI
 | |
|     // or redirects processed for unmounted fetchers so we just revert them to
 | |
|     // idle
 | |
|     if (future.v7_fetcherPersist && deletedFetchers.has(key)) {
 | |
|       if (isRedirectResult(actionResult) || isErrorResult(actionResult)) {
 | |
|         updateFetcherState(key, getDoneFetcher(undefined));
 | |
|         return;
 | |
|       }
 | |
|       // Let SuccessResult's fall through for revalidation
 | |
|     } else {
 | |
|       if (isRedirectResult(actionResult)) {
 | |
|         fetchControllers.delete(key);
 | |
|         if (pendingNavigationLoadId > originatingLoadId) {
 | |
|           // A new navigation was kicked off after our action started, so that
 | |
|           // should take precedence over this redirect navigation.  We already
 | |
|           // set isRevalidationRequired so all loaders for the new route should
 | |
|           // fire unless opted out via shouldRevalidate
 | |
|           updateFetcherState(key, getDoneFetcher(undefined));
 | |
|           return;
 | |
|         } else {
 | |
|           fetchRedirectIds.add(key);
 | |
|           updateFetcherState(key, getLoadingFetcher(submission));
 | |
|           return startRedirectNavigation(fetchRequest, actionResult, false, {
 | |
|             fetcherSubmission: submission,
 | |
|             preventScrollReset
 | |
|           });
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       // Process any non-redirect errors thrown
 | |
|       if (isErrorResult(actionResult)) {
 | |
|         setFetcherError(key, routeId, actionResult.error);
 | |
|         return;
 | |
|       }
 | |
|     }
 | |
|     if (isDeferredResult(actionResult)) {
 | |
|       throw getInternalRouterError(400, {
 | |
|         type: "defer-action"
 | |
|       });
 | |
|     }
 | |
| 
 | |
|     // Start the data load for current matches, or the next location if we're
 | |
|     // in the middle of a navigation
 | |
|     let nextLocation = state.navigation.location || state.location;
 | |
|     let revalidationRequest = createClientSideRequest(init.history, nextLocation, abortController.signal);
 | |
|     let routesToUse = inFlightDataRoutes || dataRoutes;
 | |
|     let matches = state.navigation.state !== "idle" ? matchRoutes(routesToUse, state.navigation.location, basename) : state.matches;
 | |
|     invariant(matches, "Didn't find any matches after fetcher action");
 | |
|     let loadId = ++incrementingLoadId;
 | |
|     fetchReloadIds.set(key, loadId);
 | |
|     let loadFetcher = getLoadingFetcher(submission, actionResult.data);
 | |
|     state.fetchers.set(key, loadFetcher);
 | |
|     let [matchesToLoad, revalidatingFetchers] = getMatchesToLoad(init.history, state, matches, submission, nextLocation, false, future.v7_skipActionErrorRevalidation, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, deletedFetchers, fetchLoadMatches, fetchRedirectIds, routesToUse, basename, [match.route.id, actionResult]);
 | |
| 
 | |
|     // Put all revalidating fetchers into the loading state, except for the
 | |
|     // current fetcher which we want to keep in it's current loading state which
 | |
|     // contains it's action submission info + action data
 | |
|     revalidatingFetchers.filter(rf => rf.key !== key).forEach(rf => {
 | |
|       let staleKey = rf.key;
 | |
|       let existingFetcher = state.fetchers.get(staleKey);
 | |
|       let revalidatingFetcher = getLoadingFetcher(undefined, existingFetcher ? existingFetcher.data : undefined);
 | |
|       state.fetchers.set(staleKey, revalidatingFetcher);
 | |
|       abortFetcher(staleKey);
 | |
|       if (rf.controller) {
 | |
|         fetchControllers.set(staleKey, rf.controller);
 | |
|       }
 | |
|     });
 | |
|     updateState({
 | |
|       fetchers: new Map(state.fetchers)
 | |
|     });
 | |
|     let abortPendingFetchRevalidations = () => revalidatingFetchers.forEach(rf => abortFetcher(rf.key));
 | |
|     abortController.signal.addEventListener("abort", abortPendingFetchRevalidations);
 | |
|     let {
 | |
|       loaderResults,
 | |
|       fetcherResults
 | |
|     } = await callLoadersAndMaybeResolveData(state, matches, matchesToLoad, revalidatingFetchers, revalidationRequest);
 | |
|     if (abortController.signal.aborted) {
 | |
|       return;
 | |
|     }
 | |
|     abortController.signal.removeEventListener("abort", abortPendingFetchRevalidations);
 | |
|     fetchReloadIds.delete(key);
 | |
|     fetchControllers.delete(key);
 | |
|     revalidatingFetchers.forEach(r => fetchControllers.delete(r.key));
 | |
|     let redirect = findRedirect(loaderResults);
 | |
|     if (redirect) {
 | |
|       return startRedirectNavigation(revalidationRequest, redirect.result, false, {
 | |
|         preventScrollReset
 | |
|       });
 | |
|     }
 | |
|     redirect = findRedirect(fetcherResults);
 | |
|     if (redirect) {
 | |
|       // If this redirect came from a fetcher make sure we mark it in
 | |
|       // fetchRedirectIds so it doesn't get revalidated on the next set of
 | |
|       // loader executions
 | |
|       fetchRedirectIds.add(redirect.key);
 | |
|       return startRedirectNavigation(revalidationRequest, redirect.result, false, {
 | |
|         preventScrollReset
 | |
|       });
 | |
|     }
 | |
| 
 | |
|     // Process and commit output from loaders
 | |
|     let {
 | |
|       loaderData,
 | |
|       errors
 | |
|     } = processLoaderData(state, matches, loaderResults, undefined, revalidatingFetchers, fetcherResults, activeDeferreds);
 | |
| 
 | |
|     // Since we let revalidations complete even if the submitting fetcher was
 | |
|     // deleted, only put it back to idle if it hasn't been deleted
 | |
|     if (state.fetchers.has(key)) {
 | |
|       let doneFetcher = getDoneFetcher(actionResult.data);
 | |
|       state.fetchers.set(key, doneFetcher);
 | |
|     }
 | |
|     abortStaleFetchLoads(loadId);
 | |
| 
 | |
|     // If we are currently in a navigation loading state and this fetcher is
 | |
|     // more recent than the navigation, we want the newer data so abort the
 | |
|     // navigation and complete it with the fetcher data
 | |
|     if (state.navigation.state === "loading" && loadId > pendingNavigationLoadId) {
 | |
|       invariant(pendingAction, "Expected pending action");
 | |
|       pendingNavigationController && pendingNavigationController.abort();
 | |
|       completeNavigation(state.navigation.location, {
 | |
|         matches,
 | |
|         loaderData,
 | |
|         errors,
 | |
|         fetchers: new Map(state.fetchers)
 | |
|       });
 | |
|     } else {
 | |
|       // otherwise just update with the fetcher data, preserving any existing
 | |
|       // loaderData for loaders that did not need to reload.  We have to
 | |
|       // manually merge here since we aren't going through completeNavigation
 | |
|       updateState({
 | |
|         errors,
 | |
|         loaderData: mergeLoaderData(state.loaderData, loaderData, matches, errors),
 | |
|         fetchers: new Map(state.fetchers)
 | |
|       });
 | |
|       isRevalidationRequired = false;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Call the matched loader for fetcher.load(), handling redirects, errors, etc.
 | |
|   async function handleFetcherLoader(key, routeId, path, match, matches, isFogOfWar, flushSync, preventScrollReset, submission) {
 | |
|     let existingFetcher = state.fetchers.get(key);
 | |
|     updateFetcherState(key, getLoadingFetcher(submission, existingFetcher ? existingFetcher.data : undefined), {
 | |
|       flushSync
 | |
|     });
 | |
|     let abortController = new AbortController();
 | |
|     let fetchRequest = createClientSideRequest(init.history, path, abortController.signal);
 | |
|     if (isFogOfWar) {
 | |
|       let discoverResult = await discoverRoutes(matches, new URL(fetchRequest.url).pathname, fetchRequest.signal, key);
 | |
|       if (discoverResult.type === "aborted") {
 | |
|         return;
 | |
|       } else if (discoverResult.type === "error") {
 | |
|         setFetcherError(key, routeId, discoverResult.error, {
 | |
|           flushSync
 | |
|         });
 | |
|         return;
 | |
|       } else if (!discoverResult.matches) {
 | |
|         setFetcherError(key, routeId, getInternalRouterError(404, {
 | |
|           pathname: path
 | |
|         }), {
 | |
|           flushSync
 | |
|         });
 | |
|         return;
 | |
|       } else {
 | |
|         matches = discoverResult.matches;
 | |
|         match = getTargetMatch(matches, path);
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // Call the loader for this fetcher route match
 | |
|     fetchControllers.set(key, abortController);
 | |
|     let originatingLoadId = incrementingLoadId;
 | |
|     let results = await callDataStrategy("loader", state, fetchRequest, [match], matches, key);
 | |
|     let result = results[match.route.id];
 | |
| 
 | |
|     // Deferred isn't supported for fetcher loads, await everything and treat it
 | |
|     // as a normal load.  resolveDeferredData will return undefined if this
 | |
|     // fetcher gets aborted, so we just leave result untouched and short circuit
 | |
|     // below if that happens
 | |
|     if (isDeferredResult(result)) {
 | |
|       result = (await resolveDeferredData(result, fetchRequest.signal, true)) || result;
 | |
|     }
 | |
| 
 | |
|     // We can delete this so long as we weren't aborted by our our own fetcher
 | |
|     // re-load which would have put _new_ controller is in fetchControllers
 | |
|     if (fetchControllers.get(key) === abortController) {
 | |
|       fetchControllers.delete(key);
 | |
|     }
 | |
|     if (fetchRequest.signal.aborted) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // We don't want errors bubbling up or redirects followed for unmounted
 | |
|     // fetchers, so short circuit here if it was removed from the UI
 | |
|     if (deletedFetchers.has(key)) {
 | |
|       updateFetcherState(key, getDoneFetcher(undefined));
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // If the loader threw a redirect Response, start a new REPLACE navigation
 | |
|     if (isRedirectResult(result)) {
 | |
|       if (pendingNavigationLoadId > originatingLoadId) {
 | |
|         // A new navigation was kicked off after our loader started, so that
 | |
|         // should take precedence over this redirect navigation
 | |
|         updateFetcherState(key, getDoneFetcher(undefined));
 | |
|         return;
 | |
|       } else {
 | |
|         fetchRedirectIds.add(key);
 | |
|         await startRedirectNavigation(fetchRequest, result, false, {
 | |
|           preventScrollReset
 | |
|         });
 | |
|         return;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // Process any non-redirect errors thrown
 | |
|     if (isErrorResult(result)) {
 | |
|       setFetcherError(key, routeId, result.error);
 | |
|       return;
 | |
|     }
 | |
|     invariant(!isDeferredResult(result), "Unhandled fetcher deferred data");
 | |
| 
 | |
|     // Put the fetcher back into an idle state
 | |
|     updateFetcherState(key, getDoneFetcher(result.data));
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Utility function to handle redirects returned from an action or loader.
 | |
|    * Normally, a redirect "replaces" the navigation that triggered it.  So, for
 | |
|    * example:
 | |
|    *
 | |
|    *  - user is on /a
 | |
|    *  - user clicks a link to /b
 | |
|    *  - loader for /b redirects to /c
 | |
|    *
 | |
|    * In a non-JS app the browser would track the in-flight navigation to /b and
 | |
|    * then replace it with /c when it encountered the redirect response.  In
 | |
|    * the end it would only ever update the URL bar with /c.
 | |
|    *
 | |
|    * In client-side routing using pushState/replaceState, we aim to emulate
 | |
|    * this behavior and we also do not update history until the end of the
 | |
|    * navigation (including processed redirects).  This means that we never
 | |
|    * actually touch history until we've processed redirects, so we just use
 | |
|    * the history action from the original navigation (PUSH or REPLACE).
 | |
|    */
 | |
|   async function startRedirectNavigation(request, redirect, isNavigation, _temp2) {
 | |
|     let {
 | |
|       submission,
 | |
|       fetcherSubmission,
 | |
|       preventScrollReset,
 | |
|       replace
 | |
|     } = _temp2 === void 0 ? {} : _temp2;
 | |
|     if (redirect.response.headers.has("X-Remix-Revalidate")) {
 | |
|       isRevalidationRequired = true;
 | |
|     }
 | |
|     let location = redirect.response.headers.get("Location");
 | |
|     invariant(location, "Expected a Location header on the redirect Response");
 | |
|     location = normalizeRedirectLocation(location, new URL(request.url), basename);
 | |
|     let redirectLocation = createLocation(state.location, location, {
 | |
|       _isRedirect: true
 | |
|     });
 | |
|     if (isBrowser) {
 | |
|       let isDocumentReload = false;
 | |
|       if (redirect.response.headers.has("X-Remix-Reload-Document")) {
 | |
|         // Hard reload if the response contained X-Remix-Reload-Document
 | |
|         isDocumentReload = true;
 | |
|       } else if (ABSOLUTE_URL_REGEX.test(location)) {
 | |
|         const url = init.history.createURL(location);
 | |
|         isDocumentReload =
 | |
|         // Hard reload if it's an absolute URL to a new origin
 | |
|         url.origin !== routerWindow.location.origin ||
 | |
|         // Hard reload if it's an absolute URL that does not match our basename
 | |
|         stripBasename(url.pathname, basename) == null;
 | |
|       }
 | |
|       if (isDocumentReload) {
 | |
|         if (replace) {
 | |
|           routerWindow.location.replace(location);
 | |
|         } else {
 | |
|           routerWindow.location.assign(location);
 | |
|         }
 | |
|         return;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // There's no need to abort on redirects, since we don't detect the
 | |
|     // redirect until the action/loaders have settled
 | |
|     pendingNavigationController = null;
 | |
|     let redirectHistoryAction = replace === true || redirect.response.headers.has("X-Remix-Replace") ? Action.Replace : Action.Push;
 | |
| 
 | |
|     // Use the incoming submission if provided, fallback on the active one in
 | |
|     // state.navigation
 | |
|     let {
 | |
|       formMethod,
 | |
|       formAction,
 | |
|       formEncType
 | |
|     } = state.navigation;
 | |
|     if (!submission && !fetcherSubmission && formMethod && formAction && formEncType) {
 | |
|       submission = getSubmissionFromNavigation(state.navigation);
 | |
|     }
 | |
| 
 | |
|     // If this was a 307/308 submission we want to preserve the HTTP method and
 | |
|     // re-submit the GET/POST/PUT/PATCH/DELETE as a submission navigation to the
 | |
|     // redirected location
 | |
|     let activeSubmission = submission || fetcherSubmission;
 | |
|     if (redirectPreserveMethodStatusCodes.has(redirect.response.status) && activeSubmission && isMutationMethod(activeSubmission.formMethod)) {
 | |
|       await startNavigation(redirectHistoryAction, redirectLocation, {
 | |
|         submission: _extends({}, activeSubmission, {
 | |
|           formAction: location
 | |
|         }),
 | |
|         // Preserve these flags across redirects
 | |
|         preventScrollReset: preventScrollReset || pendingPreventScrollReset,
 | |
|         enableViewTransition: isNavigation ? pendingViewTransitionEnabled : undefined
 | |
|       });
 | |
|     } else {
 | |
|       // If we have a navigation submission, we will preserve it through the
 | |
|       // redirect navigation
 | |
|       let overrideNavigation = getLoadingNavigation(redirectLocation, submission);
 | |
|       await startNavigation(redirectHistoryAction, redirectLocation, {
 | |
|         overrideNavigation,
 | |
|         // Send fetcher submissions through for shouldRevalidate
 | |
|         fetcherSubmission,
 | |
|         // Preserve these flags across redirects
 | |
|         preventScrollReset: preventScrollReset || pendingPreventScrollReset,
 | |
|         enableViewTransition: isNavigation ? pendingViewTransitionEnabled : undefined
 | |
|       });
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Utility wrapper for calling dataStrategy client-side without having to
 | |
|   // pass around the manifest, mapRouteProperties, etc.
 | |
|   async function callDataStrategy(type, state, request, matchesToLoad, matches, fetcherKey) {
 | |
|     let results;
 | |
|     let dataResults = {};
 | |
|     try {
 | |
|       results = await callDataStrategyImpl(dataStrategyImpl, type, state, request, matchesToLoad, matches, fetcherKey, manifest, mapRouteProperties);
 | |
|     } catch (e) {
 | |
|       // If the outer dataStrategy method throws, just return the error for all
 | |
|       // matches - and it'll naturally bubble to the root
 | |
|       matchesToLoad.forEach(m => {
 | |
|         dataResults[m.route.id] = {
 | |
|           type: ResultType.error,
 | |
|           error: e
 | |
|         };
 | |
|       });
 | |
|       return dataResults;
 | |
|     }
 | |
|     for (let [routeId, result] of Object.entries(results)) {
 | |
|       if (isRedirectDataStrategyResultResult(result)) {
 | |
|         let response = result.result;
 | |
|         dataResults[routeId] = {
 | |
|           type: ResultType.redirect,
 | |
|           response: normalizeRelativeRoutingRedirectResponse(response, request, routeId, matches, basename, future.v7_relativeSplatPath)
 | |
|         };
 | |
|       } else {
 | |
|         dataResults[routeId] = await convertDataStrategyResultToDataResult(result);
 | |
|       }
 | |
|     }
 | |
|     return dataResults;
 | |
|   }
 | |
|   async function callLoadersAndMaybeResolveData(state, matches, matchesToLoad, fetchersToLoad, request) {
 | |
|     let currentMatches = state.matches;
 | |
| 
 | |
|     // Kick off loaders and fetchers in parallel
 | |
|     let loaderResultsPromise = callDataStrategy("loader", state, request, matchesToLoad, matches, null);
 | |
|     let fetcherResultsPromise = Promise.all(fetchersToLoad.map(async f => {
 | |
|       if (f.matches && f.match && f.controller) {
 | |
|         let results = await callDataStrategy("loader", state, createClientSideRequest(init.history, f.path, f.controller.signal), [f.match], f.matches, f.key);
 | |
|         let result = results[f.match.route.id];
 | |
|         // Fetcher results are keyed by fetcher key from here on out, not routeId
 | |
|         return {
 | |
|           [f.key]: result
 | |
|         };
 | |
|       } else {
 | |
|         return Promise.resolve({
 | |
|           [f.key]: {
 | |
|             type: ResultType.error,
 | |
|             error: getInternalRouterError(404, {
 | |
|               pathname: f.path
 | |
|             })
 | |
|           }
 | |
|         });
 | |
|       }
 | |
|     }));
 | |
|     let loaderResults = await loaderResultsPromise;
 | |
|     let fetcherResults = (await fetcherResultsPromise).reduce((acc, r) => Object.assign(acc, r), {});
 | |
|     await Promise.all([resolveNavigationDeferredResults(matches, loaderResults, request.signal, currentMatches, state.loaderData), resolveFetcherDeferredResults(matches, fetcherResults, fetchersToLoad)]);
 | |
|     return {
 | |
|       loaderResults,
 | |
|       fetcherResults
 | |
|     };
 | |
|   }
 | |
|   function interruptActiveLoads() {
 | |
|     // Every interruption triggers a revalidation
 | |
|     isRevalidationRequired = true;
 | |
| 
 | |
|     // Cancel pending route-level deferreds and mark cancelled routes for
 | |
|     // revalidation
 | |
|     cancelledDeferredRoutes.push(...cancelActiveDeferreds());
 | |
| 
 | |
|     // Abort in-flight fetcher loads
 | |
|     fetchLoadMatches.forEach((_, key) => {
 | |
|       if (fetchControllers.has(key)) {
 | |
|         cancelledFetcherLoads.add(key);
 | |
|       }
 | |
|       abortFetcher(key);
 | |
|     });
 | |
|   }
 | |
|   function updateFetcherState(key, fetcher, opts) {
 | |
|     if (opts === void 0) {
 | |
|       opts = {};
 | |
|     }
 | |
|     state.fetchers.set(key, fetcher);
 | |
|     updateState({
 | |
|       fetchers: new Map(state.fetchers)
 | |
|     }, {
 | |
|       flushSync: (opts && opts.flushSync) === true
 | |
|     });
 | |
|   }
 | |
|   function setFetcherError(key, routeId, error, opts) {
 | |
|     if (opts === void 0) {
 | |
|       opts = {};
 | |
|     }
 | |
|     let boundaryMatch = findNearestBoundary(state.matches, routeId);
 | |
|     deleteFetcher(key);
 | |
|     updateState({
 | |
|       errors: {
 | |
|         [boundaryMatch.route.id]: error
 | |
|       },
 | |
|       fetchers: new Map(state.fetchers)
 | |
|     }, {
 | |
|       flushSync: (opts && opts.flushSync) === true
 | |
|     });
 | |
|   }
 | |
|   function getFetcher(key) {
 | |
|     activeFetchers.set(key, (activeFetchers.get(key) || 0) + 1);
 | |
|     // If this fetcher was previously marked for deletion, unmark it since we
 | |
|     // have a new instance
 | |
|     if (deletedFetchers.has(key)) {
 | |
|       deletedFetchers.delete(key);
 | |
|     }
 | |
|     return state.fetchers.get(key) || IDLE_FETCHER;
 | |
|   }
 | |
|   function deleteFetcher(key) {
 | |
|     let fetcher = state.fetchers.get(key);
 | |
|     // Don't abort the controller if this is a deletion of a fetcher.submit()
 | |
|     // in it's loading phase since - we don't want to abort the corresponding
 | |
|     // revalidation and want them to complete and land
 | |
|     if (fetchControllers.has(key) && !(fetcher && fetcher.state === "loading" && fetchReloadIds.has(key))) {
 | |
|       abortFetcher(key);
 | |
|     }
 | |
|     fetchLoadMatches.delete(key);
 | |
|     fetchReloadIds.delete(key);
 | |
|     fetchRedirectIds.delete(key);
 | |
| 
 | |
|     // If we opted into the flag we can clear this now since we're calling
 | |
|     // deleteFetcher() at the end of updateState() and we've already handed the
 | |
|     // deleted fetcher keys off to the data layer.
 | |
|     // If not, we're eagerly calling deleteFetcher() and we need to keep this
 | |
|     // Set populated until the next updateState call, and we'll clear
 | |
|     // `deletedFetchers` then
 | |
|     if (future.v7_fetcherPersist) {
 | |
|       deletedFetchers.delete(key);
 | |
|     }
 | |
|     cancelledFetcherLoads.delete(key);
 | |
|     state.fetchers.delete(key);
 | |
|   }
 | |
|   function deleteFetcherAndUpdateState(key) {
 | |
|     let count = (activeFetchers.get(key) || 0) - 1;
 | |
|     if (count <= 0) {
 | |
|       activeFetchers.delete(key);
 | |
|       deletedFetchers.add(key);
 | |
|       if (!future.v7_fetcherPersist) {
 | |
|         deleteFetcher(key);
 | |
|       }
 | |
|     } else {
 | |
|       activeFetchers.set(key, count);
 | |
|     }
 | |
|     updateState({
 | |
|       fetchers: new Map(state.fetchers)
 | |
|     });
 | |
|   }
 | |
|   function abortFetcher(key) {
 | |
|     let controller = fetchControllers.get(key);
 | |
|     if (controller) {
 | |
|       controller.abort();
 | |
|       fetchControllers.delete(key);
 | |
|     }
 | |
|   }
 | |
|   function markFetchersDone(keys) {
 | |
|     for (let key of keys) {
 | |
|       let fetcher = getFetcher(key);
 | |
|       let doneFetcher = getDoneFetcher(fetcher.data);
 | |
|       state.fetchers.set(key, doneFetcher);
 | |
|     }
 | |
|   }
 | |
|   function markFetchRedirectsDone() {
 | |
|     let doneKeys = [];
 | |
|     let updatedFetchers = false;
 | |
|     for (let key of fetchRedirectIds) {
 | |
|       let fetcher = state.fetchers.get(key);
 | |
|       invariant(fetcher, "Expected fetcher: " + key);
 | |
|       if (fetcher.state === "loading") {
 | |
|         fetchRedirectIds.delete(key);
 | |
|         doneKeys.push(key);
 | |
|         updatedFetchers = true;
 | |
|       }
 | |
|     }
 | |
|     markFetchersDone(doneKeys);
 | |
|     return updatedFetchers;
 | |
|   }
 | |
|   function abortStaleFetchLoads(landedId) {
 | |
|     let yeetedKeys = [];
 | |
|     for (let [key, id] of fetchReloadIds) {
 | |
|       if (id < landedId) {
 | |
|         let fetcher = state.fetchers.get(key);
 | |
|         invariant(fetcher, "Expected fetcher: " + key);
 | |
|         if (fetcher.state === "loading") {
 | |
|           abortFetcher(key);
 | |
|           fetchReloadIds.delete(key);
 | |
|           yeetedKeys.push(key);
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     markFetchersDone(yeetedKeys);
 | |
|     return yeetedKeys.length > 0;
 | |
|   }
 | |
|   function getBlocker(key, fn) {
 | |
|     let blocker = state.blockers.get(key) || IDLE_BLOCKER;
 | |
|     if (blockerFunctions.get(key) !== fn) {
 | |
|       blockerFunctions.set(key, fn);
 | |
|     }
 | |
|     return blocker;
 | |
|   }
 | |
|   function deleteBlocker(key) {
 | |
|     state.blockers.delete(key);
 | |
|     blockerFunctions.delete(key);
 | |
|   }
 | |
| 
 | |
|   // Utility function to update blockers, ensuring valid state transitions
 | |
|   function updateBlocker(key, newBlocker) {
 | |
|     let blocker = state.blockers.get(key) || IDLE_BLOCKER;
 | |
| 
 | |
|     // Poor mans state machine :)
 | |
|     // https://mermaid.live/edit#pako:eNqVkc9OwzAMxl8l8nnjAYrEtDIOHEBIgwvKJTReGy3_lDpIqO27k6awMG0XcrLlnz87nwdonESogKXXBuE79rq75XZO3-yHds0RJVuv70YrPlUrCEe2HfrORS3rubqZfuhtpg5C9wk5tZ4VKcRUq88q9Z8RS0-48cE1iHJkL0ugbHuFLus9L6spZy8nX9MP2CNdomVaposqu3fGayT8T8-jJQwhepo_UtpgBQaDEUom04dZhAN1aJBDlUKJBxE1ceB2Smj0Mln-IBW5AFU2dwUiktt_2Qaq2dBfaKdEup85UV7Yd-dKjlnkabl2Pvr0DTkTreM
 | |
|     invariant(blocker.state === "unblocked" && newBlocker.state === "blocked" || blocker.state === "blocked" && newBlocker.state === "blocked" || blocker.state === "blocked" && newBlocker.state === "proceeding" || blocker.state === "blocked" && newBlocker.state === "unblocked" || blocker.state === "proceeding" && newBlocker.state === "unblocked", "Invalid blocker state transition: " + blocker.state + " -> " + newBlocker.state);
 | |
|     let blockers = new Map(state.blockers);
 | |
|     blockers.set(key, newBlocker);
 | |
|     updateState({
 | |
|       blockers
 | |
|     });
 | |
|   }
 | |
|   function shouldBlockNavigation(_ref2) {
 | |
|     let {
 | |
|       currentLocation,
 | |
|       nextLocation,
 | |
|       historyAction
 | |
|     } = _ref2;
 | |
|     if (blockerFunctions.size === 0) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // We ony support a single active blocker at the moment since we don't have
 | |
|     // any compelling use cases for multi-blocker yet
 | |
|     if (blockerFunctions.size > 1) {
 | |
|       warning(false, "A router only supports one blocker at a time");
 | |
|     }
 | |
|     let entries = Array.from(blockerFunctions.entries());
 | |
|     let [blockerKey, blockerFunction] = entries[entries.length - 1];
 | |
|     let blocker = state.blockers.get(blockerKey);
 | |
|     if (blocker && blocker.state === "proceeding") {
 | |
|       // If the blocker is currently proceeding, we don't need to re-check
 | |
|       // it and can let this navigation continue
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // At this point, we know we're unblocked/blocked so we need to check the
 | |
|     // user-provided blocker function
 | |
|     if (blockerFunction({
 | |
|       currentLocation,
 | |
|       nextLocation,
 | |
|       historyAction
 | |
|     })) {
 | |
|       return blockerKey;
 | |
|     }
 | |
|   }
 | |
|   function handleNavigational404(pathname) {
 | |
|     let error = getInternalRouterError(404, {
 | |
|       pathname
 | |
|     });
 | |
|     let routesToUse = inFlightDataRoutes || dataRoutes;
 | |
|     let {
 | |
|       matches,
 | |
|       route
 | |
|     } = getShortCircuitMatches(routesToUse);
 | |
| 
 | |
|     // Cancel all pending deferred on 404s since we don't keep any routes
 | |
|     cancelActiveDeferreds();
 | |
|     return {
 | |
|       notFoundMatches: matches,
 | |
|       route,
 | |
|       error
 | |
|     };
 | |
|   }
 | |
|   function cancelActiveDeferreds(predicate) {
 | |
|     let cancelledRouteIds = [];
 | |
|     activeDeferreds.forEach((dfd, routeId) => {
 | |
|       if (!predicate || predicate(routeId)) {
 | |
|         // Cancel the deferred - but do not remove from activeDeferreds here -
 | |
|         // we rely on the subscribers to do that so our tests can assert proper
 | |
|         // cleanup via _internalActiveDeferreds
 | |
|         dfd.cancel();
 | |
|         cancelledRouteIds.push(routeId);
 | |
|         activeDeferreds.delete(routeId);
 | |
|       }
 | |
|     });
 | |
|     return cancelledRouteIds;
 | |
|   }
 | |
| 
 | |
|   // Opt in to capturing and reporting scroll positions during navigations,
 | |
|   // used by the <ScrollRestoration> component
 | |
|   function enableScrollRestoration(positions, getPosition, getKey) {
 | |
|     savedScrollPositions = positions;
 | |
|     getScrollPosition = getPosition;
 | |
|     getScrollRestorationKey = getKey || null;
 | |
| 
 | |
|     // Perform initial hydration scroll restoration, since we miss the boat on
 | |
|     // the initial updateState() because we've not yet rendered <ScrollRestoration/>
 | |
|     // and therefore have no savedScrollPositions available
 | |
|     if (!initialScrollRestored && state.navigation === IDLE_NAVIGATION) {
 | |
|       initialScrollRestored = true;
 | |
|       let y = getSavedScrollPosition(state.location, state.matches);
 | |
|       if (y != null) {
 | |
|         updateState({
 | |
|           restoreScrollPosition: y
 | |
|         });
 | |
|       }
 | |
|     }
 | |
|     return () => {
 | |
|       savedScrollPositions = null;
 | |
|       getScrollPosition = null;
 | |
|       getScrollRestorationKey = null;
 | |
|     };
 | |
|   }
 | |
|   function getScrollKey(location, matches) {
 | |
|     if (getScrollRestorationKey) {
 | |
|       let key = getScrollRestorationKey(location, matches.map(m => convertRouteMatchToUiMatch(m, state.loaderData)));
 | |
|       return key || location.key;
 | |
|     }
 | |
|     return location.key;
 | |
|   }
 | |
|   function saveScrollPosition(location, matches) {
 | |
|     if (savedScrollPositions && getScrollPosition) {
 | |
|       let key = getScrollKey(location, matches);
 | |
|       savedScrollPositions[key] = getScrollPosition();
 | |
|     }
 | |
|   }
 | |
|   function getSavedScrollPosition(location, matches) {
 | |
|     if (savedScrollPositions) {
 | |
|       let key = getScrollKey(location, matches);
 | |
|       let y = savedScrollPositions[key];
 | |
|       if (typeof y === "number") {
 | |
|         return y;
 | |
|       }
 | |
|     }
 | |
|     return null;
 | |
|   }
 | |
|   function checkFogOfWar(matches, routesToUse, pathname) {
 | |
|     if (patchRoutesOnNavigationImpl) {
 | |
|       if (!matches) {
 | |
|         let fogMatches = matchRoutesImpl(routesToUse, pathname, basename, true);
 | |
|         return {
 | |
|           active: true,
 | |
|           matches: fogMatches || []
 | |
|         };
 | |
|       } else {
 | |
|         if (Object.keys(matches[0].params).length > 0) {
 | |
|           // If we matched a dynamic param or a splat, it might only be because
 | |
|           // we haven't yet discovered other routes that would match with a
 | |
|           // higher score.  Call patchRoutesOnNavigation just to be sure
 | |
|           let partialMatches = matchRoutesImpl(routesToUse, pathname, basename, true);
 | |
|           return {
 | |
|             active: true,
 | |
|             matches: partialMatches
 | |
|           };
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     return {
 | |
|       active: false,
 | |
|       matches: null
 | |
|     };
 | |
|   }
 | |
|   async function discoverRoutes(matches, pathname, signal, fetcherKey) {
 | |
|     if (!patchRoutesOnNavigationImpl) {
 | |
|       return {
 | |
|         type: "success",
 | |
|         matches
 | |
|       };
 | |
|     }
 | |
|     let partialMatches = matches;
 | |
|     while (true) {
 | |
|       let isNonHMR = inFlightDataRoutes == null;
 | |
|       let routesToUse = inFlightDataRoutes || dataRoutes;
 | |
|       let localManifest = manifest;
 | |
|       try {
 | |
|         await patchRoutesOnNavigationImpl({
 | |
|           signal,
 | |
|           path: pathname,
 | |
|           matches: partialMatches,
 | |
|           fetcherKey,
 | |
|           patch: (routeId, children) => {
 | |
|             if (signal.aborted) return;
 | |
|             patchRoutesImpl(routeId, children, routesToUse, localManifest, mapRouteProperties);
 | |
|           }
 | |
|         });
 | |
|       } catch (e) {
 | |
|         return {
 | |
|           type: "error",
 | |
|           error: e,
 | |
|           partialMatches
 | |
|         };
 | |
|       } finally {
 | |
|         // If we are not in the middle of an HMR revalidation and we changed the
 | |
|         // routes, provide a new identity so when we `updateState` at the end of
 | |
|         // this navigation/fetch `router.routes` will be a new identity and
 | |
|         // trigger a re-run of memoized `router.routes` dependencies.
 | |
|         // HMR will already update the identity and reflow when it lands
 | |
|         // `inFlightDataRoutes` in `completeNavigation`
 | |
|         if (isNonHMR && !signal.aborted) {
 | |
|           dataRoutes = [...dataRoutes];
 | |
|         }
 | |
|       }
 | |
|       if (signal.aborted) {
 | |
|         return {
 | |
|           type: "aborted"
 | |
|         };
 | |
|       }
 | |
|       let newMatches = matchRoutes(routesToUse, pathname, basename);
 | |
|       if (newMatches) {
 | |
|         return {
 | |
|           type: "success",
 | |
|           matches: newMatches
 | |
|         };
 | |
|       }
 | |
|       let newPartialMatches = matchRoutesImpl(routesToUse, pathname, basename, true);
 | |
| 
 | |
|       // Avoid loops if the second pass results in the same partial matches
 | |
|       if (!newPartialMatches || partialMatches.length === newPartialMatches.length && partialMatches.every((m, i) => m.route.id === newPartialMatches[i].route.id)) {
 | |
|         return {
 | |
|           type: "success",
 | |
|           matches: null
 | |
|         };
 | |
|       }
 | |
|       partialMatches = newPartialMatches;
 | |
|     }
 | |
|   }
 | |
|   function _internalSetRoutes(newRoutes) {
 | |
|     manifest = {};
 | |
|     inFlightDataRoutes = convertRoutesToDataRoutes(newRoutes, mapRouteProperties, undefined, manifest);
 | |
|   }
 | |
|   function patchRoutes(routeId, children) {
 | |
|     let isNonHMR = inFlightDataRoutes == null;
 | |
|     let routesToUse = inFlightDataRoutes || dataRoutes;
 | |
|     patchRoutesImpl(routeId, children, routesToUse, manifest, mapRouteProperties);
 | |
| 
 | |
|     // If we are not in the middle of an HMR revalidation and we changed the
 | |
|     // routes, provide a new identity and trigger a reflow via `updateState`
 | |
|     // to re-run memoized `router.routes` dependencies.
 | |
|     // HMR will already update the identity and reflow when it lands
 | |
|     // `inFlightDataRoutes` in `completeNavigation`
 | |
|     if (isNonHMR) {
 | |
|       dataRoutes = [...dataRoutes];
 | |
|       updateState({});
 | |
|     }
 | |
|   }
 | |
|   router = {
 | |
|     get basename() {
 | |
|       return basename;
 | |
|     },
 | |
|     get future() {
 | |
|       return future;
 | |
|     },
 | |
|     get state() {
 | |
|       return state;
 | |
|     },
 | |
|     get routes() {
 | |
|       return dataRoutes;
 | |
|     },
 | |
|     get window() {
 | |
|       return routerWindow;
 | |
|     },
 | |
|     initialize,
 | |
|     subscribe,
 | |
|     enableScrollRestoration,
 | |
|     navigate,
 | |
|     fetch,
 | |
|     revalidate,
 | |
|     // Passthrough to history-aware createHref used by useHref so we get proper
 | |
|     // hash-aware URLs in DOM paths
 | |
|     createHref: to => init.history.createHref(to),
 | |
|     encodeLocation: to => init.history.encodeLocation(to),
 | |
|     getFetcher,
 | |
|     deleteFetcher: deleteFetcherAndUpdateState,
 | |
|     dispose,
 | |
|     getBlocker,
 | |
|     deleteBlocker,
 | |
|     patchRoutes,
 | |
|     _internalFetchControllers: fetchControllers,
 | |
|     _internalActiveDeferreds: activeDeferreds,
 | |
|     // TODO: Remove setRoutes, it's temporary to avoid dealing with
 | |
|     // updating the tree while validating the update algorithm.
 | |
|     _internalSetRoutes
 | |
|   };
 | |
|   return router;
 | |
| }
 | |
| //#endregion
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| //#region createStaticHandler
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| const UNSAFE_DEFERRED_SYMBOL = Symbol("deferred");
 | |
| 
 | |
| /**
 | |
|  * Future flags to toggle new feature behavior
 | |
|  */
 | |
| 
 | |
| function createStaticHandler(routes, opts) {
 | |
|   invariant(routes.length > 0, "You must provide a non-empty routes array to createStaticHandler");
 | |
|   let manifest = {};
 | |
|   let basename = (opts ? opts.basename : null) || "/";
 | |
|   let mapRouteProperties;
 | |
|   if (opts != null && opts.mapRouteProperties) {
 | |
|     mapRouteProperties = opts.mapRouteProperties;
 | |
|   } else if (opts != null && opts.detectErrorBoundary) {
 | |
|     // If they are still using the deprecated version, wrap it with the new API
 | |
|     let detectErrorBoundary = opts.detectErrorBoundary;
 | |
|     mapRouteProperties = route => ({
 | |
|       hasErrorBoundary: detectErrorBoundary(route)
 | |
|     });
 | |
|   } else {
 | |
|     mapRouteProperties = defaultMapRouteProperties;
 | |
|   }
 | |
|   // Config driven behavior flags
 | |
|   let future = _extends({
 | |
|     v7_relativeSplatPath: false,
 | |
|     v7_throwAbortReason: false
 | |
|   }, opts ? opts.future : null);
 | |
|   let dataRoutes = convertRoutesToDataRoutes(routes, mapRouteProperties, undefined, manifest);
 | |
| 
 | |
|   /**
 | |
|    * The query() method is intended for document requests, in which we want to
 | |
|    * call an optional action and potentially multiple loaders for all nested
 | |
|    * routes.  It returns a StaticHandlerContext object, which is very similar
 | |
|    * to the router state (location, loaderData, actionData, errors, etc.) and
 | |
|    * also adds SSR-specific information such as the statusCode and headers
 | |
|    * from action/loaders Responses.
 | |
|    *
 | |
|    * It _should_ never throw and should report all errors through the
 | |
|    * returned context.errors object, properly associating errors to their error
 | |
|    * boundary.  Additionally, it tracks _deepestRenderedBoundaryId which can be
 | |
|    * used to emulate React error boundaries during SSr by performing a second
 | |
|    * pass only down to the boundaryId.
 | |
|    *
 | |
|    * The one exception where we do not return a StaticHandlerContext is when a
 | |
|    * redirect response is returned or thrown from any action/loader.  We
 | |
|    * propagate that out and return the raw Response so the HTTP server can
 | |
|    * return it directly.
 | |
|    *
 | |
|    * - `opts.requestContext` is an optional server context that will be passed
 | |
|    *   to actions/loaders in the `context` parameter
 | |
|    * - `opts.skipLoaderErrorBubbling` is an optional parameter that will prevent
 | |
|    *   the bubbling of errors which allows single-fetch-type implementations
 | |
|    *   where the client will handle the bubbling and we may need to return data
 | |
|    *   for the handling route
 | |
|    */
 | |
|   async function query(request, _temp3) {
 | |
|     let {
 | |
|       requestContext,
 | |
|       skipLoaderErrorBubbling,
 | |
|       dataStrategy
 | |
|     } = _temp3 === void 0 ? {} : _temp3;
 | |
|     let url = new URL(request.url);
 | |
|     let method = request.method;
 | |
|     let location = createLocation("", createPath(url), null, "default");
 | |
|     let matches = matchRoutes(dataRoutes, location, basename);
 | |
| 
 | |
|     // SSR supports HEAD requests while SPA doesn't
 | |
|     if (!isValidMethod(method) && method !== "HEAD") {
 | |
|       let error = getInternalRouterError(405, {
 | |
|         method
 | |
|       });
 | |
|       let {
 | |
|         matches: methodNotAllowedMatches,
 | |
|         route
 | |
|       } = getShortCircuitMatches(dataRoutes);
 | |
|       return {
 | |
|         basename,
 | |
|         location,
 | |
|         matches: methodNotAllowedMatches,
 | |
|         loaderData: {},
 | |
|         actionData: null,
 | |
|         errors: {
 | |
|           [route.id]: error
 | |
|         },
 | |
|         statusCode: error.status,
 | |
|         loaderHeaders: {},
 | |
|         actionHeaders: {},
 | |
|         activeDeferreds: null
 | |
|       };
 | |
|     } else if (!matches) {
 | |
|       let error = getInternalRouterError(404, {
 | |
|         pathname: location.pathname
 | |
|       });
 | |
|       let {
 | |
|         matches: notFoundMatches,
 | |
|         route
 | |
|       } = getShortCircuitMatches(dataRoutes);
 | |
|       return {
 | |
|         basename,
 | |
|         location,
 | |
|         matches: notFoundMatches,
 | |
|         loaderData: {},
 | |
|         actionData: null,
 | |
|         errors: {
 | |
|           [route.id]: error
 | |
|         },
 | |
|         statusCode: error.status,
 | |
|         loaderHeaders: {},
 | |
|         actionHeaders: {},
 | |
|         activeDeferreds: null
 | |
|       };
 | |
|     }
 | |
|     let result = await queryImpl(request, location, matches, requestContext, dataStrategy || null, skipLoaderErrorBubbling === true, null);
 | |
|     if (isResponse(result)) {
 | |
|       return result;
 | |
|     }
 | |
| 
 | |
|     // When returning StaticHandlerContext, we patch back in the location here
 | |
|     // since we need it for React Context.  But this helps keep our submit and
 | |
|     // loadRouteData operating on a Request instead of a Location
 | |
|     return _extends({
 | |
|       location,
 | |
|       basename
 | |
|     }, result);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * The queryRoute() method is intended for targeted route requests, either
 | |
|    * for fetch ?_data requests or resource route requests.  In this case, we
 | |
|    * are only ever calling a single action or loader, and we are returning the
 | |
|    * returned value directly.  In most cases, this will be a Response returned
 | |
|    * from the action/loader, but it may be a primitive or other value as well -
 | |
|    * and in such cases the calling context should handle that accordingly.
 | |
|    *
 | |
|    * We do respect the throw/return differentiation, so if an action/loader
 | |
|    * throws, then this method will throw the value.  This is important so we
 | |
|    * can do proper boundary identification in Remix where a thrown Response
 | |
|    * must go to the Catch Boundary but a returned Response is happy-path.
 | |
|    *
 | |
|    * One thing to note is that any Router-initiated Errors that make sense
 | |
|    * to associate with a status code will be thrown as an ErrorResponse
 | |
|    * instance which include the raw Error, such that the calling context can
 | |
|    * serialize the error as they see fit while including the proper response
 | |
|    * code.  Examples here are 404 and 405 errors that occur prior to reaching
 | |
|    * any user-defined loaders.
 | |
|    *
 | |
|    * - `opts.routeId` allows you to specify the specific route handler to call.
 | |
|    *   If not provided the handler will determine the proper route by matching
 | |
|    *   against `request.url`
 | |
|    * - `opts.requestContext` is an optional server context that will be passed
 | |
|    *    to actions/loaders in the `context` parameter
 | |
|    */
 | |
|   async function queryRoute(request, _temp4) {
 | |
|     let {
 | |
|       routeId,
 | |
|       requestContext,
 | |
|       dataStrategy
 | |
|     } = _temp4 === void 0 ? {} : _temp4;
 | |
|     let url = new URL(request.url);
 | |
|     let method = request.method;
 | |
|     let location = createLocation("", createPath(url), null, "default");
 | |
|     let matches = matchRoutes(dataRoutes, location, basename);
 | |
| 
 | |
|     // SSR supports HEAD requests while SPA doesn't
 | |
|     if (!isValidMethod(method) && method !== "HEAD" && method !== "OPTIONS") {
 | |
|       throw getInternalRouterError(405, {
 | |
|         method
 | |
|       });
 | |
|     } else if (!matches) {
 | |
|       throw getInternalRouterError(404, {
 | |
|         pathname: location.pathname
 | |
|       });
 | |
|     }
 | |
|     let match = routeId ? matches.find(m => m.route.id === routeId) : getTargetMatch(matches, location);
 | |
|     if (routeId && !match) {
 | |
|       throw getInternalRouterError(403, {
 | |
|         pathname: location.pathname,
 | |
|         routeId
 | |
|       });
 | |
|     } else if (!match) {
 | |
|       // This should never hit I don't think?
 | |
|       throw getInternalRouterError(404, {
 | |
|         pathname: location.pathname
 | |
|       });
 | |
|     }
 | |
|     let result = await queryImpl(request, location, matches, requestContext, dataStrategy || null, false, match);
 | |
|     if (isResponse(result)) {
 | |
|       return result;
 | |
|     }
 | |
|     let error = result.errors ? Object.values(result.errors)[0] : undefined;
 | |
|     if (error !== undefined) {
 | |
|       // If we got back result.errors, that means the loader/action threw
 | |
|       // _something_ that wasn't a Response, but it's not guaranteed/required
 | |
|       // to be an `instanceof Error` either, so we have to use throw here to
 | |
|       // preserve the "error" state outside of queryImpl.
 | |
|       throw error;
 | |
|     }
 | |
| 
 | |
|     // Pick off the right state value to return
 | |
|     if (result.actionData) {
 | |
|       return Object.values(result.actionData)[0];
 | |
|     }
 | |
|     if (result.loaderData) {
 | |
|       var _result$activeDeferre;
 | |
|       let data = Object.values(result.loaderData)[0];
 | |
|       if ((_result$activeDeferre = result.activeDeferreds) != null && _result$activeDeferre[match.route.id]) {
 | |
|         data[UNSAFE_DEFERRED_SYMBOL] = result.activeDeferreds[match.route.id];
 | |
|       }
 | |
|       return data;
 | |
|     }
 | |
|     return undefined;
 | |
|   }
 | |
|   async function queryImpl(request, location, matches, requestContext, dataStrategy, skipLoaderErrorBubbling, routeMatch) {
 | |
|     invariant(request.signal, "query()/queryRoute() requests must contain an AbortController signal");
 | |
|     try {
 | |
|       if (isMutationMethod(request.method.toLowerCase())) {
 | |
|         let result = await submit(request, matches, routeMatch || getTargetMatch(matches, location), requestContext, dataStrategy, skipLoaderErrorBubbling, routeMatch != null);
 | |
|         return result;
 | |
|       }
 | |
|       let result = await loadRouteData(request, matches, requestContext, dataStrategy, skipLoaderErrorBubbling, routeMatch);
 | |
|       return isResponse(result) ? result : _extends({}, result, {
 | |
|         actionData: null,
 | |
|         actionHeaders: {}
 | |
|       });
 | |
|     } catch (e) {
 | |
|       // If the user threw/returned a Response in callLoaderOrAction for a
 | |
|       // `queryRoute` call, we throw the `DataStrategyResult` to bail out early
 | |
|       // and then return or throw the raw Response here accordingly
 | |
|       if (isDataStrategyResult(e) && isResponse(e.result)) {
 | |
|         if (e.type === ResultType.error) {
 | |
|           throw e.result;
 | |
|         }
 | |
|         return e.result;
 | |
|       }
 | |
|       // Redirects are always returned since they don't propagate to catch
 | |
|       // boundaries
 | |
|       if (isRedirectResponse(e)) {
 | |
|         return e;
 | |
|       }
 | |
|       throw e;
 | |
|     }
 | |
|   }
 | |
|   async function submit(request, matches, actionMatch, requestContext, dataStrategy, skipLoaderErrorBubbling, isRouteRequest) {
 | |
|     let result;
 | |
|     if (!actionMatch.route.action && !actionMatch.route.lazy) {
 | |
|       let error = getInternalRouterError(405, {
 | |
|         method: request.method,
 | |
|         pathname: new URL(request.url).pathname,
 | |
|         routeId: actionMatch.route.id
 | |
|       });
 | |
|       if (isRouteRequest) {
 | |
|         throw error;
 | |
|       }
 | |
|       result = {
 | |
|         type: ResultType.error,
 | |
|         error
 | |
|       };
 | |
|     } else {
 | |
|       let results = await callDataStrategy("action", request, [actionMatch], matches, isRouteRequest, requestContext, dataStrategy);
 | |
|       result = results[actionMatch.route.id];
 | |
|       if (request.signal.aborted) {
 | |
|         throwStaticHandlerAbortedError(request, isRouteRequest, future);
 | |
|       }
 | |
|     }
 | |
|     if (isRedirectResult(result)) {
 | |
|       // Uhhhh - this should never happen, we should always throw these from
 | |
|       // callLoaderOrAction, but the type narrowing here keeps TS happy and we
 | |
|       // can get back on the "throw all redirect responses" train here should
 | |
|       // this ever happen :/
 | |
|       throw new Response(null, {
 | |
|         status: result.response.status,
 | |
|         headers: {
 | |
|           Location: result.response.headers.get("Location")
 | |
|         }
 | |
|       });
 | |
|     }
 | |
|     if (isDeferredResult(result)) {
 | |
|       let error = getInternalRouterError(400, {
 | |
|         type: "defer-action"
 | |
|       });
 | |
|       if (isRouteRequest) {
 | |
|         throw error;
 | |
|       }
 | |
|       result = {
 | |
|         type: ResultType.error,
 | |
|         error
 | |
|       };
 | |
|     }
 | |
|     if (isRouteRequest) {
 | |
|       // Note: This should only be non-Response values if we get here, since
 | |
|       // isRouteRequest should throw any Response received in callLoaderOrAction
 | |
|       if (isErrorResult(result)) {
 | |
|         throw result.error;
 | |
|       }
 | |
|       return {
 | |
|         matches: [actionMatch],
 | |
|         loaderData: {},
 | |
|         actionData: {
 | |
|           [actionMatch.route.id]: result.data
 | |
|         },
 | |
|         errors: null,
 | |
|         // Note: statusCode + headers are unused here since queryRoute will
 | |
|         // return the raw Response or value
 | |
|         statusCode: 200,
 | |
|         loaderHeaders: {},
 | |
|         actionHeaders: {},
 | |
|         activeDeferreds: null
 | |
|       };
 | |
|     }
 | |
| 
 | |
|     // Create a GET request for the loaders
 | |
|     let loaderRequest = new Request(request.url, {
 | |
|       headers: request.headers,
 | |
|       redirect: request.redirect,
 | |
|       signal: request.signal
 | |
|     });
 | |
|     if (isErrorResult(result)) {
 | |
|       // Store off the pending error - we use it to determine which loaders
 | |
|       // to call and will commit it when we complete the navigation
 | |
|       let boundaryMatch = skipLoaderErrorBubbling ? actionMatch : findNearestBoundary(matches, actionMatch.route.id);
 | |
|       let context = await loadRouteData(loaderRequest, matches, requestContext, dataStrategy, skipLoaderErrorBubbling, null, [boundaryMatch.route.id, result]);
 | |
| 
 | |
|       // action status codes take precedence over loader status codes
 | |
|       return _extends({}, context, {
 | |
|         statusCode: isRouteErrorResponse(result.error) ? result.error.status : result.statusCode != null ? result.statusCode : 500,
 | |
|         actionData: null,
 | |
|         actionHeaders: _extends({}, result.headers ? {
 | |
|           [actionMatch.route.id]: result.headers
 | |
|         } : {})
 | |
|       });
 | |
|     }
 | |
|     let context = await loadRouteData(loaderRequest, matches, requestContext, dataStrategy, skipLoaderErrorBubbling, null);
 | |
|     return _extends({}, context, {
 | |
|       actionData: {
 | |
|         [actionMatch.route.id]: result.data
 | |
|       }
 | |
|     }, result.statusCode ? {
 | |
|       statusCode: result.statusCode
 | |
|     } : {}, {
 | |
|       actionHeaders: result.headers ? {
 | |
|         [actionMatch.route.id]: result.headers
 | |
|       } : {}
 | |
|     });
 | |
|   }
 | |
|   async function loadRouteData(request, matches, requestContext, dataStrategy, skipLoaderErrorBubbling, routeMatch, pendingActionResult) {
 | |
|     let isRouteRequest = routeMatch != null;
 | |
| 
 | |
|     // Short circuit if we have no loaders to run (queryRoute())
 | |
|     if (isRouteRequest && !(routeMatch != null && routeMatch.route.loader) && !(routeMatch != null && routeMatch.route.lazy)) {
 | |
|       throw getInternalRouterError(400, {
 | |
|         method: request.method,
 | |
|         pathname: new URL(request.url).pathname,
 | |
|         routeId: routeMatch == null ? void 0 : routeMatch.route.id
 | |
|       });
 | |
|     }
 | |
|     let requestMatches = routeMatch ? [routeMatch] : pendingActionResult && isErrorResult(pendingActionResult[1]) ? getLoaderMatchesUntilBoundary(matches, pendingActionResult[0]) : matches;
 | |
|     let matchesToLoad = requestMatches.filter(m => m.route.loader || m.route.lazy);
 | |
| 
 | |
|     // Short circuit if we have no loaders to run (query())
 | |
|     if (matchesToLoad.length === 0) {
 | |
|       return {
 | |
|         matches,
 | |
|         // Add a null for all matched routes for proper revalidation on the client
 | |
|         loaderData: matches.reduce((acc, m) => Object.assign(acc, {
 | |
|           [m.route.id]: null
 | |
|         }), {}),
 | |
|         errors: pendingActionResult && isErrorResult(pendingActionResult[1]) ? {
 | |
|           [pendingActionResult[0]]: pendingActionResult[1].error
 | |
|         } : null,
 | |
|         statusCode: 200,
 | |
|         loaderHeaders: {},
 | |
|         activeDeferreds: null
 | |
|       };
 | |
|     }
 | |
|     let results = await callDataStrategy("loader", request, matchesToLoad, matches, isRouteRequest, requestContext, dataStrategy);
 | |
|     if (request.signal.aborted) {
 | |
|       throwStaticHandlerAbortedError(request, isRouteRequest, future);
 | |
|     }
 | |
| 
 | |
|     // Process and commit output from loaders
 | |
|     let activeDeferreds = new Map();
 | |
|     let context = processRouteLoaderData(matches, results, pendingActionResult, activeDeferreds, skipLoaderErrorBubbling);
 | |
| 
 | |
|     // Add a null for any non-loader matches for proper revalidation on the client
 | |
|     let executedLoaders = new Set(matchesToLoad.map(match => match.route.id));
 | |
|     matches.forEach(match => {
 | |
|       if (!executedLoaders.has(match.route.id)) {
 | |
|         context.loaderData[match.route.id] = null;
 | |
|       }
 | |
|     });
 | |
|     return _extends({}, context, {
 | |
|       matches,
 | |
|       activeDeferreds: activeDeferreds.size > 0 ? Object.fromEntries(activeDeferreds.entries()) : null
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   // Utility wrapper for calling dataStrategy server-side without having to
 | |
|   // pass around the manifest, mapRouteProperties, etc.
 | |
|   async function callDataStrategy(type, request, matchesToLoad, matches, isRouteRequest, requestContext, dataStrategy) {
 | |
|     let results = await callDataStrategyImpl(dataStrategy || defaultDataStrategy, type, null, request, matchesToLoad, matches, null, manifest, mapRouteProperties, requestContext);
 | |
|     let dataResults = {};
 | |
|     await Promise.all(matches.map(async match => {
 | |
|       if (!(match.route.id in results)) {
 | |
|         return;
 | |
|       }
 | |
|       let result = results[match.route.id];
 | |
|       if (isRedirectDataStrategyResultResult(result)) {
 | |
|         let response = result.result;
 | |
|         // Throw redirects and let the server handle them with an HTTP redirect
 | |
|         throw normalizeRelativeRoutingRedirectResponse(response, request, match.route.id, matches, basename, future.v7_relativeSplatPath);
 | |
|       }
 | |
|       if (isResponse(result.result) && isRouteRequest) {
 | |
|         // For SSR single-route requests, we want to hand Responses back
 | |
|         // directly without unwrapping
 | |
|         throw result;
 | |
|       }
 | |
|       dataResults[match.route.id] = await convertDataStrategyResultToDataResult(result);
 | |
|     }));
 | |
|     return dataResults;
 | |
|   }
 | |
|   return {
 | |
|     dataRoutes,
 | |
|     query,
 | |
|     queryRoute
 | |
|   };
 | |
| }
 | |
| 
 | |
| //#endregion
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| //#region Helpers
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /**
 | |
|  * Given an existing StaticHandlerContext and an error thrown at render time,
 | |
|  * provide an updated StaticHandlerContext suitable for a second SSR render
 | |
|  */
 | |
| function getStaticContextFromError(routes, context, error) {
 | |
|   let newContext = _extends({}, context, {
 | |
|     statusCode: isRouteErrorResponse(error) ? error.status : 500,
 | |
|     errors: {
 | |
|       [context._deepestRenderedBoundaryId || routes[0].id]: error
 | |
|     }
 | |
|   });
 | |
|   return newContext;
 | |
| }
 | |
| function throwStaticHandlerAbortedError(request, isRouteRequest, future) {
 | |
|   if (future.v7_throwAbortReason && request.signal.reason !== undefined) {
 | |
|     throw request.signal.reason;
 | |
|   }
 | |
|   let method = isRouteRequest ? "queryRoute" : "query";
 | |
|   throw new Error(method + "() call aborted: " + request.method + " " + request.url);
 | |
| }
 | |
| function isSubmissionNavigation(opts) {
 | |
|   return opts != null && ("formData" in opts && opts.formData != null || "body" in opts && opts.body !== undefined);
 | |
| }
 | |
| function normalizeTo(location, matches, basename, prependBasename, to, v7_relativeSplatPath, fromRouteId, relative) {
 | |
|   let contextualMatches;
 | |
|   let activeRouteMatch;
 | |
|   if (fromRouteId) {
 | |
|     // Grab matches up to the calling route so our route-relative logic is
 | |
|     // relative to the correct source route
 | |
|     contextualMatches = [];
 | |
|     for (let match of matches) {
 | |
|       contextualMatches.push(match);
 | |
|       if (match.route.id === fromRouteId) {
 | |
|         activeRouteMatch = match;
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
|   } else {
 | |
|     contextualMatches = matches;
 | |
|     activeRouteMatch = matches[matches.length - 1];
 | |
|   }
 | |
| 
 | |
|   // Resolve the relative path
 | |
|   let path = resolveTo(to ? to : ".", getResolveToMatches(contextualMatches, v7_relativeSplatPath), stripBasename(location.pathname, basename) || location.pathname, relative === "path");
 | |
| 
 | |
|   // When `to` is not specified we inherit search/hash from the current
 | |
|   // location, unlike when to="." and we just inherit the path.
 | |
|   // See https://github.com/remix-run/remix/issues/927
 | |
|   if (to == null) {
 | |
|     path.search = location.search;
 | |
|     path.hash = location.hash;
 | |
|   }
 | |
| 
 | |
|   // Account for `?index` params when routing to the current location
 | |
|   if ((to == null || to === "" || to === ".") && activeRouteMatch) {
 | |
|     let nakedIndex = hasNakedIndexQuery(path.search);
 | |
|     if (activeRouteMatch.route.index && !nakedIndex) {
 | |
|       // Add one when we're targeting an index route
 | |
|       path.search = path.search ? path.search.replace(/^\?/, "?index&") : "?index";
 | |
|     } else if (!activeRouteMatch.route.index && nakedIndex) {
 | |
|       // Remove existing ones when we're not
 | |
|       let params = new URLSearchParams(path.search);
 | |
|       let indexValues = params.getAll("index");
 | |
|       params.delete("index");
 | |
|       indexValues.filter(v => v).forEach(v => params.append("index", v));
 | |
|       let qs = params.toString();
 | |
|       path.search = qs ? "?" + qs : "";
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // If we're operating within a basename, prepend it to the pathname.  If
 | |
|   // this is a root navigation, then just use the raw basename which allows
 | |
|   // the basename to have full control over the presence of a trailing slash
 | |
|   // on root actions
 | |
|   if (prependBasename && basename !== "/") {
 | |
|     path.pathname = path.pathname === "/" ? basename : joinPaths([basename, path.pathname]);
 | |
|   }
 | |
|   return createPath(path);
 | |
| }
 | |
| 
 | |
| // Normalize navigation options by converting formMethod=GET formData objects to
 | |
| // URLSearchParams so they behave identically to links with query params
 | |
| function normalizeNavigateOptions(normalizeFormMethod, isFetcher, path, opts) {
 | |
|   // Return location verbatim on non-submission navigations
 | |
|   if (!opts || !isSubmissionNavigation(opts)) {
 | |
|     return {
 | |
|       path
 | |
|     };
 | |
|   }
 | |
|   if (opts.formMethod && !isValidMethod(opts.formMethod)) {
 | |
|     return {
 | |
|       path,
 | |
|       error: getInternalRouterError(405, {
 | |
|         method: opts.formMethod
 | |
|       })
 | |
|     };
 | |
|   }
 | |
|   let getInvalidBodyError = () => ({
 | |
|     path,
 | |
|     error: getInternalRouterError(400, {
 | |
|       type: "invalid-body"
 | |
|     })
 | |
|   });
 | |
| 
 | |
|   // Create a Submission on non-GET navigations
 | |
|   let rawFormMethod = opts.formMethod || "get";
 | |
|   let formMethod = normalizeFormMethod ? rawFormMethod.toUpperCase() : rawFormMethod.toLowerCase();
 | |
|   let formAction = stripHashFromPath(path);
 | |
|   if (opts.body !== undefined) {
 | |
|     if (opts.formEncType === "text/plain") {
 | |
|       // text only support POST/PUT/PATCH/DELETE submissions
 | |
|       if (!isMutationMethod(formMethod)) {
 | |
|         return getInvalidBodyError();
 | |
|       }
 | |
|       let text = typeof opts.body === "string" ? opts.body : opts.body instanceof FormData || opts.body instanceof URLSearchParams ?
 | |
|       // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#plain-text-form-data
 | |
|       Array.from(opts.body.entries()).reduce((acc, _ref3) => {
 | |
|         let [name, value] = _ref3;
 | |
|         return "" + acc + name + "=" + value + "\n";
 | |
|       }, "") : String(opts.body);
 | |
|       return {
 | |
|         path,
 | |
|         submission: {
 | |
|           formMethod,
 | |
|           formAction,
 | |
|           formEncType: opts.formEncType,
 | |
|           formData: undefined,
 | |
|           json: undefined,
 | |
|           text
 | |
|         }
 | |
|       };
 | |
|     } else if (opts.formEncType === "application/json") {
 | |
|       // json only supports POST/PUT/PATCH/DELETE submissions
 | |
|       if (!isMutationMethod(formMethod)) {
 | |
|         return getInvalidBodyError();
 | |
|       }
 | |
|       try {
 | |
|         let json = typeof opts.body === "string" ? JSON.parse(opts.body) : opts.body;
 | |
|         return {
 | |
|           path,
 | |
|           submission: {
 | |
|             formMethod,
 | |
|             formAction,
 | |
|             formEncType: opts.formEncType,
 | |
|             formData: undefined,
 | |
|             json,
 | |
|             text: undefined
 | |
|           }
 | |
|         };
 | |
|       } catch (e) {
 | |
|         return getInvalidBodyError();
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   invariant(typeof FormData === "function", "FormData is not available in this environment");
 | |
|   let searchParams;
 | |
|   let formData;
 | |
|   if (opts.formData) {
 | |
|     searchParams = convertFormDataToSearchParams(opts.formData);
 | |
|     formData = opts.formData;
 | |
|   } else if (opts.body instanceof FormData) {
 | |
|     searchParams = convertFormDataToSearchParams(opts.body);
 | |
|     formData = opts.body;
 | |
|   } else if (opts.body instanceof URLSearchParams) {
 | |
|     searchParams = opts.body;
 | |
|     formData = convertSearchParamsToFormData(searchParams);
 | |
|   } else if (opts.body == null) {
 | |
|     searchParams = new URLSearchParams();
 | |
|     formData = new FormData();
 | |
|   } else {
 | |
|     try {
 | |
|       searchParams = new URLSearchParams(opts.body);
 | |
|       formData = convertSearchParamsToFormData(searchParams);
 | |
|     } catch (e) {
 | |
|       return getInvalidBodyError();
 | |
|     }
 | |
|   }
 | |
|   let submission = {
 | |
|     formMethod,
 | |
|     formAction,
 | |
|     formEncType: opts && opts.formEncType || "application/x-www-form-urlencoded",
 | |
|     formData,
 | |
|     json: undefined,
 | |
|     text: undefined
 | |
|   };
 | |
|   if (isMutationMethod(submission.formMethod)) {
 | |
|     return {
 | |
|       path,
 | |
|       submission
 | |
|     };
 | |
|   }
 | |
| 
 | |
|   // Flatten submission onto URLSearchParams for GET submissions
 | |
|   let parsedPath = parsePath(path);
 | |
|   // On GET navigation submissions we can drop the ?index param from the
 | |
|   // resulting location since all loaders will run.  But fetcher GET submissions
 | |
|   // only run a single loader so we need to preserve any incoming ?index params
 | |
|   if (isFetcher && parsedPath.search && hasNakedIndexQuery(parsedPath.search)) {
 | |
|     searchParams.append("index", "");
 | |
|   }
 | |
|   parsedPath.search = "?" + searchParams;
 | |
|   return {
 | |
|     path: createPath(parsedPath),
 | |
|     submission
 | |
|   };
 | |
| }
 | |
| 
 | |
| // Filter out all routes at/below any caught error as they aren't going to
 | |
| // render so we don't need to load them
 | |
| function getLoaderMatchesUntilBoundary(matches, boundaryId, includeBoundary) {
 | |
|   if (includeBoundary === void 0) {
 | |
|     includeBoundary = false;
 | |
|   }
 | |
|   let index = matches.findIndex(m => m.route.id === boundaryId);
 | |
|   if (index >= 0) {
 | |
|     return matches.slice(0, includeBoundary ? index + 1 : index);
 | |
|   }
 | |
|   return matches;
 | |
| }
 | |
| function getMatchesToLoad(history, state, matches, submission, location, initialHydration, skipActionErrorRevalidation, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, deletedFetchers, fetchLoadMatches, fetchRedirectIds, routesToUse, basename, pendingActionResult) {
 | |
|   let actionResult = pendingActionResult ? isErrorResult(pendingActionResult[1]) ? pendingActionResult[1].error : pendingActionResult[1].data : undefined;
 | |
|   let currentUrl = history.createURL(state.location);
 | |
|   let nextUrl = history.createURL(location);
 | |
| 
 | |
|   // Pick navigation matches that are net-new or qualify for revalidation
 | |
|   let boundaryMatches = matches;
 | |
|   if (initialHydration && state.errors) {
 | |
|     // On initial hydration, only consider matches up to _and including_ the boundary.
 | |
|     // This is inclusive to handle cases where a server loader ran successfully,
 | |
|     // a child server loader bubbled up to this route, but this route has
 | |
|     // `clientLoader.hydrate` so we want to still run the `clientLoader` so that
 | |
|     // we have a complete version of `loaderData`
 | |
|     boundaryMatches = getLoaderMatchesUntilBoundary(matches, Object.keys(state.errors)[0], true);
 | |
|   } else if (pendingActionResult && isErrorResult(pendingActionResult[1])) {
 | |
|     // If an action threw an error, we call loaders up to, but not including the
 | |
|     // boundary
 | |
|     boundaryMatches = getLoaderMatchesUntilBoundary(matches, pendingActionResult[0]);
 | |
|   }
 | |
| 
 | |
|   // Don't revalidate loaders by default after action 4xx/5xx responses
 | |
|   // when the flag is enabled.  They can still opt-into revalidation via
 | |
|   // `shouldRevalidate` via `actionResult`
 | |
|   let actionStatus = pendingActionResult ? pendingActionResult[1].statusCode : undefined;
 | |
|   let shouldSkipRevalidation = skipActionErrorRevalidation && actionStatus && actionStatus >= 400;
 | |
|   let navigationMatches = boundaryMatches.filter((match, index) => {
 | |
|     let {
 | |
|       route
 | |
|     } = match;
 | |
|     if (route.lazy) {
 | |
|       // We haven't loaded this route yet so we don't know if it's got a loader!
 | |
|       return true;
 | |
|     }
 | |
|     if (route.loader == null) {
 | |
|       return false;
 | |
|     }
 | |
|     if (initialHydration) {
 | |
|       return shouldLoadRouteOnHydration(route, state.loaderData, state.errors);
 | |
|     }
 | |
| 
 | |
|     // Always call the loader on new route instances and pending defer cancellations
 | |
|     if (isNewLoader(state.loaderData, state.matches[index], match) || cancelledDeferredRoutes.some(id => id === match.route.id)) {
 | |
|       return true;
 | |
|     }
 | |
| 
 | |
|     // This is the default implementation for when we revalidate.  If the route
 | |
|     // provides it's own implementation, then we give them full control but
 | |
|     // provide this value so they can leverage it if needed after they check
 | |
|     // their own specific use cases
 | |
|     let currentRouteMatch = state.matches[index];
 | |
|     let nextRouteMatch = match;
 | |
|     return shouldRevalidateLoader(match, _extends({
 | |
|       currentUrl,
 | |
|       currentParams: currentRouteMatch.params,
 | |
|       nextUrl,
 | |
|       nextParams: nextRouteMatch.params
 | |
|     }, submission, {
 | |
|       actionResult,
 | |
|       actionStatus,
 | |
|       defaultShouldRevalidate: shouldSkipRevalidation ? false :
 | |
|       // Forced revalidation due to submission, useRevalidator, or X-Remix-Revalidate
 | |
|       isRevalidationRequired || currentUrl.pathname + currentUrl.search === nextUrl.pathname + nextUrl.search ||
 | |
|       // Search params affect all loaders
 | |
|       currentUrl.search !== nextUrl.search || isNewRouteInstance(currentRouteMatch, nextRouteMatch)
 | |
|     }));
 | |
|   });
 | |
| 
 | |
|   // Pick fetcher.loads that need to be revalidated
 | |
|   let revalidatingFetchers = [];
 | |
|   fetchLoadMatches.forEach((f, key) => {
 | |
|     // Don't revalidate:
 | |
|     //  - on initial hydration (shouldn't be any fetchers then anyway)
 | |
|     //  - if fetcher won't be present in the subsequent render
 | |
|     //    - no longer matches the URL (v7_fetcherPersist=false)
 | |
|     //    - was unmounted but persisted due to v7_fetcherPersist=true
 | |
|     if (initialHydration || !matches.some(m => m.route.id === f.routeId) || deletedFetchers.has(key)) {
 | |
|       return;
 | |
|     }
 | |
|     let fetcherMatches = matchRoutes(routesToUse, f.path, basename);
 | |
| 
 | |
|     // If the fetcher path no longer matches, push it in with null matches so
 | |
|     // we can trigger a 404 in callLoadersAndMaybeResolveData.  Note this is
 | |
|     // currently only a use-case for Remix HMR where the route tree can change
 | |
|     // at runtime and remove a route previously loaded via a fetcher
 | |
|     if (!fetcherMatches) {
 | |
|       revalidatingFetchers.push({
 | |
|         key,
 | |
|         routeId: f.routeId,
 | |
|         path: f.path,
 | |
|         matches: null,
 | |
|         match: null,
 | |
|         controller: null
 | |
|       });
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // Revalidating fetchers are decoupled from the route matches since they
 | |
|     // load from a static href.  They revalidate based on explicit revalidation
 | |
|     // (submission, useRevalidator, or X-Remix-Revalidate)
 | |
|     let fetcher = state.fetchers.get(key);
 | |
|     let fetcherMatch = getTargetMatch(fetcherMatches, f.path);
 | |
|     let shouldRevalidate = false;
 | |
|     if (fetchRedirectIds.has(key)) {
 | |
|       // Never trigger a revalidation of an actively redirecting fetcher
 | |
|       shouldRevalidate = false;
 | |
|     } else if (cancelledFetcherLoads.has(key)) {
 | |
|       // Always mark for revalidation if the fetcher was cancelled
 | |
|       cancelledFetcherLoads.delete(key);
 | |
|       shouldRevalidate = true;
 | |
|     } else if (fetcher && fetcher.state !== "idle" && fetcher.data === undefined) {
 | |
|       // If the fetcher hasn't ever completed loading yet, then this isn't a
 | |
|       // revalidation, it would just be a brand new load if an explicit
 | |
|       // revalidation is required
 | |
|       shouldRevalidate = isRevalidationRequired;
 | |
|     } else {
 | |
|       // Otherwise fall back on any user-defined shouldRevalidate, defaulting
 | |
|       // to explicit revalidations only
 | |
|       shouldRevalidate = shouldRevalidateLoader(fetcherMatch, _extends({
 | |
|         currentUrl,
 | |
|         currentParams: state.matches[state.matches.length - 1].params,
 | |
|         nextUrl,
 | |
|         nextParams: matches[matches.length - 1].params
 | |
|       }, submission, {
 | |
|         actionResult,
 | |
|         actionStatus,
 | |
|         defaultShouldRevalidate: shouldSkipRevalidation ? false : isRevalidationRequired
 | |
|       }));
 | |
|     }
 | |
|     if (shouldRevalidate) {
 | |
|       revalidatingFetchers.push({
 | |
|         key,
 | |
|         routeId: f.routeId,
 | |
|         path: f.path,
 | |
|         matches: fetcherMatches,
 | |
|         match: fetcherMatch,
 | |
|         controller: new AbortController()
 | |
|       });
 | |
|     }
 | |
|   });
 | |
|   return [navigationMatches, revalidatingFetchers];
 | |
| }
 | |
| function shouldLoadRouteOnHydration(route, loaderData, errors) {
 | |
|   // We dunno if we have a loader - gotta find out!
 | |
|   if (route.lazy) {
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   // No loader, nothing to initialize
 | |
|   if (!route.loader) {
 | |
|     return false;
 | |
|   }
 | |
|   let hasData = loaderData != null && loaderData[route.id] !== undefined;
 | |
|   let hasError = errors != null && errors[route.id] !== undefined;
 | |
| 
 | |
|   // Don't run if we error'd during SSR
 | |
|   if (!hasData && hasError) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // Explicitly opting-in to running on hydration
 | |
|   if (typeof route.loader === "function" && route.loader.hydrate === true) {
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   // Otherwise, run if we're not yet initialized with anything
 | |
|   return !hasData && !hasError;
 | |
| }
 | |
| function isNewLoader(currentLoaderData, currentMatch, match) {
 | |
|   let isNew =
 | |
|   // [a] -> [a, b]
 | |
|   !currentMatch ||
 | |
|   // [a, b] -> [a, c]
 | |
|   match.route.id !== currentMatch.route.id;
 | |
| 
 | |
|   // Handle the case that we don't have data for a re-used route, potentially
 | |
|   // from a prior error or from a cancelled pending deferred
 | |
|   let isMissingData = currentLoaderData[match.route.id] === undefined;
 | |
| 
 | |
|   // Always load if this is a net-new route or we don't yet have data
 | |
|   return isNew || isMissingData;
 | |
| }
 | |
| function isNewRouteInstance(currentMatch, match) {
 | |
|   let currentPath = currentMatch.route.path;
 | |
|   return (
 | |
|     // param change for this match, /users/123 -> /users/456
 | |
|     currentMatch.pathname !== match.pathname ||
 | |
|     // splat param changed, which is not present in match.path
 | |
|     // e.g. /files/images/avatar.jpg -> files/finances.xls
 | |
|     currentPath != null && currentPath.endsWith("*") && currentMatch.params["*"] !== match.params["*"]
 | |
|   );
 | |
| }
 | |
| function shouldRevalidateLoader(loaderMatch, arg) {
 | |
|   if (loaderMatch.route.shouldRevalidate) {
 | |
|     let routeChoice = loaderMatch.route.shouldRevalidate(arg);
 | |
|     if (typeof routeChoice === "boolean") {
 | |
|       return routeChoice;
 | |
|     }
 | |
|   }
 | |
|   return arg.defaultShouldRevalidate;
 | |
| }
 | |
| function patchRoutesImpl(routeId, children, routesToUse, manifest, mapRouteProperties) {
 | |
|   var _childrenToPatch;
 | |
|   let childrenToPatch;
 | |
|   if (routeId) {
 | |
|     let route = manifest[routeId];
 | |
|     invariant(route, "No route found to patch children into: routeId = " + routeId);
 | |
|     if (!route.children) {
 | |
|       route.children = [];
 | |
|     }
 | |
|     childrenToPatch = route.children;
 | |
|   } else {
 | |
|     childrenToPatch = routesToUse;
 | |
|   }
 | |
| 
 | |
|   // Don't patch in routes we already know about so that `patch` is idempotent
 | |
|   // to simplify user-land code. This is useful because we re-call the
 | |
|   // `patchRoutesOnNavigation` function for matched routes with params.
 | |
|   let uniqueChildren = children.filter(newRoute => !childrenToPatch.some(existingRoute => isSameRoute(newRoute, existingRoute)));
 | |
|   let newRoutes = convertRoutesToDataRoutes(uniqueChildren, mapRouteProperties, [routeId || "_", "patch", String(((_childrenToPatch = childrenToPatch) == null ? void 0 : _childrenToPatch.length) || "0")], manifest);
 | |
|   childrenToPatch.push(...newRoutes);
 | |
| }
 | |
| function isSameRoute(newRoute, existingRoute) {
 | |
|   // Most optimal check is by id
 | |
|   if ("id" in newRoute && "id" in existingRoute && newRoute.id === existingRoute.id) {
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   // Second is by pathing differences
 | |
|   if (!(newRoute.index === existingRoute.index && newRoute.path === existingRoute.path && newRoute.caseSensitive === existingRoute.caseSensitive)) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // Pathless layout routes are trickier since we need to check children.
 | |
|   // If they have no children then they're the same as far as we can tell
 | |
|   if ((!newRoute.children || newRoute.children.length === 0) && (!existingRoute.children || existingRoute.children.length === 0)) {
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   // Otherwise, we look to see if every child in the new route is already
 | |
|   // represented in the existing route's children
 | |
|   return newRoute.children.every((aChild, i) => {
 | |
|     var _existingRoute$childr;
 | |
|     return (_existingRoute$childr = existingRoute.children) == null ? void 0 : _existingRoute$childr.some(bChild => isSameRoute(aChild, bChild));
 | |
|   });
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Execute route.lazy() methods to lazily load route modules (loader, action,
 | |
|  * shouldRevalidate) and update the routeManifest in place which shares objects
 | |
|  * with dataRoutes so those get updated as well.
 | |
|  */
 | |
| async function loadLazyRouteModule(route, mapRouteProperties, manifest) {
 | |
|   if (!route.lazy) {
 | |
|     return;
 | |
|   }
 | |
|   let lazyRoute = await route.lazy();
 | |
| 
 | |
|   // If the lazy route function was executed and removed by another parallel
 | |
|   // call then we can return - first lazy() to finish wins because the return
 | |
|   // value of lazy is expected to be static
 | |
|   if (!route.lazy) {
 | |
|     return;
 | |
|   }
 | |
|   let routeToUpdate = manifest[route.id];
 | |
|   invariant(routeToUpdate, "No route found in manifest");
 | |
| 
 | |
|   // Update the route in place.  This should be safe because there's no way
 | |
|   // we could yet be sitting on this route as we can't get there without
 | |
|   // resolving lazy() first.
 | |
|   //
 | |
|   // This is different than the HMR "update" use-case where we may actively be
 | |
|   // on the route being updated.  The main concern boils down to "does this
 | |
|   // mutation affect any ongoing navigations or any current state.matches
 | |
|   // values?".  If not, it should be safe to update in place.
 | |
|   let routeUpdates = {};
 | |
|   for (let lazyRouteProperty in lazyRoute) {
 | |
|     let staticRouteValue = routeToUpdate[lazyRouteProperty];
 | |
|     let isPropertyStaticallyDefined = staticRouteValue !== undefined &&
 | |
|     // This property isn't static since it should always be updated based
 | |
|     // on the route updates
 | |
|     lazyRouteProperty !== "hasErrorBoundary";
 | |
|     warning(!isPropertyStaticallyDefined, "Route \"" + routeToUpdate.id + "\" has a static property \"" + lazyRouteProperty + "\" " + "defined but its lazy function is also returning a value for this property. " + ("The lazy route property \"" + lazyRouteProperty + "\" will be ignored."));
 | |
|     if (!isPropertyStaticallyDefined && !immutableRouteKeys.has(lazyRouteProperty)) {
 | |
|       routeUpdates[lazyRouteProperty] = lazyRoute[lazyRouteProperty];
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Mutate the route with the provided updates.  Do this first so we pass
 | |
|   // the updated version to mapRouteProperties
 | |
|   Object.assign(routeToUpdate, routeUpdates);
 | |
| 
 | |
|   // Mutate the `hasErrorBoundary` property on the route based on the route
 | |
|   // updates and remove the `lazy` function so we don't resolve the lazy
 | |
|   // route again.
 | |
|   Object.assign(routeToUpdate, _extends({}, mapRouteProperties(routeToUpdate), {
 | |
|     lazy: undefined
 | |
|   }));
 | |
| }
 | |
| 
 | |
| // Default implementation of `dataStrategy` which fetches all loaders in parallel
 | |
| async function defaultDataStrategy(_ref4) {
 | |
|   let {
 | |
|     matches
 | |
|   } = _ref4;
 | |
|   let matchesToLoad = matches.filter(m => m.shouldLoad);
 | |
|   let results = await Promise.all(matchesToLoad.map(m => m.resolve()));
 | |
|   return results.reduce((acc, result, i) => Object.assign(acc, {
 | |
|     [matchesToLoad[i].route.id]: result
 | |
|   }), {});
 | |
| }
 | |
| async function callDataStrategyImpl(dataStrategyImpl, type, state, request, matchesToLoad, matches, fetcherKey, manifest, mapRouteProperties, requestContext) {
 | |
|   let loadRouteDefinitionsPromises = matches.map(m => m.route.lazy ? loadLazyRouteModule(m.route, mapRouteProperties, manifest) : undefined);
 | |
|   let dsMatches = matches.map((match, i) => {
 | |
|     let loadRoutePromise = loadRouteDefinitionsPromises[i];
 | |
|     let shouldLoad = matchesToLoad.some(m => m.route.id === match.route.id);
 | |
|     // `resolve` encapsulates route.lazy(), executing the loader/action,
 | |
|     // and mapping return values/thrown errors to a `DataStrategyResult`.  Users
 | |
|     // can pass a callback to take fine-grained control over the execution
 | |
|     // of the loader/action
 | |
|     let resolve = async handlerOverride => {
 | |
|       if (handlerOverride && request.method === "GET" && (match.route.lazy || match.route.loader)) {
 | |
|         shouldLoad = true;
 | |
|       }
 | |
|       return shouldLoad ? callLoaderOrAction(type, request, match, loadRoutePromise, handlerOverride, requestContext) : Promise.resolve({
 | |
|         type: ResultType.data,
 | |
|         result: undefined
 | |
|       });
 | |
|     };
 | |
|     return _extends({}, match, {
 | |
|       shouldLoad,
 | |
|       resolve
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   // Send all matches here to allow for a middleware-type implementation.
 | |
|   // handler will be a no-op for unneeded routes and we filter those results
 | |
|   // back out below.
 | |
|   let results = await dataStrategyImpl({
 | |
|     matches: dsMatches,
 | |
|     request,
 | |
|     params: matches[0].params,
 | |
|     fetcherKey,
 | |
|     context: requestContext
 | |
|   });
 | |
| 
 | |
|   // Wait for all routes to load here but 'swallow the error since we want
 | |
|   // it to bubble up from the `await loadRoutePromise` in `callLoaderOrAction` -
 | |
|   // called from `match.resolve()`
 | |
|   try {
 | |
|     await Promise.all(loadRouteDefinitionsPromises);
 | |
|   } catch (e) {
 | |
|     // No-op
 | |
|   }
 | |
|   return results;
 | |
| }
 | |
| 
 | |
| // Default logic for calling a loader/action is the user has no specified a dataStrategy
 | |
| async function callLoaderOrAction(type, request, match, loadRoutePromise, handlerOverride, staticContext) {
 | |
|   let result;
 | |
|   let onReject;
 | |
|   let runHandler = handler => {
 | |
|     // Setup a promise we can race against so that abort signals short circuit
 | |
|     let reject;
 | |
|     // This will never resolve so safe to type it as Promise<DataStrategyResult> to
 | |
|     // satisfy the function return value
 | |
|     let abortPromise = new Promise((_, r) => reject = r);
 | |
|     onReject = () => reject();
 | |
|     request.signal.addEventListener("abort", onReject);
 | |
|     let actualHandler = ctx => {
 | |
|       if (typeof handler !== "function") {
 | |
|         return Promise.reject(new Error("You cannot call the handler for a route which defines a boolean " + ("\"" + type + "\" [routeId: " + match.route.id + "]")));
 | |
|       }
 | |
|       return handler({
 | |
|         request,
 | |
|         params: match.params,
 | |
|         context: staticContext
 | |
|       }, ...(ctx !== undefined ? [ctx] : []));
 | |
|     };
 | |
|     let handlerPromise = (async () => {
 | |
|       try {
 | |
|         let val = await (handlerOverride ? handlerOverride(ctx => actualHandler(ctx)) : actualHandler());
 | |
|         return {
 | |
|           type: "data",
 | |
|           result: val
 | |
|         };
 | |
|       } catch (e) {
 | |
|         return {
 | |
|           type: "error",
 | |
|           result: e
 | |
|         };
 | |
|       }
 | |
|     })();
 | |
|     return Promise.race([handlerPromise, abortPromise]);
 | |
|   };
 | |
|   try {
 | |
|     let handler = match.route[type];
 | |
| 
 | |
|     // If we have a route.lazy promise, await that first
 | |
|     if (loadRoutePromise) {
 | |
|       if (handler) {
 | |
|         // Run statically defined handler in parallel with lazy()
 | |
|         let handlerError;
 | |
|         let [value] = await Promise.all([
 | |
|         // If the handler throws, don't let it immediately bubble out,
 | |
|         // since we need to let the lazy() execution finish so we know if this
 | |
|         // route has a boundary that can handle the error
 | |
|         runHandler(handler).catch(e => {
 | |
|           handlerError = e;
 | |
|         }), loadRoutePromise]);
 | |
|         if (handlerError !== undefined) {
 | |
|           throw handlerError;
 | |
|         }
 | |
|         result = value;
 | |
|       } else {
 | |
|         // Load lazy route module, then run any returned handler
 | |
|         await loadRoutePromise;
 | |
|         handler = match.route[type];
 | |
|         if (handler) {
 | |
|           // Handler still runs even if we got interrupted to maintain consistency
 | |
|           // with un-abortable behavior of handler execution on non-lazy or
 | |
|           // previously-lazy-loaded routes
 | |
|           result = await runHandler(handler);
 | |
|         } else if (type === "action") {
 | |
|           let url = new URL(request.url);
 | |
|           let pathname = url.pathname + url.search;
 | |
|           throw getInternalRouterError(405, {
 | |
|             method: request.method,
 | |
|             pathname,
 | |
|             routeId: match.route.id
 | |
|           });
 | |
|         } else {
 | |
|           // lazy() route has no loader to run.  Short circuit here so we don't
 | |
|           // hit the invariant below that errors on returning undefined.
 | |
|           return {
 | |
|             type: ResultType.data,
 | |
|             result: undefined
 | |
|           };
 | |
|         }
 | |
|       }
 | |
|     } else if (!handler) {
 | |
|       let url = new URL(request.url);
 | |
|       let pathname = url.pathname + url.search;
 | |
|       throw getInternalRouterError(404, {
 | |
|         pathname
 | |
|       });
 | |
|     } else {
 | |
|       result = await runHandler(handler);
 | |
|     }
 | |
|     invariant(result.result !== undefined, "You defined " + (type === "action" ? "an action" : "a loader") + " for route " + ("\"" + match.route.id + "\" but didn't return anything from your `" + type + "` ") + "function. Please return a value or `null`.");
 | |
|   } catch (e) {
 | |
|     // We should already be catching and converting normal handler executions to
 | |
|     // DataStrategyResults and returning them, so anything that throws here is an
 | |
|     // unexpected error we still need to wrap
 | |
|     return {
 | |
|       type: ResultType.error,
 | |
|       result: e
 | |
|     };
 | |
|   } finally {
 | |
|     if (onReject) {
 | |
|       request.signal.removeEventListener("abort", onReject);
 | |
|     }
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| async function convertDataStrategyResultToDataResult(dataStrategyResult) {
 | |
|   let {
 | |
|     result,
 | |
|     type
 | |
|   } = dataStrategyResult;
 | |
|   if (isResponse(result)) {
 | |
|     let data;
 | |
|     try {
 | |
|       let contentType = result.headers.get("Content-Type");
 | |
|       // Check between word boundaries instead of startsWith() due to the last
 | |
|       // paragraph of https://httpwg.org/specs/rfc9110.html#field.content-type
 | |
|       if (contentType && /\bapplication\/json\b/.test(contentType)) {
 | |
|         if (result.body == null) {
 | |
|           data = null;
 | |
|         } else {
 | |
|           data = await result.json();
 | |
|         }
 | |
|       } else {
 | |
|         data = await result.text();
 | |
|       }
 | |
|     } catch (e) {
 | |
|       return {
 | |
|         type: ResultType.error,
 | |
|         error: e
 | |
|       };
 | |
|     }
 | |
|     if (type === ResultType.error) {
 | |
|       return {
 | |
|         type: ResultType.error,
 | |
|         error: new ErrorResponseImpl(result.status, result.statusText, data),
 | |
|         statusCode: result.status,
 | |
|         headers: result.headers
 | |
|       };
 | |
|     }
 | |
|     return {
 | |
|       type: ResultType.data,
 | |
|       data,
 | |
|       statusCode: result.status,
 | |
|       headers: result.headers
 | |
|     };
 | |
|   }
 | |
|   if (type === ResultType.error) {
 | |
|     if (isDataWithResponseInit(result)) {
 | |
|       var _result$init3, _result$init4;
 | |
|       if (result.data instanceof Error) {
 | |
|         var _result$init, _result$init2;
 | |
|         return {
 | |
|           type: ResultType.error,
 | |
|           error: result.data,
 | |
|           statusCode: (_result$init = result.init) == null ? void 0 : _result$init.status,
 | |
|           headers: (_result$init2 = result.init) != null && _result$init2.headers ? new Headers(result.init.headers) : undefined
 | |
|         };
 | |
|       }
 | |
| 
 | |
|       // Convert thrown data() to ErrorResponse instances
 | |
|       return {
 | |
|         type: ResultType.error,
 | |
|         error: new ErrorResponseImpl(((_result$init3 = result.init) == null ? void 0 : _result$init3.status) || 500, undefined, result.data),
 | |
|         statusCode: isRouteErrorResponse(result) ? result.status : undefined,
 | |
|         headers: (_result$init4 = result.init) != null && _result$init4.headers ? new Headers(result.init.headers) : undefined
 | |
|       };
 | |
|     }
 | |
|     return {
 | |
|       type: ResultType.error,
 | |
|       error: result,
 | |
|       statusCode: isRouteErrorResponse(result) ? result.status : undefined
 | |
|     };
 | |
|   }
 | |
|   if (isDeferredData(result)) {
 | |
|     var _result$init5, _result$init6;
 | |
|     return {
 | |
|       type: ResultType.deferred,
 | |
|       deferredData: result,
 | |
|       statusCode: (_result$init5 = result.init) == null ? void 0 : _result$init5.status,
 | |
|       headers: ((_result$init6 = result.init) == null ? void 0 : _result$init6.headers) && new Headers(result.init.headers)
 | |
|     };
 | |
|   }
 | |
|   if (isDataWithResponseInit(result)) {
 | |
|     var _result$init7, _result$init8;
 | |
|     return {
 | |
|       type: ResultType.data,
 | |
|       data: result.data,
 | |
|       statusCode: (_result$init7 = result.init) == null ? void 0 : _result$init7.status,
 | |
|       headers: (_result$init8 = result.init) != null && _result$init8.headers ? new Headers(result.init.headers) : undefined
 | |
|     };
 | |
|   }
 | |
|   return {
 | |
|     type: ResultType.data,
 | |
|     data: result
 | |
|   };
 | |
| }
 | |
| 
 | |
| // Support relative routing in internal redirects
 | |
| function normalizeRelativeRoutingRedirectResponse(response, request, routeId, matches, basename, v7_relativeSplatPath) {
 | |
|   let location = response.headers.get("Location");
 | |
|   invariant(location, "Redirects returned/thrown from loaders/actions must have a Location header");
 | |
|   if (!ABSOLUTE_URL_REGEX.test(location)) {
 | |
|     let trimmedMatches = matches.slice(0, matches.findIndex(m => m.route.id === routeId) + 1);
 | |
|     location = normalizeTo(new URL(request.url), trimmedMatches, basename, true, location, v7_relativeSplatPath);
 | |
|     response.headers.set("Location", location);
 | |
|   }
 | |
|   return response;
 | |
| }
 | |
| function normalizeRedirectLocation(location, currentUrl, basename) {
 | |
|   if (ABSOLUTE_URL_REGEX.test(location)) {
 | |
|     // Strip off the protocol+origin for same-origin + same-basename absolute redirects
 | |
|     let normalizedLocation = location;
 | |
|     let url = normalizedLocation.startsWith("//") ? new URL(currentUrl.protocol + normalizedLocation) : new URL(normalizedLocation);
 | |
|     let isSameBasename = stripBasename(url.pathname, basename) != null;
 | |
|     if (url.origin === currentUrl.origin && isSameBasename) {
 | |
|       return url.pathname + url.search + url.hash;
 | |
|     }
 | |
|   }
 | |
|   return location;
 | |
| }
 | |
| 
 | |
| // Utility method for creating the Request instances for loaders/actions during
 | |
| // client-side navigations and fetches.  During SSR we will always have a
 | |
| // Request instance from the static handler (query/queryRoute)
 | |
| function createClientSideRequest(history, location, signal, submission) {
 | |
|   let url = history.createURL(stripHashFromPath(location)).toString();
 | |
|   let init = {
 | |
|     signal
 | |
|   };
 | |
|   if (submission && isMutationMethod(submission.formMethod)) {
 | |
|     let {
 | |
|       formMethod,
 | |
|       formEncType
 | |
|     } = submission;
 | |
|     // Didn't think we needed this but it turns out unlike other methods, patch
 | |
|     // won't be properly normalized to uppercase and results in a 405 error.
 | |
|     // See: https://fetch.spec.whatwg.org/#concept-method
 | |
|     init.method = formMethod.toUpperCase();
 | |
|     if (formEncType === "application/json") {
 | |
|       init.headers = new Headers({
 | |
|         "Content-Type": formEncType
 | |
|       });
 | |
|       init.body = JSON.stringify(submission.json);
 | |
|     } else if (formEncType === "text/plain") {
 | |
|       // Content-Type is inferred (https://fetch.spec.whatwg.org/#dom-request)
 | |
|       init.body = submission.text;
 | |
|     } else if (formEncType === "application/x-www-form-urlencoded" && submission.formData) {
 | |
|       // Content-Type is inferred (https://fetch.spec.whatwg.org/#dom-request)
 | |
|       init.body = convertFormDataToSearchParams(submission.formData);
 | |
|     } else {
 | |
|       // Content-Type is inferred (https://fetch.spec.whatwg.org/#dom-request)
 | |
|       init.body = submission.formData;
 | |
|     }
 | |
|   }
 | |
|   return new Request(url, init);
 | |
| }
 | |
| function convertFormDataToSearchParams(formData) {
 | |
|   let searchParams = new URLSearchParams();
 | |
|   for (let [key, value] of formData.entries()) {
 | |
|     // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#converting-an-entry-list-to-a-list-of-name-value-pairs
 | |
|     searchParams.append(key, typeof value === "string" ? value : value.name);
 | |
|   }
 | |
|   return searchParams;
 | |
| }
 | |
| function convertSearchParamsToFormData(searchParams) {
 | |
|   let formData = new FormData();
 | |
|   for (let [key, value] of searchParams.entries()) {
 | |
|     formData.append(key, value);
 | |
|   }
 | |
|   return formData;
 | |
| }
 | |
| function processRouteLoaderData(matches, results, pendingActionResult, activeDeferreds, skipLoaderErrorBubbling) {
 | |
|   // Fill in loaderData/errors from our loaders
 | |
|   let loaderData = {};
 | |
|   let errors = null;
 | |
|   let statusCode;
 | |
|   let foundError = false;
 | |
|   let loaderHeaders = {};
 | |
|   let pendingError = pendingActionResult && isErrorResult(pendingActionResult[1]) ? pendingActionResult[1].error : undefined;
 | |
| 
 | |
|   // Process loader results into state.loaderData/state.errors
 | |
|   matches.forEach(match => {
 | |
|     if (!(match.route.id in results)) {
 | |
|       return;
 | |
|     }
 | |
|     let id = match.route.id;
 | |
|     let result = results[id];
 | |
|     invariant(!isRedirectResult(result), "Cannot handle redirect results in processLoaderData");
 | |
|     if (isErrorResult(result)) {
 | |
|       let error = result.error;
 | |
|       // If we have a pending action error, we report it at the highest-route
 | |
|       // that throws a loader error, and then clear it out to indicate that
 | |
|       // it was consumed
 | |
|       if (pendingError !== undefined) {
 | |
|         error = pendingError;
 | |
|         pendingError = undefined;
 | |
|       }
 | |
|       errors = errors || {};
 | |
|       if (skipLoaderErrorBubbling) {
 | |
|         errors[id] = error;
 | |
|       } else {
 | |
|         // Look upwards from the matched route for the closest ancestor error
 | |
|         // boundary, defaulting to the root match.  Prefer higher error values
 | |
|         // if lower errors bubble to the same boundary
 | |
|         let boundaryMatch = findNearestBoundary(matches, id);
 | |
|         if (errors[boundaryMatch.route.id] == null) {
 | |
|           errors[boundaryMatch.route.id] = error;
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       // Clear our any prior loaderData for the throwing route
 | |
|       loaderData[id] = undefined;
 | |
| 
 | |
|       // Once we find our first (highest) error, we set the status code and
 | |
|       // prevent deeper status codes from overriding
 | |
|       if (!foundError) {
 | |
|         foundError = true;
 | |
|         statusCode = isRouteErrorResponse(result.error) ? result.error.status : 500;
 | |
|       }
 | |
|       if (result.headers) {
 | |
|         loaderHeaders[id] = result.headers;
 | |
|       }
 | |
|     } else {
 | |
|       if (isDeferredResult(result)) {
 | |
|         activeDeferreds.set(id, result.deferredData);
 | |
|         loaderData[id] = result.deferredData.data;
 | |
|         // Error status codes always override success status codes, but if all
 | |
|         // loaders are successful we take the deepest status code.
 | |
|         if (result.statusCode != null && result.statusCode !== 200 && !foundError) {
 | |
|           statusCode = result.statusCode;
 | |
|         }
 | |
|         if (result.headers) {
 | |
|           loaderHeaders[id] = result.headers;
 | |
|         }
 | |
|       } else {
 | |
|         loaderData[id] = result.data;
 | |
|         // Error status codes always override success status codes, but if all
 | |
|         // loaders are successful we take the deepest status code.
 | |
|         if (result.statusCode && result.statusCode !== 200 && !foundError) {
 | |
|           statusCode = result.statusCode;
 | |
|         }
 | |
|         if (result.headers) {
 | |
|           loaderHeaders[id] = result.headers;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   });
 | |
| 
 | |
|   // If we didn't consume the pending action error (i.e., all loaders
 | |
|   // resolved), then consume it here.  Also clear out any loaderData for the
 | |
|   // throwing route
 | |
|   if (pendingError !== undefined && pendingActionResult) {
 | |
|     errors = {
 | |
|       [pendingActionResult[0]]: pendingError
 | |
|     };
 | |
|     loaderData[pendingActionResult[0]] = undefined;
 | |
|   }
 | |
|   return {
 | |
|     loaderData,
 | |
|     errors,
 | |
|     statusCode: statusCode || 200,
 | |
|     loaderHeaders
 | |
|   };
 | |
| }
 | |
| function processLoaderData(state, matches, results, pendingActionResult, revalidatingFetchers, fetcherResults, activeDeferreds) {
 | |
|   let {
 | |
|     loaderData,
 | |
|     errors
 | |
|   } = processRouteLoaderData(matches, results, pendingActionResult, activeDeferreds, false // This method is only called client side so we always want to bubble
 | |
|   );
 | |
| 
 | |
|   // Process results from our revalidating fetchers
 | |
|   revalidatingFetchers.forEach(rf => {
 | |
|     let {
 | |
|       key,
 | |
|       match,
 | |
|       controller
 | |
|     } = rf;
 | |
|     let result = fetcherResults[key];
 | |
|     invariant(result, "Did not find corresponding fetcher result");
 | |
| 
 | |
|     // Process fetcher non-redirect errors
 | |
|     if (controller && controller.signal.aborted) {
 | |
|       // Nothing to do for aborted fetchers
 | |
|       return;
 | |
|     } else if (isErrorResult(result)) {
 | |
|       let boundaryMatch = findNearestBoundary(state.matches, match == null ? void 0 : match.route.id);
 | |
|       if (!(errors && errors[boundaryMatch.route.id])) {
 | |
|         errors = _extends({}, errors, {
 | |
|           [boundaryMatch.route.id]: result.error
 | |
|         });
 | |
|       }
 | |
|       state.fetchers.delete(key);
 | |
|     } else if (isRedirectResult(result)) {
 | |
|       // Should never get here, redirects should get processed above, but we
 | |
|       // keep this to type narrow to a success result in the else
 | |
|       invariant(false, "Unhandled fetcher revalidation redirect");
 | |
|     } else if (isDeferredResult(result)) {
 | |
|       // Should never get here, deferred data should be awaited for fetchers
 | |
|       // in resolveDeferredResults
 | |
|       invariant(false, "Unhandled fetcher deferred data");
 | |
|     } else {
 | |
|       let doneFetcher = getDoneFetcher(result.data);
 | |
|       state.fetchers.set(key, doneFetcher);
 | |
|     }
 | |
|   });
 | |
|   return {
 | |
|     loaderData,
 | |
|     errors
 | |
|   };
 | |
| }
 | |
| function mergeLoaderData(loaderData, newLoaderData, matches, errors) {
 | |
|   let mergedLoaderData = _extends({}, newLoaderData);
 | |
|   for (let match of matches) {
 | |
|     let id = match.route.id;
 | |
|     if (newLoaderData.hasOwnProperty(id)) {
 | |
|       if (newLoaderData[id] !== undefined) {
 | |
|         mergedLoaderData[id] = newLoaderData[id];
 | |
|       }
 | |
|     } else if (loaderData[id] !== undefined && match.route.loader) {
 | |
|       // Preserve existing keys not included in newLoaderData and where a loader
 | |
|       // wasn't removed by HMR
 | |
|       mergedLoaderData[id] = loaderData[id];
 | |
|     }
 | |
|     if (errors && errors.hasOwnProperty(id)) {
 | |
|       // Don't keep any loader data below the boundary
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
|   return mergedLoaderData;
 | |
| }
 | |
| function getActionDataForCommit(pendingActionResult) {
 | |
|   if (!pendingActionResult) {
 | |
|     return {};
 | |
|   }
 | |
|   return isErrorResult(pendingActionResult[1]) ? {
 | |
|     // Clear out prior actionData on errors
 | |
|     actionData: {}
 | |
|   } : {
 | |
|     actionData: {
 | |
|       [pendingActionResult[0]]: pendingActionResult[1].data
 | |
|     }
 | |
|   };
 | |
| }
 | |
| 
 | |
| // Find the nearest error boundary, looking upwards from the leaf route (or the
 | |
| // route specified by routeId) for the closest ancestor error boundary,
 | |
| // defaulting to the root match
 | |
| function findNearestBoundary(matches, routeId) {
 | |
|   let eligibleMatches = routeId ? matches.slice(0, matches.findIndex(m => m.route.id === routeId) + 1) : [...matches];
 | |
|   return eligibleMatches.reverse().find(m => m.route.hasErrorBoundary === true) || matches[0];
 | |
| }
 | |
| function getShortCircuitMatches(routes) {
 | |
|   // Prefer a root layout route if present, otherwise shim in a route object
 | |
|   let route = routes.length === 1 ? routes[0] : routes.find(r => r.index || !r.path || r.path === "/") || {
 | |
|     id: "__shim-error-route__"
 | |
|   };
 | |
|   return {
 | |
|     matches: [{
 | |
|       params: {},
 | |
|       pathname: "",
 | |
|       pathnameBase: "",
 | |
|       route
 | |
|     }],
 | |
|     route
 | |
|   };
 | |
| }
 | |
| function getInternalRouterError(status, _temp5) {
 | |
|   let {
 | |
|     pathname,
 | |
|     routeId,
 | |
|     method,
 | |
|     type,
 | |
|     message
 | |
|   } = _temp5 === void 0 ? {} : _temp5;
 | |
|   let statusText = "Unknown Server Error";
 | |
|   let errorMessage = "Unknown @remix-run/router error";
 | |
|   if (status === 400) {
 | |
|     statusText = "Bad Request";
 | |
|     if (method && pathname && routeId) {
 | |
|       errorMessage = "You made a " + method + " request to \"" + pathname + "\" but " + ("did not provide a `loader` for route \"" + routeId + "\", ") + "so there is no way to handle the request.";
 | |
|     } else if (type === "defer-action") {
 | |
|       errorMessage = "defer() is not supported in actions";
 | |
|     } else if (type === "invalid-body") {
 | |
|       errorMessage = "Unable to encode submission body";
 | |
|     }
 | |
|   } else if (status === 403) {
 | |
|     statusText = "Forbidden";
 | |
|     errorMessage = "Route \"" + routeId + "\" does not match URL \"" + pathname + "\"";
 | |
|   } else if (status === 404) {
 | |
|     statusText = "Not Found";
 | |
|     errorMessage = "No route matches URL \"" + pathname + "\"";
 | |
|   } else if (status === 405) {
 | |
|     statusText = "Method Not Allowed";
 | |
|     if (method && pathname && routeId) {
 | |
|       errorMessage = "You made a " + method.toUpperCase() + " request to \"" + pathname + "\" but " + ("did not provide an `action` for route \"" + routeId + "\", ") + "so there is no way to handle the request.";
 | |
|     } else if (method) {
 | |
|       errorMessage = "Invalid request method \"" + method.toUpperCase() + "\"";
 | |
|     }
 | |
|   }
 | |
|   return new ErrorResponseImpl(status || 500, statusText, new Error(errorMessage), true);
 | |
| }
 | |
| 
 | |
| // Find any returned redirect errors, starting from the lowest match
 | |
| function findRedirect(results) {
 | |
|   let entries = Object.entries(results);
 | |
|   for (let i = entries.length - 1; i >= 0; i--) {
 | |
|     let [key, result] = entries[i];
 | |
|     if (isRedirectResult(result)) {
 | |
|       return {
 | |
|         key,
 | |
|         result
 | |
|       };
 | |
|     }
 | |
|   }
 | |
| }
 | |
| function stripHashFromPath(path) {
 | |
|   let parsedPath = typeof path === "string" ? parsePath(path) : path;
 | |
|   return createPath(_extends({}, parsedPath, {
 | |
|     hash: ""
 | |
|   }));
 | |
| }
 | |
| function isHashChangeOnly(a, b) {
 | |
|   if (a.pathname !== b.pathname || a.search !== b.search) {
 | |
|     return false;
 | |
|   }
 | |
|   if (a.hash === "") {
 | |
|     // /page -> /page#hash
 | |
|     return b.hash !== "";
 | |
|   } else if (a.hash === b.hash) {
 | |
|     // /page#hash -> /page#hash
 | |
|     return true;
 | |
|   } else if (b.hash !== "") {
 | |
|     // /page#hash -> /page#other
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   // If the hash is removed the browser will re-perform a request to the server
 | |
|   // /page#hash -> /page
 | |
|   return false;
 | |
| }
 | |
| function isDataStrategyResult(result) {
 | |
|   return result != null && typeof result === "object" && "type" in result && "result" in result && (result.type === ResultType.data || result.type === ResultType.error);
 | |
| }
 | |
| function isRedirectDataStrategyResultResult(result) {
 | |
|   return isResponse(result.result) && redirectStatusCodes.has(result.result.status);
 | |
| }
 | |
| function isDeferredResult(result) {
 | |
|   return result.type === ResultType.deferred;
 | |
| }
 | |
| function isErrorResult(result) {
 | |
|   return result.type === ResultType.error;
 | |
| }
 | |
| function isRedirectResult(result) {
 | |
|   return (result && result.type) === ResultType.redirect;
 | |
| }
 | |
| function isDataWithResponseInit(value) {
 | |
|   return typeof value === "object" && value != null && "type" in value && "data" in value && "init" in value && value.type === "DataWithResponseInit";
 | |
| }
 | |
| function isDeferredData(value) {
 | |
|   let deferred = value;
 | |
|   return deferred && typeof deferred === "object" && typeof deferred.data === "object" && typeof deferred.subscribe === "function" && typeof deferred.cancel === "function" && typeof deferred.resolveData === "function";
 | |
| }
 | |
| function isResponse(value) {
 | |
|   return value != null && typeof value.status === "number" && typeof value.statusText === "string" && typeof value.headers === "object" && typeof value.body !== "undefined";
 | |
| }
 | |
| function isRedirectResponse(result) {
 | |
|   if (!isResponse(result)) {
 | |
|     return false;
 | |
|   }
 | |
|   let status = result.status;
 | |
|   let location = result.headers.get("Location");
 | |
|   return status >= 300 && status <= 399 && location != null;
 | |
| }
 | |
| function isValidMethod(method) {
 | |
|   return validRequestMethods.has(method.toLowerCase());
 | |
| }
 | |
| function isMutationMethod(method) {
 | |
|   return validMutationMethods.has(method.toLowerCase());
 | |
| }
 | |
| async function resolveNavigationDeferredResults(matches, results, signal, currentMatches, currentLoaderData) {
 | |
|   let entries = Object.entries(results);
 | |
|   for (let index = 0; index < entries.length; index++) {
 | |
|     let [routeId, result] = entries[index];
 | |
|     let match = matches.find(m => (m == null ? void 0 : m.route.id) === routeId);
 | |
|     // If we don't have a match, then we can have a deferred result to do
 | |
|     // anything with.  This is for revalidating fetchers where the route was
 | |
|     // removed during HMR
 | |
|     if (!match) {
 | |
|       continue;
 | |
|     }
 | |
|     let currentMatch = currentMatches.find(m => m.route.id === match.route.id);
 | |
|     let isRevalidatingLoader = currentMatch != null && !isNewRouteInstance(currentMatch, match) && (currentLoaderData && currentLoaderData[match.route.id]) !== undefined;
 | |
|     if (isDeferredResult(result) && isRevalidatingLoader) {
 | |
|       // Note: we do not have to touch activeDeferreds here since we race them
 | |
|       // against the signal in resolveDeferredData and they'll get aborted
 | |
|       // there if needed
 | |
|       await resolveDeferredData(result, signal, false).then(result => {
 | |
|         if (result) {
 | |
|           results[routeId] = result;
 | |
|         }
 | |
|       });
 | |
|     }
 | |
|   }
 | |
| }
 | |
| async function resolveFetcherDeferredResults(matches, results, revalidatingFetchers) {
 | |
|   for (let index = 0; index < revalidatingFetchers.length; index++) {
 | |
|     let {
 | |
|       key,
 | |
|       routeId,
 | |
|       controller
 | |
|     } = revalidatingFetchers[index];
 | |
|     let result = results[key];
 | |
|     let match = matches.find(m => (m == null ? void 0 : m.route.id) === routeId);
 | |
|     // If we don't have a match, then we can have a deferred result to do
 | |
|     // anything with.  This is for revalidating fetchers where the route was
 | |
|     // removed during HMR
 | |
|     if (!match) {
 | |
|       continue;
 | |
|     }
 | |
|     if (isDeferredResult(result)) {
 | |
|       // Note: we do not have to touch activeDeferreds here since we race them
 | |
|       // against the signal in resolveDeferredData and they'll get aborted
 | |
|       // there if needed
 | |
|       invariant(controller, "Expected an AbortController for revalidating fetcher deferred result");
 | |
|       await resolveDeferredData(result, controller.signal, true).then(result => {
 | |
|         if (result) {
 | |
|           results[key] = result;
 | |
|         }
 | |
|       });
 | |
|     }
 | |
|   }
 | |
| }
 | |
| async function resolveDeferredData(result, signal, unwrap) {
 | |
|   if (unwrap === void 0) {
 | |
|     unwrap = false;
 | |
|   }
 | |
|   let aborted = await result.deferredData.resolveData(signal);
 | |
|   if (aborted) {
 | |
|     return;
 | |
|   }
 | |
|   if (unwrap) {
 | |
|     try {
 | |
|       return {
 | |
|         type: ResultType.data,
 | |
|         data: result.deferredData.unwrappedData
 | |
|       };
 | |
|     } catch (e) {
 | |
|       // Handle any TrackedPromise._error values encountered while unwrapping
 | |
|       return {
 | |
|         type: ResultType.error,
 | |
|         error: e
 | |
|       };
 | |
|     }
 | |
|   }
 | |
|   return {
 | |
|     type: ResultType.data,
 | |
|     data: result.deferredData.data
 | |
|   };
 | |
| }
 | |
| function hasNakedIndexQuery(search) {
 | |
|   return new URLSearchParams(search).getAll("index").some(v => v === "");
 | |
| }
 | |
| function getTargetMatch(matches, location) {
 | |
|   let search = typeof location === "string" ? parsePath(location).search : location.search;
 | |
|   if (matches[matches.length - 1].route.index && hasNakedIndexQuery(search || "")) {
 | |
|     // Return the leaf index route when index is present
 | |
|     return matches[matches.length - 1];
 | |
|   }
 | |
|   // Otherwise grab the deepest "path contributing" match (ignoring index and
 | |
|   // pathless layout routes)
 | |
|   let pathMatches = getPathContributingMatches(matches);
 | |
|   return pathMatches[pathMatches.length - 1];
 | |
| }
 | |
| function getSubmissionFromNavigation(navigation) {
 | |
|   let {
 | |
|     formMethod,
 | |
|     formAction,
 | |
|     formEncType,
 | |
|     text,
 | |
|     formData,
 | |
|     json
 | |
|   } = navigation;
 | |
|   if (!formMethod || !formAction || !formEncType) {
 | |
|     return;
 | |
|   }
 | |
|   if (text != null) {
 | |
|     return {
 | |
|       formMethod,
 | |
|       formAction,
 | |
|       formEncType,
 | |
|       formData: undefined,
 | |
|       json: undefined,
 | |
|       text
 | |
|     };
 | |
|   } else if (formData != null) {
 | |
|     return {
 | |
|       formMethod,
 | |
|       formAction,
 | |
|       formEncType,
 | |
|       formData,
 | |
|       json: undefined,
 | |
|       text: undefined
 | |
|     };
 | |
|   } else if (json !== undefined) {
 | |
|     return {
 | |
|       formMethod,
 | |
|       formAction,
 | |
|       formEncType,
 | |
|       formData: undefined,
 | |
|       json,
 | |
|       text: undefined
 | |
|     };
 | |
|   }
 | |
| }
 | |
| function getLoadingNavigation(location, submission) {
 | |
|   if (submission) {
 | |
|     let navigation = {
 | |
|       state: "loading",
 | |
|       location,
 | |
|       formMethod: submission.formMethod,
 | |
|       formAction: submission.formAction,
 | |
|       formEncType: submission.formEncType,
 | |
|       formData: submission.formData,
 | |
|       json: submission.json,
 | |
|       text: submission.text
 | |
|     };
 | |
|     return navigation;
 | |
|   } else {
 | |
|     let navigation = {
 | |
|       state: "loading",
 | |
|       location,
 | |
|       formMethod: undefined,
 | |
|       formAction: undefined,
 | |
|       formEncType: undefined,
 | |
|       formData: undefined,
 | |
|       json: undefined,
 | |
|       text: undefined
 | |
|     };
 | |
|     return navigation;
 | |
|   }
 | |
| }
 | |
| function getSubmittingNavigation(location, submission) {
 | |
|   let navigation = {
 | |
|     state: "submitting",
 | |
|     location,
 | |
|     formMethod: submission.formMethod,
 | |
|     formAction: submission.formAction,
 | |
|     formEncType: submission.formEncType,
 | |
|     formData: submission.formData,
 | |
|     json: submission.json,
 | |
|     text: submission.text
 | |
|   };
 | |
|   return navigation;
 | |
| }
 | |
| function getLoadingFetcher(submission, data) {
 | |
|   if (submission) {
 | |
|     let fetcher = {
 | |
|       state: "loading",
 | |
|       formMethod: submission.formMethod,
 | |
|       formAction: submission.formAction,
 | |
|       formEncType: submission.formEncType,
 | |
|       formData: submission.formData,
 | |
|       json: submission.json,
 | |
|       text: submission.text,
 | |
|       data
 | |
|     };
 | |
|     return fetcher;
 | |
|   } else {
 | |
|     let fetcher = {
 | |
|       state: "loading",
 | |
|       formMethod: undefined,
 | |
|       formAction: undefined,
 | |
|       formEncType: undefined,
 | |
|       formData: undefined,
 | |
|       json: undefined,
 | |
|       text: undefined,
 | |
|       data
 | |
|     };
 | |
|     return fetcher;
 | |
|   }
 | |
| }
 | |
| function getSubmittingFetcher(submission, existingFetcher) {
 | |
|   let fetcher = {
 | |
|     state: "submitting",
 | |
|     formMethod: submission.formMethod,
 | |
|     formAction: submission.formAction,
 | |
|     formEncType: submission.formEncType,
 | |
|     formData: submission.formData,
 | |
|     json: submission.json,
 | |
|     text: submission.text,
 | |
|     data: existingFetcher ? existingFetcher.data : undefined
 | |
|   };
 | |
|   return fetcher;
 | |
| }
 | |
| function getDoneFetcher(data) {
 | |
|   let fetcher = {
 | |
|     state: "idle",
 | |
|     formMethod: undefined,
 | |
|     formAction: undefined,
 | |
|     formEncType: undefined,
 | |
|     formData: undefined,
 | |
|     json: undefined,
 | |
|     text: undefined,
 | |
|     data
 | |
|   };
 | |
|   return fetcher;
 | |
| }
 | |
| function restoreAppliedTransitions(_window, transitions) {
 | |
|   try {
 | |
|     let sessionPositions = _window.sessionStorage.getItem(TRANSITIONS_STORAGE_KEY);
 | |
|     if (sessionPositions) {
 | |
|       let json = JSON.parse(sessionPositions);
 | |
|       for (let [k, v] of Object.entries(json || {})) {
 | |
|         if (v && Array.isArray(v)) {
 | |
|           transitions.set(k, new Set(v || []));
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   } catch (e) {
 | |
|     // no-op, use default empty object
 | |
|   }
 | |
| }
 | |
| function persistAppliedTransitions(_window, transitions) {
 | |
|   if (transitions.size > 0) {
 | |
|     let json = {};
 | |
|     for (let [k, v] of transitions) {
 | |
|       json[k] = [...v];
 | |
|     }
 | |
|     try {
 | |
|       _window.sessionStorage.setItem(TRANSITIONS_STORAGE_KEY, JSON.stringify(json));
 | |
|     } catch (error) {
 | |
|       warning(false, "Failed to save applied view transitions in sessionStorage (" + error + ").");
 | |
|     }
 | |
|   }
 | |
| }
 | |
| //#endregion
 | |
| 
 | |
| exports.AbortedDeferredError = AbortedDeferredError;
 | |
| exports.Action = Action;
 | |
| exports.IDLE_BLOCKER = IDLE_BLOCKER;
 | |
| exports.IDLE_FETCHER = IDLE_FETCHER;
 | |
| exports.IDLE_NAVIGATION = IDLE_NAVIGATION;
 | |
| exports.UNSAFE_DEFERRED_SYMBOL = UNSAFE_DEFERRED_SYMBOL;
 | |
| exports.UNSAFE_DeferredData = DeferredData;
 | |
| exports.UNSAFE_ErrorResponseImpl = ErrorResponseImpl;
 | |
| exports.UNSAFE_convertRouteMatchToUiMatch = convertRouteMatchToUiMatch;
 | |
| exports.UNSAFE_convertRoutesToDataRoutes = convertRoutesToDataRoutes;
 | |
| exports.UNSAFE_decodePath = decodePath;
 | |
| exports.UNSAFE_getResolveToMatches = getResolveToMatches;
 | |
| exports.UNSAFE_invariant = invariant;
 | |
| exports.UNSAFE_warning = warning;
 | |
| exports.createBrowserHistory = createBrowserHistory;
 | |
| exports.createHashHistory = createHashHistory;
 | |
| exports.createMemoryHistory = createMemoryHistory;
 | |
| exports.createPath = createPath;
 | |
| exports.createRouter = createRouter;
 | |
| exports.createStaticHandler = createStaticHandler;
 | |
| exports.data = data;
 | |
| exports.defer = defer;
 | |
| exports.generatePath = generatePath;
 | |
| exports.getStaticContextFromError = getStaticContextFromError;
 | |
| exports.getToPathname = getToPathname;
 | |
| exports.isDataWithResponseInit = isDataWithResponseInit;
 | |
| exports.isDeferredData = isDeferredData;
 | |
| exports.isRouteErrorResponse = isRouteErrorResponse;
 | |
| exports.joinPaths = joinPaths;
 | |
| exports.json = json;
 | |
| exports.matchPath = matchPath;
 | |
| exports.matchRoutes = matchRoutes;
 | |
| exports.normalizePathname = normalizePathname;
 | |
| exports.parsePath = parsePath;
 | |
| exports.redirect = redirect;
 | |
| exports.redirectDocument = redirectDocument;
 | |
| exports.replace = replace;
 | |
| exports.resolvePath = resolvePath;
 | |
| exports.resolveTo = resolveTo;
 | |
| exports.stripBasename = stripBasename;
 | |
| //# sourceMappingURL=router.cjs.js.map
 |