UNPKG

hono

Version:

Web framework built on Web Standards

237 lines (236 loc) 6.92 kB
// src/hono-base.ts import { compose } from "./compose.js"; import { Context } from "./context.js"; import { METHODS, METHOD_NAME_ALL, METHOD_NAME_ALL_LOWERCASE } from "./router.js"; import { getPath, getPathNoStrict, mergePath } from "./utils/url.js"; var COMPOSED_HANDLER = Symbol("composedHandler"); var notFoundHandler = (c) => { return c.text("404 Not Found", 404); }; var errorHandler = (err, c) => { if ("getResponse" in err) { return err.getResponse(); } console.error(err); return c.text("Internal Server Error", 500); }; var Hono = class { get; post; put; delete; options; patch; all; on; use; router; getPath; _basePath = "/"; #path = "/"; routes = []; constructor(options = {}) { const allMethods = [...METHODS, METHOD_NAME_ALL_LOWERCASE]; allMethods.forEach((method) => { this[method] = (args1, ...args) => { if (typeof args1 === "string") { this.#path = args1; } else { this.addRoute(method, this.#path, args1); } args.forEach((handler) => { if (typeof handler !== "string") { this.addRoute(method, this.#path, handler); } }); return this; }; }); this.on = (method, path, ...handlers) => { for (const p of [path].flat()) { this.#path = p; for (const m of [method].flat()) { handlers.map((handler) => { this.addRoute(m.toUpperCase(), this.#path, handler); }); } } return this; }; this.use = (arg1, ...handlers) => { if (typeof arg1 === "string") { this.#path = arg1; } else { this.#path = "*"; handlers.unshift(arg1); } handlers.forEach((handler) => { this.addRoute(METHOD_NAME_ALL, this.#path, handler); }); return this; }; const strict = options.strict ?? true; delete options.strict; Object.assign(this, options); this.getPath = strict ? options.getPath ?? getPath : getPathNoStrict; } clone() { const clone = new Hono({ router: this.router, getPath: this.getPath }); clone.routes = this.routes; return clone; } notFoundHandler = notFoundHandler; errorHandler = errorHandler; route(path, app) { const subApp = this.basePath(path); app.routes.map((r) => { let handler; if (app.errorHandler === errorHandler) { handler = r.handler; } else { handler = async (c, next) => (await compose([], app.errorHandler)(c, () => r.handler(c, next))).res; handler[COMPOSED_HANDLER] = r.handler; } subApp.addRoute(r.method, r.path, handler); }); return this; } basePath(path) { const subApp = this.clone(); subApp._basePath = mergePath(this._basePath, path); return subApp; } onError = (handler) => { this.errorHandler = handler; return this; }; notFound = (handler) => { this.notFoundHandler = handler; return this; }; mount(path, applicationHandler, options) { let replaceRequest; let optionHandler; if (options) { if (typeof options === "function") { optionHandler = options; } else { optionHandler = options.optionHandler; replaceRequest = options.replaceRequest; } } const getOptions = optionHandler ? (c) => { const options2 = optionHandler(c); return Array.isArray(options2) ? options2 : [options2]; } : (c) => { let executionContext = void 0; try { executionContext = c.executionCtx; } catch { } return [c.env, executionContext]; }; replaceRequest ||= (() => { const mergedPath = mergePath(this._basePath, path); const pathPrefixLength = mergedPath === "/" ? 0 : mergedPath.length; return (request) => { const url = new URL(request.url); url.pathname = url.pathname.slice(pathPrefixLength) || "/"; return new Request(url, request); }; })(); const handler = async (c, next) => { const res = await applicationHandler(replaceRequest(c.req.raw), ...getOptions(c)); if (res) { return res; } await next(); }; this.addRoute(METHOD_NAME_ALL, mergePath(path, "*"), handler); return this; } addRoute(method, path, handler) { method = method.toUpperCase(); path = mergePath(this._basePath, path); const r = { path, method, handler }; this.router.add(method, path, [handler, r]); this.routes.push(r); } matchRoute(method, path) { return this.router.match(method, path); } handleError(err, c) { if (err instanceof Error) { return this.errorHandler(err, c); } throw err; } dispatch(request, executionCtx, env, method) { if (method === "HEAD") { return (async () => new Response(null, await this.dispatch(request, executionCtx, env, "GET")))(); } const path = this.getPath(request, { env }); const matchResult = this.matchRoute(method, path); const c = new Context(request, { path, matchResult, env, executionCtx, notFoundHandler: this.notFoundHandler }); if (matchResult[0].length === 1) { let res; try { res = matchResult[0][0][0][0](c, async () => { c.res = await this.notFoundHandler(c); }); } catch (err) { return this.handleError(err, c); } return res instanceof Promise ? res.then( (resolved) => resolved || (c.finalized ? c.res : this.notFoundHandler(c)) ).catch((err) => this.handleError(err, c)) : res ?? this.notFoundHandler(c); } const composed = compose(matchResult[0], this.errorHandler, this.notFoundHandler); return (async () => { try { const context = await composed(c); if (!context.finalized) { throw new Error( "Context is not finalized. Did you forget to return a Response object or `await next()`?" ); } return context.res; } catch (err) { return this.handleError(err, c); } })(); } fetch = (request, ...rest) => { return this.dispatch(request, rest[1], rest[0], request.method); }; request = (input, requestInit, Env, executionCtx) => { if (input instanceof Request) { if (requestInit !== void 0) { input = new Request(input, requestInit); } return this.fetch(input, Env, executionCtx); } input = input.toString(); const path = /^https?:\/\//.test(input) ? input : `http://localhost${mergePath("/", input)}`; const req = new Request(path, requestInit); return this.fetch(req, Env, executionCtx); }; fire = () => { addEventListener("fetch", (event) => { event.respondWith(this.dispatch(event.request, event, void 0, event.request.method)); }); }; }; export { COMPOSED_HANDLER, Hono as HonoBase };