@trifrost/core
Version:
Blazingly fast, runtime-agnostic server framework for modern edge and node environments
305 lines (304 loc) • 11.5 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = exports.Router = void 0;
const number_1 = require("@valkyriestudios/utils/number");
const object_1 = require("@valkyriestudios/utils/object");
const _RateLimit_1 = require("../modules/RateLimit/_RateLimit");
const constants_1 = require("../types/constants");
const Route_1 = require("./Route");
const Tree_1 = require("./Tree");
const util_1 = require("./util");
const Lazy_1 = require("../utils/Lazy");
const RGX_SLASH = /\/{2,}/g;
class Router {
/* Base path for this router */
#path;
/* Configured Rate limit instance from the app */
#rateLimit = null;
/* Configured Body Parser options */
#bodyParser = null;
/* Timeout effective for this router and subrouters/routes */
#timeout = null;
/* Middleware */
#middleware;
/* Tree passed by parent */
tree;
constructor(options) {
/* Check path */
if (typeof options?.path !== 'string')
throw new Error('TriFrostRouter@ctor: Path is invalid');
/* Check timeout */
if (options.timeout !== null && !(0, number_1.isIntGt)(options.timeout, 0))
throw new Error('TriFrostRouter@ctor: Timeout is invalid');
/* Check rate limit instance */
if (options.rateLimit !== null && !(options.rateLimit instanceof Lazy_1.Lazy))
throw new Error('TriFrostRouter@ctor: RateLimit is invalid');
/* Check rate limit instance */
if (!(0, util_1.isValidBodyParser)(options.bodyParser))
throw new Error('TriFrostRouter@ctor: BodyParser is invalid');
/* Check tree */
if (!(options.tree instanceof Tree_1.RouteTree))
throw new Error('TriFrostRouter@ctor: Tree is invalid');
/* Check middleware */
if (!Array.isArray(options.middleware))
throw new Error('TriFrostRouter@ctor: Middleware is invalid');
/* Configure path */
this.#path = options.path;
/* Configure timeout */
this.#timeout = options.timeout;
/* Configure RateLimit instance */
this.#rateLimit = options.rateLimit || null;
/* Configure body parser */
this.#bodyParser = options.bodyParser;
/* Configure tree */
this.tree = options.tree;
/* Configure Middleware */
this.#middleware = [...options.middleware];
}
/**
* MARK: Getters
*/
/**
* Get the base path of this router
*/
get path() {
return this.#path;
}
/**
* Returns the configured timeout
*/
get timeout() {
return this.#timeout;
}
/**
* MARK: Methods
*/
/**
* Add a router or middleware to the router
*/
use(val) {
if (!(0, util_1.isValidMiddleware)(val))
throw new Error('TriFrostRouter@use: Handler is expected');
const fn = val;
/* Get name */
let name = Reflect.get(fn, constants_1.Sym_TriFrostName) ?? fn.name;
name = typeof name === 'string' && name.length ? name : 'anonymous';
/* Add symbols for introspection/use further down the line */
Reflect.set(fn, constants_1.Sym_TriFrostName, name);
this.#middleware.push(fn);
return this;
}
/**
* Attach a rate limit to the middleware chain for this router
*/
limit(limit) {
if (!this.#rateLimit)
throw new Error('TriFrostRouter@limit: RateLimit is not configured on App');
if (!(0, util_1.isValidLimit)(limit))
throw new Error('TriFrostRouter@limit: Invalid limit');
this.use((0, _RateLimit_1.limitMiddleware)(this.#rateLimit, limit));
return this;
}
/**
* Configure body parser options for this router
*/
bodyParser(options) {
if (!(0, util_1.isValidBodyParser)(options))
throw new Error('TriFrostRouter@bodyParser: Invalid bodyparser');
this.#bodyParser = options;
return this;
}
/**
* Add a subrouter with dynamic path handling.
*/
group(path, handler) {
if (typeof path !== 'string')
throw new Error('TriFrostRouter@group: Invalid path');
if (!(0, util_1.isValidGrouper)(handler))
throw new Error('TriFrostRouter@group: Invalid handler');
/* Create config */
const { fn, timeout = undefined } = (typeof handler === 'function' ? { fn: handler } : handler);
/* Run router */
fn(new Router({
path: this.#path + path,
tree: this.tree,
rateLimit: this.#rateLimit,
timeout: timeout !== undefined ? timeout : this.#timeout,
middleware: this.#middleware,
bodyParser: this.#bodyParser,
}));
return this;
}
/**
* Add a subroute with a builder approach
*/
route(path, handler) {
if (typeof handler !== 'function')
throw new Error('TriFrostRouter@route: No handler provided for "' + path + '"');
/* Instantiate route builder */
const route = new Route_1.Route({
rateLimit: this.#rateLimit,
bodyParser: this.#bodyParser,
});
/* Run route through handler */
handler(route);
/* Loop through resulting stack and register */
for (let i = 0; i < route.stack.length; i++) {
const el = route.stack[i];
this.#register(path, [...el.middleware, el.handler], el.methods, el.bodyParser);
}
return this;
}
/**
* Configure a catch-all not found handler for subroutes of this router
*
* @param {Handler} handler - Handler to run
*/
onNotFound(handler) {
if (typeof handler !== 'function')
throw new Error('TriFrostRoute@onNotFound: Invalid handler provided for router on "' + this.#path + '"');
this.tree.addNotFound({
path: this.#path + '/*',
fn: handler,
middleware: (0, util_1.normalizeMiddleware)(this.#middleware),
kind: 'notfound',
timeout: this.#timeout,
bodyParser: this.#bodyParser,
name: 'notfound',
description: '404 Not Found Handler',
meta: null,
});
return this;
}
/**
* Configure a catch-all error handler for subroutes of this router
*
* @param {Handler} handler - Handler to run
*/
onError(handler) {
if (typeof handler !== 'function')
throw new Error('TriFrostRoute@onError: Invalid handler provided for router on "' + this.#path + '"');
this.tree.addError({
path: this.#path + '/*',
fn: handler,
middleware: (0, util_1.normalizeMiddleware)(this.#middleware),
kind: 'error',
timeout: this.#timeout,
bodyParser: this.#bodyParser,
name: 'error',
description: 'Error Handler',
meta: null,
});
return this;
}
/**
* Configure a HTTP Get route
*/
get(path, handler) {
if (typeof path !== 'string')
throw new Error('TriFrostRouter@get: Invalid path');
if (!(0, util_1.isValidHandler)(handler))
throw new Error('TriFrostRouter@get: Invalid handler');
return this.#register(path, [handler], [constants_1.HttpMethods.GET, constants_1.HttpMethods.HEAD], this.#bodyParser);
}
/**
* Configure a HTTP Post route
*/
post(path, handler) {
if (typeof path !== 'string')
throw new Error('TriFrostRouter@post: Invalid path');
if (!(0, util_1.isValidHandler)(handler))
throw new Error('TriFrostRouter@post: Invalid handler');
return this.#register(path, [handler], [constants_1.HttpMethods.POST], this.#bodyParser);
}
/**
* Configure a HTTP Put route
*/
put(path, handler) {
if (typeof path !== 'string')
throw new Error('TriFrostRouter@put: Invalid path');
if (!(0, util_1.isValidHandler)(handler))
throw new Error('TriFrostRouter@put: Invalid handler');
return this.#register(path, [handler], [constants_1.HttpMethods.PUT], this.#bodyParser);
}
/**
* Configure a HTTP Patch route
*/
patch(path, handler) {
if (typeof path !== 'string')
throw new Error('TriFrostRouter@patch: Invalid path');
if (!(0, util_1.isValidHandler)(handler))
throw new Error('TriFrostRouter@patch: Invalid handler');
return this.#register(path, [handler], [constants_1.HttpMethods.PATCH], this.#bodyParser);
}
/**
* Configure a HTTP Delete route
*/
del(path, handler) {
if (typeof path !== 'string')
throw new Error('TriFrostRouter@del: Invalid path');
if (!(0, util_1.isValidHandler)(handler))
throw new Error('TriFrostRouter@del: Invalid handler');
return this.#register(path, [handler], [constants_1.HttpMethods.DELETE], this.#bodyParser);
}
/**
* Configure a health route
*/
health(path, handler) {
return this.get(path, {
kind: 'health',
name: 'healthcheck',
description: 'Healthcheck Route',
fn: handler,
});
}
/**
* MARK: Private Fn
*/
/**
* Internal register route method
*/
#register(path, handlers, methods, bodyParser) {
const fn = handlers[handlers.length - 1];
const config = (Object.prototype.toString.call(fn) === '[object Object]' ? fn : { fn });
/* Path */
const n_path = (this.#path + path.trim()).replace(RGX_SLASH, '/');
/* Kind */
const n_kind = typeof config.kind === 'string' && config.kind.length ? config.kind : 'std';
/* Name */
const n_name = typeof config.name === 'string' && config.name.length ? config.name : Reflect.get(config.fn, constants_1.Sym_TriFrostName) || null;
/* Description */
const n_desc = typeof config.description === 'string' && config.description.length ? config.description : null;
/* Timeout */
const n_timeout = 'timeout' in config ? config.timeout : this.#timeout;
/* Body Parser */
const n_bodyparser = 'bodyParser' in config && (0, util_1.isValidBodyParser)(config.bodyParser) ? config.bodyParser : bodyParser;
/* Normalized middleware */
const n_middleware = [
/* Inherit router mware */
...(0, util_1.normalizeMiddleware)(this.#middleware),
/* Route-specific mware */
...(0, util_1.normalizeMiddleware)(handlers.slice(0, -1)),
/* Potential config mware */
...(0, util_1.normalizeMiddleware)((config.middleware || [])),
];
for (let i = 0; i < methods.length; i++) {
const method = methods[i];
this.tree.add({
name: n_name ? (method === 'HEAD' ? 'HEAD_' : '') + n_name : method + '_' + n_path,
description: n_desc,
meta: (0, object_1.isNeObject)(config.meta) ? config.meta : null,
method,
kind: n_kind,
path: n_path,
fn: config.fn,
middleware: n_middleware,
timeout: n_timeout,
bodyParser: n_bodyparser,
});
}
return this;
}
}
exports.Router = Router;
exports.default = Router;