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