tspace-spear 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/License +1 -1
  2. package/README.md +364 -47
  3. package/build/lib/core/decorators/context.js +11 -10
  4. package/build/lib/core/decorators/context.js.map +1 -0
  5. package/build/lib/core/decorators/controller.js +1 -0
  6. package/build/lib/core/decorators/controller.js.map +1 -0
  7. package/build/lib/core/decorators/headers.js +1 -0
  8. package/build/lib/core/decorators/headers.js.map +1 -0
  9. package/build/lib/core/decorators/index.d.ts +1 -0
  10. package/build/lib/core/decorators/index.js +2 -0
  11. package/build/lib/core/decorators/index.js.map +1 -0
  12. package/build/lib/core/decorators/methods.js +1 -0
  13. package/build/lib/core/decorators/methods.js.map +1 -0
  14. package/build/lib/core/decorators/middleware.d.ts +1 -1
  15. package/build/lib/core/decorators/middleware.js +1 -0
  16. package/build/lib/core/decorators/middleware.js.map +1 -0
  17. package/build/lib/core/decorators/statusCode.js +1 -0
  18. package/build/lib/core/decorators/statusCode.js.map +1 -0
  19. package/build/lib/core/decorators/swagger.d.ts +2 -0
  20. package/build/lib/core/decorators/swagger.js +15 -0
  21. package/build/lib/core/decorators/swagger.js.map +1 -0
  22. package/build/lib/core/server/index.d.ts +90 -44
  23. package/build/lib/core/server/index.js +314 -286
  24. package/build/lib/core/server/index.js.map +1 -0
  25. package/build/lib/core/server/parser-factory.d.ts +26 -0
  26. package/build/lib/core/server/parser-factory.js +428 -0
  27. package/build/lib/core/server/parser-factory.js.map +1 -0
  28. package/build/lib/core/server/q.bak.d.ts +0 -0
  29. package/build/lib/core/server/q.bak.js +965 -0
  30. package/build/lib/core/server/q.bak.js.map +1 -0
  31. package/build/lib/core/server/router.d.ts +63 -2
  32. package/build/lib/core/server/router.js +63 -1
  33. package/build/lib/core/server/router.js.map +1 -0
  34. package/build/lib/{types → core/types}/index.d.ts +85 -9
  35. package/build/lib/{types → core/types}/index.js +1 -0
  36. package/build/lib/core/types/index.js.map +1 -0
  37. package/build/lib/index.d.ts +4 -13
  38. package/build/lib/index.js +7 -23
  39. package/build/lib/index.js.map +1 -0
  40. package/build/tests/benchmark.test.d.ts +1 -0
  41. package/build/tests/benchmark.test.js +135 -0
  42. package/build/tests/benchmark.test.js.map +1 -0
  43. package/package.json +30 -19
@@ -35,22 +35,23 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
35
35
  return (mod && mod.__esModule) ? mod : { "default": mod };
36
36
  };
37
37
  Object.defineProperty(exports, "__esModule", { value: true });
38
- exports.Application = void 0;
38
+ exports.Spear = exports.Application = void 0;
39
+ const cluster_1 = __importDefault(require("cluster"));
40
+ const os_1 = __importDefault(require("os"));
39
41
  const fs_1 = __importDefault(require("fs"));
40
42
  const path_1 = __importDefault(require("path"));
41
- const find_my_way_1 = __importDefault(require("find-my-way"));
42
- const formidable_1 = __importDefault(require("formidable"));
43
43
  const url_1 = require("url");
44
44
  const on_finished_1 = __importDefault(require("on-finished"));
45
- const string_decoder_1 = require("string_decoder");
46
45
  const http_1 = __importStar(require("http"));
46
+ const find_my_way_1 = __importDefault(require("find-my-way"));
47
+ const parser_factory_1 = require("./parser-factory");
47
48
  /**
48
49
  *
49
- * The 'Application' class is used to create a server and handle HTTP requests.
50
+ * The 'Spear' class is used to create a server and handle HTTP requests.
50
51
  *
51
- * @returns {Application} application
52
+ * @returns {Spear} application
52
53
  * @example
53
- * new Application()
54
+ * new Spear()
54
55
  * .get('/' , () => 'Hello world!')
55
56
  * .get('/json' , () => {
56
57
  * return {
@@ -60,128 +61,62 @@ const http_1 = __importStar(require("http"));
60
61
  * .listen(3000 , () => console.log('server listening on port : 3000'))
61
62
  *
62
63
  */
63
- class Application {
64
- constructor({ controllers, middlewares, globalPrefix, logger } = {}) {
64
+ class Spear {
65
+ constructor({ controllers, middlewares, globalPrefix, logger, cluster } = {}) {
65
66
  this._router = (0, find_my_way_1.default)();
67
+ this._parser = new parser_factory_1.ParserFactory();
68
+ this._swagger = {
69
+ use: false,
70
+ path: '/api/docs',
71
+ servers: [
72
+ {
73
+ url: 'http://localhost:3000'
74
+ }
75
+ ],
76
+ tags: [],
77
+ info: {
78
+ title: "API Documentation",
79
+ description: "This is a sample documentation",
80
+ version: "1.0.0"
81
+ }
82
+ };
83
+ this._swaggerAdditional = [];
66
84
  this._errorHandler = null;
67
85
  this._globalMiddlewares = [];
68
86
  this._formatResponse = null;
69
87
  this._onListeners = [];
70
88
  this._fileUploadOptions = {
71
- limits: Infinity,
72
- useTempFiles: false,
89
+ limit: Infinity,
73
90
  tempFileDir: 'tmp',
74
91
  removeTempFile: {
75
- remove: true,
92
+ remove: false,
76
93
  ms: 1000 * 60 * 10
77
94
  }
78
95
  };
79
- this._filesParser = (req) => __awaiter(this, void 0, void 0, function* () {
80
- try {
81
- const temp = this._fileUploadOptions.tempFileDir;
82
- if (!fs_1.default.existsSync(temp)) {
83
- try {
84
- fs_1.default.mkdirSync(temp, { recursive: true });
85
- }
86
- catch (err) { }
87
- }
88
- const form = (0, formidable_1.default)({ uploadDir: temp });
89
- const [dataBody, dataFiles] = yield form.parse(req);
90
- const files = {};
91
- const body = {};
92
- const removeTemp = (fileTemp) => {
93
- if (!this._fileUploadOptions.removeTempFile.remove)
94
- return;
95
- const remove = () => fs_1.default.unlinkSync(fileTemp);
96
- setTimeout(remove, this._fileUploadOptions.removeTempFile.ms);
97
- };
98
- for (const key in dataFiles) {
99
- const v = dataFiles[key];
100
- if (v == null)
101
- continue;
102
- const file = v[0];
103
- if (file.size > this._fileUploadOptions.limits) {
104
- fs_1.default.unlinkSync(file.filepath);
105
- continue;
106
- }
107
- files[key] = {
108
- size: file.size,
109
- tempFilePath: file.filepath,
110
- tempFileName: file.newFilename,
111
- mimetype: file.mimetype,
112
- name: file.originalFilename
113
- };
114
- removeTemp(file.filepath);
115
- }
116
- for (const key in dataBody) {
117
- const v = dataBody[key];
118
- if (v == null)
119
- continue;
120
- body[key] = v[0];
121
- }
122
- return {
123
- body,
124
- files
125
- };
126
- }
127
- catch (e) {
128
- return {
129
- body: {},
130
- files: {}
131
- };
132
- }
133
- });
134
- this._bodyParser = (req) => {
135
- if ((req === null || req === void 0 ? void 0 : req._writeParserEnd) === true)
136
- return req._bodyParser;
137
- return new Promise((resolve, reject) => {
138
- const decoder = new string_decoder_1.StringDecoder('utf-8');
139
- let payload = '';
140
- req.on('data', (data) => {
141
- payload += decoder.write(data);
142
- });
143
- req.on('end', () => {
144
- try {
145
- payload += decoder.end();
146
- req._writeParserEnd = true;
147
- req._bodyParser = JSON.parse(payload);
148
- return resolve(JSON.parse(payload));
149
- }
150
- catch (e) {
151
- req._writeParserEnd = true;
152
- req._bodyParser = {};
153
- return resolve({});
154
- }
155
- });
156
- req.on('error', (err) => {
157
- req._writeParserEnd = true;
158
- req._bodyParser = {};
159
- return reject(err);
160
- });
161
- });
162
- };
163
96
  this._wrapHandlers = (...handlers) => {
164
97
  return (req, res, params) => __awaiter(this, void 0, void 0, function* () {
165
- const runHandler = (index = 0) => __awaiter(this, void 0, void 0, function* () {
98
+ const runHandler = (...args_1) => __awaiter(this, [...args_1], void 0, function* (index = 0) {
166
99
  const response = this._customizeResponse(req, res);
167
100
  const request = req;
168
101
  const body = request._bodyParser;
169
102
  const files = request._filesParser;
170
103
  const cookies = request._cookiesParser;
104
+ const headers = request.headers;
171
105
  const query = Object.assign({}, (0, url_1.parse)(String(req.url), true).query);
172
- const objectOrNull = (data) => {
106
+ const RecordOrEmptyRecord = (data) => {
173
107
  if (data == null)
174
- return null;
175
- return Object.keys(data).length ? data : null;
108
+ return {};
109
+ return Object.keys(data).length ? data : {};
176
110
  };
177
111
  const ctx = {
178
112
  req: request,
179
113
  res: response,
180
- params: objectOrNull(params),
181
- query: objectOrNull(query),
182
- body: objectOrNull(body),
183
- files: objectOrNull(files),
184
- cookies: objectOrNull(cookies),
114
+ headers: RecordOrEmptyRecord(headers),
115
+ params: RecordOrEmptyRecord(params),
116
+ query: RecordOrEmptyRecord(query),
117
+ body: RecordOrEmptyRecord(body),
118
+ files: RecordOrEmptyRecord(files),
119
+ cookies: RecordOrEmptyRecord(cookies),
185
120
  };
186
121
  if (index === handlers.length - 1) {
187
122
  return this._wrapResponse(handlers[index].bind(handlers[index]))(ctx, this._nextFunction(ctx));
@@ -195,53 +130,30 @@ class Application {
195
130
  const ctx = {
196
131
  req,
197
132
  res: this._customizeResponse(req, res),
198
- params: Object.keys(params).length ? params : null,
199
- query: null,
200
- body: null,
201
- files: null,
202
- cookies: null
133
+ params: Object.keys(params).length ? params : {},
134
+ headers: {},
135
+ query: {},
136
+ body: {},
137
+ files: {},
138
+ cookies: {}
203
139
  };
204
140
  return this._nextFunction(ctx)(err);
205
141
  });
206
142
  });
207
143
  };
208
144
  if (logger)
209
- this._onListeners.push(() => this.use(this._logger));
145
+ this.useLogger();
146
+ this._cluster = cluster;
210
147
  this._controllers = controllers;
211
148
  this._middlewares = middlewares;
212
149
  this._globalPrefix = globalPrefix == null ? '' : globalPrefix;
213
150
  }
214
- /**
215
- * The 'enableCors' is used to enable the cors origins on the server.
216
- *
217
- * @params {Object}
218
- * @property {(string | RegExp)[]} origins
219
- * @property {boolean} credentials
220
- * @returns
221
- */
222
- enableCors({ origins, credentials } = {}) {
223
- this._globalMiddlewares.push(({ req, res }, next) => {
224
- var _a;
225
- const origin = (_a = req.headers) === null || _a === void 0 ? void 0 : _a.origin;
226
- if (origin == null)
227
- return next();
228
- res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
229
- res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
230
- if (origins == null) {
231
- res.setHeader('Access-Control-Allow-Origin', '*');
232
- }
233
- if (Array.isArray(origins) && origins.length) {
234
- if (origins.includes(origin)) {
235
- res.setHeader('Access-Control-Allow-Origin', origin);
236
- }
237
- }
238
- if (credentials) {
239
- res.setHeader('Access-Control-Allow-Credentials', 'true');
240
- }
241
- return next();
242
- });
151
+ get instance() {
243
152
  return this;
244
153
  }
154
+ get routers() {
155
+ return this._router;
156
+ }
245
157
  /**
246
158
  * The 'use' method is used to add the middleware into the request pipeline.
247
159
  *
@@ -255,24 +167,51 @@ class Application {
255
167
  return this;
256
168
  }
257
169
  /**
258
- * The 'useRouter' method is used to add the router in the request context.
170
+ * The 'useLogger' method is used to add the middleware view logger response.
259
171
  *
260
- * @parms {Function} router
261
- * @property {Function} router - get() , post() , put() , patch() , delete()
172
+ * @callback {Function} middleware
173
+ * @property {Object} ctx - context { req , res , query , params , cookies , files , body}
174
+ * @property {Function} next - go to next function
262
175
  * @returns {this}
263
176
  */
264
- useRouter(router) {
265
- const routes = router.routes;
266
- for (const { path, method, handlers } of routes) {
267
- let normalizePath = [
268
- this._globalPrefix,
269
- path
270
- ].join('/')
271
- .replace(/\/+/g, '/')
272
- .replace(/\/+$/, '');
273
- normalizePath = normalizePath.startsWith('/') ? normalizePath : `/${normalizePath}`;
274
- this[method](normalizePath, ...handlers);
275
- }
177
+ useLogger({ methods, exceptPath } = {}) {
178
+ this._globalMiddlewares.push(({ req, res }, next) => {
179
+ const diffTime = (hrtime) => {
180
+ const MS = 1000;
181
+ if (hrtime == null)
182
+ return 0;
183
+ const [start, end] = process.hrtime(hrtime);
184
+ const time = ((start * MS) + (end / 1e6));
185
+ return `${time > MS ? `${(time / MS).toFixed(2)} s` : `${time.toFixed(2)} ms`}`;
186
+ };
187
+ const statusCode = (res) => {
188
+ const statusCode = res.statusCode == null ? 500 : Number(res.statusCode);
189
+ return statusCode < 400
190
+ ? `\x1b[32m${statusCode}\x1b[0m`
191
+ : `\x1b[31m${statusCode}\x1b[0m`;
192
+ };
193
+ if (exceptPath instanceof RegExp && !exceptPath.test(String(req.url)))
194
+ return next();
195
+ if (Array.isArray(exceptPath) && !exceptPath.some(v => String(req.url) !== v))
196
+ return next();
197
+ if (methods != null &&
198
+ methods.length &&
199
+ !methods.some(v => v.toLocaleLowerCase() === String(req.method).toLocaleLowerCase())) {
200
+ return next();
201
+ }
202
+ const startTime = process.hrtime();
203
+ (0, on_finished_1.default)(res, () => {
204
+ console.log([
205
+ `[\x1b[1m\x1b[34mINFO\x1b[0m]`,
206
+ `\x1b[34m${new Date().toJSON()}\x1b[0m`,
207
+ `\x1b[33m${req.method}\x1b[0m`,
208
+ `${decodeURIComponent(String(req.url))}`,
209
+ `${statusCode(res)}`,
210
+ `${diffTime(startTime)}`,
211
+ ].join(" "));
212
+ });
213
+ return next();
214
+ });
276
215
  return this;
277
216
  }
278
217
  /**
@@ -281,17 +220,14 @@ class Application {
281
220
  * @returns {this}
282
221
  */
283
222
  useBodyParser() {
284
- this._globalMiddlewares.push(({ req, res }, next) => __awaiter(this, void 0, void 0, function* () {
223
+ this._globalMiddlewares.push((_a, next_1) => __awaiter(this, [_a, next_1], void 0, function* ({ req, res }, next) {
285
224
  const contentType = req === null || req === void 0 ? void 0 : req.headers['content-type'];
286
225
  const isFileUpload = contentType && contentType.startsWith('multipart/form-data');
287
- const isListMethods = ['POST', 'PATCH', 'PUT'].includes(String(req.method));
288
- if (!isListMethods)
289
- return next();
290
226
  if (isFileUpload)
291
227
  return next();
292
228
  if ((req === null || req === void 0 ? void 0 : req._bodyParser) != null)
293
229
  return next();
294
- req._bodyParser = yield this._bodyParser(req);
230
+ req._bodyParser = yield this._parser.body(req);
295
231
  return next();
296
232
  }));
297
233
  return this;
@@ -305,53 +241,149 @@ class Application {
305
241
  this._globalMiddlewares.push(({ req }, next) => {
306
242
  if ((req === null || req === void 0 ? void 0 : req._cookiesParser) != null)
307
243
  return next();
308
- req._cookiesParser = this._cookiesParser(req);
244
+ req._cookiesParser = this._parser.cookies(req);
309
245
  return next();
310
246
  });
311
247
  return this;
312
248
  }
249
+ /**
250
+ * The 'useRouter' method is used to add the router in the request context.
251
+ *
252
+ * @parms {Function} router
253
+ * @property {Function} router - get() , post() , put() , patch() , delete()
254
+ * @returns {this}
255
+ */
256
+ useRouter(router) {
257
+ const routes = router.routes;
258
+ for (const { path, method, handlers } of routes) {
259
+ this[method](this._normalizePath(this._globalPrefix, path), ...handlers);
260
+ }
261
+ return this;
262
+ }
313
263
  /**
314
264
  * The 'useFileUpload' method is a middleware used to handler file uploads. It adds a file upload of incoming HTTP requests.
315
265
  *
316
266
  * @param {?Object}
317
267
  * @property {?number} limits
318
- * @property {?boolean} useTempFiles
319
268
  * @property {?string} tempFileDir
320
269
  * @property {?Object} removeTempFile
321
270
  * @property {boolean} removeTempFile.remove
322
271
  * @property {number} removeTempFile.ms
323
272
  * @returns
324
273
  */
325
- useFileUpload({ limits, useTempFiles, tempFileDir, removeTempFile } = {}) {
326
- if (limits != null) {
327
- this._fileUploadOptions.limits = limits;
274
+ useFileUpload({ limit, tempFileDir, removeTempFile } = {}) {
275
+ if (limit != null) {
276
+ this._fileUploadOptions.limit = limit;
328
277
  }
329
278
  if (tempFileDir != null) {
330
279
  this._fileUploadOptions.tempFileDir = tempFileDir;
331
280
  }
332
- if (useTempFiles != null) {
333
- this._fileUploadOptions.useTempFiles = useTempFiles;
334
- }
335
281
  if (removeTempFile != null) {
336
282
  this._fileUploadOptions.removeTempFile = removeTempFile;
337
283
  }
338
- this._globalMiddlewares.push(({ req }, next) => __awaiter(this, void 0, void 0, function* () {
284
+ this._globalMiddlewares.push((_a, next_1) => __awaiter(this, [_a, next_1], void 0, function* ({ req }, next) {
339
285
  const contentType = req === null || req === void 0 ? void 0 : req.headers['content-type'];
340
286
  const isFileUpload = contentType && contentType.startsWith('multipart/form-data');
341
- const isListMethods = ['POST', 'PATCH', 'PUT'].includes(String(req.method));
342
- if (!isListMethods)
343
- return next();
344
- if (!isFileUpload)
287
+ const isListMethods = ['POST', 'PATCH', 'PUT', 'DELETE'].includes(String(req.method));
288
+ if (!isListMethods || !isFileUpload)
345
289
  return next();
346
290
  if ((req === null || req === void 0 ? void 0 : req._filesParser) != null)
347
291
  return next();
348
- const { body, files } = yield this._filesParser(req);
292
+ const { body, files } = yield this._parser.files({ req, options: this._fileUploadOptions });
349
293
  req._filesParser = files;
350
294
  req._bodyParser = body;
351
295
  return next();
352
296
  }));
353
297
  return this;
354
298
  }
299
+ /**
300
+ * The 'useSwagger' method is a middleware used to create swagger api.
301
+ *
302
+ * @param {?Object}
303
+ * @property {?string} path
304
+ * @property {?array} servers
305
+ * @property {?object} info
306
+ * @property {?array} tags
307
+ * @returns
308
+ */
309
+ useSwagger({ path, servers, info, tags } = {}) {
310
+ this._swagger = {
311
+ use: true,
312
+ path: path !== null && path !== void 0 ? path : this._swagger.path,
313
+ servers: servers !== null && servers !== void 0 ? servers : this._swagger.servers,
314
+ tags: tags !== null && tags !== void 0 ? tags : this._swagger.tags,
315
+ info: info !== null && info !== void 0 ? info : this._swagger.info
316
+ };
317
+ return this;
318
+ }
319
+ /**
320
+ * The 'listen' method is used to bind and start a server to a particular port and optionally a hostname.
321
+ *
322
+ * @param {number} port
323
+ * @param {function} callback
324
+ * @returns
325
+ */
326
+ listen() {
327
+ var arguments_1 = arguments;
328
+ return __awaiter(this, arguments, void 0, function* (port = 3000, callback) {
329
+ if (arguments_1.length === 1 && typeof port === 'function') {
330
+ callback = port;
331
+ port = 3000;
332
+ }
333
+ const server = yield this._createServer();
334
+ if (this._cluster != null &&
335
+ this._cluster || (typeof this._cluster === 'object' && Object.keys(this._cluster).length)) {
336
+ this._clusterMode(server, Number(port), callback);
337
+ return;
338
+ }
339
+ server.listen(port == null ? 3000 : port, () => {
340
+ if (callback)
341
+ callback({ server, port });
342
+ });
343
+ server.on('listening', () => {
344
+ this._onListeners.forEach(listener => listener());
345
+ if (this._swagger.use) {
346
+ this._swaggerHandler();
347
+ }
348
+ });
349
+ server.on('error', (_) => {
350
+ port = Math.floor(Math.random() * 8999) + 1000;
351
+ server.listen(port);
352
+ });
353
+ return;
354
+ });
355
+ }
356
+ /**
357
+ * The 'enableCors' is used to enable the cors origins on the server.
358
+ *
359
+ * @params {Object}
360
+ * @property {(string | RegExp)[]} origins
361
+ * @property {boolean} credentials
362
+ * @returns
363
+ */
364
+ enableCors({ origins, credentials } = {}) {
365
+ this._globalMiddlewares.push(({ req, res }, next) => {
366
+ var _a;
367
+ const origin = (_a = req.headers) === null || _a === void 0 ? void 0 : _a.origin;
368
+ if (origin == null)
369
+ return next();
370
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE, OPTIONS');
371
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
372
+ if (origins == null) {
373
+ res.setHeader('Access-Control-Allow-Origin', '*');
374
+ }
375
+ if (Array.isArray(origins) && origins.length) {
376
+ if (origins.includes(origin)) {
377
+ res.setHeader('Access-Control-Allow-Origin', origin);
378
+ }
379
+ }
380
+ if (credentials) {
381
+ res.setHeader('Access-Control-Allow-Credentials', 'true');
382
+ }
383
+ return next();
384
+ });
385
+ return this;
386
+ }
355
387
  /**
356
388
  * The 'formatResponse' method is used to format the response
357
389
  *
@@ -363,7 +395,9 @@ class Application {
363
395
  return this;
364
396
  }
365
397
  /**
366
- * The 'errorHandler' method is middleware that is specifically designed to handle errors that occur during the processing of requests
398
+ * The 'errorHandler' method is middleware that is specifically designed to handle errors.
399
+ *
400
+ * that occur during the processing of requests
367
401
  *
368
402
  * @param {function} error
369
403
  * @returns
@@ -383,11 +417,12 @@ class Application {
383
417
  return notfound({
384
418
  req,
385
419
  res: this._customizeResponse(req, res),
386
- query: null,
387
- files: null,
388
- body: null,
389
- params: null,
390
- cookies: null
420
+ headers: {},
421
+ query: {},
422
+ files: {},
423
+ body: {},
424
+ params: {},
425
+ cookies: {}
391
426
  });
392
427
  };
393
428
  this._onListeners.push(() => {
@@ -406,7 +441,7 @@ class Application {
406
441
  */
407
442
  get(path, ...handlers) {
408
443
  this._onListeners.push(() => {
409
- return this._router.get(path, this._wrapHandlers(...this._globalMiddlewares, ...handlers));
444
+ return this._router.get(this._normalizePath(this._globalPrefix, path), this._wrapHandlers(...this._globalMiddlewares, ...handlers));
410
445
  });
411
446
  return this;
412
447
  }
@@ -421,7 +456,7 @@ class Application {
421
456
  */
422
457
  post(path, ...handlers) {
423
458
  this._onListeners.push(() => {
424
- return this._router.post(path, this._wrapHandlers(...this._globalMiddlewares, ...handlers));
459
+ return this._router.post(this._normalizePath(this._globalPrefix, path), this._wrapHandlers(...this._globalMiddlewares, ...handlers));
425
460
  });
426
461
  return this;
427
462
  }
@@ -436,7 +471,7 @@ class Application {
436
471
  */
437
472
  put(path, ...handlers) {
438
473
  this._onListeners.push(() => {
439
- return this._router.put(path, this._wrapHandlers(...this._globalMiddlewares, ...handlers));
474
+ return this._router.put(this._normalizePath(this._globalPrefix, path), this._wrapHandlers(...this._globalMiddlewares, ...handlers));
440
475
  });
441
476
  return this;
442
477
  }
@@ -451,7 +486,7 @@ class Application {
451
486
  */
452
487
  patch(path, ...handlers) {
453
488
  this._onListeners.push(() => {
454
- return this._router.patch(path, this._wrapHandlers(...this._globalMiddlewares, ...handlers));
489
+ return this._router.patch(this._normalizePath(this._globalPrefix, path), this._wrapHandlers(...this._globalMiddlewares, ...handlers));
455
490
  });
456
491
  return this;
457
492
  }
@@ -466,12 +501,12 @@ class Application {
466
501
  */
467
502
  delete(path, ...handlers) {
468
503
  this._onListeners.push(() => {
469
- return this._router.delete(path, this._wrapHandlers(...this._globalMiddlewares, ...handlers));
504
+ return this._router.delete(this._normalizePath(this._globalPrefix, path), this._wrapHandlers(...this._globalMiddlewares, ...handlers));
470
505
  });
471
506
  return this;
472
507
  }
473
508
  /**
474
- * The 'all' method is used to add the request handler to the router for the 'all' method.
509
+ * The 'all' method is used to add the request handler to the router for 'GET' 'POST' 'PUT' 'PATCH' 'DELETE' methods.
475
510
  *
476
511
  * @param {string} path
477
512
  * @callback {...Function[]} handlers of the middlewares
@@ -481,65 +516,44 @@ class Application {
481
516
  */
482
517
  all(path, ...handlers) {
483
518
  this._onListeners.push(() => {
484
- return this._router.all(path, this._wrapHandlers(...this._globalMiddlewares, ...handlers));
519
+ return this._router.all(this._normalizePath(this._globalPrefix, path), this._wrapHandlers(...this._globalMiddlewares, ...handlers));
485
520
  });
486
521
  return this;
487
522
  }
488
- /**
489
- * The 'listen' method is used to bind and start a server to a particular port and optionally a hostname.
490
- *
491
- * @param {number} port
492
- * @param {function} cb
493
- * @returns
494
- */
495
- listen(port = 3000, cb) {
496
- return __awaiter(this, arguments, void 0, function* () {
497
- if (arguments.length === 1 && typeof port === 'function') {
498
- cb = port;
499
- port = 3000;
523
+ _clusterMode(server, port, callback) {
524
+ var _a;
525
+ if (cluster_1.default.isPrimary) {
526
+ const numCPUs = os_1.default.cpus().length;
527
+ const maxWorkers = ((_a = this._cluster) === null || _a === void 0 ? void 0 : _a.maxWorkers) == null
528
+ ? numCPUs
529
+ : this._cluster.maxWorkers > numCPUs
530
+ ? numCPUs
531
+ : this._cluster.maxWorkers;
532
+ for (let i = 0; i < maxWorkers; i++) {
533
+ cluster_1.default.fork();
500
534
  }
501
- const server = yield this._createServer();
535
+ cluster_1.default.on('exit', () => {
536
+ cluster_1.default.fork();
537
+ });
538
+ }
539
+ if (cluster_1.default.isWorker) {
540
+ server.listen(port == null ? 3000 : port, () => {
541
+ if (callback) {
542
+ callback({ server, port });
543
+ }
544
+ });
502
545
  server.on('listening', () => {
503
546
  this._onListeners.forEach(listener => listener());
547
+ if (this._swagger.use) {
548
+ this._swaggerHandler();
549
+ }
504
550
  });
505
551
  server.on('error', (_) => {
506
552
  port = Math.floor(Math.random() * 8999) + 1000;
507
553
  server.listen(port);
508
554
  });
509
- server.listen(port == null ? 3000 : port, () => {
510
- if (cb)
511
- cb();
512
- });
513
- return;
514
- });
515
- }
516
- _logger({ req, res }, next) {
517
- const diffTime = (hrtime) => {
518
- const MS = 1000;
519
- if (hrtime == null)
520
- return 0;
521
- const [start, end] = process.hrtime(hrtime);
522
- const time = +(((start * MS) + (end / 1e6)).toFixed(2));
523
- return `${time > MS ? `${time / MS} s` : `${time} ms`}`;
524
- };
525
- const statusCode = (res) => {
526
- const statusCode = res.statusCode == null ? 500 : Number(res.statusCode);
527
- return statusCode < 400
528
- ? `\x1b[32m${statusCode}\x1b[0m`
529
- : `\x1b[31m${statusCode}\x1b[0m`;
530
- };
531
- const startTime = process.hrtime();
532
- (0, on_finished_1.default)(res, () => {
533
- console.log([
534
- `[\x1b[1m\x1b[34mINFO\x1b[0m]`,
535
- `\x1b[34m${new Date().toJSON()}\x1b[0m`,
536
- `\x1b[33m${req.method}\x1b[0m`,
537
- `${decodeURIComponent(String(req.url))}`,
538
- `${statusCode(res)}`,
539
- `${diffTime(startTime)}`,
540
- ].join(" "));
541
- });
542
- return next();
555
+ }
556
+ return;
543
557
  }
544
558
  _import(dir, pattern) {
545
559
  return __awaiter(this, void 0, void 0, function* () {
@@ -560,6 +574,7 @@ class Application {
560
574
  }
561
575
  _registerControllers() {
562
576
  return __awaiter(this, void 0, void 0, function* () {
577
+ var _a, _b, _c, _d, _e, _f;
563
578
  if (this._controllers == null)
564
579
  return;
565
580
  if (!Array.isArray(this._controllers)) {
@@ -568,40 +583,40 @@ class Application {
568
583
  const response = yield Promise.resolve(`${file}`).then(s => __importStar(require(s)));
569
584
  const controller = response === null || response === void 0 ? void 0 : response.default;
570
585
  const controllerInstance = new controller();
571
- const prefixPath = Reflect.getMetadata("controllers", controller);
572
- const routers = Reflect.getMetadata("routers", controller);
586
+ const prefixPath = (_a = Reflect.getMetadata("controllers", controller)) !== null && _a !== void 0 ? _a : '';
587
+ const routers = (_b = Reflect.getMetadata("routers", controller)) !== null && _b !== void 0 ? _b : [];
588
+ const swaggers = (_c = Reflect.getMetadata("swaggers", controller)) !== null && _c !== void 0 ? _c : [];
573
589
  if (prefixPath == null)
574
590
  continue;
575
- for (const { method, path, handler } of routers) {
576
- let normalizePath = [
577
- this._globalPrefix,
578
- prefixPath,
579
- path
580
- ].join('/')
581
- .replace(/\/+/g, '/')
582
- .replace(/\/+$/, '');
583
- normalizePath = normalizePath.startsWith('/') ? normalizePath : `/${normalizePath}`;
584
- this[method](normalizePath, this._wrapResponse(controllerInstance[String(handler)].bind(controllerInstance)));
591
+ for (const { method, path, handler } of Array.from(routers)) {
592
+ const find = Array.from(swaggers).find(s => s.handler === handler);
593
+ if (find != null) {
594
+ this._swaggerAdditional = [
595
+ ...this._swaggerAdditional,
596
+ Object.assign(Object.assign({}, find), { path: this._normalizePath(this._globalPrefix, prefixPath, path), method })
597
+ ];
598
+ }
599
+ this[method](this._normalizePath(this._globalPrefix, prefixPath, path), this._wrapResponse(controllerInstance[String(handler)].bind(controllerInstance)));
585
600
  }
586
601
  }
587
602
  return;
588
603
  }
589
604
  for (const controller of this._controllers) {
590
605
  const controllerInstance = new controller();
591
- const prefixPath = Reflect.getMetadata("controllers", controller);
592
- const routers = Reflect.getMetadata("routers", controller);
606
+ const prefixPath = (_d = Reflect.getMetadata("controllers", controller)) !== null && _d !== void 0 ? _d : '';
607
+ const routers = (_e = Reflect.getMetadata("routers", controller)) !== null && _e !== void 0 ? _e : [];
608
+ const swaggers = (_f = Reflect.getMetadata("swaggers", controller)) !== null && _f !== void 0 ? _f : [];
593
609
  if (prefixPath == null)
594
610
  continue;
595
- for (const { method, path, handler } of routers) {
596
- let normalizePath = [
597
- this._globalPrefix,
598
- prefixPath,
599
- path
600
- ].join('/')
601
- .replace(/\/+/g, '/')
602
- .replace(/\/+$/, '');
603
- normalizePath = normalizePath.startsWith('/') ? normalizePath : `/${normalizePath}`;
604
- this[method](normalizePath, this._wrapResponse(controllerInstance[String(handler)].bind(controllerInstance)));
611
+ for (const { method, path, handler } of Array.from(routers)) {
612
+ const find = Array.from(swaggers).find(s => s.handler === handler);
613
+ if (find != null) {
614
+ this._swaggerAdditional = [
615
+ ...this._swaggerAdditional,
616
+ Object.assign(Object.assign({}, find), { path: this._normalizePath(this._globalPrefix, prefixPath, path), method })
617
+ ];
618
+ }
619
+ this[method](this._normalizePath(this._globalPrefix, prefixPath, path), this._wrapResponse(controllerInstance[String(handler)].bind(controllerInstance)));
605
620
  }
606
621
  }
607
622
  });
@@ -626,28 +641,6 @@ class Application {
626
641
  return;
627
642
  });
628
643
  }
629
- _cookiesParser(req) {
630
- var _a;
631
- const cookies = {};
632
- const cookieString = (_a = req.headers) === null || _a === void 0 ? void 0 : _a.cookie;
633
- if (cookieString == null)
634
- return null;
635
- for (const cookie of cookieString.split(';')) {
636
- const [name, value] = cookie.split('=').map(v => v.trim());
637
- cookies[name] = decodeURIComponent(value);
638
- }
639
- for (const name of Object.keys(cookies)) {
640
- const cookie = cookies[name];
641
- if (!cookie.startsWith('Expires='))
642
- continue;
643
- const expiresString = cookie.replace('Expires=', '');
644
- const expiresDate = new Date(expiresString);
645
- if (isNaN(expiresDate.getTime()) || expiresDate < new Date()) {
646
- delete cookies[name];
647
- }
648
- }
649
- return cookies;
650
- }
651
644
  _customizeResponse(req, res) {
652
645
  const response = res;
653
646
  response.json = (results) => {
@@ -675,6 +668,10 @@ class Application {
675
668
  res.writeHead(res.statusCode, { 'Content-Type': 'text/plain' });
676
669
  return res.end(results);
677
670
  };
671
+ response.html = (results) => {
672
+ res.writeHead(res.statusCode, { 'Content-Type': 'text/html' });
673
+ return res.end(results);
674
+ };
678
675
  response.error = (err) => {
679
676
  var _a, _b, _c, _d, _e, _f, _g, _h;
680
677
  let code = +((_b = (_a = err.response) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.code) ||
@@ -805,6 +802,7 @@ class Application {
805
802
  return response;
806
803
  }
807
804
  _nextFunction(ctx) {
805
+ const NEXT_MESSAGE = "The 'next' function does not have any subsequent function.";
808
806
  return (err) => __awaiter(this, void 0, void 0, function* () {
809
807
  if (err != null) {
810
808
  if (this._errorHandler != null) {
@@ -821,24 +819,22 @@ class Application {
821
819
  }, null, 2));
822
820
  }
823
821
  if (this._errorHandler != null) {
824
- return this._errorHandler(new Error(`The 'next' function does not have any subsequent function.`), ctx);
822
+ return this._errorHandler(new Error(NEXT_MESSAGE), ctx);
825
823
  }
826
824
  ctx.res.writeHead(500, { 'Content-Type': 'application/json' });
827
825
  if (this._formatResponse != null) {
828
826
  return ctx.res.end(JSON.stringify(this._formatResponse({
829
- message: `The 'next' function does not have any subsequent function.`
827
+ message: NEXT_MESSAGE
830
828
  }, ctx.res.statusCode), null, 2));
831
829
  }
832
830
  return ctx.res.end(JSON.stringify({
833
- message: `The 'next' function does not have any subsequent function.`
831
+ message: NEXT_MESSAGE
834
832
  }, null, 2));
835
833
  });
836
834
  }
837
835
  _wrapResponse(handler) {
838
836
  return (ctx, next) => __awaiter(this, void 0, void 0, function* () {
839
837
  const result = yield handler(ctx, next);
840
- if (result == null)
841
- return;
842
838
  if (result instanceof http_1.ServerResponse)
843
839
  return;
844
840
  if (typeof result === 'string') {
@@ -858,11 +854,17 @@ class Application {
858
854
  if (!ctx.res.headersSent) {
859
855
  ctx.res.writeHead(200, { 'Content-Type': 'application/json' });
860
856
  }
857
+ if (Array.isArray(result)) {
858
+ return ctx.res.end(JSON.stringify(this._formatResponse(result)));
859
+ }
861
860
  return ctx.res.end(JSON.stringify(this._formatResponse(Object.assign({}, result), ctx.res.statusCode), null, 2));
862
861
  }
863
862
  if (!ctx.res.headersSent) {
864
863
  ctx.res.writeHead(200, { 'Content-Type': 'application/json' });
865
864
  }
865
+ if (Array.isArray(result)) {
866
+ return ctx.res.end(JSON.stringify(result));
867
+ }
866
868
  return ctx.res.end(JSON.stringify(Object.assign({}, result), null, 2));
867
869
  });
868
870
  }
@@ -873,9 +875,35 @@ class Application {
873
875
  const server = http_1.default.createServer((req, res) => {
874
876
  return this._router.lookup(req, res);
875
877
  });
878
+ server.keepAliveTimeout = 1000 * 120;
879
+ server.requestTimeout = 1000 * 120;
876
880
  return server;
877
881
  });
878
882
  }
883
+ _normalizePath(...paths) {
884
+ const path = paths
885
+ .join('/')
886
+ .replace(/\/+/g, '/')
887
+ .replace(/\/+$/, '');
888
+ const normalizedPath = path.startsWith('/') ? path : `/${path}`;
889
+ return /\/api\/api/.test(normalizedPath) ? normalizedPath.replace(/\/api\/api\//, "/api/") : normalizedPath;
890
+ }
891
+ _swaggerHandler() {
892
+ const routes = this.routers
893
+ .routes.filter(r => ["GET", "POST", "PUT", "PATCH", "DELETE"].includes(r.method));
894
+ const { path, html, staticSwaggerHandler, staticUrl } = this._parser.swagger(Object.assign(Object.assign({}, this._swagger), { options: this._swaggerAdditional, routes }));
895
+ this._router.get(staticUrl, staticSwaggerHandler);
896
+ this._router.get(String(path), (req, res) => {
897
+ res.writeHead(200, { 'Content-Type': 'text/html' });
898
+ res.write(html);
899
+ return res.end();
900
+ });
901
+ return;
902
+ }
903
+ }
904
+ exports.Spear = Spear;
905
+ class Application extends Spear {
879
906
  }
880
907
  exports.Application = Application;
881
- exports.default = Application;
908
+ exports.default = Spear;
909
+ //# sourceMappingURL=index.js.map