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
JavaScript
"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