h3
Version:
1,514 lines • 109 kB
JavaScript
import { NullProtoObj as EmptyObject, addRoute, createRouter, findRoute, removeRoute, routeToRegExp } from "rou3";
import { FastResponse, FastURL } from "srvx";
function freezeApp(app) {
app.config = Object.freeze(app.config);
app["~addRoute"] = () => {
throw new Error("Cannot add routes after the server init.");
};
}
function withoutTrailingSlash(path) {
if (!path || path === "/") return "/";
return path[path.length - 1] === "/" ? path.slice(0, -1) : path;
}
function withoutBase(input = "", base = "") {
if (!base || base === "/") return input;
const _base = withoutTrailingSlash(base);
if (!input.startsWith(_base) || input.length > _base.length && input[_base.length] !== "/") return input;
return "/" + input.slice(_base.length).replace(/^\/+/, "");
}
function decodePathname(pathname) {
return decodeURI(pathname.includes("%25") ? pathname.replace(/%25/g, "%2525") : pathname);
}
const kEventNS = "h3.internal.event.";
const kEventRes = /* @__PURE__ */ Symbol.for(`${kEventNS}res`);
const kEventResHeaders = /* @__PURE__ */ Symbol.for(`${kEventNS}res.headers`);
const kEventResErrHeaders = /* @__PURE__ */ Symbol.for(`${kEventNS}res.err.headers`);
const kMalformedURL = /* @__PURE__ */ Symbol.for(`${kEventNS}malformed`);
var H3Event = class {
app;
req;
url;
context;
static __is_event__ = true;
constructor(req, context, app) {
this.context = context || req.context || new EmptyObject();
this.req = req;
this.app = app;
const _url = req._url;
let url = _url && _url instanceof URL ? _url : new FastURL(req.url);
if (url.pathname.includes("%")) try {
const pathname = decodePathname(url.pathname);
if (pathname !== url.pathname) url = new FastURL(`${url.protocol}//${url.host}${pathname}${url.search}`);
} catch {
this[kMalformedURL] = true;
}
this.url = url;
}
get res() {
return this[kEventRes] ||= new H3EventResponse();
}
get runtime() {
return this.req.runtime;
}
waitUntil(promise) {
this.req.waitUntil?.(promise);
}
toString() {
return `[${this.req.method}] ${this.req.url}`;
}
toJSON() {
return this.toString();
}
get node() {
return this.req.runtime?.node;
}
get headers() {
return this.req.headers;
}
get path() {
return this.url.pathname + this.url.search;
}
get method() {
return this.req.method;
}
};
var H3EventResponse = class {
status;
statusText;
get headers() {
return this[kEventResHeaders] ||= new Headers();
}
get errHeaders() {
return this[kEventResErrHeaders] ||= new Headers();
}
};
const DISALLOWED_STATUS_CHARS = /[^\u0009\u0020-\u007E]/g;
function sanitizeStatusMessage(statusMessage = "") {
return statusMessage.replace(DISALLOWED_STATUS_CHARS, "");
}
function sanitizeStatusCode(statusCode, defaultStatusCode = 200) {
if (!statusCode) return defaultStatusCode;
if (typeof statusCode === "string") statusCode = +statusCode;
if (Number.isNaN(statusCode) || statusCode < 100 || statusCode > 599) return defaultStatusCode;
return statusCode;
}
var HTTPError = class HTTPError extends Error {
get name() {
return "HTTPError";
}
status;
statusText;
headers;
cause;
data;
body;
unhandled;
static isError(input) {
return input instanceof Error && input?.name === "HTTPError";
}
static status(status, statusText, details) {
return new HTTPError({
...details,
statusText,
status
});
}
constructor(arg1, arg2) {
let messageInput;
let details;
if (typeof arg1 === "string") {
messageInput = arg1;
details = arg2;
} else details = arg1;
const status = sanitizeStatusCode(details?.status || details?.statusCode || (details?.cause)?.status || (details?.cause)?.statusCode, 500);
const statusText = sanitizeStatusMessage(details?.statusText || details?.statusMessage || (details?.cause)?.statusText || (details?.cause)?.statusMessage);
const message = messageInput || details?.message || (details?.cause)?.message || details?.statusText || details?.statusMessage || [
"HTTPError",
status,
statusText
].filter(Boolean).join(" ");
super(message, { cause: details });
this.cause = details;
this.status = status;
this.statusText = statusText || void 0;
const rawHeaders = details?.headers || (details?.cause)?.headers;
this.headers = rawHeaders ? new Headers(rawHeaders) : void 0;
this.unhandled = details?.unhandled ?? (details?.cause)?.unhandled ?? void 0;
this.data = details?.data;
this.body = details?.body;
}
get statusCode() {
return this.status;
}
get statusMessage() {
return this.statusText;
}
toJSON() {
const unhandled = this.unhandled;
return {
status: this.status,
statusText: this.statusText,
unhandled,
message: unhandled ? "HTTPError" : this.message,
data: unhandled ? void 0 : this.data,
...unhandled ? void 0 : this.body
};
}
};
function hasProp(obj, prop) {
try {
return prop in obj;
} catch {
return false;
}
}
function isJSONSerializable(value, _type) {
if (value === null || value === void 0) return true;
if (_type !== "object") return _type === "boolean" || _type === "number" || _type === "string";
if (typeof value.toJSON === "function") return true;
if (Array.isArray(value)) return true;
if (typeof value.pipe === "function" || typeof value.pipeTo === "function") return false;
if (value instanceof EmptyObject) return true;
const proto = Object.getPrototypeOf(value);
return proto === Object.prototype || proto === null;
}
const kNotFound = /* @__PURE__ */ Symbol.for("h3.notFound");
const kHandled = /* @__PURE__ */ Symbol.for("h3.handled");
function toResponse(val, event, config = {}) {
if (typeof val?.then === "function") return val.then((resolvedVal) => toResponse(resolvedVal, event, config), (r) => toResponse(typeof r === "number" ? new HTTPError({ status: r }) : r, event, config));
const response = prepareResponse(val, event, config);
if (typeof response?.then === "function") return toResponse(response, event, config);
const { onResponse } = config;
return onResponse ? Promise.resolve(onResponse(response, event)).then(() => response) : response;
}
var HTTPResponse = class {
#headers;
#init;
body;
constructor(body, init) {
this.body = body;
this.#init = init;
}
get status() {
return this.#init?.status || 200;
}
get statusText() {
return this.#init?.statusText || "OK";
}
get headers() {
return this.#headers ||= new Headers(this.#init?.headers);
}
};
function prepareResponse(val, event, config, nested) {
if (val === kHandled) return new FastResponse(null);
if (val === kNotFound) val = new HTTPError({
status: 404,
message: `Cannot find any route matching [${event.req.method}] ${event.url}`
});
if (val && val instanceof Error) {
const isHTTPError = HTTPError.isError(val);
const error = isHTTPError ? val : new HTTPError(val);
if (!isHTTPError) {
error.unhandled = true;
if (val?.stack) error.stack = val.stack;
}
if (error.unhandled && !config.silent) console.error(error);
const { onError } = config;
const errHeaders = event[kEventRes]?.[kEventResErrHeaders];
return onError && !nested ? Promise.resolve(onError(error, event)).catch((error) => error).then((newVal) => prepareResponse(newVal ?? val, event, config, true)) : errorResponse(error, config.debug, errHeaders);
}
const preparedRes = event[kEventRes];
const preparedHeaders = preparedRes?.[kEventResHeaders];
event[kEventRes] = void 0;
if (!(val instanceof Response)) {
const res = prepareResponseBody(val, event, config);
const status = res.status || preparedRes?.status;
return new FastResponse(nullBody(event.req.method, status) ? null : res.body, {
status,
statusText: res.statusText || preparedRes?.statusText,
headers: res.headers && preparedHeaders ? mergeHeaders$1(res.headers, preparedHeaders) : res.headers || preparedHeaders
});
}
if (!preparedHeaders || nested || !val.ok) {
if (event.req.method === "HEAD" && val.body !== null) return new FastResponse(null, {
status: val.status,
statusText: val.statusText,
headers: val.headers
});
return val;
}
try {
mergeHeaders$1(val.headers, preparedHeaders, val.headers);
return val;
} catch {
return new FastResponse(nullBody(event.req.method, val.status) ? null : val.body, {
status: val.status,
statusText: val.statusText,
headers: mergeHeaders$1(val.headers, preparedHeaders)
});
}
}
function mergeHeaders$1(base, overrides, target = new Headers(base)) {
for (const [name, value] of overrides) if (name === "set-cookie") target.append(name, value);
else target.set(name, value);
return target;
}
const frozen = (name) => (...args) => {
throw new Error(`Headers are frozen (${name} ${args.join(", ")})`);
};
var FrozenHeaders = class extends Headers {
set = frozen("set");
append = frozen("append");
delete = frozen("delete");
};
const emptyHeaders = /* @__PURE__ */ new FrozenHeaders({ "content-length": "0" });
const jsonHeaders = /* @__PURE__ */ new FrozenHeaders({ "content-type": "application/json;charset=UTF-8" });
function prepareResponseBody(val, event, config) {
if (val === null || val === void 0) return {
body: "",
headers: emptyHeaders
};
const valType = typeof val;
if (valType === "string") return { body: val };
if (val instanceof Uint8Array) {
event.res.headers.set("content-length", val.byteLength.toString());
return { body: val };
}
if (val instanceof HTTPResponse || val?.constructor?.name === "HTTPResponse") return val;
if (isJSONSerializable(val, valType)) return {
body: JSON.stringify(val, void 0, config.debug ? 2 : void 0),
headers: jsonHeaders
};
if (valType === "bigint") return {
body: val.toString(),
headers: jsonHeaders
};
if (val instanceof Blob) {
const headers = new Headers({
"content-type": val.type,
"content-length": val.size.toString()
});
let filename = val.name;
if (filename) {
filename = encodeURIComponent(filename);
headers.set("content-disposition", `filename="${filename}"; filename*=UTF-8''${filename}`);
}
return {
body: val.stream(),
headers
};
}
if (valType === "symbol") return { body: val.toString() };
if (valType === "function") return { body: `${val.name}()` };
return { body: val };
}
function nullBody(method, status) {
return method === "HEAD" || status === 100 || status === 101 || status === 102 || status === 204 || status === 205 || status === 304;
}
function errorResponse(error, debug, errHeaders) {
let headers = error.headers ? mergeHeaders$1(jsonHeaders, error.headers) : new Headers(jsonHeaders);
if (errHeaders) headers = mergeHeaders$1(headers, errHeaders);
return new FastResponse(JSON.stringify({
...error.toJSON(),
stack: debug && error.stack ? error.stack.split("\n").map((l) => l.trim()) : void 0
}, void 0, debug ? 2 : void 0), {
status: error.status,
statusText: error.statusText,
headers
});
}
function defineMiddleware(input) {
return input;
}
function normalizeMiddleware(input, opts = {}) {
const matcher = createMatcher(opts);
if (!matcher && (input.length > 1 || input.constructor?.name === "AsyncFunction")) return input;
return (event, next) => {
if (matcher && !matcher(event)) return next();
const res = input(event, next);
return res === void 0 || res === kNotFound ? next() : res;
};
}
function createMatcher(opts) {
if (!opts.route && !opts.method && !opts.match) return;
const routeMatcher = opts.route ? routeToRegExp(opts.route) : void 0;
const method = opts.method?.toUpperCase();
return function _middlewareMatcher(event) {
if (method && event.req.method !== method) {
if (!(method === "GET" && event.req.method === "HEAD")) return false;
}
if (opts.match && !opts.match(event)) return false;
if (!routeMatcher) return true;
const match = event.url.pathname.match(routeMatcher);
if (!match) return false;
if (match.groups) event.context.middlewareParams = {
...event.context.middlewareParams,
...match.groups
};
return true;
};
}
function callMiddleware(event, middleware, handler, index = 0) {
if (index === middleware.length) return handler(event);
const fn = middleware[index];
let nextCalled;
let nextResult;
const next = () => {
if (nextCalled) return nextResult;
nextCalled = true;
nextResult = callMiddleware(event, middleware, handler, index + 1);
return nextResult;
};
const ret = fn(event, next);
return isUnhandledResponse(ret) ? next() : typeof ret?.then === "function" ? ret.then((resolved) => isUnhandledResponse(resolved) ? next() : resolved) : ret;
}
function isUnhandledResponse(val) {
return val === void 0 || val === kNotFound;
}
function toMiddleware(input) {
let h = input.handler || input;
let isFunction = typeof h === "function";
if (!isFunction && typeof input?.fetch === "function") {
isFunction = true;
h = function _fetchHandler(event) {
return input.fetch(event.req);
};
}
if (!isFunction) return function noopMiddleware(event, next) {
return next();
};
if (h.length === 2) return h;
return function _middlewareHandler(event, next) {
const res = h(event);
return typeof res?.then === "function" ? res.then((r) => {
return is404(r) ? next() : r;
}) : is404(res) ? next() : res;
};
}
function is404(val) {
return isUnhandledResponse(val) || val?.status === 404 && val instanceof Response;
}
const plusRegex = /\+/g;
function parseQuery(input) {
const params = new EmptyObject();
if (!input || input === "?") return params;
const inputLength = input.length;
let key = "";
let value = "";
let startingIndex = -1;
let equalityIndex = -1;
let shouldDecodeKey = false;
let shouldDecodeValue = false;
let keyHasPlus = false;
let valueHasPlus = false;
let hasBothKeyValuePair = false;
let c = 0;
for (let i = 0; i < inputLength + 1; i++) {
c = i === inputLength ? 38 : input.charCodeAt(i);
switch (c) {
case 38:
hasBothKeyValuePair = equalityIndex > startingIndex;
if (!hasBothKeyValuePair) equalityIndex = i;
key = input.slice(startingIndex + 1, equalityIndex);
if (hasBothKeyValuePair || key.length > 0) {
if (keyHasPlus) key = key.replace(plusRegex, " ");
if (shouldDecodeKey) try {
key = decodeURIComponent(key);
} catch {}
if (hasBothKeyValuePair) {
value = input.slice(equalityIndex + 1, i);
if (valueHasPlus) value = value.replace(plusRegex, " ");
if (shouldDecodeValue) try {
value = decodeURIComponent(value);
} catch {}
}
const currentValue = params[key];
if (currentValue === void 0) params[key] = value;
else if (Array.isArray(currentValue)) currentValue.push(value);
else params[key] = [currentValue, value];
}
value = "";
startingIndex = i;
equalityIndex = i;
shouldDecodeKey = false;
shouldDecodeValue = false;
keyHasPlus = false;
valueHasPlus = false;
break;
case 61:
if (equalityIndex <= startingIndex) equalityIndex = i;
else shouldDecodeValue = true;
break;
case 43:
if (equalityIndex > startingIndex) valueHasPlus = true;
else keyHasPlus = true;
break;
case 37:
if (equalityIndex > startingIndex) shouldDecodeValue = true;
else shouldDecodeKey = true;
break;
}
}
return params;
}
const VALIDATION_FAILED = "Validation failed";
async function validateData(data, fn, options) {
if ("~standard" in fn) {
const result = await fn["~standard"].validate(data);
if (result.issues) throw createValidationError(options?.onError?.(result) || {
message: VALIDATION_FAILED,
issues: result.issues
});
return result.value;
}
try {
const res = await fn(data);
if (res === false) throw createValidationError(options?.onError?.({ issues: [{ message: VALIDATION_FAILED }] }) || { message: VALIDATION_FAILED });
if (res === true) return data;
return res ?? data;
} catch (error) {
throw createValidationError(error);
}
}
const reqBodyKeys = /* @__PURE__ */ new Set([
"body",
"text",
"formData",
"arrayBuffer"
]);
function validatedRequest(req, validate) {
if (validate.headers) {
const validatedheaders = syncValidate("headers", Object.fromEntries(req.headers.entries()), validate.headers, validate.onError);
for (const [key, value] of Object.entries(validatedheaders)) req.headers.set(key, value);
}
if (!validate.body) return req;
return new Proxy(req, { get(_target, prop) {
if (prop === "json") return function _validatedJson() {
return req.json().then((data) => validate.body["~standard"].validate(data)).then((result) => {
if (result.issues) throw createValidationError(validate.onError?.({
_source: "body",
...result
}) || {
message: VALIDATION_FAILED,
issues: result.issues
});
return result.value;
});
};
else if (reqBodyKeys.has(prop)) throw new TypeError(`Cannot access .${prop} on request with JSON validation enabled. Use .json() instead.`);
return Reflect.get(req, prop);
} });
}
function validatedURL(url, validate) {
if (!validate.query) return url;
const validatedQuery = syncValidate("query", Object.fromEntries(url.searchParams.entries()), validate.query, validate.onError);
for (const [key, value] of Object.entries(validatedQuery)) url.searchParams.set(key, value);
return url;
}
function syncValidate(source, data, fn, onError) {
const result = fn["~standard"].validate(data);
if (result instanceof Promise) throw new TypeError(`Asynchronous validation is not supported for ${source}`);
if (result.issues) throw createValidationError(onError?.({
_source: source,
...result
}) || {
message: VALIDATION_FAILED,
issues: result.issues
});
return result.value;
}
function createValidationError(cause) {
return HTTPError.isError(cause) ? cause : new HTTPError({
cause,
status: cause?.status || 400,
statusText: cause?.statusText || VALIDATION_FAILED,
message: cause?.message || VALIDATION_FAILED,
data: {
issues: cause?.issues,
message: cause instanceof Error ? VALIDATION_FAILED : cause?.message || VALIDATION_FAILED
}
});
}
function isEvent(input) {
return input instanceof H3Event || input?.constructor?.__is_event__;
}
function isHTTPEvent(input) {
return input?.req instanceof Request;
}
function getEventContext(event) {
if (event.context) return event.context;
event.req.context ??= {};
return event.req.context;
}
function mockEvent(_request, options) {
let request;
if (options?.body && !options.duplex) options.duplex = "half";
if (typeof _request === "string") {
let url = _request;
if (url[0] === "/") url = `http://localhost${url}`;
request = new Request(url, options);
} else if (options || _request instanceof URL) request = new Request(_request, options);
else request = _request;
return new H3Event(request);
}
function requestWithURL(req, url) {
const cache = {
url,
_url: void 0
};
return new Proxy(req, { get(target, prop) {
if (prop in cache) return cache[prop];
const value = Reflect.get(target, prop);
cache[prop] = typeof value === "function" ? value.bind(target) : value;
return cache[prop];
} });
}
function requestWithBaseURL(req, base) {
const url = new URL(req.url);
let pathname;
try {
pathname = decodePathname(url.pathname);
} catch {
pathname = url.pathname;
}
url.pathname = pathname.slice(base.length) || "/";
return requestWithURL(req, url.href);
}
function toRequest(input, options) {
if (typeof input === "string") {
let url = input;
if (url[0] === "/") {
const headers = options?.headers ? new Headers(options.headers) : void 0;
const host = headers?.get("host") || "localhost";
url = `${(headers?.get("x-forwarded-proto") || "").split(",")[0].trim() === "https" ? "https" : "http"}://${host}${url}`;
}
return new Request(url, options);
} else if (options || input instanceof URL) return new Request(input, options);
return input;
}
function getQuery(event) {
return parseQuery((event.url || new URL(event.req.url)).search.slice(1));
}
function getValidatedQuery(event, validate, options) {
return validateData(getQuery(event), validate, options);
}
function getRouterParams(event, opts = {}) {
let params = getEventContext(event).params || {};
if (opts.decode) {
params = { ...params };
for (const key in params) params[key] = decodeRouterParam(params[key]);
}
return params;
}
const ENCODED_SEP_RE_G$1 = /%(?:25)*(?:2f|5c)/gi;
function decodeRouterParam(value) {
if (!value.includes("%")) return value;
let result = "";
let lastIndex = 0;
ENCODED_SEP_RE_G$1.lastIndex = 0;
for (let m; m = ENCODED_SEP_RE_G$1.exec(value);) {
result += decodeURIComponent(value.slice(lastIndex, m.index)) + m[0];
lastIndex = m.index + m[0].length;
}
return result + decodeURIComponent(value.slice(lastIndex));
}
function getValidatedRouterParams(event, validate, options = {}) {
const { decode, ...opts } = options;
return validateData(getRouterParams(event, { decode }), validate, opts);
}
function getRouterParam(event, name, opts = {}) {
return getRouterParams(event, opts)[name];
}
function isMethod(event, expected, allowHead) {
if (allowHead && event.req.method === "HEAD") return true;
if (typeof expected === "string") {
if (event.req.method === expected) return true;
} else if (expected.includes(event.req.method)) return true;
return false;
}
function assertMethod(event, expected, allowHead) {
if (!isMethod(event, expected, allowHead)) {
const allowed = Array.isArray(expected) ? expected : [expected];
throw new HTTPError({
status: 405,
headers: { Allow: allowHead ? [...allowed, "HEAD"].join(", ") : allowed.join(", ") }
});
}
}
function getRequestHost(event, opts = {}) {
if (opts.xForwardedHost) {
const xForwardedHost = (event.req.headers.get("x-forwarded-host") || "").split(",").shift()?.trim();
if (xForwardedHost) return xForwardedHost;
}
return event.req.headers.get("host") || "";
}
function getRequestProtocol(event, opts = {}) {
if (opts.xForwardedProto !== false) {
const forwardedProto = (event.req.headers.get("x-forwarded-proto") || "").split(",")[0].trim();
if (forwardedProto === "https") return "https";
if (forwardedProto === "http") return "http";
}
return (event.url || new URL(event.req.url)).protocol.slice(0, -1);
}
function getRequestURL(event, opts = {}) {
const url = new URL(event.url || event.req.url);
url.protocol = getRequestProtocol(event, opts);
if (opts.xForwardedHost) {
const host = getRequestHost(event, opts);
if (host) {
url.host = host;
if (!/:\d+$/.test(host)) url.port = "";
}
}
return url;
}
function getRequestIP(event, opts = {}) {
if (opts.xForwardedFor) {
const _header = event.req.headers.get("x-forwarded-for");
if (_header) {
const xForwardedFor = _header.split(",")[0].trim();
if (xForwardedFor) return xForwardedFor;
}
}
return event.req.context?.clientAddress || event.req.ip || void 0;
}
function defineHandler(input) {
if (typeof input === "function") return handlerWithFetch(input);
const handler = input.handler || (input.fetch ? function _fetchHandler(event) {
return input.fetch(event.req);
} : NoHandler);
return Object.assign(handlerWithFetch(input.middleware?.length ? function _handlerMiddleware(event) {
return callMiddleware(event, input.middleware, handler);
} : handler), input);
}
function defineValidatedHandler(def) {
if (!def.validate) return defineHandler(def);
return defineHandler({
...def,
handler: function _validatedHandler(event) {
event.req = validatedRequest(event.req, def.validate);
event.url = validatedURL(event.url, def.validate);
return def.handler(event);
}
});
}
function handlerWithFetch(handler) {
if ("fetch" in handler) return handler;
return Object.assign(handler, { fetch: (req) => {
if (typeof req === "string") req = new URL(req, "http://_");
if (req instanceof URL) req = new Request(req);
const event = new H3Event(req);
try {
return Promise.resolve(toResponse(handler(event), event));
} catch (error) {
return Promise.resolve(toResponse(error, event));
}
} });
}
function dynamicEventHandler(initial) {
let current = toEventHandler(initial);
return Object.assign(defineHandler(function _dynamicEventHandler(event) {
return current?.(event);
}), { set: (handler) => {
current = toEventHandler(handler);
} });
}
function defineLazyEventHandler(loader) {
let handler;
let promise;
return defineHandler(function lazyHandler(event) {
return handler ? handler(event) : (promise ??= Promise.resolve(loader()).then(function resolveLazyHandler(r) {
handler = toEventHandler(r) || toEventHandler(r.default);
if (typeof handler !== "function") throw new TypeError("Invalid lazy handler", { cause: { resolved: r } });
return handler;
})).then((r) => r(event));
});
}
function toEventHandler(handler) {
if (typeof handler === "function") return handler;
if (typeof handler?.handler === "function" && handler.constructor?.["~h3"]) return handler.handler;
if (typeof handler?.fetch === "function") return function _fetchHandler(event) {
return handler.fetch(event.req);
};
}
const NoHandler = () => kNotFound;
var H3Core = class {
static "~h3" = true;
config;
"~middleware";
"~routes" = [];
constructor(config = {}) {
this["~middleware"] = [];
this.config = config;
this.fetch = this.fetch.bind(this);
this.handler = this.handler.bind(this);
}
fetch(request) {
return this["~request"](request);
}
handler(event) {
const route = this["~findRoute"](event);
if (route) {
event.context.params = route.params;
event.context.matchedRoute = route.data;
}
const routeHandler = route?.data.handler || NoHandler;
const middleware = this["~getMiddleware"](event, route);
return middleware.length > 0 ? callMiddleware(event, middleware, routeHandler) : routeHandler(event);
}
"~request"(request, context) {
const event = new H3Event(request, context, this);
let handlerRes;
try {
if (event[kMalformedURL] && !this.config.allowMalformedURL) throw new HTTPError({
status: 400,
message: "Bad Request"
});
if (this.config.onRequest) {
const hookRes = this.config.onRequest(event);
handlerRes = typeof hookRes?.then === "function" ? hookRes.then(() => this.handler(event)) : this.handler(event);
} else handlerRes = this.handler(event);
} catch (error) {
handlerRes = Promise.reject(error);
}
return toResponse(handlerRes, event, this.config);
}
"~findRoute"(_event) {}
"~addRoute"(_route) {
this["~routes"].push(_route);
}
"~getMiddleware"(_event, route) {
const routeMiddleware = route?.data.middleware;
const globalMiddleware = this["~middleware"];
return routeMiddleware ? [...globalMiddleware, ...routeMiddleware] : globalMiddleware;
}
};
const H3 = /* @__PURE__ */ (() => {
class H3 extends H3Core {
"~rou3";
constructor(config = {}) {
super(config);
this["~rou3"] = createRouter();
this.request = this.request.bind(this);
config.plugins?.forEach((plugin) => plugin(this));
}
register(plugin) {
plugin(this);
return this;
}
request(_req, _init, context) {
return this["~request"](toRequest(_req, _init), context);
}
mount(base, input) {
if ("handler" in input) {
if (input["~middleware"].length > 0) this["~middleware"].push((event, next) => {
const originalPathname = event.url.pathname;
if (!originalPathname.startsWith(base) || originalPathname.length > base.length && originalPathname[base.length] !== "/") return next();
event.url.pathname = event.url.pathname.slice(base.length) || "/";
const restore = () => {
event.url.pathname = originalPathname;
};
try {
const result = callMiddleware(event, input["~middleware"], () => {
restore();
return next();
});
if (typeof result?.then === "function") return Promise.resolve(result).finally(restore);
restore();
return result;
} catch (err) {
restore();
throw err;
}
});
for (const r of input["~routes"]) this["~addRoute"]({
...r,
route: base + r.route
});
} else {
const fetchHandler = "fetch" in input ? input.fetch : input;
this.all(`${base}/**`, function _mountedMiddleware(event) {
return fetchHandler(requestWithBaseURL(event.req, base));
});
}
return this;
}
on(method, route, handler, opts) {
const _method = (method || "").toUpperCase();
route = new URL(route, "http://_").pathname;
this["~addRoute"]({
method: _method,
route,
handler: toEventHandler(handler),
middleware: opts?.middleware,
meta: {
...handler.meta,
...opts?.meta
}
});
return this;
}
all(route, handler, opts) {
return this.on("", route, handler, opts);
}
"~findRoute"(_event) {
const match = findRoute(this["~rou3"], _event.req.method, _event.url.pathname);
if (match === void 0 && _event.req.method === "HEAD") return findRoute(this["~rou3"], "GET", _event.url.pathname);
return match;
}
"~addRoute"(_route) {
addRoute(this["~rou3"], _route.method, _route.route, _route);
super["~addRoute"](_route);
}
use(arg1, arg2, arg3) {
let route;
let fn;
let opts;
if (typeof arg1 === "string") {
route = arg1;
fn = arg2;
opts = arg3;
} else {
fn = arg1;
opts = arg2;
}
if (typeof fn !== "function" && "handler" in fn) return this.mount(route || "", fn);
this["~middleware"].push(normalizeMiddleware(fn, {
...opts,
route
}));
return this;
}
}
for (const method of [
"GET",
"POST",
"PUT",
"DELETE",
"PATCH",
"HEAD",
"OPTIONS",
"CONNECT",
"TRACE",
"QUERY"
]) H3Core.prototype[method.toLowerCase()] = function(route, handler, opts) {
return this.on(method, route, handler, opts);
};
return H3;
})();
function definePlugin(def) {
return ((opts) => (h3) => def(h3, opts));
}
function toWebHandler(app) {
return (request, context) => {
return Promise.resolve(app.request(request, void 0, context || request.context));
};
}
function fromWebHandler(handler) {
return function _webHandler(event) {
return handler(event.req, event.context);
};
}
function fromNodeHandler(handler) {
if (typeof handler !== "function") throw new TypeError(`Invalid handler. It should be a function: ${handler}`);
return function _nodeHandler(event) {
const node = event.runtime?.node;
if (!node?.res) throw new Error("[h3] Executing Node.js middleware is not supported in this server!");
const url = event.url.pathname + event.url.search;
if (node.req.url === url) return callNodeHandler(handler, node.req, node.res);
const originalUrl = node.req.url;
node.req.url = url;
return callNodeHandler(handler, node.req, node.res).finally(() => {
node.req.url = originalUrl;
});
};
}
function defineNodeHandler(handler) {
return handler;
}
function defineNodeMiddleware(handler) {
return handler;
}
function callNodeHandler(handler, req, res) {
const isMiddleware = handler.length > 2;
return new Promise((resolve, reject) => {
res.once("close", () => resolve(kHandled));
res.once("finish", () => resolve(kHandled));
res.once("error", (error) => reject(error));
res.once("pipe", (stream) => {
resolve(new Promise((resolve, reject) => {
stream.once("close", () => resolve(kHandled));
stream.once("error", (error) => {
console.error("[h3] Stream error in Node.js handler", { cause: error });
reject(kHandled);
});
}));
});
try {
if (isMiddleware) Promise.resolve(handler(req, res, (error) => error ? reject(new HTTPError({
cause: error,
unhandled: true
})) : resolve(void 0))).catch((error) => reject(new HTTPError({
cause: error,
unhandled: true
})));
else return Promise.resolve(handler(req, res)).then(() => resolve(kHandled)).catch((error) => reject(new HTTPError({
cause: error,
unhandled: true
})));
} catch (error) {
reject(new HTTPError({
cause: error,
unhandled: true
}));
}
});
}
function defineRoute(def) {
const handler = defineValidatedHandler(def);
return (h3) => {
h3.on(def.method, def.route, handler);
};
}
function removeRoute$1(app, method, route) {
const _method = method ? method.toUpperCase() : void 0;
route = new URL(route, "http://_").pathname;
removeRoute(app["~rou3"], _method || "", route);
const idx = app["~routes"].findIndex((r) => r.route === route && (_method == null || r.method === _method));
if (idx !== -1) app["~routes"].splice(idx, 1);
}
const textEncoder = /* @__PURE__ */ new TextEncoder();
const textDecoder = /* @__PURE__ */ new TextDecoder();
const base64Code = [
65,
66,
67,
68,
69,
70,
71,
72,
73,
74,
75,
76,
77,
78,
79,
80,
81,
82,
83,
84,
85,
86,
87,
88,
89,
90,
97,
98,
99,
100,
101,
102,
103,
104,
105,
106,
107,
108,
109,
110,
111,
112,
113,
114,
115,
116,
117,
118,
119,
120,
121,
122,
48,
49,
50,
51,
52,
53,
54,
55,
56,
57,
45,
95
];
function base64Encode(data) {
const buff = validateBinaryLike(data);
if (globalThis.Buffer) return globalThis.Buffer.from(buff).toString("base64url");
const bytes = [];
let i;
const len = buff.length;
for (i = 2; i < len; i += 3) bytes.push(base64Code[buff[i - 2] >> 2], base64Code[(buff[i - 2] & 3) << 4 | buff[i - 1] >> 4], base64Code[(buff[i - 1] & 15) << 2 | buff[i] >> 6], base64Code[buff[i] & 63]);
if (i === len + 1) bytes.push(base64Code[buff[i - 2] >> 2], base64Code[(buff[i - 2] & 3) << 4]);
if (i === len) bytes.push(base64Code[buff[i - 2] >> 2], base64Code[(buff[i - 2] & 3) << 4 | buff[i - 1] >> 4], base64Code[(buff[i - 1] & 15) << 2]);
return String.fromCharCode(...bytes);
}
function base64Decode(b64Url) {
if (globalThis.Buffer) return new Uint8Array(globalThis.Buffer.from(b64Url, "base64url"));
const b64 = b64Url.replace(/-/g, "+").replace(/_/g, "/");
const binString = atob(b64);
const size = binString.length;
const bytes = new Uint8Array(size);
for (let i = 0; i < size; i++) bytes[i] = binString.charCodeAt(i);
return bytes;
}
function validateBinaryLike(source) {
if (typeof source === "string") return textEncoder.encode(source);
else if (source instanceof Uint8Array) return source;
else if (source instanceof ArrayBuffer) return new Uint8Array(source);
throw new TypeError(`The input must be a Uint8Array, a string, or an ArrayBuffer.`);
}
function serializeIterableValue(value) {
switch (typeof value) {
case "string": return textEncoder.encode(value);
case "boolean":
case "number":
case "bigint":
case "symbol": return textEncoder.encode(value.toString());
case "object":
if (value instanceof Uint8Array) return value;
return textEncoder.encode(JSON.stringify(value));
}
return /* @__PURE__ */ new Uint8Array();
}
function coerceIterable(iterable) {
if (typeof iterable === "function") iterable = iterable();
if (Symbol.iterator in iterable) return iterable[Symbol.iterator]();
if (Symbol.asyncIterator in iterable) return iterable[Symbol.asyncIterator]();
return iterable;
}
function noContent(status = 204) {
return new HTTPResponse(null, {
status,
statusText: "No Content"
});
}
function redirect(location, status = 302, statusText) {
return new HTTPResponse(`<html><head><meta http-equiv="refresh" content="0; url=${location.replace(/[&"<>]/g, (c) => ({
"&": "&",
"\"": """,
"<": "<",
">": ">"
})[c])}" /></head></html>`, {
status,
statusText: statusText || (status === 301 ? "Moved Permanently" : "Found"),
headers: {
"content-type": "text/html; charset=utf-8",
location
}
});
}
function redirectBack(event, opts = {}) {
const referer = event.req.headers.get("referer");
let location = opts.fallback ?? "/";
if (referer && URL.canParse(referer)) {
const refererURL = new URL(referer);
if (refererURL.origin === event.url.origin) {
let pathname = refererURL.pathname;
if (pathname.startsWith("//")) pathname = "/" + pathname.replace(/^\/+/, "");
location = pathname + (opts.allowQuery ? refererURL.search : "");
}
}
return redirect(location, opts.status);
}
function writeEarlyHints(event, hints) {
const linkValues = [];
for (const [name, value] of Object.entries(hints)) if (name.toLowerCase() === "link") {
for (const v of Array.isArray(value) ? value : [value]) if (v) linkValues.push(v);
}
if (event.runtime?.node?.res?.writeEarlyHints) {
if (linkValues.length === 0) return Promise.resolve();
const normalizedHints = { link: linkValues };
for (const [name, value] of Object.entries(hints)) if (name.toLowerCase() !== "link") normalizedHints[name] = value;
return new Promise((resolve) => {
event.runtime?.node?.res?.writeEarlyHints(normalizedHints, () => resolve());
});
}
for (const v of linkValues) event.res.headers.append("link", v);
}
function iterable(iterable, options) {
const serializer = options?.serializer ?? serializeIterableValue;
const iterator = coerceIterable(iterable);
return new HTTPResponse(new ReadableStream({
async pull(controller) {
const { value, done } = await iterator.next();
if (value !== void 0) {
const chunk = serializer(value);
if (chunk !== void 0) controller.enqueue(chunk);
}
if (done) controller.close();
},
cancel() {
iterator.return?.();
}
}));
}
function html(first, ...values) {
return new HTTPResponse(typeof first === "string" ? first : first.reduce((out, str, i) => out + str + (values[i] ?? ""), ""), { headers: { "content-type": "text/html; charset=utf-8" } });
}
function appendAcceptQuery(event, mediaTypes) {
const list = Array.isArray(mediaTypes) ? mediaTypes : [mediaTypes];
if (list.length === 0) return;
const value = list.map(serializeMediaType).join(", ");
event.res.headers.append("accept-query", value);
}
function requireContentType(event, acceptedTypes) {
const header = event.req.headers.get("content-type");
if (!header) throw new HTTPError({
status: 400,
statusText: "Bad Request",
message: "Content-Type header is required"
});
const mediaType = header.split(";")[0].trim().toLowerCase();
const slash = mediaType.indexOf("/");
if (slash <= 0 || slash === mediaType.length - 1) throw new HTTPError({
status: 422,
statusText: "Unprocessable Content",
message: "Malformed Content-Type header"
});
const accepted = Array.isArray(acceptedTypes) ? acceptedTypes : [acceptedTypes];
if (accepted.some((type) => mediaTypeMatches(mediaType, type.split(";")[0].trim().toLowerCase()))) return mediaType;
throw new HTTPError({
status: 415,
statusText: "Unsupported Media Type",
message: `Unsupported Content-Type: ${mediaType}. Expected one of: ${accepted.join(", ")}`
});
}
const SF_TOKEN_RE = /^[A-Za-z*][\w!#$%&'*+.^`|~:/-]*$/;
const SF_KEY_RE = /^[a-z*][a-z0-9_.*-]*$/;
function serializeMediaType(mediaType) {
const parts = splitOutsideQuotes(mediaType, ";");
const base = parts[0].trim();
if (!SF_TOKEN_RE.test(base)) throw new TypeError(`Invalid media type: ${JSON.stringify(mediaType)}`);
let result = base;
for (let i = 1; i < parts.length; i++) {
const param = parts[i].trim();
if (!param) continue;
const eq = param.indexOf("=");
const key = (eq === -1 ? param : param.slice(0, eq)).trim().toLowerCase();
if (!SF_KEY_RE.test(key)) throw new TypeError(`Invalid media type parameter: ${JSON.stringify(param)}`);
result += eq === -1 ? `;${key}` : `;${key}="${escapeQuotes(unquote(param.slice(eq + 1).trim()))}"`;
}
return result;
}
function mediaTypeMatches(mediaType, accepted) {
if (accepted === "*/*" || accepted === "*") return true;
if (accepted === mediaType) return true;
if (accepted.endsWith("/*")) return mediaType.startsWith(accepted.slice(0, -1));
return false;
}
function splitOutsideQuotes(input, sep) {
const parts = [];
let current = "";
let inQuotes = false;
for (let i = 0; i < input.length; i++) {
const ch = input[i];
if (inQuotes) {
current += ch;
if (ch === "\\" && i + 1 < input.length) current += input[++i];
else if (ch === "\"") inQuotes = false;
} else if (ch === "\"") {
inQuotes = true;
current += ch;
} else if (ch === sep) {
parts.push(current);
current = "";
} else current += ch;
}
parts.push(current);
return parts;
}
function escapeQuotes(value) {
return value.replace(/[\\"]/g, "\\$&");
}
function unquote(value) {
if (value.length >= 2 && value[0] === "\"" && value.endsWith("\"")) return value.slice(1, -1).replace(/\\(.)/g, "$1");
return value;
}
function parseURLEncodedBody(body) {
return collectEntries(new URLSearchParams(body).entries());
}
function parseFormData(form) {
return collectEntries(form.entries());
}
function collectEntries(entries) {
const parsed = new EmptyObject();
for (const [key, value] of entries) if (hasProp(parsed, key)) {
if (!Array.isArray(parsed[key])) parsed[key] = [parsed[key]];
parsed[key].push(value);
} else parsed[key] = value;
return parsed;
}
async function readBody(event, options) {
const contentType = event.req.headers.get("content-type") || "";
const type = options?.type;
if (type === "formData") {
let form;
try {
form = await event.req.formData();
} catch {
throw new HTTPError({
status: 400,
statusText: "Bad Request",
message: "Invalid form data body"
});
}
return parseFormData(form);
}
const text = await event.req.text();
if (type === "text") return text;
if (!text) return;
if (type === "urlencoded" || !type && contentType.startsWith("application/x-www-form-urlencoded")) return parseURLEncodedBody(text);
try {
return JSON.parse(text);
} catch {
throw new HTTPError({
status: 400,
statusText: "Bad Request",
message: "Invalid JSON body"
});
}
}
async function readValidatedBody(event, validate, options) {
return validateData(await readBody(event), validate, options);
}
async function assertBodySize(event, limit) {
if (!await isBodySizeWithin(event, limit)) throw new HTTPError({
status: 413,
statusText: "Request Entity Too Large",
message: `Request body size exceeds the limit of ${limit} bytes`
});
}
async function isBodySizeWithin(event, limit) {
const req = event.req;
if (req.body === null) return true;
const contentLength = req.headers.get("content-length");
if (contentLength) {
if (req.headers.get("transfer-encoding")) throw new HTTPError({ status: 400 });
if (+contentLength > limit) return false;
}
const reader = req.clone().body.getReader();
let chunk = await reader.read();
let size = 0;
while (!chunk.done) {
size += chunk.value.byteLength;
if (size > limit) {
reader.cancel();
return false;
}
chunk = await reader.read();
}
return true;
}
function onRequest(hook) {
return async function _onRequestMiddleware(event) {
await hook(event);
};
}
function onResponse(hook) {
return async function _onResponseMiddleware(event, next) {
const response = await toResponse(await next(), event);
return await hook(response, event) || response;
};
}
function onError(hook) {
return async (event, next) => {
try {
return await next();
} catch (rawError) {
const isHTTPError = HTTPError.isError(rawError);
const error = isHTTPError ? rawError : new HTTPError(rawError);
if (!isHTTPError) {
error.unhandled = true;
if (rawError?.stack) error.stack = rawError.stack;
}
const hookResponse = await hook(error, event);
if (hookResponse !== void 0) return hookResponse;
throw error;
}
};
}
function bodyLimit(limit) {
return async (event, next) => {
await assertBodySize(event, limit);
return next();
};
}
const ignoredHeaders = /* @__PURE__ */ new Set([
"transfer-encoding",
"accept-encoding",
"connection",
"keep-alive",
"upgrade",
"expect",
"te",
"trailer",
"host",
"proxy-authorization",
"proxy-connection"
]);
function connectionTokens(connection) {
return new Set((connection || "").toLowerCase().split(",").map((name) => name.trim()).filter(Boolean));
}
const ignoredResponseHeaders = /* @__PURE__ */ new Set([
"content-encoding",
"content-length",
"transfer-encoding",
"connection",
"keep-alive",
"proxy-authenticate",
"proxy-connection",
"upgrade",
"trailer",
"te"
]);
function rewriteCookieProperty(header, map, property) {
const _map = typeof map === "string" ? { "*": map } : map;
return header.replace(new RegExp(`(;\\s*${property}=)([^;]+)`, "gi"), (match, prefix, previousValue) => {
let newValue;
if (Object.hasOwn(_map, previousValue)) newValue = _map[previousValue];
else if (Object.hasOwn(_map, "*")) newValue = _map["*"];
else return match;
return newValue ? prefix + newValue : "";
});
}
function applyXForwardedHeaders(headers, event) {
const merged = headers instanceof Headers ? headers : new Headers(headers);
const ip = event.req.ip;
if (ip && !merged.has("x-forwarded-for")) merged.set("x-forwarded-for", ip);
const proto = event.url.protocol.slice(0, -1);
if (proto && !merged.has("x-forwarded-proto")) merged.set("x-forwarded-proto", proto);
if (!merged.has("x-forwarded-host")) merged.set("x-forwarded-host", event.url.host);
if (!merged.has("x-forwarded-port")) merged.set("x-forwarded-port", event.url.port || (proto === "https" ? "443" : "80"));
return merged;
}
function rewriteLocationHeaders(headers, rewrite, targetOrigin, requestOrigin) {
const rewriteValue = (value) => rewrite === true ? rewriteOrigin(value, targetOrigin, requestOrigin) : rewritePrefix(value, rewrite);
const location = headers.get("location");
if (location) {
const rewritten = rewriteValue(location);
if (rewritten) headers.set("location", rewritten);
}
const refresh = headers.get("refresh");
if (refresh) {
const match = refresh.match(/^(\s*(?:[\d.]+\s*[;,]\s*)?url\s*=\s*)(['"]?)(.*?)\2(\s*)$/i);
const rewritten = match && rewriteValue(match[3]);
if (rewritten) headers.set("refresh", match[1] + match[2] + rewritten + match[2] + match[4]);
}
}
function rewriteOrigin(value, targetOrigin, requestOrigin) {
if (!targetOrigin || targetOrigin === requestOrigin) return;
const url = value.startsWith("//") ? URL.canParse(value, targetOrigin) ? new URL(value, targetOrigin) : void 0 : URL.canParse(value) ? new URL(value) : void 0;
if (!url || url.origin !== targetOrigin) return;
return requestOrigin + url.pathname + url.search + url.hash;
}
function rewritePrefix(value, map) {
for (const prefix of Object.keys(map)) if (value.startsWith(prefix)) return map[prefix] + value.slice(prefix.length);
}
function abortable(run, signal) {
if (signal.aborted) return Promise.reject(signal.reason);
return new Promise((resolve, reject) => {
const onAbort = () => reject(signal.reason);
signal.addEventListener("abort", onAbort, { once: true });
Promise.resolve(run()).then((value) => {
signal.removeEventListener("abort", onAbort);
resolve(value);
}, (error) => {
signal.removeEventListener("abort", onAbort);
reject(error);
});
});
}
function mergeHeaders(defaults, ...inputs) {
const _inputs = inputs.filter(Boolean);
if (_inputs.length === 0) return defaults;
const merged = new Headers(defaults);
for (const input of _inputs) {
const entries = Array.isArray(input) ? input : typeof input.entries === "function" ? input.entries() : Object.entries(input);
for (const [key, value] of entries) if (value !== void 0) merged.set(key, value);
}
return merged;
}
async function proxyRequest(event, target, opts = {}) {
const method = opts.fetchOptions?.method || event.req.method;
const methodUpper = method.toUpperCase();
const incomingBody = event.req.body;
const requestBody = incomingBody != null && methodUpper !== "GET" && methodUpper !== "HEAD" ? incomingBody : void 0;
const fetchHeaders = mergeHeaders(getProxyRequestHeaders(event, {
host: target.startsWith("/"),
forwardHeaders: opts.forwardHeaders,
filterHeaders: opts.filterHeaders
}), opts.fetchOptions?.headers, opts.headers);
if (opts.fetchOptions && "body" in opts.fetchOptions || incomingBody && !requestBody) {
if (fetchHeaders instanceof Headers) fetchHeaders.delete("content-length");
else if (!Array.isArray(fetchHeaders)) delete fetchHeaders["content-length"];
}
const fetchBody = opts.fetchOptions?.body ?? requestBody;
return proxy(event, target, {
...opts,
fetchOptions: {
method,
body: requestBody,
...opts.fetchOptions,
duplex: opts.fetchOptions?.duplex ?? (fetchBody != null ? "half" : void 0),
headers: opts.xfwd ? applyXForwardedHeaders(fetchHeaders, event) : fetchHeaders
}
});
}
async function proxy(event, target, opts = {}) {
const signals = [event.req.signal];
if (opts.fetchOptions?.signal) signals.push(opts.fetchOptions.signal);
let timeoutId;
if (opts.timeout > 0 && Number.isFinite(opts.timeout)) {
const timeoutController = new AbortController();
timeoutId = setTimeout(() => timeoutController.abort(new DOMException("Proxy request timed out", "TimeoutError")), Math.min(Math.max(Math.trunc(opts.timeout), 1), 2147483647));
signals.push(timeoutController.signal);
}
const signal = signals.length > 1 ? AbortSignal.any(signals) : signals[0];
const fetchOptions = {
headers: opts.headers,
...opts.fetchOptions,
redirect: opts.fetchOptions?.redirect ?? "manual",
signal
};
let response;
try {
response = target[0] === "/" ? await abortable(() => event.app.fetch(createSubRequest(event, target, fetchOptions)), signal) : await fetch(target, fetchOptions);
} catch (error) {
if ((signal.aborted ? signal.reason : void 0)?.name === "TimeoutError" || error?.name === "TimeoutError") throw new HTTPError({
status: 504,
statusText: "Gateway Timeout",
cause: error
});
if (signal.aborted || error?.name === "AbortError") {
if (opts.propagateAbortError) throw error;
if (event.req.signal.aborted) return new HTTPResponse(null, {
status: 499,
statusText: "Client Closed Request"
});
}
throw new HTTPError({
status: 502,
cause: error
});
} finally {
if (timeoutId !== void 0) clearTimeout(timeoutId);
}
if (response.type === "opaqueredirect") throw new HTTPError({
status: 502,
message: "Cannot relay an opaque redirect response on this runtime. Set `fetchOptions: { redirect: \"follow\" }` to follow upstream redirects instead."
});
if (response.type === "opaque" || response.type === "error" || response.stat