@catbee/utils 2.0.0-next.1 → 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 +50 -36
- 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 +230 -131
- package/server/index.d.ts +81 -1
- package/server/index.mjs +226 -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 +3 -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}`;
|
|
@@ -886,6 +886,24 @@ var ExpressServer = class {
|
|
|
886
886
|
if (this.config.https) {
|
|
887
887
|
await this.validateHttpsFiles();
|
|
888
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() {
|
|
889
907
|
this.app.disable("x-powered-by");
|
|
890
908
|
if (this.config.trustProxy) {
|
|
891
909
|
this.app.set("trust proxy", true);
|
|
@@ -902,11 +920,15 @@ var ExpressServer = class {
|
|
|
902
920
|
this.app.use((_req, res, next) => {
|
|
903
921
|
if (this.isShuttingDown) {
|
|
904
922
|
res.setHeader("Connection", "close");
|
|
905
|
-
|
|
923
|
+
res.status(httpStatusCodes.HttpStatusCodes.SERVICE_UNAVAILABLE).json(new exception.ServiceUnavailableException("Server is shutting down"));
|
|
906
924
|
}
|
|
907
925
|
next();
|
|
908
|
-
return;
|
|
909
926
|
});
|
|
927
|
+
}
|
|
928
|
+
/**
|
|
929
|
+
* Set up security middleware (Helmet, CORS).
|
|
930
|
+
*/
|
|
931
|
+
setupSecurityMiddleware() {
|
|
910
932
|
if (this.config.helmet) {
|
|
911
933
|
const helmet = async.optionalRequire("helmet");
|
|
912
934
|
if (!helmet) {
|
|
@@ -925,6 +947,11 @@ var ExpressServer = class {
|
|
|
925
947
|
}
|
|
926
948
|
this.app.use(cors(this.config.cors === true ? {} : this.config.cors));
|
|
927
949
|
}
|
|
950
|
+
}
|
|
951
|
+
/**
|
|
952
|
+
* Set up global headers middleware.
|
|
953
|
+
*/
|
|
954
|
+
setupGlobalHeaders() {
|
|
928
955
|
this.app.use((_req, res, next) => {
|
|
929
956
|
if (this.config.globalHeaders) {
|
|
930
957
|
for (const key in this.config.globalHeaders) {
|
|
@@ -941,15 +968,30 @@ var ExpressServer = class {
|
|
|
941
968
|
}
|
|
942
969
|
next();
|
|
943
970
|
});
|
|
971
|
+
}
|
|
972
|
+
/**
|
|
973
|
+
* Set up request timeout middleware.
|
|
974
|
+
*/
|
|
975
|
+
setupTimeoutMiddleware() {
|
|
944
976
|
if (this.config.requestTimeout) {
|
|
945
977
|
this.app.use(middleware.timeout(this.config.requestTimeout));
|
|
946
978
|
}
|
|
979
|
+
}
|
|
980
|
+
/**
|
|
981
|
+
* Set up response time tracking middleware.
|
|
982
|
+
*/
|
|
983
|
+
setupResponseTimeMiddleware() {
|
|
947
984
|
if (this.config.responseTime?.enable) {
|
|
948
985
|
this.app.use(middleware.responseTime({
|
|
949
986
|
addHeader: this.config.responseTime.addHeader,
|
|
950
987
|
logOnComplete: this.config.responseTime.logOnComplete
|
|
951
988
|
}));
|
|
952
989
|
}
|
|
990
|
+
}
|
|
991
|
+
/**
|
|
992
|
+
* Set up rate limiting middleware.
|
|
993
|
+
*/
|
|
994
|
+
setupRateLimitingMiddleware() {
|
|
953
995
|
if (this.config.rateLimit?.enable) {
|
|
954
996
|
const rateLimit = async.optionalRequire("express-rate-limit");
|
|
955
997
|
if (!rateLimit) {
|
|
@@ -967,6 +1009,11 @@ var ExpressServer = class {
|
|
|
967
1009
|
legacyHeaders: this.config.rateLimit.legacyHeaders ?? false
|
|
968
1010
|
}));
|
|
969
1011
|
}
|
|
1012
|
+
}
|
|
1013
|
+
/**
|
|
1014
|
+
* Set up request logging middleware.
|
|
1015
|
+
*/
|
|
1016
|
+
setupRequestLoggingMiddleware() {
|
|
970
1017
|
if (this.config.requestLogging?.enable) {
|
|
971
1018
|
this.app.use((req, res, next) => {
|
|
972
1019
|
if (typeof this.config.requestLogging?.ignorePaths === "function") {
|
|
@@ -990,6 +1037,11 @@ var ExpressServer = class {
|
|
|
990
1037
|
if (this.hooks.onRequest) {
|
|
991
1038
|
this.app.use(this.hooks.onRequest);
|
|
992
1039
|
}
|
|
1040
|
+
}
|
|
1041
|
+
/**
|
|
1042
|
+
* Set up response compression middleware.
|
|
1043
|
+
*/
|
|
1044
|
+
setupCompressionMiddleware() {
|
|
993
1045
|
if (this.config.compression) {
|
|
994
1046
|
const compression = async.optionalRequire("compression");
|
|
995
1047
|
if (!compression) {
|
|
@@ -1001,6 +1053,11 @@ var ExpressServer = class {
|
|
|
1001
1053
|
this.app.use(compression());
|
|
1002
1054
|
}
|
|
1003
1055
|
}
|
|
1056
|
+
}
|
|
1057
|
+
/**
|
|
1058
|
+
* Set up static file serving middleware.
|
|
1059
|
+
*/
|
|
1060
|
+
setupStaticFilesMiddleware() {
|
|
1004
1061
|
if (this.config.staticFolders) {
|
|
1005
1062
|
this.config.staticFolders.forEach((folder) => {
|
|
1006
1063
|
this.app.use(this.normalizePath(folder.path ?? "/"), express__default.default.static(folder.directory, {
|
|
@@ -1013,6 +1070,11 @@ var ExpressServer = class {
|
|
|
1013
1070
|
logger.getLogger().info(`Serving static folder: ${folder.directory} at path ${folder.path || "/"}`);
|
|
1014
1071
|
});
|
|
1015
1072
|
}
|
|
1073
|
+
}
|
|
1074
|
+
/**
|
|
1075
|
+
* Set up body parsing middleware.
|
|
1076
|
+
*/
|
|
1077
|
+
setupBodyParsingMiddleware() {
|
|
1016
1078
|
if (this.config.bodyParser) {
|
|
1017
1079
|
if (this.config.bodyParser.json) {
|
|
1018
1080
|
this.app.use(express__default.default.json(this.config.bodyParser.json));
|
|
@@ -1021,6 +1083,11 @@ var ExpressServer = class {
|
|
|
1021
1083
|
this.app.use(express__default.default.urlencoded(this.config.bodyParser.urlencoded));
|
|
1022
1084
|
}
|
|
1023
1085
|
}
|
|
1086
|
+
}
|
|
1087
|
+
/**
|
|
1088
|
+
* Set up cookie parsing middleware.
|
|
1089
|
+
*/
|
|
1090
|
+
setupCookieParsingMiddleware() {
|
|
1024
1091
|
if (this.config.cookieParser) {
|
|
1025
1092
|
const cookieParser = async.optionalRequire("cookie-parser");
|
|
1026
1093
|
if (!cookieParser) {
|
|
@@ -1032,6 +1099,11 @@ var ExpressServer = class {
|
|
|
1032
1099
|
this.app.use(cookieParser());
|
|
1033
1100
|
}
|
|
1034
1101
|
}
|
|
1102
|
+
}
|
|
1103
|
+
/**
|
|
1104
|
+
* Set up OpenAPI documentation middleware.
|
|
1105
|
+
*/
|
|
1106
|
+
async setupOpenApiMiddleware() {
|
|
1035
1107
|
if (this.config.openApi?.enable) {
|
|
1036
1108
|
try {
|
|
1037
1109
|
const openApiMountPath = this.normalizePath(this.config.openApi.mountPath ?? "/docs", this.config.openApi.withGlobalPrefix);
|
|
@@ -1072,6 +1144,11 @@ var ExpressServer = class {
|
|
|
1072
1144
|
if (this.hooks.onResponse) {
|
|
1073
1145
|
this.app.use(this.globalPrefix, this.hooks.onResponse);
|
|
1074
1146
|
}
|
|
1147
|
+
}
|
|
1148
|
+
/**
|
|
1149
|
+
* Set up metrics tracking middleware.
|
|
1150
|
+
*/
|
|
1151
|
+
setupMetricsMiddleware() {
|
|
1075
1152
|
if (this.config.metrics?.enable) {
|
|
1076
1153
|
this.app.use((req, res, next) => {
|
|
1077
1154
|
const start = process.hrtime();
|
|
@@ -1120,45 +1197,7 @@ var ExpressServer = class {
|
|
|
1120
1197
|
async setupRoutes() {
|
|
1121
1198
|
const healthCheckPath = this.normalizePath(this.config.healthCheck?.path || "/healthz", this.config.healthCheck?.withGlobalPrefix);
|
|
1122
1199
|
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
|
-
}
|
|
1200
|
+
return this.handleHealthCheckRequest(res);
|
|
1162
1201
|
});
|
|
1163
1202
|
if (this.config.metrics?.enable) {
|
|
1164
1203
|
const metricsPath = this.normalizePath(this.config.metrics.path ?? "/metrics", this.config.metrics?.withGlobalPrefix);
|
|
@@ -1190,6 +1229,56 @@ var ExpressServer = class {
|
|
|
1190
1229
|
});
|
|
1191
1230
|
}
|
|
1192
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
|
+
/**
|
|
1193
1282
|
* Register a new health check function for monitoring service dependencies.
|
|
1194
1283
|
*
|
|
1195
1284
|
* Health checks are executed when the health endpoint is accessed and
|
|
@@ -1220,28 +1309,8 @@ var ExpressServer = class {
|
|
|
1220
1309
|
*/
|
|
1221
1310
|
async ready() {
|
|
1222
1311
|
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
|
-
});
|
|
1312
|
+
if (!this.healthChecks.length || config.getCatbeeServerGlobalConfig().skipHealthzChecksValidation) return true;
|
|
1313
|
+
const results = await this.executeHealthChecks();
|
|
1245
1314
|
return results.every((r) => r.status === true);
|
|
1246
1315
|
} catch (err) {
|
|
1247
1316
|
logger.getLogger().error({
|
|
@@ -1286,58 +1355,82 @@ var ExpressServer = class {
|
|
|
1286
1355
|
await this.runHook("beforeStart", this.app);
|
|
1287
1356
|
return new Promise((resolve, reject) => {
|
|
1288
1357
|
try {
|
|
1289
|
-
const
|
|
1290
|
-
this.
|
|
1291
|
-
this.
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
logger.getLogger().info(`Health check available at ${url}${this.normalizePath(this.config.healthCheck.path, this.config.healthCheck.withGlobalPrefix)}`);
|
|
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
|
-
});
|
|
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);
|
|
1335
1366
|
} catch (error) {
|
|
1336
1367
|
reject(error);
|
|
1337
1368
|
}
|
|
1338
1369
|
});
|
|
1339
1370
|
}
|
|
1340
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
|
+
/**
|
|
1341
1434
|
* Stop the HTTP server gracefully.
|
|
1342
1435
|
*
|
|
1343
1436
|
* This method:
|
|
@@ -1364,6 +1457,23 @@ var ExpressServer = class {
|
|
|
1364
1457
|
}
|
|
1365
1458
|
this.isShuttingDown = true;
|
|
1366
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() {
|
|
1367
1477
|
const shutdownTimeout = 1e4;
|
|
1368
1478
|
const serverClosePromise = new Promise((resolve, reject) => {
|
|
1369
1479
|
this.server.close(async (err) => {
|
|
@@ -1382,21 +1492,10 @@ var ExpressServer = class {
|
|
|
1382
1492
|
});
|
|
1383
1493
|
});
|
|
1384
1494
|
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
|
-
}
|
|
1495
|
+
await Promise.race([
|
|
1496
|
+
serverClosePromise,
|
|
1497
|
+
timeoutPromise
|
|
1498
|
+
]);
|
|
1400
1499
|
}
|
|
1401
1500
|
/**
|
|
1402
1501
|
* 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
|
*
|