UNPKG

@trifrost/core

Version:

Blazingly fast, runtime-agnostic server framework for modern edge and node environments

305 lines (304 loc) 11.5 kB
"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;