@catbee/utils 2.0.0-next.1 → 2.0.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/LICENSE +1 -1
- package/README.md +55 -38
- package/array/index.cjs +37 -5
- package/array/index.d.ts +59 -8
- package/array/index.mjs +32 -4
- package/async/index.cjs +24 -3
- package/async/index.d.ts +18 -2
- package/async/index.mjs +24 -4
- package/cache/index.cjs +1 -1
- package/cache/index.d.ts +1 -1
- package/cache/index.mjs +1 -1
- package/config/index.cjs +8 -8
- package/config/index.d.ts +1 -1
- package/config/index.mjs +4 -4
- package/context-store/index.cjs +2 -3
- package/context-store/index.d.ts +1 -1
- package/context-store/index.mjs +2 -3
- package/crypto/index.cjs +55 -5
- package/crypto/index.d.ts +63 -2
- package/crypto/index.mjs +52 -7
- package/date/index.cjs +630 -1
- package/date/index.d.ts +487 -2
- package/date/index.mjs +621 -2
- package/{decorators → decorator}/index.cjs +425 -304
- package/{decorators → decorator}/index.d.ts +3 -2
- package/{decorators → decorator}/index.mjs +425 -304
- package/{dir → directory}/index.cjs +1 -1
- package/{dir → directory}/index.d.ts +1 -1
- package/{dir → directory}/index.mjs +1 -1
- package/env/index.cjs +90 -42
- package/env/index.d.ts +12 -1
- package/env/index.mjs +90 -42
- package/exception/index.cjs +1 -1
- package/exception/index.d.ts +1 -1
- package/exception/index.mjs +1 -1
- package/fs/index.cjs +1 -1
- package/fs/index.d.ts +1 -1
- package/fs/index.mjs +1 -1
- package/http-status-codes/index.cjs +1 -1
- package/http-status-codes/index.d.ts +1 -1
- package/http-status-codes/index.mjs +1 -1
- package/id/index.cjs +1 -1
- package/id/index.d.ts +1 -1
- package/id/index.mjs +1 -1
- package/index.cjs +10 -10
- package/index.d.ts +4 -4
- package/index.mjs +4 -4
- package/logger/index.cjs +2 -4
- package/logger/index.d.ts +2 -2
- package/logger/index.mjs +2 -4
- package/middleware/index.cjs +1 -1
- package/middleware/index.d.ts +1 -1
- package/middleware/index.mjs +1 -1
- package/{obj → object}/index.cjs +182 -108
- package/{obj → object}/index.d.ts +38 -2
- package/{obj → object}/index.mjs +180 -109
- package/package.json +31 -13
- package/performance/index.cjs +2 -2
- package/performance/index.d.ts +1 -1
- package/performance/index.mjs +2 -2
- package/request/index.cjs +36 -24
- package/request/index.d.ts +1 -1
- package/request/index.mjs +36 -24
- package/response/index.cjs +1 -1
- package/response/index.d.ts +1 -1
- package/response/index.mjs +1 -1
- package/server/index.cjs +233 -131
- package/server/index.d.ts +81 -1
- package/server/index.mjs +229 -127
- package/stream/index.cjs +1 -1
- package/stream/index.d.ts +1 -1
- package/stream/index.mjs +1 -1
- package/string/index.cjs +34 -1
- package/string/index.d.ts +45 -2
- package/string/index.mjs +31 -2
- package/type/index.cjs +18 -1
- package/type/index.d.ts +38 -2
- package/type/index.mjs +16 -2
- package/types/index.cjs +1 -1
- package/types/index.d.ts +10 -3
- package/types/index.mjs +1 -1
- package/url/index.cjs +61 -1
- package/url/index.d.ts +59 -2
- package/url/index.mjs +57 -2
- package/validation/index.cjs +3 -3
- package/validation/index.d.ts +1 -1
- package/validation/index.mjs +3 -3
package/response/index.mjs
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
|
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
|
|
@@ -33,7 +33,7 @@ var env = require('@catbee/utils/env');
|
|
|
33
33
|
var logger = require('@catbee/utils/logger');
|
|
34
34
|
var exception = require('@catbee/utils/exception');
|
|
35
35
|
var config = require('@catbee/utils/config');
|
|
36
|
-
var
|
|
36
|
+
var object = require('@catbee/utils/object');
|
|
37
37
|
var fs = require('@catbee/utils/fs');
|
|
38
38
|
var validation = require('@catbee/utils/validation');
|
|
39
39
|
var async = require('@catbee/utils/async');
|
|
@@ -611,7 +611,7 @@ var ServerConfigBuilder = class {
|
|
|
611
611
|
* ```
|
|
612
612
|
*/
|
|
613
613
|
withCustom(overrides) {
|
|
614
|
-
this.config =
|
|
614
|
+
this.config = object.deepObjMerge({}, this.config, overrides);
|
|
615
615
|
return this;
|
|
616
616
|
}
|
|
617
617
|
/**
|
|
@@ -651,7 +651,7 @@ var ServerConfigBuilder = class {
|
|
|
651
651
|
* ```
|
|
652
652
|
*/
|
|
653
653
|
build() {
|
|
654
|
-
const config$1 =
|
|
654
|
+
const config$1 = object.deepObjMerge({}, config.getCatbeeServerGlobalConfig(), this.config);
|
|
655
655
|
if (config$1.openApi?.enable && !config$1.openApi.filePath) {
|
|
656
656
|
throw new Error("OpenAPI is enabled but no filePath is specified");
|
|
657
657
|
}
|
|
@@ -661,8 +661,8 @@ var ServerConfigBuilder = class {
|
|
|
661
661
|
});
|
|
662
662
|
}
|
|
663
663
|
mergeConfig(key, value) {
|
|
664
|
-
const current = this.config[key] && typeof this.config[key] === "object" ?
|
|
665
|
-
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);
|
|
666
666
|
}
|
|
667
667
|
setEnabled(key, enable, overrides = {}) {
|
|
668
668
|
this.mergeConfig(key, {
|
|
@@ -744,7 +744,7 @@ var ExpressServer = class {
|
|
|
744
744
|
if (this.hasBuildMarker(config$1)) {
|
|
745
745
|
this.config = config$1;
|
|
746
746
|
} else {
|
|
747
|
-
this.config =
|
|
747
|
+
this.config = object.deepObjMerge({}, config.getCatbeeServerGlobalConfig(), config$1);
|
|
748
748
|
}
|
|
749
749
|
if (!validation.isPort(this.config.port)) {
|
|
750
750
|
const msg = `Port must be a valid number between 1 and 65535, got: ${this.config.port}`;
|
|
@@ -860,6 +860,7 @@ var ExpressServer = class {
|
|
|
860
860
|
async initialize() {
|
|
861
861
|
await this.runHook("beforeInit", this);
|
|
862
862
|
await this.setupMiddleware();
|
|
863
|
+
await this.runHook("beforeRoutes", this.app);
|
|
863
864
|
await this.setupRoutes();
|
|
864
865
|
await this.runHook("afterInit", this);
|
|
865
866
|
}
|
|
@@ -886,6 +887,24 @@ var ExpressServer = class {
|
|
|
886
887
|
if (this.config.https) {
|
|
887
888
|
await this.validateHttpsFiles();
|
|
888
889
|
}
|
|
890
|
+
this.setupBasicMiddleware();
|
|
891
|
+
this.setupSecurityMiddleware();
|
|
892
|
+
this.setupGlobalHeaders();
|
|
893
|
+
this.setupTimeoutMiddleware();
|
|
894
|
+
this.setupResponseTimeMiddleware();
|
|
895
|
+
this.setupRateLimitingMiddleware();
|
|
896
|
+
this.setupRequestLoggingMiddleware();
|
|
897
|
+
this.setupCompressionMiddleware();
|
|
898
|
+
this.setupStaticFilesMiddleware();
|
|
899
|
+
this.setupBodyParsingMiddleware();
|
|
900
|
+
this.setupCookieParsingMiddleware();
|
|
901
|
+
await this.setupOpenApiMiddleware();
|
|
902
|
+
this.setupMetricsMiddleware();
|
|
903
|
+
}
|
|
904
|
+
/**
|
|
905
|
+
* Set up basic middleware (trust proxy, request ID, context).
|
|
906
|
+
*/
|
|
907
|
+
setupBasicMiddleware() {
|
|
889
908
|
this.app.disable("x-powered-by");
|
|
890
909
|
if (this.config.trustProxy) {
|
|
891
910
|
this.app.set("trust proxy", true);
|
|
@@ -902,11 +921,15 @@ var ExpressServer = class {
|
|
|
902
921
|
this.app.use((_req, res, next) => {
|
|
903
922
|
if (this.isShuttingDown) {
|
|
904
923
|
res.setHeader("Connection", "close");
|
|
905
|
-
|
|
924
|
+
res.status(httpStatusCodes.HttpStatusCodes.SERVICE_UNAVAILABLE).json(new exception.ServiceUnavailableException("Server is shutting down"));
|
|
906
925
|
}
|
|
907
926
|
next();
|
|
908
|
-
return;
|
|
909
927
|
});
|
|
928
|
+
}
|
|
929
|
+
/**
|
|
930
|
+
* Set up security middleware (Helmet, CORS).
|
|
931
|
+
*/
|
|
932
|
+
setupSecurityMiddleware() {
|
|
910
933
|
if (this.config.helmet) {
|
|
911
934
|
const helmet = async.optionalRequire("helmet");
|
|
912
935
|
if (!helmet) {
|
|
@@ -925,6 +948,11 @@ var ExpressServer = class {
|
|
|
925
948
|
}
|
|
926
949
|
this.app.use(cors(this.config.cors === true ? {} : this.config.cors));
|
|
927
950
|
}
|
|
951
|
+
}
|
|
952
|
+
/**
|
|
953
|
+
* Set up global headers middleware.
|
|
954
|
+
*/
|
|
955
|
+
setupGlobalHeaders() {
|
|
928
956
|
this.app.use((_req, res, next) => {
|
|
929
957
|
if (this.config.globalHeaders) {
|
|
930
958
|
for (const key in this.config.globalHeaders) {
|
|
@@ -941,15 +969,30 @@ var ExpressServer = class {
|
|
|
941
969
|
}
|
|
942
970
|
next();
|
|
943
971
|
});
|
|
972
|
+
}
|
|
973
|
+
/**
|
|
974
|
+
* Set up request timeout middleware.
|
|
975
|
+
*/
|
|
976
|
+
setupTimeoutMiddleware() {
|
|
944
977
|
if (this.config.requestTimeout) {
|
|
945
978
|
this.app.use(middleware.timeout(this.config.requestTimeout));
|
|
946
979
|
}
|
|
980
|
+
}
|
|
981
|
+
/**
|
|
982
|
+
* Set up response time tracking middleware.
|
|
983
|
+
*/
|
|
984
|
+
setupResponseTimeMiddleware() {
|
|
947
985
|
if (this.config.responseTime?.enable) {
|
|
948
986
|
this.app.use(middleware.responseTime({
|
|
949
987
|
addHeader: this.config.responseTime.addHeader,
|
|
950
988
|
logOnComplete: this.config.responseTime.logOnComplete
|
|
951
989
|
}));
|
|
952
990
|
}
|
|
991
|
+
}
|
|
992
|
+
/**
|
|
993
|
+
* Set up rate limiting middleware.
|
|
994
|
+
*/
|
|
995
|
+
setupRateLimitingMiddleware() {
|
|
953
996
|
if (this.config.rateLimit?.enable) {
|
|
954
997
|
const rateLimit = async.optionalRequire("express-rate-limit");
|
|
955
998
|
if (!rateLimit) {
|
|
@@ -967,6 +1010,11 @@ var ExpressServer = class {
|
|
|
967
1010
|
legacyHeaders: this.config.rateLimit.legacyHeaders ?? false
|
|
968
1011
|
}));
|
|
969
1012
|
}
|
|
1013
|
+
}
|
|
1014
|
+
/**
|
|
1015
|
+
* Set up request logging middleware.
|
|
1016
|
+
*/
|
|
1017
|
+
setupRequestLoggingMiddleware() {
|
|
970
1018
|
if (this.config.requestLogging?.enable) {
|
|
971
1019
|
this.app.use((req, res, next) => {
|
|
972
1020
|
if (typeof this.config.requestLogging?.ignorePaths === "function") {
|
|
@@ -990,6 +1038,11 @@ var ExpressServer = class {
|
|
|
990
1038
|
if (this.hooks.onRequest) {
|
|
991
1039
|
this.app.use(this.hooks.onRequest);
|
|
992
1040
|
}
|
|
1041
|
+
}
|
|
1042
|
+
/**
|
|
1043
|
+
* Set up response compression middleware.
|
|
1044
|
+
*/
|
|
1045
|
+
setupCompressionMiddleware() {
|
|
993
1046
|
if (this.config.compression) {
|
|
994
1047
|
const compression = async.optionalRequire("compression");
|
|
995
1048
|
if (!compression) {
|
|
@@ -1001,6 +1054,11 @@ var ExpressServer = class {
|
|
|
1001
1054
|
this.app.use(compression());
|
|
1002
1055
|
}
|
|
1003
1056
|
}
|
|
1057
|
+
}
|
|
1058
|
+
/**
|
|
1059
|
+
* Set up static file serving middleware.
|
|
1060
|
+
*/
|
|
1061
|
+
setupStaticFilesMiddleware() {
|
|
1004
1062
|
if (this.config.staticFolders) {
|
|
1005
1063
|
this.config.staticFolders.forEach((folder) => {
|
|
1006
1064
|
this.app.use(this.normalizePath(folder.path ?? "/"), express__default.default.static(folder.directory, {
|
|
@@ -1013,6 +1071,11 @@ var ExpressServer = class {
|
|
|
1013
1071
|
logger.getLogger().info(`Serving static folder: ${folder.directory} at path ${folder.path || "/"}`);
|
|
1014
1072
|
});
|
|
1015
1073
|
}
|
|
1074
|
+
}
|
|
1075
|
+
/**
|
|
1076
|
+
* Set up body parsing middleware.
|
|
1077
|
+
*/
|
|
1078
|
+
setupBodyParsingMiddleware() {
|
|
1016
1079
|
if (this.config.bodyParser) {
|
|
1017
1080
|
if (this.config.bodyParser.json) {
|
|
1018
1081
|
this.app.use(express__default.default.json(this.config.bodyParser.json));
|
|
@@ -1021,6 +1084,11 @@ var ExpressServer = class {
|
|
|
1021
1084
|
this.app.use(express__default.default.urlencoded(this.config.bodyParser.urlencoded));
|
|
1022
1085
|
}
|
|
1023
1086
|
}
|
|
1087
|
+
}
|
|
1088
|
+
/**
|
|
1089
|
+
* Set up cookie parsing middleware.
|
|
1090
|
+
*/
|
|
1091
|
+
setupCookieParsingMiddleware() {
|
|
1024
1092
|
if (this.config.cookieParser) {
|
|
1025
1093
|
const cookieParser = async.optionalRequire("cookie-parser");
|
|
1026
1094
|
if (!cookieParser) {
|
|
@@ -1032,6 +1100,11 @@ var ExpressServer = class {
|
|
|
1032
1100
|
this.app.use(cookieParser());
|
|
1033
1101
|
}
|
|
1034
1102
|
}
|
|
1103
|
+
}
|
|
1104
|
+
/**
|
|
1105
|
+
* Set up OpenAPI documentation middleware.
|
|
1106
|
+
*/
|
|
1107
|
+
async setupOpenApiMiddleware() {
|
|
1035
1108
|
if (this.config.openApi?.enable) {
|
|
1036
1109
|
try {
|
|
1037
1110
|
const openApiMountPath = this.normalizePath(this.config.openApi.mountPath ?? "/docs", this.config.openApi.withGlobalPrefix);
|
|
@@ -1072,6 +1145,11 @@ var ExpressServer = class {
|
|
|
1072
1145
|
if (this.hooks.onResponse) {
|
|
1073
1146
|
this.app.use(this.globalPrefix, this.hooks.onResponse);
|
|
1074
1147
|
}
|
|
1148
|
+
}
|
|
1149
|
+
/**
|
|
1150
|
+
* Set up metrics tracking middleware.
|
|
1151
|
+
*/
|
|
1152
|
+
setupMetricsMiddleware() {
|
|
1075
1153
|
if (this.config.metrics?.enable) {
|
|
1076
1154
|
this.app.use((req, res, next) => {
|
|
1077
1155
|
const start = process.hrtime();
|
|
@@ -1120,45 +1198,7 @@ var ExpressServer = class {
|
|
|
1120
1198
|
async setupRoutes() {
|
|
1121
1199
|
const healthCheckPath = this.normalizePath(this.config.healthCheck?.path || "/healthz", this.config.healthCheck?.withGlobalPrefix);
|
|
1122
1200
|
this.app.get(healthCheckPath, async (_req, res) => {
|
|
1123
|
-
|
|
1124
|
-
if (!this.healthChecks.length || config.getCatbeeServerGlobalConfig().skipHealthz) {
|
|
1125
|
-
return res.status(httpStatusCodes.HttpStatusCodes.OK).json(new response.SuccessResponse("OK"));
|
|
1126
|
-
}
|
|
1127
|
-
const checkResults = await Promise.allSettled(this.healthChecks.map(async ({ name, check }) => {
|
|
1128
|
-
try {
|
|
1129
|
-
const status2 = await Promise.resolve(check());
|
|
1130
|
-
return {
|
|
1131
|
-
name,
|
|
1132
|
-
status: status2,
|
|
1133
|
-
error: null
|
|
1134
|
-
};
|
|
1135
|
-
} catch (error) {
|
|
1136
|
-
return {
|
|
1137
|
-
name,
|
|
1138
|
-
status: false,
|
|
1139
|
-
error: error.message
|
|
1140
|
-
};
|
|
1141
|
-
}
|
|
1142
|
-
}));
|
|
1143
|
-
const results = checkResults.map((result) => {
|
|
1144
|
-
if (result.status === "fulfilled") return result.value;
|
|
1145
|
-
return {
|
|
1146
|
-
name: "unknown",
|
|
1147
|
-
status: false,
|
|
1148
|
-
error: result.reason
|
|
1149
|
-
};
|
|
1150
|
-
});
|
|
1151
|
-
const allOk = results.every((r) => r.status);
|
|
1152
|
-
const status = allOk ? httpStatusCodes.HttpStatusCodes.OK : httpStatusCodes.HttpStatusCodes.SERVICE_UNAVAILABLE;
|
|
1153
|
-
const response$1 = new response.SuccessResponse(allOk ? "OK" : "Service unavailable");
|
|
1154
|
-
if (!allOk) response$1.error = true;
|
|
1155
|
-
if (this.config.healthCheck?.detailed) response$1.data = {
|
|
1156
|
-
checks: results
|
|
1157
|
-
};
|
|
1158
|
-
return res.status(status).json(response$1);
|
|
1159
|
-
} catch {
|
|
1160
|
-
return res.status(httpStatusCodes.HttpStatusCodes.INTERNAL_SERVER_ERROR).json(new exception.InternalServerErrorException("Health check failed"));
|
|
1161
|
-
}
|
|
1201
|
+
return this.handleHealthCheckRequest(res);
|
|
1162
1202
|
});
|
|
1163
1203
|
if (this.config.metrics?.enable) {
|
|
1164
1204
|
const metricsPath = this.normalizePath(this.config.metrics.path ?? "/metrics", this.config.metrics?.withGlobalPrefix);
|
|
@@ -1169,6 +1209,7 @@ var ExpressServer = class {
|
|
|
1169
1209
|
}
|
|
1170
1210
|
const routerToUse = this.externalRouter || this.rootRouter;
|
|
1171
1211
|
this.app.use(this.globalPrefix, routerToUse);
|
|
1212
|
+
await this.runHook("afterRoutes", this.app);
|
|
1172
1213
|
this.app.use((req, res) => {
|
|
1173
1214
|
const status = httpStatusCodes.HttpStatusCodes.NOT_FOUND;
|
|
1174
1215
|
const response$1 = response.createFinalErrorResponse(req, status, `Route ${req.method.toUpperCase()} ${req.path} not found`);
|
|
@@ -1190,6 +1231,56 @@ var ExpressServer = class {
|
|
|
1190
1231
|
});
|
|
1191
1232
|
}
|
|
1192
1233
|
/**
|
|
1234
|
+
* Execute health check and return response.
|
|
1235
|
+
*/
|
|
1236
|
+
async handleHealthCheckRequest(res) {
|
|
1237
|
+
try {
|
|
1238
|
+
if (!this.healthChecks.length || config.getCatbeeServerGlobalConfig().skipHealthzChecksValidation) {
|
|
1239
|
+
return res.status(httpStatusCodes.HttpStatusCodes.OK).json(new response.SuccessResponse("OK"));
|
|
1240
|
+
}
|
|
1241
|
+
const results = await this.executeHealthChecks();
|
|
1242
|
+
const allOk = results.every((r) => r.status);
|
|
1243
|
+
const status = allOk ? httpStatusCodes.HttpStatusCodes.OK : httpStatusCodes.HttpStatusCodes.SERVICE_UNAVAILABLE;
|
|
1244
|
+
const response$1 = new response.SuccessResponse(allOk ? "OK" : "Service unavailable");
|
|
1245
|
+
if (!allOk) response$1.error = true;
|
|
1246
|
+
if (this.config.healthCheck?.detailed) response$1.data = {
|
|
1247
|
+
checks: results
|
|
1248
|
+
};
|
|
1249
|
+
return res.status(status).json(response$1);
|
|
1250
|
+
} catch {
|
|
1251
|
+
return res.status(httpStatusCodes.HttpStatusCodes.INTERNAL_SERVER_ERROR).json(new exception.InternalServerErrorException("Health check failed"));
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
/**
|
|
1255
|
+
* Execute all registered health checks and return results.
|
|
1256
|
+
*/
|
|
1257
|
+
async executeHealthChecks() {
|
|
1258
|
+
const checkResults = await Promise.allSettled(this.healthChecks.map(async ({ name, check }) => {
|
|
1259
|
+
try {
|
|
1260
|
+
const status = await Promise.resolve(check());
|
|
1261
|
+
return {
|
|
1262
|
+
name,
|
|
1263
|
+
status,
|
|
1264
|
+
error: null
|
|
1265
|
+
};
|
|
1266
|
+
} catch (error) {
|
|
1267
|
+
return {
|
|
1268
|
+
name,
|
|
1269
|
+
status: false,
|
|
1270
|
+
error: error.message
|
|
1271
|
+
};
|
|
1272
|
+
}
|
|
1273
|
+
}));
|
|
1274
|
+
return checkResults.map((result) => {
|
|
1275
|
+
if (result.status === "fulfilled") return result.value;
|
|
1276
|
+
return {
|
|
1277
|
+
name: "unknown",
|
|
1278
|
+
status: false,
|
|
1279
|
+
error: result.reason
|
|
1280
|
+
};
|
|
1281
|
+
});
|
|
1282
|
+
}
|
|
1283
|
+
/**
|
|
1193
1284
|
* Register a new health check function for monitoring service dependencies.
|
|
1194
1285
|
*
|
|
1195
1286
|
* Health checks are executed when the health endpoint is accessed and
|
|
@@ -1220,28 +1311,8 @@ var ExpressServer = class {
|
|
|
1220
1311
|
*/
|
|
1221
1312
|
async ready() {
|
|
1222
1313
|
try {
|
|
1223
|
-
if (!this.healthChecks.length || config.getCatbeeServerGlobalConfig().
|
|
1224
|
-
const
|
|
1225
|
-
try {
|
|
1226
|
-
const status = await Promise.resolve(check());
|
|
1227
|
-
return {
|
|
1228
|
-
name,
|
|
1229
|
-
status,
|
|
1230
|
-
error: null
|
|
1231
|
-
};
|
|
1232
|
-
} catch (error) {
|
|
1233
|
-
return {
|
|
1234
|
-
name,
|
|
1235
|
-
status: false,
|
|
1236
|
-
error: error.message
|
|
1237
|
-
};
|
|
1238
|
-
}
|
|
1239
|
-
}));
|
|
1240
|
-
const results = checkResults.map((result) => result.status === "fulfilled" ? result.value : {
|
|
1241
|
-
name: "unknown",
|
|
1242
|
-
status: false,
|
|
1243
|
-
error: result.reason
|
|
1244
|
-
});
|
|
1314
|
+
if (!this.healthChecks.length || config.getCatbeeServerGlobalConfig().skipHealthzChecksValidation) return true;
|
|
1315
|
+
const results = await this.executeHealthChecks();
|
|
1245
1316
|
return results.every((r) => r.status === true);
|
|
1246
1317
|
} catch (err) {
|
|
1247
1318
|
logger.getLogger().error({
|
|
@@ -1286,58 +1357,83 @@ var ExpressServer = class {
|
|
|
1286
1357
|
await this.runHook("beforeStart", this.app);
|
|
1287
1358
|
return new Promise((resolve, reject) => {
|
|
1288
1359
|
try {
|
|
1289
|
-
const
|
|
1290
|
-
this.
|
|
1291
|
-
this.
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
}
|
|
1299
|
-
if (this.config.metrics?.enable && this.config.metrics.path) {
|
|
1300
|
-
logger.getLogger().info(`Metrics available at ${url}${this.normalizePath(this.config.metrics.path, this.config.metrics.withGlobalPrefix)}`);
|
|
1301
|
-
}
|
|
1302
|
-
if (this.config.openApi?.enable) {
|
|
1303
|
-
logger.getLogger().info(`API docs available at ${url}${this.normalizePath(this.config.openApi.mountPath, this.config.openApi.withGlobalPrefix)}`);
|
|
1304
|
-
}
|
|
1305
|
-
if (this.server) await this.runHook("afterStart", this.server);
|
|
1306
|
-
resolve(this.server);
|
|
1307
|
-
}
|
|
1308
|
-
];
|
|
1309
|
-
if (this.config.https) {
|
|
1310
|
-
const httpsOptions = {
|
|
1311
|
-
...this.config.https,
|
|
1312
|
-
key: fs.readFileSync(this.config.https.key),
|
|
1313
|
-
cert: fs.readFileSync(this.config.https.cert)
|
|
1314
|
-
};
|
|
1315
|
-
if (this.config.https.ca) {
|
|
1316
|
-
httpsOptions.ca = fs.readFileSync(this.config.https.ca);
|
|
1317
|
-
}
|
|
1318
|
-
if (this.config.https.passphrase) {
|
|
1319
|
-
httpsOptions.passphrase = this.config.https.passphrase;
|
|
1320
|
-
}
|
|
1321
|
-
this.server = https__default.default.createServer(httpsOptions, this.app).listen(...listenArgs);
|
|
1322
|
-
} else {
|
|
1323
|
-
this.server = this.app.listen(...listenArgs);
|
|
1324
|
-
}
|
|
1325
|
-
this.server.on("connection", (conn) => {
|
|
1326
|
-
this.connections.add(conn);
|
|
1327
|
-
conn.on("close", () => this.connections.delete(conn));
|
|
1328
|
-
});
|
|
1329
|
-
this.server.on("error", (err) => {
|
|
1330
|
-
logger.getLogger().error({
|
|
1331
|
-
err
|
|
1332
|
-
}, "Server failed to start");
|
|
1333
|
-
reject(err);
|
|
1334
|
-
});
|
|
1360
|
+
const onListening = /* @__PURE__ */ __name(async () => {
|
|
1361
|
+
this.logServerStartInfo();
|
|
1362
|
+
if (this.server) await this.runHook("afterStart", this.server);
|
|
1363
|
+
resolve(this.server);
|
|
1364
|
+
}, "onListening");
|
|
1365
|
+
this.server = this.createServerInstance(onListening);
|
|
1366
|
+
this.runHook("onServerCreated", this.server);
|
|
1367
|
+
this.setupConnectionTracking();
|
|
1368
|
+
this.setupServerErrorHandling(reject);
|
|
1335
1369
|
} catch (error) {
|
|
1336
1370
|
reject(error);
|
|
1337
1371
|
}
|
|
1338
1372
|
});
|
|
1339
1373
|
}
|
|
1340
1374
|
/**
|
|
1375
|
+
* Create HTTP or HTTPS server instance.
|
|
1376
|
+
*/
|
|
1377
|
+
createServerInstance(onListening) {
|
|
1378
|
+
const listenArgs = [
|
|
1379
|
+
this.config.port,
|
|
1380
|
+
this.config.host,
|
|
1381
|
+
onListening
|
|
1382
|
+
];
|
|
1383
|
+
if (this.config.https) {
|
|
1384
|
+
const httpsOptions = {
|
|
1385
|
+
...this.config.https,
|
|
1386
|
+
key: fs.readFileSync(this.config.https.key),
|
|
1387
|
+
cert: fs.readFileSync(this.config.https.cert)
|
|
1388
|
+
};
|
|
1389
|
+
if (this.config.https.ca) {
|
|
1390
|
+
httpsOptions.ca = fs.readFileSync(this.config.https.ca);
|
|
1391
|
+
}
|
|
1392
|
+
if (this.config.https.passphrase) {
|
|
1393
|
+
httpsOptions.passphrase = this.config.https.passphrase;
|
|
1394
|
+
}
|
|
1395
|
+
return https__default.default.createServer(httpsOptions, this.app).listen(...listenArgs);
|
|
1396
|
+
}
|
|
1397
|
+
return this.app.listen(...listenArgs);
|
|
1398
|
+
}
|
|
1399
|
+
/**
|
|
1400
|
+
* Set up connection tracking for graceful shutdown.
|
|
1401
|
+
*/
|
|
1402
|
+
setupConnectionTracking() {
|
|
1403
|
+
this.server.on("connection", (conn) => {
|
|
1404
|
+
this.connections.add(conn);
|
|
1405
|
+
conn.on("close", () => this.connections.delete(conn));
|
|
1406
|
+
});
|
|
1407
|
+
}
|
|
1408
|
+
/**
|
|
1409
|
+
* Set up error handling for server startup.
|
|
1410
|
+
*/
|
|
1411
|
+
setupServerErrorHandling(reject) {
|
|
1412
|
+
this.server.on("error", (err) => {
|
|
1413
|
+
logger.getLogger().error({
|
|
1414
|
+
err
|
|
1415
|
+
}, "Server failed to start");
|
|
1416
|
+
reject(err);
|
|
1417
|
+
});
|
|
1418
|
+
}
|
|
1419
|
+
/**
|
|
1420
|
+
* Log server startup information.
|
|
1421
|
+
*/
|
|
1422
|
+
logServerStartInfo() {
|
|
1423
|
+
const protocol = this.config.https ? "https" : "http";
|
|
1424
|
+
const url = `${protocol}://${this.config.host}:${this.config.port}`;
|
|
1425
|
+
logger.getLogger().info(`Server running on ${url}`);
|
|
1426
|
+
if (this.config.healthCheck?.path) {
|
|
1427
|
+
logger.getLogger().info(`Health check available at ${url}${this.normalizePath(this.config.healthCheck.path, this.config.healthCheck.withGlobalPrefix)}`);
|
|
1428
|
+
}
|
|
1429
|
+
if (this.config.metrics?.enable && this.config.metrics.path) {
|
|
1430
|
+
logger.getLogger().info(`Metrics available at ${url}${this.normalizePath(this.config.metrics.path, this.config.metrics.withGlobalPrefix)}`);
|
|
1431
|
+
}
|
|
1432
|
+
if (this.config.openApi?.enable) {
|
|
1433
|
+
logger.getLogger().info(`API docs available at ${url}${this.normalizePath(this.config.openApi.mountPath, this.config.openApi.withGlobalPrefix)}`);
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
/**
|
|
1341
1437
|
* Stop the HTTP server gracefully.
|
|
1342
1438
|
*
|
|
1343
1439
|
* This method:
|
|
@@ -1364,6 +1460,23 @@ var ExpressServer = class {
|
|
|
1364
1460
|
}
|
|
1365
1461
|
this.isShuttingDown = true;
|
|
1366
1462
|
await this.runHook("beforeStop", this.server);
|
|
1463
|
+
try {
|
|
1464
|
+
await this.gracefulShutdown();
|
|
1465
|
+
} catch (err) {
|
|
1466
|
+
logger.getLogger().error({
|
|
1467
|
+
err
|
|
1468
|
+
}, "Graceful shutdown timed out");
|
|
1469
|
+
if (force) {
|
|
1470
|
+
logger.getLogger().warn("Forcing connection destroy due to shutdown timeout");
|
|
1471
|
+
}
|
|
1472
|
+
} finally {
|
|
1473
|
+
await this.destroyConnections();
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
/**
|
|
1477
|
+
* Perform graceful server shutdown with timeout.
|
|
1478
|
+
*/
|
|
1479
|
+
async gracefulShutdown() {
|
|
1367
1480
|
const shutdownTimeout = 1e4;
|
|
1368
1481
|
const serverClosePromise = new Promise((resolve, reject) => {
|
|
1369
1482
|
this.server.close(async (err) => {
|
|
@@ -1382,21 +1495,10 @@ var ExpressServer = class {
|
|
|
1382
1495
|
});
|
|
1383
1496
|
});
|
|
1384
1497
|
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error("Shutdown timeout")), shutdownTimeout));
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
]);
|
|
1390
|
-
} catch (err) {
|
|
1391
|
-
logger.getLogger().error({
|
|
1392
|
-
err
|
|
1393
|
-
}, "Graceful shutdown timed out");
|
|
1394
|
-
if (force) {
|
|
1395
|
-
logger.getLogger().warn("Forcing connection destroy due to shutdown timeout");
|
|
1396
|
-
}
|
|
1397
|
-
} finally {
|
|
1398
|
-
await this.destroyConnections();
|
|
1399
|
-
}
|
|
1498
|
+
await Promise.race([
|
|
1499
|
+
serverClosePromise,
|
|
1500
|
+
timeoutPromise
|
|
1501
|
+
]);
|
|
1400
1502
|
}
|
|
1401
1503
|
/**
|
|
1402
1504
|
* Enable graceful shutdown on OS signals for production deployment.
|
package/server/index.d.ts
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
|
|
@@ -138,6 +138,58 @@ declare class ExpressServer {
|
|
|
138
138
|
* 14. Custom response hooks
|
|
139
139
|
*/
|
|
140
140
|
protected setupMiddleware(): Promise<void>;
|
|
141
|
+
/**
|
|
142
|
+
* Set up basic middleware (trust proxy, request ID, context).
|
|
143
|
+
*/
|
|
144
|
+
private setupBasicMiddleware;
|
|
145
|
+
/**
|
|
146
|
+
* Set up security middleware (Helmet, CORS).
|
|
147
|
+
*/
|
|
148
|
+
private setupSecurityMiddleware;
|
|
149
|
+
/**
|
|
150
|
+
* Set up global headers middleware.
|
|
151
|
+
*/
|
|
152
|
+
private setupGlobalHeaders;
|
|
153
|
+
/**
|
|
154
|
+
* Set up request timeout middleware.
|
|
155
|
+
*/
|
|
156
|
+
private setupTimeoutMiddleware;
|
|
157
|
+
/**
|
|
158
|
+
* Set up response time tracking middleware.
|
|
159
|
+
*/
|
|
160
|
+
private setupResponseTimeMiddleware;
|
|
161
|
+
/**
|
|
162
|
+
* Set up rate limiting middleware.
|
|
163
|
+
*/
|
|
164
|
+
private setupRateLimitingMiddleware;
|
|
165
|
+
/**
|
|
166
|
+
* Set up request logging middleware.
|
|
167
|
+
*/
|
|
168
|
+
private setupRequestLoggingMiddleware;
|
|
169
|
+
/**
|
|
170
|
+
* Set up response compression middleware.
|
|
171
|
+
*/
|
|
172
|
+
private setupCompressionMiddleware;
|
|
173
|
+
/**
|
|
174
|
+
* Set up static file serving middleware.
|
|
175
|
+
*/
|
|
176
|
+
private setupStaticFilesMiddleware;
|
|
177
|
+
/**
|
|
178
|
+
* Set up body parsing middleware.
|
|
179
|
+
*/
|
|
180
|
+
private setupBodyParsingMiddleware;
|
|
181
|
+
/**
|
|
182
|
+
* Set up cookie parsing middleware.
|
|
183
|
+
*/
|
|
184
|
+
private setupCookieParsingMiddleware;
|
|
185
|
+
/**
|
|
186
|
+
* Set up OpenAPI documentation middleware.
|
|
187
|
+
*/
|
|
188
|
+
private setupOpenApiMiddleware;
|
|
189
|
+
/**
|
|
190
|
+
* Set up metrics tracking middleware.
|
|
191
|
+
*/
|
|
192
|
+
private setupMetricsMiddleware;
|
|
141
193
|
/**
|
|
142
194
|
* Configure server routes and error handling.
|
|
143
195
|
* Sets up in following order:
|
|
@@ -148,6 +200,14 @@ declare class ExpressServer {
|
|
|
148
200
|
* 4. Error handler
|
|
149
201
|
*/
|
|
150
202
|
protected setupRoutes(): Promise<void>;
|
|
203
|
+
/**
|
|
204
|
+
* Execute health check and return response.
|
|
205
|
+
*/
|
|
206
|
+
private handleHealthCheckRequest;
|
|
207
|
+
/**
|
|
208
|
+
* Execute all registered health checks and return results.
|
|
209
|
+
*/
|
|
210
|
+
private executeHealthChecks;
|
|
151
211
|
/**
|
|
152
212
|
* Register a new health check function for monitoring service dependencies.
|
|
153
213
|
*
|
|
@@ -200,6 +260,22 @@ declare class ExpressServer {
|
|
|
200
260
|
* @throws Error if server fails to start or port is already in use
|
|
201
261
|
*/
|
|
202
262
|
start(): Promise<http.Server | https.Server>;
|
|
263
|
+
/**
|
|
264
|
+
* Create HTTP or HTTPS server instance.
|
|
265
|
+
*/
|
|
266
|
+
private createServerInstance;
|
|
267
|
+
/**
|
|
268
|
+
* Set up connection tracking for graceful shutdown.
|
|
269
|
+
*/
|
|
270
|
+
private setupConnectionTracking;
|
|
271
|
+
/**
|
|
272
|
+
* Set up error handling for server startup.
|
|
273
|
+
*/
|
|
274
|
+
private setupServerErrorHandling;
|
|
275
|
+
/**
|
|
276
|
+
* Log server startup information.
|
|
277
|
+
*/
|
|
278
|
+
private logServerStartInfo;
|
|
203
279
|
/**
|
|
204
280
|
* Stop the HTTP server gracefully.
|
|
205
281
|
*
|
|
@@ -217,6 +293,10 @@ declare class ExpressServer {
|
|
|
217
293
|
* - Monitoring systems are notified
|
|
218
294
|
*/
|
|
219
295
|
stop(force?: boolean): Promise<void>;
|
|
296
|
+
/**
|
|
297
|
+
* Perform graceful server shutdown with timeout.
|
|
298
|
+
*/
|
|
299
|
+
private gracefulShutdown;
|
|
220
300
|
/**
|
|
221
301
|
* Enable graceful shutdown on OS signals for production deployment.
|
|
222
302
|
*
|