hono-base.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. var __defProp = Object.defineProperty;
  2. var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
  3. var __getOwnPropNames = Object.getOwnPropertyNames;
  4. var __hasOwnProp = Object.prototype.hasOwnProperty;
  5. var __export = (target, all) => {
  6. for (var name in all)
  7. __defProp(target, name, { get: all[name], enumerable: true });
  8. };
  9. var __copyProps = (to, from, except, desc) => {
  10. if (from && typeof from === "object" || typeof from === "function") {
  11. for (let key of __getOwnPropNames(from))
  12. if (!__hasOwnProp.call(to, key) && key !== except)
  13. __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  14. }
  15. return to;
  16. };
  17. var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
  18. var hono_base_exports = {};
  19. __export(hono_base_exports, {
  20. HonoBase: () => Hono
  21. });
  22. module.exports = __toCommonJS(hono_base_exports);
  23. var import_compose = require("./compose");
  24. var import_context = require("./context");
  25. var import_router = require("./router");
  26. var import_constants = require("./utils/constants");
  27. var import_url = require("./utils/url");
  28. const notFoundHandler = (c) => {
  29. return c.text("404 Not Found", 404);
  30. };
  31. const errorHandler = (err, c) => {
  32. if ("getResponse" in err) {
  33. const res = err.getResponse();
  34. return c.newResponse(res.body, res);
  35. }
  36. console.error(err);
  37. return c.text("Internal Server Error", 500);
  38. };
  39. class Hono {
  40. get;
  41. post;
  42. put;
  43. delete;
  44. options;
  45. patch;
  46. all;
  47. on;
  48. use;
  49. /*
  50. This class is like an abstract class and does not have a router.
  51. To use it, inherit the class and implement router in the constructor.
  52. */
  53. router;
  54. getPath;
  55. // Cannot use `#` because it requires visibility at JavaScript runtime.
  56. _basePath = "/";
  57. #path = "/";
  58. routes = [];
  59. constructor(options = {}) {
  60. const allMethods = [...import_router.METHODS, import_router.METHOD_NAME_ALL_LOWERCASE];
  61. allMethods.forEach((method) => {
  62. this[method] = (args1, ...args) => {
  63. if (typeof args1 === "string") {
  64. this.#path = args1;
  65. } else {
  66. this.#addRoute(method, this.#path, args1);
  67. }
  68. args.forEach((handler) => {
  69. this.#addRoute(method, this.#path, handler);
  70. });
  71. return this;
  72. };
  73. });
  74. this.on = (method, path, ...handlers) => {
  75. for (const p of [path].flat()) {
  76. this.#path = p;
  77. for (const m of [method].flat()) {
  78. handlers.map((handler) => {
  79. this.#addRoute(m.toUpperCase(), this.#path, handler);
  80. });
  81. }
  82. }
  83. return this;
  84. };
  85. this.use = (arg1, ...handlers) => {
  86. if (typeof arg1 === "string") {
  87. this.#path = arg1;
  88. } else {
  89. this.#path = "*";
  90. handlers.unshift(arg1);
  91. }
  92. handlers.forEach((handler) => {
  93. this.#addRoute(import_router.METHOD_NAME_ALL, this.#path, handler);
  94. });
  95. return this;
  96. };
  97. const { strict, ...optionsWithoutStrict } = options;
  98. Object.assign(this, optionsWithoutStrict);
  99. this.getPath = strict ?? true ? options.getPath ?? import_url.getPath : import_url.getPathNoStrict;
  100. }
  101. #clone() {
  102. const clone = new Hono({
  103. router: this.router,
  104. getPath: this.getPath
  105. });
  106. clone.errorHandler = this.errorHandler;
  107. clone.#notFoundHandler = this.#notFoundHandler;
  108. clone.routes = this.routes;
  109. return clone;
  110. }
  111. #notFoundHandler = notFoundHandler;
  112. // Cannot use `#` because it requires visibility at JavaScript runtime.
  113. errorHandler = errorHandler;
  114. /**
  115. * `.route()` allows grouping other Hono instance in routes.
  116. *
  117. * @see {@link https://hono.dev/docs/api/routing#grouping}
  118. *
  119. * @param {string} path - base Path
  120. * @param {Hono} app - other Hono instance
  121. * @returns {Hono} routed Hono instance
  122. *
  123. * @example
  124. * ```ts
  125. * const app = new Hono()
  126. * const app2 = new Hono()
  127. *
  128. * app2.get("/user", (c) => c.text("user"))
  129. * app.route("/api", app2) // GET /api/user
  130. * ```
  131. */
  132. route(path, app) {
  133. const subApp = this.basePath(path);
  134. app.routes.map((r) => {
  135. let handler;
  136. if (app.errorHandler === errorHandler) {
  137. handler = r.handler;
  138. } else {
  139. handler = async (c, next) => (await (0, import_compose.compose)([], app.errorHandler)(c, () => r.handler(c, next))).res;
  140. handler[import_constants.COMPOSED_HANDLER] = r.handler;
  141. }
  142. subApp.#addRoute(r.method, r.path, handler);
  143. });
  144. return this;
  145. }
  146. /**
  147. * `.basePath()` allows base paths to be specified.
  148. *
  149. * @see {@link https://hono.dev/docs/api/routing#base-path}
  150. *
  151. * @param {string} path - base Path
  152. * @returns {Hono} changed Hono instance
  153. *
  154. * @example
  155. * ```ts
  156. * const api = new Hono().basePath('/api')
  157. * ```
  158. */
  159. basePath(path) {
  160. const subApp = this.#clone();
  161. subApp._basePath = (0, import_url.mergePath)(this._basePath, path);
  162. return subApp;
  163. }
  164. /**
  165. * `.onError()` handles an error and returns a customized Response.
  166. *
  167. * @see {@link https://hono.dev/docs/api/hono#error-handling}
  168. *
  169. * @param {ErrorHandler} handler - request Handler for error
  170. * @returns {Hono} changed Hono instance
  171. *
  172. * @example
  173. * ```ts
  174. * app.onError((err, c) => {
  175. * console.error(`${err}`)
  176. * return c.text('Custom Error Message', 500)
  177. * })
  178. * ```
  179. */
  180. onError = (handler) => {
  181. this.errorHandler = handler;
  182. return this;
  183. };
  184. /**
  185. * `.notFound()` allows you to customize a Not Found Response.
  186. *
  187. * @see {@link https://hono.dev/docs/api/hono#not-found}
  188. *
  189. * @param {NotFoundHandler} handler - request handler for not-found
  190. * @returns {Hono} changed Hono instance
  191. *
  192. * @example
  193. * ```ts
  194. * app.notFound((c) => {
  195. * return c.text('Custom 404 Message', 404)
  196. * })
  197. * ```
  198. */
  199. notFound = (handler) => {
  200. this.#notFoundHandler = handler;
  201. return this;
  202. };
  203. /**
  204. * `.mount()` allows you to mount applications built with other frameworks into your Hono application.
  205. *
  206. * @see {@link https://hono.dev/docs/api/hono#mount}
  207. *
  208. * @param {string} path - base Path
  209. * @param {Function} applicationHandler - other Request Handler
  210. * @param {MountOptions} [options] - options of `.mount()`
  211. * @returns {Hono} mounted Hono instance
  212. *
  213. * @example
  214. * ```ts
  215. * import { Router as IttyRouter } from 'itty-router'
  216. * import { Hono } from 'hono'
  217. * // Create itty-router application
  218. * const ittyRouter = IttyRouter()
  219. * // GET /itty-router/hello
  220. * ittyRouter.get('/hello', () => new Response('Hello from itty-router'))
  221. *
  222. * const app = new Hono()
  223. * app.mount('/itty-router', ittyRouter.handle)
  224. * ```
  225. *
  226. * @example
  227. * ```ts
  228. * const app = new Hono()
  229. * // Send the request to another application without modification.
  230. * app.mount('/app', anotherApp, {
  231. * replaceRequest: (req) => req,
  232. * })
  233. * ```
  234. */
  235. mount(path, applicationHandler, options) {
  236. let replaceRequest;
  237. let optionHandler;
  238. if (options) {
  239. if (typeof options === "function") {
  240. optionHandler = options;
  241. } else {
  242. optionHandler = options.optionHandler;
  243. if (options.replaceRequest === false) {
  244. replaceRequest = (request) => request;
  245. } else {
  246. replaceRequest = options.replaceRequest;
  247. }
  248. }
  249. }
  250. const getOptions = optionHandler ? (c) => {
  251. const options2 = optionHandler(c);
  252. return Array.isArray(options2) ? options2 : [options2];
  253. } : (c) => {
  254. let executionContext = void 0;
  255. try {
  256. executionContext = c.executionCtx;
  257. } catch {
  258. }
  259. return [c.env, executionContext];
  260. };
  261. replaceRequest ||= (() => {
  262. const mergedPath = (0, import_url.mergePath)(this._basePath, path);
  263. const pathPrefixLength = mergedPath === "/" ? 0 : mergedPath.length;
  264. return (request) => {
  265. const url = new URL(request.url);
  266. url.pathname = url.pathname.slice(pathPrefixLength) || "/";
  267. return new Request(url, request);
  268. };
  269. })();
  270. const handler = async (c, next) => {
  271. const res = await applicationHandler(replaceRequest(c.req.raw), ...getOptions(c));
  272. if (res) {
  273. return res;
  274. }
  275. await next();
  276. };
  277. this.#addRoute(import_router.METHOD_NAME_ALL, (0, import_url.mergePath)(path, "*"), handler);
  278. return this;
  279. }
  280. #addRoute(method, path, handler) {
  281. method = method.toUpperCase();
  282. path = (0, import_url.mergePath)(this._basePath, path);
  283. const r = { basePath: this._basePath, path, method, handler };
  284. this.router.add(method, path, [handler, r]);
  285. this.routes.push(r);
  286. }
  287. #handleError(err, c) {
  288. if (err instanceof Error) {
  289. return this.errorHandler(err, c);
  290. }
  291. throw err;
  292. }
  293. #dispatch(request, executionCtx, env, method) {
  294. if (method === "HEAD") {
  295. return (async () => new Response(null, await this.#dispatch(request, executionCtx, env, "GET")))();
  296. }
  297. const path = this.getPath(request, { env });
  298. const matchResult = this.router.match(method, path);
  299. const c = new import_context.Context(request, {
  300. path,
  301. matchResult,
  302. env,
  303. executionCtx,
  304. notFoundHandler: this.#notFoundHandler
  305. });
  306. if (matchResult[0].length === 1) {
  307. let res;
  308. try {
  309. res = matchResult[0][0][0][0](c, async () => {
  310. c.res = await this.#notFoundHandler(c);
  311. });
  312. } catch (err) {
  313. return this.#handleError(err, c);
  314. }
  315. return res instanceof Promise ? res.then(
  316. (resolved) => resolved || (c.finalized ? c.res : this.#notFoundHandler(c))
  317. ).catch((err) => this.#handleError(err, c)) : res ?? this.#notFoundHandler(c);
  318. }
  319. const composed = (0, import_compose.compose)(matchResult[0], this.errorHandler, this.#notFoundHandler);
  320. return (async () => {
  321. try {
  322. const context = await composed(c);
  323. if (!context.finalized) {
  324. throw new Error(
  325. "Context is not finalized. Did you forget to return a Response object or `await next()`?"
  326. );
  327. }
  328. return context.res;
  329. } catch (err) {
  330. return this.#handleError(err, c);
  331. }
  332. })();
  333. }
  334. /**
  335. * `.fetch()` will be entry point of your app.
  336. *
  337. * @see {@link https://hono.dev/docs/api/hono#fetch}
  338. *
  339. * @param {Request} request - request Object of request
  340. * @param {Env} Env - env Object
  341. * @param {ExecutionContext} - context of execution
  342. * @returns {Response | Promise<Response>} response of request
  343. *
  344. */
  345. fetch = (request, ...rest) => {
  346. return this.#dispatch(request, rest[1], rest[0], request.method);
  347. };
  348. /**
  349. * `.request()` is a useful method for testing.
  350. * You can pass a URL or pathname to send a GET request.
  351. * app will return a Response object.
  352. * ```ts
  353. * test('GET /hello is ok', async () => {
  354. * const res = await app.request('/hello')
  355. * expect(res.status).toBe(200)
  356. * })
  357. * ```
  358. * @see https://hono.dev/docs/api/hono#request
  359. */
  360. request = (input, requestInit, Env, executionCtx) => {
  361. if (input instanceof Request) {
  362. return this.fetch(requestInit ? new Request(input, requestInit) : input, Env, executionCtx);
  363. }
  364. input = input.toString();
  365. return this.fetch(
  366. new Request(
  367. /^https?:\/\//.test(input) ? input : `http://localhost${(0, import_url.mergePath)("/", input)}`,
  368. requestInit
  369. ),
  370. Env,
  371. executionCtx
  372. );
  373. };
  374. /**
  375. * `.fire()` automatically adds a global fetch event listener.
  376. * This can be useful for environments that adhere to the Service Worker API, such as non-ES module Cloudflare Workers.
  377. * @deprecated
  378. * Use `fire` from `hono/service-worker` instead.
  379. * ```ts
  380. * import { Hono } from 'hono'
  381. * import { fire } from 'hono/service-worker'
  382. *
  383. * const app = new Hono()
  384. * // ...
  385. * fire(app)
  386. * ```
  387. * @see https://hono.dev/docs/api/hono#fire
  388. * @see https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API
  389. * @see https://developers.cloudflare.com/workers/reference/migrate-to-module-workers/
  390. */
  391. fire = () => {
  392. addEventListener("fetch", (event) => {
  393. event.respondWith(this.#dispatch(event.request, event, void 0, event.request.method));
  394. });
  395. };
  396. }
  397. // Annotate the CommonJS export names for ESM import in node:
  398. 0 && (module.exports = {
  399. HonoBase
  400. });