@catbee/utils 0.0.5 → 0.0.7

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.
Files changed (246) hide show
  1. package/LICENSE +21 -201
  2. package/README.md +51 -888
  3. package/build/esm/config.d.ts +84 -7
  4. package/build/esm/config.js +89 -5
  5. package/build/esm/config.js.map +1 -1
  6. package/build/esm/index.d.ts +4 -0
  7. package/build/esm/index.js +27 -0
  8. package/build/esm/index.js.map +1 -1
  9. package/build/esm/servers/server.builder.d.ts +508 -0
  10. package/build/esm/servers/server.builder.js +702 -0
  11. package/build/esm/servers/server.builder.js.map +1 -0
  12. package/build/esm/servers/server.d.ts +256 -0
  13. package/build/esm/servers/server.js +1201 -0
  14. package/build/esm/servers/server.js.map +1 -0
  15. package/build/esm/types/api-response.d.ts +33 -7
  16. package/build/esm/types/api-response.js +33 -1
  17. package/build/esm/types/api-response.js.map +1 -1
  18. package/build/esm/types/index.d.ts +125 -0
  19. package/build/esm/types/index.js +25 -0
  20. package/build/esm/types/index.js.map +1 -0
  21. package/build/esm/types/server.d.ts +268 -0
  22. package/build/esm/types/server.js +25 -0
  23. package/build/esm/types/server.js.map +1 -0
  24. package/build/esm/utils/array.utils.js +23 -0
  25. package/build/esm/utils/array.utils.js.map +1 -1
  26. package/build/esm/utils/async.utils.d.ts +91 -0
  27. package/build/esm/utils/async.utils.js +267 -0
  28. package/build/esm/utils/async.utils.js.map +1 -1
  29. package/build/esm/utils/cache.utils.js +23 -0
  30. package/build/esm/utils/cache.utils.js.map +1 -1
  31. package/build/esm/utils/context-store.utils.d.ts +3 -0
  32. package/build/esm/utils/context-store.utils.js +32 -6
  33. package/build/esm/utils/context-store.utils.js.map +1 -1
  34. package/build/esm/utils/crypto.utils.js +23 -0
  35. package/build/esm/utils/crypto.utils.js.map +1 -1
  36. package/build/esm/utils/date.utils.d.ts +159 -0
  37. package/build/esm/utils/date.utils.js +389 -0
  38. package/build/esm/utils/date.utils.js.map +1 -0
  39. package/build/esm/utils/decorators.utils.js +23 -1
  40. package/build/esm/utils/decorators.utils.js.map +1 -1
  41. package/build/esm/utils/dir.utils.js +23 -0
  42. package/build/esm/utils/dir.utils.js.map +1 -1
  43. package/build/esm/utils/env.utils.js +23 -0
  44. package/build/esm/utils/env.utils.js.map +1 -1
  45. package/build/esm/utils/exception.utils.js +31 -8
  46. package/build/esm/utils/exception.utils.js.map +1 -1
  47. package/build/esm/utils/fs.utils.js +23 -0
  48. package/build/esm/utils/fs.utils.js.map +1 -1
  49. package/build/esm/utils/http-status-codes.js +23 -0
  50. package/build/esm/utils/http-status-codes.js.map +1 -1
  51. package/build/esm/utils/id.utils.js +23 -0
  52. package/build/esm/utils/id.utils.js.map +1 -1
  53. package/build/esm/utils/logger.utils.d.ts +5 -0
  54. package/build/esm/utils/logger.utils.js +40 -3
  55. package/build/esm/utils/logger.utils.js.map +1 -1
  56. package/build/esm/utils/middleware.utils.d.ts +2 -0
  57. package/build/esm/utils/middleware.utils.js +61 -50
  58. package/build/esm/utils/middleware.utils.js.map +1 -1
  59. package/build/esm/utils/obj.utils.d.ts +10 -3
  60. package/build/esm/utils/obj.utils.js +225 -15
  61. package/build/esm/utils/obj.utils.js.map +1 -1
  62. package/build/esm/utils/performance.utils.d.ts +136 -0
  63. package/build/esm/utils/performance.utils.js +376 -0
  64. package/build/esm/utils/performance.utils.js.map +1 -0
  65. package/build/esm/utils/request.utils.d.ts +9 -0
  66. package/build/esm/utils/request.utils.js +55 -5
  67. package/build/esm/utils/request.utils.js.map +1 -1
  68. package/build/esm/utils/response.utils.d.ts +15 -1
  69. package/build/esm/utils/response.utils.js +51 -0
  70. package/build/esm/utils/response.utils.js.map +1 -1
  71. package/build/esm/utils/stream.utils.d.ts +88 -0
  72. package/build/esm/utils/stream.utils.js +284 -0
  73. package/build/esm/utils/stream.utils.js.map +1 -0
  74. package/build/esm/utils/string.utils.js +23 -0
  75. package/build/esm/utils/string.utils.js.map +1 -1
  76. package/build/esm/utils/type.utils.d.ts +90 -0
  77. package/build/esm/utils/type.utils.js +190 -0
  78. package/build/esm/utils/type.utils.js.map +1 -0
  79. package/build/esm/utils/url.utils.js +23 -0
  80. package/build/esm/utils/url.utils.js.map +1 -1
  81. package/build/esm/utils/validate.utils.d.ts +7 -0
  82. package/build/esm/utils/validate.utils.js +35 -2
  83. package/build/esm/utils/validate.utils.js.map +1 -1
  84. package/build/esnext/config.d.ts +84 -7
  85. package/build/esnext/config.js +89 -5
  86. package/build/esnext/config.js.map +1 -1
  87. package/build/esnext/index.d.ts +4 -0
  88. package/build/esnext/index.js +27 -0
  89. package/build/esnext/index.js.map +1 -1
  90. package/build/esnext/servers/server.builder.d.ts +508 -0
  91. package/build/esnext/servers/server.builder.js +657 -0
  92. package/build/esnext/servers/server.builder.js.map +1 -0
  93. package/build/esnext/servers/server.d.ts +256 -0
  94. package/build/esnext/servers/server.js +908 -0
  95. package/build/esnext/servers/server.js.map +1 -0
  96. package/build/esnext/types/api-response.d.ts +33 -7
  97. package/build/esnext/types/api-response.js +33 -1
  98. package/build/esnext/types/api-response.js.map +1 -1
  99. package/build/esnext/types/index.d.ts +125 -0
  100. package/build/esnext/types/index.js +25 -0
  101. package/build/esnext/types/index.js.map +1 -0
  102. package/build/esnext/types/server.d.ts +268 -0
  103. package/build/esnext/types/server.js +25 -0
  104. package/build/esnext/types/server.js.map +1 -0
  105. package/build/esnext/utils/array.utils.js +23 -0
  106. package/build/esnext/utils/array.utils.js.map +1 -1
  107. package/build/esnext/utils/async.utils.d.ts +91 -0
  108. package/build/esnext/utils/async.utils.js +213 -0
  109. package/build/esnext/utils/async.utils.js.map +1 -1
  110. package/build/esnext/utils/cache.utils.js +23 -0
  111. package/build/esnext/utils/cache.utils.js.map +1 -1
  112. package/build/esnext/utils/context-store.utils.d.ts +3 -0
  113. package/build/esnext/utils/context-store.utils.js +32 -6
  114. package/build/esnext/utils/context-store.utils.js.map +1 -1
  115. package/build/esnext/utils/crypto.utils.js +23 -0
  116. package/build/esnext/utils/crypto.utils.js.map +1 -1
  117. package/build/esnext/utils/date.utils.d.ts +159 -0
  118. package/build/esnext/utils/date.utils.js +384 -0
  119. package/build/esnext/utils/date.utils.js.map +1 -0
  120. package/build/esnext/utils/decorators.utils.js +23 -1
  121. package/build/esnext/utils/decorators.utils.js.map +1 -1
  122. package/build/esnext/utils/dir.utils.js +23 -0
  123. package/build/esnext/utils/dir.utils.js.map +1 -1
  124. package/build/esnext/utils/env.utils.js +23 -0
  125. package/build/esnext/utils/env.utils.js.map +1 -1
  126. package/build/esnext/utils/exception.utils.js +30 -7
  127. package/build/esnext/utils/exception.utils.js.map +1 -1
  128. package/build/esnext/utils/fs.utils.js +23 -0
  129. package/build/esnext/utils/fs.utils.js.map +1 -1
  130. package/build/esnext/utils/http-status-codes.js +23 -0
  131. package/build/esnext/utils/http-status-codes.js.map +1 -1
  132. package/build/esnext/utils/id.utils.js +23 -0
  133. package/build/esnext/utils/id.utils.js.map +1 -1
  134. package/build/esnext/utils/logger.utils.d.ts +5 -0
  135. package/build/esnext/utils/logger.utils.js +40 -3
  136. package/build/esnext/utils/logger.utils.js.map +1 -1
  137. package/build/esnext/utils/middleware.utils.d.ts +2 -0
  138. package/build/esnext/utils/middleware.utils.js +57 -50
  139. package/build/esnext/utils/middleware.utils.js.map +1 -1
  140. package/build/esnext/utils/obj.utils.d.ts +10 -3
  141. package/build/esnext/utils/obj.utils.js +176 -12
  142. package/build/esnext/utils/obj.utils.js.map +1 -1
  143. package/build/esnext/utils/performance.utils.d.ts +136 -0
  144. package/build/esnext/utils/performance.utils.js +271 -0
  145. package/build/esnext/utils/performance.utils.js.map +1 -0
  146. package/build/esnext/utils/request.utils.d.ts +9 -0
  147. package/build/esnext/utils/request.utils.js +46 -5
  148. package/build/esnext/utils/request.utils.js.map +1 -1
  149. package/build/esnext/utils/response.utils.d.ts +15 -1
  150. package/build/esnext/utils/response.utils.js +51 -0
  151. package/build/esnext/utils/response.utils.js.map +1 -1
  152. package/build/esnext/utils/stream.utils.d.ts +88 -0
  153. package/build/esnext/utils/stream.utils.js +210 -0
  154. package/build/esnext/utils/stream.utils.js.map +1 -0
  155. package/build/esnext/utils/string.utils.js +23 -0
  156. package/build/esnext/utils/string.utils.js.map +1 -1
  157. package/build/esnext/utils/type.utils.d.ts +90 -0
  158. package/build/esnext/utils/type.utils.js +187 -0
  159. package/build/esnext/utils/type.utils.js.map +1 -0
  160. package/build/esnext/utils/url.utils.js +23 -0
  161. package/build/esnext/utils/url.utils.js.map +1 -1
  162. package/build/esnext/utils/validate.utils.d.ts +7 -0
  163. package/build/esnext/utils/validate.utils.js +33 -0
  164. package/build/esnext/utils/validate.utils.js.map +1 -1
  165. package/build/src/config.d.ts +84 -7
  166. package/build/src/config.js +90 -6
  167. package/build/src/config.js.map +1 -1
  168. package/build/src/index.d.ts +4 -0
  169. package/build/src/index.js +27 -0
  170. package/build/src/index.js.map +1 -1
  171. package/build/src/servers/server.builder.d.ts +508 -0
  172. package/build/src/servers/server.builder.js +661 -0
  173. package/build/src/servers/server.builder.js.map +1 -0
  174. package/build/src/servers/server.d.ts +256 -0
  175. package/build/src/servers/server.js +948 -0
  176. package/build/src/servers/server.js.map +1 -0
  177. package/build/src/types/api-response.d.ts +33 -7
  178. package/build/src/types/api-response.js +34 -0
  179. package/build/src/types/api-response.js.map +1 -1
  180. package/build/src/types/index.d.ts +125 -0
  181. package/build/src/types/index.js +26 -0
  182. package/build/src/types/index.js.map +1 -0
  183. package/build/src/types/server.d.ts +268 -0
  184. package/build/src/types/server.js +26 -0
  185. package/build/src/types/server.js.map +1 -0
  186. package/build/src/utils/array.utils.js +23 -0
  187. package/build/src/utils/array.utils.js.map +1 -1
  188. package/build/src/utils/async.utils.d.ts +91 -0
  189. package/build/src/utils/async.utils.js +217 -0
  190. package/build/src/utils/async.utils.js.map +1 -1
  191. package/build/src/utils/cache.utils.js +23 -0
  192. package/build/src/utils/cache.utils.js.map +1 -1
  193. package/build/src/utils/context-store.utils.d.ts +3 -0
  194. package/build/src/utils/context-store.utils.js +32 -6
  195. package/build/src/utils/context-store.utils.js.map +1 -1
  196. package/build/src/utils/crypto.utils.js +23 -0
  197. package/build/src/utils/crypto.utils.js.map +1 -1
  198. package/build/src/utils/date.utils.d.ts +159 -0
  199. package/build/src/utils/date.utils.js +396 -0
  200. package/build/src/utils/date.utils.js.map +1 -0
  201. package/build/src/utils/decorators.utils.js +23 -1
  202. package/build/src/utils/decorators.utils.js.map +1 -1
  203. package/build/src/utils/dir.utils.js +23 -0
  204. package/build/src/utils/dir.utils.js.map +1 -1
  205. package/build/src/utils/env.utils.js +23 -0
  206. package/build/src/utils/env.utils.js.map +1 -1
  207. package/build/src/utils/exception.utils.js +30 -7
  208. package/build/src/utils/exception.utils.js.map +1 -1
  209. package/build/src/utils/fs.utils.js +23 -0
  210. package/build/src/utils/fs.utils.js.map +1 -1
  211. package/build/src/utils/http-status-codes.js +23 -0
  212. package/build/src/utils/http-status-codes.js.map +1 -1
  213. package/build/src/utils/id.utils.js +23 -0
  214. package/build/src/utils/id.utils.js.map +1 -1
  215. package/build/src/utils/logger.utils.d.ts +5 -0
  216. package/build/src/utils/logger.utils.js +41 -4
  217. package/build/src/utils/logger.utils.js.map +1 -1
  218. package/build/src/utils/middleware.utils.d.ts +2 -0
  219. package/build/src/utils/middleware.utils.js +56 -49
  220. package/build/src/utils/middleware.utils.js.map +1 -1
  221. package/build/src/utils/obj.utils.d.ts +10 -3
  222. package/build/src/utils/obj.utils.js +177 -12
  223. package/build/src/utils/obj.utils.js.map +1 -1
  224. package/build/src/utils/performance.utils.d.ts +136 -0
  225. package/build/src/utils/performance.utils.js +278 -0
  226. package/build/src/utils/performance.utils.js.map +1 -0
  227. package/build/src/utils/request.utils.d.ts +9 -0
  228. package/build/src/utils/request.utils.js +48 -5
  229. package/build/src/utils/request.utils.js.map +1 -1
  230. package/build/src/utils/response.utils.d.ts +15 -1
  231. package/build/src/utils/response.utils.js +52 -0
  232. package/build/src/utils/response.utils.js.map +1 -1
  233. package/build/src/utils/stream.utils.d.ts +88 -0
  234. package/build/src/utils/stream.utils.js +218 -0
  235. package/build/src/utils/stream.utils.js.map +1 -0
  236. package/build/src/utils/string.utils.js +23 -0
  237. package/build/src/utils/string.utils.js.map +1 -1
  238. package/build/src/utils/type.utils.d.ts +90 -0
  239. package/build/src/utils/type.utils.js +196 -0
  240. package/build/src/utils/type.utils.js.map +1 -0
  241. package/build/src/utils/url.utils.js +23 -0
  242. package/build/src/utils/url.utils.js.map +1 -1
  243. package/build/src/utils/validate.utils.d.ts +7 -0
  244. package/build/src/utils/validate.utils.js +36 -2
  245. package/build/src/utils/validate.utils.js.map +1 -1
  246. package/package.json +39 -9
@@ -0,0 +1,948 @@
1
+ "use strict";
2
+ /*
3
+ * The MIT License
4
+ *
5
+ * Copyright (c) 2025 Catbee Technologies
6
+ *
7
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ * of this software and associated documentation files (the "Software"), to deal
9
+ * in the Software without restriction, including without limitation the rights
10
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ * copies of the Software, and to permit persons to whom the Software is
12
+ * furnished to do so, subject to the following conditions:
13
+ *
14
+ * The above copyright notice and this permission notice shall be included in all
15
+ * copies or substantial portions of the Software.
16
+ *
17
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ * SOFTWARE.
24
+ */
25
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
26
+ if (k2 === undefined) k2 = k;
27
+ var desc = Object.getOwnPropertyDescriptor(m, k);
28
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
29
+ desc = { enumerable: true, get: function() { return m[k]; } };
30
+ }
31
+ Object.defineProperty(o, k2, desc);
32
+ }) : (function(o, m, k, k2) {
33
+ if (k2 === undefined) k2 = k;
34
+ o[k2] = m[k];
35
+ }));
36
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
37
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
38
+ }) : function(o, v) {
39
+ o["default"] = v;
40
+ });
41
+ var __importStar = (this && this.__importStar) || (function () {
42
+ var ownKeys = function(o) {
43
+ ownKeys = Object.getOwnPropertyNames || function (o) {
44
+ var ar = [];
45
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
46
+ return ar;
47
+ };
48
+ return ownKeys(o);
49
+ };
50
+ return function (mod) {
51
+ if (mod && mod.__esModule) return mod;
52
+ var result = {};
53
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
54
+ __setModuleDefault(result, mod);
55
+ return result;
56
+ };
57
+ })();
58
+ var __importDefault = (this && this.__importDefault) || function (mod) {
59
+ return (mod && mod.__esModule) ? mod : { "default": mod };
60
+ };
61
+ Object.defineProperty(exports, "__esModule", { value: true });
62
+ exports.ExpressServer = void 0;
63
+ const express_1 = __importDefault(require("express"));
64
+ const https_1 = __importDefault(require("https"));
65
+ const cors_1 = __importDefault(require("cors"));
66
+ const helmet_1 = __importDefault(require("helmet"));
67
+ const compression_1 = __importDefault(require("compression"));
68
+ const cookie_parser_1 = __importDefault(require("cookie-parser"));
69
+ const express_rate_limit_1 = __importDefault(require("express-rate-limit"));
70
+ const http_status_codes_1 = require("../utils/http-status-codes");
71
+ const response_utils_1 = require("../utils/response.utils");
72
+ const middleware_utils_1 = require("../utils/middleware.utils");
73
+ const env_utils_1 = require("../utils/env.utils");
74
+ const logger_utils_1 = require("../utils/logger.utils");
75
+ const exception_utils_1 = require("../utils/exception.utils");
76
+ const prom_client_1 = __importStar(require("prom-client"));
77
+ const exception_utils_2 = require("../utils/exception.utils");
78
+ const fs_1 = __importDefault(require("fs"));
79
+ const express_api_reference_1 = require("@scalar/express-api-reference");
80
+ const config_1 = require("../config");
81
+ const obj_utils_1 = require("../utils/obj.utils");
82
+ const fs_utils_1 = require("../utils/fs.utils");
83
+ const server_builder_1 = require("./server.builder");
84
+ const validate_utils_1 = require("../utils/validate.utils");
85
+ /**
86
+ * Production-ready Express server with enterprise features.
87
+ *
88
+ * Core Features:
89
+ * - Security: Helmet, CORS, rate limiting, timeouts
90
+ * - Monitoring: Request logs, metrics, health checks
91
+ * - Performance: Compression, caching, static files
92
+ * - Reliability: Graceful shutdown, error handling
93
+ * - Developer UX: OpenAPI docs, debugging tools
94
+ * - Extensibility: Hooks, middleware, custom routes
95
+ *
96
+ * Designed for microservices and production workloads.
97
+ * Includes K8s readiness probes and zero-downtime support.
98
+ */
99
+ class ExpressServer {
100
+ /**
101
+ * Initializes server with intelligent defaults and security best practices.
102
+ * All settings can be customized via config and hooks.
103
+ *
104
+ * Default Security:
105
+ * - Secure headers (Helmet)
106
+ * - Rate limiting
107
+ * - Request timeouts
108
+ * - Body size limits
109
+ * - CORS protection
110
+ *
111
+ * Default Monitoring:
112
+ * - Request/Response logging
113
+ * - Prometheus metrics
114
+ * - Health checks
115
+ * - Request tracing
116
+ */
117
+ constructor(config, hooks = {}) {
118
+ var _a, _b, _c;
119
+ /** Prometheus client registry for metrics collection */
120
+ this.register = new prom_client_1.default.Registry();
121
+ /** HTTP server instance (null when not running) */
122
+ this.server = null;
123
+ /** Set of active WebSocket connections */
124
+ this.connections = new Set();
125
+ /** Flag indicating if the server is shutting down */
126
+ this.isShuttingDown = false;
127
+ /**
128
+ * Collection of registered health check functions.
129
+ * These are executed when the health check endpoint is accessed.
130
+ */
131
+ this.healthChecks = [];
132
+ if (ExpressServer.isBuiltServerConfig(config)) {
133
+ this.config = config;
134
+ }
135
+ else {
136
+ // Deep merge config with user overrides
137
+ this.config = (0, obj_utils_1.deepObjMerge)({}, config_1.defaultServerConfig, config);
138
+ }
139
+ if (!(0, validate_utils_1.isPort)(this.config.port)) {
140
+ throw new Error(`Port must be a valid number between 1 and 65535, got: ${this.config.port}`);
141
+ }
142
+ // Sanitize app name for metrics (replace invalid characters with underscore)
143
+ const safeAppName = (this.config.appName || 'express_app').toLowerCase().replace(/[^a-z0-9_]/g, '_');
144
+ if ((_a = this.config.metrics) === null || _a === void 0 ? void 0 : _a.enable) {
145
+ // Initialize Prometheus metrics with sanitized names
146
+ this.requestCounter = new prom_client_1.Counter({
147
+ name: `${safeAppName}_http_requests_total`,
148
+ help: 'Total HTTP requests',
149
+ labelNames: ['method', 'route', 'status'],
150
+ registers: [this.register]
151
+ });
152
+ this.routeTimings = new prom_client_1.Histogram({
153
+ name: `${safeAppName}_http_request_duration_seconds`,
154
+ help: 'Duration of HTTP requests by route',
155
+ labelNames: ['method', 'route', 'status'],
156
+ buckets: [0.1, 0.3, 0.5, 0.7, 1, 3, 5, 7, 10],
157
+ registers: [this.register]
158
+ });
159
+ this.requestSizes = new prom_client_1.Histogram({
160
+ name: `${safeAppName}_http_request_size_bytes`,
161
+ help: 'Size of HTTP request bodies',
162
+ labelNames: ['method', 'route'],
163
+ buckets: [100, 1000, 10000, 100000, 1000000],
164
+ registers: [this.register]
165
+ });
166
+ this.clientIPs = new prom_client_1.Counter({
167
+ name: `${safeAppName}_http_client_ip_total`,
168
+ help: 'Client IP request counter',
169
+ labelNames: ['ip', 'method'],
170
+ registers: [this.register]
171
+ });
172
+ // Default system metrics (CPU, memory, event loop lag, etc.)
173
+ prom_client_1.default.collectDefaultMetrics({
174
+ register: this.register,
175
+ prefix: `${safeAppName}_`
176
+ });
177
+ }
178
+ // Health checks
179
+ if ((_b = config.healthCheck) === null || _b === void 0 ? void 0 : _b.checks) {
180
+ this.healthChecks.push(...config.healthCheck.checks);
181
+ }
182
+ // Set global prefix (normalize to empty string or "/prefix" without trailing slash)
183
+ this.globalPrefix = this.normalizePath((_c = this.config.globalPrefix) !== null && _c !== void 0 ? _c : '', false);
184
+ this.hooks = hooks;
185
+ this.app = (0, express_1.default)();
186
+ this.rootRouter = express_1.default.Router();
187
+ // Store initialization promise to prevent race conditions with start()
188
+ this.initPromise = this.initialize();
189
+ }
190
+ /**
191
+ * Execute a lifecycle hook safely with comprehensive error handling.
192
+ * Prevents hook failures from crashing the server while logging issues.
193
+ *
194
+ * @param hook Name of the lifecycle hook to execute
195
+ * @param args Arguments to pass to the hook function
196
+ */
197
+ async runHook(hook, ...args) {
198
+ try {
199
+ const fn = this.hooks[hook];
200
+ if (fn)
201
+ await fn.apply(null, args);
202
+ }
203
+ catch (err) {
204
+ (0, logger_utils_1.getLogger)().error({ err, hook }, `Error executing ${hook} hook:`);
205
+ }
206
+ }
207
+ /**
208
+ * Initialize the Express server with middleware and routes.
209
+ */
210
+ async initialize() {
211
+ await this.runHook('beforeInit', this);
212
+ // Set up middleware stack (order is critical)
213
+ await this.setupMiddleware();
214
+ // Set up default routes and error handling
215
+ await this.setupRoutes();
216
+ await this.runHook('afterInit', this);
217
+ }
218
+ /**
219
+ * Configure and register all middlewares in the optimal order.
220
+ *
221
+ * Middleware Order (CRITICAL - don't change without understanding implications):
222
+ * 1. Basic server configuration (trust proxy, x-powered-by)
223
+ * 2. Request ID generation (for tracing)
224
+ * 3. Request context setup (for logging correlation)
225
+ * 4. Timeout protection (prevents hanging requests)
226
+ * 5. Response time tracking (for performance monitoring)
227
+ * 6. Request logging (after ID/context setup)
228
+ * 7. Custom request hooks
229
+ * 8. Security middleware (rate limiting, CORS, Helmet)
230
+ * 9. Response compression
231
+ * 10. Static file serving
232
+ * 11. Request parsing (body parsing, cookies)
233
+ * 12. API documentation (OpenAPI)
234
+ * 13. Global headers
235
+ * 14. Custom response hooks
236
+ */
237
+ async setupMiddleware() {
238
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;
239
+ if (this.config.https) {
240
+ await this.validateHttpsFiles();
241
+ }
242
+ // Basic middleware should be first
243
+ this.app.disable('x-powered-by');
244
+ if (this.config.trustProxy) {
245
+ this.app.set('trust proxy', true);
246
+ }
247
+ // Request ID generation - must be first for proper tracing
248
+ this.app.use((0, middleware_utils_1.requestId)({
249
+ headerName: (_a = this.config.requestId) === null || _a === void 0 ? void 0 : _a.headerName,
250
+ exposeHeader: (_b = this.config.requestId) === null || _b === void 0 ? void 0 : _b.exposeHeader,
251
+ generator: (_c = this.config.requestId) === null || _c === void 0 ? void 0 : _c.generator
252
+ }));
253
+ // Request context setup for logging correlation
254
+ this.app.use((0, middleware_utils_1.setupRequestContext)({
255
+ headerName: (_d = this.config.requestId) === null || _d === void 0 ? void 0 : _d.headerName,
256
+ autoLog: false
257
+ }));
258
+ // Early shutdown-awareness middleware (lets load balancers drain connections gracefully)
259
+ this.app.use((_req, res, next) => {
260
+ if (this.isShuttingDown) {
261
+ res.setHeader('Connection', 'close');
262
+ return res
263
+ .status(http_status_codes_1.HttpStatusCodes.SERVICE_UNAVAILABLE)
264
+ .json(new exception_utils_1.ServiceUnavailableException('Server is shutting down'));
265
+ }
266
+ next();
267
+ return;
268
+ });
269
+ // Security middleware should come early
270
+ if (this.config.helmet) {
271
+ if (typeof this.config.helmet === 'object') {
272
+ this.app.use((0, helmet_1.default)(this.config.helmet));
273
+ }
274
+ else {
275
+ this.app.use((0, helmet_1.default)());
276
+ }
277
+ }
278
+ // CORS middleware should be early
279
+ if (this.config.cors) {
280
+ this.app.use((0, cors_1.default)(this.config.cors === true ? {} : this.config.cors));
281
+ }
282
+ // Global headers
283
+ this.app.use((_req, res, next) => {
284
+ var _a, _b, _c, _d;
285
+ if (this.config.globalHeaders) {
286
+ for (const key in this.config.globalHeaders) {
287
+ const value = this.config.globalHeaders[key];
288
+ res.setHeader(key, typeof value === 'function' ? value() : value);
289
+ }
290
+ }
291
+ if (this.config.isMicroservice) {
292
+ res.setHeader('X-Microservice', this.config.appName || 'express_app');
293
+ }
294
+ if ((_a = this.config.serviceVersion) === null || _a === void 0 ? void 0 : _a.enable) {
295
+ const version = typeof ((_b = this.config.serviceVersion) === null || _b === void 0 ? void 0 : _b.version) === 'function'
296
+ ? this.config.serviceVersion.version()
297
+ : (_c = this.config.serviceVersion) === null || _c === void 0 ? void 0 : _c.version;
298
+ res.setHeader(((_d = this.config.serviceVersion) === null || _d === void 0 ? void 0 : _d.headerName) || 'x-service-version', version || '0.0.0');
299
+ }
300
+ next();
301
+ });
302
+ // Global request timeout protection
303
+ if (this.config.requestTimeout) {
304
+ this.app.use((0, middleware_utils_1.timeout)(this.config.requestTimeout));
305
+ }
306
+ // Response time tracking for performance monitoring
307
+ if ((_e = this.config.responseTime) === null || _e === void 0 ? void 0 : _e.enable) {
308
+ this.app.use((0, middleware_utils_1.responseTime)({
309
+ addHeader: this.config.responseTime.addHeader,
310
+ logOnComplete: this.config.responseTime.logOnComplete
311
+ }));
312
+ }
313
+ // Rate limiting should be early to prevent unnecessary processing
314
+ if ((_f = this.config.rateLimit) === null || _f === void 0 ? void 0 : _f.enable) {
315
+ this.app.use((0, express_rate_limit_1.default)({
316
+ windowMs: (_g = this.config.rateLimit.windowMs) !== null && _g !== void 0 ? _g : 15 * 60 * 1000,
317
+ max: (_h = this.config.rateLimit.max) !== null && _h !== void 0 ? _h : 100,
318
+ handler: (req, res) => {
319
+ var _a;
320
+ const status = http_status_codes_1.HttpStatusCodes.TOO_MANY_REQUESTS;
321
+ const response = (0, response_utils_1.createFinalErrorResponse)(req, status, ((_a = this.config.rateLimit) === null || _a === void 0 ? void 0 : _a.message) || 'Too many requests');
322
+ res.status(status).json(response);
323
+ },
324
+ standardHeaders: (_j = this.config.rateLimit.standardHeaders) !== null && _j !== void 0 ? _j : true,
325
+ legacyHeaders: (_k = this.config.rateLimit.legacyHeaders) !== null && _k !== void 0 ? _k : false
326
+ }));
327
+ }
328
+ // Request logging with filtering
329
+ if ((_l = this.config.requestLogging) === null || _l === void 0 ? void 0 : _l.enable) {
330
+ this.app.use((req, res, next) => {
331
+ var _a, _b, _c, _d, _e, _f;
332
+ if (typeof ((_a = this.config.requestLogging) === null || _a === void 0 ? void 0 : _a.ignorePaths) === 'function') {
333
+ const skip = (_c = (_b = this.config.requestLogging) === null || _b === void 0 ? void 0 : _b.ignorePaths) === null || _c === void 0 ? void 0 : _c.call(_b, req, res);
334
+ if (skip)
335
+ return next();
336
+ }
337
+ else if (Array.isArray((_d = this.config.requestLogging) === null || _d === void 0 ? void 0 : _d.ignorePaths)) {
338
+ const skip = (_f = (_e = this.config.requestLogging) === null || _e === void 0 ? void 0 : _e.ignorePaths) === null || _f === void 0 ? void 0 : _f.includes(req.path);
339
+ if (skip)
340
+ return next();
341
+ }
342
+ const logger = (0, logger_utils_1.getLogger)();
343
+ const incomingRequestMetaData = {
344
+ requestId: req.id,
345
+ method: req.method,
346
+ url: req.originalUrl || req.url,
347
+ ip: req.ip
348
+ };
349
+ logger.info(incomingRequestMetaData, 'Incoming Request');
350
+ next();
351
+ });
352
+ }
353
+ // Custom request preprocessing hook
354
+ if (this.hooks.onRequest) {
355
+ this.app.use(this.hooks.onRequest);
356
+ }
357
+ // Response compression for better performance
358
+ if (this.config.compression) {
359
+ if (typeof this.config.compression === 'object') {
360
+ this.app.use((0, compression_1.default)(this.config.compression));
361
+ }
362
+ else {
363
+ this.app.use((0, compression_1.default)());
364
+ }
365
+ }
366
+ // Static file serving (do NOT normalize filesystem path; only normalize route)
367
+ if (this.config.staticFolders) {
368
+ this.config.staticFolders.forEach(folder => {
369
+ var _a;
370
+ this.app.use(this.normalizePath((_a = folder.path) !== null && _a !== void 0 ? _a : '/'), express_1.default.static(folder.directory, {
371
+ maxAge: folder.maxAge || 0,
372
+ etag: folder.etag !== false,
373
+ immutable: folder.immutable === true,
374
+ lastModified: folder.lastModified !== false,
375
+ cacheControl: folder.cacheControl !== false
376
+ }));
377
+ (0, logger_utils_1.getLogger)().info(`Serving static folder: ${folder.directory} at path ${folder.path || '/'}`);
378
+ });
379
+ }
380
+ // Request body parsing with size limits
381
+ if (this.config.bodyParser) {
382
+ if (this.config.bodyParser.json) {
383
+ this.app.use(express_1.default.json(this.config.bodyParser.json));
384
+ }
385
+ if (this.config.bodyParser.urlencoded) {
386
+ this.app.use(express_1.default.urlencoded(this.config.bodyParser.urlencoded));
387
+ }
388
+ }
389
+ // Cookie parser middleware
390
+ if (this.config.cookieParser) {
391
+ if (typeof this.config.cookieParser === 'object') {
392
+ this.app.use((0, cookie_parser_1.default)(undefined, this.config.cookieParser));
393
+ }
394
+ else {
395
+ this.app.use((0, cookie_parser_1.default)());
396
+ }
397
+ }
398
+ // OpenAPI docs via @scalar/express-api-reference
399
+ if ((_m = this.config.openApi) === null || _m === void 0 ? void 0 : _m.enable) {
400
+ try {
401
+ const openApiMountPath = this.normalizePath((_o = this.config.openApi.mountPath) !== null && _o !== void 0 ? _o : '/docs', this.config.openApi.withGlobalPrefix);
402
+ const openApiFilePath = this.config.openApi.filePath;
403
+ if (!openApiFilePath) {
404
+ throw new Error('OpenAPI file path is required');
405
+ }
406
+ const isOpenApiFilePathExists = await (0, fs_utils_1.fileExists)(openApiFilePath);
407
+ if (!isOpenApiFilePathExists) {
408
+ throw new Error(`OpenAPI spec file not found at ${openApiFilePath}`);
409
+ }
410
+ if ((_p = this.config.openApi) === null || _p === void 0 ? void 0 : _p.verbose) {
411
+ (0, logger_utils_1.getLogger)().info(`Mounting OpenAPI docs at ${openApiMountPath}`);
412
+ (0, logger_utils_1.getLogger)().info(`Using OpenAPI spec file at ${openApiFilePath}`);
413
+ }
414
+ this.app.use(openApiMountPath, (0, express_api_reference_1.apiReference)({
415
+ spec: {
416
+ content: await fs_1.default.promises.readFile(openApiFilePath, 'utf8')
417
+ }
418
+ }));
419
+ if ((_q = this.config.openApi) === null || _q === void 0 ? void 0 : _q.verbose) {
420
+ (0, logger_utils_1.getLogger)().info(`Mounted OpenAPI docs at ${openApiMountPath}`);
421
+ }
422
+ }
423
+ catch (err) {
424
+ (0, logger_utils_1.getLogger)().error({ err }, 'Failed to mount OpenAPI docs');
425
+ }
426
+ }
427
+ // Custom response preprocessing hook (apply global prefix if set)
428
+ if (this.hooks.onResponse) {
429
+ this.app.use(this.globalPrefix, this.hooks.onResponse);
430
+ }
431
+ if ((_r = this.config.metrics) === null || _r === void 0 ? void 0 : _r.enable) {
432
+ // Add metrics tracking middleware
433
+ this.app.use((req, res, next) => {
434
+ var _a, _b;
435
+ const start = process.hrtime();
436
+ // Track client IPs
437
+ (_a = this.clientIPs) === null || _a === void 0 ? void 0 : _a.inc({ ip: req.ip, method: req.method });
438
+ // Track request sizes (parse safely)
439
+ const cl = req.headers['content-length'];
440
+ if (cl) {
441
+ const size = Number(cl);
442
+ if (!Number.isNaN(size) && size >= 0) {
443
+ const route = this.normalizeRouteForMetrics(req, res);
444
+ (_b = this.requestSizes) === null || _b === void 0 ? void 0 : _b.observe({ method: req.method, route }, size);
445
+ }
446
+ }
447
+ res.once('finish', () => {
448
+ var _a, _b;
449
+ const [seconds, nanoseconds] = process.hrtime(start);
450
+ const finalRoute = this.normalizeRouteForMetrics(req, res);
451
+ (_a = this.requestCounter) === null || _a === void 0 ? void 0 : _a.inc({
452
+ method: req.method,
453
+ route: finalRoute,
454
+ status: res.statusCode.toString()
455
+ });
456
+ (_b = this.routeTimings) === null || _b === void 0 ? void 0 : _b.observe({
457
+ method: req.method,
458
+ route: finalRoute,
459
+ status: res.statusCode.toString()
460
+ }, seconds + nanoseconds / 1e9);
461
+ });
462
+ next();
463
+ });
464
+ }
465
+ }
466
+ /**
467
+ * Configure server routes and error handling.
468
+ * Sets up in following order:
469
+ *
470
+ * 1. Built-in routes (health, metrics)
471
+ * 2. Application routes
472
+ * 3. 404 handler
473
+ * 4. Error handler
474
+ */
475
+ async setupRoutes() {
476
+ var _a, _b, _c, _d, _e;
477
+ // Health check endpoint
478
+ const healthCheckPath = this.normalizePath(((_a = this.config.healthCheck) === null || _a === void 0 ? void 0 : _a.path) || '/healthz', (_b = this.config.healthCheck) === null || _b === void 0 ? void 0 : _b.withGlobalPrefix);
479
+ this.app.get(healthCheckPath, async (_req, res) => {
480
+ var _a;
481
+ try {
482
+ if (!this.healthChecks.length) {
483
+ return res.status(http_status_codes_1.HttpStatusCodes.OK).json(new response_utils_1.SuccessResponse('OK'));
484
+ }
485
+ const checkResults = await Promise.allSettled(this.healthChecks.map(async ({ name, check }) => {
486
+ try {
487
+ const status = await Promise.resolve(check());
488
+ return { name, status, error: null };
489
+ }
490
+ catch (error) {
491
+ return { name, status: false, error: error.message };
492
+ }
493
+ }));
494
+ const results = checkResults.map(result => {
495
+ if (result.status === 'fulfilled')
496
+ return result.value;
497
+ return { name: 'unknown', status: false, error: result.reason };
498
+ });
499
+ const allOk = results.every(r => r.status);
500
+ const status = allOk ? http_status_codes_1.HttpStatusCodes.OK : http_status_codes_1.HttpStatusCodes.SERVICE_UNAVAILABLE;
501
+ const response = new response_utils_1.SuccessResponse(allOk ? 'OK' : 'Service unavailable');
502
+ if (!allOk)
503
+ response.error = true;
504
+ if ((_a = this.config.healthCheck) === null || _a === void 0 ? void 0 : _a.detailed)
505
+ response.data = { checks: results };
506
+ return res.status(status).json(response);
507
+ }
508
+ catch (_b) {
509
+ return res
510
+ .status(http_status_codes_1.HttpStatusCodes.INTERNAL_SERVER_ERROR)
511
+ .json(new exception_utils_1.InternalServerErrorException('Health check failed'));
512
+ }
513
+ });
514
+ // Metrics endpoint
515
+ if ((_c = this.config.metrics) === null || _c === void 0 ? void 0 : _c.enable) {
516
+ const metricsPath = this.normalizePath((_d = this.config.metrics.path) !== null && _d !== void 0 ? _d : '/metrics', (_e = this.config.metrics) === null || _e === void 0 ? void 0 : _e.withGlobalPrefix);
517
+ this.app.get(metricsPath, async (_req, res) => {
518
+ res.set('Content-Type', this.register.contentType);
519
+ res.end(await this.register.metrics());
520
+ });
521
+ }
522
+ // Application routes
523
+ const routerToUse = this.externalRouter || this.rootRouter;
524
+ this.app.use(this.globalPrefix, routerToUse);
525
+ // 404 handler (must be after all other routes)
526
+ this.app.use((req, res) => {
527
+ const status = http_status_codes_1.HttpStatusCodes.NOT_FOUND;
528
+ const response = (0, response_utils_1.createFinalErrorResponse)(req, status, `Route ${req.method.toUpperCase()} ${req.path} not found`);
529
+ res.status(status).json(response);
530
+ });
531
+ // Global error handler (must be the last middleware)
532
+ this.app.use((err, req, res, next) => {
533
+ var _a;
534
+ // Check if this is a 404 error that should be handled with special logging rules
535
+ const isNotFoundError = err instanceof exception_utils_2.NotFoundException;
536
+ const shouldSkipLogging = !this.hooks.onError &&
537
+ isNotFoundError &&
538
+ ((_a = this.config.requestLogging) === null || _a === void 0 ? void 0 : _a.enable) &&
539
+ this.config.requestLogging.skipNotFoundRoutes === true;
540
+ if (this.hooks.onError) {
541
+ // Use custom error handler if provided
542
+ this.hooks.onError(err, req, res, next);
543
+ }
544
+ else {
545
+ // Default error handler with logging
546
+ const errorHandlerMiddleware = (0, middleware_utils_1.errorHandler)({
547
+ logErrors: !shouldSkipLogging,
548
+ includeDetails: env_utils_1.Env.isDev() // Only show stack traces in development
549
+ });
550
+ errorHandlerMiddleware(err, req, res, next);
551
+ }
552
+ });
553
+ }
554
+ /**
555
+ * Register a new health check function for monitoring service dependencies.
556
+ *
557
+ * Health checks are executed when the health endpoint is accessed and
558
+ * help determine if the service is ready to handle requests.
559
+ *
560
+ * Examples:
561
+ * - Database connectivity
562
+ * - External service availability
563
+ * - File system access
564
+ * - Memory/CPU usage checks
565
+ *
566
+ * @param name Unique identifier for the check (used in detailed responses)
567
+ * @param check Function returning boolean or Promise<boolean> indicating health
568
+ * @returns This instance for method chaining
569
+ */
570
+ registerHealthCheck(name, check) {
571
+ this.healthChecks.push({ name, check });
572
+ return this;
573
+ }
574
+ /**
575
+ * Get the underlying Express application instance.
576
+ * Use this for advanced Express features not exposed by this wrapper.
577
+ *
578
+ * @returns The raw Express app instance
579
+ */
580
+ getApp() {
581
+ return this.app;
582
+ }
583
+ /**
584
+ * Get the active HTTP/HTTPS server instance.
585
+ * Returns null if the server is not currently running.
586
+ *
587
+ * @returns The HTTP/HTTPS server instance or null
588
+ */
589
+ getServer() {
590
+ return this.server;
591
+ }
592
+ /**
593
+ * Start the HTTP server and begin listening for requests.
594
+ *
595
+ * This method:
596
+ * - Executes beforeStart hooks
597
+ * - Binds to the configured host/port
598
+ * - Sets up error handling for startup failures
599
+ * - Executes afterStart hooks on success
600
+ * - Logs startup information
601
+ *
602
+ * @returns Promise resolving to the running HTTP server instance
603
+ * @throws Error if server fails to start or port is already in use
604
+ */
605
+ async start() {
606
+ // Ensure initialization (middleware + routes) completed before starting
607
+ await this.initPromise;
608
+ await this.runHook('beforeStart', this.app);
609
+ return new Promise((resolve, reject) => {
610
+ try {
611
+ // Prepare listen arguments with optional host parameter
612
+ const listenArgs = [
613
+ this.config.port,
614
+ this.config.host,
615
+ async () => {
616
+ var _a, _b, _c;
617
+ const protocol = this.config.https ? 'https' : 'http';
618
+ const url = `${protocol}://${this.config.host}:${this.config.port}`;
619
+ (0, logger_utils_1.getLogger)().info(`Server running on ${url}`);
620
+ if ((_a = this.config.healthCheck) === null || _a === void 0 ? void 0 : _a.path) {
621
+ (0, logger_utils_1.getLogger)().info(`Health check available at ${url}${this.normalizePath(this.config.healthCheck.path, this.config.healthCheck.withGlobalPrefix)}`);
622
+ }
623
+ if (((_b = this.config.metrics) === null || _b === void 0 ? void 0 : _b.enable) && this.config.metrics.path) {
624
+ (0, logger_utils_1.getLogger)().info(`Metrics available at ${url}${this.normalizePath(this.config.metrics.path, this.config.metrics.withGlobalPrefix)}`);
625
+ }
626
+ if ((_c = this.config.openApi) === null || _c === void 0 ? void 0 : _c.enable) {
627
+ (0, logger_utils_1.getLogger)().info(`API docs available at ${url}${this.normalizePath(this.config.openApi.mountPath, this.config.openApi.withGlobalPrefix)}`);
628
+ }
629
+ if (this.server)
630
+ await this.runHook('afterStart', this.server);
631
+ resolve(this.server);
632
+ }
633
+ ];
634
+ if (this.config.https) {
635
+ const httpsOptions = Object.assign(Object.assign({}, this.config.https), { key: fs_1.default.readFileSync(this.config.https.key), cert: fs_1.default.readFileSync(this.config.https.cert) });
636
+ if (this.config.https.ca) {
637
+ httpsOptions.ca = fs_1.default.readFileSync(this.config.https.ca);
638
+ }
639
+ if (this.config.https.passphrase) {
640
+ httpsOptions.passphrase = this.config.https.passphrase;
641
+ }
642
+ this.server = https_1.default.createServer(httpsOptions, this.app).listen(...listenArgs);
643
+ }
644
+ else {
645
+ // Start the HTTP server
646
+ this.server = this.app.listen(...listenArgs);
647
+ }
648
+ // Track connections
649
+ this.server.on('connection', (conn) => {
650
+ this.connections.add(conn);
651
+ conn.on('close', () => this.connections.delete(conn));
652
+ });
653
+ // Handle server startup errors (port in use, permission denied, etc.)
654
+ this.server.on('error', err => {
655
+ (0, logger_utils_1.getLogger)().error({ err }, 'Server failed to start');
656
+ reject(err);
657
+ });
658
+ }
659
+ catch (error) {
660
+ reject(error);
661
+ }
662
+ });
663
+ }
664
+ /**
665
+ * Stop the HTTP server gracefully.
666
+ *
667
+ * This method:
668
+ * - Executes beforeStop hooks
669
+ * - Stops accepting new connections
670
+ * - Waits for existing connections to finish
671
+ * - Closes the server
672
+ * - Executes afterStop hooks
673
+ * - Logs shutdown information
674
+ *
675
+ * Graceful shutdown ensures:
676
+ * - No requests are dropped
677
+ * - Resources are properly cleaned up
678
+ * - Monitoring systems are notified
679
+ */
680
+ async stop(force = false) {
681
+ if (!this.server) {
682
+ (0, logger_utils_1.getLogger)().warn('Stop called but server is not running');
683
+ return;
684
+ }
685
+ if (this.isShuttingDown) {
686
+ (0, logger_utils_1.getLogger)().warn('Stop called while shutdown is already in progress');
687
+ return;
688
+ }
689
+ this.isShuttingDown = true;
690
+ await this.runHook('beforeStop', this.server);
691
+ const shutdownTimeout = 10000; // 10s max wait
692
+ const serverClosePromise = new Promise((resolve, reject) => {
693
+ this.server.close(async (err) => {
694
+ if (err) {
695
+ (0, logger_utils_1.getLogger)().error({ err }, 'Error while closing server');
696
+ reject(err);
697
+ return;
698
+ }
699
+ this.server = null;
700
+ this.isShuttingDown = false;
701
+ (0, logger_utils_1.getLogger)().info('Server stopped gracefully');
702
+ await this.runHook('afterStop');
703
+ resolve();
704
+ });
705
+ });
706
+ const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('Shutdown timeout')), shutdownTimeout));
707
+ try {
708
+ await Promise.race([serverClosePromise, timeoutPromise]);
709
+ }
710
+ catch (err) {
711
+ (0, logger_utils_1.getLogger)().error({ err }, 'Graceful shutdown timed out');
712
+ if (force) {
713
+ (0, logger_utils_1.getLogger)().warn('Forcing connection destroy due to shutdown timeout');
714
+ }
715
+ }
716
+ finally {
717
+ // Always clean up connections
718
+ await this.destroyConnections();
719
+ }
720
+ }
721
+ /**
722
+ * Enable graceful shutdown on OS signals for production deployment.
723
+ *
724
+ * This is essential for:
725
+ * - Container orchestration (Docker, Kubernetes)
726
+ * - Process managers (PM2, systemd)
727
+ * - Load balancer health checks
728
+ * - Zero-downtime deployments
729
+ *
730
+ * @param signals Array of process signals to listen for (default: SIGINT, SIGTERM)
731
+ */
732
+ enableGracefulShutdown(signals = ['SIGINT', 'SIGTERM']) {
733
+ signals.forEach(signal => {
734
+ process.on(signal, async () => {
735
+ (0, logger_utils_1.getLogger)().info(`Received ${signal}, initiating graceful shutdown...`);
736
+ try {
737
+ await this.stop();
738
+ process.exit(0);
739
+ }
740
+ catch (err) {
741
+ (0, logger_utils_1.getLogger)().error({ err }, 'Error during graceful shutdown, forcing stop...');
742
+ try {
743
+ await this.stop(true); // fallback to forced shutdown
744
+ process.exit(1);
745
+ }
746
+ catch (forceError) {
747
+ (0, logger_utils_1.getLogger)().fatal({ forceError }, 'Forced shutdown failed, exiting hard');
748
+ process.exit(1);
749
+ }
750
+ }
751
+ });
752
+ });
753
+ return this;
754
+ }
755
+ /**
756
+ * Set an externally created base router.
757
+ * This will override the internal rootRouter.
758
+ */
759
+ setBaseRouter(router) {
760
+ this.externalRouter = router;
761
+ return this;
762
+ }
763
+ /**
764
+ * Create and register a new router (only used if not injecting one externally - use `setBaseRouter` instead).
765
+ */
766
+ createRouter(prefix = '') {
767
+ const router = express_1.default.Router();
768
+ const path = this.normalizePath(prefix, true);
769
+ this.rootRouter.use(path, router);
770
+ return router;
771
+ }
772
+ /**
773
+ * Register a new route handler with support for multiple HTTP methods.
774
+ * The route is automatically registered under the globalPrefix if set.
775
+ *
776
+ * @param methods Array of HTTP methods (get, post, put, delete, etc.)
777
+ * @param path Route path with Express path patterns support
778
+ * @param handlers One or more Express request handlers (middleware + final handler)
779
+ * @returns This instance for method chaining
780
+ */
781
+ registerRoute(methods, path, ...handlers) {
782
+ const fullPath = this.normalizePath(path, true);
783
+ const methodMap = {
784
+ get: this.app.get.bind(this.app),
785
+ post: this.app.post.bind(this.app),
786
+ put: this.app.put.bind(this.app),
787
+ delete: this.app.delete.bind(this.app),
788
+ patch: this.app.patch.bind(this.app),
789
+ options: this.app.options.bind(this.app),
790
+ head: this.app.head.bind(this.app)
791
+ };
792
+ methods.forEach(m => {
793
+ const fn = methodMap[m];
794
+ if (fn) {
795
+ fn(fullPath, ...handlers);
796
+ }
797
+ else {
798
+ throw new Error(`Unsupported HTTP method: ${m}`);
799
+ }
800
+ });
801
+ return this;
802
+ }
803
+ /**
804
+ * Register custom middleware with optional path restriction.
805
+ *
806
+ * Use this for:
807
+ * - Adding authentication to specific routes
808
+ * - Custom logging or validation
809
+ * - Request transformation
810
+ * - Third-party middleware integration
811
+ *
812
+ * @param path Optional path prefix or middleware function if no path
813
+ * @param middleware Middleware handler (required if path is provided)
814
+ * @returns This instance for method chaining
815
+ */
816
+ registerMiddleware(path, middleware) {
817
+ if (typeof path === 'string') {
818
+ const normalizedPath = this.normalizePath(path);
819
+ if (normalizedPath) {
820
+ this.app.use(normalizedPath, middleware);
821
+ }
822
+ else {
823
+ this.app.use(middleware);
824
+ }
825
+ }
826
+ else {
827
+ this.app.use(path);
828
+ }
829
+ return this;
830
+ }
831
+ /**
832
+ * Register one or more middleware functions to be applied globally.
833
+ * This is a simpler alternative to registerMiddleware when you just want
834
+ * to add middleware without path restrictions.
835
+ *
836
+ * @param middlewares One or more Express middleware functions
837
+ * @returns This instance for method chaining
838
+ */
839
+ useMiddleware(...middlewares) {
840
+ middlewares.forEach(middleware => {
841
+ this.app.use(middleware);
842
+ });
843
+ return this;
844
+ }
845
+ /**
846
+ * Get Prometheus registry (to add custom counters/histograms)
847
+ *
848
+ * @return {*} {client.Registry}
849
+ */
850
+ getMetricsRegistry() {
851
+ return this.register;
852
+ }
853
+ /**
854
+ * Get server configuration
855
+ *
856
+ * @return {*} {ServerConfig}
857
+ */
858
+ getConfig() {
859
+ return this.config;
860
+ }
861
+ /**
862
+ * Wait until server initialization (middleware + routes) has completed.
863
+ * Useful for integration tests that inspect app before starting.
864
+ */
865
+ async waitUntilReady() {
866
+ await this.initPromise;
867
+ }
868
+ normalizePath(path, withGlobalPrefix = false) {
869
+ const sanitize = (p) => {
870
+ return ('/' +
871
+ p
872
+ .trim()
873
+ .replace(/^\/+/, '') // remove leading slashes
874
+ .replace(/\/{2,}/g, '/') // collapse multiple slashes
875
+ .replace(/\/+$/, '')); // remove trailing slash
876
+ };
877
+ // Resolve global prefix if enabled
878
+ const prefix = withGlobalPrefix && this.globalPrefix ? sanitize(this.globalPrefix) : '';
879
+ // If path is invalid, default to prefix or root
880
+ if (typeof path !== 'string' || !path.trim()) {
881
+ return prefix || '/';
882
+ }
883
+ return sanitize(prefix + '/' + path);
884
+ }
885
+ normalizeRouteForMetrics(req, res) {
886
+ var _a;
887
+ // Prevent high cardinality metrics by normalizing routes
888
+ if ((_a = req === null || req === void 0 ? void 0 : req.route) === null || _a === void 0 ? void 0 : _a.path) {
889
+ // Use Express route pattern instead of actual URL
890
+ return req.route.path;
891
+ }
892
+ // Group common patterns
893
+ if (res.statusCode === 404)
894
+ return '/404';
895
+ const path = (req.path || 'unknown').split('?')[0];
896
+ // Replace IDs and UUIDs with placeholders
897
+ return path
898
+ .replace(/\/[0-9]+/g, '/:id')
899
+ .replace(/\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/g, '/:uuid');
900
+ }
901
+ /**
902
+ * Destroy all active connections (gracefully if possible).
903
+ * If a connection does not close cleanly, it will be force-destroyed.
904
+ */
905
+ async destroyConnections() {
906
+ const total = this.connections.size;
907
+ if (total === 0) {
908
+ (0, logger_utils_1.getLogger)().debug('No active connections to close');
909
+ return;
910
+ }
911
+ const timeoutMs = 5000;
912
+ await Promise.race([
913
+ Promise.all(Array.from(this.connections).map(conn => new Promise(resolve => {
914
+ conn.end(() => {
915
+ if (!conn.destroyed)
916
+ conn.destroy();
917
+ resolve();
918
+ });
919
+ conn.on('error', () => {
920
+ conn.destroy();
921
+ resolve();
922
+ });
923
+ }))),
924
+ new Promise(resolve => setTimeout(resolve, timeoutMs))
925
+ ]);
926
+ this.connections.clear();
927
+ (0, logger_utils_1.getLogger)().info(`Closed ${total} active connections`);
928
+ }
929
+ async validateHttpsFiles() {
930
+ if (!(await (0, fs_utils_1.fileExists)(this.config.https.key))) {
931
+ throw new Error(`HTTPS key file not found: ${this.config.https.key}`);
932
+ }
933
+ if (!(await (0, fs_utils_1.fileExists)(this.config.https.cert))) {
934
+ throw new Error(`HTTPS cert file not found: ${this.config.https.cert}`);
935
+ }
936
+ if (this.config.https.ca && !(await (0, fs_utils_1.fileExists)(this.config.https.ca))) {
937
+ throw new Error(`HTTPS CA file not found: ${this.config.https.ca}`);
938
+ }
939
+ }
940
+ static isBuiltServerConfig(config) {
941
+ if (config[server_builder_1.BUILD_MARKER]) {
942
+ return true;
943
+ }
944
+ return false;
945
+ }
946
+ }
947
+ exports.ExpressServer = ExpressServer;
948
+ //# sourceMappingURL=server.js.map