@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/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';
|
|
@@ -879,6 +879,24 @@ var ExpressServer = class {
|
|
|
879
879
|
if (this.config.https) {
|
|
880
880
|
await this.validateHttpsFiles();
|
|
881
881
|
}
|
|
882
|
+
this.setupBasicMiddleware();
|
|
883
|
+
this.setupSecurityMiddleware();
|
|
884
|
+
this.setupGlobalHeaders();
|
|
885
|
+
this.setupTimeoutMiddleware();
|
|
886
|
+
this.setupResponseTimeMiddleware();
|
|
887
|
+
this.setupRateLimitingMiddleware();
|
|
888
|
+
this.setupRequestLoggingMiddleware();
|
|
889
|
+
this.setupCompressionMiddleware();
|
|
890
|
+
this.setupStaticFilesMiddleware();
|
|
891
|
+
this.setupBodyParsingMiddleware();
|
|
892
|
+
this.setupCookieParsingMiddleware();
|
|
893
|
+
await this.setupOpenApiMiddleware();
|
|
894
|
+
this.setupMetricsMiddleware();
|
|
895
|
+
}
|
|
896
|
+
/**
|
|
897
|
+
* Set up basic middleware (trust proxy, request ID, context).
|
|
898
|
+
*/
|
|
899
|
+
setupBasicMiddleware() {
|
|
882
900
|
this.app.disable("x-powered-by");
|
|
883
901
|
if (this.config.trustProxy) {
|
|
884
902
|
this.app.set("trust proxy", true);
|
|
@@ -895,11 +913,15 @@ var ExpressServer = class {
|
|
|
895
913
|
this.app.use((_req, res, next) => {
|
|
896
914
|
if (this.isShuttingDown) {
|
|
897
915
|
res.setHeader("Connection", "close");
|
|
898
|
-
|
|
916
|
+
res.status(HttpStatusCodes.SERVICE_UNAVAILABLE).json(new ServiceUnavailableException("Server is shutting down"));
|
|
899
917
|
}
|
|
900
918
|
next();
|
|
901
|
-
return;
|
|
902
919
|
});
|
|
920
|
+
}
|
|
921
|
+
/**
|
|
922
|
+
* Set up security middleware (Helmet, CORS).
|
|
923
|
+
*/
|
|
924
|
+
setupSecurityMiddleware() {
|
|
903
925
|
if (this.config.helmet) {
|
|
904
926
|
const helmet = optionalRequire("helmet");
|
|
905
927
|
if (!helmet) {
|
|
@@ -918,6 +940,11 @@ var ExpressServer = class {
|
|
|
918
940
|
}
|
|
919
941
|
this.app.use(cors(this.config.cors === true ? {} : this.config.cors));
|
|
920
942
|
}
|
|
943
|
+
}
|
|
944
|
+
/**
|
|
945
|
+
* Set up global headers middleware.
|
|
946
|
+
*/
|
|
947
|
+
setupGlobalHeaders() {
|
|
921
948
|
this.app.use((_req, res, next) => {
|
|
922
949
|
if (this.config.globalHeaders) {
|
|
923
950
|
for (const key in this.config.globalHeaders) {
|
|
@@ -934,15 +961,30 @@ var ExpressServer = class {
|
|
|
934
961
|
}
|
|
935
962
|
next();
|
|
936
963
|
});
|
|
964
|
+
}
|
|
965
|
+
/**
|
|
966
|
+
* Set up request timeout middleware.
|
|
967
|
+
*/
|
|
968
|
+
setupTimeoutMiddleware() {
|
|
937
969
|
if (this.config.requestTimeout) {
|
|
938
970
|
this.app.use(timeout(this.config.requestTimeout));
|
|
939
971
|
}
|
|
972
|
+
}
|
|
973
|
+
/**
|
|
974
|
+
* Set up response time tracking middleware.
|
|
975
|
+
*/
|
|
976
|
+
setupResponseTimeMiddleware() {
|
|
940
977
|
if (this.config.responseTime?.enable) {
|
|
941
978
|
this.app.use(responseTime({
|
|
942
979
|
addHeader: this.config.responseTime.addHeader,
|
|
943
980
|
logOnComplete: this.config.responseTime.logOnComplete
|
|
944
981
|
}));
|
|
945
982
|
}
|
|
983
|
+
}
|
|
984
|
+
/**
|
|
985
|
+
* Set up rate limiting middleware.
|
|
986
|
+
*/
|
|
987
|
+
setupRateLimitingMiddleware() {
|
|
946
988
|
if (this.config.rateLimit?.enable) {
|
|
947
989
|
const rateLimit = optionalRequire("express-rate-limit");
|
|
948
990
|
if (!rateLimit) {
|
|
@@ -960,6 +1002,11 @@ var ExpressServer = class {
|
|
|
960
1002
|
legacyHeaders: this.config.rateLimit.legacyHeaders ?? false
|
|
961
1003
|
}));
|
|
962
1004
|
}
|
|
1005
|
+
}
|
|
1006
|
+
/**
|
|
1007
|
+
* Set up request logging middleware.
|
|
1008
|
+
*/
|
|
1009
|
+
setupRequestLoggingMiddleware() {
|
|
963
1010
|
if (this.config.requestLogging?.enable) {
|
|
964
1011
|
this.app.use((req, res, next) => {
|
|
965
1012
|
if (typeof this.config.requestLogging?.ignorePaths === "function") {
|
|
@@ -983,6 +1030,11 @@ var ExpressServer = class {
|
|
|
983
1030
|
if (this.hooks.onRequest) {
|
|
984
1031
|
this.app.use(this.hooks.onRequest);
|
|
985
1032
|
}
|
|
1033
|
+
}
|
|
1034
|
+
/**
|
|
1035
|
+
* Set up response compression middleware.
|
|
1036
|
+
*/
|
|
1037
|
+
setupCompressionMiddleware() {
|
|
986
1038
|
if (this.config.compression) {
|
|
987
1039
|
const compression = optionalRequire("compression");
|
|
988
1040
|
if (!compression) {
|
|
@@ -994,6 +1046,11 @@ var ExpressServer = class {
|
|
|
994
1046
|
this.app.use(compression());
|
|
995
1047
|
}
|
|
996
1048
|
}
|
|
1049
|
+
}
|
|
1050
|
+
/**
|
|
1051
|
+
* Set up static file serving middleware.
|
|
1052
|
+
*/
|
|
1053
|
+
setupStaticFilesMiddleware() {
|
|
997
1054
|
if (this.config.staticFolders) {
|
|
998
1055
|
this.config.staticFolders.forEach((folder) => {
|
|
999
1056
|
this.app.use(this.normalizePath(folder.path ?? "/"), express.static(folder.directory, {
|
|
@@ -1006,6 +1063,11 @@ var ExpressServer = class {
|
|
|
1006
1063
|
getLogger().info(`Serving static folder: ${folder.directory} at path ${folder.path || "/"}`);
|
|
1007
1064
|
});
|
|
1008
1065
|
}
|
|
1066
|
+
}
|
|
1067
|
+
/**
|
|
1068
|
+
* Set up body parsing middleware.
|
|
1069
|
+
*/
|
|
1070
|
+
setupBodyParsingMiddleware() {
|
|
1009
1071
|
if (this.config.bodyParser) {
|
|
1010
1072
|
if (this.config.bodyParser.json) {
|
|
1011
1073
|
this.app.use(express.json(this.config.bodyParser.json));
|
|
@@ -1014,6 +1076,11 @@ var ExpressServer = class {
|
|
|
1014
1076
|
this.app.use(express.urlencoded(this.config.bodyParser.urlencoded));
|
|
1015
1077
|
}
|
|
1016
1078
|
}
|
|
1079
|
+
}
|
|
1080
|
+
/**
|
|
1081
|
+
* Set up cookie parsing middleware.
|
|
1082
|
+
*/
|
|
1083
|
+
setupCookieParsingMiddleware() {
|
|
1017
1084
|
if (this.config.cookieParser) {
|
|
1018
1085
|
const cookieParser = optionalRequire("cookie-parser");
|
|
1019
1086
|
if (!cookieParser) {
|
|
@@ -1025,6 +1092,11 @@ var ExpressServer = class {
|
|
|
1025
1092
|
this.app.use(cookieParser());
|
|
1026
1093
|
}
|
|
1027
1094
|
}
|
|
1095
|
+
}
|
|
1096
|
+
/**
|
|
1097
|
+
* Set up OpenAPI documentation middleware.
|
|
1098
|
+
*/
|
|
1099
|
+
async setupOpenApiMiddleware() {
|
|
1028
1100
|
if (this.config.openApi?.enable) {
|
|
1029
1101
|
try {
|
|
1030
1102
|
const openApiMountPath = this.normalizePath(this.config.openApi.mountPath ?? "/docs", this.config.openApi.withGlobalPrefix);
|
|
@@ -1065,6 +1137,11 @@ var ExpressServer = class {
|
|
|
1065
1137
|
if (this.hooks.onResponse) {
|
|
1066
1138
|
this.app.use(this.globalPrefix, this.hooks.onResponse);
|
|
1067
1139
|
}
|
|
1140
|
+
}
|
|
1141
|
+
/**
|
|
1142
|
+
* Set up metrics tracking middleware.
|
|
1143
|
+
*/
|
|
1144
|
+
setupMetricsMiddleware() {
|
|
1068
1145
|
if (this.config.metrics?.enable) {
|
|
1069
1146
|
this.app.use((req, res, next) => {
|
|
1070
1147
|
const start = process.hrtime();
|
|
@@ -1113,45 +1190,7 @@ var ExpressServer = class {
|
|
|
1113
1190
|
async setupRoutes() {
|
|
1114
1191
|
const healthCheckPath = this.normalizePath(this.config.healthCheck?.path || "/healthz", this.config.healthCheck?.withGlobalPrefix);
|
|
1115
1192
|
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
|
-
}
|
|
1193
|
+
return this.handleHealthCheckRequest(res);
|
|
1155
1194
|
});
|
|
1156
1195
|
if (this.config.metrics?.enable) {
|
|
1157
1196
|
const metricsPath = this.normalizePath(this.config.metrics.path ?? "/metrics", this.config.metrics?.withGlobalPrefix);
|
|
@@ -1183,6 +1222,56 @@ var ExpressServer = class {
|
|
|
1183
1222
|
});
|
|
1184
1223
|
}
|
|
1185
1224
|
/**
|
|
1225
|
+
* Execute health check and return response.
|
|
1226
|
+
*/
|
|
1227
|
+
async handleHealthCheckRequest(res) {
|
|
1228
|
+
try {
|
|
1229
|
+
if (!this.healthChecks.length || getCatbeeServerGlobalConfig().skipHealthzChecksValidation) {
|
|
1230
|
+
return res.status(HttpStatusCodes.OK).json(new SuccessResponse("OK"));
|
|
1231
|
+
}
|
|
1232
|
+
const results = await this.executeHealthChecks();
|
|
1233
|
+
const allOk = results.every((r) => r.status);
|
|
1234
|
+
const status = allOk ? HttpStatusCodes.OK : HttpStatusCodes.SERVICE_UNAVAILABLE;
|
|
1235
|
+
const response = new SuccessResponse(allOk ? "OK" : "Service unavailable");
|
|
1236
|
+
if (!allOk) response.error = true;
|
|
1237
|
+
if (this.config.healthCheck?.detailed) response.data = {
|
|
1238
|
+
checks: results
|
|
1239
|
+
};
|
|
1240
|
+
return res.status(status).json(response);
|
|
1241
|
+
} catch {
|
|
1242
|
+
return res.status(HttpStatusCodes.INTERNAL_SERVER_ERROR).json(new InternalServerErrorException("Health check failed"));
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
/**
|
|
1246
|
+
* Execute all registered health checks and return results.
|
|
1247
|
+
*/
|
|
1248
|
+
async executeHealthChecks() {
|
|
1249
|
+
const checkResults = await Promise.allSettled(this.healthChecks.map(async ({ name, check }) => {
|
|
1250
|
+
try {
|
|
1251
|
+
const status = await Promise.resolve(check());
|
|
1252
|
+
return {
|
|
1253
|
+
name,
|
|
1254
|
+
status,
|
|
1255
|
+
error: null
|
|
1256
|
+
};
|
|
1257
|
+
} catch (error) {
|
|
1258
|
+
return {
|
|
1259
|
+
name,
|
|
1260
|
+
status: false,
|
|
1261
|
+
error: error.message
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
}));
|
|
1265
|
+
return checkResults.map((result) => {
|
|
1266
|
+
if (result.status === "fulfilled") return result.value;
|
|
1267
|
+
return {
|
|
1268
|
+
name: "unknown",
|
|
1269
|
+
status: false,
|
|
1270
|
+
error: result.reason
|
|
1271
|
+
};
|
|
1272
|
+
});
|
|
1273
|
+
}
|
|
1274
|
+
/**
|
|
1186
1275
|
* Register a new health check function for monitoring service dependencies.
|
|
1187
1276
|
*
|
|
1188
1277
|
* Health checks are executed when the health endpoint is accessed and
|
|
@@ -1213,28 +1302,8 @@ var ExpressServer = class {
|
|
|
1213
1302
|
*/
|
|
1214
1303
|
async ready() {
|
|
1215
1304
|
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
|
-
});
|
|
1305
|
+
if (!this.healthChecks.length || getCatbeeServerGlobalConfig().skipHealthzChecksValidation) return true;
|
|
1306
|
+
const results = await this.executeHealthChecks();
|
|
1238
1307
|
return results.every((r) => r.status === true);
|
|
1239
1308
|
} catch (err) {
|
|
1240
1309
|
getLogger().error({
|
|
@@ -1279,58 +1348,82 @@ var ExpressServer = class {
|
|
|
1279
1348
|
await this.runHook("beforeStart", this.app);
|
|
1280
1349
|
return new Promise((resolve, reject) => {
|
|
1281
1350
|
try {
|
|
1282
|
-
const
|
|
1283
|
-
this.
|
|
1284
|
-
this.
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
getLogger().info(`Health check available at ${url}${this.normalizePath(this.config.healthCheck.path, this.config.healthCheck.withGlobalPrefix)}`);
|
|
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
|
-
});
|
|
1351
|
+
const onListening = /* @__PURE__ */ __name(async () => {
|
|
1352
|
+
this.logServerStartInfo();
|
|
1353
|
+
if (this.server) await this.runHook("afterStart", this.server);
|
|
1354
|
+
resolve(this.server);
|
|
1355
|
+
}, "onListening");
|
|
1356
|
+
this.server = this.createServerInstance(onListening);
|
|
1357
|
+
this.setupConnectionTracking();
|
|
1358
|
+
this.setupServerErrorHandling(reject);
|
|
1328
1359
|
} catch (error) {
|
|
1329
1360
|
reject(error);
|
|
1330
1361
|
}
|
|
1331
1362
|
});
|
|
1332
1363
|
}
|
|
1333
1364
|
/**
|
|
1365
|
+
* Create HTTP or HTTPS server instance.
|
|
1366
|
+
*/
|
|
1367
|
+
createServerInstance(onListening) {
|
|
1368
|
+
const listenArgs = [
|
|
1369
|
+
this.config.port,
|
|
1370
|
+
this.config.host,
|
|
1371
|
+
onListening
|
|
1372
|
+
];
|
|
1373
|
+
if (this.config.https) {
|
|
1374
|
+
const httpsOptions = {
|
|
1375
|
+
...this.config.https,
|
|
1376
|
+
key: readFileSync(this.config.https.key),
|
|
1377
|
+
cert: readFileSync(this.config.https.cert)
|
|
1378
|
+
};
|
|
1379
|
+
if (this.config.https.ca) {
|
|
1380
|
+
httpsOptions.ca = readFileSync(this.config.https.ca);
|
|
1381
|
+
}
|
|
1382
|
+
if (this.config.https.passphrase) {
|
|
1383
|
+
httpsOptions.passphrase = this.config.https.passphrase;
|
|
1384
|
+
}
|
|
1385
|
+
return https.createServer(httpsOptions, this.app).listen(...listenArgs);
|
|
1386
|
+
}
|
|
1387
|
+
return this.app.listen(...listenArgs);
|
|
1388
|
+
}
|
|
1389
|
+
/**
|
|
1390
|
+
* Set up connection tracking for graceful shutdown.
|
|
1391
|
+
*/
|
|
1392
|
+
setupConnectionTracking() {
|
|
1393
|
+
this.server.on("connection", (conn) => {
|
|
1394
|
+
this.connections.add(conn);
|
|
1395
|
+
conn.on("close", () => this.connections.delete(conn));
|
|
1396
|
+
});
|
|
1397
|
+
}
|
|
1398
|
+
/**
|
|
1399
|
+
* Set up error handling for server startup.
|
|
1400
|
+
*/
|
|
1401
|
+
setupServerErrorHandling(reject) {
|
|
1402
|
+
this.server.on("error", (err) => {
|
|
1403
|
+
getLogger().error({
|
|
1404
|
+
err
|
|
1405
|
+
}, "Server failed to start");
|
|
1406
|
+
reject(err);
|
|
1407
|
+
});
|
|
1408
|
+
}
|
|
1409
|
+
/**
|
|
1410
|
+
* Log server startup information.
|
|
1411
|
+
*/
|
|
1412
|
+
logServerStartInfo() {
|
|
1413
|
+
const protocol = this.config.https ? "https" : "http";
|
|
1414
|
+
const url = `${protocol}://${this.config.host}:${this.config.port}`;
|
|
1415
|
+
getLogger().info(`Server running on ${url}`);
|
|
1416
|
+
if (this.config.healthCheck?.path) {
|
|
1417
|
+
getLogger().info(`Health check available at ${url}${this.normalizePath(this.config.healthCheck.path, this.config.healthCheck.withGlobalPrefix)}`);
|
|
1418
|
+
}
|
|
1419
|
+
if (this.config.metrics?.enable && this.config.metrics.path) {
|
|
1420
|
+
getLogger().info(`Metrics available at ${url}${this.normalizePath(this.config.metrics.path, this.config.metrics.withGlobalPrefix)}`);
|
|
1421
|
+
}
|
|
1422
|
+
if (this.config.openApi?.enable) {
|
|
1423
|
+
getLogger().info(`API docs available at ${url}${this.normalizePath(this.config.openApi.mountPath, this.config.openApi.withGlobalPrefix)}`);
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
/**
|
|
1334
1427
|
* Stop the HTTP server gracefully.
|
|
1335
1428
|
*
|
|
1336
1429
|
* This method:
|
|
@@ -1357,6 +1450,23 @@ var ExpressServer = class {
|
|
|
1357
1450
|
}
|
|
1358
1451
|
this.isShuttingDown = true;
|
|
1359
1452
|
await this.runHook("beforeStop", this.server);
|
|
1453
|
+
try {
|
|
1454
|
+
await this.gracefulShutdown();
|
|
1455
|
+
} catch (err) {
|
|
1456
|
+
getLogger().error({
|
|
1457
|
+
err
|
|
1458
|
+
}, "Graceful shutdown timed out");
|
|
1459
|
+
if (force) {
|
|
1460
|
+
getLogger().warn("Forcing connection destroy due to shutdown timeout");
|
|
1461
|
+
}
|
|
1462
|
+
} finally {
|
|
1463
|
+
await this.destroyConnections();
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
/**
|
|
1467
|
+
* Perform graceful server shutdown with timeout.
|
|
1468
|
+
*/
|
|
1469
|
+
async gracefulShutdown() {
|
|
1360
1470
|
const shutdownTimeout = 1e4;
|
|
1361
1471
|
const serverClosePromise = new Promise((resolve, reject) => {
|
|
1362
1472
|
this.server.close(async (err) => {
|
|
@@ -1375,21 +1485,10 @@ var ExpressServer = class {
|
|
|
1375
1485
|
});
|
|
1376
1486
|
});
|
|
1377
1487
|
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
|
-
}
|
|
1488
|
+
await Promise.race([
|
|
1489
|
+
serverClosePromise,
|
|
1490
|
+
timeoutPromise
|
|
1491
|
+
]);
|
|
1393
1492
|
}
|
|
1394
1493
|
/**
|
|
1395
1494
|
* 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 };
|
package/string/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
|
|
@@ -91,5 +91,34 @@ function toTitleCase(str) {
|
|
|
91
91
|
return str.split(/(\s+)/).map((part) => part.trim().length === 0 ? part : part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()).join("");
|
|
92
92
|
}
|
|
93
93
|
__name(toTitleCase, "toTitleCase");
|
|
94
|
+
function escapeRegex(str) {
|
|
95
|
+
if (typeof str !== "string") throw new TypeError("Expected a string");
|
|
96
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
97
|
+
}
|
|
98
|
+
__name(escapeRegex, "escapeRegex");
|
|
99
|
+
function unescapeHtml(str) {
|
|
100
|
+
if (typeof str !== "string") return "";
|
|
101
|
+
const entities = {
|
|
102
|
+
"&": "&",
|
|
103
|
+
"<": "<",
|
|
104
|
+
">": ">",
|
|
105
|
+
""": '"',
|
|
106
|
+
"'": "'",
|
|
107
|
+
"'": "'"
|
|
108
|
+
};
|
|
109
|
+
return str.replace(/&(?:amp|lt|gt|quot|#39|#x27);/g, (match) => entities[match] || match);
|
|
110
|
+
}
|
|
111
|
+
__name(unescapeHtml, "unescapeHtml");
|
|
112
|
+
function isBlank(str) {
|
|
113
|
+
return typeof str === "string" && str.trim().length === 0;
|
|
114
|
+
}
|
|
115
|
+
__name(isBlank, "isBlank");
|
|
116
|
+
function ellipsis(str, maxLength, suffix = "...") {
|
|
117
|
+
if (typeof str !== "string" || str.length <= maxLength) return str;
|
|
118
|
+
const truncated = str.slice(0, maxLength - suffix.length);
|
|
119
|
+
const lastSpace = truncated.lastIndexOf(" ");
|
|
120
|
+
return (lastSpace > 0 ? truncated.slice(0, lastSpace) : truncated) + suffix;
|
|
121
|
+
}
|
|
122
|
+
__name(ellipsis, "ellipsis");
|
|
94
123
|
|
|
95
|
-
export { capitalize, countOccurrences, equalsIgnoreCase, mask, reverse, slugify, stripHtml, toCamelCase, toKebabCase, toPascalCase, toSnakeCase, toTitleCase, truncate };
|
|
124
|
+
export { capitalize, countOccurrences, ellipsis, equalsIgnoreCase, escapeRegex, isBlank, mask, reverse, slugify, stripHtml, toCamelCase, toKebabCase, toPascalCase, toSnakeCase, toTitleCase, truncate, unescapeHtml };
|