@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/server/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
|
|
@@ -25,13 +25,13 @@
|
|
|
25
25
|
import express from 'express';
|
|
26
26
|
import https from 'https';
|
|
27
27
|
import { HttpStatusCodes } from '@catbee/utils/http-status-codes';
|
|
28
|
-
import {
|
|
28
|
+
import { createFinalErrorResponse, SuccessResponse } from '@catbee/utils/response';
|
|
29
29
|
import { requestId, setupRequestContext, timeout, responseTime, errorHandler } from '@catbee/utils/middleware';
|
|
30
30
|
import { Env } from '@catbee/utils/env';
|
|
31
31
|
import { getLogger } from '@catbee/utils/logger';
|
|
32
32
|
import { ServiceUnavailableException, InternalServerErrorException, NotFoundException } from '@catbee/utils/exception';
|
|
33
33
|
import { getCatbeeServerGlobalConfig } from '@catbee/utils/config';
|
|
34
|
-
import { deepObjMerge, deepClone } from '@catbee/utils/
|
|
34
|
+
import { deepObjMerge, deepClone } from '@catbee/utils/object';
|
|
35
35
|
import { fileExists, readFile, readFileSync } from '@catbee/utils/fs';
|
|
36
36
|
import { isPort } from '@catbee/utils/validation';
|
|
37
37
|
import { optionalRequire } from '@catbee/utils/async';
|
|
@@ -853,6 +853,7 @@ var ExpressServer = class {
|
|
|
853
853
|
async initialize() {
|
|
854
854
|
await this.runHook("beforeInit", this);
|
|
855
855
|
await this.setupMiddleware();
|
|
856
|
+
await this.runHook("beforeRoutes", this.app);
|
|
856
857
|
await this.setupRoutes();
|
|
857
858
|
await this.runHook("afterInit", this);
|
|
858
859
|
}
|
|
@@ -879,6 +880,24 @@ var ExpressServer = class {
|
|
|
879
880
|
if (this.config.https) {
|
|
880
881
|
await this.validateHttpsFiles();
|
|
881
882
|
}
|
|
883
|
+
this.setupBasicMiddleware();
|
|
884
|
+
this.setupSecurityMiddleware();
|
|
885
|
+
this.setupGlobalHeaders();
|
|
886
|
+
this.setupTimeoutMiddleware();
|
|
887
|
+
this.setupResponseTimeMiddleware();
|
|
888
|
+
this.setupRateLimitingMiddleware();
|
|
889
|
+
this.setupRequestLoggingMiddleware();
|
|
890
|
+
this.setupCompressionMiddleware();
|
|
891
|
+
this.setupStaticFilesMiddleware();
|
|
892
|
+
this.setupBodyParsingMiddleware();
|
|
893
|
+
this.setupCookieParsingMiddleware();
|
|
894
|
+
await this.setupOpenApiMiddleware();
|
|
895
|
+
this.setupMetricsMiddleware();
|
|
896
|
+
}
|
|
897
|
+
/**
|
|
898
|
+
* Set up basic middleware (trust proxy, request ID, context).
|
|
899
|
+
*/
|
|
900
|
+
setupBasicMiddleware() {
|
|
882
901
|
this.app.disable("x-powered-by");
|
|
883
902
|
if (this.config.trustProxy) {
|
|
884
903
|
this.app.set("trust proxy", true);
|
|
@@ -895,11 +914,15 @@ var ExpressServer = class {
|
|
|
895
914
|
this.app.use((_req, res, next) => {
|
|
896
915
|
if (this.isShuttingDown) {
|
|
897
916
|
res.setHeader("Connection", "close");
|
|
898
|
-
|
|
917
|
+
res.status(HttpStatusCodes.SERVICE_UNAVAILABLE).json(new ServiceUnavailableException("Server is shutting down"));
|
|
899
918
|
}
|
|
900
919
|
next();
|
|
901
|
-
return;
|
|
902
920
|
});
|
|
921
|
+
}
|
|
922
|
+
/**
|
|
923
|
+
* Set up security middleware (Helmet, CORS).
|
|
924
|
+
*/
|
|
925
|
+
setupSecurityMiddleware() {
|
|
903
926
|
if (this.config.helmet) {
|
|
904
927
|
const helmet = optionalRequire("helmet");
|
|
905
928
|
if (!helmet) {
|
|
@@ -918,6 +941,11 @@ var ExpressServer = class {
|
|
|
918
941
|
}
|
|
919
942
|
this.app.use(cors(this.config.cors === true ? {} : this.config.cors));
|
|
920
943
|
}
|
|
944
|
+
}
|
|
945
|
+
/**
|
|
946
|
+
* Set up global headers middleware.
|
|
947
|
+
*/
|
|
948
|
+
setupGlobalHeaders() {
|
|
921
949
|
this.app.use((_req, res, next) => {
|
|
922
950
|
if (this.config.globalHeaders) {
|
|
923
951
|
for (const key in this.config.globalHeaders) {
|
|
@@ -934,15 +962,30 @@ var ExpressServer = class {
|
|
|
934
962
|
}
|
|
935
963
|
next();
|
|
936
964
|
});
|
|
965
|
+
}
|
|
966
|
+
/**
|
|
967
|
+
* Set up request timeout middleware.
|
|
968
|
+
*/
|
|
969
|
+
setupTimeoutMiddleware() {
|
|
937
970
|
if (this.config.requestTimeout) {
|
|
938
971
|
this.app.use(timeout(this.config.requestTimeout));
|
|
939
972
|
}
|
|
973
|
+
}
|
|
974
|
+
/**
|
|
975
|
+
* Set up response time tracking middleware.
|
|
976
|
+
*/
|
|
977
|
+
setupResponseTimeMiddleware() {
|
|
940
978
|
if (this.config.responseTime?.enable) {
|
|
941
979
|
this.app.use(responseTime({
|
|
942
980
|
addHeader: this.config.responseTime.addHeader,
|
|
943
981
|
logOnComplete: this.config.responseTime.logOnComplete
|
|
944
982
|
}));
|
|
945
983
|
}
|
|
984
|
+
}
|
|
985
|
+
/**
|
|
986
|
+
* Set up rate limiting middleware.
|
|
987
|
+
*/
|
|
988
|
+
setupRateLimitingMiddleware() {
|
|
946
989
|
if (this.config.rateLimit?.enable) {
|
|
947
990
|
const rateLimit = optionalRequire("express-rate-limit");
|
|
948
991
|
if (!rateLimit) {
|
|
@@ -960,6 +1003,11 @@ var ExpressServer = class {
|
|
|
960
1003
|
legacyHeaders: this.config.rateLimit.legacyHeaders ?? false
|
|
961
1004
|
}));
|
|
962
1005
|
}
|
|
1006
|
+
}
|
|
1007
|
+
/**
|
|
1008
|
+
* Set up request logging middleware.
|
|
1009
|
+
*/
|
|
1010
|
+
setupRequestLoggingMiddleware() {
|
|
963
1011
|
if (this.config.requestLogging?.enable) {
|
|
964
1012
|
this.app.use((req, res, next) => {
|
|
965
1013
|
if (typeof this.config.requestLogging?.ignorePaths === "function") {
|
|
@@ -983,6 +1031,11 @@ var ExpressServer = class {
|
|
|
983
1031
|
if (this.hooks.onRequest) {
|
|
984
1032
|
this.app.use(this.hooks.onRequest);
|
|
985
1033
|
}
|
|
1034
|
+
}
|
|
1035
|
+
/**
|
|
1036
|
+
* Set up response compression middleware.
|
|
1037
|
+
*/
|
|
1038
|
+
setupCompressionMiddleware() {
|
|
986
1039
|
if (this.config.compression) {
|
|
987
1040
|
const compression = optionalRequire("compression");
|
|
988
1041
|
if (!compression) {
|
|
@@ -994,6 +1047,11 @@ var ExpressServer = class {
|
|
|
994
1047
|
this.app.use(compression());
|
|
995
1048
|
}
|
|
996
1049
|
}
|
|
1050
|
+
}
|
|
1051
|
+
/**
|
|
1052
|
+
* Set up static file serving middleware.
|
|
1053
|
+
*/
|
|
1054
|
+
setupStaticFilesMiddleware() {
|
|
997
1055
|
if (this.config.staticFolders) {
|
|
998
1056
|
this.config.staticFolders.forEach((folder) => {
|
|
999
1057
|
this.app.use(this.normalizePath(folder.path ?? "/"), express.static(folder.directory, {
|
|
@@ -1006,6 +1064,11 @@ var ExpressServer = class {
|
|
|
1006
1064
|
getLogger().info(`Serving static folder: ${folder.directory} at path ${folder.path || "/"}`);
|
|
1007
1065
|
});
|
|
1008
1066
|
}
|
|
1067
|
+
}
|
|
1068
|
+
/**
|
|
1069
|
+
* Set up body parsing middleware.
|
|
1070
|
+
*/
|
|
1071
|
+
setupBodyParsingMiddleware() {
|
|
1009
1072
|
if (this.config.bodyParser) {
|
|
1010
1073
|
if (this.config.bodyParser.json) {
|
|
1011
1074
|
this.app.use(express.json(this.config.bodyParser.json));
|
|
@@ -1014,6 +1077,11 @@ var ExpressServer = class {
|
|
|
1014
1077
|
this.app.use(express.urlencoded(this.config.bodyParser.urlencoded));
|
|
1015
1078
|
}
|
|
1016
1079
|
}
|
|
1080
|
+
}
|
|
1081
|
+
/**
|
|
1082
|
+
* Set up cookie parsing middleware.
|
|
1083
|
+
*/
|
|
1084
|
+
setupCookieParsingMiddleware() {
|
|
1017
1085
|
if (this.config.cookieParser) {
|
|
1018
1086
|
const cookieParser = optionalRequire("cookie-parser");
|
|
1019
1087
|
if (!cookieParser) {
|
|
@@ -1025,6 +1093,11 @@ var ExpressServer = class {
|
|
|
1025
1093
|
this.app.use(cookieParser());
|
|
1026
1094
|
}
|
|
1027
1095
|
}
|
|
1096
|
+
}
|
|
1097
|
+
/**
|
|
1098
|
+
* Set up OpenAPI documentation middleware.
|
|
1099
|
+
*/
|
|
1100
|
+
async setupOpenApiMiddleware() {
|
|
1028
1101
|
if (this.config.openApi?.enable) {
|
|
1029
1102
|
try {
|
|
1030
1103
|
const openApiMountPath = this.normalizePath(this.config.openApi.mountPath ?? "/docs", this.config.openApi.withGlobalPrefix);
|
|
@@ -1065,6 +1138,11 @@ var ExpressServer = class {
|
|
|
1065
1138
|
if (this.hooks.onResponse) {
|
|
1066
1139
|
this.app.use(this.globalPrefix, this.hooks.onResponse);
|
|
1067
1140
|
}
|
|
1141
|
+
}
|
|
1142
|
+
/**
|
|
1143
|
+
* Set up metrics tracking middleware.
|
|
1144
|
+
*/
|
|
1145
|
+
setupMetricsMiddleware() {
|
|
1068
1146
|
if (this.config.metrics?.enable) {
|
|
1069
1147
|
this.app.use((req, res, next) => {
|
|
1070
1148
|
const start = process.hrtime();
|
|
@@ -1113,45 +1191,7 @@ var ExpressServer = class {
|
|
|
1113
1191
|
async setupRoutes() {
|
|
1114
1192
|
const healthCheckPath = this.normalizePath(this.config.healthCheck?.path || "/healthz", this.config.healthCheck?.withGlobalPrefix);
|
|
1115
1193
|
this.app.get(healthCheckPath, async (_req, res) => {
|
|
1116
|
-
|
|
1117
|
-
if (!this.healthChecks.length || getCatbeeServerGlobalConfig().skipHealthz) {
|
|
1118
|
-
return res.status(HttpStatusCodes.OK).json(new SuccessResponse("OK"));
|
|
1119
|
-
}
|
|
1120
|
-
const checkResults = await Promise.allSettled(this.healthChecks.map(async ({ name, check }) => {
|
|
1121
|
-
try {
|
|
1122
|
-
const status2 = await Promise.resolve(check());
|
|
1123
|
-
return {
|
|
1124
|
-
name,
|
|
1125
|
-
status: status2,
|
|
1126
|
-
error: null
|
|
1127
|
-
};
|
|
1128
|
-
} catch (error) {
|
|
1129
|
-
return {
|
|
1130
|
-
name,
|
|
1131
|
-
status: false,
|
|
1132
|
-
error: error.message
|
|
1133
|
-
};
|
|
1134
|
-
}
|
|
1135
|
-
}));
|
|
1136
|
-
const results = checkResults.map((result) => {
|
|
1137
|
-
if (result.status === "fulfilled") return result.value;
|
|
1138
|
-
return {
|
|
1139
|
-
name: "unknown",
|
|
1140
|
-
status: false,
|
|
1141
|
-
error: result.reason
|
|
1142
|
-
};
|
|
1143
|
-
});
|
|
1144
|
-
const allOk = results.every((r) => r.status);
|
|
1145
|
-
const status = allOk ? HttpStatusCodes.OK : HttpStatusCodes.SERVICE_UNAVAILABLE;
|
|
1146
|
-
const response = new SuccessResponse(allOk ? "OK" : "Service unavailable");
|
|
1147
|
-
if (!allOk) response.error = true;
|
|
1148
|
-
if (this.config.healthCheck?.detailed) response.data = {
|
|
1149
|
-
checks: results
|
|
1150
|
-
};
|
|
1151
|
-
return res.status(status).json(response);
|
|
1152
|
-
} catch {
|
|
1153
|
-
return res.status(HttpStatusCodes.INTERNAL_SERVER_ERROR).json(new InternalServerErrorException("Health check failed"));
|
|
1154
|
-
}
|
|
1194
|
+
return this.handleHealthCheckRequest(res);
|
|
1155
1195
|
});
|
|
1156
1196
|
if (this.config.metrics?.enable) {
|
|
1157
1197
|
const metricsPath = this.normalizePath(this.config.metrics.path ?? "/metrics", this.config.metrics?.withGlobalPrefix);
|
|
@@ -1162,6 +1202,7 @@ var ExpressServer = class {
|
|
|
1162
1202
|
}
|
|
1163
1203
|
const routerToUse = this.externalRouter || this.rootRouter;
|
|
1164
1204
|
this.app.use(this.globalPrefix, routerToUse);
|
|
1205
|
+
await this.runHook("afterRoutes", this.app);
|
|
1165
1206
|
this.app.use((req, res) => {
|
|
1166
1207
|
const status = HttpStatusCodes.NOT_FOUND;
|
|
1167
1208
|
const response = createFinalErrorResponse(req, status, `Route ${req.method.toUpperCase()} ${req.path} not found`);
|
|
@@ -1183,6 +1224,56 @@ var ExpressServer = class {
|
|
|
1183
1224
|
});
|
|
1184
1225
|
}
|
|
1185
1226
|
/**
|
|
1227
|
+
* Execute health check and return response.
|
|
1228
|
+
*/
|
|
1229
|
+
async handleHealthCheckRequest(res) {
|
|
1230
|
+
try {
|
|
1231
|
+
if (!this.healthChecks.length || getCatbeeServerGlobalConfig().skipHealthzChecksValidation) {
|
|
1232
|
+
return res.status(HttpStatusCodes.OK).json(new SuccessResponse("OK"));
|
|
1233
|
+
}
|
|
1234
|
+
const results = await this.executeHealthChecks();
|
|
1235
|
+
const allOk = results.every((r) => r.status);
|
|
1236
|
+
const status = allOk ? HttpStatusCodes.OK : HttpStatusCodes.SERVICE_UNAVAILABLE;
|
|
1237
|
+
const response = new SuccessResponse(allOk ? "OK" : "Service unavailable");
|
|
1238
|
+
if (!allOk) response.error = true;
|
|
1239
|
+
if (this.config.healthCheck?.detailed) response.data = {
|
|
1240
|
+
checks: results
|
|
1241
|
+
};
|
|
1242
|
+
return res.status(status).json(response);
|
|
1243
|
+
} catch {
|
|
1244
|
+
return res.status(HttpStatusCodes.INTERNAL_SERVER_ERROR).json(new InternalServerErrorException("Health check failed"));
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
/**
|
|
1248
|
+
* Execute all registered health checks and return results.
|
|
1249
|
+
*/
|
|
1250
|
+
async executeHealthChecks() {
|
|
1251
|
+
const checkResults = await Promise.allSettled(this.healthChecks.map(async ({ name, check }) => {
|
|
1252
|
+
try {
|
|
1253
|
+
const status = await Promise.resolve(check());
|
|
1254
|
+
return {
|
|
1255
|
+
name,
|
|
1256
|
+
status,
|
|
1257
|
+
error: null
|
|
1258
|
+
};
|
|
1259
|
+
} catch (error) {
|
|
1260
|
+
return {
|
|
1261
|
+
name,
|
|
1262
|
+
status: false,
|
|
1263
|
+
error: error.message
|
|
1264
|
+
};
|
|
1265
|
+
}
|
|
1266
|
+
}));
|
|
1267
|
+
return checkResults.map((result) => {
|
|
1268
|
+
if (result.status === "fulfilled") return result.value;
|
|
1269
|
+
return {
|
|
1270
|
+
name: "unknown",
|
|
1271
|
+
status: false,
|
|
1272
|
+
error: result.reason
|
|
1273
|
+
};
|
|
1274
|
+
});
|
|
1275
|
+
}
|
|
1276
|
+
/**
|
|
1186
1277
|
* Register a new health check function for monitoring service dependencies.
|
|
1187
1278
|
*
|
|
1188
1279
|
* Health checks are executed when the health endpoint is accessed and
|
|
@@ -1213,28 +1304,8 @@ var ExpressServer = class {
|
|
|
1213
1304
|
*/
|
|
1214
1305
|
async ready() {
|
|
1215
1306
|
try {
|
|
1216
|
-
if (!this.healthChecks.length || getCatbeeServerGlobalConfig().
|
|
1217
|
-
const
|
|
1218
|
-
try {
|
|
1219
|
-
const status = await Promise.resolve(check());
|
|
1220
|
-
return {
|
|
1221
|
-
name,
|
|
1222
|
-
status,
|
|
1223
|
-
error: null
|
|
1224
|
-
};
|
|
1225
|
-
} catch (error) {
|
|
1226
|
-
return {
|
|
1227
|
-
name,
|
|
1228
|
-
status: false,
|
|
1229
|
-
error: error.message
|
|
1230
|
-
};
|
|
1231
|
-
}
|
|
1232
|
-
}));
|
|
1233
|
-
const results = checkResults.map((result) => result.status === "fulfilled" ? result.value : {
|
|
1234
|
-
name: "unknown",
|
|
1235
|
-
status: false,
|
|
1236
|
-
error: result.reason
|
|
1237
|
-
});
|
|
1307
|
+
if (!this.healthChecks.length || getCatbeeServerGlobalConfig().skipHealthzChecksValidation) return true;
|
|
1308
|
+
const results = await this.executeHealthChecks();
|
|
1238
1309
|
return results.every((r) => r.status === true);
|
|
1239
1310
|
} catch (err) {
|
|
1240
1311
|
getLogger().error({
|
|
@@ -1279,58 +1350,83 @@ var ExpressServer = class {
|
|
|
1279
1350
|
await this.runHook("beforeStart", this.app);
|
|
1280
1351
|
return new Promise((resolve, reject) => {
|
|
1281
1352
|
try {
|
|
1282
|
-
const
|
|
1283
|
-
this.
|
|
1284
|
-
this.
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
}
|
|
1292
|
-
if (this.config.metrics?.enable && this.config.metrics.path) {
|
|
1293
|
-
getLogger().info(`Metrics available at ${url}${this.normalizePath(this.config.metrics.path, this.config.metrics.withGlobalPrefix)}`);
|
|
1294
|
-
}
|
|
1295
|
-
if (this.config.openApi?.enable) {
|
|
1296
|
-
getLogger().info(`API docs available at ${url}${this.normalizePath(this.config.openApi.mountPath, this.config.openApi.withGlobalPrefix)}`);
|
|
1297
|
-
}
|
|
1298
|
-
if (this.server) await this.runHook("afterStart", this.server);
|
|
1299
|
-
resolve(this.server);
|
|
1300
|
-
}
|
|
1301
|
-
];
|
|
1302
|
-
if (this.config.https) {
|
|
1303
|
-
const httpsOptions = {
|
|
1304
|
-
...this.config.https,
|
|
1305
|
-
key: readFileSync(this.config.https.key),
|
|
1306
|
-
cert: readFileSync(this.config.https.cert)
|
|
1307
|
-
};
|
|
1308
|
-
if (this.config.https.ca) {
|
|
1309
|
-
httpsOptions.ca = readFileSync(this.config.https.ca);
|
|
1310
|
-
}
|
|
1311
|
-
if (this.config.https.passphrase) {
|
|
1312
|
-
httpsOptions.passphrase = this.config.https.passphrase;
|
|
1313
|
-
}
|
|
1314
|
-
this.server = https.createServer(httpsOptions, this.app).listen(...listenArgs);
|
|
1315
|
-
} else {
|
|
1316
|
-
this.server = this.app.listen(...listenArgs);
|
|
1317
|
-
}
|
|
1318
|
-
this.server.on("connection", (conn) => {
|
|
1319
|
-
this.connections.add(conn);
|
|
1320
|
-
conn.on("close", () => this.connections.delete(conn));
|
|
1321
|
-
});
|
|
1322
|
-
this.server.on("error", (err) => {
|
|
1323
|
-
getLogger().error({
|
|
1324
|
-
err
|
|
1325
|
-
}, "Server failed to start");
|
|
1326
|
-
reject(err);
|
|
1327
|
-
});
|
|
1353
|
+
const onListening = /* @__PURE__ */ __name(async () => {
|
|
1354
|
+
this.logServerStartInfo();
|
|
1355
|
+
if (this.server) await this.runHook("afterStart", this.server);
|
|
1356
|
+
resolve(this.server);
|
|
1357
|
+
}, "onListening");
|
|
1358
|
+
this.server = this.createServerInstance(onListening);
|
|
1359
|
+
this.runHook("onServerCreated", this.server);
|
|
1360
|
+
this.setupConnectionTracking();
|
|
1361
|
+
this.setupServerErrorHandling(reject);
|
|
1328
1362
|
} catch (error) {
|
|
1329
1363
|
reject(error);
|
|
1330
1364
|
}
|
|
1331
1365
|
});
|
|
1332
1366
|
}
|
|
1333
1367
|
/**
|
|
1368
|
+
* Create HTTP or HTTPS server instance.
|
|
1369
|
+
*/
|
|
1370
|
+
createServerInstance(onListening) {
|
|
1371
|
+
const listenArgs = [
|
|
1372
|
+
this.config.port,
|
|
1373
|
+
this.config.host,
|
|
1374
|
+
onListening
|
|
1375
|
+
];
|
|
1376
|
+
if (this.config.https) {
|
|
1377
|
+
const httpsOptions = {
|
|
1378
|
+
...this.config.https,
|
|
1379
|
+
key: readFileSync(this.config.https.key),
|
|
1380
|
+
cert: readFileSync(this.config.https.cert)
|
|
1381
|
+
};
|
|
1382
|
+
if (this.config.https.ca) {
|
|
1383
|
+
httpsOptions.ca = readFileSync(this.config.https.ca);
|
|
1384
|
+
}
|
|
1385
|
+
if (this.config.https.passphrase) {
|
|
1386
|
+
httpsOptions.passphrase = this.config.https.passphrase;
|
|
1387
|
+
}
|
|
1388
|
+
return https.createServer(httpsOptions, this.app).listen(...listenArgs);
|
|
1389
|
+
}
|
|
1390
|
+
return this.app.listen(...listenArgs);
|
|
1391
|
+
}
|
|
1392
|
+
/**
|
|
1393
|
+
* Set up connection tracking for graceful shutdown.
|
|
1394
|
+
*/
|
|
1395
|
+
setupConnectionTracking() {
|
|
1396
|
+
this.server.on("connection", (conn) => {
|
|
1397
|
+
this.connections.add(conn);
|
|
1398
|
+
conn.on("close", () => this.connections.delete(conn));
|
|
1399
|
+
});
|
|
1400
|
+
}
|
|
1401
|
+
/**
|
|
1402
|
+
* Set up error handling for server startup.
|
|
1403
|
+
*/
|
|
1404
|
+
setupServerErrorHandling(reject) {
|
|
1405
|
+
this.server.on("error", (err) => {
|
|
1406
|
+
getLogger().error({
|
|
1407
|
+
err
|
|
1408
|
+
}, "Server failed to start");
|
|
1409
|
+
reject(err);
|
|
1410
|
+
});
|
|
1411
|
+
}
|
|
1412
|
+
/**
|
|
1413
|
+
* Log server startup information.
|
|
1414
|
+
*/
|
|
1415
|
+
logServerStartInfo() {
|
|
1416
|
+
const protocol = this.config.https ? "https" : "http";
|
|
1417
|
+
const url = `${protocol}://${this.config.host}:${this.config.port}`;
|
|
1418
|
+
getLogger().info(`Server running on ${url}`);
|
|
1419
|
+
if (this.config.healthCheck?.path) {
|
|
1420
|
+
getLogger().info(`Health check available at ${url}${this.normalizePath(this.config.healthCheck.path, this.config.healthCheck.withGlobalPrefix)}`);
|
|
1421
|
+
}
|
|
1422
|
+
if (this.config.metrics?.enable && this.config.metrics.path) {
|
|
1423
|
+
getLogger().info(`Metrics available at ${url}${this.normalizePath(this.config.metrics.path, this.config.metrics.withGlobalPrefix)}`);
|
|
1424
|
+
}
|
|
1425
|
+
if (this.config.openApi?.enable) {
|
|
1426
|
+
getLogger().info(`API docs available at ${url}${this.normalizePath(this.config.openApi.mountPath, this.config.openApi.withGlobalPrefix)}`);
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
/**
|
|
1334
1430
|
* Stop the HTTP server gracefully.
|
|
1335
1431
|
*
|
|
1336
1432
|
* This method:
|
|
@@ -1357,6 +1453,23 @@ var ExpressServer = class {
|
|
|
1357
1453
|
}
|
|
1358
1454
|
this.isShuttingDown = true;
|
|
1359
1455
|
await this.runHook("beforeStop", this.server);
|
|
1456
|
+
try {
|
|
1457
|
+
await this.gracefulShutdown();
|
|
1458
|
+
} catch (err) {
|
|
1459
|
+
getLogger().error({
|
|
1460
|
+
err
|
|
1461
|
+
}, "Graceful shutdown timed out");
|
|
1462
|
+
if (force) {
|
|
1463
|
+
getLogger().warn("Forcing connection destroy due to shutdown timeout");
|
|
1464
|
+
}
|
|
1465
|
+
} finally {
|
|
1466
|
+
await this.destroyConnections();
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
/**
|
|
1470
|
+
* Perform graceful server shutdown with timeout.
|
|
1471
|
+
*/
|
|
1472
|
+
async gracefulShutdown() {
|
|
1360
1473
|
const shutdownTimeout = 1e4;
|
|
1361
1474
|
const serverClosePromise = new Promise((resolve, reject) => {
|
|
1362
1475
|
this.server.close(async (err) => {
|
|
@@ -1375,21 +1488,10 @@ var ExpressServer = class {
|
|
|
1375
1488
|
});
|
|
1376
1489
|
});
|
|
1377
1490
|
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error("Shutdown timeout")), shutdownTimeout));
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
]);
|
|
1383
|
-
} catch (err) {
|
|
1384
|
-
getLogger().error({
|
|
1385
|
-
err
|
|
1386
|
-
}, "Graceful shutdown timed out");
|
|
1387
|
-
if (force) {
|
|
1388
|
-
getLogger().warn("Forcing connection destroy due to shutdown timeout");
|
|
1389
|
-
}
|
|
1390
|
-
} finally {
|
|
1391
|
-
await this.destroyConnections();
|
|
1392
|
-
}
|
|
1491
|
+
await Promise.race([
|
|
1492
|
+
serverClosePromise,
|
|
1493
|
+
timeoutPromise
|
|
1494
|
+
]);
|
|
1393
1495
|
}
|
|
1394
1496
|
/**
|
|
1395
1497
|
* Enable graceful shutdown on OS signals for production deployment.
|
package/stream/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
|
package/stream/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
|
package/stream/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/string/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
|
|
@@ -93,10 +93,42 @@ function toTitleCase(str) {
|
|
|
93
93
|
return str.split(/(\s+)/).map((part) => part.trim().length === 0 ? part : part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()).join("");
|
|
94
94
|
}
|
|
95
95
|
__name(toTitleCase, "toTitleCase");
|
|
96
|
+
function escapeRegex(str) {
|
|
97
|
+
if (typeof str !== "string") throw new TypeError("Expected a string");
|
|
98
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
99
|
+
}
|
|
100
|
+
__name(escapeRegex, "escapeRegex");
|
|
101
|
+
function unescapeHtml(str) {
|
|
102
|
+
if (typeof str !== "string") return "";
|
|
103
|
+
const entities = {
|
|
104
|
+
"&": "&",
|
|
105
|
+
"<": "<",
|
|
106
|
+
">": ">",
|
|
107
|
+
""": '"',
|
|
108
|
+
"'": "'",
|
|
109
|
+
"'": "'"
|
|
110
|
+
};
|
|
111
|
+
return str.replace(/&(?:amp|lt|gt|quot|#39|#x27);/g, (match) => entities[match] || match);
|
|
112
|
+
}
|
|
113
|
+
__name(unescapeHtml, "unescapeHtml");
|
|
114
|
+
function isBlank(str) {
|
|
115
|
+
return typeof str === "string" && str.trim().length === 0;
|
|
116
|
+
}
|
|
117
|
+
__name(isBlank, "isBlank");
|
|
118
|
+
function ellipsis(str, maxLength, suffix = "...") {
|
|
119
|
+
if (typeof str !== "string" || str.length <= maxLength) return str;
|
|
120
|
+
const truncated = str.slice(0, maxLength - suffix.length);
|
|
121
|
+
const lastSpace = truncated.lastIndexOf(" ");
|
|
122
|
+
return (lastSpace > 0 ? truncated.slice(0, lastSpace) : truncated) + suffix;
|
|
123
|
+
}
|
|
124
|
+
__name(ellipsis, "ellipsis");
|
|
96
125
|
|
|
97
126
|
exports.capitalize = capitalize;
|
|
98
127
|
exports.countOccurrences = countOccurrences;
|
|
128
|
+
exports.ellipsis = ellipsis;
|
|
99
129
|
exports.equalsIgnoreCase = equalsIgnoreCase;
|
|
130
|
+
exports.escapeRegex = escapeRegex;
|
|
131
|
+
exports.isBlank = isBlank;
|
|
100
132
|
exports.mask = mask;
|
|
101
133
|
exports.reverse = reverse;
|
|
102
134
|
exports.slugify = slugify;
|
|
@@ -107,3 +139,4 @@ exports.toPascalCase = toPascalCase;
|
|
|
107
139
|
exports.toSnakeCase = toSnakeCase;
|
|
108
140
|
exports.toTitleCase = toTitleCase;
|
|
109
141
|
exports.truncate = truncate;
|
|
142
|
+
exports.unescapeHtml = unescapeHtml;
|
package/string/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
|
|
@@ -122,5 +122,48 @@ declare function countOccurrences(str: string, substring: string, caseSensitive?
|
|
|
122
122
|
* @returns Title-cased string
|
|
123
123
|
*/
|
|
124
124
|
declare function toTitleCase(str: string): string;
|
|
125
|
+
/**
|
|
126
|
+
* Escapes special regex characters in a string.
|
|
127
|
+
*
|
|
128
|
+
* @param {string} str - The input string.
|
|
129
|
+
* @returns {string} String with regex characters escaped.
|
|
130
|
+
*
|
|
131
|
+
* @example
|
|
132
|
+
* escapeRegex('Hello (world)'); // 'Hello \\(world\\)'
|
|
133
|
+
*/
|
|
134
|
+
declare function escapeRegex(str: string): string;
|
|
135
|
+
/**
|
|
136
|
+
* Unescapes HTML entities in a string.
|
|
137
|
+
*
|
|
138
|
+
* @param {string} str - The HTML string.
|
|
139
|
+
* @returns {string} String with HTML entities unescaped.
|
|
140
|
+
*
|
|
141
|
+
* @example
|
|
142
|
+
* unescapeHtml('<div>Hello</div>'); // '<div>Hello</div>'
|
|
143
|
+
*/
|
|
144
|
+
declare function unescapeHtml(str: string): string;
|
|
145
|
+
/**
|
|
146
|
+
* Checks if a string is blank (empty or only whitespace).
|
|
147
|
+
*
|
|
148
|
+
* @param {string} str - The input string.
|
|
149
|
+
* @returns {boolean} True if blank.
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* isBlank(' '); // true
|
|
153
|
+
* isBlank('hello'); // false
|
|
154
|
+
*/
|
|
155
|
+
declare function isBlank(str: string): boolean;
|
|
156
|
+
/**
|
|
157
|
+
* Truncates a string with an ellipsis, ensuring word boundaries.
|
|
158
|
+
*
|
|
159
|
+
* @param {string} str - The input string.
|
|
160
|
+
* @param {number} maxLength - Maximum length.
|
|
161
|
+
* @param {string} [suffix='...'] - Suffix to append.
|
|
162
|
+
* @returns {string} Truncated string.
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* ellipsis('The quick brown fox', 10); // 'The quick...'
|
|
166
|
+
*/
|
|
167
|
+
declare function ellipsis(str: string, maxLength: number, suffix?: string): string;
|
|
125
168
|
|
|
126
|
-
export { capitalize, countOccurrences, equalsIgnoreCase, mask, reverse, slugify, stripHtml, toCamelCase, toKebabCase, toPascalCase, toSnakeCase, toTitleCase, truncate };
|
|
169
|
+
export { capitalize, countOccurrences, ellipsis, equalsIgnoreCase, escapeRegex, isBlank, mask, reverse, slugify, stripHtml, toCamelCase, toKebabCase, toPascalCase, toSnakeCase, toTitleCase, truncate, unescapeHtml };
|