tspace-spear 1.2.1-beta.1 → 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.
- package/README.md +41 -11
- package/dist/lib/core/decorators/context.js +86 -0
- package/dist/lib/core/decorators/context.js.map +1 -1
- package/dist/lib/core/decorators/controller.js +33 -0
- package/dist/lib/core/decorators/controller.js.map +1 -1
- package/dist/lib/core/decorators/headers.js +29 -0
- package/dist/lib/core/decorators/headers.js.map +1 -1
- package/dist/lib/core/decorators/methods.js +78 -1
- package/dist/lib/core/decorators/methods.js.map +1 -1
- package/dist/lib/core/decorators/middleware.js +37 -0
- package/dist/lib/core/decorators/middleware.js.map +1 -1
- package/dist/lib/core/decorators/statusCode.js +24 -0
- package/dist/lib/core/decorators/statusCode.js.map +1 -1
- package/dist/lib/core/decorators/swagger.js +35 -1
- package/dist/lib/core/decorators/swagger.js.map +1 -1
- package/dist/lib/core/server/index.js +331 -347
- package/dist/lib/core/server/index.js.map +1 -1
- package/dist/lib/core/server/router.js +34 -0
- package/dist/lib/core/server/router.js.map +1 -1
- package/dist/tests/benchmark.test.js +2 -11
- package/dist/tests/benchmark.test.js.map +1 -1
- package/package.json +6 -7
|
@@ -79,7 +79,7 @@ class Spear {
|
|
|
79
79
|
_wss;
|
|
80
80
|
_ws;
|
|
81
81
|
_wsOptions;
|
|
82
|
-
|
|
82
|
+
_swaggerSpecs = [];
|
|
83
83
|
_errorHandler = null;
|
|
84
84
|
_globalMiddlewares = [];
|
|
85
85
|
_formatResponse = null;
|
|
@@ -97,7 +97,6 @@ class Spear {
|
|
|
97
97
|
this.useLogger();
|
|
98
98
|
if (cluster)
|
|
99
99
|
this.useCluster(cluster);
|
|
100
|
-
this._cluster = cluster;
|
|
101
100
|
this._controllers = controllers;
|
|
102
101
|
this._middlewares = middlewares;
|
|
103
102
|
this._globalPrefix = globalPrefix == null ? '' : globalPrefix;
|
|
@@ -260,11 +259,11 @@ class Spear {
|
|
|
260
259
|
}
|
|
261
260
|
this._globalMiddlewares.push((ctx, next) => {
|
|
262
261
|
const { req } = ctx;
|
|
263
|
-
const contentType = req?.headers['content-type'];
|
|
264
|
-
const isFileUpload = contentType && contentType.startsWith('multipart/form-data');
|
|
265
262
|
if (req.method === 'GET') {
|
|
266
263
|
return next();
|
|
267
264
|
}
|
|
265
|
+
const contentType = req?.headers['content-type'];
|
|
266
|
+
const isFileUpload = contentType && contentType.startsWith('multipart/form-data');
|
|
268
267
|
if (!isFileUpload)
|
|
269
268
|
return next();
|
|
270
269
|
if (req?.files != null)
|
|
@@ -379,7 +378,7 @@ class Spear {
|
|
|
379
378
|
const origin = req.headers?.origin ?? null;
|
|
380
379
|
if (origin == null)
|
|
381
380
|
return;
|
|
382
|
-
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');
|
|
383
382
|
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
384
383
|
if (Array.isArray(origins) && origins.length) {
|
|
385
384
|
for (const o of origins) {
|
|
@@ -404,27 +403,6 @@ class Spear {
|
|
|
404
403
|
});
|
|
405
404
|
return this;
|
|
406
405
|
}
|
|
407
|
-
/**
|
|
408
|
-
* The 'enableCors' is used to enable the cors origins on the server.
|
|
409
|
-
*
|
|
410
|
-
* @params {Object}
|
|
411
|
-
* @property {(string | RegExp)[]} origins
|
|
412
|
-
* @property {boolean} credentials
|
|
413
|
-
* @returns
|
|
414
|
-
*/
|
|
415
|
-
enableCors({ origins, credentials } = {}) {
|
|
416
|
-
return this.cors({ origins, credentials });
|
|
417
|
-
}
|
|
418
|
-
/**
|
|
419
|
-
* The 'formaT.Response' method is used to format the response
|
|
420
|
-
*
|
|
421
|
-
* @param {function} format
|
|
422
|
-
* @returns
|
|
423
|
-
*/
|
|
424
|
-
formatResponse(format) {
|
|
425
|
-
this._formatResponse = format;
|
|
426
|
-
return this;
|
|
427
|
-
}
|
|
428
406
|
/**
|
|
429
407
|
* The 'response' method is used to format the response
|
|
430
408
|
*
|
|
@@ -435,18 +413,6 @@ class Spear {
|
|
|
435
413
|
this._formatResponse = format;
|
|
436
414
|
return this;
|
|
437
415
|
}
|
|
438
|
-
/**
|
|
439
|
-
* The 'errorHandler' method is middleware that is specifically designed to handle errors.
|
|
440
|
-
*
|
|
441
|
-
* that occur during the processing of requests
|
|
442
|
-
*
|
|
443
|
-
* @param {function} error
|
|
444
|
-
* @returns
|
|
445
|
-
*/
|
|
446
|
-
errorHandler(error) {
|
|
447
|
-
this._errorHandler = error;
|
|
448
|
-
return this;
|
|
449
|
-
}
|
|
450
416
|
/**
|
|
451
417
|
* The 'catch' method is middleware that is specifically designed to handle errors.
|
|
452
418
|
*
|
|
@@ -459,34 +425,10 @@ class Spear {
|
|
|
459
425
|
this._errorHandler = error;
|
|
460
426
|
return this;
|
|
461
427
|
}
|
|
462
|
-
/**
|
|
463
|
-
* The 'notFoundHandler' method is middleware that is specifically designed to handle errors notfound that occur during the processing of requests
|
|
464
|
-
*
|
|
465
|
-
* @param {function} notfound
|
|
466
|
-
* @returns
|
|
467
|
-
*/
|
|
468
|
-
notFoundHandler(fn) {
|
|
469
|
-
const handler = ({ req, res }) => {
|
|
470
|
-
return fn({
|
|
471
|
-
req,
|
|
472
|
-
res: this._customizeResponse(req, res),
|
|
473
|
-
headers: {},
|
|
474
|
-
query: {},
|
|
475
|
-
files: {},
|
|
476
|
-
body: {},
|
|
477
|
-
params: {},
|
|
478
|
-
cookies: {}
|
|
479
|
-
});
|
|
480
|
-
};
|
|
481
|
-
this._onListeners.push(() => {
|
|
482
|
-
return this.all('*', ...this._globalMiddlewares, handler);
|
|
483
|
-
});
|
|
484
|
-
return this;
|
|
485
|
-
}
|
|
486
428
|
/**
|
|
487
429
|
* The 'notfound' method is middleware that is specifically designed to handle errors notfound that occur during the processing of requests
|
|
488
430
|
*
|
|
489
|
-
* @param {function}
|
|
431
|
+
* @param {function} fn
|
|
490
432
|
* @returns
|
|
491
433
|
*/
|
|
492
434
|
notfound(fn) {
|
|
@@ -583,7 +525,7 @@ class Spear {
|
|
|
583
525
|
return this;
|
|
584
526
|
}
|
|
585
527
|
/**
|
|
586
|
-
* The '
|
|
528
|
+
* The 'head' method is used to add the request handler to the router for 'HEAD' methods.
|
|
587
529
|
*
|
|
588
530
|
* @param {string} path
|
|
589
531
|
* @callback {...Function[]} handlers of the middlewares
|
|
@@ -591,14 +533,29 @@ class Spear {
|
|
|
591
533
|
* @property {function} next - go to next function
|
|
592
534
|
* @returns {this}
|
|
593
535
|
*/
|
|
594
|
-
|
|
536
|
+
head(path, ...handlers) {
|
|
595
537
|
this._onListeners.push(() => {
|
|
596
|
-
return this._router.
|
|
538
|
+
return this._router.head(this._normalizePath(this._globalPrefix, path), this._wrapHandlers(...this._globalMiddlewares, ...handlers));
|
|
539
|
+
});
|
|
540
|
+
return this;
|
|
541
|
+
}
|
|
542
|
+
/**
|
|
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));
|
|
597
554
|
});
|
|
598
555
|
return this;
|
|
599
556
|
}
|
|
600
557
|
/**
|
|
601
|
-
* The 'any' method is used to add the request handler to the router for 'GET' 'POST' 'PUT' 'PATCH' 'DELETE' methods.
|
|
558
|
+
* The 'any' method is used to add the request handler to the router for 'GET' 'POST' 'PUT' 'PATCH' 'DELETE' 'HEAD' 'OPTIONS' methods.
|
|
602
559
|
*
|
|
603
560
|
* @param {string} path
|
|
604
561
|
* @callback {...Function[]} handlers of the middlewares
|
|
@@ -612,6 +569,21 @@ class Spear {
|
|
|
612
569
|
});
|
|
613
570
|
return this;
|
|
614
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
|
+
}
|
|
615
587
|
_clusterMode({ server, port, hostname, callback }) {
|
|
616
588
|
if (cluster_1.default.isPrimary) {
|
|
617
589
|
const numCPUs = os_1.default.cpus().length;
|
|
@@ -688,8 +660,8 @@ class Spear {
|
|
|
688
660
|
for (const { method, path, handler } of Array.from(routers)) {
|
|
689
661
|
const find = Array.from(swaggers).find(s => s.handler === handler);
|
|
690
662
|
if (find != null) {
|
|
691
|
-
this.
|
|
692
|
-
...this.
|
|
663
|
+
this._swaggerSpecs = [
|
|
664
|
+
...this._swaggerSpecs,
|
|
693
665
|
{
|
|
694
666
|
...find,
|
|
695
667
|
path: this._normalizePath(this._globalPrefix, prefixPath, path),
|
|
@@ -712,8 +684,8 @@ class Spear {
|
|
|
712
684
|
for (const { method, path, handler } of Array.from(routers)) {
|
|
713
685
|
const find = Array.from(swaggers).find(s => s.handler === handler);
|
|
714
686
|
if (find != null) {
|
|
715
|
-
this.
|
|
716
|
-
...this.
|
|
687
|
+
this._swaggerSpecs = [
|
|
688
|
+
...this._swaggerSpecs,
|
|
717
689
|
{
|
|
718
690
|
...find,
|
|
719
691
|
path: this._normalizePath(this._globalPrefix, prefixPath, path),
|
|
@@ -743,7 +715,7 @@ class Spear {
|
|
|
743
715
|
}
|
|
744
716
|
const middleware = maybeMiddleware;
|
|
745
717
|
if (typeof middleware !== "function") {
|
|
746
|
-
console.log(`\x1b[31m[
|
|
718
|
+
console.log(`\x1b[31m[MiddlewareLoader ERROR]\x1b[0m \x1b[36m${file}\x1b[0m must export a middleware`);
|
|
747
719
|
continue;
|
|
748
720
|
}
|
|
749
721
|
this.use(middleware);
|
|
@@ -758,296 +730,276 @@ class Spear {
|
|
|
758
730
|
}
|
|
759
731
|
_customizeResponse(req, res) {
|
|
760
732
|
const response = res;
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
// return res.end(results)
|
|
768
|
-
// }
|
|
769
|
-
// if(!res.headersSent) {
|
|
770
|
-
// res.writeHead(200, { 'Content-Type': 'application/json' })
|
|
771
|
-
// }
|
|
772
|
-
// if(results == null) {
|
|
773
|
-
// if(this._formatResponse != null) {
|
|
774
|
-
// return res.end(JSON.stringify(this._formatResponse(null, res.statusCode),null,2))
|
|
775
|
-
// }
|
|
776
|
-
// return res.end()
|
|
777
|
-
// }
|
|
778
|
-
// if(this._formatResponse != null) {
|
|
779
|
-
// return res.end(JSON.stringify(
|
|
780
|
-
// this._formatResponse({
|
|
781
|
-
// ...results
|
|
782
|
-
// }, res.statusCode) ,null, 2)
|
|
783
|
-
// )
|
|
784
|
-
// }
|
|
785
|
-
// return res.end(JSON.stringify({
|
|
786
|
-
// ...results,
|
|
787
|
-
// },null,2))
|
|
788
|
-
// }
|
|
789
|
-
// response.send = (results : string) => {
|
|
790
|
-
// if (res.writableEnded) return;
|
|
791
|
-
// res.writeHead(res.statusCode, { 'Content-Type': 'text/plain' })
|
|
792
|
-
// return res.end(results)
|
|
793
|
-
// }
|
|
794
|
-
// response.html = (results : string) => {
|
|
795
|
-
// if (res.writableEnded) return;
|
|
796
|
-
// res.writeHead(res.statusCode, {'Content-Type': 'text/html'})
|
|
797
|
-
// return res.end(results)
|
|
798
|
-
// }
|
|
799
|
-
// response.error = (err ) => {
|
|
800
|
-
// let code =
|
|
801
|
-
// +err.response?.data?.code ||
|
|
802
|
-
// +err.code ||
|
|
803
|
-
// +err.status ||
|
|
804
|
-
// +err.statusCode ||
|
|
805
|
-
// +err.response?.data?.statusCode ||
|
|
806
|
-
// 500;
|
|
807
|
-
// code = (code == null || typeof code !== 'number') ? 500 : Number.isNaN(code) ? 500 : code < 400 ? 500 : code
|
|
808
|
-
// const message =
|
|
809
|
-
// err.response?.data?.errorMessage ||
|
|
810
|
-
// err.response?.data?.message ||
|
|
811
|
-
// err.message ||
|
|
812
|
-
// `The url '${req.url}' resulted in a server error. Please investigate.`
|
|
813
|
-
// ;
|
|
814
|
-
// response.status(code as any)
|
|
815
|
-
// if(this._formatResponse != null) {
|
|
816
|
-
// return res.end(JSON.stringify(this._formatResponse({ message }, code) ,null,2))
|
|
817
|
-
// }
|
|
818
|
-
// return res.end(JSON.stringify({
|
|
819
|
-
// message : message
|
|
820
|
-
// },null,2))
|
|
821
|
-
// }
|
|
822
|
-
// response.ok = (results ?: Record<string,any> ) => {
|
|
823
|
-
// return response.json(results == null ? {} : results)
|
|
824
|
-
// }
|
|
825
|
-
// response.created = (results ?: Record<string,any>) => {
|
|
826
|
-
// response.status(201)
|
|
827
|
-
// return response.json(results == null ? {} : results)
|
|
828
|
-
// }
|
|
829
|
-
// response.accepted = (results ?: Record<string,any>) => {
|
|
830
|
-
// response.status(202)
|
|
831
|
-
// return response.json(results == null ? {} : results)
|
|
832
|
-
// }
|
|
833
|
-
// response.noContent = () => {
|
|
834
|
-
// response.status(204)
|
|
835
|
-
// return res.end()
|
|
836
|
-
// }
|
|
837
|
-
// response.badRequest = (message ?: string) => {
|
|
838
|
-
// if (res.writableEnded) return;
|
|
839
|
-
// response.status(400)
|
|
840
|
-
// message = message ?? `The url '${req.url}' resulted in a bad request. Please review the data and try again.`
|
|
841
|
-
// if(this._formatResponse != null) {
|
|
842
|
-
// return res.end(JSON.stringify(this._formatResponse({ message }, 400) ,null,2))
|
|
843
|
-
// }
|
|
844
|
-
// return res.end(JSON.stringify({
|
|
845
|
-
// message : message
|
|
846
|
-
// },null,2))
|
|
847
|
-
// }
|
|
848
|
-
// response.unauthorized = (message ?: string) => {
|
|
849
|
-
// response.status(401)
|
|
850
|
-
// message = message ?? `The url '${req.url}' is unauthorized. Please verify.`
|
|
851
|
-
// if(this._formatResponse != null) {
|
|
852
|
-
// return res.end(JSON.stringify(this._formatResponse({ message }, 401) ,null,2))
|
|
853
|
-
// }
|
|
854
|
-
// return res.end(JSON.stringify({
|
|
855
|
-
// message
|
|
856
|
-
// },null,2))
|
|
857
|
-
// }
|
|
858
|
-
// response.paymentRequired = (message ?: string) => {
|
|
859
|
-
// response.status(402)
|
|
860
|
-
// message = message ?? `The url '${req.url}' requires payment. Please proceed with payment.`
|
|
861
|
-
// if(this._formatResponse != null) {
|
|
862
|
-
// return res.end(JSON.stringify(this._formatResponse({ message }, 402) ,null,2))
|
|
863
|
-
// }
|
|
864
|
-
// return res.end(JSON.stringify({
|
|
865
|
-
// message
|
|
866
|
-
// },null,2))
|
|
867
|
-
// }
|
|
868
|
-
// response.forbidden = (message ?: string) => {
|
|
869
|
-
// response.status(403)
|
|
870
|
-
// message = message ?? `The url '${req.url}' is forbidden. Please check the permissions or access rights.`
|
|
871
|
-
// if(this._formatResponse != null) {
|
|
872
|
-
// return res.end(JSON.stringify(this._formatResponse({ message }, 403) ,null,2))
|
|
873
|
-
// }
|
|
874
|
-
// return res.end(JSON.stringify({
|
|
875
|
-
// message
|
|
876
|
-
// },null,2))
|
|
877
|
-
// }
|
|
878
|
-
// response.notFound = (message ?: string) => {
|
|
879
|
-
// response.status(404)
|
|
880
|
-
// message = message ?? `The url '${req.url}' was not found. Please re-check the your url again.`
|
|
881
|
-
// if(this._formatResponse != null) {
|
|
882
|
-
// return res.end(JSON.stringify(this._formatResponse({ message }, 404) ,null,2))
|
|
883
|
-
// }
|
|
884
|
-
// return res.end(JSON.stringify({
|
|
885
|
-
// message
|
|
886
|
-
// },null,2))
|
|
887
|
-
// }
|
|
888
|
-
// response.tooManyRequests = (message ?: string) => {
|
|
889
|
-
// response.status(429)
|
|
890
|
-
// message = message ?? `The url '${req.url}' is too many request. Please wait and try agian.`
|
|
891
|
-
// if(this._formatResponse != null) {
|
|
892
|
-
// return res.end(JSON.stringify(this._formatResponse({ message }, 404) ,null,2))
|
|
893
|
-
// }
|
|
894
|
-
// return res.end(JSON.stringify({
|
|
895
|
-
// message
|
|
896
|
-
// },null,2))
|
|
897
|
-
// }
|
|
898
|
-
// response.serverError = (message ?: string) => {
|
|
899
|
-
// response.status(500)
|
|
900
|
-
// message = message ?? `The url '${req.url}' resulted in a server error. Please investigate.`
|
|
901
|
-
// if(this._formatResponse != null) {
|
|
902
|
-
// return res.end(JSON.stringify(this._formatResponse({ message }, 500) ,null,2))
|
|
903
|
-
// }
|
|
904
|
-
// return res.end(JSON.stringify({
|
|
905
|
-
// message
|
|
906
|
-
// },null,2))
|
|
907
|
-
// }
|
|
908
|
-
// response.status = (code : number) => {
|
|
909
|
-
// res.writeHead(code, { 'Content-Type': 'application/json' })
|
|
910
|
-
// return res as unknown as {
|
|
911
|
-
// json : (data?: { [key: string]: any }) => void;
|
|
912
|
-
// send : (message : string) => void;
|
|
913
|
-
// }
|
|
914
|
-
// }
|
|
915
|
-
// response.setCookies = (cookies : Record<string,string | {
|
|
916
|
-
// value : string
|
|
917
|
-
// sameSite ?: 'Strict' | 'Lax' | 'None'
|
|
918
|
-
// domain ?: string
|
|
919
|
-
// secure ?: boolean
|
|
920
|
-
// httpOnly ?: boolean
|
|
921
|
-
// expires ?: Date
|
|
922
|
-
// }> ) => {
|
|
923
|
-
// for(const [key,v] of Object.entries(cookies)) {
|
|
924
|
-
// if(typeof v === 'string') {
|
|
925
|
-
// res.setHeader('Set-Cookie', `${key}=${v}`);
|
|
926
|
-
// continue
|
|
927
|
-
// }
|
|
928
|
-
// if(v.value === '' || v.value == null) continue
|
|
929
|
-
// let str = `${key}=${v.value}`
|
|
930
|
-
// if(v.sameSite != null) {
|
|
931
|
-
// str += ` ;SameSite=${v.sameSite}`
|
|
932
|
-
// }
|
|
933
|
-
// if(v.domain != null) {
|
|
934
|
-
// str += ` ;Domain=${v.domain}`
|
|
935
|
-
// }
|
|
936
|
-
// if(v.httpOnly != null) {
|
|
937
|
-
// str += ` ;HttpOnly`
|
|
938
|
-
// }
|
|
939
|
-
// if(v.secure != null) {
|
|
940
|
-
// str += ` ;Secure`
|
|
941
|
-
// }
|
|
942
|
-
// if(v.expires != null) {
|
|
943
|
-
// str += ` ;Expires=${v.expires.toUTCString()}`
|
|
944
|
-
// }
|
|
945
|
-
// res.setHeader('Set-Cookie', str);
|
|
946
|
-
// }
|
|
947
|
-
// }
|
|
948
|
-
return response;
|
|
949
|
-
}
|
|
950
|
-
_nextFunction(ctx) {
|
|
951
|
-
const NEXT_MESSAGE = "The 'next' function does not have any subsequent function.";
|
|
952
|
-
return (err) => {
|
|
953
|
-
if (err != null) {
|
|
954
|
-
if (this._errorHandler != null) {
|
|
955
|
-
return this._errorHandler(err, ctx);
|
|
733
|
+
response.json = (results) => {
|
|
734
|
+
if (res.writableEnded)
|
|
735
|
+
return;
|
|
736
|
+
if (typeof results === 'string') {
|
|
737
|
+
if (!res.headersSent) {
|
|
738
|
+
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
956
739
|
}
|
|
957
|
-
|
|
740
|
+
return res.end(results);
|
|
741
|
+
}
|
|
742
|
+
if (!res.headersSent) {
|
|
743
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
744
|
+
}
|
|
745
|
+
if (results == null) {
|
|
958
746
|
if (this._formatResponse != null) {
|
|
959
|
-
return
|
|
960
|
-
message: err?.message,
|
|
961
|
-
}, ctx.res.statusCode), null, 2));
|
|
747
|
+
return res.end(JSON.stringify(this._formatResponse(null, res.statusCode), null, 2));
|
|
962
748
|
}
|
|
963
|
-
return
|
|
964
|
-
message: err?.message,
|
|
965
|
-
}, null, 2));
|
|
749
|
+
return res.end();
|
|
966
750
|
}
|
|
967
|
-
if (this.
|
|
968
|
-
return this.
|
|
751
|
+
if (this._formatResponse != null) {
|
|
752
|
+
return res.end(JSON.stringify(this._formatResponse({
|
|
753
|
+
...results
|
|
754
|
+
}, res.statusCode), null, 2));
|
|
969
755
|
}
|
|
970
|
-
|
|
756
|
+
return res.end(JSON.stringify({
|
|
757
|
+
...results,
|
|
758
|
+
}, null, 2));
|
|
759
|
+
};
|
|
760
|
+
response.send = (results) => {
|
|
761
|
+
if (res.writableEnded)
|
|
762
|
+
return;
|
|
763
|
+
res.writeHead(res.statusCode, { 'Content-Type': 'text/plain' });
|
|
764
|
+
return res.end(results);
|
|
765
|
+
};
|
|
766
|
+
response.html = (results) => {
|
|
767
|
+
if (res.writableEnded)
|
|
768
|
+
return;
|
|
769
|
+
res.writeHead(res.statusCode, { 'Content-Type': 'text/html' });
|
|
770
|
+
return res.end(results);
|
|
771
|
+
};
|
|
772
|
+
response.error = (err) => {
|
|
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.`;
|
|
787
|
+
response.status(code);
|
|
788
|
+
const payload = { message };
|
|
789
|
+
if (this._formatResponse) {
|
|
790
|
+
return res.end(JSON.stringify(this._formatResponse(payload, code)));
|
|
791
|
+
}
|
|
792
|
+
return res.end(JSON.stringify(payload));
|
|
793
|
+
};
|
|
794
|
+
response.ok = (results) => {
|
|
795
|
+
return response.json(results == null ? {} : results);
|
|
796
|
+
};
|
|
797
|
+
response.created = (results) => {
|
|
798
|
+
response.status(201);
|
|
799
|
+
return response.json(results == null ? {} : results);
|
|
800
|
+
};
|
|
801
|
+
response.accepted = (results) => {
|
|
802
|
+
response.status(202);
|
|
803
|
+
return response.json(results == null ? {} : results);
|
|
804
|
+
};
|
|
805
|
+
response.noContent = () => {
|
|
806
|
+
response.status(204);
|
|
807
|
+
return res.end();
|
|
808
|
+
};
|
|
809
|
+
response.badRequest = (message) => {
|
|
810
|
+
if (res.writableEnded)
|
|
811
|
+
return;
|
|
812
|
+
response.status(400);
|
|
813
|
+
message = message ?? `The request '${req.url}' resulted in a bad request. Please review the data and try again.`;
|
|
971
814
|
if (this._formatResponse != null) {
|
|
972
|
-
return
|
|
973
|
-
message: NEXT_MESSAGE
|
|
974
|
-
}, ctx.res.statusCode), null, 2));
|
|
815
|
+
return res.end(JSON.stringify(this._formatResponse({ message }, 400), null, 2));
|
|
975
816
|
}
|
|
976
|
-
return
|
|
977
|
-
message:
|
|
817
|
+
return res.end(JSON.stringify({
|
|
818
|
+
message: message
|
|
819
|
+
}, null, 2));
|
|
820
|
+
};
|
|
821
|
+
response.unauthorized = (message) => {
|
|
822
|
+
response.status(401);
|
|
823
|
+
message = message ?? `The request '${req.url}' is unauthorized. Please verify.`;
|
|
824
|
+
if (this._formatResponse != null) {
|
|
825
|
+
return res.end(JSON.stringify(this._formatResponse({ message }, 401), null, 2));
|
|
826
|
+
}
|
|
827
|
+
return res.end(JSON.stringify({
|
|
828
|
+
message
|
|
829
|
+
}, null, 2));
|
|
830
|
+
};
|
|
831
|
+
response.paymentRequired = (message) => {
|
|
832
|
+
response.status(402);
|
|
833
|
+
message = message ?? `The request '${req.url}' requires payment. Please proceed with payment.`;
|
|
834
|
+
if (this._formatResponse != null) {
|
|
835
|
+
return res.end(JSON.stringify(this._formatResponse({ message }, 402), null, 2));
|
|
836
|
+
}
|
|
837
|
+
return res.end(JSON.stringify({
|
|
838
|
+
message
|
|
839
|
+
}, null, 2));
|
|
840
|
+
};
|
|
841
|
+
response.forbidden = (message) => {
|
|
842
|
+
response.status(403);
|
|
843
|
+
message = message ?? `The request '${req.url}' is forbidden. Please check the permissions or access rights.`;
|
|
844
|
+
if (this._formatResponse != null) {
|
|
845
|
+
return res.end(JSON.stringify(this._formatResponse({ message }, 403), null, 2));
|
|
846
|
+
}
|
|
847
|
+
return res.end(JSON.stringify({
|
|
848
|
+
message
|
|
849
|
+
}, null, 2));
|
|
850
|
+
};
|
|
851
|
+
response.notFound = (message) => {
|
|
852
|
+
response.status(404);
|
|
853
|
+
message = message ?? `The request '${req.url}' was not found. Please re-check the your url again.`;
|
|
854
|
+
if (this._formatResponse != null) {
|
|
855
|
+
return res.end(JSON.stringify(this._formatResponse({ message }, 404), null, 2));
|
|
856
|
+
}
|
|
857
|
+
return res.end(JSON.stringify({
|
|
858
|
+
message
|
|
978
859
|
}, null, 2));
|
|
979
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
|
+
};
|
|
871
|
+
response.tooManyRequests = (message) => {
|
|
872
|
+
response.status(429);
|
|
873
|
+
message = message ?? `The request '${req.url}' is too many request. Please wait and try agian.`;
|
|
874
|
+
if (this._formatResponse != null) {
|
|
875
|
+
return res.end(JSON.stringify(this._formatResponse({ message }, 429), null, 2));
|
|
876
|
+
}
|
|
877
|
+
return res.end(JSON.stringify({
|
|
878
|
+
message
|
|
879
|
+
}, null, 2));
|
|
880
|
+
};
|
|
881
|
+
response.serverError = (message) => {
|
|
882
|
+
response.status(500);
|
|
883
|
+
message = message ?? `The request '${req.url}' resulted in a server error. Please investigate.`;
|
|
884
|
+
if (this._formatResponse != null) {
|
|
885
|
+
return res.end(JSON.stringify(this._formatResponse({ message }, 500), null, 2));
|
|
886
|
+
}
|
|
887
|
+
return res.end(JSON.stringify({
|
|
888
|
+
message
|
|
889
|
+
}, null, 2));
|
|
890
|
+
};
|
|
891
|
+
response.status = (code) => {
|
|
892
|
+
res.writeHead(code, { 'Content-Type': 'application/json' });
|
|
893
|
+
return res;
|
|
894
|
+
};
|
|
895
|
+
response.setCookies = (cookies) => {
|
|
896
|
+
for (const [key, v] of Object.entries(cookies)) {
|
|
897
|
+
if (typeof v === 'string') {
|
|
898
|
+
res.setHeader('Set-Cookie', `${key}=${v}`);
|
|
899
|
+
continue;
|
|
900
|
+
}
|
|
901
|
+
if (v.value === '' || v.value == null)
|
|
902
|
+
continue;
|
|
903
|
+
let str = `${key}=${v.value}`;
|
|
904
|
+
if (v.sameSite != null) {
|
|
905
|
+
str += ` ;SameSite=${v.sameSite}`;
|
|
906
|
+
}
|
|
907
|
+
if (v.domain != null) {
|
|
908
|
+
str += ` ;Domain=${v.domain}`;
|
|
909
|
+
}
|
|
910
|
+
if (v.httpOnly != null) {
|
|
911
|
+
str += ` ;HttpOnly`;
|
|
912
|
+
}
|
|
913
|
+
if (v.secure != null) {
|
|
914
|
+
str += ` ;Secure`;
|
|
915
|
+
}
|
|
916
|
+
if (v.expires != null) {
|
|
917
|
+
str += ` ;Expires=${v.expires.toUTCString()}`;
|
|
918
|
+
}
|
|
919
|
+
res.setHeader('Set-Cookie', str);
|
|
920
|
+
}
|
|
921
|
+
};
|
|
922
|
+
return response;
|
|
980
923
|
}
|
|
981
|
-
_wrapHandlers
|
|
924
|
+
_wrapHandlers(...handlers) {
|
|
982
925
|
return (req, res, params) => {
|
|
983
926
|
const nextHandler = (index = 0) => {
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
.
|
|
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);
|
|
1011
972
|
}
|
|
1012
|
-
return handlers[index](ctx, () => {
|
|
1013
|
-
return nextHandler(index + 1);
|
|
1014
|
-
});
|
|
1015
973
|
};
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
return nextHandler();
|
|
1020
|
-
}
|
|
1021
|
-
catch (err) {
|
|
1022
|
-
const ctx = {
|
|
1023
|
-
req,
|
|
1024
|
-
res: this._customizeResponse(req, res),
|
|
1025
|
-
params: Object.keys(params).length ? params : {},
|
|
1026
|
-
headers: {},
|
|
1027
|
-
query: {},
|
|
1028
|
-
body: {},
|
|
1029
|
-
files: {},
|
|
1030
|
-
cookies: {}
|
|
1031
|
-
};
|
|
1032
|
-
return this._nextFunction(ctx)(err);
|
|
1033
|
-
}
|
|
974
|
+
if (res.writableEnded)
|
|
975
|
+
return;
|
|
976
|
+
return nextHandler();
|
|
1034
977
|
};
|
|
1035
|
-
}
|
|
978
|
+
}
|
|
1036
979
|
_wrapResponse(handler) {
|
|
1037
980
|
return (ctx, next) => {
|
|
1038
981
|
Promise.resolve(handler(ctx, next))
|
|
1039
982
|
.then(result => {
|
|
1040
983
|
if (ctx.res.writableEnded)
|
|
1041
984
|
return;
|
|
1042
|
-
if (result instanceof http_1.ServerResponse)
|
|
985
|
+
if (result instanceof http_1.ServerResponse) {
|
|
986
|
+
if (result?.end) {
|
|
987
|
+
result.end();
|
|
988
|
+
}
|
|
1043
989
|
return;
|
|
1044
|
-
|
|
990
|
+
}
|
|
991
|
+
if (result == null) {
|
|
992
|
+
if (!ctx.res.headersSent) {
|
|
993
|
+
ctx.res.writeHead(204, { 'Content-Type': 'text/plain' });
|
|
994
|
+
}
|
|
995
|
+
ctx.res.end();
|
|
1045
996
|
return;
|
|
997
|
+
}
|
|
1046
998
|
if (typeof result === 'string') {
|
|
1047
999
|
if (!ctx.res.headersSent) {
|
|
1048
1000
|
ctx.res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
1049
1001
|
}
|
|
1050
|
-
ctx.res.end(result);
|
|
1002
|
+
ctx.res.end(result ?? '');
|
|
1051
1003
|
return;
|
|
1052
1004
|
}
|
|
1053
1005
|
if (this._formatResponse != null) {
|
|
@@ -1062,26 +1014,58 @@ class Spear {
|
|
|
1062
1014
|
if (!ctx.res.headersSent) {
|
|
1063
1015
|
ctx.res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1064
1016
|
}
|
|
1065
|
-
ctx.res.end(JSON.stringify(formattedResult
|
|
1017
|
+
ctx.res.end(JSON.stringify(formattedResult));
|
|
1066
1018
|
return;
|
|
1067
1019
|
}
|
|
1068
1020
|
if (!ctx.res.headersSent) {
|
|
1069
1021
|
ctx.res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1070
1022
|
}
|
|
1071
|
-
ctx.res.end(
|
|
1023
|
+
ctx.res.end(JSON.stringify(result));
|
|
1072
1024
|
return;
|
|
1073
1025
|
})
|
|
1074
1026
|
.catch(err => {
|
|
1075
|
-
|
|
1076
|
-
return;
|
|
1077
|
-
if (!ctx.res.headersSent) {
|
|
1078
|
-
ctx.res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
1079
|
-
}
|
|
1080
|
-
ctx.res.end(JSON.stringify({ message: err?.message || 'Internal Server Error' }));
|
|
1081
|
-
return;
|
|
1027
|
+
return this._nextFunction(ctx)(err);
|
|
1082
1028
|
});
|
|
1083
1029
|
};
|
|
1084
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
|
+
}
|
|
1085
1069
|
async _createServer() {
|
|
1086
1070
|
await this._registerMiddlewares();
|
|
1087
1071
|
await this._registerControllers();
|
|
@@ -1090,10 +1074,10 @@ class Spear {
|
|
|
1090
1074
|
const server = http_1.default.createServer({ maxHeaderSize: 1024 * 1024 }, cors
|
|
1091
1075
|
? (req, res) => {
|
|
1092
1076
|
cors(req, res);
|
|
1093
|
-
lookup(req, res);
|
|
1077
|
+
return lookup(req, res);
|
|
1094
1078
|
}
|
|
1095
1079
|
: (req, res) => {
|
|
1096
|
-
lookup(req, res);
|
|
1080
|
+
return lookup(req, res);
|
|
1097
1081
|
});
|
|
1098
1082
|
if (this._ws) {
|
|
1099
1083
|
this._wss = new ws_1.default.Server({ server, ...this._wsOptions });
|
|
@@ -1133,7 +1117,7 @@ class Spear {
|
|
|
1133
1117
|
.routes.filter(r => ["GET", "POST", "PUT", "PATCH", "DELETE"].includes(r.method));
|
|
1134
1118
|
const { path, html, staticSwaggerHandler, staticUrl } = this._parser.swagger({
|
|
1135
1119
|
...this._swagger,
|
|
1136
|
-
specs: this.
|
|
1120
|
+
specs: this._swaggerSpecs,
|
|
1137
1121
|
routes
|
|
1138
1122
|
});
|
|
1139
1123
|
this._router.get(staticUrl, staticSwaggerHandler);
|