UNPKG

tspace-spear

Version:

tspace-spear is a lightweight, high-performance API framework for Node.js that leverages the native HTTP server and supports uWebSockets.js (C++) for maximum speed and efficiency.

1,111 lines 41.8 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 http_1 = __importStar(require("http")); const const_1 = require("../const"); const stream_1 = require("stream"); 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 on_finished_1 = __importDefault(require("on-finished")); const ws_1 = __importDefault(require("ws")); const net_1 = __importDefault(require("net")); const parser_factory_1 = require("./parser-factory"); const fast_router_1 = require("./fast-router"); const response_1 = require("./response"); const compiler_1 = require("../compiler"); const uWS_1 = require("./uWS"); const net_2 = require("./net"); /** * * 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; _router = new fast_router_1.FastRouter(); _parser = new parser_factory_1.ParserFactory(); _globalPrefix = ''; _adapter = { kind: 'http', server: http_1.default }; _cluster; _cors; _swagger = { use: false, path: '/api/docs', servers: [ { url: '/' } ], tags: [], info: { title: "API Documentation", description: "This is a sample documentation", version: "1.0.0" } }; _swaggerSpecs = []; _ws = { handler: null, server: null, options: null }; _errorHandler = null; _globalMiddlewares = []; _formatResponse = null; _onListeners = []; _fileUploadOptions = { limit: Infinity, tempFileDir: 'tmp', removeTempFile: { remove: false, ms: 1000 * 60 * 10 } }; _generatePreRouteTypes; constructor({ controllers, middlewares, globalPrefix, logger, cluster, adapter } = {}) { this._controllers = controllers; this._middlewares = middlewares; if (logger) this.useLogger(); if (cluster) this.useCluster(cluster); if (adapter) this.useAdater(adapter); if (globalPrefix) this.useGlobalPrefix(globalPrefix); // Ensure controllers is NOT an array and has the required shape // before enabling automatic route generation (used for E2E typing). const isValidControllerObject = controllers && !Array.isArray(controllers) && typeof controllers === "object" && "folder" in controllers && "name" in controllers && "preRouteTypes" in controllers && controllers.folder && controllers.name && controllers.preRouteTypes; if (isValidControllerObject) { // Auto-generate route metadata for type-safe E2E usage; this._generatePreRouteTypes = { folder: controllers.folder, name: controllers.name, }; } } /** * 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 {FastRouter} */ get routers() { return this._router; } get contract() { return {}; } /** * The 'usePreRouteTypes' method is used to create pre routes for e2e and swagger * * @param {{object}} options options * @property {string} options.folder * @property {RegExp} options.name * @returns {this} */ usePreRouteTypes(options) { this._generatePreRouteTypes = options; return this; } /** * The 'ws' method is used to creates the WebSocket server. * * @callback {Function} WebSocketServer * @param {WebSocketServer} wss - WebSocketServer * @returns {this} */ ws(handlers, options) { this._ws.handler = handlers(); this._ws.options = options ?? {}; return this; } /** * 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 'useGlobalPrefix' method is used to sets a global prefix for all routes in the router. * * If `globalPrefix` is `null` or `undefined`, it will default to an empty string, * meaning no prefix will be applied. * * @param {string | null} globalPrefix - The base path prefix to apply to all routes. * @returns {this} Returns the current instance for method chaining. */ useGlobalPrefix(globalPrefix) { this._globalPrefix = globalPrefix == null ? '' : globalPrefix.replace(/^\/+|\/+$/g, ''); return this; } /** * The 'useAdater' method is used to switch between different server implementations, * such as the native Node.js HTTP server or uWebSockets.js (uWS). * * @param {T.AdapterServer} adapter - The adapter instance (e.g., HTTP or uWS). * @returns {this} Returns the current instance for chaining */ useAdater(adapter) { if (adapter === http_1.default) { this._adapter = { kind: 'http', server: adapter }; } else if (adapter === net_1.default) { this._adapter = { kind: 'net', server: adapter }; } else { //@ts-ignore this._adapter = { kind: 'uWS', server: adapter }; } this._parser.useAdater(this._adapter); return this; } /** * The 'useCluster' method is used cluster run the server * * @param {boolean | number} cluster * @returns {this} */ useCluster(cluster) { if (cluster === false) return this; this._cluster = cluster ?? true; 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(req.url)) return next(); if (Array.isArray(exceptPath) && exceptPath.some(v => req.url === v)) return next(); if (methods != null && methods.length && !methods.some(v => v.toLowerCase() === req.method.toLowerCase())) { 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(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, res } = ctx; if (Array.isArray(except) && except.some(v => v.toLowerCase() === (req.method).toLowerCase())) { return next(); } const contentType = req?.headers['content-type'] ?? null; if (contentType == null) return next(); const isFileUpload = contentType && contentType.startsWith('multipart/form-data'); if (isFileUpload) return next(); if (req?.body != null) return next(); Promise.resolve(this._parser.body(req, res)) .then(body => { req.body = body; return next(); }) .catch(err => { return this._nextError(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 // bytes. default Infinity * @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, res } = ctx; if (req.method === 'GET') { return next(); } const contentType = req?.headers['content-type']; const isFileUpload = contentType && contentType.startsWith('multipart/form-data'); if (!isFileUpload) return next(); if (req?.files != null) return next(); Promise .resolve(this._parser.files({ req, res, options: this._fileUploadOptions })) .then(r => { req.files = r.files; req.body = r.body; return next(); }) .catch(err => { return this._nextError(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} doc * @returns */ useSwagger(doc = {}) { const { path, servers, tags, info, options } = doc; this._swagger = { use: true, options: options, 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._generatePreRouteTypes) { await new compiler_1.Compiler().generateRoutes(this._globalPrefix, this._generatePreRouteTypes); } if (this._cluster != null && this._cluster || typeof this._cluster === 'number') { this._clusterMode({ server, port, hostname, callback }); return server; } if (this._adapter.kind === 'uWS') { const handler = async () => { this._onListeners.forEach(listener => listener()); if (this._swagger.use) { await this._swaggerHandler(); } callback?.({ server, port }); }; if (hostname) { server.listen(port, String(hostname), handler); } else { server.listen(port, handler); } return server; } const args = hostname ? [port, hostname, () => callback?.({ server, port: port })] : [port, () => callback?.({ server, port: port })]; server.listen(...args); server.on('listening', async () => { this._onListeners.forEach(listener => listener()); if (this._swagger.use) { await this._swaggerHandler(); } }); 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, HEAD, 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 'response' method is used to format the response * * @param {function} format * @returns */ response(format) { this._formatResponse = format; 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 'notfound' method is middleware that is specifically designed to handle errors notfound that occur during the processing of requests * * @param {function} fn * @returns */ notfound(fn) { const handler = ({ req, res }) => { const ctx = this._createContext({ req, res, ps: {} }); return fn(ctx); }; this.all('*', 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 'head' method is used to add the request handler to the router for 'HEAD' 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} */ head(path, ...handlers) { this._onListeners.push(() => { return this._router.head(this._normalizePath(this._globalPrefix, path), this._wrapHandlers(...this._globalMiddlewares, ...handlers)); }); return this; } /** * The 'head' method is used to add the request handler to the router for 'HEAD' 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} */ options(path, ...handlers) { this._onListeners.push(() => { return this._router.options(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' 'HEAD' 'OPTIONS' 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; } async _import(dir, pattern) { const recursive = dir.endsWith("*"); const root = recursive ? dir.slice(0, -1) : dir; return this._scan(root, pattern, recursive); } async _scan(dir, pattern, recursive = false) { const entries = await fs_1.default.promises.readdir(dir, { withFileTypes: true, }); const result = []; for (const entry of entries) { const fullPath = path_1.default.resolve(dir, entry.name); if (entry.isDirectory()) { if (recursive) { result.push(...(await this._scan(fullPath, pattern, true))); } continue; } if (!pattern || pattern.test(entry.name)) { result.push(fullPath); } } return result; } async _registerControllers() { if (this._controllers == null) return; if (!Array.isArray(this._controllers)) { let failDir = false; const controllers = await this._import(this._controllers.folder, this._controllers.name).catch(err => { console.log(`\x1b[31m[ControllerLoader ERROR]\x1b[0m Failed to read directory:\n` + `Error: ${err.message}\n`); failDir = true; return []; }); if (!controllers.length && !failDir) { console.log(`\x1b[33m[ControllerLoader Warning]\x1b[0m No controllers found in:\n` + `\x1b[36m${this._controllers.folder}\x1b[0m\n\n` + `Using pattern:\n` + `\x1b[36m${this._controllers.name}\x1b[0m`); console.log(`\nMake sure that:\n` + `- the folder path exists\n` + `- controller files match the pattern\n` + `- recursive scanning is enabled for nested folders\n\n` + `Example:\n` + `\x1b[36m${this._controllers.folder}/*\x1b[0m\n`); } for (const file of controllers) { const imported = await Promise.resolve(`${file}`).then(s => __importStar(require(s))); let maybeController = imported?.default; if (maybeController == null) { const entry = Object .entries(imported) .find(([name]) => { return /controller$/i.test(name); }); maybeController = entry?.[1]; } const controller = maybeController; if (typeof controller !== "function") { console.log(`\x1b[31m[ControllerLoader ERROR]\x1b[0m \x1b[36m${file}\x1b[0m must export a controller class`); continue; } 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._swaggerSpecs = [ ...this._swaggerSpecs, { ...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._swaggerSpecs = [ ...this._swaggerSpecs, { ...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)) { let failDir = false; const middlewares = await this._import(this._middlewares.folder, this._middlewares.name) .catch(err => { console.log(`\x1b[31m[MiddlewareLoader ERROR]\x1b[0m Failed to read directory:\n` + `Error: ${err.message}\n`); failDir = true; return []; }); if (!middlewares.length && !failDir) { console.log(`\x1b[33m[MiddlewareLoader Warning]\x1b[0m No middlewares found in:\n` + `\x1b[36m${this._middlewares.folder}\x1b[0m\n\n` + `Using pattern:\n` + `\x1b[36m${this._middlewares.name}\x1b[0m`); console.log(`\nMake sure that:\n` + `- the folder path exists\n` + `- middleware files match the pattern\n` + `- recursive scanning is enabled for nested folders\n\n` + `Example:\n` + `\x1b[36m${this._middlewares.folder}/*\x1b[0m\n`); } for (const file of middlewares) { const imported = await Promise.resolve(`${file}`).then(s => __importStar(require(s))); let maybeMiddleware = imported?.default; if (maybeMiddleware == null) { const entry = Object .entries(imported) .find(([name]) => { return /middleware$/i.test(name); }); maybeMiddleware = entry?.[1]; } const middleware = maybeMiddleware; if (typeof middleware !== "function") { console.log(`\x1b[31m[MiddlewareLoader ERROR]\x1b[0m \x1b[36m${file}\x1b[0m must export a middleware`); continue; } this.use(middleware); } return; } const middlewares = this._middlewares; for (const middleware of middlewares) { this.use(middleware); } return; } _wrapHandlers(...handlers) { return (req, res, ps) => { const dispatch = (index = 0) => { const handler = handlers[index]; if (!handler) return; const ctx = this._createContext({ req, res, ps }); try { const next = () => dispatch(index + 1); const isLast = index === handlers.length - 1; if (isLast) { return this._wrapResponse(handler)(ctx, this._nextError(ctx)); } return handler(ctx, next); } catch (err) { return this._nextError(ctx)(err); } }; return dispatch(); }; } _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 instanceof stream_1.Stream) { return; } if (result == null) { ctx.res.noContent(); return; } if (typeof result === 'string') { ctx.res.send(result); return; } ctx.res.json(result); return; }) .catch(err => next(err)); }; } _nextError(ctx) { const NEXT_MESSAGE = "The 'next' function does not have any subsequent function."; return (err) => { if (ctx.res.writableEnded) return; const errorMessage = err?.message || NEXT_MESSAGE; if (this._errorHandler != null) { return this._errorHandler(err, ctx); } if (!ctx.res.headersSent) { ctx.res.writeHead(500, const_1.HEADER_CONTENT_TYPES['json']); } if (this._formatResponse != null) { ctx.res.end(JSON.stringify(this._formatResponse({ message: errorMessage }, ctx.res.statusCode))); return; } ctx.res.end(JSON.stringify({ message: errorMessage })); return; }; } _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) { if (this._adapter.kind === 'uWS') { const handler = () => { this._onListeners.forEach(listener => listener()); if (this._swagger.use) { this._swaggerHandler(); } callback?.({ server, port }); }; if (hostname) { server.listen(port, hostname, handler); return server; } server.listen(port, handler); 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; } async _createServer() { await this._registerMiddlewares(); await this._registerControllers(); const lookup = this._router.lookup.bind(this._router); const cors = this._cors; const adapter = this._adapter; if (adapter.kind === 'uWS') { const server = adapter.server.App(); server.any('/*', (uwsRes, uwsReq) => { const { req, res } = (0, uWS_1.uWSAdaptRequestResponse)(uwsReq, uwsRes); if (cors) cors(req, res); return lookup(req, res); }); if (this._ws?.handler) { server.ws('/*', { open: (ws) => { this._ws.handler?.connection?.(ws); }, message: (ws, message) => { this._ws.handler?.message?.(ws, Buffer.from(message)); }, close: (ws, code, message) => { this._ws.handler?.close?.(ws, code, Buffer.from(message)); } }); } return server; } if (adapter.kind === 'net') { const server = net_1.default.createServer((socket) => { (0, net_2.netAdaptRequestResponse)(socket, (req, res) => { if (cors) cors(req, res); return lookup(req, res); }); }); if (this._ws?.handler) { this._ws.server = new ws_1.default.Server({ server, ...this._ws.options }); this._ws.server.on('connection', (ws) => { if (this._ws.handler?.connection) { this._ws.handler.connection(ws); } ws.on('message', (data) => { this._ws.handler?.message?.(ws, data); }); ws.on('close', (code, reason) => { if (this._ws.handler?.close) { this._ws.handler?.close(ws, code, reason); } }); ws.on('error', (err) => { if (this._ws.handler?.error) { this._ws.handler.error(ws, err); } }); }); } return server; } const server = http_1.default.createServer((req, res) => { if (cors) cors(req, res); return lookup(req, res); }); if (this._ws?.handler) { this._ws.server = new ws_1.default.Server({ server, ...this._ws.options }); this._ws.server.on('connection', (ws) => { if (this._ws.handler?.connection) { this._ws.handler.connection(ws); } ws.on('message', (data) => { this._ws.handler?.message?.(ws, data); }); ws.on('close', (code, reason) => { if (this._ws.handler?.close) { this._ws.handler?.close(ws, code, reason); } }); ws.on('error', (err) => { if (this._ws.handler?.error) { this._ws.handler.error(ws, err); } }); }); } return server; } _createContext({ req, res, ps }) { const request = req; const response = (0, response_1.Response)(req, res, { formatResponse: this._formatResponse, isUwebSocket: this._adapter.kind === 'uWS' }); const headers = req.headers; const params = ps; const body = request.body; const files = request.files; const cookies = request.cookies; const query = this._parser.queryString(req.url) || {}; const xff = headers['x-forwarded-for']; const xrip = headers['x-real-ip']; const cfip = headers['cf-connecting-ip']; let ips = []; if (cfip) { ips = Array.isArray(cfip) ? cfip : [cfip]; } else if (xff) { ips = Array.isArray(xff) ? xff : [xff]; } else if (xrip) { ips = Array.isArray(xrip) ? xrip : [xrip]; } else { const addr = req.socket?.remoteAddress; ips = addr ? [addr] : []; } const ip = (ips.length ? ips[0] : null); request.params = params; request.query = query; request.ip = ip; request.ips = ips; return { req: request, res: response, headers: headers || Object.create(null), params: params || Object.create(null), query, body: body || Object.create(null), files: files || Object.create(null), cookies: cookies || Object.create(null), ip, ips }; } _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; } async _swaggerHandler() { const routes = this.routers .routes .filter(r => { return [ "GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS" ].includes(r.method); }); const { path, html, staticSwaggerHandler, staticUrl } = await this._parser.swagger({ ...this._swagger, specs: this._swaggerSpecs, routes, globalPrefix: this._globalPrefix }); this._router.get(staticUrl, staticSwaggerHandler); this._router.get(path, (req, res) => { res.writeHead(200, const_1.HEADER_CONTENT_TYPES['html']); res.end(html); return; }); return; } } exports.Spear = Spear; class Application extends Spear { } exports.Application = Application; exports.default = Spear; //# sourceMappingURL=index.js.map