@catbee/utils 2.0.0-next.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +102 -52
- package/array/index.cjs +215 -74
- package/array/index.d.ts +345 -2
- package/array/index.mjs +201 -74
- package/async/index.cjs +116 -39
- package/async/index.d.ts +292 -2
- package/async/index.mjs +116 -40
- package/cache/index.cjs +2 -2
- package/cache/index.d.ts +156 -2
- package/cache/index.mjs +3 -3
- package/config/index.cjs +80 -66
- package/config/index.d.ts +65 -3
- package/config/index.mjs +77 -65
- package/context-store/index.cjs +2 -3
- package/context-store/index.d.ts +193 -2
- package/context-store/index.mjs +2 -3
- package/crypto/index.cjs +55 -5
- package/crypto/index.d.ts +225 -2
- package/crypto/index.mjs +52 -7
- package/date/index.cjs +676 -2
- package/date/index.d.ts +676 -2
- package/date/index.mjs +665 -3
- package/decorator/index.cjs +2172 -0
- package/{decorators/decorators.utils.d.ts → decorator/index.d.ts} +58 -54
- package/decorator/index.mjs +2131 -0
- package/{dir → directory}/index.cjs +5 -4
- package/{dir/dir.utils.d.ts → directory/index.d.ts} +24 -21
- package/{dir → directory}/index.mjs +5 -4
- package/env/index.cjs +100 -68
- package/env/index.d.ts +391 -2
- package/env/index.mjs +100 -68
- package/exception/index.cjs +1 -1
- package/exception/index.d.ts +233 -2
- package/exception/index.mjs +1 -1
- package/fs/index.cjs +71 -37
- package/fs/index.d.ts +206 -2
- package/fs/index.mjs +65 -35
- package/http-status-codes/index.cjs +1 -1
- package/http-status-codes/index.d.ts +268 -2
- package/http-status-codes/index.mjs +1 -1
- package/id/index.cjs +1 -1
- package/id/index.d.ts +38 -2
- package/id/index.mjs +1 -1
- package/index.cjs +13 -13
- package/index.d.ts +5 -5
- package/index.mjs +5 -5
- package/logger/index.cjs +13 -15
- package/logger/index.d.ts +190 -2
- package/logger/index.mjs +14 -16
- package/middleware/index.cjs +1 -1
- package/middleware/index.d.ts +104 -2
- package/middleware/index.mjs +1 -1
- package/object/index.cjs +379 -0
- package/{obj/obj.utils.d.ts → object/index.d.ts} +73 -33
- package/object/index.mjs +360 -0
- package/package.json +41 -23
- package/performance/index.cjs +4 -4
- package/performance/index.d.ts +139 -2
- package/performance/index.mjs +4 -4
- package/request/index.cjs +37 -25
- package/request/index.d.ts +242 -3
- package/request/index.mjs +37 -25
- package/response/index.cjs +1 -1
- package/response/index.d.ts +319 -3
- package/response/index.mjs +1 -1
- package/server/index.cjs +249 -146
- package/server/index.d.ts +866 -5
- package/server/index.mjs +248 -144
- package/stream/index.cjs +1 -1
- package/stream/index.d.ts +91 -2
- package/stream/index.mjs +1 -1
- package/string/index.cjs +34 -1
- package/string/index.d.ts +146 -2
- package/string/index.mjs +31 -2
- package/type/index.cjs +19 -2
- package/type/index.d.ts +144 -2
- package/type/index.mjs +17 -3
- package/types/index.cjs +1 -1
- package/types/index.d.ts +775 -5
- package/types/index.mjs +1 -1
- package/url/index.cjs +63 -5
- package/url/index.d.ts +200 -2
- package/url/index.mjs +59 -6
- package/{validate → validation}/index.cjs +91 -44
- package/{validate/validate.utils.d.ts → validation/index.d.ts} +33 -24
- package/{validate → validation}/index.mjs +87 -44
- package/array/array.utils.d.ts +0 -191
- package/async/async.utils.d.ts +0 -296
- package/cache/cache.utils.d.ts +0 -176
- package/config/config.d.ts +0 -57
- package/context-store/context-store.utils.d.ts +0 -212
- package/crypto/crypto.utils.d.ts +0 -183
- package/date/date.utils.d.ts +0 -190
- package/decorators/index.cjs +0 -913
- package/decorators/index.d.ts +0 -25
- package/decorators/index.mjs +0 -872
- package/dir/index.d.ts +0 -25
- package/env/env.utils.d.ts +0 -400
- package/exception/exception.utils.d.ts +0 -253
- package/fs/fs.utils.d.ts +0 -196
- package/http-status-codes/http-status-codes.d.ts +0 -289
- package/id/id.utils.d.ts +0 -59
- package/logger/logger.utils.d.ts +0 -210
- package/middleware/middleware.utils.d.ts +0 -123
- package/obj/index.cjs +0 -317
- package/obj/index.d.ts +0 -25
- package/obj/index.mjs +0 -301
- package/performance/performance.utils.d.ts +0 -159
- package/request/request.utils.d.ts +0 -109
- package/response/response.utils.d.ts +0 -186
- package/server/server.builder.d.ts +0 -531
- package/server/server.d.ts +0 -303
- package/stream/stream.utils.d.ts +0 -111
- package/string/string.utils.d.ts +0 -124
- package/type/type.utils.d.ts +0 -129
- package/types/api-response.d.ts +0 -175
- package/types/common.d.ts +0 -148
- package/types/config.d.ts +0 -88
- package/types/server.d.ts +0 -291
- package/url/url.utils.d.ts +0 -164
- package/validate/index.d.ts +0 -25
package/server/index.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,17 +25,17 @@
|
|
|
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
|
-
import
|
|
34
|
-
import {
|
|
35
|
-
import {
|
|
36
|
-
import {
|
|
37
|
-
import { isPort } from '@catbee/utils/validate';
|
|
33
|
+
import { getCatbeeServerGlobalConfig } from '@catbee/utils/config';
|
|
34
|
+
import { deepObjMerge, deepClone } from '@catbee/utils/object';
|
|
35
|
+
import { fileExists, readFile, readFileSync } from '@catbee/utils/fs';
|
|
36
|
+
import { isPort } from '@catbee/utils/validation';
|
|
38
37
|
import { optionalRequire } from '@catbee/utils/async';
|
|
38
|
+
import { uuid } from '@catbee/utils/id';
|
|
39
39
|
|
|
40
40
|
var __defProp = Object.defineProperty;
|
|
41
41
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
@@ -498,7 +498,10 @@ var ServerConfigBuilder = class {
|
|
|
498
498
|
* ```
|
|
499
499
|
*/
|
|
500
500
|
withBodyParser(opts) {
|
|
501
|
-
this.config.bodyParser =
|
|
501
|
+
this.config.bodyParser = {
|
|
502
|
+
...this.config.bodyParser,
|
|
503
|
+
...opts
|
|
504
|
+
};
|
|
502
505
|
return this;
|
|
503
506
|
}
|
|
504
507
|
/**
|
|
@@ -641,7 +644,7 @@ var ServerConfigBuilder = class {
|
|
|
641
644
|
* ```
|
|
642
645
|
*/
|
|
643
646
|
build() {
|
|
644
|
-
const config = deepObjMerge({},
|
|
647
|
+
const config = deepObjMerge({}, getCatbeeServerGlobalConfig(), this.config);
|
|
645
648
|
if (config.openApi?.enable && !config.openApi.filePath) {
|
|
646
649
|
throw new Error("OpenAPI is enabled but no filePath is specified");
|
|
647
650
|
}
|
|
@@ -651,7 +654,7 @@ var ServerConfigBuilder = class {
|
|
|
651
654
|
});
|
|
652
655
|
}
|
|
653
656
|
mergeConfig(key, value) {
|
|
654
|
-
const current =
|
|
657
|
+
const current = this.config[key] && typeof this.config[key] === "object" ? deepClone(this.config[key]) : {};
|
|
655
658
|
this.config[key] = deepObjMerge({}, current, value);
|
|
656
659
|
}
|
|
657
660
|
setEnabled(key, enable, overrides = {}) {
|
|
@@ -662,9 +665,11 @@ var ServerConfigBuilder = class {
|
|
|
662
665
|
return this;
|
|
663
666
|
}
|
|
664
667
|
};
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
+
var getDependencyErrorMessage = /* @__PURE__ */ __name((packageName, context) => {
|
|
669
|
+
const ctxPart = context ? `for ${context}` : "";
|
|
670
|
+
const spacer = ctxPart ? ` ${ctxPart}` : "";
|
|
671
|
+
return `Missing required dependency${spacer}: ${packageName}. Please install it to proceed.`;
|
|
672
|
+
}, "getDependencyErrorMessage");
|
|
668
673
|
var DependencyErrors = {
|
|
669
674
|
express: getDependencyErrorMessage("express"),
|
|
670
675
|
helmet: getDependencyErrorMessage("helmet"),
|
|
@@ -729,10 +734,10 @@ var ExpressServer = class {
|
|
|
729
734
|
* - Request tracing
|
|
730
735
|
*/
|
|
731
736
|
constructor(config, hooks = {}) {
|
|
732
|
-
if (this.
|
|
737
|
+
if (this.hasBuildMarker(config)) {
|
|
733
738
|
this.config = config;
|
|
734
739
|
} else {
|
|
735
|
-
this.config = deepObjMerge({},
|
|
740
|
+
this.config = deepObjMerge({}, getCatbeeServerGlobalConfig(), config);
|
|
736
741
|
}
|
|
737
742
|
if (!isPort(this.config.port)) {
|
|
738
743
|
const msg = `Port must be a valid number between 1 and 65535, got: ${this.config.port}`;
|
|
@@ -834,7 +839,7 @@ var ExpressServer = class {
|
|
|
834
839
|
async runHook(hook, ...args) {
|
|
835
840
|
try {
|
|
836
841
|
const fn = this.hooks[hook];
|
|
837
|
-
if (fn) await fn
|
|
842
|
+
if (fn) await fn(...args);
|
|
838
843
|
} catch (err) {
|
|
839
844
|
getLogger().error({
|
|
840
845
|
err,
|
|
@@ -874,6 +879,24 @@ var ExpressServer = class {
|
|
|
874
879
|
if (this.config.https) {
|
|
875
880
|
await this.validateHttpsFiles();
|
|
876
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() {
|
|
877
900
|
this.app.disable("x-powered-by");
|
|
878
901
|
if (this.config.trustProxy) {
|
|
879
902
|
this.app.set("trust proxy", true);
|
|
@@ -881,7 +904,7 @@ var ExpressServer = class {
|
|
|
881
904
|
this.app.use(requestId({
|
|
882
905
|
headerName: this.config.requestId?.headerName,
|
|
883
906
|
exposeHeader: this.config.requestId?.exposeHeader,
|
|
884
|
-
generator: this.config.requestId?.generator
|
|
907
|
+
generator: this.config.requestId?.generator || uuid
|
|
885
908
|
}));
|
|
886
909
|
this.app.use(setupRequestContext({
|
|
887
910
|
headerName: this.config.requestId?.headerName,
|
|
@@ -890,11 +913,15 @@ var ExpressServer = class {
|
|
|
890
913
|
this.app.use((_req, res, next) => {
|
|
891
914
|
if (this.isShuttingDown) {
|
|
892
915
|
res.setHeader("Connection", "close");
|
|
893
|
-
|
|
916
|
+
res.status(HttpStatusCodes.SERVICE_UNAVAILABLE).json(new ServiceUnavailableException("Server is shutting down"));
|
|
894
917
|
}
|
|
895
918
|
next();
|
|
896
|
-
return;
|
|
897
919
|
});
|
|
920
|
+
}
|
|
921
|
+
/**
|
|
922
|
+
* Set up security middleware (Helmet, CORS).
|
|
923
|
+
*/
|
|
924
|
+
setupSecurityMiddleware() {
|
|
898
925
|
if (this.config.helmet) {
|
|
899
926
|
const helmet = optionalRequire("helmet");
|
|
900
927
|
if (!helmet) {
|
|
@@ -913,6 +940,11 @@ var ExpressServer = class {
|
|
|
913
940
|
}
|
|
914
941
|
this.app.use(cors(this.config.cors === true ? {} : this.config.cors));
|
|
915
942
|
}
|
|
943
|
+
}
|
|
944
|
+
/**
|
|
945
|
+
* Set up global headers middleware.
|
|
946
|
+
*/
|
|
947
|
+
setupGlobalHeaders() {
|
|
916
948
|
this.app.use((_req, res, next) => {
|
|
917
949
|
if (this.config.globalHeaders) {
|
|
918
950
|
for (const key in this.config.globalHeaders) {
|
|
@@ -929,15 +961,30 @@ var ExpressServer = class {
|
|
|
929
961
|
}
|
|
930
962
|
next();
|
|
931
963
|
});
|
|
964
|
+
}
|
|
965
|
+
/**
|
|
966
|
+
* Set up request timeout middleware.
|
|
967
|
+
*/
|
|
968
|
+
setupTimeoutMiddleware() {
|
|
932
969
|
if (this.config.requestTimeout) {
|
|
933
970
|
this.app.use(timeout(this.config.requestTimeout));
|
|
934
971
|
}
|
|
972
|
+
}
|
|
973
|
+
/**
|
|
974
|
+
* Set up response time tracking middleware.
|
|
975
|
+
*/
|
|
976
|
+
setupResponseTimeMiddleware() {
|
|
935
977
|
if (this.config.responseTime?.enable) {
|
|
936
978
|
this.app.use(responseTime({
|
|
937
979
|
addHeader: this.config.responseTime.addHeader,
|
|
938
980
|
logOnComplete: this.config.responseTime.logOnComplete
|
|
939
981
|
}));
|
|
940
982
|
}
|
|
983
|
+
}
|
|
984
|
+
/**
|
|
985
|
+
* Set up rate limiting middleware.
|
|
986
|
+
*/
|
|
987
|
+
setupRateLimitingMiddleware() {
|
|
941
988
|
if (this.config.rateLimit?.enable) {
|
|
942
989
|
const rateLimit = optionalRequire("express-rate-limit");
|
|
943
990
|
if (!rateLimit) {
|
|
@@ -955,6 +1002,11 @@ var ExpressServer = class {
|
|
|
955
1002
|
legacyHeaders: this.config.rateLimit.legacyHeaders ?? false
|
|
956
1003
|
}));
|
|
957
1004
|
}
|
|
1005
|
+
}
|
|
1006
|
+
/**
|
|
1007
|
+
* Set up request logging middleware.
|
|
1008
|
+
*/
|
|
1009
|
+
setupRequestLoggingMiddleware() {
|
|
958
1010
|
if (this.config.requestLogging?.enable) {
|
|
959
1011
|
this.app.use((req, res, next) => {
|
|
960
1012
|
if (typeof this.config.requestLogging?.ignorePaths === "function") {
|
|
@@ -978,6 +1030,11 @@ var ExpressServer = class {
|
|
|
978
1030
|
if (this.hooks.onRequest) {
|
|
979
1031
|
this.app.use(this.hooks.onRequest);
|
|
980
1032
|
}
|
|
1033
|
+
}
|
|
1034
|
+
/**
|
|
1035
|
+
* Set up response compression middleware.
|
|
1036
|
+
*/
|
|
1037
|
+
setupCompressionMiddleware() {
|
|
981
1038
|
if (this.config.compression) {
|
|
982
1039
|
const compression = optionalRequire("compression");
|
|
983
1040
|
if (!compression) {
|
|
@@ -989,6 +1046,11 @@ var ExpressServer = class {
|
|
|
989
1046
|
this.app.use(compression());
|
|
990
1047
|
}
|
|
991
1048
|
}
|
|
1049
|
+
}
|
|
1050
|
+
/**
|
|
1051
|
+
* Set up static file serving middleware.
|
|
1052
|
+
*/
|
|
1053
|
+
setupStaticFilesMiddleware() {
|
|
992
1054
|
if (this.config.staticFolders) {
|
|
993
1055
|
this.config.staticFolders.forEach((folder) => {
|
|
994
1056
|
this.app.use(this.normalizePath(folder.path ?? "/"), express.static(folder.directory, {
|
|
@@ -1001,6 +1063,11 @@ var ExpressServer = class {
|
|
|
1001
1063
|
getLogger().info(`Serving static folder: ${folder.directory} at path ${folder.path || "/"}`);
|
|
1002
1064
|
});
|
|
1003
1065
|
}
|
|
1066
|
+
}
|
|
1067
|
+
/**
|
|
1068
|
+
* Set up body parsing middleware.
|
|
1069
|
+
*/
|
|
1070
|
+
setupBodyParsingMiddleware() {
|
|
1004
1071
|
if (this.config.bodyParser) {
|
|
1005
1072
|
if (this.config.bodyParser.json) {
|
|
1006
1073
|
this.app.use(express.json(this.config.bodyParser.json));
|
|
@@ -1009,6 +1076,11 @@ var ExpressServer = class {
|
|
|
1009
1076
|
this.app.use(express.urlencoded(this.config.bodyParser.urlencoded));
|
|
1010
1077
|
}
|
|
1011
1078
|
}
|
|
1079
|
+
}
|
|
1080
|
+
/**
|
|
1081
|
+
* Set up cookie parsing middleware.
|
|
1082
|
+
*/
|
|
1083
|
+
setupCookieParsingMiddleware() {
|
|
1012
1084
|
if (this.config.cookieParser) {
|
|
1013
1085
|
const cookieParser = optionalRequire("cookie-parser");
|
|
1014
1086
|
if (!cookieParser) {
|
|
@@ -1020,6 +1092,11 @@ var ExpressServer = class {
|
|
|
1020
1092
|
this.app.use(cookieParser());
|
|
1021
1093
|
}
|
|
1022
1094
|
}
|
|
1095
|
+
}
|
|
1096
|
+
/**
|
|
1097
|
+
* Set up OpenAPI documentation middleware.
|
|
1098
|
+
*/
|
|
1099
|
+
async setupOpenApiMiddleware() {
|
|
1023
1100
|
if (this.config.openApi?.enable) {
|
|
1024
1101
|
try {
|
|
1025
1102
|
const openApiMountPath = this.normalizePath(this.config.openApi.mountPath ?? "/docs", this.config.openApi.withGlobalPrefix);
|
|
@@ -1045,7 +1122,7 @@ var ExpressServer = class {
|
|
|
1045
1122
|
}
|
|
1046
1123
|
this.app.use(openApiMountPath, apiReference({
|
|
1047
1124
|
spec: {
|
|
1048
|
-
content: await
|
|
1125
|
+
content: await readFile(openApiFilePath, "utf8")
|
|
1049
1126
|
}
|
|
1050
1127
|
}));
|
|
1051
1128
|
if (this.config.openApi?.verbose) {
|
|
@@ -1060,6 +1137,11 @@ var ExpressServer = class {
|
|
|
1060
1137
|
if (this.hooks.onResponse) {
|
|
1061
1138
|
this.app.use(this.globalPrefix, this.hooks.onResponse);
|
|
1062
1139
|
}
|
|
1140
|
+
}
|
|
1141
|
+
/**
|
|
1142
|
+
* Set up metrics tracking middleware.
|
|
1143
|
+
*/
|
|
1144
|
+
setupMetricsMiddleware() {
|
|
1063
1145
|
if (this.config.metrics?.enable) {
|
|
1064
1146
|
this.app.use((req, res, next) => {
|
|
1065
1147
|
const start = process.hrtime();
|
|
@@ -1108,45 +1190,7 @@ var ExpressServer = class {
|
|
|
1108
1190
|
async setupRoutes() {
|
|
1109
1191
|
const healthCheckPath = this.normalizePath(this.config.healthCheck?.path || "/healthz", this.config.healthCheck?.withGlobalPrefix);
|
|
1110
1192
|
this.app.get(healthCheckPath, async (_req, res) => {
|
|
1111
|
-
|
|
1112
|
-
if (!this.healthChecks.length || defaultCatbeeConfig.server.skipHealthz) {
|
|
1113
|
-
return res.status(HttpStatusCodes.OK).json(new SuccessResponse("OK"));
|
|
1114
|
-
}
|
|
1115
|
-
const checkResults = await Promise.allSettled(this.healthChecks.map(async ({ name, check }) => {
|
|
1116
|
-
try {
|
|
1117
|
-
const status2 = await Promise.resolve(check());
|
|
1118
|
-
return {
|
|
1119
|
-
name,
|
|
1120
|
-
status: status2,
|
|
1121
|
-
error: null
|
|
1122
|
-
};
|
|
1123
|
-
} catch (error) {
|
|
1124
|
-
return {
|
|
1125
|
-
name,
|
|
1126
|
-
status: false,
|
|
1127
|
-
error: error.message
|
|
1128
|
-
};
|
|
1129
|
-
}
|
|
1130
|
-
}));
|
|
1131
|
-
const results = checkResults.map((result) => {
|
|
1132
|
-
if (result.status === "fulfilled") return result.value;
|
|
1133
|
-
return {
|
|
1134
|
-
name: "unknown",
|
|
1135
|
-
status: false,
|
|
1136
|
-
error: result.reason
|
|
1137
|
-
};
|
|
1138
|
-
});
|
|
1139
|
-
const allOk = results.every((r) => r.status);
|
|
1140
|
-
const status = allOk ? HttpStatusCodes.OK : HttpStatusCodes.SERVICE_UNAVAILABLE;
|
|
1141
|
-
const response = new SuccessResponse(allOk ? "OK" : "Service unavailable");
|
|
1142
|
-
if (!allOk) response.error = true;
|
|
1143
|
-
if (this.config.healthCheck?.detailed) response.data = {
|
|
1144
|
-
checks: results
|
|
1145
|
-
};
|
|
1146
|
-
return res.status(status).json(response);
|
|
1147
|
-
} catch {
|
|
1148
|
-
return res.status(HttpStatusCodes.INTERNAL_SERVER_ERROR).json(new InternalServerErrorException("Health check failed"));
|
|
1149
|
-
}
|
|
1193
|
+
return this.handleHealthCheckRequest(res);
|
|
1150
1194
|
});
|
|
1151
1195
|
if (this.config.metrics?.enable) {
|
|
1152
1196
|
const metricsPath = this.normalizePath(this.config.metrics.path ?? "/metrics", this.config.metrics?.withGlobalPrefix);
|
|
@@ -1178,6 +1222,56 @@ var ExpressServer = class {
|
|
|
1178
1222
|
});
|
|
1179
1223
|
}
|
|
1180
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
|
+
/**
|
|
1181
1275
|
* Register a new health check function for monitoring service dependencies.
|
|
1182
1276
|
*
|
|
1183
1277
|
* Health checks are executed when the health endpoint is accessed and
|
|
@@ -1208,28 +1302,8 @@ var ExpressServer = class {
|
|
|
1208
1302
|
*/
|
|
1209
1303
|
async ready() {
|
|
1210
1304
|
try {
|
|
1211
|
-
if (!this.healthChecks.length ||
|
|
1212
|
-
const
|
|
1213
|
-
try {
|
|
1214
|
-
const status = await Promise.resolve(check());
|
|
1215
|
-
return {
|
|
1216
|
-
name,
|
|
1217
|
-
status,
|
|
1218
|
-
error: null
|
|
1219
|
-
};
|
|
1220
|
-
} catch (error) {
|
|
1221
|
-
return {
|
|
1222
|
-
name,
|
|
1223
|
-
status: false,
|
|
1224
|
-
error: error.message
|
|
1225
|
-
};
|
|
1226
|
-
}
|
|
1227
|
-
}));
|
|
1228
|
-
const results = checkResults.map((result) => result.status === "fulfilled" ? result.value : {
|
|
1229
|
-
name: "unknown",
|
|
1230
|
-
status: false,
|
|
1231
|
-
error: result.reason
|
|
1232
|
-
});
|
|
1305
|
+
if (!this.healthChecks.length || getCatbeeServerGlobalConfig().skipHealthzChecksValidation) return true;
|
|
1306
|
+
const results = await this.executeHealthChecks();
|
|
1233
1307
|
return results.every((r) => r.status === true);
|
|
1234
1308
|
} catch (err) {
|
|
1235
1309
|
getLogger().error({
|
|
@@ -1274,58 +1348,82 @@ var ExpressServer = class {
|
|
|
1274
1348
|
await this.runHook("beforeStart", this.app);
|
|
1275
1349
|
return new Promise((resolve, reject) => {
|
|
1276
1350
|
try {
|
|
1277
|
-
const
|
|
1278
|
-
this.
|
|
1279
|
-
this.
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
getLogger().info(`Health check available at ${url}${this.normalizePath(this.config.healthCheck.path, this.config.healthCheck.withGlobalPrefix)}`);
|
|
1286
|
-
}
|
|
1287
|
-
if (this.config.metrics?.enable && this.config.metrics.path) {
|
|
1288
|
-
getLogger().info(`Metrics available at ${url}${this.normalizePath(this.config.metrics.path, this.config.metrics.withGlobalPrefix)}`);
|
|
1289
|
-
}
|
|
1290
|
-
if (this.config.openApi?.enable) {
|
|
1291
|
-
getLogger().info(`API docs available at ${url}${this.normalizePath(this.config.openApi.mountPath, this.config.openApi.withGlobalPrefix)}`);
|
|
1292
|
-
}
|
|
1293
|
-
if (this.server) await this.runHook("afterStart", this.server);
|
|
1294
|
-
resolve(this.server);
|
|
1295
|
-
}
|
|
1296
|
-
];
|
|
1297
|
-
if (this.config.https) {
|
|
1298
|
-
const httpsOptions = {
|
|
1299
|
-
...this.config.https,
|
|
1300
|
-
key: fs.readFileSync(this.config.https.key),
|
|
1301
|
-
cert: fs.readFileSync(this.config.https.cert)
|
|
1302
|
-
};
|
|
1303
|
-
if (this.config.https.ca) {
|
|
1304
|
-
httpsOptions.ca = fs.readFileSync(this.config.https.ca);
|
|
1305
|
-
}
|
|
1306
|
-
if (this.config.https.passphrase) {
|
|
1307
|
-
httpsOptions.passphrase = this.config.https.passphrase;
|
|
1308
|
-
}
|
|
1309
|
-
this.server = https.createServer(httpsOptions, this.app).listen(...listenArgs);
|
|
1310
|
-
} else {
|
|
1311
|
-
this.server = this.app.listen(...listenArgs);
|
|
1312
|
-
}
|
|
1313
|
-
this.server.on("connection", (conn) => {
|
|
1314
|
-
this.connections.add(conn);
|
|
1315
|
-
conn.on("close", () => this.connections.delete(conn));
|
|
1316
|
-
});
|
|
1317
|
-
this.server.on("error", (err) => {
|
|
1318
|
-
getLogger().error({
|
|
1319
|
-
err
|
|
1320
|
-
}, "Server failed to start");
|
|
1321
|
-
reject(err);
|
|
1322
|
-
});
|
|
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);
|
|
1323
1359
|
} catch (error) {
|
|
1324
1360
|
reject(error);
|
|
1325
1361
|
}
|
|
1326
1362
|
});
|
|
1327
1363
|
}
|
|
1328
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
|
+
/**
|
|
1329
1427
|
* Stop the HTTP server gracefully.
|
|
1330
1428
|
*
|
|
1331
1429
|
* This method:
|
|
@@ -1352,6 +1450,23 @@ var ExpressServer = class {
|
|
|
1352
1450
|
}
|
|
1353
1451
|
this.isShuttingDown = true;
|
|
1354
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() {
|
|
1355
1470
|
const shutdownTimeout = 1e4;
|
|
1356
1471
|
const serverClosePromise = new Promise((resolve, reject) => {
|
|
1357
1472
|
this.server.close(async (err) => {
|
|
@@ -1370,21 +1485,10 @@ var ExpressServer = class {
|
|
|
1370
1485
|
});
|
|
1371
1486
|
});
|
|
1372
1487
|
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error("Shutdown timeout")), shutdownTimeout));
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
]);
|
|
1378
|
-
} catch (err) {
|
|
1379
|
-
getLogger().error({
|
|
1380
|
-
err
|
|
1381
|
-
}, "Graceful shutdown timed out");
|
|
1382
|
-
if (force) {
|
|
1383
|
-
getLogger().warn("Forcing connection destroy due to shutdown timeout");
|
|
1384
|
-
}
|
|
1385
|
-
} finally {
|
|
1386
|
-
await this.destroyConnections();
|
|
1387
|
-
}
|
|
1488
|
+
await Promise.race([
|
|
1489
|
+
serverClosePromise,
|
|
1490
|
+
timeoutPromise
|
|
1491
|
+
]);
|
|
1388
1492
|
}
|
|
1389
1493
|
/**
|
|
1390
1494
|
* Enable graceful shutdown on OS signals for production deployment.
|
|
@@ -1526,7 +1630,7 @@ var ExpressServer = class {
|
|
|
1526
1630
|
/**
|
|
1527
1631
|
* Get server configuration
|
|
1528
1632
|
*
|
|
1529
|
-
* @return {*} {
|
|
1633
|
+
* @return {*} {CatbeeServerConfig}
|
|
1530
1634
|
*/
|
|
1531
1635
|
getConfig() {
|
|
1532
1636
|
return this.config;
|
|
@@ -1600,7 +1704,7 @@ var ExpressServer = class {
|
|
|
1600
1704
|
throw new Error(msg);
|
|
1601
1705
|
}
|
|
1602
1706
|
}
|
|
1603
|
-
|
|
1707
|
+
hasBuildMarker(config) {
|
|
1604
1708
|
if (config?.[BUILD_MARKER]) {
|
|
1605
1709
|
return true;
|
|
1606
1710
|
}
|
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
|