ufiber
Version:
Next-gen webserver for node-js developer
176 lines (174 loc) • 4.96 kB
JavaScript
import { METHODS, METHOD_NAME_ALL, METHOD_NAME_ALL_LOWERCASE } from "../consts.js";
import { mergePath } from "../utils/url.js";
import { SmartRouter } from "./smart.js";
import { TrieRouter } from "./trie-tree/index.js";
import { RegExpRouter } from "./reg-exp/index.js";
import { compose } from "./compose.js";
//#region src/router/index.ts
const onNotFound = (ctx) => ctx.status(404).json({ message: `Cannot ${ctx.url} on ${ctx.method}` });
const onError = (err, ctx) => {
console.error(err);
ctx.text("Internal Server Error", 500);
};
const kNotFound = Symbol("notFound");
const kErrorHandler = Symbol("errorHandler");
/**
* Lightweight, router built on top of a RegExp/Trie based matcher.
* Supports single or multiple route matchers (Trie, RegExp, or both).
* like multi-router delegation and single-router optimization.
*/
var Router = class {
routes = [];
#basePath = "/";
#path = "/";
router;
[kNotFound] = onNotFound;
[kErrorHandler] = onError;
/** Register a GET route. */
get;
/** Register a POST route. */
post;
/** Register a PUT route. */
put;
/** Register a DELETE route. */
delete;
/** Register a PATCH route. */
patch;
/** Register a HEAD route. */
head;
/** Register an OPTIONS route. */
options;
/** Register a route matching any HTTP method. */
all;
constructor() {
[...METHODS, METHOD_NAME_ALL_LOWERCASE].forEach((method) => {
this[method] = (arg1, ...args) => {
const path = typeof arg1 === "string" ? arg1 : this.#path;
if (typeof arg1 !== "string") this.#addRoute(method, path, arg1);
args.forEach((handler) => this.#addRoute(method, path, handler));
return this;
};
});
this.router = new SmartRouter({ routers: [new RegExpRouter(), new TrieRouter()] });
}
/**
* Register a route for one or more HTTP methods and paths.
*
* @param method - A single method or an array of methods (e.g. `'get'`, `'post'`).
* @param path - A single path or an array of paths.
* @param handlers - One or more handlers to attach.
* @returns This router instance (for chaining).
*
* @example
* ```ts
* router.on(['get', 'post'], ['/user', '/account'], handler);
* ```
*/
on = (method, path, ...handlers) => {
for (const p of [path].flat()) {
this.#path = p;
for (const m of [method].flat()) handlers.forEach((handler) => this.#addRoute(m.toUpperCase(), this.#path, handler));
}
return this;
};
/**
* Registers middleware handlers.
* Works similarly to `app.use()` in Express.
*
* @param arg1 - Path string or the first handler function.
* @param handlers - Additional handler functions.
* @returns This router instance.
*
* @example
* ```ts
* router.use(authMiddleware);
* router.use('/api', apiMiddleware);
* ```
*/
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;
};
/**
* `.onError()` handles an error and returns a customized Response.
*
* @param {ErrorHandler} handler - request Handler for error
* @returns {Hono} changed Hono instance
*
* @example
* ```ts
* app.onError((err, c) => {
* console.error(`${err}`)
* return c.text('Custom Error Message', 500)
* })
* ```
*/
onError = (handler) => {
this[kErrorHandler] = handler;
return this;
};
/**
* `.notFound()` allows you to customize a Not Found Response.
*
* @param {$404Handler} handler - request handler for not-found
* @returns {Hono} changed Hono instance
*
* @example
* ```ts
* app.notFound((c) => {
* return c.text('Custom 404 Message', 404)
* })
* ```
*/
notFound = (handler) => {
this[kNotFound] = handler;
return this;
};
/**
* `.route()` allows grouping other Fiber instance in routes.
*
* @param {string} path - base Path
* @param {Router} router - other Router instance
* @returns {Router} routed Router instance
*
* @example
* ```ts
* const app = new Router()
* const app2 = new Router()
*
* app2.get("/user", (c) => c.text("user"))
* app.route("/api", app2) // GET /api/user
* ```
*/
route(path, router) {
if (router === this) throw new Error("Cannot mount router onto itself");
const base = mergePath(this.#basePath, path);
router.routes.forEach((r) => {
let handler;
if (router[kErrorHandler] === onError) handler = r.handler;
else handler = async (c, next) => await compose([], { onError: router[kErrorHandler] })(c, () => r.handler(c, next));
this.#addRoute(r.method, mergePath(base, r.path), handler);
});
}
/**
* Internal method that registers a route into the internal matcher.
*/
#addRoute(method, path, handler) {
method = method.toUpperCase();
const r = {
path: mergePath(this.#basePath, path),
method,
handler,
basePath: this.#basePath
};
this.router.add(method, path, [handler, r]);
this.routes.push(r);
}
};
//#endregion
export { Router, kErrorHandler, kNotFound };