1100 lines
31 KiB
JavaScript
1100 lines
31 KiB
JavaScript
import { a as withoutTrailingSlash, g as getQuery$1, b as withoutBase } from '../shared/nuxi.af709901.mjs';
|
|
import { d as destr } from '../shared/nuxi.e90bf846.mjs';
|
|
|
|
const NODE_TYPES = {
|
|
NORMAL: 0,
|
|
WILDCARD: 1,
|
|
PLACEHOLDER: 2
|
|
};
|
|
|
|
function createRouter$1(options = {}) {
|
|
const ctx = {
|
|
options,
|
|
rootNode: createRadixNode(),
|
|
staticRoutesMap: {}
|
|
};
|
|
const normalizeTrailingSlash = (p) => options.strictTrailingSlash ? p : p.replace(/\/$/, "") || "/";
|
|
if (options.routes) {
|
|
for (const path in options.routes) {
|
|
insert(ctx, normalizeTrailingSlash(path), options.routes[path]);
|
|
}
|
|
}
|
|
return {
|
|
ctx,
|
|
lookup: (path) => lookup(ctx, normalizeTrailingSlash(path)),
|
|
insert: (path, data) => insert(ctx, normalizeTrailingSlash(path), data),
|
|
remove: (path) => remove(ctx, normalizeTrailingSlash(path))
|
|
};
|
|
}
|
|
function lookup(ctx, path) {
|
|
const staticPathNode = ctx.staticRoutesMap[path];
|
|
if (staticPathNode) {
|
|
return staticPathNode.data;
|
|
}
|
|
const sections = path.split("/");
|
|
const params = {};
|
|
let paramsFound = false;
|
|
let wildcardNode = null;
|
|
let node = ctx.rootNode;
|
|
let wildCardParam = null;
|
|
for (let i = 0; i < sections.length; i++) {
|
|
const section = sections[i];
|
|
if (node.wildcardChildNode !== null) {
|
|
wildcardNode = node.wildcardChildNode;
|
|
wildCardParam = sections.slice(i).join("/");
|
|
}
|
|
const nextNode = node.children.get(section);
|
|
if (nextNode !== void 0) {
|
|
node = nextNode;
|
|
} else {
|
|
node = node.placeholderChildNode;
|
|
if (node !== null) {
|
|
params[node.paramName] = section;
|
|
paramsFound = true;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if ((node === null || node.data === null) && wildcardNode !== null) {
|
|
node = wildcardNode;
|
|
params[node.paramName || "_"] = wildCardParam;
|
|
paramsFound = true;
|
|
}
|
|
if (!node) {
|
|
return null;
|
|
}
|
|
if (paramsFound) {
|
|
return {
|
|
...node.data,
|
|
params: paramsFound ? params : void 0
|
|
};
|
|
}
|
|
return node.data;
|
|
}
|
|
function insert(ctx, path, data) {
|
|
let isStaticRoute = true;
|
|
const sections = path.split("/");
|
|
let node = ctx.rootNode;
|
|
let _unnamedPlaceholderCtr = 0;
|
|
for (let i = 0; i < sections.length; i++) {
|
|
const section = sections[i];
|
|
let childNode;
|
|
if (childNode = node.children.get(section)) {
|
|
node = childNode;
|
|
} else {
|
|
const type = getNodeType(section);
|
|
childNode = createRadixNode({ type, parent: node });
|
|
node.children.set(section, childNode);
|
|
if (type === NODE_TYPES.PLACEHOLDER) {
|
|
childNode.paramName = section === "*" ? `_${_unnamedPlaceholderCtr++}` : section.slice(1);
|
|
node.placeholderChildNode = childNode;
|
|
isStaticRoute = false;
|
|
} else if (type === NODE_TYPES.WILDCARD) {
|
|
node.wildcardChildNode = childNode;
|
|
childNode.paramName = section.substring(3) || "_";
|
|
isStaticRoute = false;
|
|
}
|
|
node = childNode;
|
|
}
|
|
}
|
|
node.data = data;
|
|
if (isStaticRoute === true) {
|
|
ctx.staticRoutesMap[path] = node;
|
|
}
|
|
return node;
|
|
}
|
|
function remove(ctx, path) {
|
|
let success = false;
|
|
const sections = path.split("/");
|
|
let node = ctx.rootNode;
|
|
for (let i = 0; i < sections.length; i++) {
|
|
const section = sections[i];
|
|
node = node.children.get(section);
|
|
if (!node) {
|
|
return success;
|
|
}
|
|
}
|
|
if (node.data) {
|
|
const lastSection = sections[sections.length - 1];
|
|
node.data = null;
|
|
if (Object.keys(node.children).length === 0) {
|
|
const parentNode = node.parent;
|
|
delete parentNode[lastSection];
|
|
parentNode.wildcardChildNode = null;
|
|
parentNode.placeholderChildNode = null;
|
|
}
|
|
success = true;
|
|
}
|
|
return success;
|
|
}
|
|
function createRadixNode(options = {}) {
|
|
return {
|
|
type: options.type || NODE_TYPES.NORMAL,
|
|
parent: options.parent || null,
|
|
children: /* @__PURE__ */ new Map(),
|
|
data: options.data || null,
|
|
paramName: options.paramName || null,
|
|
wildcardChildNode: null,
|
|
placeholderChildNode: null
|
|
};
|
|
}
|
|
function getNodeType(str) {
|
|
if (str.startsWith("**")) {
|
|
return NODE_TYPES.WILDCARD;
|
|
}
|
|
if (str[0] === ":" || str === "*") {
|
|
return NODE_TYPES.PLACEHOLDER;
|
|
}
|
|
return NODE_TYPES.NORMAL;
|
|
}
|
|
|
|
const decode = decodeURIComponent;
|
|
const encode = encodeURIComponent;
|
|
const pairSplitRegExp = /; */;
|
|
const fieldContentRegExp = /^[\u0009\u0020-\u007e\u0080-\u00ff]+$/;
|
|
function parse(str, options) {
|
|
if (typeof str !== "string") {
|
|
throw new TypeError("argument str must be a string");
|
|
}
|
|
let obj = {};
|
|
let opt = options || {};
|
|
let pairs = str.split(pairSplitRegExp);
|
|
let dec = opt.decode || decode;
|
|
for (let i = 0; i < pairs.length; i++) {
|
|
let pair = pairs[i];
|
|
let eq_idx = pair.indexOf("=");
|
|
if (eq_idx < 0) {
|
|
continue;
|
|
}
|
|
let key = pair.substr(0, eq_idx).trim();
|
|
let val = pair.substr(++eq_idx, pair.length).trim();
|
|
if (val[0] == '"') {
|
|
val = val.slice(1, -1);
|
|
}
|
|
if (obj[key] == void 0) {
|
|
obj[key] = tryDecode(val, dec);
|
|
}
|
|
}
|
|
return obj;
|
|
}
|
|
function serialize(name, value, options) {
|
|
let opt = options || {};
|
|
let enc = opt.encode || encode;
|
|
if (typeof enc !== "function") {
|
|
throw new TypeError("option encode is invalid");
|
|
}
|
|
if (!fieldContentRegExp.test(name)) {
|
|
throw new TypeError("argument name is invalid");
|
|
}
|
|
let encodedValue = enc(value);
|
|
if (encodedValue && !fieldContentRegExp.test(encodedValue)) {
|
|
throw new TypeError("argument val is invalid");
|
|
}
|
|
let str = name + "=" + encodedValue;
|
|
if (opt.maxAge != null) {
|
|
let maxAge = opt.maxAge - 0;
|
|
if (isNaN(maxAge) || !isFinite(maxAge)) {
|
|
throw new TypeError("option maxAge is invalid");
|
|
}
|
|
str += "; Max-Age=" + Math.floor(maxAge);
|
|
}
|
|
if (opt.domain) {
|
|
if (!fieldContentRegExp.test(opt.domain)) {
|
|
throw new TypeError("option domain is invalid");
|
|
}
|
|
str += "; Domain=" + opt.domain;
|
|
}
|
|
if (opt.path) {
|
|
if (!fieldContentRegExp.test(opt.path)) {
|
|
throw new TypeError("option path is invalid");
|
|
}
|
|
str += "; Path=" + opt.path;
|
|
}
|
|
if (opt.expires) {
|
|
if (typeof opt.expires.toUTCString !== "function") {
|
|
throw new TypeError("option expires is invalid");
|
|
}
|
|
str += "; Expires=" + opt.expires.toUTCString();
|
|
}
|
|
if (opt.httpOnly) {
|
|
str += "; HttpOnly";
|
|
}
|
|
if (opt.secure) {
|
|
str += "; Secure";
|
|
}
|
|
if (opt.sameSite) {
|
|
let sameSite = typeof opt.sameSite === "string" ? opt.sameSite.toLowerCase() : opt.sameSite;
|
|
switch (sameSite) {
|
|
case true:
|
|
str += "; SameSite=Strict";
|
|
break;
|
|
case "lax":
|
|
str += "; SameSite=Lax";
|
|
break;
|
|
case "strict":
|
|
str += "; SameSite=Strict";
|
|
break;
|
|
case "none":
|
|
str += "; SameSite=None";
|
|
break;
|
|
default:
|
|
throw new TypeError("option sameSite is invalid");
|
|
}
|
|
}
|
|
return str;
|
|
}
|
|
function tryDecode(str, decode2) {
|
|
try {
|
|
return decode2(str);
|
|
} catch (e) {
|
|
return str;
|
|
}
|
|
}
|
|
|
|
function useBase(base, handler) {
|
|
base = withoutTrailingSlash(base);
|
|
if (!base) {
|
|
return handler;
|
|
}
|
|
return eventHandler((event) => {
|
|
event.node.req.originalUrl = event.node.req.originalUrl || event.node.req.url || "/";
|
|
event.node.req.url = withoutBase(event.node.req.url || "/", base);
|
|
return handler(event);
|
|
});
|
|
}
|
|
|
|
class H3Error extends Error {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.statusCode = 500;
|
|
this.fatal = false;
|
|
this.unhandled = false;
|
|
this.statusMessage = void 0;
|
|
}
|
|
toJSON() {
|
|
const obj = {
|
|
message: this.message,
|
|
statusCode: this.statusCode
|
|
};
|
|
if (this.statusMessage) {
|
|
obj.statusMessage = this.statusMessage;
|
|
}
|
|
if (this.data !== void 0) {
|
|
obj.data = this.data;
|
|
}
|
|
return obj;
|
|
}
|
|
}
|
|
H3Error.__h3_error__ = true;
|
|
function createError(input) {
|
|
if (typeof input === "string") {
|
|
return new H3Error(input);
|
|
}
|
|
if (isError(input)) {
|
|
return input;
|
|
}
|
|
const err = new H3Error(input.message ?? input.statusMessage, input.cause ? { cause: input.cause } : void 0);
|
|
if ("stack" in input) {
|
|
try {
|
|
Object.defineProperty(err, "stack", { get() {
|
|
return input.stack;
|
|
} });
|
|
} catch {
|
|
try {
|
|
err.stack = input.stack;
|
|
} catch {
|
|
}
|
|
}
|
|
}
|
|
if (input.data) {
|
|
err.data = input.data;
|
|
}
|
|
if (input.statusCode) {
|
|
err.statusCode = input.statusCode;
|
|
} else if (input.status) {
|
|
err.statusCode = input.status;
|
|
}
|
|
if (input.statusMessage) {
|
|
err.statusMessage = input.statusMessage;
|
|
} else if (input.statusText) {
|
|
err.statusMessage = input.statusText;
|
|
}
|
|
if (input.fatal !== void 0) {
|
|
err.fatal = input.fatal;
|
|
}
|
|
if (input.unhandled !== void 0) {
|
|
err.unhandled = input.unhandled;
|
|
}
|
|
return err;
|
|
}
|
|
function sendError(event, error, debug) {
|
|
if (event.node.res.writableEnded) {
|
|
return;
|
|
}
|
|
const h3Error = isError(error) ? error : createError(error);
|
|
const responseBody = {
|
|
statusCode: h3Error.statusCode,
|
|
statusMessage: h3Error.statusMessage,
|
|
stack: [],
|
|
data: h3Error.data
|
|
};
|
|
if (debug) {
|
|
responseBody.stack = (h3Error.stack || "").split("\n").map((l) => l.trim());
|
|
}
|
|
if (event.node.res.writableEnded) {
|
|
return;
|
|
}
|
|
const _code = Number.parseInt(h3Error.statusCode);
|
|
if (_code) {
|
|
event.node.res.statusCode = _code;
|
|
}
|
|
if (h3Error.statusMessage) {
|
|
event.node.res.statusMessage = h3Error.statusMessage;
|
|
}
|
|
event.node.res.setHeader("content-type", MIMES.json);
|
|
event.node.res.end(JSON.stringify(responseBody, void 0, 2));
|
|
}
|
|
function isError(input) {
|
|
return input?.constructor?.__h3_error__ === true;
|
|
}
|
|
|
|
function getQuery(event) {
|
|
return getQuery$1(event.node.req.url || "");
|
|
}
|
|
function getRouterParams(event) {
|
|
return event.context.params || {};
|
|
}
|
|
function getRouterParam(event, name) {
|
|
const params = getRouterParams(event);
|
|
return params[name];
|
|
}
|
|
function getMethod(event, defaultMethod = "GET") {
|
|
return (event.node.req.method || defaultMethod).toUpperCase();
|
|
}
|
|
function isMethod(event, expected, allowHead) {
|
|
const method = getMethod(event);
|
|
if (allowHead && method === "HEAD") {
|
|
return true;
|
|
}
|
|
if (typeof expected === "string") {
|
|
if (method === expected) {
|
|
return true;
|
|
}
|
|
} else if (expected.includes(method)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
function assertMethod(event, expected, allowHead) {
|
|
if (!isMethod(event, expected, allowHead)) {
|
|
throw createError({
|
|
statusCode: 405,
|
|
statusMessage: "HTTP method is not allowed."
|
|
});
|
|
}
|
|
}
|
|
function getRequestHeaders(event) {
|
|
const _headers = {};
|
|
for (const key in event.node.req.headers) {
|
|
const val = event.node.req.headers[key];
|
|
_headers[key] = Array.isArray(val) ? val.filter(Boolean).join(", ") : val;
|
|
}
|
|
return _headers;
|
|
}
|
|
const getHeaders = getRequestHeaders;
|
|
function getRequestHeader(event, name) {
|
|
const headers = getRequestHeaders(event);
|
|
const value = headers[name.toLowerCase()];
|
|
return value;
|
|
}
|
|
const getHeader = getRequestHeader;
|
|
|
|
const RawBodySymbol = Symbol.for("h3RawBody");
|
|
const ParsedBodySymbol = Symbol.for("h3ParsedBody");
|
|
const PayloadMethods$1 = ["PATCH", "POST", "PUT", "DELETE"];
|
|
function readRawBody(event, encoding = "utf8") {
|
|
assertMethod(event, PayloadMethods$1);
|
|
if (RawBodySymbol in event.node.req) {
|
|
const promise2 = Promise.resolve(event.node.req[RawBodySymbol]);
|
|
return encoding ? promise2.then((buff) => buff.toString(encoding)) : promise2;
|
|
}
|
|
if ("body" in event.node.req) {
|
|
return Promise.resolve(event.node.req.body);
|
|
}
|
|
if (!Number.parseInt(event.node.req.headers["content-length"] || "")) {
|
|
return Promise.resolve(void 0);
|
|
}
|
|
const promise = event.node.req[RawBodySymbol] = new Promise((resolve, reject) => {
|
|
const bodyData = [];
|
|
event.node.req.on("error", (err) => {
|
|
reject(err);
|
|
}).on("data", (chunk) => {
|
|
bodyData.push(chunk);
|
|
}).on("end", () => {
|
|
resolve(Buffer.concat(bodyData));
|
|
});
|
|
});
|
|
return encoding ? promise.then((buff) => buff.toString(encoding)) : promise;
|
|
}
|
|
async function readBody(event) {
|
|
if (ParsedBodySymbol in event.node.req) {
|
|
return event.node.req[ParsedBodySymbol];
|
|
}
|
|
const body = await readRawBody(event);
|
|
if (event.node.req.headers["content-type"] === "application/x-www-form-urlencoded") {
|
|
const parsedForm = Object.fromEntries(new URLSearchParams(body));
|
|
return parsedForm;
|
|
}
|
|
const json = destr(body);
|
|
event.node.req[ParsedBodySymbol] = json;
|
|
return json;
|
|
}
|
|
|
|
function handleCacheHeaders(event, opts) {
|
|
const cacheControls = ["public", ...opts.cacheControls || []];
|
|
let cacheMatched = false;
|
|
if (opts.maxAge !== void 0) {
|
|
cacheControls.push(`max-age=${+opts.maxAge}`, `s-maxage=${+opts.maxAge}`);
|
|
}
|
|
if (opts.modifiedTime) {
|
|
const modifiedTime = new Date(opts.modifiedTime);
|
|
const ifModifiedSince = event.node.req.headers["if-modified-since"];
|
|
event.node.res.setHeader("last-modified", modifiedTime.toUTCString());
|
|
if (ifModifiedSince && new Date(ifModifiedSince) >= opts.modifiedTime) {
|
|
cacheMatched = true;
|
|
}
|
|
}
|
|
if (opts.etag) {
|
|
event.node.res.setHeader("etag", opts.etag);
|
|
const ifNonMatch = event.node.req.headers["if-none-match"];
|
|
if (ifNonMatch === opts.etag) {
|
|
cacheMatched = true;
|
|
}
|
|
}
|
|
event.node.res.setHeader("cache-control", cacheControls.join(", "));
|
|
if (cacheMatched) {
|
|
event.node.res.statusCode = 304;
|
|
event.node.res.end();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
const MIMES = {
|
|
html: "text/html",
|
|
json: "application/json"
|
|
};
|
|
|
|
const defer = typeof setImmediate !== "undefined" ? setImmediate : (fn) => fn();
|
|
function send(event, data, type) {
|
|
if (type) {
|
|
defaultContentType(event, type);
|
|
}
|
|
return new Promise((resolve) => {
|
|
defer(() => {
|
|
event.node.res.end(data);
|
|
resolve();
|
|
});
|
|
});
|
|
}
|
|
function defaultContentType(event, type) {
|
|
if (type && !event.node.res.getHeader("content-type")) {
|
|
event.node.res.setHeader("content-type", type);
|
|
}
|
|
}
|
|
function sendRedirect(event, location, code = 302) {
|
|
event.node.res.statusCode = code;
|
|
event.node.res.setHeader("location", location);
|
|
const encodedLoc = location.replace(/"/g, "%22");
|
|
const html = `<!DOCTYPE html><html><head><meta http-equiv="refresh" content="0; url=${encodedLoc}"></head></html>`;
|
|
return send(event, html, MIMES.html);
|
|
}
|
|
function getResponseHeaders(event) {
|
|
return event.node.res.getHeaders();
|
|
}
|
|
function getResponseHeader(event, name) {
|
|
return event.node.res.getHeader(name);
|
|
}
|
|
function setResponseHeaders(event, headers) {
|
|
for (const [name, value] of Object.entries(headers)) {
|
|
event.node.res.setHeader(name, value);
|
|
}
|
|
}
|
|
const setHeaders = setResponseHeaders;
|
|
function setResponseHeader(event, name, value) {
|
|
event.node.res.setHeader(name, value);
|
|
}
|
|
const setHeader = setResponseHeader;
|
|
function appendResponseHeaders(event, headers) {
|
|
for (const [name, value] of Object.entries(headers)) {
|
|
appendResponseHeader(event, name, value);
|
|
}
|
|
}
|
|
const appendHeaders = appendResponseHeaders;
|
|
function appendResponseHeader(event, name, value) {
|
|
let current = event.node.res.getHeader(name);
|
|
if (!current) {
|
|
event.node.res.setHeader(name, value);
|
|
return;
|
|
}
|
|
if (!Array.isArray(current)) {
|
|
current = [current.toString()];
|
|
}
|
|
event.node.res.setHeader(name, [...current, value]);
|
|
}
|
|
const appendHeader = appendResponseHeader;
|
|
function isStream(data) {
|
|
return data && typeof data === "object" && typeof data.pipe === "function" && typeof data.on === "function";
|
|
}
|
|
function sendStream(event, data) {
|
|
return new Promise((resolve, reject) => {
|
|
data.pipe(event.node.res);
|
|
data.on("end", () => resolve());
|
|
data.on("error", (error) => reject(createError(error)));
|
|
});
|
|
}
|
|
const noop = () => {
|
|
};
|
|
function writeEarlyHints(event, hints, cb = noop) {
|
|
if (!event.node.res.socket) {
|
|
cb();
|
|
return;
|
|
}
|
|
if (typeof hints === "string" || Array.isArray(hints)) {
|
|
hints = { link: hints };
|
|
}
|
|
if (hints.link) {
|
|
hints.link = Array.isArray(hints.link) ? hints.link : hints.link.split(",");
|
|
}
|
|
const headers = Object.entries(hints).map((e) => [e[0].toLowerCase(), e[1]]);
|
|
if (headers.length === 0) {
|
|
cb();
|
|
return;
|
|
}
|
|
let hint = "HTTP/1.1 103 Early Hints";
|
|
if (hints.link) {
|
|
hint += `\r
|
|
Link: ${hints.link.join(", ")}`;
|
|
}
|
|
for (const [header, value] of headers) {
|
|
if (header === "link") {
|
|
continue;
|
|
}
|
|
hint += `\r
|
|
${header}: ${value}`;
|
|
}
|
|
event.node.res.socket.write(`${hint}\r
|
|
\r
|
|
`, "utf8", cb);
|
|
}
|
|
|
|
function parseCookies(event) {
|
|
return parse(event.node.req.headers.cookie || "");
|
|
}
|
|
function getCookie(event, name) {
|
|
return parseCookies(event)[name];
|
|
}
|
|
function setCookie(event, name, value, serializeOptions) {
|
|
const cookieStr = serialize(name, value, {
|
|
path: "/",
|
|
...serializeOptions
|
|
});
|
|
appendHeader(event, "Set-Cookie", cookieStr);
|
|
}
|
|
function deleteCookie(event, name, serializeOptions) {
|
|
setCookie(event, name, "", {
|
|
...serializeOptions,
|
|
maxAge: 0
|
|
});
|
|
}
|
|
|
|
const PayloadMethods = /* @__PURE__ */ new Set(["PATCH", "POST", "PUT", "DELETE"]);
|
|
const ignoredHeaders = /* @__PURE__ */ new Set([
|
|
"transfer-encoding",
|
|
"connection",
|
|
"keep-alive",
|
|
"upgrade",
|
|
"expect"
|
|
]);
|
|
async function proxyRequest(event, target, opts = {}) {
|
|
const method = getMethod(event);
|
|
let body;
|
|
if (PayloadMethods.has(method)) {
|
|
body = await readRawBody(event).catch(() => void 0);
|
|
}
|
|
const headers = /* @__PURE__ */ Object.create(null);
|
|
const reqHeaders = getRequestHeaders(event);
|
|
for (const name in reqHeaders) {
|
|
if (!ignoredHeaders.has(name)) {
|
|
headers[name] = reqHeaders[name];
|
|
}
|
|
}
|
|
if (opts.fetchOptions?.headers) {
|
|
Object.assign(headers, opts.fetchOptions.headers);
|
|
}
|
|
if (opts.headers) {
|
|
Object.assign(headers, opts.headers);
|
|
}
|
|
return sendProxy(event, target, {
|
|
...opts,
|
|
fetchOptions: {
|
|
headers,
|
|
method,
|
|
body,
|
|
...opts.fetchOptions
|
|
}
|
|
});
|
|
}
|
|
async function sendProxy(event, target, opts = {}) {
|
|
const _fetch = opts.fetch || globalThis.fetch;
|
|
if (!_fetch) {
|
|
throw new Error("fetch is not available. Try importing `node-fetch-native/polyfill` for Node.js.");
|
|
}
|
|
const response = await _fetch(target, {
|
|
headers: opts.headers,
|
|
...opts.fetchOptions
|
|
});
|
|
event.node.res.statusCode = response.status;
|
|
event.node.res.statusMessage = response.statusText;
|
|
for (const [key, value] of response.headers.entries()) {
|
|
if (key === "content-encoding") {
|
|
continue;
|
|
}
|
|
if (key === "content-length") {
|
|
continue;
|
|
}
|
|
event.node.res.setHeader(key, value);
|
|
}
|
|
try {
|
|
if (response.body) {
|
|
if (opts.sendStream === false) {
|
|
const data = new Uint8Array(await response.arrayBuffer());
|
|
event.node.res.end(data);
|
|
} else {
|
|
for await (const chunk of response.body) {
|
|
event.node.res.write(chunk);
|
|
}
|
|
event.node.res.end();
|
|
}
|
|
}
|
|
} catch (error) {
|
|
event.node.res.end();
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
class H3Headers {
|
|
constructor(init) {
|
|
if (!init) {
|
|
this._headers = {};
|
|
} else if (Array.isArray(init)) {
|
|
this._headers = Object.fromEntries(init.map(([key, value]) => [key.toLowerCase(), value]));
|
|
} else if (init && "append" in init) {
|
|
this._headers = Object.fromEntries(init.entries());
|
|
} else {
|
|
this._headers = Object.fromEntries(Object.entries(init).map(([key, value]) => [key.toLowerCase(), value]));
|
|
}
|
|
}
|
|
[Symbol.iterator]() {
|
|
return this.entries();
|
|
}
|
|
entries() {
|
|
throw Object.entries(this._headers)[Symbol.iterator]();
|
|
}
|
|
keys() {
|
|
return Object.keys(this._headers)[Symbol.iterator]();
|
|
}
|
|
values() {
|
|
throw Object.values(this._headers)[Symbol.iterator]();
|
|
}
|
|
append(name, value) {
|
|
const _name = name.toLowerCase();
|
|
this.set(_name, [this.get(_name), value].filter(Boolean).join(", "));
|
|
}
|
|
delete(name) {
|
|
delete this._headers[name.toLowerCase()];
|
|
}
|
|
get(name) {
|
|
return this._headers[name.toLowerCase()];
|
|
}
|
|
has(name) {
|
|
return name.toLowerCase() in this._headers;
|
|
}
|
|
set(name, value) {
|
|
this._headers[name.toLowerCase()] = String(value);
|
|
}
|
|
forEach(callbackfn) {
|
|
for (const [key, value] of Object.entries(this._headers)) {
|
|
callbackfn(value, key, this);
|
|
}
|
|
}
|
|
}
|
|
|
|
class H3Response {
|
|
constructor(body = null, init = {}) {
|
|
this.body = null;
|
|
this.type = "default";
|
|
this.bodyUsed = false;
|
|
this.headers = new H3Headers(init.headers);
|
|
this.status = init.status ?? 200;
|
|
this.statusText = init.statusText || "";
|
|
this.redirected = !!init.status && [301, 302, 307, 308].includes(init.status);
|
|
this._body = body;
|
|
this.url = "";
|
|
this.ok = this.status < 300 && this.status > 199;
|
|
}
|
|
clone() {
|
|
return new H3Response(this.body, {
|
|
headers: this.headers,
|
|
status: this.status,
|
|
statusText: this.statusText
|
|
});
|
|
}
|
|
arrayBuffer() {
|
|
return Promise.resolve(this._body);
|
|
}
|
|
blob() {
|
|
return Promise.resolve(this._body);
|
|
}
|
|
formData() {
|
|
return Promise.resolve(this._body);
|
|
}
|
|
json() {
|
|
return Promise.resolve(this._body);
|
|
}
|
|
text() {
|
|
return Promise.resolve(this._body);
|
|
}
|
|
}
|
|
|
|
class H3Event {
|
|
constructor(req, res) {
|
|
this["__is_event__"] = true;
|
|
this.context = {};
|
|
this.node = { req, res };
|
|
}
|
|
get path() {
|
|
return this.req.url;
|
|
}
|
|
get req() {
|
|
return this.node.req;
|
|
}
|
|
get res() {
|
|
return this.node.res;
|
|
}
|
|
respondWith(r) {
|
|
Promise.resolve(r).then((_response) => {
|
|
if (this.res.writableEnded) {
|
|
return;
|
|
}
|
|
const response = _response instanceof H3Response ? _response : new H3Response(_response);
|
|
for (const [key, value] of response.headers.entries()) {
|
|
this.res.setHeader(key, value);
|
|
}
|
|
if (response.status) {
|
|
this.res.statusCode = response.status;
|
|
}
|
|
if (response.statusText) {
|
|
this.res.statusMessage = response.statusText;
|
|
}
|
|
if (response.redirected) {
|
|
this.res.setHeader("location", response.url);
|
|
}
|
|
if (!response._body) {
|
|
return this.res.end();
|
|
}
|
|
if (typeof response._body === "string" || "buffer" in response._body || "byteLength" in response._body) {
|
|
return this.res.end(response._body);
|
|
}
|
|
if (!response.headers.has("content-type")) {
|
|
response.headers.set("content-type", MIMES.json);
|
|
}
|
|
this.res.end(JSON.stringify(response._body));
|
|
});
|
|
}
|
|
}
|
|
function isEvent(input) {
|
|
return "__is_event__" in input;
|
|
}
|
|
function createEvent(req, res) {
|
|
return new H3Event(req, res);
|
|
}
|
|
|
|
function defineEventHandler(handler) {
|
|
handler.__is_handler__ = true;
|
|
return handler;
|
|
}
|
|
const eventHandler = defineEventHandler;
|
|
function isEventHandler(input) {
|
|
return "__is_handler__" in input;
|
|
}
|
|
function toEventHandler(input, _, _route) {
|
|
if (!isEventHandler(input)) {
|
|
console.warn(
|
|
"[h3] Implicit event handler conversion is deprecated. Use `eventHandler()` or `fromNodeMiddleware()` to define event handlers.",
|
|
_route && _route !== "/" ? `
|
|
Route: ${_route}` : "",
|
|
`
|
|
Handler: ${input}`
|
|
);
|
|
}
|
|
return input;
|
|
}
|
|
function dynamicEventHandler(initial) {
|
|
let current = initial;
|
|
const wrapper = eventHandler((event) => {
|
|
if (current) {
|
|
return current(event);
|
|
}
|
|
});
|
|
wrapper.set = (handler) => {
|
|
current = handler;
|
|
};
|
|
return wrapper;
|
|
}
|
|
function defineLazyEventHandler(factory) {
|
|
let _promise;
|
|
let _resolved;
|
|
const resolveHandler = () => {
|
|
if (_resolved) {
|
|
return Promise.resolve(_resolved);
|
|
}
|
|
if (!_promise) {
|
|
_promise = Promise.resolve(factory()).then((r) => {
|
|
const handler = r.default || r;
|
|
if (typeof handler !== "function") {
|
|
throw new TypeError("Invalid lazy handler result. It should be a function:", handler);
|
|
}
|
|
_resolved = toEventHandler(r.default || r);
|
|
return _resolved;
|
|
});
|
|
}
|
|
return _promise;
|
|
};
|
|
return eventHandler((event) => {
|
|
if (_resolved) {
|
|
return _resolved(event);
|
|
}
|
|
return resolveHandler().then((handler) => handler(event));
|
|
});
|
|
}
|
|
const lazyEventHandler = defineLazyEventHandler;
|
|
|
|
function createApp(options = {}) {
|
|
const stack = [];
|
|
const handler = createAppEventHandler(stack, options);
|
|
const app = {
|
|
use: (arg1, arg2, arg3) => use(app, arg1, arg2, arg3),
|
|
handler,
|
|
stack,
|
|
options
|
|
};
|
|
return app;
|
|
}
|
|
function use(app, arg1, arg2, arg3) {
|
|
if (Array.isArray(arg1)) {
|
|
for (const i of arg1) {
|
|
use(app, i, arg2, arg3);
|
|
}
|
|
} else if (Array.isArray(arg2)) {
|
|
for (const i of arg2) {
|
|
use(app, arg1, i, arg3);
|
|
}
|
|
} else if (typeof arg1 === "string") {
|
|
app.stack.push(normalizeLayer({ ...arg3, route: arg1, handler: arg2 }));
|
|
} else if (typeof arg1 === "function") {
|
|
app.stack.push(normalizeLayer({ ...arg2, route: "/", handler: arg1 }));
|
|
} else {
|
|
app.stack.push(normalizeLayer({ ...arg1 }));
|
|
}
|
|
return app;
|
|
}
|
|
function createAppEventHandler(stack, options) {
|
|
const spacing = options.debug ? 2 : void 0;
|
|
return eventHandler(async (event) => {
|
|
event.node.req.originalUrl = event.node.req.originalUrl || event.node.req.url || "/";
|
|
const reqUrl = event.node.req.url || "/";
|
|
for (const layer of stack) {
|
|
if (layer.route.length > 1) {
|
|
if (!reqUrl.startsWith(layer.route)) {
|
|
continue;
|
|
}
|
|
event.node.req.url = reqUrl.slice(layer.route.length) || "/";
|
|
} else {
|
|
event.node.req.url = reqUrl;
|
|
}
|
|
if (layer.match && !layer.match(event.node.req.url, event)) {
|
|
continue;
|
|
}
|
|
const val = await layer.handler(event);
|
|
if (event.node.res.writableEnded) {
|
|
return;
|
|
}
|
|
const type = typeof val;
|
|
if (type === "string") {
|
|
return send(event, val, MIMES.html);
|
|
} else if (isStream(val)) {
|
|
return sendStream(event, val);
|
|
} else if (val === null) {
|
|
event.node.res.statusCode = 204;
|
|
return send(event);
|
|
} else if (type === "object" || type === "boolean" || type === "number") {
|
|
if (val.buffer) {
|
|
return send(event, val);
|
|
} else if (val instanceof Error) {
|
|
throw createError(val);
|
|
} else {
|
|
return send(event, JSON.stringify(val, void 0, spacing), MIMES.json);
|
|
}
|
|
}
|
|
}
|
|
if (!event.node.res.writableEnded) {
|
|
throw createError({
|
|
statusCode: 404,
|
|
statusMessage: `Cannot find any route matching ${event.node.req.url || "/"}.`
|
|
});
|
|
}
|
|
});
|
|
}
|
|
function normalizeLayer(input) {
|
|
let handler = input.handler;
|
|
if (handler.handler) {
|
|
handler = handler.handler;
|
|
}
|
|
if (input.lazy) {
|
|
handler = lazyEventHandler(handler);
|
|
} else if (!isEventHandler(handler)) {
|
|
handler = toEventHandler(handler, void 0, input.route);
|
|
}
|
|
return {
|
|
route: withoutTrailingSlash(input.route),
|
|
match: input.match,
|
|
handler
|
|
};
|
|
}
|
|
|
|
const defineNodeListener = (handler) => handler;
|
|
const defineNodeMiddleware = (middleware) => middleware;
|
|
function fromNodeMiddleware(handler) {
|
|
if (isEventHandler(handler)) {
|
|
return handler;
|
|
}
|
|
if (typeof handler !== "function") {
|
|
throw new TypeError("Invalid handler. It should be a function:", handler);
|
|
}
|
|
return eventHandler((event) => {
|
|
return callNodeListener(handler, event.node.req, event.node.res);
|
|
});
|
|
}
|
|
function toNodeListener(app) {
|
|
const toNodeHandle = async function(req, res) {
|
|
const event = createEvent(req, res);
|
|
try {
|
|
await app.handler(event);
|
|
} catch (_error) {
|
|
const error = createError(_error);
|
|
if (!isError(_error)) {
|
|
error.unhandled = true;
|
|
}
|
|
if (app.options.onError) {
|
|
await app.options.onError(error, event);
|
|
} else {
|
|
if (error.unhandled || error.fatal) {
|
|
console.error("[h3]", error.fatal ? "[fatal]" : "[unhandled]", error);
|
|
}
|
|
await sendError(event, error, !!app.options.debug);
|
|
}
|
|
}
|
|
};
|
|
return toNodeHandle;
|
|
}
|
|
function promisifyNodeListener(handler) {
|
|
return function(req, res) {
|
|
return callNodeListener(handler, req, res);
|
|
};
|
|
}
|
|
function callNodeListener(handler, req, res) {
|
|
const isMiddleware = handler.length > 2;
|
|
return new Promise((resolve, reject) => {
|
|
const next = (err) => {
|
|
if (isMiddleware) {
|
|
res.off("close", next);
|
|
res.off("error", next);
|
|
}
|
|
return err ? reject(createError(err)) : resolve(void 0);
|
|
};
|
|
try {
|
|
const returned = handler(req, res, next);
|
|
if (isMiddleware && returned === void 0) {
|
|
res.once("close", next);
|
|
res.once("error", next);
|
|
} else {
|
|
resolve(returned);
|
|
}
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
});
|
|
}
|
|
|
|
const RouterMethods = ["connect", "delete", "get", "head", "options", "post", "put", "trace", "patch"];
|
|
function createRouter(opts = {}) {
|
|
const _router = createRouter$1({});
|
|
const routes = {};
|
|
const router = {};
|
|
const addRoute = (path, handler, method) => {
|
|
let route = routes[path];
|
|
if (!route) {
|
|
routes[path] = route = { handlers: {} };
|
|
_router.insert(path, route);
|
|
}
|
|
if (Array.isArray(method)) {
|
|
for (const m of method) {
|
|
addRoute(path, handler, m);
|
|
}
|
|
} else {
|
|
route.handlers[method] = toEventHandler(handler, void 0, path);
|
|
}
|
|
return router;
|
|
};
|
|
router.use = router.add = (path, handler, method) => addRoute(path, handler, method || "all");
|
|
for (const method of RouterMethods) {
|
|
router[method] = (path, handle) => router.add(path, handle, method);
|
|
}
|
|
router.handler = eventHandler((event) => {
|
|
let path = event.node.req.url || "/";
|
|
const qIndex = path.indexOf("?");
|
|
if (qIndex !== -1) {
|
|
path = path.slice(0, Math.max(0, qIndex));
|
|
}
|
|
const matched = _router.lookup(path);
|
|
if (!matched) {
|
|
if (opts.preemtive) {
|
|
throw createError({
|
|
statusCode: 404,
|
|
name: "Not Found",
|
|
statusMessage: `Cannot find any route matching ${event.node.req.url || "/"}.`
|
|
});
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
const method = (event.node.req.method || "get").toLowerCase();
|
|
const handler = matched.handlers[method] || matched.handlers.all;
|
|
if (!handler) {
|
|
throw createError({
|
|
statusCode: 405,
|
|
name: "Method Not Allowed",
|
|
statusMessage: `Method ${method} is not allowed on this route.`
|
|
});
|
|
}
|
|
const params = matched.params || {};
|
|
event.context.params = params;
|
|
return handler(event);
|
|
});
|
|
return router;
|
|
}
|
|
|
|
export { H3Error, H3Event, H3Headers, H3Response, MIMES, appendHeader, appendHeaders, appendResponseHeader, appendResponseHeaders, assertMethod, callNodeListener, createApp, createAppEventHandler, createError, createEvent, createRouter, defaultContentType, defineEventHandler, defineLazyEventHandler, defineNodeListener, defineNodeMiddleware, deleteCookie, dynamicEventHandler, eventHandler, fromNodeMiddleware, getCookie, getHeader, getHeaders, getMethod, getQuery, getRequestHeader, getRequestHeaders, getResponseHeader, getResponseHeaders, getRouterParam, getRouterParams, handleCacheHeaders, isError, isEvent, isEventHandler, isMethod, isStream, lazyEventHandler, parseCookies, promisifyNodeListener, proxyRequest, readBody, readRawBody, send, sendError, sendProxy, sendRedirect, sendStream, setCookie, setHeader, setHeaders, setResponseHeader, setResponseHeaders, toEventHandler, toNodeListener, use, useBase, writeEarlyHints };
|