@catbee/utils 2.0.0-next.0 → 2.0.0
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/LICENSE +1 -1
- package/README.md +102 -52
- package/array/index.cjs +215 -74
- package/array/index.d.ts +345 -2
- package/array/index.mjs +201 -74
- package/async/index.cjs +116 -39
- package/async/index.d.ts +292 -2
- package/async/index.mjs +116 -40
- package/cache/index.cjs +2 -2
- package/cache/index.d.ts +156 -2
- package/cache/index.mjs +3 -3
- package/config/index.cjs +80 -66
- package/config/index.d.ts +65 -3
- package/config/index.mjs +77 -65
- package/context-store/index.cjs +2 -3
- package/context-store/index.d.ts +193 -2
- package/context-store/index.mjs +2 -3
- package/crypto/index.cjs +55 -5
- package/crypto/index.d.ts +225 -2
- package/crypto/index.mjs +52 -7
- package/date/index.cjs +676 -2
- package/date/index.d.ts +676 -2
- package/date/index.mjs +665 -3
- package/decorator/index.cjs +2172 -0
- package/{decorators/decorators.utils.d.ts → decorator/index.d.ts} +58 -54
- package/decorator/index.mjs +2131 -0
- package/{dir → directory}/index.cjs +5 -4
- package/{dir/dir.utils.d.ts → directory/index.d.ts} +24 -21
- package/{dir → directory}/index.mjs +5 -4
- package/env/index.cjs +100 -68
- package/env/index.d.ts +391 -2
- package/env/index.mjs +100 -68
- package/exception/index.cjs +1 -1
- package/exception/index.d.ts +233 -2
- package/exception/index.mjs +1 -1
- package/fs/index.cjs +71 -37
- package/fs/index.d.ts +206 -2
- package/fs/index.mjs +65 -35
- package/http-status-codes/index.cjs +1 -1
- package/http-status-codes/index.d.ts +268 -2
- package/http-status-codes/index.mjs +1 -1
- package/id/index.cjs +1 -1
- package/id/index.d.ts +38 -2
- package/id/index.mjs +1 -1
- package/index.cjs +13 -13
- package/index.d.ts +5 -5
- package/index.mjs +5 -5
- package/logger/index.cjs +13 -15
- package/logger/index.d.ts +190 -2
- package/logger/index.mjs +14 -16
- package/middleware/index.cjs +1 -1
- package/middleware/index.d.ts +104 -2
- package/middleware/index.mjs +1 -1
- package/object/index.cjs +379 -0
- package/{obj/obj.utils.d.ts → object/index.d.ts} +73 -33
- package/object/index.mjs +360 -0
- package/package.json +41 -23
- package/performance/index.cjs +4 -4
- package/performance/index.d.ts +139 -2
- package/performance/index.mjs +4 -4
- package/request/index.cjs +37 -25
- package/request/index.d.ts +242 -3
- package/request/index.mjs +37 -25
- package/response/index.cjs +1 -1
- package/response/index.d.ts +319 -3
- package/response/index.mjs +1 -1
- package/server/index.cjs +249 -146
- package/server/index.d.ts +866 -5
- package/server/index.mjs +248 -144
- package/stream/index.cjs +1 -1
- package/stream/index.d.ts +91 -2
- package/stream/index.mjs +1 -1
- package/string/index.cjs +34 -1
- package/string/index.d.ts +146 -2
- package/string/index.mjs +31 -2
- package/type/index.cjs +19 -2
- package/type/index.d.ts +144 -2
- package/type/index.mjs +17 -3
- package/types/index.cjs +1 -1
- package/types/index.d.ts +775 -5
- package/types/index.mjs +1 -1
- package/url/index.cjs +63 -5
- package/url/index.d.ts +200 -2
- package/url/index.mjs +59 -6
- package/{validate → validation}/index.cjs +91 -44
- package/{validate/validate.utils.d.ts → validation/index.d.ts} +33 -24
- package/{validate → validation}/index.mjs +87 -44
- package/array/array.utils.d.ts +0 -191
- package/async/async.utils.d.ts +0 -296
- package/cache/cache.utils.d.ts +0 -176
- package/config/config.d.ts +0 -57
- package/context-store/context-store.utils.d.ts +0 -212
- package/crypto/crypto.utils.d.ts +0 -183
- package/date/date.utils.d.ts +0 -190
- package/decorators/index.cjs +0 -913
- package/decorators/index.d.ts +0 -25
- package/decorators/index.mjs +0 -872
- package/dir/index.d.ts +0 -25
- package/env/env.utils.d.ts +0 -400
- package/exception/exception.utils.d.ts +0 -253
- package/fs/fs.utils.d.ts +0 -196
- package/http-status-codes/http-status-codes.d.ts +0 -289
- package/id/id.utils.d.ts +0 -59
- package/logger/logger.utils.d.ts +0 -210
- package/middleware/middleware.utils.d.ts +0 -123
- package/obj/index.cjs +0 -317
- package/obj/index.d.ts +0 -25
- package/obj/index.mjs +0 -301
- package/performance/performance.utils.d.ts +0 -159
- package/request/request.utils.d.ts +0 -109
- package/response/response.utils.d.ts +0 -186
- package/server/server.builder.d.ts +0 -531
- package/server/server.d.ts +0 -303
- package/stream/stream.utils.d.ts +0 -111
- package/string/string.utils.d.ts +0 -124
- package/type/type.utils.d.ts +0 -129
- package/types/api-response.d.ts +0 -175
- package/types/common.d.ts +0 -148
- package/types/config.d.ts +0 -88
- package/types/server.d.ts +0 -291
- package/url/url.utils.d.ts +0 -164
- package/validate/index.d.ts +0 -25
package/server/index.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* The MIT License
|
|
3
3
|
*
|
|
4
|
-
* Copyright (c)
|
|
4
|
+
* Copyright (c) 2026 Catbee Technologies. https://catbee.in/license
|
|
5
5
|
*
|
|
6
6
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
7
|
* of this software and associated documentation files (the "Software"), to deal
|
|
@@ -32,18 +32,17 @@ var middleware = require('@catbee/utils/middleware');
|
|
|
32
32
|
var env = require('@catbee/utils/env');
|
|
33
33
|
var logger = require('@catbee/utils/logger');
|
|
34
34
|
var exception = require('@catbee/utils/exception');
|
|
35
|
-
var fs$1 = require('fs');
|
|
36
35
|
var config = require('@catbee/utils/config');
|
|
37
|
-
var
|
|
36
|
+
var object = require('@catbee/utils/object');
|
|
38
37
|
var fs = require('@catbee/utils/fs');
|
|
39
|
-
var
|
|
38
|
+
var validation = require('@catbee/utils/validation');
|
|
40
39
|
var async = require('@catbee/utils/async');
|
|
40
|
+
var id = require('@catbee/utils/id');
|
|
41
41
|
|
|
42
42
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
43
43
|
|
|
44
44
|
var express__default = /*#__PURE__*/_interopDefault(express);
|
|
45
45
|
var https__default = /*#__PURE__*/_interopDefault(https);
|
|
46
|
-
var fs__default = /*#__PURE__*/_interopDefault(fs$1);
|
|
47
46
|
|
|
48
47
|
var __defProp = Object.defineProperty;
|
|
49
48
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
@@ -61,7 +60,7 @@ var ServerConfigBuilder = class {
|
|
|
61
60
|
* @throws {Error} If port is not an integer or is outside the valid range (1-65535)
|
|
62
61
|
*/
|
|
63
62
|
validatePort(port) {
|
|
64
|
-
if (!
|
|
63
|
+
if (!validation.isPort(port)) {
|
|
65
64
|
throw new Error(`Port must be a valid number between 1 and 65535, got: ${port}`);
|
|
66
65
|
}
|
|
67
66
|
}
|
|
@@ -506,7 +505,10 @@ var ServerConfigBuilder = class {
|
|
|
506
505
|
* ```
|
|
507
506
|
*/
|
|
508
507
|
withBodyParser(opts) {
|
|
509
|
-
this.config.bodyParser =
|
|
508
|
+
this.config.bodyParser = {
|
|
509
|
+
...this.config.bodyParser,
|
|
510
|
+
...opts
|
|
511
|
+
};
|
|
510
512
|
return this;
|
|
511
513
|
}
|
|
512
514
|
/**
|
|
@@ -609,7 +611,7 @@ var ServerConfigBuilder = class {
|
|
|
609
611
|
* ```
|
|
610
612
|
*/
|
|
611
613
|
withCustom(overrides) {
|
|
612
|
-
this.config =
|
|
614
|
+
this.config = object.deepObjMerge({}, this.config, overrides);
|
|
613
615
|
return this;
|
|
614
616
|
}
|
|
615
617
|
/**
|
|
@@ -649,7 +651,7 @@ var ServerConfigBuilder = class {
|
|
|
649
651
|
* ```
|
|
650
652
|
*/
|
|
651
653
|
build() {
|
|
652
|
-
const config$1 =
|
|
654
|
+
const config$1 = object.deepObjMerge({}, config.getCatbeeServerGlobalConfig(), this.config);
|
|
653
655
|
if (config$1.openApi?.enable && !config$1.openApi.filePath) {
|
|
654
656
|
throw new Error("OpenAPI is enabled but no filePath is specified");
|
|
655
657
|
}
|
|
@@ -659,8 +661,8 @@ var ServerConfigBuilder = class {
|
|
|
659
661
|
});
|
|
660
662
|
}
|
|
661
663
|
mergeConfig(key, value) {
|
|
662
|
-
const current =
|
|
663
|
-
this.config[key] =
|
|
664
|
+
const current = this.config[key] && typeof this.config[key] === "object" ? object.deepClone(this.config[key]) : {};
|
|
665
|
+
this.config[key] = object.deepObjMerge({}, current, value);
|
|
664
666
|
}
|
|
665
667
|
setEnabled(key, enable, overrides = {}) {
|
|
666
668
|
this.mergeConfig(key, {
|
|
@@ -670,9 +672,11 @@ var ServerConfigBuilder = class {
|
|
|
670
672
|
return this;
|
|
671
673
|
}
|
|
672
674
|
};
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
675
|
+
var getDependencyErrorMessage = /* @__PURE__ */ __name((packageName, context) => {
|
|
676
|
+
const ctxPart = context ? `for ${context}` : "";
|
|
677
|
+
const spacer = ctxPart ? ` ${ctxPart}` : "";
|
|
678
|
+
return `Missing required dependency${spacer}: ${packageName}. Please install it to proceed.`;
|
|
679
|
+
}, "getDependencyErrorMessage");
|
|
676
680
|
var DependencyErrors = {
|
|
677
681
|
express: getDependencyErrorMessage("express"),
|
|
678
682
|
helmet: getDependencyErrorMessage("helmet"),
|
|
@@ -737,12 +741,12 @@ var ExpressServer = class {
|
|
|
737
741
|
* - Request tracing
|
|
738
742
|
*/
|
|
739
743
|
constructor(config$1, hooks = {}) {
|
|
740
|
-
if (this.
|
|
744
|
+
if (this.hasBuildMarker(config$1)) {
|
|
741
745
|
this.config = config$1;
|
|
742
746
|
} else {
|
|
743
|
-
this.config =
|
|
747
|
+
this.config = object.deepObjMerge({}, config.getCatbeeServerGlobalConfig(), config$1);
|
|
744
748
|
}
|
|
745
|
-
if (!
|
|
749
|
+
if (!validation.isPort(this.config.port)) {
|
|
746
750
|
const msg = `Port must be a valid number between 1 and 65535, got: ${this.config.port}`;
|
|
747
751
|
logger.getLogger().error(msg);
|
|
748
752
|
throw new Error(msg);
|
|
@@ -842,7 +846,7 @@ var ExpressServer = class {
|
|
|
842
846
|
async runHook(hook, ...args) {
|
|
843
847
|
try {
|
|
844
848
|
const fn = this.hooks[hook];
|
|
845
|
-
if (fn) await fn
|
|
849
|
+
if (fn) await fn(...args);
|
|
846
850
|
} catch (err) {
|
|
847
851
|
logger.getLogger().error({
|
|
848
852
|
err,
|
|
@@ -882,6 +886,24 @@ var ExpressServer = class {
|
|
|
882
886
|
if (this.config.https) {
|
|
883
887
|
await this.validateHttpsFiles();
|
|
884
888
|
}
|
|
889
|
+
this.setupBasicMiddleware();
|
|
890
|
+
this.setupSecurityMiddleware();
|
|
891
|
+
this.setupGlobalHeaders();
|
|
892
|
+
this.setupTimeoutMiddleware();
|
|
893
|
+
this.setupResponseTimeMiddleware();
|
|
894
|
+
this.setupRateLimitingMiddleware();
|
|
895
|
+
this.setupRequestLoggingMiddleware();
|
|
896
|
+
this.setupCompressionMiddleware();
|
|
897
|
+
this.setupStaticFilesMiddleware();
|
|
898
|
+
this.setupBodyParsingMiddleware();
|
|
899
|
+
this.setupCookieParsingMiddleware();
|
|
900
|
+
await this.setupOpenApiMiddleware();
|
|
901
|
+
this.setupMetricsMiddleware();
|
|
902
|
+
}
|
|
903
|
+
/**
|
|
904
|
+
* Set up basic middleware (trust proxy, request ID, context).
|
|
905
|
+
*/
|
|
906
|
+
setupBasicMiddleware() {
|
|
885
907
|
this.app.disable("x-powered-by");
|
|
886
908
|
if (this.config.trustProxy) {
|
|
887
909
|
this.app.set("trust proxy", true);
|
|
@@ -889,7 +911,7 @@ var ExpressServer = class {
|
|
|
889
911
|
this.app.use(middleware.requestId({
|
|
890
912
|
headerName: this.config.requestId?.headerName,
|
|
891
913
|
exposeHeader: this.config.requestId?.exposeHeader,
|
|
892
|
-
generator: this.config.requestId?.generator
|
|
914
|
+
generator: this.config.requestId?.generator || id.uuid
|
|
893
915
|
}));
|
|
894
916
|
this.app.use(middleware.setupRequestContext({
|
|
895
917
|
headerName: this.config.requestId?.headerName,
|
|
@@ -898,11 +920,15 @@ var ExpressServer = class {
|
|
|
898
920
|
this.app.use((_req, res, next) => {
|
|
899
921
|
if (this.isShuttingDown) {
|
|
900
922
|
res.setHeader("Connection", "close");
|
|
901
|
-
|
|
923
|
+
res.status(httpStatusCodes.HttpStatusCodes.SERVICE_UNAVAILABLE).json(new exception.ServiceUnavailableException("Server is shutting down"));
|
|
902
924
|
}
|
|
903
925
|
next();
|
|
904
|
-
return;
|
|
905
926
|
});
|
|
927
|
+
}
|
|
928
|
+
/**
|
|
929
|
+
* Set up security middleware (Helmet, CORS).
|
|
930
|
+
*/
|
|
931
|
+
setupSecurityMiddleware() {
|
|
906
932
|
if (this.config.helmet) {
|
|
907
933
|
const helmet = async.optionalRequire("helmet");
|
|
908
934
|
if (!helmet) {
|
|
@@ -921,6 +947,11 @@ var ExpressServer = class {
|
|
|
921
947
|
}
|
|
922
948
|
this.app.use(cors(this.config.cors === true ? {} : this.config.cors));
|
|
923
949
|
}
|
|
950
|
+
}
|
|
951
|
+
/**
|
|
952
|
+
* Set up global headers middleware.
|
|
953
|
+
*/
|
|
954
|
+
setupGlobalHeaders() {
|
|
924
955
|
this.app.use((_req, res, next) => {
|
|
925
956
|
if (this.config.globalHeaders) {
|
|
926
957
|
for (const key in this.config.globalHeaders) {
|
|
@@ -937,15 +968,30 @@ var ExpressServer = class {
|
|
|
937
968
|
}
|
|
938
969
|
next();
|
|
939
970
|
});
|
|
971
|
+
}
|
|
972
|
+
/**
|
|
973
|
+
* Set up request timeout middleware.
|
|
974
|
+
*/
|
|
975
|
+
setupTimeoutMiddleware() {
|
|
940
976
|
if (this.config.requestTimeout) {
|
|
941
977
|
this.app.use(middleware.timeout(this.config.requestTimeout));
|
|
942
978
|
}
|
|
979
|
+
}
|
|
980
|
+
/**
|
|
981
|
+
* Set up response time tracking middleware.
|
|
982
|
+
*/
|
|
983
|
+
setupResponseTimeMiddleware() {
|
|
943
984
|
if (this.config.responseTime?.enable) {
|
|
944
985
|
this.app.use(middleware.responseTime({
|
|
945
986
|
addHeader: this.config.responseTime.addHeader,
|
|
946
987
|
logOnComplete: this.config.responseTime.logOnComplete
|
|
947
988
|
}));
|
|
948
989
|
}
|
|
990
|
+
}
|
|
991
|
+
/**
|
|
992
|
+
* Set up rate limiting middleware.
|
|
993
|
+
*/
|
|
994
|
+
setupRateLimitingMiddleware() {
|
|
949
995
|
if (this.config.rateLimit?.enable) {
|
|
950
996
|
const rateLimit = async.optionalRequire("express-rate-limit");
|
|
951
997
|
if (!rateLimit) {
|
|
@@ -963,6 +1009,11 @@ var ExpressServer = class {
|
|
|
963
1009
|
legacyHeaders: this.config.rateLimit.legacyHeaders ?? false
|
|
964
1010
|
}));
|
|
965
1011
|
}
|
|
1012
|
+
}
|
|
1013
|
+
/**
|
|
1014
|
+
* Set up request logging middleware.
|
|
1015
|
+
*/
|
|
1016
|
+
setupRequestLoggingMiddleware() {
|
|
966
1017
|
if (this.config.requestLogging?.enable) {
|
|
967
1018
|
this.app.use((req, res, next) => {
|
|
968
1019
|
if (typeof this.config.requestLogging?.ignorePaths === "function") {
|
|
@@ -986,6 +1037,11 @@ var ExpressServer = class {
|
|
|
986
1037
|
if (this.hooks.onRequest) {
|
|
987
1038
|
this.app.use(this.hooks.onRequest);
|
|
988
1039
|
}
|
|
1040
|
+
}
|
|
1041
|
+
/**
|
|
1042
|
+
* Set up response compression middleware.
|
|
1043
|
+
*/
|
|
1044
|
+
setupCompressionMiddleware() {
|
|
989
1045
|
if (this.config.compression) {
|
|
990
1046
|
const compression = async.optionalRequire("compression");
|
|
991
1047
|
if (!compression) {
|
|
@@ -997,6 +1053,11 @@ var ExpressServer = class {
|
|
|
997
1053
|
this.app.use(compression());
|
|
998
1054
|
}
|
|
999
1055
|
}
|
|
1056
|
+
}
|
|
1057
|
+
/**
|
|
1058
|
+
* Set up static file serving middleware.
|
|
1059
|
+
*/
|
|
1060
|
+
setupStaticFilesMiddleware() {
|
|
1000
1061
|
if (this.config.staticFolders) {
|
|
1001
1062
|
this.config.staticFolders.forEach((folder) => {
|
|
1002
1063
|
this.app.use(this.normalizePath(folder.path ?? "/"), express__default.default.static(folder.directory, {
|
|
@@ -1009,6 +1070,11 @@ var ExpressServer = class {
|
|
|
1009
1070
|
logger.getLogger().info(`Serving static folder: ${folder.directory} at path ${folder.path || "/"}`);
|
|
1010
1071
|
});
|
|
1011
1072
|
}
|
|
1073
|
+
}
|
|
1074
|
+
/**
|
|
1075
|
+
* Set up body parsing middleware.
|
|
1076
|
+
*/
|
|
1077
|
+
setupBodyParsingMiddleware() {
|
|
1012
1078
|
if (this.config.bodyParser) {
|
|
1013
1079
|
if (this.config.bodyParser.json) {
|
|
1014
1080
|
this.app.use(express__default.default.json(this.config.bodyParser.json));
|
|
@@ -1017,6 +1083,11 @@ var ExpressServer = class {
|
|
|
1017
1083
|
this.app.use(express__default.default.urlencoded(this.config.bodyParser.urlencoded));
|
|
1018
1084
|
}
|
|
1019
1085
|
}
|
|
1086
|
+
}
|
|
1087
|
+
/**
|
|
1088
|
+
* Set up cookie parsing middleware.
|
|
1089
|
+
*/
|
|
1090
|
+
setupCookieParsingMiddleware() {
|
|
1020
1091
|
if (this.config.cookieParser) {
|
|
1021
1092
|
const cookieParser = async.optionalRequire("cookie-parser");
|
|
1022
1093
|
if (!cookieParser) {
|
|
@@ -1028,6 +1099,11 @@ var ExpressServer = class {
|
|
|
1028
1099
|
this.app.use(cookieParser());
|
|
1029
1100
|
}
|
|
1030
1101
|
}
|
|
1102
|
+
}
|
|
1103
|
+
/**
|
|
1104
|
+
* Set up OpenAPI documentation middleware.
|
|
1105
|
+
*/
|
|
1106
|
+
async setupOpenApiMiddleware() {
|
|
1031
1107
|
if (this.config.openApi?.enable) {
|
|
1032
1108
|
try {
|
|
1033
1109
|
const openApiMountPath = this.normalizePath(this.config.openApi.mountPath ?? "/docs", this.config.openApi.withGlobalPrefix);
|
|
@@ -1053,7 +1129,7 @@ var ExpressServer = class {
|
|
|
1053
1129
|
}
|
|
1054
1130
|
this.app.use(openApiMountPath, apiReference({
|
|
1055
1131
|
spec: {
|
|
1056
|
-
content: await
|
|
1132
|
+
content: await fs.readFile(openApiFilePath, "utf8")
|
|
1057
1133
|
}
|
|
1058
1134
|
}));
|
|
1059
1135
|
if (this.config.openApi?.verbose) {
|
|
@@ -1068,6 +1144,11 @@ var ExpressServer = class {
|
|
|
1068
1144
|
if (this.hooks.onResponse) {
|
|
1069
1145
|
this.app.use(this.globalPrefix, this.hooks.onResponse);
|
|
1070
1146
|
}
|
|
1147
|
+
}
|
|
1148
|
+
/**
|
|
1149
|
+
* Set up metrics tracking middleware.
|
|
1150
|
+
*/
|
|
1151
|
+
setupMetricsMiddleware() {
|
|
1071
1152
|
if (this.config.metrics?.enable) {
|
|
1072
1153
|
this.app.use((req, res, next) => {
|
|
1073
1154
|
const start = process.hrtime();
|
|
@@ -1116,45 +1197,7 @@ var ExpressServer = class {
|
|
|
1116
1197
|
async setupRoutes() {
|
|
1117
1198
|
const healthCheckPath = this.normalizePath(this.config.healthCheck?.path || "/healthz", this.config.healthCheck?.withGlobalPrefix);
|
|
1118
1199
|
this.app.get(healthCheckPath, async (_req, res) => {
|
|
1119
|
-
|
|
1120
|
-
if (!this.healthChecks.length || config.defaultCatbeeConfig.server.skipHealthz) {
|
|
1121
|
-
return res.status(httpStatusCodes.HttpStatusCodes.OK).json(new response.SuccessResponse("OK"));
|
|
1122
|
-
}
|
|
1123
|
-
const checkResults = await Promise.allSettled(this.healthChecks.map(async ({ name, check }) => {
|
|
1124
|
-
try {
|
|
1125
|
-
const status2 = await Promise.resolve(check());
|
|
1126
|
-
return {
|
|
1127
|
-
name,
|
|
1128
|
-
status: status2,
|
|
1129
|
-
error: null
|
|
1130
|
-
};
|
|
1131
|
-
} catch (error) {
|
|
1132
|
-
return {
|
|
1133
|
-
name,
|
|
1134
|
-
status: false,
|
|
1135
|
-
error: error.message
|
|
1136
|
-
};
|
|
1137
|
-
}
|
|
1138
|
-
}));
|
|
1139
|
-
const results = checkResults.map((result) => {
|
|
1140
|
-
if (result.status === "fulfilled") return result.value;
|
|
1141
|
-
return {
|
|
1142
|
-
name: "unknown",
|
|
1143
|
-
status: false,
|
|
1144
|
-
error: result.reason
|
|
1145
|
-
};
|
|
1146
|
-
});
|
|
1147
|
-
const allOk = results.every((r) => r.status);
|
|
1148
|
-
const status = allOk ? httpStatusCodes.HttpStatusCodes.OK : httpStatusCodes.HttpStatusCodes.SERVICE_UNAVAILABLE;
|
|
1149
|
-
const response$1 = new response.SuccessResponse(allOk ? "OK" : "Service unavailable");
|
|
1150
|
-
if (!allOk) response$1.error = true;
|
|
1151
|
-
if (this.config.healthCheck?.detailed) response$1.data = {
|
|
1152
|
-
checks: results
|
|
1153
|
-
};
|
|
1154
|
-
return res.status(status).json(response$1);
|
|
1155
|
-
} catch {
|
|
1156
|
-
return res.status(httpStatusCodes.HttpStatusCodes.INTERNAL_SERVER_ERROR).json(new exception.InternalServerErrorException("Health check failed"));
|
|
1157
|
-
}
|
|
1200
|
+
return this.handleHealthCheckRequest(res);
|
|
1158
1201
|
});
|
|
1159
1202
|
if (this.config.metrics?.enable) {
|
|
1160
1203
|
const metricsPath = this.normalizePath(this.config.metrics.path ?? "/metrics", this.config.metrics?.withGlobalPrefix);
|
|
@@ -1186,6 +1229,56 @@ var ExpressServer = class {
|
|
|
1186
1229
|
});
|
|
1187
1230
|
}
|
|
1188
1231
|
/**
|
|
1232
|
+
* Execute health check and return response.
|
|
1233
|
+
*/
|
|
1234
|
+
async handleHealthCheckRequest(res) {
|
|
1235
|
+
try {
|
|
1236
|
+
if (!this.healthChecks.length || config.getCatbeeServerGlobalConfig().skipHealthzChecksValidation) {
|
|
1237
|
+
return res.status(httpStatusCodes.HttpStatusCodes.OK).json(new response.SuccessResponse("OK"));
|
|
1238
|
+
}
|
|
1239
|
+
const results = await this.executeHealthChecks();
|
|
1240
|
+
const allOk = results.every((r) => r.status);
|
|
1241
|
+
const status = allOk ? httpStatusCodes.HttpStatusCodes.OK : httpStatusCodes.HttpStatusCodes.SERVICE_UNAVAILABLE;
|
|
1242
|
+
const response$1 = new response.SuccessResponse(allOk ? "OK" : "Service unavailable");
|
|
1243
|
+
if (!allOk) response$1.error = true;
|
|
1244
|
+
if (this.config.healthCheck?.detailed) response$1.data = {
|
|
1245
|
+
checks: results
|
|
1246
|
+
};
|
|
1247
|
+
return res.status(status).json(response$1);
|
|
1248
|
+
} catch {
|
|
1249
|
+
return res.status(httpStatusCodes.HttpStatusCodes.INTERNAL_SERVER_ERROR).json(new exception.InternalServerErrorException("Health check failed"));
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
/**
|
|
1253
|
+
* Execute all registered health checks and return results.
|
|
1254
|
+
*/
|
|
1255
|
+
async executeHealthChecks() {
|
|
1256
|
+
const checkResults = await Promise.allSettled(this.healthChecks.map(async ({ name, check }) => {
|
|
1257
|
+
try {
|
|
1258
|
+
const status = await Promise.resolve(check());
|
|
1259
|
+
return {
|
|
1260
|
+
name,
|
|
1261
|
+
status,
|
|
1262
|
+
error: null
|
|
1263
|
+
};
|
|
1264
|
+
} catch (error) {
|
|
1265
|
+
return {
|
|
1266
|
+
name,
|
|
1267
|
+
status: false,
|
|
1268
|
+
error: error.message
|
|
1269
|
+
};
|
|
1270
|
+
}
|
|
1271
|
+
}));
|
|
1272
|
+
return checkResults.map((result) => {
|
|
1273
|
+
if (result.status === "fulfilled") return result.value;
|
|
1274
|
+
return {
|
|
1275
|
+
name: "unknown",
|
|
1276
|
+
status: false,
|
|
1277
|
+
error: result.reason
|
|
1278
|
+
};
|
|
1279
|
+
});
|
|
1280
|
+
}
|
|
1281
|
+
/**
|
|
1189
1282
|
* Register a new health check function for monitoring service dependencies.
|
|
1190
1283
|
*
|
|
1191
1284
|
* Health checks are executed when the health endpoint is accessed and
|
|
@@ -1216,28 +1309,8 @@ var ExpressServer = class {
|
|
|
1216
1309
|
*/
|
|
1217
1310
|
async ready() {
|
|
1218
1311
|
try {
|
|
1219
|
-
if (!this.healthChecks.length || config.
|
|
1220
|
-
const
|
|
1221
|
-
try {
|
|
1222
|
-
const status = await Promise.resolve(check());
|
|
1223
|
-
return {
|
|
1224
|
-
name,
|
|
1225
|
-
status,
|
|
1226
|
-
error: null
|
|
1227
|
-
};
|
|
1228
|
-
} catch (error) {
|
|
1229
|
-
return {
|
|
1230
|
-
name,
|
|
1231
|
-
status: false,
|
|
1232
|
-
error: error.message
|
|
1233
|
-
};
|
|
1234
|
-
}
|
|
1235
|
-
}));
|
|
1236
|
-
const results = checkResults.map((result) => result.status === "fulfilled" ? result.value : {
|
|
1237
|
-
name: "unknown",
|
|
1238
|
-
status: false,
|
|
1239
|
-
error: result.reason
|
|
1240
|
-
});
|
|
1312
|
+
if (!this.healthChecks.length || config.getCatbeeServerGlobalConfig().skipHealthzChecksValidation) return true;
|
|
1313
|
+
const results = await this.executeHealthChecks();
|
|
1241
1314
|
return results.every((r) => r.status === true);
|
|
1242
1315
|
} catch (err) {
|
|
1243
1316
|
logger.getLogger().error({
|
|
@@ -1282,58 +1355,82 @@ var ExpressServer = class {
|
|
|
1282
1355
|
await this.runHook("beforeStart", this.app);
|
|
1283
1356
|
return new Promise((resolve, reject) => {
|
|
1284
1357
|
try {
|
|
1285
|
-
const
|
|
1286
|
-
this.
|
|
1287
|
-
this.
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
logger.getLogger().info(`Health check available at ${url}${this.normalizePath(this.config.healthCheck.path, this.config.healthCheck.withGlobalPrefix)}`);
|
|
1294
|
-
}
|
|
1295
|
-
if (this.config.metrics?.enable && this.config.metrics.path) {
|
|
1296
|
-
logger.getLogger().info(`Metrics available at ${url}${this.normalizePath(this.config.metrics.path, this.config.metrics.withGlobalPrefix)}`);
|
|
1297
|
-
}
|
|
1298
|
-
if (this.config.openApi?.enable) {
|
|
1299
|
-
logger.getLogger().info(`API docs available at ${url}${this.normalizePath(this.config.openApi.mountPath, this.config.openApi.withGlobalPrefix)}`);
|
|
1300
|
-
}
|
|
1301
|
-
if (this.server) await this.runHook("afterStart", this.server);
|
|
1302
|
-
resolve(this.server);
|
|
1303
|
-
}
|
|
1304
|
-
];
|
|
1305
|
-
if (this.config.https) {
|
|
1306
|
-
const httpsOptions = {
|
|
1307
|
-
...this.config.https,
|
|
1308
|
-
key: fs__default.default.readFileSync(this.config.https.key),
|
|
1309
|
-
cert: fs__default.default.readFileSync(this.config.https.cert)
|
|
1310
|
-
};
|
|
1311
|
-
if (this.config.https.ca) {
|
|
1312
|
-
httpsOptions.ca = fs__default.default.readFileSync(this.config.https.ca);
|
|
1313
|
-
}
|
|
1314
|
-
if (this.config.https.passphrase) {
|
|
1315
|
-
httpsOptions.passphrase = this.config.https.passphrase;
|
|
1316
|
-
}
|
|
1317
|
-
this.server = https__default.default.createServer(httpsOptions, this.app).listen(...listenArgs);
|
|
1318
|
-
} else {
|
|
1319
|
-
this.server = this.app.listen(...listenArgs);
|
|
1320
|
-
}
|
|
1321
|
-
this.server.on("connection", (conn) => {
|
|
1322
|
-
this.connections.add(conn);
|
|
1323
|
-
conn.on("close", () => this.connections.delete(conn));
|
|
1324
|
-
});
|
|
1325
|
-
this.server.on("error", (err) => {
|
|
1326
|
-
logger.getLogger().error({
|
|
1327
|
-
err
|
|
1328
|
-
}, "Server failed to start");
|
|
1329
|
-
reject(err);
|
|
1330
|
-
});
|
|
1358
|
+
const onListening = /* @__PURE__ */ __name(async () => {
|
|
1359
|
+
this.logServerStartInfo();
|
|
1360
|
+
if (this.server) await this.runHook("afterStart", this.server);
|
|
1361
|
+
resolve(this.server);
|
|
1362
|
+
}, "onListening");
|
|
1363
|
+
this.server = this.createServerInstance(onListening);
|
|
1364
|
+
this.setupConnectionTracking();
|
|
1365
|
+
this.setupServerErrorHandling(reject);
|
|
1331
1366
|
} catch (error) {
|
|
1332
1367
|
reject(error);
|
|
1333
1368
|
}
|
|
1334
1369
|
});
|
|
1335
1370
|
}
|
|
1336
1371
|
/**
|
|
1372
|
+
* Create HTTP or HTTPS server instance.
|
|
1373
|
+
*/
|
|
1374
|
+
createServerInstance(onListening) {
|
|
1375
|
+
const listenArgs = [
|
|
1376
|
+
this.config.port,
|
|
1377
|
+
this.config.host,
|
|
1378
|
+
onListening
|
|
1379
|
+
];
|
|
1380
|
+
if (this.config.https) {
|
|
1381
|
+
const httpsOptions = {
|
|
1382
|
+
...this.config.https,
|
|
1383
|
+
key: fs.readFileSync(this.config.https.key),
|
|
1384
|
+
cert: fs.readFileSync(this.config.https.cert)
|
|
1385
|
+
};
|
|
1386
|
+
if (this.config.https.ca) {
|
|
1387
|
+
httpsOptions.ca = fs.readFileSync(this.config.https.ca);
|
|
1388
|
+
}
|
|
1389
|
+
if (this.config.https.passphrase) {
|
|
1390
|
+
httpsOptions.passphrase = this.config.https.passphrase;
|
|
1391
|
+
}
|
|
1392
|
+
return https__default.default.createServer(httpsOptions, this.app).listen(...listenArgs);
|
|
1393
|
+
}
|
|
1394
|
+
return this.app.listen(...listenArgs);
|
|
1395
|
+
}
|
|
1396
|
+
/**
|
|
1397
|
+
* Set up connection tracking for graceful shutdown.
|
|
1398
|
+
*/
|
|
1399
|
+
setupConnectionTracking() {
|
|
1400
|
+
this.server.on("connection", (conn) => {
|
|
1401
|
+
this.connections.add(conn);
|
|
1402
|
+
conn.on("close", () => this.connections.delete(conn));
|
|
1403
|
+
});
|
|
1404
|
+
}
|
|
1405
|
+
/**
|
|
1406
|
+
* Set up error handling for server startup.
|
|
1407
|
+
*/
|
|
1408
|
+
setupServerErrorHandling(reject) {
|
|
1409
|
+
this.server.on("error", (err) => {
|
|
1410
|
+
logger.getLogger().error({
|
|
1411
|
+
err
|
|
1412
|
+
}, "Server failed to start");
|
|
1413
|
+
reject(err);
|
|
1414
|
+
});
|
|
1415
|
+
}
|
|
1416
|
+
/**
|
|
1417
|
+
* Log server startup information.
|
|
1418
|
+
*/
|
|
1419
|
+
logServerStartInfo() {
|
|
1420
|
+
const protocol = this.config.https ? "https" : "http";
|
|
1421
|
+
const url = `${protocol}://${this.config.host}:${this.config.port}`;
|
|
1422
|
+
logger.getLogger().info(`Server running on ${url}`);
|
|
1423
|
+
if (this.config.healthCheck?.path) {
|
|
1424
|
+
logger.getLogger().info(`Health check available at ${url}${this.normalizePath(this.config.healthCheck.path, this.config.healthCheck.withGlobalPrefix)}`);
|
|
1425
|
+
}
|
|
1426
|
+
if (this.config.metrics?.enable && this.config.metrics.path) {
|
|
1427
|
+
logger.getLogger().info(`Metrics available at ${url}${this.normalizePath(this.config.metrics.path, this.config.metrics.withGlobalPrefix)}`);
|
|
1428
|
+
}
|
|
1429
|
+
if (this.config.openApi?.enable) {
|
|
1430
|
+
logger.getLogger().info(`API docs available at ${url}${this.normalizePath(this.config.openApi.mountPath, this.config.openApi.withGlobalPrefix)}`);
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
/**
|
|
1337
1434
|
* Stop the HTTP server gracefully.
|
|
1338
1435
|
*
|
|
1339
1436
|
* This method:
|
|
@@ -1360,6 +1457,23 @@ var ExpressServer = class {
|
|
|
1360
1457
|
}
|
|
1361
1458
|
this.isShuttingDown = true;
|
|
1362
1459
|
await this.runHook("beforeStop", this.server);
|
|
1460
|
+
try {
|
|
1461
|
+
await this.gracefulShutdown();
|
|
1462
|
+
} catch (err) {
|
|
1463
|
+
logger.getLogger().error({
|
|
1464
|
+
err
|
|
1465
|
+
}, "Graceful shutdown timed out");
|
|
1466
|
+
if (force) {
|
|
1467
|
+
logger.getLogger().warn("Forcing connection destroy due to shutdown timeout");
|
|
1468
|
+
}
|
|
1469
|
+
} finally {
|
|
1470
|
+
await this.destroyConnections();
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
/**
|
|
1474
|
+
* Perform graceful server shutdown with timeout.
|
|
1475
|
+
*/
|
|
1476
|
+
async gracefulShutdown() {
|
|
1363
1477
|
const shutdownTimeout = 1e4;
|
|
1364
1478
|
const serverClosePromise = new Promise((resolve, reject) => {
|
|
1365
1479
|
this.server.close(async (err) => {
|
|
@@ -1378,21 +1492,10 @@ var ExpressServer = class {
|
|
|
1378
1492
|
});
|
|
1379
1493
|
});
|
|
1380
1494
|
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error("Shutdown timeout")), shutdownTimeout));
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
]);
|
|
1386
|
-
} catch (err) {
|
|
1387
|
-
logger.getLogger().error({
|
|
1388
|
-
err
|
|
1389
|
-
}, "Graceful shutdown timed out");
|
|
1390
|
-
if (force) {
|
|
1391
|
-
logger.getLogger().warn("Forcing connection destroy due to shutdown timeout");
|
|
1392
|
-
}
|
|
1393
|
-
} finally {
|
|
1394
|
-
await this.destroyConnections();
|
|
1395
|
-
}
|
|
1495
|
+
await Promise.race([
|
|
1496
|
+
serverClosePromise,
|
|
1497
|
+
timeoutPromise
|
|
1498
|
+
]);
|
|
1396
1499
|
}
|
|
1397
1500
|
/**
|
|
1398
1501
|
* Enable graceful shutdown on OS signals for production deployment.
|
|
@@ -1534,7 +1637,7 @@ var ExpressServer = class {
|
|
|
1534
1637
|
/**
|
|
1535
1638
|
* Get server configuration
|
|
1536
1639
|
*
|
|
1537
|
-
* @return {*} {
|
|
1640
|
+
* @return {*} {CatbeeServerConfig}
|
|
1538
1641
|
*/
|
|
1539
1642
|
getConfig() {
|
|
1540
1643
|
return this.config;
|
|
@@ -1608,7 +1711,7 @@ var ExpressServer = class {
|
|
|
1608
1711
|
throw new Error(msg);
|
|
1609
1712
|
}
|
|
1610
1713
|
}
|
|
1611
|
-
|
|
1714
|
+
hasBuildMarker(config) {
|
|
1612
1715
|
if (config?.[BUILD_MARKER]) {
|
|
1613
1716
|
return true;
|
|
1614
1717
|
}
|