tspace-spear 1.2.0 → 1.2.1

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 (62) hide show
  1. package/README.md +777 -705
  2. package/{build → dist}/lib/core/decorators/context.js +86 -0
  3. package/dist/lib/core/decorators/context.js.map +1 -0
  4. package/dist/lib/core/decorators/controller.js +43 -0
  5. package/dist/lib/core/decorators/controller.js.map +1 -0
  6. package/dist/lib/core/decorators/headers.js +44 -0
  7. package/dist/lib/core/decorators/headers.js.map +1 -0
  8. package/dist/lib/core/decorators/methods.js +102 -0
  9. package/dist/lib/core/decorators/methods.js.map +1 -0
  10. package/dist/lib/core/decorators/middleware.js +61 -0
  11. package/dist/lib/core/decorators/middleware.js.map +1 -0
  12. package/dist/lib/core/decorators/statusCode.js +40 -0
  13. package/dist/lib/core/decorators/statusCode.js.map +1 -0
  14. package/dist/lib/core/decorators/swagger.js +52 -0
  15. package/dist/lib/core/decorators/swagger.js.map +1 -0
  16. package/{build → dist}/lib/core/server/index.js +272 -205
  17. package/dist/lib/core/server/index.js.map +1 -0
  18. package/{build → dist}/lib/core/server/parser-factory.js +29 -7
  19. package/dist/lib/core/server/parser-factory.js.map +1 -0
  20. package/dist/lib/core/server/radix-router.js +65 -0
  21. package/dist/lib/core/server/radix-router.js.map +1 -0
  22. package/{build → dist}/lib/core/server/router.js +34 -0
  23. package/dist/lib/core/server/router.js.map +1 -0
  24. package/{build → dist}/lib/index.js +0 -1
  25. package/{build → dist}/lib/index.js.map +1 -1
  26. package/{build → dist}/tests/benchmark.test.js +38 -8
  27. package/{build → dist}/tests/benchmark.test.js.map +1 -1
  28. package/package.json +15 -10
  29. package/build/lib/core/decorators/context.d.ts +0 -5
  30. package/build/lib/core/decorators/context.js.map +0 -1
  31. package/build/lib/core/decorators/controller.d.ts +0 -1
  32. package/build/lib/core/decorators/controller.js +0 -10
  33. package/build/lib/core/decorators/controller.js.map +0 -1
  34. package/build/lib/core/decorators/headers.d.ts +0 -3
  35. package/build/lib/core/decorators/headers.js +0 -15
  36. package/build/lib/core/decorators/headers.js.map +0 -1
  37. package/build/lib/core/decorators/index.d.ts +0 -9
  38. package/build/lib/core/decorators/methods.d.ts +0 -5
  39. package/build/lib/core/decorators/methods.js +0 -25
  40. package/build/lib/core/decorators/methods.js.map +0 -1
  41. package/build/lib/core/decorators/middleware.d.ts +0 -2
  42. package/build/lib/core/decorators/middleware.js +0 -24
  43. package/build/lib/core/decorators/middleware.js.map +0 -1
  44. package/build/lib/core/decorators/statusCode.d.ts +0 -1
  45. package/build/lib/core/decorators/statusCode.js +0 -16
  46. package/build/lib/core/decorators/statusCode.js.map +0 -1
  47. package/build/lib/core/decorators/swagger.d.ts +0 -2
  48. package/build/lib/core/decorators/swagger.js +0 -18
  49. package/build/lib/core/decorators/swagger.js.map +0 -1
  50. package/build/lib/core/server/index.d.ts +0 -312
  51. package/build/lib/core/server/index.js.map +0 -1
  52. package/build/lib/core/server/parser-factory.d.ts +0 -24
  53. package/build/lib/core/server/parser-factory.js.map +0 -1
  54. package/build/lib/core/server/router.d.ts +0 -79
  55. package/build/lib/core/server/router.js.map +0 -1
  56. package/build/lib/core/types/index.d.ts +0 -168
  57. package/build/lib/index.d.ts +0 -11
  58. package/build/tests/benchmark.test.d.ts +0 -1
  59. /package/{build → dist}/lib/core/decorators/index.js +0 -0
  60. /package/{build → dist}/lib/core/decorators/index.js.map +0 -0
  61. /package/{build → dist}/lib/core/types/index.js +0 -0
  62. /package/{build → dist}/lib/core/types/index.js.map +0 -0
@@ -33,6 +33,7 @@ const fs_1 = __importDefault(require("fs"));
33
33
  const path_1 = __importDefault(require("path"));
34
34
  const url_1 = require("url");
35
35
  const on_finished_1 = __importDefault(require("on-finished"));
36
+ const ws_1 = __importDefault(require("ws"));
36
37
  const http_1 = __importStar(require("http"));
37
38
  const find_my_way_1 = __importDefault(require("find-my-way"));
38
39
  const parser_factory_1 = require("./parser-factory");
@@ -75,7 +76,10 @@ class Spear {
75
76
  version: "1.0.0"
76
77
  }
77
78
  };
78
- _swaggerAdditional = [];
79
+ _wss;
80
+ _ws;
81
+ _wsOptions;
82
+ _swaggerSpecs = [];
79
83
  _errorHandler = null;
80
84
  _globalMiddlewares = [];
81
85
  _formatResponse = null;
@@ -91,7 +95,8 @@ class Spear {
91
95
  constructor({ controllers, middlewares, globalPrefix, logger, cluster } = {}) {
92
96
  if (logger)
93
97
  this.useLogger();
94
- this._cluster = cluster;
98
+ if (cluster)
99
+ this.useCluster(cluster);
95
100
  this._controllers = controllers;
96
101
  this._middlewares = middlewares;
97
102
  this._globalPrefix = globalPrefix == null ? '' : globalPrefix;
@@ -112,6 +117,18 @@ class Spear {
112
117
  get routers() {
113
118
  return this._router;
114
119
  }
120
+ /**
121
+ * The 'ws' method is used to creates the WebSocket server.
122
+ *
123
+ * @callback {Function} WebSocketServer
124
+ * @param {WebSocketServer} wss - WebSocketServer
125
+ * @returns {this}
126
+ */
127
+ ws(handlers, options) {
128
+ this._ws = handlers();
129
+ this._wsOptions = options ?? {};
130
+ return this;
131
+ }
115
132
  /**
116
133
  * The 'use' method is used to add the middleware into the request pipeline.
117
134
  *
@@ -131,7 +148,9 @@ class Spear {
131
148
  * @returns {this}
132
149
  */
133
150
  useCluster(cluster) {
134
- this._cluster = cluster == null ? true : cluster;
151
+ if (cluster === false)
152
+ return this;
153
+ this._cluster = cluster ?? true;
135
154
  return this;
136
155
  }
137
156
  /**
@@ -193,7 +212,8 @@ class Spear {
193
212
  const { req } = ctx;
194
213
  const contentType = req?.headers['content-type'] ?? '';
195
214
  const isFileUpload = contentType && contentType.startsWith('multipart/form-data');
196
- const isCanParserBody = contentType.includes('application/json') || contentType.includes('application/x-www-form-urlencoded');
215
+ const isCanParserBody = contentType.includes('application/json') ||
216
+ contentType.includes('application/x-www-form-urlencoded');
197
217
  if (except != null &&
198
218
  Array.isArray(except) &&
199
219
  except.some(v => v.toLocaleLowerCase() === (req.method)?.toLocaleLowerCase())) {
@@ -239,11 +259,11 @@ class Spear {
239
259
  }
240
260
  this._globalMiddlewares.push((ctx, next) => {
241
261
  const { req } = ctx;
242
- const contentType = req?.headers['content-type'];
243
- const isFileUpload = contentType && contentType.startsWith('multipart/form-data');
244
262
  if (req.method === 'GET') {
245
263
  return next();
246
264
  }
265
+ const contentType = req?.headers['content-type'];
266
+ const isFileUpload = contentType && contentType.startsWith('multipart/form-data');
247
267
  if (!isFileUpload)
248
268
  return next();
249
269
  if (req?.files != null)
@@ -292,16 +312,14 @@ class Spear {
292
312
  /**
293
313
  * The 'useSwagger' method is a middleware used to create swagger api.
294
314
  *
295
- * @param {?Object}
296
- * @property {?string} path
297
- * @property {?array} servers
298
- * @property {?object} info
299
- * @property {?array} tags
315
+ * @param {?Object} doc
300
316
  * @returns
301
317
  */
302
- useSwagger({ path, servers, info, tags } = {}) {
318
+ useSwagger(doc = {}) {
319
+ const { path, servers, tags, info, options } = doc;
303
320
  this._swagger = {
304
321
  use: true,
322
+ options: options,
305
323
  path: path ?? this._swagger.path,
306
324
  servers: servers ?? this._swagger.servers,
307
325
  tags: tags ?? this._swagger.tags,
@@ -360,7 +378,7 @@ class Spear {
360
378
  const origin = req.headers?.origin ?? null;
361
379
  if (origin == null)
362
380
  return;
363
- res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE, OPTIONS');
381
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS');
364
382
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
365
383
  if (Array.isArray(origins) && origins.length) {
366
384
  for (const o of origins) {
@@ -385,27 +403,6 @@ class Spear {
385
403
  });
386
404
  return this;
387
405
  }
388
- /**
389
- * The 'enableCors' is used to enable the cors origins on the server.
390
- *
391
- * @params {Object}
392
- * @property {(string | RegExp)[]} origins
393
- * @property {boolean} credentials
394
- * @returns
395
- */
396
- enableCors({ origins, credentials } = {}) {
397
- return this.cors({ origins, credentials });
398
- }
399
- /**
400
- * The 'formatResponse' method is used to format the response
401
- *
402
- * @param {function} format
403
- * @returns
404
- */
405
- formatResponse(format) {
406
- this._formatResponse = format;
407
- return this;
408
- }
409
406
  /**
410
407
  * The 'response' method is used to format the response
411
408
  *
@@ -416,18 +413,6 @@ class Spear {
416
413
  this._formatResponse = format;
417
414
  return this;
418
415
  }
419
- /**
420
- * The 'errorHandler' method is middleware that is specifically designed to handle errors.
421
- *
422
- * that occur during the processing of requests
423
- *
424
- * @param {function} error
425
- * @returns
426
- */
427
- errorHandler(error) {
428
- this._errorHandler = error;
429
- return this;
430
- }
431
416
  /**
432
417
  * The 'catch' method is middleware that is specifically designed to handle errors.
433
418
  *
@@ -440,34 +425,10 @@ class Spear {
440
425
  this._errorHandler = error;
441
426
  return this;
442
427
  }
443
- /**
444
- * The 'notFoundHandler' method is middleware that is specifically designed to handle errors notfound that occur during the processing of requests
445
- *
446
- * @param {function} notfound
447
- * @returns
448
- */
449
- notFoundHandler(fn) {
450
- const handler = ({ req, res }) => {
451
- return fn({
452
- req,
453
- res: this._customizeResponse(req, res),
454
- headers: {},
455
- query: {},
456
- files: {},
457
- body: {},
458
- params: {},
459
- cookies: {}
460
- });
461
- };
462
- this._onListeners.push(() => {
463
- return this.all('*', ...this._globalMiddlewares, handler);
464
- });
465
- return this;
466
- }
467
428
  /**
468
429
  * The 'notfound' method is middleware that is specifically designed to handle errors notfound that occur during the processing of requests
469
430
  *
470
- * @param {function} notfound
431
+ * @param {function} fn
471
432
  * @returns
472
433
  */
473
434
  notfound(fn) {
@@ -564,7 +525,7 @@ class Spear {
564
525
  return this;
565
526
  }
566
527
  /**
567
- * The 'all' method is used to add the request handler to the router for 'GET' 'POST' 'PUT' 'PATCH' 'DELETE' methods.
528
+ * The 'head' method is used to add the request handler to the router for 'HEAD' methods.
568
529
  *
569
530
  * @param {string} path
570
531
  * @callback {...Function[]} handlers of the middlewares
@@ -572,14 +533,29 @@ class Spear {
572
533
  * @property {function} next - go to next function
573
534
  * @returns {this}
574
535
  */
575
- all(path, ...handlers) {
536
+ head(path, ...handlers) {
576
537
  this._onListeners.push(() => {
577
- return this._router.all(this._normalizePath(this._globalPrefix, path), this._wrapHandlers(...this._globalMiddlewares, ...handlers));
538
+ return this._router.head(this._normalizePath(this._globalPrefix, path), this._wrapHandlers(...this._globalMiddlewares, ...handlers));
578
539
  });
579
540
  return this;
580
541
  }
581
542
  /**
582
- * The 'any' method is used to add the request handler to the router for 'GET' 'POST' 'PUT' 'PATCH' 'DELETE' methods.
543
+ * The 'head' method is used to add the request handler to the router for 'HEAD' methods.
544
+ *
545
+ * @param {string} path
546
+ * @callback {...Function[]} handlers of the middlewares
547
+ * @property {object} ctx - context { req , res , query , params , cookies , files , body}
548
+ * @property {function} next - go to next function
549
+ * @returns {this}
550
+ */
551
+ options(path, ...handlers) {
552
+ this._onListeners.push(() => {
553
+ return this._router.options(this._normalizePath(this._globalPrefix, path), this._wrapHandlers(...this._globalMiddlewares, ...handlers));
554
+ });
555
+ return this;
556
+ }
557
+ /**
558
+ * The 'any' method is used to add the request handler to the router for 'GET' 'POST' 'PUT' 'PATCH' 'DELETE' 'HEAD' 'OPTIONS' methods.
583
559
  *
584
560
  * @param {string} path
585
561
  * @callback {...Function[]} handlers of the middlewares
@@ -593,6 +569,21 @@ class Spear {
593
569
  });
594
570
  return this;
595
571
  }
572
+ /**
573
+ * The 'all' method is used to add the request handler to the router for 'GET' 'POST' 'PUT' 'PATCH' 'DELETE' 'HEAD' 'OPTIONS' methods.
574
+ *
575
+ * @param {string} path
576
+ * @callback {...Function[]} handlers of the middlewares
577
+ * @property {object} ctx - context { req , res , query , params , cookies , files , body}
578
+ * @property {function} next - go to next function
579
+ * @returns {this}
580
+ */
581
+ all(path, ...handlers) {
582
+ this._onListeners.push(() => {
583
+ return this._router.all(this._normalizePath(this._globalPrefix, path), this._wrapHandlers(...this._globalMiddlewares, ...handlers));
584
+ });
585
+ return this;
586
+ }
596
587
  _clusterMode({ server, port, hostname, callback }) {
597
588
  if (cluster_1.default.isPrimary) {
598
589
  const numCPUs = os_1.default.cpus().length;
@@ -645,8 +636,21 @@ class Spear {
645
636
  if (!Array.isArray(this._controllers)) {
646
637
  const controllers = await this._import(this._controllers.folder, this._controllers.name);
647
638
  for (const file of controllers) {
648
- const response = await Promise.resolve(`${file}`).then(s => __importStar(require(s)));
649
- const controller = response?.default;
639
+ const imported = await Promise.resolve(`${file}`).then(s => __importStar(require(s)));
640
+ let maybeController = imported?.default;
641
+ if (maybeController == null) {
642
+ const entry = Object
643
+ .entries(imported)
644
+ .find(([name]) => {
645
+ return /controller$/i.test(name);
646
+ });
647
+ maybeController = entry?.[1];
648
+ }
649
+ const controller = maybeController;
650
+ if (typeof controller !== "function") {
651
+ console.log(`\x1b[31m[ControllerLoader ERROR]\x1b[0m \x1b[36m${file}\x1b[0m must export a controller class`);
652
+ continue;
653
+ }
650
654
  const controllerInstance = new controller();
651
655
  const prefixPath = Reflect.getMetadata("controllers", controller) ?? '';
652
656
  const routers = Reflect.getMetadata("routers", controller) ?? [];
@@ -656,8 +660,8 @@ class Spear {
656
660
  for (const { method, path, handler } of Array.from(routers)) {
657
661
  const find = Array.from(swaggers).find(s => s.handler === handler);
658
662
  if (find != null) {
659
- this._swaggerAdditional = [
660
- ...this._swaggerAdditional,
663
+ this._swaggerSpecs = [
664
+ ...this._swaggerSpecs,
661
665
  {
662
666
  ...find,
663
667
  path: this._normalizePath(this._globalPrefix, prefixPath, path),
@@ -680,8 +684,8 @@ class Spear {
680
684
  for (const { method, path, handler } of Array.from(routers)) {
681
685
  const find = Array.from(swaggers).find(s => s.handler === handler);
682
686
  if (find != null) {
683
- this._swaggerAdditional = [
684
- ...this._swaggerAdditional,
687
+ this._swaggerSpecs = [
688
+ ...this._swaggerSpecs,
685
689
  {
686
690
  ...find,
687
691
  path: this._normalizePath(this._globalPrefix, prefixPath, path),
@@ -699,8 +703,21 @@ class Spear {
699
703
  if (!Array.isArray(this._middlewares)) {
700
704
  const middlewares = await this._import(this._middlewares.folder, this._middlewares.name);
701
705
  for (const file of middlewares) {
702
- const response = await Promise.resolve(`${file}`).then(s => __importStar(require(s)));
703
- const middleware = response?.default;
706
+ const imported = await Promise.resolve(`${file}`).then(s => __importStar(require(s)));
707
+ let maybeMiddleware = imported?.default;
708
+ if (maybeMiddleware == null) {
709
+ const entry = Object
710
+ .entries(imported)
711
+ .find(([name]) => {
712
+ return /middleware$/i.test(name);
713
+ });
714
+ maybeMiddleware = entry?.[1];
715
+ }
716
+ const middleware = maybeMiddleware;
717
+ if (typeof middleware !== "function") {
718
+ console.log(`\x1b[31m[MiddlewareLoader ERROR]\x1b[0m \x1b[36m${file}\x1b[0m must export a middleware`);
719
+ continue;
720
+ }
704
721
  this.use(middleware);
705
722
  }
706
723
  return;
@@ -753,24 +770,26 @@ class Spear {
753
770
  return res.end(results);
754
771
  };
755
772
  response.error = (err) => {
756
- let code = +err.response?.data?.code ||
757
- +err.code ||
758
- +err.status ||
759
- +err.statusCode ||
760
- +err.response?.data?.statusCode ||
761
- 500;
762
- code = (code == null || typeof code !== 'number') ? 500 : Number.isNaN(code) ? 500 : code < 400 ? 500 : code;
763
- const message = err.response?.data?.errorMessage ||
764
- err.response?.data?.message ||
765
- err.message ||
766
- `The url '${req.url}' resulted in a server error. Please investigate.`;
773
+ const statusCandidates = [
774
+ err?.response?.data?.code,
775
+ err?.code,
776
+ err?.status,
777
+ err?.statusCode,
778
+ err?.response?.data?.statusCode
779
+ ];
780
+ let code = statusCandidates
781
+ .map(v => Number(v))
782
+ .find(v => Number.isFinite(v) && v >= 400) ?? 500;
783
+ const message = err?.response?.data?.errorMessage ??
784
+ err?.response?.data?.message ??
785
+ err?.message ??
786
+ `The request '${req.url}' resulted in a server error.`;
767
787
  response.status(code);
768
- if (this._formatResponse != null) {
769
- return res.end(JSON.stringify(this._formatResponse({ message }, code), null, 2));
788
+ const payload = { message };
789
+ if (this._formatResponse) {
790
+ return res.end(JSON.stringify(this._formatResponse(payload, code)));
770
791
  }
771
- return res.end(JSON.stringify({
772
- message: message
773
- }, null, 2));
792
+ return res.end(JSON.stringify(payload));
774
793
  };
775
794
  response.ok = (results) => {
776
795
  return response.json(results == null ? {} : results);
@@ -791,7 +810,7 @@ class Spear {
791
810
  if (res.writableEnded)
792
811
  return;
793
812
  response.status(400);
794
- message = message ?? `The url '${req.url}' resulted in a bad request. Please review the data and try again.`;
813
+ message = message ?? `The request '${req.url}' resulted in a bad request. Please review the data and try again.`;
795
814
  if (this._formatResponse != null) {
796
815
  return res.end(JSON.stringify(this._formatResponse({ message }, 400), null, 2));
797
816
  }
@@ -801,7 +820,7 @@ class Spear {
801
820
  };
802
821
  response.unauthorized = (message) => {
803
822
  response.status(401);
804
- message = message ?? `The url '${req.url}' is unauthorized. Please verify.`;
823
+ message = message ?? `The request '${req.url}' is unauthorized. Please verify.`;
805
824
  if (this._formatResponse != null) {
806
825
  return res.end(JSON.stringify(this._formatResponse({ message }, 401), null, 2));
807
826
  }
@@ -811,7 +830,7 @@ class Spear {
811
830
  };
812
831
  response.paymentRequired = (message) => {
813
832
  response.status(402);
814
- message = message ?? `The url '${req.url}' requires payment. Please proceed with payment.`;
833
+ message = message ?? `The request '${req.url}' requires payment. Please proceed with payment.`;
815
834
  if (this._formatResponse != null) {
816
835
  return res.end(JSON.stringify(this._formatResponse({ message }, 402), null, 2));
817
836
  }
@@ -821,7 +840,7 @@ class Spear {
821
840
  };
822
841
  response.forbidden = (message) => {
823
842
  response.status(403);
824
- message = message ?? `The url '${req.url}' is forbidden. Please check the permissions or access rights.`;
843
+ message = message ?? `The request '${req.url}' is forbidden. Please check the permissions or access rights.`;
825
844
  if (this._formatResponse != null) {
826
845
  return res.end(JSON.stringify(this._formatResponse({ message }, 403), null, 2));
827
846
  }
@@ -831,7 +850,7 @@ class Spear {
831
850
  };
832
851
  response.notFound = (message) => {
833
852
  response.status(404);
834
- message = message ?? `The url '${req.url}' was not found. Please re-check the your url again.`;
853
+ message = message ?? `The request '${req.url}' was not found. Please re-check the your url again.`;
835
854
  if (this._formatResponse != null) {
836
855
  return res.end(JSON.stringify(this._formatResponse({ message }, 404), null, 2));
837
856
  }
@@ -839,11 +858,21 @@ class Spear {
839
858
  message
840
859
  }, null, 2));
841
860
  };
861
+ response.unprocessable = (message) => {
862
+ response.status(422);
863
+ message = message ?? `The request to '${req.url}' failed validation.`;
864
+ if (this._formatResponse != null) {
865
+ return res.end(JSON.stringify(this._formatResponse({ message }, 422), null, 2));
866
+ }
867
+ return res.end(JSON.stringify({
868
+ message
869
+ }, null, 2));
870
+ };
842
871
  response.tooManyRequests = (message) => {
843
872
  response.status(429);
844
- message = message ?? `The url '${req.url}' is too many request. Please wait and try agian.`;
873
+ message = message ?? `The request '${req.url}' is too many request. Please wait and try agian.`;
845
874
  if (this._formatResponse != null) {
846
- return res.end(JSON.stringify(this._formatResponse({ message }, 404), null, 2));
875
+ return res.end(JSON.stringify(this._formatResponse({ message }, 429), null, 2));
847
876
  }
848
877
  return res.end(JSON.stringify({
849
878
  message
@@ -851,7 +880,7 @@ class Spear {
851
880
  };
852
881
  response.serverError = (message) => {
853
882
  response.status(500);
854
- message = message ?? `The url '${req.url}' resulted in a server error. Please investigate.`;
883
+ message = message ?? `The request '${req.url}' resulted in a server error. Please investigate.`;
855
884
  if (this._formatResponse != null) {
856
885
  return res.end(JSON.stringify(this._formatResponse({ message }, 500), null, 2));
857
886
  }
@@ -892,106 +921,85 @@ class Spear {
892
921
  };
893
922
  return response;
894
923
  }
895
- _nextFunction(ctx) {
896
- const NEXT_MESSAGE = "The 'next' function does not have any subsequent function.";
897
- return (err) => {
898
- if (err != null) {
899
- if (this._errorHandler != null) {
900
- return this._errorHandler(err, ctx);
901
- }
902
- ctx.res.writeHead(500, { 'Content-Type': 'application/json' });
903
- if (this._formatResponse != null) {
904
- return ctx.res.end(JSON.stringify(this._formatResponse({
905
- message: err?.message,
906
- }, ctx.res.statusCode), null, 2));
907
- }
908
- return ctx.res.end(JSON.stringify({
909
- message: err?.message,
910
- }, null, 2));
911
- }
912
- if (this._errorHandler != null) {
913
- return this._errorHandler(new Error(NEXT_MESSAGE), ctx);
914
- }
915
- ctx.res.writeHead(500, { 'Content-Type': 'application/json' });
916
- if (this._formatResponse != null) {
917
- return ctx.res.end(JSON.stringify(this._formatResponse({
918
- message: NEXT_MESSAGE
919
- }, ctx.res.statusCode), null, 2));
920
- }
921
- return ctx.res.end(JSON.stringify({
922
- message: NEXT_MESSAGE
923
- }, null, 2));
924
- };
925
- }
926
- _wrapHandlers = (...handlers) => {
924
+ _wrapHandlers(...handlers) {
927
925
  return (req, res, params) => {
928
926
  const nextHandler = (index = 0) => {
929
- const response = this._customizeResponse(req, res);
930
- const request = req;
931
- request.params = params;
932
- const body = request.body;
933
- const files = request.files;
934
- const cookies = request.cookies;
935
- const headers = request.headers;
936
- const query = { ...(0, url_1.parse)(String(req.url), true).query };
937
- const RecordOrEmptyRecord = (data) => {
938
- if (data == null)
939
- return {};
940
- return Object.keys(data).length ? data : {};
941
- };
942
- const ctx = {
943
- req: request,
944
- res: response,
945
- headers: RecordOrEmptyRecord(headers),
946
- params: RecordOrEmptyRecord(params),
947
- query: RecordOrEmptyRecord(query),
948
- body: RecordOrEmptyRecord(body),
949
- files: RecordOrEmptyRecord(files),
950
- cookies: RecordOrEmptyRecord(cookies)
951
- };
952
- if (index === handlers.length - 1) {
953
- return this._wrapResponse(handlers[index]
954
- .bind(handlers[index]))(ctx, this._nextFunction(ctx));
927
+ try {
928
+ const response = this._customizeResponse(req, res);
929
+ const request = req;
930
+ request.params = params;
931
+ const body = request.body;
932
+ const files = request.files;
933
+ const cookies = request.cookies;
934
+ const headers = request.headers;
935
+ const url = new url_1.URL(req.url, "http://localhost");
936
+ const query = Object.fromEntries(url.searchParams);
937
+ const RecordOrEmptyRecord = (data) => {
938
+ if (data == null)
939
+ return {};
940
+ return Object.keys(data).length ? data : {};
941
+ };
942
+ const ctx = {
943
+ req: request,
944
+ res: response,
945
+ headers: RecordOrEmptyRecord(headers),
946
+ params: RecordOrEmptyRecord(params),
947
+ query: RecordOrEmptyRecord(query),
948
+ body: RecordOrEmptyRecord(body),
949
+ files: RecordOrEmptyRecord(files),
950
+ cookies: RecordOrEmptyRecord(cookies)
951
+ };
952
+ if (index === handlers.length - 1) {
953
+ return this._wrapResponse(handlers[index]
954
+ .bind(handlers[index]))(ctx, this._nextFunction(ctx));
955
+ }
956
+ return handlers[index](ctx, () => {
957
+ return nextHandler(index + 1);
958
+ });
959
+ }
960
+ catch (err) {
961
+ const ctx = {
962
+ req,
963
+ res: this._customizeResponse(req, res),
964
+ params: Object.keys(params).length ? params : {},
965
+ headers: {},
966
+ query: {},
967
+ body: {},
968
+ files: {},
969
+ cookies: {}
970
+ };
971
+ return this._nextFunction(ctx)(err);
955
972
  }
956
- return handlers[index](ctx, () => {
957
- return nextHandler(index + 1);
958
- });
959
973
  };
960
- try {
961
- if (res.writableEnded)
962
- return;
963
- return nextHandler();
964
- }
965
- catch (err) {
966
- const ctx = {
967
- req,
968
- res: this._customizeResponse(req, res),
969
- params: Object.keys(params).length ? params : {},
970
- headers: {},
971
- query: {},
972
- body: {},
973
- files: {},
974
- cookies: {}
975
- };
976
- return this._nextFunction(ctx)(err);
977
- }
974
+ if (res.writableEnded)
975
+ return;
976
+ return nextHandler();
978
977
  };
979
- };
978
+ }
980
979
  _wrapResponse(handler) {
981
980
  return (ctx, next) => {
982
981
  Promise.resolve(handler(ctx, next))
983
982
  .then(result => {
984
983
  if (ctx.res.writableEnded)
985
984
  return;
986
- if (result instanceof http_1.ServerResponse)
985
+ if (result instanceof http_1.ServerResponse) {
986
+ if (result?.end) {
987
+ result.end();
988
+ }
987
989
  return;
988
- if (result == null)
990
+ }
991
+ if (result == null) {
992
+ if (!ctx.res.headersSent) {
993
+ ctx.res.writeHead(204, { 'Content-Type': 'text/plain' });
994
+ }
995
+ ctx.res.end();
989
996
  return;
997
+ }
990
998
  if (typeof result === 'string') {
991
999
  if (!ctx.res.headersSent) {
992
1000
  ctx.res.writeHead(200, { 'Content-Type': 'text/plain' });
993
1001
  }
994
- ctx.res.end(result);
1002
+ ctx.res.end(result ?? '');
995
1003
  return;
996
1004
  }
997
1005
  if (this._formatResponse != null) {
@@ -1006,35 +1014,94 @@ class Spear {
1006
1014
  if (!ctx.res.headersSent) {
1007
1015
  ctx.res.writeHead(200, { 'Content-Type': 'application/json' });
1008
1016
  }
1009
- ctx.res.end(JSON.stringify(formattedResult, null, 2));
1017
+ ctx.res.end(JSON.stringify(formattedResult));
1010
1018
  return;
1011
1019
  }
1012
1020
  if (!ctx.res.headersSent) {
1013
1021
  ctx.res.writeHead(200, { 'Content-Type': 'application/json' });
1014
1022
  }
1015
- ctx.res.end(result == null ? undefined : JSON.stringify(result, null, 2));
1023
+ ctx.res.end(JSON.stringify(result));
1016
1024
  return;
1017
1025
  })
1018
1026
  .catch(err => {
1019
- if (ctx.res.writableEnded)
1020
- return;
1021
- if (!ctx.res.headersSent) {
1022
- ctx.res.writeHead(500, { 'Content-Type': 'application/json' });
1023
- }
1024
- ctx.res.end(JSON.stringify({ message: err?.message || 'Internal Server Error' }));
1025
- return;
1027
+ return this._nextFunction(ctx)(err);
1026
1028
  });
1027
1029
  };
1028
1030
  }
1031
+ _nextFunction(ctx) {
1032
+ const NEXT_MESSAGE = "The 'next' function does not have any subsequent function.";
1033
+ return (err) => {
1034
+ if (err != null) {
1035
+ if (this._errorHandler != null) {
1036
+ const callback = this._errorHandler(err, ctx);
1037
+ if (callback == null || !(callback instanceof http_1.ServerResponse)) {
1038
+ ctx.res.writeHead(500, { 'Content-Type': 'application/json' });
1039
+ return ctx.res.end(JSON.stringify({
1040
+ message: err?.message
1041
+ }, null, 2));
1042
+ }
1043
+ return callback;
1044
+ }
1045
+ ctx.res.writeHead(500, { 'Content-Type': 'application/json' });
1046
+ if (this._formatResponse != null) {
1047
+ return ctx.res.end(JSON.stringify(this._formatResponse({
1048
+ message: err?.message,
1049
+ }, ctx.res.statusCode), null, 2));
1050
+ }
1051
+ return ctx.res.end(JSON.stringify({
1052
+ message: err?.message
1053
+ }, null, 2));
1054
+ }
1055
+ if (this._errorHandler != null) {
1056
+ return this._errorHandler(new Error(NEXT_MESSAGE), ctx);
1057
+ }
1058
+ ctx.res.writeHead(500, { 'Content-Type': 'application/json' });
1059
+ if (this._formatResponse != null) {
1060
+ return ctx.res.end(JSON.stringify(this._formatResponse({
1061
+ message: NEXT_MESSAGE
1062
+ }, ctx.res.statusCode), null, 2));
1063
+ }
1064
+ return ctx.res.end(JSON.stringify({
1065
+ message: NEXT_MESSAGE
1066
+ }, null, 2));
1067
+ };
1068
+ }
1029
1069
  async _createServer() {
1030
1070
  await this._registerMiddlewares();
1031
1071
  await this._registerControllers();
1032
- const server = http_1.default.createServer({ maxHeaderSize: 1024 * 1024 }, (req, res) => {
1033
- if (this._cors != null) {
1034
- this._cors(req, res);
1072
+ const lookup = this._router.lookup.bind(this._router);
1073
+ const cors = this._cors;
1074
+ const server = http_1.default.createServer({ maxHeaderSize: 1024 * 1024 }, cors
1075
+ ? (req, res) => {
1076
+ cors(req, res);
1077
+ return lookup(req, res);
1035
1078
  }
1036
- return this._router.lookup(req, res);
1037
- });
1079
+ : (req, res) => {
1080
+ return lookup(req, res);
1081
+ });
1082
+ if (this._ws) {
1083
+ this._wss = new ws_1.default.Server({ server, ...this._wsOptions });
1084
+ this._wss.on('connection', (ws) => {
1085
+ if (this._ws?.connection) {
1086
+ this._ws.connection(ws);
1087
+ }
1088
+ ws.on('message', (data) => {
1089
+ if (this._ws?.message) {
1090
+ this._ws.message(ws, data);
1091
+ }
1092
+ });
1093
+ ws.on('close', (code, reason) => {
1094
+ if (this._ws?.close) {
1095
+ this._ws.close(ws, code, reason);
1096
+ }
1097
+ });
1098
+ ws.on('error', (err) => {
1099
+ if (this._ws?.error) {
1100
+ this._ws.error(ws, err);
1101
+ }
1102
+ });
1103
+ });
1104
+ }
1038
1105
  return server;
1039
1106
  }
1040
1107
  _normalizePath(...paths) {
@@ -1050,7 +1117,7 @@ class Spear {
1050
1117
  .routes.filter(r => ["GET", "POST", "PUT", "PATCH", "DELETE"].includes(r.method));
1051
1118
  const { path, html, staticSwaggerHandler, staticUrl } = this._parser.swagger({
1052
1119
  ...this._swagger,
1053
- options: this._swaggerAdditional,
1120
+ specs: this._swaggerSpecs,
1054
1121
  routes
1055
1122
  });
1056
1123
  this._router.get(staticUrl, staticSwaggerHandler);