UNPKG

tspace-spear

Version:

tspace-spear is a lightweight API framework for Node.js that is fast and highly focused on providing the best developer experience. It utilizes the native HTTP server

1,070 lines 40.9 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Spear = exports.Application = void 0; const cluster_1 = __importDefault(require("cluster")); const os_1 = __importDefault(require("os")); const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); const url_1 = require("url"); const on_finished_1 = __importDefault(require("on-finished")); const http_1 = __importStar(require("http")); const find_my_way_1 = __importDefault(require("find-my-way")); const parser_factory_1 = require("./parser-factory"); /** * * The 'Spear' class is used to create a server and handle HTTP requests. * * @returns {Spear} application * @example * new Spear() * .get('/' , () => 'Hello world!') * .get('/json' , () => { * return { * message : 'Hello world!' * } * }) * .listen(3000 , () => console.log('server listening on port : 3000')) * */ class Spear { _controllers; _middlewares; _globalPrefix; _router = (0, find_my_way_1.default)(); _parser = new parser_factory_1.ParserFactory(); _cluster; _cors; _swagger = { use: false, path: '/api/docs', servers: [ { url: 'http://localhost:3000' } ], tags: [], info: { title: "API Documentation", description: "This is a sample documentation", version: "1.0.0" } }; _swaggerAdditional = []; _errorHandler = null; _globalMiddlewares = []; _formatResponse = null; _onListeners = []; _fileUploadOptions = { limit: Infinity, tempFileDir: 'tmp', removeTempFile: { remove: false, ms: 1000 * 60 * 10 } }; constructor({ controllers, middlewares, globalPrefix, logger, cluster } = {}) { if (logger) this.useLogger(); this._cluster = cluster; this._controllers = controllers; this._middlewares = middlewares; this._globalPrefix = globalPrefix == null ? '' : globalPrefix; } /** * The get 'instance' method is used to get the instance of Spear. * * @returns {this} */ get instance() { return this; } /** * The get 'routers' method is used get the all routers. * * @returns {Instance<findMyWayRouter.HTTPVersion.V1>} */ get routers() { return this._router; } /** * The 'use' method is used to add the middleware into the request pipeline. * * @callback {Function} middleware * @property {Object} ctx - context { req , res , query , params , cookies , files , body} * @property {Function} next - go to next function * @returns {this} */ use(middleware) { this._globalMiddlewares.push(middleware); return this; } /** * The 'useCluster' method is used cluster run the server * * @param {boolean | number} cluster * @returns {this} */ useCluster(cluster) { this._cluster = cluster == null ? true : cluster; return this; } /** * The 'useLogger' method is used to add the middleware view logger response. * * @callback {Function} middleware * @property {Object} ctx - context { req , res , query , params , cookies , files , body} * @property {Function} next - go to next function * @returns {this} */ useLogger({ methods, exceptPath } = {}) { this._globalMiddlewares.push(({ req, res }, next) => { const diffTime = (hrtime) => { const MS = 1000; if (hrtime == null) return 0; const [start, end] = process.hrtime(hrtime); const time = ((start * MS) + (end / 1e6)); return `${time > MS ? `${(time / MS).toFixed(2)} s` : `${time.toFixed(2)} ms`}`; }; const statusCode = (res) => { const statusCode = res.statusCode == null ? 500 : Number(res.statusCode); return statusCode < 400 ? `\x1b[32m${statusCode}\x1b[0m` : `\x1b[31m${statusCode}\x1b[0m`; }; if (exceptPath instanceof RegExp && exceptPath.test(String(req.url))) return next(); if (Array.isArray(exceptPath) && exceptPath.some(v => String(req.url) === v)) return next(); if (methods != null && methods.length && !methods.some(v => v.toLocaleLowerCase() === String(req.method).toLocaleLowerCase())) { return next(); } const startTime = process.hrtime(); (0, on_finished_1.default)(res, () => { console.log([ `[\x1b[1m\x1b[34mINFO\x1b[0m]`, `\x1b[34m${new Date().toJSON()}\x1b[0m`, `\x1b[33m${req.method}\x1b[0m`, `${decodeURIComponent(String(req.url))}`, `${statusCode(res)}`, `${diffTime(startTime)}`, ].join(" ")); }); return next(); }); return this; } /** * The 'useBodyParser' method is a middleware used to parse the request body of incoming HTTP requests. * @param {object?} * @property {array?} except the body parser with some methods * @returns {this} */ useBodyParser({ except } = {}) { this._globalMiddlewares.push((ctx, next) => { const { req } = ctx; const contentType = req?.headers['content-type'] ?? ''; const isFileUpload = contentType && contentType.startsWith('multipart/form-data'); const isCanParserBody = contentType.includes('application/json') || contentType.includes('application/x-www-form-urlencoded'); if (except != null && Array.isArray(except) && except.some(v => v.toLocaleLowerCase() === (req.method)?.toLocaleLowerCase())) { return next(); } if (isFileUpload) return next(); if (!isCanParserBody) return next(); if (req?.body != null) return next(); Promise.resolve(this._parser.body(req)) .then(r => { req.body = r; return next(); }) .catch(err => { return this._nextFunction(ctx)(err); }); }); return this; } /** * The 'useFileUpload' method is a middleware used to handler file uploads. It adds a file upload of incoming HTTP requests. * * @param {?Object} * @property {?number} limits * @property {?string} tempFileDir * @property {?Object} removeTempFile * @property {boolean} removeTempFile.remove * @property {number} removeTempFile.ms * @returns */ useFileUpload({ limit, tempFileDir, removeTempFile } = {}) { if (limit != null) { this._fileUploadOptions.limit = limit; } if (tempFileDir != null) { this._fileUploadOptions.tempFileDir = tempFileDir; } if (removeTempFile != null) { this._fileUploadOptions.removeTempFile = removeTempFile; } this._globalMiddlewares.push((ctx, next) => { const { req } = ctx; const contentType = req?.headers['content-type']; const isFileUpload = contentType && contentType.startsWith('multipart/form-data'); if (req.method === 'GET') { return next(); } if (!isFileUpload) return next(); if (req?.files != null) return next(); Promise .resolve(this._parser.files(req, this._fileUploadOptions)) .then(r => { req.files = r.files; req.body = r.body; return next(); }) .catch(err => { return this._nextFunction(ctx)(err); }); }); return this; } /** * The 'useCookiesParser' method is a middleware used to parses cookies attached to the client request object. * * @returns {this} */ useCookiesParser() { this._globalMiddlewares.push(({ req }, next) => { if (req?.cookies != null) return next(); req.cookies = this._parser.cookies(req); return next(); }); return this; } /** * The 'useRouter' method is used to add the router in the request context. * * @parms {Function} router * @property {Function} router - get() , post() , put() , patch() , delete() * @returns {this} */ useRouter(router) { const routes = router.routes; for (const { path, method, handlers } of routes) { this[method](this._normalizePath(this._globalPrefix, path), ...handlers); } return this; } /** * The 'useSwagger' method is a middleware used to create swagger api. * * @param {?Object} * @property {?string} path * @property {?array} servers * @property {?object} info * @property {?array} tags * @returns */ useSwagger({ path, servers, info, tags } = {}) { this._swagger = { use: true, path: path ?? this._swagger.path, servers: servers ?? this._swagger.servers, tags: tags ?? this._swagger.tags, info: info ?? this._swagger.info }; return this; } /** * The 'listen' method is used to bind and start a server to a particular port and optionally a hostname. * * @param {number} port * @param {function} callback * @returns */ async listen(port, hostname, callback) { if (arguments.length === 2 && typeof hostname === 'function') { callback = hostname; } const server = await this._createServer(); if (this._cluster != null && this._cluster || typeof this._cluster === 'number') { this._clusterMode({ server, port, hostname, callback }); return server; } const args = hostname ? [port, hostname, () => callback?.({ server, port: port })] : [port, () => callback?.({ server, port: port })]; server.listen(...args); server.on('listening', () => { this._onListeners.forEach(listener => listener()); if (this._swagger.use) { this._swaggerHandler(); } }); server.on('error', (_) => { port = Math.floor(Math.random() * 8999) + 1000; server.listen(port); }); return server; } /** * The 'cors' is used to enable the cors origins on the server. * * @params {Object} * @property {(string | RegExp)[]} origins * @property {boolean} credentials * @returns */ cors({ origins, credentials } = {}) { this._cors = ((req, res) => { const origin = req.headers?.origin ?? null; if (origin == null) return; res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE, OPTIONS'); res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization'); if (Array.isArray(origins) && origins.length) { for (const o of origins) { if (typeof o === 'string' && (o === origin || o === '*')) { res.setHeader('Access-Control-Allow-Origin', origin); continue; } if (o instanceof RegExp && o.test(origin)) { res.setHeader('Access-Control-Allow-Origin', origin); } } } if (credentials) { res.setHeader('Access-Control-Allow-Credentials', 'true'); } if (req.method === 'OPTIONS') { res.writeHead(204, { 'Content-Length': '0' }); res.end(); return; } return; }); return this; } /** * The 'enableCors' is used to enable the cors origins on the server. * * @params {Object} * @property {(string | RegExp)[]} origins * @property {boolean} credentials * @returns */ enableCors({ origins, credentials } = {}) { return this.cors({ origins, credentials }); } /** * The 'formatResponse' method is used to format the response * * @param {function} format * @returns */ formatResponse(format) { this._formatResponse = format; return this; } /** * The 'response' method is used to format the response * * @param {function} format * @returns */ response(format) { this._formatResponse = format; return this; } /** * The 'errorHandler' method is middleware that is specifically designed to handle errors. * * that occur during the processing of requests * * @param {function} error * @returns */ errorHandler(error) { this._errorHandler = error; return this; } /** * The 'catch' method is middleware that is specifically designed to handle errors. * * that occur during the processing of requests * * @param {function} error * @returns */ catch(error) { this._errorHandler = error; return this; } /** * The 'notFoundHandler' method is middleware that is specifically designed to handle errors notfound that occur during the processing of requests * * @param {function} notfound * @returns */ notFoundHandler(fn) { const handler = ({ req, res }) => { return fn({ req, res: this._customizeResponse(req, res), headers: {}, query: {}, files: {}, body: {}, params: {}, cookies: {} }); }; this._onListeners.push(() => { return this.all('*', ...this._globalMiddlewares, handler); }); return this; } /** * The 'notfound' method is middleware that is specifically designed to handle errors notfound that occur during the processing of requests * * @param {function} notfound * @returns */ notfound(fn) { const handler = ({ req, res }) => { return fn({ req, res: this._customizeResponse(req, res), headers: {}, query: {}, files: {}, body: {}, params: {}, cookies: {} }); }; this._onListeners.push(() => { return this.all('*', ...this._globalMiddlewares, handler); }); return this; } /** * The 'get' method is used to add the request handler to the router for the 'GET' method. * * @param {string} path * @callback {...Function[]} handlers of the middlewares * @property {Object} ctx - context { req , res , query , params , cookies , files , body} * @property {Function} next - go to next function * @returns {this} */ get(path, ...handlers) { this._onListeners.push(() => { return this._router.get(this._normalizePath(this._globalPrefix, path), this._wrapHandlers(...this._globalMiddlewares, ...handlers)); }); return this; } /** * The 'post' method is used to add the request handler to the router for the 'POST' method. * * @param {string} path * @callback {...Function[]} handlers of the middlewares * @property {Object} ctx - context { req , res , query , params , cookies , files , body} * @property {Function} next - go to next function * @returns {this} */ post(path, ...handlers) { this._onListeners.push(() => { return this._router.post(this._normalizePath(this._globalPrefix, path), this._wrapHandlers(...this._globalMiddlewares, ...handlers)); }); return this; } /** * The 'put' method is used to add the request handler to the router for the 'PUT' method. * * @param {string} path * @callback {...Function[]} handlers of the middlewares * @property {Object} ctx - context { req , res , query , params , cookies , files , body} * @property {Function} next - go to next function * @returns {this} */ put(path, ...handlers) { this._onListeners.push(() => { return this._router.put(this._normalizePath(this._globalPrefix, path), this._wrapHandlers(...this._globalMiddlewares, ...handlers)); }); return this; } /** * The 'patch' method is used to add the request handler to the router for the 'PATCH' method. * * @param {string} path * @callback {...Function[]} handlers of the middlewares * @property {Object} ctx - context { req , res , query , params , cookies , files , body} * @property {Function} next - go to next function * @returns {this} */ patch(path, ...handlers) { this._onListeners.push(() => { return this._router.patch(this._normalizePath(this._globalPrefix, path), this._wrapHandlers(...this._globalMiddlewares, ...handlers)); }); return this; } /** * The 'delete' method is used to add the request handler to the router for the 'DELETE' method. * * @param {string} path * @callback {...Function[]} handlers of the middlewares * @property {Object} ctx - context { req , res , query , params , cookies , files , body} * @property {Function} next - go to next function * @returns {this} */ delete(path, ...handlers) { this._onListeners.push(() => { return this._router.delete(this._normalizePath(this._globalPrefix, path), this._wrapHandlers(...this._globalMiddlewares, ...handlers)); }); return this; } /** * The 'all' method is used to add the request handler to the router for 'GET' 'POST' 'PUT' 'PATCH' 'DELETE' methods. * * @param {string} path * @callback {...Function[]} handlers of the middlewares * @property {object} ctx - context { req , res , query , params , cookies , files , body} * @property {function} next - go to next function * @returns {this} */ all(path, ...handlers) { this._onListeners.push(() => { return this._router.all(this._normalizePath(this._globalPrefix, path), this._wrapHandlers(...this._globalMiddlewares, ...handlers)); }); return this; } /** * The 'any' method is used to add the request handler to the router for 'GET' 'POST' 'PUT' 'PATCH' 'DELETE' methods. * * @param {string} path * @callback {...Function[]} handlers of the middlewares * @property {object} ctx - context { req , res , query , params , cookies , files , body} * @property {function} next - go to next function * @returns {this} */ any(path, ...handlers) { this._onListeners.push(() => { return this._router.all(this._normalizePath(this._globalPrefix, path), this._wrapHandlers(...this._globalMiddlewares, ...handlers)); }); return this; } _clusterMode({ server, port, hostname, callback }) { if (cluster_1.default.isPrimary) { const numCPUs = os_1.default.cpus().length; const maxWorkers = typeof this._cluster === 'boolean' || this._cluster == null ? numCPUs : this._cluster; for (let i = 0; i < maxWorkers; i++) { cluster_1.default.fork(); } cluster_1.default.on('exit', () => { cluster_1.default.fork(); }); } if (cluster_1.default.isWorker) { const args = hostname ? [port, hostname, () => callback?.({ server, port: port })] : [port, () => callback?.({ server, port: port })]; server.listen(...args); server.on('listening', () => { this._onListeners.forEach(listener => listener()); if (this._swagger.use) { this._swaggerHandler(); } }); server.on('error', (_) => { port = Math.floor(Math.random() * 8999) + 1000; server.listen(port); }); } return; } async _import(dir, pattern) { const directories = fs_1.default.readdirSync(dir, { withFileTypes: true }); const files = (await Promise.all(directories.map((directory) => { const newDir = path_1.default.resolve(String(dir), directory.name); if (pattern == null) { return directory.isDirectory() ? this._import(newDir) : newDir; } return directory.isDirectory() ? this._import(newDir) : pattern.test(directory.name) ? newDir : null; }))).filter(d => d != null); return [].concat(...files); } async _registerControllers() { if (this._controllers == null) return; if (!Array.isArray(this._controllers)) { const controllers = await this._import(this._controllers.folder, this._controllers.name); for (const file of controllers) { const response = await Promise.resolve(`${file}`).then(s => __importStar(require(s))); const controller = response?.default; const controllerInstance = new controller(); const prefixPath = Reflect.getMetadata("controllers", controller) ?? ''; const routers = Reflect.getMetadata("routers", controller) ?? []; const swaggers = Reflect.getMetadata("swaggers", controller) ?? []; if (prefixPath == null) continue; for (const { method, path, handler } of Array.from(routers)) { const find = Array.from(swaggers).find(s => s.handler === handler); if (find != null) { this._swaggerAdditional = [ ...this._swaggerAdditional, { ...find, path: this._normalizePath(this._globalPrefix, prefixPath, path), method } ]; } this[method](this._normalizePath(this._globalPrefix, prefixPath, path), controllerInstance[String(handler)].bind(controllerInstance)); } } return; } for (const controller of this._controllers) { const controllerInstance = new controller(); const prefixPath = Reflect.getMetadata("controllers", controller) ?? ''; const routers = Reflect.getMetadata("routers", controller) ?? []; const swaggers = Reflect.getMetadata("swaggers", controller) ?? []; if (prefixPath == null) continue; for (const { method, path, handler } of Array.from(routers)) { const find = Array.from(swaggers).find(s => s.handler === handler); if (find != null) { this._swaggerAdditional = [ ...this._swaggerAdditional, { ...find, path: this._normalizePath(this._globalPrefix, prefixPath, path), method } ]; } this[method](this._normalizePath(this._globalPrefix, prefixPath, path), controllerInstance[String(handler)].bind(controllerInstance)); } } } async _registerMiddlewares() { if (this._middlewares == null) return; if (!Array.isArray(this._middlewares)) { const middlewares = await this._import(this._middlewares.folder, this._middlewares.name); for (const file of middlewares) { const response = await Promise.resolve(`${file}`).then(s => __importStar(require(s))); const middleware = response?.default; this.use(middleware); } return; } const middlewares = this._middlewares; for (const middleware of middlewares) { this.use(middleware); } return; } _customizeResponse(req, res) { const response = res; response.json = (results) => { if (res.writableEnded) return; if (typeof results === 'string') { if (!res.headersSent) { res.writeHead(200, { 'Content-Type': 'text/plain' }); } return res.end(results); } if (!res.headersSent) { res.writeHead(200, { 'Content-Type': 'application/json' }); } if (results == null) { if (this._formatResponse != null) { return res.end(JSON.stringify(this._formatResponse(null, res.statusCode), null, 2)); } return res.end(); } if (this._formatResponse != null) { return res.end(JSON.stringify(this._formatResponse({ ...results }, res.statusCode), null, 2)); } return res.end(JSON.stringify({ ...results, }, null, 2)); }; response.send = (results) => { if (res.writableEnded) return; res.writeHead(res.statusCode, { 'Content-Type': 'text/plain' }); return res.end(results); }; response.html = (results) => { if (res.writableEnded) return; res.writeHead(res.statusCode, { 'Content-Type': 'text/html' }); return res.end(results); }; response.error = (err) => { let code = +err.response?.data?.code || +err.code || +err.status || +err.statusCode || +err.response?.data?.statusCode || 500; code = (code == null || typeof code !== 'number') ? 500 : Number.isNaN(code) ? 500 : code < 400 ? 500 : code; const message = err.response?.data?.errorMessage || err.response?.data?.message || err.message || `The url '${req.url}' resulted in a server error. Please investigate.`; response.status(code); if (this._formatResponse != null) { return res.end(JSON.stringify(this._formatResponse({ message }, code), null, 2)); } return res.end(JSON.stringify({ message: message }, null, 2)); }; response.ok = (results) => { return response.json(results == null ? {} : results); }; response.created = (results) => { response.status(201); return response.json(results == null ? {} : results); }; response.accepted = (results) => { response.status(202); return response.json(results == null ? {} : results); }; response.noContent = () => { response.status(204); return res.end(); }; response.badRequest = (message) => { if (res.writableEnded) return; response.status(400); message = message ?? `The url '${req.url}' resulted in a bad request. Please review the data and try again.`; if (this._formatResponse != null) { return res.end(JSON.stringify(this._formatResponse({ message }, 400), null, 2)); } return res.end(JSON.stringify({ message: message }, null, 2)); }; response.unauthorized = (message) => { response.status(401); message = message ?? `The url '${req.url}' is unauthorized. Please verify.`; if (this._formatResponse != null) { return res.end(JSON.stringify(this._formatResponse({ message }, 401), null, 2)); } return res.end(JSON.stringify({ message }, null, 2)); }; response.paymentRequired = (message) => { response.status(402); message = message ?? `The url '${req.url}' requires payment. Please proceed with payment.`; if (this._formatResponse != null) { return res.end(JSON.stringify(this._formatResponse({ message }, 402), null, 2)); } return res.end(JSON.stringify({ message }, null, 2)); }; response.forbidden = (message) => { response.status(403); message = message ?? `The url '${req.url}' is forbidden. Please check the permissions or access rights.`; if (this._formatResponse != null) { return res.end(JSON.stringify(this._formatResponse({ message }, 403), null, 2)); } return res.end(JSON.stringify({ message }, null, 2)); }; response.notFound = (message) => { response.status(404); message = message ?? `The url '${req.url}' was not found. Please re-check the your url again.`; if (this._formatResponse != null) { return res.end(JSON.stringify(this._formatResponse({ message }, 404), null, 2)); } return res.end(JSON.stringify({ message }, null, 2)); }; response.tooManyRequests = (message) => { response.status(429); message = message ?? `The url '${req.url}' is too many request. Please wait and try agian.`; if (this._formatResponse != null) { return res.end(JSON.stringify(this._formatResponse({ message }, 404), null, 2)); } return res.end(JSON.stringify({ message }, null, 2)); }; response.serverError = (message) => { response.status(500); message = message ?? `The url '${req.url}' resulted in a server error. Please investigate.`; if (this._formatResponse != null) { return res.end(JSON.stringify(this._formatResponse({ message }, 500), null, 2)); } return res.end(JSON.stringify({ message }, null, 2)); }; response.status = (code) => { res.writeHead(code, { 'Content-Type': 'application/json' }); return res; }; response.setCookies = (cookies) => { for (const [key, v] of Object.entries(cookies)) { if (typeof v === 'string') { res.setHeader('Set-Cookie', `${key}=${v}`); continue; } if (v.value === '' || v.value == null) continue; let str = `${key}=${v.value}`; if (v.sameSite != null) { str += ` ;SameSite=${v.sameSite}`; } if (v.domain != null) { str += ` ;Domain=${v.domain}`; } if (v.httpOnly != null) { str += ` ;HttpOnly`; } if (v.secure != null) { str += ` ;Secure`; } if (v.expires != null) { str += ` ;Expires=${v.expires.toUTCString()}`; } res.setHeader('Set-Cookie', str); } }; return response; } _nextFunction(ctx) { const NEXT_MESSAGE = "The 'next' function does not have any subsequent function."; return (err) => { if (err != null) { if (this._errorHandler != null) { return this._errorHandler(err, ctx); } ctx.res.writeHead(500, { 'Content-Type': 'application/json' }); if (this._formatResponse != null) { return ctx.res.end(JSON.stringify(this._formatResponse({ message: err?.message, }, ctx.res.statusCode), null, 2)); } return ctx.res.end(JSON.stringify({ message: err?.message, }, null, 2)); } if (this._errorHandler != null) { return this._errorHandler(new Error(NEXT_MESSAGE), ctx); } ctx.res.writeHead(500, { 'Content-Type': 'application/json' }); if (this._formatResponse != null) { return ctx.res.end(JSON.stringify(this._formatResponse({ message: NEXT_MESSAGE }, ctx.res.statusCode), null, 2)); } return ctx.res.end(JSON.stringify({ message: NEXT_MESSAGE }, null, 2)); }; } _wrapHandlers = (...handlers) => { return (req, res, params) => { const nextHandler = (index = 0) => { const response = this._customizeResponse(req, res); const request = req; request.params = params; const body = request.body; const files = request.files; const cookies = request.cookies; const headers = request.headers; const query = { ...(0, url_1.parse)(String(req.url), true).query }; const RecordOrEmptyRecord = (data) => { if (data == null) return {}; return Object.keys(data).length ? data : {}; }; const ctx = { req: request, res: response, headers: RecordOrEmptyRecord(headers), params: RecordOrEmptyRecord(params), query: RecordOrEmptyRecord(query), body: RecordOrEmptyRecord(body), files: RecordOrEmptyRecord(files), cookies: RecordOrEmptyRecord(cookies) }; if (index === handlers.length - 1) { return this._wrapResponse(handlers[index] .bind(handlers[index]))(ctx, this._nextFunction(ctx)); } return handlers[index](ctx, () => { return nextHandler(index + 1); }); }; try { if (res.writableEnded) return; return nextHandler(); } catch (err) { const ctx = { req, res: this._customizeResponse(req, res), params: Object.keys(params).length ? params : {}, headers: {}, query: {}, body: {}, files: {}, cookies: {} }; return this._nextFunction(ctx)(err); } }; }; _wrapResponse(handler) { return (ctx, next) => { Promise.resolve(handler(ctx, next)) .then(result => { if (ctx.res.writableEnded) return; if (result instanceof http_1.ServerResponse) return; if (result == null) return; if (typeof result === 'string') { if (!ctx.res.headersSent) { ctx.res.writeHead(200, { 'Content-Type': 'text/plain' }); } ctx.res.end(result); return; } if (this._formatResponse != null) { const formattedResult = this._formatResponse(result, ctx.res.statusCode); if (typeof formattedResult === 'string') { if (!ctx.res.headersSent) { ctx.res.writeHead(200, { 'Content-Type': 'text/plain' }); } ctx.res.end(formattedResult); return; } if (!ctx.res.headersSent) { ctx.res.writeHead(200, { 'Content-Type': 'application/json' }); } ctx.res.end(JSON.stringify(formattedResult, null, 2)); return; } if (!ctx.res.headersSent) { ctx.res.writeHead(200, { 'Content-Type': 'application/json' }); } ctx.res.end(result == null ? undefined : JSON.stringify(result, null, 2)); return; }) .catch(err => { if (ctx.res.writableEnded) return; if (!ctx.res.headersSent) { ctx.res.writeHead(500, { 'Content-Type': 'application/json' }); } ctx.res.end(JSON.stringify({ message: err?.message || 'Internal Server Error' })); return; }); }; } async _createServer() { await this._registerMiddlewares(); await this._registerControllers(); const server = http_1.default.createServer({ maxHeaderSize: 1024 * 1024 }, (req, res) => { if (this._cors != null) { this._cors(req, res); } return this._router.lookup(req, res); }); return server; } _normalizePath(...paths) { const path = paths .join('/') .replace(/\/+/g, '/') .replace(/\/+$/, ''); const normalizedPath = path.startsWith('/') ? path : `/${path}`; return /\/api\/api/.test(normalizedPath) ? normalizedPath.replace(/\/api\/api\//, "/api/") : normalizedPath; } _swaggerHandler() { const routes = this.routers .routes.filter(r => ["GET", "POST", "PUT", "PATCH", "DELETE"].includes(r.method)); const { path, html, staticSwaggerHandler, staticUrl } = this._parser.swagger({ ...this._swagger, options: this._swaggerAdditional, routes }); this._router.get(staticUrl, staticSwaggerHandler); this._router.get(String(path), (req, res) => { res.writeHead(200, { 'Content-Type': 'text/html' }); res.write(html); return res.end(); }); return; } } exports.Spear = Spear; class Application extends Spear { } exports.Application = Application; exports.default = Spear; //# sourceMappingURL=index.js.map