UNPKG

h3

Version:

Minimal H(TTP) framework built for high performance and portability.

1,547 lines 94.3 kB
import "./h3-DagAgogP.mjs"; 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 withLeadingSlash(path) { if (!path || path === "/") return "/"; return path[0] === "/" ? path : `/${path}`; } 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); } function resolveDotSegments(path) { if (!path.includes(".") && !path.includes("%2")) return path; const segments = path.replaceAll("\\", "/").split("/"); const resolved = []; for (const segment of segments) { const normalized = segment.replace(/%2e/gi, "."); if (normalized === "..") { if (resolved.length > 1) resolved.pop(); } else if (normalized !== ".") resolved.push(segment); } return resolved.join("/") || "/"; } 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`); 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; const url = _url && _url instanceof URL ? _url : new FastURL(req.url); if (url.pathname.includes("%")) url.pathname = decodePathname(url.pathname); 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 (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) 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) 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 = 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 (validate.body) { 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 }; 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); url.pathname = decodePathname(url.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") === "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] = decodeURIComponent(params[key]); } return params; } 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"); 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") return handler.handler; if (typeof handler?.fetch === "function") return function _fetchHandler(event) { return handler.fetch(event.req); }; } const NoHandler = () => kNotFound; var H3Core = class { 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 (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) || "/"; return callMiddleware(event, input["~middleware"], () => { event.url.pathname = originalPathname; return next(); }); }); 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) { return findRoute(this["~rou3"], _event.req.method, _event.url.pathname); } "~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" ]) H3Core.prototype[method.toLowerCase()] = function(route, handler, opts) { return this.on(method, route, handler, opts); }; return H3; })(); 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) { if (!event.runtime?.node?.res) throw new Error("[h3] Executing Node.js middleware is not supported in this server!"); return callNodeHandler(handler, event.runtime?.node.req, event.runtime?.node.res); }; } 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 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) => ({ "&": "&amp;", "\"": "&quot;", "<": "&lt;", ">": "&gt;" })[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) { if (event.runtime?.node?.res?.writeEarlyHints) return new Promise((resolve) => { event.runtime?.node?.res?.writeEarlyHints(hints, () => resolve()); }); for (const [name, value] of Object.entries(hints)) { if (name.toLowerCase() !== "link") continue; if (Array.isArray(value)) for (const v of value) event.res.headers.append("link", v); else event.res.headers.append("link", value); } } 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 parseURLEncodedBody(body) { const form = new URLSearchParams(body); const parsedForm = new EmptyObject(); for (const [key, value] of form.entries()) if (hasProp(parsedForm, key)) { if (!Array.isArray(parsedForm[key])) parsedForm[key] = [parsedForm[key]]; parsedForm[key].push(value); } else parsedForm[key] = value; return parsedForm; } async function readBody(event) { const text = await event.req.text(); if (!text) return; if ((event.req.headers.get("content-type") || "").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 PayloadMethods = new Set([ "PATCH", "POST", "PUT", "DELETE" ]); const ignoredHeaders = new Set([ "transfer-encoding", "connection", "keep-alive", "upgrade", "expect", "host", "accept" ]); 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 (previousValue in _map) newValue = _map[previousValue]; else if ("*" in _map) newValue = _map["*"]; else return match; return newValue ? prefix + newValue : ""; }); } 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 requestBody = PayloadMethods.has(event.req.method) ? event.req.body : void 0; const method = opts.fetchOptions?.method || event.req.method; const fetchHeaders = mergeHeaders(getProxyRequestHeaders(event, { host: target.startsWith("/"), forwardHeaders: opts.forwardHeaders, filterHeaders: opts.filterHeaders }), opts.fetchOptions?.headers, opts.headers); return proxy(event, target, { ...opts, fetchOptions: { method, body: requestBody, duplex: requestBody ? "half" : void 0, ...opts.fetchOptions, headers: fetchHeaders } }); } async function proxy(event, target, opts = {}) { const fetchOptions = { headers: opts.headers, ...opts.fetchOptions }; let response; try { response = target[0] === "/" ? await event.app.fetch(createSubRequest(event, target, fetchOptions)) : await fetch(target, fetchOptions); } catch (error) { throw new HTTPError({ status: 502, cause: error }); } const headers = new Headers(); const cookies = []; for (const [key, value] of response.headers.entries()) { if (key === "content-encoding" || key === "content-length" || key === "transfer-encoding") continue; if (key === "set-cookie") { cookies.push(value); continue; } headers.append(key, value); } if (cookies.length > 0) { const _cookies = cookies.map((cookie) => { if (opts.cookieDomainRewrite) cookie = rewriteCookieProperty(cookie, opts.cookieDomainRewrite, "domain"); if (opts.cookiePathRewrite) cookie = rewriteCookieProperty(cookie, opts.cookiePathRewrite, "path"); return cookie; }); for (const cookie of _cookies) headers.append("set-cookie", cookie); } if (opts.onResponse) await opts.onResponse(event, response); return new HTTPResponse(response.body, { status: response.status, statusText: response.statusText, headers }); } function getProxyRequestHeaders(event, opts) { const headers = new EmptyObject(); for (const [name, value] of event.req.headers.entries()) { if (opts?.filterHeaders?.includes(name)) continue; if (opts?.forwardHeaders?.includes(name)) { headers[name] = value; continue; } if (!ignoredHeaders.has(name) || name === "host" && opts?.host) { headers[name] = value; continue; } } return headers; } async function fetchWithEvent(event, url, init) { if (url[0] !== "/") return fetch(url, init); return event.app.fetch(createSubRequest(event, url, { ...init, headers: mergeHeaders(getProxyRequestHeaders(event, { host: true }), init?.headers) })); } function createSubRequest(event, path, init) { const url = new URL(path, event.url); const req = new Request(url, init); req.runtime = event.req.runtime; req.waitUntil = event.req.waitUntil; req.ip = event.req.ip; return req; } const COOKIE_MAX_AGE_LIMIT = 3456e4; function endIndex(str, min, len) { const index = str.indexOf(";", min); return index === -1 ? len : index; } function eqIndex(str, min, max) { const index = str.indexOf("=", min); return index < max ? index : -1; } function valueSlice(str, min, max) { if (min === max) return ""; let start = min; let end = max; do { const code = str.charCodeAt(start); if (code !== 32 && code !== 9) break; } while (++start < end); while (end > start) { const code = str.charCodeAt(end - 1); if (code !== 32 && code !== 9) break; end--; } return str.slice(start, end); } const NullObject = /* @__PURE__ */ (() => { const C = function() {}; C.prototype = Object.create(null); return C; })(); function parse(str, options) { const obj = new NullObject(); const len = str.length; if (len < 2) return obj; const dec = options?.decode || decode; const allowMultiple = options?.allowMultiple || false; let index = 0; do { const eqIdx = eqIndex(str, index, len); if (eqIdx === -1) break; const endIdx = endIndex(str, index, len); if (eqIdx > endIdx) { index = str.lastIndexOf(";", eqIdx - 1) + 1; continue; } const key = valueSlice(str, index, eqIdx); if (options?.filter && !options.filter(key)) { index = endIdx + 1; continue; } const val = dec(valueSlice(str, eqIdx + 1, endIdx)); if (allowMultiple) { const existing = obj[key]; if (existing === void 0) obj[key] = val; else if (Array.isArray(existing)) existing.push(val); else obj[key] = [existing, val]; } else if (obj[key] === void 0) obj[key] = val; index = endIdx + 1; } while (index < len); return obj; } function decode(str) { if (!str.includes("%")) return str; try { return decodeURIComponent(str); } catch { return str; } } const cookieNameRegExp = /^[\u0021-\u003A\u003C\u003E-\u007E]+$/; const cookieValueRegExp = /^[\u0021-\u003A\u003C-\u007E]*$/; 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; const pathValueRegExp = /^[\u0020-\u003A\u003C-\u007E]*$/; const __toString = Object.prototype.toString; function serialize(_a0, _a1, _a2) { const isObj = typeof _a0 === "object" && _a0 !== null; const options = isObj ? _a1 : _a2; const stringify = options?.stringify || JSON.stringify; const cookie = isObj ? _a0 : { ..._a2, name: _a0, value: _a1 == void 0 ? "" : typeof _a1 === "string" ? _a1 : stringify(_a1) }; 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}`); if (!cookie.secure) { if (cookie.partitioned) throw new TypeError(`Partitioned cookies must have the Secure attribute`); if (cookie.sameSite && String(cookie.sameSite).toLowerCase() === "none") throw new TypeError(`SameSite=None cookies must have the Secure attribute`); if (cookie.name.length > 9 && cookie.name.charCodeAt(0) === 95 && cookie.name.charCodeAt(1) === 95) { const nameLower = cookie.name.toLowerCase(); if (nameLower.startsWith("__secure-") || nameLower.startsWith("__host-")) throw new TypeError(`${cookie.name} cookies must have the Secure attribute`); } } if (cookie.name.length > 7 && cookie.name.charCodeAt(0) === 95 && cookie.name.charCodeAt(1) === 95 && cookie.name.toLowerCase().startsWith("__host-")) { if (cookie.path !== "/") throw new TypeError(`__Host- cookies must have Path=/`); if (cookie.domain) throw new TypeError(`__Host- cookies must not have a Domain attribute`); } let str = cookie.name + "=" + value; if (cookie.maxAge !== void 0) { if (!Number.isInteger(cookie.maxAge)) throw new TypeError(`option maxAge is invalid: ${cookie.maxAge}`); str += "; Max-Age=" + Math.max(0, Math.min(cookie.maxAge, COOKIE_MAX_AGE_LIMIT)); } 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) switch (typeof cookie.priority === "string" ? cookie.priority.toLowerCase() : void 0) { 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) switch (typeof cookie.sameSite === "string" ? cookie.sameSite.toLowerCase() : cookie.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; } function isDate(val) { return __toString.call(val) === "[object Date]"; } const maxAgeRegExp = /^-?\d+$/; const _nullProto = /* @__PURE__ */ Object.getPrototypeOf({}); function parseSetCookie(str, options) { const len = str.length; let _endIdx = len; let eqIdx = -1; for (let i = 0; i < len; i++) { const c = str.charCodeAt(i); if (c === 59) { _endIdx = i; break; } if (c === 61 && eqIdx === -1) eqIdx = i; } if (eqIdx >= _endIdx) eqIdx = -1; const name = eqIdx === -1 ? "" : _trim(str, 0, eqIdx); if (name && name in _nullProto) return void 0; let value = eqIdx === -1 ? _trim(str, 0, _endIdx) : _trim(str, eqIdx + 1, _endIdx); if (!name && !value) return void 0; if (name.length + value.length > 4096) return void 0; if (options?.decode !== false) value = _decode(value, options?.decode); const setCookie = { name, value }; let index = _endIdx + 1; while (index < len) { let endIdx = len; let attrEqIdx = -1; for (let i = index; i < len; i++) { const c = str.charCodeAt(i); if (c === 59) { endIdx = i; break; } if (c === 61 && attrEqIdx === -1) attrEqIdx = i; } if (attrEqIdx >= endIdx) attrEqIdx = -1; const attr = attrEqIdx === -1 ? _trim(str, index, endIdx) : _trim(str, index, attrEqIdx); const val = attrEqIdx === -1 ? void 0 : _trim(str, attrEqIdx + 1, endIdx); if (val === void 0 || val.length <= 1024) switch (attr.toLowerCase()) { case "httponly": setCookie.httpOnly = true; break; case "secure": setCookie.secure = true; break; case "partitioned": setCookie.partitioned = true; break; case "domain": if (val) setCookie.domain = (val.charCodeAt(0) === 46 ? val.slice(1) : val).toLowerCase(); break; case "path": setCookie.path = val; break; case "max-age": if (val && maxAgeRegExp.test(val)) setCookie.maxAge = Math.min(Number(val), COOKIE_MAX_AGE_LIMIT); break; case "expires": { if (!val) break; const date = new Date(val); if (Number.isFinite(date.valueOf())) { const maxDate = new Date(Date.now() + COOKIE_MAX_AGE_LIMIT * 1e3); setCookie.expires = date > maxDate ? maxDate : 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 = sam