16965 lines
580 KiB
JavaScript
16965 lines
580 KiB
JavaScript
import require$$0 from 'url';
|
|
import nodePath from 'node:path';
|
|
import React, { createElement } from 'react';
|
|
import ReactDOM from 'react-dom/server';
|
|
|
|
function normalizeLF(code) {
|
|
return code.replace(/\r\n|\r(?!\n)|\n/g, "\n");
|
|
}
|
|
|
|
function codeFrame(src, loc) {
|
|
if (!loc || loc.line === void 0 || loc.column === void 0) {
|
|
return "";
|
|
}
|
|
const lines = normalizeLF(src).split("\n").map((ln) => ln.replace(/\t/g, " "));
|
|
const visibleLines = [];
|
|
for (let n = -2; n <= 2; n++) {
|
|
if (lines[loc.line + n]) visibleLines.push(loc.line + n);
|
|
}
|
|
let gutterWidth = 0;
|
|
for (const lineNo of visibleLines) {
|
|
let w = `> ${lineNo}`;
|
|
if (w.length > gutterWidth) gutterWidth = w.length;
|
|
}
|
|
let output = "";
|
|
for (const lineNo of visibleLines) {
|
|
const isFocusedLine = lineNo === loc.line - 1;
|
|
output += isFocusedLine ? "> " : " ";
|
|
output += `${lineNo + 1} | ${lines[lineNo]}
|
|
`;
|
|
if (isFocusedLine)
|
|
output += `${Array.from({ length: gutterWidth }).join(" ")} | ${Array.from({
|
|
length: loc.column
|
|
}).join(" ")}^
|
|
`;
|
|
}
|
|
return output;
|
|
}
|
|
|
|
class AstroError extends Error {
|
|
loc;
|
|
title;
|
|
hint;
|
|
frame;
|
|
type = "AstroError";
|
|
constructor(props, options) {
|
|
const { name, title, message, stack, location, hint, frame } = props;
|
|
super(message, options);
|
|
this.title = title;
|
|
this.name = name;
|
|
if (message) this.message = message;
|
|
this.stack = stack ? stack : this.stack;
|
|
this.loc = location;
|
|
this.hint = hint;
|
|
this.frame = frame;
|
|
}
|
|
setLocation(location) {
|
|
this.loc = location;
|
|
}
|
|
setName(name) {
|
|
this.name = name;
|
|
}
|
|
setMessage(message) {
|
|
this.message = message;
|
|
}
|
|
setHint(hint) {
|
|
this.hint = hint;
|
|
}
|
|
setFrame(source, location) {
|
|
this.frame = codeFrame(source, location);
|
|
}
|
|
static is(err) {
|
|
return err?.type === "AstroError";
|
|
}
|
|
}
|
|
|
|
const ClientAddressNotAvailable = {
|
|
name: "ClientAddressNotAvailable",
|
|
title: "`Astro.clientAddress` is not available in current adapter.",
|
|
message: (adapterName) => `\`Astro.clientAddress\` is not available in the \`${adapterName}\` adapter. File an issue with the adapter to add support.`
|
|
};
|
|
const PrerenderClientAddressNotAvailable = {
|
|
name: "PrerenderClientAddressNotAvailable",
|
|
title: "`Astro.clientAddress` cannot be used inside prerendered routes.",
|
|
message: (name) => `\`Astro.clientAddress\` cannot be used inside prerendered route ${name}`
|
|
};
|
|
const StaticClientAddressNotAvailable = {
|
|
name: "StaticClientAddressNotAvailable",
|
|
title: "`Astro.clientAddress` is not available in prerendered pages.",
|
|
message: "`Astro.clientAddress` is only available on pages that are server-rendered.",
|
|
hint: "See https://docs.astro.build/en/guides/on-demand-rendering/ for more information on how to enable SSR."
|
|
};
|
|
const NoMatchingStaticPathFound = {
|
|
name: "NoMatchingStaticPathFound",
|
|
title: "No static path found for requested path.",
|
|
message: (pathName) => `A \`getStaticPaths()\` route pattern was matched, but no matching static path was found for requested path \`${pathName}\`.`,
|
|
hint: (possibleRoutes) => `Possible dynamic routes being matched: ${possibleRoutes.join(", ")}.`
|
|
};
|
|
const OnlyResponseCanBeReturned = {
|
|
name: "OnlyResponseCanBeReturned",
|
|
title: "Invalid type returned by Astro page.",
|
|
message: (route, returnedValue) => `Route \`${route ? route : ""}\` returned a \`${returnedValue}\`. Only a [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) can be returned from Astro files.`,
|
|
hint: "See https://docs.astro.build/en/guides/on-demand-rendering/#response for more information."
|
|
};
|
|
const MissingMediaQueryDirective = {
|
|
name: "MissingMediaQueryDirective",
|
|
title: "Missing value for `client:media` directive.",
|
|
message: 'Media query not provided for `client:media` directive. A media query similar to `client:media="(max-width: 600px)"` must be provided'
|
|
};
|
|
const NoMatchingRenderer = {
|
|
name: "NoMatchingRenderer",
|
|
title: "No matching renderer found.",
|
|
message: (componentName, componentExtension, plural, validRenderersCount) => `Unable to render \`${componentName}\`.
|
|
|
|
${validRenderersCount > 0 ? `There ${plural ? "are" : "is"} ${validRenderersCount} renderer${plural ? "s" : ""} configured in your \`astro.config.mjs\` file,
|
|
but ${plural ? "none were" : "it was not"} able to server-side render \`${componentName}\`.` : `No valid renderer was found ${componentExtension ? `for the \`.${componentExtension}\` file extension.` : `for this file extension.`}`}`,
|
|
hint: (probableRenderers) => `Did you mean to enable the ${probableRenderers} integration?
|
|
|
|
See https://docs.astro.build/en/guides/framework-components/ for more information on how to install and configure integrations.`
|
|
};
|
|
const NoClientOnlyHint = {
|
|
name: "NoClientOnlyHint",
|
|
title: "Missing hint on client:only directive.",
|
|
message: (componentName) => `Unable to render \`${componentName}\`. When using the \`client:only\` hydration strategy, Astro needs a hint to use the correct renderer.`,
|
|
hint: (probableRenderers) => `Did you mean to pass \`client:only="${probableRenderers}"\`? See https://docs.astro.build/en/reference/directives-reference/#clientonly for more information on client:only`
|
|
};
|
|
const InvalidGetStaticPathsEntry = {
|
|
name: "InvalidGetStaticPathsEntry",
|
|
title: "Invalid entry inside getStaticPath's return value",
|
|
message: (entryType) => `Invalid entry returned by getStaticPaths. Expected an object, got \`${entryType}\``,
|
|
hint: "If you're using a `.map` call, you might be looking for `.flatMap()` instead. See https://docs.astro.build/en/reference/routing-reference/#getstaticpaths for more information on getStaticPaths."
|
|
};
|
|
const InvalidGetStaticPathsReturn = {
|
|
name: "InvalidGetStaticPathsReturn",
|
|
title: "Invalid value returned by getStaticPaths.",
|
|
message: (returnType) => `Invalid type returned by \`getStaticPaths\`. Expected an \`array\`, got \`${returnType}\``,
|
|
hint: "See https://docs.astro.build/en/reference/routing-reference/#getstaticpaths for more information on getStaticPaths."
|
|
};
|
|
const GetStaticPathsExpectedParams = {
|
|
name: "GetStaticPathsExpectedParams",
|
|
title: "Missing params property on `getStaticPaths` route.",
|
|
message: "Missing or empty required `params` property on `getStaticPaths` route.",
|
|
hint: "See https://docs.astro.build/en/reference/routing-reference/#getstaticpaths for more information on getStaticPaths."
|
|
};
|
|
const GetStaticPathsInvalidRouteParam = {
|
|
name: "GetStaticPathsInvalidRouteParam",
|
|
title: "Invalid route parameter returned by `getStaticPaths()`.",
|
|
message: (key, value, valueType) => `Invalid \`getStaticPaths()\` route parameter for \`${key}\`. Expected a string or undefined, received \`${valueType}\` (\`${value}\`)`,
|
|
hint: "See https://docs.astro.build/en/reference/routing-reference/#getstaticpaths for more information on getStaticPaths."
|
|
};
|
|
const GetStaticPathsRequired = {
|
|
name: "GetStaticPathsRequired",
|
|
title: "`getStaticPaths()` function required for dynamic routes.",
|
|
message: "`getStaticPaths()` function is required for dynamic routes. Make sure that you `export` a `getStaticPaths` function from your dynamic route.",
|
|
hint: `See https://docs.astro.build/en/guides/routing/#dynamic-routes for more information on dynamic routes.
|
|
|
|
If you meant for this route to be server-rendered, set \`export const prerender = false;\` in the page.`
|
|
};
|
|
const ReservedSlotName = {
|
|
name: "ReservedSlotName",
|
|
title: "Invalid slot name.",
|
|
message: (slotName) => `Unable to create a slot named \`${slotName}\`. \`${slotName}\` is a reserved slot name. Please update the name of this slot.`
|
|
};
|
|
const NoMatchingImport = {
|
|
name: "NoMatchingImport",
|
|
title: "No import found for component.",
|
|
message: (componentName) => `Could not render \`${componentName}\`. No matching import has been found for \`${componentName}\`.`,
|
|
hint: "Please make sure the component is properly imported."
|
|
};
|
|
const InvalidComponentArgs = {
|
|
name: "InvalidComponentArgs",
|
|
title: "Invalid component arguments.",
|
|
message: (name) => `Invalid arguments passed to${name ? ` <${name}>` : ""} component.`,
|
|
hint: "Astro components cannot be rendered directly via function call, such as `Component()` or `{items.map(Component)}`."
|
|
};
|
|
const PageNumberParamNotFound = {
|
|
name: "PageNumberParamNotFound",
|
|
title: "Page number param not found.",
|
|
message: (paramName) => `[paginate()] page number param \`${paramName}\` not found in your filepath.`,
|
|
hint: "Rename your file to `[page].astro` or `[...page].astro`."
|
|
};
|
|
const ImageMissingAlt = {
|
|
name: "ImageMissingAlt",
|
|
title: 'Image missing required "alt" property.',
|
|
message: 'Image missing "alt" property. "alt" text is required to describe important images on the page.',
|
|
hint: 'Use an empty string ("") for decorative images.'
|
|
};
|
|
const InvalidImageService = {
|
|
name: "InvalidImageService",
|
|
title: "Error while loading image service.",
|
|
message: "There was an error loading the configured image service. Please see the stack trace for more information."
|
|
};
|
|
const MissingImageDimension = {
|
|
name: "MissingImageDimension",
|
|
title: "Missing image dimensions",
|
|
message: (missingDimension, imageURL) => `Missing ${missingDimension === "both" ? "width and height attributes" : `${missingDimension} attribute`} for ${imageURL}. When using remote images, both dimensions are required in order to avoid CLS.`,
|
|
hint: "If your image is inside your `src` folder, you probably meant to import it instead. See [the Imports guide for more information](https://docs.astro.build/en/guides/imports/#other-assets). You can also use `inferSize={true}` for remote images to get the original dimensions."
|
|
};
|
|
const FailedToFetchRemoteImageDimensions = {
|
|
name: "FailedToFetchRemoteImageDimensions",
|
|
title: "Failed to retrieve remote image dimensions",
|
|
message: (imageURL) => `Failed to get the dimensions for ${imageURL}.`,
|
|
hint: "Verify your remote image URL is accurate, and that you are not using `inferSize` with a file located in your `public/` folder."
|
|
};
|
|
const RemoteImageNotAllowed = {
|
|
name: "RemoteImageNotAllowed",
|
|
title: "Remote image is not allowed",
|
|
message: (imageURL) => `Remote image ${imageURL} is not allowed by your image configuration.`,
|
|
hint: "Update `image.domains` or `image.remotePatterns`, or remove `inferSize` for this image."
|
|
};
|
|
const UnsupportedImageFormat = {
|
|
name: "UnsupportedImageFormat",
|
|
title: "Unsupported image format",
|
|
message: (format, imagePath, supportedFormats) => `Received unsupported format \`${format}\` from \`${imagePath}\`. Currently only ${supportedFormats.join(
|
|
", "
|
|
)} are supported by our image services.`,
|
|
hint: "Using an `img` tag directly instead of the `Image` component might be what you're looking for."
|
|
};
|
|
const UnsupportedImageConversion = {
|
|
name: "UnsupportedImageConversion",
|
|
title: "Unsupported image conversion",
|
|
message: "Converting between vector (such as SVGs) and raster (such as PNGs and JPEGs) images is not currently supported."
|
|
};
|
|
const PrerenderDynamicEndpointPathCollide = {
|
|
name: "PrerenderDynamicEndpointPathCollide",
|
|
title: "Prerendered dynamic endpoint has path collision.",
|
|
message: (pathname) => `Could not render \`${pathname}\` with an \`undefined\` param as the generated path will collide during prerendering. Prevent passing \`undefined\` as \`params\` for the endpoint's \`getStaticPaths()\` function, or add an additional extension to the endpoint's filename.`,
|
|
hint: (filename) => `Rename \`${filename}\` to \`${filename.replace(/\.(?:js|ts)/, (m) => `.json` + m)}\``
|
|
};
|
|
const ExpectedImage = {
|
|
name: "ExpectedImage",
|
|
title: "Expected src to be an image.",
|
|
message: (src, typeofOptions, fullOptions) => `Expected \`src\` property for \`getImage\` or \`<Image />\` to be either an ESM imported image or a string with the path of a remote image. Received \`${src}\` (type: \`${typeofOptions}\`).
|
|
|
|
Full serialized options received: \`${fullOptions}\`.`,
|
|
hint: "This error can often happen because of a wrong path. Make sure the path to your image is correct. If you're passing an async function, make sure to call and await it."
|
|
};
|
|
const ExpectedImageOptions = {
|
|
name: "ExpectedImageOptions",
|
|
title: "Expected image options.",
|
|
message: (options) => `Expected getImage() parameter to be an object. Received \`${options}\`.`
|
|
};
|
|
const ExpectedNotESMImage = {
|
|
name: "ExpectedNotESMImage",
|
|
title: "Expected image options, not an ESM-imported image.",
|
|
message: "An ESM-imported image cannot be passed directly to `getImage()`. Instead, pass an object with the image in the `src` property.",
|
|
hint: "Try changing `getImage(myImage)` to `getImage({ src: myImage })`"
|
|
};
|
|
const IncompatibleDescriptorOptions = {
|
|
name: "IncompatibleDescriptorOptions",
|
|
title: "Cannot set both `densities` and `widths`",
|
|
message: "Only one of `densities` or `widths` can be specified. In most cases, you'll probably want to use only `widths` if you require specific widths.",
|
|
hint: "Those attributes are used to construct a `srcset` attribute, which cannot have both `x` and `w` descriptors."
|
|
};
|
|
const NoImageMetadata = {
|
|
name: "NoImageMetadata",
|
|
title: "Could not process image metadata.",
|
|
message: (imagePath) => `Could not process image metadata${imagePath ? ` for \`${imagePath}\`` : ""}.`,
|
|
hint: "This is often caused by a corrupted or malformed image. Re-exporting the image from your image editor may fix this issue."
|
|
};
|
|
const ResponseSentError = {
|
|
name: "ResponseSentError",
|
|
title: "Unable to set response.",
|
|
message: "The response has already been sent to the browser and cannot be altered."
|
|
};
|
|
const MiddlewareNoDataOrNextCalled = {
|
|
name: "MiddlewareNoDataOrNextCalled",
|
|
title: "The middleware didn't return a `Response`.",
|
|
message: "Make sure your middleware returns a `Response` object, either directly or by returning the `Response` from calling the `next` function."
|
|
};
|
|
const MiddlewareNotAResponse = {
|
|
name: "MiddlewareNotAResponse",
|
|
title: "The middleware returned something that is not a `Response` object.",
|
|
message: "Any data returned from middleware must be a valid `Response` object."
|
|
};
|
|
const EndpointDidNotReturnAResponse = {
|
|
name: "EndpointDidNotReturnAResponse",
|
|
title: "The endpoint did not return a `Response`.",
|
|
message: "An endpoint must return either a `Response`, or a `Promise` that resolves with a `Response`."
|
|
};
|
|
const LocalsNotAnObject = {
|
|
name: "LocalsNotAnObject",
|
|
title: "Value assigned to `locals` is not accepted.",
|
|
message: "`locals` can only be assigned to an object. Other values like numbers, strings, etc. are not accepted.",
|
|
hint: "If you tried to remove some information from the `locals` object, try to use `delete` or set the property to `undefined`."
|
|
};
|
|
const LocalsReassigned = {
|
|
name: "LocalsReassigned",
|
|
title: "`locals` must not be reassigned.",
|
|
message: "`locals` cannot be assigned directly.",
|
|
hint: "Set a `locals` property instead."
|
|
};
|
|
const AstroResponseHeadersReassigned = {
|
|
name: "AstroResponseHeadersReassigned",
|
|
title: "`Astro.response.headers` must not be reassigned.",
|
|
message: "Individual headers can be added to and removed from `Astro.response.headers`, but it must not be replaced with another instance of `Headers` altogether.",
|
|
hint: "Consider using `Astro.response.headers.add()`, and `Astro.response.headers.delete()`."
|
|
};
|
|
const LocalImageUsedWrongly = {
|
|
name: "LocalImageUsedWrongly",
|
|
title: "Local images must be imported.",
|
|
message: (imageFilePath) => `\`Image\`'s and \`getImage\`'s \`src\` parameter must be an imported image or an URL, it cannot be a string filepath. Received \`${imageFilePath}\`.`,
|
|
hint: "If you want to use an image from your `src` folder, you need to either import it or if the image is coming from a content collection, use the [image() schema helper](https://docs.astro.build/en/guides/images/#images-in-content-collections). See https://docs.astro.build/en/guides/images/#src-required for more information on the `src` property."
|
|
};
|
|
const MissingSharp = {
|
|
name: "MissingSharp",
|
|
title: "Could not find Sharp.",
|
|
message: "Could not find Sharp. Please install Sharp (`sharp`) manually into your project or migrate to another image service.",
|
|
hint: "See Sharp's installation instructions for more information: https://sharp.pixelplumbing.com/install. If you are not relying on `astro:assets` to optimize, transform, or process any images, you can configure a passthrough image service instead of installing Sharp. See https://docs.astro.build/en/reference/errors/missing-sharp for more information.\n\nSee https://docs.astro.build/en/guides/images/#default-image-service for more information on how to migrate to another image service."
|
|
};
|
|
const i18nNoLocaleFoundInPath = {
|
|
name: "i18nNoLocaleFoundInPath",
|
|
title: "The path doesn't contain any locale",
|
|
message: "You tried to use an i18n utility on a path that doesn't contain any locale. You can use `pathHasLocale` first to determine if the path has a locale."
|
|
};
|
|
const RewriteWithBodyUsed = {
|
|
name: "RewriteWithBodyUsed",
|
|
title: "Cannot use Astro.rewrite after the request body has been read",
|
|
message: "Astro.rewrite() cannot be used if the request body has already been read. If you need to read the body, first clone the request."
|
|
};
|
|
const ForbiddenRewrite = {
|
|
name: "ForbiddenRewrite",
|
|
title: "Forbidden rewrite to a static route.",
|
|
message: (from, to, component) => `You tried to rewrite the on-demand route '${from}' with the static route '${to}', when using the 'server' output.
|
|
|
|
The static route '${to}' is rendered by the component
|
|
'${component}', which is marked as prerendered. This is a forbidden operation because during the build, the component '${component}' is compiled to an
|
|
HTML file, which can't be retrieved at runtime by Astro.`,
|
|
hint: (component) => `Add \`export const prerender = false\` to the component '${component}', or use a Astro.redirect().`
|
|
};
|
|
const FontFamilyNotFound = {
|
|
name: "FontFamilyNotFound",
|
|
title: "Font family not found",
|
|
message: (family) => `No data was found for the \`"${family}"\` family passed to the \`<Font>\` component.`,
|
|
hint: "This is often caused by a typo. Check that the `<Font />` component is using a `cssVariable` specified in your config."
|
|
};
|
|
const ActionsReturnedInvalidDataError = {
|
|
name: "ActionsReturnedInvalidDataError",
|
|
title: "Action handler returned invalid data.",
|
|
message: (error) => `Action handler returned invalid data. Handlers should return serializable data types like objects, arrays, strings, and numbers. Parse error: ${error}`,
|
|
hint: "See the devalue library for all supported types: https://github.com/rich-harris/devalue"
|
|
};
|
|
const ActionNotFoundError = {
|
|
name: "ActionNotFoundError",
|
|
title: "Action not found.",
|
|
message: (actionName) => `The server received a request for an action named \`${actionName}\` but could not find a match. If you renamed an action, check that you've updated your \`actions/index\` file and your calling code to match.`,
|
|
hint: "You can run `astro check` to detect type errors caused by mismatched action names."
|
|
};
|
|
const SessionStorageInitError = {
|
|
name: "SessionStorageInitError",
|
|
title: "Session storage could not be initialized.",
|
|
message: (error, driver) => `Error when initializing session storage${driver ? ` with driver \`${driver}\`` : ""}. \`${error ?? ""}\``,
|
|
hint: "For more information, see https://docs.astro.build/en/guides/sessions/"
|
|
};
|
|
const SessionStorageSaveError = {
|
|
name: "SessionStorageSaveError",
|
|
title: "Session data could not be saved.",
|
|
message: (error, driver) => `Error when saving session data${driver ? ` with driver \`${driver}\`` : ""}. \`${error ?? ""}\``,
|
|
hint: "For more information, see https://docs.astro.build/en/guides/sessions/"
|
|
};
|
|
const CacheNotEnabled = {
|
|
name: "CacheNotEnabled",
|
|
title: "Cache is not enabled.",
|
|
message: "`Astro.cache` is not available because the cache feature is not enabled. To use caching, configure a cache provider in your Astro config under `experimental.cache`.",
|
|
hint: 'Use an adapter that provides a default cache provider, or set one explicitly: `experimental: { cache: { provider: "..." } }`. See https://docs.astro.build/en/reference/experimental-flags/route-caching/.'
|
|
};
|
|
|
|
function getDefaultExportFromCjs (x) {
|
|
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
|
|
}
|
|
|
|
function getAugmentedNamespace(n) {
|
|
if (Object.prototype.hasOwnProperty.call(n, '__esModule')) return n;
|
|
var f = n.default;
|
|
if (typeof f == "function") {
|
|
var a = function a () {
|
|
var isInstance = false;
|
|
try {
|
|
isInstance = this instanceof a;
|
|
} catch {}
|
|
if (isInstance) {
|
|
return Reflect.construct(f, arguments, this.constructor);
|
|
}
|
|
return f.apply(this, arguments);
|
|
};
|
|
a.prototype = f.prototype;
|
|
} else a = {};
|
|
Object.defineProperty(a, '__esModule', {value: true});
|
|
Object.keys(n).forEach(function (k) {
|
|
var d = Object.getOwnPropertyDescriptor(n, k);
|
|
Object.defineProperty(a, k, d.get ? d : {
|
|
enumerable: true,
|
|
get: function () {
|
|
return n[k];
|
|
}
|
|
});
|
|
});
|
|
return a;
|
|
}
|
|
|
|
var dist$1 = {exports: {}};
|
|
|
|
/**
|
|
* Tokenize input string.
|
|
*/
|
|
function lexer$1(str) {
|
|
var tokens = [];
|
|
var i = 0;
|
|
while (i < str.length) {
|
|
var char = str[i];
|
|
if (char === "*" || char === "+" || char === "?") {
|
|
tokens.push({ type: "MODIFIER", index: i, value: str[i++] });
|
|
continue;
|
|
}
|
|
if (char === "\\") {
|
|
tokens.push({ type: "ESCAPED_CHAR", index: i++, value: str[i++] });
|
|
continue;
|
|
}
|
|
if (char === "{") {
|
|
tokens.push({ type: "OPEN", index: i, value: str[i++] });
|
|
continue;
|
|
}
|
|
if (char === "}") {
|
|
tokens.push({ type: "CLOSE", index: i, value: str[i++] });
|
|
continue;
|
|
}
|
|
if (char === ":") {
|
|
var name = "";
|
|
var j = i + 1;
|
|
while (j < str.length) {
|
|
var code = str.charCodeAt(j);
|
|
if (
|
|
// `0-9`
|
|
(code >= 48 && code <= 57) ||
|
|
// `A-Z`
|
|
(code >= 65 && code <= 90) ||
|
|
// `a-z`
|
|
(code >= 97 && code <= 122) ||
|
|
// `_`
|
|
code === 95) {
|
|
name += str[j++];
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
if (!name)
|
|
throw new TypeError("Missing parameter name at " + i);
|
|
tokens.push({ type: "NAME", index: i, value: name });
|
|
i = j;
|
|
continue;
|
|
}
|
|
if (char === "(") {
|
|
var count = 1;
|
|
var pattern = "";
|
|
var j = i + 1;
|
|
if (str[j] === "?") {
|
|
throw new TypeError("Pattern cannot start with \"?\" at " + j);
|
|
}
|
|
while (j < str.length) {
|
|
if (str[j] === "\\") {
|
|
pattern += str[j++] + str[j++];
|
|
continue;
|
|
}
|
|
if (str[j] === ")") {
|
|
count--;
|
|
if (count === 0) {
|
|
j++;
|
|
break;
|
|
}
|
|
}
|
|
else if (str[j] === "(") {
|
|
count++;
|
|
if (str[j + 1] !== "?") {
|
|
throw new TypeError("Capturing groups are not allowed at " + j);
|
|
}
|
|
}
|
|
pattern += str[j++];
|
|
}
|
|
if (count)
|
|
throw new TypeError("Unbalanced pattern at " + i);
|
|
if (!pattern)
|
|
throw new TypeError("Missing pattern at " + i);
|
|
tokens.push({ type: "PATTERN", index: i, value: pattern });
|
|
i = j;
|
|
continue;
|
|
}
|
|
tokens.push({ type: "CHAR", index: i, value: str[i++] });
|
|
}
|
|
tokens.push({ type: "END", index: i, value: "" });
|
|
return tokens;
|
|
}
|
|
/**
|
|
* Parse a string for the raw tokens.
|
|
*/
|
|
function parse$3(str, options) {
|
|
if (options === void 0) { options = {}; }
|
|
var tokens = lexer$1(str);
|
|
var _a = options.prefixes, prefixes = _a === void 0 ? "./" : _a;
|
|
var defaultPattern = "[^" + escapeString$1(options.delimiter || "/#?") + "]+?";
|
|
var result = [];
|
|
var key = 0;
|
|
var i = 0;
|
|
var path = "";
|
|
var tryConsume = function (type) {
|
|
if (i < tokens.length && tokens[i].type === type)
|
|
return tokens[i++].value;
|
|
};
|
|
var mustConsume = function (type) {
|
|
var value = tryConsume(type);
|
|
if (value !== undefined)
|
|
return value;
|
|
var _a = tokens[i], nextType = _a.type, index = _a.index;
|
|
throw new TypeError("Unexpected " + nextType + " at " + index + ", expected " + type);
|
|
};
|
|
var consumeText = function () {
|
|
var result = "";
|
|
var value;
|
|
// tslint:disable-next-line
|
|
while ((value = tryConsume("CHAR") || tryConsume("ESCAPED_CHAR"))) {
|
|
result += value;
|
|
}
|
|
return result;
|
|
};
|
|
while (i < tokens.length) {
|
|
var char = tryConsume("CHAR");
|
|
var name = tryConsume("NAME");
|
|
var pattern = tryConsume("PATTERN");
|
|
if (name || pattern) {
|
|
var prefix = char || "";
|
|
if (prefixes.indexOf(prefix) === -1) {
|
|
path += prefix;
|
|
prefix = "";
|
|
}
|
|
if (path) {
|
|
result.push(path);
|
|
path = "";
|
|
}
|
|
result.push({
|
|
name: name || key++,
|
|
prefix: prefix,
|
|
suffix: "",
|
|
pattern: pattern || defaultPattern,
|
|
modifier: tryConsume("MODIFIER") || ""
|
|
});
|
|
continue;
|
|
}
|
|
var value = char || tryConsume("ESCAPED_CHAR");
|
|
if (value) {
|
|
path += value;
|
|
continue;
|
|
}
|
|
if (path) {
|
|
result.push(path);
|
|
path = "";
|
|
}
|
|
var open = tryConsume("OPEN");
|
|
if (open) {
|
|
var prefix = consumeText();
|
|
var name_1 = tryConsume("NAME") || "";
|
|
var pattern_1 = tryConsume("PATTERN") || "";
|
|
var suffix = consumeText();
|
|
mustConsume("CLOSE");
|
|
result.push({
|
|
name: name_1 || (pattern_1 ? key++ : ""),
|
|
pattern: name_1 && !pattern_1 ? defaultPattern : pattern_1,
|
|
prefix: prefix,
|
|
suffix: suffix,
|
|
modifier: tryConsume("MODIFIER") || ""
|
|
});
|
|
continue;
|
|
}
|
|
mustConsume("END");
|
|
}
|
|
return result;
|
|
}
|
|
/**
|
|
* Compile a string to a template function for the path.
|
|
*/
|
|
function compile$1(str, options) {
|
|
return tokensToFunction$1(parse$3(str, options), options);
|
|
}
|
|
/**
|
|
* Expose a method for transforming tokens into the path function.
|
|
*/
|
|
function tokensToFunction$1(tokens, options) {
|
|
if (options === void 0) { options = {}; }
|
|
var reFlags = flags$1(options);
|
|
var _a = options.encode, encode = _a === void 0 ? function (x) { return x; } : _a, _b = options.validate, validate = _b === void 0 ? true : _b;
|
|
// Compile all the tokens into regexps.
|
|
var matches = tokens.map(function (token) {
|
|
if (typeof token === "object") {
|
|
return new RegExp("^(?:" + token.pattern + ")$", reFlags);
|
|
}
|
|
});
|
|
return function (data) {
|
|
var path = "";
|
|
for (var i = 0; i < tokens.length; i++) {
|
|
var token = tokens[i];
|
|
if (typeof token === "string") {
|
|
path += token;
|
|
continue;
|
|
}
|
|
var value = data ? data[token.name] : undefined;
|
|
var optional = token.modifier === "?" || token.modifier === "*";
|
|
var repeat = token.modifier === "*" || token.modifier === "+";
|
|
if (Array.isArray(value)) {
|
|
if (!repeat) {
|
|
throw new TypeError("Expected \"" + token.name + "\" to not repeat, but got an array");
|
|
}
|
|
if (value.length === 0) {
|
|
if (optional)
|
|
continue;
|
|
throw new TypeError("Expected \"" + token.name + "\" to not be empty");
|
|
}
|
|
for (var j = 0; j < value.length; j++) {
|
|
var segment = encode(value[j], token);
|
|
if (validate && !matches[i].test(segment)) {
|
|
throw new TypeError("Expected all \"" + token.name + "\" to match \"" + token.pattern + "\", but got \"" + segment + "\"");
|
|
}
|
|
path += token.prefix + segment + token.suffix;
|
|
}
|
|
continue;
|
|
}
|
|
if (typeof value === "string" || typeof value === "number") {
|
|
var segment = encode(String(value), token);
|
|
if (validate && !matches[i].test(segment)) {
|
|
throw new TypeError("Expected \"" + token.name + "\" to match \"" + token.pattern + "\", but got \"" + segment + "\"");
|
|
}
|
|
path += token.prefix + segment + token.suffix;
|
|
continue;
|
|
}
|
|
if (optional)
|
|
continue;
|
|
var typeOfMessage = repeat ? "an array" : "a string";
|
|
throw new TypeError("Expected \"" + token.name + "\" to be " + typeOfMessage);
|
|
}
|
|
return path;
|
|
};
|
|
}
|
|
/**
|
|
* Create path match function from `path-to-regexp` spec.
|
|
*/
|
|
function match$1(str, options) {
|
|
var keys = [];
|
|
var re = pathToRegexp$1(str, keys, options);
|
|
return regexpToFunction$1(re, keys, options);
|
|
}
|
|
/**
|
|
* Create a path match function from `path-to-regexp` output.
|
|
*/
|
|
function regexpToFunction$1(re, keys, options) {
|
|
if (options === void 0) { options = {}; }
|
|
var _a = options.decode, decode = _a === void 0 ? function (x) { return x; } : _a;
|
|
return function (pathname) {
|
|
var m = re.exec(pathname);
|
|
if (!m)
|
|
return false;
|
|
var path = m[0], index = m.index;
|
|
var params = Object.create(null);
|
|
var _loop_1 = function (i) {
|
|
// tslint:disable-next-line
|
|
if (m[i] === undefined)
|
|
return "continue";
|
|
var key = keys[i - 1];
|
|
if (key.modifier === "*" || key.modifier === "+") {
|
|
params[key.name] = m[i].split(key.prefix + key.suffix).map(function (value) {
|
|
return decode(value, key);
|
|
});
|
|
}
|
|
else {
|
|
params[key.name] = decode(m[i], key);
|
|
}
|
|
};
|
|
for (var i = 1; i < m.length; i++) {
|
|
_loop_1(i);
|
|
}
|
|
return { path: path, index: index, params: params };
|
|
};
|
|
}
|
|
/**
|
|
* Escape a regular expression string.
|
|
*/
|
|
function escapeString$1(str) {
|
|
return str.replace(/([.+*?=^!:${}()[\]|/\\])/g, "\\$1");
|
|
}
|
|
/**
|
|
* Get the flags for a regexp from the options.
|
|
*/
|
|
function flags$1(options) {
|
|
return options && options.sensitive ? "" : "i";
|
|
}
|
|
/**
|
|
* Pull out keys from a regexp.
|
|
*/
|
|
function regexpToRegexp$1(path, keys) {
|
|
if (!keys)
|
|
return path;
|
|
// Use a negative lookahead to match only capturing groups.
|
|
var groups = path.source.match(/\((?!\?)/g);
|
|
if (groups) {
|
|
for (var i = 0; i < groups.length; i++) {
|
|
keys.push({
|
|
name: i,
|
|
prefix: "",
|
|
suffix: "",
|
|
modifier: "",
|
|
pattern: ""
|
|
});
|
|
}
|
|
}
|
|
return path;
|
|
}
|
|
/**
|
|
* Transform an array into a regexp.
|
|
*/
|
|
function arrayToRegexp$1(paths, keys, options) {
|
|
var parts = paths.map(function (path) { return pathToRegexp$1(path, keys, options).source; });
|
|
return new RegExp("(?:" + parts.join("|") + ")", flags$1(options));
|
|
}
|
|
/**
|
|
* Create a path regexp from string input.
|
|
*/
|
|
function stringToRegexp$1(path, keys, options) {
|
|
return tokensToRegexp$1(parse$3(path, options), keys, options);
|
|
}
|
|
/**
|
|
* Expose a function for taking tokens and returning a RegExp.
|
|
*/
|
|
function tokensToRegexp$1(tokens, keys, options) {
|
|
if (options === void 0) { options = {}; }
|
|
var _a = options.strict, strict = _a === void 0 ? false : _a, _b = options.start, start = _b === void 0 ? true : _b, _c = options.end, end = _c === void 0 ? true : _c, _d = options.encode, encode = _d === void 0 ? function (x) { return x; } : _d;
|
|
var endsWith = "[" + escapeString$1(options.endsWith || "") + "]|$";
|
|
var delimiter = "[" + escapeString$1(options.delimiter || "/#?") + "]";
|
|
var route = start ? "^" : "";
|
|
// Iterate over the tokens and create our regexp string.
|
|
for (var _i = 0, tokens_1 = tokens; _i < tokens_1.length; _i++) {
|
|
var token = tokens_1[_i];
|
|
if (typeof token === "string") {
|
|
route += escapeString$1(encode(token));
|
|
}
|
|
else {
|
|
var prefix = escapeString$1(encode(token.prefix));
|
|
var suffix = escapeString$1(encode(token.suffix));
|
|
if (token.pattern) {
|
|
if (keys)
|
|
keys.push(token);
|
|
if (prefix || suffix) {
|
|
if (token.modifier === "+" || token.modifier === "*") {
|
|
var mod = token.modifier === "*" ? "?" : "";
|
|
route += "(?:" + prefix + "((?:" + token.pattern + ")(?:" + suffix + prefix + "(?:" + token.pattern + "))*)" + suffix + ")" + mod;
|
|
}
|
|
else {
|
|
route += "(?:" + prefix + "(" + token.pattern + ")" + suffix + ")" + token.modifier;
|
|
}
|
|
}
|
|
else {
|
|
route += "(" + token.pattern + ")" + token.modifier;
|
|
}
|
|
}
|
|
else {
|
|
route += "(?:" + prefix + suffix + ")" + token.modifier;
|
|
}
|
|
}
|
|
}
|
|
if (end) {
|
|
if (!strict)
|
|
route += delimiter + "?";
|
|
route += !options.endsWith ? "$" : "(?=" + endsWith + ")";
|
|
}
|
|
else {
|
|
var endToken = tokens[tokens.length - 1];
|
|
var isEndDelimited = typeof endToken === "string"
|
|
? delimiter.indexOf(endToken[endToken.length - 1]) > -1
|
|
: // tslint:disable-next-line
|
|
endToken === undefined;
|
|
if (!strict) {
|
|
route += "(?:" + delimiter + "(?=" + endsWith + "))?";
|
|
}
|
|
if (!isEndDelimited) {
|
|
route += "(?=" + delimiter + "|" + endsWith + ")";
|
|
}
|
|
}
|
|
return new RegExp(route, flags$1(options));
|
|
}
|
|
/**
|
|
* Normalize the given path string, returning a regular expression.
|
|
*
|
|
* An empty array can be passed in for the keys, which will hold the
|
|
* placeholder key descriptions. For example, using `/user/:id`, `keys` will
|
|
* contain `[{ name: 'id', delimiter: '/', optional: false, repeat: false }]`.
|
|
*/
|
|
function pathToRegexp$1(path, keys, options) {
|
|
if (path instanceof RegExp)
|
|
return regexpToRegexp$1(path, keys);
|
|
if (Array.isArray(path))
|
|
return arrayToRegexp$1(path, keys, options);
|
|
return stringToRegexp$1(path, keys, options);
|
|
}
|
|
|
|
const dist_es2015$1 = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
|
|
__proto__: null,
|
|
compile: compile$1,
|
|
match: match$1,
|
|
parse: parse$3,
|
|
pathToRegexp: pathToRegexp$1,
|
|
regexpToFunction: regexpToFunction$1,
|
|
tokensToFunction: tokensToFunction$1,
|
|
tokensToRegexp: tokensToRegexp$1
|
|
}, Symbol.toStringTag, { value: 'Module' }));
|
|
|
|
const require$$1 = /*@__PURE__*/getAugmentedNamespace(dist_es2015$1);
|
|
|
|
/**
|
|
* Tokenize input string.
|
|
*/
|
|
function lexer(str) {
|
|
var tokens = [];
|
|
var i = 0;
|
|
while (i < str.length) {
|
|
var char = str[i];
|
|
if (char === "*" || char === "+" || char === "?") {
|
|
tokens.push({ type: "MODIFIER", index: i, value: str[i++] });
|
|
continue;
|
|
}
|
|
if (char === "\\") {
|
|
tokens.push({ type: "ESCAPED_CHAR", index: i++, value: str[i++] });
|
|
continue;
|
|
}
|
|
if (char === "{") {
|
|
tokens.push({ type: "OPEN", index: i, value: str[i++] });
|
|
continue;
|
|
}
|
|
if (char === "}") {
|
|
tokens.push({ type: "CLOSE", index: i, value: str[i++] });
|
|
continue;
|
|
}
|
|
if (char === ":") {
|
|
var name = "";
|
|
var j = i + 1;
|
|
while (j < str.length) {
|
|
var code = str.charCodeAt(j);
|
|
if (
|
|
// `0-9`
|
|
(code >= 48 && code <= 57) ||
|
|
// `A-Z`
|
|
(code >= 65 && code <= 90) ||
|
|
// `a-z`
|
|
(code >= 97 && code <= 122) ||
|
|
// `_`
|
|
code === 95) {
|
|
name += str[j++];
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
if (!name)
|
|
throw new TypeError("Missing parameter name at ".concat(i));
|
|
tokens.push({ type: "NAME", index: i, value: name });
|
|
i = j;
|
|
continue;
|
|
}
|
|
if (char === "(") {
|
|
var count = 1;
|
|
var pattern = "";
|
|
var j = i + 1;
|
|
if (str[j] === "?") {
|
|
throw new TypeError("Pattern cannot start with \"?\" at ".concat(j));
|
|
}
|
|
while (j < str.length) {
|
|
if (str[j] === "\\") {
|
|
pattern += str[j++] + str[j++];
|
|
continue;
|
|
}
|
|
if (str[j] === ")") {
|
|
count--;
|
|
if (count === 0) {
|
|
j++;
|
|
break;
|
|
}
|
|
}
|
|
else if (str[j] === "(") {
|
|
count++;
|
|
if (str[j + 1] !== "?") {
|
|
throw new TypeError("Capturing groups are not allowed at ".concat(j));
|
|
}
|
|
}
|
|
pattern += str[j++];
|
|
}
|
|
if (count)
|
|
throw new TypeError("Unbalanced pattern at ".concat(i));
|
|
if (!pattern)
|
|
throw new TypeError("Missing pattern at ".concat(i));
|
|
tokens.push({ type: "PATTERN", index: i, value: pattern });
|
|
i = j;
|
|
continue;
|
|
}
|
|
tokens.push({ type: "CHAR", index: i, value: str[i++] });
|
|
}
|
|
tokens.push({ type: "END", index: i, value: "" });
|
|
return tokens;
|
|
}
|
|
/**
|
|
* Parse a string for the raw tokens.
|
|
*/
|
|
function parse$2(str, options) {
|
|
if (options === void 0) { options = {}; }
|
|
var tokens = lexer(str);
|
|
var _a = options.prefixes, prefixes = _a === void 0 ? "./" : _a, _b = options.delimiter, delimiter = _b === void 0 ? "/#?" : _b;
|
|
var result = [];
|
|
var key = 0;
|
|
var i = 0;
|
|
var path = "";
|
|
var tryConsume = function (type) {
|
|
if (i < tokens.length && tokens[i].type === type)
|
|
return tokens[i++].value;
|
|
};
|
|
var mustConsume = function (type) {
|
|
var value = tryConsume(type);
|
|
if (value !== undefined)
|
|
return value;
|
|
var _a = tokens[i], nextType = _a.type, index = _a.index;
|
|
throw new TypeError("Unexpected ".concat(nextType, " at ").concat(index, ", expected ").concat(type));
|
|
};
|
|
var consumeText = function () {
|
|
var result = "";
|
|
var value;
|
|
while ((value = tryConsume("CHAR") || tryConsume("ESCAPED_CHAR"))) {
|
|
result += value;
|
|
}
|
|
return result;
|
|
};
|
|
var isSafe = function (value) {
|
|
for (var _i = 0, delimiter_1 = delimiter; _i < delimiter_1.length; _i++) {
|
|
var char = delimiter_1[_i];
|
|
if (value.indexOf(char) > -1)
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
var safePattern = function (prefix) {
|
|
var prev = result[result.length - 1];
|
|
var prevText = prefix || (prev && typeof prev === "string" ? prev : "");
|
|
if (prev && !prevText) {
|
|
throw new TypeError("Must have text between two parameters, missing text after \"".concat(prev.name, "\""));
|
|
}
|
|
if (!prevText || isSafe(prevText))
|
|
return "[^".concat(escapeString(delimiter), "]+?");
|
|
return "(?:(?!".concat(escapeString(prevText), ")[^").concat(escapeString(delimiter), "])+?");
|
|
};
|
|
while (i < tokens.length) {
|
|
var char = tryConsume("CHAR");
|
|
var name = tryConsume("NAME");
|
|
var pattern = tryConsume("PATTERN");
|
|
if (name || pattern) {
|
|
var prefix = char || "";
|
|
if (prefixes.indexOf(prefix) === -1) {
|
|
path += prefix;
|
|
prefix = "";
|
|
}
|
|
if (path) {
|
|
result.push(path);
|
|
path = "";
|
|
}
|
|
result.push({
|
|
name: name || key++,
|
|
prefix: prefix,
|
|
suffix: "",
|
|
pattern: pattern || safePattern(prefix),
|
|
modifier: tryConsume("MODIFIER") || "",
|
|
});
|
|
continue;
|
|
}
|
|
var value = char || tryConsume("ESCAPED_CHAR");
|
|
if (value) {
|
|
path += value;
|
|
continue;
|
|
}
|
|
if (path) {
|
|
result.push(path);
|
|
path = "";
|
|
}
|
|
var open = tryConsume("OPEN");
|
|
if (open) {
|
|
var prefix = consumeText();
|
|
var name_1 = tryConsume("NAME") || "";
|
|
var pattern_1 = tryConsume("PATTERN") || "";
|
|
var suffix = consumeText();
|
|
mustConsume("CLOSE");
|
|
result.push({
|
|
name: name_1 || (pattern_1 ? key++ : ""),
|
|
pattern: name_1 && !pattern_1 ? safePattern(prefix) : pattern_1,
|
|
prefix: prefix,
|
|
suffix: suffix,
|
|
modifier: tryConsume("MODIFIER") || "",
|
|
});
|
|
continue;
|
|
}
|
|
mustConsume("END");
|
|
}
|
|
return result;
|
|
}
|
|
/**
|
|
* Compile a string to a template function for the path.
|
|
*/
|
|
function compile(str, options) {
|
|
return tokensToFunction(parse$2(str, options), options);
|
|
}
|
|
/**
|
|
* Expose a method for transforming tokens into the path function.
|
|
*/
|
|
function tokensToFunction(tokens, options) {
|
|
if (options === void 0) { options = {}; }
|
|
var reFlags = flags(options);
|
|
var _a = options.encode, encode = _a === void 0 ? function (x) { return x; } : _a, _b = options.validate, validate = _b === void 0 ? true : _b;
|
|
// Compile all the tokens into regexps.
|
|
var matches = tokens.map(function (token) {
|
|
if (typeof token === "object") {
|
|
return new RegExp("^(?:".concat(token.pattern, ")$"), reFlags);
|
|
}
|
|
});
|
|
return function (data) {
|
|
var path = "";
|
|
for (var i = 0; i < tokens.length; i++) {
|
|
var token = tokens[i];
|
|
if (typeof token === "string") {
|
|
path += token;
|
|
continue;
|
|
}
|
|
var value = data ? data[token.name] : undefined;
|
|
var optional = token.modifier === "?" || token.modifier === "*";
|
|
var repeat = token.modifier === "*" || token.modifier === "+";
|
|
if (Array.isArray(value)) {
|
|
if (!repeat) {
|
|
throw new TypeError("Expected \"".concat(token.name, "\" to not repeat, but got an array"));
|
|
}
|
|
if (value.length === 0) {
|
|
if (optional)
|
|
continue;
|
|
throw new TypeError("Expected \"".concat(token.name, "\" to not be empty"));
|
|
}
|
|
for (var j = 0; j < value.length; j++) {
|
|
var segment = encode(value[j], token);
|
|
if (validate && !matches[i].test(segment)) {
|
|
throw new TypeError("Expected all \"".concat(token.name, "\" to match \"").concat(token.pattern, "\", but got \"").concat(segment, "\""));
|
|
}
|
|
path += token.prefix + segment + token.suffix;
|
|
}
|
|
continue;
|
|
}
|
|
if (typeof value === "string" || typeof value === "number") {
|
|
var segment = encode(String(value), token);
|
|
if (validate && !matches[i].test(segment)) {
|
|
throw new TypeError("Expected \"".concat(token.name, "\" to match \"").concat(token.pattern, "\", but got \"").concat(segment, "\""));
|
|
}
|
|
path += token.prefix + segment + token.suffix;
|
|
continue;
|
|
}
|
|
if (optional)
|
|
continue;
|
|
var typeOfMessage = repeat ? "an array" : "a string";
|
|
throw new TypeError("Expected \"".concat(token.name, "\" to be ").concat(typeOfMessage));
|
|
}
|
|
return path;
|
|
};
|
|
}
|
|
/**
|
|
* Create path match function from `path-to-regexp` spec.
|
|
*/
|
|
function match(str, options) {
|
|
var keys = [];
|
|
var re = pathToRegexp(str, keys, options);
|
|
return regexpToFunction(re, keys, options);
|
|
}
|
|
/**
|
|
* Create a path match function from `path-to-regexp` output.
|
|
*/
|
|
function regexpToFunction(re, keys, options) {
|
|
if (options === void 0) { options = {}; }
|
|
var _a = options.decode, decode = _a === void 0 ? function (x) { return x; } : _a;
|
|
return function (pathname) {
|
|
var m = re.exec(pathname);
|
|
if (!m)
|
|
return false;
|
|
var path = m[0], index = m.index;
|
|
var params = Object.create(null);
|
|
var _loop_1 = function (i) {
|
|
if (m[i] === undefined)
|
|
return "continue";
|
|
var key = keys[i - 1];
|
|
if (key.modifier === "*" || key.modifier === "+") {
|
|
params[key.name] = m[i].split(key.prefix + key.suffix).map(function (value) {
|
|
return decode(value, key);
|
|
});
|
|
}
|
|
else {
|
|
params[key.name] = decode(m[i], key);
|
|
}
|
|
};
|
|
for (var i = 1; i < m.length; i++) {
|
|
_loop_1(i);
|
|
}
|
|
return { path: path, index: index, params: params };
|
|
};
|
|
}
|
|
/**
|
|
* Escape a regular expression string.
|
|
*/
|
|
function escapeString(str) {
|
|
return str.replace(/([.+*?=^!:${}()[\]|/\\])/g, "\\$1");
|
|
}
|
|
/**
|
|
* Get the flags for a regexp from the options.
|
|
*/
|
|
function flags(options) {
|
|
return options && options.sensitive ? "" : "i";
|
|
}
|
|
/**
|
|
* Pull out keys from a regexp.
|
|
*/
|
|
function regexpToRegexp(path, keys) {
|
|
if (!keys)
|
|
return path;
|
|
var groupsRegex = /\((?:\?<(.*?)>)?(?!\?)/g;
|
|
var index = 0;
|
|
var execResult = groupsRegex.exec(path.source);
|
|
while (execResult) {
|
|
keys.push({
|
|
// Use parenthesized substring match if available, index otherwise
|
|
name: execResult[1] || index++,
|
|
prefix: "",
|
|
suffix: "",
|
|
modifier: "",
|
|
pattern: "",
|
|
});
|
|
execResult = groupsRegex.exec(path.source);
|
|
}
|
|
return path;
|
|
}
|
|
/**
|
|
* Transform an array into a regexp.
|
|
*/
|
|
function arrayToRegexp(paths, keys, options) {
|
|
var parts = paths.map(function (path) { return pathToRegexp(path, keys, options).source; });
|
|
return new RegExp("(?:".concat(parts.join("|"), ")"), flags(options));
|
|
}
|
|
/**
|
|
* Create a path regexp from string input.
|
|
*/
|
|
function stringToRegexp(path, keys, options) {
|
|
return tokensToRegexp(parse$2(path, options), keys, options);
|
|
}
|
|
/**
|
|
* Expose a function for taking tokens and returning a RegExp.
|
|
*/
|
|
function tokensToRegexp(tokens, keys, options) {
|
|
if (options === void 0) { options = {}; }
|
|
var _a = options.strict, strict = _a === void 0 ? false : _a, _b = options.start, start = _b === void 0 ? true : _b, _c = options.end, end = _c === void 0 ? true : _c, _d = options.encode, encode = _d === void 0 ? function (x) { return x; } : _d, _e = options.delimiter, delimiter = _e === void 0 ? "/#?" : _e, _f = options.endsWith, endsWith = _f === void 0 ? "" : _f;
|
|
var endsWithRe = "[".concat(escapeString(endsWith), "]|$");
|
|
var delimiterRe = "[".concat(escapeString(delimiter), "]");
|
|
var route = start ? "^" : "";
|
|
// Iterate over the tokens and create our regexp string.
|
|
for (var _i = 0, tokens_1 = tokens; _i < tokens_1.length; _i++) {
|
|
var token = tokens_1[_i];
|
|
if (typeof token === "string") {
|
|
route += escapeString(encode(token));
|
|
}
|
|
else {
|
|
var prefix = escapeString(encode(token.prefix));
|
|
var suffix = escapeString(encode(token.suffix));
|
|
if (token.pattern) {
|
|
if (keys)
|
|
keys.push(token);
|
|
if (prefix || suffix) {
|
|
if (token.modifier === "+" || token.modifier === "*") {
|
|
var mod = token.modifier === "*" ? "?" : "";
|
|
route += "(?:".concat(prefix, "((?:").concat(token.pattern, ")(?:").concat(suffix).concat(prefix, "(?:").concat(token.pattern, "))*)").concat(suffix, ")").concat(mod);
|
|
}
|
|
else {
|
|
route += "(?:".concat(prefix, "(").concat(token.pattern, ")").concat(suffix, ")").concat(token.modifier);
|
|
}
|
|
}
|
|
else {
|
|
if (token.modifier === "+" || token.modifier === "*") {
|
|
throw new TypeError("Can not repeat \"".concat(token.name, "\" without a prefix and suffix"));
|
|
}
|
|
route += "(".concat(token.pattern, ")").concat(token.modifier);
|
|
}
|
|
}
|
|
else {
|
|
route += "(?:".concat(prefix).concat(suffix, ")").concat(token.modifier);
|
|
}
|
|
}
|
|
}
|
|
if (end) {
|
|
if (!strict)
|
|
route += "".concat(delimiterRe, "?");
|
|
route += !options.endsWith ? "$" : "(?=".concat(endsWithRe, ")");
|
|
}
|
|
else {
|
|
var endToken = tokens[tokens.length - 1];
|
|
var isEndDelimited = typeof endToken === "string"
|
|
? delimiterRe.indexOf(endToken[endToken.length - 1]) > -1
|
|
: endToken === undefined;
|
|
if (!strict) {
|
|
route += "(?:".concat(delimiterRe, "(?=").concat(endsWithRe, "))?");
|
|
}
|
|
if (!isEndDelimited) {
|
|
route += "(?=".concat(delimiterRe, "|").concat(endsWithRe, ")");
|
|
}
|
|
}
|
|
return new RegExp(route, flags(options));
|
|
}
|
|
/**
|
|
* Normalize the given path string, returning a regular expression.
|
|
*
|
|
* An empty array can be passed in for the keys, which will hold the
|
|
* placeholder key descriptions. For example, using `/user/:id`, `keys` will
|
|
* contain `[{ name: 'id', delimiter: '/', optional: false, repeat: false }]`.
|
|
*/
|
|
function pathToRegexp(path, keys, options) {
|
|
if (path instanceof RegExp)
|
|
return regexpToRegexp(path, keys);
|
|
if (Array.isArray(path))
|
|
return arrayToRegexp(path, keys, options);
|
|
return stringToRegexp(path, keys, options);
|
|
}
|
|
|
|
const dist_es2015 = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
|
|
__proto__: null,
|
|
compile,
|
|
match,
|
|
parse: parse$2,
|
|
pathToRegexp,
|
|
regexpToFunction,
|
|
tokensToFunction,
|
|
tokensToRegexp
|
|
}, Symbol.toStringTag, { value: 'Module' }));
|
|
|
|
const require$$2 = /*@__PURE__*/getAugmentedNamespace(dist_es2015);
|
|
|
|
var superstatic;
|
|
var hasRequiredSuperstatic;
|
|
|
|
function requireSuperstatic () {
|
|
if (hasRequiredSuperstatic) return superstatic;
|
|
hasRequiredSuperstatic = 1;
|
|
var __defProp = Object.defineProperty;
|
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
var __export = (target, all) => {
|
|
for (var name in all)
|
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
};
|
|
var __copyProps = (to, from, except, desc) => {
|
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
for (let key of __getOwnPropNames(from))
|
|
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
}
|
|
return to;
|
|
};
|
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
var superstatic_exports = {};
|
|
__export(superstatic_exports, {
|
|
collectHasSegments: () => collectHasSegments,
|
|
convertCleanUrls: () => convertCleanUrls,
|
|
convertHeaders: () => convertHeaders,
|
|
convertRedirects: () => convertRedirects,
|
|
convertRewrites: () => convertRewrites,
|
|
convertTrailingSlash: () => convertTrailingSlash,
|
|
getCleanUrls: () => getCleanUrls,
|
|
pathToRegexp: () => pathToRegexp,
|
|
sourceToRegex: () => sourceToRegex
|
|
});
|
|
superstatic = __toCommonJS(superstatic_exports);
|
|
var import_url = require$$0;
|
|
var import_path_to_regexp = require$$1;
|
|
var import_path_to_regexp_updated = require$$2;
|
|
function cloneKeys(keys) {
|
|
if (typeof keys === "undefined") {
|
|
return void 0;
|
|
}
|
|
return keys.slice(0);
|
|
}
|
|
function compareKeys(left, right) {
|
|
const leftSerialized = typeof left === "undefined" ? "undefined" : left.toString();
|
|
const rightSerialized = typeof right === "undefined" ? "undefined" : right.toString();
|
|
return leftSerialized === rightSerialized;
|
|
}
|
|
function pathToRegexp(callerId, path, keys, options) {
|
|
const newKeys = cloneKeys(keys);
|
|
const currentRegExp = (0, import_path_to_regexp.pathToRegexp)(path, keys, options);
|
|
try {
|
|
const currentKeys = keys;
|
|
const newRegExp = (0, import_path_to_regexp_updated.pathToRegexp)(path, newKeys, options);
|
|
const isDiffRegExp = currentRegExp.toString() !== newRegExp.toString();
|
|
if (process.env.FORCE_PATH_TO_REGEXP_LOG || isDiffRegExp) {
|
|
const message = JSON.stringify({
|
|
path,
|
|
currentRegExp: currentRegExp.toString(),
|
|
newRegExp: newRegExp.toString()
|
|
});
|
|
console.error(`[vc] PATH TO REGEXP PATH DIFF @ #${callerId}: ${message}`);
|
|
}
|
|
const isDiffKeys = !compareKeys(keys, newKeys);
|
|
if (process.env.FORCE_PATH_TO_REGEXP_LOG || isDiffKeys) {
|
|
const message = JSON.stringify({
|
|
isDiffKeys,
|
|
currentKeys,
|
|
newKeys
|
|
});
|
|
console.error(`[vc] PATH TO REGEXP KEYS DIFF @ #${callerId}: ${message}`);
|
|
}
|
|
} catch (err) {
|
|
const error = err;
|
|
const message = JSON.stringify({
|
|
path,
|
|
error: error.message
|
|
});
|
|
console.error(`[vc] PATH TO REGEXP ERROR @ #${callerId}: ${message}`);
|
|
}
|
|
return currentRegExp;
|
|
}
|
|
const UN_NAMED_SEGMENT = "__UN_NAMED_SEGMENT__";
|
|
function getCleanUrls(filePaths) {
|
|
const htmlFiles = filePaths.map(toRoute).filter((f) => f.endsWith(".html")).map((f) => ({
|
|
html: f,
|
|
clean: f.slice(0, -5)
|
|
}));
|
|
return htmlFiles;
|
|
}
|
|
function convertCleanUrls(cleanUrls, trailingSlash, status = 308) {
|
|
const routes = [];
|
|
if (cleanUrls) {
|
|
const loc = trailingSlash ? "/$1/" : "/$1";
|
|
routes.push({
|
|
src: "^/(?:(.+)/)?index(?:\\.html)?/?$",
|
|
headers: { Location: loc },
|
|
status
|
|
});
|
|
routes.push({
|
|
src: "^/(.*)\\.html/?$",
|
|
headers: { Location: loc },
|
|
status
|
|
});
|
|
}
|
|
return routes;
|
|
}
|
|
function convertRedirects(redirects, defaultStatus = 308) {
|
|
return redirects.map((r) => {
|
|
const { src, segments } = sourceToRegex(r.source);
|
|
const hasSegments = collectHasSegments(r.has);
|
|
normalizeHasKeys(r.has);
|
|
normalizeHasKeys(r.missing);
|
|
try {
|
|
const loc = replaceSegments(segments, hasSegments, r.destination, true);
|
|
let status;
|
|
if (typeof r.permanent === "boolean") {
|
|
status = r.permanent ? 308 : 307;
|
|
} else if (r.statusCode) {
|
|
status = r.statusCode;
|
|
} else {
|
|
status = defaultStatus;
|
|
}
|
|
const route = {
|
|
src,
|
|
headers: { Location: loc },
|
|
status
|
|
};
|
|
if (typeof r.env !== "undefined") {
|
|
route.env = r.env;
|
|
}
|
|
if (r.has) {
|
|
route.has = r.has;
|
|
}
|
|
if (r.missing) {
|
|
route.missing = r.missing;
|
|
}
|
|
return route;
|
|
} catch (e) {
|
|
throw new Error(`Failed to parse redirect: ${JSON.stringify(r)}`);
|
|
}
|
|
});
|
|
}
|
|
function convertRewrites(rewrites, internalParamNames) {
|
|
return rewrites.map((r) => {
|
|
const { src, segments } = sourceToRegex(r.source);
|
|
const hasSegments = collectHasSegments(r.has);
|
|
normalizeHasKeys(r.has);
|
|
normalizeHasKeys(r.missing);
|
|
try {
|
|
const dest = replaceSegments(
|
|
segments,
|
|
hasSegments,
|
|
r.destination,
|
|
false,
|
|
internalParamNames
|
|
);
|
|
const route = { src, dest, check: true };
|
|
if (typeof r.env !== "undefined") {
|
|
route.env = r.env;
|
|
}
|
|
if (r.has) {
|
|
route.has = r.has;
|
|
}
|
|
if (r.missing) {
|
|
route.missing = r.missing;
|
|
}
|
|
if (r.statusCode) {
|
|
route.status = r.statusCode;
|
|
}
|
|
return route;
|
|
} catch (e) {
|
|
throw new Error(`Failed to parse rewrite: ${JSON.stringify(r)}`);
|
|
}
|
|
});
|
|
}
|
|
function convertHeaders(headers) {
|
|
return headers.map((h) => {
|
|
const obj = {};
|
|
const { src, segments } = sourceToRegex(h.source);
|
|
const hasSegments = collectHasSegments(h.has);
|
|
normalizeHasKeys(h.has);
|
|
normalizeHasKeys(h.missing);
|
|
const namedSegments = segments.filter((name) => name !== UN_NAMED_SEGMENT);
|
|
const indexes = {};
|
|
segments.forEach((name, index) => {
|
|
indexes[name] = toSegmentDest(index);
|
|
});
|
|
hasSegments.forEach((name) => {
|
|
indexes[name] = "$" + name;
|
|
});
|
|
h.headers.forEach(({ key, value }) => {
|
|
if (namedSegments.length > 0 || hasSegments.length > 0) {
|
|
if (key.includes(":")) {
|
|
key = safelyCompile(key, indexes);
|
|
}
|
|
if (value.includes(":")) {
|
|
value = safelyCompile(value, indexes);
|
|
}
|
|
}
|
|
obj[key] = value;
|
|
});
|
|
const route = {
|
|
src,
|
|
headers: obj,
|
|
continue: true
|
|
};
|
|
if (h.has) {
|
|
route.has = h.has;
|
|
}
|
|
if (h.missing) {
|
|
route.missing = h.missing;
|
|
}
|
|
return route;
|
|
});
|
|
}
|
|
function convertTrailingSlash(enable, status = 308) {
|
|
const routes = [];
|
|
if (enable) {
|
|
routes.push({
|
|
src: "^/\\.well-known(?:/.*)?$"
|
|
});
|
|
routes.push({
|
|
src: "^/((?:[^/]+/)*[^/\\.]+)$",
|
|
headers: { Location: "/$1/" },
|
|
status
|
|
});
|
|
routes.push({
|
|
src: "^/((?:[^/]+/)*[^/]+\\.\\w+)/$",
|
|
headers: { Location: "/$1" },
|
|
status
|
|
});
|
|
} else {
|
|
routes.push({
|
|
src: "^/(.*)\\/$",
|
|
headers: { Location: "/$1" },
|
|
status
|
|
});
|
|
}
|
|
return routes;
|
|
}
|
|
function sourceToRegex(source) {
|
|
const keys = [];
|
|
const r = pathToRegexp("632", source, keys, {
|
|
strict: true,
|
|
sensitive: true,
|
|
delimiter: "/"
|
|
});
|
|
const segments = keys.map((k) => k.name).map((name) => {
|
|
if (typeof name !== "string") {
|
|
return UN_NAMED_SEGMENT;
|
|
}
|
|
return name;
|
|
});
|
|
return { src: r.source, segments };
|
|
}
|
|
const namedGroupsRegex = /\(\?<([a-zA-Z][a-zA-Z0-9_]*)>/g;
|
|
const normalizeHasKeys = (hasItems = []) => {
|
|
for (const hasItem of hasItems) {
|
|
if ("key" in hasItem && hasItem.type === "header") {
|
|
hasItem.key = hasItem.key.toLowerCase();
|
|
}
|
|
}
|
|
return hasItems;
|
|
};
|
|
function getStringValueForRegex(value) {
|
|
if (typeof value === "string") {
|
|
return value;
|
|
}
|
|
if (value && typeof value === "object" && value !== null) {
|
|
if ("re" in value && typeof value.re === "string") {
|
|
return value.re;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
function collectHasSegments(has) {
|
|
const hasSegments = /* @__PURE__ */ new Set();
|
|
for (const hasItem of has || []) {
|
|
if (!hasItem.value && "key" in hasItem) {
|
|
hasSegments.add(hasItem.key);
|
|
}
|
|
const stringValue = getStringValueForRegex(hasItem.value);
|
|
if (stringValue) {
|
|
for (const match of stringValue.matchAll(namedGroupsRegex)) {
|
|
if (match[1]) {
|
|
hasSegments.add(match[1]);
|
|
}
|
|
}
|
|
if (hasItem.type === "host") {
|
|
hasSegments.add("host");
|
|
}
|
|
}
|
|
}
|
|
return [...hasSegments];
|
|
}
|
|
const escapeSegment = (str, segmentName) => str.replace(new RegExp(`:${segmentName}`, "g"), `__ESC_COLON_${segmentName}`);
|
|
const unescapeSegments = (str) => str.replace(/__ESC_COLON_/gi, ":");
|
|
function replaceSegments(segments, hasItemSegments, destination, isRedirect, internalParamNames) {
|
|
const namedSegments = segments.filter((name) => name !== UN_NAMED_SEGMENT);
|
|
const canNeedReplacing = destination.includes(":") && namedSegments.length > 0 || hasItemSegments.length > 0 || !isRedirect;
|
|
if (!canNeedReplacing) {
|
|
return destination;
|
|
}
|
|
let escapedDestination = destination;
|
|
const indexes = {};
|
|
segments.forEach((name, index) => {
|
|
indexes[name] = toSegmentDest(index);
|
|
escapedDestination = escapeSegment(escapedDestination, name);
|
|
});
|
|
hasItemSegments.forEach((name) => {
|
|
indexes[name] = "$" + name;
|
|
escapedDestination = escapeSegment(escapedDestination, name);
|
|
});
|
|
const parsedDestination = (0, import_url.parse)(escapedDestination, true);
|
|
delete parsedDestination.href;
|
|
delete parsedDestination.path;
|
|
delete parsedDestination.search;
|
|
delete parsedDestination.host;
|
|
let { pathname, hash, query, hostname, ...rest } = parsedDestination;
|
|
pathname = unescapeSegments(pathname || "");
|
|
hash = unescapeSegments(hash || "");
|
|
hostname = unescapeSegments(hostname || "");
|
|
let destParams = /* @__PURE__ */ new Set();
|
|
const pathnameKeys = [];
|
|
const hashKeys = [];
|
|
const hostnameKeys = [];
|
|
try {
|
|
pathToRegexp("528", pathname, pathnameKeys);
|
|
pathToRegexp("834", hash || "", hashKeys);
|
|
pathToRegexp("712", hostname || "", hostnameKeys);
|
|
} catch (_) {
|
|
}
|
|
destParams = new Set(
|
|
[...pathnameKeys, ...hashKeys, ...hostnameKeys].map((key) => key.name).filter((val) => typeof val === "string")
|
|
);
|
|
pathname = safelyCompile(pathname, indexes, true);
|
|
hash = hash ? safelyCompile(hash, indexes, true) : null;
|
|
hostname = hostname ? safelyCompile(hostname, indexes, true) : null;
|
|
for (const [key, strOrArray] of Object.entries(query)) {
|
|
if (Array.isArray(strOrArray)) {
|
|
query[key] = strOrArray.map(
|
|
(str) => safelyCompile(unescapeSegments(str), indexes, true)
|
|
);
|
|
} else {
|
|
query[key] = safelyCompile(
|
|
unescapeSegments(strOrArray),
|
|
indexes,
|
|
true
|
|
);
|
|
}
|
|
}
|
|
const paramKeys = Object.keys(indexes);
|
|
const needsQueryUpdating = (
|
|
// we do not consider an internal param since it is added automatically
|
|
!isRedirect && !paramKeys.some(
|
|
(param) => !(internalParamNames && internalParamNames.includes(param)) && destParams.has(param)
|
|
)
|
|
);
|
|
if (needsQueryUpdating) {
|
|
for (const param of paramKeys) {
|
|
if (!(param in query) && param !== UN_NAMED_SEGMENT) {
|
|
query[param] = indexes[param];
|
|
}
|
|
}
|
|
}
|
|
destination = (0, import_url.format)({
|
|
...rest,
|
|
hostname,
|
|
pathname,
|
|
query,
|
|
hash
|
|
});
|
|
return destination.replace(/%24/g, "$");
|
|
}
|
|
function safelyCompile(value, indexes, attemptDirectCompile) {
|
|
if (!value) {
|
|
return value;
|
|
}
|
|
if (attemptDirectCompile) {
|
|
try {
|
|
return (0, import_path_to_regexp.compile)(value, { validate: false })(indexes);
|
|
} catch (e) {
|
|
}
|
|
}
|
|
for (const key of Object.keys(indexes)) {
|
|
if (value.includes(`:${key}`)) {
|
|
value = value.replace(
|
|
new RegExp(`:${key}\\*`, "g"),
|
|
`:${key}--ESCAPED_PARAM_ASTERISK`
|
|
).replace(
|
|
new RegExp(`:${key}\\?`, "g"),
|
|
`:${key}--ESCAPED_PARAM_QUESTION`
|
|
).replace(new RegExp(`:${key}\\+`, "g"), `:${key}--ESCAPED_PARAM_PLUS`).replace(
|
|
new RegExp(`:${key}(?!\\w)`, "g"),
|
|
`--ESCAPED_PARAM_COLON${key}`
|
|
);
|
|
}
|
|
}
|
|
value = value.replace(/(:|\*|\?|\+|\(|\)|\{|\})/g, "\\$1").replace(/--ESCAPED_PARAM_PLUS/g, "+").replace(/--ESCAPED_PARAM_COLON/g, ":").replace(/--ESCAPED_PARAM_QUESTION/g, "?").replace(/--ESCAPED_PARAM_ASTERISK/g, "*");
|
|
return (0, import_path_to_regexp.compile)(`/${value}`, { validate: false })(indexes).slice(1);
|
|
}
|
|
function toSegmentDest(index) {
|
|
const i = index + 1;
|
|
return "$" + i.toString();
|
|
}
|
|
function toRoute(filePath) {
|
|
return filePath.startsWith("/") ? filePath : "/" + filePath;
|
|
}
|
|
return superstatic;
|
|
}
|
|
|
|
var append;
|
|
var hasRequiredAppend;
|
|
|
|
function requireAppend () {
|
|
if (hasRequiredAppend) return append;
|
|
hasRequiredAppend = 1;
|
|
var __defProp = Object.defineProperty;
|
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
var __export = (target, all) => {
|
|
for (var name in all)
|
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
};
|
|
var __copyProps = (to, from, except, desc) => {
|
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
for (let key of __getOwnPropNames(from))
|
|
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
}
|
|
return to;
|
|
};
|
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
var append_exports = {};
|
|
__export(append_exports, {
|
|
appendRoutesToPhase: () => appendRoutesToPhase
|
|
});
|
|
append = __toCommonJS(append_exports);
|
|
var import_index = requireDist$1();
|
|
function appendRoutesToPhase({
|
|
routes: prevRoutes,
|
|
newRoutes,
|
|
phase
|
|
}) {
|
|
const routes = prevRoutes ? [...prevRoutes] : [];
|
|
if (newRoutes === null || newRoutes.length === 0) {
|
|
return routes;
|
|
}
|
|
let isInPhase = false;
|
|
let insertIndex = -1;
|
|
routes.forEach((r, i) => {
|
|
if ((0, import_index.isHandler)(r)) {
|
|
if (r.handle === phase) {
|
|
isInPhase = true;
|
|
} else if (isInPhase) {
|
|
insertIndex = i;
|
|
isInPhase = false;
|
|
}
|
|
}
|
|
});
|
|
if (isInPhase) {
|
|
routes.push(...newRoutes);
|
|
} else if (phase === null) {
|
|
const lastPhase = routes.findIndex((r) => (0, import_index.isHandler)(r) && r.handle);
|
|
if (lastPhase === -1) {
|
|
routes.push(...newRoutes);
|
|
} else {
|
|
routes.splice(lastPhase, 0, ...newRoutes);
|
|
}
|
|
} else if (insertIndex > -1) {
|
|
routes.splice(insertIndex, 0, ...newRoutes);
|
|
} else {
|
|
routes.push({ handle: phase });
|
|
routes.push(...newRoutes);
|
|
}
|
|
return routes;
|
|
}
|
|
return append;
|
|
}
|
|
|
|
var merge;
|
|
var hasRequiredMerge;
|
|
|
|
function requireMerge () {
|
|
if (hasRequiredMerge) return merge;
|
|
hasRequiredMerge = 1;
|
|
var __defProp = Object.defineProperty;
|
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
var __export = (target, all) => {
|
|
for (var name in all)
|
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
};
|
|
var __copyProps = (to, from, except, desc) => {
|
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
for (let key of __getOwnPropNames(from))
|
|
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
}
|
|
return to;
|
|
};
|
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
var merge_exports = {};
|
|
__export(merge_exports, {
|
|
mergeRoutes: () => mergeRoutes
|
|
});
|
|
merge = __toCommonJS(merge_exports);
|
|
var import_index = requireDist$1();
|
|
function getBuilderRoutesMapping(builds) {
|
|
const builderRoutes = {};
|
|
for (const { entrypoint, routes, use } of builds) {
|
|
if (routes) {
|
|
if (!builderRoutes[entrypoint]) {
|
|
builderRoutes[entrypoint] = {};
|
|
}
|
|
builderRoutes[entrypoint][use] = routes;
|
|
}
|
|
}
|
|
return builderRoutes;
|
|
}
|
|
function getCheckAndContinue(routes) {
|
|
const checks = [];
|
|
const continues = [];
|
|
const others = [];
|
|
for (const route of routes) {
|
|
if ((0, import_index.isHandler)(route)) {
|
|
throw new Error(
|
|
`Unexpected route found in getCheckAndContinue(): ${JSON.stringify(
|
|
route
|
|
)}`
|
|
);
|
|
} else if (route.check && !route.override) {
|
|
checks.push(route);
|
|
} else if (route.continue && !route.override) {
|
|
continues.push(route);
|
|
} else {
|
|
others.push(route);
|
|
}
|
|
}
|
|
return { checks, continues, others };
|
|
}
|
|
function mergeRoutes({ userRoutes, builds }) {
|
|
const userHandleMap = /* @__PURE__ */ new Map();
|
|
let userPrevHandle = null;
|
|
(userRoutes || []).forEach((route) => {
|
|
if ((0, import_index.isHandler)(route)) {
|
|
userPrevHandle = route.handle;
|
|
} else {
|
|
const routes = userHandleMap.get(userPrevHandle);
|
|
if (!routes) {
|
|
userHandleMap.set(userPrevHandle, [route]);
|
|
} else {
|
|
routes.push(route);
|
|
}
|
|
}
|
|
});
|
|
const builderHandleMap = /* @__PURE__ */ new Map();
|
|
const builderRoutes = getBuilderRoutesMapping(builds);
|
|
const sortedPaths = Object.keys(builderRoutes).sort();
|
|
sortedPaths.forEach((path) => {
|
|
const br = builderRoutes[path];
|
|
const sortedBuilders = Object.keys(br).sort();
|
|
sortedBuilders.forEach((use) => {
|
|
let builderPrevHandle = null;
|
|
br[use].forEach((route) => {
|
|
if ((0, import_index.isHandler)(route)) {
|
|
builderPrevHandle = route.handle;
|
|
} else {
|
|
const routes = builderHandleMap.get(builderPrevHandle);
|
|
if (!routes) {
|
|
builderHandleMap.set(builderPrevHandle, [route]);
|
|
} else {
|
|
routes.push(route);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
});
|
|
const outputRoutes = [];
|
|
const uniqueHandleValues = /* @__PURE__ */ new Set([
|
|
null,
|
|
...userHandleMap.keys(),
|
|
...builderHandleMap.keys()
|
|
]);
|
|
for (const handle of uniqueHandleValues) {
|
|
const userRoutes2 = userHandleMap.get(handle) || [];
|
|
const builderRoutes2 = builderHandleMap.get(handle) || [];
|
|
const builderSorted = getCheckAndContinue(builderRoutes2);
|
|
if (handle !== null && (userRoutes2.length > 0 || builderRoutes2.length > 0)) {
|
|
outputRoutes.push({ handle });
|
|
}
|
|
outputRoutes.push(...builderSorted.continues);
|
|
outputRoutes.push(...userRoutes2);
|
|
outputRoutes.push(...builderSorted.checks);
|
|
outputRoutes.push(...builderSorted.others);
|
|
}
|
|
return outputRoutes;
|
|
}
|
|
return merge;
|
|
}
|
|
|
|
var serviceRouteOwnership;
|
|
var hasRequiredServiceRouteOwnership;
|
|
|
|
function requireServiceRouteOwnership () {
|
|
if (hasRequiredServiceRouteOwnership) return serviceRouteOwnership;
|
|
hasRequiredServiceRouteOwnership = 1;
|
|
var __defProp = Object.defineProperty;
|
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
var __export = (target, all) => {
|
|
for (var name in all)
|
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
};
|
|
var __copyProps = (to, from, except, desc) => {
|
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
for (let key of __getOwnPropNames(from))
|
|
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
}
|
|
return to;
|
|
};
|
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
var service_route_ownership_exports = {};
|
|
__export(service_route_ownership_exports, {
|
|
getOwnershipGuard: () => getOwnershipGuard,
|
|
normalizeRoutePrefix: () => normalizeRoutePrefix,
|
|
scopeRouteSourceToOwnership: () => scopeRouteSourceToOwnership
|
|
});
|
|
serviceRouteOwnership = __toCommonJS(service_route_ownership_exports);
|
|
function normalizeRoutePrefix(routePrefix) {
|
|
let normalized = routePrefix.startsWith("/") ? routePrefix : `/${routePrefix}`;
|
|
if (normalized !== "/" && normalized.endsWith("/")) {
|
|
normalized = normalized.slice(0, -1);
|
|
}
|
|
return normalized || "/";
|
|
}
|
|
function escapeForRegex(value) {
|
|
return value.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&");
|
|
}
|
|
function toPrefixMatcher(routePrefix) {
|
|
return `${escapeForRegex(routePrefix)}(?:/|$)`;
|
|
}
|
|
function isDescendantPrefix(candidate, prefix) {
|
|
return candidate !== prefix && candidate.startsWith(`${prefix}/`);
|
|
}
|
|
function getOwnershipGuard(ownerPrefix, allRoutePrefixes) {
|
|
const owner = normalizeRoutePrefix(ownerPrefix);
|
|
const normalizedPrefixes = Array.from(
|
|
new Set(allRoutePrefixes.map(normalizeRoutePrefix))
|
|
);
|
|
const nonRootPrefixes = normalizedPrefixes.filter((prefix) => prefix !== "/").sort((a, b) => b.length - a.length);
|
|
if (owner === "/") {
|
|
return nonRootPrefixes.map((prefix) => `(?!${toPrefixMatcher(prefix)})`).join("");
|
|
}
|
|
const descendants = nonRootPrefixes.filter(
|
|
(prefix) => isDescendantPrefix(prefix, owner)
|
|
);
|
|
const positive = `(?=${toPrefixMatcher(owner)})`;
|
|
const negative = descendants.map((prefix) => `(?!${toPrefixMatcher(prefix)})`).join("");
|
|
return `${positive}${negative}`;
|
|
}
|
|
function scopeRouteSourceToOwnership(source, ownershipGuard) {
|
|
if (!ownershipGuard) {
|
|
return source;
|
|
}
|
|
const inner = source.startsWith("^") ? source.slice(1) : source;
|
|
return `^${ownershipGuard}(?:${inner})`;
|
|
}
|
|
return serviceRouteOwnership;
|
|
}
|
|
|
|
var schemas;
|
|
var hasRequiredSchemas;
|
|
|
|
function requireSchemas () {
|
|
if (hasRequiredSchemas) return schemas;
|
|
hasRequiredSchemas = 1;
|
|
var __defProp = Object.defineProperty;
|
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
var __export = (target, all) => {
|
|
for (var name in all)
|
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
};
|
|
var __copyProps = (to, from, except, desc) => {
|
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
for (let key of __getOwnPropNames(from))
|
|
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
}
|
|
return to;
|
|
};
|
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
var schemas_exports = {};
|
|
__export(schemas_exports, {
|
|
bulkRedirectsSchema: () => bulkRedirectsSchema,
|
|
cleanUrlsSchema: () => cleanUrlsSchema,
|
|
hasSchema: () => hasSchema,
|
|
headersSchema: () => headersSchema,
|
|
redirectsSchema: () => redirectsSchema,
|
|
rewritesSchema: () => rewritesSchema,
|
|
routesSchema: () => routesSchema,
|
|
trailingSlashSchema: () => trailingSlashSchema
|
|
});
|
|
schemas = __toCommonJS(schemas_exports);
|
|
const mitigateSchema = {
|
|
description: "Mitigation action to take on a route",
|
|
type: "object",
|
|
additionalProperties: false,
|
|
required: ["action"],
|
|
properties: {
|
|
action: {
|
|
description: "The mitigation action to take",
|
|
type: "string",
|
|
enum: ["challenge", "deny"]
|
|
}
|
|
}
|
|
};
|
|
const matchableValueSchema = {
|
|
description: "A value to match against. Can be a string (regex) or a condition operation object",
|
|
anyOf: [
|
|
{
|
|
description: "A regular expression used to match thev value. Named groups can be used in the destination.",
|
|
type: "string",
|
|
maxLength: 4096
|
|
},
|
|
{
|
|
description: "A condition operation object",
|
|
type: "object",
|
|
additionalProperties: false,
|
|
minProperties: 1,
|
|
properties: {
|
|
eq: {
|
|
description: "Equal to",
|
|
anyOf: [
|
|
{
|
|
type: "string",
|
|
maxLength: 4096
|
|
},
|
|
{
|
|
type: "number"
|
|
}
|
|
]
|
|
},
|
|
neq: {
|
|
description: "Not equal",
|
|
type: "string",
|
|
maxLength: 4096
|
|
},
|
|
inc: {
|
|
description: "In array",
|
|
type: "array",
|
|
items: {
|
|
type: "string",
|
|
maxLength: 4096
|
|
}
|
|
},
|
|
ninc: {
|
|
description: "Not in array",
|
|
type: "array",
|
|
items: {
|
|
type: "string",
|
|
maxLength: 4096
|
|
}
|
|
},
|
|
pre: {
|
|
description: "Starts with",
|
|
type: "string",
|
|
maxLength: 4096
|
|
},
|
|
suf: {
|
|
description: "Ends with",
|
|
type: "string",
|
|
maxLength: 4096
|
|
},
|
|
re: {
|
|
description: "Regex",
|
|
type: "string",
|
|
maxLength: 4096
|
|
},
|
|
gt: {
|
|
description: "Greater than",
|
|
type: "number"
|
|
},
|
|
gte: {
|
|
description: "Greater than or equal to",
|
|
type: "number"
|
|
},
|
|
lt: {
|
|
description: "Less than",
|
|
type: "number"
|
|
},
|
|
lte: {
|
|
description: "Less than or equal to",
|
|
type: "number"
|
|
}
|
|
}
|
|
}
|
|
]
|
|
};
|
|
const hasSchema = {
|
|
description: "An array of requirements that are needed to match",
|
|
type: "array",
|
|
maxItems: 16,
|
|
items: {
|
|
anyOf: [
|
|
{
|
|
type: "object",
|
|
additionalProperties: false,
|
|
required: ["type", "value"],
|
|
properties: {
|
|
type: {
|
|
description: "The type of request element to check",
|
|
type: "string",
|
|
enum: ["host"]
|
|
},
|
|
value: matchableValueSchema
|
|
}
|
|
},
|
|
{
|
|
type: "object",
|
|
additionalProperties: false,
|
|
required: ["type", "key"],
|
|
properties: {
|
|
type: {
|
|
description: "The type of request element to check",
|
|
type: "string",
|
|
enum: ["header", "cookie", "query"]
|
|
},
|
|
key: {
|
|
description: "The name of the element contained in the particular type",
|
|
type: "string",
|
|
maxLength: 4096
|
|
},
|
|
value: matchableValueSchema
|
|
}
|
|
}
|
|
]
|
|
}
|
|
};
|
|
const transformsSchema = {
|
|
description: "A list of transform rules to adjust the query parameters of a request or HTTP headers of request or response",
|
|
type: "array",
|
|
minItems: 1,
|
|
items: {
|
|
type: "object",
|
|
additionalProperties: false,
|
|
required: ["type", "op", "target"],
|
|
properties: {
|
|
type: {
|
|
description: "The scope of the transform to apply",
|
|
type: "string",
|
|
enum: ["request.headers", "request.query", "response.headers"]
|
|
},
|
|
op: {
|
|
description: "The operation to perform on the target",
|
|
type: "string",
|
|
enum: ["append", "set", "delete"]
|
|
},
|
|
target: {
|
|
description: "The target of the transform",
|
|
type: "object",
|
|
required: ["key"],
|
|
properties: {
|
|
// re is not supported for transforms. Once supported, replace target.key with matchableValueSchema
|
|
key: {
|
|
description: "A value to match against. Can be a string or a condition operation object (without regex support)",
|
|
anyOf: [
|
|
{
|
|
description: "A valid header name (letters, numbers, hyphens, underscores)",
|
|
type: "string",
|
|
maxLength: 4096
|
|
},
|
|
{
|
|
description: "A condition operation object",
|
|
type: "object",
|
|
additionalProperties: false,
|
|
minProperties: 1,
|
|
properties: {
|
|
eq: {
|
|
description: "Equal to",
|
|
anyOf: [
|
|
{
|
|
type: "string",
|
|
maxLength: 4096
|
|
},
|
|
{
|
|
type: "number"
|
|
}
|
|
]
|
|
},
|
|
neq: {
|
|
description: "Not equal",
|
|
type: "string",
|
|
maxLength: 4096
|
|
},
|
|
inc: {
|
|
description: "In array",
|
|
type: "array",
|
|
items: {
|
|
type: "string",
|
|
maxLength: 4096
|
|
}
|
|
},
|
|
ninc: {
|
|
description: "Not in array",
|
|
type: "array",
|
|
items: {
|
|
type: "string",
|
|
maxLength: 4096
|
|
}
|
|
},
|
|
pre: {
|
|
description: "Starts with",
|
|
type: "string",
|
|
maxLength: 4096
|
|
},
|
|
suf: {
|
|
description: "Ends with",
|
|
type: "string",
|
|
maxLength: 4096
|
|
},
|
|
gt: {
|
|
description: "Greater than",
|
|
type: "number"
|
|
},
|
|
gte: {
|
|
description: "Greater than or equal to",
|
|
type: "number"
|
|
},
|
|
lt: {
|
|
description: "Less than",
|
|
type: "number"
|
|
},
|
|
lte: {
|
|
description: "Less than or equal to",
|
|
type: "number"
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
},
|
|
args: {
|
|
description: "The arguments to the operation",
|
|
anyOf: [
|
|
{
|
|
type: "string",
|
|
maxLength: 4096
|
|
},
|
|
{
|
|
type: "array",
|
|
minItems: 1,
|
|
items: {
|
|
type: "string",
|
|
maxLength: 4096
|
|
}
|
|
}
|
|
]
|
|
},
|
|
env: {
|
|
description: "An array of environment variable names that should be replaced at runtime in the args value",
|
|
type: "array",
|
|
minItems: 1,
|
|
maxItems: 64,
|
|
items: {
|
|
type: "string",
|
|
maxLength: 256
|
|
}
|
|
}
|
|
},
|
|
allOf: [
|
|
{
|
|
if: {
|
|
properties: {
|
|
op: {
|
|
enum: ["append", "set"]
|
|
}
|
|
}
|
|
},
|
|
then: {
|
|
required: ["args"]
|
|
}
|
|
},
|
|
{
|
|
if: {
|
|
allOf: [
|
|
{
|
|
properties: {
|
|
type: {
|
|
enum: ["request.headers", "response.headers"]
|
|
}
|
|
}
|
|
},
|
|
{
|
|
properties: {
|
|
op: {
|
|
enum: ["set", "append"]
|
|
}
|
|
}
|
|
}
|
|
]
|
|
},
|
|
then: {
|
|
properties: {
|
|
target: {
|
|
properties: {
|
|
key: {
|
|
if: {
|
|
type: "string"
|
|
},
|
|
then: {
|
|
pattern: "^[a-zA-Z0-9_-]+$"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
args: {
|
|
anyOf: [
|
|
{
|
|
type: "string",
|
|
pattern: "^[a-zA-Z0-9_ :;.,\"'?!(){}\\[\\]@<>=+*#$&`|~\\^%/-]+$"
|
|
},
|
|
{
|
|
type: "array",
|
|
items: {
|
|
type: "string",
|
|
pattern: "^[a-zA-Z0-9_ :;.,\"'?!(){}\\[\\]@<>=+*#$&`|~\\^%/-]+$"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
};
|
|
const routesSchema = {
|
|
type: "array",
|
|
deprecated: true,
|
|
description: "A list of routes objects used to rewrite paths to point towards other internal or external paths",
|
|
example: [{ dest: "https://docs.example.com", src: "/docs" }],
|
|
items: {
|
|
anyOf: [
|
|
{
|
|
type: "object",
|
|
required: ["src"],
|
|
additionalProperties: false,
|
|
properties: {
|
|
src: {
|
|
type: "string",
|
|
maxLength: 4096
|
|
},
|
|
dest: {
|
|
type: "string",
|
|
maxLength: 4096
|
|
},
|
|
headers: {
|
|
type: "object",
|
|
additionalProperties: false,
|
|
minProperties: 1,
|
|
maxProperties: 100,
|
|
patternProperties: {
|
|
"^.{1,256}$": {
|
|
type: "string",
|
|
maxLength: 32768
|
|
}
|
|
}
|
|
},
|
|
methods: {
|
|
type: "array",
|
|
maxItems: 10,
|
|
items: {
|
|
type: "string",
|
|
maxLength: 32
|
|
}
|
|
},
|
|
caseSensitive: {
|
|
type: "boolean"
|
|
},
|
|
important: {
|
|
type: "boolean"
|
|
},
|
|
user: {
|
|
type: "boolean"
|
|
},
|
|
continue: {
|
|
type: "boolean"
|
|
},
|
|
override: {
|
|
type: "boolean"
|
|
},
|
|
check: {
|
|
type: "boolean"
|
|
},
|
|
isInternal: {
|
|
type: "boolean"
|
|
},
|
|
status: {
|
|
type: "integer",
|
|
minimum: 100,
|
|
maximum: 999
|
|
},
|
|
locale: {
|
|
type: "object",
|
|
additionalProperties: false,
|
|
minProperties: 1,
|
|
properties: {
|
|
redirect: {
|
|
type: "object",
|
|
additionalProperties: false,
|
|
minProperties: 1,
|
|
maxProperties: 100,
|
|
patternProperties: {
|
|
"^.{1,256}$": {
|
|
type: "string",
|
|
maxLength: 4096
|
|
}
|
|
}
|
|
},
|
|
value: {
|
|
type: "string",
|
|
maxLength: 4096
|
|
},
|
|
path: {
|
|
type: "string",
|
|
maxLength: 4096
|
|
},
|
|
cookie: {
|
|
type: "string",
|
|
maxLength: 4096
|
|
},
|
|
default: {
|
|
type: "string",
|
|
maxLength: 4096
|
|
}
|
|
}
|
|
},
|
|
middleware: { type: "number" },
|
|
middlewarePath: { type: "string" },
|
|
middlewareRawSrc: {
|
|
type: "array",
|
|
items: {
|
|
type: "string"
|
|
}
|
|
},
|
|
has: hasSchema,
|
|
missing: hasSchema,
|
|
mitigate: mitigateSchema,
|
|
transforms: transformsSchema,
|
|
env: {
|
|
description: "An array of environment variable names that should be replaced at runtime in the destination or headers",
|
|
type: "array",
|
|
minItems: 1,
|
|
maxItems: 64,
|
|
items: {
|
|
type: "string",
|
|
maxLength: 256
|
|
}
|
|
},
|
|
respectOriginCacheControl: {
|
|
description: "When set to true (default), external rewrites will respect the Cache-Control header from the origin. When false, caching is disabled for this rewrite.",
|
|
type: "boolean"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
type: "object",
|
|
required: ["handle"],
|
|
additionalProperties: false,
|
|
properties: {
|
|
handle: {
|
|
type: "string",
|
|
maxLength: 32,
|
|
enum: ["error", "filesystem", "hit", "miss", "resource", "rewrite"]
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
};
|
|
const rewritesSchema = {
|
|
type: "array",
|
|
maxItems: 2048,
|
|
description: "A list of rewrite definitions.",
|
|
items: {
|
|
type: "object",
|
|
additionalProperties: false,
|
|
required: ["source", "destination"],
|
|
properties: {
|
|
source: {
|
|
description: "A pattern that matches each incoming pathname (excluding querystring).",
|
|
type: "string",
|
|
maxLength: 4096
|
|
},
|
|
destination: {
|
|
description: "An absolute pathname to an existing resource or an external URL.",
|
|
type: "string",
|
|
maxLength: 4096
|
|
},
|
|
has: hasSchema,
|
|
missing: hasSchema,
|
|
statusCode: {
|
|
description: "An optional integer to override the status code of the response.",
|
|
type: "integer",
|
|
minimum: 100,
|
|
maximum: 999
|
|
},
|
|
env: {
|
|
description: "An array of environment variable names that should be replaced at runtime in the destination",
|
|
type: "array",
|
|
minItems: 1,
|
|
maxItems: 64,
|
|
items: {
|
|
type: "string",
|
|
maxLength: 256
|
|
}
|
|
},
|
|
respectOriginCacheControl: {
|
|
description: "When set to true (default), external rewrites will respect the Cache-Control header from the origin. When false, caching is disabled for this rewrite.",
|
|
type: "boolean"
|
|
}
|
|
}
|
|
}
|
|
};
|
|
const redirectsSchema = {
|
|
title: "Redirects",
|
|
type: "array",
|
|
maxItems: 2048,
|
|
description: "A list of redirect definitions.",
|
|
items: {
|
|
type: "object",
|
|
additionalProperties: false,
|
|
required: ["source", "destination"],
|
|
properties: {
|
|
source: {
|
|
description: "A pattern that matches each incoming pathname (excluding querystring).",
|
|
type: "string",
|
|
maxLength: 4096
|
|
},
|
|
destination: {
|
|
description: "A location destination defined as an absolute pathname or external URL.",
|
|
type: "string",
|
|
maxLength: 4096
|
|
},
|
|
permanent: {
|
|
description: "A boolean to toggle between permanent and temporary redirect. When `true`, the status code is `308`. When `false` the status code is `307`.",
|
|
type: "boolean"
|
|
},
|
|
statusCode: {
|
|
description: "An optional integer to define the status code of the redirect.",
|
|
private: true,
|
|
type: "integer",
|
|
minimum: 100,
|
|
maximum: 999
|
|
},
|
|
has: hasSchema,
|
|
missing: hasSchema,
|
|
env: {
|
|
description: "An array of environment variable names that should be replaced at runtime in the destination",
|
|
type: "array",
|
|
minItems: 1,
|
|
maxItems: 64,
|
|
items: {
|
|
type: "string",
|
|
maxLength: 256
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
const headersSchema = {
|
|
type: "array",
|
|
maxItems: 2048,
|
|
description: "A list of header definitions.",
|
|
items: {
|
|
type: "object",
|
|
additionalProperties: false,
|
|
required: ["source", "headers"],
|
|
properties: {
|
|
source: {
|
|
description: "A pattern that matches each incoming pathname (excluding querystring)",
|
|
type: "string",
|
|
maxLength: 4096
|
|
},
|
|
headers: {
|
|
description: "An array of key/value pairs representing each response header.",
|
|
type: "array",
|
|
maxItems: 1024,
|
|
items: {
|
|
type: "object",
|
|
additionalProperties: false,
|
|
required: ["key", "value"],
|
|
properties: {
|
|
key: {
|
|
type: "string",
|
|
maxLength: 4096
|
|
},
|
|
value: {
|
|
type: "string",
|
|
maxLength: 32768
|
|
}
|
|
}
|
|
}
|
|
},
|
|
has: hasSchema,
|
|
missing: hasSchema
|
|
}
|
|
}
|
|
};
|
|
const cleanUrlsSchema = {
|
|
description: "When set to `true`, all HTML files and Serverless Functions will have their extension removed. When visiting a path that ends with the extension, a 308 response will redirect the client to the extensionless path.",
|
|
type: "boolean"
|
|
};
|
|
const trailingSlashSchema = {
|
|
description: "When `false`, visiting a path that ends with a forward slash will respond with a `308` status code and redirect to the path without the trailing slash.",
|
|
type: "boolean"
|
|
};
|
|
const bulkRedirectsSchema = {
|
|
type: "array",
|
|
description: "A list of bulk redirect definitions.",
|
|
items: {
|
|
type: "object",
|
|
additionalProperties: false,
|
|
required: ["source", "destination"],
|
|
properties: {
|
|
source: {
|
|
description: "The exact URL path or pattern to match.",
|
|
type: "string",
|
|
maxLength: 2048
|
|
},
|
|
destination: {
|
|
description: "The target URL path where traffic should be redirected.",
|
|
type: "string",
|
|
maxLength: 2048
|
|
},
|
|
permanent: {
|
|
description: "A boolean to toggle between permanent and temporary redirect. When `true`, the status code is `308`. When `false` the status code is `307`.",
|
|
type: "boolean"
|
|
},
|
|
statusCode: {
|
|
description: "An optional integer to define the status code of the redirect.",
|
|
type: "integer",
|
|
enum: [301, 302, 307, 308]
|
|
},
|
|
sensitive: {
|
|
description: "A boolean to toggle between case-sensitive and case-insensitive redirect. When `true`, the redirect is case-sensitive. When `false` the redirect is case-insensitive.",
|
|
type: "boolean"
|
|
},
|
|
query: {
|
|
description: "Whether the query string should be preserved by the redirect. The default is `false`.",
|
|
type: "boolean"
|
|
}
|
|
}
|
|
}
|
|
};
|
|
return schemas;
|
|
}
|
|
|
|
var types$1;
|
|
var hasRequiredTypes;
|
|
|
|
function requireTypes () {
|
|
if (hasRequiredTypes) return types$1;
|
|
hasRequiredTypes = 1;
|
|
var __defProp = Object.defineProperty;
|
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
var __copyProps = (to, from, except, desc) => {
|
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
for (let key of __getOwnPropNames(from))
|
|
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
}
|
|
return to;
|
|
};
|
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
var types_exports = {};
|
|
types$1 = __toCommonJS(types_exports);
|
|
return types$1;
|
|
}
|
|
|
|
var hasRequiredDist$1;
|
|
|
|
function requireDist$1 () {
|
|
if (hasRequiredDist$1) return dist$1.exports;
|
|
hasRequiredDist$1 = 1;
|
|
(function (module) {
|
|
var __defProp = Object.defineProperty;
|
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
var __export = (target, all) => {
|
|
for (var name in all)
|
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
};
|
|
var __copyProps = (to, from, except, desc) => {
|
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
for (let key of __getOwnPropNames(from))
|
|
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
}
|
|
return to;
|
|
};
|
|
var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
|
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
var src_exports = {};
|
|
__export(src_exports, {
|
|
appendRoutesToPhase: () => import_append.appendRoutesToPhase,
|
|
getCleanUrls: () => import_superstatic2.getCleanUrls,
|
|
getOwnershipGuard: () => import_service_route_ownership.getOwnershipGuard,
|
|
getTransformedRoutes: () => getTransformedRoutes,
|
|
isHandler: () => isHandler,
|
|
isValidHandleValue: () => isValidHandleValue,
|
|
mergeRoutes: () => import_merge.mergeRoutes,
|
|
normalizeRoutePrefix: () => import_service_route_ownership.normalizeRoutePrefix,
|
|
normalizeRoutes: () => normalizeRoutes,
|
|
scopeRouteSourceToOwnership: () => import_service_route_ownership.scopeRouteSourceToOwnership,
|
|
sourceToRegex: () => import_superstatic2.sourceToRegex
|
|
});
|
|
module.exports = __toCommonJS(src_exports);
|
|
var import_url = require$$0;
|
|
var import_superstatic = requireSuperstatic();
|
|
var import_append = requireAppend();
|
|
var import_merge = requireMerge();
|
|
var import_service_route_ownership = requireServiceRouteOwnership();
|
|
__reExport(src_exports, requireSchemas(), module.exports);
|
|
var import_superstatic2 = requireSuperstatic();
|
|
__reExport(src_exports, requireTypes(), module.exports);
|
|
const VALID_HANDLE_VALUES = [
|
|
"filesystem",
|
|
"hit",
|
|
"miss",
|
|
"rewrite",
|
|
"error",
|
|
"resource"
|
|
];
|
|
const validHandleValues = new Set(VALID_HANDLE_VALUES);
|
|
function isHandler(route) {
|
|
return typeof route.handle !== "undefined";
|
|
}
|
|
function isValidHandleValue(handle) {
|
|
return validHandleValues.has(handle);
|
|
}
|
|
function normalizeRoutes(inputRoutes) {
|
|
if (!inputRoutes || inputRoutes.length === 0) {
|
|
return { routes: inputRoutes, error: null };
|
|
}
|
|
const routes = [];
|
|
const handling = [];
|
|
const errors = [];
|
|
inputRoutes.forEach((r, i) => {
|
|
const route = { ...r };
|
|
routes.push(route);
|
|
const keys = Object.keys(route);
|
|
if (isHandler(route)) {
|
|
const { handle } = route;
|
|
if (keys.length !== 1) {
|
|
const unknownProp = keys.find((prop) => prop !== "handle");
|
|
errors.push(
|
|
`Route at index ${i} has unknown property \`${unknownProp}\`.`
|
|
);
|
|
} else if (!isValidHandleValue(handle)) {
|
|
errors.push(
|
|
`Route at index ${i} has unknown handle value \`handle: ${handle}\`.`
|
|
);
|
|
} else if (handling.includes(handle)) {
|
|
errors.push(
|
|
`Route at index ${i} is a duplicate. Please use one \`handle: ${handle}\` at most.`
|
|
);
|
|
} else {
|
|
handling.push(handle);
|
|
}
|
|
} else if (route.src) {
|
|
if (!route.src.startsWith("^")) {
|
|
route.src = `^${route.src}`;
|
|
}
|
|
if (!route.src.endsWith("$")) {
|
|
route.src = `${route.src}$`;
|
|
}
|
|
route.src = route.src.replace(/\\\//g, "/");
|
|
const regError = checkRegexSyntax("Route", i, route.src);
|
|
if (regError) {
|
|
errors.push(regError);
|
|
}
|
|
const handleValue = handling[handling.length - 1];
|
|
if (handleValue === "hit") {
|
|
if (route.dest) {
|
|
errors.push(
|
|
`Route at index ${i} cannot define \`dest\` after \`handle: hit\`.`
|
|
);
|
|
}
|
|
if (route.status) {
|
|
errors.push(
|
|
`Route at index ${i} cannot define \`status\` after \`handle: hit\`.`
|
|
);
|
|
}
|
|
if (!route.continue) {
|
|
errors.push(
|
|
`Route at index ${i} must define \`continue: true\` after \`handle: hit\`.`
|
|
);
|
|
}
|
|
} else if (handleValue === "miss") {
|
|
if (route.dest && !route.check) {
|
|
errors.push(
|
|
`Route at index ${i} must define \`check: true\` after \`handle: miss\`.`
|
|
);
|
|
} else if (!route.dest && !route.continue) {
|
|
errors.push(
|
|
`Route at index ${i} must define \`continue: true\` after \`handle: miss\`.`
|
|
);
|
|
}
|
|
}
|
|
} else {
|
|
errors.push(
|
|
`Route at index ${i} must define either \`handle\` or \`src\` property.`
|
|
);
|
|
}
|
|
});
|
|
const error = errors.length > 0 ? createError(
|
|
"invalid_route",
|
|
errors,
|
|
"https://vercel.link/routes-json",
|
|
"Learn More"
|
|
) : null;
|
|
return { routes, error };
|
|
}
|
|
function checkRegexSyntax(type, index, src) {
|
|
try {
|
|
new RegExp(src);
|
|
} catch (err) {
|
|
const prop = type === "Route" ? "src" : "source";
|
|
return `${type} at index ${index} has invalid \`${prop}\` regular expression "${src}".`;
|
|
}
|
|
return null;
|
|
}
|
|
function checkPatternSyntax(type, index, {
|
|
source,
|
|
destination,
|
|
has
|
|
}) {
|
|
let sourceSegments = /* @__PURE__ */ new Set();
|
|
const destinationSegments = /* @__PURE__ */ new Set();
|
|
try {
|
|
sourceSegments = new Set((0, import_superstatic.sourceToRegex)(source).segments);
|
|
} catch (err) {
|
|
return {
|
|
message: `${type} at index ${index} has invalid \`source\` pattern "${source}".`,
|
|
link: "https://vercel.link/invalid-route-source-pattern"
|
|
};
|
|
}
|
|
if (destination) {
|
|
try {
|
|
const { hostname, pathname, query } = (0, import_url.parse)(destination, true);
|
|
(0, import_superstatic.sourceToRegex)(hostname || "").segments.forEach(
|
|
(name) => destinationSegments.add(name)
|
|
);
|
|
(0, import_superstatic.sourceToRegex)(pathname || "").segments.forEach(
|
|
(name) => destinationSegments.add(name)
|
|
);
|
|
for (const strOrArray of Object.values(query)) {
|
|
const value = Array.isArray(strOrArray) ? strOrArray[0] : strOrArray;
|
|
(0, import_superstatic.sourceToRegex)(value || "").segments.forEach(
|
|
(name) => destinationSegments.add(name)
|
|
);
|
|
}
|
|
} catch (err) {
|
|
}
|
|
const hasSegments = (0, import_superstatic.collectHasSegments)(has);
|
|
for (const segment of destinationSegments) {
|
|
if (!sourceSegments.has(segment) && !hasSegments.includes(segment)) {
|
|
return {
|
|
message: `${type} at index ${index} has segment ":${segment}" in \`destination\` property but not in \`source\` or \`has\` property.`,
|
|
link: "https://vercel.link/invalid-route-destination-segment"
|
|
};
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
function checkRedirect(r, index) {
|
|
if (typeof r.permanent !== "undefined" && typeof r.statusCode !== "undefined") {
|
|
return `Redirect at index ${index} cannot define both \`permanent\` and \`statusCode\` properties.`;
|
|
}
|
|
return null;
|
|
}
|
|
function createError(code, allErrors, link, action) {
|
|
const errors = Array.isArray(allErrors) ? allErrors : [allErrors];
|
|
const message = errors[0];
|
|
const error = {
|
|
name: "RouteApiError",
|
|
code,
|
|
message,
|
|
link,
|
|
action,
|
|
errors
|
|
};
|
|
return error;
|
|
}
|
|
function notEmpty(value) {
|
|
return value !== null && value !== void 0;
|
|
}
|
|
function getTransformedRoutes(vercelConfig) {
|
|
const { cleanUrls, rewrites, redirects, headers, trailingSlash } = vercelConfig;
|
|
let { routes = null } = vercelConfig;
|
|
if (routes) {
|
|
const hasNewProperties = typeof cleanUrls !== "undefined" || typeof trailingSlash !== "undefined" || typeof redirects !== "undefined" || typeof headers !== "undefined" || typeof rewrites !== "undefined";
|
|
if (hasNewProperties) {
|
|
const error = createError(
|
|
"invalid_mixed_routes",
|
|
"If `rewrites`, `redirects`, `headers`, `cleanUrls` or `trailingSlash` are used, then `routes` cannot be present.",
|
|
"https://vercel.link/mix-routing-props",
|
|
"Learn More"
|
|
);
|
|
return { routes, error };
|
|
}
|
|
return normalizeRoutes(routes);
|
|
}
|
|
if (typeof cleanUrls !== "undefined") {
|
|
const normalized = normalizeRoutes(
|
|
(0, import_superstatic.convertCleanUrls)(cleanUrls, trailingSlash)
|
|
);
|
|
if (normalized.error) {
|
|
normalized.error.code = "invalid_clean_urls";
|
|
return { routes, error: normalized.error };
|
|
}
|
|
routes = routes || [];
|
|
routes.push(...normalized.routes || []);
|
|
}
|
|
if (typeof trailingSlash !== "undefined") {
|
|
const normalized = normalizeRoutes((0, import_superstatic.convertTrailingSlash)(trailingSlash));
|
|
if (normalized.error) {
|
|
normalized.error.code = "invalid_trailing_slash";
|
|
return { routes, error: normalized.error };
|
|
}
|
|
routes = routes || [];
|
|
routes.push(...normalized.routes || []);
|
|
}
|
|
if (typeof redirects !== "undefined") {
|
|
const code = "invalid_redirect";
|
|
const regexErrorMessage = redirects.map((r, i) => checkRegexSyntax("Redirect", i, r.source)).find(notEmpty);
|
|
if (regexErrorMessage) {
|
|
return {
|
|
routes,
|
|
error: createError(
|
|
"invalid_redirect",
|
|
regexErrorMessage,
|
|
"https://vercel.link/invalid-route-source-pattern",
|
|
"Learn More"
|
|
)
|
|
};
|
|
}
|
|
const patternError = redirects.map((r, i) => checkPatternSyntax("Redirect", i, r)).find(notEmpty);
|
|
if (patternError) {
|
|
return {
|
|
routes,
|
|
error: createError(
|
|
code,
|
|
patternError.message,
|
|
patternError.link,
|
|
"Learn More"
|
|
)
|
|
};
|
|
}
|
|
const redirectErrorMessage = redirects.map(checkRedirect).find(notEmpty);
|
|
if (redirectErrorMessage) {
|
|
return {
|
|
routes,
|
|
error: createError(
|
|
code,
|
|
redirectErrorMessage,
|
|
"https://vercel.link/redirects-json",
|
|
"Learn More"
|
|
)
|
|
};
|
|
}
|
|
const normalized = normalizeRoutes((0, import_superstatic.convertRedirects)(redirects));
|
|
if (normalized.error) {
|
|
normalized.error.code = code;
|
|
return { routes, error: normalized.error };
|
|
}
|
|
routes = routes || [];
|
|
routes.push(...normalized.routes || []);
|
|
}
|
|
if (typeof headers !== "undefined") {
|
|
const code = "invalid_header";
|
|
const regexErrorMessage = headers.map((r, i) => checkRegexSyntax("Header", i, r.source)).find(notEmpty);
|
|
if (regexErrorMessage) {
|
|
return {
|
|
routes,
|
|
error: createError(
|
|
code,
|
|
regexErrorMessage,
|
|
"https://vercel.link/invalid-route-source-pattern",
|
|
"Learn More"
|
|
)
|
|
};
|
|
}
|
|
const patternError = headers.map((r, i) => checkPatternSyntax("Header", i, r)).find(notEmpty);
|
|
if (patternError) {
|
|
return {
|
|
routes,
|
|
error: createError(
|
|
code,
|
|
patternError.message,
|
|
patternError.link,
|
|
"Learn More"
|
|
)
|
|
};
|
|
}
|
|
const normalized = normalizeRoutes((0, import_superstatic.convertHeaders)(headers));
|
|
if (normalized.error) {
|
|
normalized.error.code = code;
|
|
return { routes, error: normalized.error };
|
|
}
|
|
routes = routes || [];
|
|
routes.push(...normalized.routes || []);
|
|
}
|
|
if (typeof rewrites !== "undefined") {
|
|
const code = "invalid_rewrite";
|
|
const regexErrorMessage = rewrites.map((r, i) => checkRegexSyntax("Rewrite", i, r.source)).find(notEmpty);
|
|
if (regexErrorMessage) {
|
|
return {
|
|
routes,
|
|
error: createError(
|
|
code,
|
|
regexErrorMessage,
|
|
"https://vercel.link/invalid-route-source-pattern",
|
|
"Learn More"
|
|
)
|
|
};
|
|
}
|
|
const patternError = rewrites.map((r, i) => checkPatternSyntax("Rewrite", i, r)).find(notEmpty);
|
|
if (patternError) {
|
|
return {
|
|
routes,
|
|
error: createError(
|
|
code,
|
|
patternError.message,
|
|
patternError.link,
|
|
"Learn More"
|
|
)
|
|
};
|
|
}
|
|
const normalized = normalizeRoutes((0, import_superstatic.convertRewrites)(rewrites));
|
|
if (normalized.error) {
|
|
normalized.error.code = code;
|
|
return { routes, error: normalized.error };
|
|
}
|
|
routes = routes || [];
|
|
routes.push({ handle: "filesystem" });
|
|
routes.push(...normalized.routes || []);
|
|
}
|
|
return { routes, error: null };
|
|
}
|
|
} (dist$1));
|
|
return dist$1.exports;
|
|
}
|
|
|
|
requireDist$1();
|
|
|
|
function matchPattern(url, remotePattern) {
|
|
return matchProtocol(url, remotePattern.protocol) && matchHostname(url, remotePattern.hostname, true) && matchPort(url, remotePattern.port) && matchPathname(url, remotePattern.pathname, true);
|
|
}
|
|
function matchPort(url, port) {
|
|
return !port || port === url.port;
|
|
}
|
|
function matchProtocol(url, protocol) {
|
|
return !protocol || protocol === url.protocol.slice(0, -1);
|
|
}
|
|
function matchHostname(url, hostname, allowWildcard = false) {
|
|
if (!hostname) {
|
|
return true;
|
|
} else if (!allowWildcard || !hostname.startsWith("*")) {
|
|
return hostname === url.hostname;
|
|
} else if (hostname.startsWith("**.")) {
|
|
const slicedHostname = hostname.slice(2);
|
|
return slicedHostname !== url.hostname && url.hostname.endsWith(slicedHostname);
|
|
} else if (hostname.startsWith("*.")) {
|
|
const slicedHostname = hostname.slice(1);
|
|
if (!url.hostname.endsWith(slicedHostname)) {
|
|
return false;
|
|
}
|
|
const subdomainWithDot = url.hostname.slice(0, -(slicedHostname.length - 1));
|
|
return subdomainWithDot.endsWith(".") && !subdomainWithDot.slice(0, -1).includes(".");
|
|
}
|
|
return false;
|
|
}
|
|
function matchPathname(url, pathname, allowWildcard = false) {
|
|
if (!pathname) {
|
|
return true;
|
|
} else if (!allowWildcard || !pathname.endsWith("*")) {
|
|
return pathname === url.pathname;
|
|
} else if (pathname.endsWith("/**")) {
|
|
const slicedPathname = pathname.slice(0, -2);
|
|
return slicedPathname !== url.pathname && url.pathname.startsWith(slicedPathname);
|
|
} else if (pathname.endsWith("/*")) {
|
|
const slicedPathname = pathname.slice(0, -1);
|
|
if (!url.pathname.startsWith(slicedPathname)) {
|
|
return false;
|
|
}
|
|
const additionalPathChunks = url.pathname.slice(slicedPathname.length).split("/").filter(Boolean);
|
|
return additionalPathChunks.length === 1;
|
|
}
|
|
return false;
|
|
}
|
|
function isRemoteAllowed(src, {
|
|
domains,
|
|
remotePatterns
|
|
}) {
|
|
if (!URL.canParse(src)) {
|
|
return false;
|
|
}
|
|
const url = new URL(src);
|
|
if (!["http:", "https:", "data:"].includes(url.protocol)) {
|
|
return false;
|
|
}
|
|
return domains.some((domain) => matchHostname(url, domain)) || remotePatterns.some((remotePattern) => matchPattern(url, remotePattern));
|
|
}
|
|
|
|
const decoder$2 = new TextDecoder();
|
|
const toUTF8String = (input, start = 0, end = input.length) => decoder$2.decode(input.slice(start, end));
|
|
const toHexString = (input, start = 0, end = input.length) => input.slice(start, end).reduce((memo, i) => memo + `0${i.toString(16)}`.slice(-2), "");
|
|
const getView = (input, offset) => new DataView(input.buffer, input.byteOffset + offset);
|
|
const readInt16LE = (input, offset = 0) => getView(input, offset).getInt16(0, true);
|
|
const readUInt16BE = (input, offset = 0) => getView(input, offset).getUint16(0, false);
|
|
const readUInt16LE = (input, offset = 0) => getView(input, offset).getUint16(0, true);
|
|
const readUInt24LE = (input, offset = 0) => {
|
|
const view = getView(input, offset);
|
|
return view.getUint16(0, true) + (view.getUint8(2) << 16);
|
|
};
|
|
const readInt32LE = (input, offset = 0) => getView(input, offset).getInt32(0, true);
|
|
const readUInt32BE = (input, offset = 0) => getView(input, offset).getUint32(0, false);
|
|
const readUInt32LE = (input, offset = 0) => getView(input, offset).getUint32(0, true);
|
|
const readUInt64 = (input, offset, isBigEndian) => getView(input, offset).getBigUint64(0, !isBigEndian);
|
|
const methods = {
|
|
readUInt16BE,
|
|
readUInt16LE,
|
|
readUInt32BE,
|
|
readUInt32LE
|
|
};
|
|
function readUInt(input, bits, offset = 0, isBigEndian = false) {
|
|
const endian = isBigEndian ? "BE" : "LE";
|
|
const methodName = `readUInt${bits}${endian}`;
|
|
return methods[methodName](input, offset);
|
|
}
|
|
function readBox(input, offset) {
|
|
if (input.length - offset < 4) return;
|
|
const boxSize = readUInt32BE(input, offset);
|
|
if (input.length - offset < boxSize) return;
|
|
return {
|
|
name: toUTF8String(input, 4 + offset, 8 + offset),
|
|
offset,
|
|
size: boxSize
|
|
};
|
|
}
|
|
function findBox(input, boxName, currentOffset) {
|
|
while (currentOffset < input.length) {
|
|
const box = readBox(input, currentOffset);
|
|
if (!box) break;
|
|
if (box.name === boxName) return box;
|
|
currentOffset += box.size > 0 ? box.size : 8;
|
|
}
|
|
}
|
|
|
|
const BMP = {
|
|
validate: (input) => toUTF8String(input, 0, 2) === "BM",
|
|
calculate: (input) => ({
|
|
height: Math.abs(readInt32LE(input, 22)),
|
|
width: readUInt32LE(input, 18)
|
|
})
|
|
};
|
|
|
|
const TYPE_ICON = 1;
|
|
const SIZE_HEADER$1 = 2 + 2 + 2;
|
|
const SIZE_IMAGE_ENTRY = 1 + 1 + 1 + 1 + 2 + 2 + 4 + 4;
|
|
function getSizeFromOffset(input, offset) {
|
|
const value = input[offset];
|
|
return value === 0 ? 256 : value;
|
|
}
|
|
function getImageSize$1(input, imageIndex) {
|
|
const offset = SIZE_HEADER$1 + imageIndex * SIZE_IMAGE_ENTRY;
|
|
return {
|
|
height: getSizeFromOffset(input, offset + 1),
|
|
width: getSizeFromOffset(input, offset)
|
|
};
|
|
}
|
|
const ICO = {
|
|
validate(input) {
|
|
const reserved = readUInt16LE(input, 0);
|
|
const imageCount = readUInt16LE(input, 4);
|
|
if (reserved !== 0 || imageCount === 0) return false;
|
|
const imageType = readUInt16LE(input, 2);
|
|
return imageType === TYPE_ICON;
|
|
},
|
|
calculate(input) {
|
|
const nbImages = readUInt16LE(input, 4);
|
|
const imageSize = getImageSize$1(input, 0);
|
|
if (nbImages === 1) return imageSize;
|
|
const images = [];
|
|
for (let imageIndex = 0; imageIndex < nbImages; imageIndex += 1) {
|
|
images.push(getImageSize$1(input, imageIndex));
|
|
}
|
|
return {
|
|
width: imageSize.width,
|
|
height: imageSize.height,
|
|
images
|
|
};
|
|
}
|
|
};
|
|
|
|
const TYPE_CURSOR = 2;
|
|
const CUR = {
|
|
validate(input) {
|
|
const reserved = readUInt16LE(input, 0);
|
|
const imageCount = readUInt16LE(input, 4);
|
|
if (reserved !== 0 || imageCount === 0) return false;
|
|
const imageType = readUInt16LE(input, 2);
|
|
return imageType === TYPE_CURSOR;
|
|
},
|
|
calculate: (input) => ICO.calculate(input)
|
|
};
|
|
|
|
const DDS = {
|
|
validate: (input) => readUInt32LE(input, 0) === 542327876,
|
|
calculate: (input) => ({
|
|
height: readUInt32LE(input, 12),
|
|
width: readUInt32LE(input, 16)
|
|
})
|
|
};
|
|
|
|
const gifRegexp = /^GIF8[79]a/;
|
|
const GIF = {
|
|
validate: (input) => gifRegexp.test(toUTF8String(input, 0, 6)),
|
|
calculate: (input) => ({
|
|
height: readUInt16LE(input, 8),
|
|
width: readUInt16LE(input, 6)
|
|
})
|
|
};
|
|
|
|
const brandMap = {
|
|
avif: "avif",
|
|
avis: "avif",
|
|
// avif-sequence
|
|
mif1: "heif",
|
|
msf1: "heif",
|
|
// heif-sequence
|
|
heic: "heic",
|
|
heix: "heic",
|
|
hevc: "heic",
|
|
// heic-sequence
|
|
hevx: "heic"
|
|
// heic-sequence
|
|
};
|
|
function detectType(input, start, end) {
|
|
let hasAvif = false;
|
|
let hasHeic = false;
|
|
let hasHeif = false;
|
|
for (let i = start; i <= end; i += 4) {
|
|
const brand = toUTF8String(input, i, i + 4);
|
|
if (brand === "avif" || brand === "avis") hasAvif = true;
|
|
else if (brand === "heic" || brand === "heix" || brand === "hevc" || brand === "hevx") hasHeic = true;
|
|
else if (brand === "mif1" || brand === "msf1") hasHeif = true;
|
|
}
|
|
if (hasAvif) return "avif";
|
|
if (hasHeic) return "heic";
|
|
if (hasHeif) return "heif";
|
|
}
|
|
const HEIF = {
|
|
validate(input) {
|
|
const boxType = toUTF8String(input, 4, 8);
|
|
if (boxType !== "ftyp") return false;
|
|
const ftypBox = findBox(input, "ftyp", 0);
|
|
if (!ftypBox) return false;
|
|
const brand = toUTF8String(input, ftypBox.offset + 8, ftypBox.offset + 12);
|
|
return brand in brandMap;
|
|
},
|
|
calculate(input) {
|
|
const metaBox = findBox(input, "meta", 0);
|
|
const iprpBox = metaBox && findBox(input, "iprp", metaBox.offset + 12);
|
|
const ipcoBox = iprpBox && findBox(input, "ipco", iprpBox.offset + 8);
|
|
if (!ipcoBox) {
|
|
throw new TypeError("Invalid HEIF, no ipco box found");
|
|
}
|
|
const type = detectType(input, 8, metaBox.offset);
|
|
const images = [];
|
|
let currentOffset = ipcoBox.offset + 8;
|
|
while (currentOffset < ipcoBox.offset + ipcoBox.size) {
|
|
const ispeBox = findBox(input, "ispe", currentOffset);
|
|
if (!ispeBox) break;
|
|
const rawWidth = readUInt32BE(input, ispeBox.offset + 12);
|
|
const rawHeight = readUInt32BE(input, ispeBox.offset + 16);
|
|
const clapBox = findBox(input, "clap", currentOffset);
|
|
let width = rawWidth;
|
|
let height = rawHeight;
|
|
if (clapBox && clapBox.offset < ipcoBox.offset + ipcoBox.size) {
|
|
const cropRight = readUInt32BE(input, clapBox.offset + 12);
|
|
width = rawWidth - cropRight;
|
|
}
|
|
images.push({ height, width });
|
|
currentOffset = ispeBox.offset + ispeBox.size;
|
|
}
|
|
if (images.length === 0) {
|
|
throw new TypeError("Invalid HEIF, no sizes found");
|
|
}
|
|
return {
|
|
width: images[0].width,
|
|
height: images[0].height,
|
|
type,
|
|
...images.length > 1 ? { images } : {}
|
|
};
|
|
}
|
|
};
|
|
|
|
const SIZE_HEADER = 4 + 4;
|
|
const FILE_LENGTH_OFFSET = 4;
|
|
const ENTRY_LENGTH_OFFSET = 4;
|
|
const ICON_TYPE_SIZE = {
|
|
ICON: 32,
|
|
"ICN#": 32,
|
|
// m => 16 x 16
|
|
"icm#": 16,
|
|
icm4: 16,
|
|
icm8: 16,
|
|
// s => 16 x 16
|
|
"ics#": 16,
|
|
ics4: 16,
|
|
ics8: 16,
|
|
is32: 16,
|
|
s8mk: 16,
|
|
icp4: 16,
|
|
// l => 32 x 32
|
|
icl4: 32,
|
|
icl8: 32,
|
|
il32: 32,
|
|
l8mk: 32,
|
|
icp5: 32,
|
|
ic11: 32,
|
|
// h => 48 x 48
|
|
ich4: 48,
|
|
ich8: 48,
|
|
ih32: 48,
|
|
h8mk: 48,
|
|
// . => 64 x 64
|
|
icp6: 64,
|
|
ic12: 32,
|
|
// t => 128 x 128
|
|
it32: 128,
|
|
t8mk: 128,
|
|
ic07: 128,
|
|
// . => 256 x 256
|
|
ic08: 256,
|
|
ic13: 256,
|
|
// . => 512 x 512
|
|
ic09: 512,
|
|
ic14: 512,
|
|
// . => 1024 x 1024
|
|
ic10: 1024
|
|
};
|
|
function readImageHeader(input, imageOffset) {
|
|
const imageLengthOffset = imageOffset + ENTRY_LENGTH_OFFSET;
|
|
return [
|
|
toUTF8String(input, imageOffset, imageLengthOffset),
|
|
readUInt32BE(input, imageLengthOffset)
|
|
];
|
|
}
|
|
function getImageSize(type) {
|
|
const size = ICON_TYPE_SIZE[type];
|
|
return { width: size, height: size, type };
|
|
}
|
|
const ICNS = {
|
|
validate: (input) => toUTF8String(input, 0, 4) === "icns",
|
|
calculate(input) {
|
|
const inputLength = input.length;
|
|
const fileLength = readUInt32BE(input, FILE_LENGTH_OFFSET);
|
|
let imageOffset = SIZE_HEADER;
|
|
const images = [];
|
|
while (imageOffset < fileLength && imageOffset < inputLength) {
|
|
const imageHeader = readImageHeader(input, imageOffset);
|
|
const imageSize = getImageSize(imageHeader[0]);
|
|
images.push(imageSize);
|
|
imageOffset += imageHeader[1];
|
|
}
|
|
if (images.length === 0) {
|
|
throw new TypeError("Invalid ICNS, no sizes found");
|
|
}
|
|
return {
|
|
width: images[0].width,
|
|
height: images[0].height,
|
|
...images.length > 1 ? { images } : {}
|
|
};
|
|
}
|
|
};
|
|
|
|
const J2C = {
|
|
// TODO: this doesn't seem right. SIZ marker doesn't have to be right after the SOC
|
|
validate: (input) => readUInt32BE(input, 0) === 4283432785,
|
|
calculate: (input) => ({
|
|
height: readUInt32BE(input, 12),
|
|
width: readUInt32BE(input, 8)
|
|
})
|
|
};
|
|
|
|
const JP2 = {
|
|
validate(input) {
|
|
const boxType = toUTF8String(input, 4, 8);
|
|
if (boxType !== "jP ") return false;
|
|
const ftypBox = findBox(input, "ftyp", 0);
|
|
if (!ftypBox) return false;
|
|
const brand = toUTF8String(input, ftypBox.offset + 8, ftypBox.offset + 12);
|
|
return brand === "jp2 ";
|
|
},
|
|
calculate(input) {
|
|
const jp2hBox = findBox(input, "jp2h", 0);
|
|
const ihdrBox = jp2hBox && findBox(input, "ihdr", jp2hBox.offset + 8);
|
|
if (ihdrBox) {
|
|
return {
|
|
height: readUInt32BE(input, ihdrBox.offset + 8),
|
|
width: readUInt32BE(input, ihdrBox.offset + 12)
|
|
};
|
|
}
|
|
throw new TypeError("Unsupported JPEG 2000 format");
|
|
}
|
|
};
|
|
|
|
const EXIF_MARKER = "45786966";
|
|
const APP1_DATA_SIZE_BYTES = 2;
|
|
const EXIF_HEADER_BYTES = 6;
|
|
const TIFF_BYTE_ALIGN_BYTES = 2;
|
|
const BIG_ENDIAN_BYTE_ALIGN = "4d4d";
|
|
const LITTLE_ENDIAN_BYTE_ALIGN = "4949";
|
|
const IDF_ENTRY_BYTES = 12;
|
|
const NUM_DIRECTORY_ENTRIES_BYTES = 2;
|
|
function isEXIF(input) {
|
|
return toHexString(input, 2, 6) === EXIF_MARKER;
|
|
}
|
|
function extractSize(input, index) {
|
|
return {
|
|
height: readUInt16BE(input, index),
|
|
width: readUInt16BE(input, index + 2)
|
|
};
|
|
}
|
|
function extractOrientation(exifBlock, isBigEndian) {
|
|
const idfOffset = 8;
|
|
const offset = EXIF_HEADER_BYTES + idfOffset;
|
|
const idfDirectoryEntries = readUInt(exifBlock, 16, offset, isBigEndian);
|
|
for (let directoryEntryNumber = 0; directoryEntryNumber < idfDirectoryEntries; directoryEntryNumber++) {
|
|
const start = offset + NUM_DIRECTORY_ENTRIES_BYTES + directoryEntryNumber * IDF_ENTRY_BYTES;
|
|
const end = start + IDF_ENTRY_BYTES;
|
|
if (start > exifBlock.length) {
|
|
return;
|
|
}
|
|
const block = exifBlock.slice(start, end);
|
|
const tagNumber = readUInt(block, 16, 0, isBigEndian);
|
|
if (tagNumber === 274) {
|
|
const dataFormat = readUInt(block, 16, 2, isBigEndian);
|
|
if (dataFormat !== 3) {
|
|
return;
|
|
}
|
|
const numberOfComponents = readUInt(block, 32, 4, isBigEndian);
|
|
if (numberOfComponents !== 1) {
|
|
return;
|
|
}
|
|
return readUInt(block, 16, 8, isBigEndian);
|
|
}
|
|
}
|
|
}
|
|
function validateExifBlock(input, index) {
|
|
const exifBlock = input.slice(APP1_DATA_SIZE_BYTES, index);
|
|
const byteAlign = toHexString(
|
|
exifBlock,
|
|
EXIF_HEADER_BYTES,
|
|
EXIF_HEADER_BYTES + TIFF_BYTE_ALIGN_BYTES
|
|
);
|
|
const isBigEndian = byteAlign === BIG_ENDIAN_BYTE_ALIGN;
|
|
const isLittleEndian = byteAlign === LITTLE_ENDIAN_BYTE_ALIGN;
|
|
if (isBigEndian || isLittleEndian) {
|
|
return extractOrientation(exifBlock, isBigEndian);
|
|
}
|
|
}
|
|
function validateInput(input, index) {
|
|
if (index > input.length) {
|
|
throw new TypeError("Corrupt JPG, exceeded buffer limits");
|
|
}
|
|
}
|
|
const JPG = {
|
|
validate: (input) => toHexString(input, 0, 2) === "ffd8",
|
|
calculate(_input) {
|
|
let input = _input.slice(4);
|
|
let orientation;
|
|
let next;
|
|
while (input.length) {
|
|
const i = readUInt16BE(input, 0);
|
|
validateInput(input, i);
|
|
if (input[i] !== 255) {
|
|
input = input.slice(1);
|
|
continue;
|
|
}
|
|
if (isEXIF(input)) {
|
|
orientation = validateExifBlock(input, i);
|
|
}
|
|
next = input[i + 1];
|
|
if (next === 192 || next === 193 || next === 194) {
|
|
const size = extractSize(input, i + 5);
|
|
if (!orientation) {
|
|
return size;
|
|
}
|
|
return {
|
|
height: size.height,
|
|
orientation,
|
|
width: size.width
|
|
};
|
|
}
|
|
input = input.slice(i + 2);
|
|
}
|
|
throw new TypeError("Invalid JPG, no size found");
|
|
}
|
|
};
|
|
|
|
class BitReader {
|
|
// Skip the first 16 bits (2 bytes) of signature
|
|
byteOffset = 2;
|
|
bitOffset = 0;
|
|
input;
|
|
endianness;
|
|
constructor(input, endianness) {
|
|
this.input = input;
|
|
this.endianness = endianness;
|
|
}
|
|
/** Reads a specified number of bits, and move the offset */
|
|
getBits(length = 1) {
|
|
let result = 0;
|
|
let bitsRead = 0;
|
|
while (bitsRead < length) {
|
|
if (this.byteOffset >= this.input.length) {
|
|
throw new Error("Reached end of input");
|
|
}
|
|
const currentByte = this.input[this.byteOffset];
|
|
const bitsLeft = 8 - this.bitOffset;
|
|
const bitsToRead = Math.min(length - bitsRead, bitsLeft);
|
|
if (this.endianness === "little-endian") {
|
|
const mask = (1 << bitsToRead) - 1;
|
|
const bits = currentByte >> this.bitOffset & mask;
|
|
result |= bits << bitsRead;
|
|
} else {
|
|
const mask = (1 << bitsToRead) - 1 << 8 - this.bitOffset - bitsToRead;
|
|
const bits = (currentByte & mask) >> 8 - this.bitOffset - bitsToRead;
|
|
result = result << bitsToRead | bits;
|
|
}
|
|
bitsRead += bitsToRead;
|
|
this.bitOffset += bitsToRead;
|
|
if (this.bitOffset === 8) {
|
|
this.byteOffset++;
|
|
this.bitOffset = 0;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
|
|
function calculateImageDimension(reader, isSmallImage) {
|
|
if (isSmallImage) {
|
|
return 8 * (1 + reader.getBits(5));
|
|
}
|
|
const sizeClass = reader.getBits(2);
|
|
const extraBits = [9, 13, 18, 30][sizeClass];
|
|
return 1 + reader.getBits(extraBits);
|
|
}
|
|
function calculateImageWidth(reader, isSmallImage, widthMode, height) {
|
|
if (isSmallImage && widthMode === 0) {
|
|
return 8 * (1 + reader.getBits(5));
|
|
}
|
|
if (widthMode === 0) {
|
|
return calculateImageDimension(reader, false);
|
|
}
|
|
const aspectRatios = [1, 1.2, 4 / 3, 1.5, 16 / 9, 5 / 4, 2];
|
|
return Math.floor(height * aspectRatios[widthMode - 1]);
|
|
}
|
|
const JXLStream = {
|
|
validate: (input) => {
|
|
return toHexString(input, 0, 2) === "ff0a";
|
|
},
|
|
calculate(input) {
|
|
const reader = new BitReader(input, "little-endian");
|
|
const isSmallImage = reader.getBits(1) === 1;
|
|
const height = calculateImageDimension(reader, isSmallImage);
|
|
const widthMode = reader.getBits(3);
|
|
const width = calculateImageWidth(reader, isSmallImage, widthMode, height);
|
|
return { width, height };
|
|
}
|
|
};
|
|
|
|
function extractCodestream(input) {
|
|
const jxlcBox = findBox(input, "jxlc", 0);
|
|
if (jxlcBox) {
|
|
return input.slice(jxlcBox.offset + 8, jxlcBox.offset + jxlcBox.size);
|
|
}
|
|
const partialStreams = extractPartialStreams(input);
|
|
if (partialStreams.length > 0) {
|
|
return concatenateCodestreams(partialStreams);
|
|
}
|
|
return void 0;
|
|
}
|
|
function extractPartialStreams(input) {
|
|
const partialStreams = [];
|
|
let offset = 0;
|
|
while (offset < input.length) {
|
|
const jxlpBox = findBox(input, "jxlp", offset);
|
|
if (!jxlpBox) break;
|
|
partialStreams.push(
|
|
input.slice(jxlpBox.offset + 12, jxlpBox.offset + jxlpBox.size)
|
|
);
|
|
offset = jxlpBox.offset + jxlpBox.size;
|
|
}
|
|
return partialStreams;
|
|
}
|
|
function concatenateCodestreams(partialCodestreams) {
|
|
const totalLength = partialCodestreams.reduce(
|
|
(acc, curr) => acc + curr.length,
|
|
0
|
|
);
|
|
const codestream = new Uint8Array(totalLength);
|
|
let position = 0;
|
|
for (const partial of partialCodestreams) {
|
|
codestream.set(partial, position);
|
|
position += partial.length;
|
|
}
|
|
return codestream;
|
|
}
|
|
const JXL = {
|
|
validate: (input) => {
|
|
const boxType = toUTF8String(input, 4, 8);
|
|
if (boxType !== "JXL ") return false;
|
|
const ftypBox = findBox(input, "ftyp", 0);
|
|
if (!ftypBox) return false;
|
|
const brand = toUTF8String(input, ftypBox.offset + 8, ftypBox.offset + 12);
|
|
return brand === "jxl ";
|
|
},
|
|
calculate(input) {
|
|
const codestream = extractCodestream(input);
|
|
if (codestream) return JXLStream.calculate(codestream);
|
|
throw new Error("No codestream found in JXL container");
|
|
}
|
|
};
|
|
|
|
const KTX = {
|
|
validate: (input) => {
|
|
const signature = toUTF8String(input, 1, 7);
|
|
return ["KTX 11", "KTX 20"].includes(signature);
|
|
},
|
|
calculate: (input) => {
|
|
const type = input[5] === 49 ? "ktx" : "ktx2";
|
|
const offset = type === "ktx" ? 36 : 20;
|
|
return {
|
|
height: readUInt32LE(input, offset + 4),
|
|
width: readUInt32LE(input, offset),
|
|
type
|
|
};
|
|
}
|
|
};
|
|
|
|
const pngSignature = "PNG\r\n\n";
|
|
const pngImageHeaderChunkName = "IHDR";
|
|
const pngFriedChunkName = "CgBI";
|
|
const PNG = {
|
|
validate(input) {
|
|
if (pngSignature === toUTF8String(input, 1, 8)) {
|
|
let chunkName = toUTF8String(input, 12, 16);
|
|
if (chunkName === pngFriedChunkName) {
|
|
chunkName = toUTF8String(input, 28, 32);
|
|
}
|
|
if (chunkName !== pngImageHeaderChunkName) {
|
|
throw new TypeError("Invalid PNG");
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
},
|
|
calculate(input) {
|
|
if (toUTF8String(input, 12, 16) === pngFriedChunkName) {
|
|
return {
|
|
height: readUInt32BE(input, 36),
|
|
width: readUInt32BE(input, 32)
|
|
};
|
|
}
|
|
return {
|
|
height: readUInt32BE(input, 20),
|
|
width: readUInt32BE(input, 16)
|
|
};
|
|
}
|
|
};
|
|
|
|
const PNMTypes = {
|
|
P1: "pbm/ascii",
|
|
P2: "pgm/ascii",
|
|
P3: "ppm/ascii",
|
|
P4: "pbm",
|
|
P5: "pgm",
|
|
P6: "ppm",
|
|
P7: "pam",
|
|
PF: "pfm"
|
|
};
|
|
const handlers = {
|
|
default: (lines) => {
|
|
let dimensions = [];
|
|
while (lines.length > 0) {
|
|
const line = lines.shift();
|
|
if (line[0] === "#") {
|
|
continue;
|
|
}
|
|
dimensions = line.split(" ");
|
|
break;
|
|
}
|
|
if (dimensions.length === 2) {
|
|
return {
|
|
height: Number.parseInt(dimensions[1], 10),
|
|
width: Number.parseInt(dimensions[0], 10)
|
|
};
|
|
}
|
|
throw new TypeError("Invalid PNM");
|
|
},
|
|
pam: (lines) => {
|
|
const size = {};
|
|
while (lines.length > 0) {
|
|
const line = lines.shift();
|
|
if (line.length > 16 || line.charCodeAt(0) > 128) {
|
|
continue;
|
|
}
|
|
const [key, value] = line.split(" ");
|
|
if (key && value) {
|
|
size[key.toLowerCase()] = Number.parseInt(value, 10);
|
|
}
|
|
if (size.height && size.width) {
|
|
break;
|
|
}
|
|
}
|
|
if (size.height && size.width) {
|
|
return {
|
|
height: size.height,
|
|
width: size.width
|
|
};
|
|
}
|
|
throw new TypeError("Invalid PAM");
|
|
}
|
|
};
|
|
const PNM = {
|
|
validate: (input) => toUTF8String(input, 0, 2) in PNMTypes,
|
|
calculate(input) {
|
|
const signature = toUTF8String(input, 0, 2);
|
|
const type = PNMTypes[signature];
|
|
const lines = toUTF8String(input, 3).split(/[\r\n]+/);
|
|
const handler = handlers[type] || handlers.default;
|
|
return handler(lines);
|
|
}
|
|
};
|
|
|
|
const PSD = {
|
|
validate: (input) => toUTF8String(input, 0, 4) === "8BPS",
|
|
calculate: (input) => ({
|
|
height: readUInt32BE(input, 14),
|
|
width: readUInt32BE(input, 18)
|
|
})
|
|
};
|
|
|
|
const svgReg = /<svg\s([^>"']|"[^"]*"|'[^']*')*>/;
|
|
const extractorRegExps = {
|
|
height: /\sheight=(['"])([^%]+?)\1/,
|
|
root: svgReg,
|
|
viewbox: /\sviewBox=(['"])(.+?)\1/i,
|
|
width: /\swidth=(['"])([^%]+?)\1/
|
|
};
|
|
const INCH_CM = 2.54;
|
|
const units = {
|
|
in: 96,
|
|
cm: 96 / INCH_CM,
|
|
em: 16,
|
|
ex: 8,
|
|
m: 96 / INCH_CM * 100,
|
|
mm: 96 / INCH_CM / 10,
|
|
pc: 96 / 72 / 12,
|
|
pt: 96 / 72,
|
|
px: 1
|
|
};
|
|
const unitsReg = new RegExp(
|
|
`^([0-9.]+(?:e\\d+)?)(${Object.keys(units).join("|")})?$`
|
|
);
|
|
function parseLength(len) {
|
|
const m = unitsReg.exec(len);
|
|
if (!m) {
|
|
return void 0;
|
|
}
|
|
return Math.round(Number(m[1]) * (units[m[2]] || 1));
|
|
}
|
|
function parseViewbox(viewbox) {
|
|
const bounds = viewbox.split(" ");
|
|
return {
|
|
height: parseLength(bounds[3]),
|
|
width: parseLength(bounds[2])
|
|
};
|
|
}
|
|
function parseAttributes(root) {
|
|
const width = extractorRegExps.width.exec(root);
|
|
const height = extractorRegExps.height.exec(root);
|
|
const viewbox = extractorRegExps.viewbox.exec(root);
|
|
return {
|
|
height: height && parseLength(height[2]),
|
|
viewbox: viewbox && parseViewbox(viewbox[2]),
|
|
width: width && parseLength(width[2])
|
|
};
|
|
}
|
|
function calculateByDimensions(attrs) {
|
|
return {
|
|
height: attrs.height,
|
|
width: attrs.width
|
|
};
|
|
}
|
|
function calculateByViewbox(attrs, viewbox) {
|
|
const ratio = viewbox.width / viewbox.height;
|
|
if (attrs.width) {
|
|
return {
|
|
height: Math.floor(attrs.width / ratio),
|
|
width: attrs.width
|
|
};
|
|
}
|
|
if (attrs.height) {
|
|
return {
|
|
height: attrs.height,
|
|
width: Math.floor(attrs.height * ratio)
|
|
};
|
|
}
|
|
return {
|
|
height: viewbox.height,
|
|
width: viewbox.width
|
|
};
|
|
}
|
|
const SVG = {
|
|
// Scan only the first kilo-byte to speed up the check on larger files
|
|
validate: (input) => svgReg.test(toUTF8String(input, 0, 1e3)),
|
|
calculate(input) {
|
|
const root = extractorRegExps.root.exec(toUTF8String(input));
|
|
if (root) {
|
|
const attrs = parseAttributes(root[0]);
|
|
if (attrs.width && attrs.height) {
|
|
return calculateByDimensions(attrs);
|
|
}
|
|
if (attrs.viewbox) {
|
|
return calculateByViewbox(attrs, attrs.viewbox);
|
|
}
|
|
}
|
|
throw new TypeError("Invalid SVG");
|
|
}
|
|
};
|
|
|
|
const TGA = {
|
|
validate(input) {
|
|
return readUInt16LE(input, 0) === 0 && readUInt16LE(input, 4) === 0;
|
|
},
|
|
calculate(input) {
|
|
return {
|
|
height: readUInt16LE(input, 14),
|
|
width: readUInt16LE(input, 12)
|
|
};
|
|
}
|
|
};
|
|
|
|
const CONSTANTS = {
|
|
TAG: {
|
|
WIDTH: 256,
|
|
HEIGHT: 257,
|
|
COMPRESSION: 259
|
|
},
|
|
TYPE: {
|
|
SHORT: 3,
|
|
LONG: 4,
|
|
LONG8: 16
|
|
},
|
|
ENTRY_SIZE: {
|
|
STANDARD: 12,
|
|
BIG: 20
|
|
},
|
|
COUNT_SIZE: {
|
|
STANDARD: 2,
|
|
BIG: 8
|
|
}
|
|
};
|
|
function readIFD(input, { isBigEndian, isBigTiff }) {
|
|
const ifdOffset = isBigTiff ? Number(readUInt64(input, 8, isBigEndian)) : readUInt(input, 32, 4, isBigEndian);
|
|
const entryCountSize = isBigTiff ? CONSTANTS.COUNT_SIZE.BIG : CONSTANTS.COUNT_SIZE.STANDARD;
|
|
return input.slice(ifdOffset + entryCountSize);
|
|
}
|
|
function readTagValue(input, type, offset, isBigEndian) {
|
|
switch (type) {
|
|
case CONSTANTS.TYPE.SHORT:
|
|
return readUInt(input, 16, offset, isBigEndian);
|
|
case CONSTANTS.TYPE.LONG:
|
|
return readUInt(input, 32, offset, isBigEndian);
|
|
case CONSTANTS.TYPE.LONG8: {
|
|
const value = Number(readUInt64(input, offset, isBigEndian));
|
|
if (value > Number.MAX_SAFE_INTEGER) {
|
|
throw new TypeError("Value too large");
|
|
}
|
|
return value;
|
|
}
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
function nextTag(input, isBigTiff) {
|
|
const entrySize = isBigTiff ? CONSTANTS.ENTRY_SIZE.BIG : CONSTANTS.ENTRY_SIZE.STANDARD;
|
|
if (input.length > entrySize) {
|
|
return input.slice(entrySize);
|
|
}
|
|
}
|
|
function extractTags(input, { isBigEndian, isBigTiff }) {
|
|
const tags = {};
|
|
let temp = input;
|
|
while (temp?.length) {
|
|
const code = readUInt(temp, 16, 0, isBigEndian);
|
|
const type = readUInt(temp, 16, 2, isBigEndian);
|
|
const length = isBigTiff ? Number(readUInt64(temp, 4, isBigEndian)) : readUInt(temp, 32, 4, isBigEndian);
|
|
if (code === 0) break;
|
|
if (length === 1 && (type === CONSTANTS.TYPE.SHORT || type === CONSTANTS.TYPE.LONG || isBigTiff && type === CONSTANTS.TYPE.LONG8)) {
|
|
const valueOffset = isBigTiff ? 12 : 8;
|
|
tags[code] = readTagValue(temp, type, valueOffset, isBigEndian);
|
|
}
|
|
temp = nextTag(temp, isBigTiff);
|
|
}
|
|
return tags;
|
|
}
|
|
function determineFormat(input) {
|
|
const signature = toUTF8String(input, 0, 2);
|
|
const version = readUInt(input, 16, 2, signature === "MM");
|
|
return {
|
|
isBigEndian: signature === "MM",
|
|
isBigTiff: version === 43
|
|
};
|
|
}
|
|
function validateBigTIFFHeader(input, isBigEndian) {
|
|
const byteSize = readUInt(input, 16, 4, isBigEndian);
|
|
const reserved = readUInt(input, 16, 6, isBigEndian);
|
|
if (byteSize !== 8 || reserved !== 0) {
|
|
throw new TypeError("Invalid BigTIFF header");
|
|
}
|
|
}
|
|
const signatures = /* @__PURE__ */ new Set([
|
|
"49492a00",
|
|
// Little Endian
|
|
"4d4d002a",
|
|
// Big Endian
|
|
"49492b00",
|
|
// BigTIFF Little Endian
|
|
"4d4d002b"
|
|
// BigTIFF Big Endian
|
|
]);
|
|
const TIFF = {
|
|
validate: (input) => {
|
|
const signature = toHexString(input, 0, 4);
|
|
return signatures.has(signature);
|
|
},
|
|
calculate(input) {
|
|
const format = determineFormat(input);
|
|
if (format.isBigTiff) {
|
|
validateBigTIFFHeader(input, format.isBigEndian);
|
|
}
|
|
const ifdBuffer = readIFD(input, format);
|
|
const tags = extractTags(ifdBuffer, format);
|
|
const info = {
|
|
height: tags[CONSTANTS.TAG.HEIGHT],
|
|
width: tags[CONSTANTS.TAG.WIDTH],
|
|
type: format.isBigTiff ? "bigtiff" : "tiff"
|
|
};
|
|
if (tags[CONSTANTS.TAG.COMPRESSION]) {
|
|
info.compression = tags[CONSTANTS.TAG.COMPRESSION];
|
|
}
|
|
if (!info.width || !info.height) {
|
|
throw new TypeError("Invalid Tiff. Missing tags");
|
|
}
|
|
return info;
|
|
}
|
|
};
|
|
|
|
function calculateExtended(input) {
|
|
return {
|
|
height: 1 + readUInt24LE(input, 7),
|
|
width: 1 + readUInt24LE(input, 4)
|
|
};
|
|
}
|
|
function calculateLossless(input) {
|
|
return {
|
|
height: 1 + ((input[4] & 15) << 10 | input[3] << 2 | (input[2] & 192) >> 6),
|
|
width: 1 + ((input[2] & 63) << 8 | input[1])
|
|
};
|
|
}
|
|
function calculateLossy(input) {
|
|
return {
|
|
height: readInt16LE(input, 8) & 16383,
|
|
width: readInt16LE(input, 6) & 16383
|
|
};
|
|
}
|
|
const WEBP = {
|
|
validate(input) {
|
|
const riffHeader = "RIFF" === toUTF8String(input, 0, 4);
|
|
const webpHeader = "WEBP" === toUTF8String(input, 8, 12);
|
|
const vp8Header = "VP8" === toUTF8String(input, 12, 15);
|
|
return riffHeader && webpHeader && vp8Header;
|
|
},
|
|
calculate(_input) {
|
|
const chunkHeader = toUTF8String(_input, 12, 16);
|
|
const input = _input.slice(20, 30);
|
|
if (chunkHeader === "VP8X") {
|
|
const extendedHeader = input[0];
|
|
const validStart = (extendedHeader & 192) === 0;
|
|
const validEnd = (extendedHeader & 1) === 0;
|
|
if (validStart && validEnd) {
|
|
return calculateExtended(input);
|
|
}
|
|
throw new TypeError("Invalid WebP");
|
|
}
|
|
if (chunkHeader === "VP8 " && input[0] !== 47) {
|
|
return calculateLossy(input);
|
|
}
|
|
const signature = toHexString(input, 3, 6);
|
|
if (chunkHeader === "VP8L" && signature !== "9d012a") {
|
|
return calculateLossless(input);
|
|
}
|
|
throw new TypeError("Invalid WebP");
|
|
}
|
|
};
|
|
|
|
const typeHandlers = /* @__PURE__ */ new Map([
|
|
["bmp", BMP],
|
|
["cur", CUR],
|
|
["dds", DDS],
|
|
["gif", GIF],
|
|
["heif", HEIF],
|
|
["icns", ICNS],
|
|
["ico", ICO],
|
|
["j2c", J2C],
|
|
["jp2", JP2],
|
|
["jpg", JPG],
|
|
["jxl", JXL],
|
|
["jxl-stream", JXLStream],
|
|
["ktx", KTX],
|
|
["png", PNG],
|
|
["pnm", PNM],
|
|
["psd", PSD],
|
|
["svg", SVG],
|
|
["tga", TGA],
|
|
["tiff", TIFF],
|
|
["webp", WEBP]
|
|
]);
|
|
const types = Array.from(typeHandlers.keys());
|
|
|
|
function appendForwardSlash(path) {
|
|
return path.endsWith("/") ? path : path + "/";
|
|
}
|
|
function prependForwardSlash(path) {
|
|
return path[0] === "/" ? path : "/" + path;
|
|
}
|
|
const MANY_LEADING_SLASHES = /^\/{2,}/;
|
|
function collapseDuplicateLeadingSlashes(path) {
|
|
if (!path) {
|
|
return path;
|
|
}
|
|
return path.replace(MANY_LEADING_SLASHES, "/");
|
|
}
|
|
const MANY_SLASHES = /\/{2,}/g;
|
|
function collapseDuplicateSlashes(path) {
|
|
if (!path) {
|
|
return path;
|
|
}
|
|
return path.replace(MANY_SLASHES, "/");
|
|
}
|
|
const MANY_TRAILING_SLASHES = /\/{2,}$/g;
|
|
function collapseDuplicateTrailingSlashes(path, trailingSlash) {
|
|
if (!path) {
|
|
return path;
|
|
}
|
|
return path.replace(MANY_TRAILING_SLASHES, trailingSlash ? "/" : "") || "/";
|
|
}
|
|
function removeTrailingForwardSlash(path) {
|
|
return path.endsWith("/") ? path.slice(0, path.length - 1) : path;
|
|
}
|
|
function removeLeadingForwardSlash(path) {
|
|
return path.startsWith("/") ? path.substring(1) : path;
|
|
}
|
|
function trimSlashes(path) {
|
|
return path.replace(/^\/|\/$/g, "");
|
|
}
|
|
function isString(path) {
|
|
return typeof path === "string" || path instanceof String;
|
|
}
|
|
const INTERNAL_PREFIXES = /* @__PURE__ */ new Set(["/_", "/@", "/.", "//"]);
|
|
const JUST_SLASHES = /^\/{2,}$/;
|
|
function isInternalPath(path) {
|
|
return INTERNAL_PREFIXES.has(path.slice(0, 2)) && !JUST_SLASHES.test(path);
|
|
}
|
|
function joinPaths(...paths) {
|
|
return paths.filter(isString).map((path, i) => {
|
|
if (i === 0) {
|
|
return removeTrailingForwardSlash(path);
|
|
} else if (i === paths.length - 1) {
|
|
return removeLeadingForwardSlash(path);
|
|
} else {
|
|
return trimSlashes(path);
|
|
}
|
|
}).join("/");
|
|
}
|
|
function isRemotePath(src) {
|
|
if (!src) return false;
|
|
const trimmed = src.trim();
|
|
if (!trimmed) return false;
|
|
let decoded = trimmed;
|
|
let previousDecoded = "";
|
|
let maxIterations = 10;
|
|
while (decoded !== previousDecoded && maxIterations > 0) {
|
|
previousDecoded = decoded;
|
|
try {
|
|
decoded = decodeURIComponent(decoded);
|
|
} catch {
|
|
break;
|
|
}
|
|
maxIterations--;
|
|
}
|
|
if (/^[a-zA-Z]:/.test(decoded)) {
|
|
return false;
|
|
}
|
|
if (decoded[0] === "/" && decoded[1] !== "/" && decoded[1] !== "\\") {
|
|
return false;
|
|
}
|
|
if (decoded[0] === "\\") {
|
|
return true;
|
|
}
|
|
if (decoded.startsWith("//")) {
|
|
return true;
|
|
}
|
|
try {
|
|
const url = new URL(decoded, "http://n");
|
|
if (url.username || url.password) {
|
|
return true;
|
|
}
|
|
if (decoded.includes("@") && !url.pathname.includes("@") && !url.search.includes("@")) {
|
|
return true;
|
|
}
|
|
if (url.origin !== "http://n") {
|
|
const protocol = url.protocol.toLowerCase();
|
|
if (protocol === "file:") {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
if (URL.canParse(decoded)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
} catch {
|
|
return true;
|
|
}
|
|
}
|
|
function slash(path) {
|
|
return path.replace(/\\/g, "/");
|
|
}
|
|
function fileExtension(path) {
|
|
const ext = path.split(".").pop();
|
|
return ext !== path ? `.${ext}` : "";
|
|
}
|
|
const WITH_FILE_EXT = /\/[^/]+\.\w+$/;
|
|
function hasFileExtension(path) {
|
|
return WITH_FILE_EXT.test(path);
|
|
}
|
|
|
|
nodePath.posix.join;
|
|
|
|
const ASTRO_PATH_HEADER = "x-astro-path";
|
|
const ASTRO_PATH_PARAM = "x_astro_path";
|
|
const ASTRO_LOCALS_HEADER = "x-astro-locals";
|
|
const ASTRO_MIDDLEWARE_SECRET_HEADER = "x-astro-middleware-secret";
|
|
|
|
const middlewareSecret = "540c10bd-4b14-40ea-a067-00b109f1af2c";
|
|
|
|
function shouldAppendForwardSlash(trailingSlash, buildFormat) {
|
|
switch (trailingSlash) {
|
|
case "always":
|
|
return true;
|
|
case "never":
|
|
return false;
|
|
case "ignore": {
|
|
switch (buildFormat) {
|
|
case "directory":
|
|
return true;
|
|
case "preserve":
|
|
case "file":
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const ASTRO_VERSION = "6.1.10";
|
|
const ASTRO_GENERATOR = `Astro v${ASTRO_VERSION}`;
|
|
const REROUTE_DIRECTIVE_HEADER = "X-Astro-Reroute";
|
|
const REWRITE_DIRECTIVE_HEADER_KEY = "X-Astro-Rewrite";
|
|
const REWRITE_DIRECTIVE_HEADER_VALUE = "yes";
|
|
const NOOP_MIDDLEWARE_HEADER = "X-Astro-Noop";
|
|
const ROUTE_TYPE_HEADER = "X-Astro-Route-Type";
|
|
const DEFAULT_404_COMPONENT = "astro-default-404.astro";
|
|
const REDIRECT_STATUS_CODES = [301, 302, 303, 307, 308, 300, 304];
|
|
const REROUTABLE_STATUS_CODES = [404, 500];
|
|
const clientAddressSymbol = /* @__PURE__ */ Symbol.for("astro.clientAddress");
|
|
const originPathnameSymbol = /* @__PURE__ */ Symbol.for("astro.originPathname");
|
|
const pipelineSymbol = /* @__PURE__ */ Symbol.for("astro.pipeline");
|
|
const responseSentSymbol$1 = /* @__PURE__ */ Symbol.for("astro.responseSent");
|
|
|
|
function computeFallbackRoute(options) {
|
|
const {
|
|
pathname,
|
|
responseStatus,
|
|
fallback,
|
|
fallbackType,
|
|
locales,
|
|
defaultLocale,
|
|
strategy,
|
|
base
|
|
} = options;
|
|
if (responseStatus !== 404) {
|
|
return { type: "none" };
|
|
}
|
|
if (!fallback || Object.keys(fallback).length === 0) {
|
|
return { type: "none" };
|
|
}
|
|
const segments = pathname.split("/");
|
|
const urlLocale = segments.find((segment) => {
|
|
for (const locale of locales) {
|
|
if (typeof locale === "string") {
|
|
if (locale === segment) {
|
|
return true;
|
|
}
|
|
} else if (locale.path === segment) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
});
|
|
if (!urlLocale) {
|
|
return { type: "none" };
|
|
}
|
|
const fallbackKeys = Object.keys(fallback);
|
|
if (!fallbackKeys.includes(urlLocale)) {
|
|
return { type: "none" };
|
|
}
|
|
const fallbackLocale = fallback[urlLocale];
|
|
const pathFallbackLocale = getPathByLocale(fallbackLocale, locales);
|
|
let newPathname;
|
|
if (pathFallbackLocale === defaultLocale && strategy === "pathname-prefix-other-locales") {
|
|
if (pathname.includes(`${base}`)) {
|
|
newPathname = pathname.replace(`/${urlLocale}`, ``);
|
|
} else {
|
|
newPathname = pathname.replace(`/${urlLocale}`, `/`);
|
|
}
|
|
} else {
|
|
newPathname = pathname.replace(`/${urlLocale}`, `/${pathFallbackLocale}`);
|
|
}
|
|
return {
|
|
type: fallbackType,
|
|
pathname: newPathname
|
|
};
|
|
}
|
|
|
|
class I18nRouter {
|
|
#strategy;
|
|
#defaultLocale;
|
|
#locales;
|
|
#base;
|
|
#domains;
|
|
constructor(options) {
|
|
this.#strategy = options.strategy;
|
|
this.#defaultLocale = options.defaultLocale;
|
|
this.#locales = options.locales;
|
|
this.#base = options.base === "/" ? "/" : removeTrailingForwardSlash(options.base || "");
|
|
this.#domains = options.domains;
|
|
}
|
|
/**
|
|
* Evaluate routing strategy for a pathname.
|
|
* Returns decision object (not HTTP Response).
|
|
*/
|
|
match(pathname, context) {
|
|
if (this.shouldSkipProcessing(pathname, context)) {
|
|
return { type: "continue" };
|
|
}
|
|
switch (this.#strategy) {
|
|
case "manual":
|
|
return { type: "continue" };
|
|
case "pathname-prefix-always":
|
|
return this.matchPrefixAlways(pathname, context);
|
|
case "domains-prefix-always":
|
|
if (this.localeHasntDomain(context.currentLocale, context.currentDomain)) {
|
|
return { type: "continue" };
|
|
}
|
|
return this.matchPrefixAlways(pathname, context);
|
|
case "pathname-prefix-other-locales":
|
|
return this.matchPrefixOtherLocales(pathname, context);
|
|
case "domains-prefix-other-locales":
|
|
if (this.localeHasntDomain(context.currentLocale, context.currentDomain)) {
|
|
return { type: "continue" };
|
|
}
|
|
return this.matchPrefixOtherLocales(pathname, context);
|
|
case "pathname-prefix-always-no-redirect":
|
|
return this.matchPrefixAlwaysNoRedirect(pathname, context);
|
|
case "domains-prefix-always-no-redirect":
|
|
if (this.localeHasntDomain(context.currentLocale, context.currentDomain)) {
|
|
return { type: "continue" };
|
|
}
|
|
return this.matchPrefixAlwaysNoRedirect(pathname, context);
|
|
default:
|
|
return { type: "continue" };
|
|
}
|
|
}
|
|
/**
|
|
* Check if i18n processing should be skipped for this request
|
|
*/
|
|
shouldSkipProcessing(pathname, context) {
|
|
if (pathname.includes("/404") || pathname.includes("/500")) {
|
|
return true;
|
|
}
|
|
if (pathname.includes("/_server-islands/")) {
|
|
return true;
|
|
}
|
|
if (context.isReroute) {
|
|
return true;
|
|
}
|
|
if (context.routeType && context.routeType !== "page" && context.routeType !== "fallback") {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
/**
|
|
* Strategy: pathname-prefix-always
|
|
* All locales must have a prefix, including the default locale.
|
|
*/
|
|
matchPrefixAlways(pathname, _context) {
|
|
const isRoot = pathname === this.#base + "/" || pathname === this.#base;
|
|
if (isRoot) {
|
|
const basePrefix = this.#base === "/" ? "" : this.#base;
|
|
return {
|
|
type: "redirect",
|
|
location: `${basePrefix}/${this.#defaultLocale}`
|
|
};
|
|
}
|
|
if (!pathHasLocale(pathname, this.#locales)) {
|
|
return { type: "notFound" };
|
|
}
|
|
return { type: "continue" };
|
|
}
|
|
/**
|
|
* Strategy: pathname-prefix-other-locales
|
|
* Default locale has no prefix, other locales must have a prefix.
|
|
*/
|
|
matchPrefixOtherLocales(pathname, _context) {
|
|
let pathnameContainsDefaultLocale = false;
|
|
for (const segment of pathname.split("/")) {
|
|
if (normalizeTheLocale(segment) === normalizeTheLocale(this.#defaultLocale)) {
|
|
pathnameContainsDefaultLocale = true;
|
|
break;
|
|
}
|
|
}
|
|
if (pathnameContainsDefaultLocale) {
|
|
const newLocation = pathname.replace(`/${this.#defaultLocale}`, "");
|
|
return {
|
|
type: "notFound",
|
|
location: newLocation
|
|
};
|
|
}
|
|
return { type: "continue" };
|
|
}
|
|
/**
|
|
* Strategy: pathname-prefix-always-no-redirect
|
|
* Like prefix-always but allows root to serve instead of redirecting
|
|
*/
|
|
matchPrefixAlwaysNoRedirect(pathname, _context) {
|
|
const isRoot = pathname === this.#base + "/" || pathname === this.#base;
|
|
if (isRoot) {
|
|
return { type: "continue" };
|
|
}
|
|
if (!pathHasLocale(pathname, this.#locales)) {
|
|
return { type: "notFound" };
|
|
}
|
|
return { type: "continue" };
|
|
}
|
|
/**
|
|
* Check if the current locale doesn't belong to the configured domain.
|
|
* Used for domain-based routing strategies.
|
|
*/
|
|
localeHasntDomain(currentLocale, currentDomain) {
|
|
if (!this.#domains || !currentDomain) {
|
|
return false;
|
|
}
|
|
if (!currentLocale) {
|
|
return false;
|
|
}
|
|
const localesForDomain = this.#domains[currentDomain];
|
|
if (!localesForDomain) {
|
|
return true;
|
|
}
|
|
return !localesForDomain.includes(currentLocale);
|
|
}
|
|
}
|
|
|
|
function createI18nMiddleware(i18n, base, trailingSlash, format) {
|
|
if (!i18n) return (_, next) => next();
|
|
const i18nRouter = new I18nRouter({
|
|
strategy: i18n.strategy,
|
|
defaultLocale: i18n.defaultLocale,
|
|
locales: i18n.locales,
|
|
base,
|
|
domains: i18n.domainLookupTable ? Object.keys(i18n.domainLookupTable).reduce(
|
|
(acc, domain) => {
|
|
const locale = i18n.domainLookupTable[domain];
|
|
if (!acc[domain]) {
|
|
acc[domain] = [];
|
|
}
|
|
acc[domain].push(locale);
|
|
return acc;
|
|
},
|
|
{}
|
|
) : void 0
|
|
});
|
|
return async (context, next) => {
|
|
const response = await next();
|
|
const typeHeader = response.headers.get(ROUTE_TYPE_HEADER);
|
|
const isReroute = response.headers.get(REROUTE_DIRECTIVE_HEADER);
|
|
if (isReroute === "no" && typeof i18n.fallback === "undefined") {
|
|
return response;
|
|
}
|
|
if (typeHeader !== "page" && typeHeader !== "fallback") {
|
|
return response;
|
|
}
|
|
const routerContext = {
|
|
currentLocale: context.currentLocale,
|
|
currentDomain: context.url.hostname,
|
|
routeType: typeHeader,
|
|
isReroute: isReroute === "yes"
|
|
};
|
|
const routeDecision = i18nRouter.match(context.url.pathname, routerContext);
|
|
switch (routeDecision.type) {
|
|
case "redirect": {
|
|
let location = routeDecision.location;
|
|
if (shouldAppendForwardSlash(trailingSlash, format)) {
|
|
location = appendForwardSlash(location);
|
|
}
|
|
return context.redirect(location, routeDecision.status);
|
|
}
|
|
case "notFound": {
|
|
if (context.isPrerendered) {
|
|
const prerenderedRes = new Response(response.body, {
|
|
status: 404,
|
|
headers: response.headers
|
|
});
|
|
prerenderedRes.headers.set(REROUTE_DIRECTIVE_HEADER, "no");
|
|
if (routeDecision.location) {
|
|
prerenderedRes.headers.set("Location", routeDecision.location);
|
|
}
|
|
return prerenderedRes;
|
|
}
|
|
const headers = new Headers();
|
|
if (routeDecision.location) {
|
|
headers.set("Location", routeDecision.location);
|
|
}
|
|
return new Response(null, { status: 404, headers });
|
|
}
|
|
}
|
|
if (i18n.fallback && i18n.fallbackType) {
|
|
const fallbackDecision = computeFallbackRoute({
|
|
pathname: context.url.pathname,
|
|
responseStatus: response.status,
|
|
currentLocale: context.currentLocale,
|
|
fallback: i18n.fallback,
|
|
fallbackType: i18n.fallbackType,
|
|
locales: i18n.locales,
|
|
defaultLocale: i18n.defaultLocale,
|
|
strategy: i18n.strategy,
|
|
base
|
|
});
|
|
switch (fallbackDecision.type) {
|
|
case "redirect":
|
|
return context.redirect(fallbackDecision.pathname + context.url.search);
|
|
case "rewrite":
|
|
return await context.rewrite(fallbackDecision.pathname + context.url.search);
|
|
}
|
|
}
|
|
return response;
|
|
};
|
|
}
|
|
|
|
function pathHasLocale(path, locales) {
|
|
const segments = path.split("/").map(normalizeThePath);
|
|
for (const segment of segments) {
|
|
for (const locale of locales) {
|
|
if (typeof locale === "string") {
|
|
if (normalizeTheLocale(segment) === normalizeTheLocale(locale)) {
|
|
return true;
|
|
}
|
|
} else if (segment === locale.path) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
function getPathByLocale(locale, locales) {
|
|
for (const loopLocale of locales) {
|
|
if (typeof loopLocale === "string") {
|
|
if (loopLocale === locale) {
|
|
return loopLocale;
|
|
}
|
|
} else {
|
|
for (const code of loopLocale.codes) {
|
|
if (code === locale) {
|
|
return loopLocale.path;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
throw new AstroError(i18nNoLocaleFoundInPath);
|
|
}
|
|
function normalizeTheLocale(locale) {
|
|
return locale.replaceAll("_", "-").toLowerCase();
|
|
}
|
|
function normalizeThePath(path) {
|
|
return path.endsWith(".html") ? path.slice(0, -5) : path;
|
|
}
|
|
function getAllCodes(locales) {
|
|
const result = [];
|
|
for (const loopLocale of locales) {
|
|
if (typeof loopLocale === "string") {
|
|
result.push(loopLocale);
|
|
} else {
|
|
result.push(...loopLocale.codes);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
var dist = {};
|
|
|
|
var hasRequiredDist;
|
|
|
|
function requireDist () {
|
|
if (hasRequiredDist) return dist;
|
|
hasRequiredDist = 1;
|
|
Object.defineProperty(dist, "__esModule", { value: true });
|
|
dist.parseCookie = parseCookie;
|
|
dist.parse = parseCookie;
|
|
dist.stringifyCookie = stringifyCookie;
|
|
dist.stringifySetCookie = stringifySetCookie;
|
|
dist.serialize = stringifySetCookie;
|
|
dist.parseSetCookie = parseSetCookie;
|
|
dist.stringifySetCookie = stringifySetCookie;
|
|
dist.serialize = stringifySetCookie;
|
|
/**
|
|
* RegExp to match cookie-name in RFC 6265 sec 4.1.1
|
|
* This refers out to the obsoleted definition of token in RFC 2616 sec 2.2
|
|
* which has been replaced by the token definition in RFC 7230 appendix B.
|
|
*
|
|
* cookie-name = token
|
|
* token = 1*tchar
|
|
* tchar = "!" / "#" / "$" / "%" / "&" / "'" /
|
|
* "*" / "+" / "-" / "." / "^" / "_" /
|
|
* "`" / "|" / "~" / DIGIT / ALPHA
|
|
*
|
|
* Note: Allowing more characters - https://github.com/jshttp/cookie/issues/191
|
|
* Allow same range as cookie value, except `=`, which delimits end of name.
|
|
*/
|
|
const cookieNameRegExp = /^[\u0021-\u003A\u003C\u003E-\u007E]+$/;
|
|
/**
|
|
* RegExp to match cookie-value in RFC 6265 sec 4.1.1
|
|
*
|
|
* cookie-value = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE )
|
|
* cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E
|
|
* ; US-ASCII characters excluding CTLs,
|
|
* ; whitespace DQUOTE, comma, semicolon,
|
|
* ; and backslash
|
|
*
|
|
* Allowing more characters: https://github.com/jshttp/cookie/issues/191
|
|
* Comma, backslash, and DQUOTE are not part of the parsing algorithm.
|
|
*/
|
|
const cookieValueRegExp = /^[\u0021-\u003A\u003C-\u007E]*$/;
|
|
/**
|
|
* RegExp to match domain-value in RFC 6265 sec 4.1.1
|
|
*
|
|
* domain-value = <subdomain>
|
|
* ; defined in [RFC1034], Section 3.5, as
|
|
* ; enhanced by [RFC1123], Section 2.1
|
|
* <subdomain> = <label> | <subdomain> "." <label>
|
|
* <label> = <let-dig> [ [ <ldh-str> ] <let-dig> ]
|
|
* Labels must be 63 characters or less.
|
|
* 'let-dig' not 'letter' in the first char, per RFC1123
|
|
* <ldh-str> = <let-dig-hyp> | <let-dig-hyp> <ldh-str>
|
|
* <let-dig-hyp> = <let-dig> | "-"
|
|
* <let-dig> = <letter> | <digit>
|
|
* <letter> = any one of the 52 alphabetic characters A through Z in
|
|
* upper case and a through z in lower case
|
|
* <digit> = any one of the ten digits 0 through 9
|
|
*
|
|
* Keep support for leading dot: https://github.com/jshttp/cookie/issues/173
|
|
*
|
|
* > (Note that a leading %x2E ("."), if present, is ignored even though that
|
|
* character is not permitted, but a trailing %x2E ("."), if present, will
|
|
* cause the user agent to ignore the attribute.)
|
|
*/
|
|
const domainValueRegExp = /^([.]?[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)([.][a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*$/i;
|
|
/**
|
|
* RegExp to match path-value in RFC 6265 sec 4.1.1
|
|
*
|
|
* path-value = <any CHAR except CTLs or ";">
|
|
* CHAR = %x01-7F
|
|
* ; defined in RFC 5234 appendix B.1
|
|
*/
|
|
const pathValueRegExp = /^[\u0020-\u003A\u003D-\u007E]*$/;
|
|
/**
|
|
* RegExp to match max-age-value in RFC 6265 sec 5.6.2
|
|
*/
|
|
const maxAgeRegExp = /^-?\d+$/;
|
|
const __toString = Object.prototype.toString;
|
|
const NullObject = /* @__PURE__ */ (() => {
|
|
const C = function () { };
|
|
C.prototype = Object.create(null);
|
|
return C;
|
|
})();
|
|
/**
|
|
* Parse a `Cookie` header.
|
|
*
|
|
* Parse the given cookie header string into an object
|
|
* The object has the various cookies as keys(names) => values
|
|
*/
|
|
function parseCookie(str, options) {
|
|
const obj = new NullObject();
|
|
const len = str.length;
|
|
// RFC 6265 sec 4.1.1, RFC 2616 2.2 defines a cookie name consists of one char minimum, plus '='.
|
|
if (len < 2)
|
|
return obj;
|
|
const dec = options?.decode || decode;
|
|
let index = 0;
|
|
do {
|
|
const eqIdx = eqIndex(str, index, len);
|
|
if (eqIdx === -1)
|
|
break; // No more cookie pairs.
|
|
const endIdx = endIndex(str, index, len);
|
|
if (eqIdx > endIdx) {
|
|
// backtrack on prior semicolon
|
|
index = str.lastIndexOf(";", eqIdx - 1) + 1;
|
|
continue;
|
|
}
|
|
const key = valueSlice(str, index, eqIdx);
|
|
// only assign once
|
|
if (obj[key] === undefined) {
|
|
obj[key] = dec(valueSlice(str, eqIdx + 1, endIdx));
|
|
}
|
|
index = endIdx + 1;
|
|
} while (index < len);
|
|
return obj;
|
|
}
|
|
/**
|
|
* Stringifies an object into an HTTP `Cookie` header.
|
|
*/
|
|
function stringifyCookie(cookie, options) {
|
|
const enc = options?.encode || encodeURIComponent;
|
|
const cookieStrings = [];
|
|
for (const name of Object.keys(cookie)) {
|
|
const val = cookie[name];
|
|
if (val === undefined)
|
|
continue;
|
|
if (!cookieNameRegExp.test(name)) {
|
|
throw new TypeError(`cookie name is invalid: ${name}`);
|
|
}
|
|
const value = enc(val);
|
|
if (!cookieValueRegExp.test(value)) {
|
|
throw new TypeError(`cookie val is invalid: ${val}`);
|
|
}
|
|
cookieStrings.push(`${name}=${value}`);
|
|
}
|
|
return cookieStrings.join("; ");
|
|
}
|
|
function stringifySetCookie(_name, _val, _opts) {
|
|
const cookie = typeof _name === "object"
|
|
? _name
|
|
: { ..._opts, name: _name, value: String(_val) };
|
|
const options = typeof _val === "object" ? _val : _opts;
|
|
const enc = options?.encode || encodeURIComponent;
|
|
if (!cookieNameRegExp.test(cookie.name)) {
|
|
throw new TypeError(`argument name is invalid: ${cookie.name}`);
|
|
}
|
|
const value = cookie.value ? enc(cookie.value) : "";
|
|
if (!cookieValueRegExp.test(value)) {
|
|
throw new TypeError(`argument val is invalid: ${cookie.value}`);
|
|
}
|
|
let str = cookie.name + "=" + value;
|
|
if (cookie.maxAge !== undefined) {
|
|
if (!Number.isInteger(cookie.maxAge)) {
|
|
throw new TypeError(`option maxAge is invalid: ${cookie.maxAge}`);
|
|
}
|
|
str += "; Max-Age=" + cookie.maxAge;
|
|
}
|
|
if (cookie.domain) {
|
|
if (!domainValueRegExp.test(cookie.domain)) {
|
|
throw new TypeError(`option domain is invalid: ${cookie.domain}`);
|
|
}
|
|
str += "; Domain=" + cookie.domain;
|
|
}
|
|
if (cookie.path) {
|
|
if (!pathValueRegExp.test(cookie.path)) {
|
|
throw new TypeError(`option path is invalid: ${cookie.path}`);
|
|
}
|
|
str += "; Path=" + cookie.path;
|
|
}
|
|
if (cookie.expires) {
|
|
if (!isDate(cookie.expires) || !Number.isFinite(cookie.expires.valueOf())) {
|
|
throw new TypeError(`option expires is invalid: ${cookie.expires}`);
|
|
}
|
|
str += "; Expires=" + cookie.expires.toUTCString();
|
|
}
|
|
if (cookie.httpOnly) {
|
|
str += "; HttpOnly";
|
|
}
|
|
if (cookie.secure) {
|
|
str += "; Secure";
|
|
}
|
|
if (cookie.partitioned) {
|
|
str += "; Partitioned";
|
|
}
|
|
if (cookie.priority) {
|
|
const priority = typeof cookie.priority === "string"
|
|
? cookie.priority.toLowerCase()
|
|
: undefined;
|
|
switch (priority) {
|
|
case "low":
|
|
str += "; Priority=Low";
|
|
break;
|
|
case "medium":
|
|
str += "; Priority=Medium";
|
|
break;
|
|
case "high":
|
|
str += "; Priority=High";
|
|
break;
|
|
default:
|
|
throw new TypeError(`option priority is invalid: ${cookie.priority}`);
|
|
}
|
|
}
|
|
if (cookie.sameSite) {
|
|
const sameSite = typeof cookie.sameSite === "string"
|
|
? cookie.sameSite.toLowerCase()
|
|
: cookie.sameSite;
|
|
switch (sameSite) {
|
|
case true:
|
|
case "strict":
|
|
str += "; SameSite=Strict";
|
|
break;
|
|
case "lax":
|
|
str += "; SameSite=Lax";
|
|
break;
|
|
case "none":
|
|
str += "; SameSite=None";
|
|
break;
|
|
default:
|
|
throw new TypeError(`option sameSite is invalid: ${cookie.sameSite}`);
|
|
}
|
|
}
|
|
return str;
|
|
}
|
|
/**
|
|
* Deserialize a `Set-Cookie` header into an object.
|
|
*
|
|
* deserialize('foo=bar; httpOnly')
|
|
* => { name: 'foo', value: 'bar', httpOnly: true }
|
|
*/
|
|
function parseSetCookie(str, options) {
|
|
const dec = options?.decode || decode;
|
|
const len = str.length;
|
|
const endIdx = endIndex(str, 0, len);
|
|
const eqIdx = eqIndex(str, 0, endIdx);
|
|
const setCookie = eqIdx === -1
|
|
? { name: "", value: dec(valueSlice(str, 0, endIdx)) }
|
|
: {
|
|
name: valueSlice(str, 0, eqIdx),
|
|
value: dec(valueSlice(str, eqIdx + 1, endIdx)),
|
|
};
|
|
let index = endIdx + 1;
|
|
while (index < len) {
|
|
const endIdx = endIndex(str, index, len);
|
|
const eqIdx = eqIndex(str, index, endIdx);
|
|
const attr = eqIdx === -1
|
|
? valueSlice(str, index, endIdx)
|
|
: valueSlice(str, index, eqIdx);
|
|
const val = eqIdx === -1 ? undefined : valueSlice(str, eqIdx + 1, endIdx);
|
|
switch (attr.toLowerCase()) {
|
|
case "httponly":
|
|
setCookie.httpOnly = true;
|
|
break;
|
|
case "secure":
|
|
setCookie.secure = true;
|
|
break;
|
|
case "partitioned":
|
|
setCookie.partitioned = true;
|
|
break;
|
|
case "domain":
|
|
setCookie.domain = val;
|
|
break;
|
|
case "path":
|
|
setCookie.path = val;
|
|
break;
|
|
case "max-age":
|
|
if (val && maxAgeRegExp.test(val))
|
|
setCookie.maxAge = Number(val);
|
|
break;
|
|
case "expires":
|
|
if (!val)
|
|
break;
|
|
const date = new Date(val);
|
|
if (Number.isFinite(date.valueOf()))
|
|
setCookie.expires = date;
|
|
break;
|
|
case "priority":
|
|
if (!val)
|
|
break;
|
|
const priority = val.toLowerCase();
|
|
if (priority === "low" ||
|
|
priority === "medium" ||
|
|
priority === "high") {
|
|
setCookie.priority = priority;
|
|
}
|
|
break;
|
|
case "samesite":
|
|
if (!val)
|
|
break;
|
|
const sameSite = val.toLowerCase();
|
|
if (sameSite === "lax" ||
|
|
sameSite === "strict" ||
|
|
sameSite === "none") {
|
|
setCookie.sameSite = sameSite;
|
|
}
|
|
break;
|
|
}
|
|
index = endIdx + 1;
|
|
}
|
|
return setCookie;
|
|
}
|
|
/**
|
|
* Find the `;` character between `min` and `len` in str.
|
|
*/
|
|
function endIndex(str, min, len) {
|
|
const index = str.indexOf(";", min);
|
|
return index === -1 ? len : index;
|
|
}
|
|
/**
|
|
* Find the `=` character between `min` and `max` in str.
|
|
*/
|
|
function eqIndex(str, min, max) {
|
|
const index = str.indexOf("=", min);
|
|
return index < max ? index : -1;
|
|
}
|
|
/**
|
|
* Slice out a value between startPod to max.
|
|
*/
|
|
function valueSlice(str, min, max) {
|
|
let start = min;
|
|
let end = max;
|
|
do {
|
|
const code = str.charCodeAt(start);
|
|
if (code !== 0x20 /* */ && code !== 0x09 /* \t */)
|
|
break;
|
|
} while (++start < end);
|
|
while (end > start) {
|
|
const code = str.charCodeAt(end - 1);
|
|
if (code !== 0x20 /* */ && code !== 0x09 /* \t */)
|
|
break;
|
|
end--;
|
|
}
|
|
return str.slice(start, end);
|
|
}
|
|
/**
|
|
* URL-decode string value. Optimized to skip native call when no %.
|
|
*/
|
|
function decode(str) {
|
|
if (str.indexOf("%") === -1)
|
|
return str;
|
|
try {
|
|
return decodeURIComponent(str);
|
|
}
|
|
catch (e) {
|
|
return str;
|
|
}
|
|
}
|
|
/**
|
|
* Determine if value is a Date.
|
|
*/
|
|
function isDate(val) {
|
|
return __toString.call(val) === "[object Date]";
|
|
}
|
|
|
|
return dist;
|
|
}
|
|
|
|
var distExports = /*@__PURE__*/ requireDist();
|
|
|
|
const DELETED_EXPIRATION = /* @__PURE__ */ new Date(0);
|
|
const DELETED_VALUE = "deleted";
|
|
const responseSentSymbol = /* @__PURE__ */ Symbol.for("astro.responseSent");
|
|
const identity = (value) => value;
|
|
class AstroCookie {
|
|
value;
|
|
constructor(value) {
|
|
this.value = value;
|
|
}
|
|
json() {
|
|
if (this.value === void 0) {
|
|
throw new Error(`Cannot convert undefined to an object.`);
|
|
}
|
|
return JSON.parse(this.value);
|
|
}
|
|
number() {
|
|
return Number(this.value);
|
|
}
|
|
boolean() {
|
|
if (this.value === "false") return false;
|
|
if (this.value === "0") return false;
|
|
return Boolean(this.value);
|
|
}
|
|
}
|
|
class AstroCookies {
|
|
#request;
|
|
#requestValues;
|
|
#outgoing;
|
|
#consumed;
|
|
constructor(request) {
|
|
this.#request = request;
|
|
this.#requestValues = null;
|
|
this.#outgoing = null;
|
|
this.#consumed = false;
|
|
}
|
|
/**
|
|
* Astro.cookies.delete(key) is used to delete a cookie. Using this method will result
|
|
* in a Set-Cookie header added to the response.
|
|
* @param key The cookie to delete
|
|
* @param options Options related to this deletion, such as the path of the cookie.
|
|
*/
|
|
delete(key, options) {
|
|
const {
|
|
// @ts-expect-error
|
|
maxAge: _ignoredMaxAge,
|
|
// @ts-expect-error
|
|
expires: _ignoredExpires,
|
|
...sanitizedOptions
|
|
} = options || {};
|
|
const serializeOptions = {
|
|
expires: DELETED_EXPIRATION,
|
|
...sanitizedOptions
|
|
};
|
|
this.#ensureOutgoingMap().set(key, [
|
|
DELETED_VALUE,
|
|
distExports.serialize(key, DELETED_VALUE, serializeOptions),
|
|
false
|
|
]);
|
|
}
|
|
/**
|
|
* Astro.cookies.get(key) is used to get a cookie value. The cookie value is read from the
|
|
* request. If you have set a cookie via Astro.cookies.set(key, value), the value will be taken
|
|
* from that set call, overriding any values already part of the request.
|
|
* @param key The cookie to get.
|
|
* @returns An object containing the cookie value as well as convenience methods for converting its value.
|
|
*/
|
|
get(key, options = void 0) {
|
|
if (this.#outgoing?.has(key)) {
|
|
let [serializedValue, , isSetValue] = this.#outgoing.get(key);
|
|
if (isSetValue) {
|
|
return new AstroCookie(serializedValue);
|
|
} else {
|
|
return void 0;
|
|
}
|
|
}
|
|
const decode = options?.decode ?? decodeURIComponent;
|
|
const values = this.#ensureParsed();
|
|
if (key in values) {
|
|
const value = values[key];
|
|
if (value) {
|
|
let decodedValue;
|
|
try {
|
|
decodedValue = decode(value);
|
|
} catch (_error) {
|
|
decodedValue = value;
|
|
}
|
|
return new AstroCookie(decodedValue);
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Astro.cookies.has(key) returns a boolean indicating whether this cookie is either
|
|
* part of the initial request or set via Astro.cookies.set(key)
|
|
* @param key The cookie to check for.
|
|
* @param _options This parameter is no longer used.
|
|
* @returns
|
|
*/
|
|
has(key, _options) {
|
|
if (this.#outgoing?.has(key)) {
|
|
let [, , isSetValue] = this.#outgoing.get(key);
|
|
return isSetValue;
|
|
}
|
|
const values = this.#ensureParsed();
|
|
return values[key] !== void 0;
|
|
}
|
|
/**
|
|
* Astro.cookies.set(key, value) is used to set a cookie's value. If provided
|
|
* an object it will be stringified via JSON.stringify(value). Additionally you
|
|
* can provide options customizing how this cookie will be set, such as setting httpOnly
|
|
* in order to prevent the cookie from being read in client-side JavaScript.
|
|
* @param key The name of the cookie to set.
|
|
* @param value A value, either a string or other primitive or an object.
|
|
* @param options Options for the cookie, such as the path and security settings.
|
|
*/
|
|
set(key, value, options) {
|
|
if (this.#consumed) {
|
|
const warning = new Error(
|
|
"Astro.cookies.set() was called after the cookies had already been sent to the browser.\nThis may have happened if this method was called in an imported component.\nPlease make sure that Astro.cookies.set() is only called in the frontmatter of the main page."
|
|
);
|
|
warning.name = "Warning";
|
|
console.warn(warning);
|
|
}
|
|
let serializedValue;
|
|
if (typeof value === "string") {
|
|
serializedValue = value;
|
|
} else {
|
|
let toStringValue = value.toString();
|
|
if (toStringValue === Object.prototype.toString.call(value)) {
|
|
serializedValue = JSON.stringify(value);
|
|
} else {
|
|
serializedValue = toStringValue;
|
|
}
|
|
}
|
|
const serializeOptions = {};
|
|
if (options) {
|
|
Object.assign(serializeOptions, options);
|
|
}
|
|
this.#ensureOutgoingMap().set(key, [
|
|
serializedValue,
|
|
distExports.serialize(key, serializedValue, serializeOptions),
|
|
true
|
|
]);
|
|
if (this.#request[responseSentSymbol]) {
|
|
throw new AstroError({
|
|
...ResponseSentError
|
|
});
|
|
}
|
|
}
|
|
/**
|
|
* Merges a new AstroCookies instance into the current instance. Any new cookies
|
|
* will be added to the current instance, overwriting any existing cookies with the same name.
|
|
*/
|
|
merge(cookies) {
|
|
const outgoing = cookies.#outgoing;
|
|
if (outgoing) {
|
|
for (const [key, value] of outgoing) {
|
|
this.#ensureOutgoingMap().set(key, value);
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Astro.cookies.header() returns an iterator for the cookies that have previously
|
|
* been set by either Astro.cookies.set() or Astro.cookies.delete().
|
|
* This method is primarily used by adapters to set the header on outgoing responses.
|
|
* @returns
|
|
*/
|
|
*headers() {
|
|
if (this.#outgoing == null) return;
|
|
for (const [, value] of this.#outgoing) {
|
|
yield value[1];
|
|
}
|
|
}
|
|
/**
|
|
* Behaves the same as AstroCookies.prototype.headers(),
|
|
* but allows a warning when cookies are set after the instance is consumed.
|
|
*/
|
|
static consume(cookies) {
|
|
cookies.#consumed = true;
|
|
return cookies.headers();
|
|
}
|
|
#ensureParsed() {
|
|
if (!this.#requestValues) {
|
|
this.#parse();
|
|
}
|
|
if (!this.#requestValues) {
|
|
this.#requestValues = /* @__PURE__ */ Object.create(null);
|
|
}
|
|
return this.#requestValues;
|
|
}
|
|
#ensureOutgoingMap() {
|
|
if (!this.#outgoing) {
|
|
this.#outgoing = /* @__PURE__ */ new Map();
|
|
}
|
|
return this.#outgoing;
|
|
}
|
|
#parse() {
|
|
const raw = this.#request.headers.get("cookie");
|
|
if (!raw) {
|
|
return;
|
|
}
|
|
this.#requestValues = distExports.parse(raw, { decode: identity });
|
|
}
|
|
}
|
|
|
|
const astroCookiesSymbol = /* @__PURE__ */ Symbol.for("astro.cookies");
|
|
function attachCookiesToResponse(response, cookies) {
|
|
Reflect.set(response, astroCookiesSymbol, cookies);
|
|
}
|
|
function getCookiesFromResponse(response) {
|
|
let cookies = Reflect.get(response, astroCookiesSymbol);
|
|
if (cookies != null) {
|
|
return cookies;
|
|
} else {
|
|
return void 0;
|
|
}
|
|
}
|
|
function* getSetCookiesFromResponse(response) {
|
|
const cookies = getCookiesFromResponse(response);
|
|
if (!cookies) {
|
|
return [];
|
|
}
|
|
for (const headerValue of AstroCookies.consume(cookies)) {
|
|
yield headerValue;
|
|
}
|
|
return [];
|
|
}
|
|
|
|
let e=globalThis.process||{},t=e.argv||[],n=e.env||{},r$1=!(n.NO_COLOR||t.includes(`--no-color`))&&(!!n.FORCE_COLOR||t.includes(`--color`)||e.platform===`win32`||(e.stdout||{}).isTTY&&n.TERM!==`dumb`||!!n.CI),i=(e,t,n=e)=>r=>{let i=``+r,o=i.indexOf(t,e.length);return ~o?e+a(i,t,n,o)+t:e+i+t},a=(e,t,n,r)=>{let i=``,a=0;do i+=e.substring(a,r)+n,a=r+t.length,r=e.indexOf(t,a);while(~r);return i+e.substring(a)},o=(e=r$1)=>{let t=e?i:()=>String;return {isColorSupported:e,reset:t(`\x1B[0m`,`\x1B[0m`),bold:t(`\x1B[1m`,`\x1B[22m`,`\x1B[22m\x1B[1m`),dim:t(`\x1B[2m`,`\x1B[22m`,`\x1B[22m\x1B[2m`),italic:t(`\x1B[3m`,`\x1B[23m`),underline:t(`\x1B[4m`,`\x1B[24m`),inverse:t(`\x1B[7m`,`\x1B[27m`),hidden:t(`\x1B[8m`,`\x1B[28m`),strikethrough:t(`\x1B[9m`,`\x1B[29m`),black:t(`\x1B[30m`,`\x1B[39m`),red:t(`\x1B[31m`,`\x1B[39m`),green:t(`\x1B[32m`,`\x1B[39m`),yellow:t(`\x1B[33m`,`\x1B[39m`),blue:t(`\x1B[34m`,`\x1B[39m`),magenta:t(`\x1B[35m`,`\x1B[39m`),cyan:t(`\x1B[36m`,`\x1B[39m`),white:t(`\x1B[37m`,`\x1B[39m`),gray:t(`\x1B[90m`,`\x1B[39m`),bgBlack:t(`\x1B[40m`,`\x1B[49m`),bgRed:t(`\x1B[41m`,`\x1B[49m`),bgGreen:t(`\x1B[42m`,`\x1B[49m`),bgYellow:t(`\x1B[43m`,`\x1B[49m`),bgBlue:t(`\x1B[44m`,`\x1B[49m`),bgMagenta:t(`\x1B[45m`,`\x1B[49m`),bgCyan:t(`\x1B[46m`,`\x1B[49m`),bgWhite:t(`\x1B[47m`,`\x1B[49m`),blackBright:t(`\x1B[90m`,`\x1B[39m`),redBright:t(`\x1B[91m`,`\x1B[39m`),greenBright:t(`\x1B[92m`,`\x1B[39m`),yellowBright:t(`\x1B[93m`,`\x1B[39m`),blueBright:t(`\x1B[94m`,`\x1B[39m`),magentaBright:t(`\x1B[95m`,`\x1B[39m`),cyanBright:t(`\x1B[96m`,`\x1B[39m`),whiteBright:t(`\x1B[97m`,`\x1B[39m`),bgBlackBright:t(`\x1B[100m`,`\x1B[49m`),bgRedBright:t(`\x1B[101m`,`\x1B[49m`),bgGreenBright:t(`\x1B[102m`,`\x1B[49m`),bgYellowBright:t(`\x1B[103m`,`\x1B[49m`),bgBlueBright:t(`\x1B[104m`,`\x1B[49m`),bgMagentaBright:t(`\x1B[105m`,`\x1B[49m`),bgCyanBright:t(`\x1B[106m`,`\x1B[49m`),bgWhiteBright:t(`\x1B[107m`,`\x1B[49m`)}};var s=o();
|
|
|
|
const dateTimeFormat = new Intl.DateTimeFormat([], {
|
|
hour: "2-digit",
|
|
minute: "2-digit",
|
|
second: "2-digit",
|
|
hour12: false
|
|
});
|
|
const levels = {
|
|
debug: 20,
|
|
info: 30,
|
|
warn: 40,
|
|
error: 50,
|
|
silent: 90
|
|
};
|
|
function log(opts, level, label, message, newLine = true) {
|
|
const logLevel = opts.level;
|
|
const dest = opts.destination;
|
|
const event = {
|
|
label,
|
|
level,
|
|
message,
|
|
newLine,
|
|
_format: opts._format
|
|
};
|
|
if (!isLogLevelEnabled(logLevel, level)) {
|
|
return;
|
|
}
|
|
dest.write(event);
|
|
}
|
|
function isLogLevelEnabled(configuredLogLevel, level) {
|
|
return levels[configuredLogLevel] <= levels[level];
|
|
}
|
|
function info(opts, label, message, newLine = true) {
|
|
return log(opts, "info", label, message, newLine);
|
|
}
|
|
function warn(opts, label, message, newLine = true) {
|
|
return log(opts, "warn", label, message, newLine);
|
|
}
|
|
function error(opts, label, message, newLine = true) {
|
|
return log(opts, "error", label, message, newLine);
|
|
}
|
|
function debug(...args) {
|
|
if ("_astroGlobalDebug" in globalThis) {
|
|
globalThis._astroGlobalDebug(...args);
|
|
}
|
|
}
|
|
function getEventPrefix({ level, label }) {
|
|
const timestamp = `${dateTimeFormat.format(/* @__PURE__ */ new Date())}`;
|
|
const prefix = [];
|
|
if (level === "error" || level === "warn") {
|
|
prefix.push(s.bold(timestamp));
|
|
prefix.push(`[${level.toUpperCase()}]`);
|
|
} else {
|
|
prefix.push(timestamp);
|
|
}
|
|
if (label) {
|
|
prefix.push(`[${label}]`);
|
|
}
|
|
if (level === "error") {
|
|
return s.red(prefix.join(" "));
|
|
}
|
|
if (level === "warn") {
|
|
return s.yellow(prefix.join(" "));
|
|
}
|
|
if (prefix.length === 1) {
|
|
return s.dim(prefix[0]);
|
|
}
|
|
return s.dim(prefix[0]) + " " + s.blue(prefix.splice(1).join(" "));
|
|
}
|
|
class AstroLogger {
|
|
options;
|
|
constructor(options) {
|
|
if (!options._format) {
|
|
options._format = "default";
|
|
}
|
|
this.options = options;
|
|
}
|
|
info(label, message, newLine = true) {
|
|
info(this.options, label, message, newLine);
|
|
}
|
|
warn(label, message, newLine = true) {
|
|
warn(this.options, label, message, newLine);
|
|
}
|
|
error(label, message, newLine = true) {
|
|
error(this.options, label, message, newLine);
|
|
}
|
|
debug(label, ...messages) {
|
|
debug(label, ...messages);
|
|
}
|
|
level() {
|
|
return this.options.level;
|
|
}
|
|
forkIntegrationLogger(label) {
|
|
return new AstroIntegrationLogger(this.options, label);
|
|
}
|
|
}
|
|
class AstroIntegrationLogger {
|
|
options;
|
|
label;
|
|
constructor(logging, label) {
|
|
this.options = logging;
|
|
this.label = label;
|
|
}
|
|
/**
|
|
* Creates a new logger instance with a new label, but the same log options.
|
|
*/
|
|
fork(label) {
|
|
return new AstroIntegrationLogger(this.options, label);
|
|
}
|
|
info(message) {
|
|
info(this.options, this.label, message);
|
|
}
|
|
warn(message) {
|
|
warn(this.options, this.label, message);
|
|
}
|
|
error(message) {
|
|
error(this.options, this.label, message);
|
|
}
|
|
debug(message) {
|
|
debug(this.label, message);
|
|
}
|
|
}
|
|
|
|
const consoleLogDestination = {
|
|
write(event) {
|
|
let dest = console.error;
|
|
if (levels[event.level] < levels["error"]) {
|
|
dest = console.info;
|
|
}
|
|
if (event.label === "SKIP_FORMAT") {
|
|
dest(event.message);
|
|
} else {
|
|
dest(getEventPrefix(event) + " " + event.message);
|
|
}
|
|
return true;
|
|
}
|
|
};
|
|
|
|
/** @type {Record<string, string>} */
|
|
|
|
class DevalueError extends Error {
|
|
/**
|
|
* @param {string} message
|
|
* @param {string[]} keys
|
|
* @param {any} [value] - The value that failed to be serialized
|
|
* @param {any} [root] - The root value being serialized
|
|
*/
|
|
constructor(message, keys, value, root) {
|
|
super(message);
|
|
this.name = 'DevalueError';
|
|
this.path = keys.join('');
|
|
this.value = value;
|
|
this.root = root;
|
|
}
|
|
}
|
|
|
|
/** @param {any} thing */
|
|
function is_primitive(thing) {
|
|
return thing === null || (typeof thing !== 'object' && typeof thing !== 'function');
|
|
}
|
|
|
|
const object_proto_names = /* @__PURE__ */ Object.getOwnPropertyNames(Object.prototype)
|
|
.sort()
|
|
.join('\0');
|
|
|
|
/** @param {any} thing */
|
|
function is_plain_object(thing) {
|
|
const proto = Object.getPrototypeOf(thing);
|
|
|
|
return (
|
|
proto === Object.prototype ||
|
|
proto === null ||
|
|
Object.getPrototypeOf(proto) === null ||
|
|
Object.getOwnPropertyNames(proto).sort().join('\0') === object_proto_names
|
|
);
|
|
}
|
|
|
|
/** @param {any} thing */
|
|
function get_type(thing) {
|
|
return Object.prototype.toString.call(thing).slice(8, -1);
|
|
}
|
|
|
|
/** @param {string} char */
|
|
function get_escaped_char(char) {
|
|
switch (char) {
|
|
case '"':
|
|
return '\\"';
|
|
case '<':
|
|
return '\\u003C';
|
|
case '\\':
|
|
return '\\\\';
|
|
case '\n':
|
|
return '\\n';
|
|
case '\r':
|
|
return '\\r';
|
|
case '\t':
|
|
return '\\t';
|
|
case '\b':
|
|
return '\\b';
|
|
case '\f':
|
|
return '\\f';
|
|
case '\u2028':
|
|
return '\\u2028';
|
|
case '\u2029':
|
|
return '\\u2029';
|
|
default:
|
|
return char < ' ' ? `\\u${char.charCodeAt(0).toString(16).padStart(4, '0')}` : '';
|
|
}
|
|
}
|
|
|
|
/** @param {string} str */
|
|
function stringify_string(str) {
|
|
let result = '';
|
|
let last_pos = 0;
|
|
const len = str.length;
|
|
|
|
for (let i = 0; i < len; i += 1) {
|
|
const char = str[i];
|
|
const replacement = get_escaped_char(char);
|
|
if (replacement) {
|
|
result += str.slice(last_pos, i) + replacement;
|
|
last_pos = i + 1;
|
|
}
|
|
}
|
|
|
|
return `"${last_pos === 0 ? str : result + str.slice(last_pos)}"`;
|
|
}
|
|
|
|
/** @param {Record<string | symbol, any>} object */
|
|
function enumerable_symbols(object) {
|
|
return Object.getOwnPropertySymbols(object).filter(
|
|
(symbol) => Object.getOwnPropertyDescriptor(object, symbol).enumerable
|
|
);
|
|
}
|
|
|
|
const is_identifier = /^[a-zA-Z_$][a-zA-Z_$0-9]*$/;
|
|
|
|
/** @param {string} key */
|
|
function stringify_key(key) {
|
|
return is_identifier.test(key) ? '.' + key : '[' + JSON.stringify(key) + ']';
|
|
}
|
|
|
|
/** @param {string} s */
|
|
function is_valid_array_index(s) {
|
|
if (s.length === 0) return false;
|
|
if (s.length > 1 && s.charCodeAt(0) === 48) return false; // leading zero
|
|
for (let i = 0; i < s.length; i++) {
|
|
const c = s.charCodeAt(i);
|
|
if (c < 48 || c > 57) return false;
|
|
}
|
|
// by this point we know it's a string of digits, but it has to be within the range of valid array indices
|
|
const n = +s;
|
|
if (n >= 2 ** 32 - 1) return false;
|
|
if (n < 0) return false;
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Finds the populated indices of an array.
|
|
* @param {unknown[]} array
|
|
*/
|
|
function valid_array_indices(array) {
|
|
const keys = Object.keys(array);
|
|
for (var i = keys.length - 1; i >= 0; i--) {
|
|
if (is_valid_array_index(keys[i])) {
|
|
break;
|
|
}
|
|
}
|
|
keys.length = i + 1;
|
|
return keys;
|
|
}
|
|
|
|
/* Baseline 2025 runtimes */
|
|
|
|
/** @type {(array_buffer: ArrayBuffer) => string} */
|
|
function encode_native(array_buffer) {
|
|
return new Uint8Array(array_buffer).toBase64();
|
|
}
|
|
|
|
/** @type {(base64: string) => ArrayBuffer} */
|
|
function decode_native(base64) {
|
|
return Uint8Array.fromBase64(base64).buffer;
|
|
}
|
|
|
|
/* Node-compatible runtimes */
|
|
|
|
/** @type {(array_buffer: ArrayBuffer) => string} */
|
|
function encode_buffer(array_buffer) {
|
|
return Buffer.from(array_buffer).toString('base64');
|
|
}
|
|
|
|
/** @type {(base64: string) => ArrayBuffer} */
|
|
function decode_buffer(base64) {
|
|
return Uint8Array.from(Buffer.from(base64, 'base64')).buffer;
|
|
}
|
|
|
|
/* Legacy runtimes */
|
|
|
|
/** @type {(array_buffer: ArrayBuffer) => string} */
|
|
function encode_legacy(array_buffer) {
|
|
const array = new Uint8Array(array_buffer);
|
|
let binary = '';
|
|
|
|
// the maximum number of arguments to String.fromCharCode.apply
|
|
// should be around 0xFFFF in modern engines
|
|
const chunk_size = 0x8000;
|
|
for (let i = 0; i < array.length; i += chunk_size) {
|
|
const chunk = array.subarray(i, i + chunk_size);
|
|
binary += String.fromCharCode.apply(null, chunk);
|
|
}
|
|
|
|
return btoa(binary);
|
|
}
|
|
|
|
/** @type {(base64: string) => ArrayBuffer} */
|
|
function decode_legacy(base64) {
|
|
const binary_string = atob(base64);
|
|
const len = binary_string.length;
|
|
const array = new Uint8Array(len);
|
|
|
|
for (let i = 0; i < len; i++) {
|
|
array[i] = binary_string.charCodeAt(i);
|
|
}
|
|
|
|
return array.buffer;
|
|
}
|
|
|
|
const native = typeof Uint8Array.fromBase64 === 'function';
|
|
const buffer = typeof process === 'object' && process.versions?.node !== undefined;
|
|
|
|
const encode64 = native ? encode_native : buffer ? encode_buffer : encode_legacy;
|
|
const decode64 = native ? decode_native : buffer ? decode_buffer : decode_legacy;
|
|
|
|
const UNDEFINED = -1;
|
|
const HOLE = -2;
|
|
const NAN = -3;
|
|
const POSITIVE_INFINITY = -4;
|
|
const NEGATIVE_INFINITY = -5;
|
|
const NEGATIVE_ZERO = -6;
|
|
const SPARSE = -7;
|
|
|
|
/**
|
|
* Revive a value serialized with `devalue.stringify`
|
|
* @param {string} serialized
|
|
* @param {Record<string, (value: any) => any>} [revivers]
|
|
*/
|
|
function parse$1(serialized, revivers) {
|
|
return unflatten$1(JSON.parse(serialized), revivers);
|
|
}
|
|
|
|
/**
|
|
* Revive a value flattened with `devalue.stringify`
|
|
* @param {number | any[]} parsed
|
|
* @param {Record<string, (value: any) => any>} [revivers]
|
|
*/
|
|
function unflatten$1(parsed, revivers) {
|
|
if (typeof parsed === 'number') return hydrate(parsed, true);
|
|
|
|
if (!Array.isArray(parsed) || parsed.length === 0) {
|
|
throw new Error('Invalid input');
|
|
}
|
|
|
|
const values = /** @type {any[]} */ (parsed);
|
|
|
|
const hydrated = Array(values.length);
|
|
|
|
/**
|
|
* A set of values currently being hydrated with custom revivers,
|
|
* used to detect invalid cyclical dependencies
|
|
* @type {Set<number> | null}
|
|
*/
|
|
let hydrating = null;
|
|
|
|
/**
|
|
* @param {number} index
|
|
* @returns {any}
|
|
*/
|
|
function hydrate(index, standalone = false) {
|
|
if (index === UNDEFINED) return undefined;
|
|
if (index === NAN) return NaN;
|
|
if (index === POSITIVE_INFINITY) return Infinity;
|
|
if (index === NEGATIVE_INFINITY) return -Infinity;
|
|
if (index === NEGATIVE_ZERO) return -0;
|
|
|
|
if (standalone || typeof index !== 'number') {
|
|
throw new Error(`Invalid input`);
|
|
}
|
|
|
|
if (index in hydrated) return hydrated[index];
|
|
|
|
const value = values[index];
|
|
|
|
if (!value || typeof value !== 'object') {
|
|
hydrated[index] = value;
|
|
} else if (Array.isArray(value)) {
|
|
if (typeof value[0] === 'string') {
|
|
const type = value[0];
|
|
|
|
const reviver = revivers && Object.hasOwn(revivers, type) ? revivers[type] : undefined;
|
|
|
|
if (reviver) {
|
|
let i = value[1];
|
|
if (typeof i !== 'number') {
|
|
// if it's not a number, it was serialized by a builtin reviver
|
|
// so we need to munge it into the format expected by a custom reviver
|
|
i = values.push(value[1]) - 1;
|
|
}
|
|
|
|
hydrating ??= new Set();
|
|
|
|
if (hydrating.has(i)) {
|
|
throw new Error('Invalid circular reference');
|
|
}
|
|
|
|
hydrating.add(i);
|
|
hydrated[index] = reviver(hydrate(i));
|
|
hydrating.delete(i);
|
|
|
|
return hydrated[index];
|
|
}
|
|
|
|
switch (type) {
|
|
case 'Date':
|
|
hydrated[index] = new Date(value[1]);
|
|
break;
|
|
|
|
case 'Set':
|
|
const set = new Set();
|
|
hydrated[index] = set;
|
|
for (let i = 1; i < value.length; i += 1) {
|
|
set.add(hydrate(value[i]));
|
|
}
|
|
break;
|
|
|
|
case 'Map':
|
|
const map = new Map();
|
|
hydrated[index] = map;
|
|
for (let i = 1; i < value.length; i += 2) {
|
|
map.set(hydrate(value[i]), hydrate(value[i + 1]));
|
|
}
|
|
break;
|
|
|
|
case 'RegExp':
|
|
hydrated[index] = new RegExp(value[1], value[2]);
|
|
break;
|
|
|
|
case 'Object': {
|
|
const wrapped_index = value[1];
|
|
|
|
if (
|
|
typeof values[wrapped_index] === 'object' &&
|
|
values[wrapped_index][0] !== 'BigInt'
|
|
) {
|
|
// avoid infinite recusion in case of malformed input
|
|
throw new Error('Invalid input');
|
|
}
|
|
|
|
hydrated[index] = Object(hydrate(wrapped_index));
|
|
break;
|
|
}
|
|
|
|
case 'BigInt':
|
|
hydrated[index] = BigInt(value[1]);
|
|
break;
|
|
|
|
case 'null':
|
|
const obj = Object.create(null);
|
|
hydrated[index] = obj;
|
|
for (let i = 1; i < value.length; i += 2) {
|
|
if (value[i] === '__proto__') {
|
|
throw new Error('Cannot parse an object with a `__proto__` property');
|
|
}
|
|
|
|
obj[value[i]] = hydrate(value[i + 1]);
|
|
}
|
|
break;
|
|
|
|
case 'Int8Array':
|
|
case 'Uint8Array':
|
|
case 'Uint8ClampedArray':
|
|
case 'Int16Array':
|
|
case 'Uint16Array':
|
|
case 'Float16Array':
|
|
case 'Int32Array':
|
|
case 'Uint32Array':
|
|
case 'Float32Array':
|
|
case 'Float64Array':
|
|
case 'BigInt64Array':
|
|
case 'BigUint64Array':
|
|
case 'DataView': {
|
|
if (values[value[1]][0] !== 'ArrayBuffer') {
|
|
// without this, if we receive malformed input we could
|
|
// end up trying to hydrate in a circle or allocate
|
|
// huge amounts of memory when we call `new TypedArrayConstructor(buffer)`
|
|
throw new Error('Invalid data');
|
|
}
|
|
|
|
const TypedArrayConstructor = globalThis[type];
|
|
const buffer = hydrate(value[1]);
|
|
|
|
hydrated[index] =
|
|
value[2] !== undefined
|
|
? new TypedArrayConstructor(buffer, value[2], value[3])
|
|
: new TypedArrayConstructor(buffer);
|
|
|
|
break;
|
|
}
|
|
|
|
case 'ArrayBuffer': {
|
|
const base64 = value[1];
|
|
if (typeof base64 !== 'string') {
|
|
throw new Error('Invalid ArrayBuffer encoding');
|
|
}
|
|
const arraybuffer = decode64(base64);
|
|
hydrated[index] = arraybuffer;
|
|
break;
|
|
}
|
|
|
|
case 'Temporal.Duration':
|
|
case 'Temporal.Instant':
|
|
case 'Temporal.PlainDate':
|
|
case 'Temporal.PlainTime':
|
|
case 'Temporal.PlainDateTime':
|
|
case 'Temporal.PlainMonthDay':
|
|
case 'Temporal.PlainYearMonth':
|
|
case 'Temporal.ZonedDateTime': {
|
|
const temporalName = type.slice(9);
|
|
// @ts-expect-error TS doesn't know about Temporal yet
|
|
hydrated[index] = Temporal[temporalName].from(value[1]);
|
|
break;
|
|
}
|
|
|
|
case 'URL': {
|
|
const url = new URL(value[1]);
|
|
hydrated[index] = url;
|
|
break;
|
|
}
|
|
|
|
case 'URLSearchParams': {
|
|
const url = new URLSearchParams(value[1]);
|
|
hydrated[index] = url;
|
|
break;
|
|
}
|
|
|
|
default:
|
|
throw new Error(`Unknown type ${type}`);
|
|
}
|
|
} else if (value[0] === SPARSE) {
|
|
// Sparse array encoding: [SPARSE, length, idx, val, idx, val, ...]
|
|
const len = value[1];
|
|
|
|
if (!Number.isInteger(len) || len < 0) {
|
|
throw new Error('Invalid input');
|
|
}
|
|
|
|
const array = new Array(len);
|
|
hydrated[index] = array;
|
|
|
|
for (let i = 2; i < value.length; i += 2) {
|
|
const idx = value[i];
|
|
|
|
if (!Number.isInteger(idx) || idx < 0 || idx >= len) {
|
|
throw new Error('Invalid input');
|
|
}
|
|
|
|
array[idx] = hydrate(value[i + 1]);
|
|
}
|
|
} else {
|
|
const array = new Array(value.length);
|
|
hydrated[index] = array;
|
|
|
|
for (let i = 0; i < value.length; i += 1) {
|
|
const n = value[i];
|
|
if (n === HOLE) continue;
|
|
|
|
array[i] = hydrate(n);
|
|
}
|
|
}
|
|
} else {
|
|
/** @type {Record<string, any>} */
|
|
const object = {};
|
|
hydrated[index] = object;
|
|
|
|
for (const key of Object.keys(value)) {
|
|
if (key === '__proto__') {
|
|
throw new Error('Cannot parse an object with a `__proto__` property');
|
|
}
|
|
|
|
const n = value[key];
|
|
object[key] = hydrate(n);
|
|
}
|
|
}
|
|
|
|
return hydrated[index];
|
|
}
|
|
|
|
return hydrate(0);
|
|
}
|
|
|
|
/**
|
|
* Turn a value into a JSON string that can be parsed with `devalue.parse`
|
|
* @param {any} value
|
|
* @param {Record<string, (value: any) => any>} [reducers]
|
|
*/
|
|
function stringify$2(value, reducers) {
|
|
/** @type {any[]} */
|
|
const stringified = [];
|
|
|
|
/** @type {Map<any, number>} */
|
|
const indexes = new Map();
|
|
|
|
/** @type {Array<{ key: string, fn: (value: any) => any }>} */
|
|
const custom = [];
|
|
if (reducers) {
|
|
for (const key of Object.getOwnPropertyNames(reducers)) {
|
|
custom.push({ key, fn: reducers[key] });
|
|
}
|
|
}
|
|
|
|
/** @type {string[]} */
|
|
const keys = [];
|
|
|
|
let p = 0;
|
|
|
|
/** @param {any} thing */
|
|
function flatten(thing) {
|
|
if (thing === undefined) return UNDEFINED;
|
|
if (Number.isNaN(thing)) return NAN;
|
|
if (thing === Infinity) return POSITIVE_INFINITY;
|
|
if (thing === -Infinity) return NEGATIVE_INFINITY;
|
|
if (thing === 0 && 1 / thing < 0) return NEGATIVE_ZERO;
|
|
|
|
if (indexes.has(thing)) return /** @type {number} */ (indexes.get(thing));
|
|
|
|
const index = p++;
|
|
indexes.set(thing, index);
|
|
|
|
for (const { key, fn } of custom) {
|
|
const value = fn(thing);
|
|
if (value) {
|
|
stringified[index] = `["${key}",${flatten(value)}]`;
|
|
return index;
|
|
}
|
|
}
|
|
|
|
if (typeof thing === 'function') {
|
|
throw new DevalueError(`Cannot stringify a function`, keys, thing, value);
|
|
} else if (typeof thing === 'symbol') {
|
|
throw new DevalueError(`Cannot stringify a Symbol primitive`, keys, thing, value);
|
|
}
|
|
|
|
let str = '';
|
|
|
|
if (is_primitive(thing)) {
|
|
str = stringify_primitive(thing);
|
|
} else {
|
|
const type = get_type(thing);
|
|
|
|
switch (type) {
|
|
case 'Number':
|
|
case 'String':
|
|
case 'Boolean':
|
|
case 'BigInt':
|
|
str = `["Object",${flatten(thing.valueOf())}]`;
|
|
break;
|
|
|
|
case 'Date':
|
|
const valid = !isNaN(thing.getDate());
|
|
str = `["Date","${valid ? thing.toISOString() : ''}"]`;
|
|
break;
|
|
|
|
case 'URL':
|
|
str = `["URL",${stringify_string(thing.toString())}]`;
|
|
break;
|
|
|
|
case 'URLSearchParams':
|
|
str = `["URLSearchParams",${stringify_string(thing.toString())}]`;
|
|
break;
|
|
|
|
case 'RegExp':
|
|
const { source, flags } = thing;
|
|
str = flags
|
|
? `["RegExp",${stringify_string(source)},"${flags}"]`
|
|
: `["RegExp",${stringify_string(source)}]`;
|
|
break;
|
|
|
|
case 'Array': {
|
|
// For dense arrays (no holes), we iterate normally.
|
|
// When we encounter the first hole, we call Object.keys
|
|
// to determine the sparseness, then decide between:
|
|
// - HOLE encoding: [-2, val, -2, ...] (default)
|
|
// - Sparse encoding: [-7, length, idx, val, ...] (for very sparse arrays)
|
|
// Only the sparse path avoids iterating every slot, which
|
|
// is what protects against the DoS of e.g. `arr[1000000] = 1`.
|
|
let mostly_dense = false;
|
|
|
|
str = '[';
|
|
|
|
for (let i = 0; i < thing.length; i += 1) {
|
|
if (i > 0) str += ',';
|
|
|
|
if (Object.hasOwn(thing, i)) {
|
|
keys.push(`[${i}]`);
|
|
str += flatten(thing[i]);
|
|
keys.pop();
|
|
} else if (mostly_dense) {
|
|
// Use dense encoding. The heuristic guarantees the
|
|
// array is only mildly sparse, so iterating over every
|
|
// slot is fine.
|
|
str += HOLE;
|
|
} else {
|
|
// Decide between HOLE encoding and sparse encoding.
|
|
//
|
|
// HOLE encoding: each hole is serialized as the HOLE
|
|
// sentinel (-2). For example, [, "a", ,] becomes
|
|
// [-2, 0, -2]. Each hole costs 3 chars ("-2" + comma).
|
|
//
|
|
// Sparse encoding: lists only populated indices.
|
|
// For example, [, "a", ,] becomes [-7, 3, 1, 0] — the
|
|
// -7 sentinel, the array length (3), then index-value
|
|
// pairs. This avoids paying per-hole, but each element
|
|
// costs extra chars to write its index.
|
|
//
|
|
// The values are the same size either way, so the
|
|
// choice comes down to structural overhead:
|
|
//
|
|
// HOLE overhead:
|
|
// 3 chars per hole ("-2" + comma)
|
|
// = (L - P) * 3
|
|
//
|
|
// Sparse overhead:
|
|
// "-7," — 3 chars (sparse sentinel + comma)
|
|
// + length + "," — (d + 1) chars (array length + comma)
|
|
// + per element: index + "," — (d + 1) chars
|
|
// = (4 + d) + P * (d + 1)
|
|
//
|
|
// where L is the array length, P is the number of
|
|
// populated elements, and d is the number of digits
|
|
// in L (an upper bound on the digits in any index).
|
|
//
|
|
// Sparse encoding is cheaper when:
|
|
// (4 + d) + P * (d + 1) < (L - P) * 3
|
|
const populated_keys = valid_array_indices(/** @type {any[]} */ (thing));
|
|
const population = populated_keys.length;
|
|
const d = String(thing.length).length;
|
|
|
|
const hole_cost = (thing.length - population) * 3;
|
|
const sparse_cost = 4 + d + population * (d + 1);
|
|
|
|
if (hole_cost > sparse_cost) {
|
|
str = '[' + SPARSE + ',' + thing.length;
|
|
for (let j = 0; j < populated_keys.length; j++) {
|
|
const key = populated_keys[j];
|
|
keys.push(`[${key}]`);
|
|
str += ',' + key + ',' + flatten(thing[key]);
|
|
keys.pop();
|
|
}
|
|
break;
|
|
} else {
|
|
mostly_dense = true;
|
|
str += HOLE;
|
|
}
|
|
}
|
|
}
|
|
|
|
str += ']';
|
|
|
|
break;
|
|
}
|
|
|
|
case 'Set':
|
|
str = '["Set"';
|
|
|
|
for (const value of thing) {
|
|
str += `,${flatten(value)}`;
|
|
}
|
|
|
|
str += ']';
|
|
break;
|
|
|
|
case 'Map':
|
|
str = '["Map"';
|
|
|
|
for (const [key, value] of thing) {
|
|
keys.push(`.get(${is_primitive(key) ? stringify_primitive(key) : '...'})`);
|
|
str += `,${flatten(key)},${flatten(value)}`;
|
|
keys.pop();
|
|
}
|
|
|
|
str += ']';
|
|
break;
|
|
|
|
case 'Int8Array':
|
|
case 'Uint8Array':
|
|
case 'Uint8ClampedArray':
|
|
case 'Int16Array':
|
|
case 'Uint16Array':
|
|
case 'Float16Array':
|
|
case 'Int32Array':
|
|
case 'Uint32Array':
|
|
case 'Float32Array':
|
|
case 'Float64Array':
|
|
case 'BigInt64Array':
|
|
case 'BigUint64Array':
|
|
case 'DataView': {
|
|
/** @type {import("./types.js").TypedArray} */
|
|
const typedArray = thing;
|
|
str = '["' + type + '",' + flatten(typedArray.buffer);
|
|
|
|
// handle subarrays
|
|
if (typedArray.byteLength !== typedArray.buffer.byteLength) {
|
|
// to be used with `new TypedArray(buffer, byteOffset, length)`
|
|
str += `,${typedArray.byteOffset},${typedArray.length}`;
|
|
}
|
|
|
|
str += ']';
|
|
break;
|
|
}
|
|
|
|
case 'ArrayBuffer': {
|
|
/** @type {ArrayBuffer} */
|
|
const arraybuffer = thing;
|
|
const base64 = encode64(arraybuffer);
|
|
|
|
str = `["ArrayBuffer","${base64}"]`;
|
|
break;
|
|
}
|
|
|
|
case 'Temporal.Duration':
|
|
case 'Temporal.Instant':
|
|
case 'Temporal.PlainDate':
|
|
case 'Temporal.PlainTime':
|
|
case 'Temporal.PlainDateTime':
|
|
case 'Temporal.PlainMonthDay':
|
|
case 'Temporal.PlainYearMonth':
|
|
case 'Temporal.ZonedDateTime':
|
|
str = `["${type}",${stringify_string(thing.toString())}]`;
|
|
break;
|
|
|
|
default:
|
|
if (!is_plain_object(thing)) {
|
|
throw new DevalueError(`Cannot stringify arbitrary non-POJOs`, keys, thing, value);
|
|
}
|
|
|
|
if (enumerable_symbols(thing).length > 0) {
|
|
throw new DevalueError(`Cannot stringify POJOs with symbolic keys`, keys, thing, value);
|
|
}
|
|
|
|
if (Object.getPrototypeOf(thing) === null) {
|
|
str = '["null"';
|
|
for (const key of Object.keys(thing)) {
|
|
if (key === '__proto__') {
|
|
throw new DevalueError(
|
|
`Cannot stringify objects with __proto__ keys`,
|
|
keys,
|
|
thing,
|
|
value
|
|
);
|
|
}
|
|
|
|
keys.push(stringify_key(key));
|
|
str += `,${stringify_string(key)},${flatten(thing[key])}`;
|
|
keys.pop();
|
|
}
|
|
str += ']';
|
|
} else {
|
|
str = '{';
|
|
let started = false;
|
|
for (const key of Object.keys(thing)) {
|
|
if (key === '__proto__') {
|
|
throw new DevalueError(
|
|
`Cannot stringify objects with __proto__ keys`,
|
|
keys,
|
|
thing,
|
|
value
|
|
);
|
|
}
|
|
|
|
if (started) str += ',';
|
|
started = true;
|
|
keys.push(stringify_key(key));
|
|
str += `${stringify_string(key)}:${flatten(thing[key])}`;
|
|
keys.pop();
|
|
}
|
|
str += '}';
|
|
}
|
|
}
|
|
}
|
|
|
|
stringified[index] = str;
|
|
return index;
|
|
}
|
|
|
|
const index = flatten(value);
|
|
|
|
// special case — value is represented as a negative index
|
|
if (index < 0) return `${index}`;
|
|
|
|
return `[${stringified.join(',')}]`;
|
|
}
|
|
|
|
/**
|
|
* @param {any} thing
|
|
* @returns {string}
|
|
*/
|
|
function stringify_primitive(thing) {
|
|
const type = typeof thing;
|
|
if (type === 'string') return stringify_string(thing);
|
|
if (thing === void 0) return UNDEFINED.toString();
|
|
if (thing === 0 && 1 / thing < 0) return NEGATIVE_ZERO.toString();
|
|
if (type === 'bigint') return `["BigInt","${thing}"]`;
|
|
return String(thing);
|
|
}
|
|
|
|
const ACTION_QUERY_PARAMS = {
|
|
actionName: "_action"};
|
|
const ACTION_RPC_ROUTE_PATTERN = "/_actions/[...path]";
|
|
|
|
const __vite_import_meta_env__$1 = {"ASSETS_PREFIX": undefined, "BASE_URL": "/", "DEV": false, "MODE": "production", "PROD": true, "SITE": "https://www.upiecep.org", "SSR": true};
|
|
const codeToStatusMap = {
|
|
// Implemented from IANA HTTP Status Code Registry
|
|
// https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
|
|
BAD_REQUEST: 400,
|
|
UNAUTHORIZED: 401,
|
|
PAYMENT_REQUIRED: 402,
|
|
FORBIDDEN: 403,
|
|
NOT_FOUND: 404,
|
|
METHOD_NOT_ALLOWED: 405,
|
|
NOT_ACCEPTABLE: 406,
|
|
PROXY_AUTHENTICATION_REQUIRED: 407,
|
|
REQUEST_TIMEOUT: 408,
|
|
CONFLICT: 409,
|
|
GONE: 410,
|
|
LENGTH_REQUIRED: 411,
|
|
PRECONDITION_FAILED: 412,
|
|
CONTENT_TOO_LARGE: 413,
|
|
URI_TOO_LONG: 414,
|
|
UNSUPPORTED_MEDIA_TYPE: 415,
|
|
RANGE_NOT_SATISFIABLE: 416,
|
|
EXPECTATION_FAILED: 417,
|
|
MISDIRECTED_REQUEST: 421,
|
|
UNPROCESSABLE_CONTENT: 422,
|
|
LOCKED: 423,
|
|
FAILED_DEPENDENCY: 424,
|
|
TOO_EARLY: 425,
|
|
UPGRADE_REQUIRED: 426,
|
|
PRECONDITION_REQUIRED: 428,
|
|
TOO_MANY_REQUESTS: 429,
|
|
REQUEST_HEADER_FIELDS_TOO_LARGE: 431,
|
|
UNAVAILABLE_FOR_LEGAL_REASONS: 451,
|
|
INTERNAL_SERVER_ERROR: 500,
|
|
NOT_IMPLEMENTED: 501,
|
|
BAD_GATEWAY: 502,
|
|
SERVICE_UNAVAILABLE: 503,
|
|
GATEWAY_TIMEOUT: 504,
|
|
HTTP_VERSION_NOT_SUPPORTED: 505,
|
|
VARIANT_ALSO_NEGOTIATES: 506,
|
|
INSUFFICIENT_STORAGE: 507,
|
|
LOOP_DETECTED: 508,
|
|
NETWORK_AUTHENTICATION_REQUIRED: 511
|
|
};
|
|
const statusToCodeMap = Object.fromEntries(
|
|
Object.entries(codeToStatusMap).map(([key, value]) => [value, key])
|
|
);
|
|
class ActionError extends Error {
|
|
type = "AstroActionError";
|
|
code = "INTERNAL_SERVER_ERROR";
|
|
status = 500;
|
|
constructor(params) {
|
|
super(params.message);
|
|
this.code = params.code;
|
|
this.status = ActionError.codeToStatus(params.code);
|
|
if (params.stack) {
|
|
this.stack = params.stack;
|
|
}
|
|
}
|
|
static codeToStatus(code) {
|
|
return codeToStatusMap[code];
|
|
}
|
|
static statusToCode(status) {
|
|
return statusToCodeMap[status] ?? "INTERNAL_SERVER_ERROR";
|
|
}
|
|
static fromJson(body) {
|
|
if (isInputError(body)) {
|
|
return new ActionInputError(body.issues);
|
|
}
|
|
if (isActionError(body)) {
|
|
return new ActionError(body);
|
|
}
|
|
return new ActionError({
|
|
code: "INTERNAL_SERVER_ERROR"
|
|
});
|
|
}
|
|
}
|
|
function isActionError(error) {
|
|
return typeof error === "object" && error != null && "type" in error && error.type === "AstroActionError";
|
|
}
|
|
function isInputError(error) {
|
|
return typeof error === "object" && error != null && "type" in error && error.type === "AstroActionInputError" && "issues" in error && Array.isArray(error.issues);
|
|
}
|
|
class ActionInputError extends ActionError {
|
|
type = "AstroActionInputError";
|
|
// We don't expose all ZodError properties.
|
|
// Not all properties will serialize from server to client,
|
|
// and we don't want to import the full ZodError object into the client.
|
|
issues;
|
|
fields;
|
|
constructor(issues) {
|
|
super({
|
|
message: `Failed to validate: ${JSON.stringify(issues, null, 2)}`,
|
|
code: "BAD_REQUEST"
|
|
});
|
|
this.issues = issues;
|
|
this.fields = {};
|
|
for (const issue of issues) {
|
|
if (issue.path.length > 0) {
|
|
const key = issue.path[0].toString();
|
|
this.fields[key] ??= [];
|
|
this.fields[key]?.push(issue.message);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
function deserializeActionResult(res) {
|
|
if (res.type === "error") {
|
|
let json;
|
|
try {
|
|
json = JSON.parse(res.body);
|
|
} catch {
|
|
return {
|
|
data: void 0,
|
|
error: new ActionError({
|
|
message: res.body,
|
|
code: "INTERNAL_SERVER_ERROR"
|
|
})
|
|
};
|
|
}
|
|
if (Object.assign(__vite_import_meta_env__$1, {})?.PROD) {
|
|
return { error: ActionError.fromJson(json), data: void 0 };
|
|
} else {
|
|
const error = ActionError.fromJson(json);
|
|
error.stack = actionResultErrorStack.get();
|
|
return {
|
|
error,
|
|
data: void 0
|
|
};
|
|
}
|
|
}
|
|
if (res.type === "empty") {
|
|
return { data: void 0, error: void 0 };
|
|
}
|
|
return {
|
|
data: parse$1(res.body, {
|
|
URL: (href) => new URL(href)
|
|
}),
|
|
error: void 0
|
|
};
|
|
}
|
|
const actionResultErrorStack = /* @__PURE__ */ (function actionResultErrorStackFn() {
|
|
let errorStack;
|
|
return {
|
|
set(stack) {
|
|
errorStack = stack;
|
|
},
|
|
get() {
|
|
return errorStack;
|
|
}
|
|
};
|
|
})();
|
|
function getActionQueryString(name) {
|
|
const searchParams = new URLSearchParams({ [ACTION_QUERY_PARAMS.actionName]: name });
|
|
return `?${searchParams.toString()}`;
|
|
}
|
|
|
|
/** A special constant with type `never` */
|
|
function $constructor(name, initializer, params) {
|
|
function init(inst, def) {
|
|
if (!inst._zod) {
|
|
Object.defineProperty(inst, "_zod", {
|
|
value: {
|
|
def,
|
|
constr: _,
|
|
traits: new Set(),
|
|
},
|
|
enumerable: false,
|
|
});
|
|
}
|
|
if (inst._zod.traits.has(name)) {
|
|
return;
|
|
}
|
|
inst._zod.traits.add(name);
|
|
initializer(inst, def);
|
|
// support prototype modifications
|
|
const proto = _.prototype;
|
|
const keys = Object.keys(proto);
|
|
for (let i = 0; i < keys.length; i++) {
|
|
const k = keys[i];
|
|
if (!(k in inst)) {
|
|
inst[k] = proto[k].bind(inst);
|
|
}
|
|
}
|
|
}
|
|
// doesn't work if Parent has a constructor with arguments
|
|
const Parent = params?.Parent ?? Object;
|
|
class Definition extends Parent {
|
|
}
|
|
Object.defineProperty(Definition, "name", { value: name });
|
|
function _(def) {
|
|
var _a;
|
|
const inst = params?.Parent ? new Definition() : this;
|
|
init(inst, def);
|
|
(_a = inst._zod).deferred ?? (_a.deferred = []);
|
|
for (const fn of inst._zod.deferred) {
|
|
fn();
|
|
}
|
|
return inst;
|
|
}
|
|
Object.defineProperty(_, "init", { value: init });
|
|
Object.defineProperty(_, Symbol.hasInstance, {
|
|
value: (inst) => {
|
|
if (params?.Parent && inst instanceof params.Parent)
|
|
return true;
|
|
return inst?._zod?.traits?.has(name);
|
|
},
|
|
});
|
|
Object.defineProperty(_, "name", { value: name });
|
|
return _;
|
|
}
|
|
class $ZodAsyncError extends Error {
|
|
constructor() {
|
|
super(`Encountered Promise during synchronous parse. Use .parseAsync() instead.`);
|
|
}
|
|
}
|
|
class $ZodEncodeError extends Error {
|
|
constructor(name) {
|
|
super(`Encountered unidirectional transform during encode: ${name}`);
|
|
this.name = "ZodEncodeError";
|
|
}
|
|
}
|
|
const globalConfig = {};
|
|
function config(newConfig) {
|
|
return globalConfig;
|
|
}
|
|
|
|
// functions
|
|
function getEnumValues(entries) {
|
|
const numericValues = Object.values(entries).filter((v) => typeof v === "number");
|
|
const values = Object.entries(entries)
|
|
.filter(([k, _]) => numericValues.indexOf(+k) === -1)
|
|
.map(([_, v]) => v);
|
|
return values;
|
|
}
|
|
function jsonStringifyReplacer(_, value) {
|
|
if (typeof value === "bigint")
|
|
return value.toString();
|
|
return value;
|
|
}
|
|
function nullish(input) {
|
|
return input === null || input === undefined;
|
|
}
|
|
function cleanRegex(source) {
|
|
const start = source.startsWith("^") ? 1 : 0;
|
|
const end = source.endsWith("$") ? source.length - 1 : source.length;
|
|
return source.slice(start, end);
|
|
}
|
|
const EVALUATING = Symbol("evaluating");
|
|
function defineLazy(object, key, getter) {
|
|
let value = undefined;
|
|
Object.defineProperty(object, key, {
|
|
get() {
|
|
if (value === EVALUATING) {
|
|
// Circular reference detected, return undefined to break the cycle
|
|
return undefined;
|
|
}
|
|
if (value === undefined) {
|
|
value = EVALUATING;
|
|
value = getter();
|
|
}
|
|
return value;
|
|
},
|
|
set(v) {
|
|
Object.defineProperty(object, key, {
|
|
value: v,
|
|
// configurable: true,
|
|
});
|
|
// object[key] = v;
|
|
},
|
|
configurable: true,
|
|
});
|
|
}
|
|
function mergeDefs(...defs) {
|
|
const mergedDescriptors = {};
|
|
for (const def of defs) {
|
|
const descriptors = Object.getOwnPropertyDescriptors(def);
|
|
Object.assign(mergedDescriptors, descriptors);
|
|
}
|
|
return Object.defineProperties({}, mergedDescriptors);
|
|
}
|
|
const captureStackTrace = ("captureStackTrace" in Error ? Error.captureStackTrace : (..._args) => { });
|
|
function isObject(data) {
|
|
return typeof data === "object" && data !== null && !Array.isArray(data);
|
|
}
|
|
function isPlainObject(o) {
|
|
if (isObject(o) === false)
|
|
return false;
|
|
// modified constructor
|
|
const ctor = o.constructor;
|
|
if (ctor === undefined)
|
|
return true;
|
|
if (typeof ctor !== "function")
|
|
return true;
|
|
// modified prototype
|
|
const prot = ctor.prototype;
|
|
if (isObject(prot) === false)
|
|
return false;
|
|
// ctor doesn't have static `isPrototypeOf`
|
|
if (Object.prototype.hasOwnProperty.call(prot, "isPrototypeOf") === false) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
function shallowClone(o) {
|
|
if (isPlainObject(o))
|
|
return { ...o };
|
|
if (Array.isArray(o))
|
|
return [...o];
|
|
return o;
|
|
}
|
|
const propertyKeyTypes = new Set(["string", "number", "symbol"]);
|
|
function escapeRegex(str) {
|
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
}
|
|
// zod-specific utils
|
|
function clone(inst, def, params) {
|
|
const cl = new inst._zod.constr(def ?? inst._zod.def);
|
|
if (!def || params?.parent)
|
|
cl._zod.parent = inst;
|
|
return cl;
|
|
}
|
|
function normalizeParams(_params) {
|
|
const params = _params;
|
|
if (!params)
|
|
return {};
|
|
if (typeof params === "string")
|
|
return { error: () => params };
|
|
if (params?.message !== undefined) {
|
|
if (params?.error !== undefined)
|
|
throw new Error("Cannot specify both `message` and `error` params");
|
|
params.error = params.message;
|
|
}
|
|
delete params.message;
|
|
if (typeof params.error === "string")
|
|
return { ...params, error: () => params.error };
|
|
return params;
|
|
}
|
|
// invalid_type | too_big | too_small | invalid_format | not_multiple_of | unrecognized_keys | invalid_union | invalid_key | invalid_element | invalid_value | custom
|
|
function aborted(x, startIndex = 0) {
|
|
if (x.aborted === true)
|
|
return true;
|
|
for (let i = startIndex; i < x.issues.length; i++) {
|
|
if (x.issues[i]?.continue !== true) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
function prefixIssues(path, issues) {
|
|
return issues.map((iss) => {
|
|
var _a;
|
|
(_a = iss).path ?? (_a.path = []);
|
|
iss.path.unshift(path);
|
|
return iss;
|
|
});
|
|
}
|
|
function unwrapMessage(message) {
|
|
return typeof message === "string" ? message : message?.message;
|
|
}
|
|
function finalizeIssue(iss, ctx, config) {
|
|
const full = { ...iss, path: iss.path ?? [] };
|
|
// for backwards compatibility
|
|
if (!iss.message) {
|
|
const message = unwrapMessage(iss.inst?._zod.def?.error?.(iss)) ??
|
|
unwrapMessage(ctx?.error?.(iss)) ??
|
|
unwrapMessage(config.customError?.(iss)) ??
|
|
unwrapMessage(config.localeError?.(iss)) ??
|
|
"Invalid input";
|
|
full.message = message;
|
|
}
|
|
// delete (full as any).def;
|
|
delete full.inst;
|
|
delete full.continue;
|
|
if (!ctx?.reportInput) {
|
|
delete full.input;
|
|
}
|
|
return full;
|
|
}
|
|
function getLengthableOrigin(input) {
|
|
if (Array.isArray(input))
|
|
return "array";
|
|
if (typeof input === "string")
|
|
return "string";
|
|
return "unknown";
|
|
}
|
|
function issue(...args) {
|
|
const [iss, input, inst] = args;
|
|
if (typeof iss === "string") {
|
|
return {
|
|
message: iss,
|
|
code: "custom",
|
|
input,
|
|
inst,
|
|
};
|
|
}
|
|
return { ...iss };
|
|
}
|
|
|
|
const initializer$1 = (inst, def) => {
|
|
inst.name = "$ZodError";
|
|
Object.defineProperty(inst, "_zod", {
|
|
value: inst._zod,
|
|
enumerable: false,
|
|
});
|
|
Object.defineProperty(inst, "issues", {
|
|
value: def,
|
|
enumerable: false,
|
|
});
|
|
inst.message = JSON.stringify(def, jsonStringifyReplacer, 2);
|
|
Object.defineProperty(inst, "toString", {
|
|
value: () => inst.message,
|
|
enumerable: false,
|
|
});
|
|
};
|
|
const $ZodError = $constructor("$ZodError", initializer$1);
|
|
const $ZodRealError = $constructor("$ZodError", initializer$1, { Parent: Error });
|
|
function flattenError(error, mapper = (issue) => issue.message) {
|
|
const fieldErrors = {};
|
|
const formErrors = [];
|
|
for (const sub of error.issues) {
|
|
if (sub.path.length > 0) {
|
|
fieldErrors[sub.path[0]] = fieldErrors[sub.path[0]] || [];
|
|
fieldErrors[sub.path[0]].push(mapper(sub));
|
|
}
|
|
else {
|
|
formErrors.push(mapper(sub));
|
|
}
|
|
}
|
|
return { formErrors, fieldErrors };
|
|
}
|
|
function formatError(error, mapper = (issue) => issue.message) {
|
|
const fieldErrors = { _errors: [] };
|
|
const processError = (error) => {
|
|
for (const issue of error.issues) {
|
|
if (issue.code === "invalid_union" && issue.errors.length) {
|
|
issue.errors.map((issues) => processError({ issues }));
|
|
}
|
|
else if (issue.code === "invalid_key") {
|
|
processError({ issues: issue.issues });
|
|
}
|
|
else if (issue.code === "invalid_element") {
|
|
processError({ issues: issue.issues });
|
|
}
|
|
else if (issue.path.length === 0) {
|
|
fieldErrors._errors.push(mapper(issue));
|
|
}
|
|
else {
|
|
let curr = fieldErrors;
|
|
let i = 0;
|
|
while (i < issue.path.length) {
|
|
const el = issue.path[i];
|
|
const terminal = i === issue.path.length - 1;
|
|
if (!terminal) {
|
|
curr[el] = curr[el] || { _errors: [] };
|
|
}
|
|
else {
|
|
curr[el] = curr[el] || { _errors: [] };
|
|
curr[el]._errors.push(mapper(issue));
|
|
}
|
|
curr = curr[el];
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
processError(error);
|
|
return fieldErrors;
|
|
}
|
|
|
|
const _parse = (_Err) => (schema, value, _ctx, _params) => {
|
|
const ctx = _ctx ? Object.assign(_ctx, { async: false }) : { async: false };
|
|
const result = schema._zod.run({ value, issues: [] }, ctx);
|
|
if (result instanceof Promise) {
|
|
throw new $ZodAsyncError();
|
|
}
|
|
if (result.issues.length) {
|
|
const e = new (_params?.Err ?? _Err)(result.issues.map((iss) => finalizeIssue(iss, ctx, config())));
|
|
captureStackTrace(e, _params?.callee);
|
|
throw e;
|
|
}
|
|
return result.value;
|
|
};
|
|
const _parseAsync = (_Err) => async (schema, value, _ctx, params) => {
|
|
const ctx = _ctx ? Object.assign(_ctx, { async: true }) : { async: true };
|
|
let result = schema._zod.run({ value, issues: [] }, ctx);
|
|
if (result instanceof Promise)
|
|
result = await result;
|
|
if (result.issues.length) {
|
|
const e = new (params?.Err ?? _Err)(result.issues.map((iss) => finalizeIssue(iss, ctx, config())));
|
|
captureStackTrace(e, params?.callee);
|
|
throw e;
|
|
}
|
|
return result.value;
|
|
};
|
|
const _safeParse = (_Err) => (schema, value, _ctx) => {
|
|
const ctx = _ctx ? { ..._ctx, async: false } : { async: false };
|
|
const result = schema._zod.run({ value, issues: [] }, ctx);
|
|
if (result instanceof Promise) {
|
|
throw new $ZodAsyncError();
|
|
}
|
|
return result.issues.length
|
|
? {
|
|
success: false,
|
|
error: new (_Err ?? $ZodError)(result.issues.map((iss) => finalizeIssue(iss, ctx, config()))),
|
|
}
|
|
: { success: true, data: result.value };
|
|
};
|
|
const safeParse$1 = /* @__PURE__*/ _safeParse($ZodRealError);
|
|
const _safeParseAsync = (_Err) => async (schema, value, _ctx) => {
|
|
const ctx = _ctx ? Object.assign(_ctx, { async: true }) : { async: true };
|
|
let result = schema._zod.run({ value, issues: [] }, ctx);
|
|
if (result instanceof Promise)
|
|
result = await result;
|
|
return result.issues.length
|
|
? {
|
|
success: false,
|
|
error: new _Err(result.issues.map((iss) => finalizeIssue(iss, ctx, config()))),
|
|
}
|
|
: { success: true, data: result.value };
|
|
};
|
|
const safeParseAsync$1 = /* @__PURE__*/ _safeParseAsync($ZodRealError);
|
|
const _encode = (_Err) => (schema, value, _ctx) => {
|
|
const ctx = _ctx ? Object.assign(_ctx, { direction: "backward" }) : { direction: "backward" };
|
|
return _parse(_Err)(schema, value, ctx);
|
|
};
|
|
const _decode = (_Err) => (schema, value, _ctx) => {
|
|
return _parse(_Err)(schema, value, _ctx);
|
|
};
|
|
const _encodeAsync = (_Err) => async (schema, value, _ctx) => {
|
|
const ctx = _ctx ? Object.assign(_ctx, { direction: "backward" }) : { direction: "backward" };
|
|
return _parseAsync(_Err)(schema, value, ctx);
|
|
};
|
|
const _decodeAsync = (_Err) => async (schema, value, _ctx) => {
|
|
return _parseAsync(_Err)(schema, value, _ctx);
|
|
};
|
|
const _safeEncode = (_Err) => (schema, value, _ctx) => {
|
|
const ctx = _ctx ? Object.assign(_ctx, { direction: "backward" }) : { direction: "backward" };
|
|
return _safeParse(_Err)(schema, value, ctx);
|
|
};
|
|
const _safeDecode = (_Err) => (schema, value, _ctx) => {
|
|
return _safeParse(_Err)(schema, value, _ctx);
|
|
};
|
|
const _safeEncodeAsync = (_Err) => async (schema, value, _ctx) => {
|
|
const ctx = _ctx ? Object.assign(_ctx, { direction: "backward" }) : { direction: "backward" };
|
|
return _safeParseAsync(_Err)(schema, value, ctx);
|
|
};
|
|
const _safeDecodeAsync = (_Err) => async (schema, value, _ctx) => {
|
|
return _safeParseAsync(_Err)(schema, value, _ctx);
|
|
};
|
|
|
|
// import { $ZodType } from "./schemas.js";
|
|
const $ZodCheck = /*@__PURE__*/ $constructor("$ZodCheck", (inst, def) => {
|
|
var _a;
|
|
inst._zod ?? (inst._zod = {});
|
|
inst._zod.def = def;
|
|
(_a = inst._zod).onattach ?? (_a.onattach = []);
|
|
});
|
|
const $ZodCheckMaxLength = /*@__PURE__*/ $constructor("$ZodCheckMaxLength", (inst, def) => {
|
|
var _a;
|
|
$ZodCheck.init(inst, def);
|
|
(_a = inst._zod.def).when ?? (_a.when = (payload) => {
|
|
const val = payload.value;
|
|
return !nullish(val) && val.length !== undefined;
|
|
});
|
|
inst._zod.onattach.push((inst) => {
|
|
const curr = (inst._zod.bag.maximum ?? Number.POSITIVE_INFINITY);
|
|
if (def.maximum < curr)
|
|
inst._zod.bag.maximum = def.maximum;
|
|
});
|
|
inst._zod.check = (payload) => {
|
|
const input = payload.value;
|
|
const length = input.length;
|
|
if (length <= def.maximum)
|
|
return;
|
|
const origin = getLengthableOrigin(input);
|
|
payload.issues.push({
|
|
origin,
|
|
code: "too_big",
|
|
maximum: def.maximum,
|
|
inclusive: true,
|
|
input,
|
|
inst,
|
|
continue: !def.abort,
|
|
});
|
|
};
|
|
});
|
|
const $ZodCheckMinLength = /*@__PURE__*/ $constructor("$ZodCheckMinLength", (inst, def) => {
|
|
var _a;
|
|
$ZodCheck.init(inst, def);
|
|
(_a = inst._zod.def).when ?? (_a.when = (payload) => {
|
|
const val = payload.value;
|
|
return !nullish(val) && val.length !== undefined;
|
|
});
|
|
inst._zod.onattach.push((inst) => {
|
|
const curr = (inst._zod.bag.minimum ?? Number.NEGATIVE_INFINITY);
|
|
if (def.minimum > curr)
|
|
inst._zod.bag.minimum = def.minimum;
|
|
});
|
|
inst._zod.check = (payload) => {
|
|
const input = payload.value;
|
|
const length = input.length;
|
|
if (length >= def.minimum)
|
|
return;
|
|
const origin = getLengthableOrigin(input);
|
|
payload.issues.push({
|
|
origin,
|
|
code: "too_small",
|
|
minimum: def.minimum,
|
|
inclusive: true,
|
|
input,
|
|
inst,
|
|
continue: !def.abort,
|
|
});
|
|
};
|
|
});
|
|
const $ZodCheckLengthEquals = /*@__PURE__*/ $constructor("$ZodCheckLengthEquals", (inst, def) => {
|
|
var _a;
|
|
$ZodCheck.init(inst, def);
|
|
(_a = inst._zod.def).when ?? (_a.when = (payload) => {
|
|
const val = payload.value;
|
|
return !nullish(val) && val.length !== undefined;
|
|
});
|
|
inst._zod.onattach.push((inst) => {
|
|
const bag = inst._zod.bag;
|
|
bag.minimum = def.length;
|
|
bag.maximum = def.length;
|
|
bag.length = def.length;
|
|
});
|
|
inst._zod.check = (payload) => {
|
|
const input = payload.value;
|
|
const length = input.length;
|
|
if (length === def.length)
|
|
return;
|
|
const origin = getLengthableOrigin(input);
|
|
const tooBig = length > def.length;
|
|
payload.issues.push({
|
|
origin,
|
|
...(tooBig ? { code: "too_big", maximum: def.length } : { code: "too_small", minimum: def.length }),
|
|
inclusive: true,
|
|
exact: true,
|
|
input: payload.value,
|
|
inst,
|
|
continue: !def.abort,
|
|
});
|
|
};
|
|
});
|
|
const $ZodCheckOverwrite = /*@__PURE__*/ $constructor("$ZodCheckOverwrite", (inst, def) => {
|
|
$ZodCheck.init(inst, def);
|
|
inst._zod.check = (payload) => {
|
|
payload.value = def.tx(payload.value);
|
|
};
|
|
});
|
|
|
|
const version = {
|
|
major: 4,
|
|
minor: 3,
|
|
patch: 6,
|
|
};
|
|
|
|
const $ZodType = /*@__PURE__*/ $constructor("$ZodType", (inst, def) => {
|
|
var _a;
|
|
inst ?? (inst = {});
|
|
inst._zod.def = def; // set _def property
|
|
inst._zod.bag = inst._zod.bag || {}; // initialize _bag object
|
|
inst._zod.version = version;
|
|
const checks = [...(inst._zod.def.checks ?? [])];
|
|
// if inst is itself a checks.$ZodCheck, run it as a check
|
|
if (inst._zod.traits.has("$ZodCheck")) {
|
|
checks.unshift(inst);
|
|
}
|
|
for (const ch of checks) {
|
|
for (const fn of ch._zod.onattach) {
|
|
fn(inst);
|
|
}
|
|
}
|
|
if (checks.length === 0) {
|
|
// deferred initializer
|
|
// inst._zod.parse is not yet defined
|
|
(_a = inst._zod).deferred ?? (_a.deferred = []);
|
|
inst._zod.deferred?.push(() => {
|
|
inst._zod.run = inst._zod.parse;
|
|
});
|
|
}
|
|
else {
|
|
const runChecks = (payload, checks, ctx) => {
|
|
let isAborted = aborted(payload);
|
|
let asyncResult;
|
|
for (const ch of checks) {
|
|
if (ch._zod.def.when) {
|
|
const shouldRun = ch._zod.def.when(payload);
|
|
if (!shouldRun)
|
|
continue;
|
|
}
|
|
else if (isAborted) {
|
|
continue;
|
|
}
|
|
const currLen = payload.issues.length;
|
|
const _ = ch._zod.check(payload);
|
|
if (_ instanceof Promise && ctx?.async === false) {
|
|
throw new $ZodAsyncError();
|
|
}
|
|
if (asyncResult || _ instanceof Promise) {
|
|
asyncResult = (asyncResult ?? Promise.resolve()).then(async () => {
|
|
await _;
|
|
const nextLen = payload.issues.length;
|
|
if (nextLen === currLen)
|
|
return;
|
|
if (!isAborted)
|
|
isAborted = aborted(payload, currLen);
|
|
});
|
|
}
|
|
else {
|
|
const nextLen = payload.issues.length;
|
|
if (nextLen === currLen)
|
|
continue;
|
|
if (!isAborted)
|
|
isAborted = aborted(payload, currLen);
|
|
}
|
|
}
|
|
if (asyncResult) {
|
|
return asyncResult.then(() => {
|
|
return payload;
|
|
});
|
|
}
|
|
return payload;
|
|
};
|
|
const handleCanaryResult = (canary, payload, ctx) => {
|
|
// abort if the canary is aborted
|
|
if (aborted(canary)) {
|
|
canary.aborted = true;
|
|
return canary;
|
|
}
|
|
// run checks first, then
|
|
const checkResult = runChecks(payload, checks, ctx);
|
|
if (checkResult instanceof Promise) {
|
|
if (ctx.async === false)
|
|
throw new $ZodAsyncError();
|
|
return checkResult.then((checkResult) => inst._zod.parse(checkResult, ctx));
|
|
}
|
|
return inst._zod.parse(checkResult, ctx);
|
|
};
|
|
inst._zod.run = (payload, ctx) => {
|
|
if (ctx.skipChecks) {
|
|
return inst._zod.parse(payload, ctx);
|
|
}
|
|
if (ctx.direction === "backward") {
|
|
// run canary
|
|
// initial pass (no checks)
|
|
const canary = inst._zod.parse({ value: payload.value, issues: [] }, { ...ctx, skipChecks: true });
|
|
if (canary instanceof Promise) {
|
|
return canary.then((canary) => {
|
|
return handleCanaryResult(canary, payload, ctx);
|
|
});
|
|
}
|
|
return handleCanaryResult(canary, payload, ctx);
|
|
}
|
|
// forward
|
|
const result = inst._zod.parse(payload, ctx);
|
|
if (result instanceof Promise) {
|
|
if (ctx.async === false)
|
|
throw new $ZodAsyncError();
|
|
return result.then((result) => runChecks(result, checks, ctx));
|
|
}
|
|
return runChecks(result, checks, ctx);
|
|
};
|
|
}
|
|
// Lazy initialize ~standard to avoid creating objects for every schema
|
|
defineLazy(inst, "~standard", () => ({
|
|
validate: (value) => {
|
|
try {
|
|
const r = safeParse$1(inst, value);
|
|
return r.success ? { value: r.data } : { issues: r.error?.issues };
|
|
}
|
|
catch (_) {
|
|
return safeParseAsync$1(inst, value).then((r) => (r.success ? { value: r.data } : { issues: r.error?.issues }));
|
|
}
|
|
},
|
|
vendor: "zod",
|
|
version: 1,
|
|
}));
|
|
});
|
|
function handleArrayResult(result, final, index) {
|
|
if (result.issues.length) {
|
|
final.issues.push(...prefixIssues(index, result.issues));
|
|
}
|
|
final.value[index] = result.value;
|
|
}
|
|
const $ZodArray = /*@__PURE__*/ $constructor("$ZodArray", (inst, def) => {
|
|
$ZodType.init(inst, def);
|
|
inst._zod.parse = (payload, ctx) => {
|
|
const input = payload.value;
|
|
if (!Array.isArray(input)) {
|
|
payload.issues.push({
|
|
expected: "array",
|
|
code: "invalid_type",
|
|
input,
|
|
inst,
|
|
});
|
|
return payload;
|
|
}
|
|
payload.value = Array(input.length);
|
|
const proms = [];
|
|
for (let i = 0; i < input.length; i++) {
|
|
const item = input[i];
|
|
const result = def.element._zod.run({
|
|
value: item,
|
|
issues: [],
|
|
}, ctx);
|
|
if (result instanceof Promise) {
|
|
proms.push(result.then((result) => handleArrayResult(result, payload, i)));
|
|
}
|
|
else {
|
|
handleArrayResult(result, payload, i);
|
|
}
|
|
}
|
|
if (proms.length) {
|
|
return Promise.all(proms).then(() => payload);
|
|
}
|
|
return payload; //handleArrayResultsAsync(parseResults, final);
|
|
};
|
|
});
|
|
function handleUnionResults(results, final, inst, ctx) {
|
|
for (const result of results) {
|
|
if (result.issues.length === 0) {
|
|
final.value = result.value;
|
|
return final;
|
|
}
|
|
}
|
|
const nonaborted = results.filter((r) => !aborted(r));
|
|
if (nonaborted.length === 1) {
|
|
final.value = nonaborted[0].value;
|
|
return nonaborted[0];
|
|
}
|
|
final.issues.push({
|
|
code: "invalid_union",
|
|
input: final.value,
|
|
inst,
|
|
errors: results.map((result) => result.issues.map((iss) => finalizeIssue(iss, ctx, config()))),
|
|
});
|
|
return final;
|
|
}
|
|
const $ZodUnion = /*@__PURE__*/ $constructor("$ZodUnion", (inst, def) => {
|
|
$ZodType.init(inst, def);
|
|
defineLazy(inst._zod, "optin", () => def.options.some((o) => o._zod.optin === "optional") ? "optional" : undefined);
|
|
defineLazy(inst._zod, "optout", () => def.options.some((o) => o._zod.optout === "optional") ? "optional" : undefined);
|
|
defineLazy(inst._zod, "values", () => {
|
|
if (def.options.every((o) => o._zod.values)) {
|
|
return new Set(def.options.flatMap((option) => Array.from(option._zod.values)));
|
|
}
|
|
return undefined;
|
|
});
|
|
defineLazy(inst._zod, "pattern", () => {
|
|
if (def.options.every((o) => o._zod.pattern)) {
|
|
const patterns = def.options.map((o) => o._zod.pattern);
|
|
return new RegExp(`^(${patterns.map((p) => cleanRegex(p.source)).join("|")})$`);
|
|
}
|
|
return undefined;
|
|
});
|
|
const single = def.options.length === 1;
|
|
const first = def.options[0]._zod.run;
|
|
inst._zod.parse = (payload, ctx) => {
|
|
if (single) {
|
|
return first(payload, ctx);
|
|
}
|
|
let async = false;
|
|
const results = [];
|
|
for (const option of def.options) {
|
|
const result = option._zod.run({
|
|
value: payload.value,
|
|
issues: [],
|
|
}, ctx);
|
|
if (result instanceof Promise) {
|
|
results.push(result);
|
|
async = true;
|
|
}
|
|
else {
|
|
if (result.issues.length === 0)
|
|
return result;
|
|
results.push(result);
|
|
}
|
|
}
|
|
if (!async)
|
|
return handleUnionResults(results, payload, inst, ctx);
|
|
return Promise.all(results).then((results) => {
|
|
return handleUnionResults(results, payload, inst, ctx);
|
|
});
|
|
};
|
|
});
|
|
const $ZodIntersection = /*@__PURE__*/ $constructor("$ZodIntersection", (inst, def) => {
|
|
$ZodType.init(inst, def);
|
|
inst._zod.parse = (payload, ctx) => {
|
|
const input = payload.value;
|
|
const left = def.left._zod.run({ value: input, issues: [] }, ctx);
|
|
const right = def.right._zod.run({ value: input, issues: [] }, ctx);
|
|
const async = left instanceof Promise || right instanceof Promise;
|
|
if (async) {
|
|
return Promise.all([left, right]).then(([left, right]) => {
|
|
return handleIntersectionResults(payload, left, right);
|
|
});
|
|
}
|
|
return handleIntersectionResults(payload, left, right);
|
|
};
|
|
});
|
|
function mergeValues(a, b) {
|
|
// const aType = parse.t(a);
|
|
// const bType = parse.t(b);
|
|
if (a === b) {
|
|
return { valid: true, data: a };
|
|
}
|
|
if (a instanceof Date && b instanceof Date && +a === +b) {
|
|
return { valid: true, data: a };
|
|
}
|
|
if (isPlainObject(a) && isPlainObject(b)) {
|
|
const bKeys = Object.keys(b);
|
|
const sharedKeys = Object.keys(a).filter((key) => bKeys.indexOf(key) !== -1);
|
|
const newObj = { ...a, ...b };
|
|
for (const key of sharedKeys) {
|
|
const sharedValue = mergeValues(a[key], b[key]);
|
|
if (!sharedValue.valid) {
|
|
return {
|
|
valid: false,
|
|
mergeErrorPath: [key, ...sharedValue.mergeErrorPath],
|
|
};
|
|
}
|
|
newObj[key] = sharedValue.data;
|
|
}
|
|
return { valid: true, data: newObj };
|
|
}
|
|
if (Array.isArray(a) && Array.isArray(b)) {
|
|
if (a.length !== b.length) {
|
|
return { valid: false, mergeErrorPath: [] };
|
|
}
|
|
const newArray = [];
|
|
for (let index = 0; index < a.length; index++) {
|
|
const itemA = a[index];
|
|
const itemB = b[index];
|
|
const sharedValue = mergeValues(itemA, itemB);
|
|
if (!sharedValue.valid) {
|
|
return {
|
|
valid: false,
|
|
mergeErrorPath: [index, ...sharedValue.mergeErrorPath],
|
|
};
|
|
}
|
|
newArray.push(sharedValue.data);
|
|
}
|
|
return { valid: true, data: newArray };
|
|
}
|
|
return { valid: false, mergeErrorPath: [] };
|
|
}
|
|
function handleIntersectionResults(result, left, right) {
|
|
// Track which side(s) report each key as unrecognized
|
|
const unrecKeys = new Map();
|
|
let unrecIssue;
|
|
for (const iss of left.issues) {
|
|
if (iss.code === "unrecognized_keys") {
|
|
unrecIssue ?? (unrecIssue = iss);
|
|
for (const k of iss.keys) {
|
|
if (!unrecKeys.has(k))
|
|
unrecKeys.set(k, {});
|
|
unrecKeys.get(k).l = true;
|
|
}
|
|
}
|
|
else {
|
|
result.issues.push(iss);
|
|
}
|
|
}
|
|
for (const iss of right.issues) {
|
|
if (iss.code === "unrecognized_keys") {
|
|
for (const k of iss.keys) {
|
|
if (!unrecKeys.has(k))
|
|
unrecKeys.set(k, {});
|
|
unrecKeys.get(k).r = true;
|
|
}
|
|
}
|
|
else {
|
|
result.issues.push(iss);
|
|
}
|
|
}
|
|
// Report only keys unrecognized by BOTH sides
|
|
const bothKeys = [...unrecKeys].filter(([, f]) => f.l && f.r).map(([k]) => k);
|
|
if (bothKeys.length && unrecIssue) {
|
|
result.issues.push({ ...unrecIssue, keys: bothKeys });
|
|
}
|
|
if (aborted(result))
|
|
return result;
|
|
const merged = mergeValues(left.value, right.value);
|
|
if (!merged.valid) {
|
|
throw new Error(`Unmergable intersection. Error path: ` + `${JSON.stringify(merged.mergeErrorPath)}`);
|
|
}
|
|
result.value = merged.data;
|
|
return result;
|
|
}
|
|
const $ZodEnum = /*@__PURE__*/ $constructor("$ZodEnum", (inst, def) => {
|
|
$ZodType.init(inst, def);
|
|
const values = getEnumValues(def.entries);
|
|
const valuesSet = new Set(values);
|
|
inst._zod.values = valuesSet;
|
|
inst._zod.pattern = new RegExp(`^(${values
|
|
.filter((k) => propertyKeyTypes.has(typeof k))
|
|
.map((o) => (typeof o === "string" ? escapeRegex(o) : o.toString()))
|
|
.join("|")})$`);
|
|
inst._zod.parse = (payload, _ctx) => {
|
|
const input = payload.value;
|
|
if (valuesSet.has(input)) {
|
|
return payload;
|
|
}
|
|
payload.issues.push({
|
|
code: "invalid_value",
|
|
values,
|
|
input,
|
|
inst,
|
|
});
|
|
return payload;
|
|
};
|
|
});
|
|
const $ZodTransform = /*@__PURE__*/ $constructor("$ZodTransform", (inst, def) => {
|
|
$ZodType.init(inst, def);
|
|
inst._zod.parse = (payload, ctx) => {
|
|
if (ctx.direction === "backward") {
|
|
throw new $ZodEncodeError(inst.constructor.name);
|
|
}
|
|
const _out = def.transform(payload.value, payload);
|
|
if (ctx.async) {
|
|
const output = _out instanceof Promise ? _out : Promise.resolve(_out);
|
|
return output.then((output) => {
|
|
payload.value = output;
|
|
return payload;
|
|
});
|
|
}
|
|
if (_out instanceof Promise) {
|
|
throw new $ZodAsyncError();
|
|
}
|
|
payload.value = _out;
|
|
return payload;
|
|
};
|
|
});
|
|
function handleOptionalResult(result, input) {
|
|
if (result.issues.length && input === undefined) {
|
|
return { issues: [], value: undefined };
|
|
}
|
|
return result;
|
|
}
|
|
const $ZodOptional = /*@__PURE__*/ $constructor("$ZodOptional", (inst, def) => {
|
|
$ZodType.init(inst, def);
|
|
inst._zod.optin = "optional";
|
|
inst._zod.optout = "optional";
|
|
defineLazy(inst._zod, "values", () => {
|
|
return def.innerType._zod.values ? new Set([...def.innerType._zod.values, undefined]) : undefined;
|
|
});
|
|
defineLazy(inst._zod, "pattern", () => {
|
|
const pattern = def.innerType._zod.pattern;
|
|
return pattern ? new RegExp(`^(${cleanRegex(pattern.source)})?$`) : undefined;
|
|
});
|
|
inst._zod.parse = (payload, ctx) => {
|
|
if (def.innerType._zod.optin === "optional") {
|
|
const result = def.innerType._zod.run(payload, ctx);
|
|
if (result instanceof Promise)
|
|
return result.then((r) => handleOptionalResult(r, payload.value));
|
|
return handleOptionalResult(result, payload.value);
|
|
}
|
|
if (payload.value === undefined) {
|
|
return payload;
|
|
}
|
|
return def.innerType._zod.run(payload, ctx);
|
|
};
|
|
});
|
|
const $ZodExactOptional = /*@__PURE__*/ $constructor("$ZodExactOptional", (inst, def) => {
|
|
// Call parent init - inherits optin/optout = "optional"
|
|
$ZodOptional.init(inst, def);
|
|
// Override values/pattern to NOT add undefined
|
|
defineLazy(inst._zod, "values", () => def.innerType._zod.values);
|
|
defineLazy(inst._zod, "pattern", () => def.innerType._zod.pattern);
|
|
// Override parse to just delegate (no undefined handling)
|
|
inst._zod.parse = (payload, ctx) => {
|
|
return def.innerType._zod.run(payload, ctx);
|
|
};
|
|
});
|
|
const $ZodNullable = /*@__PURE__*/ $constructor("$ZodNullable", (inst, def) => {
|
|
$ZodType.init(inst, def);
|
|
defineLazy(inst._zod, "optin", () => def.innerType._zod.optin);
|
|
defineLazy(inst._zod, "optout", () => def.innerType._zod.optout);
|
|
defineLazy(inst._zod, "pattern", () => {
|
|
const pattern = def.innerType._zod.pattern;
|
|
return pattern ? new RegExp(`^(${cleanRegex(pattern.source)}|null)$`) : undefined;
|
|
});
|
|
defineLazy(inst._zod, "values", () => {
|
|
return def.innerType._zod.values ? new Set([...def.innerType._zod.values, null]) : undefined;
|
|
});
|
|
inst._zod.parse = (payload, ctx) => {
|
|
// Forward direction (decode): allow null to pass through
|
|
if (payload.value === null)
|
|
return payload;
|
|
return def.innerType._zod.run(payload, ctx);
|
|
};
|
|
});
|
|
const $ZodDefault = /*@__PURE__*/ $constructor("$ZodDefault", (inst, def) => {
|
|
$ZodType.init(inst, def);
|
|
// inst._zod.qin = "true";
|
|
inst._zod.optin = "optional";
|
|
defineLazy(inst._zod, "values", () => def.innerType._zod.values);
|
|
inst._zod.parse = (payload, ctx) => {
|
|
if (ctx.direction === "backward") {
|
|
return def.innerType._zod.run(payload, ctx);
|
|
}
|
|
// Forward direction (decode): apply defaults for undefined input
|
|
if (payload.value === undefined) {
|
|
payload.value = def.defaultValue;
|
|
/**
|
|
* $ZodDefault returns the default value immediately in forward direction.
|
|
* It doesn't pass the default value into the validator ("prefault"). There's no reason to pass the default value through validation. The validity of the default is enforced by TypeScript statically. Otherwise, it's the responsibility of the user to ensure the default is valid. In the case of pipes with divergent in/out types, you can specify the default on the `in` schema of your ZodPipe to set a "prefault" for the pipe. */
|
|
return payload;
|
|
}
|
|
// Forward direction: continue with default handling
|
|
const result = def.innerType._zod.run(payload, ctx);
|
|
if (result instanceof Promise) {
|
|
return result.then((result) => handleDefaultResult(result, def));
|
|
}
|
|
return handleDefaultResult(result, def);
|
|
};
|
|
});
|
|
function handleDefaultResult(payload, def) {
|
|
if (payload.value === undefined) {
|
|
payload.value = def.defaultValue;
|
|
}
|
|
return payload;
|
|
}
|
|
const $ZodPrefault = /*@__PURE__*/ $constructor("$ZodPrefault", (inst, def) => {
|
|
$ZodType.init(inst, def);
|
|
inst._zod.optin = "optional";
|
|
defineLazy(inst._zod, "values", () => def.innerType._zod.values);
|
|
inst._zod.parse = (payload, ctx) => {
|
|
if (ctx.direction === "backward") {
|
|
return def.innerType._zod.run(payload, ctx);
|
|
}
|
|
// Forward direction (decode): apply prefault for undefined input
|
|
if (payload.value === undefined) {
|
|
payload.value = def.defaultValue;
|
|
}
|
|
return def.innerType._zod.run(payload, ctx);
|
|
};
|
|
});
|
|
const $ZodNonOptional = /*@__PURE__*/ $constructor("$ZodNonOptional", (inst, def) => {
|
|
$ZodType.init(inst, def);
|
|
defineLazy(inst._zod, "values", () => {
|
|
const v = def.innerType._zod.values;
|
|
return v ? new Set([...v].filter((x) => x !== undefined)) : undefined;
|
|
});
|
|
inst._zod.parse = (payload, ctx) => {
|
|
const result = def.innerType._zod.run(payload, ctx);
|
|
if (result instanceof Promise) {
|
|
return result.then((result) => handleNonOptionalResult(result, inst));
|
|
}
|
|
return handleNonOptionalResult(result, inst);
|
|
};
|
|
});
|
|
function handleNonOptionalResult(payload, inst) {
|
|
if (!payload.issues.length && payload.value === undefined) {
|
|
payload.issues.push({
|
|
code: "invalid_type",
|
|
expected: "nonoptional",
|
|
input: payload.value,
|
|
inst,
|
|
});
|
|
}
|
|
return payload;
|
|
}
|
|
const $ZodCatch = /*@__PURE__*/ $constructor("$ZodCatch", (inst, def) => {
|
|
$ZodType.init(inst, def);
|
|
defineLazy(inst._zod, "optin", () => def.innerType._zod.optin);
|
|
defineLazy(inst._zod, "optout", () => def.innerType._zod.optout);
|
|
defineLazy(inst._zod, "values", () => def.innerType._zod.values);
|
|
inst._zod.parse = (payload, ctx) => {
|
|
if (ctx.direction === "backward") {
|
|
return def.innerType._zod.run(payload, ctx);
|
|
}
|
|
// Forward direction (decode): apply catch logic
|
|
const result = def.innerType._zod.run(payload, ctx);
|
|
if (result instanceof Promise) {
|
|
return result.then((result) => {
|
|
payload.value = result.value;
|
|
if (result.issues.length) {
|
|
payload.value = def.catchValue({
|
|
...payload,
|
|
error: {
|
|
issues: result.issues.map((iss) => finalizeIssue(iss, ctx, config())),
|
|
},
|
|
input: payload.value,
|
|
});
|
|
payload.issues = [];
|
|
}
|
|
return payload;
|
|
});
|
|
}
|
|
payload.value = result.value;
|
|
if (result.issues.length) {
|
|
payload.value = def.catchValue({
|
|
...payload,
|
|
error: {
|
|
issues: result.issues.map((iss) => finalizeIssue(iss, ctx, config())),
|
|
},
|
|
input: payload.value,
|
|
});
|
|
payload.issues = [];
|
|
}
|
|
return payload;
|
|
};
|
|
});
|
|
const $ZodPipe = /*@__PURE__*/ $constructor("$ZodPipe", (inst, def) => {
|
|
$ZodType.init(inst, def);
|
|
defineLazy(inst._zod, "values", () => def.in._zod.values);
|
|
defineLazy(inst._zod, "optin", () => def.in._zod.optin);
|
|
defineLazy(inst._zod, "optout", () => def.out._zod.optout);
|
|
defineLazy(inst._zod, "propValues", () => def.in._zod.propValues);
|
|
inst._zod.parse = (payload, ctx) => {
|
|
if (ctx.direction === "backward") {
|
|
const right = def.out._zod.run(payload, ctx);
|
|
if (right instanceof Promise) {
|
|
return right.then((right) => handlePipeResult(right, def.in, ctx));
|
|
}
|
|
return handlePipeResult(right, def.in, ctx);
|
|
}
|
|
const left = def.in._zod.run(payload, ctx);
|
|
if (left instanceof Promise) {
|
|
return left.then((left) => handlePipeResult(left, def.out, ctx));
|
|
}
|
|
return handlePipeResult(left, def.out, ctx);
|
|
};
|
|
});
|
|
function handlePipeResult(left, next, ctx) {
|
|
if (left.issues.length) {
|
|
// prevent further checks
|
|
left.aborted = true;
|
|
return left;
|
|
}
|
|
return next._zod.run({ value: left.value, issues: left.issues }, ctx);
|
|
}
|
|
const $ZodReadonly = /*@__PURE__*/ $constructor("$ZodReadonly", (inst, def) => {
|
|
$ZodType.init(inst, def);
|
|
defineLazy(inst._zod, "propValues", () => def.innerType._zod.propValues);
|
|
defineLazy(inst._zod, "values", () => def.innerType._zod.values);
|
|
defineLazy(inst._zod, "optin", () => def.innerType?._zod?.optin);
|
|
defineLazy(inst._zod, "optout", () => def.innerType?._zod?.optout);
|
|
inst._zod.parse = (payload, ctx) => {
|
|
if (ctx.direction === "backward") {
|
|
return def.innerType._zod.run(payload, ctx);
|
|
}
|
|
const result = def.innerType._zod.run(payload, ctx);
|
|
if (result instanceof Promise) {
|
|
return result.then(handleReadonlyResult);
|
|
}
|
|
return handleReadonlyResult(result);
|
|
};
|
|
});
|
|
function handleReadonlyResult(payload) {
|
|
payload.value = Object.freeze(payload.value);
|
|
return payload;
|
|
}
|
|
const $ZodCustom = /*@__PURE__*/ $constructor("$ZodCustom", (inst, def) => {
|
|
$ZodCheck.init(inst, def);
|
|
$ZodType.init(inst, def);
|
|
inst._zod.parse = (payload, _) => {
|
|
return payload;
|
|
};
|
|
inst._zod.check = (payload) => {
|
|
const input = payload.value;
|
|
const r = def.fn(input);
|
|
if (r instanceof Promise) {
|
|
return r.then((r) => handleRefineResult(r, payload, input, inst));
|
|
}
|
|
handleRefineResult(r, payload, input, inst);
|
|
return;
|
|
};
|
|
});
|
|
function handleRefineResult(result, payload, input, inst) {
|
|
if (!result) {
|
|
const _iss = {
|
|
code: "custom",
|
|
input,
|
|
inst, // incorporates params.error into issue reporting
|
|
path: [...(inst._zod.def.path ?? [])], // incorporates params.error into issue reporting
|
|
continue: !inst._zod.def.abort,
|
|
// params: inst._zod.def.params,
|
|
};
|
|
if (inst._zod.def.params)
|
|
_iss.params = inst._zod.def.params;
|
|
payload.issues.push(issue(_iss));
|
|
}
|
|
}
|
|
|
|
var _a;
|
|
class $ZodRegistry {
|
|
constructor() {
|
|
this._map = new WeakMap();
|
|
this._idmap = new Map();
|
|
}
|
|
add(schema, ..._meta) {
|
|
const meta = _meta[0];
|
|
this._map.set(schema, meta);
|
|
if (meta && typeof meta === "object" && "id" in meta) {
|
|
this._idmap.set(meta.id, schema);
|
|
}
|
|
return this;
|
|
}
|
|
clear() {
|
|
this._map = new WeakMap();
|
|
this._idmap = new Map();
|
|
return this;
|
|
}
|
|
remove(schema) {
|
|
const meta = this._map.get(schema);
|
|
if (meta && typeof meta === "object" && "id" in meta) {
|
|
this._idmap.delete(meta.id);
|
|
}
|
|
this._map.delete(schema);
|
|
return this;
|
|
}
|
|
get(schema) {
|
|
// return this._map.get(schema) as any;
|
|
// inherit metadata
|
|
const p = schema._zod.parent;
|
|
if (p) {
|
|
const pm = { ...(this.get(p) ?? {}) };
|
|
delete pm.id; // do not inherit id
|
|
const f = { ...pm, ...this._map.get(schema) };
|
|
return Object.keys(f).length ? f : undefined;
|
|
}
|
|
return this._map.get(schema);
|
|
}
|
|
has(schema) {
|
|
return this._map.has(schema);
|
|
}
|
|
}
|
|
// registries
|
|
function registry() {
|
|
return new $ZodRegistry();
|
|
}
|
|
(_a = globalThis).__zod_globalRegistry ?? (_a.__zod_globalRegistry = registry());
|
|
const globalRegistry = globalThis.__zod_globalRegistry;
|
|
|
|
// @__NO_SIDE_EFFECTS__
|
|
function _maxLength(maximum, params) {
|
|
const ch = new $ZodCheckMaxLength({
|
|
check: "max_length",
|
|
...normalizeParams(params),
|
|
maximum,
|
|
});
|
|
return ch;
|
|
}
|
|
// @__NO_SIDE_EFFECTS__
|
|
function _minLength(minimum, params) {
|
|
return new $ZodCheckMinLength({
|
|
check: "min_length",
|
|
...normalizeParams(params),
|
|
minimum,
|
|
});
|
|
}
|
|
// @__NO_SIDE_EFFECTS__
|
|
function _length(length, params) {
|
|
return new $ZodCheckLengthEquals({
|
|
check: "length_equals",
|
|
...normalizeParams(params),
|
|
length,
|
|
});
|
|
}
|
|
// @__NO_SIDE_EFFECTS__
|
|
function _overwrite(tx) {
|
|
return new $ZodCheckOverwrite({
|
|
check: "overwrite",
|
|
tx,
|
|
});
|
|
}
|
|
// @__NO_SIDE_EFFECTS__
|
|
function _array(Class, element, params) {
|
|
return new Class({
|
|
type: "array",
|
|
element,
|
|
// get element() {
|
|
// return element;
|
|
// },
|
|
...normalizeParams(params),
|
|
});
|
|
}
|
|
// same as _custom but defaults to abort:false
|
|
// @__NO_SIDE_EFFECTS__
|
|
function _refine(Class, fn, _params) {
|
|
const schema = new Class({
|
|
type: "custom",
|
|
check: "custom",
|
|
fn: fn,
|
|
...normalizeParams(_params),
|
|
});
|
|
return schema;
|
|
}
|
|
// @__NO_SIDE_EFFECTS__
|
|
function _superRefine(fn) {
|
|
const ch = _check((payload) => {
|
|
payload.addIssue = (issue$1) => {
|
|
if (typeof issue$1 === "string") {
|
|
payload.issues.push(issue(issue$1, payload.value, ch._zod.def));
|
|
}
|
|
else {
|
|
// for Zod 3 backwards compatibility
|
|
const _issue = issue$1;
|
|
if (_issue.fatal)
|
|
_issue.continue = false;
|
|
_issue.code ?? (_issue.code = "custom");
|
|
_issue.input ?? (_issue.input = payload.value);
|
|
_issue.inst ?? (_issue.inst = ch);
|
|
_issue.continue ?? (_issue.continue = !ch._zod.def.abort); // abort is always undefined, so this is always true...
|
|
payload.issues.push(issue(_issue));
|
|
}
|
|
};
|
|
return fn(payload.value, payload);
|
|
});
|
|
return ch;
|
|
}
|
|
// @__NO_SIDE_EFFECTS__
|
|
function _check(fn, params) {
|
|
const ch = new $ZodCheck({
|
|
check: "custom",
|
|
...normalizeParams(params),
|
|
});
|
|
ch._zod.check = fn;
|
|
return ch;
|
|
}
|
|
|
|
// function initializeContext<T extends schemas.$ZodType>(inputs: JSONSchemaGeneratorParams<T>): ToJSONSchemaContext<T> {
|
|
// return {
|
|
// processor: inputs.processor,
|
|
// metadataRegistry: inputs.metadata ?? globalRegistry,
|
|
// target: inputs.target ?? "draft-2020-12",
|
|
// unrepresentable: inputs.unrepresentable ?? "throw",
|
|
// };
|
|
// }
|
|
function initializeContext(params) {
|
|
// Normalize target: convert old non-hyphenated versions to hyphenated versions
|
|
let target = params?.target ?? "draft-2020-12";
|
|
if (target === "draft-4")
|
|
target = "draft-04";
|
|
if (target === "draft-7")
|
|
target = "draft-07";
|
|
return {
|
|
processors: params.processors ?? {},
|
|
metadataRegistry: params?.metadata ?? globalRegistry,
|
|
target,
|
|
unrepresentable: params?.unrepresentable ?? "throw",
|
|
override: params?.override ?? (() => { }),
|
|
io: params?.io ?? "output",
|
|
counter: 0,
|
|
seen: new Map(),
|
|
cycles: params?.cycles ?? "ref",
|
|
reused: params?.reused ?? "inline",
|
|
external: params?.external ?? undefined,
|
|
};
|
|
}
|
|
function process$1(schema, ctx, _params = { path: [], schemaPath: [] }) {
|
|
var _a;
|
|
const def = schema._zod.def;
|
|
// check for schema in seens
|
|
const seen = ctx.seen.get(schema);
|
|
if (seen) {
|
|
seen.count++;
|
|
// check if cycle
|
|
const isCycle = _params.schemaPath.includes(schema);
|
|
if (isCycle) {
|
|
seen.cycle = _params.path;
|
|
}
|
|
return seen.schema;
|
|
}
|
|
// initialize
|
|
const result = { schema: {}, count: 1, cycle: undefined, path: _params.path };
|
|
ctx.seen.set(schema, result);
|
|
// custom method overrides default behavior
|
|
const overrideSchema = schema._zod.toJSONSchema?.();
|
|
if (overrideSchema) {
|
|
result.schema = overrideSchema;
|
|
}
|
|
else {
|
|
const params = {
|
|
..._params,
|
|
schemaPath: [..._params.schemaPath, schema],
|
|
path: _params.path,
|
|
};
|
|
if (schema._zod.processJSONSchema) {
|
|
schema._zod.processJSONSchema(ctx, result.schema, params);
|
|
}
|
|
else {
|
|
const _json = result.schema;
|
|
const processor = ctx.processors[def.type];
|
|
if (!processor) {
|
|
throw new Error(`[toJSONSchema]: Non-representable type encountered: ${def.type}`);
|
|
}
|
|
processor(schema, ctx, _json, params);
|
|
}
|
|
const parent = schema._zod.parent;
|
|
if (parent) {
|
|
// Also set ref if processor didn't (for inheritance)
|
|
if (!result.ref)
|
|
result.ref = parent;
|
|
process$1(parent, ctx, params);
|
|
ctx.seen.get(parent).isParent = true;
|
|
}
|
|
}
|
|
// metadata
|
|
const meta = ctx.metadataRegistry.get(schema);
|
|
if (meta)
|
|
Object.assign(result.schema, meta);
|
|
if (ctx.io === "input" && isTransforming(schema)) {
|
|
// examples/defaults only apply to output type of pipe
|
|
delete result.schema.examples;
|
|
delete result.schema.default;
|
|
}
|
|
// set prefault as default
|
|
if (ctx.io === "input" && result.schema._prefault)
|
|
(_a = result.schema).default ?? (_a.default = result.schema._prefault);
|
|
delete result.schema._prefault;
|
|
// pulling fresh from ctx.seen in case it was overwritten
|
|
const _result = ctx.seen.get(schema);
|
|
return _result.schema;
|
|
}
|
|
function extractDefs(ctx, schema
|
|
// params: EmitParams
|
|
) {
|
|
// iterate over seen map;
|
|
const root = ctx.seen.get(schema);
|
|
if (!root)
|
|
throw new Error("Unprocessed schema. This is a bug in Zod.");
|
|
// Track ids to detect duplicates across different schemas
|
|
const idToSchema = new Map();
|
|
for (const entry of ctx.seen.entries()) {
|
|
const id = ctx.metadataRegistry.get(entry[0])?.id;
|
|
if (id) {
|
|
const existing = idToSchema.get(id);
|
|
if (existing && existing !== entry[0]) {
|
|
throw new Error(`Duplicate schema id "${id}" detected during JSON Schema conversion. Two different schemas cannot share the same id when converted together.`);
|
|
}
|
|
idToSchema.set(id, entry[0]);
|
|
}
|
|
}
|
|
// returns a ref to the schema
|
|
// defId will be empty if the ref points to an external schema (or #)
|
|
const makeURI = (entry) => {
|
|
// comparing the seen objects because sometimes
|
|
// multiple schemas map to the same seen object.
|
|
// e.g. lazy
|
|
// external is configured
|
|
const defsSegment = ctx.target === "draft-2020-12" ? "$defs" : "definitions";
|
|
if (ctx.external) {
|
|
const externalId = ctx.external.registry.get(entry[0])?.id; // ?? "__shared";// `__schema${ctx.counter++}`;
|
|
// check if schema is in the external registry
|
|
const uriGenerator = ctx.external.uri ?? ((id) => id);
|
|
if (externalId) {
|
|
return { ref: uriGenerator(externalId) };
|
|
}
|
|
// otherwise, add to __shared
|
|
const id = entry[1].defId ?? entry[1].schema.id ?? `schema${ctx.counter++}`;
|
|
entry[1].defId = id; // set defId so it will be reused if needed
|
|
return { defId: id, ref: `${uriGenerator("__shared")}#/${defsSegment}/${id}` };
|
|
}
|
|
if (entry[1] === root) {
|
|
return { ref: "#" };
|
|
}
|
|
// self-contained schema
|
|
const uriPrefix = `#`;
|
|
const defUriPrefix = `${uriPrefix}/${defsSegment}/`;
|
|
const defId = entry[1].schema.id ?? `__schema${ctx.counter++}`;
|
|
return { defId, ref: defUriPrefix + defId };
|
|
};
|
|
// stored cached version in `def` property
|
|
// remove all properties, set $ref
|
|
const extractToDef = (entry) => {
|
|
// if the schema is already a reference, do not extract it
|
|
if (entry[1].schema.$ref) {
|
|
return;
|
|
}
|
|
const seen = entry[1];
|
|
const { ref, defId } = makeURI(entry);
|
|
seen.def = { ...seen.schema };
|
|
// defId won't be set if the schema is a reference to an external schema
|
|
// or if the schema is the root schema
|
|
if (defId)
|
|
seen.defId = defId;
|
|
// wipe away all properties except $ref
|
|
const schema = seen.schema;
|
|
for (const key in schema) {
|
|
delete schema[key];
|
|
}
|
|
schema.$ref = ref;
|
|
};
|
|
// throw on cycles
|
|
// break cycles
|
|
if (ctx.cycles === "throw") {
|
|
for (const entry of ctx.seen.entries()) {
|
|
const seen = entry[1];
|
|
if (seen.cycle) {
|
|
throw new Error("Cycle detected: " +
|
|
`#/${seen.cycle?.join("/")}/<root>` +
|
|
'\n\nSet the `cycles` parameter to `"ref"` to resolve cyclical schemas with defs.');
|
|
}
|
|
}
|
|
}
|
|
// extract schemas into $defs
|
|
for (const entry of ctx.seen.entries()) {
|
|
const seen = entry[1];
|
|
// convert root schema to # $ref
|
|
if (schema === entry[0]) {
|
|
extractToDef(entry); // this has special handling for the root schema
|
|
continue;
|
|
}
|
|
// extract schemas that are in the external registry
|
|
if (ctx.external) {
|
|
const ext = ctx.external.registry.get(entry[0])?.id;
|
|
if (schema !== entry[0] && ext) {
|
|
extractToDef(entry);
|
|
continue;
|
|
}
|
|
}
|
|
// extract schemas with `id` meta
|
|
const id = ctx.metadataRegistry.get(entry[0])?.id;
|
|
if (id) {
|
|
extractToDef(entry);
|
|
continue;
|
|
}
|
|
// break cycles
|
|
if (seen.cycle) {
|
|
// any
|
|
extractToDef(entry);
|
|
continue;
|
|
}
|
|
// extract reused schemas
|
|
if (seen.count > 1) {
|
|
if (ctx.reused === "ref") {
|
|
extractToDef(entry);
|
|
// biome-ignore lint:
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
function finalize(ctx, schema) {
|
|
const root = ctx.seen.get(schema);
|
|
if (!root)
|
|
throw new Error("Unprocessed schema. This is a bug in Zod.");
|
|
// flatten refs - inherit properties from parent schemas
|
|
const flattenRef = (zodSchema) => {
|
|
const seen = ctx.seen.get(zodSchema);
|
|
// already processed
|
|
if (seen.ref === null)
|
|
return;
|
|
const schema = seen.def ?? seen.schema;
|
|
const _cached = { ...schema };
|
|
const ref = seen.ref;
|
|
seen.ref = null; // prevent infinite recursion
|
|
if (ref) {
|
|
flattenRef(ref);
|
|
const refSeen = ctx.seen.get(ref);
|
|
const refSchema = refSeen.schema;
|
|
// merge referenced schema into current
|
|
if (refSchema.$ref && (ctx.target === "draft-07" || ctx.target === "draft-04" || ctx.target === "openapi-3.0")) {
|
|
// older drafts can't combine $ref with other properties
|
|
schema.allOf = schema.allOf ?? [];
|
|
schema.allOf.push(refSchema);
|
|
}
|
|
else {
|
|
Object.assign(schema, refSchema);
|
|
}
|
|
// restore child's own properties (child wins)
|
|
Object.assign(schema, _cached);
|
|
const isParentRef = zodSchema._zod.parent === ref;
|
|
// For parent chain, child is a refinement - remove parent-only properties
|
|
if (isParentRef) {
|
|
for (const key in schema) {
|
|
if (key === "$ref" || key === "allOf")
|
|
continue;
|
|
if (!(key in _cached)) {
|
|
delete schema[key];
|
|
}
|
|
}
|
|
}
|
|
// When ref was extracted to $defs, remove properties that match the definition
|
|
if (refSchema.$ref && refSeen.def) {
|
|
for (const key in schema) {
|
|
if (key === "$ref" || key === "allOf")
|
|
continue;
|
|
if (key in refSeen.def && JSON.stringify(schema[key]) === JSON.stringify(refSeen.def[key])) {
|
|
delete schema[key];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// If parent was extracted (has $ref), propagate $ref to this schema
|
|
// This handles cases like: readonly().meta({id}).describe()
|
|
// where processor sets ref to innerType but parent should be referenced
|
|
const parent = zodSchema._zod.parent;
|
|
if (parent && parent !== ref) {
|
|
// Ensure parent is processed first so its def has inherited properties
|
|
flattenRef(parent);
|
|
const parentSeen = ctx.seen.get(parent);
|
|
if (parentSeen?.schema.$ref) {
|
|
schema.$ref = parentSeen.schema.$ref;
|
|
// De-duplicate with parent's definition
|
|
if (parentSeen.def) {
|
|
for (const key in schema) {
|
|
if (key === "$ref" || key === "allOf")
|
|
continue;
|
|
if (key in parentSeen.def && JSON.stringify(schema[key]) === JSON.stringify(parentSeen.def[key])) {
|
|
delete schema[key];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// execute overrides
|
|
ctx.override({
|
|
zodSchema: zodSchema,
|
|
jsonSchema: schema,
|
|
path: seen.path ?? [],
|
|
});
|
|
};
|
|
for (const entry of [...ctx.seen.entries()].reverse()) {
|
|
flattenRef(entry[0]);
|
|
}
|
|
const result = {};
|
|
if (ctx.target === "draft-2020-12") {
|
|
result.$schema = "https://json-schema.org/draft/2020-12/schema";
|
|
}
|
|
else if (ctx.target === "draft-07") {
|
|
result.$schema = "http://json-schema.org/draft-07/schema#";
|
|
}
|
|
else if (ctx.target === "draft-04") {
|
|
result.$schema = "http://json-schema.org/draft-04/schema#";
|
|
}
|
|
else if (ctx.target === "openapi-3.0") ;
|
|
else ;
|
|
if (ctx.external?.uri) {
|
|
const id = ctx.external.registry.get(schema)?.id;
|
|
if (!id)
|
|
throw new Error("Schema is missing an `id` property");
|
|
result.$id = ctx.external.uri(id);
|
|
}
|
|
Object.assign(result, root.def ?? root.schema);
|
|
// build defs object
|
|
const defs = ctx.external?.defs ?? {};
|
|
for (const entry of ctx.seen.entries()) {
|
|
const seen = entry[1];
|
|
if (seen.def && seen.defId) {
|
|
defs[seen.defId] = seen.def;
|
|
}
|
|
}
|
|
// set definitions in result
|
|
if (ctx.external) ;
|
|
else {
|
|
if (Object.keys(defs).length > 0) {
|
|
if (ctx.target === "draft-2020-12") {
|
|
result.$defs = defs;
|
|
}
|
|
else {
|
|
result.definitions = defs;
|
|
}
|
|
}
|
|
}
|
|
try {
|
|
// this "finalizes" this schema and ensures all cycles are removed
|
|
// each call to finalize() is functionally independent
|
|
// though the seen map is shared
|
|
const finalized = JSON.parse(JSON.stringify(result));
|
|
Object.defineProperty(finalized, "~standard", {
|
|
value: {
|
|
...schema["~standard"],
|
|
jsonSchema: {
|
|
input: createStandardJSONSchemaMethod(schema, "input", ctx.processors),
|
|
output: createStandardJSONSchemaMethod(schema, "output", ctx.processors),
|
|
},
|
|
},
|
|
enumerable: false,
|
|
writable: false,
|
|
});
|
|
return finalized;
|
|
}
|
|
catch (_err) {
|
|
throw new Error("Error converting schema to JSON.");
|
|
}
|
|
}
|
|
function isTransforming(_schema, _ctx) {
|
|
const ctx = _ctx ?? { seen: new Set() };
|
|
if (ctx.seen.has(_schema))
|
|
return false;
|
|
ctx.seen.add(_schema);
|
|
const def = _schema._zod.def;
|
|
if (def.type === "transform")
|
|
return true;
|
|
if (def.type === "array")
|
|
return isTransforming(def.element, ctx);
|
|
if (def.type === "set")
|
|
return isTransforming(def.valueType, ctx);
|
|
if (def.type === "lazy")
|
|
return isTransforming(def.getter(), ctx);
|
|
if (def.type === "promise" ||
|
|
def.type === "optional" ||
|
|
def.type === "nonoptional" ||
|
|
def.type === "nullable" ||
|
|
def.type === "readonly" ||
|
|
def.type === "default" ||
|
|
def.type === "prefault") {
|
|
return isTransforming(def.innerType, ctx);
|
|
}
|
|
if (def.type === "intersection") {
|
|
return isTransforming(def.left, ctx) || isTransforming(def.right, ctx);
|
|
}
|
|
if (def.type === "record" || def.type === "map") {
|
|
return isTransforming(def.keyType, ctx) || isTransforming(def.valueType, ctx);
|
|
}
|
|
if (def.type === "pipe") {
|
|
return isTransforming(def.in, ctx) || isTransforming(def.out, ctx);
|
|
}
|
|
if (def.type === "object") {
|
|
for (const key in def.shape) {
|
|
if (isTransforming(def.shape[key], ctx))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
if (def.type === "union") {
|
|
for (const option of def.options) {
|
|
if (isTransforming(option, ctx))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
if (def.type === "tuple") {
|
|
for (const item of def.items) {
|
|
if (isTransforming(item, ctx))
|
|
return true;
|
|
}
|
|
if (def.rest && isTransforming(def.rest, ctx))
|
|
return true;
|
|
return false;
|
|
}
|
|
return false;
|
|
}
|
|
/**
|
|
* Creates a toJSONSchema method for a schema instance.
|
|
* This encapsulates the logic of initializing context, processing, extracting defs, and finalizing.
|
|
*/
|
|
const createToJSONSchemaMethod = (schema, processors = {}) => (params) => {
|
|
const ctx = initializeContext({ ...params, processors });
|
|
process$1(schema, ctx);
|
|
extractDefs(ctx, schema);
|
|
return finalize(ctx, schema);
|
|
};
|
|
const createStandardJSONSchemaMethod = (schema, io, processors = {}) => (params) => {
|
|
const { libraryOptions, target } = params ?? {};
|
|
const ctx = initializeContext({ ...(libraryOptions ?? {}), target, io, processors });
|
|
process$1(schema, ctx);
|
|
extractDefs(ctx, schema);
|
|
return finalize(ctx, schema);
|
|
};
|
|
|
|
const enumProcessor = (schema, _ctx, json, _params) => {
|
|
const def = schema._zod.def;
|
|
const values = getEnumValues(def.entries);
|
|
// Number enums can have both string and number values
|
|
if (values.every((v) => typeof v === "number"))
|
|
json.type = "number";
|
|
if (values.every((v) => typeof v === "string"))
|
|
json.type = "string";
|
|
json.enum = values;
|
|
};
|
|
const customProcessor = (_schema, ctx, _json, _params) => {
|
|
if (ctx.unrepresentable === "throw") {
|
|
throw new Error("Custom types cannot be represented in JSON Schema");
|
|
}
|
|
};
|
|
const transformProcessor = (_schema, ctx, _json, _params) => {
|
|
if (ctx.unrepresentable === "throw") {
|
|
throw new Error("Transforms cannot be represented in JSON Schema");
|
|
}
|
|
};
|
|
// ==================== COMPOSITE TYPE PROCESSORS ====================
|
|
const arrayProcessor = (schema, ctx, _json, params) => {
|
|
const json = _json;
|
|
const def = schema._zod.def;
|
|
const { minimum, maximum } = schema._zod.bag;
|
|
if (typeof minimum === "number")
|
|
json.minItems = minimum;
|
|
if (typeof maximum === "number")
|
|
json.maxItems = maximum;
|
|
json.type = "array";
|
|
json.items = process$1(def.element, ctx, { ...params, path: [...params.path, "items"] });
|
|
};
|
|
const unionProcessor = (schema, ctx, json, params) => {
|
|
const def = schema._zod.def;
|
|
// Exclusive unions (inclusive === false) use oneOf (exactly one match) instead of anyOf (one or more matches)
|
|
// This includes both z.xor() and discriminated unions
|
|
const isExclusive = def.inclusive === false;
|
|
const options = def.options.map((x, i) => process$1(x, ctx, {
|
|
...params,
|
|
path: [...params.path, isExclusive ? "oneOf" : "anyOf", i],
|
|
}));
|
|
if (isExclusive) {
|
|
json.oneOf = options;
|
|
}
|
|
else {
|
|
json.anyOf = options;
|
|
}
|
|
};
|
|
const intersectionProcessor = (schema, ctx, json, params) => {
|
|
const def = schema._zod.def;
|
|
const a = process$1(def.left, ctx, {
|
|
...params,
|
|
path: [...params.path, "allOf", 0],
|
|
});
|
|
const b = process$1(def.right, ctx, {
|
|
...params,
|
|
path: [...params.path, "allOf", 1],
|
|
});
|
|
const isSimpleIntersection = (val) => "allOf" in val && Object.keys(val).length === 1;
|
|
const allOf = [
|
|
...(isSimpleIntersection(a) ? a.allOf : [a]),
|
|
...(isSimpleIntersection(b) ? b.allOf : [b]),
|
|
];
|
|
json.allOf = allOf;
|
|
};
|
|
const nullableProcessor = (schema, ctx, json, params) => {
|
|
const def = schema._zod.def;
|
|
const inner = process$1(def.innerType, ctx, params);
|
|
const seen = ctx.seen.get(schema);
|
|
if (ctx.target === "openapi-3.0") {
|
|
seen.ref = def.innerType;
|
|
json.nullable = true;
|
|
}
|
|
else {
|
|
json.anyOf = [inner, { type: "null" }];
|
|
}
|
|
};
|
|
const nonoptionalProcessor = (schema, ctx, _json, params) => {
|
|
const def = schema._zod.def;
|
|
process$1(def.innerType, ctx, params);
|
|
const seen = ctx.seen.get(schema);
|
|
seen.ref = def.innerType;
|
|
};
|
|
const defaultProcessor = (schema, ctx, json, params) => {
|
|
const def = schema._zod.def;
|
|
process$1(def.innerType, ctx, params);
|
|
const seen = ctx.seen.get(schema);
|
|
seen.ref = def.innerType;
|
|
json.default = JSON.parse(JSON.stringify(def.defaultValue));
|
|
};
|
|
const prefaultProcessor = (schema, ctx, json, params) => {
|
|
const def = schema._zod.def;
|
|
process$1(def.innerType, ctx, params);
|
|
const seen = ctx.seen.get(schema);
|
|
seen.ref = def.innerType;
|
|
if (ctx.io === "input")
|
|
json._prefault = JSON.parse(JSON.stringify(def.defaultValue));
|
|
};
|
|
const catchProcessor = (schema, ctx, json, params) => {
|
|
const def = schema._zod.def;
|
|
process$1(def.innerType, ctx, params);
|
|
const seen = ctx.seen.get(schema);
|
|
seen.ref = def.innerType;
|
|
let catchValue;
|
|
try {
|
|
catchValue = def.catchValue(undefined);
|
|
}
|
|
catch {
|
|
throw new Error("Dynamic catch values are not supported in JSON Schema");
|
|
}
|
|
json.default = catchValue;
|
|
};
|
|
const pipeProcessor = (schema, ctx, _json, params) => {
|
|
const def = schema._zod.def;
|
|
const innerType = ctx.io === "input" ? (def.in._zod.def.type === "transform" ? def.out : def.in) : def.out;
|
|
process$1(innerType, ctx, params);
|
|
const seen = ctx.seen.get(schema);
|
|
seen.ref = innerType;
|
|
};
|
|
const readonlyProcessor = (schema, ctx, json, params) => {
|
|
const def = schema._zod.def;
|
|
process$1(def.innerType, ctx, params);
|
|
const seen = ctx.seen.get(schema);
|
|
seen.ref = def.innerType;
|
|
json.readOnly = true;
|
|
};
|
|
const optionalProcessor = (schema, ctx, _json, params) => {
|
|
const def = schema._zod.def;
|
|
process$1(def.innerType, ctx, params);
|
|
const seen = ctx.seen.get(schema);
|
|
seen.ref = def.innerType;
|
|
};
|
|
|
|
async function readBodyWithLimit(request, limit) {
|
|
const contentLengthHeader = request.headers.get("content-length");
|
|
if (contentLengthHeader) {
|
|
const contentLength = Number.parseInt(contentLengthHeader, 10);
|
|
if (Number.isFinite(contentLength) && contentLength > limit) {
|
|
throw new BodySizeLimitError(limit);
|
|
}
|
|
}
|
|
if (!request.body) return new Uint8Array();
|
|
const reader = request.body.getReader();
|
|
const chunks = [];
|
|
let received = 0;
|
|
while (true) {
|
|
const { done, value } = await reader.read();
|
|
if (done) break;
|
|
if (value) {
|
|
received += value.byteLength;
|
|
if (received > limit) {
|
|
throw new BodySizeLimitError(limit);
|
|
}
|
|
chunks.push(value);
|
|
}
|
|
}
|
|
const buffer = new Uint8Array(received);
|
|
let offset = 0;
|
|
for (const chunk of chunks) {
|
|
buffer.set(chunk, offset);
|
|
offset += chunk.byteLength;
|
|
}
|
|
return buffer;
|
|
}
|
|
class BodySizeLimitError extends Error {
|
|
limit;
|
|
constructor(limit) {
|
|
super(`Request body exceeds the configured limit of ${limit} bytes`);
|
|
this.name = "BodySizeLimitError";
|
|
this.limit = limit;
|
|
}
|
|
}
|
|
|
|
const __vite_import_meta_env__ = {"ASSETS_PREFIX": undefined, "BASE_URL": "/", "DEV": false, "MODE": "production", "PROD": true, "SITE": "https://www.upiecep.org", "SSR": true};
|
|
function getActionContext(context) {
|
|
const callerInfo = getCallerInfo(context);
|
|
const actionResultAlreadySet = Boolean(context.locals._actionPayload);
|
|
let action = void 0;
|
|
if (callerInfo && context.request.method === "POST" && !actionResultAlreadySet) {
|
|
action = {
|
|
calledFrom: callerInfo.from,
|
|
name: callerInfo.name,
|
|
handler: async () => {
|
|
const pipeline = Reflect.get(context, pipelineSymbol);
|
|
const callerInfoName = shouldAppendForwardSlash(
|
|
pipeline.manifest.trailingSlash,
|
|
pipeline.manifest.buildFormat
|
|
) ? removeTrailingForwardSlash(callerInfo.name) : callerInfo.name;
|
|
let baseAction;
|
|
try {
|
|
baseAction = await pipeline.getAction(callerInfoName);
|
|
} catch (error) {
|
|
if (error instanceof Error && "name" in error && typeof error.name === "string" && error.name === ActionNotFoundError.name) {
|
|
return { data: void 0, error: new ActionError({ code: "NOT_FOUND" }) };
|
|
}
|
|
throw error;
|
|
}
|
|
const bodySizeLimit = pipeline.manifest.actionBodySizeLimit;
|
|
let input;
|
|
try {
|
|
input = await parseRequestBody(context.request, bodySizeLimit);
|
|
} catch (e) {
|
|
if (e instanceof ActionError) {
|
|
return { data: void 0, error: e };
|
|
}
|
|
if (e instanceof TypeError) {
|
|
return { data: void 0, error: new ActionError({ code: "UNSUPPORTED_MEDIA_TYPE" }) };
|
|
}
|
|
throw e;
|
|
}
|
|
const omitKeys = ["props", "getActionResult", "callAction", "redirect"];
|
|
const actionAPIContext = Object.create(
|
|
Object.getPrototypeOf(context),
|
|
Object.fromEntries(
|
|
Object.entries(Object.getOwnPropertyDescriptors(context)).filter(
|
|
([key]) => !omitKeys.includes(key)
|
|
)
|
|
)
|
|
);
|
|
Reflect.set(actionAPIContext, ACTION_API_CONTEXT_SYMBOL, true);
|
|
const handler = baseAction.bind(actionAPIContext);
|
|
return handler(input);
|
|
}
|
|
};
|
|
}
|
|
function setActionResult(actionName, actionResult) {
|
|
context.locals._actionPayload = {
|
|
actionResult,
|
|
actionName
|
|
};
|
|
}
|
|
return {
|
|
action,
|
|
setActionResult,
|
|
serializeActionResult,
|
|
deserializeActionResult
|
|
};
|
|
}
|
|
function getCallerInfo(ctx) {
|
|
if (ctx.routePattern === ACTION_RPC_ROUTE_PATTERN) {
|
|
return { from: "rpc", name: ctx.url.pathname.replace(/^.*\/_actions\//, "") };
|
|
}
|
|
const queryParam = ctx.url.searchParams.get(ACTION_QUERY_PARAMS.actionName);
|
|
if (queryParam) {
|
|
return { from: "form", name: queryParam };
|
|
}
|
|
return void 0;
|
|
}
|
|
async function parseRequestBody(request, bodySizeLimit) {
|
|
const contentType = request.headers.get("content-type");
|
|
const contentLengthHeader = request.headers.get("content-length");
|
|
const contentLength = contentLengthHeader ? Number.parseInt(contentLengthHeader, 10) : void 0;
|
|
const hasContentLength = typeof contentLength === "number" && Number.isFinite(contentLength);
|
|
if (!contentType) return void 0;
|
|
if (hasContentLength && contentLength > bodySizeLimit) {
|
|
throw new ActionError({
|
|
code: "CONTENT_TOO_LARGE",
|
|
message: `Request body exceeds ${bodySizeLimit} bytes`
|
|
});
|
|
}
|
|
try {
|
|
if (hasContentType(contentType, formContentTypes$1)) {
|
|
if (!hasContentLength) {
|
|
const body = await readBodyWithLimit(request.clone(), bodySizeLimit);
|
|
const formRequest = new Request(request.url, {
|
|
method: request.method,
|
|
headers: request.headers,
|
|
body: toArrayBuffer(body)
|
|
});
|
|
return await formRequest.formData();
|
|
}
|
|
return await request.clone().formData();
|
|
}
|
|
if (hasContentType(contentType, ["application/json"])) {
|
|
if (contentLength === 0) return void 0;
|
|
if (!hasContentLength) {
|
|
const body = await readBodyWithLimit(request.clone(), bodySizeLimit);
|
|
if (body.byteLength === 0) return void 0;
|
|
return JSON.parse(new TextDecoder().decode(body));
|
|
}
|
|
return await request.clone().json();
|
|
}
|
|
} catch (e) {
|
|
if (e instanceof BodySizeLimitError) {
|
|
throw new ActionError({
|
|
code: "CONTENT_TOO_LARGE",
|
|
message: `Request body exceeds ${bodySizeLimit} bytes`
|
|
});
|
|
}
|
|
throw e;
|
|
}
|
|
throw new TypeError("Unsupported content type");
|
|
}
|
|
const ACTION_API_CONTEXT_SYMBOL = /* @__PURE__ */ Symbol.for("astro.actionAPIContext");
|
|
const formContentTypes$1 = ["application/x-www-form-urlencoded", "multipart/form-data"];
|
|
function hasContentType(contentType, expected) {
|
|
const type = contentType.split(";")[0].toLowerCase();
|
|
return expected.some((t) => type === t);
|
|
}
|
|
function serializeActionResult(res) {
|
|
if (res.error) {
|
|
if (Object.assign(__vite_import_meta_env__, {})?.DEV) {
|
|
actionResultErrorStack.set(res.error.stack);
|
|
}
|
|
let body2;
|
|
if (res.error instanceof ActionInputError) {
|
|
body2 = {
|
|
type: res.error.type,
|
|
issues: res.error.issues,
|
|
fields: res.error.fields
|
|
};
|
|
} else {
|
|
body2 = {
|
|
...res.error,
|
|
message: res.error.message
|
|
};
|
|
}
|
|
return {
|
|
type: "error",
|
|
status: res.error.status,
|
|
contentType: "application/json",
|
|
body: JSON.stringify(body2)
|
|
};
|
|
}
|
|
if (res.data === void 0) {
|
|
return {
|
|
type: "empty",
|
|
status: 204
|
|
};
|
|
}
|
|
let body;
|
|
try {
|
|
body = stringify$2(res.data, {
|
|
// Add support for URL objects
|
|
URL: (value) => value instanceof URL && value.href
|
|
});
|
|
} catch (e) {
|
|
let hint = ActionsReturnedInvalidDataError.hint;
|
|
if (res.data instanceof Response) {
|
|
hint = REDIRECT_STATUS_CODES.includes(res.data.status) ? "If you need to redirect when the action succeeds, trigger a redirect where the action is called. See the Actions guide for server and client redirect examples: https://docs.astro.build/en/guides/actions." : "If you need to return a Response object, try using a server endpoint instead. See https://docs.astro.build/en/guides/endpoints/#server-endpoints-api-routes";
|
|
}
|
|
throw new AstroError({
|
|
...ActionsReturnedInvalidDataError,
|
|
message: ActionsReturnedInvalidDataError.message(String(e)),
|
|
hint
|
|
});
|
|
}
|
|
return {
|
|
type: "data",
|
|
status: 200,
|
|
contentType: "application/json+devalue",
|
|
body
|
|
};
|
|
}
|
|
function toArrayBuffer(buffer) {
|
|
const copy = new Uint8Array(buffer.byteLength);
|
|
copy.set(buffer);
|
|
return copy.buffer;
|
|
}
|
|
|
|
/* es-module-lexer 2.1.0 */
|
|
var ImportType;!function(A){A[A.Static=1]="Static",A[A.Dynamic=2]="Dynamic",A[A.ImportMeta=3]="ImportMeta",A[A.StaticSourcePhase=4]="StaticSourcePhase",A[A.DynamicSourcePhase=5]="DynamicSourcePhase",A[A.StaticDeferPhase=6]="StaticDeferPhase",A[A.DynamicDeferPhase=7]="DynamicDeferPhase";}(ImportType||(ImportType={}));1===new Uint8Array(new Uint16Array([1]).buffer)[0];const E=()=>{return A="AGFzbQEAAAABKwhgAX8Bf2AEf39/fwBgAAF/YAAAYAF/AGADf39/AX9gAn9/AX9gA39/fwADODcAAQECAgICAgICAgICAgICAgICAgICAgICAwIAAwMDBAAEAAAABQAAAAAAAwMDAAAGAAcABgIFBAUBcAEBAQUDAQABBg8CfwFBsPIAC38AQbDyAAsHnQEbBm1lbW9yeQIAAnNhAAABZQADAmlzAAQCaWUABQJzcwAGAnNlAAcCaXQACAJhaQAJAmlkAAoCaXAACwJlcwAMAmVlAA0DZWxzAA4DZWxlAA8CcmkAEAJyZQARAWYAEgJtcwATAnJhABQDYWtzABUDYWtlABYDYXZzABcDYXZlABgDcnNhABkFcGFyc2UAGgtfX2hlYXBfYmFzZQMBCrxJN2gBAX9BACAANgL0CUEAKALQCSIBIABBAXRqIgBBADsBAEEAIABBAmoiADYC+AlBACAANgL8CUEAQQA2AtQJQQBBADYC5AlBAEEANgLcCUEAQQA2AtgJQQBBADYC7AlBAEEANgLgCSABC9MBAQN/QQAoAuQJIQRBAEEAKAL8CSIFNgLkCUEAIAQ2AugJQQAgBUEoajYC/AkgBEEkakHUCSAEGyAFNgIAQQAoAsgJIQRBACgCxAkhBiAFIAE2AgAgBSAANgIIIAUgAiACQQJqQQAgBiADRiIAGyAEIANGIgQbNgIMIAUgAzYCFCAFQQA2AhAgBSACNgIEIAVCADcCICAFQQNBAUECIAAbIAQbNgIcIAVBACgCxAkgA0YiAjoAGAJAAkAgAg0AQQAoAsgJIANHDQELQQBBAToAgAoLC14BAX9BACgC7AkiBEEQakHYCSAEG0EAKAL8CSIENgIAQQAgBDYC7AlBACAEQRRqNgL8CUEAQQE6AIAKIARBADYCECAEIAM2AgwgBCACNgIIIAQgATYCBCAEIAA2AgALCABBACgChAoLFQBBACgC3AkoAgBBACgC0AlrQQF1Cx4BAX9BACgC3AkoAgQiAEEAKALQCWtBAXVBfyAAGwsVAEEAKALcCSgCCEEAKALQCWtBAXULHgEBf0EAKALcCSgCDCIAQQAoAtAJa0EBdUF/IAAbCwsAQQAoAtwJKAIcCx4BAX9BACgC3AkoAhAiAEEAKALQCWtBAXVBfyAAGws7AQF/AkBBACgC3AkoAhQiAEEAKALECUcNAEF/DwsCQCAAQQAoAsgJRw0AQX4PCyAAQQAoAtAJa0EBdQsLAEEAKALcCS0AGAsVAEEAKALgCSgCAEEAKALQCWtBAXULFQBBACgC4AkoAgRBACgC0AlrQQF1Cx4BAX9BACgC4AkoAggiAEEAKALQCWtBAXVBfyAAGwseAQF/QQAoAuAJKAIMIgBBACgC0AlrQQF1QX8gABsLJQEBf0EAQQAoAtwJIgBBJGpB1AkgABsoAgAiADYC3AkgAEEARwslAQF/QQBBACgC4AkiAEEQakHYCSAAGygCACIANgLgCSAAQQBHCwgAQQAtAIgKCwgAQQAtAIAKCysBAX9BAEEAKAKMCiIAQRBqQQAoAtwJQSBqIAAbKAIAIgA2AowKIABBAEcLFQBBACgCjAooAgBBACgC0AlrQQF1CxUAQQAoAowKKAIEQQAoAtAJa0EBdQsVAEEAKAKMCigCCEEAKALQCWtBAXULFQBBACgCjAooAgxBACgC0AlrQQF1CwoAQQBBADYCjAoLuw8BBX8jAEGA0ABrIgAkAEEAQQE6AIgKQQBBACgCzAk2ApQKQQBBACgC0AlBfmoiATYCqApBACABQQAoAvQJQQF0aiICNgKsCkEAQQA6AIAKQQBBADsBkApBAEEAOwGSCkEAQQA6AJgKQQBBADYChApBAEEAOgDwCUEAIABBgBBqNgKcCkEAIAA2AqAKQQBBADoApAoCQAJAAkACQANAQQAgAUECaiIDNgKoCiABIAJPDQECQCADLwEAIgJBd2pBBUkNAAJAAkACQAJAAkAgAkGbf2oOBQEICAgCAAsgAkEgRg0EIAJBL0YNAyACQTtGDQIMBwtBAC8BkgoNASADEBtFDQEgAUEEakGCCEEKEDYNARAcQQAtAIgKDQFBAEEAKAKoCiIBNgKUCgwHCyADEBtFDQAgAUEEakGMCEEKEDYNABAdC0EAQQAoAqgKNgKUCgwBCwJAIAEvAQQiA0EqRg0AIANBL0cNBBAeDAELQQEQHwtBACgCrAohAkEAKAKoCiEBDAALC0EAIQIgAyEBQQAtAPAJDQIMAQtBACABNgKoCkEAQQA6AIgKCwNAQQAgAUECaiIDNgKoCgJAAkACQAJAAkACQAJAIAFBACgCrApPDQACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQCADLwEAIgJBYGoOEBMSCRISEhIIAQUSEgQSEgoACwJAAkACQAJAIAJBpX9qDg8FFQYVFQ4VFQMVARUVFQIACyACQXdqQQVJDRUgAkGFf2oOAwgUCRQLQQAvAZIKDRMgAxAbRQ0TIAFBBGpBgghBChA2DRMQHAwTCyADEBtFDRIgAUEEakGMCEEKEDYNEhAdDBILIAMQG0UNESABKQAEQuyAhIOwjsA5Ug0RIAEvAQwiA0F3aiIBQRdLDQ9BASABdEGfgIAEcUUNDwwQC0EAQQAvAZIKIgFBAWo7AZIKQQAoApwKIAFBA3RqIgFBATYCACABQQAoApQKNgIEDBALQQBBAC8BkgoiAUEBajsBkgpBACgCnAogAUEDdGoiAUEINgIAIAFBACgClAo2AgQMDwtBAC8BkgoiAUUNC0EAIAFBf2o7AZIKDA4LQQAvAZAKIgNFDQ1BAC8BkgoiAkUNDSACQQN0QQAoApwKakF4aigCAEEFRw0NIANBAnRBACgCoApqQXxqKAIAIgMoAgQNDUEAIAFBBGo2AqgKIANBACgClApBAmo2AgRBARAgGiADQQAoAqgKIgE2AhBBACABQX5qNgKoCgwNC0EALwGSCiIDRQ0JQQAgA0F/aiIDOwGSCkEALwGQCiICRQ0MQQAoApwKIANB//8DcUEDdGooAgBBBUcNDAJAIAJBAnRBACgCoApqQXxqKAIAIgMoAgQNACADQQAoApQKQQJqNgIEC0EAIAJBf2o7AZAKIAMgAUEEajYCDAwMCwJAQQAoApQKIgEvAQBBKUcNAEEAKALkCSIDRQ0AIAMoAgQgAUcNAEEAQQAoAugJIgM2AuQJAkAgA0UNACADQQA2AiQMAQtBAEEANgLUCQtBAEEALwGSCiIDQQFqOwGSCkEAKAKcCiADQQN0aiIDQQZBAkEALQCkChs2AgAgAyABNgIEQQBBADoApAoMCwtBAC8BkgoiAUUNB0EAIAFBf2oiATsBkgpBACgCnAogAUH//wNxQQN0aigCAEEERg0EDAoLQScQIQwJC0EiECEMCAsCQAJAIAEvAQQiAUEqRg0AIAFBL0cNARAeDAoLQQEQHwwJCwJAAkACQAJAQQAoApQKIgEvAQAiAxAiRQ0AAkACQCADQVVqDgQACQEDCQsgAUF+ai8BAEErRg0DDAgLIAFBfmovAQBBLUYNAgwHCyADQSlHDQFBACgCnApBAC8BkgoiAkEDdGooAgQQI0UNAgwGCyABQX5qLwEAQVBqQf//A3FBCk8NBQtBAC8BkgohAgsCQAJAIAJB//8DcSICRQ0AIANB5gBHDQBBACgCnAogAkF/akEDdGoiBCgCAEEBRw0AIAFBfmovAQBB7wBHDQEgAUF8ahAkRQ0BIAQoAgRBlghBAxAlRQ0BDAULIANB/QBHDQBBACgCnAogAkEDdGoiAigCBBAmDQQgAigCAEEGRg0ECyABECcNAyADRQ0DIANBL0ZBAC0AmApBAEdxDQMCQEEAKALsCSICRQ0AIAEgAigCAEkNACABIAIoAgRNDQQLIAFBfmohAUEAKALQCSECAkADQCABQQJqIgQgAk0NAUEAIAE2ApQKIAEvAQAhAyABQX5qIgQhASADEChFDQALIARBAmohBAsCQCADQf//A3EQKUUNACAEQX5qIQECQANAIAFBAmoiAyACTQ0BQQAgATYClAogAS8BACEDIAFBfmoiBCEBIAMQKQ0ACyAEQQJqIQMLIAMQKg0EC0EAQQE6AJgKDAcLQQAoApwKQQAvAZIKIgFBA3QiA2pBACgClAo2AgRBACABQQFqOwGSCkEAKAKcCiADakEDNgIACxArDAULQQAtAPAJQQAvAZAKQQAvAZIKcnJFIQIMBwsQLEEAQQA6AJgKDAMLEC1BACECDAULIANBoAFHDQELQQBBAToApAoLQQBBACgCqAo2ApQKC0EAKAKoCiEBDAALCyAAQYDQAGokACACCxoAAkBBACgC0AkgAEcNAEEBDwsgAEF+ahAuC/4KAQZ/QQBBACgCqAoiAEEMaiIBNgKoCkEAKALsCSECQQEQICEDAkACQAJAAkACQAJAAkACQAJAQQAoAqgKIgQgAUcNACADEC9FDQELAkACQAJAAkACQAJAAkAgA0EqRg0AIANB+wBHDQFBACAEQQJqNgKoCkEBECAhA0EAKAKoCiEEA0ACQAJAIANB//8DcSIDQSJGDQAgA0EnRg0AIAMQMxpBACgCqAohAwwBCyADECFBAEEAKAKoCkECaiIDNgKoCgtBARAgGgJAIAQgAxA0IgNBLEcNAEEAQQAoAqgKQQJqNgKoCkEBECAhAwsgA0H9AEYNA0EAKAKoCiIFIARGDQ8gBSEEIAVBACgCrApNDQAMDwsLQQAgBEECajYCqApBARAgGkEAKAKoCiIDIAMQNBoMAgtBAEEAOgCICgJAAkACQAJAAkACQCADQZ9/ag4MAgsEAQsDCwsLCwsFAAsgA0H2AEYNBAwKC0EAIARBDmoiAzYCqAoCQAJAAkBBARAgQZ9/ag4GABICEhIBEgtBACgCqAoiBSkAAkLzgOSD4I3AMVINESAFLwEKEClFDRFBACAFQQpqNgKoCkEAECAaC0EAKAKoCiIFQQJqQbIIQQ4QNg0QIAUvARAiAkF3aiIBQRdLDQ1BASABdEGfgIAEcUUNDQwOC0EAKAKoCiIFKQACQuyAhIOwjsA5Ug0PIAUvAQoiAkF3aiIBQRdNDQYMCgtBACAEQQpqNgKoCkEAECAaQQAoAqgKIQQLQQAgBEEQajYCqAoCQEEBECAiBEEqRw0AQQBBACgCqApBAmo2AqgKQQEQICEEC0EAKAKoCiEDIAQQMxogA0EAKAKoCiIEIAMgBBACQQBBACgCqApBfmo2AqgKDwsCQCAEKQACQuyAhIOwjsA5Ug0AIAQvAQoQKEUNAEEAIARBCmo2AqgKQQEQICEEQQAoAqgKIQMgBBAzGiADQQAoAqgKIgQgAyAEEAJBAEEAKAKoCkF+ajYCqAoPC0EAIARBBGoiBDYCqAoLQQAgBEEGajYCqApBAEEAOgCICkEBECAhBEEAKAKoCiEDIAQQMyEEQQAoAqgKIQIgBEHf/wNxIgFB2wBHDQNBACACQQJqNgKoCkEBECAhBUEAKAKoCiEDQQAhBAwEC0EAQQE6AIAKQQBBACgCqApBAmo2AqgKC0EBECAhBEEAKAKoCiEDAkAgBEHmAEcNACADQQJqQawIQQYQNg0AQQAgA0EIajYCqAogAEEBECBBABAyIAJBEGpB2AkgAhshAwNAIAMoAgAiA0UNBSADQgA3AgggA0EQaiEDDAALC0EAIANBfmo2AqgKDAMLQQEgAXRBn4CABHFFDQMMBAtBASEECwNAAkACQCAEDgIAAQELIAVB//8DcRAzGkEBIQQMAQsCQAJAQQAoAqgKIgQgA0YNACADIAQgAyAEEAJBARAgIQQCQCABQdsARw0AIARBIHJB/QBGDQQLQQAoAqgKIQMCQCAEQSxHDQBBACADQQJqNgKoCkEBECAhBUEAKAKoCiEDIAVBIHJB+wBHDQILQQAgA0F+ajYCqAoLIAFB2wBHDQJBACACQX5qNgKoCg8LQQAhBAwACwsPCyACQaABRg0AIAJB+wBHDQQLQQAgBUEKajYCqApBARAgIgVB+wBGDQMMAgsCQCACQVhqDgMBAwEACyACQaABRw0CC0EAIAVBEGo2AqgKAkBBARAgIgVBKkcNAEEAQQAoAqgKQQJqNgKoCkEBECAhBQsgBUEoRg0BC0EAKAKoCiEBIAUQMxpBACgCqAoiBSABTQ0AIAQgAyABIAUQAkEAQQAoAqgKQX5qNgKoCg8LIAQgA0EAQQAQAkEAIARBDGo2AqgKDwsQLQuFDAEKf0EAQQAoAqgKIgBBDGoiATYCqApBARAgIQJBACgCqAohAwJAAkACQAJAAkACQAJAAkAgAkEuRw0AQQAgA0ECajYCqAoCQEEBECAiAkHkAEYNAAJAIAJB8wBGDQAgAkHtAEcNB0EAKAKoCiICQQJqQZwIQQYQNg0HAkBBACgClAoiAxAxDQAgAy8BAEEuRg0ICyAAIAAgAkEIakEAKALICRABDwtBACgCqAoiAkECakGiCEEKEDYNBgJAQQAoApQKIgMQMQ0AIAMvAQBBLkYNBwtBACEEQQAgAkEMajYCqApBASEFQQUhBkEBECAhAkEAIQdBASEIDAILQQAoAqgKIgIpAAJC5YCYg9CMgDlSDQUCQEEAKAKUCiIDEDENACADLwEAQS5GDQYLQQAhBEEAIAJBCmo2AqgKQQIhCEEHIQZBASEHQQEQICECQQEhBQwBCwJAAkACQAJAIAJB8wBHDQAgAyABTQ0AIANBAmpBoghBChA2DQACQCADLwEMIgRBd2oiB0EXSw0AQQEgB3RBn4CABHENAgsgBEGgAUYNAQtBACEHQQchBkEBIQQgAkHkAEYNAQwCC0EAIQRBACADQQxqIgI2AqgKQQEhBUEBECAhCQJAQQAoAqgKIgYgAkYNAEHmACECAkAgCUHmAEYNAEEFIQZBACEHQQEhCCAJIQIMBAtBACEHQQEhCCAGQQJqQawIQQYQNg0EIAYvAQgQKEUNBAtBACEHQQAgAzYCqApBByEGQQEhBEEAIQVBACEIIAkhAgwCCyADIABBCmpNDQBBACEIQeQAIQICQCADKQACQuWAmIPQjIA5Ug0AAkACQCADLwEKIgRBd2oiB0EXSw0AQQEgB3RBn4CABHENAQtBACEIIARBoAFHDQELQQAhBUEAIANBCmo2AqgKQSohAkEBIQdBAiEIQQEQICIJQSpGDQRBACADNgKoCkEBIQRBACEHQQAhCCAJIQIMAgsgAyEGQQAhBwwCC0EAIQVBACEICwJAIAJBKEcNAEEAKAKcCkEALwGSCiICQQN0aiIDQQAoAqgKNgIEQQAgAkEBajsBkgogA0EFNgIAQQAoApQKLwEAQS5GDQRBAEEAKAKoCiIDQQJqNgKoCkEBECAhAiAAQQAoAqgKQQAgAxABAkACQCAFDQBBACgC5AkhAQwBC0EAKALkCSIBIAY2AhwLQQBBAC8BkAoiA0EBajsBkApBACgCoAogA0ECdGogATYCAAJAIAJBIkYNACACQSdGDQBBAEEAKAKoCkF+ajYCqAoPCyACECFBAEEAKAKoCkECaiICNgKoCgJAAkACQEEBECBBV2oOBAECAgACC0EAQQAoAqgKQQJqNgKoCkEBECAaQQAoAuQJIgMgAjYCBCADQQE6ABggA0EAKAKoCiICNgIQQQAgAkF+ajYCqAoPC0EAKALkCSIDIAI2AgQgA0EBOgAYQQBBAC8BkgpBf2o7AZIKIANBACgCqApBAmo2AgxBAEEALwGQCkF/ajsBkAoPC0EAQQAoAqgKQX5qNgKoCg8LAkAgBEEBcyACQfsAR3INAEEAKAKoCiECQQAvAZIKDQUDQAJAAkACQCACQQAoAqwKTw0AQQEQICICQSJGDQEgAkEnRg0BIAJB/QBHDQJBAEEAKAKoCkECajYCqAoLQQEQICEDQQAoAqgKIQICQCADQeYARw0AIAJBAmpBrAhBBhA2DQcLQQAgAkEIajYCqAoCQEEBECAiAkEiRg0AIAJBJ0cNBwsgACACQQAQMg8LIAIQIQtBAEEAKAKoCkECaiICNgKoCgwACwsCQAJAIAJBWWoOBAMBAQMACyACQSJGDQILQQAoAqgKIQYLIAYgAUcNAEEAIABBCmo2AqgKDwsgAkEqRyAHcQ0DQQAvAZIKQf//A3ENA0EAKAKoCiECQQAoAqwKIQEDQCACIAFPDQECQAJAIAIvAQAiA0EnRg0AIANBIkcNAQsgACADIAgQMg8LQQAgAkECaiICNgKoCgwACwsQLQsPC0EAIAJBfmo2AqgKDwtBAEEAKAKoCkF+ajYCqAoLRwEDf0EAKAKoCkECaiEAQQAoAqwKIQECQANAIAAiAkF+aiABTw0BIAJBAmohACACLwEAQXZqDgQBAAABAAsLQQAgAjYCqAoLmAEBA39BAEEAKAKoCiIBQQJqNgKoCiABQQZqIQFBACgCrAohAgNAAkACQAJAIAFBfGogAk8NACABQX5qLwEAIQMCQAJAIAANACADQSpGDQEgA0F2ag4EAgQEAgQLIANBKkcNAwsgAS8BAEEvRw0CQQAgAUF+ajYCqAoMAQsgAUF+aiEBC0EAIAE2AqgKDwsgAUECaiEBDAALC5wBAQN/QQAoAqgKIQECQANAAkACQCABLwEAIgJBL0cNAAJAIAEvAQIiAUEqRg0AIAFBL0cNBBAeDAILIAAQHwwBCwJAAkAgAEUNACACQXdqIgFBF0sNAUEBIAF0QZ+AgARxRQ0BDAILIAIQKUUNAwwBCyACQaABRw0CC0EAQQAoAqgKIgNBAmoiATYCqAogA0EAKAKsCkkNAAsLIAILiAEBBH9BACgCqAohAUEAKAKsCiECAkACQANAIAEiA0ECaiEBIAMgAk8NASABLwEAIgQgAEYNAgJAIARB3ABGDQAgBEF2ag4EAgEBAgELIANBBGohASADLwEEQQ1HDQAgA0EGaiABIAMvAQZBCkYbIQEMAAsLQQAgATYCqAoQLQ8LQQAgATYCqAoLbAEBfwJAAkAgAEFfaiIBQQVLDQBBASABdEExcQ0BCyAAQUZqQf//A3FBBkkNACAAQSlHIABBWGpB//8DcUEHSXENAAJAIABBpX9qDgQBAAABAAsgAEH9AEcgAEGFf2pB//8DcUEESXEPC0EBCy4BAX9BASEBAkAgAEGcCUEFECUNACAAQZYIQQMQJQ0AIABBpglBAhAlIQELIAELygEBAn8CQAJAIAAvAQAiAUF3akEFSQ0AIAFBIEYNACABQSlGDQAgAUHdAEYNACABQaABRg0AQQAhAiABQf0ARw0BC0EAKALQCSECAkACQANAIAAvAQAhASAAIAJNDQECQCABQXdqQQVJDQAgAUEgRg0AIAFBoAFGDQACQCABQSlGDQAgAUHdAEYNACABQf0ARw0EC0EBDwsgAEF+aiEADAALC0EBIQIgAUEpRg0BIAFB3QBGDQEgAUH9AEYNAQsgARAvQQFzIQILIAILRgEDf0EAIQMCQCAAIAJBAXQiAmsiBEECaiIAQQAoAtAJIgVJDQAgACABIAIQNg0AAkAgACAFRw0AQQEPCyAEEC4hAwsgAwuDAQECf0EBIQECQAJAAkACQAJAAkAgAC8BACICQUVqDgQFBAQBAAsCQCACQZt/ag4EAwQEAgALIAJBKUYNBCACQfkARw0DIABBfmpBsglBBhAlDwsgAEF+ai8BAEE9Rg8LIABBfmpBqglBBBAlDwsgAEF+akG+CUEDECUPC0EAIQELIAELtAMBAn9BACEBAkACQAJAAkACQAJAAkACQAJAAkAgAC8BAEGcf2oOFAABAgkJCQkDCQkEBQkJBgkHCQkICQsCQAJAIABBfmovAQBBl39qDgQACgoBCgsgAEF8akHACEECECUPCyAAQXxqQcQIQQMQJQ8LAkACQAJAIABBfmovAQBBjX9qDgMAAQIKCwJAIABBfGovAQAiAkHhAEYNACACQewARw0KIABBempB5QAQMA8LIABBempB4wAQMA8LIABBfGpByghBBBAlDwsgAEF8akHSCEEGECUPCyAAQX5qLwEAQe8ARw0GIABBfGovAQBB5QBHDQYCQCAAQXpqLwEAIgJB8ABGDQAgAkHjAEcNByAAQXhqQd4IQQYQJQ8LIABBeGpB6ghBAhAlDwsgAEF+akHuCEEEECUPC0EBIQEgAEF+aiIAQekAEDANBCAAQfYIQQUQJQ8LIABBfmpB5AAQMA8LIABBfmpBgAlBBxAlDwsgAEF+akGOCUEEECUPCwJAIABBfmovAQAiAkHvAEYNACACQeUARw0BIABBfGpB7gAQMA8LIABBfGpBlglBAxAlIQELIAELNAEBf0EBIQECQCAAQXdqQf//A3FBBUkNACAAQYABckGgAUYNACAAQS5HIAAQL3EhAQsgAQswAQF/AkACQCAAQXdqIgFBF0sNAEEBIAF0QY2AgARxDQELIABBoAFGDQBBAA8LQQELTgECf0EAIQECQAJAIAAvAQAiAkHlAEYNACACQesARw0BIABBfmpB7ghBBBAlDwsgAEF+ai8BAEH1AEcNACAAQXxqQdIIQQYQJSEBCyABC94BAQR/QQAoAqgKIQBBACgCrAohAQJAAkACQANAIAAiAkECaiEAIAIgAU8NAQJAAkACQCAALwEAIgNBpH9qDgUCAwMDAQALIANBJEcNAiACLwEEQfsARw0CQQAgAkEEaiIANgKoCkEAQQAvAZIKIgJBAWo7AZIKQQAoApwKIAJBA3RqIgJBBDYCACACIAA2AgQPC0EAIAA2AqgKQQBBAC8BkgpBf2oiADsBkgpBACgCnAogAEH//wNxQQN0aigCAEEDRw0DDAQLIAJBBGohAAwACwtBACAANgKoCgsQLQsLcAECfwJAAkADQEEAQQAoAqgKIgBBAmoiATYCqAogAEEAKAKsCk8NAQJAAkACQCABLwEAIgFBpX9qDgIBAgALAkAgAUF2ag4EBAMDBAALIAFBL0cNAgwECxA1GgwBC0EAIABBBGo2AqgKDAALCxAtCws1AQF/QQBBAToA8AlBACgCqAohAEEAQQAoAqwKQQJqNgKoCkEAIABBACgC0AlrQQF1NgKECgtDAQJ/QQEhAQJAIAAvAQAiAkF3akH//wNxQQVJDQAgAkGAAXJBoAFGDQBBACEBIAIQL0UNACACQS5HIAAQMXIPCyABC2gBAn9BASEBAkACQCAAQV9qIgJBBUsNAEEBIAJ0QTFxDQELIABB+P8DcUEoRg0AIABBRmpB//8DcUEGSQ0AAkAgAEGlf2oiAkEDSw0AIAJBAUcNAQsgAEGFf2pB//8DcUEESSEBCyABCz0BAn9BACECAkBBACgC0AkiAyAASw0AIAAvAQAgAUcNAAJAIAMgAEcNAEEBDwsgAEF+ai8BABAoIQILIAILMQEBf0EAIQECQCAALwEAQS5HDQAgAEF+ai8BAEEuRw0AIABBfGovAQBBLkYhAQsgAQvbBAEFfwJAIAFBIkYNACABQSdGDQAQLQ8LQQAoAqgKIQMgARAhIAAgA0ECakEAKAKoCkEAKALECRABAkAgAkEBSA0AQQAoAuQJQQRBBiACQQFGGzYCHAtBAEEAKAKoCkECajYCqApBABAgIQJBACgCqAohAQJAAkAgAkH3AEcNACABLwECQekARw0AIAEvAQRB9ABHDQAgAS8BBkHoAEYNAQtBACABQX5qNgKoCg8LQQAgAUEIajYCqAoCQEEBECBB+wBGDQBBACABNgKoCg8LQQAoAqgKIgQhA0EAIQADQEEAIANBAmo2AqgKAkACQAJAAkBBARAgIgJBJ0cNAEEAKAKoCiEFQScQIUEAKAKoCkECaiEDDAELQQAoAqgKIQUgAkEiRw0BQSIQIUEAKAKoCkECaiEDC0EAIAM2AqgKQQEQICECDAELIAIQMyECQQAoAqgKIQMLAkAgAkE6Rg0AQQAgATYCqAoPC0EAQQAoAqgKQQJqNgKoCgJAQQEQICICQSJGDQAgAkEnRg0AQQAgATYCqAoPC0EAKAKoCiEGIAIQIUEAQQAoAvwJIgJBFGo2AvwJQQAoAqgKIQcgAiAFNgIAIAJBADYCECACIAY2AgggAiADNgIEIAIgB0ECajYCDEEAQQAoAqgKQQJqNgKoCiAAQRBqQQAoAuQJQSBqIAAbIAI2AgACQAJAQQEQICIAQSxGDQAgAEH9AEYNAUEAIAE2AqgKDwtBAEEAKAKoCkECaiIDNgKoCiACIQAMAQsLQQAoAuQJIgEgBDYCECABQQAoAqgKQQJqNgIMC20BAn8CQAJAA0ACQCAAQf//A3EiAUF3aiICQRdLDQBBASACdEGfgIAEcQ0CCyABQaABRg0BIAAhAiABEC8NAkEAIQJBAEEAKAKoCiIAQQJqNgKoCiAALwECIgANAAwCCwsgACECCyACQf//A3ELqwEBBH8CQAJAQQAoAqgKIgIvAQAiA0HhAEYNACABIQQgACEFDAELQQAgAkEEajYCqApBARAgIQJBACgCqAohBQJAAkAgAkEiRg0AIAJBJ0YNACACEDMaQQAoAqgKIQQMAQsgAhAhQQBBACgCqApBAmoiBDYCqAoLQQEQICEDQQAoAqgKIQILAkAgAiAFRg0AIAUgBEEAIAAgACABRiICG0EAIAEgAhsQAgsgAwtyAQR/QQAoAqgKIQBBACgCrAohAQJAAkADQCAAQQJqIQIgACABTw0BAkACQCACLwEAIgNBpH9qDgIBBAALIAIhACADQXZqDgQCAQECAQsgAEEEaiEADAALC0EAIAI2AqgKEC1BAA8LQQAgAjYCqApB3QALSQEDf0EAIQMCQCACRQ0AAkADQCAALQAAIgQgAS0AACIFRw0BIAFBAWohASAAQQFqIQAgAkF/aiICDQAMAgsLIAQgBWshAwsgAwsL4gECAEGACAvEAQAAeABwAG8AcgB0AG0AcABvAHIAdABmAG8AcgBlAHQAYQBvAHUAcgBjAGUAcgBvAG0AdQBuAGMAdABpAG8AbgB2AG8AeQBpAGUAZABlAGwAZQBjAG8AbgB0AGkAbgBpAG4AcwB0AGEAbgB0AHkAYgByAGUAYQByAGUAdAB1AHIAZABlAGIAdQBnAGcAZQBhAHcAYQBpAHQAaAByAHcAaABpAGwAZQBpAGYAYwBhAHQAYwBmAGkAbgBhAGwAbABlAGwAcwAAQcQJCxABAAAAAgAAAAAEAAAwOQAA","undefined"!=typeof Buffer?Buffer.from(A,"base64"):Uint8Array.from(atob(A),(A=>A.charCodeAt(0)));var A;};WebAssembly.compile(E()).then(WebAssembly.instantiate).then((({exports:A})=>{}));
|
|
|
|
function hasActionPayload(locals) {
|
|
return "_actionPayload" in locals;
|
|
}
|
|
function createGetActionResult(locals) {
|
|
return (actionFn) => {
|
|
if (!hasActionPayload(locals) || actionFn.toString() !== getActionQueryString(locals._actionPayload.actionName)) {
|
|
return void 0;
|
|
}
|
|
return deserializeActionResult(locals._actionPayload.actionResult);
|
|
};
|
|
}
|
|
function createCallAction(context) {
|
|
return (baseAction, input) => {
|
|
Reflect.set(context, ACTION_API_CONTEXT_SYMBOL, true);
|
|
const action = baseAction.bind(context);
|
|
return action(input);
|
|
};
|
|
}
|
|
|
|
function parseLocale(header) {
|
|
if (header === "*") {
|
|
return [{ locale: header, qualityValue: void 0 }];
|
|
}
|
|
const result = [];
|
|
const localeValues = header.split(",").map((str) => str.trim());
|
|
for (const localeValue of localeValues) {
|
|
const split = localeValue.split(";").map((str) => str.trim());
|
|
const localeName = split[0];
|
|
const qualityValue = split[1];
|
|
if (!split) {
|
|
continue;
|
|
}
|
|
if (qualityValue && qualityValue.startsWith("q=")) {
|
|
const qualityValueAsFloat = Number.parseFloat(qualityValue.slice("q=".length));
|
|
if (Number.isNaN(qualityValueAsFloat) || qualityValueAsFloat > 1) {
|
|
result.push({
|
|
locale: localeName,
|
|
qualityValue: void 0
|
|
});
|
|
} else {
|
|
result.push({
|
|
locale: localeName,
|
|
qualityValue: qualityValueAsFloat
|
|
});
|
|
}
|
|
} else {
|
|
result.push({
|
|
locale: localeName,
|
|
qualityValue: void 0
|
|
});
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
function sortAndFilterLocales(browserLocaleList, locales) {
|
|
const normalizedLocales = getAllCodes(locales).map(normalizeTheLocale);
|
|
return browserLocaleList.filter((browserLocale) => {
|
|
if (browserLocale.locale !== "*") {
|
|
return normalizedLocales.includes(normalizeTheLocale(browserLocale.locale));
|
|
}
|
|
return true;
|
|
}).sort((a, b) => {
|
|
if (a.qualityValue && b.qualityValue) {
|
|
return Math.sign(b.qualityValue - a.qualityValue);
|
|
}
|
|
return 0;
|
|
});
|
|
}
|
|
function computePreferredLocale(request, locales) {
|
|
const acceptHeader = request.headers.get("Accept-Language");
|
|
let result = void 0;
|
|
if (acceptHeader) {
|
|
const browserLocaleList = sortAndFilterLocales(parseLocale(acceptHeader), locales);
|
|
const firstResult = browserLocaleList.at(0);
|
|
if (firstResult && firstResult.locale !== "*") {
|
|
for (const currentLocale of locales) {
|
|
if (typeof currentLocale === "string") {
|
|
if (normalizeTheLocale(currentLocale) === normalizeTheLocale(firstResult.locale)) {
|
|
result = currentLocale;
|
|
break;
|
|
}
|
|
} else {
|
|
for (const currentCode of currentLocale.codes) {
|
|
if (normalizeTheLocale(currentCode) === normalizeTheLocale(firstResult.locale)) {
|
|
result = currentCode;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
function computePreferredLocaleList(request, locales) {
|
|
const acceptHeader = request.headers.get("Accept-Language");
|
|
let result = [];
|
|
if (acceptHeader) {
|
|
const browserLocaleList = sortAndFilterLocales(parseLocale(acceptHeader), locales);
|
|
if (browserLocaleList.length === 1 && browserLocaleList.at(0).locale === "*") {
|
|
return getAllCodes(locales);
|
|
} else if (browserLocaleList.length > 0) {
|
|
for (const browserLocale of browserLocaleList) {
|
|
for (const loopLocale of locales) {
|
|
if (typeof loopLocale === "string") {
|
|
if (normalizeTheLocale(loopLocale) === normalizeTheLocale(browserLocale.locale)) {
|
|
result.push(loopLocale);
|
|
}
|
|
} else {
|
|
for (const code of loopLocale.codes) {
|
|
if (code === browserLocale.locale) {
|
|
result.push(code);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
function computeCurrentLocale(pathname, locales, defaultLocale) {
|
|
for (const segment of pathname.split("/").map(normalizeThePath)) {
|
|
for (const locale of locales) {
|
|
if (typeof locale === "string") {
|
|
if (!segment.includes(locale)) continue;
|
|
if (normalizeTheLocale(locale) === normalizeTheLocale(segment)) {
|
|
return locale;
|
|
}
|
|
} else {
|
|
if (locale.path === segment) {
|
|
return locale.codes.at(0);
|
|
} else {
|
|
for (const code of locale.codes) {
|
|
if (normalizeTheLocale(code) === normalizeTheLocale(segment)) {
|
|
return code;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for (const locale of locales) {
|
|
if (typeof locale === "string") {
|
|
if (locale === defaultLocale) {
|
|
return locale;
|
|
}
|
|
} else {
|
|
if (locale.path === defaultLocale) {
|
|
return locale.codes.at(0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
function computeCurrentLocaleFromParams(params, locales) {
|
|
const byNormalizedCode = /* @__PURE__ */ new Map();
|
|
const byPath = /* @__PURE__ */ new Map();
|
|
for (const locale of locales) {
|
|
if (typeof locale === "string") {
|
|
byNormalizedCode.set(normalizeTheLocale(locale), locale);
|
|
} else {
|
|
byPath.set(locale.path, locale.codes[0]);
|
|
for (const code of locale.codes) {
|
|
byNormalizedCode.set(normalizeTheLocale(code), code);
|
|
}
|
|
}
|
|
}
|
|
for (const value of Object.values(params)) {
|
|
if (!value) continue;
|
|
const pathMatch = byPath.get(value);
|
|
if (pathMatch) return pathMatch;
|
|
const codeMatch = byNormalizedCode.get(normalizeTheLocale(value));
|
|
if (codeMatch) return codeMatch;
|
|
}
|
|
}
|
|
|
|
async function renderEndpoint(mod, context, isPrerendered, logger) {
|
|
const { request, url } = context;
|
|
const method = request.method.toUpperCase();
|
|
let handler = mod[method] ?? mod["ALL"];
|
|
if (!handler && method === "HEAD" && mod["GET"]) {
|
|
handler = mod["GET"];
|
|
}
|
|
if (isPrerendered && !["GET", "HEAD"].includes(method)) {
|
|
logger.warn(
|
|
"router",
|
|
`${url.pathname} ${s.bold(
|
|
method
|
|
)} requests are not available in static endpoints. Mark this page as server-rendered (\`export const prerender = false;\`) or update your config to \`output: 'server'\` to make all your pages server-rendered by default.`
|
|
);
|
|
}
|
|
if (handler === void 0) {
|
|
logger.warn(
|
|
"router",
|
|
`No API Route handler exists for the method "${method}" for the route "${url.pathname}".
|
|
Found handlers: ${Object.keys(mod).map((exp) => JSON.stringify(exp)).join(", ")}
|
|
` + ("all" in mod ? `One of the exported handlers is "all" (lowercase), did you mean to export 'ALL'?
|
|
` : "")
|
|
);
|
|
return new Response(null, { status: 404 });
|
|
}
|
|
if (typeof handler !== "function") {
|
|
logger.error(
|
|
"router",
|
|
`The route "${url.pathname}" exports a value for the method "${method}", but it is of the type ${typeof handler} instead of a function.`
|
|
);
|
|
return new Response(null, { status: 500 });
|
|
}
|
|
let response = await handler.call(mod, context);
|
|
if (!response || response instanceof Response === false) {
|
|
throw new AstroError(EndpointDidNotReturnAResponse);
|
|
}
|
|
if (REROUTABLE_STATUS_CODES.includes(response.status)) {
|
|
try {
|
|
response.headers.set(REROUTE_DIRECTIVE_HEADER, "no");
|
|
} catch (err) {
|
|
if (err.message?.includes("immutable")) {
|
|
response = new Response(response.body, response);
|
|
response.headers.set(REROUTE_DIRECTIVE_HEADER, "no");
|
|
} else {
|
|
throw err;
|
|
}
|
|
}
|
|
}
|
|
if (method === "HEAD") {
|
|
return new Response(null, response);
|
|
}
|
|
return response;
|
|
}
|
|
|
|
/**
|
|
* Copyright (C) 2017-present by Andrea Giammarchi - @WebReflection
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
*/
|
|
|
|
const {replace} = '';
|
|
const ca = /[&<>'"]/g;
|
|
|
|
const esca = {
|
|
'&': '&',
|
|
'<': '<',
|
|
'>': '>',
|
|
"'": ''',
|
|
'"': '"'
|
|
};
|
|
const pe = m => esca[m];
|
|
|
|
/**
|
|
* Safely escape HTML entities such as `&`, `<`, `>`, `"`, and `'`.
|
|
* @param {string} es the input to safely escape
|
|
* @returns {string} the escaped input, and it **throws** an error if
|
|
* the input type is unexpected, except for boolean and numbers,
|
|
* converted as string.
|
|
*/
|
|
const escape = es => replace.call(es, ca, pe);
|
|
|
|
function isPromise(value) {
|
|
return !!value && typeof value === "object" && "then" in value && typeof value.then === "function";
|
|
}
|
|
async function* streamAsyncIterator(stream) {
|
|
const reader = stream.getReader();
|
|
try {
|
|
while (true) {
|
|
const { done, value } = await reader.read();
|
|
if (done) return;
|
|
yield value;
|
|
}
|
|
} finally {
|
|
reader.releaseLock();
|
|
}
|
|
}
|
|
|
|
const escapeHTML = escape;
|
|
function stringifyForScript(value) {
|
|
return JSON.stringify(value)?.replace(/</g, "\\u003c");
|
|
}
|
|
class HTMLBytes extends Uint8Array {
|
|
}
|
|
Object.defineProperty(HTMLBytes.prototype, Symbol.toStringTag, {
|
|
get() {
|
|
return "HTMLBytes";
|
|
}
|
|
});
|
|
const htmlStringSymbol = /* @__PURE__ */ Symbol.for("astro:html-string");
|
|
class HTMLString extends String {
|
|
[htmlStringSymbol] = true;
|
|
}
|
|
const markHTMLString = (value) => {
|
|
if (isHTMLString(value)) {
|
|
return value;
|
|
}
|
|
if (typeof value === "string") {
|
|
return new HTMLString(value);
|
|
}
|
|
return value;
|
|
};
|
|
function isHTMLString(value) {
|
|
return !!value?.[htmlStringSymbol];
|
|
}
|
|
function markHTMLBytes(bytes) {
|
|
return new HTMLBytes(bytes);
|
|
}
|
|
function hasGetReader(obj) {
|
|
return typeof obj.getReader === "function";
|
|
}
|
|
async function* unescapeChunksAsync(iterable) {
|
|
if (hasGetReader(iterable)) {
|
|
for await (const chunk of streamAsyncIterator(iterable)) {
|
|
yield unescapeHTML(chunk);
|
|
}
|
|
} else {
|
|
for await (const chunk of iterable) {
|
|
yield unescapeHTML(chunk);
|
|
}
|
|
}
|
|
}
|
|
function* unescapeChunks(iterable) {
|
|
for (const chunk of iterable) {
|
|
yield unescapeHTML(chunk);
|
|
}
|
|
}
|
|
function unescapeHTML(str) {
|
|
if (!!str && typeof str === "object") {
|
|
if (str instanceof Uint8Array) {
|
|
return markHTMLBytes(str);
|
|
} else if (str instanceof Response && str.body) {
|
|
const body = str.body;
|
|
return unescapeChunksAsync(body);
|
|
} else if (typeof str.then === "function") {
|
|
return Promise.resolve(str).then((value) => {
|
|
return unescapeHTML(value);
|
|
});
|
|
} else if (str[/* @__PURE__ */ Symbol.for("astro:slot-string")]) {
|
|
return str;
|
|
} else if (Symbol.iterator in str) {
|
|
return unescapeChunks(str);
|
|
} else if (Symbol.asyncIterator in str || hasGetReader(str)) {
|
|
return unescapeChunksAsync(str);
|
|
}
|
|
}
|
|
return markHTMLString(str);
|
|
}
|
|
|
|
const AstroJSX = "astro:jsx";
|
|
function isVNode(vnode) {
|
|
return vnode && typeof vnode === "object" && vnode[AstroJSX];
|
|
}
|
|
|
|
function resolvePropagationHint(input) {
|
|
const explicitHint = input.factoryHint ?? "none";
|
|
if (explicitHint !== "none") {
|
|
return explicitHint;
|
|
}
|
|
if (!input.moduleId) {
|
|
return "none";
|
|
}
|
|
return input.metadataLookup(input.moduleId) ?? "none";
|
|
}
|
|
function isPropagatingHint(hint) {
|
|
return hint === "self" || hint === "in-tree";
|
|
}
|
|
function getPropagationHint$1(result, factory) {
|
|
return resolvePropagationHint({
|
|
factoryHint: factory.propagation,
|
|
moduleId: factory.moduleId,
|
|
metadataLookup: (moduleId) => result.componentMetadata.get(moduleId)?.propagation
|
|
});
|
|
}
|
|
|
|
function isAstroComponentFactory(obj) {
|
|
return obj == null ? false : obj.isAstroComponentFactory === true;
|
|
}
|
|
function isAPropagatingComponent(result, factory) {
|
|
return isPropagatingHint(getPropagationHint(result, factory));
|
|
}
|
|
function getPropagationHint(result, factory) {
|
|
return getPropagationHint$1(result, factory);
|
|
}
|
|
|
|
function r(e){var t,f,n="";if("string"==typeof e||"number"==typeof e)n+=e;else if("object"==typeof e)if(Array.isArray(e)){var o=e.length;for(t=0;t<o;t++)e[t]&&(f=r(e[t]))&&(n&&(n+=" "),n+=f);}else for(f in e)e[f]&&(n&&(n+=" "),n+=f);return n}function clsx(){for(var e,t,f=0,n="",o=arguments.length;f<o;f++)(e=arguments[f])&&(t=r(e))&&(n&&(n+=" "),n+=t);return n}
|
|
|
|
const PROP_TYPE = {
|
|
Value: 0,
|
|
JSON: 1,
|
|
// Actually means Array
|
|
RegExp: 2,
|
|
Date: 3,
|
|
Map: 4,
|
|
Set: 5,
|
|
BigInt: 6,
|
|
URL: 7,
|
|
Uint8Array: 8,
|
|
Uint16Array: 9,
|
|
Uint32Array: 10,
|
|
Infinity: 11
|
|
};
|
|
function serializeArray(value, metadata = {}, parents = /* @__PURE__ */ new WeakSet()) {
|
|
if (parents.has(value)) {
|
|
throw new Error(`Cyclic reference detected while serializing props for <${metadata.displayName} client:${metadata.hydrate}>!
|
|
|
|
Cyclic references cannot be safely serialized for client-side usage. Please remove the cyclic reference.`);
|
|
}
|
|
parents.add(value);
|
|
const serialized = value.map((v) => {
|
|
return convertToSerializedForm(v, metadata, parents);
|
|
});
|
|
parents.delete(value);
|
|
return serialized;
|
|
}
|
|
function serializeObject(value, metadata = {}, parents = /* @__PURE__ */ new WeakSet()) {
|
|
if (parents.has(value)) {
|
|
throw new Error(`Cyclic reference detected while serializing props for <${metadata.displayName} client:${metadata.hydrate}>!
|
|
|
|
Cyclic references cannot be safely serialized for client-side usage. Please remove the cyclic reference.`);
|
|
}
|
|
parents.add(value);
|
|
const serialized = Object.fromEntries(
|
|
Object.entries(value).map(([k, v]) => {
|
|
return [k, convertToSerializedForm(v, metadata, parents)];
|
|
})
|
|
);
|
|
parents.delete(value);
|
|
return serialized;
|
|
}
|
|
function convertToSerializedForm(value, metadata = {}, parents = /* @__PURE__ */ new WeakSet()) {
|
|
const tag = Object.prototype.toString.call(value);
|
|
switch (tag) {
|
|
case "[object Date]": {
|
|
return [PROP_TYPE.Date, value.toISOString()];
|
|
}
|
|
case "[object RegExp]": {
|
|
return [PROP_TYPE.RegExp, value.source];
|
|
}
|
|
case "[object Map]": {
|
|
return [PROP_TYPE.Map, serializeArray(Array.from(value), metadata, parents)];
|
|
}
|
|
case "[object Set]": {
|
|
return [PROP_TYPE.Set, serializeArray(Array.from(value), metadata, parents)];
|
|
}
|
|
case "[object BigInt]": {
|
|
return [PROP_TYPE.BigInt, value.toString()];
|
|
}
|
|
case "[object URL]": {
|
|
return [PROP_TYPE.URL, value.toString()];
|
|
}
|
|
case "[object Array]": {
|
|
return [PROP_TYPE.JSON, serializeArray(value, metadata, parents)];
|
|
}
|
|
case "[object Uint8Array]": {
|
|
return [PROP_TYPE.Uint8Array, Array.from(value)];
|
|
}
|
|
case "[object Uint16Array]": {
|
|
return [PROP_TYPE.Uint16Array, Array.from(value)];
|
|
}
|
|
case "[object Uint32Array]": {
|
|
return [PROP_TYPE.Uint32Array, Array.from(value)];
|
|
}
|
|
default: {
|
|
if (value !== null && typeof value === "object") {
|
|
return [PROP_TYPE.Value, serializeObject(value, metadata, parents)];
|
|
}
|
|
if (value === Number.POSITIVE_INFINITY) {
|
|
return [PROP_TYPE.Infinity, 1];
|
|
}
|
|
if (value === Number.NEGATIVE_INFINITY) {
|
|
return [PROP_TYPE.Infinity, -1];
|
|
}
|
|
if (value === void 0) {
|
|
return [PROP_TYPE.Value];
|
|
}
|
|
return [PROP_TYPE.Value, value];
|
|
}
|
|
}
|
|
}
|
|
function serializeProps(props, metadata) {
|
|
const serialized = JSON.stringify(serializeObject(props, metadata));
|
|
return serialized;
|
|
}
|
|
|
|
const transitionDirectivesToCopyOnIsland = Object.freeze([
|
|
"data-astro-transition-scope",
|
|
"data-astro-transition-persist",
|
|
"data-astro-transition-persist-props"
|
|
]);
|
|
function extractDirectives(inputProps, clientDirectives) {
|
|
let extracted = {
|
|
isPage: false,
|
|
hydration: null,
|
|
props: {},
|
|
propsWithoutTransitionAttributes: {}
|
|
};
|
|
for (const [key, value] of Object.entries(inputProps)) {
|
|
if (key.startsWith("server:")) {
|
|
if (key === "server:root") {
|
|
extracted.isPage = true;
|
|
}
|
|
}
|
|
if (key.startsWith("client:")) {
|
|
if (!extracted.hydration) {
|
|
extracted.hydration = {
|
|
directive: "",
|
|
value: "",
|
|
componentUrl: "",
|
|
componentExport: { value: "" }
|
|
};
|
|
}
|
|
switch (key) {
|
|
case "client:component-path": {
|
|
extracted.hydration.componentUrl = value;
|
|
break;
|
|
}
|
|
case "client:component-export": {
|
|
extracted.hydration.componentExport.value = value;
|
|
break;
|
|
}
|
|
// This is a special prop added to prove that the client hydration method
|
|
// was added statically.
|
|
case "client:component-hydration": {
|
|
break;
|
|
}
|
|
case "client:display-name": {
|
|
break;
|
|
}
|
|
default: {
|
|
extracted.hydration.directive = key.split(":")[1];
|
|
extracted.hydration.value = value;
|
|
if (!clientDirectives.has(extracted.hydration.directive)) {
|
|
const hydrationMethods = Array.from(clientDirectives.keys()).map((d) => `client:${d}`).join(", ");
|
|
throw new Error(
|
|
`Error: invalid hydration directive "${key}". Supported hydration methods: ${hydrationMethods}`
|
|
);
|
|
}
|
|
if (extracted.hydration.directive === "media" && typeof extracted.hydration.value !== "string") {
|
|
throw new AstroError(MissingMediaQueryDirective);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
extracted.props[key] = value;
|
|
if (!transitionDirectivesToCopyOnIsland.includes(key)) {
|
|
extracted.propsWithoutTransitionAttributes[key] = value;
|
|
}
|
|
}
|
|
}
|
|
for (const sym of Object.getOwnPropertySymbols(inputProps)) {
|
|
extracted.props[sym] = inputProps[sym];
|
|
extracted.propsWithoutTransitionAttributes[sym] = inputProps[sym];
|
|
}
|
|
return extracted;
|
|
}
|
|
async function generateHydrateScript(scriptOptions, metadata) {
|
|
const { renderer, result, astroId, props, attrs } = scriptOptions;
|
|
const { hydrate, componentUrl, componentExport } = metadata;
|
|
if (!componentExport.value) {
|
|
throw new AstroError({
|
|
...NoMatchingImport,
|
|
message: NoMatchingImport.message(metadata.displayName)
|
|
});
|
|
}
|
|
const island = {
|
|
children: "",
|
|
props: {
|
|
// This is for HMR, probably can avoid it in prod
|
|
uid: astroId
|
|
}
|
|
};
|
|
if (attrs) {
|
|
for (const [key, value] of Object.entries(attrs)) {
|
|
island.props[key] = escapeHTML(value);
|
|
}
|
|
}
|
|
island.props["component-url"] = await result.resolve(decodeURI(componentUrl));
|
|
if (renderer.clientEntrypoint) {
|
|
island.props["component-export"] = componentExport.value;
|
|
island.props["renderer-url"] = await result.resolve(
|
|
decodeURI(renderer.clientEntrypoint.toString())
|
|
);
|
|
island.props["props"] = escapeHTML(serializeProps(props, metadata));
|
|
}
|
|
island.props["ssr"] = "";
|
|
island.props["client"] = hydrate;
|
|
let beforeHydrationUrl = await result.resolve("astro:scripts/before-hydration.js");
|
|
if (beforeHydrationUrl.length) {
|
|
island.props["before-hydration-url"] = beforeHydrationUrl;
|
|
}
|
|
island.props["opts"] = escapeHTML(
|
|
JSON.stringify({
|
|
name: metadata.displayName,
|
|
value: metadata.hydrateArgs || ""
|
|
})
|
|
);
|
|
transitionDirectivesToCopyOnIsland.forEach((name) => {
|
|
if (typeof props[name] !== "undefined") {
|
|
island.props[name] = props[name];
|
|
}
|
|
});
|
|
return island;
|
|
}
|
|
|
|
/**
|
|
* shortdash - https://github.com/bibig/node-shorthash
|
|
*
|
|
* @license
|
|
*
|
|
* (The MIT License)
|
|
*
|
|
* Copyright (c) 2013 Bibig <bibig@me.com>
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person
|
|
* obtaining a copy of this software and associated documentation
|
|
* files (the "Software"), to deal in the Software without
|
|
* restriction, including without limitation the rights to use,
|
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following
|
|
* conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be
|
|
* included in all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
* OTHER DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
const dictionary = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXY";
|
|
const binary = dictionary.length;
|
|
function bitwise(str) {
|
|
let hash = 0;
|
|
if (str.length === 0) return hash;
|
|
for (let i = 0; i < str.length; i++) {
|
|
const ch = str.charCodeAt(i);
|
|
hash = (hash << 5) - hash + ch;
|
|
hash = hash & hash;
|
|
}
|
|
return hash;
|
|
}
|
|
function shorthash(text) {
|
|
let num;
|
|
let result = "";
|
|
let integer = bitwise(text);
|
|
const sign = integer < 0 ? "Z" : "";
|
|
integer = Math.abs(integer);
|
|
while (integer >= binary) {
|
|
num = integer % binary;
|
|
integer = Math.floor(integer / binary);
|
|
result = dictionary[num] + result;
|
|
}
|
|
if (integer > 0) {
|
|
result = dictionary[integer] + result;
|
|
}
|
|
return sign + result;
|
|
}
|
|
|
|
const headAndContentSym = /* @__PURE__ */ Symbol.for("astro.headAndContent");
|
|
function isHeadAndContent(obj) {
|
|
return typeof obj === "object" && obj !== null && !!obj[headAndContentSym];
|
|
}
|
|
function createThinHead() {
|
|
return {
|
|
[headAndContentSym]: true
|
|
};
|
|
}
|
|
|
|
var astro_island_prebuilt_default = `(()=>{var A=Object.defineProperty;var C=(i,o,a)=>o in i?A(i,o,{enumerable:!0,configurable:!0,writable:!0,value:a}):i[o]=a;var l=(i,o,a)=>C(i,typeof o!="symbol"?o+"":o,a);var E=new Set(["__proto__","constructor","prototype"]);{let i={0:t=>y(t),1:t=>a(t),2:t=>new RegExp(t),3:t=>new Date(t),4:t=>new Map(a(t)),5:t=>new Set(a(t)),6:t=>BigInt(t),7:t=>new URL(t),8:t=>new Uint8Array(t),9:t=>new Uint16Array(t),10:t=>new Uint32Array(t),11:t=>Number.POSITIVE_INFINITY*t},o=t=>{let[h,e]=t;return h in i?i[h](e):void 0},a=t=>t.map(o),y=t=>typeof t!="object"||t===null?t:Object.fromEntries(Object.entries(t).map(([h,e])=>[h,o(e)]));class f extends HTMLElement{constructor(){super(...arguments);l(this,"Component");l(this,"hydrator");l(this,"hydrate",async()=>{var b;if(!this.hydrator||!this.isConnected)return;let e=(b=this.parentElement)==null?void 0:b.closest("astro-island[ssr]");if(e){e.addEventListener("astro:hydrate",this.hydrate,{once:!0});return}let c=this.querySelectorAll("astro-slot"),n={},p=this.querySelectorAll("template[data-astro-template]");for(let r of p){let s=r.closest(this.tagName);s!=null&&s.isSameNode(this)&&(n[r.getAttribute("data-astro-template")||"default"]=r.innerHTML,r.remove())}for(let r of c){let s=r.closest(this.tagName);s!=null&&s.isSameNode(this)&&(n[r.getAttribute("name")||"default"]=r.innerHTML)}let u;try{u=this.hasAttribute("props")?y(JSON.parse(this.getAttribute("props"))):{}}catch(r){let s=this.getAttribute("component-url")||"<unknown>",v=this.getAttribute("component-export");throw v&&(s+=\` (export \${v})\`),console.error(\`[hydrate] Error parsing props for component \${s}\`,this.getAttribute("props"),r),r}let d;await this.hydrator(this)(this.Component,u,n,{client:this.getAttribute("client")}),this.removeAttribute("ssr"),this.dispatchEvent(new CustomEvent("astro:hydrate"))});l(this,"unmount",()=>{this.isConnected||this.dispatchEvent(new CustomEvent("astro:unmount"))})}disconnectedCallback(){document.removeEventListener("astro:after-swap",this.unmount),document.addEventListener("astro:after-swap",this.unmount,{once:!0})}connectedCallback(){if(!this.hasAttribute("await-children")||document.readyState==="interactive"||document.readyState==="complete")this.childrenConnectedCallback();else{let e=()=>{document.removeEventListener("DOMContentLoaded",e),c.disconnect(),this.childrenConnectedCallback()},c=new MutationObserver(()=>{var n;((n=this.lastChild)==null?void 0:n.nodeType)===Node.COMMENT_NODE&&this.lastChild.nodeValue==="astro:end"&&(this.lastChild.remove(),e())});c.observe(this,{childList:!0}),document.addEventListener("DOMContentLoaded",e)}}async childrenConnectedCallback(){let e=this.getAttribute("before-hydration-url");e&&await import(e),this.start()}async start(){let e=JSON.parse(this.getAttribute("opts")),c=this.getAttribute("client");if(Astro[c]===void 0){window.addEventListener(\`astro:\${c}\`,()=>this.start(),{once:!0});return}try{await Astro[c](async()=>{let n=this.getAttribute("renderer-url"),[p,{default:u}]=await Promise.all([import(this.getAttribute("component-url")),n?import(n):()=>()=>{}]),d=this.getAttribute("component-export")||"default";if(d.includes(".")){this.Component=p;for(let m of d.split(".")){if(E.has(m)||!this.Component||typeof this.Component!="object"&&typeof this.Component!="function"||!Object.hasOwn(this.Component,m))throw new Error(\`Invalid component export path: \${d}\`);this.Component=this.Component[m]}}else{if(E.has(d))throw new Error(\`Invalid component export path: \${d}\`);this.Component=p[d]}return this.hydrator=u,this.hydrate},e,this)}catch(n){console.error("[astro-island] Error hydrating %s",this.getAttribute("component-url"),n)}}attributeChangedCallback(){this.hydrate()}}l(f,"observedAttributes",["props"]),customElements.get("astro-island")||customElements.define("astro-island",f)}})();`;
|
|
|
|
var astro_island_prebuilt_dev_default = `(()=>{var A=Object.defineProperty;var C=(a,r,c)=>r in a?A(a,r,{enumerable:!0,configurable:!0,writable:!0,value:c}):a[r]=c;var l=(a,r,c)=>C(a,typeof r!="symbol"?r+"":r,c);var E=new Set(["__proto__","constructor","prototype"]);{let a={0:t=>y(t),1:t=>c(t),2:t=>new RegExp(t),3:t=>new Date(t),4:t=>new Map(c(t)),5:t=>new Set(c(t)),6:t=>BigInt(t),7:t=>new URL(t),8:t=>new Uint8Array(t),9:t=>new Uint16Array(t),10:t=>new Uint32Array(t),11:t=>Number.POSITIVE_INFINITY*t},r=t=>{let[p,e]=t;return p in a?a[p](e):void 0},c=t=>t.map(r),y=t=>typeof t!="object"||t===null?t:Object.fromEntries(Object.entries(t).map(([p,e])=>[p,r(e)]));class f extends HTMLElement{constructor(){super(...arguments);l(this,"Component");l(this,"hydrator");l(this,"hydrate",async()=>{var b;if(!this.hydrator||!this.isConnected)return;let e=(b=this.parentElement)==null?void 0:b.closest("astro-island[ssr]");if(e){e.addEventListener("astro:hydrate",this.hydrate,{once:!0});return}let d=this.querySelectorAll("astro-slot"),n={},u=this.querySelectorAll("template[data-astro-template]");for(let o of u){let i=o.closest(this.tagName);i!=null&&i.isSameNode(this)&&(n[o.getAttribute("data-astro-template")||"default"]=o.innerHTML,o.remove())}for(let o of d){let i=o.closest(this.tagName);i!=null&&i.isSameNode(this)&&(n[o.getAttribute("name")||"default"]=o.innerHTML)}let m;try{m=this.hasAttribute("props")?y(JSON.parse(this.getAttribute("props"))):{}}catch(o){let i=this.getAttribute("component-url")||"<unknown>",v=this.getAttribute("component-export");throw v&&(i+=\` (export \${v})\`),console.error(\`[hydrate] Error parsing props for component \${i}\`,this.getAttribute("props"),o),o}let s,h=this.hydrator(this);s=performance.now(),await h(this.Component,m,n,{client:this.getAttribute("client")}),s&&this.setAttribute("client-render-time",(performance.now()-s).toString()),this.removeAttribute("ssr"),this.dispatchEvent(new CustomEvent("astro:hydrate"))});l(this,"unmount",()=>{this.isConnected||this.dispatchEvent(new CustomEvent("astro:unmount"))})}disconnectedCallback(){document.removeEventListener("astro:after-swap",this.unmount),document.addEventListener("astro:after-swap",this.unmount,{once:!0})}connectedCallback(){if(!this.hasAttribute("await-children")||document.readyState==="interactive"||document.readyState==="complete")this.childrenConnectedCallback();else{let e=()=>{document.removeEventListener("DOMContentLoaded",e),d.disconnect(),this.childrenConnectedCallback()},d=new MutationObserver(()=>{var n;((n=this.lastChild)==null?void 0:n.nodeType)===Node.COMMENT_NODE&&this.lastChild.nodeValue==="astro:end"&&(this.lastChild.remove(),e())});d.observe(this,{childList:!0}),document.addEventListener("DOMContentLoaded",e)}}async childrenConnectedCallback(){let e=this.getAttribute("before-hydration-url");e&&await import(e),this.start()}async start(){let e=JSON.parse(this.getAttribute("opts")),d=this.getAttribute("client");if(Astro[d]===void 0){window.addEventListener(\`astro:\${d}\`,()=>this.start(),{once:!0});return}try{await Astro[d](async()=>{let n=this.getAttribute("renderer-url"),[u,{default:m}]=await Promise.all([import(this.getAttribute("component-url")),n?import(n):()=>()=>{}]),s=this.getAttribute("component-export")||"default";if(s.includes(".")){this.Component=u;for(let h of s.split(".")){if(E.has(h)||!this.Component||typeof this.Component!="object"&&typeof this.Component!="function"||!Object.hasOwn(this.Component,h))throw new Error(\`Invalid component export path: \${s}\`);this.Component=this.Component[h]}}else{if(E.has(s))throw new Error(\`Invalid component export path: \${s}\`);this.Component=u[s]}return this.hydrator=m,this.hydrate},e,this)}catch(n){console.error("[astro-island] Error hydrating %s",this.getAttribute("component-url"),n)}}attributeChangedCallback(){this.hydrate()}}l(f,"observedAttributes",["props"]),customElements.get("astro-island")||customElements.define("astro-island",f)}})();`;
|
|
|
|
const ISLAND_STYLES = "astro-island,astro-slot,astro-static-slot{display:contents}";
|
|
|
|
function determineIfNeedsHydrationScript(result) {
|
|
if (result._metadata.hasHydrationScript) {
|
|
return false;
|
|
}
|
|
return result._metadata.hasHydrationScript = true;
|
|
}
|
|
function determinesIfNeedsDirectiveScript(result, directive) {
|
|
if (result._metadata.hasDirectives.has(directive)) {
|
|
return false;
|
|
}
|
|
result._metadata.hasDirectives.add(directive);
|
|
return true;
|
|
}
|
|
function getDirectiveScriptText(result, directive) {
|
|
const clientDirectives = result.clientDirectives;
|
|
const clientDirective = clientDirectives.get(directive);
|
|
if (!clientDirective) {
|
|
throw new Error(`Unknown directive: ${directive}`);
|
|
}
|
|
return clientDirective;
|
|
}
|
|
function getPrescripts(result, type, directive) {
|
|
switch (type) {
|
|
case "both":
|
|
return `<style>${ISLAND_STYLES}</style><script>${getDirectiveScriptText(result, directive)}</script><script>${process.env.NODE_ENV === "development" ? astro_island_prebuilt_dev_default : astro_island_prebuilt_default}</script>`;
|
|
case "directive":
|
|
return `<script>${getDirectiveScriptText(result, directive)}</script>`;
|
|
}
|
|
}
|
|
|
|
async function collectPropagatedHeadParts(input) {
|
|
const collectedHeadParts = [];
|
|
const iterator = input.propagators.values();
|
|
while (true) {
|
|
const { value, done } = iterator.next();
|
|
if (done) {
|
|
break;
|
|
}
|
|
const returnValue = await value.init(input.result);
|
|
if (input.isHeadAndContent(returnValue) && returnValue.head) {
|
|
collectedHeadParts.push(returnValue.head);
|
|
}
|
|
}
|
|
return collectedHeadParts;
|
|
}
|
|
|
|
function shouldRenderHeadInstruction(state) {
|
|
return !state.hasRenderedHead && !state.partial;
|
|
}
|
|
function shouldRenderMaybeHeadInstruction(state) {
|
|
return !state.hasRenderedHead && !state.headInTree && !state.partial;
|
|
}
|
|
function shouldRenderInstruction$1(type, state) {
|
|
return type === "head" ? shouldRenderHeadInstruction(state) : shouldRenderMaybeHeadInstruction(state);
|
|
}
|
|
|
|
function registerIfPropagating(result, factory, instance) {
|
|
if (factory.propagation === "self" || factory.propagation === "in-tree") {
|
|
result._metadata.propagators.add(
|
|
instance
|
|
);
|
|
return;
|
|
}
|
|
if (factory.moduleId) {
|
|
const hint = result.componentMetadata.get(factory.moduleId)?.propagation;
|
|
if (isPropagatingHint(hint ?? "none")) {
|
|
result._metadata.propagators.add(
|
|
instance
|
|
);
|
|
}
|
|
}
|
|
}
|
|
async function bufferPropagatedHead(result) {
|
|
const collected = await collectPropagatedHeadParts({
|
|
propagators: result._metadata.propagators,
|
|
result,
|
|
isHeadAndContent
|
|
});
|
|
result._metadata.extraHead.push(...collected);
|
|
}
|
|
function shouldRenderInstruction(type, state) {
|
|
return shouldRenderInstruction$1(type, state);
|
|
}
|
|
function getInstructionRenderState(result) {
|
|
return {
|
|
hasRenderedHead: result._metadata.hasRenderedHead,
|
|
headInTree: result._metadata.headInTree,
|
|
partial: result.partial
|
|
};
|
|
}
|
|
|
|
function renderCspContent(result) {
|
|
const finalScriptHashes = /* @__PURE__ */ new Set();
|
|
const finalStyleHashes = /* @__PURE__ */ new Set();
|
|
for (const scriptHash of result.scriptHashes) {
|
|
finalScriptHashes.add(`'${scriptHash}'`);
|
|
}
|
|
for (const styleHash of result.styleHashes) {
|
|
finalStyleHashes.add(`'${styleHash}'`);
|
|
}
|
|
for (const styleHash of result._metadata.extraStyleHashes) {
|
|
finalStyleHashes.add(`'${styleHash}'`);
|
|
}
|
|
for (const scriptHash of result._metadata.extraScriptHashes) {
|
|
finalScriptHashes.add(`'${scriptHash}'`);
|
|
}
|
|
let directives;
|
|
if (result.directives.length > 0) {
|
|
directives = result.directives.join(";") + ";";
|
|
}
|
|
let scriptResources = "'self'";
|
|
if (result.scriptResources.length > 0) {
|
|
scriptResources = result.scriptResources.map((r) => `${r}`).join(" ");
|
|
}
|
|
let styleResources = "'self'";
|
|
if (result.styleResources.length > 0) {
|
|
styleResources = result.styleResources.map((r) => `${r}`).join(" ");
|
|
}
|
|
const strictDynamic = result.isStrictDynamic ? ` 'strict-dynamic'` : "";
|
|
const scriptSrc = `script-src ${scriptResources} ${Array.from(finalScriptHashes).join(" ")}${strictDynamic};`;
|
|
const styleSrc = `style-src ${styleResources} ${Array.from(finalStyleHashes).join(" ")};`;
|
|
return [directives, scriptSrc, styleSrc].filter(Boolean).join(" ");
|
|
}
|
|
|
|
const RenderInstructionSymbol = /* @__PURE__ */ Symbol.for("astro:render");
|
|
function createRenderInstruction(instruction) {
|
|
return Object.defineProperty(instruction, RenderInstructionSymbol, {
|
|
value: true
|
|
});
|
|
}
|
|
function isRenderInstruction(chunk) {
|
|
return chunk && typeof chunk === "object" && chunk[RenderInstructionSymbol];
|
|
}
|
|
|
|
const voidElementNames = /^(area|base|br|col|command|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)$/i;
|
|
const htmlBooleanAttributes = /^(?:allowfullscreen|async|autofocus|autoplay|checked|controls|default|defer|disabled|disablepictureinpicture|disableremoteplayback|formnovalidate|inert|loop|muted|nomodule|novalidate|open|playsinline|readonly|required|reversed|scoped|seamless|selected|itemscope)$/i;
|
|
const AMPERSAND_REGEX = /&/g;
|
|
const DOUBLE_QUOTE_REGEX = /"/g;
|
|
const STATIC_DIRECTIVES = /* @__PURE__ */ new Set(["set:html", "set:text"]);
|
|
const toIdent = (k) => k.trim().replace(/(?!^)\b\w|\s+|\W+/g, (match, index) => {
|
|
if (/\W/.test(match)) return "";
|
|
return index === 0 ? match : match.toUpperCase();
|
|
});
|
|
const toAttributeString = (value, shouldEscape = true) => shouldEscape ? String(value).replace(AMPERSAND_REGEX, "&").replace(DOUBLE_QUOTE_REGEX, """) : value;
|
|
const kebab = (k) => k.toLowerCase() === k ? k : k.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
|
|
const toStyleString = (obj) => Object.entries(obj).filter(([_, v]) => typeof v === "string" && v.trim() || typeof v === "number").map(([k, v]) => {
|
|
if (k[0] !== "-" && k[1] !== "-") return `${kebab(k)}:${v}`;
|
|
return `${k}:${v}`;
|
|
}).join(";");
|
|
function defineScriptVars(vars) {
|
|
let output = "";
|
|
for (const [key, value] of Object.entries(vars)) {
|
|
output += `const ${toIdent(key)} = ${stringifyForScript(value)};
|
|
`;
|
|
}
|
|
return markHTMLString(output);
|
|
}
|
|
function formatList(values) {
|
|
if (values.length === 1) {
|
|
return values[0];
|
|
}
|
|
return `${values.slice(0, -1).join(", ")} or ${values[values.length - 1]}`;
|
|
}
|
|
function isCustomElement(tagName) {
|
|
return tagName.includes("-");
|
|
}
|
|
function handleBooleanAttribute(key, value, shouldEscape, tagName) {
|
|
if (tagName && isCustomElement(tagName)) {
|
|
return markHTMLString(` ${key}="${toAttributeString(value, shouldEscape)}"`);
|
|
}
|
|
return markHTMLString(value ? ` ${key}` : "");
|
|
}
|
|
function addAttribute(value, key, shouldEscape = true, tagName = "") {
|
|
if (value == null) {
|
|
return "";
|
|
}
|
|
if (STATIC_DIRECTIVES.has(key)) {
|
|
console.warn(`[astro] The "${key}" directive cannot be applied dynamically at runtime. It will not be rendered as an attribute.
|
|
|
|
Make sure to use the static attribute syntax (\`${key}={value}\`) instead of the dynamic spread syntax (\`{...{ "${key}": value }}\`).`);
|
|
return "";
|
|
}
|
|
if (key === "class:list") {
|
|
const listValue = toAttributeString(clsx(value), shouldEscape);
|
|
if (listValue === "") {
|
|
return "";
|
|
}
|
|
return markHTMLString(` ${key.slice(0, -5)}="${listValue}"`);
|
|
}
|
|
if (key === "style" && !(value instanceof HTMLString)) {
|
|
if (Array.isArray(value) && value.length === 2) {
|
|
return markHTMLString(
|
|
` ${key}="${toAttributeString(`${toStyleString(value[0])};${value[1]}`, shouldEscape)}"`
|
|
);
|
|
}
|
|
if (typeof value === "object") {
|
|
return markHTMLString(` ${key}="${toAttributeString(toStyleString(value), shouldEscape)}"`);
|
|
}
|
|
}
|
|
if (key === "className") {
|
|
return markHTMLString(` class="${toAttributeString(value, shouldEscape)}"`);
|
|
}
|
|
if (htmlBooleanAttributes.test(key)) {
|
|
return handleBooleanAttribute(key, value, shouldEscape, tagName);
|
|
}
|
|
if (value === "") {
|
|
return markHTMLString(` ${key}`);
|
|
}
|
|
if (key === "popover" && typeof value === "boolean") {
|
|
return handleBooleanAttribute(key, value, shouldEscape, tagName);
|
|
}
|
|
if (key === "download" && typeof value === "boolean") {
|
|
return handleBooleanAttribute(key, value, shouldEscape, tagName);
|
|
}
|
|
if (key === "hidden" && typeof value === "boolean") {
|
|
return handleBooleanAttribute(key, value, shouldEscape, tagName);
|
|
}
|
|
return markHTMLString(` ${key}="${toAttributeString(value, shouldEscape)}"`);
|
|
}
|
|
function internalSpreadAttributes(values, shouldEscape = true, tagName) {
|
|
let output = "";
|
|
for (const [key, value] of Object.entries(values)) {
|
|
output += addAttribute(value, key, shouldEscape, tagName);
|
|
}
|
|
return markHTMLString(output);
|
|
}
|
|
function renderElement$1(name, { props: _props, children = "" }, shouldEscape = true) {
|
|
const { lang: _, "data-astro-id": astroId, "define:vars": defineVars, ...props } = _props;
|
|
if (defineVars) {
|
|
if (name === "style") {
|
|
delete props["is:global"];
|
|
delete props["is:scoped"];
|
|
}
|
|
if (name === "script") {
|
|
delete props.hoist;
|
|
children = defineScriptVars(defineVars) + "\n" + children;
|
|
}
|
|
}
|
|
if ((children == null || children === "") && voidElementNames.test(name)) {
|
|
return `<${name}${internalSpreadAttributes(props, shouldEscape, name)}>`;
|
|
}
|
|
return `<${name}${internalSpreadAttributes(props, shouldEscape, name)}>${children}</${name}>`;
|
|
}
|
|
const noop = () => {
|
|
};
|
|
class BufferedRenderer {
|
|
chunks = [];
|
|
renderPromise;
|
|
destination;
|
|
/**
|
|
* Determines whether buffer has been flushed
|
|
* to the final destination.
|
|
*/
|
|
flushed = false;
|
|
constructor(destination, renderFunction) {
|
|
this.destination = destination;
|
|
this.renderPromise = renderFunction(this);
|
|
if (isPromise(this.renderPromise)) {
|
|
Promise.resolve(this.renderPromise).catch(noop);
|
|
}
|
|
}
|
|
write(chunk) {
|
|
if (this.flushed) {
|
|
this.destination.write(chunk);
|
|
} else {
|
|
this.chunks.push(chunk);
|
|
}
|
|
}
|
|
flush() {
|
|
if (this.flushed) {
|
|
throw new Error("The render buffer has already been flushed.");
|
|
}
|
|
this.flushed = true;
|
|
for (const chunk of this.chunks) {
|
|
this.destination.write(chunk);
|
|
}
|
|
return this.renderPromise;
|
|
}
|
|
}
|
|
function createBufferedRenderer(destination, renderFunction) {
|
|
return new BufferedRenderer(destination, renderFunction);
|
|
}
|
|
const isNode = typeof process !== "undefined" && Object.prototype.toString.call(process) === "[object process]";
|
|
const isDeno = typeof Deno !== "undefined";
|
|
function promiseWithResolvers() {
|
|
let resolve, reject;
|
|
const promise = new Promise((_resolve, _reject) => {
|
|
resolve = _resolve;
|
|
reject = _reject;
|
|
});
|
|
return {
|
|
promise,
|
|
resolve,
|
|
reject
|
|
};
|
|
}
|
|
|
|
function stablePropsKey(props) {
|
|
const keys = Object.keys(props).sort();
|
|
let result = "{";
|
|
for (let i = 0; i < keys.length; i++) {
|
|
if (i > 0) result += ",";
|
|
result += JSON.stringify(keys[i]) + ":" + JSON.stringify(props[keys[i]]);
|
|
}
|
|
result += "}";
|
|
return result;
|
|
}
|
|
function deduplicateElements(elements) {
|
|
if (elements.length <= 1) return elements;
|
|
const seen = /* @__PURE__ */ new Set();
|
|
return elements.filter((item) => {
|
|
const key = stablePropsKey(item.props) + item.children;
|
|
if (seen.has(key)) return false;
|
|
seen.add(key);
|
|
return true;
|
|
});
|
|
}
|
|
function renderAllHeadContent(result) {
|
|
result._metadata.hasRenderedHead = true;
|
|
let content = "";
|
|
if (result.shouldInjectCspMetaTags && result.cspDestination === "meta") {
|
|
content += renderElement$1(
|
|
"meta",
|
|
{
|
|
props: {
|
|
"http-equiv": "content-security-policy",
|
|
content: renderCspContent(result)
|
|
},
|
|
children: ""
|
|
},
|
|
false
|
|
);
|
|
}
|
|
const styles = deduplicateElements(Array.from(result.styles)).map(
|
|
(style) => style.props.rel === "stylesheet" ? renderElement$1("link", style) : renderElement$1("style", style)
|
|
);
|
|
result.styles.clear();
|
|
const scripts = deduplicateElements(Array.from(result.scripts)).map((script) => {
|
|
if (result.userAssetsBase) {
|
|
script.props.src = (result.base === "/" ? "" : result.base) + result.userAssetsBase + script.props.src;
|
|
}
|
|
return renderElement$1("script", script, false);
|
|
});
|
|
const links = deduplicateElements(Array.from(result.links)).map(
|
|
(link) => renderElement$1("link", link, false)
|
|
);
|
|
content += styles.join("\n") + links.join("\n") + scripts.join("\n");
|
|
if (result._metadata.extraHead.length > 0) {
|
|
for (const part of result._metadata.extraHead) {
|
|
content += part;
|
|
}
|
|
}
|
|
return markHTMLString(content);
|
|
}
|
|
function renderHead() {
|
|
return createRenderInstruction({ type: "head" });
|
|
}
|
|
function maybeRenderHead() {
|
|
return createRenderInstruction({ type: "maybe-head" });
|
|
}
|
|
|
|
function encodeHexUpperCase(data) {
|
|
let result = "";
|
|
for (let i = 0; i < data.length; i++) {
|
|
result += alphabetUpperCase[data[i] >> 4];
|
|
result += alphabetUpperCase[data[i] & 0x0f];
|
|
}
|
|
return result;
|
|
}
|
|
function decodeHex(data) {
|
|
if (data.length % 2 !== 0) {
|
|
throw new Error("Invalid hex string");
|
|
}
|
|
const result = new Uint8Array(data.length / 2);
|
|
for (let i = 0; i < data.length; i += 2) {
|
|
if (!(data[i] in decodeMap)) {
|
|
throw new Error("Invalid character");
|
|
}
|
|
if (!(data[i + 1] in decodeMap)) {
|
|
throw new Error("Invalid character");
|
|
}
|
|
result[i / 2] |= decodeMap[data[i]] << 4;
|
|
result[i / 2] |= decodeMap[data[i + 1]];
|
|
}
|
|
return result;
|
|
}
|
|
const alphabetUpperCase = "0123456789ABCDEF";
|
|
const decodeMap = {
|
|
"0": 0,
|
|
"1": 1,
|
|
"2": 2,
|
|
"3": 3,
|
|
"4": 4,
|
|
"5": 5,
|
|
"6": 6,
|
|
"7": 7,
|
|
"8": 8,
|
|
"9": 9,
|
|
a: 10,
|
|
A: 10,
|
|
b: 11,
|
|
B: 11,
|
|
c: 12,
|
|
C: 12,
|
|
d: 13,
|
|
D: 13,
|
|
e: 14,
|
|
E: 14,
|
|
f: 15,
|
|
F: 15
|
|
};
|
|
|
|
var EncodingPadding$1;
|
|
(function (EncodingPadding) {
|
|
EncodingPadding[EncodingPadding["Include"] = 0] = "Include";
|
|
EncodingPadding[EncodingPadding["None"] = 1] = "None";
|
|
})(EncodingPadding$1 || (EncodingPadding$1 = {}));
|
|
var DecodingPadding$1;
|
|
(function (DecodingPadding) {
|
|
DecodingPadding[DecodingPadding["Required"] = 0] = "Required";
|
|
DecodingPadding[DecodingPadding["Ignore"] = 1] = "Ignore";
|
|
})(DecodingPadding$1 || (DecodingPadding$1 = {}));
|
|
|
|
function encodeBase64(bytes) {
|
|
return encodeBase64_internal(bytes, base64Alphabet, EncodingPadding.Include);
|
|
}
|
|
function encodeBase64_internal(bytes, alphabet, padding) {
|
|
let result = "";
|
|
for (let i = 0; i < bytes.byteLength; i += 3) {
|
|
let buffer = 0;
|
|
let bufferBitSize = 0;
|
|
for (let j = 0; j < 3 && i + j < bytes.byteLength; j++) {
|
|
buffer = (buffer << 8) | bytes[i + j];
|
|
bufferBitSize += 8;
|
|
}
|
|
for (let j = 0; j < 4; j++) {
|
|
if (bufferBitSize >= 6) {
|
|
result += alphabet[(buffer >> (bufferBitSize - 6)) & 0x3f];
|
|
bufferBitSize -= 6;
|
|
}
|
|
else if (bufferBitSize > 0) {
|
|
result += alphabet[(buffer << (6 - bufferBitSize)) & 0x3f];
|
|
bufferBitSize = 0;
|
|
}
|
|
else if (padding === EncodingPadding.Include) {
|
|
result += "=";
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
const base64Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
function decodeBase64(encoded) {
|
|
return decodeBase64_internal(encoded, base64DecodeMap, DecodingPadding.Required);
|
|
}
|
|
function decodeBase64_internal(encoded, decodeMap, padding) {
|
|
const result = new Uint8Array(Math.ceil(encoded.length / 4) * 3);
|
|
let totalBytes = 0;
|
|
for (let i = 0; i < encoded.length; i += 4) {
|
|
let chunk = 0;
|
|
let bitsRead = 0;
|
|
for (let j = 0; j < 4; j++) {
|
|
if (padding === DecodingPadding.Required && encoded[i + j] === "=") {
|
|
continue;
|
|
}
|
|
if (padding === DecodingPadding.Ignore &&
|
|
(i + j >= encoded.length || encoded[i + j] === "=")) {
|
|
continue;
|
|
}
|
|
if (j > 0 && encoded[i + j - 1] === "=") {
|
|
throw new Error("Invalid padding");
|
|
}
|
|
if (!(encoded[i + j] in decodeMap)) {
|
|
throw new Error("Invalid character");
|
|
}
|
|
chunk |= decodeMap[encoded[i + j]] << ((3 - j) * 6);
|
|
bitsRead += 6;
|
|
}
|
|
if (bitsRead < 24) {
|
|
let unused;
|
|
if (bitsRead === 12) {
|
|
unused = chunk & 0xffff;
|
|
}
|
|
else if (bitsRead === 18) {
|
|
unused = chunk & 0xff;
|
|
}
|
|
else {
|
|
throw new Error("Invalid padding");
|
|
}
|
|
if (unused !== 0) {
|
|
throw new Error("Invalid padding");
|
|
}
|
|
}
|
|
const byteLength = Math.floor(bitsRead / 8);
|
|
for (let i = 0; i < byteLength; i++) {
|
|
result[totalBytes] = (chunk >> (16 - i * 8)) & 0xff;
|
|
totalBytes++;
|
|
}
|
|
}
|
|
return result.slice(0, totalBytes);
|
|
}
|
|
var EncodingPadding;
|
|
(function (EncodingPadding) {
|
|
EncodingPadding[EncodingPadding["Include"] = 0] = "Include";
|
|
EncodingPadding[EncodingPadding["None"] = 1] = "None";
|
|
})(EncodingPadding || (EncodingPadding = {}));
|
|
var DecodingPadding;
|
|
(function (DecodingPadding) {
|
|
DecodingPadding[DecodingPadding["Required"] = 0] = "Required";
|
|
DecodingPadding[DecodingPadding["Ignore"] = 1] = "Ignore";
|
|
})(DecodingPadding || (DecodingPadding = {}));
|
|
const base64DecodeMap = {
|
|
"0": 52,
|
|
"1": 53,
|
|
"2": 54,
|
|
"3": 55,
|
|
"4": 56,
|
|
"5": 57,
|
|
"6": 58,
|
|
"7": 59,
|
|
"8": 60,
|
|
"9": 61,
|
|
A: 0,
|
|
B: 1,
|
|
C: 2,
|
|
D: 3,
|
|
E: 4,
|
|
F: 5,
|
|
G: 6,
|
|
H: 7,
|
|
I: 8,
|
|
J: 9,
|
|
K: 10,
|
|
L: 11,
|
|
M: 12,
|
|
N: 13,
|
|
O: 14,
|
|
P: 15,
|
|
Q: 16,
|
|
R: 17,
|
|
S: 18,
|
|
T: 19,
|
|
U: 20,
|
|
V: 21,
|
|
W: 22,
|
|
X: 23,
|
|
Y: 24,
|
|
Z: 25,
|
|
a: 26,
|
|
b: 27,
|
|
c: 28,
|
|
d: 29,
|
|
e: 30,
|
|
f: 31,
|
|
g: 32,
|
|
h: 33,
|
|
i: 34,
|
|
j: 35,
|
|
k: 36,
|
|
l: 37,
|
|
m: 38,
|
|
n: 39,
|
|
o: 40,
|
|
p: 41,
|
|
q: 42,
|
|
r: 43,
|
|
s: 44,
|
|
t: 45,
|
|
u: 46,
|
|
v: 47,
|
|
w: 48,
|
|
x: 49,
|
|
y: 50,
|
|
z: 51,
|
|
"+": 62,
|
|
"/": 63
|
|
};
|
|
|
|
const initializer = (inst, issues) => {
|
|
$ZodError.init(inst, issues);
|
|
inst.name = "ZodError";
|
|
Object.defineProperties(inst, {
|
|
format: {
|
|
value: (mapper) => formatError(inst, mapper),
|
|
// enumerable: false,
|
|
},
|
|
flatten: {
|
|
value: (mapper) => flattenError(inst, mapper),
|
|
// enumerable: false,
|
|
},
|
|
addIssue: {
|
|
value: (issue) => {
|
|
inst.issues.push(issue);
|
|
inst.message = JSON.stringify(inst.issues, jsonStringifyReplacer, 2);
|
|
},
|
|
// enumerable: false,
|
|
},
|
|
addIssues: {
|
|
value: (issues) => {
|
|
inst.issues.push(...issues);
|
|
inst.message = JSON.stringify(inst.issues, jsonStringifyReplacer, 2);
|
|
},
|
|
// enumerable: false,
|
|
},
|
|
isEmpty: {
|
|
get() {
|
|
return inst.issues.length === 0;
|
|
},
|
|
// enumerable: false,
|
|
},
|
|
});
|
|
// Object.defineProperty(inst, "isEmpty", {
|
|
// get() {
|
|
// return inst.issues.length === 0;
|
|
// },
|
|
// });
|
|
};
|
|
const ZodRealError = $constructor("ZodError", initializer, {
|
|
Parent: Error,
|
|
});
|
|
// /** @deprecated Use `z.core.$ZodErrorMapCtx` instead. */
|
|
// export type ErrorMapCtx = core.$ZodErrorMapCtx;
|
|
|
|
const parse = /* @__PURE__ */ _parse(ZodRealError);
|
|
const parseAsync = /* @__PURE__ */ _parseAsync(ZodRealError);
|
|
const safeParse = /* @__PURE__ */ _safeParse(ZodRealError);
|
|
const safeParseAsync = /* @__PURE__ */ _safeParseAsync(ZodRealError);
|
|
// Codec functions
|
|
const encode = /* @__PURE__ */ _encode(ZodRealError);
|
|
const decode = /* @__PURE__ */ _decode(ZodRealError);
|
|
const encodeAsync = /* @__PURE__ */ _encodeAsync(ZodRealError);
|
|
const decodeAsync = /* @__PURE__ */ _decodeAsync(ZodRealError);
|
|
const safeEncode = /* @__PURE__ */ _safeEncode(ZodRealError);
|
|
const safeDecode = /* @__PURE__ */ _safeDecode(ZodRealError);
|
|
const safeEncodeAsync = /* @__PURE__ */ _safeEncodeAsync(ZodRealError);
|
|
const safeDecodeAsync = /* @__PURE__ */ _safeDecodeAsync(ZodRealError);
|
|
|
|
const ZodType = /*@__PURE__*/ $constructor("ZodType", (inst, def) => {
|
|
$ZodType.init(inst, def);
|
|
Object.assign(inst["~standard"], {
|
|
jsonSchema: {
|
|
input: createStandardJSONSchemaMethod(inst, "input"),
|
|
output: createStandardJSONSchemaMethod(inst, "output"),
|
|
},
|
|
});
|
|
inst.toJSONSchema = createToJSONSchemaMethod(inst, {});
|
|
inst.def = def;
|
|
inst.type = def.type;
|
|
Object.defineProperty(inst, "_def", { value: def });
|
|
// base methods
|
|
inst.check = (...checks) => {
|
|
return inst.clone(mergeDefs(def, {
|
|
checks: [
|
|
...(def.checks ?? []),
|
|
...checks.map((ch) => typeof ch === "function" ? { _zod: { check: ch, def: { check: "custom" }, onattach: [] } } : ch),
|
|
],
|
|
}), {
|
|
parent: true,
|
|
});
|
|
};
|
|
inst.with = inst.check;
|
|
inst.clone = (def, params) => clone(inst, def, params);
|
|
inst.brand = () => inst;
|
|
inst.register = ((reg, meta) => {
|
|
reg.add(inst, meta);
|
|
return inst;
|
|
});
|
|
// parsing
|
|
inst.parse = (data, params) => parse(inst, data, params, { callee: inst.parse });
|
|
inst.safeParse = (data, params) => safeParse(inst, data, params);
|
|
inst.parseAsync = async (data, params) => parseAsync(inst, data, params, { callee: inst.parseAsync });
|
|
inst.safeParseAsync = async (data, params) => safeParseAsync(inst, data, params);
|
|
inst.spa = inst.safeParseAsync;
|
|
// encoding/decoding
|
|
inst.encode = (data, params) => encode(inst, data, params);
|
|
inst.decode = (data, params) => decode(inst, data, params);
|
|
inst.encodeAsync = async (data, params) => encodeAsync(inst, data, params);
|
|
inst.decodeAsync = async (data, params) => decodeAsync(inst, data, params);
|
|
inst.safeEncode = (data, params) => safeEncode(inst, data, params);
|
|
inst.safeDecode = (data, params) => safeDecode(inst, data, params);
|
|
inst.safeEncodeAsync = async (data, params) => safeEncodeAsync(inst, data, params);
|
|
inst.safeDecodeAsync = async (data, params) => safeDecodeAsync(inst, data, params);
|
|
// refinements
|
|
inst.refine = (check, params) => inst.check(refine(check, params));
|
|
inst.superRefine = (refinement) => inst.check(superRefine(refinement));
|
|
inst.overwrite = (fn) => inst.check(_overwrite(fn));
|
|
// wrappers
|
|
inst.optional = () => optional(inst);
|
|
inst.exactOptional = () => exactOptional(inst);
|
|
inst.nullable = () => nullable(inst);
|
|
inst.nullish = () => optional(nullable(inst));
|
|
inst.nonoptional = (params) => nonoptional(inst, params);
|
|
inst.array = () => array(inst);
|
|
inst.or = (arg) => union([inst, arg]);
|
|
inst.and = (arg) => intersection(inst, arg);
|
|
inst.transform = (tx) => pipe(inst, transform(tx));
|
|
inst.default = (def) => _default(inst, def);
|
|
inst.prefault = (def) => prefault(inst, def);
|
|
// inst.coalesce = (def, params) => coalesce(inst, def, params);
|
|
inst.catch = (params) => _catch(inst, params);
|
|
inst.pipe = (target) => pipe(inst, target);
|
|
inst.readonly = () => readonly(inst);
|
|
// meta
|
|
inst.describe = (description) => {
|
|
const cl = inst.clone();
|
|
globalRegistry.add(cl, { description });
|
|
return cl;
|
|
};
|
|
Object.defineProperty(inst, "description", {
|
|
get() {
|
|
return globalRegistry.get(inst)?.description;
|
|
},
|
|
configurable: true,
|
|
});
|
|
inst.meta = (...args) => {
|
|
if (args.length === 0) {
|
|
return globalRegistry.get(inst);
|
|
}
|
|
const cl = inst.clone();
|
|
globalRegistry.add(cl, args[0]);
|
|
return cl;
|
|
};
|
|
// helpers
|
|
inst.isOptional = () => inst.safeParse(undefined).success;
|
|
inst.isNullable = () => inst.safeParse(null).success;
|
|
inst.apply = (fn) => fn(inst);
|
|
return inst;
|
|
});
|
|
const ZodArray = /*@__PURE__*/ $constructor("ZodArray", (inst, def) => {
|
|
$ZodArray.init(inst, def);
|
|
ZodType.init(inst, def);
|
|
inst._zod.processJSONSchema = (ctx, json, params) => arrayProcessor(inst, ctx, json, params);
|
|
inst.element = def.element;
|
|
inst.min = (minLength, params) => inst.check(_minLength(minLength, params));
|
|
inst.nonempty = (params) => inst.check(_minLength(1, params));
|
|
inst.max = (maxLength, params) => inst.check(_maxLength(maxLength, params));
|
|
inst.length = (len, params) => inst.check(_length(len, params));
|
|
inst.unwrap = () => inst.element;
|
|
});
|
|
function array(element, params) {
|
|
return _array(ZodArray, element, params);
|
|
}
|
|
const ZodUnion = /*@__PURE__*/ $constructor("ZodUnion", (inst, def) => {
|
|
$ZodUnion.init(inst, def);
|
|
ZodType.init(inst, def);
|
|
inst._zod.processJSONSchema = (ctx, json, params) => unionProcessor(inst, ctx, json, params);
|
|
inst.options = def.options;
|
|
});
|
|
function union(options, params) {
|
|
return new ZodUnion({
|
|
type: "union",
|
|
options: options,
|
|
...normalizeParams(params),
|
|
});
|
|
}
|
|
const ZodIntersection = /*@__PURE__*/ $constructor("ZodIntersection", (inst, def) => {
|
|
$ZodIntersection.init(inst, def);
|
|
ZodType.init(inst, def);
|
|
inst._zod.processJSONSchema = (ctx, json, params) => intersectionProcessor(inst, ctx, json, params);
|
|
});
|
|
function intersection(left, right) {
|
|
return new ZodIntersection({
|
|
type: "intersection",
|
|
left: left,
|
|
right: right,
|
|
});
|
|
}
|
|
const ZodEnum = /*@__PURE__*/ $constructor("ZodEnum", (inst, def) => {
|
|
$ZodEnum.init(inst, def);
|
|
ZodType.init(inst, def);
|
|
inst._zod.processJSONSchema = (ctx, json, params) => enumProcessor(inst, ctx, json);
|
|
inst.enum = def.entries;
|
|
inst.options = Object.values(def.entries);
|
|
const keys = new Set(Object.keys(def.entries));
|
|
inst.extract = (values, params) => {
|
|
const newEntries = {};
|
|
for (const value of values) {
|
|
if (keys.has(value)) {
|
|
newEntries[value] = def.entries[value];
|
|
}
|
|
else
|
|
throw new Error(`Key ${value} not found in enum`);
|
|
}
|
|
return new ZodEnum({
|
|
...def,
|
|
checks: [],
|
|
...normalizeParams(params),
|
|
entries: newEntries,
|
|
});
|
|
};
|
|
inst.exclude = (values, params) => {
|
|
const newEntries = { ...def.entries };
|
|
for (const value of values) {
|
|
if (keys.has(value)) {
|
|
delete newEntries[value];
|
|
}
|
|
else
|
|
throw new Error(`Key ${value} not found in enum`);
|
|
}
|
|
return new ZodEnum({
|
|
...def,
|
|
checks: [],
|
|
...normalizeParams(params),
|
|
entries: newEntries,
|
|
});
|
|
};
|
|
});
|
|
function _enum(values, params) {
|
|
const entries = Array.isArray(values) ? Object.fromEntries(values.map((v) => [v, v])) : values;
|
|
return new ZodEnum({
|
|
type: "enum",
|
|
entries,
|
|
...normalizeParams(params),
|
|
});
|
|
}
|
|
const ZodTransform = /*@__PURE__*/ $constructor("ZodTransform", (inst, def) => {
|
|
$ZodTransform.init(inst, def);
|
|
ZodType.init(inst, def);
|
|
inst._zod.processJSONSchema = (ctx, json, params) => transformProcessor(inst, ctx);
|
|
inst._zod.parse = (payload, _ctx) => {
|
|
if (_ctx.direction === "backward") {
|
|
throw new $ZodEncodeError(inst.constructor.name);
|
|
}
|
|
payload.addIssue = (issue$1) => {
|
|
if (typeof issue$1 === "string") {
|
|
payload.issues.push(issue(issue$1, payload.value, def));
|
|
}
|
|
else {
|
|
// for Zod 3 backwards compatibility
|
|
const _issue = issue$1;
|
|
if (_issue.fatal)
|
|
_issue.continue = false;
|
|
_issue.code ?? (_issue.code = "custom");
|
|
_issue.input ?? (_issue.input = payload.value);
|
|
_issue.inst ?? (_issue.inst = inst);
|
|
// _issue.continue ??= true;
|
|
payload.issues.push(issue(_issue));
|
|
}
|
|
};
|
|
const output = def.transform(payload.value, payload);
|
|
if (output instanceof Promise) {
|
|
return output.then((output) => {
|
|
payload.value = output;
|
|
return payload;
|
|
});
|
|
}
|
|
payload.value = output;
|
|
return payload;
|
|
};
|
|
});
|
|
function transform(fn) {
|
|
return new ZodTransform({
|
|
type: "transform",
|
|
transform: fn,
|
|
});
|
|
}
|
|
const ZodOptional = /*@__PURE__*/ $constructor("ZodOptional", (inst, def) => {
|
|
$ZodOptional.init(inst, def);
|
|
ZodType.init(inst, def);
|
|
inst._zod.processJSONSchema = (ctx, json, params) => optionalProcessor(inst, ctx, json, params);
|
|
inst.unwrap = () => inst._zod.def.innerType;
|
|
});
|
|
function optional(innerType) {
|
|
return new ZodOptional({
|
|
type: "optional",
|
|
innerType: innerType,
|
|
});
|
|
}
|
|
const ZodExactOptional = /*@__PURE__*/ $constructor("ZodExactOptional", (inst, def) => {
|
|
$ZodExactOptional.init(inst, def);
|
|
ZodType.init(inst, def);
|
|
inst._zod.processJSONSchema = (ctx, json, params) => optionalProcessor(inst, ctx, json, params);
|
|
inst.unwrap = () => inst._zod.def.innerType;
|
|
});
|
|
function exactOptional(innerType) {
|
|
return new ZodExactOptional({
|
|
type: "optional",
|
|
innerType: innerType,
|
|
});
|
|
}
|
|
const ZodNullable = /*@__PURE__*/ $constructor("ZodNullable", (inst, def) => {
|
|
$ZodNullable.init(inst, def);
|
|
ZodType.init(inst, def);
|
|
inst._zod.processJSONSchema = (ctx, json, params) => nullableProcessor(inst, ctx, json, params);
|
|
inst.unwrap = () => inst._zod.def.innerType;
|
|
});
|
|
function nullable(innerType) {
|
|
return new ZodNullable({
|
|
type: "nullable",
|
|
innerType: innerType,
|
|
});
|
|
}
|
|
const ZodDefault = /*@__PURE__*/ $constructor("ZodDefault", (inst, def) => {
|
|
$ZodDefault.init(inst, def);
|
|
ZodType.init(inst, def);
|
|
inst._zod.processJSONSchema = (ctx, json, params) => defaultProcessor(inst, ctx, json, params);
|
|
inst.unwrap = () => inst._zod.def.innerType;
|
|
inst.removeDefault = inst.unwrap;
|
|
});
|
|
function _default(innerType, defaultValue) {
|
|
return new ZodDefault({
|
|
type: "default",
|
|
innerType: innerType,
|
|
get defaultValue() {
|
|
return typeof defaultValue === "function" ? defaultValue() : shallowClone(defaultValue);
|
|
},
|
|
});
|
|
}
|
|
const ZodPrefault = /*@__PURE__*/ $constructor("ZodPrefault", (inst, def) => {
|
|
$ZodPrefault.init(inst, def);
|
|
ZodType.init(inst, def);
|
|
inst._zod.processJSONSchema = (ctx, json, params) => prefaultProcessor(inst, ctx, json, params);
|
|
inst.unwrap = () => inst._zod.def.innerType;
|
|
});
|
|
function prefault(innerType, defaultValue) {
|
|
return new ZodPrefault({
|
|
type: "prefault",
|
|
innerType: innerType,
|
|
get defaultValue() {
|
|
return typeof defaultValue === "function" ? defaultValue() : shallowClone(defaultValue);
|
|
},
|
|
});
|
|
}
|
|
const ZodNonOptional = /*@__PURE__*/ $constructor("ZodNonOptional", (inst, def) => {
|
|
$ZodNonOptional.init(inst, def);
|
|
ZodType.init(inst, def);
|
|
inst._zod.processJSONSchema = (ctx, json, params) => nonoptionalProcessor(inst, ctx, json, params);
|
|
inst.unwrap = () => inst._zod.def.innerType;
|
|
});
|
|
function nonoptional(innerType, params) {
|
|
return new ZodNonOptional({
|
|
type: "nonoptional",
|
|
innerType: innerType,
|
|
...normalizeParams(params),
|
|
});
|
|
}
|
|
const ZodCatch = /*@__PURE__*/ $constructor("ZodCatch", (inst, def) => {
|
|
$ZodCatch.init(inst, def);
|
|
ZodType.init(inst, def);
|
|
inst._zod.processJSONSchema = (ctx, json, params) => catchProcessor(inst, ctx, json, params);
|
|
inst.unwrap = () => inst._zod.def.innerType;
|
|
inst.removeCatch = inst.unwrap;
|
|
});
|
|
function _catch(innerType, catchValue) {
|
|
return new ZodCatch({
|
|
type: "catch",
|
|
innerType: innerType,
|
|
catchValue: (typeof catchValue === "function" ? catchValue : () => catchValue),
|
|
});
|
|
}
|
|
const ZodPipe = /*@__PURE__*/ $constructor("ZodPipe", (inst, def) => {
|
|
$ZodPipe.init(inst, def);
|
|
ZodType.init(inst, def);
|
|
inst._zod.processJSONSchema = (ctx, json, params) => pipeProcessor(inst, ctx, json, params);
|
|
inst.in = def.in;
|
|
inst.out = def.out;
|
|
});
|
|
function pipe(in_, out) {
|
|
return new ZodPipe({
|
|
type: "pipe",
|
|
in: in_,
|
|
out: out,
|
|
// ...util.normalizeParams(params),
|
|
});
|
|
}
|
|
const ZodReadonly = /*@__PURE__*/ $constructor("ZodReadonly", (inst, def) => {
|
|
$ZodReadonly.init(inst, def);
|
|
ZodType.init(inst, def);
|
|
inst._zod.processJSONSchema = (ctx, json, params) => readonlyProcessor(inst, ctx, json, params);
|
|
inst.unwrap = () => inst._zod.def.innerType;
|
|
});
|
|
function readonly(innerType) {
|
|
return new ZodReadonly({
|
|
type: "readonly",
|
|
innerType: innerType,
|
|
});
|
|
}
|
|
const ZodCustom = /*@__PURE__*/ $constructor("ZodCustom", (inst, def) => {
|
|
$ZodCustom.init(inst, def);
|
|
ZodType.init(inst, def);
|
|
inst._zod.processJSONSchema = (ctx, json, params) => customProcessor(inst, ctx);
|
|
});
|
|
function refine(fn, _params = {}) {
|
|
return _refine(ZodCustom, fn, _params);
|
|
}
|
|
// superRefine
|
|
function superRefine(fn) {
|
|
return _superRefine(fn);
|
|
}
|
|
|
|
const ALGORITHMS = {
|
|
"SHA-256": "sha256-",
|
|
"SHA-384": "sha384-",
|
|
"SHA-512": "sha512-"
|
|
};
|
|
_enum(Object.keys(ALGORITHMS)).optional().default("SHA-256");
|
|
|
|
const ALGORITHM = "AES-GCM";
|
|
async function decodeKey(encoded) {
|
|
const bytes = decodeBase64(encoded);
|
|
return crypto.subtle.importKey("raw", bytes.buffer, ALGORITHM, true, [
|
|
"encrypt",
|
|
"decrypt"
|
|
]);
|
|
}
|
|
const encoder$1 = new TextEncoder();
|
|
const decoder$1 = new TextDecoder();
|
|
const IV_LENGTH = 24;
|
|
async function encryptString(key, raw, additionalData) {
|
|
const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH / 2));
|
|
const data = encoder$1.encode(raw);
|
|
const params = { name: ALGORITHM, iv };
|
|
if (additionalData) {
|
|
params.additionalData = encoder$1.encode(additionalData);
|
|
}
|
|
const buffer = await crypto.subtle.encrypt(params, key, data);
|
|
return encodeHexUpperCase(iv) + encodeBase64(new Uint8Array(buffer));
|
|
}
|
|
async function decryptString(key, encoded, additionalData) {
|
|
const iv = decodeHex(encoded.slice(0, IV_LENGTH));
|
|
const dataArray = decodeBase64(encoded.slice(IV_LENGTH));
|
|
const params = { name: ALGORITHM, iv };
|
|
if (additionalData) {
|
|
params.additionalData = encoder$1.encode(additionalData);
|
|
}
|
|
const decryptedBuffer = await crypto.subtle.decrypt(params, key, dataArray);
|
|
const decryptedString = decoder$1.decode(decryptedBuffer);
|
|
return decryptedString;
|
|
}
|
|
async function generateCspDigest(data, algorithm) {
|
|
const hashBuffer = await crypto.subtle.digest(algorithm, encoder$1.encode(data));
|
|
const hash = encodeBase64(new Uint8Array(hashBuffer));
|
|
return `${ALGORITHMS[algorithm]}${hash}`;
|
|
}
|
|
|
|
const renderTemplateResultSym = /* @__PURE__ */ Symbol.for("astro.renderTemplateResult");
|
|
class RenderTemplateResult {
|
|
[renderTemplateResultSym] = true;
|
|
htmlParts;
|
|
expressions;
|
|
error;
|
|
constructor(htmlParts, expressions) {
|
|
this.htmlParts = htmlParts;
|
|
this.error = void 0;
|
|
this.expressions = expressions.map((expression) => {
|
|
if (isPromise(expression)) {
|
|
return Promise.resolve(expression).catch((err) => {
|
|
if (!this.error) {
|
|
this.error = err;
|
|
throw err;
|
|
}
|
|
});
|
|
}
|
|
return expression;
|
|
});
|
|
}
|
|
render(destination) {
|
|
const { htmlParts, expressions } = this;
|
|
for (let i = 0; i < htmlParts.length; i++) {
|
|
const html = htmlParts[i];
|
|
if (html) {
|
|
destination.write(markHTMLString(html));
|
|
}
|
|
if (i >= expressions.length) break;
|
|
const exp = expressions[i];
|
|
if (!(exp || exp === 0)) continue;
|
|
const result = renderChild(destination, exp);
|
|
if (isPromise(result)) {
|
|
const startIdx = i + 1;
|
|
const remaining = expressions.length - startIdx;
|
|
const flushers = new Array(remaining);
|
|
for (let j = 0; j < remaining; j++) {
|
|
const rExp = expressions[startIdx + j];
|
|
flushers[j] = createBufferedRenderer(destination, (bufferDestination) => {
|
|
if (rExp || rExp === 0) {
|
|
return renderChild(bufferDestination, rExp);
|
|
}
|
|
});
|
|
}
|
|
return result.then(() => {
|
|
let k = 0;
|
|
const iterate = () => {
|
|
while (k < flushers.length) {
|
|
const rHtml = htmlParts[startIdx + k];
|
|
if (rHtml) {
|
|
destination.write(markHTMLString(rHtml));
|
|
}
|
|
const flushResult = flushers[k++].flush();
|
|
if (isPromise(flushResult)) {
|
|
return flushResult.then(iterate);
|
|
}
|
|
}
|
|
const lastHtml = htmlParts[htmlParts.length - 1];
|
|
if (lastHtml) {
|
|
destination.write(markHTMLString(lastHtml));
|
|
}
|
|
};
|
|
return iterate();
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
function isRenderTemplateResult(obj) {
|
|
return typeof obj === "object" && obj !== null && !!obj[renderTemplateResultSym];
|
|
}
|
|
function renderTemplate(htmlParts, ...expressions) {
|
|
return new RenderTemplateResult(htmlParts, expressions);
|
|
}
|
|
|
|
const slotString = /* @__PURE__ */ Symbol.for("astro:slot-string");
|
|
class SlotString extends HTMLString {
|
|
instructions;
|
|
[slotString];
|
|
constructor(content, instructions) {
|
|
super(content);
|
|
this.instructions = instructions;
|
|
this[slotString] = true;
|
|
}
|
|
}
|
|
function isSlotString(str) {
|
|
return !!str[slotString];
|
|
}
|
|
function mergeSlotInstructions(target, source) {
|
|
if (source.instructions?.length) {
|
|
target ??= [];
|
|
target.push(...source.instructions);
|
|
}
|
|
return target;
|
|
}
|
|
function renderSlot(result, slotted, fallback) {
|
|
if (!slotted && fallback) {
|
|
return renderSlot(result, fallback);
|
|
}
|
|
return {
|
|
async render(destination) {
|
|
await renderChild(destination, typeof slotted === "function" ? slotted(result) : slotted);
|
|
}
|
|
};
|
|
}
|
|
async function renderSlotToString(result, slotted, fallback) {
|
|
let content = "";
|
|
let instructions = null;
|
|
const temporaryDestination = {
|
|
write(chunk) {
|
|
if (chunk instanceof SlotString) {
|
|
content += chunk;
|
|
instructions = mergeSlotInstructions(instructions, chunk);
|
|
} else if (chunk instanceof Response) return;
|
|
else if (typeof chunk === "object" && "type" in chunk && typeof chunk.type === "string") {
|
|
if (instructions === null) {
|
|
instructions = [];
|
|
}
|
|
instructions.push(chunk);
|
|
} else {
|
|
content += chunkToString(result, chunk);
|
|
}
|
|
}
|
|
};
|
|
const renderInstance = renderSlot(result, slotted, fallback);
|
|
await renderInstance.render(temporaryDestination);
|
|
return markHTMLString(new SlotString(content, instructions));
|
|
}
|
|
async function renderSlots(result, slots = {}) {
|
|
let slotInstructions = null;
|
|
let children = {};
|
|
if (slots) {
|
|
await Promise.all(
|
|
Object.entries(slots).map(
|
|
([key, value]) => renderSlotToString(result, value).then((output) => {
|
|
if (output.instructions) {
|
|
if (slotInstructions === null) {
|
|
slotInstructions = [];
|
|
}
|
|
slotInstructions.push(...output.instructions);
|
|
}
|
|
children[key] = output;
|
|
})
|
|
)
|
|
);
|
|
}
|
|
return { slotInstructions, children };
|
|
}
|
|
function createSlotValueFromString(content) {
|
|
return function() {
|
|
return renderTemplate`${unescapeHTML(content)}`;
|
|
};
|
|
}
|
|
|
|
const internalProps = /* @__PURE__ */ new Set([
|
|
"server:component-path",
|
|
"server:component-export",
|
|
"server:component-directive",
|
|
"server:defer"
|
|
]);
|
|
function containsServerDirective(props) {
|
|
return "server:component-directive" in props;
|
|
}
|
|
function createSearchParams(encryptedComponentExport, encryptedProps, slots) {
|
|
const params = new URLSearchParams();
|
|
params.set("e", encryptedComponentExport);
|
|
params.set("p", encryptedProps);
|
|
params.set("s", slots);
|
|
return params;
|
|
}
|
|
function isWithinURLLimit(pathname, params) {
|
|
const url = pathname + "?" + params.toString();
|
|
const chars = url.length;
|
|
return chars < 2048;
|
|
}
|
|
class ServerIslandComponent {
|
|
result;
|
|
props;
|
|
slots;
|
|
displayName;
|
|
hostId;
|
|
islandContent;
|
|
componentPath;
|
|
componentExport;
|
|
componentId;
|
|
constructor(result, props, slots, displayName) {
|
|
this.result = result;
|
|
this.props = props;
|
|
this.slots = slots;
|
|
this.displayName = displayName;
|
|
}
|
|
async init() {
|
|
const content = await this.getIslandContent();
|
|
if (this.result.cspDestination) {
|
|
this.result._metadata.extraScriptHashes.push(
|
|
await generateCspDigest(SERVER_ISLAND_REPLACER, this.result.cspAlgorithm)
|
|
);
|
|
const contentDigest = await generateCspDigest(content, this.result.cspAlgorithm);
|
|
this.result._metadata.extraScriptHashes.push(contentDigest);
|
|
}
|
|
return createThinHead();
|
|
}
|
|
async render(destination) {
|
|
const hostId = await this.getHostId();
|
|
const islandContent = await this.getIslandContent();
|
|
destination.write(createRenderInstruction({ type: "server-island-runtime" }));
|
|
destination.write("<!--[if astro]>server-island-start<![endif]-->");
|
|
for (const name in this.slots) {
|
|
if (name === "fallback") {
|
|
await renderChild(destination, this.slots.fallback(this.result));
|
|
}
|
|
}
|
|
destination.write(
|
|
`<script type="module" data-astro-rerun data-island-id="${hostId}">${islandContent}</script>`
|
|
);
|
|
}
|
|
getComponentPath() {
|
|
if (this.componentPath) {
|
|
return this.componentPath;
|
|
}
|
|
const componentPath = this.props["server:component-path"];
|
|
if (!componentPath) {
|
|
throw new Error(`Could not find server component path`);
|
|
}
|
|
this.componentPath = componentPath;
|
|
return componentPath;
|
|
}
|
|
getComponentExport() {
|
|
if (this.componentExport) {
|
|
return this.componentExport;
|
|
}
|
|
const componentExport = this.props["server:component-export"];
|
|
if (!componentExport) {
|
|
throw new Error(`Could not find server component export`);
|
|
}
|
|
this.componentExport = componentExport;
|
|
return componentExport;
|
|
}
|
|
async getHostId() {
|
|
if (!this.hostId) {
|
|
this.hostId = await crypto.randomUUID();
|
|
}
|
|
return this.hostId;
|
|
}
|
|
async getIslandContent() {
|
|
if (this.islandContent) {
|
|
return this.islandContent;
|
|
}
|
|
const componentPath = this.getComponentPath();
|
|
const componentExport = this.getComponentExport();
|
|
let componentId = this.result.serverIslandNameMap.get(componentPath);
|
|
if (!componentId) {
|
|
throw new Error(`Could not find server component name ${componentPath}`);
|
|
}
|
|
for (const key2 of Object.keys(this.props)) {
|
|
if (internalProps.has(key2)) {
|
|
delete this.props[key2];
|
|
}
|
|
}
|
|
const renderedSlots = {};
|
|
for (const name in this.slots) {
|
|
if (name !== "fallback") {
|
|
const content = await renderSlotToString(this.result, this.slots[name]);
|
|
let slotHtml = content.toString();
|
|
const slotContent = content;
|
|
if (Array.isArray(slotContent.instructions)) {
|
|
for (const instruction of slotContent.instructions) {
|
|
if (instruction.type === "script") {
|
|
slotHtml += instruction.content;
|
|
}
|
|
}
|
|
}
|
|
renderedSlots[name] = slotHtml;
|
|
}
|
|
}
|
|
const key = await this.result.key;
|
|
const componentExportEncrypted = await encryptString(
|
|
key,
|
|
componentExport,
|
|
`export:${componentId}`
|
|
);
|
|
const propsEncrypted = Object.keys(this.props).length === 0 ? "" : await encryptString(key, JSON.stringify(this.props), `props:${componentId}`);
|
|
const slotsEncrypted = Object.keys(renderedSlots).length === 0 ? "" : await encryptString(key, JSON.stringify(renderedSlots), `slots:${componentId}`);
|
|
const hostId = await this.getHostId();
|
|
const slash = this.result.base.endsWith("/") ? "" : "/";
|
|
let serverIslandUrl = `${this.result.base}${slash}_server-islands/${componentId}${this.result.trailingSlash === "always" ? "/" : ""}`;
|
|
const potentialSearchParams = createSearchParams(
|
|
componentExportEncrypted,
|
|
propsEncrypted,
|
|
slotsEncrypted
|
|
);
|
|
const useGETRequest = isWithinURLLimit(serverIslandUrl, potentialSearchParams);
|
|
if (useGETRequest) {
|
|
serverIslandUrl += "?" + potentialSearchParams.toString();
|
|
this.result._metadata.extraHead.push(
|
|
markHTMLString(
|
|
`<link rel="preload" as="fetch" href="${serverIslandUrl}" crossorigin="anonymous">`
|
|
)
|
|
);
|
|
}
|
|
const adapterHeaders = this.result.internalFetchHeaders || {};
|
|
const headersJson = stringifyForScript(adapterHeaders);
|
|
const method = useGETRequest ? (
|
|
// GET request
|
|
`const headers = new Headers(${headersJson});
|
|
let response = await fetch('${serverIslandUrl}', { headers });`
|
|
) : (
|
|
// POST request
|
|
`let data = {
|
|
encryptedComponentExport: ${stringifyForScript(componentExportEncrypted)},
|
|
encryptedProps: ${stringifyForScript(propsEncrypted)},
|
|
encryptedSlots: ${stringifyForScript(slotsEncrypted)},
|
|
};
|
|
const headers = new Headers({ 'Content-Type': 'application/json', ...${headersJson} });
|
|
let response = await fetch('${serverIslandUrl}', {
|
|
method: 'POST',
|
|
body: JSON.stringify(data),
|
|
headers,
|
|
});`
|
|
);
|
|
this.islandContent = `${method}replaceServerIsland('${hostId}', response);`;
|
|
return this.islandContent;
|
|
}
|
|
}
|
|
const renderServerIslandRuntime = () => {
|
|
return `<script>${SERVER_ISLAND_REPLACER}</script>`;
|
|
};
|
|
const SERVER_ISLAND_REPLACER = markHTMLString(
|
|
`async function replaceServerIsland(id, r) {
|
|
let s = document.querySelector(\`script[data-island-id="\${id}"]\`);
|
|
// If there's no matching script, or the request fails then return
|
|
if (!s || r.status !== 200 || r.headers.get('content-type')?.split(';')[0].trim() !== 'text/html') return;
|
|
// Load the HTML before modifying the DOM in case of errors
|
|
let html = await r.text();
|
|
// Remove any placeholder content before the island script
|
|
while (s.previousSibling && s.previousSibling.nodeType !== 8 && s.previousSibling.data !== '[if astro]>server-island-start<![endif]')
|
|
s.previousSibling.remove();
|
|
s.previousSibling?.remove();
|
|
// Insert the new HTML
|
|
s.before(document.createRange().createContextualFragment(html));
|
|
// Remove the script. Prior to v5.4.2, this was the trick to force rerun of scripts. Keeping it to minimize change to the existing behavior.
|
|
s.remove();
|
|
}`.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("//")).join(" ")
|
|
);
|
|
|
|
const Fragment = /* @__PURE__ */ Symbol.for("astro:fragment");
|
|
const Renderer = /* @__PURE__ */ Symbol.for("astro:renderer");
|
|
const encoder = new TextEncoder();
|
|
const decoder = new TextDecoder();
|
|
function stringifyChunk(result, chunk) {
|
|
if (isRenderInstruction(chunk)) {
|
|
const instruction = chunk;
|
|
switch (instruction.type) {
|
|
case "directive": {
|
|
const { hydration } = instruction;
|
|
let needsHydrationScript = hydration && determineIfNeedsHydrationScript(result);
|
|
let needsDirectiveScript = hydration && determinesIfNeedsDirectiveScript(result, hydration.directive);
|
|
if (needsHydrationScript) {
|
|
let prescripts = getPrescripts(result, "both", hydration.directive);
|
|
return markHTMLString(prescripts);
|
|
} else if (needsDirectiveScript) {
|
|
let prescripts = getPrescripts(result, "directive", hydration.directive);
|
|
return markHTMLString(prescripts);
|
|
} else {
|
|
return "";
|
|
}
|
|
}
|
|
case "head": {
|
|
if (!shouldRenderInstruction("head", getInstructionRenderState(result))) {
|
|
return "";
|
|
}
|
|
return renderAllHeadContent(result);
|
|
}
|
|
case "maybe-head": {
|
|
if (!shouldRenderInstruction("maybe-head", getInstructionRenderState(result))) {
|
|
return "";
|
|
}
|
|
return renderAllHeadContent(result);
|
|
}
|
|
case "renderer-hydration-script": {
|
|
const { rendererSpecificHydrationScripts } = result._metadata;
|
|
const { rendererName } = instruction;
|
|
if (!rendererSpecificHydrationScripts.has(rendererName)) {
|
|
rendererSpecificHydrationScripts.add(rendererName);
|
|
return instruction.render();
|
|
}
|
|
return "";
|
|
}
|
|
case "server-island-runtime": {
|
|
if (result._metadata.hasRenderedServerIslandRuntime) {
|
|
return "";
|
|
}
|
|
result._metadata.hasRenderedServerIslandRuntime = true;
|
|
return renderServerIslandRuntime();
|
|
}
|
|
case "script": {
|
|
const { id, content } = instruction;
|
|
if (result._metadata.renderedScripts.has(id)) {
|
|
return "";
|
|
}
|
|
result._metadata.renderedScripts.add(id);
|
|
return content;
|
|
}
|
|
default: {
|
|
throw new Error(`Unknown chunk type: ${chunk.type}`);
|
|
}
|
|
}
|
|
} else if (chunk instanceof Response) {
|
|
return "";
|
|
} else if (isSlotString(chunk)) {
|
|
let out = "";
|
|
const c = chunk;
|
|
if (c.instructions) {
|
|
for (const instr of c.instructions) {
|
|
out += stringifyChunk(result, instr);
|
|
}
|
|
}
|
|
out += chunk.toString();
|
|
return out;
|
|
}
|
|
return chunk.toString();
|
|
}
|
|
function chunkToString(result, chunk) {
|
|
if (ArrayBuffer.isView(chunk)) {
|
|
return decoder.decode(chunk);
|
|
} else {
|
|
return stringifyChunk(result, chunk);
|
|
}
|
|
}
|
|
function chunkToByteArray(result, chunk) {
|
|
if (ArrayBuffer.isView(chunk)) {
|
|
return chunk;
|
|
} else {
|
|
const stringified = stringifyChunk(result, chunk);
|
|
return encoder.encode(stringified.toString());
|
|
}
|
|
}
|
|
function chunkToByteArrayOrString(result, chunk) {
|
|
if (ArrayBuffer.isView(chunk)) {
|
|
return chunk;
|
|
} else {
|
|
return stringifyChunk(result, chunk).toString();
|
|
}
|
|
}
|
|
function isRenderInstance(obj) {
|
|
return !!obj && typeof obj === "object" && "render" in obj && typeof obj.render === "function";
|
|
}
|
|
|
|
function renderChild(destination, child) {
|
|
if (typeof child === "string") {
|
|
destination.write(markHTMLString(escapeHTML(child)));
|
|
return;
|
|
}
|
|
if (isPromise(child)) {
|
|
return child.then((x) => renderChild(destination, x));
|
|
}
|
|
if (child instanceof SlotString) {
|
|
destination.write(child);
|
|
return;
|
|
}
|
|
if (isHTMLString(child)) {
|
|
destination.write(child);
|
|
return;
|
|
}
|
|
if (!child && child !== 0) {
|
|
return;
|
|
}
|
|
if (Array.isArray(child)) {
|
|
return renderArray(destination, child);
|
|
}
|
|
if (typeof child === "function") {
|
|
return renderChild(destination, child());
|
|
}
|
|
if (isRenderInstance(child)) {
|
|
return child.render(destination);
|
|
}
|
|
if (isRenderTemplateResult(child)) {
|
|
return child.render(destination);
|
|
}
|
|
if (isAstroComponentInstance(child)) {
|
|
return child.render(destination);
|
|
}
|
|
if (ArrayBuffer.isView(child)) {
|
|
destination.write(child);
|
|
return;
|
|
}
|
|
if (typeof child === "object" && (Symbol.asyncIterator in child || Symbol.iterator in child)) {
|
|
if (Symbol.asyncIterator in child) {
|
|
return renderAsyncIterable(destination, child);
|
|
}
|
|
return renderIterable(destination, child);
|
|
}
|
|
destination.write(child);
|
|
}
|
|
function renderArray(destination, children) {
|
|
for (let i = 0; i < children.length; i++) {
|
|
const result = renderChild(destination, children[i]);
|
|
if (isPromise(result)) {
|
|
if (i + 1 >= children.length) {
|
|
return result;
|
|
}
|
|
const remaining = children.length - i - 1;
|
|
const flushers = new Array(remaining);
|
|
for (let j = 0; j < remaining; j++) {
|
|
flushers[j] = createBufferedRenderer(destination, (bufferDestination) => {
|
|
return renderChild(bufferDestination, children[i + 1 + j]);
|
|
});
|
|
}
|
|
return result.then(() => {
|
|
let k = 0;
|
|
const iterate = () => {
|
|
while (k < flushers.length) {
|
|
const flushResult = flushers[k++].flush();
|
|
if (isPromise(flushResult)) {
|
|
return flushResult.then(iterate);
|
|
}
|
|
}
|
|
};
|
|
return iterate();
|
|
});
|
|
}
|
|
}
|
|
}
|
|
function renderIterable(destination, children) {
|
|
const iterator = children[Symbol.iterator]();
|
|
const iterate = () => {
|
|
for (; ; ) {
|
|
const { value, done } = iterator.next();
|
|
if (done) {
|
|
break;
|
|
}
|
|
const result = renderChild(destination, value);
|
|
if (isPromise(result)) {
|
|
return result.then(iterate);
|
|
}
|
|
}
|
|
};
|
|
return iterate();
|
|
}
|
|
async function renderAsyncIterable(destination, children) {
|
|
for await (const value of children) {
|
|
await renderChild(destination, value);
|
|
}
|
|
}
|
|
|
|
const astroComponentInstanceSym = /* @__PURE__ */ Symbol.for("astro.componentInstance");
|
|
class AstroComponentInstance {
|
|
[astroComponentInstanceSym] = true;
|
|
result;
|
|
props;
|
|
slotValues;
|
|
factory;
|
|
returnValue;
|
|
constructor(result, props, slots, factory) {
|
|
this.result = result;
|
|
this.props = props;
|
|
this.factory = factory;
|
|
this.slotValues = {};
|
|
for (const name in slots) {
|
|
let didRender = false;
|
|
let value = slots[name](result);
|
|
this.slotValues[name] = () => {
|
|
if (!didRender) {
|
|
didRender = true;
|
|
return value;
|
|
}
|
|
return slots[name](result);
|
|
};
|
|
}
|
|
}
|
|
init(result) {
|
|
if (this.returnValue !== void 0) {
|
|
return this.returnValue;
|
|
}
|
|
this.returnValue = this.factory(result, this.props, this.slotValues);
|
|
if (isPromise(this.returnValue)) {
|
|
this.returnValue.then((resolved) => {
|
|
this.returnValue = resolved;
|
|
}).catch(() => {
|
|
});
|
|
}
|
|
return this.returnValue;
|
|
}
|
|
render(destination) {
|
|
const returnValue = this.init(this.result);
|
|
if (isPromise(returnValue)) {
|
|
return returnValue.then((x) => this.renderImpl(destination, x));
|
|
}
|
|
return this.renderImpl(destination, returnValue);
|
|
}
|
|
renderImpl(destination, returnValue) {
|
|
if (isHeadAndContent(returnValue)) {
|
|
return returnValue.content.render(destination);
|
|
} else {
|
|
return renderChild(destination, returnValue);
|
|
}
|
|
}
|
|
}
|
|
function validateComponentProps(props, clientDirectives, displayName) {
|
|
if (props != null) {
|
|
const directives = [...clientDirectives.keys()].map((directive) => `client:${directive}`);
|
|
for (const prop of Object.keys(props)) {
|
|
if (directives.includes(prop)) {
|
|
console.warn(
|
|
`You are attempting to render <${displayName} ${prop} />, but ${displayName} is an Astro component. Astro components do not render in the client and should not have a hydration directive. Please use a framework component for client rendering.`
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
function createAstroComponentInstance(result, displayName, factory, props, slots = {}) {
|
|
validateComponentProps(props, result.clientDirectives, displayName);
|
|
const instance = new AstroComponentInstance(result, props, slots, factory);
|
|
registerIfPropagating(result, factory, instance);
|
|
return instance;
|
|
}
|
|
function isAstroComponentInstance(obj) {
|
|
return typeof obj === "object" && obj !== null && !!obj[astroComponentInstanceSym];
|
|
}
|
|
|
|
const DOCTYPE_EXP = /<!doctype html/i;
|
|
async function renderToString(result, componentFactory, props, children, isPage = false, route) {
|
|
const templateResult = await callComponentAsTemplateResultOrResponse(
|
|
result,
|
|
componentFactory,
|
|
props,
|
|
children,
|
|
route
|
|
);
|
|
if (templateResult instanceof Response) return templateResult;
|
|
let str = "";
|
|
let renderedFirstPageChunk = false;
|
|
if (isPage) {
|
|
await bufferHeadContent(result);
|
|
}
|
|
const destination = {
|
|
write(chunk) {
|
|
if (isPage && !renderedFirstPageChunk) {
|
|
renderedFirstPageChunk = true;
|
|
if (!result.partial && !DOCTYPE_EXP.test(String(chunk))) {
|
|
const doctype = result.compressHTML ? "<!DOCTYPE html>" : "<!DOCTYPE html>\n";
|
|
str += doctype;
|
|
}
|
|
}
|
|
if (chunk instanceof Response) return;
|
|
str += chunkToString(result, chunk);
|
|
}
|
|
};
|
|
await templateResult.render(destination);
|
|
return str;
|
|
}
|
|
async function renderToReadableStream(result, componentFactory, props, children, isPage = false, route) {
|
|
const templateResult = await callComponentAsTemplateResultOrResponse(
|
|
result,
|
|
componentFactory,
|
|
props,
|
|
children,
|
|
route
|
|
);
|
|
if (templateResult instanceof Response) return templateResult;
|
|
let renderedFirstPageChunk = false;
|
|
if (isPage) {
|
|
await bufferHeadContent(result);
|
|
}
|
|
return new ReadableStream({
|
|
start(controller) {
|
|
const destination = {
|
|
write(chunk) {
|
|
if (isPage && !renderedFirstPageChunk) {
|
|
renderedFirstPageChunk = true;
|
|
if (!result.partial && !DOCTYPE_EXP.test(String(chunk))) {
|
|
const doctype = result.compressHTML ? "<!DOCTYPE html>" : "<!DOCTYPE html>\n";
|
|
controller.enqueue(encoder.encode(doctype));
|
|
}
|
|
}
|
|
if (chunk instanceof Response) {
|
|
throw new AstroError({
|
|
...ResponseSentError
|
|
});
|
|
}
|
|
const bytes = chunkToByteArray(result, chunk);
|
|
controller.enqueue(bytes);
|
|
}
|
|
};
|
|
(async () => {
|
|
try {
|
|
await templateResult.render(destination);
|
|
controller.close();
|
|
} catch (e) {
|
|
if (AstroError.is(e) && !e.loc) {
|
|
e.setLocation({
|
|
file: route?.component
|
|
});
|
|
}
|
|
setTimeout(() => controller.error(e), 0);
|
|
}
|
|
})();
|
|
},
|
|
cancel() {
|
|
result.cancelled = true;
|
|
}
|
|
});
|
|
}
|
|
async function callComponentAsTemplateResultOrResponse(result, componentFactory, props, children, route) {
|
|
const factoryResult = await componentFactory(result, props, children);
|
|
if (factoryResult instanceof Response) {
|
|
return factoryResult;
|
|
} else if (isHeadAndContent(factoryResult)) {
|
|
if (!isRenderTemplateResult(factoryResult.content)) {
|
|
throw new AstroError({
|
|
...OnlyResponseCanBeReturned,
|
|
message: OnlyResponseCanBeReturned.message(
|
|
route?.route,
|
|
typeof factoryResult
|
|
),
|
|
location: {
|
|
file: route?.component
|
|
}
|
|
});
|
|
}
|
|
return factoryResult.content;
|
|
} else if (!isRenderTemplateResult(factoryResult)) {
|
|
throw new AstroError({
|
|
...OnlyResponseCanBeReturned,
|
|
message: OnlyResponseCanBeReturned.message(route?.route, typeof factoryResult),
|
|
location: {
|
|
file: route?.component
|
|
}
|
|
});
|
|
}
|
|
return factoryResult;
|
|
}
|
|
async function bufferHeadContent(result) {
|
|
await bufferPropagatedHead(result);
|
|
}
|
|
async function renderToAsyncIterable(result, componentFactory, props, children, isPage = false, route) {
|
|
const templateResult = await callComponentAsTemplateResultOrResponse(
|
|
result,
|
|
componentFactory,
|
|
props,
|
|
children,
|
|
route
|
|
);
|
|
if (templateResult instanceof Response) return templateResult;
|
|
let renderedFirstPageChunk = false;
|
|
if (isPage) {
|
|
await bufferHeadContent(result);
|
|
}
|
|
let error = null;
|
|
let next = null;
|
|
const buffer = [];
|
|
let renderingComplete = false;
|
|
const iterator = {
|
|
async next() {
|
|
if (result.cancelled) return { done: true, value: void 0 };
|
|
if (next !== null) {
|
|
await next.promise;
|
|
} else if (!renderingComplete && !buffer.length) {
|
|
next = promiseWithResolvers();
|
|
await next.promise;
|
|
}
|
|
if (!renderingComplete) {
|
|
next = promiseWithResolvers();
|
|
}
|
|
if (error) {
|
|
throw error;
|
|
}
|
|
let length = 0;
|
|
let stringToEncode = "";
|
|
for (let i = 0, len = buffer.length; i < len; i++) {
|
|
const bufferEntry = buffer[i];
|
|
if (typeof bufferEntry === "string") {
|
|
const nextIsString = i + 1 < len && typeof buffer[i + 1] === "string";
|
|
stringToEncode += bufferEntry;
|
|
if (!nextIsString) {
|
|
const encoded = encoder.encode(stringToEncode);
|
|
length += encoded.length;
|
|
stringToEncode = "";
|
|
buffer[i] = encoded;
|
|
} else {
|
|
buffer[i] = "";
|
|
}
|
|
} else {
|
|
length += bufferEntry.length;
|
|
}
|
|
}
|
|
let mergedArray = new Uint8Array(length);
|
|
let offset = 0;
|
|
for (let i = 0, len = buffer.length; i < len; i++) {
|
|
const item = buffer[i];
|
|
if (item === "") {
|
|
continue;
|
|
}
|
|
mergedArray.set(item, offset);
|
|
offset += item.length;
|
|
}
|
|
buffer.length = 0;
|
|
const returnValue = {
|
|
// The iterator is done when rendering has finished
|
|
// and there are no more chunks to return.
|
|
done: length === 0 && renderingComplete,
|
|
value: mergedArray
|
|
};
|
|
return returnValue;
|
|
},
|
|
async return() {
|
|
result.cancelled = true;
|
|
return { done: true, value: void 0 };
|
|
}
|
|
};
|
|
const destination = {
|
|
write(chunk) {
|
|
if (isPage && !renderedFirstPageChunk) {
|
|
renderedFirstPageChunk = true;
|
|
if (!result.partial && !DOCTYPE_EXP.test(String(chunk))) {
|
|
const doctype = result.compressHTML ? "<!DOCTYPE html>" : "<!DOCTYPE html>\n";
|
|
buffer.push(encoder.encode(doctype));
|
|
}
|
|
}
|
|
if (chunk instanceof Response) {
|
|
throw new AstroError(ResponseSentError);
|
|
}
|
|
const bytes = chunkToByteArrayOrString(result, chunk);
|
|
if (bytes.length > 0) {
|
|
buffer.push(bytes);
|
|
next?.resolve();
|
|
} else if (buffer.length > 0) {
|
|
next?.resolve();
|
|
}
|
|
}
|
|
};
|
|
const renderResult = toPromise(() => templateResult.render(destination));
|
|
renderResult.catch((err) => {
|
|
error = err;
|
|
}).finally(() => {
|
|
renderingComplete = true;
|
|
next?.resolve();
|
|
});
|
|
return {
|
|
[Symbol.asyncIterator]() {
|
|
return iterator;
|
|
}
|
|
};
|
|
}
|
|
function toPromise(fn) {
|
|
try {
|
|
const result = fn();
|
|
return isPromise(result) ? result : Promise.resolve(result);
|
|
} catch (err) {
|
|
return Promise.reject(err);
|
|
}
|
|
}
|
|
|
|
function componentIsHTMLElement(Component) {
|
|
return typeof HTMLElement !== "undefined" && HTMLElement.isPrototypeOf(Component);
|
|
}
|
|
async function renderHTMLElement$1(result, constructor, props, slots) {
|
|
const name = getHTMLElementName(constructor);
|
|
let attrHTML = "";
|
|
for (const attr in props) {
|
|
attrHTML += ` ${attr}="${toAttributeString(await props[attr])}"`;
|
|
}
|
|
return markHTMLString(
|
|
`<${name}${attrHTML}>${await renderSlotToString(result, slots?.default)}</${name}>`
|
|
);
|
|
}
|
|
function getHTMLElementName(constructor) {
|
|
const definedName = customElements.getName(constructor);
|
|
if (definedName) return definedName;
|
|
const assignedName = constructor.name.replace(/^HTML|Element$/g, "").replace(/[A-Z]/g, "-$&").toLowerCase().replace(/^-/, "html-");
|
|
return assignedName;
|
|
}
|
|
|
|
const needsHeadRenderingSymbol = /* @__PURE__ */ Symbol.for("astro.needsHeadRendering");
|
|
const rendererAliases = /* @__PURE__ */ new Map([["solid", "solid-js"]]);
|
|
const clientOnlyValues = /* @__PURE__ */ new Set(["solid-js", "react", "preact", "vue", "svelte"]);
|
|
function guessRenderers(componentUrl) {
|
|
const extname = componentUrl?.split(".").pop();
|
|
switch (extname) {
|
|
case "svelte":
|
|
return ["@astrojs/svelte"];
|
|
case "vue":
|
|
return ["@astrojs/vue"];
|
|
case "jsx":
|
|
case "tsx":
|
|
return ["@astrojs/react", "@astrojs/preact", "@astrojs/solid-js", "@astrojs/vue (jsx)"];
|
|
case void 0:
|
|
default:
|
|
return [
|
|
"@astrojs/react",
|
|
"@astrojs/preact",
|
|
"@astrojs/solid-js",
|
|
"@astrojs/vue",
|
|
"@astrojs/svelte"
|
|
];
|
|
}
|
|
}
|
|
function isFragmentComponent(Component) {
|
|
return Component === Fragment;
|
|
}
|
|
function isHTMLComponent(Component) {
|
|
return Component && Component["astro:html"] === true;
|
|
}
|
|
const ASTRO_SLOT_EXP = /<\/?astro-slot\b[^>]*>/g;
|
|
const ASTRO_STATIC_SLOT_EXP = /<\/?astro-static-slot\b[^>]*>/g;
|
|
function removeStaticAstroSlot(html, supportsAstroStaticSlot = true) {
|
|
const exp = supportsAstroStaticSlot ? ASTRO_STATIC_SLOT_EXP : ASTRO_SLOT_EXP;
|
|
return html.replace(exp, "");
|
|
}
|
|
async function renderFrameworkComponent(result, displayName, Component, _props, slots = {}) {
|
|
if (!Component && "client:only" in _props === false) {
|
|
throw new Error(
|
|
`Unable to render ${displayName} because it is ${Component}!
|
|
Did you forget to import the component or is it possible there is a typo?`
|
|
);
|
|
}
|
|
const { renderers, clientDirectives } = result;
|
|
const metadata = {
|
|
astroStaticSlot: true,
|
|
displayName
|
|
};
|
|
const { hydration, isPage, props, propsWithoutTransitionAttributes } = extractDirectives(
|
|
_props,
|
|
clientDirectives
|
|
);
|
|
let html = "";
|
|
let attrs = void 0;
|
|
if (hydration) {
|
|
metadata.hydrate = hydration.directive;
|
|
metadata.hydrateArgs = hydration.value;
|
|
metadata.componentExport = hydration.componentExport;
|
|
metadata.componentUrl = hydration.componentUrl;
|
|
}
|
|
const probableRendererNames = guessRenderers(metadata.componentUrl);
|
|
const validRenderers = renderers.filter((r) => r.name !== "astro:jsx");
|
|
const { children, slotInstructions } = await renderSlots(result, slots);
|
|
let renderer;
|
|
if (metadata.hydrate !== "only") {
|
|
let isTagged = false;
|
|
try {
|
|
isTagged = Component && Component[Renderer];
|
|
} catch {
|
|
}
|
|
if (isTagged) {
|
|
const rendererName = Component[Renderer];
|
|
renderer = renderers.find(({ name }) => name === rendererName);
|
|
}
|
|
if (!renderer) {
|
|
let error;
|
|
for (const r of renderers) {
|
|
try {
|
|
if (await r.ssr.check.call({ result }, Component, props, children, metadata)) {
|
|
renderer = r;
|
|
break;
|
|
}
|
|
} catch (e) {
|
|
error ??= e;
|
|
}
|
|
}
|
|
if (!renderer && error) {
|
|
throw error;
|
|
}
|
|
}
|
|
if (!renderer && typeof HTMLElement === "function" && componentIsHTMLElement(Component)) {
|
|
const output = await renderHTMLElement$1(
|
|
result,
|
|
Component,
|
|
_props,
|
|
slots
|
|
);
|
|
return {
|
|
render(destination) {
|
|
destination.write(output);
|
|
}
|
|
};
|
|
}
|
|
} else {
|
|
if (metadata.hydrateArgs) {
|
|
const rendererName = rendererAliases.has(metadata.hydrateArgs) ? rendererAliases.get(metadata.hydrateArgs) : metadata.hydrateArgs;
|
|
if (clientOnlyValues.has(rendererName)) {
|
|
renderer = renderers.find(
|
|
({ name }) => name === `@astrojs/${rendererName}` || name === rendererName
|
|
);
|
|
}
|
|
}
|
|
if (!renderer && validRenderers.length === 1) {
|
|
renderer = validRenderers[0];
|
|
}
|
|
if (!renderer) {
|
|
const extname = metadata.componentUrl?.split(".").pop();
|
|
renderer = renderers.find(({ name }) => name === `@astrojs/${extname}` || name === extname);
|
|
}
|
|
if (!renderer && metadata.hydrateArgs) {
|
|
const rendererName = metadata.hydrateArgs;
|
|
if (typeof rendererName === "string") {
|
|
renderer = renderers.find(({ name }) => name === rendererName);
|
|
}
|
|
}
|
|
}
|
|
let componentServerRenderEndTime;
|
|
if (!renderer) {
|
|
if (metadata.hydrate === "only") {
|
|
const rendererName = rendererAliases.has(metadata.hydrateArgs) ? rendererAliases.get(metadata.hydrateArgs) : metadata.hydrateArgs;
|
|
if (clientOnlyValues.has(rendererName)) {
|
|
const plural = validRenderers.length > 1;
|
|
throw new AstroError({
|
|
...NoMatchingRenderer,
|
|
message: NoMatchingRenderer.message(
|
|
metadata.displayName,
|
|
metadata?.componentUrl?.split(".").pop(),
|
|
plural,
|
|
validRenderers.length
|
|
),
|
|
hint: NoMatchingRenderer.hint(
|
|
formatList(probableRendererNames.map((r) => "`" + r + "`"))
|
|
)
|
|
});
|
|
} else {
|
|
throw new AstroError({
|
|
...NoClientOnlyHint,
|
|
message: NoClientOnlyHint.message(metadata.displayName),
|
|
hint: NoClientOnlyHint.hint(
|
|
probableRendererNames.map((r) => r.replace("@astrojs/", "")).join("|")
|
|
)
|
|
});
|
|
}
|
|
} else if (typeof Component !== "string") {
|
|
const matchingRenderers = validRenderers.filter(
|
|
(r) => probableRendererNames.includes(r.name)
|
|
);
|
|
const plural = validRenderers.length > 1;
|
|
if (matchingRenderers.length === 0) {
|
|
throw new AstroError({
|
|
...NoMatchingRenderer,
|
|
message: NoMatchingRenderer.message(
|
|
metadata.displayName,
|
|
metadata?.componentUrl?.split(".").pop(),
|
|
plural,
|
|
validRenderers.length
|
|
),
|
|
hint: NoMatchingRenderer.hint(
|
|
formatList(probableRendererNames.map((r) => "`" + r + "`"))
|
|
)
|
|
});
|
|
} else if (matchingRenderers.length === 1) {
|
|
renderer = matchingRenderers[0];
|
|
({ html, attrs } = await renderer.ssr.renderToStaticMarkup.call(
|
|
{ result },
|
|
Component,
|
|
propsWithoutTransitionAttributes,
|
|
children,
|
|
metadata
|
|
));
|
|
} else {
|
|
throw new Error(`Unable to render ${metadata.displayName}!
|
|
|
|
This component likely uses ${formatList(probableRendererNames)},
|
|
but Astro encountered an error during server-side rendering.
|
|
|
|
Please ensure that ${metadata.displayName}:
|
|
1. Does not unconditionally access browser-specific globals like \`window\` or \`document\`.
|
|
If this is unavoidable, use the \`client:only\` hydration directive.
|
|
2. Does not conditionally return \`null\` or \`undefined\` when rendered on the server.
|
|
3. If using multiple JSX frameworks at the same time (e.g. React + Preact), pass the correct \`include\`/\`exclude\` options to integrations.
|
|
|
|
If you're still stuck, please open an issue on GitHub or join us at https://astro.build/chat.`);
|
|
}
|
|
}
|
|
} else {
|
|
if (metadata.hydrate === "only") {
|
|
html = await renderSlotToString(result, slots?.fallback);
|
|
} else {
|
|
const componentRenderStartTime = performance.now();
|
|
({ html, attrs } = await renderer.ssr.renderToStaticMarkup.call(
|
|
{ result },
|
|
Component,
|
|
propsWithoutTransitionAttributes,
|
|
children,
|
|
metadata
|
|
));
|
|
if (process.env.NODE_ENV === "development")
|
|
componentServerRenderEndTime = performance.now() - componentRenderStartTime;
|
|
}
|
|
}
|
|
if (!html && typeof Component === "string") {
|
|
const Tag = sanitizeElementName(Component);
|
|
const childSlots = Object.values(children).join("");
|
|
const renderTemplateResult = renderTemplate`<${Tag}${internalSpreadAttributes(
|
|
props,
|
|
true,
|
|
Tag
|
|
)}${markHTMLString(
|
|
childSlots === "" && voidElementNames.test(Tag) ? `/>` : `>${childSlots}</${Tag}>`
|
|
)}`;
|
|
html = "";
|
|
const destination = {
|
|
write(chunk) {
|
|
if (chunk instanceof Response) return;
|
|
html += chunkToString(result, chunk);
|
|
}
|
|
};
|
|
await renderTemplateResult.render(destination);
|
|
}
|
|
if (!hydration) {
|
|
return {
|
|
render(destination) {
|
|
if (slotInstructions) {
|
|
for (const instruction of slotInstructions) {
|
|
destination.write(instruction);
|
|
}
|
|
}
|
|
if (isPage || renderer?.name === "astro:jsx") {
|
|
destination.write(html);
|
|
} else if (html && html.length > 0) {
|
|
destination.write(
|
|
markHTMLString(removeStaticAstroSlot(html, renderer?.ssr?.supportsAstroStaticSlot))
|
|
);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
const astroId = shorthash(
|
|
`<!--${metadata.componentExport.value}:${metadata.componentUrl}-->
|
|
${html}
|
|
${serializeProps(
|
|
props,
|
|
metadata
|
|
)}`
|
|
);
|
|
const island = await generateHydrateScript(
|
|
{ renderer, result, astroId, props, attrs },
|
|
metadata
|
|
);
|
|
if (componentServerRenderEndTime && process.env.NODE_ENV === "development")
|
|
island.props["server-render-time"] = componentServerRenderEndTime;
|
|
let unrenderedSlots = [];
|
|
if (html) {
|
|
if (Object.keys(children).length > 0) {
|
|
for (const key of Object.keys(children)) {
|
|
let tagName = renderer?.ssr?.supportsAstroStaticSlot ? !!metadata.hydrate ? "astro-slot" : "astro-static-slot" : "astro-slot";
|
|
let expectedHTML = key === "default" ? `<${tagName}>` : `<${tagName} name="${key}">`;
|
|
if (!html.includes(expectedHTML)) {
|
|
unrenderedSlots.push(key);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
unrenderedSlots = Object.keys(children);
|
|
}
|
|
const template = unrenderedSlots.length > 0 ? unrenderedSlots.map(
|
|
(key) => `<template data-astro-template${key !== "default" ? `="${key}"` : ""}>${children[key]}</template>`
|
|
).join("") : "";
|
|
island.children = `${html ?? ""}${template}`;
|
|
if (island.children) {
|
|
island.props["await-children"] = "";
|
|
island.children += `<!--astro:end-->`;
|
|
}
|
|
return {
|
|
render(destination) {
|
|
if (slotInstructions) {
|
|
for (const instruction of slotInstructions) {
|
|
destination.write(instruction);
|
|
}
|
|
}
|
|
destination.write(createRenderInstruction({ type: "directive", hydration }));
|
|
if (hydration.directive !== "only" && renderer?.ssr.renderHydrationScript) {
|
|
destination.write(
|
|
createRenderInstruction({
|
|
type: "renderer-hydration-script",
|
|
rendererName: renderer.name,
|
|
render: renderer.ssr.renderHydrationScript
|
|
})
|
|
);
|
|
}
|
|
const renderedElement = renderElement$1("astro-island", island, false);
|
|
destination.write(markHTMLString(renderedElement));
|
|
}
|
|
};
|
|
}
|
|
function sanitizeElementName(tag) {
|
|
const unsafe = /[&<>'"\s]+/;
|
|
if (!unsafe.test(tag)) return tag;
|
|
return tag.trim().split(unsafe)[0].trim();
|
|
}
|
|
function renderFragmentComponent(result, slots = {}) {
|
|
const slot = slots?.default;
|
|
return {
|
|
render(destination) {
|
|
if (slot == null) return;
|
|
return renderSlot(result, slot).render(destination);
|
|
}
|
|
};
|
|
}
|
|
async function renderHTMLComponent(result, Component, _props, slots = {}) {
|
|
const { slotInstructions, children } = await renderSlots(result, slots);
|
|
const html = Component({ slots: children });
|
|
const hydrationHtml = slotInstructions ? slotInstructions.map((instr) => chunkToString(result, instr)).join("") : "";
|
|
return {
|
|
render(destination) {
|
|
destination.write(markHTMLString(hydrationHtml + html));
|
|
}
|
|
};
|
|
}
|
|
function renderAstroComponent(result, displayName, Component, props, slots = {}) {
|
|
if (containsServerDirective(props)) {
|
|
const serverIslandComponent = new ServerIslandComponent(result, props, slots, displayName);
|
|
result._metadata.propagators.add(serverIslandComponent);
|
|
return serverIslandComponent;
|
|
}
|
|
const instance = createAstroComponentInstance(result, displayName, Component, props, slots);
|
|
return {
|
|
render(destination) {
|
|
return instance.render(destination);
|
|
}
|
|
};
|
|
}
|
|
function renderComponent(result, displayName, Component, props, slots = {}) {
|
|
if (isPromise(Component)) {
|
|
return Component.catch(handleCancellation).then((x) => {
|
|
return renderComponent(result, displayName, x, props, slots);
|
|
});
|
|
}
|
|
if (isFragmentComponent(Component)) {
|
|
return renderFragmentComponent(result, slots);
|
|
}
|
|
props = normalizeProps(props);
|
|
if (isHTMLComponent(Component)) {
|
|
return renderHTMLComponent(result, Component, props, slots).catch(handleCancellation);
|
|
}
|
|
if (isAstroComponentFactory(Component)) {
|
|
return renderAstroComponent(result, displayName, Component, props, slots);
|
|
}
|
|
return renderFrameworkComponent(result, displayName, Component, props, slots).catch(
|
|
handleCancellation
|
|
);
|
|
function handleCancellation(e) {
|
|
if (result.cancelled)
|
|
return {
|
|
render() {
|
|
}
|
|
};
|
|
throw e;
|
|
}
|
|
}
|
|
function normalizeProps(props) {
|
|
if (props["class:list"] !== void 0) {
|
|
const value = props["class:list"];
|
|
delete props["class:list"];
|
|
props["class"] = clsx(props["class"], value);
|
|
if (props["class"] === "") {
|
|
delete props["class"];
|
|
}
|
|
}
|
|
return props;
|
|
}
|
|
async function renderComponentToString(result, displayName, Component, props, slots = {}, isPage = false, route) {
|
|
let str = "";
|
|
let renderedFirstPageChunk = false;
|
|
let head = "";
|
|
if (isPage && !result.partial && nonAstroPageNeedsHeadInjection(Component)) {
|
|
head += chunkToString(result, maybeRenderHead());
|
|
}
|
|
try {
|
|
const destination = {
|
|
write(chunk) {
|
|
if (isPage && !result.partial && !renderedFirstPageChunk) {
|
|
renderedFirstPageChunk = true;
|
|
if (!/<!doctype html/i.test(String(chunk))) {
|
|
const doctype = result.compressHTML ? "<!DOCTYPE html>" : "<!DOCTYPE html>\n";
|
|
str += doctype + head;
|
|
}
|
|
}
|
|
if (chunk instanceof Response) return;
|
|
str += chunkToString(result, chunk);
|
|
}
|
|
};
|
|
const renderInstance = await renderComponent(result, displayName, Component, props, slots);
|
|
if (containsServerDirective(props)) {
|
|
await bufferHeadContent(result);
|
|
}
|
|
await renderInstance.render(destination);
|
|
} catch (e) {
|
|
if (AstroError.is(e) && !e.loc) {
|
|
e.setLocation({
|
|
file: route?.component
|
|
});
|
|
}
|
|
throw e;
|
|
}
|
|
return str;
|
|
}
|
|
function nonAstroPageNeedsHeadInjection(pageComponent) {
|
|
return !!pageComponent?.[needsHeadRenderingSymbol];
|
|
}
|
|
|
|
const ClientOnlyPlaceholder$1 = "astro-client-only";
|
|
const hasTriedRenderComponentSymbol = /* @__PURE__ */ Symbol("hasTriedRenderComponent");
|
|
async function renderJSX(result, vnode) {
|
|
switch (true) {
|
|
case vnode instanceof HTMLString:
|
|
if (vnode.toString().trim() === "") {
|
|
return "";
|
|
}
|
|
return vnode;
|
|
case typeof vnode === "string":
|
|
return markHTMLString(escapeHTML(vnode));
|
|
case typeof vnode === "function":
|
|
return vnode;
|
|
case (!vnode && vnode !== 0):
|
|
return "";
|
|
case Array.isArray(vnode): {
|
|
const renderedItems = await Promise.all(vnode.map((v) => renderJSX(result, v)));
|
|
let instructions = null;
|
|
let content = "";
|
|
for (const item of renderedItems) {
|
|
if (item instanceof SlotString) {
|
|
content += item;
|
|
instructions = mergeSlotInstructions(instructions, item);
|
|
} else {
|
|
content += item;
|
|
}
|
|
}
|
|
if (instructions) {
|
|
return markHTMLString(new SlotString(content, instructions));
|
|
}
|
|
return markHTMLString(content);
|
|
}
|
|
}
|
|
return renderJSXVNode(result, vnode);
|
|
}
|
|
async function renderJSXVNode(result, vnode) {
|
|
if (isVNode(vnode)) {
|
|
switch (true) {
|
|
case !vnode.type: {
|
|
throw new Error(`Unable to render ${result.pathname} because it contains an undefined Component!
|
|
Did you forget to import the component or is it possible there is a typo?`);
|
|
}
|
|
case vnode.type === /* @__PURE__ */ Symbol.for("astro:fragment"):
|
|
return renderJSX(result, vnode.props.children);
|
|
case isAstroComponentFactory(vnode.type): {
|
|
let props = {};
|
|
let slots = {};
|
|
for (const [key, value] of Object.entries(vnode.props ?? {})) {
|
|
if (key === "children" || value && typeof value === "object" && value["$$slot"]) {
|
|
slots[key === "children" ? "default" : key] = () => renderJSX(result, value);
|
|
} else {
|
|
props[key] = value;
|
|
}
|
|
}
|
|
const str = await renderComponentToString(
|
|
result,
|
|
vnode.type.name,
|
|
vnode.type,
|
|
props,
|
|
slots
|
|
);
|
|
const html = markHTMLString(str);
|
|
return html;
|
|
}
|
|
case (!vnode.type && vnode.type !== 0):
|
|
return "";
|
|
case (typeof vnode.type === "string" && vnode.type !== ClientOnlyPlaceholder$1):
|
|
return markHTMLString(await renderElement(result, vnode.type, vnode.props ?? {}));
|
|
}
|
|
if (vnode.type) {
|
|
let extractSlots2 = function(child) {
|
|
if (Array.isArray(child)) {
|
|
return child.map((c) => extractSlots2(c));
|
|
}
|
|
if (!isVNode(child)) {
|
|
_slots.default.push(child);
|
|
return;
|
|
}
|
|
if ("slot" in child.props) {
|
|
_slots[child.props.slot] = [..._slots[child.props.slot] ?? [], child];
|
|
delete child.props.slot;
|
|
return;
|
|
}
|
|
_slots.default.push(child);
|
|
};
|
|
if (typeof vnode.type === "function" && vnode.props["server:root"]) {
|
|
const output2 = await vnode.type(vnode.props ?? {});
|
|
return await renderJSX(result, output2);
|
|
}
|
|
if (typeof vnode.type === "function") {
|
|
if (vnode.props[hasTriedRenderComponentSymbol]) {
|
|
delete vnode.props[hasTriedRenderComponentSymbol];
|
|
const output2 = await vnode.type(vnode.props ?? {});
|
|
if (output2?.[AstroJSX] || !output2) {
|
|
return await renderJSXVNode(result, output2);
|
|
} else {
|
|
return;
|
|
}
|
|
} else {
|
|
vnode.props[hasTriedRenderComponentSymbol] = true;
|
|
}
|
|
}
|
|
const { children = null, ...props } = vnode.props ?? {};
|
|
const _slots = {
|
|
default: []
|
|
};
|
|
extractSlots2(children);
|
|
for (const [key, value] of Object.entries(props)) {
|
|
if (value?.["$$slot"]) {
|
|
_slots[key] = value;
|
|
delete props[key];
|
|
}
|
|
}
|
|
const slotPromises = [];
|
|
const slots = {};
|
|
for (const [key, value] of Object.entries(_slots)) {
|
|
slotPromises.push(
|
|
renderJSX(result, value).then((output2) => {
|
|
if (output2.toString().trim().length === 0) return;
|
|
slots[key] = () => output2;
|
|
})
|
|
);
|
|
}
|
|
await Promise.all(slotPromises);
|
|
let output;
|
|
if (vnode.type === ClientOnlyPlaceholder$1 && vnode.props["client:only"]) {
|
|
output = await renderComponentToString(
|
|
result,
|
|
vnode.props["client:display-name"] ?? "",
|
|
null,
|
|
props,
|
|
slots
|
|
);
|
|
} else {
|
|
output = await renderComponentToString(
|
|
result,
|
|
typeof vnode.type === "function" ? vnode.type.name : vnode.type,
|
|
vnode.type,
|
|
props,
|
|
slots
|
|
);
|
|
}
|
|
return markHTMLString(output);
|
|
}
|
|
}
|
|
return markHTMLString(`${vnode}`);
|
|
}
|
|
async function renderElement(result, tag, { children, ...props }) {
|
|
return markHTMLString(
|
|
`<${tag}${spreadAttributes(props)}${markHTMLString(
|
|
(children == null || children === "") && voidElementNames.test(tag) ? `/>` : `>${children == null ? "" : await renderJSX(result, prerenderElementChildren$1(tag, children))}</${tag}>`
|
|
)}`
|
|
);
|
|
}
|
|
function prerenderElementChildren$1(tag, children) {
|
|
if (typeof children === "string" && (tag === "style" || tag === "script")) {
|
|
return markHTMLString(children);
|
|
} else {
|
|
return children;
|
|
}
|
|
}
|
|
|
|
const ClientOnlyPlaceholder = "astro-client-only";
|
|
function renderJSXToQueue(vnode, result, queue, pool, stack, parent, metadata) {
|
|
if (vnode instanceof HTMLString) {
|
|
const html = vnode.toString();
|
|
if (html.trim() === "") return;
|
|
const node = pool.acquire("html-string", html);
|
|
node.html = html;
|
|
queue.nodes.push(node);
|
|
return;
|
|
}
|
|
if (typeof vnode === "string") {
|
|
const node = pool.acquire("text", vnode);
|
|
node.content = vnode;
|
|
queue.nodes.push(node);
|
|
return;
|
|
}
|
|
if (typeof vnode === "number" || typeof vnode === "boolean") {
|
|
const str = String(vnode);
|
|
const node = pool.acquire("text", str);
|
|
node.content = str;
|
|
queue.nodes.push(node);
|
|
return;
|
|
}
|
|
if (vnode == null || vnode === false) {
|
|
return;
|
|
}
|
|
if (Array.isArray(vnode)) {
|
|
for (let i = vnode.length - 1; i >= 0; i = i - 1) {
|
|
stack.push({ node: vnode[i], parent, metadata });
|
|
}
|
|
return;
|
|
}
|
|
if (!isVNode(vnode)) {
|
|
const str = String(vnode);
|
|
const node = pool.acquire("text", str);
|
|
node.content = str;
|
|
queue.nodes.push(node);
|
|
return;
|
|
}
|
|
handleVNode(vnode, result, queue, pool, stack, parent, metadata);
|
|
}
|
|
function handleVNode(vnode, result, queue, pool, stack, parent, metadata) {
|
|
if (!vnode.type) {
|
|
throw new Error(
|
|
`Unable to render ${result.pathname} because it contains an undefined Component!
|
|
Did you forget to import the component or is it possible there is a typo?`
|
|
);
|
|
}
|
|
if (vnode.type === /* @__PURE__ */ Symbol.for("astro:fragment")) {
|
|
stack.push({ node: vnode.props?.children, parent, metadata });
|
|
return;
|
|
}
|
|
if (isAstroComponentFactory(vnode.type)) {
|
|
const factory = vnode.type;
|
|
let props = {};
|
|
let slots = {};
|
|
for (const [key, value] of Object.entries(vnode.props ?? {})) {
|
|
if (key === "children" || value && typeof value === "object" && value["$$slot"]) {
|
|
slots[key === "children" ? "default" : key] = () => renderJSX(result, value);
|
|
} else {
|
|
props[key] = value;
|
|
}
|
|
}
|
|
const displayName = metadata?.displayName || factory.name || "Anonymous";
|
|
const instance = createAstroComponentInstance(result, displayName, factory, props, slots);
|
|
const queueNode = pool.acquire("component");
|
|
queueNode.instance = instance;
|
|
queue.nodes.push(queueNode);
|
|
return;
|
|
}
|
|
if (typeof vnode.type === "string" && vnode.type !== ClientOnlyPlaceholder) {
|
|
renderHTMLElement(vnode, result, queue, pool, stack, parent, metadata);
|
|
return;
|
|
}
|
|
if (typeof vnode.type === "function") {
|
|
if (vnode.props?.["server:root"]) {
|
|
const output3 = vnode.type(vnode.props ?? {});
|
|
stack.push({ node: output3, parent, metadata });
|
|
return;
|
|
}
|
|
const output2 = vnode.type(vnode.props ?? {});
|
|
stack.push({ node: output2, parent, metadata });
|
|
return;
|
|
}
|
|
const output = renderJSX(result, vnode);
|
|
stack.push({ node: output, parent, metadata });
|
|
}
|
|
function renderHTMLElement(vnode, _result, queue, pool, stack, parent, metadata) {
|
|
const tag = vnode.type;
|
|
const { children, ...props } = vnode.props ?? {};
|
|
const attrs = spreadAttributes(props);
|
|
const isVoidElement = (children == null || children === "") && voidElementNames.test(tag);
|
|
if (isVoidElement) {
|
|
const html = `<${tag}${attrs}/>`;
|
|
const node = pool.acquire("html-string", html);
|
|
node.html = html;
|
|
queue.nodes.push(node);
|
|
return;
|
|
}
|
|
const openTag = `<${tag}${attrs}>`;
|
|
const openTagHtml = queue.htmlStringCache ? queue.htmlStringCache.getOrCreate(openTag) : markHTMLString(openTag);
|
|
stack.push({ node: openTagHtml, parent, metadata });
|
|
if (children != null && children !== "") {
|
|
const processedChildren = prerenderElementChildren(tag, children, queue.htmlStringCache);
|
|
stack.push({ node: processedChildren, parent, metadata });
|
|
}
|
|
const closeTag = `</${tag}>`;
|
|
const closeTagHtml = queue.htmlStringCache ? queue.htmlStringCache.getOrCreate(closeTag) : markHTMLString(closeTag);
|
|
stack.push({ node: closeTagHtml, parent, metadata });
|
|
}
|
|
function prerenderElementChildren(tag, children, htmlStringCache) {
|
|
if (typeof children === "string" && (tag === "style" || tag === "script")) {
|
|
return htmlStringCache ? htmlStringCache.getOrCreate(children) : markHTMLString(children);
|
|
}
|
|
return children;
|
|
}
|
|
|
|
async function buildRenderQueue(root, result, pool) {
|
|
const queue = {
|
|
nodes: [],
|
|
result,
|
|
pool,
|
|
htmlStringCache: result._experimentalQueuedRendering?.htmlStringCache
|
|
};
|
|
const stack = [{ node: root, parent: null }];
|
|
while (stack.length > 0) {
|
|
const item = stack.pop();
|
|
if (!item) {
|
|
continue;
|
|
}
|
|
let { node, parent } = item;
|
|
if (isPromise(node)) {
|
|
try {
|
|
const resolved = await node;
|
|
stack.push({ node: resolved, parent, metadata: item.metadata });
|
|
} catch (error) {
|
|
throw error;
|
|
}
|
|
continue;
|
|
}
|
|
if (node == null || node === false) {
|
|
continue;
|
|
}
|
|
if (typeof node === "string") {
|
|
const queueNode = pool.acquire("text", node);
|
|
queueNode.content = node;
|
|
queue.nodes.push(queueNode);
|
|
continue;
|
|
}
|
|
if (typeof node === "number" || typeof node === "boolean") {
|
|
const str = String(node);
|
|
const queueNode = pool.acquire("text", str);
|
|
queueNode.content = str;
|
|
queue.nodes.push(queueNode);
|
|
continue;
|
|
}
|
|
if (isHTMLString(node)) {
|
|
const html = node.toString();
|
|
const queueNode = pool.acquire("html-string", html);
|
|
queueNode.html = html;
|
|
queue.nodes.push(queueNode);
|
|
continue;
|
|
}
|
|
if (node instanceof SlotString) {
|
|
const html = node.toString();
|
|
const queueNode = pool.acquire("html-string", html);
|
|
queueNode.html = html;
|
|
queue.nodes.push(queueNode);
|
|
continue;
|
|
}
|
|
if (isVNode(node)) {
|
|
renderJSXToQueue(node, result, queue, pool, stack, parent, item.metadata);
|
|
continue;
|
|
}
|
|
if (Array.isArray(node)) {
|
|
for (const n of node) {
|
|
stack.push({ node: n, parent, metadata: item.metadata });
|
|
}
|
|
continue;
|
|
}
|
|
if (isRenderInstruction(node)) {
|
|
const queueNode = pool.acquire("instruction");
|
|
queueNode.instruction = node;
|
|
queue.nodes.push(queueNode);
|
|
continue;
|
|
}
|
|
if (isRenderTemplateResult(node)) {
|
|
const htmlParts = node["htmlParts"];
|
|
const expressions = node["expressions"];
|
|
if (htmlParts[0]) {
|
|
const htmlString = queue.htmlStringCache ? queue.htmlStringCache.getOrCreate(htmlParts[0]) : markHTMLString(htmlParts[0]);
|
|
stack.push({
|
|
node: htmlString,
|
|
parent,
|
|
metadata: item.metadata
|
|
});
|
|
}
|
|
for (let i = 0; i < expressions.length; i = i + 1) {
|
|
stack.push({ node: expressions[i], parent, metadata: item.metadata });
|
|
if (htmlParts[i + 1]) {
|
|
const htmlString = queue.htmlStringCache ? queue.htmlStringCache.getOrCreate(htmlParts[i + 1]) : markHTMLString(htmlParts[i + 1]);
|
|
stack.push({
|
|
node: htmlString,
|
|
parent,
|
|
metadata: item.metadata
|
|
});
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
if (isAstroComponentInstance(node)) {
|
|
const queueNode = pool.acquire("component");
|
|
queueNode.instance = node;
|
|
queue.nodes.push(queueNode);
|
|
continue;
|
|
}
|
|
if (isAstroComponentFactory(node)) {
|
|
const factory = node;
|
|
const props = item.metadata?.props || {};
|
|
const slots = item.metadata?.slots || {};
|
|
const displayName = item.metadata?.displayName || factory.name || "Anonymous";
|
|
const instance = createAstroComponentInstance(result, displayName, factory, props, slots);
|
|
const queueNode = pool.acquire("component");
|
|
queueNode.instance = instance;
|
|
if (isAPropagatingComponent(result, factory)) {
|
|
try {
|
|
const returnValue = await instance.init(result);
|
|
if (isHeadAndContent(returnValue) && returnValue.head) {
|
|
result._metadata.extraHead.push(returnValue.head);
|
|
}
|
|
} catch (error) {
|
|
throw error;
|
|
}
|
|
}
|
|
queue.nodes.push(queueNode);
|
|
continue;
|
|
}
|
|
if (isRenderInstance(node)) {
|
|
const queueNode = pool.acquire("component");
|
|
queueNode.instance = node;
|
|
queue.nodes.push(queueNode);
|
|
continue;
|
|
}
|
|
if (typeof node === "object" && Symbol.iterator in node) {
|
|
const items = Array.from(node);
|
|
for (const iterItem of items) {
|
|
stack.push({ node: iterItem, parent, metadata: item.metadata });
|
|
}
|
|
continue;
|
|
}
|
|
if (typeof node === "object" && Symbol.asyncIterator in node) {
|
|
try {
|
|
const items = [];
|
|
for await (const asyncItem of node) {
|
|
items.push(asyncItem);
|
|
}
|
|
for (const iterItem of items) {
|
|
stack.push({ node: iterItem, parent, metadata: item.metadata });
|
|
}
|
|
} catch (error) {
|
|
throw error;
|
|
}
|
|
continue;
|
|
}
|
|
if (node instanceof Response) {
|
|
const queueNode = pool.acquire("html-string", "");
|
|
queueNode.html = "";
|
|
queue.nodes.push(queueNode);
|
|
continue;
|
|
}
|
|
if (isHTMLString(node)) {
|
|
const html = String(node);
|
|
const queueNode = pool.acquire("html-string", html);
|
|
queueNode.html = html;
|
|
queue.nodes.push(queueNode);
|
|
} else {
|
|
const str = String(node);
|
|
const queueNode = pool.acquire("text", str);
|
|
queueNode.content = str;
|
|
queue.nodes.push(queueNode);
|
|
}
|
|
}
|
|
queue.nodes.reverse();
|
|
return queue;
|
|
}
|
|
|
|
async function renderQueue(queue, destination) {
|
|
const result = queue.result;
|
|
const pool = queue.pool;
|
|
const cache = queue.htmlStringCache;
|
|
let batchBuffer = "";
|
|
let i = 0;
|
|
while (i < queue.nodes.length) {
|
|
const node = queue.nodes[i];
|
|
try {
|
|
if (canBatch(node)) {
|
|
const batchStart = i;
|
|
while (i < queue.nodes.length && canBatch(queue.nodes[i])) {
|
|
batchBuffer += renderNodeToString(queue.nodes[i]);
|
|
i = i + 1;
|
|
}
|
|
if (batchBuffer) {
|
|
const htmlString = cache ? cache.getOrCreate(batchBuffer) : markHTMLString(batchBuffer);
|
|
destination.write(htmlString);
|
|
batchBuffer = "";
|
|
}
|
|
if (pool) {
|
|
for (let j = batchStart; j < i; j++) {
|
|
pool.release(queue.nodes[j]);
|
|
}
|
|
}
|
|
} else {
|
|
await renderNode(node, destination, result);
|
|
if (pool) {
|
|
pool.release(node);
|
|
}
|
|
i = i + 1;
|
|
}
|
|
} catch (error) {
|
|
throw error;
|
|
}
|
|
}
|
|
if (batchBuffer) {
|
|
const htmlString = cache ? cache.getOrCreate(batchBuffer) : markHTMLString(batchBuffer);
|
|
destination.write(htmlString);
|
|
}
|
|
}
|
|
function canBatch(node) {
|
|
return node.type === "text" || node.type === "html-string";
|
|
}
|
|
function renderNodeToString(node) {
|
|
switch (node.type) {
|
|
case "text":
|
|
return node.content ? escapeHTML(node.content) : "";
|
|
case "html-string":
|
|
return node.html || "";
|
|
case "component":
|
|
case "instruction": {
|
|
return "";
|
|
}
|
|
}
|
|
}
|
|
async function renderNode(node, destination, result) {
|
|
const cache = result._experimentalQueuedRendering?.htmlStringCache;
|
|
switch (node.type) {
|
|
case "text": {
|
|
if (node.content) {
|
|
const escaped = escapeHTML(node.content);
|
|
const htmlString = cache ? cache.getOrCreate(escaped) : markHTMLString(escaped);
|
|
destination.write(htmlString);
|
|
}
|
|
break;
|
|
}
|
|
case "html-string": {
|
|
if (node.html) {
|
|
const htmlString = cache ? cache.getOrCreate(node.html) : markHTMLString(node.html);
|
|
destination.write(htmlString);
|
|
}
|
|
break;
|
|
}
|
|
case "instruction": {
|
|
if (node.instruction) {
|
|
destination.write(node.instruction);
|
|
}
|
|
break;
|
|
}
|
|
case "component": {
|
|
if (node.instance) {
|
|
let componentHtml = "";
|
|
const componentDestination = {
|
|
write(chunk) {
|
|
if (chunk instanceof Response) return;
|
|
componentHtml += chunkToString(result, chunk);
|
|
}
|
|
};
|
|
await node.instance.render(componentDestination);
|
|
if (componentHtml) {
|
|
destination.write(componentHtml);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
async function renderPage(result, componentFactory, props, children, streaming, route) {
|
|
if (!isAstroComponentFactory(componentFactory)) {
|
|
result._metadata.headInTree = result.componentMetadata.get(componentFactory.moduleId)?.containsHead ?? false;
|
|
const pageProps = { ...props ?? {}, "server:root": true };
|
|
let str;
|
|
if (result._experimentalQueuedRendering && result._experimentalQueuedRendering.enabled) {
|
|
let vnode = await componentFactory(pageProps);
|
|
if (componentFactory["astro:html"] && typeof vnode === "string") {
|
|
vnode = markHTMLString(vnode);
|
|
}
|
|
const queue = await buildRenderQueue(
|
|
vnode,
|
|
result,
|
|
result._experimentalQueuedRendering.pool
|
|
);
|
|
let html = "";
|
|
let renderedFirst = false;
|
|
const destination = {
|
|
write(chunk) {
|
|
if (chunk instanceof Response) return;
|
|
if (!renderedFirst && !result.partial) {
|
|
renderedFirst = true;
|
|
const chunkStr = String(chunk);
|
|
if (!/<!doctype html/i.test(chunkStr)) {
|
|
const doctype = result.compressHTML ? "<!DOCTYPE html>" : "<!DOCTYPE html>\n";
|
|
html += doctype;
|
|
}
|
|
}
|
|
html += chunkToString(result, chunk);
|
|
}
|
|
};
|
|
await renderQueue(queue, destination);
|
|
str = html;
|
|
} else {
|
|
str = await renderComponentToString(
|
|
result,
|
|
componentFactory.name,
|
|
componentFactory,
|
|
pageProps,
|
|
{},
|
|
true,
|
|
route
|
|
);
|
|
}
|
|
const bytes = encoder.encode(str);
|
|
const headers2 = new Headers([
|
|
["Content-Type", "text/html"],
|
|
["Content-Length", bytes.byteLength.toString()]
|
|
]);
|
|
if (result.shouldInjectCspMetaTags && (result.cspDestination === "header" || result.cspDestination === "adapter")) {
|
|
headers2.set("content-security-policy", renderCspContent(result));
|
|
}
|
|
return new Response(bytes, {
|
|
headers: headers2,
|
|
status: result.response.status
|
|
});
|
|
}
|
|
result._metadata.headInTree = result.componentMetadata.get(componentFactory.moduleId)?.containsHead ?? false;
|
|
let body;
|
|
if (streaming) {
|
|
if (isNode && !isDeno) {
|
|
const nodeBody = await renderToAsyncIterable(
|
|
result,
|
|
componentFactory,
|
|
props,
|
|
children,
|
|
true,
|
|
route
|
|
);
|
|
body = nodeBody;
|
|
} else {
|
|
body = await renderToReadableStream(result, componentFactory, props, children, true, route);
|
|
}
|
|
} else {
|
|
body = await renderToString(result, componentFactory, props, children, true, route);
|
|
}
|
|
if (body instanceof Response) return body;
|
|
const init = result.response;
|
|
const headers = new Headers(init.headers);
|
|
if (result.shouldInjectCspMetaTags && result.cspDestination === "header" || result.cspDestination === "adapter") {
|
|
headers.set("content-security-policy", renderCspContent(result));
|
|
}
|
|
if (!streaming && typeof body === "string") {
|
|
body = encoder.encode(body);
|
|
headers.set("Content-Length", body.byteLength.toString());
|
|
}
|
|
let status = init.status;
|
|
let statusText = init.statusText;
|
|
if (route?.route === "/404") {
|
|
status = 404;
|
|
if (statusText === "OK") {
|
|
statusText = "Not Found";
|
|
}
|
|
} else if (route?.route === "/500") {
|
|
status = 500;
|
|
if (statusText === "OK") {
|
|
statusText = "Internal Server Error";
|
|
}
|
|
}
|
|
if (status) {
|
|
return new Response(body, { ...init, headers, status, statusText });
|
|
} else {
|
|
return new Response(body, { ...init, headers });
|
|
}
|
|
}
|
|
|
|
"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_".split("").reduce((v, c) => (v[c.charCodeAt(0)] = c, v), []);
|
|
"-0123456789_".split("").reduce((v, c) => (v[c.charCodeAt(0)] = c, v), []);
|
|
|
|
function spreadAttributes(values = {}, _name, { class: scopedClassName } = {}) {
|
|
let output = "";
|
|
if (scopedClassName) {
|
|
if (typeof values.class !== "undefined") {
|
|
values.class += ` ${scopedClassName}`;
|
|
} else if (typeof values["class:list"] !== "undefined") {
|
|
values["class:list"] = [values["class:list"], scopedClassName];
|
|
} else {
|
|
values.class = scopedClassName;
|
|
}
|
|
}
|
|
for (const [key, value] of Object.entries(values)) {
|
|
output += addAttribute(value, key, true, _name);
|
|
}
|
|
return markHTMLString(output);
|
|
}
|
|
|
|
function deduplicateDirectiveValues(existingDirective, newDirective) {
|
|
const [directiveName, ...existingValues] = existingDirective.split(/\s+/).filter(Boolean);
|
|
const [newDirectiveName, ...newValues] = newDirective.split(/\s+/).filter(Boolean);
|
|
if (directiveName !== newDirectiveName) {
|
|
return void 0;
|
|
}
|
|
const finalDirectives = Array.from(/* @__PURE__ */ new Set([...existingValues, ...newValues]));
|
|
return `${directiveName} ${finalDirectives.join(" ")}`;
|
|
}
|
|
function pushDirective(directives, newDirective) {
|
|
let deduplicated = false;
|
|
if (directives.length === 0) {
|
|
return [newDirective];
|
|
}
|
|
const finalDirectives = [];
|
|
for (const directive of directives) {
|
|
if (deduplicated) {
|
|
finalDirectives.push(directive);
|
|
continue;
|
|
}
|
|
const result = deduplicateDirectiveValues(directive, newDirective);
|
|
if (result) {
|
|
finalDirectives.push(result);
|
|
deduplicated = true;
|
|
} else {
|
|
finalDirectives.push(directive);
|
|
finalDirectives.push(newDirective);
|
|
}
|
|
}
|
|
return finalDirectives;
|
|
}
|
|
|
|
async function callMiddleware(onRequest, apiContext, responseFunction) {
|
|
let nextCalled = false;
|
|
let responseFunctionPromise = void 0;
|
|
const next = async (payload) => {
|
|
nextCalled = true;
|
|
responseFunctionPromise = responseFunction(apiContext, payload);
|
|
return responseFunctionPromise;
|
|
};
|
|
const middlewarePromise = onRequest(apiContext, next);
|
|
return await Promise.resolve(middlewarePromise).then(async (value) => {
|
|
if (nextCalled) {
|
|
if (typeof value !== "undefined") {
|
|
if (value instanceof Response === false) {
|
|
throw new AstroError(MiddlewareNotAResponse);
|
|
}
|
|
return value;
|
|
} else {
|
|
if (responseFunctionPromise) {
|
|
return responseFunctionPromise;
|
|
} else {
|
|
throw new AstroError(MiddlewareNotAResponse);
|
|
}
|
|
}
|
|
} else if (typeof value === "undefined") {
|
|
throw new AstroError(MiddlewareNoDataOrNextCalled);
|
|
} else if (value instanceof Response === false) {
|
|
throw new AstroError(MiddlewareNotAResponse);
|
|
} else {
|
|
return value;
|
|
}
|
|
});
|
|
}
|
|
|
|
const EMPTY_OPTIONS = Object.freeze({ tags: [] });
|
|
class NoopAstroCache {
|
|
enabled = false;
|
|
set() {
|
|
}
|
|
get tags() {
|
|
return [];
|
|
}
|
|
get options() {
|
|
return EMPTY_OPTIONS;
|
|
}
|
|
async invalidate() {
|
|
}
|
|
}
|
|
let hasWarned = false;
|
|
class DisabledAstroCache {
|
|
enabled = false;
|
|
#logger;
|
|
constructor(logger) {
|
|
this.#logger = logger;
|
|
}
|
|
#warn() {
|
|
if (!hasWarned) {
|
|
hasWarned = true;
|
|
this.#logger?.warn(
|
|
"cache",
|
|
"`cache.set()` was called but caching is not enabled. Configure a cache provider in your Astro config under `experimental.cache` to enable caching."
|
|
);
|
|
}
|
|
}
|
|
set() {
|
|
this.#warn();
|
|
}
|
|
get tags() {
|
|
return [];
|
|
}
|
|
get options() {
|
|
return EMPTY_OPTIONS;
|
|
}
|
|
async invalidate() {
|
|
throw new AstroError(CacheNotEnabled);
|
|
}
|
|
}
|
|
|
|
function createRequest({
|
|
url,
|
|
headers,
|
|
method = "GET",
|
|
body = void 0,
|
|
logger,
|
|
isPrerendered = false,
|
|
routePattern,
|
|
init
|
|
}) {
|
|
const headersObj = isPrerendered ? void 0 : headers instanceof Headers ? headers : new Headers(
|
|
// Filter out HTTP/2 pseudo-headers. These are internally-generated headers added to all HTTP/2 requests with trusted metadata about the request.
|
|
// Examples include `:method`, `:scheme`, `:authority`, and `:path`.
|
|
// They are always prefixed with a colon to distinguish them from other headers, and it is an error to add the to a Headers object manually.
|
|
// See https://httpwg.org/specs/rfc7540.html#HttpRequest
|
|
Object.entries(headers).filter(([name]) => !name.startsWith(":"))
|
|
);
|
|
if (typeof url === "string") url = new URL(url);
|
|
if (isPrerendered) {
|
|
url.search = "";
|
|
}
|
|
const request = new Request(url, {
|
|
method,
|
|
headers: headersObj,
|
|
// body is made available only if the request is for a page that will be on-demand rendered
|
|
body: isPrerendered ? null : body,
|
|
...init
|
|
});
|
|
if (isPrerendered) {
|
|
let _headers = request.headers;
|
|
const { value, writable, ...headersDesc } = Object.getOwnPropertyDescriptor(request, "headers") || {};
|
|
Object.defineProperty(request, "headers", {
|
|
...headersDesc,
|
|
get() {
|
|
logger.warn(
|
|
null,
|
|
`\`Astro.request.headers\` was used when rendering the route \`${routePattern}'\`. \`Astro.request.headers\` is not available on prerendered pages. If you need access to request headers, make sure that the page is server-rendered using \`export const prerender = false;\` or by setting \`output\` to \`"server"\` in your Astro config to make all your pages server-rendered by default.`
|
|
);
|
|
return _headers;
|
|
},
|
|
set(newHeaders) {
|
|
_headers = newHeaders;
|
|
}
|
|
});
|
|
}
|
|
return request;
|
|
}
|
|
|
|
function template({
|
|
title,
|
|
pathname,
|
|
statusCode = 404,
|
|
tabTitle,
|
|
body
|
|
}) {
|
|
return `<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>${tabTitle}</title>
|
|
<style>
|
|
:root {
|
|
--gray-10: hsl(258, 7%, 10%);
|
|
--gray-20: hsl(258, 7%, 20%);
|
|
--gray-30: hsl(258, 7%, 30%);
|
|
--gray-40: hsl(258, 7%, 40%);
|
|
--gray-50: hsl(258, 7%, 50%);
|
|
--gray-60: hsl(258, 7%, 60%);
|
|
--gray-70: hsl(258, 7%, 70%);
|
|
--gray-80: hsl(258, 7%, 80%);
|
|
--gray-90: hsl(258, 7%, 90%);
|
|
--black: #13151A;
|
|
--accent-light: #E0CCFA;
|
|
}
|
|
|
|
* {
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
html {
|
|
background: var(--black);
|
|
color-scheme: dark;
|
|
accent-color: var(--accent-light);
|
|
}
|
|
|
|
body {
|
|
background-color: var(--gray-10);
|
|
color: var(--gray-80);
|
|
font-family: ui-monospace, Menlo, Monaco, "Cascadia Mono", "Segoe UI Mono", "Roboto Mono", "Oxygen Mono", "Ubuntu Monospace", "Source Code Pro", "Fira Mono", "Droid Sans Mono", "Courier New", monospace;
|
|
line-height: 1.5;
|
|
margin: 0;
|
|
}
|
|
|
|
a {
|
|
color: var(--accent-light);
|
|
}
|
|
|
|
.center {
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
align-items: center;
|
|
height: 100vh;
|
|
width: 100vw;
|
|
}
|
|
|
|
h1 {
|
|
margin-bottom: 8px;
|
|
color: white;
|
|
font-family: system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
|
font-weight: 700;
|
|
margin-top: 1rem;
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.statusCode {
|
|
color: var(--accent-light);
|
|
}
|
|
|
|
.astro-icon {
|
|
height: 124px;
|
|
width: 124px;
|
|
}
|
|
|
|
pre, code {
|
|
padding: 2px 8px;
|
|
background: rgba(0,0,0, 0.25);
|
|
border: 1px solid rgba(255,255,255, 0.25);
|
|
border-radius: 4px;
|
|
font-size: 1.2em;
|
|
margin-top: 0;
|
|
max-width: 60em;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<main class="center">
|
|
<svg class="astro-icon" xmlns="http://www.w3.org/2000/svg" width="64" height="80" viewBox="0 0 64 80" fill="none"> <path d="M20.5253 67.6322C16.9291 64.3531 15.8793 57.4632 17.3776 52.4717C19.9755 55.6188 23.575 56.6157 27.3035 57.1784C33.0594 58.0468 38.7122 57.722 44.0592 55.0977C44.6709 54.7972 45.2362 54.3978 45.9045 53.9931C46.4062 55.4451 46.5368 56.9109 46.3616 58.4028C45.9355 62.0362 44.1228 64.8429 41.2397 66.9705C40.0868 67.8215 38.8669 68.5822 37.6762 69.3846C34.0181 71.8508 33.0285 74.7426 34.403 78.9491C34.4357 79.0516 34.4649 79.1541 34.5388 79.4042C32.6711 78.5705 31.3069 77.3565 30.2674 75.7604C29.1694 74.0757 28.6471 72.2121 28.6196 70.1957C28.6059 69.2144 28.6059 68.2244 28.4736 67.257C28.1506 64.8985 27.0406 63.8425 24.9496 63.7817C22.8036 63.7192 21.106 65.0426 20.6559 67.1268C20.6215 67.2865 20.5717 67.4446 20.5218 67.6304L20.5253 67.6322Z" fill="white"/> <path d="M20.5253 67.6322C16.9291 64.3531 15.8793 57.4632 17.3776 52.4717C19.9755 55.6188 23.575 56.6157 27.3035 57.1784C33.0594 58.0468 38.7122 57.722 44.0592 55.0977C44.6709 54.7972 45.2362 54.3978 45.9045 53.9931C46.4062 55.4451 46.5368 56.9109 46.3616 58.4028C45.9355 62.0362 44.1228 64.8429 41.2397 66.9705C40.0868 67.8215 38.8669 68.5822 37.6762 69.3846C34.0181 71.8508 33.0285 74.7426 34.403 78.9491C34.4357 79.0516 34.4649 79.1541 34.5388 79.4042C32.6711 78.5705 31.3069 77.3565 30.2674 75.7604C29.1694 74.0757 28.6471 72.2121 28.6196 70.1957C28.6059 69.2144 28.6059 68.2244 28.4736 67.257C28.1506 64.8985 27.0406 63.8425 24.9496 63.7817C22.8036 63.7192 21.106 65.0426 20.6559 67.1268C20.6215 67.2865 20.5717 67.4446 20.5218 67.6304L20.5253 67.6322Z" fill="url(#paint0_linear_738_686)"/> <path d="M0 51.6401C0 51.6401 10.6488 46.4654 21.3274 46.4654L29.3786 21.6102C29.6801 20.4082 30.5602 19.5913 31.5538 19.5913C32.5474 19.5913 33.4275 20.4082 33.7289 21.6102L41.7802 46.4654C54.4274 46.4654 63.1076 51.6401 63.1076 51.6401C63.1076 51.6401 45.0197 2.48776 44.9843 2.38914C44.4652 0.935933 43.5888 0 42.4073 0H20.7022C19.5206 0 18.6796 0.935933 18.1251 2.38914C18.086 2.4859 0 51.6401 0 51.6401Z" fill="white"/> <defs> <linearGradient id="paint0_linear_738_686" x1="31.554" y1="75.4423" x2="39.7462" y2="48.376" gradientUnits="userSpaceOnUse"> <stop stop-color="#D83333"/> <stop offset="1" stop-color="#F041FF"/> </linearGradient> </defs> </svg>
|
|
<h1>${statusCode ? `<span class="statusCode">${statusCode}: </span> ` : ""}<span class="statusMessage">${title}</span></h1>
|
|
${body || `
|
|
<pre>Path: ${escape(pathname)}</pre>
|
|
`}
|
|
</main>
|
|
</body>
|
|
</html>`;
|
|
}
|
|
|
|
const DEFAULT_404_ROUTE = {
|
|
component: DEFAULT_404_COMPONENT,
|
|
params: [],
|
|
pattern: /^\/404\/?$/,
|
|
prerender: false,
|
|
pathname: "/404",
|
|
segments: [[{ content: "404", dynamic: false, spread: false }]],
|
|
type: "page",
|
|
route: "/404",
|
|
fallbackRoutes: [],
|
|
isIndex: false,
|
|
origin: "internal",
|
|
distURL: []
|
|
};
|
|
async function default404Page({ pathname }) {
|
|
return new Response(
|
|
template({
|
|
statusCode: 404,
|
|
title: "Not found",
|
|
tabTitle: "404: Not Found",
|
|
pathname
|
|
}),
|
|
{ status: 404, headers: { "Content-Type": "text/html" } }
|
|
);
|
|
}
|
|
default404Page.isAstroComponentFactory = true;
|
|
const default404Instance = {
|
|
default: default404Page
|
|
};
|
|
|
|
const ROUTE404_RE = /^\/404\/?$/;
|
|
const ROUTE500_RE = /^\/500\/?$/;
|
|
function isRoute404(route) {
|
|
return ROUTE404_RE.test(route);
|
|
}
|
|
function isRoute500(route) {
|
|
return ROUTE500_RE.test(route);
|
|
}
|
|
|
|
function findRouteToRewrite({
|
|
payload,
|
|
routes,
|
|
request,
|
|
trailingSlash,
|
|
buildFormat,
|
|
base,
|
|
outDir
|
|
}) {
|
|
let newUrl = void 0;
|
|
if (payload instanceof URL) {
|
|
newUrl = payload;
|
|
} else if (payload instanceof Request) {
|
|
newUrl = new URL(payload.url);
|
|
} else {
|
|
newUrl = new URL(collapseDuplicateSlashes(payload), new URL(request.url).origin);
|
|
}
|
|
const { pathname, resolvedUrlPathname } = normalizeRewritePathname(
|
|
newUrl.pathname,
|
|
base,
|
|
trailingSlash,
|
|
buildFormat
|
|
);
|
|
newUrl.pathname = resolvedUrlPathname;
|
|
const decodedPathname = decodeURI(pathname);
|
|
if (isRoute404(decodedPathname)) {
|
|
const errorRoute = routes.find((route) => route.route === "/404");
|
|
if (errorRoute) {
|
|
return { routeData: errorRoute, newUrl, pathname: decodedPathname };
|
|
}
|
|
}
|
|
if (isRoute500(decodedPathname)) {
|
|
const errorRoute = routes.find((route) => route.route === "/500");
|
|
if (errorRoute) {
|
|
return { routeData: errorRoute, newUrl, pathname: decodedPathname };
|
|
}
|
|
}
|
|
let foundRoute;
|
|
for (const route of routes) {
|
|
if (route.pattern.test(decodedPathname)) {
|
|
if (route.params && route.params.length !== 0 && route.distURL && route.distURL.length !== 0) {
|
|
if (!route.distURL.find(
|
|
(url) => url.href.replace(outDir.toString(), "").replace(/(?:\/index\.html|\.html)$/, "") === trimSlashes(pathname)
|
|
)) {
|
|
continue;
|
|
}
|
|
}
|
|
foundRoute = route;
|
|
break;
|
|
}
|
|
}
|
|
if (foundRoute) {
|
|
return {
|
|
routeData: foundRoute,
|
|
newUrl,
|
|
pathname: decodedPathname
|
|
};
|
|
} else {
|
|
const custom404 = routes.find((route) => route.route === "/404");
|
|
if (custom404) {
|
|
return { routeData: custom404, newUrl, pathname };
|
|
} else {
|
|
return { routeData: DEFAULT_404_ROUTE, newUrl, pathname };
|
|
}
|
|
}
|
|
}
|
|
function copyRequest(newUrl, oldRequest, isPrerendered, logger, routePattern) {
|
|
if (oldRequest.bodyUsed) {
|
|
throw new AstroError(RewriteWithBodyUsed);
|
|
}
|
|
return createRequest({
|
|
url: newUrl,
|
|
method: oldRequest.method,
|
|
body: oldRequest.body,
|
|
isPrerendered,
|
|
logger,
|
|
headers: isPrerendered ? {} : oldRequest.headers,
|
|
routePattern,
|
|
init: {
|
|
referrer: oldRequest.referrer,
|
|
referrerPolicy: oldRequest.referrerPolicy,
|
|
mode: oldRequest.mode,
|
|
credentials: oldRequest.credentials,
|
|
cache: oldRequest.cache,
|
|
redirect: oldRequest.redirect,
|
|
integrity: oldRequest.integrity,
|
|
signal: oldRequest.signal,
|
|
keepalive: oldRequest.keepalive,
|
|
// https://fetch.spec.whatwg.org/#dom-request-duplex
|
|
// @ts-expect-error It isn't part of the types, but undici accepts it and it allows to carry over the body to a new request
|
|
duplex: "half"
|
|
}
|
|
});
|
|
}
|
|
function setOriginPathname(request, pathname, trailingSlash, buildFormat) {
|
|
if (!pathname) {
|
|
pathname = "/";
|
|
}
|
|
const shouldAppendSlash = shouldAppendForwardSlash(trailingSlash, buildFormat);
|
|
let finalPathname;
|
|
if (pathname === "/") {
|
|
finalPathname = "/";
|
|
} else if (shouldAppendSlash) {
|
|
finalPathname = appendForwardSlash(pathname);
|
|
} else {
|
|
finalPathname = removeTrailingForwardSlash(pathname);
|
|
}
|
|
Reflect.set(request, originPathnameSymbol, encodeURIComponent(finalPathname));
|
|
}
|
|
function getOriginPathname(request) {
|
|
const origin = Reflect.get(request, originPathnameSymbol);
|
|
if (origin) {
|
|
return decodeURIComponent(origin);
|
|
}
|
|
return new URL(request.url).pathname;
|
|
}
|
|
function normalizeRewritePathname(urlPathname, base, trailingSlash, buildFormat) {
|
|
let pathname = collapseDuplicateSlashes(urlPathname);
|
|
const shouldAppendSlash = shouldAppendForwardSlash(trailingSlash, buildFormat);
|
|
if (base !== "/") {
|
|
const isBasePathRequest = urlPathname === base || urlPathname === removeTrailingForwardSlash(base);
|
|
if (isBasePathRequest) {
|
|
pathname = shouldAppendSlash ? "/" : "";
|
|
} else if (urlPathname.startsWith(base)) {
|
|
pathname = shouldAppendSlash ? appendForwardSlash(urlPathname) : removeTrailingForwardSlash(urlPathname);
|
|
pathname = pathname.slice(base.length);
|
|
}
|
|
}
|
|
if (!pathname.startsWith("/") && shouldAppendSlash && urlPathname.endsWith("/")) {
|
|
pathname = prependForwardSlash(pathname);
|
|
}
|
|
if (pathname === "/" && base !== "/" && !shouldAppendSlash) {
|
|
pathname = "";
|
|
}
|
|
if (buildFormat === "file") {
|
|
pathname = pathname.replace(/\.html$/, "");
|
|
}
|
|
let resolvedUrlPathname;
|
|
if (base !== "/" && (pathname === "" || pathname === "/") && !shouldAppendSlash) {
|
|
resolvedUrlPathname = removeTrailingForwardSlash(base);
|
|
} else {
|
|
resolvedUrlPathname = joinPaths(...[base, pathname].filter(Boolean));
|
|
}
|
|
return { pathname, resolvedUrlPathname };
|
|
}
|
|
|
|
const NOOP_ACTIONS_MOD = {
|
|
server: {}
|
|
};
|
|
|
|
function defineMiddleware(fn) {
|
|
return fn;
|
|
}
|
|
|
|
const FORM_CONTENT_TYPES = [
|
|
"application/x-www-form-urlencoded",
|
|
"multipart/form-data",
|
|
"text/plain"
|
|
];
|
|
const SAFE_METHODS = ["GET", "HEAD", "OPTIONS"];
|
|
function createOriginCheckMiddleware() {
|
|
return defineMiddleware((context, next) => {
|
|
const { request, url, isPrerendered } = context;
|
|
if (isPrerendered) {
|
|
return next();
|
|
}
|
|
if (SAFE_METHODS.includes(request.method)) {
|
|
return next();
|
|
}
|
|
const isSameOrigin = request.headers.get("origin") === url.origin;
|
|
const hasContentType = request.headers.has("content-type");
|
|
if (hasContentType) {
|
|
const formLikeHeader = hasFormLikeHeader(request.headers.get("content-type"));
|
|
if (formLikeHeader && !isSameOrigin) {
|
|
return new Response(`Cross-site ${request.method} form submissions are forbidden`, {
|
|
status: 403
|
|
});
|
|
}
|
|
} else {
|
|
if (!isSameOrigin) {
|
|
return new Response(`Cross-site ${request.method} form submissions are forbidden`, {
|
|
status: 403
|
|
});
|
|
}
|
|
}
|
|
return next();
|
|
});
|
|
}
|
|
function hasFormLikeHeader(contentType) {
|
|
if (contentType) {
|
|
for (const FORM_CONTENT_TYPE of FORM_CONTENT_TYPES) {
|
|
if (contentType.toLowerCase().includes(FORM_CONTENT_TYPE)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
const NOOP_MIDDLEWARE_FN = async (_ctx, next) => {
|
|
const response = await next();
|
|
response.headers.set(NOOP_MIDDLEWARE_HEADER, "true");
|
|
return response;
|
|
};
|
|
|
|
const RedirectComponentInstance = {
|
|
default() {
|
|
return new Response(null, {
|
|
status: 301
|
|
});
|
|
}
|
|
};
|
|
const RedirectSinglePageBuiltModule = {
|
|
page: () => Promise.resolve(RedirectComponentInstance),
|
|
onRequest: (_, next) => next()
|
|
};
|
|
|
|
function sanitizeParams(params) {
|
|
return Object.fromEntries(
|
|
Object.entries(params).map(([key, value]) => {
|
|
if (typeof value === "string") {
|
|
return [key, value.normalize().replace(/#/g, "%23").replace(/\?/g, "%3F")];
|
|
}
|
|
return [key, value];
|
|
})
|
|
);
|
|
}
|
|
function getParameter(part, params) {
|
|
if (part.spread) {
|
|
return params[part.content.slice(3)] || "";
|
|
}
|
|
if (part.dynamic) {
|
|
if (!params[part.content]) {
|
|
throw new TypeError(`Missing parameter: ${part.content}`);
|
|
}
|
|
return params[part.content];
|
|
}
|
|
return part.content.normalize().replace(/\?/g, "%3F").replace(/#/g, "%23").replace(/%5B/g, "[").replace(/%5D/g, "]");
|
|
}
|
|
function getSegment(segment, params) {
|
|
const segmentPath = segment.map((part) => getParameter(part, params)).join("");
|
|
return segmentPath ? collapseDuplicateLeadingSlashes("/" + segmentPath) : "";
|
|
}
|
|
function getRouteGenerator(segments, addTrailingSlash) {
|
|
return (params) => {
|
|
const sanitizedParams = sanitizeParams(params);
|
|
let trailing = "";
|
|
if (addTrailingSlash === "always" && segments.length) {
|
|
trailing = "/";
|
|
}
|
|
const path = segments.map((segment) => getSegment(segment, sanitizedParams)).join("") + trailing;
|
|
return path || "/";
|
|
};
|
|
}
|
|
|
|
const VALID_PARAM_TYPES = ["string", "undefined"];
|
|
function validateGetStaticPathsParameter([key, value], route) {
|
|
if (!VALID_PARAM_TYPES.includes(typeof value)) {
|
|
throw new AstroError({
|
|
...GetStaticPathsInvalidRouteParam,
|
|
message: GetStaticPathsInvalidRouteParam.message(key, value, typeof value),
|
|
location: {
|
|
file: route
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
function stringifyParams(params, route, trailingSlash) {
|
|
const validatedParams = {};
|
|
for (const [key, value] of Object.entries(params)) {
|
|
validateGetStaticPathsParameter([key, value], route.component);
|
|
if (value !== void 0) {
|
|
validatedParams[key] = trimSlashes(value);
|
|
}
|
|
}
|
|
return getRouteGenerator(route.segments, trailingSlash)(validatedParams);
|
|
}
|
|
|
|
function validateDynamicRouteModule(mod, {
|
|
ssr,
|
|
route
|
|
}) {
|
|
if ((!ssr || route.prerender) && !mod.getStaticPaths) {
|
|
throw new AstroError({
|
|
...GetStaticPathsRequired,
|
|
location: { file: route.component }
|
|
});
|
|
}
|
|
}
|
|
function validateGetStaticPathsResult(result, route) {
|
|
if (!Array.isArray(result)) {
|
|
throw new AstroError({
|
|
...InvalidGetStaticPathsReturn,
|
|
message: InvalidGetStaticPathsReturn.message(typeof result),
|
|
location: {
|
|
file: route.component
|
|
}
|
|
});
|
|
}
|
|
result.forEach((pathObject) => {
|
|
if (typeof pathObject === "object" && Array.isArray(pathObject) || pathObject === null) {
|
|
throw new AstroError({
|
|
...InvalidGetStaticPathsEntry,
|
|
message: InvalidGetStaticPathsEntry.message(
|
|
Array.isArray(pathObject) ? "array" : typeof pathObject
|
|
)
|
|
});
|
|
}
|
|
if (pathObject.params === void 0 || pathObject.params === null || pathObject.params && Object.keys(pathObject.params).length === 0) {
|
|
throw new AstroError({
|
|
...GetStaticPathsExpectedParams,
|
|
location: {
|
|
file: route.component
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
function generatePaginateFunction(routeMatch, base, trailingSlash) {
|
|
return function paginateUtility(data, args = {}) {
|
|
const generate = getRouteGenerator(routeMatch.segments, trailingSlash);
|
|
let { pageSize: _pageSize, params: _params, props: _props } = args;
|
|
const pageSize = _pageSize || 10;
|
|
const paramName = "page";
|
|
const additionalParams = _params || {};
|
|
const additionalProps = _props || {};
|
|
let includesFirstPageNumber;
|
|
if (routeMatch.params.includes(`...${paramName}`)) {
|
|
includesFirstPageNumber = false;
|
|
} else if (routeMatch.params.includes(`${paramName}`)) {
|
|
includesFirstPageNumber = true;
|
|
} else {
|
|
throw new AstroError({
|
|
...PageNumberParamNotFound,
|
|
message: PageNumberParamNotFound.message(paramName)
|
|
});
|
|
}
|
|
const lastPage = Math.max(1, Math.ceil(data.length / pageSize));
|
|
const result = [...Array(lastPage).keys()].map((num) => {
|
|
const pageNum = num + 1;
|
|
const start = pageSize === Number.POSITIVE_INFINITY ? 0 : (pageNum - 1) * pageSize;
|
|
const end = Math.min(start + pageSize, data.length);
|
|
const params = {
|
|
...additionalParams,
|
|
[paramName]: includesFirstPageNumber || pageNum > 1 ? String(pageNum) : void 0
|
|
};
|
|
const current = addRouteBase(generate({ ...params }), base);
|
|
const next = pageNum === lastPage ? void 0 : addRouteBase(generate({ ...params, page: String(pageNum + 1) }), base);
|
|
const prev = pageNum === 1 ? void 0 : addRouteBase(
|
|
generate({
|
|
...params,
|
|
page: !includesFirstPageNumber && pageNum - 1 === 1 ? void 0 : String(pageNum - 1)
|
|
}),
|
|
base
|
|
);
|
|
const first = pageNum === 1 ? void 0 : addRouteBase(
|
|
generate({
|
|
...params,
|
|
page: includesFirstPageNumber ? "1" : void 0
|
|
}),
|
|
base
|
|
);
|
|
const last = pageNum === lastPage ? void 0 : addRouteBase(generate({ ...params, page: String(lastPage) }), base);
|
|
return {
|
|
params,
|
|
props: {
|
|
...additionalProps,
|
|
page: {
|
|
data: data.slice(start, end),
|
|
start,
|
|
end: end - 1,
|
|
size: pageSize,
|
|
total: data.length,
|
|
currentPage: pageNum,
|
|
lastPage,
|
|
url: { current, next, prev, first, last }
|
|
}
|
|
}
|
|
};
|
|
});
|
|
return result;
|
|
};
|
|
}
|
|
function addRouteBase(route, base) {
|
|
let routeWithBase = joinPaths(base, route);
|
|
if (routeWithBase === "") routeWithBase = "/";
|
|
return routeWithBase;
|
|
}
|
|
|
|
async function callGetStaticPaths({
|
|
mod,
|
|
route,
|
|
routeCache,
|
|
ssr,
|
|
base,
|
|
trailingSlash
|
|
}) {
|
|
const cached = routeCache.get(route);
|
|
if (!mod) {
|
|
throw new Error("This is an error caused by Astro and not your code. Please file an issue.");
|
|
}
|
|
if (cached?.staticPaths) {
|
|
return cached.staticPaths;
|
|
}
|
|
validateDynamicRouteModule(mod, { ssr, route });
|
|
if (ssr && !route.prerender) {
|
|
const entry = Object.assign([], { keyed: /* @__PURE__ */ new Map() });
|
|
routeCache.set(route, { ...cached, staticPaths: entry });
|
|
return entry;
|
|
}
|
|
let staticPaths = [];
|
|
if (!mod.getStaticPaths) {
|
|
throw new Error("Unexpected Error.");
|
|
}
|
|
staticPaths = await mod.getStaticPaths({
|
|
// Q: Why the cast?
|
|
// A: So users downstream can have nicer typings, we have to make some sacrifice in our internal typings, which necessitate a cast here
|
|
paginate: generatePaginateFunction(route, base, trailingSlash),
|
|
routePattern: route.route
|
|
});
|
|
validateGetStaticPathsResult(staticPaths, route);
|
|
const keyedStaticPaths = staticPaths;
|
|
keyedStaticPaths.keyed = /* @__PURE__ */ new Map();
|
|
for (const sp of keyedStaticPaths) {
|
|
const paramsKey = stringifyParams(sp.params, route, trailingSlash);
|
|
keyedStaticPaths.keyed.set(paramsKey, sp);
|
|
}
|
|
routeCache.set(route, { ...cached, staticPaths: keyedStaticPaths });
|
|
return keyedStaticPaths;
|
|
}
|
|
class RouteCache {
|
|
logger;
|
|
cache = {};
|
|
runtimeMode;
|
|
constructor(logger, runtimeMode = "production") {
|
|
this.logger = logger;
|
|
this.runtimeMode = runtimeMode;
|
|
}
|
|
/** Clear the cache. */
|
|
clearAll() {
|
|
this.cache = {};
|
|
}
|
|
set(route, entry) {
|
|
const key = this.key(route);
|
|
if (this.runtimeMode === "production" && this.cache[key]?.staticPaths) {
|
|
this.logger.warn(null, `Internal Warning: route cache overwritten. (${key})`);
|
|
}
|
|
this.cache[key] = entry;
|
|
}
|
|
get(route) {
|
|
return this.cache[this.key(route)];
|
|
}
|
|
key(route) {
|
|
return `${route.route}_${route.component}`;
|
|
}
|
|
}
|
|
function findPathItemByKey(staticPaths, params, route, logger, trailingSlash) {
|
|
const paramsKey = stringifyParams(params, route, trailingSlash);
|
|
const matchedStaticPath = staticPaths.keyed.get(paramsKey);
|
|
if (matchedStaticPath) {
|
|
return matchedStaticPath;
|
|
}
|
|
logger.debug("router", `findPathItemByKey() - Unexpected cache miss looking for ${paramsKey}`);
|
|
}
|
|
|
|
function getPattern(segments, base, addTrailingSlash) {
|
|
const pathname = segments.map((segment) => {
|
|
if (segment.length === 1 && segment[0].spread) {
|
|
return "(?:\\/(.*?))?";
|
|
} else {
|
|
return "\\/" + segment.map((part) => {
|
|
if (part.spread) {
|
|
return "(.*?)";
|
|
} else if (part.dynamic) {
|
|
return "([^/]+?)";
|
|
} else {
|
|
return part.content.normalize().replace(/\?/g, "%3F").replace(/#/g, "%23").replace(/%5B/g, "[").replace(/%5D/g, "]").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
}
|
|
}).join("");
|
|
}
|
|
}).join("");
|
|
const trailing = addTrailingSlash && segments.length ? getTrailingSlashPattern(addTrailingSlash) : "$";
|
|
let initial = "\\/";
|
|
if (addTrailingSlash === "never" && base !== "/") {
|
|
initial = "";
|
|
}
|
|
return new RegExp(`^${pathname || initial}${trailing}`);
|
|
}
|
|
function getTrailingSlashPattern(addTrailingSlash) {
|
|
if (addTrailingSlash === "always") {
|
|
return "\\/$";
|
|
}
|
|
if (addTrailingSlash === "never") {
|
|
return "$";
|
|
}
|
|
return "\\/?$";
|
|
}
|
|
|
|
const SERVER_ISLAND_ROUTE = "/_server-islands/[name]";
|
|
const SERVER_ISLAND_COMPONENT = "_server-islands.astro";
|
|
function badRequest(reason) {
|
|
return new Response(null, {
|
|
status: 400,
|
|
statusText: "Bad request: " + reason
|
|
});
|
|
}
|
|
const DEFAULT_BODY_SIZE_LIMIT = 1024 * 1024;
|
|
async function getRequestData(request, bodySizeLimit = DEFAULT_BODY_SIZE_LIMIT) {
|
|
switch (request.method) {
|
|
case "GET": {
|
|
const url = new URL(request.url);
|
|
const params = url.searchParams;
|
|
if (!params.has("s") || !params.has("e") || !params.has("p")) {
|
|
return badRequest("Missing required query parameters.");
|
|
}
|
|
const encryptedSlots = params.get("s");
|
|
return {
|
|
encryptedComponentExport: params.get("e"),
|
|
encryptedProps: params.get("p"),
|
|
encryptedSlots
|
|
};
|
|
}
|
|
case "POST": {
|
|
try {
|
|
const body = await readBodyWithLimit(request, bodySizeLimit);
|
|
const raw = new TextDecoder().decode(body);
|
|
const data = JSON.parse(raw);
|
|
if (Object.hasOwn(data, "slots") && typeof data.slots === "object") {
|
|
return badRequest("Plaintext slots are not allowed. Slots must be encrypted.");
|
|
}
|
|
if (Object.hasOwn(data, "componentExport") && typeof data.componentExport === "string") {
|
|
return badRequest(
|
|
"Plaintext componentExport is not allowed. componentExport must be encrypted."
|
|
);
|
|
}
|
|
return data;
|
|
} catch (e) {
|
|
if (e instanceof BodySizeLimitError) {
|
|
return new Response(null, {
|
|
status: 413,
|
|
statusText: e.message
|
|
});
|
|
}
|
|
if (e instanceof SyntaxError) {
|
|
return badRequest("Request format is invalid.");
|
|
}
|
|
throw e;
|
|
}
|
|
}
|
|
default: {
|
|
return new Response(null, { status: 405 });
|
|
}
|
|
}
|
|
}
|
|
function createEndpoint(manifest) {
|
|
const page = async (result) => {
|
|
const params = result.params;
|
|
if (!params.name) {
|
|
return new Response(null, {
|
|
status: 400,
|
|
statusText: "Bad request"
|
|
});
|
|
}
|
|
const componentId = params.name;
|
|
const data = await getRequestData(result.request, manifest.serverIslandBodySizeLimit);
|
|
if (data instanceof Response) {
|
|
return data;
|
|
}
|
|
const serverIslandMappings = await manifest.serverIslandMappings?.();
|
|
const serverIslandMap = await serverIslandMappings?.serverIslandMap;
|
|
let imp = serverIslandMap?.get(componentId);
|
|
if (!imp) {
|
|
return new Response(null, {
|
|
status: 404,
|
|
statusText: "Not found"
|
|
});
|
|
}
|
|
const key = await manifest.key;
|
|
let componentExport;
|
|
try {
|
|
componentExport = await decryptString(
|
|
key,
|
|
data.encryptedComponentExport,
|
|
`export:${componentId}`
|
|
);
|
|
} catch (_e) {
|
|
return badRequest("Encrypted componentExport value is invalid.");
|
|
}
|
|
const encryptedProps = data.encryptedProps;
|
|
let props = {};
|
|
if (encryptedProps !== "") {
|
|
try {
|
|
const propString = await decryptString(key, encryptedProps, `props:${componentId}`);
|
|
props = JSON.parse(propString);
|
|
} catch (_e) {
|
|
return badRequest("Encrypted props value is invalid.");
|
|
}
|
|
}
|
|
let decryptedSlots = {};
|
|
const encryptedSlots = data.encryptedSlots;
|
|
if (encryptedSlots !== "") {
|
|
try {
|
|
const slotsString = await decryptString(key, encryptedSlots, `slots:${componentId}`);
|
|
decryptedSlots = JSON.parse(slotsString);
|
|
} catch (_e) {
|
|
return badRequest("Encrypted slots value is invalid.");
|
|
}
|
|
}
|
|
const componentModule = await imp();
|
|
let Component = componentModule[componentExport];
|
|
const slots = {};
|
|
for (const prop in decryptedSlots) {
|
|
slots[prop] = createSlotValueFromString(decryptedSlots[prop]);
|
|
}
|
|
result.response.headers.set("X-Robots-Tag", "noindex");
|
|
if (isAstroComponentFactory(Component)) {
|
|
const ServerIsland = Component;
|
|
Component = function(...args) {
|
|
return ServerIsland.apply(this, args);
|
|
};
|
|
Object.assign(Component, ServerIsland);
|
|
Component.propagation = "self";
|
|
}
|
|
return renderTemplate`${renderComponent(result, "Component", Component, props, slots)}`;
|
|
};
|
|
page.isAstroComponentFactory = true;
|
|
const instance = {
|
|
default: page,
|
|
partial: true
|
|
};
|
|
return instance;
|
|
}
|
|
|
|
function createDefaultRoutes(manifest) {
|
|
const root = new URL(manifest.rootDir);
|
|
return [
|
|
{
|
|
instance: default404Instance,
|
|
matchesComponent: (filePath) => filePath.href === new URL(DEFAULT_404_COMPONENT, root).href,
|
|
route: DEFAULT_404_ROUTE.route,
|
|
component: DEFAULT_404_COMPONENT
|
|
},
|
|
{
|
|
instance: createEndpoint(manifest),
|
|
matchesComponent: (filePath) => filePath.href === new URL(SERVER_ISLAND_COMPONENT, root).href,
|
|
route: SERVER_ISLAND_ROUTE,
|
|
component: SERVER_ISLAND_COMPONENT
|
|
}
|
|
];
|
|
}
|
|
|
|
function deserializeManifest(serializedManifest, routesList) {
|
|
const routes = [];
|
|
if (serializedManifest.routes) {
|
|
for (const serializedRoute of serializedManifest.routes) {
|
|
routes.push({
|
|
...serializedRoute,
|
|
routeData: deserializeRouteData(serializedRoute.routeData)
|
|
});
|
|
const route = serializedRoute;
|
|
route.routeData = deserializeRouteData(serializedRoute.routeData);
|
|
}
|
|
}
|
|
const assets = new Set(serializedManifest.assets);
|
|
const componentMetadata = new Map(serializedManifest.componentMetadata);
|
|
const inlinedScripts = new Map(serializedManifest.inlinedScripts);
|
|
const clientDirectives = new Map(serializedManifest.clientDirectives);
|
|
const key = decodeKey(serializedManifest.key);
|
|
return {
|
|
// in case user middleware exists, this no-op middleware will be reassigned (see plugin-ssr.ts)
|
|
middleware() {
|
|
return { onRequest: NOOP_MIDDLEWARE_FN };
|
|
},
|
|
...serializedManifest,
|
|
rootDir: new URL(serializedManifest.rootDir),
|
|
srcDir: new URL(serializedManifest.srcDir),
|
|
publicDir: new URL(serializedManifest.publicDir),
|
|
outDir: new URL(serializedManifest.outDir),
|
|
cacheDir: new URL(serializedManifest.cacheDir),
|
|
buildClientDir: new URL(serializedManifest.buildClientDir),
|
|
buildServerDir: new URL(serializedManifest.buildServerDir),
|
|
assets,
|
|
componentMetadata,
|
|
inlinedScripts,
|
|
clientDirectives,
|
|
routes,
|
|
key
|
|
};
|
|
}
|
|
function deserializeRouteData(rawRouteData) {
|
|
return {
|
|
route: rawRouteData.route,
|
|
type: rawRouteData.type,
|
|
// nosemgrep: javascript.lang.security.audit.detect-non-literal-regexp.detect-non-literal-regexp
|
|
// This pattern is serialized from Astro's own route manifest.
|
|
pattern: new RegExp(rawRouteData.pattern),
|
|
params: rawRouteData.params,
|
|
component: rawRouteData.component,
|
|
pathname: rawRouteData.pathname || void 0,
|
|
segments: rawRouteData.segments,
|
|
prerender: rawRouteData.prerender,
|
|
redirect: rawRouteData.redirect,
|
|
redirectRoute: rawRouteData.redirectRoute ? deserializeRouteData(rawRouteData.redirectRoute) : void 0,
|
|
fallbackRoutes: rawRouteData.fallbackRoutes.map((fallback) => {
|
|
return deserializeRouteData(fallback);
|
|
}),
|
|
isIndex: rawRouteData.isIndex,
|
|
origin: rawRouteData.origin,
|
|
distURL: rawRouteData.distURL
|
|
};
|
|
}
|
|
function deserializeRouteInfo(rawRouteInfo) {
|
|
return {
|
|
styles: rawRouteInfo.styles,
|
|
file: rawRouteInfo.file,
|
|
links: rawRouteInfo.links,
|
|
scripts: rawRouteInfo.scripts,
|
|
routeData: deserializeRouteData(rawRouteInfo.routeData)
|
|
};
|
|
}
|
|
|
|
class NodePool {
|
|
textPool = [];
|
|
htmlStringPool = [];
|
|
componentPool = [];
|
|
instructionPool = [];
|
|
maxSize;
|
|
enableStats;
|
|
stats = {
|
|
acquireFromPool: 0,
|
|
acquireNew: 0,
|
|
released: 0,
|
|
releasedDropped: 0
|
|
};
|
|
/**
|
|
* Creates a new object pool for queue nodes.
|
|
*
|
|
* @param maxSize - Maximum number of nodes to keep in the pool (default: 1000).
|
|
* The cap is shared across all typed sub-pools.
|
|
* @param enableStats - Enable statistics tracking (default: false for performance)
|
|
*/
|
|
constructor(maxSize = 1e3, enableStats = false) {
|
|
this.maxSize = maxSize;
|
|
this.enableStats = enableStats;
|
|
}
|
|
/**
|
|
* Acquires a queue node from the pool or creates a new one if the pool is empty.
|
|
* Pops from the type-specific sub-pool to reuse an existing object when available.
|
|
*
|
|
* @param type - The type of queue node to acquire
|
|
* @param content - Optional content to set on the node (for text or html-string types)
|
|
* @returns A queue node ready to be populated with data
|
|
*/
|
|
acquire(type, content) {
|
|
const pooledNode = this.popFromTypedPool(type);
|
|
if (pooledNode) {
|
|
if (this.enableStats) {
|
|
this.stats.acquireFromPool = this.stats.acquireFromPool + 1;
|
|
}
|
|
this.resetNodeContent(pooledNode, type, content);
|
|
return pooledNode;
|
|
}
|
|
if (this.enableStats) {
|
|
this.stats.acquireNew = this.stats.acquireNew + 1;
|
|
}
|
|
return this.createNode(type, content);
|
|
}
|
|
/**
|
|
* Creates a new node of the specified type with the given content.
|
|
* Helper method to reduce branching in acquire().
|
|
*/
|
|
createNode(type, content = "") {
|
|
switch (type) {
|
|
case "text":
|
|
return { type: "text", content };
|
|
case "html-string":
|
|
return { type: "html-string", html: content };
|
|
case "component":
|
|
return { type: "component", instance: void 0 };
|
|
case "instruction":
|
|
return { type: "instruction", instruction: void 0 };
|
|
}
|
|
}
|
|
/**
|
|
* Pops a node from the type-specific sub-pool.
|
|
* Returns undefined if the sub-pool for the requested type is empty.
|
|
*/
|
|
popFromTypedPool(type) {
|
|
switch (type) {
|
|
case "text":
|
|
return this.textPool.pop();
|
|
case "html-string":
|
|
return this.htmlStringPool.pop();
|
|
case "component":
|
|
return this.componentPool.pop();
|
|
case "instruction":
|
|
return this.instructionPool.pop();
|
|
}
|
|
}
|
|
/**
|
|
* Resets the content/value field on a reused pooled node.
|
|
* The type discriminant is already correct since we pop from the matching sub-pool.
|
|
*/
|
|
resetNodeContent(node, type, content) {
|
|
switch (type) {
|
|
case "text":
|
|
node.content = content ?? "";
|
|
break;
|
|
case "html-string":
|
|
node.html = content ?? "";
|
|
break;
|
|
case "component":
|
|
node.instance = void 0;
|
|
break;
|
|
case "instruction":
|
|
node.instruction = void 0;
|
|
break;
|
|
}
|
|
}
|
|
/**
|
|
* Returns the total number of nodes across all typed sub-pools.
|
|
*/
|
|
totalPoolSize() {
|
|
return this.textPool.length + this.htmlStringPool.length + this.componentPool.length + this.instructionPool.length;
|
|
}
|
|
/**
|
|
* Releases a queue node back to the pool for reuse.
|
|
* If the pool is at max capacity, the node is discarded (will be GC'd).
|
|
*
|
|
* @param node - The node to release back to the pool
|
|
*/
|
|
release(node) {
|
|
if (this.totalPoolSize() >= this.maxSize) {
|
|
if (this.enableStats) {
|
|
this.stats.releasedDropped = this.stats.releasedDropped + 1;
|
|
}
|
|
return;
|
|
}
|
|
switch (node.type) {
|
|
case "text":
|
|
node.content = "";
|
|
this.textPool.push(node);
|
|
break;
|
|
case "html-string":
|
|
node.html = "";
|
|
this.htmlStringPool.push(node);
|
|
break;
|
|
case "component":
|
|
node.instance = void 0;
|
|
this.componentPool.push(node);
|
|
break;
|
|
case "instruction":
|
|
node.instruction = void 0;
|
|
this.instructionPool.push(node);
|
|
break;
|
|
}
|
|
if (this.enableStats) {
|
|
this.stats.released = this.stats.released + 1;
|
|
}
|
|
}
|
|
/**
|
|
* Releases all nodes in an array back to the pool.
|
|
* This is a convenience method for releasing multiple nodes at once.
|
|
*
|
|
* @param nodes - Array of nodes to release
|
|
*/
|
|
releaseAll(nodes) {
|
|
for (const node of nodes) {
|
|
this.release(node);
|
|
}
|
|
}
|
|
/**
|
|
* Clears all typed sub-pools, discarding all cached nodes.
|
|
* This can be useful if you want to free memory after a large render.
|
|
*/
|
|
clear() {
|
|
this.textPool.length = 0;
|
|
this.htmlStringPool.length = 0;
|
|
this.componentPool.length = 0;
|
|
this.instructionPool.length = 0;
|
|
}
|
|
/**
|
|
* Gets the current total number of nodes across all typed sub-pools.
|
|
* Useful for monitoring pool usage and tuning maxSize.
|
|
*
|
|
* @returns Number of nodes currently available in the pool
|
|
*/
|
|
size() {
|
|
return this.totalPoolSize();
|
|
}
|
|
/**
|
|
* Gets pool statistics for debugging.
|
|
*
|
|
* @returns Pool usage statistics including computed metrics
|
|
*/
|
|
getStats() {
|
|
return {
|
|
...this.stats,
|
|
poolSize: this.totalPoolSize(),
|
|
maxSize: this.maxSize,
|
|
hitRate: this.stats.acquireFromPool + this.stats.acquireNew > 0 ? this.stats.acquireFromPool / (this.stats.acquireFromPool + this.stats.acquireNew) * 100 : 0
|
|
};
|
|
}
|
|
/**
|
|
* Resets pool statistics.
|
|
*/
|
|
resetStats() {
|
|
this.stats = {
|
|
acquireFromPool: 0,
|
|
acquireNew: 0,
|
|
released: 0,
|
|
releasedDropped: 0
|
|
};
|
|
}
|
|
}
|
|
|
|
class HTMLStringCache {
|
|
cache = /* @__PURE__ */ new Map();
|
|
maxSize;
|
|
constructor(maxSize = 1e3) {
|
|
this.maxSize = maxSize;
|
|
this.warm(COMMON_HTML_PATTERNS);
|
|
}
|
|
/**
|
|
* Get or create an HTMLString for the given content.
|
|
* If cached, the existing object is returned and moved to end (most recently used).
|
|
* If not cached, a new HTMLString is created, cached, and returned.
|
|
*
|
|
* @param content - The HTML string content
|
|
* @returns HTMLString object (cached or newly created)
|
|
*/
|
|
getOrCreate(content) {
|
|
const cached = this.cache.get(content);
|
|
if (cached) {
|
|
this.cache.delete(content);
|
|
this.cache.set(content, cached);
|
|
return cached;
|
|
}
|
|
const htmlString = new HTMLString(content);
|
|
this.cache.set(content, htmlString);
|
|
if (this.cache.size > this.maxSize) {
|
|
const firstKey = this.cache.keys().next().value;
|
|
if (firstKey !== void 0) {
|
|
this.cache.delete(firstKey);
|
|
}
|
|
}
|
|
return htmlString;
|
|
}
|
|
/**
|
|
* Get current cache size
|
|
*/
|
|
size() {
|
|
return this.cache.size;
|
|
}
|
|
/**
|
|
* Pre-warms the cache with common HTML patterns.
|
|
* This ensures first-render cache hits for frequently used tags.
|
|
*
|
|
* @param patterns - Array of HTML strings to pre-cache
|
|
*/
|
|
warm(patterns) {
|
|
for (const pattern of patterns) {
|
|
if (!this.cache.has(pattern)) {
|
|
this.cache.set(pattern, new HTMLString(pattern));
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Clear the entire cache
|
|
*/
|
|
clear() {
|
|
this.cache.clear();
|
|
}
|
|
}
|
|
const COMMON_HTML_PATTERNS = [
|
|
// Structural elements
|
|
"<div>",
|
|
"</div>",
|
|
"<span>",
|
|
"</span>",
|
|
"<p>",
|
|
"</p>",
|
|
"<section>",
|
|
"</section>",
|
|
"<article>",
|
|
"</article>",
|
|
"<header>",
|
|
"</header>",
|
|
"<footer>",
|
|
"</footer>",
|
|
"<nav>",
|
|
"</nav>",
|
|
"<main>",
|
|
"</main>",
|
|
"<aside>",
|
|
"</aside>",
|
|
// List elements
|
|
"<ul>",
|
|
"</ul>",
|
|
"<ol>",
|
|
"</ol>",
|
|
"<li>",
|
|
"</li>",
|
|
// Void/self-closing elements
|
|
"<br>",
|
|
"<hr>",
|
|
"<br/>",
|
|
"<hr/>",
|
|
// Heading elements
|
|
"<h1>",
|
|
"</h1>",
|
|
"<h2>",
|
|
"</h2>",
|
|
"<h3>",
|
|
"</h3>",
|
|
"<h4>",
|
|
"</h4>",
|
|
// Inline elements
|
|
"<a>",
|
|
"</a>",
|
|
"<strong>",
|
|
"</strong>",
|
|
"<em>",
|
|
"</em>",
|
|
"<code>",
|
|
"</code>",
|
|
// Common whitespace
|
|
" ",
|
|
"\n"
|
|
];
|
|
|
|
const FORBIDDEN_PATH_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
|
|
class Pipeline {
|
|
internalMiddleware;
|
|
resolvedMiddleware = void 0;
|
|
resolvedActions = void 0;
|
|
resolvedSessionDriver = void 0;
|
|
resolvedCacheProvider = void 0;
|
|
compiledCacheRoutes = void 0;
|
|
nodePool;
|
|
htmlStringCache;
|
|
logger;
|
|
manifest;
|
|
/**
|
|
* "development" or "production" only
|
|
*/
|
|
runtimeMode;
|
|
renderers;
|
|
resolve;
|
|
streaming;
|
|
/**
|
|
* Used to provide better error messages for `Astro.clientAddress`
|
|
*/
|
|
adapterName;
|
|
clientDirectives;
|
|
inlinedScripts;
|
|
compressHTML;
|
|
i18n;
|
|
middleware;
|
|
routeCache;
|
|
/**
|
|
* Used for `Astro.site`.
|
|
*/
|
|
site;
|
|
/**
|
|
* Array of built-in, internal, routes.
|
|
* Used to find the route module
|
|
*/
|
|
defaultRoutes;
|
|
actions;
|
|
sessionDriver;
|
|
cacheProvider;
|
|
cacheConfig;
|
|
serverIslands;
|
|
constructor(logger, manifest, runtimeMode, renderers, resolve, streaming, adapterName = manifest.adapterName, clientDirectives = manifest.clientDirectives, inlinedScripts = manifest.inlinedScripts, compressHTML = manifest.compressHTML, i18n = manifest.i18n, middleware = manifest.middleware, routeCache = new RouteCache(logger, runtimeMode), site = manifest.site ? new URL(manifest.site) : void 0, defaultRoutes = createDefaultRoutes(manifest), actions = manifest.actions, sessionDriver = manifest.sessionDriver, cacheProvider = manifest.cacheProvider, cacheConfig = manifest.cacheConfig, serverIslands = manifest.serverIslandMappings) {
|
|
this.logger = logger;
|
|
this.manifest = manifest;
|
|
this.runtimeMode = runtimeMode;
|
|
this.renderers = renderers;
|
|
this.resolve = resolve;
|
|
this.streaming = streaming;
|
|
this.adapterName = adapterName;
|
|
this.clientDirectives = clientDirectives;
|
|
this.inlinedScripts = inlinedScripts;
|
|
this.compressHTML = compressHTML;
|
|
this.i18n = i18n;
|
|
this.middleware = middleware;
|
|
this.routeCache = routeCache;
|
|
this.site = site;
|
|
this.defaultRoutes = defaultRoutes;
|
|
this.actions = actions;
|
|
this.sessionDriver = sessionDriver;
|
|
this.cacheProvider = cacheProvider;
|
|
this.cacheConfig = cacheConfig;
|
|
this.serverIslands = serverIslands;
|
|
this.internalMiddleware = [];
|
|
if (i18n?.strategy !== "manual") {
|
|
this.internalMiddleware.push(
|
|
createI18nMiddleware(i18n, manifest.base, manifest.trailingSlash, manifest.buildFormat)
|
|
);
|
|
}
|
|
if (manifest.experimentalQueuedRendering.enabled) {
|
|
this.nodePool = this.createNodePool(
|
|
manifest.experimentalQueuedRendering.poolSize ?? 1e3,
|
|
false
|
|
);
|
|
if (manifest.experimentalQueuedRendering.contentCache) {
|
|
this.htmlStringCache = this.createStringCache();
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Resolves the middleware from the manifest, and returns the `onRequest` function. If `onRequest` isn't there,
|
|
* it returns a no-op function
|
|
*/
|
|
async getMiddleware() {
|
|
if (this.resolvedMiddleware) {
|
|
return this.resolvedMiddleware;
|
|
}
|
|
if (this.middleware) {
|
|
const middlewareInstance = await this.middleware();
|
|
const onRequest = middlewareInstance.onRequest ?? NOOP_MIDDLEWARE_FN;
|
|
const internalMiddlewares = [onRequest];
|
|
if (this.manifest.checkOrigin) {
|
|
internalMiddlewares.unshift(createOriginCheckMiddleware());
|
|
}
|
|
this.resolvedMiddleware = sequence(...internalMiddlewares);
|
|
return this.resolvedMiddleware;
|
|
} else {
|
|
this.resolvedMiddleware = NOOP_MIDDLEWARE_FN;
|
|
return this.resolvedMiddleware;
|
|
}
|
|
}
|
|
/**
|
|
* Clears the cached middleware so it is re-resolved on the next request.
|
|
* Called via HMR when middleware files change during development.
|
|
*/
|
|
clearMiddleware() {
|
|
this.resolvedMiddleware = void 0;
|
|
}
|
|
async getActions() {
|
|
if (this.resolvedActions) {
|
|
return this.resolvedActions;
|
|
} else if (this.actions) {
|
|
return this.actions();
|
|
}
|
|
return NOOP_ACTIONS_MOD;
|
|
}
|
|
async getSessionDriver() {
|
|
if (this.resolvedSessionDriver !== void 0) {
|
|
return this.resolvedSessionDriver;
|
|
}
|
|
if (this.sessionDriver) {
|
|
const driverModule = await this.sessionDriver();
|
|
this.resolvedSessionDriver = driverModule?.default || null;
|
|
return this.resolvedSessionDriver;
|
|
}
|
|
this.resolvedSessionDriver = null;
|
|
return null;
|
|
}
|
|
async getCacheProvider() {
|
|
if (this.resolvedCacheProvider !== void 0) {
|
|
return this.resolvedCacheProvider;
|
|
}
|
|
if (this.cacheProvider) {
|
|
const mod = await this.cacheProvider();
|
|
const factory = mod?.default || null;
|
|
this.resolvedCacheProvider = factory ? factory(this.cacheConfig?.options) : null;
|
|
return this.resolvedCacheProvider;
|
|
}
|
|
this.resolvedCacheProvider = null;
|
|
return null;
|
|
}
|
|
async getServerIslands() {
|
|
if (this.serverIslands) {
|
|
return this.serverIslands();
|
|
}
|
|
return {
|
|
serverIslandMap: /* @__PURE__ */ new Map(),
|
|
serverIslandNameMap: /* @__PURE__ */ new Map()
|
|
};
|
|
}
|
|
async getAction(path) {
|
|
const pathKeys = path.split(".").map((key) => decodeURIComponent(key));
|
|
let { server } = await this.getActions();
|
|
if (!server || !(typeof server === "object")) {
|
|
throw new TypeError(
|
|
`Expected \`server\` export in actions file to be an object. Received ${typeof server}.`
|
|
);
|
|
}
|
|
for (const key of pathKeys) {
|
|
if (FORBIDDEN_PATH_KEYS.has(key)) {
|
|
throw new AstroError({
|
|
...ActionNotFoundError,
|
|
message: ActionNotFoundError.message(pathKeys.join("."))
|
|
});
|
|
}
|
|
if (!Object.hasOwn(server, key)) {
|
|
throw new AstroError({
|
|
...ActionNotFoundError,
|
|
message: ActionNotFoundError.message(pathKeys.join("."))
|
|
});
|
|
}
|
|
server = server[key];
|
|
}
|
|
if (typeof server !== "function") {
|
|
throw new TypeError(
|
|
`Expected handler for action ${pathKeys.join(".")} to be a function. Received ${typeof server}.`
|
|
);
|
|
}
|
|
return server;
|
|
}
|
|
async getModuleForRoute(route) {
|
|
for (const defaultRoute of this.defaultRoutes) {
|
|
if (route.component === defaultRoute.component) {
|
|
return {
|
|
page: () => Promise.resolve(defaultRoute.instance)
|
|
};
|
|
}
|
|
}
|
|
if (route.type === "redirect") {
|
|
return RedirectSinglePageBuiltModule;
|
|
} else {
|
|
if (this.manifest.pageMap) {
|
|
const importComponentInstance = this.manifest.pageMap.get(route.component);
|
|
if (!importComponentInstance) {
|
|
throw new Error(
|
|
`Unexpectedly unable to find a component instance for route ${route.route}`
|
|
);
|
|
}
|
|
return await importComponentInstance();
|
|
} else if (this.manifest.pageModule) {
|
|
return this.manifest.pageModule;
|
|
}
|
|
throw new Error(
|
|
"Astro couldn't find the correct page to render, probably because it wasn't correctly mapped for SSR usage. This is an internal error, please file an issue."
|
|
);
|
|
}
|
|
}
|
|
createNodePool(poolSize, stats) {
|
|
return new NodePool(poolSize, stats);
|
|
}
|
|
createStringCache() {
|
|
return new HTMLStringCache(1e3);
|
|
}
|
|
}
|
|
|
|
function routeIsRedirect(route) {
|
|
return route?.type === "redirect";
|
|
}
|
|
function routeIsFallback(route) {
|
|
return route?.type === "fallback";
|
|
}
|
|
function getFallbackRoute(route, routeList) {
|
|
const fallbackRoute = routeList.find((r) => {
|
|
if (route.route === "/" && r.routeData.route === "/") {
|
|
return true;
|
|
}
|
|
return r.routeData.fallbackRoutes.find((f) => {
|
|
return f.route === route.route;
|
|
});
|
|
});
|
|
if (!fallbackRoute) {
|
|
throw new Error(`No fallback route found for route ${route.route}`);
|
|
}
|
|
return fallbackRoute.routeData;
|
|
}
|
|
function routeHasHtmlExtension(route) {
|
|
return route.segments.some(
|
|
(segment) => segment.some((part) => !part.dynamic && part.content.includes(".html"))
|
|
);
|
|
}
|
|
|
|
async function getProps(opts) {
|
|
const {
|
|
logger,
|
|
mod,
|
|
routeData: route,
|
|
routeCache,
|
|
pathname,
|
|
serverLike,
|
|
base,
|
|
trailingSlash
|
|
} = opts;
|
|
if (!route || route.pathname) {
|
|
return {};
|
|
}
|
|
if (routeIsRedirect(route) || routeIsFallback(route) || route.component === DEFAULT_404_COMPONENT) {
|
|
return {};
|
|
}
|
|
const staticPaths = await callGetStaticPaths({
|
|
mod,
|
|
route,
|
|
routeCache,
|
|
ssr: serverLike,
|
|
base,
|
|
trailingSlash
|
|
});
|
|
const params = getParams(route, pathname);
|
|
const matchedStaticPath = findPathItemByKey(staticPaths, params, route, logger, trailingSlash);
|
|
if (!matchedStaticPath && (serverLike ? route.prerender : true)) {
|
|
throw new AstroError({
|
|
...NoMatchingStaticPathFound,
|
|
message: NoMatchingStaticPathFound.message(pathname),
|
|
hint: NoMatchingStaticPathFound.hint([route.component])
|
|
});
|
|
}
|
|
if (mod) {
|
|
validatePrerenderEndpointCollision(route, mod, params);
|
|
}
|
|
const props = matchedStaticPath?.props ? { ...matchedStaticPath.props } : {};
|
|
return props;
|
|
}
|
|
function getParams(route, pathname) {
|
|
if (!route.params.length) return {};
|
|
const path = pathname.endsWith(".html") && !routeHasHtmlExtension(route) ? pathname.slice(0, -5) : pathname;
|
|
const allPatterns = [route, ...route.fallbackRoutes].map((r) => r.pattern);
|
|
const paramsMatch = allPatterns.map((pattern) => pattern.exec(path)).find((x) => x);
|
|
if (!paramsMatch) return {};
|
|
const params = {};
|
|
route.params.forEach((key, i) => {
|
|
if (key.startsWith("...")) {
|
|
params[key.slice(3)] = paramsMatch[i + 1] ? paramsMatch[i + 1] : void 0;
|
|
} else {
|
|
params[key] = paramsMatch[i + 1];
|
|
}
|
|
});
|
|
return params;
|
|
}
|
|
function validatePrerenderEndpointCollision(route, mod, params) {
|
|
if (route.type === "endpoint" && mod.getStaticPaths) {
|
|
const lastSegment = route.segments[route.segments.length - 1];
|
|
const paramValues = Object.values(params);
|
|
const lastParam = paramValues[paramValues.length - 1];
|
|
if (lastSegment.length === 1 && lastSegment[0].dynamic && lastParam === void 0) {
|
|
throw new AstroError({
|
|
...PrerenderDynamicEndpointPathCollide,
|
|
message: PrerenderDynamicEndpointPathCollide.message(route.route),
|
|
hint: PrerenderDynamicEndpointPathCollide.hint(route.component),
|
|
location: {
|
|
file: route.component
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
function getFunctionExpression(slot) {
|
|
if (!slot) return;
|
|
const expressions = slot?.expressions?.filter((e) => isRenderInstruction(e) === false);
|
|
if (expressions?.length !== 1) return;
|
|
return expressions[0];
|
|
}
|
|
class Slots {
|
|
#result;
|
|
#slots;
|
|
#logger;
|
|
constructor(result, slots, logger) {
|
|
this.#result = result;
|
|
this.#slots = slots;
|
|
this.#logger = logger;
|
|
if (slots) {
|
|
for (const key of Object.keys(slots)) {
|
|
if (this[key] !== void 0) {
|
|
throw new AstroError({
|
|
...ReservedSlotName,
|
|
message: ReservedSlotName.message(key)
|
|
});
|
|
}
|
|
Object.defineProperty(this, key, {
|
|
get() {
|
|
return true;
|
|
},
|
|
enumerable: true
|
|
});
|
|
}
|
|
}
|
|
}
|
|
has(name) {
|
|
if (!this.#slots) return false;
|
|
return Boolean(this.#slots[name]);
|
|
}
|
|
async render(name, args = []) {
|
|
if (!this.#slots || !this.has(name)) return;
|
|
const result = this.#result;
|
|
if (!Array.isArray(args)) {
|
|
this.#logger.warn(
|
|
null,
|
|
`Expected second parameter to be an array, received a ${typeof args}. If you're trying to pass an array as a single argument and getting unexpected results, make sure you're passing your array as an item of an array. Ex: Astro.slots.render('default', [["Hello", "World"]])`
|
|
);
|
|
} else if (args.length > 0) {
|
|
const slotValue = this.#slots[name];
|
|
const component = typeof slotValue === "function" ? await slotValue(result) : await slotValue;
|
|
const expression = getFunctionExpression(component);
|
|
if (expression) {
|
|
const slot = async () => typeof expression === "function" ? expression(...args) : expression;
|
|
return await renderSlotToString(result, slot).then((res) => {
|
|
return res;
|
|
});
|
|
}
|
|
if (typeof component === "function") {
|
|
return await renderJSX(result, component(...args)).then(
|
|
(res) => res != null ? String(res) : res
|
|
);
|
|
}
|
|
}
|
|
const content = await renderSlotToString(result, this.#slots[name]);
|
|
const outHTML = chunkToString(result, content);
|
|
return outHTML;
|
|
}
|
|
}
|
|
|
|
function sequence(...handlers) {
|
|
const filtered = handlers.filter((h) => !!h);
|
|
const length = filtered.length;
|
|
if (!length) {
|
|
return defineMiddleware((_context, next) => {
|
|
return next();
|
|
});
|
|
}
|
|
return defineMiddleware((context, next) => {
|
|
let carriedPayload = void 0;
|
|
return applyHandle(0, context);
|
|
function applyHandle(i, handleContext) {
|
|
const handle = filtered[i];
|
|
const result = handle(handleContext, async (payload) => {
|
|
if (i < length - 1) {
|
|
if (payload) {
|
|
let newRequest;
|
|
if (payload instanceof Request) {
|
|
newRequest = payload;
|
|
} else if (payload instanceof URL) {
|
|
newRequest = new Request(payload, handleContext.request.clone());
|
|
} else {
|
|
newRequest = new Request(
|
|
new URL(payload, handleContext.url.origin),
|
|
handleContext.request.clone()
|
|
);
|
|
}
|
|
const oldPathname = handleContext.url.pathname;
|
|
const pipeline = Reflect.get(handleContext, pipelineSymbol);
|
|
const { routeData, pathname } = await pipeline.tryRewrite(
|
|
payload,
|
|
handleContext.request
|
|
);
|
|
if (pipeline.manifest.serverLike === true && handleContext.isPrerendered === false && routeData.prerender === true) {
|
|
throw new AstroError({
|
|
...ForbiddenRewrite,
|
|
message: ForbiddenRewrite.message(
|
|
handleContext.url.pathname,
|
|
pathname,
|
|
routeData.component
|
|
),
|
|
hint: ForbiddenRewrite.hint(routeData.component)
|
|
});
|
|
}
|
|
carriedPayload = payload;
|
|
handleContext.request = newRequest;
|
|
handleContext.url = new URL(newRequest.url);
|
|
handleContext.params = getParams(routeData, pathname);
|
|
handleContext.routePattern = routeData.route;
|
|
setOriginPathname(
|
|
handleContext.request,
|
|
oldPathname,
|
|
pipeline.manifest.trailingSlash,
|
|
pipeline.manifest.buildFormat
|
|
);
|
|
}
|
|
return applyHandle(i + 1, handleContext);
|
|
} else {
|
|
return next(payload ?? carriedPayload);
|
|
}
|
|
});
|
|
return result;
|
|
}
|
|
});
|
|
}
|
|
|
|
function isExternalURL(url) {
|
|
return url.startsWith("http://") || url.startsWith("https://") || url.startsWith("//");
|
|
}
|
|
function redirectIsExternal(redirect) {
|
|
if (typeof redirect === "string") {
|
|
return isExternalURL(redirect);
|
|
} else {
|
|
return isExternalURL(redirect.destination);
|
|
}
|
|
}
|
|
function computeRedirectStatus(method, redirect, redirectRoute) {
|
|
return redirectRoute && typeof redirect === "object" ? redirect.status : method === "GET" ? 301 : 308;
|
|
}
|
|
function resolveRedirectTarget(params, redirect, redirectRoute, trailingSlash) {
|
|
if (typeof redirectRoute !== "undefined") {
|
|
const generate = getRouteGenerator(redirectRoute.segments, trailingSlash);
|
|
return generate(params);
|
|
} else if (typeof redirect === "string") {
|
|
if (redirectIsExternal(redirect)) {
|
|
return redirect;
|
|
} else {
|
|
let target = redirect;
|
|
for (const param of Object.keys(params)) {
|
|
const paramValue = params[param];
|
|
target = target.replace(`[${param}]`, paramValue).replace(`[...${param}]`, paramValue);
|
|
}
|
|
return target;
|
|
}
|
|
} else if (typeof redirect === "undefined") {
|
|
return "/";
|
|
}
|
|
return redirect.destination;
|
|
}
|
|
async function renderRedirect(renderContext) {
|
|
const {
|
|
request: { method },
|
|
routeData
|
|
} = renderContext;
|
|
const { redirect, redirectRoute } = routeData;
|
|
const status = computeRedirectStatus(method, redirect, redirectRoute);
|
|
const headers = {
|
|
location: encodeURI(
|
|
resolveRedirectTarget(
|
|
renderContext.params,
|
|
redirect,
|
|
redirectRoute,
|
|
renderContext.pipeline.manifest.trailingSlash
|
|
)
|
|
)
|
|
};
|
|
if (redirect && redirectIsExternal(redirect)) {
|
|
if (typeof redirect === "string") {
|
|
return Response.redirect(redirect, status);
|
|
} else {
|
|
return Response.redirect(redirect.destination, status);
|
|
}
|
|
}
|
|
return new Response(null, { status, headers });
|
|
}
|
|
|
|
function matchRoute(pathname, manifest) {
|
|
if (isRoute404(pathname)) {
|
|
const errorRoute = manifest.routes.find((route) => isRoute404(route.route));
|
|
if (errorRoute) return errorRoute;
|
|
}
|
|
if (isRoute500(pathname)) {
|
|
const errorRoute = manifest.routes.find((route) => isRoute500(route.route));
|
|
if (errorRoute) return errorRoute;
|
|
}
|
|
return manifest.routes.find((route) => {
|
|
return route.pattern.test(pathname) || route.fallbackRoutes.some((fallbackRoute) => fallbackRoute.pattern.test(pathname));
|
|
});
|
|
}
|
|
function isRoute404or500(route) {
|
|
return isRoute404(route.route) || isRoute500(route.route);
|
|
}
|
|
function isRouteServerIsland(route) {
|
|
return route.component === SERVER_ISLAND_COMPONENT;
|
|
}
|
|
function isRouteExternalRedirect(route) {
|
|
return !!(route.type === "redirect" && route.redirect && redirectIsExternal(route.redirect));
|
|
}
|
|
|
|
function defaultSetHeaders(options) {
|
|
const headers = new Headers();
|
|
const directives = [];
|
|
if (options.maxAge !== void 0) {
|
|
directives.push(`max-age=${options.maxAge}`);
|
|
}
|
|
if (options.swr !== void 0) {
|
|
directives.push(`stale-while-revalidate=${options.swr}`);
|
|
}
|
|
if (directives.length > 0) {
|
|
headers.set("CDN-Cache-Control", directives.join(", "));
|
|
}
|
|
if (options.tags && options.tags.length > 0) {
|
|
headers.set("Cache-Tag", options.tags.join(", "));
|
|
}
|
|
if (options.lastModified) {
|
|
headers.set("Last-Modified", options.lastModified.toUTCString());
|
|
}
|
|
if (options.etag) {
|
|
headers.set("ETag", options.etag);
|
|
}
|
|
return headers;
|
|
}
|
|
function isLiveDataEntry(value) {
|
|
return value != null && typeof value === "object" && "id" in value && "data" in value && "cacheHint" in value;
|
|
}
|
|
|
|
const APPLY_HEADERS = /* @__PURE__ */ Symbol.for("astro:cache:apply");
|
|
const IS_ACTIVE = /* @__PURE__ */ Symbol.for("astro:cache:active");
|
|
class AstroCache {
|
|
#options = {};
|
|
#tags = /* @__PURE__ */ new Set();
|
|
#disabled = false;
|
|
#provider;
|
|
enabled = true;
|
|
constructor(provider) {
|
|
this.#provider = provider;
|
|
}
|
|
set(input) {
|
|
if (input === false) {
|
|
this.#disabled = true;
|
|
this.#tags.clear();
|
|
this.#options = {};
|
|
return;
|
|
}
|
|
this.#disabled = false;
|
|
let options;
|
|
if (isLiveDataEntry(input)) {
|
|
if (!input.cacheHint) return;
|
|
options = input.cacheHint;
|
|
} else {
|
|
options = input;
|
|
}
|
|
if ("maxAge" in options && options.maxAge !== void 0) this.#options.maxAge = options.maxAge;
|
|
if ("swr" in options && options.swr !== void 0)
|
|
this.#options.swr = options.swr;
|
|
if ("etag" in options && options.etag !== void 0)
|
|
this.#options.etag = options.etag;
|
|
if (options.lastModified !== void 0) {
|
|
if (!this.#options.lastModified || options.lastModified > this.#options.lastModified) {
|
|
this.#options.lastModified = options.lastModified;
|
|
}
|
|
}
|
|
if (options.tags) {
|
|
for (const tag of options.tags) this.#tags.add(tag);
|
|
}
|
|
}
|
|
get tags() {
|
|
return [...this.#tags];
|
|
}
|
|
/**
|
|
* Get the current cache options (read-only snapshot).
|
|
* Includes all accumulated options: maxAge, swr, tags, etag, lastModified.
|
|
*/
|
|
get options() {
|
|
return {
|
|
...this.#options,
|
|
tags: this.tags
|
|
};
|
|
}
|
|
async invalidate(input) {
|
|
if (!this.#provider) {
|
|
throw new AstroError(CacheNotEnabled);
|
|
}
|
|
let options;
|
|
if (isLiveDataEntry(input)) {
|
|
options = { tags: input.cacheHint?.tags ?? [] };
|
|
} else {
|
|
options = input;
|
|
}
|
|
return this.#provider.invalidate(options);
|
|
}
|
|
/** @internal */
|
|
[APPLY_HEADERS](response) {
|
|
if (this.#disabled) return;
|
|
const finalOptions = { ...this.#options, tags: this.tags };
|
|
if (finalOptions.maxAge === void 0 && !finalOptions.tags?.length) return;
|
|
const headers = this.#provider?.setHeaders?.(finalOptions) ?? defaultSetHeaders(finalOptions);
|
|
for (const [key, value] of headers) {
|
|
response.headers.set(key, value);
|
|
}
|
|
}
|
|
/** @internal */
|
|
get [IS_ACTIVE]() {
|
|
return !this.#disabled && (this.#options.maxAge !== void 0 || this.#tags.size > 0);
|
|
}
|
|
}
|
|
function applyCacheHeaders(cache, response) {
|
|
if (APPLY_HEADERS in cache) {
|
|
cache[APPLY_HEADERS](response);
|
|
}
|
|
}
|
|
|
|
const ROUTE_DYNAMIC_SPLIT = /\[(.+?\(.+?\)|.+?)\]/;
|
|
const ROUTE_SPREAD = /^\.{3}.+$/;
|
|
function getParts(part, file) {
|
|
const result = [];
|
|
part.split(ROUTE_DYNAMIC_SPLIT).map((str, i) => {
|
|
if (!str) return;
|
|
const dynamic = i % 2 === 1;
|
|
const [, content] = dynamic ? /([^(]+)$/.exec(str) || [null, null] : [null, str];
|
|
if (!content || dynamic && !/^(?:\.\.\.)?[\w$]+$/.test(content)) {
|
|
throw new Error(`Invalid route ${file} \u2014 parameter name must match /^[a-zA-Z0-9_$]+$/`);
|
|
}
|
|
result.push({
|
|
content,
|
|
dynamic,
|
|
spread: dynamic && ROUTE_SPREAD.test(content)
|
|
});
|
|
});
|
|
return result;
|
|
}
|
|
|
|
function routeComparator(a, b) {
|
|
const commonLength = Math.min(a.segments.length, b.segments.length);
|
|
for (let index = 0; index < commonLength; index++) {
|
|
const aSegment = a.segments[index];
|
|
const bSegment = b.segments[index];
|
|
const aIsStatic = aSegment.every((part) => !part.dynamic && !part.spread);
|
|
const bIsStatic = bSegment.every((part) => !part.dynamic && !part.spread);
|
|
if (aIsStatic && bIsStatic) {
|
|
const aContent = aSegment.map((part) => part.content).join("");
|
|
const bContent = bSegment.map((part) => part.content).join("");
|
|
if (aContent !== bContent) {
|
|
return aContent.localeCompare(bContent);
|
|
}
|
|
}
|
|
if (aIsStatic !== bIsStatic) {
|
|
return aIsStatic ? -1 : 1;
|
|
}
|
|
const aAllDynamic = aSegment.every((part) => part.dynamic);
|
|
const bAllDynamic = bSegment.every((part) => part.dynamic);
|
|
if (aAllDynamic !== bAllDynamic) {
|
|
return aAllDynamic ? 1 : -1;
|
|
}
|
|
const aHasSpread = aSegment.some((part) => part.spread);
|
|
const bHasSpread = bSegment.some((part) => part.spread);
|
|
if (aHasSpread !== bHasSpread) {
|
|
return aHasSpread ? 1 : -1;
|
|
}
|
|
}
|
|
const aLength = a.segments.length;
|
|
const bLength = b.segments.length;
|
|
if (aLength !== bLength) {
|
|
const aEndsInRest = a.segments.at(-1)?.some((part) => part.spread);
|
|
const bEndsInRest = b.segments.at(-1)?.some((part) => part.spread);
|
|
if (aEndsInRest !== bEndsInRest && Math.abs(aLength - bLength) === 1) {
|
|
if (aLength > bLength && aEndsInRest) {
|
|
return 1;
|
|
}
|
|
if (bLength > aLength && bEndsInRest) {
|
|
return -1;
|
|
}
|
|
}
|
|
return aLength > bLength ? -1 : 1;
|
|
}
|
|
if (a.type === "endpoint" !== (b.type === "endpoint")) {
|
|
return a.type === "endpoint" ? -1 : 1;
|
|
}
|
|
return a.route.localeCompare(b.route);
|
|
}
|
|
|
|
function compileCacheRoutes(routes, base, trailingSlash) {
|
|
const compiled = Object.entries(routes).map(([path, options]) => {
|
|
const segments = removeLeadingForwardSlash(path).split("/").filter(Boolean).map((s) => getParts(s, path));
|
|
const pattern = getPattern(segments, base, trailingSlash);
|
|
return { pattern, options, segments, route: path };
|
|
});
|
|
compiled.sort(
|
|
(a, b) => routeComparator(
|
|
{ segments: a.segments, route: a.route, type: "page" },
|
|
{ segments: b.segments, route: b.route, type: "page" }
|
|
)
|
|
);
|
|
return compiled;
|
|
}
|
|
function matchCacheRoute(pathname, compiledRoutes) {
|
|
for (const route of compiledRoutes) {
|
|
if (route.pattern.test(pathname)) return route.options;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
const suspectProtoRx = /"(?:_|\\u0{2}5[Ff]){2}(?:p|\\u0{2}70)(?:r|\\u0{2}72)(?:o|\\u0{2}6[Ff])(?:t|\\u0{2}74)(?:o|\\u0{2}6[Ff])(?:_|\\u0{2}5[Ff]){2}"\s*:/;
|
|
const suspectConstructorRx = /"(?:c|\\u0063)(?:o|\\u006[Ff])(?:n|\\u006[Ee])(?:s|\\u0073)(?:t|\\u0074)(?:r|\\u0072)(?:u|\\u0075)(?:c|\\u0063)(?:t|\\u0074)(?:o|\\u006[Ff])(?:r|\\u0072)"\s*:/;
|
|
const JsonSigRx = /^\s*["[{]|^\s*-?\d{1,16}(\.\d{1,17})?([Ee][+-]?\d+)?\s*$/;
|
|
function jsonParseTransform(key, value) {
|
|
if (key === "__proto__" || key === "constructor" && value && typeof value === "object" && "prototype" in value) {
|
|
warnKeyDropped(key);
|
|
return;
|
|
}
|
|
return value;
|
|
}
|
|
function warnKeyDropped(key) {
|
|
console.warn(`[destr] Dropping "${key}" key to prevent prototype pollution.`);
|
|
}
|
|
function destr(value, options = {}) {
|
|
if (typeof value !== "string") {
|
|
return value;
|
|
}
|
|
if (value[0] === '"' && value[value.length - 1] === '"' && value.indexOf("\\") === -1) {
|
|
return value.slice(1, -1);
|
|
}
|
|
const _value = value.trim();
|
|
if (_value.length <= 9) {
|
|
switch (_value.toLowerCase()) {
|
|
case "true": {
|
|
return true;
|
|
}
|
|
case "false": {
|
|
return false;
|
|
}
|
|
case "undefined": {
|
|
return void 0;
|
|
}
|
|
case "null": {
|
|
return null;
|
|
}
|
|
case "nan": {
|
|
return Number.NaN;
|
|
}
|
|
case "infinity": {
|
|
return Number.POSITIVE_INFINITY;
|
|
}
|
|
case "-infinity": {
|
|
return Number.NEGATIVE_INFINITY;
|
|
}
|
|
}
|
|
}
|
|
if (!JsonSigRx.test(value)) {
|
|
if (options.strict) {
|
|
throw new SyntaxError("[destr] Invalid JSON");
|
|
}
|
|
return value;
|
|
}
|
|
try {
|
|
if (suspectProtoRx.test(value) || suspectConstructorRx.test(value)) {
|
|
if (options.strict) {
|
|
throw new Error("[destr] Possible prototype pollution");
|
|
}
|
|
return JSON.parse(value, jsonParseTransform);
|
|
}
|
|
return JSON.parse(value);
|
|
} catch (error) {
|
|
if (options.strict) {
|
|
throw error;
|
|
}
|
|
return value;
|
|
}
|
|
}
|
|
|
|
function wrapToPromise(value) {
|
|
if (!value || typeof value.then !== "function") {
|
|
return Promise.resolve(value);
|
|
}
|
|
return value;
|
|
}
|
|
function asyncCall(function_, ...arguments_) {
|
|
try {
|
|
return wrapToPromise(function_(...arguments_));
|
|
} catch (error) {
|
|
return Promise.reject(error);
|
|
}
|
|
}
|
|
function isPrimitive(value) {
|
|
const type = typeof value;
|
|
return value === null || type !== "object" && type !== "function";
|
|
}
|
|
function isPureObject(value) {
|
|
const proto = Object.getPrototypeOf(value);
|
|
return !proto || proto.isPrototypeOf(Object);
|
|
}
|
|
function stringify$1(value) {
|
|
if (isPrimitive(value)) {
|
|
return String(value);
|
|
}
|
|
if (isPureObject(value) || Array.isArray(value)) {
|
|
return JSON.stringify(value);
|
|
}
|
|
if (typeof value.toJSON === "function") {
|
|
return stringify$1(value.toJSON());
|
|
}
|
|
throw new Error("[unstorage] Cannot stringify value!");
|
|
}
|
|
const BASE64_PREFIX = "base64:";
|
|
function serializeRaw(value) {
|
|
if (typeof value === "string") {
|
|
return value;
|
|
}
|
|
return BASE64_PREFIX + base64Encode(value);
|
|
}
|
|
function deserializeRaw(value) {
|
|
if (typeof value !== "string") {
|
|
return value;
|
|
}
|
|
if (!value.startsWith(BASE64_PREFIX)) {
|
|
return value;
|
|
}
|
|
return base64Decode(value.slice(BASE64_PREFIX.length));
|
|
}
|
|
function base64Decode(input) {
|
|
if (globalThis.Buffer) {
|
|
return Buffer.from(input, "base64");
|
|
}
|
|
return Uint8Array.from(
|
|
globalThis.atob(input),
|
|
(c) => c.codePointAt(0)
|
|
);
|
|
}
|
|
function base64Encode(input) {
|
|
if (globalThis.Buffer) {
|
|
return Buffer.from(input).toString("base64");
|
|
}
|
|
return globalThis.btoa(String.fromCodePoint(...input));
|
|
}
|
|
function normalizeKey(key) {
|
|
if (!key) {
|
|
return "";
|
|
}
|
|
return key.split("?")[0]?.replace(/[/\\]/g, ":").replace(/:+/g, ":").replace(/^:|:$/g, "") || "";
|
|
}
|
|
function joinKeys(...keys) {
|
|
return normalizeKey(keys.join(":"));
|
|
}
|
|
function normalizeBaseKey(base) {
|
|
base = normalizeKey(base);
|
|
return base ? base + ":" : "";
|
|
}
|
|
function filterKeyByDepth(key, depth) {
|
|
if (depth === void 0) {
|
|
return true;
|
|
}
|
|
let substrCount = 0;
|
|
let index = key.indexOf(":");
|
|
while (index > -1) {
|
|
substrCount++;
|
|
index = key.indexOf(":", index + 1);
|
|
}
|
|
return substrCount <= depth;
|
|
}
|
|
function filterKeyByBase(key, base) {
|
|
if (base) {
|
|
return key.startsWith(base) && key[key.length - 1] !== "$";
|
|
}
|
|
return key[key.length - 1] !== "$";
|
|
}
|
|
|
|
function defineDriver(factory) {
|
|
return factory;
|
|
}
|
|
|
|
const DRIVER_NAME = "memory";
|
|
const memory = defineDriver(() => {
|
|
const data = /* @__PURE__ */ new Map();
|
|
return {
|
|
name: DRIVER_NAME,
|
|
getInstance: () => data,
|
|
hasItem(key) {
|
|
return data.has(key);
|
|
},
|
|
getItem(key) {
|
|
return data.get(key) ?? null;
|
|
},
|
|
getItemRaw(key) {
|
|
return data.get(key) ?? null;
|
|
},
|
|
setItem(key, value) {
|
|
data.set(key, value);
|
|
},
|
|
setItemRaw(key, value) {
|
|
data.set(key, value);
|
|
},
|
|
removeItem(key) {
|
|
data.delete(key);
|
|
},
|
|
getKeys() {
|
|
return [...data.keys()];
|
|
},
|
|
clear() {
|
|
data.clear();
|
|
},
|
|
dispose() {
|
|
data.clear();
|
|
}
|
|
};
|
|
});
|
|
|
|
function createStorage(options = {}) {
|
|
const context = {
|
|
mounts: { "": options.driver || memory() },
|
|
mountpoints: [""],
|
|
watching: false,
|
|
watchListeners: [],
|
|
unwatch: {}
|
|
};
|
|
const getMount = (key) => {
|
|
for (const base of context.mountpoints) {
|
|
if (key.startsWith(base)) {
|
|
return {
|
|
base,
|
|
relativeKey: key.slice(base.length),
|
|
driver: context.mounts[base]
|
|
};
|
|
}
|
|
}
|
|
return {
|
|
base: "",
|
|
relativeKey: key,
|
|
driver: context.mounts[""]
|
|
};
|
|
};
|
|
const getMounts = (base, includeParent) => {
|
|
return context.mountpoints.filter(
|
|
(mountpoint) => mountpoint.startsWith(base) || includeParent && base.startsWith(mountpoint)
|
|
).map((mountpoint) => ({
|
|
relativeBase: base.length > mountpoint.length ? base.slice(mountpoint.length) : void 0,
|
|
mountpoint,
|
|
driver: context.mounts[mountpoint]
|
|
}));
|
|
};
|
|
const onChange = (event, key) => {
|
|
if (!context.watching) {
|
|
return;
|
|
}
|
|
key = normalizeKey(key);
|
|
for (const listener of context.watchListeners) {
|
|
listener(event, key);
|
|
}
|
|
};
|
|
const startWatch = async () => {
|
|
if (context.watching) {
|
|
return;
|
|
}
|
|
context.watching = true;
|
|
for (const mountpoint in context.mounts) {
|
|
context.unwatch[mountpoint] = await watch(
|
|
context.mounts[mountpoint],
|
|
onChange,
|
|
mountpoint
|
|
);
|
|
}
|
|
};
|
|
const stopWatch = async () => {
|
|
if (!context.watching) {
|
|
return;
|
|
}
|
|
for (const mountpoint in context.unwatch) {
|
|
await context.unwatch[mountpoint]();
|
|
}
|
|
context.unwatch = {};
|
|
context.watching = false;
|
|
};
|
|
const runBatch = (items, commonOptions, cb) => {
|
|
const batches = /* @__PURE__ */ new Map();
|
|
const getBatch = (mount) => {
|
|
let batch = batches.get(mount.base);
|
|
if (!batch) {
|
|
batch = {
|
|
driver: mount.driver,
|
|
base: mount.base,
|
|
items: []
|
|
};
|
|
batches.set(mount.base, batch);
|
|
}
|
|
return batch;
|
|
};
|
|
for (const item of items) {
|
|
const isStringItem = typeof item === "string";
|
|
const key = normalizeKey(isStringItem ? item : item.key);
|
|
const value = isStringItem ? void 0 : item.value;
|
|
const options2 = isStringItem || !item.options ? commonOptions : { ...commonOptions, ...item.options };
|
|
const mount = getMount(key);
|
|
getBatch(mount).items.push({
|
|
key,
|
|
value,
|
|
relativeKey: mount.relativeKey,
|
|
options: options2
|
|
});
|
|
}
|
|
return Promise.all([...batches.values()].map((batch) => cb(batch))).then(
|
|
(r) => r.flat()
|
|
);
|
|
};
|
|
const storage = {
|
|
// Item
|
|
hasItem(key, opts = {}) {
|
|
key = normalizeKey(key);
|
|
const { relativeKey, driver } = getMount(key);
|
|
return asyncCall(driver.hasItem, relativeKey, opts);
|
|
},
|
|
getItem(key, opts = {}) {
|
|
key = normalizeKey(key);
|
|
const { relativeKey, driver } = getMount(key);
|
|
return asyncCall(driver.getItem, relativeKey, opts).then(
|
|
(value) => destr(value)
|
|
);
|
|
},
|
|
getItems(items, commonOptions = {}) {
|
|
return runBatch(items, commonOptions, (batch) => {
|
|
if (batch.driver.getItems) {
|
|
return asyncCall(
|
|
batch.driver.getItems,
|
|
batch.items.map((item) => ({
|
|
key: item.relativeKey,
|
|
options: item.options
|
|
})),
|
|
commonOptions
|
|
).then(
|
|
(r) => r.map((item) => ({
|
|
key: joinKeys(batch.base, item.key),
|
|
value: destr(item.value)
|
|
}))
|
|
);
|
|
}
|
|
return Promise.all(
|
|
batch.items.map((item) => {
|
|
return asyncCall(
|
|
batch.driver.getItem,
|
|
item.relativeKey,
|
|
item.options
|
|
).then((value) => ({
|
|
key: item.key,
|
|
value: destr(value)
|
|
}));
|
|
})
|
|
);
|
|
});
|
|
},
|
|
getItemRaw(key, opts = {}) {
|
|
key = normalizeKey(key);
|
|
const { relativeKey, driver } = getMount(key);
|
|
if (driver.getItemRaw) {
|
|
return asyncCall(driver.getItemRaw, relativeKey, opts);
|
|
}
|
|
return asyncCall(driver.getItem, relativeKey, opts).then(
|
|
(value) => deserializeRaw(value)
|
|
);
|
|
},
|
|
async setItem(key, value, opts = {}) {
|
|
if (value === void 0) {
|
|
return storage.removeItem(key);
|
|
}
|
|
key = normalizeKey(key);
|
|
const { relativeKey, driver } = getMount(key);
|
|
if (!driver.setItem) {
|
|
return;
|
|
}
|
|
await asyncCall(driver.setItem, relativeKey, stringify$1(value), opts);
|
|
if (!driver.watch) {
|
|
onChange("update", key);
|
|
}
|
|
},
|
|
async setItems(items, commonOptions) {
|
|
await runBatch(items, commonOptions, async (batch) => {
|
|
if (batch.driver.setItems) {
|
|
return asyncCall(
|
|
batch.driver.setItems,
|
|
batch.items.map((item) => ({
|
|
key: item.relativeKey,
|
|
value: stringify$1(item.value),
|
|
options: item.options
|
|
})),
|
|
commonOptions
|
|
);
|
|
}
|
|
if (!batch.driver.setItem) {
|
|
return;
|
|
}
|
|
await Promise.all(
|
|
batch.items.map((item) => {
|
|
return asyncCall(
|
|
batch.driver.setItem,
|
|
item.relativeKey,
|
|
stringify$1(item.value),
|
|
item.options
|
|
);
|
|
})
|
|
);
|
|
});
|
|
},
|
|
async setItemRaw(key, value, opts = {}) {
|
|
if (value === void 0) {
|
|
return storage.removeItem(key, opts);
|
|
}
|
|
key = normalizeKey(key);
|
|
const { relativeKey, driver } = getMount(key);
|
|
if (driver.setItemRaw) {
|
|
await asyncCall(driver.setItemRaw, relativeKey, value, opts);
|
|
} else if (driver.setItem) {
|
|
await asyncCall(driver.setItem, relativeKey, serializeRaw(value), opts);
|
|
} else {
|
|
return;
|
|
}
|
|
if (!driver.watch) {
|
|
onChange("update", key);
|
|
}
|
|
},
|
|
async removeItem(key, opts = {}) {
|
|
if (typeof opts === "boolean") {
|
|
opts = { removeMeta: opts };
|
|
}
|
|
key = normalizeKey(key);
|
|
const { relativeKey, driver } = getMount(key);
|
|
if (!driver.removeItem) {
|
|
return;
|
|
}
|
|
await asyncCall(driver.removeItem, relativeKey, opts);
|
|
if (opts.removeMeta || opts.removeMata) {
|
|
await asyncCall(driver.removeItem, relativeKey + "$", opts);
|
|
}
|
|
if (!driver.watch) {
|
|
onChange("remove", key);
|
|
}
|
|
},
|
|
// Meta
|
|
async getMeta(key, opts = {}) {
|
|
if (typeof opts === "boolean") {
|
|
opts = { nativeOnly: opts };
|
|
}
|
|
key = normalizeKey(key);
|
|
const { relativeKey, driver } = getMount(key);
|
|
const meta = /* @__PURE__ */ Object.create(null);
|
|
if (driver.getMeta) {
|
|
Object.assign(meta, await asyncCall(driver.getMeta, relativeKey, opts));
|
|
}
|
|
if (!opts.nativeOnly) {
|
|
const value = await asyncCall(
|
|
driver.getItem,
|
|
relativeKey + "$",
|
|
opts
|
|
).then((value_) => destr(value_));
|
|
if (value && typeof value === "object") {
|
|
if (typeof value.atime === "string") {
|
|
value.atime = new Date(value.atime);
|
|
}
|
|
if (typeof value.mtime === "string") {
|
|
value.mtime = new Date(value.mtime);
|
|
}
|
|
Object.assign(meta, value);
|
|
}
|
|
}
|
|
return meta;
|
|
},
|
|
setMeta(key, value, opts = {}) {
|
|
return this.setItem(key + "$", value, opts);
|
|
},
|
|
removeMeta(key, opts = {}) {
|
|
return this.removeItem(key + "$", opts);
|
|
},
|
|
// Keys
|
|
async getKeys(base, opts = {}) {
|
|
base = normalizeBaseKey(base);
|
|
const mounts = getMounts(base, true);
|
|
let maskedMounts = [];
|
|
const allKeys = [];
|
|
let allMountsSupportMaxDepth = true;
|
|
for (const mount of mounts) {
|
|
if (!mount.driver.flags?.maxDepth) {
|
|
allMountsSupportMaxDepth = false;
|
|
}
|
|
const rawKeys = await asyncCall(
|
|
mount.driver.getKeys,
|
|
mount.relativeBase,
|
|
opts
|
|
);
|
|
for (const key of rawKeys) {
|
|
const fullKey = mount.mountpoint + normalizeKey(key);
|
|
if (!maskedMounts.some((p) => fullKey.startsWith(p))) {
|
|
allKeys.push(fullKey);
|
|
}
|
|
}
|
|
maskedMounts = [
|
|
mount.mountpoint,
|
|
...maskedMounts.filter((p) => !p.startsWith(mount.mountpoint))
|
|
];
|
|
}
|
|
const shouldFilterByDepth = opts.maxDepth !== void 0 && !allMountsSupportMaxDepth;
|
|
return allKeys.filter(
|
|
(key) => (!shouldFilterByDepth || filterKeyByDepth(key, opts.maxDepth)) && filterKeyByBase(key, base)
|
|
);
|
|
},
|
|
// Utils
|
|
async clear(base, opts = {}) {
|
|
base = normalizeBaseKey(base);
|
|
await Promise.all(
|
|
getMounts(base, false).map(async (m) => {
|
|
if (m.driver.clear) {
|
|
return asyncCall(m.driver.clear, m.relativeBase, opts);
|
|
}
|
|
if (m.driver.removeItem) {
|
|
const keys = await m.driver.getKeys(m.relativeBase || "", opts);
|
|
return Promise.all(
|
|
keys.map((key) => m.driver.removeItem(key, opts))
|
|
);
|
|
}
|
|
})
|
|
);
|
|
},
|
|
async dispose() {
|
|
await Promise.all(
|
|
Object.values(context.mounts).map((driver) => dispose(driver))
|
|
);
|
|
},
|
|
async watch(callback) {
|
|
await startWatch();
|
|
context.watchListeners.push(callback);
|
|
return async () => {
|
|
context.watchListeners = context.watchListeners.filter(
|
|
(listener) => listener !== callback
|
|
);
|
|
if (context.watchListeners.length === 0) {
|
|
await stopWatch();
|
|
}
|
|
};
|
|
},
|
|
async unwatch() {
|
|
context.watchListeners = [];
|
|
await stopWatch();
|
|
},
|
|
// Mount
|
|
mount(base, driver) {
|
|
base = normalizeBaseKey(base);
|
|
if (base && context.mounts[base]) {
|
|
throw new Error(`already mounted at ${base}`);
|
|
}
|
|
if (base) {
|
|
context.mountpoints.push(base);
|
|
context.mountpoints.sort((a, b) => b.length - a.length);
|
|
}
|
|
context.mounts[base] = driver;
|
|
if (context.watching) {
|
|
Promise.resolve(watch(driver, onChange, base)).then((unwatcher) => {
|
|
context.unwatch[base] = unwatcher;
|
|
}).catch(console.error);
|
|
}
|
|
return storage;
|
|
},
|
|
async unmount(base, _dispose = true) {
|
|
base = normalizeBaseKey(base);
|
|
if (!base || !context.mounts[base]) {
|
|
return;
|
|
}
|
|
if (context.watching && base in context.unwatch) {
|
|
context.unwatch[base]?.();
|
|
delete context.unwatch[base];
|
|
}
|
|
if (_dispose) {
|
|
await dispose(context.mounts[base]);
|
|
}
|
|
context.mountpoints = context.mountpoints.filter((key) => key !== base);
|
|
delete context.mounts[base];
|
|
},
|
|
getMount(key = "") {
|
|
key = normalizeKey(key) + ":";
|
|
const m = getMount(key);
|
|
return {
|
|
driver: m.driver,
|
|
base: m.base
|
|
};
|
|
},
|
|
getMounts(base = "", opts = {}) {
|
|
base = normalizeKey(base);
|
|
const mounts = getMounts(base, opts.parents);
|
|
return mounts.map((m) => ({
|
|
driver: m.driver,
|
|
base: m.mountpoint
|
|
}));
|
|
},
|
|
// Aliases
|
|
keys: (base, opts = {}) => storage.getKeys(base, opts),
|
|
get: (key, opts = {}) => storage.getItem(key, opts),
|
|
set: (key, value, opts = {}) => storage.setItem(key, value, opts),
|
|
has: (key, opts = {}) => storage.hasItem(key, opts),
|
|
del: (key, opts = {}) => storage.removeItem(key, opts),
|
|
remove: (key, opts = {}) => storage.removeItem(key, opts)
|
|
};
|
|
return storage;
|
|
}
|
|
function watch(driver, onChange, base) {
|
|
return driver.watch ? driver.watch((event, key) => onChange(event, base + key)) : () => {
|
|
};
|
|
}
|
|
async function dispose(driver) {
|
|
if (typeof driver.dispose === "function") {
|
|
await asyncCall(driver.dispose);
|
|
}
|
|
}
|
|
|
|
const PERSIST_SYMBOL = /* @__PURE__ */ Symbol();
|
|
const DEFAULT_COOKIE_NAME = "astro-session";
|
|
const VALID_COOKIE_REGEX = /^[\w-]+$/;
|
|
const unflatten = (parsed, _) => {
|
|
return unflatten$1(parsed, {
|
|
URL: (href) => new URL(href)
|
|
});
|
|
};
|
|
const stringify = (data, _) => {
|
|
return stringify$2(data, {
|
|
// Support URL objects
|
|
URL: (val) => val instanceof URL && val.href
|
|
});
|
|
};
|
|
class AstroSession {
|
|
// The cookies object.
|
|
#cookies;
|
|
// The session configuration.
|
|
#config;
|
|
// The cookie config
|
|
#cookieConfig;
|
|
// The cookie name
|
|
#cookieName;
|
|
// The unstorage object for the session driver.
|
|
#storage;
|
|
#data;
|
|
// The session ID. A v4 UUID.
|
|
#sessionID;
|
|
// Sessions to destroy. Needed because we won't have the old session ID after it's destroyed locally.
|
|
#toDestroy = /* @__PURE__ */ new Set();
|
|
// Session keys to delete. Used for partial data sets to avoid overwriting the deleted value.
|
|
#toDelete = /* @__PURE__ */ new Set();
|
|
// Whether the session is dirty and needs to be saved.
|
|
#dirty = false;
|
|
// Whether the session cookie has been set.
|
|
#cookieSet = false;
|
|
// Whether the session ID was sourced from a client cookie rather than freshly generated.
|
|
#sessionIDFromCookie = false;
|
|
// The local data is "partial" if it has not been loaded from storage yet and only
|
|
// contains values that have been set or deleted in-memory locally.
|
|
// We do this to avoid the need to block on loading data when it is only being set.
|
|
// When we load the data from storage, we need to merge it with the local partial data,
|
|
// preserving in-memory changes and deletions.
|
|
#partial = true;
|
|
// The driver factory function provided by the pipeline
|
|
#driverFactory;
|
|
static #sharedStorage = /* @__PURE__ */ new Map();
|
|
constructor({
|
|
cookies,
|
|
config,
|
|
runtimeMode,
|
|
driverFactory,
|
|
mockStorage
|
|
}) {
|
|
if (!config) {
|
|
throw new AstroError({
|
|
...SessionStorageInitError,
|
|
message: SessionStorageInitError.message(
|
|
"No driver was defined in the session configuration and the adapter did not provide a default driver."
|
|
)
|
|
});
|
|
}
|
|
this.#cookies = cookies;
|
|
this.#driverFactory = driverFactory;
|
|
const { cookie: cookieConfig = DEFAULT_COOKIE_NAME, ...configRest } = config;
|
|
let cookieConfigObject;
|
|
if (typeof cookieConfig === "object") {
|
|
const { name = DEFAULT_COOKIE_NAME, ...rest } = cookieConfig;
|
|
this.#cookieName = name;
|
|
cookieConfigObject = rest;
|
|
} else {
|
|
this.#cookieName = cookieConfig || DEFAULT_COOKIE_NAME;
|
|
}
|
|
this.#cookieConfig = {
|
|
sameSite: "lax",
|
|
secure: runtimeMode === "production",
|
|
path: "/",
|
|
...cookieConfigObject,
|
|
httpOnly: true
|
|
};
|
|
this.#config = configRest;
|
|
if (mockStorage) {
|
|
this.#storage = mockStorage;
|
|
}
|
|
}
|
|
/**
|
|
* Gets a session value. Returns `undefined` if the session or value does not exist.
|
|
*/
|
|
async get(key) {
|
|
return (await this.#ensureData()).get(key)?.data;
|
|
}
|
|
/**
|
|
* Checks if a session value exists.
|
|
*/
|
|
async has(key) {
|
|
return (await this.#ensureData()).has(key);
|
|
}
|
|
/**
|
|
* Gets all session values.
|
|
*/
|
|
async keys() {
|
|
return (await this.#ensureData()).keys();
|
|
}
|
|
/**
|
|
* Gets all session values.
|
|
*/
|
|
async values() {
|
|
return [...(await this.#ensureData()).values()].map((entry) => entry.data);
|
|
}
|
|
/**
|
|
* Gets all session entries.
|
|
*/
|
|
async entries() {
|
|
return [...(await this.#ensureData()).entries()].map(([key, entry]) => [key, entry.data]);
|
|
}
|
|
/**
|
|
* Deletes a session value.
|
|
*/
|
|
delete(key) {
|
|
this.#data?.delete(key);
|
|
if (this.#partial) {
|
|
this.#toDelete.add(key);
|
|
}
|
|
this.#dirty = true;
|
|
}
|
|
/**
|
|
* Sets a session value. The session is created if it does not exist.
|
|
*/
|
|
set(key, value, { ttl } = {}) {
|
|
if (!key) {
|
|
throw new AstroError({
|
|
...SessionStorageSaveError,
|
|
message: "The session key was not provided."
|
|
});
|
|
}
|
|
let cloned;
|
|
try {
|
|
cloned = unflatten(JSON.parse(stringify(value)));
|
|
} catch (err) {
|
|
throw new AstroError(
|
|
{
|
|
...SessionStorageSaveError,
|
|
message: `The session data for ${key} could not be serialized.`,
|
|
hint: "See the devalue library for all supported types: https://github.com/rich-harris/devalue"
|
|
},
|
|
{ cause: err }
|
|
);
|
|
}
|
|
if (!this.#cookieSet) {
|
|
this.#setCookie();
|
|
this.#cookieSet = true;
|
|
}
|
|
this.#data ??= /* @__PURE__ */ new Map();
|
|
const lifetime = ttl ?? this.#config.ttl;
|
|
const expires = typeof lifetime === "number" ? Date.now() + lifetime * 1e3 : lifetime;
|
|
this.#data.set(key, {
|
|
data: cloned,
|
|
expires
|
|
});
|
|
this.#dirty = true;
|
|
}
|
|
/**
|
|
* Destroys the session, clearing the cookie and storage if it exists.
|
|
*/
|
|
destroy() {
|
|
const sessionId = this.#sessionID ?? this.#cookies.get(this.#cookieName)?.value;
|
|
if (sessionId) {
|
|
this.#toDestroy.add(sessionId);
|
|
}
|
|
this.#cookies.delete(this.#cookieName, this.#cookieConfig);
|
|
this.#sessionID = void 0;
|
|
this.#data = void 0;
|
|
this.#dirty = true;
|
|
}
|
|
/**
|
|
* Regenerates the session, creating a new session ID. The existing session data is preserved.
|
|
*/
|
|
async regenerate() {
|
|
let data = /* @__PURE__ */ new Map();
|
|
try {
|
|
data = await this.#ensureData();
|
|
} catch (err) {
|
|
console.error("Failed to load session data during regeneration:", err);
|
|
}
|
|
const oldSessionId = this.#sessionID;
|
|
this.#sessionID = crypto.randomUUID();
|
|
this.#sessionIDFromCookie = false;
|
|
this.#data = data;
|
|
this.#dirty = true;
|
|
await this.#setCookie();
|
|
if (oldSessionId && this.#storage) {
|
|
this.#storage.removeItem(oldSessionId).catch((err) => {
|
|
console.error("Failed to remove old session data:", err);
|
|
});
|
|
}
|
|
}
|
|
// Persists the session data to storage.
|
|
// This is called automatically at the end of the request.
|
|
// Uses a symbol to prevent users from calling it directly.
|
|
async [PERSIST_SYMBOL]() {
|
|
if (!this.#dirty && !this.#toDestroy.size) {
|
|
return;
|
|
}
|
|
const storage = await this.#ensureStorage();
|
|
if (this.#dirty && this.#data) {
|
|
const data = await this.#ensureData();
|
|
this.#toDelete.forEach((key2) => data.delete(key2));
|
|
const key = this.#ensureSessionID();
|
|
let serialized;
|
|
try {
|
|
serialized = stringify(data);
|
|
} catch (err) {
|
|
throw new AstroError(
|
|
{
|
|
...SessionStorageSaveError,
|
|
message: SessionStorageSaveError.message(
|
|
"The session data could not be serialized.",
|
|
this.#config.driver
|
|
)
|
|
},
|
|
{ cause: err }
|
|
);
|
|
}
|
|
await storage.setItem(key, serialized);
|
|
this.#dirty = false;
|
|
}
|
|
if (this.#toDestroy.size > 0) {
|
|
const cleanupPromises = [...this.#toDestroy].map(
|
|
(sessionId) => storage.removeItem(sessionId).catch((err) => {
|
|
console.error("Failed to clean up session %s:", sessionId, err);
|
|
})
|
|
);
|
|
await Promise.all(cleanupPromises);
|
|
this.#toDestroy.clear();
|
|
}
|
|
}
|
|
get sessionID() {
|
|
return this.#sessionID;
|
|
}
|
|
/**
|
|
* Loads a session from storage with the given ID, and replaces the current session.
|
|
* Any changes made to the current session will be lost.
|
|
* This is not normally needed, as the session is automatically loaded using the cookie.
|
|
* However it can be used to restore a session where the ID has been recorded somewhere
|
|
* else (e.g. in a database).
|
|
*/
|
|
async load(sessionID) {
|
|
this.#sessionID = sessionID;
|
|
this.#data = void 0;
|
|
await this.#setCookie();
|
|
await this.#ensureData();
|
|
}
|
|
/**
|
|
* Sets the session cookie.
|
|
*/
|
|
async #setCookie() {
|
|
if (!VALID_COOKIE_REGEX.test(this.#cookieName)) {
|
|
throw new AstroError({
|
|
...SessionStorageSaveError,
|
|
message: "Invalid cookie name. Cookie names can only contain letters, numbers, and dashes."
|
|
});
|
|
}
|
|
const value = this.#ensureSessionID();
|
|
this.#cookies.set(this.#cookieName, value, this.#cookieConfig);
|
|
}
|
|
/**
|
|
* Attempts to load the session data from storage, or creates a new data object if none exists.
|
|
* If there is existing partial data, it will be merged into the new data object.
|
|
*/
|
|
async #ensureData() {
|
|
const storage = await this.#ensureStorage();
|
|
if (this.#data && !this.#partial) {
|
|
return this.#data;
|
|
}
|
|
this.#data ??= /* @__PURE__ */ new Map();
|
|
const raw = await storage.get(this.#ensureSessionID());
|
|
if (!raw) {
|
|
if (this.#sessionIDFromCookie) {
|
|
this.#sessionID = crypto.randomUUID();
|
|
this.#sessionIDFromCookie = false;
|
|
if (this.#cookieSet) {
|
|
await this.#setCookie();
|
|
}
|
|
}
|
|
return this.#data;
|
|
}
|
|
try {
|
|
const storedMap = unflatten(raw);
|
|
if (!(storedMap instanceof Map)) {
|
|
await this.destroy();
|
|
throw new AstroError({
|
|
...SessionStorageInitError,
|
|
message: SessionStorageInitError.message(
|
|
"The session data was an invalid type.",
|
|
this.#config.driver
|
|
)
|
|
});
|
|
}
|
|
const now = Date.now();
|
|
for (const [key, value] of storedMap) {
|
|
const expired = typeof value.expires === "number" && value.expires < now;
|
|
if (!this.#data.has(key) && !this.#toDelete.has(key) && !expired) {
|
|
this.#data.set(key, value);
|
|
}
|
|
}
|
|
this.#partial = false;
|
|
return this.#data;
|
|
} catch (err) {
|
|
await this.destroy();
|
|
if (err instanceof AstroError) {
|
|
throw err;
|
|
}
|
|
throw new AstroError(
|
|
{
|
|
...SessionStorageInitError,
|
|
message: SessionStorageInitError.message(
|
|
"The session data could not be parsed.",
|
|
this.#config.driver
|
|
)
|
|
},
|
|
{ cause: err }
|
|
);
|
|
}
|
|
}
|
|
/**
|
|
* Returns the session ID, generating a new one if it does not exist.
|
|
*/
|
|
#ensureSessionID() {
|
|
if (!this.#sessionID) {
|
|
const cookieValue = this.#cookies.get(this.#cookieName)?.value;
|
|
if (cookieValue) {
|
|
this.#sessionID = cookieValue;
|
|
this.#sessionIDFromCookie = true;
|
|
} else {
|
|
this.#sessionID = crypto.randomUUID();
|
|
}
|
|
}
|
|
return this.#sessionID;
|
|
}
|
|
/**
|
|
* Ensures the storage is initialized.
|
|
* This is called automatically when a storage operation is needed.
|
|
*/
|
|
async #ensureStorage() {
|
|
if (this.#storage) {
|
|
return this.#storage;
|
|
}
|
|
if (AstroSession.#sharedStorage.has(this.#config.driver)) {
|
|
this.#storage = AstroSession.#sharedStorage.get(this.#config.driver);
|
|
return this.#storage;
|
|
}
|
|
if (!this.#driverFactory) {
|
|
throw new AstroError({
|
|
...SessionStorageInitError,
|
|
message: SessionStorageInitError.message(
|
|
"Astro could not load the driver correctly. Does it exist?",
|
|
this.#config.driver
|
|
)
|
|
});
|
|
}
|
|
const driver = this.#driverFactory;
|
|
try {
|
|
this.#storage = createStorage({
|
|
driver: {
|
|
...driver(this.#config.options),
|
|
// Unused methods
|
|
hasItem() {
|
|
return false;
|
|
},
|
|
getKeys() {
|
|
return [];
|
|
}
|
|
}
|
|
});
|
|
AstroSession.#sharedStorage.set(this.#config.driver, this.#storage);
|
|
return this.#storage;
|
|
} catch (err) {
|
|
throw new AstroError(
|
|
{
|
|
...SessionStorageInitError,
|
|
message: SessionStorageInitError.message("Unknown error", this.#config.driver)
|
|
},
|
|
{ cause: err }
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
function validateAndDecodePathname(pathname) {
|
|
let decoded;
|
|
try {
|
|
decoded = decodeURI(pathname);
|
|
} catch (_e) {
|
|
throw new Error("Invalid URL encoding");
|
|
}
|
|
const hasDecoding = decoded !== pathname;
|
|
const decodedStillHasEncoding = /%[0-9a-fA-F]{2}/.test(decoded);
|
|
if (hasDecoding && decodedStillHasEncoding) {
|
|
throw new Error("Multi-level URL encoding is not allowed");
|
|
}
|
|
return decoded;
|
|
}
|
|
|
|
class RenderContext {
|
|
pipeline;
|
|
locals;
|
|
middleware;
|
|
actions;
|
|
serverIslands;
|
|
// It must be a DECODED pathname
|
|
pathname;
|
|
request;
|
|
routeData;
|
|
status;
|
|
clientAddress;
|
|
cookies;
|
|
params;
|
|
url;
|
|
props;
|
|
partial;
|
|
shouldInjectCspMetaTags;
|
|
session;
|
|
cache;
|
|
skipMiddleware;
|
|
constructor(pipeline, locals, middleware, actions, serverIslands, pathname, request, routeData, status, clientAddress, cookies = new AstroCookies(request), params = getParams(routeData, pathname), url = RenderContext.#createNormalizedUrl(request.url), props = {}, partial = void 0, shouldInjectCspMetaTags = pipeline.manifest.shouldInjectCspMetaTags, session = void 0, cache, skipMiddleware = false) {
|
|
this.pipeline = pipeline;
|
|
this.locals = locals;
|
|
this.middleware = middleware;
|
|
this.actions = actions;
|
|
this.serverIslands = serverIslands;
|
|
this.pathname = pathname;
|
|
this.request = request;
|
|
this.routeData = routeData;
|
|
this.status = status;
|
|
this.clientAddress = clientAddress;
|
|
this.cookies = cookies;
|
|
this.params = params;
|
|
this.url = url;
|
|
this.props = props;
|
|
this.partial = partial;
|
|
this.shouldInjectCspMetaTags = shouldInjectCspMetaTags;
|
|
this.session = session;
|
|
this.cache = cache;
|
|
this.skipMiddleware = skipMiddleware;
|
|
}
|
|
static #createNormalizedUrl(requestUrl) {
|
|
const url = new URL(requestUrl);
|
|
try {
|
|
url.pathname = validateAndDecodePathname(url.pathname);
|
|
} catch {
|
|
try {
|
|
url.pathname = decodeURI(url.pathname);
|
|
} catch {
|
|
}
|
|
}
|
|
url.pathname = collapseDuplicateSlashes(url.pathname);
|
|
return url;
|
|
}
|
|
/**
|
|
* A flag that tells the render content if the rewriting was triggered
|
|
*/
|
|
isRewriting = false;
|
|
/**
|
|
* A safety net in case of loops
|
|
*/
|
|
counter = 0;
|
|
result = void 0;
|
|
static async create({
|
|
locals = {},
|
|
pathname,
|
|
pipeline,
|
|
request,
|
|
routeData,
|
|
clientAddress,
|
|
status = 200,
|
|
props,
|
|
partial = void 0,
|
|
shouldInjectCspMetaTags,
|
|
skipMiddleware = false
|
|
}) {
|
|
const pipelineMiddleware = await pipeline.getMiddleware();
|
|
const pipelineActions = await pipeline.getActions();
|
|
const pipelineSessionDriver = await pipeline.getSessionDriver();
|
|
const serverIslands = await pipeline.getServerIslands();
|
|
setOriginPathname(
|
|
request,
|
|
pathname,
|
|
pipeline.manifest.trailingSlash,
|
|
pipeline.manifest.buildFormat
|
|
);
|
|
const cookies = new AstroCookies(request);
|
|
const session = pipeline.manifest.sessionConfig && pipelineSessionDriver ? new AstroSession({
|
|
cookies,
|
|
config: pipeline.manifest.sessionConfig,
|
|
runtimeMode: pipeline.runtimeMode,
|
|
driverFactory: pipelineSessionDriver,
|
|
mockStorage: null
|
|
}) : void 0;
|
|
let cache;
|
|
if (!pipeline.cacheConfig) {
|
|
cache = new DisabledAstroCache(pipeline.logger);
|
|
} else if (pipeline.runtimeMode === "development") {
|
|
cache = new NoopAstroCache();
|
|
} else {
|
|
const cacheProvider = await pipeline.getCacheProvider();
|
|
cache = new AstroCache(cacheProvider);
|
|
if (pipeline.cacheConfig?.routes) {
|
|
if (!pipeline.compiledCacheRoutes) {
|
|
pipeline.compiledCacheRoutes = compileCacheRoutes(
|
|
pipeline.cacheConfig.routes,
|
|
pipeline.manifest.base,
|
|
pipeline.manifest.trailingSlash
|
|
);
|
|
}
|
|
const matched = matchCacheRoute(pathname, pipeline.compiledCacheRoutes);
|
|
if (matched) {
|
|
cache.set(matched);
|
|
}
|
|
}
|
|
}
|
|
return new RenderContext(
|
|
pipeline,
|
|
locals,
|
|
sequence(...pipeline.internalMiddleware, pipelineMiddleware),
|
|
pipelineActions,
|
|
serverIslands,
|
|
pathname,
|
|
request,
|
|
routeData,
|
|
status,
|
|
clientAddress,
|
|
cookies,
|
|
void 0,
|
|
void 0,
|
|
props,
|
|
partial,
|
|
shouldInjectCspMetaTags ?? pipeline.manifest.shouldInjectCspMetaTags,
|
|
session,
|
|
cache,
|
|
skipMiddleware
|
|
);
|
|
}
|
|
/**
|
|
* The main function of the RenderContext.
|
|
*
|
|
* Use this function to render any route known to Astro.
|
|
* It attempts to render a route. A route can be a:
|
|
*
|
|
* - page
|
|
* - redirect
|
|
* - endpoint
|
|
* - fallback
|
|
*/
|
|
async render(componentInstance, slots = {}) {
|
|
const { middleware, pipeline } = this;
|
|
const { logger, streaming, manifest } = pipeline;
|
|
const props = Object.keys(this.props).length > 0 ? this.props : await getProps({
|
|
mod: componentInstance,
|
|
routeData: this.routeData,
|
|
routeCache: this.pipeline.routeCache,
|
|
pathname: this.pathname,
|
|
logger,
|
|
serverLike: manifest.serverLike,
|
|
base: manifest.base,
|
|
trailingSlash: manifest.trailingSlash
|
|
});
|
|
const actionApiContext = this.createActionAPIContext();
|
|
const apiContext = this.createAPIContext(props, actionApiContext);
|
|
this.counter++;
|
|
if (this.counter === 4) {
|
|
return new Response("Loop Detected", {
|
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/508
|
|
status: 508,
|
|
statusText: "Astro detected a loop where you tried to call the rewriting logic more than four times."
|
|
});
|
|
}
|
|
const lastNext = async (ctx, payload) => {
|
|
if (payload) {
|
|
const oldPathname = this.pathname;
|
|
pipeline.logger.debug("router", "Called rewriting to:", payload);
|
|
const {
|
|
routeData,
|
|
componentInstance: newComponent,
|
|
pathname,
|
|
newUrl
|
|
} = await pipeline.tryRewrite(payload, this.request);
|
|
if (this.pipeline.manifest.serverLike === true && this.routeData.prerender === false && routeData.prerender === true) {
|
|
throw new AstroError({
|
|
...ForbiddenRewrite,
|
|
message: ForbiddenRewrite.message(this.pathname, pathname, routeData.component),
|
|
hint: ForbiddenRewrite.hint(routeData.component)
|
|
});
|
|
}
|
|
this.routeData = routeData;
|
|
componentInstance = newComponent;
|
|
if (payload instanceof Request) {
|
|
this.request = payload;
|
|
} else {
|
|
this.request = copyRequest(
|
|
newUrl,
|
|
this.request,
|
|
// need to send the flag of the previous routeData
|
|
routeData.prerender,
|
|
this.pipeline.logger,
|
|
this.routeData.route
|
|
);
|
|
}
|
|
this.isRewriting = true;
|
|
this.url = RenderContext.#createNormalizedUrl(this.request.url);
|
|
this.params = getParams(routeData, pathname);
|
|
this.pathname = pathname;
|
|
this.status = 200;
|
|
setOriginPathname(
|
|
this.request,
|
|
oldPathname,
|
|
this.pipeline.manifest.trailingSlash,
|
|
this.pipeline.manifest.buildFormat
|
|
);
|
|
}
|
|
let response2;
|
|
if (!ctx.isPrerendered && !this.skipMiddleware) {
|
|
const { action, setActionResult, serializeActionResult } = getActionContext(ctx);
|
|
if (action?.calledFrom === "form") {
|
|
const actionResult = await action.handler();
|
|
setActionResult(action.name, serializeActionResult(actionResult));
|
|
}
|
|
}
|
|
switch (this.routeData.type) {
|
|
case "endpoint": {
|
|
response2 = await renderEndpoint(
|
|
componentInstance,
|
|
ctx,
|
|
this.routeData.prerender,
|
|
logger
|
|
);
|
|
break;
|
|
}
|
|
case "redirect":
|
|
return renderRedirect(this);
|
|
case "page": {
|
|
this.result = await this.createResult(componentInstance, actionApiContext);
|
|
try {
|
|
response2 = await renderPage(
|
|
this.result,
|
|
componentInstance?.default,
|
|
props,
|
|
slots,
|
|
streaming,
|
|
this.routeData
|
|
);
|
|
} catch (e) {
|
|
this.result.cancelled = true;
|
|
throw e;
|
|
}
|
|
response2.headers.set(ROUTE_TYPE_HEADER, "page");
|
|
if (this.routeData.route === "/404" || this.routeData.route === "/500") {
|
|
response2.headers.set(REROUTE_DIRECTIVE_HEADER, "no");
|
|
}
|
|
if (this.isRewriting) {
|
|
response2.headers.set(REWRITE_DIRECTIVE_HEADER_KEY, REWRITE_DIRECTIVE_HEADER_VALUE);
|
|
}
|
|
break;
|
|
}
|
|
case "fallback": {
|
|
return new Response(null, { status: 500, headers: { [ROUTE_TYPE_HEADER]: "fallback" } });
|
|
}
|
|
}
|
|
const responseCookies = getCookiesFromResponse(response2);
|
|
if (responseCookies) {
|
|
this.cookies.merge(responseCookies);
|
|
}
|
|
return response2;
|
|
};
|
|
if (isRouteExternalRedirect(this.routeData)) {
|
|
return renderRedirect(this);
|
|
}
|
|
const response = this.skipMiddleware ? await lastNext(apiContext) : await callMiddleware(middleware, apiContext, lastNext);
|
|
if (response.headers.get(ROUTE_TYPE_HEADER)) {
|
|
response.headers.delete(ROUTE_TYPE_HEADER);
|
|
}
|
|
attachCookiesToResponse(response, this.cookies);
|
|
return response;
|
|
}
|
|
createAPIContext(props, context) {
|
|
const redirect = (path, status = 302) => new Response(null, { status, headers: { Location: path } });
|
|
const rewrite = async (reroutePayload) => {
|
|
return await this.#executeRewrite(reroutePayload);
|
|
};
|
|
Reflect.set(context, pipelineSymbol, this.pipeline);
|
|
return Object.assign(context, {
|
|
props,
|
|
redirect,
|
|
rewrite,
|
|
getActionResult: createGetActionResult(context.locals),
|
|
callAction: createCallAction(context)
|
|
});
|
|
}
|
|
async #executeRewrite(reroutePayload) {
|
|
this.pipeline.logger.debug("router", "Calling rewrite: ", reroutePayload);
|
|
const oldPathname = this.pathname;
|
|
const { routeData, componentInstance, newUrl, pathname } = await this.pipeline.tryRewrite(
|
|
reroutePayload,
|
|
this.request
|
|
);
|
|
const isI18nFallback = routeData.fallbackRoutes && routeData.fallbackRoutes.length > 0;
|
|
if (this.pipeline.manifest.serverLike && !this.routeData.prerender && routeData.prerender && !isI18nFallback) {
|
|
throw new AstroError({
|
|
...ForbiddenRewrite,
|
|
message: ForbiddenRewrite.message(this.pathname, pathname, routeData.component),
|
|
hint: ForbiddenRewrite.hint(routeData.component)
|
|
});
|
|
}
|
|
this.routeData = routeData;
|
|
if (reroutePayload instanceof Request) {
|
|
this.request = reroutePayload;
|
|
} else {
|
|
this.request = copyRequest(
|
|
newUrl,
|
|
this.request,
|
|
// need to send the flag of the previous routeData
|
|
routeData.prerender,
|
|
this.pipeline.logger,
|
|
this.routeData.route
|
|
);
|
|
}
|
|
this.url = RenderContext.#createNormalizedUrl(this.request.url);
|
|
const newCookies = new AstroCookies(this.request);
|
|
if (this.cookies) {
|
|
newCookies.merge(this.cookies);
|
|
}
|
|
this.cookies = newCookies;
|
|
this.params = getParams(routeData, pathname);
|
|
this.pathname = pathname;
|
|
this.isRewriting = true;
|
|
this.status = 200;
|
|
setOriginPathname(
|
|
this.request,
|
|
oldPathname,
|
|
this.pipeline.manifest.trailingSlash,
|
|
this.pipeline.manifest.buildFormat
|
|
);
|
|
return await this.render(componentInstance);
|
|
}
|
|
createActionAPIContext() {
|
|
const renderContext = this;
|
|
const { params, pipeline, url } = this;
|
|
return {
|
|
// Don't allow reassignment of cookies because it doesn't work
|
|
get cookies() {
|
|
return renderContext.cookies;
|
|
},
|
|
routePattern: this.routeData.route,
|
|
isPrerendered: this.routeData.prerender,
|
|
get clientAddress() {
|
|
return renderContext.getClientAddress();
|
|
},
|
|
get currentLocale() {
|
|
return renderContext.computeCurrentLocale();
|
|
},
|
|
generator: ASTRO_GENERATOR,
|
|
get locals() {
|
|
return renderContext.locals;
|
|
},
|
|
set locals(_) {
|
|
throw new AstroError(LocalsReassigned);
|
|
},
|
|
params,
|
|
get preferredLocale() {
|
|
return renderContext.computePreferredLocale();
|
|
},
|
|
get preferredLocaleList() {
|
|
return renderContext.computePreferredLocaleList();
|
|
},
|
|
request: this.request,
|
|
site: pipeline.site,
|
|
url,
|
|
get originPathname() {
|
|
return getOriginPathname(renderContext.request);
|
|
},
|
|
get session() {
|
|
if (this.isPrerendered) {
|
|
pipeline.logger.warn(
|
|
"session",
|
|
`context.session was used when rendering the route ${s.green(this.routePattern)}, but it is not available on prerendered routes. If you need access to sessions, make sure that the route is server-rendered using \`export const prerender = false;\` or by setting \`output\` to \`"server"\` in your Astro config to make all your routes server-rendered by default. For more information, see https://docs.astro.build/en/guides/sessions/`
|
|
);
|
|
return void 0;
|
|
}
|
|
if (!renderContext.session) {
|
|
pipeline.logger.warn(
|
|
"session",
|
|
`context.session was used when rendering the route ${s.green(this.routePattern)}, but no storage configuration was provided. Either configure the storage manually or use an adapter that provides session storage. For more information, see https://docs.astro.build/en/guides/sessions/`
|
|
);
|
|
return void 0;
|
|
}
|
|
return renderContext.session;
|
|
},
|
|
get cache() {
|
|
return renderContext.cache;
|
|
},
|
|
get csp() {
|
|
if (!pipeline.manifest.csp) {
|
|
if (pipeline.runtimeMode === "production") {
|
|
pipeline.logger.warn(
|
|
"csp",
|
|
`context.csp was used when rendering the route ${s.green(this.routePattern)}, but CSP was not configured. For more information, see https://docs.astro.build/en/reference/experimental-flags/csp/`
|
|
);
|
|
}
|
|
return void 0;
|
|
}
|
|
return {
|
|
insertDirective(payload) {
|
|
if (renderContext?.result?.directives) {
|
|
renderContext.result.directives = pushDirective(
|
|
renderContext.result.directives,
|
|
payload
|
|
);
|
|
} else {
|
|
renderContext?.result?.directives.push(payload);
|
|
}
|
|
},
|
|
insertScriptResource(resource) {
|
|
renderContext.result?.scriptResources.push(resource);
|
|
},
|
|
insertStyleResource(resource) {
|
|
renderContext.result?.styleResources.push(resource);
|
|
},
|
|
insertStyleHash(hash) {
|
|
renderContext.result?.styleHashes.push(hash);
|
|
},
|
|
insertScriptHash(hash) {
|
|
renderContext.result?.scriptHashes.push(hash);
|
|
}
|
|
};
|
|
}
|
|
};
|
|
}
|
|
async createResult(mod, ctx) {
|
|
const { cookies, pathname, pipeline, routeData, status } = this;
|
|
const { clientDirectives, inlinedScripts, compressHTML, manifest, renderers, resolve } = pipeline;
|
|
const { links, scripts, styles } = await pipeline.headElements(routeData);
|
|
const extraStyleHashes = [];
|
|
const extraScriptHashes = [];
|
|
const shouldInjectCspMetaTags = this.shouldInjectCspMetaTags;
|
|
const cspAlgorithm = manifest.csp?.algorithm ?? "SHA-256";
|
|
if (shouldInjectCspMetaTags) {
|
|
for (const style of styles) {
|
|
extraStyleHashes.push(await generateCspDigest(style.children, cspAlgorithm));
|
|
}
|
|
for (const script of scripts) {
|
|
extraScriptHashes.push(await generateCspDigest(script.children, cspAlgorithm));
|
|
}
|
|
}
|
|
const componentMetadata = await pipeline.componentMetadata(routeData) ?? manifest.componentMetadata;
|
|
const headers = new Headers({ "Content-Type": "text/html" });
|
|
const partial = typeof this.partial === "boolean" ? this.partial : Boolean(mod.partial);
|
|
const actionResult = hasActionPayload(this.locals) ? deserializeActionResult(this.locals._actionPayload.actionResult) : void 0;
|
|
const response = {
|
|
status: actionResult?.error ? actionResult?.error.status : status,
|
|
statusText: actionResult?.error ? actionResult?.error.type : "OK",
|
|
get headers() {
|
|
return headers;
|
|
},
|
|
// Disallow `Astro.response.headers = new Headers`
|
|
set headers(_) {
|
|
throw new AstroError(AstroResponseHeadersReassigned);
|
|
}
|
|
};
|
|
const result = {
|
|
base: manifest.base,
|
|
userAssetsBase: manifest.userAssetsBase,
|
|
cancelled: false,
|
|
clientDirectives,
|
|
inlinedScripts,
|
|
componentMetadata,
|
|
compressHTML,
|
|
cookies,
|
|
/** This function returns the `Astro` faux-global */
|
|
createAstro: (props, slots) => this.createAstro(result, props, slots, ctx),
|
|
links,
|
|
params: this.params,
|
|
partial,
|
|
pathname,
|
|
renderers,
|
|
resolve,
|
|
response,
|
|
request: this.request,
|
|
scripts,
|
|
styles,
|
|
actionResult,
|
|
serverIslandNameMap: this.serverIslands.serverIslandNameMap ?? /* @__PURE__ */ new Map(),
|
|
key: manifest.key,
|
|
trailingSlash: manifest.trailingSlash,
|
|
_experimentalQueuedRendering: {
|
|
pool: pipeline.nodePool,
|
|
htmlStringCache: pipeline.htmlStringCache,
|
|
enabled: manifest.experimentalQueuedRendering?.enabled,
|
|
poolSize: manifest.experimentalQueuedRendering?.poolSize,
|
|
contentCache: manifest.experimentalQueuedRendering?.contentCache
|
|
},
|
|
_metadata: {
|
|
hasHydrationScript: false,
|
|
rendererSpecificHydrationScripts: /* @__PURE__ */ new Set(),
|
|
hasRenderedHead: false,
|
|
renderedScripts: /* @__PURE__ */ new Set(),
|
|
hasDirectives: /* @__PURE__ */ new Set(),
|
|
hasRenderedServerIslandRuntime: false,
|
|
headInTree: false,
|
|
extraHead: [],
|
|
extraStyleHashes,
|
|
extraScriptHashes,
|
|
propagators: /* @__PURE__ */ new Set()
|
|
},
|
|
cspDestination: manifest.csp?.cspDestination ?? (routeData.prerender ? "meta" : "header"),
|
|
shouldInjectCspMetaTags,
|
|
cspAlgorithm,
|
|
// The following arrays must be cloned; otherwise, they become mutable across routes.
|
|
scriptHashes: manifest.csp?.scriptHashes ? [...manifest.csp.scriptHashes] : [],
|
|
scriptResources: manifest.csp?.scriptResources ? [...manifest.csp.scriptResources] : [],
|
|
styleHashes: manifest.csp?.styleHashes ? [...manifest.csp.styleHashes] : [],
|
|
styleResources: manifest.csp?.styleResources ? [...manifest.csp.styleResources] : [],
|
|
directives: manifest.csp?.directives ? [...manifest.csp.directives] : [],
|
|
isStrictDynamic: manifest.csp?.isStrictDynamic ?? false,
|
|
internalFetchHeaders: manifest.internalFetchHeaders
|
|
};
|
|
return result;
|
|
}
|
|
#astroPagePartial;
|
|
/**
|
|
* The Astro global is sourced in 3 different phases:
|
|
* - **Static**: `.generator` and `.glob` is printed by the compiler, instantiated once per process per astro file
|
|
* - **Page-level**: `.request`, `.cookies`, `.locals` etc. These remain the same for the duration of the request.
|
|
* - **Component-level**: `.props`, `.slots`, and `.self` are unique to each _use_ of each component.
|
|
*
|
|
* The page level partial is used as the prototype of the user-visible `Astro` global object, which is instantiated once per use of a component.
|
|
*/
|
|
createAstro(result, props, slotValues, apiContext) {
|
|
let astroPagePartial;
|
|
if (this.isRewriting) {
|
|
astroPagePartial = this.#astroPagePartial = this.createAstroPagePartial(result, apiContext);
|
|
} else {
|
|
astroPagePartial = this.#astroPagePartial ??= this.createAstroPagePartial(result, apiContext);
|
|
}
|
|
const astroComponentPartial = { props, self: null };
|
|
const Astro = Object.assign(
|
|
Object.create(astroPagePartial),
|
|
astroComponentPartial
|
|
);
|
|
let _slots;
|
|
Object.defineProperty(Astro, "slots", {
|
|
get: () => {
|
|
if (!_slots) {
|
|
_slots = new Slots(
|
|
result,
|
|
slotValues,
|
|
this.pipeline.logger
|
|
);
|
|
}
|
|
return _slots;
|
|
}
|
|
});
|
|
return Astro;
|
|
}
|
|
createAstroPagePartial(result, apiContext) {
|
|
const renderContext = this;
|
|
const { cookies, locals, params, pipeline, url } = this;
|
|
const { response } = result;
|
|
const redirect = (path, status = 302) => {
|
|
if (this.request[responseSentSymbol$1]) {
|
|
throw new AstroError({
|
|
...ResponseSentError
|
|
});
|
|
}
|
|
return new Response(null, { status, headers: { Location: path } });
|
|
};
|
|
const rewrite = async (reroutePayload) => {
|
|
return await this.#executeRewrite(reroutePayload);
|
|
};
|
|
const callAction = createCallAction(apiContext);
|
|
return {
|
|
generator: ASTRO_GENERATOR,
|
|
routePattern: this.routeData.route,
|
|
isPrerendered: this.routeData.prerender,
|
|
cookies,
|
|
get session() {
|
|
if (this.isPrerendered) {
|
|
pipeline.logger.warn(
|
|
"session",
|
|
`Astro.session was used when rendering the route ${s.green(this.routePattern)}, but it is not available on prerendered pages. If you need access to sessions, make sure that the page is server-rendered using \`export const prerender = false;\` or by setting \`output\` to \`"server"\` in your Astro config to make all your pages server-rendered by default. For more information, see https://docs.astro.build/en/guides/sessions/`
|
|
);
|
|
return void 0;
|
|
}
|
|
if (!renderContext.session) {
|
|
pipeline.logger.warn(
|
|
"session",
|
|
`Astro.session was used when rendering the route ${s.green(this.routePattern)}, but no storage configuration was provided. Either configure the storage manually or use an adapter that provides session storage. For more information, see https://docs.astro.build/en/guides/sessions/`
|
|
);
|
|
return void 0;
|
|
}
|
|
return renderContext.session;
|
|
},
|
|
get cache() {
|
|
return renderContext.cache;
|
|
},
|
|
get clientAddress() {
|
|
return renderContext.getClientAddress();
|
|
},
|
|
get currentLocale() {
|
|
return renderContext.computeCurrentLocale();
|
|
},
|
|
params,
|
|
get preferredLocale() {
|
|
return renderContext.computePreferredLocale();
|
|
},
|
|
get preferredLocaleList() {
|
|
return renderContext.computePreferredLocaleList();
|
|
},
|
|
locals,
|
|
redirect,
|
|
rewrite,
|
|
request: this.request,
|
|
response,
|
|
site: pipeline.site,
|
|
getActionResult: createGetActionResult(locals),
|
|
get callAction() {
|
|
return callAction;
|
|
},
|
|
url,
|
|
get originPathname() {
|
|
return getOriginPathname(renderContext.request);
|
|
},
|
|
get csp() {
|
|
if (!pipeline.manifest.csp) {
|
|
if (pipeline.runtimeMode === "production") {
|
|
pipeline.logger.warn(
|
|
"csp",
|
|
`Astro.csp was used when rendering the route ${s.green(this.routePattern)}, but CSP was not configured. For more information, see https://docs.astro.build/en/reference/experimental-flags/csp/`
|
|
);
|
|
}
|
|
return void 0;
|
|
}
|
|
return {
|
|
insertDirective(payload) {
|
|
if (renderContext?.result?.directives) {
|
|
renderContext.result.directives = pushDirective(
|
|
renderContext.result.directives,
|
|
payload
|
|
);
|
|
} else {
|
|
renderContext?.result?.directives.push(payload);
|
|
}
|
|
},
|
|
insertScriptResource(resource) {
|
|
renderContext.result?.scriptResources.push(resource);
|
|
},
|
|
insertStyleResource(resource) {
|
|
renderContext.result?.styleResources.push(resource);
|
|
},
|
|
insertStyleHash(hash) {
|
|
renderContext.result?.styleHashes.push(hash);
|
|
},
|
|
insertScriptHash(hash) {
|
|
renderContext.result?.scriptHashes.push(hash);
|
|
}
|
|
};
|
|
}
|
|
};
|
|
}
|
|
getClientAddress() {
|
|
const { pipeline, routeData, clientAddress } = this;
|
|
if (routeData.prerender) {
|
|
throw new AstroError({
|
|
...PrerenderClientAddressNotAvailable,
|
|
message: PrerenderClientAddressNotAvailable.message(routeData.component)
|
|
});
|
|
}
|
|
if (clientAddress) {
|
|
return clientAddress;
|
|
}
|
|
if (pipeline.adapterName) {
|
|
throw new AstroError({
|
|
...ClientAddressNotAvailable,
|
|
message: ClientAddressNotAvailable.message(pipeline.adapterName)
|
|
});
|
|
}
|
|
throw new AstroError(StaticClientAddressNotAvailable);
|
|
}
|
|
/**
|
|
* API Context may be created multiple times per request, i18n data needs to be computed only once.
|
|
* So, it is computed and saved here on creation of the first APIContext and reused for later ones.
|
|
*/
|
|
#currentLocale;
|
|
computeCurrentLocale() {
|
|
const {
|
|
url,
|
|
pipeline: { i18n },
|
|
routeData
|
|
} = this;
|
|
if (!i18n) return;
|
|
const { defaultLocale, locales, strategy } = i18n;
|
|
const fallbackTo = strategy === "pathname-prefix-other-locales" || strategy === "domains-prefix-other-locales" ? defaultLocale : void 0;
|
|
if (this.#currentLocale) {
|
|
return this.#currentLocale;
|
|
}
|
|
let computedLocale;
|
|
if (isRouteServerIsland(routeData)) {
|
|
let referer = this.request.headers.get("referer");
|
|
if (referer) {
|
|
if (URL.canParse(referer)) {
|
|
referer = new URL(referer).pathname;
|
|
}
|
|
computedLocale = computeCurrentLocale(referer, locales, defaultLocale);
|
|
}
|
|
} else {
|
|
let pathname = routeData.pathname;
|
|
if (!routeData.pattern.test(url.pathname)) {
|
|
for (const fallbackRoute of routeData.fallbackRoutes) {
|
|
if (fallbackRoute.pattern.test(url.pathname)) {
|
|
pathname = fallbackRoute.pathname;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
pathname = pathname && !isRoute404or500(routeData) ? pathname : url.pathname;
|
|
computedLocale = computeCurrentLocale(pathname, locales, defaultLocale);
|
|
if (routeData.params.length > 0) {
|
|
const localeFromParams = computeCurrentLocaleFromParams(this.params, locales);
|
|
if (localeFromParams) {
|
|
computedLocale = localeFromParams;
|
|
}
|
|
}
|
|
}
|
|
this.#currentLocale = computedLocale ?? fallbackTo;
|
|
return this.#currentLocale;
|
|
}
|
|
#preferredLocale;
|
|
computePreferredLocale() {
|
|
const {
|
|
pipeline: { i18n },
|
|
request
|
|
} = this;
|
|
if (!i18n) return;
|
|
return this.#preferredLocale ??= computePreferredLocale(request, i18n.locales);
|
|
}
|
|
#preferredLocaleList;
|
|
computePreferredLocaleList() {
|
|
const {
|
|
pipeline: { i18n },
|
|
request
|
|
} = this;
|
|
if (!i18n) return;
|
|
return this.#preferredLocaleList ??= computePreferredLocaleList(request, i18n.locales);
|
|
}
|
|
}
|
|
|
|
function redirectTemplate({
|
|
status,
|
|
absoluteLocation,
|
|
relativeLocation,
|
|
from
|
|
}) {
|
|
const delay = status === 302 ? 2 : 0;
|
|
return `<!doctype html>
|
|
<title>Redirecting to: ${relativeLocation}</title>
|
|
<meta http-equiv="refresh" content="${delay};url=${relativeLocation}">
|
|
<meta name="robots" content="noindex">
|
|
<link rel="canonical" href="${absoluteLocation}">
|
|
<body>
|
|
<a href="${relativeLocation}">Redirecting ${from ? `from <code>${from}</code> ` : ""}to <code>${relativeLocation}</code></a>
|
|
</body>`;
|
|
}
|
|
|
|
function ensure404Route(manifest) {
|
|
if (!manifest.routes.some((route) => route.route === "/404")) {
|
|
manifest.routes.push(DEFAULT_404_ROUTE);
|
|
}
|
|
return manifest;
|
|
}
|
|
|
|
class Router {
|
|
#routes;
|
|
#base;
|
|
#baseWithoutTrailingSlash;
|
|
#buildFormat;
|
|
#trailingSlash;
|
|
constructor(routes, options) {
|
|
this.#routes = [...routes].sort(routeComparator);
|
|
this.#base = normalizeBase(options.base);
|
|
this.#baseWithoutTrailingSlash = removeTrailingForwardSlash(this.#base);
|
|
this.#buildFormat = options.buildFormat;
|
|
this.#trailingSlash = options.trailingSlash;
|
|
}
|
|
/**
|
|
* Match an input pathname against the route list.
|
|
* If allowWithoutBase is true, a non-base-prefixed path is still considered.
|
|
*/
|
|
match(inputPathname, { allowWithoutBase = false } = {}) {
|
|
const normalized = getRedirectForPathname(inputPathname);
|
|
if (normalized.redirect) {
|
|
return { type: "redirect", location: normalized.redirect, status: 301 };
|
|
}
|
|
if (this.#base !== "/") {
|
|
const baseWithSlash = `${this.#baseWithoutTrailingSlash}/`;
|
|
if (this.#trailingSlash === "always" && (normalized.pathname === this.#baseWithoutTrailingSlash || normalized.pathname === this.#base)) {
|
|
return { type: "redirect", location: baseWithSlash, status: 301 };
|
|
}
|
|
if (this.#trailingSlash === "never" && normalized.pathname === baseWithSlash) {
|
|
return { type: "redirect", location: this.#baseWithoutTrailingSlash, status: 301 };
|
|
}
|
|
}
|
|
const baseResult = stripBase(
|
|
normalized.pathname,
|
|
this.#base,
|
|
this.#baseWithoutTrailingSlash,
|
|
this.#trailingSlash
|
|
);
|
|
if (!baseResult) {
|
|
if (!allowWithoutBase) {
|
|
return { type: "none", reason: "outside-base" };
|
|
}
|
|
}
|
|
let pathname = baseResult ?? normalized.pathname;
|
|
if (this.#buildFormat === "file") {
|
|
pathname = normalizeFileFormatPathname(pathname);
|
|
}
|
|
const route = this.#routes.find((candidate) => {
|
|
if (candidate.pattern.test(pathname)) return true;
|
|
return candidate.fallbackRoutes.some((fallbackRoute) => fallbackRoute.pattern.test(pathname));
|
|
});
|
|
if (!route) {
|
|
return { type: "none", reason: "no-match" };
|
|
}
|
|
const params = getParams(route, pathname);
|
|
return { type: "match", route, params, pathname };
|
|
}
|
|
}
|
|
function normalizeBase(base) {
|
|
if (!base) return "/";
|
|
if (base === "/") return base;
|
|
return prependForwardSlash(base);
|
|
}
|
|
function getRedirectForPathname(pathname) {
|
|
let value = prependForwardSlash(pathname);
|
|
if (value.startsWith("//")) {
|
|
const collapsed = `/${value.replace(/^\/+/, "")}`;
|
|
return { pathname: value, redirect: collapsed };
|
|
}
|
|
return { pathname: value };
|
|
}
|
|
function stripBase(pathname, base, baseWithoutTrailingSlash, trailingSlash) {
|
|
if (base === "/") return pathname;
|
|
const baseWithSlash = `${baseWithoutTrailingSlash}/`;
|
|
if (pathname === baseWithoutTrailingSlash || pathname === base) {
|
|
return trailingSlash === "always" ? null : "/";
|
|
}
|
|
if (pathname === baseWithSlash) {
|
|
return trailingSlash === "never" ? null : "/";
|
|
}
|
|
if (pathname.startsWith(baseWithSlash)) {
|
|
return pathname.slice(baseWithoutTrailingSlash.length);
|
|
}
|
|
return null;
|
|
}
|
|
function normalizeFileFormatPathname(pathname) {
|
|
if (pathname.endsWith("/index.html")) {
|
|
const trimmed = pathname.slice(0, -"/index.html".length);
|
|
return trimmed === "" ? "/" : trimmed;
|
|
}
|
|
if (pathname.endsWith(".html")) {
|
|
const trimmed = pathname.slice(0, -".html".length);
|
|
return trimmed === "" ? "/" : trimmed;
|
|
}
|
|
return pathname;
|
|
}
|
|
|
|
class BaseApp {
|
|
manifest;
|
|
manifestData;
|
|
pipeline;
|
|
adapterLogger;
|
|
baseWithoutTrailingSlash;
|
|
logger;
|
|
#router;
|
|
constructor(manifest, streaming = true, ...args) {
|
|
this.manifest = manifest;
|
|
this.manifestData = { routes: manifest.routes.map((route) => route.routeData) };
|
|
this.baseWithoutTrailingSlash = removeTrailingForwardSlash(manifest.base);
|
|
this.pipeline = this.createPipeline(streaming, manifest, ...args);
|
|
this.logger = new AstroLogger({
|
|
destination: consoleLogDestination,
|
|
level: manifest.logLevel
|
|
});
|
|
this.adapterLogger = new AstroIntegrationLogger(this.logger.options, manifest.adapterName);
|
|
ensure404Route(this.manifestData);
|
|
this.#router = this.createRouter(this.manifestData);
|
|
}
|
|
async createRenderContext(payload) {
|
|
return RenderContext.create(payload);
|
|
}
|
|
getAdapterLogger() {
|
|
return this.adapterLogger;
|
|
}
|
|
getAllowedDomains() {
|
|
return this.manifest.allowedDomains;
|
|
}
|
|
matchesAllowedDomains(forwardedHost, protocol) {
|
|
return BaseApp.validateForwardedHost(forwardedHost, this.manifest.allowedDomains, protocol);
|
|
}
|
|
static validateForwardedHost(forwardedHost, allowedDomains, protocol) {
|
|
if (!allowedDomains || allowedDomains.length === 0) {
|
|
return false;
|
|
}
|
|
try {
|
|
const testUrl = new URL(`${protocol || "https"}://${forwardedHost}`);
|
|
return allowedDomains.some((pattern) => {
|
|
return matchPattern(testUrl, pattern);
|
|
});
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
set setManifestData(newManifestData) {
|
|
this.manifestData = newManifestData;
|
|
this.#router = this.createRouter(this.manifestData);
|
|
}
|
|
removeBase(pathname) {
|
|
pathname = collapseDuplicateLeadingSlashes(pathname);
|
|
if (pathname.startsWith(this.manifest.base)) {
|
|
return pathname.slice(this.baseWithoutTrailingSlash.length + 1);
|
|
}
|
|
return pathname;
|
|
}
|
|
/**
|
|
* It removes the base from the request URL, prepends it with a forward slash and attempts to decoded it.
|
|
*
|
|
* If the decoding fails, it logs the error and return the pathname as is.
|
|
* @param request
|
|
*/
|
|
getPathnameFromRequest(request) {
|
|
const url = new URL(request.url);
|
|
const pathname = prependForwardSlash(this.removeBase(url.pathname));
|
|
try {
|
|
return decodeURI(pathname);
|
|
} catch (e) {
|
|
this.getAdapterLogger().error(e.toString());
|
|
return pathname;
|
|
}
|
|
}
|
|
/**
|
|
* Given a `Request`, it returns the `RouteData` that matches its `pathname`. By default, prerendered
|
|
* routes aren't returned, even if they are matched.
|
|
*
|
|
* When `allowPrerenderedRoutes` is `true`, the function returns matched prerendered routes too.
|
|
* @param request
|
|
* @param allowPrerenderedRoutes
|
|
*/
|
|
match(request, allowPrerenderedRoutes = false) {
|
|
const url = new URL(request.url);
|
|
if (this.manifest.assets.has(url.pathname)) return void 0;
|
|
let pathname = this.computePathnameFromDomain(request);
|
|
if (!pathname) {
|
|
pathname = prependForwardSlash(this.removeBase(url.pathname));
|
|
}
|
|
const match = this.#router.match(decodeURI(pathname), { allowWithoutBase: true });
|
|
if (match.type !== "match") return void 0;
|
|
const routeData = match.route;
|
|
if (allowPrerenderedRoutes) {
|
|
return routeData;
|
|
} else if (routeData.prerender) {
|
|
return void 0;
|
|
}
|
|
return routeData;
|
|
}
|
|
createRouter(manifestData) {
|
|
return new Router(manifestData.routes, {
|
|
base: this.manifest.base,
|
|
trailingSlash: this.manifest.trailingSlash,
|
|
buildFormat: this.manifest.buildFormat
|
|
});
|
|
}
|
|
/**
|
|
* A matching route function to use in the development server.
|
|
* Contrary to the `.match` function, this function resolves props and params, returning the correct
|
|
* route based on the priority, segments. It also returns the correct, resolved pathname.
|
|
* @param pathname
|
|
*/
|
|
devMatch(pathname) {
|
|
return void 0;
|
|
}
|
|
computePathnameFromDomain(request) {
|
|
let pathname = void 0;
|
|
const url = new URL(request.url);
|
|
if (this.manifest.i18n && (this.manifest.i18n.strategy === "domains-prefix-always" || this.manifest.i18n.strategy === "domains-prefix-other-locales" || this.manifest.i18n.strategy === "domains-prefix-always-no-redirect")) {
|
|
let host = request.headers.get("X-Forwarded-Host");
|
|
let protocol = request.headers.get("X-Forwarded-Proto");
|
|
if (protocol) {
|
|
protocol = protocol + ":";
|
|
} else {
|
|
protocol = url.protocol;
|
|
}
|
|
if (!host) {
|
|
host = request.headers.get("Host");
|
|
}
|
|
if (host && protocol) {
|
|
host = host.split(":")[0];
|
|
try {
|
|
let locale;
|
|
const hostAsUrl = new URL(`${protocol}//${host}`);
|
|
for (const [domainKey, localeValue] of Object.entries(
|
|
this.manifest.i18n.domainLookupTable
|
|
)) {
|
|
const domainKeyAsUrl = new URL(domainKey);
|
|
if (hostAsUrl.host === domainKeyAsUrl.host && hostAsUrl.protocol === domainKeyAsUrl.protocol) {
|
|
locale = localeValue;
|
|
break;
|
|
}
|
|
}
|
|
if (locale) {
|
|
pathname = prependForwardSlash(
|
|
joinPaths(normalizeTheLocale(locale), this.removeBase(url.pathname))
|
|
);
|
|
if (this.manifest.trailingSlash === "always") {
|
|
pathname = appendForwardSlash(pathname);
|
|
} else if (this.manifest.trailingSlash === "never") {
|
|
pathname = removeTrailingForwardSlash(pathname);
|
|
} else if (url.pathname.endsWith("/")) {
|
|
pathname = appendForwardSlash(pathname);
|
|
}
|
|
}
|
|
} catch (e) {
|
|
this.logger.error(
|
|
"router",
|
|
`Astro tried to parse ${protocol}//${host} as an URL, but it threw a parsing error. Check the X-Forwarded-Host and X-Forwarded-Proto headers.`
|
|
);
|
|
this.logger.error("router", `Error: ${e}`);
|
|
}
|
|
}
|
|
}
|
|
return pathname;
|
|
}
|
|
redirectTrailingSlash(pathname) {
|
|
const { trailingSlash } = this.manifest;
|
|
if (pathname === "/" || isInternalPath(pathname)) {
|
|
return pathname;
|
|
}
|
|
const path = collapseDuplicateTrailingSlashes(pathname, trailingSlash !== "never");
|
|
if (path !== pathname) {
|
|
return path;
|
|
}
|
|
if (trailingSlash === "ignore") {
|
|
return pathname;
|
|
}
|
|
if (trailingSlash === "always" && !hasFileExtension(pathname)) {
|
|
return appendForwardSlash(pathname);
|
|
}
|
|
if (trailingSlash === "never") {
|
|
return removeTrailingForwardSlash(pathname);
|
|
}
|
|
return pathname;
|
|
}
|
|
async render(request, {
|
|
addCookieHeader = false,
|
|
clientAddress = Reflect.get(request, clientAddressSymbol),
|
|
locals,
|
|
prerenderedErrorPageFetch = fetch,
|
|
routeData
|
|
} = {}) {
|
|
const timeStart = performance.now();
|
|
const url = new URL(request.url);
|
|
const redirect = this.redirectTrailingSlash(url.pathname);
|
|
if (redirect !== url.pathname) {
|
|
const status = request.method === "GET" ? 301 : 308;
|
|
const response2 = new Response(
|
|
redirectTemplate({
|
|
status,
|
|
relativeLocation: url.pathname,
|
|
absoluteLocation: redirect,
|
|
from: request.url
|
|
}),
|
|
{
|
|
status,
|
|
headers: {
|
|
location: redirect + url.search
|
|
}
|
|
}
|
|
);
|
|
this.#prepareResponse(response2, { addCookieHeader });
|
|
return response2;
|
|
}
|
|
if (routeData) {
|
|
this.logger.debug(
|
|
"router",
|
|
"The adapter " + this.manifest.adapterName + " provided a custom RouteData for ",
|
|
request.url
|
|
);
|
|
this.logger.debug("router", "RouteData");
|
|
this.logger.debug("router", routeData);
|
|
}
|
|
const resolvedRenderOptions = {
|
|
addCookieHeader,
|
|
clientAddress,
|
|
prerenderedErrorPageFetch,
|
|
locals,
|
|
routeData
|
|
};
|
|
if (locals) {
|
|
if (typeof locals !== "object") {
|
|
const error = new AstroError(LocalsNotAnObject);
|
|
this.logger.error(null, error.stack);
|
|
return this.renderError(request, {
|
|
...resolvedRenderOptions,
|
|
// If locals are invalid, we don't want to include them when
|
|
// rendering the error page
|
|
locals: void 0,
|
|
status: 500,
|
|
error
|
|
});
|
|
}
|
|
}
|
|
if (!routeData) {
|
|
if (this.isDev()) {
|
|
const result = await this.devMatch(this.getPathnameFromRequest(request));
|
|
if (result) {
|
|
routeData = result.routeData;
|
|
}
|
|
} else {
|
|
routeData = this.match(request);
|
|
}
|
|
this.logger.debug("router", "Astro matched the following route for " + request.url);
|
|
this.logger.debug("router", "RouteData:\n" + routeData);
|
|
}
|
|
if (!routeData) {
|
|
routeData = this.manifestData.routes.find(
|
|
(route) => route.component === "404.astro" || route.component === DEFAULT_404_COMPONENT
|
|
);
|
|
}
|
|
if (!routeData) {
|
|
this.logger.debug("router", "Astro hasn't found routes that match " + request.url);
|
|
this.logger.debug("router", "Here's the available routes:\n", this.manifestData);
|
|
return this.renderError(request, {
|
|
...resolvedRenderOptions,
|
|
status: 404
|
|
});
|
|
}
|
|
let pathname = this.getPathnameFromRequest(request);
|
|
if (this.isDev() && !routeHasHtmlExtension(routeData)) {
|
|
pathname = pathname.replace(/\/index\.html$/, "/").replace(/\.html$/, "");
|
|
}
|
|
const defaultStatus = this.getDefaultStatusCode(routeData, pathname);
|
|
let response;
|
|
let session;
|
|
let cache;
|
|
try {
|
|
const componentInstance = await this.pipeline.getComponentByRoute(routeData);
|
|
const renderContext = await this.createRenderContext({
|
|
pipeline: this.pipeline,
|
|
locals,
|
|
pathname,
|
|
request,
|
|
routeData,
|
|
status: defaultStatus,
|
|
clientAddress
|
|
});
|
|
session = renderContext.session;
|
|
cache = renderContext.cache;
|
|
if (this.pipeline.cacheProvider) {
|
|
const cacheProvider = await this.pipeline.getCacheProvider();
|
|
if (cacheProvider?.onRequest) {
|
|
response = await cacheProvider.onRequest(
|
|
{
|
|
request,
|
|
url: new URL(request.url)
|
|
},
|
|
async () => {
|
|
const res = await renderContext.render(componentInstance);
|
|
applyCacheHeaders(cache, res);
|
|
return res;
|
|
}
|
|
);
|
|
response.headers.delete("CDN-Cache-Control");
|
|
response.headers.delete("Cache-Tag");
|
|
} else {
|
|
response = await renderContext.render(componentInstance);
|
|
applyCacheHeaders(cache, response);
|
|
}
|
|
} else {
|
|
response = await renderContext.render(componentInstance);
|
|
}
|
|
const isRewrite = response.headers.has(REWRITE_DIRECTIVE_HEADER_KEY);
|
|
this.logThisRequest({
|
|
pathname,
|
|
method: request.method,
|
|
statusCode: response.status,
|
|
isRewrite,
|
|
timeStart
|
|
});
|
|
} catch (err) {
|
|
this.logger.error(null, err.stack || err.message || String(err));
|
|
return this.renderError(request, {
|
|
...resolvedRenderOptions,
|
|
status: 500,
|
|
error: err
|
|
});
|
|
} finally {
|
|
await session?.[PERSIST_SYMBOL]();
|
|
}
|
|
if (REROUTABLE_STATUS_CODES.includes(response.status) && // If the body isn't null, that means the user sets the 404 status
|
|
// but uses the current route to handle the 404
|
|
response.body === null && response.headers.get(REROUTE_DIRECTIVE_HEADER) !== "no") {
|
|
return this.renderError(request, {
|
|
...resolvedRenderOptions,
|
|
response,
|
|
status: response.status,
|
|
// We don't have an error to report here. Passing null means we pass nothing intentionally
|
|
// while undefined means there's no error
|
|
error: response.status === 500 ? null : void 0
|
|
});
|
|
}
|
|
this.#prepareResponse(response, { addCookieHeader });
|
|
return response;
|
|
}
|
|
#prepareResponse(response, { addCookieHeader }) {
|
|
for (const headerName of [
|
|
REROUTE_DIRECTIVE_HEADER,
|
|
REWRITE_DIRECTIVE_HEADER_KEY,
|
|
NOOP_MIDDLEWARE_HEADER,
|
|
ROUTE_TYPE_HEADER
|
|
]) {
|
|
if (response.headers.has(headerName)) {
|
|
response.headers.delete(headerName);
|
|
}
|
|
}
|
|
if (addCookieHeader) {
|
|
for (const setCookieHeaderValue of getSetCookiesFromResponse(response)) {
|
|
response.headers.append("set-cookie", setCookieHeaderValue);
|
|
}
|
|
}
|
|
Reflect.set(response, responseSentSymbol$1, true);
|
|
}
|
|
setCookieHeaders(response) {
|
|
return getSetCookiesFromResponse(response);
|
|
}
|
|
/**
|
|
* Reads all the cookies written by `Astro.cookie.set()` onto the passed response.
|
|
* For example,
|
|
* ```ts
|
|
* for (const cookie_ of App.getSetCookieFromResponse(response)) {
|
|
* const cookie: string = cookie_
|
|
* }
|
|
* ```
|
|
* @param response The response to read cookies from.
|
|
* @returns An iterator that yields key-value pairs as equal-sign-separated strings.
|
|
*/
|
|
static getSetCookieFromResponse = getSetCookiesFromResponse;
|
|
/**
|
|
* If it is a known error code, try sending the according page (e.g. 404.astro / 500.astro).
|
|
* This also handles pre-rendered /404 or /500 routes
|
|
*/
|
|
async renderError(request, {
|
|
status,
|
|
response: originalResponse,
|
|
skipMiddleware = false,
|
|
error,
|
|
...resolvedRenderOptions
|
|
}) {
|
|
const errorRoutePath = `/${status}${this.manifest.trailingSlash === "always" ? "/" : ""}`;
|
|
const errorRouteData = matchRoute(errorRoutePath, this.manifestData);
|
|
const url = new URL(request.url);
|
|
if (errorRouteData) {
|
|
if (errorRouteData.prerender) {
|
|
const maybeDotHtml = errorRouteData.route.endsWith(`/${status}`) ? ".html" : "";
|
|
const statusURL = new URL(`${this.baseWithoutTrailingSlash}/${status}${maybeDotHtml}`, url);
|
|
if (statusURL.toString() !== request.url && resolvedRenderOptions.prerenderedErrorPageFetch) {
|
|
const response2 = await resolvedRenderOptions.prerenderedErrorPageFetch(
|
|
statusURL.toString()
|
|
);
|
|
const override = { status, removeContentEncodingHeaders: true };
|
|
const newResponse = this.mergeResponses(response2, originalResponse, override);
|
|
this.#prepareResponse(newResponse, resolvedRenderOptions);
|
|
return newResponse;
|
|
}
|
|
}
|
|
const mod = await this.pipeline.getComponentByRoute(errorRouteData);
|
|
let session;
|
|
try {
|
|
const renderContext = await this.createRenderContext({
|
|
locals: resolvedRenderOptions.locals,
|
|
pipeline: this.pipeline,
|
|
skipMiddleware,
|
|
pathname: this.getPathnameFromRequest(request),
|
|
request,
|
|
routeData: errorRouteData,
|
|
status,
|
|
props: { error },
|
|
clientAddress: resolvedRenderOptions.clientAddress
|
|
});
|
|
session = renderContext.session;
|
|
const response2 = await renderContext.render(mod);
|
|
const newResponse = this.mergeResponses(response2, originalResponse);
|
|
this.#prepareResponse(newResponse, resolvedRenderOptions);
|
|
return newResponse;
|
|
} catch {
|
|
if (skipMiddleware === false) {
|
|
return this.renderError(request, {
|
|
...resolvedRenderOptions,
|
|
status,
|
|
response: originalResponse,
|
|
skipMiddleware: true
|
|
});
|
|
}
|
|
} finally {
|
|
await session?.[PERSIST_SYMBOL]();
|
|
}
|
|
}
|
|
const response = this.mergeResponses(new Response(null, { status }), originalResponse);
|
|
this.#prepareResponse(response, resolvedRenderOptions);
|
|
return response;
|
|
}
|
|
mergeResponses(newResponse, originalResponse, override) {
|
|
let newResponseHeaders = newResponse.headers;
|
|
if (override?.removeContentEncodingHeaders) {
|
|
newResponseHeaders = new Headers(newResponseHeaders);
|
|
newResponseHeaders.delete("Content-Encoding");
|
|
newResponseHeaders.delete("Content-Length");
|
|
}
|
|
if (!originalResponse) {
|
|
if (override !== void 0) {
|
|
return new Response(newResponse.body, {
|
|
status: override.status,
|
|
statusText: newResponse.statusText,
|
|
headers: newResponseHeaders
|
|
});
|
|
}
|
|
return newResponse;
|
|
}
|
|
const status = override?.status ? override.status : originalResponse.status === 200 ? newResponse.status : originalResponse.status;
|
|
try {
|
|
originalResponse.headers.delete("Content-type");
|
|
originalResponse.headers.delete("Content-Length");
|
|
originalResponse.headers.delete("Transfer-Encoding");
|
|
} catch {
|
|
}
|
|
const newHeaders = new Headers();
|
|
const seen = /* @__PURE__ */ new Set();
|
|
for (const [name, value] of originalResponse.headers) {
|
|
newHeaders.append(name, value);
|
|
seen.add(name.toLowerCase());
|
|
}
|
|
for (const [name, value] of newResponseHeaders) {
|
|
if (!seen.has(name.toLowerCase())) {
|
|
newHeaders.append(name, value);
|
|
}
|
|
}
|
|
const mergedResponse = new Response(newResponse.body, {
|
|
status,
|
|
statusText: status === 200 ? newResponse.statusText : originalResponse.statusText,
|
|
// If you're looking at here for possible bugs, it means that it's not a bug.
|
|
// With the middleware, users can meddle with headers, and we should pass to the 404/500.
|
|
// If users see something weird, it's because they are setting some headers they should not.
|
|
//
|
|
// Although, we don't want it to replace the content-type, because the error page must return `text/html`
|
|
headers: newHeaders
|
|
});
|
|
const originalCookies = getCookiesFromResponse(originalResponse);
|
|
const newCookies = getCookiesFromResponse(newResponse);
|
|
if (originalCookies) {
|
|
if (newCookies) {
|
|
for (const cookieValue of AstroCookies.consume(newCookies)) {
|
|
originalResponse.headers.append("set-cookie", cookieValue);
|
|
}
|
|
}
|
|
attachCookiesToResponse(mergedResponse, originalCookies);
|
|
} else if (newCookies) {
|
|
attachCookiesToResponse(mergedResponse, newCookies);
|
|
}
|
|
return mergedResponse;
|
|
}
|
|
getDefaultStatusCode(routeData, pathname) {
|
|
if (!routeData.pattern.test(pathname)) {
|
|
for (const fallbackRoute of routeData.fallbackRoutes) {
|
|
if (fallbackRoute.pattern.test(pathname)) {
|
|
return 302;
|
|
}
|
|
}
|
|
}
|
|
const route = removeTrailingForwardSlash(routeData.route);
|
|
if (route.endsWith("/404")) return 404;
|
|
if (route.endsWith("/500")) return 500;
|
|
return 200;
|
|
}
|
|
getManifest() {
|
|
return this.pipeline.manifest;
|
|
}
|
|
logThisRequest({
|
|
pathname,
|
|
method,
|
|
statusCode,
|
|
isRewrite,
|
|
timeStart
|
|
}) {
|
|
const timeEnd = performance.now();
|
|
this.logRequest({
|
|
pathname,
|
|
method,
|
|
statusCode,
|
|
isRewrite,
|
|
reqTime: timeEnd - timeStart
|
|
});
|
|
}
|
|
}
|
|
|
|
function getAssetsPrefix(fileExtension, assetsPrefix) {
|
|
let prefix = "";
|
|
if (!assetsPrefix) {
|
|
prefix = "";
|
|
} else if (typeof assetsPrefix === "string") {
|
|
prefix = assetsPrefix;
|
|
} else {
|
|
const dotLessFileExtension = fileExtension.slice(1);
|
|
prefix = assetsPrefix[dotLessFileExtension] || assetsPrefix.fallback;
|
|
}
|
|
return prefix;
|
|
}
|
|
|
|
const URL_PARSE_BASE = "https://astro.build";
|
|
function splitAssetPath(path) {
|
|
const parsed = new URL(path, URL_PARSE_BASE);
|
|
const isAbsolute = URL.canParse(path);
|
|
const pathname = !isAbsolute && !path.startsWith("/") ? parsed.pathname.slice(1) : parsed.pathname;
|
|
return {
|
|
pathname,
|
|
suffix: `${parsed.search}${parsed.hash}`
|
|
};
|
|
}
|
|
function createAssetLink(href, base, assetsPrefix, queryParams) {
|
|
const { pathname, suffix } = splitAssetPath(href);
|
|
let url = "";
|
|
if (assetsPrefix) {
|
|
const pf = getAssetsPrefix(fileExtension(pathname), assetsPrefix);
|
|
url = joinPaths(pf, slash(pathname)) + suffix;
|
|
} else if (base) {
|
|
url = prependForwardSlash(joinPaths(base, slash(pathname))) + suffix;
|
|
} else {
|
|
url = href;
|
|
}
|
|
return url;
|
|
}
|
|
function createStylesheetElement(stylesheet, base, assetsPrefix, queryParams) {
|
|
if (stylesheet.type === "inline") {
|
|
return {
|
|
props: {},
|
|
children: stylesheet.content
|
|
};
|
|
} else {
|
|
return {
|
|
props: {
|
|
rel: "stylesheet",
|
|
href: createAssetLink(stylesheet.src, base, assetsPrefix)
|
|
},
|
|
children: ""
|
|
};
|
|
}
|
|
}
|
|
function createStylesheetElementSet(stylesheets, base, assetsPrefix, queryParams) {
|
|
return new Set(
|
|
stylesheets.map((s) => createStylesheetElement(s, base, assetsPrefix))
|
|
);
|
|
}
|
|
function createModuleScriptElement(script, base, assetsPrefix, queryParams) {
|
|
if (script.type === "external") {
|
|
return createModuleScriptElementWithSrc(script.value, base, assetsPrefix);
|
|
} else {
|
|
return {
|
|
props: {
|
|
type: "module"
|
|
},
|
|
children: script.value
|
|
};
|
|
}
|
|
}
|
|
function createModuleScriptElementWithSrc(src, base, assetsPrefix, queryParams) {
|
|
return {
|
|
props: {
|
|
type: "module",
|
|
src: createAssetLink(src, base, assetsPrefix)
|
|
},
|
|
children: ""
|
|
};
|
|
}
|
|
|
|
function createConsoleLogger(level) {
|
|
return new AstroLogger({
|
|
destination: consoleLogDestination,
|
|
level: level ?? "info"
|
|
});
|
|
}
|
|
|
|
class AppPipeline extends Pipeline {
|
|
getName() {
|
|
return "AppPipeline";
|
|
}
|
|
static create({ manifest, streaming }) {
|
|
const resolve = async function resolve2(specifier) {
|
|
if (!(specifier in manifest.entryModules)) {
|
|
throw new Error(`Unable to resolve [${specifier}]`);
|
|
}
|
|
const bundlePath = manifest.entryModules[specifier];
|
|
if (bundlePath.startsWith("data:") || bundlePath.length === 0) {
|
|
return bundlePath;
|
|
} else {
|
|
return createAssetLink(bundlePath, manifest.base, manifest.assetsPrefix);
|
|
}
|
|
};
|
|
const logger = createConsoleLogger(manifest.logLevel);
|
|
const pipeline = new AppPipeline(
|
|
logger,
|
|
manifest,
|
|
"production",
|
|
manifest.renderers,
|
|
resolve,
|
|
streaming,
|
|
void 0,
|
|
void 0,
|
|
void 0,
|
|
void 0,
|
|
void 0,
|
|
void 0,
|
|
void 0,
|
|
void 0
|
|
);
|
|
return pipeline;
|
|
}
|
|
async headElements(routeData) {
|
|
const { assetsPrefix, base } = this.manifest;
|
|
const routeInfo = this.manifest.routes.find(
|
|
(route) => route.routeData.route === routeData.route
|
|
);
|
|
const links = /* @__PURE__ */ new Set();
|
|
const scripts = /* @__PURE__ */ new Set();
|
|
const styles = createStylesheetElementSet(routeInfo?.styles ?? [], base, assetsPrefix);
|
|
for (const script of routeInfo?.scripts ?? []) {
|
|
if ("stage" in script) {
|
|
if (script.stage === "head-inline") {
|
|
scripts.add({
|
|
props: {},
|
|
children: script.children
|
|
});
|
|
}
|
|
} else {
|
|
scripts.add(createModuleScriptElement(script, base, assetsPrefix));
|
|
}
|
|
}
|
|
return { links, styles, scripts };
|
|
}
|
|
componentMetadata() {
|
|
}
|
|
async getComponentByRoute(routeData) {
|
|
const module = await this.getModuleForRoute(routeData);
|
|
return module.page();
|
|
}
|
|
async getModuleForRoute(route) {
|
|
for (const defaultRoute of this.defaultRoutes) {
|
|
if (route.component === defaultRoute.component) {
|
|
return {
|
|
page: () => Promise.resolve(defaultRoute.instance)
|
|
};
|
|
}
|
|
}
|
|
let routeToProcess = route;
|
|
if (routeIsRedirect(route)) {
|
|
if (route.redirectRoute) {
|
|
routeToProcess = route.redirectRoute;
|
|
} else {
|
|
return RedirectSinglePageBuiltModule;
|
|
}
|
|
} else if (routeIsFallback(route)) {
|
|
routeToProcess = getFallbackRoute(route, this.manifest.routes);
|
|
}
|
|
if (this.manifest.pageMap) {
|
|
const importComponentInstance = this.manifest.pageMap.get(routeToProcess.component);
|
|
if (!importComponentInstance) {
|
|
throw new Error(
|
|
`Unexpectedly unable to find a component instance for route ${route.route}`
|
|
);
|
|
}
|
|
return await importComponentInstance();
|
|
} else if (this.manifest.pageModule) {
|
|
return this.manifest.pageModule;
|
|
}
|
|
throw new Error(
|
|
"Astro couldn't find the correct page to render, probably because it wasn't correctly mapped for SSR usage. This is an internal error, please file an issue."
|
|
);
|
|
}
|
|
async tryRewrite(payload, request) {
|
|
const { newUrl, pathname, routeData } = findRouteToRewrite({
|
|
payload,
|
|
request,
|
|
routes: this.manifest?.routes.map((r) => r.routeData),
|
|
trailingSlash: this.manifest.trailingSlash,
|
|
buildFormat: this.manifest.buildFormat,
|
|
base: this.manifest.base,
|
|
outDir: this.manifest?.serverLike ? this.manifest.buildClientDir : this.manifest.outDir
|
|
});
|
|
const componentInstance = await this.getComponentByRoute(routeData);
|
|
return { newUrl, pathname, componentInstance, routeData };
|
|
}
|
|
}
|
|
|
|
class App extends BaseApp {
|
|
createPipeline(streaming) {
|
|
return AppPipeline.create({
|
|
manifest: this.manifest,
|
|
streaming
|
|
});
|
|
}
|
|
isDev() {
|
|
return false;
|
|
}
|
|
// Should we log something for our users?
|
|
logRequest(_options) {
|
|
}
|
|
}
|
|
|
|
const contexts = new WeakMap();
|
|
|
|
const ID_PREFIX = 'r';
|
|
|
|
function getContext(rendererContextResult) {
|
|
if (contexts.has(rendererContextResult)) {
|
|
return contexts.get(rendererContextResult);
|
|
}
|
|
const ctx = {
|
|
currentIndex: 0,
|
|
get id() {
|
|
return ID_PREFIX + this.currentIndex.toString();
|
|
},
|
|
};
|
|
contexts.set(rendererContextResult, ctx);
|
|
return ctx;
|
|
}
|
|
|
|
function incrementId(rendererContextResult) {
|
|
const ctx = getContext(rendererContextResult);
|
|
const id = ctx.id;
|
|
ctx.currentIndex++;
|
|
return id;
|
|
}
|
|
|
|
/**
|
|
* Astro passes `children` as a string of HTML, so we need
|
|
* a wrapper `div` to render that content as VNodes.
|
|
*
|
|
* As a bonus, we can signal to React that this subtree is
|
|
* entirely static and will never change via `shouldComponentUpdate`.
|
|
*/
|
|
const StaticHtml = ({ value, name, hydrate = true }) => {
|
|
if (!value) return null;
|
|
const tagName = hydrate ? 'astro-slot' : 'astro-static-slot';
|
|
return createElement(tagName, {
|
|
name,
|
|
suppressHydrationWarning: true,
|
|
dangerouslySetInnerHTML: { __html: value },
|
|
});
|
|
};
|
|
|
|
/**
|
|
* This tells React to opt-out of re-rendering this subtree,
|
|
* In addition to being a performance optimization,
|
|
* this also allows other frameworks to attach to `children`.
|
|
*
|
|
* See https://preactjs.com/guide/v8/external-dom-mutations
|
|
*/
|
|
StaticHtml.shouldComponentUpdate = () => false;
|
|
|
|
const slotName = (str) => str.trim().replace(/[-_]([a-z])/g, (_, w) => w.toUpperCase());
|
|
const reactTypeof = Symbol.for('react.element');
|
|
|
|
async function check(Component, props, children) {
|
|
// Note: there are packages that do some unholy things to create "components".
|
|
// Checking the $$typeof property catches most of these patterns.
|
|
if (typeof Component === 'object') {
|
|
return Component['$$typeof'].toString().slice('Symbol('.length).startsWith('react');
|
|
}
|
|
if (typeof Component !== 'function') return false;
|
|
if (Component.name === 'QwikComponent') return false;
|
|
|
|
// Preact forwarded-ref components can be functions, which React does not support
|
|
if (typeof Component === 'function' && Component['$$typeof'] === Symbol.for('react.forward_ref'))
|
|
return false;
|
|
|
|
if (Component.prototype != null && typeof Component.prototype.render === 'function') {
|
|
return React.Component.isPrototypeOf(Component) || React.PureComponent.isPrototypeOf(Component);
|
|
}
|
|
|
|
let isReactComponent = false;
|
|
function Tester(...args) {
|
|
try {
|
|
const vnode = Component(...args);
|
|
if (vnode && vnode['$$typeof'] === reactTypeof) {
|
|
isReactComponent = true;
|
|
}
|
|
} catch {}
|
|
|
|
return React.createElement('div');
|
|
}
|
|
|
|
await renderToStaticMarkup(Tester, props, children, {});
|
|
|
|
return isReactComponent;
|
|
}
|
|
|
|
async function getNodeWritable() {
|
|
let nodeStreamBuiltinModuleName = 'node:stream';
|
|
let { Writable } = await import(/* @vite-ignore */ nodeStreamBuiltinModuleName);
|
|
return Writable;
|
|
}
|
|
|
|
function needsHydration(metadata) {
|
|
// Adjust how this is hydrated only when the version of Astro supports `astroStaticSlot`
|
|
return metadata.astroStaticSlot ? !!metadata.hydrate : true;
|
|
}
|
|
|
|
async function renderToStaticMarkup(Component, props, { default: children, ...slotted }, metadata) {
|
|
let prefix;
|
|
if (this && this.result) {
|
|
prefix = incrementId(this.result);
|
|
}
|
|
const attrs = { prefix };
|
|
|
|
delete props['class'];
|
|
const slots = {};
|
|
for (const [key, value] of Object.entries(slotted)) {
|
|
const name = slotName(key);
|
|
slots[name] = React.createElement(StaticHtml, {
|
|
hydrate: needsHydration(metadata),
|
|
value,
|
|
name,
|
|
});
|
|
}
|
|
// Note: create newProps to avoid mutating `props` before they are serialized
|
|
const newProps = {
|
|
...props,
|
|
...slots,
|
|
};
|
|
const newChildren = children ?? props.children;
|
|
if (newChildren != null) {
|
|
newProps.children = React.createElement(StaticHtml, {
|
|
hydrate: needsHydration(metadata),
|
|
value: newChildren,
|
|
});
|
|
}
|
|
const formState = this ? await getFormState(this) : undefined;
|
|
if (formState) {
|
|
attrs['data-action-result'] = JSON.stringify(formState[0]);
|
|
attrs['data-action-key'] = formState[1];
|
|
attrs['data-action-name'] = formState[2];
|
|
}
|
|
const vnode = React.createElement(Component, newProps);
|
|
const renderOptions = {
|
|
identifierPrefix: prefix,
|
|
formState,
|
|
};
|
|
let html;
|
|
if ('renderToReadableStream' in ReactDOM) {
|
|
html = await renderToReadableStreamAsync(vnode, renderOptions);
|
|
} else {
|
|
html = await renderToPipeableStreamAsync(vnode, renderOptions);
|
|
}
|
|
return { html, attrs };
|
|
}
|
|
|
|
/**
|
|
* @returns {Promise<[actionResult: any, actionKey: string, actionName: string] | undefined>}
|
|
*/
|
|
async function getFormState({ result }) {
|
|
const { request, actionResult } = result;
|
|
|
|
if (!actionResult) return undefined;
|
|
if (!isFormRequest(request.headers.get('content-type'))) return undefined;
|
|
|
|
const { searchParams } = new URL(request.url);
|
|
const formData = await request.clone().formData();
|
|
/**
|
|
* The key generated by React to identify each `useActionState()` call.
|
|
* @example "k511f74df5a35d32e7cf266450d85cb6c"
|
|
*/
|
|
const actionKey = formData.get('$ACTION_KEY')?.toString();
|
|
/**
|
|
* The action name returned by an action's `toString()` property.
|
|
* This matches the endpoint path.
|
|
* @example "/_actions/blog.like"
|
|
*/
|
|
const actionName =
|
|
searchParams.get('_astroAction') ??
|
|
/* Legacy. TODO: remove for stable */ formData
|
|
.get('_astroAction')
|
|
?.toString();
|
|
|
|
if (!actionKey || !actionName) return undefined;
|
|
|
|
return [actionResult, actionKey, actionName];
|
|
}
|
|
|
|
async function renderToPipeableStreamAsync(vnode, options) {
|
|
const Writable = await getNodeWritable();
|
|
let html = '';
|
|
return new Promise((resolve, reject) => {
|
|
let error = undefined;
|
|
let stream = ReactDOM.renderToPipeableStream(vnode, {
|
|
...options,
|
|
onError(err) {
|
|
error = err;
|
|
reject(error);
|
|
},
|
|
onAllReady() {
|
|
stream.pipe(
|
|
new Writable({
|
|
write(chunk, _encoding, callback) {
|
|
html += chunk.toString('utf-8');
|
|
callback();
|
|
},
|
|
destroy() {
|
|
resolve(html);
|
|
},
|
|
}),
|
|
);
|
|
},
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Use a while loop instead of "for await" due to cloudflare and Vercel Edge issues
|
|
* See https://github.com/facebook/react/issues/24169
|
|
*/
|
|
async function readResult(stream) {
|
|
const reader = stream.getReader();
|
|
let result = '';
|
|
const decoder = new TextDecoder('utf-8');
|
|
while (true) {
|
|
const { done, value } = await reader.read();
|
|
if (done) {
|
|
if (value) {
|
|
result += decoder.decode(value);
|
|
} else {
|
|
// This closes the decoder
|
|
decoder.decode(new Uint8Array());
|
|
}
|
|
|
|
return result;
|
|
}
|
|
result += decoder.decode(value, { stream: true });
|
|
}
|
|
}
|
|
|
|
async function renderToReadableStreamAsync(vnode, options) {
|
|
return await readResult(await ReactDOM.renderToReadableStream(vnode, options));
|
|
}
|
|
|
|
const formContentTypes = ['application/x-www-form-urlencoded', 'multipart/form-data'];
|
|
|
|
function isFormRequest(contentType) {
|
|
// Split off parameters like charset or boundary
|
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type#content-type_in_html_forms
|
|
const type = contentType?.split(';')[0].toLowerCase();
|
|
|
|
return formContentTypes.some((t) => type === t);
|
|
}
|
|
|
|
const _renderer0 = {
|
|
name: '@astrojs/react',
|
|
check,
|
|
renderToStaticMarkup,
|
|
supportsAstroStaticSlot: true,
|
|
};
|
|
|
|
const renderers = [Object.assign({"name":"@astrojs/react","clientEntrypoint":"@astrojs/react/client.js","serverEntrypoint":"@astrojs/react/server.js"}, { ssr: _renderer0 }),];
|
|
|
|
const serializedData = [{"file":"","links":[],"scripts":[],"styles":[],"routeData":{"type":"page","component":"_server-islands.astro","params":["name"],"segments":[[{"content":"_server-islands","dynamic":false,"spread":false}],[{"content":"name","dynamic":true,"spread":false}]],"pattern":"^\\/_server-islands\\/([^/]+?)\\/?$","prerender":false,"isIndex":false,"fallbackRoutes":[],"route":"/_server-islands/[name]","origin":"internal","distURL":[],"_meta":{"trailingSlash":"ignore"}}},{"file":"","links":[],"scripts":[],"styles":[],"routeData":{"route":"/_image","component":"node_modules/.pnpm/astro@6.1.10_@types+node@24.12.2_@vercel+functions@3.4.4_rollup@4.60.2_sass@1.99.0_typescript@5.9.3_yaml@2.8.3/node_modules/astro/dist/assets/endpoint/generic.js","params":[],"pathname":"/_image","pattern":"^\\/_image\\/?$","segments":[[{"content":"_image","dynamic":false,"spread":false}]],"type":"endpoint","prerender":false,"fallbackRoutes":[],"distURL":[],"isIndex":false,"origin":"internal","_meta":{"trailingSlash":"ignore"}}},{"file":"","links":[],"scripts":[],"styles":[],"routeData":{"route":"/apply","isIndex":false,"type":"page","pattern":"^\\/apply\\/?$","segments":[[{"content":"apply","dynamic":false,"spread":false}]],"params":[],"component":"src/pages/apply.astro","pathname":"/apply","prerender":false,"fallbackRoutes":[],"distURL":[],"origin":"project","_meta":{"trailingSlash":"ignore"}}},{"file":"","links":[],"scripts":[],"styles":[],"routeData":{"route":"/contact","isIndex":false,"type":"page","pattern":"^\\/contact\\/?$","segments":[[{"content":"contact","dynamic":false,"spread":false}]],"params":[],"component":"src/pages/contact.astro","pathname":"/contact","prerender":false,"fallbackRoutes":[],"distURL":[],"origin":"project","_meta":{"trailingSlash":"ignore"}}},{"file":"","links":[],"scripts":[],"styles":[],"routeData":{"route":"/get-licensed","isIndex":false,"type":"page","pattern":"^\\/get-licensed\\/?$","segments":[[{"content":"get-licensed","dynamic":false,"spread":false}]],"params":[],"component":"src/pages/get-licensed.astro","pathname":"/get-licensed","prerender":false,"fallbackRoutes":[],"distURL":[],"origin":"project","_meta":{"trailingSlash":"ignore"}}},{"file":"","links":[],"scripts":[],"styles":[],"routeData":{"route":"/page","isIndex":false,"type":"page","pattern":"^\\/page\\/?$","segments":[[{"content":"page","dynamic":false,"spread":false}]],"params":[],"component":"src/pages/page.astro","pathname":"/page","prerender":false,"fallbackRoutes":[],"distURL":[],"origin":"project","_meta":{"trailingSlash":"ignore"}}},{"file":"","links":[],"scripts":[],"styles":[],"routeData":{"route":"/shop","isIndex":false,"type":"page","pattern":"^\\/shop\\/?$","segments":[[{"content":"shop","dynamic":false,"spread":false}]],"params":[],"component":"src/pages/shop.astro","pathname":"/shop","prerender":false,"fallbackRoutes":[],"distURL":[],"origin":"project","_meta":{"trailingSlash":"ignore"}}},{"file":"","links":[],"scripts":[],"styles":[],"routeData":{"route":"/","isIndex":true,"type":"page","pattern":"^\\/$","segments":[],"params":[],"component":"src/pages/index.astro","pathname":"/","prerender":false,"fallbackRoutes":[],"distURL":[],"origin":"project","_meta":{"trailingSlash":"ignore"}}}];
|
|
serializedData.map(deserializeRouteInfo);
|
|
|
|
const _page0 = () => import('./generic_DFmc8mZo.mjs').then(n => n.g);
|
|
const _page1 = () => import('./apply_BttEsA5o.mjs');
|
|
const _page2 = () => import('./contact_DBQtpM13.mjs');
|
|
const _page3 = () => import('./get-licensed_CWRP2G7G.mjs');
|
|
const _page4 = () => import('./page_CHxdTGv3.mjs');
|
|
const _page5 = () => import('./shop_CoYk1oFX.mjs');
|
|
const _page6 = () => import('./index_DRgiByZ1.mjs');
|
|
const pageMap = new Map([
|
|
["node_modules/.pnpm/astro@6.1.10_@types+node@24.12.2_@vercel+functions@3.4.4_rollup@4.60.2_sass@1.99.0_typescript@5.9.3_yaml@2.8.3/node_modules/astro/dist/assets/endpoint/generic.js", _page0],
|
|
["src/pages/apply.astro", _page1],
|
|
["src/pages/contact.astro", _page2],
|
|
["src/pages/get-licensed.astro", _page3],
|
|
["src/pages/page.astro", _page4],
|
|
["src/pages/shop.astro", _page5],
|
|
["src/pages/index.astro", _page6]
|
|
]);
|
|
|
|
const _manifest = deserializeManifest(({"rootDir":"file:///Users/kntdgrtnnly/Documents/upiecep/upiecep-website/","cacheDir":"file:///Users/kntdgrtnnly/Documents/upiecep/upiecep-website/node_modules/.astro/","outDir":"file:///Users/kntdgrtnnly/Documents/upiecep/upiecep-website/dist/","srcDir":"file:///Users/kntdgrtnnly/Documents/upiecep/upiecep-website/src/","publicDir":"file:///Users/kntdgrtnnly/Documents/upiecep/upiecep-website/public/","buildClientDir":"file:///Users/kntdgrtnnly/Documents/upiecep/upiecep-website/dist/client/","buildServerDir":"file:///Users/kntdgrtnnly/Documents/upiecep/upiecep-website/dist/server/","adapterName":"@astrojs/vercel","assetsDir":"_astro","routes":[{"file":"","links":[],"scripts":[],"styles":[],"routeData":{"type":"page","component":"_server-islands.astro","params":["name"],"segments":[[{"content":"_server-islands","dynamic":false,"spread":false}],[{"content":"name","dynamic":true,"spread":false}]],"pattern":"^\\/_server-islands\\/([^/]+?)\\/?$","prerender":false,"isIndex":false,"fallbackRoutes":[],"route":"/_server-islands/[name]","origin":"internal","distURL":[],"_meta":{"trailingSlash":"ignore"}}},{"file":"","links":[],"scripts":[],"styles":[],"routeData":{"route":"/_image","component":"node_modules/.pnpm/astro@6.1.10_@types+node@24.12.2_@vercel+functions@3.4.4_rollup@4.60.2_sass@1.99.0_typescript@5.9.3_yaml@2.8.3/node_modules/astro/dist/assets/endpoint/generic.js","params":[],"pathname":"/_image","pattern":"^\\/_image\\/?$","segments":[[{"content":"_image","dynamic":false,"spread":false}]],"type":"endpoint","prerender":false,"fallbackRoutes":[],"distURL":[],"isIndex":false,"origin":"internal","_meta":{"trailingSlash":"ignore"}}},{"file":"","links":[],"scripts":[],"styles":[{"type":"external","src":"_astro/Layout.De3kcDir.css"}],"routeData":{"route":"/apply","isIndex":false,"type":"page","pattern":"^\\/apply\\/?$","segments":[[{"content":"apply","dynamic":false,"spread":false}]],"params":[],"component":"src/pages/apply.astro","pathname":"/apply","prerender":false,"fallbackRoutes":[],"distURL":[],"origin":"project","_meta":{"trailingSlash":"ignore"}}},{"file":"","links":[],"scripts":[],"styles":[{"type":"external","src":"_astro/Layout.De3kcDir.css"}],"routeData":{"route":"/contact","isIndex":false,"type":"page","pattern":"^\\/contact\\/?$","segments":[[{"content":"contact","dynamic":false,"spread":false}]],"params":[],"component":"src/pages/contact.astro","pathname":"/contact","prerender":false,"fallbackRoutes":[],"distURL":[],"origin":"project","_meta":{"trailingSlash":"ignore"}}},{"file":"","links":[],"scripts":[],"styles":[{"type":"external","src":"_astro/Layout.De3kcDir.css"}],"routeData":{"route":"/get-licensed","isIndex":false,"type":"page","pattern":"^\\/get-licensed\\/?$","segments":[[{"content":"get-licensed","dynamic":false,"spread":false}]],"params":[],"component":"src/pages/get-licensed.astro","pathname":"/get-licensed","prerender":false,"fallbackRoutes":[],"distURL":[],"origin":"project","_meta":{"trailingSlash":"ignore"}}},{"file":"","links":[],"scripts":[],"styles":[{"type":"external","src":"_astro/Layout.De3kcDir.css"}],"routeData":{"route":"/page","isIndex":false,"type":"page","pattern":"^\\/page\\/?$","segments":[[{"content":"page","dynamic":false,"spread":false}]],"params":[],"component":"src/pages/page.astro","pathname":"/page","prerender":false,"fallbackRoutes":[],"distURL":[],"origin":"project","_meta":{"trailingSlash":"ignore"}}},{"file":"","links":[],"scripts":[],"styles":[{"type":"external","src":"_astro/Layout.De3kcDir.css"}],"routeData":{"route":"/shop","isIndex":false,"type":"page","pattern":"^\\/shop\\/?$","segments":[[{"content":"shop","dynamic":false,"spread":false}]],"params":[],"component":"src/pages/shop.astro","pathname":"/shop","prerender":false,"fallbackRoutes":[],"distURL":[],"origin":"project","_meta":{"trailingSlash":"ignore"}}},{"file":"","links":[],"scripts":[],"styles":[{"type":"external","src":"_astro/Layout.De3kcDir.css"}],"routeData":{"route":"/","isIndex":true,"type":"page","pattern":"^\\/$","segments":[],"params":[],"component":"src/pages/index.astro","pathname":"/","prerender":false,"fallbackRoutes":[],"distURL":[],"origin":"project","_meta":{"trailingSlash":"ignore"}}}],"serverLike":true,"middlewareMode":"classic","site":"https://www.upiecep.org","base":"/","trailingSlash":"ignore","compressHTML":true,"experimentalQueuedRendering":{"enabled":false,"poolSize":0,"contentCache":false},"componentMetadata":[["/Users/kntdgrtnnly/Documents/upiecep/upiecep-website/src/pages/apply.astro",{"propagation":"none","containsHead":true}],["/Users/kntdgrtnnly/Documents/upiecep/upiecep-website/src/pages/contact.astro",{"propagation":"none","containsHead":true}],["/Users/kntdgrtnnly/Documents/upiecep/upiecep-website/src/pages/get-licensed.astro",{"propagation":"none","containsHead":true}],["/Users/kntdgrtnnly/Documents/upiecep/upiecep-website/src/pages/index.astro",{"propagation":"none","containsHead":true}],["/Users/kntdgrtnnly/Documents/upiecep/upiecep-website/src/pages/page.astro",{"propagation":"none","containsHead":true}],["/Users/kntdgrtnnly/Documents/upiecep/upiecep-website/src/pages/shop.astro",{"propagation":"none","containsHead":true}]],"renderers":[],"clientDirectives":[["idle","(()=>{var l=(n,t)=>{let i=async()=>{await(await n())()},e=typeof t.value==\"object\"?t.value:void 0,s={timeout:e==null?void 0:e.timeout};\"requestIdleCallback\"in window?window.requestIdleCallback(i,s):setTimeout(i,s.timeout||200)};(self.Astro||(self.Astro={})).idle=l;window.dispatchEvent(new Event(\"astro:idle\"));})();"],["load","(()=>{var e=async t=>{await(await t())()};(self.Astro||(self.Astro={})).load=e;window.dispatchEvent(new Event(\"astro:load\"));})();"],["media","(()=>{var n=(a,t)=>{let i=async()=>{await(await a())()};if(t.value){let e=matchMedia(t.value);e.matches?i():e.addEventListener(\"change\",i,{once:!0})}};(self.Astro||(self.Astro={})).media=n;window.dispatchEvent(new Event(\"astro:media\"));})();"],["only","(()=>{var e=async t=>{await(await t())()};(self.Astro||(self.Astro={})).only=e;window.dispatchEvent(new Event(\"astro:only\"));})();"],["visible","(()=>{var a=(s,i,o)=>{let r=async()=>{await(await s())()},t=typeof i.value==\"object\"?i.value:void 0,c={rootMargin:t==null?void 0:t.rootMargin},n=new IntersectionObserver(e=>{for(let l of e)if(l.isIntersecting){n.disconnect(),r();break}},c);for(let e of o.children)n.observe(e)};(self.Astro||(self.Astro={})).visible=a;window.dispatchEvent(new Event(\"astro:visible\"));})();"]],"entryModules":{"astro/entrypoints/prerender":"prerender-entry.D5IQwUtQ.mjs","\u0000virtual:astro:actions/noop-entrypoint":"chunks/noop-entrypoint_BOlrdqWF.mjs","\u0000noop-middleware":"virtual_astro_middleware.mjs","\u0000virtual:astro:session-driver":"chunks/_virtual_astro_session-driver_DYx9Bb3p.mjs","\u0000virtual:astro:server-island-manifest":"chunks/_virtual_astro_server-island-manifest_CQQ1F5PF.mjs","@astrojs/vercel/entrypoint":"entry.mjs","\u0000virtual:astro:page:src/pages/apply@_@astro":"chunks/apply_BttEsA5o.mjs","\u0000virtual:astro:page:src/pages/contact@_@astro":"chunks/contact_DBQtpM13.mjs","\u0000virtual:astro:page:src/pages/get-licensed@_@astro":"chunks/get-licensed_CWRP2G7G.mjs","\u0000virtual:astro:page:src/pages/page@_@astro":"chunks/page_CHxdTGv3.mjs","\u0000virtual:astro:page:src/pages/shop@_@astro":"chunks/shop_CoYk1oFX.mjs","\u0000virtual:astro:page:src/pages/index@_@astro":"chunks/index_DRgiByZ1.mjs","/Users/kntdgrtnnly/Documents/upiecep/upiecep-website/node_modules/.pnpm/astro@6.1.10_@types+node@24.12.2_@vercel+functions@3.4.4_rollup@4.60.2_sass@1.99.0_typescript@5.9.3_yaml@2.8.3/node_modules/astro/dist/assets/services/sharp.js":"chunks/sharp_BETh8Fss.mjs","/Users/kntdgrtnnly/Documents/upiecep/upiecep-website/node_modules/.pnpm/astro@6.1.10_@types+node@24.12.2_@vercel+functions@3.4.4_rollup@4.60.2_sass@1.99.0_typescript@5.9.3_yaml@2.8.3/node_modules/astro/components/ClientRouter.astro?astro&type=script&index=0&lang.ts":"_astro/ClientRouter.astro_astro_type_script_index_0_lang.j56hQv-j.js","/Users/kntdgrtnnly/Documents/upiecep/upiecep-website/src/components/Header/Header":"_astro/Header.DB7-RASS.js","/Users/kntdgrtnnly/Documents/upiecep/upiecep-website/src/components/Icons/Icons":"_astro/Icons.CVNZL9xf.js","/Users/kntdgrtnnly/Documents/upiecep/upiecep-website/src/components/IecepLogo/IecepLogo":"_astro/IecepLogo.adKbHysH.js","/Users/kntdgrtnnly/Documents/upiecep/upiecep-website/src/components/Slider/Slider":"_astro/Slider.D8GpNpXH.js","@astrojs/react/client.js":"_astro/client.DJy5JLSV.js","astro:scripts/before-hydration.js":""},"inlinedScripts":[],"assets":["/android-chrome-192x192.png","/android-chrome-512x512.png","/apple-touch-icon.png","/favicon-16x16.png","/favicon-32x32.png","/favicon.svg","/robots.txt","/site.webmanifest","/_astro/ClientRouter.astro_astro_type_script_index_0_lang.j56hQv-j.js","/_astro/Header.DB7-RASS.js","/_astro/Icons.CVNZL9xf.js","/_astro/IecepLogo.adKbHysH.js","/_astro/Slider.D8GpNpXH.js","/_astro/client.DJy5JLSV.js","/_astro/iecep-logo.CocXYifd.svg","/_astro/index.5Al-6Vsa.js","/_astro/index.kA4PVysc.js","/_astro/jsx-runtime.C63prpkc.js","/img/meta.jpg","/slider/carousel_1.jpg","/slider/carousel_2.jpg","/slider/carousel_3.jpg","/_astro/family_1.Cnn-5HiK.png","/_astro/family_2.s6jV6d3A.png","/_astro/family_3.CzxcB1Ir.png","/_astro/projects_getlicensed.8OZUwnDw.jpg","/_astro/projects_iecepday.DnP904Zh.jpg","/_astro/Layout.De3kcDir.css"],"buildFormat":"directory","checkOrigin":true,"actionBodySizeLimit":1048576,"serverIslandBodySizeLimit":1048576,"allowedDomains":[],"key":"76ZhGfme8hC/4ZoUvhpjfWBGz0KTYBxHx4oVwU0yxZU=","image":{},"devToolbar":{"enabled":false,"debugInfoOutput":""},"logLevel":"info","shouldInjectCspMetaTags":false}));
|
|
const manifestRoutes = _manifest.routes;
|
|
|
|
const manifest = Object.assign(_manifest, {
|
|
renderers,
|
|
actions: () => import('./noop-entrypoint_BOlrdqWF.mjs'),
|
|
middleware: () => import('../virtual_astro_middleware.mjs'),
|
|
sessionDriver: () => import('./_virtual_astro_session-driver_DYx9Bb3p.mjs'),
|
|
|
|
serverIslandMappings: () => import('./_virtual_astro_server-island-manifest_CQQ1F5PF.mjs'),
|
|
routes: manifestRoutes,
|
|
pageMap,
|
|
});
|
|
|
|
const createApp$1 = ({ streaming } = {}) => {
|
|
return new App(manifest, streaming);
|
|
};
|
|
|
|
const createApp = createApp$1;
|
|
|
|
function getFirstForwardedValue(multiValueHeader) {
|
|
return multiValueHeader?.toString()?.split(",").map((e) => e.trim())?.[0];
|
|
}
|
|
const IP_RE = /^[0-9a-fA-F.:]{1,45}$/;
|
|
function isValidIpAddress(value) {
|
|
return IP_RE.test(value);
|
|
}
|
|
function getValidatedIpFromHeader(headerValue) {
|
|
const raw = getFirstForwardedValue(headerValue);
|
|
if (raw && isValidIpAddress(raw)) {
|
|
return raw;
|
|
}
|
|
return void 0;
|
|
}
|
|
function getClientIpAddress(request) {
|
|
return getValidatedIpFromHeader(request.headers.get("x-forwarded-for"));
|
|
}
|
|
|
|
const app = createApp();
|
|
var entrypoint_default = {
|
|
async fetch(request) {
|
|
const url = new URL(request.url);
|
|
const middlewareSecretHeader = request.headers.get(ASTRO_MIDDLEWARE_SECRET_HEADER);
|
|
const hasValidMiddlewareSecret = middlewareSecretHeader === middlewareSecret;
|
|
let realPath = void 0;
|
|
if (hasValidMiddlewareSecret) {
|
|
realPath = request.headers.get(ASTRO_PATH_HEADER);
|
|
} else if (request.headers.get("x-vercel-isr") === "1") {
|
|
realPath = url.searchParams.get(ASTRO_PATH_PARAM);
|
|
}
|
|
if (typeof realPath === "string") {
|
|
url.pathname = realPath;
|
|
request = new Request(url.toString(), {
|
|
method: request.method,
|
|
headers: request.headers,
|
|
...request.body ? { body: request.body, duplex: "half" } : {}
|
|
});
|
|
}
|
|
const routeData = app.match(request);
|
|
let locals = {};
|
|
const astroLocalsHeader = request.headers.get(ASTRO_LOCALS_HEADER);
|
|
if (astroLocalsHeader) {
|
|
if (!hasValidMiddlewareSecret) {
|
|
return new Response("Forbidden", { status: 403 });
|
|
}
|
|
locals = JSON.parse(astroLocalsHeader);
|
|
}
|
|
if (hasValidMiddlewareSecret) {
|
|
request.headers.delete(ASTRO_MIDDLEWARE_SECRET_HEADER);
|
|
}
|
|
const response = await app.render(request, {
|
|
routeData,
|
|
clientAddress: getClientIpAddress(request),
|
|
locals
|
|
});
|
|
if (app.setCookieHeaders) {
|
|
for (const setCookieHeader of app.setCookieHeaders(response)) {
|
|
response.headers.append("Set-Cookie", setCookieHeader);
|
|
}
|
|
}
|
|
return response;
|
|
}
|
|
};
|
|
|
|
export { AstroError as A, ExpectedImage as E, FailedToFetchRemoteImageDimensions as F, IncompatibleDescriptorOptions as I, LocalImageUsedWrongly as L, MissingImageDimension as M, NoImageMetadata as N, RemoteImageNotAllowed as R, UnsupportedImageFormat as U, types as a, isRemotePath as b, UnsupportedImageConversion as c, InvalidImageService as d, ExpectedImageOptions as e, ExpectedNotESMImage as f, ImageMissingAlt as g, addAttribute as h, isRemoteAllowed as i, joinPaths as j, FontFamilyNotFound as k, renderComponent as l, maybeRenderHead as m, createRenderInstruction as n, generateCspDigest as o, renderHead as p, renderSlot as q, renderTemplate as r, spreadAttributes as s, typeHandlers as t, unescapeHTML as u, InvalidComponentArgs as v, MissingSharp as w, getDefaultExportFromCjs as x, entrypoint_default as y };
|