UNPKG

31.2 kBJavaScriptView Raw
1/**
2 * react-router v8.0.0
3 *
4 * Copyright (c) Remix Software Inc.
5 *
6 * This source code is licensed under the MIT license found in the
7 * LICENSE.md file in the root directory of this source tree.
8 *
9 * @license MIT
10 */
11import { ABSOLUTE_URL_REGEX, PROTOCOL_RELATIVE_URL_REGEX, normalizeProtocolRelativeUrl } from "./url.js";
12import { invariant, parsePath, warning } from "./history.js";
13import * as React$1 from "react";
14//#region lib/router/utils.ts
15/**
16* Creates a type-safe {@link RouterContext} object that can be used to
17* store and retrieve arbitrary values in [`action`](../../start/framework/route-module#action)s,
18* [`loader`](../../start/framework/route-module#loader)s, and [middleware](../../how-to/middleware).
19* Similar to React's [`createContext`](https://react.dev/reference/react/createContext),
20* but specifically designed for React Router's request/response lifecycle.
21*
22* If a `defaultValue` is provided, it will be returned from `context.get()`
23* when no value has been set for the context. Otherwise, reading this context
24* when no value has been set will throw an error.
25*
26* ```tsx filename=app/context.ts
27* import { createContext } from "react-router";
28*
29* // Create a context for user data
30* export const userContext =
31* createContext<User | null>(null);
32* ```
33*
34* ```tsx filename=app/middleware/auth.ts
35* import { getUserFromSession } from "~/auth.server";
36* import { userContext } from "~/context";
37*
38* export const authMiddleware = async ({
39* context,
40* request,
41* }) => {
42* const user = await getUserFromSession(request);
43* context.set(userContext, user);
44* };
45* ```
46*
47* ```tsx filename=app/routes/profile.tsx
48* import { userContext } from "~/context";
49*
50* export async function loader({
51* context,
52* }: Route.LoaderArgs) {
53* const user = context.get(userContext);
54*
55* if (!user) {
56* throw new Response("Unauthorized", { status: 401 });
57* }
58*
59* return { user };
60* }
61* ```
62*
63* @public
64* @category Utils
65* @mode framework
66* @mode data
67* @param defaultValue An optional default value for the context. This value
68* will be returned if no value has been set for this context.
69* @returns A {@link RouterContext} object that can be used with
70* `context.get()` and `context.set()` in [`action`](../../start/framework/route-module#action)s,
71* [`loader`](../../start/framework/route-module#loader)s, and [middleware](../../how-to/middleware).
72*/
73function createContext(defaultValue) {
74 return { defaultValue };
75}
76/**
77* Provides methods for writing/reading values in application context in a
78* type-safe way. Primarily for usage with [middleware](../../how-to/middleware).
79*
80* @example
81* import {
82* createContext,
83* RouterContextProvider
84* } from "react-router";
85*
86* const userContext = createContext<User | null>(null);
87* const contextProvider = new RouterContextProvider();
88* contextProvider.set(userContext, getUser());
89* // ^ Type-safe
90* const user = contextProvider.get(userContext);
91* // ^ User
92*
93* @public
94* @category Utils
95* @mode framework
96* @mode data
97*/
98var RouterContextProvider = class {
99 #map = /* @__PURE__ */ new Map();
100 /**
101 * Create a new `RouterContextProvider` instance
102 * @param init An optional initial context map to populate the provider with
103 */
104 constructor(init) {
105 if (init) for (let [context, value] of init) this.set(context, value);
106 }
107 /**
108 * Access a value from the context. If no value has been set for the context,
109 * it will return the context's `defaultValue` if provided, or throw an error
110 * if no `defaultValue` was set.
111 * @param context The context to get the value for
112 * @returns The value for the context, or the context's `defaultValue` if no
113 * value was set
114 */
115 get(context) {
116 if (this.#map.has(context)) return this.#map.get(context);
117 if (context.defaultValue !== void 0) return context.defaultValue;
118 throw new Error("No value found for context");
119 }
120 /**
121 * Set a value for the context. If the context already has a value set, this
122 * will overwrite it.
123 *
124 * @param context The context to set the value for
125 * @param value The value to set for the context
126 * @returns {void}
127 */
128 set(context, value) {
129 this.#map.set(context, value);
130 }
131};
132const unsupportedLazyRouteObjectKeys = new Set([
133 "lazy",
134 "caseSensitive",
135 "path",
136 "id",
137 "index",
138 "children"
139]);
140function isUnsupportedLazyRouteObjectKey(key) {
141 return unsupportedLazyRouteObjectKeys.has(key);
142}
143const unsupportedLazyRouteFunctionKeys = new Set([
144 "lazy",
145 "caseSensitive",
146 "path",
147 "id",
148 "index",
149 "middleware",
150 "children"
151]);
152function isUnsupportedLazyRouteFunctionKey(key) {
153 return unsupportedLazyRouteFunctionKeys.has(key);
154}
155function isIndexRoute(route) {
156 return route.index === true;
157}
158function defaultMapRouteProperties(route) {
159 let updates = {};
160 if (route.Component) {
161 if (route.element) warning(false, "You should not include both `Component` and `element` on your route - `Component` will be used.");
162 Object.assign(updates, {
163 element: React$1.createElement(route.Component),
164 Component: void 0
165 });
166 }
167 if (route.HydrateFallback) {
168 if (route.hydrateFallbackElement) warning(false, "You should not include both `HydrateFallback` and `hydrateFallbackElement` on your route - `HydrateFallback` will be used.");
169 Object.assign(updates, {
170 hydrateFallbackElement: React$1.createElement(route.HydrateFallback),
171 HydrateFallback: void 0
172 });
173 }
174 if (route.ErrorBoundary) {
175 if (route.errorElement) warning(false, "You should not include both `ErrorBoundary` and `errorElement` on your route - `ErrorBoundary` will be used.");
176 Object.assign(updates, {
177 errorElement: React$1.createElement(route.ErrorBoundary),
178 ErrorBoundary: void 0
179 });
180 }
181 return updates;
182}
183function convertRoutesToDataRoutes(routes, mapRouteProperties = defaultMapRouteProperties, parentPath = [], manifest = {}, allowInPlaceMutations = false) {
184 return routes.map((route, index) => {
185 let treePath = [...parentPath, String(index)];
186 let id = typeof route.id === "string" ? route.id : treePath.join("-");
187 invariant(route.index !== true || !route.children, `Cannot specify children on an index route`);
188 invariant(allowInPlaceMutations || !manifest[id], `Found a route id collision on id "${id}". Route id's must be globally unique within Data Router usages`);
189 if (isIndexRoute(route)) {
190 let indexRoute = {
191 ...route,
192 id
193 };
194 manifest[id] = mergeRouteUpdates(indexRoute, mapRouteProperties(indexRoute));
195 return indexRoute;
196 } else {
197 let pathOrLayoutRoute = {
198 ...route,
199 id,
200 children: void 0
201 };
202 manifest[id] = mergeRouteUpdates(pathOrLayoutRoute, mapRouteProperties(pathOrLayoutRoute));
203 if (route.children) pathOrLayoutRoute.children = convertRoutesToDataRoutes(route.children, mapRouteProperties, treePath, manifest, allowInPlaceMutations);
204 return pathOrLayoutRoute;
205 }
206 });
207}
208function mergeRouteUpdates(route, updates) {
209 return Object.assign(route, {
210 ...updates,
211 ...typeof updates.lazy === "object" && updates.lazy != null ? { lazy: {
212 ...route.lazy,
213 ...updates.lazy
214 } } : {}
215 });
216}
217/**
218* Matches the given routes to a location and returns the match data.
219*
220* @example
221* import { matchRoutes } from "react-router";
222*
223* let routes = [{
224* path: "/",
225* Component: Root,
226* children: [{
227* path: "dashboard",
228* Component: Dashboard,
229* }]
230* }];
231*
232* matchRoutes(routes, "/dashboard"); // [rootMatch, dashboardMatch]
233*
234* @public
235* @category Utils
236* @param routes The array of route objects to match against.
237* @param locationArg The location to match against, either a string path or a
238* partial {@link Location} object
239* @param basename Optional base path to strip from the location before matching.
240* Defaults to `/`.
241* @returns An array of matched routes, or `null` if no matches were found.
242*/
243function matchRoutes(routes, locationArg, basename = "/") {
244 return matchRoutesImpl(routes, locationArg, basename, false);
245}
246function matchRoutesImpl(routes, locationArg, basename, allowPartial, precomputedBranches) {
247 let pathname = stripBasename((typeof locationArg === "string" ? parsePath(locationArg) : locationArg).pathname || "/", basename);
248 if (pathname == null) return null;
249 let branches = precomputedBranches ?? flattenAndRankRoutes(routes);
250 let matches = null;
251 let decoded = decodePath(pathname);
252 for (let i = 0; matches == null && i < branches.length; ++i) matches = matchRouteBranch(branches[i], decoded, allowPartial);
253 return matches;
254}
255function convertRouteMatchToUiMatch(match, loaderData) {
256 let { route, pathname, params } = match;
257 return {
258 id: route.id,
259 pathname,
260 params,
261 loaderData: loaderData[route.id],
262 handle: route.handle
263 };
264}
265function flattenAndRankRoutes(routes) {
266 let branches = flattenRoutes(routes);
267 rankRouteBranches(branches);
268 return branches;
269}
270function flattenRoutes(routes, branches = [], parentsMeta = [], parentPath = "", _hasParentOptionalSegments = false) {
271 let flattenRoute = (route, index, hasParentOptionalSegments = _hasParentOptionalSegments, relativePath) => {
272 let meta = {
273 relativePath: relativePath === void 0 ? route.path || "" : relativePath,
274 caseSensitive: route.caseSensitive === true,
275 childrenIndex: index,
276 route
277 };
278 if (meta.relativePath.startsWith("/")) {
279 if (!meta.relativePath.startsWith(parentPath) && hasParentOptionalSegments) return;
280 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.`);
281 meta.relativePath = meta.relativePath.slice(parentPath.length);
282 }
283 let path = joinPaths([parentPath, meta.relativePath]);
284 let routesMeta = parentsMeta.concat(meta);
285 if (route.children && route.children.length > 0) {
286 invariant(route.index !== true, `Index routes must not have child routes. Please remove all child routes from route path "${path}".`);
287 flattenRoutes(route.children, branches, routesMeta, path, hasParentOptionalSegments);
288 }
289 if (route.path == null && !route.index) return;
290 branches.push({
291 path,
292 score: computeScore(path, route.index),
293 routesMeta: routesMeta.map((meta, i) => {
294 let [matcher, params] = compilePath(meta.relativePath, meta.caseSensitive, i === routesMeta.length - 1);
295 return {
296 ...meta,
297 matcher,
298 compiledParams: params
299 };
300 })
301 });
302 };
303 routes.forEach((route, index) => {
304 if (route.path === "" || !route.path?.includes("?")) flattenRoute(route, index);
305 else for (let exploded of explodeOptionalSegments(route.path)) flattenRoute(route, index, true, exploded);
306 });
307 return branches;
308}
309function explodeOptionalSegments(path) {
310 let segments = path.split("/");
311 if (segments.length === 0) return [];
312 let [first, ...rest] = segments;
313 let isOptional = first.endsWith("?");
314 let required = first.replace(/\?$/, "");
315 if (rest.length === 0) return isOptional ? [required, ""] : [required];
316 let restExploded = explodeOptionalSegments(rest.join("/"));
317 let result = [];
318 result.push(...restExploded.map((subpath) => subpath === "" ? required : [required, subpath].join("/")));
319 if (isOptional) result.push(...restExploded);
320 return result.map((exploded) => path.startsWith("/") && exploded === "" ? "/" : exploded);
321}
322function rankRouteBranches(branches) {
323 branches.sort((a, b) => a.score !== b.score ? b.score - a.score : compareIndexes(a.routesMeta.map((meta) => meta.childrenIndex), b.routesMeta.map((meta) => meta.childrenIndex)));
324}
325const paramRe = /^:[\w-]+$/;
326const dynamicSegmentValue = 3;
327const indexRouteValue = 2;
328const emptySegmentValue = 1;
329const staticSegmentValue = 10;
330const splatPenalty = -2;
331const isSplat = (s) => s === "*";
332function computeScore(path, index) {
333 let segments = path.split("/");
334 let initialScore = segments.length;
335 if (segments.some(isSplat)) initialScore += splatPenalty;
336 if (index) initialScore += indexRouteValue;
337 return segments.filter((s) => !isSplat(s)).reduce((score, segment) => score + (paramRe.test(segment) ? dynamicSegmentValue : segment === "" ? emptySegmentValue : staticSegmentValue), initialScore);
338}
339function compareIndexes(a, b) {
340 return a.length === b.length && a.slice(0, -1).every((n, i) => n === b[i]) ? a[a.length - 1] - b[b.length - 1] : 0;
341}
342function matchRouteBranch(branch, pathname, allowPartial = false) {
343 let { routesMeta } = branch;
344 let matchedParams = {};
345 let matchedPathname = "/";
346 let matches = [];
347 for (let i = 0; i < routesMeta.length; ++i) {
348 let meta = routesMeta[i];
349 let end = i === routesMeta.length - 1;
350 let remainingPathname = matchedPathname === "/" ? pathname : pathname.slice(matchedPathname.length) || "/";
351 let pattern = {
352 path: meta.relativePath,
353 caseSensitive: meta.caseSensitive,
354 end
355 };
356 let match = meta.matcher && meta.compiledParams ? matchPathImpl(pattern, remainingPathname, meta.matcher, meta.compiledParams) : matchPath(pattern, remainingPathname);
357 let route = meta.route;
358 if (!match && end && allowPartial && !routesMeta[routesMeta.length - 1].route.index) match = matchPath({
359 path: meta.relativePath,
360 caseSensitive: meta.caseSensitive,
361 end: false
362 }, remainingPathname);
363 if (!match) return null;
364 Object.assign(matchedParams, match.params);
365 matches.push({
366 params: matchedParams,
367 pathname: joinPaths([matchedPathname, match.pathname]),
368 pathnameBase: normalizePathname(joinPaths([matchedPathname, match.pathnameBase])),
369 route
370 });
371 if (match.pathnameBase !== "/") matchedPathname = joinPaths([matchedPathname, match.pathnameBase]);
372 }
373 return matches;
374}
375/**
376* Returns a path with params interpolated.
377*
378* @example
379* import { generatePath } from "react-router";
380*
381* generatePath("/users/:id", { id: "123" }); // "/users/123"
382*
383* @public
384* @category Utils
385* @param originalPath The original path to generate.
386* @param params The parameters to interpolate into the path.
387* @returns The generated path with parameters interpolated.
388*/
389function generatePath(originalPath, params = {}) {
390 let path = originalPath;
391 if (path.endsWith("*") && path !== "*" && !path.endsWith("/*")) {
392 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(/\*$/, "/*")}".`);
393 path = path.replace(/\*$/, "/*");
394 }
395 const prefix = path.startsWith("/") ? "/" : "";
396 const stringify = (p) => p == null ? "" : typeof p === "string" ? p : String(p);
397 return prefix + path.split(/\/+/).map((segment, index, array) => {
398 if (index === array.length - 1 && segment === "*") return stringify(params["*"]);
399 const keyMatch = segment.match(/^:([\w-]+)(\??)(.*)/);
400 if (keyMatch) {
401 const [, key, optional, suffix] = keyMatch;
402 let param = params[key];
403 invariant(optional === "?" || param != null, `Missing ":${key}" param`);
404 return encodeURIComponent(stringify(param)) + suffix;
405 }
406 return segment.replace(/\?$/g, "");
407 }).filter((segment) => !!segment).join("/");
408}
409/**
410* Performs pattern matching on a URL pathname and returns information about
411* the match.
412*
413* @public
414* @category Utils
415* @param pattern The pattern to match against the URL pathname. This can be a
416* string or a {@link PathPattern} object. If a string is provided, it will be
417* treated as a pattern with `caseSensitive` set to `false` and `end` set to
418* `true`.
419* @param pathname The URL pathname to match against the pattern.
420* @returns A path match object if the pattern matches the pathname,
421* or `null` if it does not match.
422*/
423function matchPath(pattern, pathname) {
424 if (typeof pattern === "string") pattern = {
425 path: pattern,
426 caseSensitive: false,
427 end: true
428 };
429 let [matcher, compiledParams] = compilePath(pattern.path, pattern.caseSensitive, pattern.end);
430 return matchPathImpl(pattern, pathname, matcher, compiledParams);
431}
432function matchPathImpl(pattern, pathname, matcher, compiledParams) {
433 let match = pathname.match(matcher);
434 if (!match) return null;
435 let matchedPathname = match[0];
436 let pathnameBase = matchedPathname.replace(/(.)\/+$/, "$1");
437 let captureGroups = match.slice(1);
438 return {
439 params: compiledParams.reduce((memo, { paramName, isOptional }, index) => {
440 if (paramName === "*") {
441 let splatValue = captureGroups[index] || "";
442 pathnameBase = matchedPathname.slice(0, matchedPathname.length - splatValue.length).replace(/(.)\/+$/, "$1");
443 }
444 const value = captureGroups[index];
445 if (isOptional && !value) memo[paramName] = void 0;
446 else memo[paramName] = (value || "").replace(/%2F/g, "/");
447 return memo;
448 }, {}),
449 pathname: matchedPathname,
450 pathnameBase,
451 pattern
452 };
453}
454function compilePath(path, caseSensitive = false, end = true) {
455 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(/\*$/, "/*")}".`);
456 let params = [];
457 let regexpSource = "^" + path.replace(/\/*\*?$/, "").replace(/^\/*/, "/").replace(/[\\.*+^${}|()[\]]/g, "\\$&").replace(/\/:([\w-]+)(\?)?/g, (match, paramName, isOptional, index, str) => {
458 params.push({
459 paramName,
460 isOptional: isOptional != null
461 });
462 if (isOptional) {
463 let nextChar = str.charAt(index + match.length);
464 if (nextChar && nextChar !== "/") return "/([^\\/]*)";
465 return "(?:/([^\\/]*))?";
466 }
467 return "/([^\\/]+)";
468 }).replace(/\/([\w-]+)\?(\/|$)/g, "(/$1)?$2");
469 if (path.endsWith("*")) {
470 params.push({ paramName: "*" });
471 regexpSource += path === "*" || path === "/*" ? "(.*)$" : "(?:\\/(.+)|\\/*)$";
472 } else if (end) regexpSource += "\\/*$";
473 else if (path !== "" && path !== "/") regexpSource += "(?:(?=\\/|$))";
474 return [new RegExp(regexpSource, caseSensitive ? void 0 : "i"), params];
475}
476function decodePath(value) {
477 try {
478 return value.split("/").map((v) => decodeURIComponent(v).replace(/\//g, "%2F")).join("/");
479 } catch (error) {
480 warning(false, `The URL path "${value}" could not be decoded because it is a malformed URL segment. This is probably due to a bad percent encoding (${error}).`);
481 return value;
482 }
483}
484function stripBasename(pathname, basename) {
485 if (basename === "/") return pathname;
486 if (!pathname.toLowerCase().startsWith(basename.toLowerCase())) return null;
487 let startIndex = basename.endsWith("/") ? basename.length - 1 : basename.length;
488 let nextChar = pathname.charAt(startIndex);
489 if (nextChar && nextChar !== "/") return null;
490 return pathname.slice(startIndex) || "/";
491}
492function prependBasename({ basename, pathname }) {
493 return pathname === "/" ? basename : joinPaths([basename, pathname]);
494}
495const isAbsoluteUrl = (url) => ABSOLUTE_URL_REGEX.test(url);
496/**
497* Returns a resolved {@link Path} object relative to the given pathname.
498*
499* @public
500* @category Utils
501* @param to The path to resolve, either a string or a partial {@link Path}
502* object.
503* @param fromPathname The pathname to resolve the path from. Defaults to `/`.
504* @returns A {@link Path} object with the resolved pathname, search, and hash.
505*/
506function resolvePath(to, fromPathname = "/") {
507 let { pathname: toPathname, search = "", hash = "" } = typeof to === "string" ? parsePath(to) : to;
508 let pathname;
509 if (toPathname) {
510 toPathname = removeDoubleSlashes(toPathname);
511 if (toPathname.startsWith("/")) pathname = resolvePathname(toPathname.substring(1), "/");
512 else pathname = resolvePathname(toPathname, fromPathname);
513 } else pathname = fromPathname;
514 return {
515 pathname,
516 search: normalizeSearch(search),
517 hash: normalizeHash(hash)
518 };
519}
520function resolvePathname(relativePath, fromPathname) {
521 let segments = removeTrailingSlash(fromPathname).split("/");
522 relativePath.split("/").forEach((segment) => {
523 if (segment === "..") {
524 if (segments.length > 1) segments.pop();
525 } else if (segment !== ".") segments.push(segment);
526 });
527 return segments.length > 1 ? segments.join("/") : "/";
528}
529function getInvalidPathError(char, field, dest, path) {
530 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.`;
531}
532function getPathContributingMatches(matches) {
533 return matches.filter((match, index) => index === 0 || match.route.path && match.route.path.length > 0);
534}
535function getResolveToMatches(matches) {
536 let pathMatches = getPathContributingMatches(matches);
537 return pathMatches.map((match, idx) => idx === pathMatches.length - 1 ? match.pathname : match.pathnameBase);
538}
539function resolveTo(toArg, routePathnames, locationPathname, isPathRelative = false) {
540 let to;
541 if (typeof toArg === "string") to = parsePath(toArg);
542 else {
543 to = { ...toArg };
544 invariant(!to.pathname || !to.pathname.includes("?"), getInvalidPathError("?", "pathname", "search", to));
545 invariant(!to.pathname || !to.pathname.includes("#"), getInvalidPathError("#", "pathname", "hash", to));
546 invariant(!to.search || !to.search.includes("#"), getInvalidPathError("#", "search", "hash", to));
547 }
548 let isEmptyPath = toArg === "" || to.pathname === "";
549 let toPathname = isEmptyPath ? "/" : to.pathname;
550 let from;
551 if (toPathname == null) from = locationPathname;
552 else {
553 let routePathnameIndex = routePathnames.length - 1;
554 if (!isPathRelative && toPathname.startsWith("..")) {
555 let toSegments = toPathname.split("/");
556 while (toSegments[0] === "..") {
557 toSegments.shift();
558 routePathnameIndex -= 1;
559 }
560 to.pathname = toSegments.join("/");
561 }
562 from = routePathnameIndex >= 0 ? routePathnames[routePathnameIndex] : "/";
563 }
564 let path = resolvePath(to, from);
565 let hasExplicitTrailingSlash = toPathname && toPathname !== "/" && toPathname.endsWith("/");
566 let hasCurrentTrailingSlash = (isEmptyPath || toPathname === ".") && locationPathname.endsWith("/");
567 if (!path.pathname.endsWith("/") && (hasExplicitTrailingSlash || hasCurrentTrailingSlash)) path.pathname += "/";
568 return path;
569}
570const removeDoubleSlashes = (path) => path.replace(/[\\/]{2,}/g, "/");
571const joinPaths = (paths) => removeDoubleSlashes(paths.join("/"));
572const removeTrailingSlash = (path) => path.replace(/\/+$/, "");
573const normalizePathname = (pathname) => removeTrailingSlash(pathname).replace(/^\/*/, "/");
574const normalizeSearch = (search) => !search || search === "?" ? "" : search.startsWith("?") ? search : "?" + search;
575const normalizeHash = (hash) => !hash || hash === "#" ? "" : hash.startsWith("#") ? hash : "#" + hash;
576var DataWithResponseInit = class {
577 type = "DataWithResponseInit";
578 data;
579 init;
580 constructor(data, init) {
581 this.data = data;
582 this.init = init || null;
583 }
584};
585/**
586* Create "responses" that contain `headers`/`status` without forcing
587* serialization into an actual [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response)
588*
589* @example
590* import { data } from "react-router";
591*
592* export async function action({ request }: Route.ActionArgs) {
593* let formData = await request.formData();
594* let item = await createItem(formData);
595* return data(item, {
596* headers: { "X-Custom-Header": "value" }
597* status: 201,
598* });
599* }
600*
601* @public
602* @category Utils
603* @mode framework
604* @mode data
605* @param data The data to be included in the response.
606* @param init The status code or a `ResponseInit` object to be included in the
607* response.
608* @returns A {@link DataWithResponseInit} instance containing the data and
609* response init.
610*/
611function data(data, init) {
612 return new DataWithResponseInit(data, typeof init === "number" ? { status: init } : init);
613}
614/**
615* A redirect [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response).
616* Sets the status code and the [`Location`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Location)
617* header. Defaults to [`302 Found`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/302).
618*
619* This utility accepts absolute URLs and can navigate to external domains, so
620* the application should validate any user-supplied inputs to redirects.
621*
622* @example
623* import { redirect } from "react-router";
624*
625* export async function loader({ request }: Route.LoaderArgs) {
626* if (!isLoggedIn(request))
627* throw redirect("/login");
628* }
629*
630* // ...
631* }
632*
633* @public
634* @category Utils
635* @mode framework
636* @mode data
637* @param url The URL to redirect to.
638* @param init The status code or a `ResponseInit` object to be included in the
639* response.
640* @returns A [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response)
641* object with the redirect status and [`Location`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Location)
642* header.
643*/
644const redirect = (url, init = 302) => {
645 let responseInit = init;
646 if (typeof responseInit === "number") responseInit = { status: responseInit };
647 else if (typeof responseInit.status === "undefined") responseInit.status = 302;
648 let headers = new Headers(responseInit.headers);
649 headers.set("Location", url);
650 return new Response(null, {
651 ...responseInit,
652 headers
653 });
654};
655/**
656* A redirect [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response)
657* that will force a document reload to the new location. Sets the status code
658* and the [`Location`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Location)
659* header. Defaults to [`302 Found`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/302).
660*
661* This utility accepts absolute URLs and can navigate to external domains, so
662* the application should validate any user-supplied inputs to redirects.
663*
664* ```tsx filename=routes/logout.tsx
665* import { redirectDocument } from "react-router";
666*
667* import { destroySession } from "../sessions.server";
668*
669* export async function action({ request }: Route.ActionArgs) {
670* let session = await getSession(request.headers.get("Cookie"));
671* return redirectDocument("/", {
672* headers: { "Set-Cookie": await destroySession(session) }
673* });
674* }
675* ```
676*
677* @public
678* @category Utils
679* @mode framework
680* @mode data
681* @param url The URL to redirect to.
682* @param init The status code or a `ResponseInit` object to be included in the
683* response.
684* @returns A [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response)
685* object with the redirect status and [`Location`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Location)
686* header.
687*/
688const redirectDocument = (url, init) => {
689 let response = redirect(url, init);
690 response.headers.set("X-Remix-Reload-Document", "true");
691 return response;
692};
693/**
694* A redirect [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response)
695* that will perform a [`history.replaceState`](https://developer.mozilla.org/en-US/docs/Web/API/History/replaceState)
696* instead of a [`history.pushState`](https://developer.mozilla.org/en-US/docs/Web/API/History/pushState)
697* for client-side navigation redirects. Sets the status code and the [`Location`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Location)
698* header. Defaults to [`302 Found`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/302).
699*
700* @example
701* import { replace } from "react-router";
702*
703* export async function loader() {
704* return replace("/new-location");
705* }
706*
707* @public
708* @category Utils
709* @mode framework
710* @mode data
711* @param url The URL to redirect to.
712* @param init The status code or a `ResponseInit` object to be included in the
713* response.
714* @returns A [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response)
715* object with the redirect status and [`Location`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Location)
716* header.
717*/
718const replace = (url, init) => {
719 let response = redirect(url, init);
720 response.headers.set("X-Remix-Replace", "true");
721 return response;
722};
723const SUPPORTED_ERROR_TYPES = [
724 "EvalError",
725 "RangeError",
726 "ReferenceError",
727 "SyntaxError",
728 "TypeError",
729 "URIError"
730];
731var ErrorResponseImpl = class {
732 status;
733 statusText;
734 data;
735 error;
736 internal;
737 constructor(status, statusText, data, internal = false) {
738 this.status = status;
739 this.statusText = statusText || "";
740 this.internal = internal;
741 if (data instanceof Error) {
742 this.data = data.toString();
743 this.error = data;
744 } else this.data = data;
745 }
746};
747/**
748* Check if the given error is an {@link ErrorResponse} generated from a 4xx/5xx
749* [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response)
750* thrown from an [`action`](../../start/framework/route-module#action) or
751* [`loader`](../../start/framework/route-module#loader) function.
752*
753* @example
754* import { isRouteErrorResponse } from "react-router";
755*
756* export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
757* if (isRouteErrorResponse(error)) {
758* return (
759* <>
760* <p>Error: `${error.status}: ${error.statusText}`</p>
761* <p>{error.data}</p>
762* </>
763* );
764* }
765*
766* return (
767* <p>Error: {error instanceof Error ? error.message : "Unknown Error"}</p>
768* );
769* }
770*
771* @public
772* @category Utils
773* @mode framework
774* @mode data
775* @param error The error to check.
776* @returns `true` if the error is an {@link ErrorResponse}, `false` otherwise.
777*/
778function isRouteErrorResponse(error) {
779 return error != null && typeof error.status === "number" && typeof error.statusText === "string" && typeof error.internal === "boolean" && "data" in error;
780}
781function getRoutePattern(matches) {
782 return joinPaths(matches.map((m) => m.route.path).filter(Boolean)) || "/";
783}
784const isBrowser = typeof window !== "undefined" && typeof window.document !== "undefined" && typeof window.document.createElement !== "undefined";
785function parseToInfo(_to, basename) {
786 let to = _to;
787 if (typeof to !== "string" || !ABSOLUTE_URL_REGEX.test(to)) return {
788 absoluteURL: void 0,
789 isExternal: false,
790 to
791 };
792 let absoluteURL = to;
793 let isExternal = false;
794 if (isBrowser) try {
795 let currentUrl = new URL(window.location.href);
796 let targetUrl = PROTOCOL_RELATIVE_URL_REGEX.test(to) ? new URL(normalizeProtocolRelativeUrl(to, currentUrl.protocol)) : new URL(to);
797 let path = stripBasename(targetUrl.pathname, basename);
798 if (targetUrl.origin === currentUrl.origin && path != null) to = path + targetUrl.search + targetUrl.hash;
799 else isExternal = true;
800 } catch (e) {
801 warning(false, `<Link to="${to}"> contains an invalid URL which will probably break when clicked - please update to a valid URL path.`);
802 }
803 return {
804 absoluteURL,
805 isExternal,
806 to
807 };
808}
809//#endregion
810export { ErrorResponseImpl, RouterContextProvider, SUPPORTED_ERROR_TYPES, compilePath, convertRouteMatchToUiMatch, convertRoutesToDataRoutes, createContext, data, decodePath, defaultMapRouteProperties, flattenAndRankRoutes, generatePath, getPathContributingMatches, getResolveToMatches, getRoutePattern, isAbsoluteUrl, isBrowser, isRouteErrorResponse, isUnsupportedLazyRouteFunctionKey, isUnsupportedLazyRouteObjectKey, joinPaths, matchPath, matchRoutes, matchRoutesImpl, parseToInfo, prependBasename, redirect, redirectDocument, removeDoubleSlashes, removeTrailingSlash, replace, resolvePath, resolveTo, stripBasename };