hono
Version:
Web framework built on Web Standards
237 lines (236 loc) • 6.92 kB
JavaScript
// 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
};