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.
- package/README.md +777 -705
- package/{build → dist}/lib/core/decorators/context.js +86 -0
- package/dist/lib/core/decorators/context.js.map +1 -0
- package/dist/lib/core/decorators/controller.js +43 -0
- package/dist/lib/core/decorators/controller.js.map +1 -0
- package/dist/lib/core/decorators/headers.js +44 -0
- package/dist/lib/core/decorators/headers.js.map +1 -0
- package/dist/lib/core/decorators/methods.js +102 -0
- package/dist/lib/core/decorators/methods.js.map +1 -0
- package/dist/lib/core/decorators/middleware.js +61 -0
- package/dist/lib/core/decorators/middleware.js.map +1 -0
- package/dist/lib/core/decorators/statusCode.js +40 -0
- package/dist/lib/core/decorators/statusCode.js.map +1 -0
- package/dist/lib/core/decorators/swagger.js +52 -0
- package/dist/lib/core/decorators/swagger.js.map +1 -0
- package/{build → dist}/lib/core/server/index.js +272 -205
- package/dist/lib/core/server/index.js.map +1 -0
- package/{build → dist}/lib/core/server/parser-factory.js +29 -7
- package/dist/lib/core/server/parser-factory.js.map +1 -0
- package/dist/lib/core/server/radix-router.js +65 -0
- package/dist/lib/core/server/radix-router.js.map +1 -0
- package/{build → dist}/lib/core/server/router.js +34 -0
- package/dist/lib/core/server/router.js.map +1 -0
- package/{build → dist}/lib/index.js +0 -1
- package/{build → dist}/lib/index.js.map +1 -1
- package/{build → dist}/tests/benchmark.test.js +38 -8
- package/{build → dist}/tests/benchmark.test.js.map +1 -1
- package/package.json +15 -10
- package/build/lib/core/decorators/context.d.ts +0 -5
- package/build/lib/core/decorators/context.js.map +0 -1
- package/build/lib/core/decorators/controller.d.ts +0 -1
- package/build/lib/core/decorators/controller.js +0 -10
- package/build/lib/core/decorators/controller.js.map +0 -1
- package/build/lib/core/decorators/headers.d.ts +0 -3
- package/build/lib/core/decorators/headers.js +0 -15
- package/build/lib/core/decorators/headers.js.map +0 -1
- package/build/lib/core/decorators/index.d.ts +0 -9
- package/build/lib/core/decorators/methods.d.ts +0 -5
- package/build/lib/core/decorators/methods.js +0 -25
- package/build/lib/core/decorators/methods.js.map +0 -1
- package/build/lib/core/decorators/middleware.d.ts +0 -2
- package/build/lib/core/decorators/middleware.js +0 -24
- package/build/lib/core/decorators/middleware.js.map +0 -1
- package/build/lib/core/decorators/statusCode.d.ts +0 -1
- package/build/lib/core/decorators/statusCode.js +0 -16
- package/build/lib/core/decorators/statusCode.js.map +0 -1
- package/build/lib/core/decorators/swagger.d.ts +0 -2
- package/build/lib/core/decorators/swagger.js +0 -18
- package/build/lib/core/decorators/swagger.js.map +0 -1
- package/build/lib/core/server/index.d.ts +0 -312
- package/build/lib/core/server/index.js.map +0 -1
- package/build/lib/core/server/parser-factory.d.ts +0 -24
- package/build/lib/core/server/parser-factory.js.map +0 -1
- package/build/lib/core/server/router.d.ts +0 -79
- package/build/lib/core/server/router.js.map +0 -1
- package/build/lib/core/types/index.d.ts +0 -168
- package/build/lib/index.d.ts +0 -11
- package/build/tests/benchmark.test.d.ts +0 -1
- /package/{build → dist}/lib/core/decorators/index.js +0 -0
- /package/{build → dist}/lib/core/decorators/index.js.map +0 -0
- /package/{build → dist}/lib/core/types/index.js +0 -0
- /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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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') ||
|
|
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(
|
|
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}
|
|
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 '
|
|
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
|
-
|
|
536
|
+
head(path, ...handlers) {
|
|
576
537
|
this._onListeners.push(() => {
|
|
577
|
-
return this._router.
|
|
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 '
|
|
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
|
|
649
|
-
|
|
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.
|
|
660
|
-
...this.
|
|
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.
|
|
684
|
-
...this.
|
|
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
|
|
703
|
-
|
|
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
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
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
|
-
|
|
769
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 },
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
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
|
-
|
|
961
|
-
|
|
962
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
1023
|
+
ctx.res.end(JSON.stringify(result));
|
|
1016
1024
|
return;
|
|
1017
1025
|
})
|
|
1018
1026
|
.catch(err => {
|
|
1019
|
-
|
|
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
|
|
1033
|
-
|
|
1034
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1120
|
+
specs: this._swaggerSpecs,
|
|
1054
1121
|
routes
|
|
1055
1122
|
});
|
|
1056
1123
|
this._router.get(staticUrl, staticSwaggerHandler);
|