@catbee/utils 0.0.8-rc.0 → 0.0.8-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -1
- package/build/esm/config.d.ts +0 -1
- package/build/esm/config.js +6 -7
- package/build/esm/index.d.ts +0 -1
- package/build/esm/index.js +0 -1
- package/build/esm/servers/server.builder.d.ts +0 -1
- package/build/esm/servers/server.builder.js +99 -147
- package/build/esm/servers/server.d.ts +0 -1
- package/build/esm/servers/server.js +591 -876
- package/build/esm/types/api-response.d.ts +0 -1
- package/build/esm/types/api-response.js +0 -1
- package/build/esm/types/index.d.ts +0 -1
- package/build/esm/types/index.js +0 -1
- package/build/esm/types/server.d.ts +0 -1
- package/build/esm/types/server.js +0 -1
- package/build/esm/utils/array.utils.d.ts +0 -1
- package/build/esm/utils/array.utils.js +48 -86
- package/build/esm/utils/async.utils.d.ts +0 -1
- package/build/esm/utils/async.utils.js +247 -524
- package/build/esm/utils/cache.utils.d.ts +0 -1
- package/build/esm/utils/cache.utils.js +96 -301
- package/build/esm/utils/context-store.utils.d.ts +0 -1
- package/build/esm/utils/context-store.utils.js +62 -79
- package/build/esm/utils/crypto.utils.d.ts +0 -1
- package/build/esm/utils/crypto.utils.js +78 -175
- package/build/esm/utils/date.utils.d.ts +0 -1
- package/build/esm/utils/date.utils.js +47 -53
- package/build/esm/utils/decorators.utils.d.ts +201 -5
- package/build/esm/utils/decorators.utils.js +632 -285
- package/build/esm/utils/dir.utils.d.ts +0 -1
- package/build/esm/utils/dir.utils.js +264 -786
- package/build/esm/utils/env.utils.d.ts +0 -1
- package/build/esm/utils/env.utils.js +180 -271
- package/build/esm/utils/exception.utils.d.ts +0 -1
- package/build/esm/utils/exception.utils.js +112 -289
- package/build/esm/utils/fs.utils.d.ts +0 -1
- package/build/esm/utils/fs.utils.js +169 -401
- package/build/esm/utils/http-status-codes.d.ts +0 -1
- package/build/esm/utils/http-status-codes.js +0 -1
- package/build/esm/utils/id.utils.d.ts +0 -1
- package/build/esm/utils/id.utils.js +5 -9
- package/build/esm/utils/logger.utils.d.ts +0 -1
- package/build/esm/utils/logger.utils.js +34 -71
- package/build/esm/utils/middleware.utils.d.ts +0 -1
- package/build/esm/utils/middleware.utils.js +65 -155
- package/build/esm/utils/obj.utils.d.ts +0 -1
- package/build/esm/utils/obj.utils.js +69 -176
- package/build/esm/utils/performance.utils.d.ts +0 -1
- package/build/esm/utils/performance.utils.js +92 -195
- package/build/esm/utils/request.utils.d.ts +0 -1
- package/build/esm/utils/request.utils.js +41 -74
- package/build/esm/utils/response.utils.d.ts +0 -1
- package/build/esm/utils/response.utils.js +84 -107
- package/build/esm/utils/stream.utils.d.ts +0 -1
- package/build/esm/utils/stream.utils.js +38 -113
- package/build/esm/utils/string.utils.d.ts +0 -1
- package/build/esm/utils/string.utils.js +6 -11
- package/build/esm/utils/type.utils.d.ts +0 -1
- package/build/esm/utils/type.utils.js +8 -12
- package/build/esm/utils/url.utils.d.ts +0 -1
- package/build/esm/utils/url.utils.js +49 -127
- package/build/esm/utils/validate.utils.d.ts +0 -1
- package/build/esm/utils/validate.utils.js +23 -40
- package/package.json +3 -33
- package/build/esm/config.js.map +0 -1
- package/build/esm/index.js.map +0 -1
- package/build/esm/servers/server.builder.js.map +0 -1
- package/build/esm/servers/server.js.map +0 -1
- package/build/esm/types/api-response.js.map +0 -1
- package/build/esm/types/index.js.map +0 -1
- package/build/esm/types/server.js.map +0 -1
- package/build/esm/utils/array.utils.js.map +0 -1
- package/build/esm/utils/async.utils.js.map +0 -1
- package/build/esm/utils/cache.utils.js.map +0 -1
- package/build/esm/utils/context-store.utils.js.map +0 -1
- package/build/esm/utils/crypto.utils.js.map +0 -1
- package/build/esm/utils/date.utils.js.map +0 -1
- package/build/esm/utils/decorators.utils.js.map +0 -1
- package/build/esm/utils/dir.utils.js.map +0 -1
- package/build/esm/utils/env.utils.js.map +0 -1
- package/build/esm/utils/exception.utils.js.map +0 -1
- package/build/esm/utils/fs.utils.js.map +0 -1
- package/build/esm/utils/http-status-codes.js.map +0 -1
- package/build/esm/utils/id.utils.js.map +0 -1
- package/build/esm/utils/logger.utils.js.map +0 -1
- package/build/esm/utils/middleware.utils.js.map +0 -1
- package/build/esm/utils/obj.utils.js.map +0 -1
- package/build/esm/utils/performance.utils.js.map +0 -1
- package/build/esm/utils/request.utils.js.map +0 -1
- package/build/esm/utils/response.utils.js.map +0 -1
- package/build/esm/utils/stream.utils.js.map +0 -1
- package/build/esm/utils/string.utils.js.map +0 -1
- package/build/esm/utils/type.utils.js.map +0 -1
- package/build/esm/utils/url.utils.js.map +0 -1
- package/build/esm/utils/validate.utils.js.map +0 -1
- package/build/esnext/config.d.ts +0 -122
- package/build/esnext/config.js +0 -133
- package/build/esnext/config.js.map +0 -1
- package/build/esnext/index.d.ts +0 -27
- package/build/esnext/index.js +0 -50
- package/build/esnext/index.js.map +0 -1
- package/build/esnext/servers/server.builder.d.ts +0 -508
- package/build/esnext/servers/server.builder.js +0 -657
- package/build/esnext/servers/server.builder.js.map +0 -1
- package/build/esnext/servers/server.d.ts +0 -256
- package/build/esnext/servers/server.js +0 -955
- package/build/esnext/servers/server.js.map +0 -1
- package/build/esnext/types/api-response.d.ts +0 -152
- package/build/esnext/types/api-response.js +0 -34
- package/build/esnext/types/api-response.js.map +0 -1
- package/build/esnext/types/index.d.ts +0 -125
- package/build/esnext/types/index.js +0 -25
- package/build/esnext/types/index.js.map +0 -1
- package/build/esnext/types/server.d.ts +0 -268
- package/build/esnext/types/server.js +0 -25
- package/build/esnext/types/server.js.map +0 -1
- package/build/esnext/utils/array.utils.d.ts +0 -168
- package/build/esnext/utils/array.utils.js +0 -345
- package/build/esnext/utils/array.utils.js.map +0 -1
- package/build/esnext/utils/async.utils.d.ts +0 -265
- package/build/esnext/utils/async.utils.js +0 -620
- package/build/esnext/utils/async.utils.js.map +0 -1
- package/build/esnext/utils/cache.utils.d.ts +0 -153
- package/build/esnext/utils/cache.utils.js +0 -293
- package/build/esnext/utils/cache.utils.js.map +0 -1
- package/build/esnext/utils/context-store.utils.d.ts +0 -189
- package/build/esnext/utils/context-store.utils.js +0 -289
- package/build/esnext/utils/context-store.utils.js.map +0 -1
- package/build/esnext/utils/crypto.utils.d.ts +0 -160
- package/build/esnext/utils/crypto.utils.js +0 -276
- package/build/esnext/utils/crypto.utils.js.map +0 -1
- package/build/esnext/utils/date.utils.d.ts +0 -159
- package/build/esnext/utils/date.utils.js +0 -384
- package/build/esnext/utils/date.utils.js.map +0 -1
- package/build/esnext/utils/decorators.utils.d.ts +0 -315
- package/build/esnext/utils/decorators.utils.js +0 -504
- package/build/esnext/utils/decorators.utils.js.map +0 -1
- package/build/esnext/utils/dir.utils.d.ts +0 -196
- package/build/esnext/utils/dir.utils.js +0 -477
- package/build/esnext/utils/dir.utils.js.map +0 -1
- package/build/esnext/utils/env.utils.d.ts +0 -377
- package/build/esnext/utils/env.utils.js +0 -785
- package/build/esnext/utils/env.utils.js.map +0 -1
- package/build/esnext/utils/exception.utils.d.ts +0 -230
- package/build/esnext/utils/exception.utils.js +0 -381
- package/build/esnext/utils/exception.utils.js.map +0 -1
- package/build/esnext/utils/fs.utils.d.ts +0 -164
- package/build/esnext/utils/fs.utils.js +0 -349
- package/build/esnext/utils/fs.utils.js.map +0 -1
- package/build/esnext/utils/http-status-codes.d.ts +0 -266
- package/build/esnext/utils/http-status-codes.js +0 -295
- package/build/esnext/utils/http-status-codes.js.map +0 -1
- package/build/esnext/utils/id.utils.d.ts +0 -36
- package/build/esnext/utils/id.utils.js +0 -84
- package/build/esnext/utils/id.utils.js.map +0 -1
- package/build/esnext/utils/logger.utils.d.ts +0 -160
- package/build/esnext/utils/logger.utils.js +0 -300
- package/build/esnext/utils/logger.utils.js.map +0 -1
- package/build/esnext/utils/middleware.utils.d.ts +0 -100
- package/build/esnext/utils/middleware.utils.js +0 -236
- package/build/esnext/utils/middleware.utils.js.map +0 -1
- package/build/esnext/utils/obj.utils.d.ts +0 -124
- package/build/esnext/utils/obj.utils.js +0 -413
- package/build/esnext/utils/obj.utils.js.map +0 -1
- package/build/esnext/utils/performance.utils.d.ts +0 -136
- package/build/esnext/utils/performance.utils.js +0 -271
- package/build/esnext/utils/performance.utils.js.map +0 -1
- package/build/esnext/utils/request.utils.d.ts +0 -86
- package/build/esnext/utils/request.utils.js +0 -186
- package/build/esnext/utils/request.utils.js.map +0 -1
- package/build/esnext/utils/response.utils.d.ts +0 -163
- package/build/esnext/utils/response.utils.js +0 -247
- package/build/esnext/utils/response.utils.js.map +0 -1
- package/build/esnext/utils/stream.utils.d.ts +0 -88
- package/build/esnext/utils/stream.utils.js +0 -210
- package/build/esnext/utils/stream.utils.js.map +0 -1
- package/build/esnext/utils/string.utils.d.ts +0 -93
- package/build/esnext/utils/string.utils.js +0 -165
- package/build/esnext/utils/string.utils.js.map +0 -1
- package/build/esnext/utils/type.utils.d.ts +0 -90
- package/build/esnext/utils/type.utils.js +0 -187
- package/build/esnext/utils/type.utils.js.map +0 -1
- package/build/esnext/utils/url.utils.d.ts +0 -141
- package/build/esnext/utils/url.utils.js +0 -304
- package/build/esnext/utils/url.utils.js.map +0 -1
- package/build/esnext/utils/validate.utils.d.ts +0 -177
- package/build/esnext/utils/validate.utils.js +0 -320
- package/build/esnext/utils/validate.utils.js.map +0 -1
- package/build/src/config.d.ts +0 -122
- package/build/src/config.js +0 -138
- package/build/src/config.js.map +0 -1
- package/build/src/index.d.ts +0 -27
- package/build/src/index.js +0 -69
- package/build/src/index.js.map +0 -1
- package/build/src/servers/server.builder.d.ts +0 -508
- package/build/src/servers/server.builder.js +0 -661
- package/build/src/servers/server.builder.js.map +0 -1
- package/build/src/servers/server.d.ts +0 -256
- package/build/src/servers/server.js +0 -962
- package/build/src/servers/server.js.map +0 -1
- package/build/src/types/api-response.d.ts +0 -152
- package/build/src/types/api-response.js +0 -37
- package/build/src/types/api-response.js.map +0 -1
- package/build/src/types/index.d.ts +0 -125
- package/build/src/types/index.js +0 -26
- package/build/src/types/index.js.map +0 -1
- package/build/src/types/server.d.ts +0 -268
- package/build/src/types/server.js +0 -26
- package/build/src/types/server.js.map +0 -1
- package/build/src/utils/array.utils.d.ts +0 -168
- package/build/src/utils/array.utils.js +0 -364
- package/build/src/utils/array.utils.js.map +0 -1
- package/build/src/utils/async.utils.d.ts +0 -265
- package/build/src/utils/async.utils.js +0 -641
- package/build/src/utils/async.utils.js.map +0 -1
- package/build/src/utils/cache.utils.d.ts +0 -153
- package/build/src/utils/cache.utils.js +0 -297
- package/build/src/utils/cache.utils.js.map +0 -1
- package/build/src/utils/context-store.utils.d.ts +0 -189
- package/build/src/utils/context-store.utils.js +0 -296
- package/build/src/utils/context-store.utils.js.map +0 -1
- package/build/src/utils/crypto.utils.d.ts +0 -160
- package/build/src/utils/crypto.utils.js +0 -293
- package/build/src/utils/crypto.utils.js.map +0 -1
- package/build/src/utils/date.utils.d.ts +0 -159
- package/build/src/utils/date.utils.js +0 -396
- package/build/src/utils/date.utils.js.map +0 -1
- package/build/src/utils/decorators.utils.d.ts +0 -315
- package/build/src/utils/decorators.utils.js +0 -516
- package/build/src/utils/decorators.utils.js.map +0 -1
- package/build/src/utils/dir.utils.d.ts +0 -196
- package/build/src/utils/dir.utils.js +0 -501
- package/build/src/utils/dir.utils.js.map +0 -1
- package/build/src/utils/env.utils.d.ts +0 -377
- package/build/src/utils/env.utils.js +0 -789
- package/build/src/utils/env.utils.js.map +0 -1
- package/build/src/utils/exception.utils.d.ts +0 -230
- package/build/src/utils/exception.utils.js +0 -407
- package/build/src/utils/exception.utils.js.map +0 -1
- package/build/src/utils/fs.utils.d.ts +0 -164
- package/build/src/utils/fs.utils.js +0 -372
- package/build/src/utils/fs.utils.js.map +0 -1
- package/build/src/utils/http-status-codes.d.ts +0 -266
- package/build/src/utils/http-status-codes.js +0 -298
- package/build/src/utils/http-status-codes.js.map +0 -1
- package/build/src/utils/id.utils.d.ts +0 -36
- package/build/src/utils/id.utils.js +0 -91
- package/build/src/utils/id.utils.js.map +0 -1
- package/build/src/utils/logger.utils.d.ts +0 -160
- package/build/src/utils/logger.utils.js +0 -346
- package/build/src/utils/logger.utils.js.map +0 -1
- package/build/src/utils/middleware.utils.d.ts +0 -100
- package/build/src/utils/middleware.utils.js +0 -244
- package/build/src/utils/middleware.utils.js.map +0 -1
- package/build/src/utils/obj.utils.d.ts +0 -124
- package/build/src/utils/obj.utils.js +0 -429
- package/build/src/utils/obj.utils.js.map +0 -1
- package/build/src/utils/performance.utils.d.ts +0 -136
- package/build/src/utils/performance.utils.js +0 -278
- package/build/src/utils/performance.utils.js.map +0 -1
- package/build/src/utils/request.utils.d.ts +0 -86
- package/build/src/utils/request.utils.js +0 -195
- package/build/src/utils/request.utils.js.map +0 -1
- package/build/src/utils/response.utils.d.ts +0 -163
- package/build/src/utils/response.utils.js +0 -260
- package/build/src/utils/response.utils.js.map +0 -1
- package/build/src/utils/stream.utils.d.ts +0 -88
- package/build/src/utils/stream.utils.js +0 -218
- package/build/src/utils/stream.utils.js.map +0 -1
- package/build/src/utils/string.utils.d.ts +0 -93
- package/build/src/utils/string.utils.js +0 -179
- package/build/src/utils/string.utils.js.map +0 -1
- package/build/src/utils/type.utils.d.ts +0 -90
- package/build/src/utils/type.utils.js +0 -196
- package/build/src/utils/type.utils.js.map +0 -1
- package/build/src/utils/url.utils.d.ts +0 -141
- package/build/src/utils/url.utils.js +0 -317
- package/build/src/utils/url.utils.js.map +0 -1
- package/build/src/utils/validate.utils.d.ts +0 -177
- package/build/src/utils/validate.utils.js +0 -345
- package/build/src/utils/validate.utils.js.map +0 -1
|
@@ -21,78 +21,6 @@
|
|
|
21
21
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
22
|
* SOFTWARE.
|
|
23
23
|
*/
|
|
24
|
-
var __assign = (this && this.__assign) || function () {
|
|
25
|
-
__assign = Object.assign || function(t) {
|
|
26
|
-
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
27
|
-
s = arguments[i];
|
|
28
|
-
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
29
|
-
t[p] = s[p];
|
|
30
|
-
}
|
|
31
|
-
return t;
|
|
32
|
-
};
|
|
33
|
-
return __assign.apply(this, arguments);
|
|
34
|
-
};
|
|
35
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
36
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
37
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
38
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
39
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
40
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
41
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
42
|
-
});
|
|
43
|
-
};
|
|
44
|
-
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
45
|
-
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
|
|
46
|
-
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
47
|
-
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
48
|
-
function step(op) {
|
|
49
|
-
if (f) throw new TypeError("Generator is already executing.");
|
|
50
|
-
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
51
|
-
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
52
|
-
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
53
|
-
switch (op[0]) {
|
|
54
|
-
case 0: case 1: t = op; break;
|
|
55
|
-
case 4: _.label++; return { value: op[1], done: false };
|
|
56
|
-
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
57
|
-
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
58
|
-
default:
|
|
59
|
-
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
60
|
-
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
61
|
-
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
62
|
-
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
63
|
-
if (t[2]) _.ops.pop();
|
|
64
|
-
_.trys.pop(); continue;
|
|
65
|
-
}
|
|
66
|
-
op = body.call(thisArg, _);
|
|
67
|
-
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
68
|
-
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
69
|
-
}
|
|
70
|
-
};
|
|
71
|
-
var __read = (this && this.__read) || function (o, n) {
|
|
72
|
-
var m = typeof Symbol === "function" && o[Symbol.iterator];
|
|
73
|
-
if (!m) return o;
|
|
74
|
-
var i = m.call(o), r, ar = [], e;
|
|
75
|
-
try {
|
|
76
|
-
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
|
|
77
|
-
}
|
|
78
|
-
catch (error) { e = { error: error }; }
|
|
79
|
-
finally {
|
|
80
|
-
try {
|
|
81
|
-
if (r && !r.done && (m = i["return"])) m.call(i);
|
|
82
|
-
}
|
|
83
|
-
finally { if (e) throw e.error; }
|
|
84
|
-
}
|
|
85
|
-
return ar;
|
|
86
|
-
};
|
|
87
|
-
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
|
|
88
|
-
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
|
|
89
|
-
if (ar || !(i in from)) {
|
|
90
|
-
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
|
|
91
|
-
ar[i] = from[i];
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
return to.concat(ar || Array.prototype.slice.call(from));
|
|
95
|
-
};
|
|
96
24
|
import express from 'express';
|
|
97
25
|
import https from 'https';
|
|
98
26
|
import { HttpStatusCodes } from '../utils/http-status-codes';
|
|
@@ -122,7 +50,39 @@ import { isPort } from '../utils/validate.utils';
|
|
|
122
50
|
* Designed for microservices and production workloads.
|
|
123
51
|
* Includes K8s readiness probes and zero-downtime support.
|
|
124
52
|
*/
|
|
125
|
-
|
|
53
|
+
export class ExpressServer {
|
|
54
|
+
/** Prometheus client registry for metrics collection */
|
|
55
|
+
register = null;
|
|
56
|
+
/** HTTP server instance (null when not running) */
|
|
57
|
+
server = null;
|
|
58
|
+
/** Merged configuration with defaults applied */
|
|
59
|
+
config;
|
|
60
|
+
/** User-defined lifecycle hooks */
|
|
61
|
+
hooks;
|
|
62
|
+
/** Global API prefix (from config) */
|
|
63
|
+
globalPrefix;
|
|
64
|
+
/** Internal fallback router */
|
|
65
|
+
rootRouter;
|
|
66
|
+
/** User-supplied router */
|
|
67
|
+
externalRouter;
|
|
68
|
+
/** Internal Express app instance */
|
|
69
|
+
app;
|
|
70
|
+
/** Set of active WebSocket connections */
|
|
71
|
+
connections = new Set();
|
|
72
|
+
/** Flag indicating if the server is shutting down */
|
|
73
|
+
isShuttingDown = false;
|
|
74
|
+
/**
|
|
75
|
+
* Collection of registered health check functions.
|
|
76
|
+
* These are executed when the health check endpoint is accessed.
|
|
77
|
+
*/
|
|
78
|
+
healthChecks = [];
|
|
79
|
+
/** Prometheus metrics for monitoring */
|
|
80
|
+
requestCounter;
|
|
81
|
+
routeTimings;
|
|
82
|
+
requestSizes;
|
|
83
|
+
clientIPs;
|
|
84
|
+
/** Promise that resolves when initialization (middleware + routes) is complete */
|
|
85
|
+
initPromise;
|
|
126
86
|
/**
|
|
127
87
|
* Initializes server with intelligent defaults and security best practices.
|
|
128
88
|
* All settings can be customized via config and hooks.
|
|
@@ -140,23 +100,7 @@ var ExpressServer = /** @class */ (function () {
|
|
|
140
100
|
* - Health checks
|
|
141
101
|
* - Request tracing
|
|
142
102
|
*/
|
|
143
|
-
|
|
144
|
-
var _a;
|
|
145
|
-
if (hooks === void 0) { hooks = {}; }
|
|
146
|
-
var _b, _c, _d;
|
|
147
|
-
/** Prometheus client registry for metrics collection */
|
|
148
|
-
this.register = null;
|
|
149
|
-
/** HTTP server instance (null when not running) */
|
|
150
|
-
this.server = null;
|
|
151
|
-
/** Set of active WebSocket connections */
|
|
152
|
-
this.connections = new Set();
|
|
153
|
-
/** Flag indicating if the server is shutting down */
|
|
154
|
-
this.isShuttingDown = false;
|
|
155
|
-
/**
|
|
156
|
-
* Collection of registered health check functions.
|
|
157
|
-
* These are executed when the health check endpoint is accessed.
|
|
158
|
-
*/
|
|
159
|
-
this.healthChecks = [];
|
|
103
|
+
constructor(config, hooks = {}) {
|
|
160
104
|
if (ExpressServer.isBuiltServerConfig(config)) {
|
|
161
105
|
this.config = config;
|
|
162
106
|
}
|
|
@@ -165,13 +109,13 @@ var ExpressServer = /** @class */ (function () {
|
|
|
165
109
|
this.config = deepObjMerge({}, defaultServerConfig, config);
|
|
166
110
|
}
|
|
167
111
|
if (!isPort(this.config.port)) {
|
|
168
|
-
getLogger().error(
|
|
112
|
+
getLogger().error(`Port must be a valid number between 1 and 65535, got: ${this.config.port}`);
|
|
169
113
|
process.exit(1);
|
|
170
114
|
}
|
|
171
115
|
// Sanitize app name for metrics (replace invalid characters with underscore)
|
|
172
|
-
|
|
173
|
-
if (
|
|
174
|
-
|
|
116
|
+
const safeAppName = (this.config.appName || 'express_app').toLowerCase().replace(/[^a-z0-9_]/g, '_');
|
|
117
|
+
if (this.config.metrics?.enable) {
|
|
118
|
+
const client = ExpressServer.optionalRequire('prom-client');
|
|
175
119
|
if (!client) {
|
|
176
120
|
getLogger().error({ command: 'npm install prom-client' }, 'prom-client is required for metrics but not installed. Please add it to your dependencies');
|
|
177
121
|
process.exit(1);
|
|
@@ -179,27 +123,27 @@ var ExpressServer = /** @class */ (function () {
|
|
|
179
123
|
this.register = new client.Registry();
|
|
180
124
|
// Initialize Prometheus metrics with sanitized names
|
|
181
125
|
this.requestCounter = new client.Counter({
|
|
182
|
-
name:
|
|
126
|
+
name: `${safeAppName}_http_requests_total`,
|
|
183
127
|
help: 'Total HTTP requests',
|
|
184
128
|
labelNames: ['method', 'route', 'status'],
|
|
185
129
|
registers: [this.register]
|
|
186
130
|
});
|
|
187
131
|
this.routeTimings = new client.Histogram({
|
|
188
|
-
name:
|
|
132
|
+
name: `${safeAppName}_http_request_duration_seconds`,
|
|
189
133
|
help: 'Duration of HTTP requests by route',
|
|
190
134
|
labelNames: ['method', 'route', 'status'],
|
|
191
135
|
buckets: [0.1, 0.3, 0.5, 0.7, 1, 3, 5, 7, 10],
|
|
192
136
|
registers: [this.register]
|
|
193
137
|
});
|
|
194
138
|
this.requestSizes = new client.Histogram({
|
|
195
|
-
name:
|
|
139
|
+
name: `${safeAppName}_http_request_size_bytes`,
|
|
196
140
|
help: 'Size of HTTP request bodies',
|
|
197
141
|
labelNames: ['method', 'route'],
|
|
198
142
|
buckets: [100, 1000, 10000, 100000, 1000000],
|
|
199
143
|
registers: [this.register]
|
|
200
144
|
});
|
|
201
145
|
this.clientIPs = new client.Counter({
|
|
202
|
-
name:
|
|
146
|
+
name: `${safeAppName}_http_client_ip_total`,
|
|
203
147
|
help: 'Client IP request counter',
|
|
204
148
|
labelNames: ['ip', 'method'],
|
|
205
149
|
registers: [this.register]
|
|
@@ -207,15 +151,15 @@ var ExpressServer = /** @class */ (function () {
|
|
|
207
151
|
// Default system metrics (CPU, memory, event loop lag, etc.)
|
|
208
152
|
client.collectDefaultMetrics({
|
|
209
153
|
register: this.register,
|
|
210
|
-
prefix:
|
|
154
|
+
prefix: `${safeAppName}_`
|
|
211
155
|
});
|
|
212
156
|
}
|
|
213
157
|
// Health checks
|
|
214
|
-
if (
|
|
215
|
-
|
|
158
|
+
if (config.healthCheck?.checks) {
|
|
159
|
+
this.healthChecks.push(...config.healthCheck.checks);
|
|
216
160
|
}
|
|
217
161
|
// Set global prefix (normalize to empty string or "/prefix" without trailing slash)
|
|
218
|
-
this.globalPrefix = this.normalizePath(
|
|
162
|
+
this.globalPrefix = this.normalizePath(this.config.globalPrefix ?? '', false);
|
|
219
163
|
this.hooks = hooks;
|
|
220
164
|
this.app = express();
|
|
221
165
|
this.rootRouter = express.Router();
|
|
@@ -229,61 +173,27 @@ var ExpressServer = /** @class */ (function () {
|
|
|
229
173
|
* @param hook Name of the lifecycle hook to execute
|
|
230
174
|
* @param args Arguments to pass to the hook function
|
|
231
175
|
*/
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
176
|
+
async runHook(hook, ...args) {
|
|
177
|
+
try {
|
|
178
|
+
const fn = this.hooks[hook];
|
|
179
|
+
if (fn)
|
|
180
|
+
await fn.apply(null, args);
|
|
236
181
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
case 0:
|
|
242
|
-
_a.trys.push([0, 3, , 4]);
|
|
243
|
-
fn = this.hooks[hook];
|
|
244
|
-
if (!fn) return [3 /*break*/, 2];
|
|
245
|
-
return [4 /*yield*/, fn.apply(null, args)];
|
|
246
|
-
case 1:
|
|
247
|
-
_a.sent();
|
|
248
|
-
_a.label = 2;
|
|
249
|
-
case 2: return [3 /*break*/, 4];
|
|
250
|
-
case 3:
|
|
251
|
-
err_1 = _a.sent();
|
|
252
|
-
getLogger().error({ err: err_1, hook: hook }, "Error executing ".concat(hook, " hook:"));
|
|
253
|
-
return [3 /*break*/, 4];
|
|
254
|
-
case 4: return [2 /*return*/];
|
|
255
|
-
}
|
|
256
|
-
});
|
|
257
|
-
});
|
|
258
|
-
};
|
|
182
|
+
catch (err) {
|
|
183
|
+
getLogger().error({ err, hook }, `Error executing ${hook} hook:`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
259
186
|
/**
|
|
260
187
|
* Initialize the Express server with middleware and routes.
|
|
261
188
|
*/
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
return [4 /*yield*/, this.setupMiddleware()];
|
|
271
|
-
case 2:
|
|
272
|
-
// Set up middleware stack (order is critical)
|
|
273
|
-
_a.sent();
|
|
274
|
-
// Set up default routes and error handling
|
|
275
|
-
return [4 /*yield*/, this.setupRoutes()];
|
|
276
|
-
case 3:
|
|
277
|
-
// Set up default routes and error handling
|
|
278
|
-
_a.sent();
|
|
279
|
-
return [4 /*yield*/, this.runHook('afterInit', this)];
|
|
280
|
-
case 4:
|
|
281
|
-
_a.sent();
|
|
282
|
-
return [2 /*return*/];
|
|
283
|
-
}
|
|
284
|
-
});
|
|
285
|
-
});
|
|
286
|
-
};
|
|
189
|
+
async initialize() {
|
|
190
|
+
await this.runHook('beforeInit', this);
|
|
191
|
+
// Set up middleware stack (order is critical)
|
|
192
|
+
await this.setupMiddleware();
|
|
193
|
+
// Set up default routes and error handling
|
|
194
|
+
await this.setupRoutes();
|
|
195
|
+
await this.runHook('afterInit', this);
|
|
196
|
+
}
|
|
287
197
|
/**
|
|
288
198
|
* Configure and register all middlewares in the optimal order.
|
|
289
199
|
*
|
|
@@ -303,290 +213,260 @@ var ExpressServer = /** @class */ (function () {
|
|
|
303
213
|
* 13. Global headers
|
|
304
214
|
* 14. Custom response hooks
|
|
305
215
|
*/
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
getLogger().error({ command: 'npm install cors' }, 'cors is required but not installed. Please add it to your dependencies');
|
|
367
|
-
process.exit(1);
|
|
368
|
-
}
|
|
369
|
-
this.app.use(cors(this.config.cors === true ? {} : this.config.cors));
|
|
370
|
-
}
|
|
371
|
-
// Global headers
|
|
372
|
-
this.app.use(function (_req, res, next) {
|
|
373
|
-
var _a, _b, _c, _d;
|
|
374
|
-
if (_this.config.globalHeaders) {
|
|
375
|
-
for (var key in _this.config.globalHeaders) {
|
|
376
|
-
var value = _this.config.globalHeaders[key];
|
|
377
|
-
res.setHeader(key, typeof value === 'function' ? value() : value);
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
if (_this.config.isMicroservice) {
|
|
381
|
-
res.setHeader('X-Microservice', _this.config.appName || 'express_app');
|
|
382
|
-
}
|
|
383
|
-
if ((_a = _this.config.serviceVersion) === null || _a === void 0 ? void 0 : _a.enable) {
|
|
384
|
-
var version = typeof ((_b = _this.config.serviceVersion) === null || _b === void 0 ? void 0 : _b.version) === 'function'
|
|
385
|
-
? _this.config.serviceVersion.version()
|
|
386
|
-
: (_c = _this.config.serviceVersion) === null || _c === void 0 ? void 0 : _c.version;
|
|
387
|
-
res.setHeader(((_d = _this.config.serviceVersion) === null || _d === void 0 ? void 0 : _d.headerName) || 'x-service-version', version || '0.0.0');
|
|
388
|
-
}
|
|
389
|
-
next();
|
|
390
|
-
});
|
|
391
|
-
// Global request timeout protection
|
|
392
|
-
if (this.config.requestTimeout) {
|
|
393
|
-
this.app.use(timeout(this.config.requestTimeout));
|
|
394
|
-
}
|
|
395
|
-
// Response time tracking for performance monitoring
|
|
396
|
-
if ((_l = this.config.responseTime) === null || _l === void 0 ? void 0 : _l.enable) {
|
|
397
|
-
this.app.use(responseTime({
|
|
398
|
-
addHeader: this.config.responseTime.addHeader,
|
|
399
|
-
logOnComplete: this.config.responseTime.logOnComplete
|
|
400
|
-
}));
|
|
401
|
-
}
|
|
402
|
-
// Rate limiting should be early to prevent unnecessary processing
|
|
403
|
-
if ((_m = this.config.rateLimit) === null || _m === void 0 ? void 0 : _m.enable) {
|
|
404
|
-
rateLimit = ExpressServer.optionalRequire('express-rate-limit');
|
|
405
|
-
if (!rateLimit) {
|
|
406
|
-
getLogger().error({ command: 'npm install express-rate-limit' }, 'express-rate-limit is required but not installed. Please add it to your dependencies');
|
|
407
|
-
process.exit(1);
|
|
408
|
-
}
|
|
409
|
-
this.app.use(rateLimit({
|
|
410
|
-
windowMs: (_o = this.config.rateLimit.windowMs) !== null && _o !== void 0 ? _o : 15 * 60 * 1000,
|
|
411
|
-
max: (_p = this.config.rateLimit.max) !== null && _p !== void 0 ? _p : 100,
|
|
412
|
-
handler: function (req, res) {
|
|
413
|
-
var _a;
|
|
414
|
-
var status = HttpStatusCodes.TOO_MANY_REQUESTS;
|
|
415
|
-
var response = createFinalErrorResponse(req, status, ((_a = _this.config.rateLimit) === null || _a === void 0 ? void 0 : _a.message) || 'Too many requests');
|
|
416
|
-
res.status(status).json(response);
|
|
417
|
-
},
|
|
418
|
-
standardHeaders: (_q = this.config.rateLimit.standardHeaders) !== null && _q !== void 0 ? _q : true,
|
|
419
|
-
legacyHeaders: (_r = this.config.rateLimit.legacyHeaders) !== null && _r !== void 0 ? _r : false
|
|
420
|
-
}));
|
|
421
|
-
}
|
|
422
|
-
// Request logging with filtering
|
|
423
|
-
if ((_s = this.config.requestLogging) === null || _s === void 0 ? void 0 : _s.enable) {
|
|
424
|
-
this.app.use(function (req, res, next) {
|
|
425
|
-
var _a, _b, _c, _d, _e, _f;
|
|
426
|
-
if (typeof ((_a = _this.config.requestLogging) === null || _a === void 0 ? void 0 : _a.ignorePaths) === 'function') {
|
|
427
|
-
var 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);
|
|
428
|
-
if (skip)
|
|
429
|
-
return next();
|
|
430
|
-
}
|
|
431
|
-
else if (Array.isArray((_d = _this.config.requestLogging) === null || _d === void 0 ? void 0 : _d.ignorePaths)) {
|
|
432
|
-
var skip = (_f = (_e = _this.config.requestLogging) === null || _e === void 0 ? void 0 : _e.ignorePaths) === null || _f === void 0 ? void 0 : _f.includes(req.path);
|
|
433
|
-
if (skip)
|
|
434
|
-
return next();
|
|
435
|
-
}
|
|
436
|
-
var logger = getLogger();
|
|
437
|
-
var incomingRequestMetaData = {
|
|
438
|
-
requestId: req.id,
|
|
439
|
-
method: req.method,
|
|
440
|
-
url: req.originalUrl || req.url,
|
|
441
|
-
ip: req.ip
|
|
442
|
-
};
|
|
443
|
-
logger.info(incomingRequestMetaData, 'Incoming Request');
|
|
444
|
-
next();
|
|
445
|
-
});
|
|
446
|
-
}
|
|
447
|
-
// Custom request preprocessing hook
|
|
448
|
-
if (this.hooks.onRequest) {
|
|
449
|
-
this.app.use(this.hooks.onRequest);
|
|
450
|
-
}
|
|
451
|
-
// Response compression for better performance
|
|
452
|
-
if (this.config.compression) {
|
|
453
|
-
compression = ExpressServer.optionalRequire('compression');
|
|
454
|
-
if (!compression) {
|
|
455
|
-
getLogger().error({ command: 'npm install compression' }, 'compression is required but not installed. Please add it to your dependencies');
|
|
456
|
-
process.exit(1);
|
|
457
|
-
}
|
|
458
|
-
if (typeof this.config.compression === 'object') {
|
|
459
|
-
this.app.use(compression(this.config.compression));
|
|
460
|
-
}
|
|
461
|
-
else {
|
|
462
|
-
this.app.use(compression());
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
// Static file serving (do NOT normalize filesystem path; only normalize route)
|
|
466
|
-
if (this.config.staticFolders) {
|
|
467
|
-
this.config.staticFolders.forEach(function (folder) {
|
|
468
|
-
var _a;
|
|
469
|
-
_this.app.use(_this.normalizePath((_a = folder.path) !== null && _a !== void 0 ? _a : '/'), express.static(folder.directory, {
|
|
470
|
-
maxAge: folder.maxAge || 0,
|
|
471
|
-
etag: folder.etag !== false,
|
|
472
|
-
immutable: folder.immutable === true,
|
|
473
|
-
lastModified: folder.lastModified !== false,
|
|
474
|
-
cacheControl: folder.cacheControl !== false
|
|
475
|
-
}));
|
|
476
|
-
getLogger().info("Serving static folder: ".concat(folder.directory, " at path ").concat(folder.path || '/'));
|
|
477
|
-
});
|
|
478
|
-
}
|
|
479
|
-
// Request body parsing with size limits
|
|
480
|
-
if (this.config.bodyParser) {
|
|
481
|
-
if (this.config.bodyParser.json) {
|
|
482
|
-
this.app.use(express.json(this.config.bodyParser.json));
|
|
483
|
-
}
|
|
484
|
-
if (this.config.bodyParser.urlencoded) {
|
|
485
|
-
this.app.use(express.urlencoded(this.config.bodyParser.urlencoded));
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
// Cookie parser middleware
|
|
489
|
-
if (this.config.cookieParser) {
|
|
490
|
-
cookieParser = ExpressServer.optionalRequire('cookie-parser');
|
|
491
|
-
if (!cookieParser) {
|
|
492
|
-
getLogger().error({ command: 'npm install cookie-parser' }, 'cookie-parser is required but not installed. Please add it to your dependencies');
|
|
493
|
-
process.exit(1);
|
|
494
|
-
}
|
|
495
|
-
if (typeof this.config.cookieParser === 'object') {
|
|
496
|
-
this.app.use(cookieParser(undefined, this.config.cookieParser));
|
|
497
|
-
}
|
|
498
|
-
else {
|
|
499
|
-
this.app.use(cookieParser());
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
if (!((_t = this.config.openApi) === null || _t === void 0 ? void 0 : _t.enable)) return [3 /*break*/, 7];
|
|
503
|
-
_y.label = 3;
|
|
504
|
-
case 3:
|
|
505
|
-
_y.trys.push([3, 6, , 7]);
|
|
506
|
-
openApiMountPath = this.normalizePath((_u = this.config.openApi.mountPath) !== null && _u !== void 0 ? _u : '/docs', this.config.openApi.withGlobalPrefix);
|
|
507
|
-
openApiFilePath = this.config.openApi.filePath;
|
|
508
|
-
if (!openApiFilePath) {
|
|
509
|
-
getLogger().error('OpenAPI file path is required');
|
|
510
|
-
process.exit(1);
|
|
511
|
-
}
|
|
512
|
-
return [4 /*yield*/, fileExists(openApiFilePath)];
|
|
513
|
-
case 4:
|
|
514
|
-
isOpenApiFilePathExists = _y.sent();
|
|
515
|
-
if (!isOpenApiFilePathExists) {
|
|
516
|
-
getLogger().error("OpenAPI spec file not found at ".concat(openApiFilePath));
|
|
517
|
-
process.exit(1);
|
|
518
|
-
}
|
|
519
|
-
if ((_v = this.config.openApi) === null || _v === void 0 ? void 0 : _v.verbose) {
|
|
520
|
-
getLogger().info("Mounting OpenAPI docs at ".concat(openApiMountPath));
|
|
521
|
-
getLogger().info("Using OpenAPI spec file at ".concat(openApiFilePath));
|
|
522
|
-
}
|
|
523
|
-
apiReference = ExpressServer.optionalRequire('@scalar/express-api-reference').apiReference;
|
|
524
|
-
if (!apiReference) {
|
|
525
|
-
getLogger().error({ command: 'npm install @scalar/express-api-reference' }, '@scalar/express-api-reference is required for OpenAPI docs but not installed. Please add it to your dependencies');
|
|
526
|
-
process.exit(1);
|
|
527
|
-
}
|
|
528
|
-
_b = (_a = this.app).use;
|
|
529
|
-
_c = [openApiMountPath];
|
|
530
|
-
_d = apiReference;
|
|
531
|
-
_e = {};
|
|
532
|
-
_f = {};
|
|
533
|
-
return [4 /*yield*/, fs.promises.readFile(openApiFilePath, 'utf8')];
|
|
534
|
-
case 5:
|
|
535
|
-
_b.apply(_a, _c.concat([_d.apply(void 0, [(_e.spec = (_f.content = _y.sent(),
|
|
536
|
-
_f),
|
|
537
|
-
_e)])]));
|
|
538
|
-
if ((_w = this.config.openApi) === null || _w === void 0 ? void 0 : _w.verbose) {
|
|
539
|
-
getLogger().info("Mounted OpenAPI docs at ".concat(openApiMountPath));
|
|
540
|
-
}
|
|
541
|
-
return [3 /*break*/, 7];
|
|
542
|
-
case 6:
|
|
543
|
-
err_2 = _y.sent();
|
|
544
|
-
getLogger().error({ err: err_2 }, 'Failed to mount OpenAPI docs');
|
|
545
|
-
return [3 /*break*/, 7];
|
|
546
|
-
case 7:
|
|
547
|
-
// Custom response preprocessing hook (apply global prefix if set)
|
|
548
|
-
if (this.hooks.onResponse) {
|
|
549
|
-
this.app.use(this.globalPrefix, this.hooks.onResponse);
|
|
550
|
-
}
|
|
551
|
-
if ((_x = this.config.metrics) === null || _x === void 0 ? void 0 : _x.enable) {
|
|
552
|
-
// Add metrics tracking middleware
|
|
553
|
-
this.app.use(function (req, res, next) {
|
|
554
|
-
var _a, _b;
|
|
555
|
-
var start = process.hrtime();
|
|
556
|
-
// Track client IPs
|
|
557
|
-
(_a = _this.clientIPs) === null || _a === void 0 ? void 0 : _a.inc({ ip: req.ip, method: req.method });
|
|
558
|
-
// Track request sizes (parse safely)
|
|
559
|
-
var cl = req.headers['content-length'];
|
|
560
|
-
if (cl) {
|
|
561
|
-
var size = Number(cl);
|
|
562
|
-
if (!Number.isNaN(size) && size >= 0) {
|
|
563
|
-
var route = _this.normalizeRouteForMetrics(req, res);
|
|
564
|
-
(_b = _this.requestSizes) === null || _b === void 0 ? void 0 : _b.observe({ method: req.method, route: route }, size);
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
res.once('finish', function () {
|
|
568
|
-
var _a, _b;
|
|
569
|
-
var _c = __read(process.hrtime(start), 2), seconds = _c[0], nanoseconds = _c[1];
|
|
570
|
-
var finalRoute = _this.normalizeRouteForMetrics(req, res);
|
|
571
|
-
(_a = _this.requestCounter) === null || _a === void 0 ? void 0 : _a.inc({
|
|
572
|
-
method: req.method,
|
|
573
|
-
route: finalRoute,
|
|
574
|
-
status: res.statusCode.toString()
|
|
575
|
-
});
|
|
576
|
-
(_b = _this.routeTimings) === null || _b === void 0 ? void 0 : _b.observe({
|
|
577
|
-
method: req.method,
|
|
578
|
-
route: finalRoute,
|
|
579
|
-
status: res.statusCode.toString()
|
|
580
|
-
}, seconds + nanoseconds / 1e9);
|
|
581
|
-
});
|
|
582
|
-
next();
|
|
583
|
-
});
|
|
584
|
-
}
|
|
585
|
-
return [2 /*return*/];
|
|
216
|
+
async setupMiddleware() {
|
|
217
|
+
if (this.config.https) {
|
|
218
|
+
await this.validateHttpsFiles();
|
|
219
|
+
}
|
|
220
|
+
// Basic middleware should be first
|
|
221
|
+
this.app.disable('x-powered-by');
|
|
222
|
+
if (this.config.trustProxy) {
|
|
223
|
+
this.app.set('trust proxy', true);
|
|
224
|
+
}
|
|
225
|
+
// Request ID generation - must be first for proper tracing
|
|
226
|
+
this.app.use(requestId({
|
|
227
|
+
headerName: this.config.requestId?.headerName,
|
|
228
|
+
exposeHeader: this.config.requestId?.exposeHeader,
|
|
229
|
+
generator: this.config.requestId?.generator
|
|
230
|
+
}));
|
|
231
|
+
// Request context setup for logging correlation
|
|
232
|
+
this.app.use(setupRequestContext({
|
|
233
|
+
headerName: this.config.requestId?.headerName,
|
|
234
|
+
autoLog: false
|
|
235
|
+
}));
|
|
236
|
+
// Early shutdown-awareness middleware (lets load balancers drain connections gracefully)
|
|
237
|
+
this.app.use((_req, res, next) => {
|
|
238
|
+
if (this.isShuttingDown) {
|
|
239
|
+
res.setHeader('Connection', 'close');
|
|
240
|
+
return res
|
|
241
|
+
.status(HttpStatusCodes.SERVICE_UNAVAILABLE)
|
|
242
|
+
.json(new ServiceUnavailableException('Server is shutting down'));
|
|
243
|
+
}
|
|
244
|
+
next();
|
|
245
|
+
return;
|
|
246
|
+
});
|
|
247
|
+
// Security middleware should come early
|
|
248
|
+
if (this.config.helmet) {
|
|
249
|
+
const helmet = ExpressServer.optionalRequire('helmet');
|
|
250
|
+
if (!helmet) {
|
|
251
|
+
getLogger().error({ command: 'npm install helmet' }, 'helmet is required but not installed. Please add it to your dependencies');
|
|
252
|
+
process.exit(1);
|
|
253
|
+
}
|
|
254
|
+
if (typeof this.config.helmet === 'object') {
|
|
255
|
+
this.app.use(helmet(this.config.helmet));
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
this.app.use(helmet());
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
// CORS middleware should be early
|
|
262
|
+
if (this.config.cors) {
|
|
263
|
+
const cors = ExpressServer.optionalRequire('cors');
|
|
264
|
+
if (!cors) {
|
|
265
|
+
getLogger().error({ command: 'npm install cors' }, 'cors is required but not installed. Please add it to your dependencies');
|
|
266
|
+
process.exit(1);
|
|
267
|
+
}
|
|
268
|
+
this.app.use(cors(this.config.cors === true ? {} : this.config.cors));
|
|
269
|
+
}
|
|
270
|
+
// Global headers
|
|
271
|
+
this.app.use((_req, res, next) => {
|
|
272
|
+
if (this.config.globalHeaders) {
|
|
273
|
+
for (const key in this.config.globalHeaders) {
|
|
274
|
+
const value = this.config.globalHeaders[key];
|
|
275
|
+
res.setHeader(key, typeof value === 'function' ? value() : value);
|
|
586
276
|
}
|
|
587
|
-
}
|
|
277
|
+
}
|
|
278
|
+
if (this.config.isMicroservice) {
|
|
279
|
+
res.setHeader('X-Microservice', this.config.appName || 'express_app');
|
|
280
|
+
}
|
|
281
|
+
if (this.config.serviceVersion?.enable) {
|
|
282
|
+
const version = typeof this.config.serviceVersion?.version === 'function'
|
|
283
|
+
? this.config.serviceVersion.version()
|
|
284
|
+
: this.config.serviceVersion?.version;
|
|
285
|
+
res.setHeader(this.config.serviceVersion?.headerName || 'x-service-version', version || '0.0.0');
|
|
286
|
+
}
|
|
287
|
+
next();
|
|
588
288
|
});
|
|
589
|
-
|
|
289
|
+
// Global request timeout protection
|
|
290
|
+
if (this.config.requestTimeout) {
|
|
291
|
+
this.app.use(timeout(this.config.requestTimeout));
|
|
292
|
+
}
|
|
293
|
+
// Response time tracking for performance monitoring
|
|
294
|
+
if (this.config.responseTime?.enable) {
|
|
295
|
+
this.app.use(responseTime({
|
|
296
|
+
addHeader: this.config.responseTime.addHeader,
|
|
297
|
+
logOnComplete: this.config.responseTime.logOnComplete
|
|
298
|
+
}));
|
|
299
|
+
}
|
|
300
|
+
// Rate limiting should be early to prevent unnecessary processing
|
|
301
|
+
if (this.config.rateLimit?.enable) {
|
|
302
|
+
const rateLimit = ExpressServer.optionalRequire('express-rate-limit');
|
|
303
|
+
if (!rateLimit) {
|
|
304
|
+
getLogger().error({ command: 'npm install express-rate-limit' }, 'express-rate-limit is required but not installed. Please add it to your dependencies');
|
|
305
|
+
process.exit(1);
|
|
306
|
+
}
|
|
307
|
+
this.app.use(rateLimit({
|
|
308
|
+
windowMs: this.config.rateLimit.windowMs ?? 15 * 60 * 1000,
|
|
309
|
+
max: this.config.rateLimit.max ?? 100,
|
|
310
|
+
handler: (req, res) => {
|
|
311
|
+
const status = HttpStatusCodes.TOO_MANY_REQUESTS;
|
|
312
|
+
const response = createFinalErrorResponse(req, status, this.config.rateLimit?.message || 'Too many requests');
|
|
313
|
+
res.status(status).json(response);
|
|
314
|
+
},
|
|
315
|
+
standardHeaders: this.config.rateLimit.standardHeaders ?? true,
|
|
316
|
+
legacyHeaders: this.config.rateLimit.legacyHeaders ?? false
|
|
317
|
+
}));
|
|
318
|
+
}
|
|
319
|
+
// Request logging with filtering
|
|
320
|
+
if (this.config.requestLogging?.enable) {
|
|
321
|
+
this.app.use((req, res, next) => {
|
|
322
|
+
if (typeof this.config.requestLogging?.ignorePaths === 'function') {
|
|
323
|
+
const skip = this.config.requestLogging?.ignorePaths?.(req, res);
|
|
324
|
+
if (skip)
|
|
325
|
+
return next();
|
|
326
|
+
}
|
|
327
|
+
else if (Array.isArray(this.config.requestLogging?.ignorePaths)) {
|
|
328
|
+
const skip = this.config.requestLogging?.ignorePaths?.includes(req.path);
|
|
329
|
+
if (skip)
|
|
330
|
+
return next();
|
|
331
|
+
}
|
|
332
|
+
const logger = getLogger();
|
|
333
|
+
const incomingRequestMetaData = {
|
|
334
|
+
requestId: req.id,
|
|
335
|
+
method: req.method,
|
|
336
|
+
url: req.originalUrl || req.url,
|
|
337
|
+
ip: req.ip
|
|
338
|
+
};
|
|
339
|
+
logger.info(incomingRequestMetaData, 'Incoming Request');
|
|
340
|
+
next();
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
// Custom request preprocessing hook
|
|
344
|
+
if (this.hooks.onRequest) {
|
|
345
|
+
this.app.use(this.hooks.onRequest);
|
|
346
|
+
}
|
|
347
|
+
// Response compression for better performance
|
|
348
|
+
if (this.config.compression) {
|
|
349
|
+
const compression = ExpressServer.optionalRequire('compression');
|
|
350
|
+
if (!compression) {
|
|
351
|
+
getLogger().error({ command: 'npm install compression' }, 'compression is required but not installed. Please add it to your dependencies');
|
|
352
|
+
process.exit(1);
|
|
353
|
+
}
|
|
354
|
+
if (typeof this.config.compression === 'object') {
|
|
355
|
+
this.app.use(compression(this.config.compression));
|
|
356
|
+
}
|
|
357
|
+
else {
|
|
358
|
+
this.app.use(compression());
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
// Static file serving (do NOT normalize filesystem path; only normalize route)
|
|
362
|
+
if (this.config.staticFolders) {
|
|
363
|
+
this.config.staticFolders.forEach(folder => {
|
|
364
|
+
this.app.use(this.normalizePath(folder.path ?? '/'), express.static(folder.directory, {
|
|
365
|
+
maxAge: folder.maxAge || 0,
|
|
366
|
+
etag: folder.etag !== false,
|
|
367
|
+
immutable: folder.immutable === true,
|
|
368
|
+
lastModified: folder.lastModified !== false,
|
|
369
|
+
cacheControl: folder.cacheControl !== false
|
|
370
|
+
}));
|
|
371
|
+
getLogger().info(`Serving static folder: ${folder.directory} at path ${folder.path || '/'}`);
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
// Request body parsing with size limits
|
|
375
|
+
if (this.config.bodyParser) {
|
|
376
|
+
if (this.config.bodyParser.json) {
|
|
377
|
+
this.app.use(express.json(this.config.bodyParser.json));
|
|
378
|
+
}
|
|
379
|
+
if (this.config.bodyParser.urlencoded) {
|
|
380
|
+
this.app.use(express.urlencoded(this.config.bodyParser.urlencoded));
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
// Cookie parser middleware
|
|
384
|
+
if (this.config.cookieParser) {
|
|
385
|
+
const cookieParser = ExpressServer.optionalRequire('cookie-parser');
|
|
386
|
+
if (!cookieParser) {
|
|
387
|
+
getLogger().error({ command: 'npm install cookie-parser' }, 'cookie-parser is required but not installed. Please add it to your dependencies');
|
|
388
|
+
process.exit(1);
|
|
389
|
+
}
|
|
390
|
+
if (typeof this.config.cookieParser === 'object') {
|
|
391
|
+
this.app.use(cookieParser(undefined, this.config.cookieParser));
|
|
392
|
+
}
|
|
393
|
+
else {
|
|
394
|
+
this.app.use(cookieParser());
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
// OpenAPI docs via @scalar/express-api-reference
|
|
398
|
+
if (this.config.openApi?.enable) {
|
|
399
|
+
try {
|
|
400
|
+
const openApiMountPath = this.normalizePath(this.config.openApi.mountPath ?? '/docs', this.config.openApi.withGlobalPrefix);
|
|
401
|
+
const openApiFilePath = this.config.openApi.filePath;
|
|
402
|
+
if (!openApiFilePath) {
|
|
403
|
+
getLogger().error('OpenAPI file path is required');
|
|
404
|
+
process.exit(1);
|
|
405
|
+
}
|
|
406
|
+
const isOpenApiFilePathExists = await fileExists(openApiFilePath);
|
|
407
|
+
if (!isOpenApiFilePathExists) {
|
|
408
|
+
getLogger().error(`OpenAPI spec file not found at ${openApiFilePath}`);
|
|
409
|
+
process.exit(1);
|
|
410
|
+
}
|
|
411
|
+
if (this.config.openApi?.verbose) {
|
|
412
|
+
getLogger().info(`Mounting OpenAPI docs at ${openApiMountPath}`);
|
|
413
|
+
getLogger().info(`Using OpenAPI spec file at ${openApiFilePath}`);
|
|
414
|
+
}
|
|
415
|
+
const apiReference = ExpressServer.optionalRequire('@scalar/express-api-reference').apiReference;
|
|
416
|
+
if (!apiReference) {
|
|
417
|
+
getLogger().error({ command: 'npm install @scalar/express-api-reference' }, '@scalar/express-api-reference is required for OpenAPI docs but not installed. Please add it to your dependencies');
|
|
418
|
+
process.exit(1);
|
|
419
|
+
}
|
|
420
|
+
this.app.use(openApiMountPath, apiReference({
|
|
421
|
+
spec: {
|
|
422
|
+
content: await fs.promises.readFile(openApiFilePath, 'utf8')
|
|
423
|
+
}
|
|
424
|
+
}));
|
|
425
|
+
if (this.config.openApi?.verbose) {
|
|
426
|
+
getLogger().info(`Mounted OpenAPI docs at ${openApiMountPath}`);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
catch (err) {
|
|
430
|
+
getLogger().error({ err }, 'Failed to mount OpenAPI docs');
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
// Custom response preprocessing hook (apply global prefix if set)
|
|
434
|
+
if (this.hooks.onResponse) {
|
|
435
|
+
this.app.use(this.globalPrefix, this.hooks.onResponse);
|
|
436
|
+
}
|
|
437
|
+
if (this.config.metrics?.enable) {
|
|
438
|
+
// Add metrics tracking middleware
|
|
439
|
+
this.app.use((req, res, next) => {
|
|
440
|
+
const start = process.hrtime();
|
|
441
|
+
// Track client IPs
|
|
442
|
+
this.clientIPs?.inc({ ip: req.ip, method: req.method });
|
|
443
|
+
// Track request sizes (parse safely)
|
|
444
|
+
const cl = req.headers['content-length'];
|
|
445
|
+
if (cl) {
|
|
446
|
+
const size = Number(cl);
|
|
447
|
+
if (!Number.isNaN(size) && size >= 0) {
|
|
448
|
+
const route = this.normalizeRouteForMetrics(req, res);
|
|
449
|
+
this.requestSizes?.observe({ method: req.method, route }, size);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
res.once('finish', () => {
|
|
453
|
+
const [seconds, nanoseconds] = process.hrtime(start);
|
|
454
|
+
const finalRoute = this.normalizeRouteForMetrics(req, res);
|
|
455
|
+
this.requestCounter?.inc({
|
|
456
|
+
method: req.method,
|
|
457
|
+
route: finalRoute,
|
|
458
|
+
status: res.statusCode.toString()
|
|
459
|
+
});
|
|
460
|
+
this.routeTimings?.observe({
|
|
461
|
+
method: req.method,
|
|
462
|
+
route: finalRoute,
|
|
463
|
+
status: res.statusCode.toString()
|
|
464
|
+
}, seconds + nanoseconds / 1e9);
|
|
465
|
+
});
|
|
466
|
+
next();
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
}
|
|
590
470
|
/**
|
|
591
471
|
* Configure server routes and error handling.
|
|
592
472
|
* Sets up in following order:
|
|
@@ -596,118 +476,82 @@ var ExpressServer = /** @class */ (function () {
|
|
|
596
476
|
* 3. 404 handler
|
|
597
477
|
* 4. Error handler
|
|
598
478
|
*/
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
this.app.get(healthCheckPath, function (_req, res) { return __awaiter(_this, void 0, void 0, function () {
|
|
607
|
-
var checkResults, results, allOk, status_1, response, _a;
|
|
608
|
-
var _this = this;
|
|
609
|
-
var _b;
|
|
610
|
-
return __generator(this, function (_c) {
|
|
611
|
-
switch (_c.label) {
|
|
612
|
-
case 0:
|
|
613
|
-
_c.trys.push([0, 2, , 3]);
|
|
614
|
-
if (!this.healthChecks.length) {
|
|
615
|
-
return [2 /*return*/, res.status(HttpStatusCodes.OK).json(new SuccessResponse('OK'))];
|
|
616
|
-
}
|
|
617
|
-
return [4 /*yield*/, Promise.allSettled(this.healthChecks.map(function (_a) { return __awaiter(_this, [_a], void 0, function (_b) {
|
|
618
|
-
var status_2, error_1;
|
|
619
|
-
var name = _b.name, check = _b.check;
|
|
620
|
-
return __generator(this, function (_c) {
|
|
621
|
-
switch (_c.label) {
|
|
622
|
-
case 0:
|
|
623
|
-
_c.trys.push([0, 2, , 3]);
|
|
624
|
-
return [4 /*yield*/, Promise.resolve(check())];
|
|
625
|
-
case 1:
|
|
626
|
-
status_2 = _c.sent();
|
|
627
|
-
return [2 /*return*/, { name: name, status: status_2, error: null }];
|
|
628
|
-
case 2:
|
|
629
|
-
error_1 = _c.sent();
|
|
630
|
-
return [2 /*return*/, { name: name, status: false, error: error_1.message }];
|
|
631
|
-
case 3: return [2 /*return*/];
|
|
632
|
-
}
|
|
633
|
-
});
|
|
634
|
-
}); }))];
|
|
635
|
-
case 1:
|
|
636
|
-
checkResults = _c.sent();
|
|
637
|
-
results = checkResults.map(function (result) {
|
|
638
|
-
if (result.status === 'fulfilled')
|
|
639
|
-
return result.value;
|
|
640
|
-
return { name: 'unknown', status: false, error: result.reason };
|
|
641
|
-
});
|
|
642
|
-
allOk = results.every(function (r) { return r.status; });
|
|
643
|
-
status_1 = allOk ? HttpStatusCodes.OK : HttpStatusCodes.SERVICE_UNAVAILABLE;
|
|
644
|
-
response = new SuccessResponse(allOk ? 'OK' : 'Service unavailable');
|
|
645
|
-
if (!allOk)
|
|
646
|
-
response.error = true;
|
|
647
|
-
if ((_b = this.config.healthCheck) === null || _b === void 0 ? void 0 : _b.detailed)
|
|
648
|
-
response.data = { checks: results };
|
|
649
|
-
return [2 /*return*/, res.status(status_1).json(response)];
|
|
650
|
-
case 2:
|
|
651
|
-
_a = _c.sent();
|
|
652
|
-
return [2 /*return*/, res
|
|
653
|
-
.status(HttpStatusCodes.INTERNAL_SERVER_ERROR)
|
|
654
|
-
.json(new InternalServerErrorException('Health check failed'))];
|
|
655
|
-
case 3: return [2 /*return*/];
|
|
656
|
-
}
|
|
657
|
-
});
|
|
658
|
-
}); });
|
|
659
|
-
// Metrics endpoint
|
|
660
|
-
if ((_c = this.config.metrics) === null || _c === void 0 ? void 0 : _c.enable) {
|
|
661
|
-
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);
|
|
662
|
-
this.app.get(metricsPath, function (_req, res) { return __awaiter(_this, void 0, void 0, function () {
|
|
663
|
-
var _a, _b;
|
|
664
|
-
return __generator(this, function (_c) {
|
|
665
|
-
switch (_c.label) {
|
|
666
|
-
case 0:
|
|
667
|
-
res.set('Content-Type', this.register.contentType);
|
|
668
|
-
_b = (_a = res).end;
|
|
669
|
-
return [4 /*yield*/, this.register.metrics()];
|
|
670
|
-
case 1:
|
|
671
|
-
_b.apply(_a, [_c.sent()]);
|
|
672
|
-
return [2 /*return*/];
|
|
673
|
-
}
|
|
674
|
-
});
|
|
675
|
-
}); });
|
|
479
|
+
async setupRoutes() {
|
|
480
|
+
// Health check endpoint
|
|
481
|
+
const healthCheckPath = this.normalizePath(this.config.healthCheck?.path || '/healthz', this.config.healthCheck?.withGlobalPrefix);
|
|
482
|
+
this.app.get(healthCheckPath, async (_req, res) => {
|
|
483
|
+
try {
|
|
484
|
+
if (!this.healthChecks.length) {
|
|
485
|
+
return res.status(HttpStatusCodes.OK).json(new SuccessResponse('OK'));
|
|
676
486
|
}
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
var status = HttpStatusCodes.NOT_FOUND;
|
|
682
|
-
var response = createFinalErrorResponse(req, status, "Route ".concat(req.method.toUpperCase(), " ").concat(req.path, " not found"));
|
|
683
|
-
res.status(status).json(response);
|
|
684
|
-
});
|
|
685
|
-
// Global error handler (must be the last middleware)
|
|
686
|
-
this.app.use(function (err, req, res, next) {
|
|
687
|
-
var _a;
|
|
688
|
-
// Check if this is a 404 error that should be handled with special logging rules
|
|
689
|
-
var isNotFoundError = err instanceof NotFoundException;
|
|
690
|
-
var shouldSkipLogging = !_this.hooks.onError &&
|
|
691
|
-
isNotFoundError &&
|
|
692
|
-
((_a = _this.config.requestLogging) === null || _a === void 0 ? void 0 : _a.enable) &&
|
|
693
|
-
_this.config.requestLogging.skipNotFoundRoutes === true;
|
|
694
|
-
if (_this.hooks.onError) {
|
|
695
|
-
// Use custom error handler if provided
|
|
696
|
-
_this.hooks.onError(err, req, res, next);
|
|
487
|
+
const checkResults = await Promise.allSettled(this.healthChecks.map(async ({ name, check }) => {
|
|
488
|
+
try {
|
|
489
|
+
const status = await Promise.resolve(check());
|
|
490
|
+
return { name, status, error: null };
|
|
697
491
|
}
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
var errorHandlerMiddleware = errorHandler({
|
|
701
|
-
logErrors: !shouldSkipLogging,
|
|
702
|
-
includeDetails: Env.isDev() // Only show stack traces in development
|
|
703
|
-
});
|
|
704
|
-
errorHandlerMiddleware(err, req, res, next);
|
|
492
|
+
catch (error) {
|
|
493
|
+
return { name, status: false, error: error.message };
|
|
705
494
|
}
|
|
495
|
+
}));
|
|
496
|
+
const results = checkResults.map(result => {
|
|
497
|
+
if (result.status === 'fulfilled')
|
|
498
|
+
return result.value;
|
|
499
|
+
return { name: 'unknown', status: false, error: result.reason };
|
|
706
500
|
});
|
|
707
|
-
|
|
501
|
+
const allOk = results.every(r => r.status);
|
|
502
|
+
const status = allOk ? HttpStatusCodes.OK : HttpStatusCodes.SERVICE_UNAVAILABLE;
|
|
503
|
+
const response = new SuccessResponse(allOk ? 'OK' : 'Service unavailable');
|
|
504
|
+
if (!allOk)
|
|
505
|
+
response.error = true;
|
|
506
|
+
if (this.config.healthCheck?.detailed)
|
|
507
|
+
response.data = { checks: results };
|
|
508
|
+
return res.status(status).json(response);
|
|
509
|
+
}
|
|
510
|
+
catch {
|
|
511
|
+
return res
|
|
512
|
+
.status(HttpStatusCodes.INTERNAL_SERVER_ERROR)
|
|
513
|
+
.json(new InternalServerErrorException('Health check failed'));
|
|
514
|
+
}
|
|
515
|
+
});
|
|
516
|
+
// Metrics endpoint
|
|
517
|
+
if (this.config.metrics?.enable) {
|
|
518
|
+
const metricsPath = this.normalizePath(this.config.metrics.path ?? '/metrics', this.config.metrics?.withGlobalPrefix);
|
|
519
|
+
this.app.get(metricsPath, async (_req, res) => {
|
|
520
|
+
res.set('Content-Type', this.register.contentType);
|
|
521
|
+
res.end(await this.register.metrics());
|
|
708
522
|
});
|
|
523
|
+
}
|
|
524
|
+
// Application routes
|
|
525
|
+
const routerToUse = this.externalRouter || this.rootRouter;
|
|
526
|
+
this.app.use(this.globalPrefix, routerToUse);
|
|
527
|
+
// 404 handler (must be after all other routes)
|
|
528
|
+
this.app.use((req, res) => {
|
|
529
|
+
const status = HttpStatusCodes.NOT_FOUND;
|
|
530
|
+
const response = createFinalErrorResponse(req, status, `Route ${req.method.toUpperCase()} ${req.path} not found`);
|
|
531
|
+
res.status(status).json(response);
|
|
532
|
+
});
|
|
533
|
+
// Global error handler (must be the last middleware)
|
|
534
|
+
this.app.use((err, req, res, next) => {
|
|
535
|
+
// Check if this is a 404 error that should be handled with special logging rules
|
|
536
|
+
const isNotFoundError = err instanceof NotFoundException;
|
|
537
|
+
const shouldSkipLogging = !this.hooks.onError &&
|
|
538
|
+
isNotFoundError &&
|
|
539
|
+
this.config.requestLogging?.enable &&
|
|
540
|
+
this.config.requestLogging.skipNotFoundRoutes === true;
|
|
541
|
+
if (this.hooks.onError) {
|
|
542
|
+
// Use custom error handler if provided
|
|
543
|
+
this.hooks.onError(err, req, res, next);
|
|
544
|
+
}
|
|
545
|
+
else {
|
|
546
|
+
// Default error handler with logging
|
|
547
|
+
const errorHandlerMiddleware = errorHandler({
|
|
548
|
+
logErrors: !shouldSkipLogging,
|
|
549
|
+
includeDetails: Env.isDev() // Only show stack traces in development
|
|
550
|
+
});
|
|
551
|
+
errorHandlerMiddleware(err, req, res, next);
|
|
552
|
+
}
|
|
709
553
|
});
|
|
710
|
-
}
|
|
554
|
+
}
|
|
711
555
|
/**
|
|
712
556
|
* Register a new health check function for monitoring service dependencies.
|
|
713
557
|
*
|
|
@@ -724,28 +568,28 @@ var ExpressServer = /** @class */ (function () {
|
|
|
724
568
|
* @param check Function returning boolean or Promise<boolean> indicating health
|
|
725
569
|
* @returns This instance for method chaining
|
|
726
570
|
*/
|
|
727
|
-
|
|
728
|
-
this.healthChecks.push({ name
|
|
571
|
+
registerHealthCheck(name, check) {
|
|
572
|
+
this.healthChecks.push({ name, check });
|
|
729
573
|
return this;
|
|
730
|
-
}
|
|
574
|
+
}
|
|
731
575
|
/**
|
|
732
576
|
* Get the underlying Express application instance.
|
|
733
577
|
* Use this for advanced Express features not exposed by this wrapper.
|
|
734
578
|
*
|
|
735
579
|
* @returns The raw Express app instance
|
|
736
580
|
*/
|
|
737
|
-
|
|
581
|
+
getApp() {
|
|
738
582
|
return this.app;
|
|
739
|
-
}
|
|
583
|
+
}
|
|
740
584
|
/**
|
|
741
585
|
* Get the active HTTP/HTTPS server instance.
|
|
742
586
|
* Returns null if the server is not currently running.
|
|
743
587
|
*
|
|
744
588
|
* @returns The HTTP/HTTPS server instance or null
|
|
745
589
|
*/
|
|
746
|
-
|
|
590
|
+
getServer() {
|
|
747
591
|
return this.server;
|
|
748
|
-
}
|
|
592
|
+
}
|
|
749
593
|
/**
|
|
750
594
|
* Start the HTTP server and begin listening for requests.
|
|
751
595
|
*
|
|
@@ -759,90 +603,68 @@ var ExpressServer = /** @class */ (function () {
|
|
|
759
603
|
* @returns Promise resolving to the running HTTP server instance
|
|
760
604
|
* @throws Error if server fails to start or port is already in use
|
|
761
605
|
*/
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
case 1:
|
|
804
|
-
_d.sent();
|
|
805
|
-
_d.label = 2;
|
|
806
|
-
case 2:
|
|
807
|
-
resolve(this.server);
|
|
808
|
-
return [2 /*return*/];
|
|
809
|
-
}
|
|
810
|
-
});
|
|
811
|
-
}); }
|
|
812
|
-
];
|
|
813
|
-
if (_this.config.https) {
|
|
814
|
-
var httpsOptions = __assign(__assign({}, _this.config.https), { key: fs.readFileSync(_this.config.https.key), cert: fs.readFileSync(_this.config.https.cert) });
|
|
815
|
-
if (_this.config.https.ca) {
|
|
816
|
-
httpsOptions.ca = fs.readFileSync(_this.config.https.ca);
|
|
817
|
-
}
|
|
818
|
-
if (_this.config.https.passphrase) {
|
|
819
|
-
httpsOptions.passphrase = _this.config.https.passphrase;
|
|
820
|
-
}
|
|
821
|
-
_this.server = (_a = https.createServer(httpsOptions, _this.app)).listen.apply(_a, __spreadArray([], __read(listenArgs), false));
|
|
822
|
-
}
|
|
823
|
-
else {
|
|
824
|
-
// Start the HTTP server
|
|
825
|
-
_this.server = (_b = _this.app).listen.apply(_b, __spreadArray([], __read(listenArgs), false));
|
|
826
|
-
}
|
|
827
|
-
// Track connections
|
|
828
|
-
_this.server.on('connection', function (conn) {
|
|
829
|
-
_this.connections.add(conn);
|
|
830
|
-
conn.on('close', function () { return _this.connections.delete(conn); });
|
|
831
|
-
});
|
|
832
|
-
// Handle server startup errors (port in use, permission denied, etc.)
|
|
833
|
-
_this.server.on('error', function (err) {
|
|
834
|
-
getLogger().error({ err: err }, 'Server failed to start');
|
|
835
|
-
reject(err);
|
|
836
|
-
});
|
|
837
|
-
}
|
|
838
|
-
catch (error) {
|
|
839
|
-
reject(error);
|
|
840
|
-
}
|
|
841
|
-
})];
|
|
606
|
+
async start() {
|
|
607
|
+
// Ensure initialization (middleware + routes) completed before starting
|
|
608
|
+
await this.initPromise;
|
|
609
|
+
await this.runHook('beforeStart', this.app);
|
|
610
|
+
return new Promise((resolve, reject) => {
|
|
611
|
+
try {
|
|
612
|
+
// Prepare listen arguments with optional host parameter
|
|
613
|
+
const listenArgs = [
|
|
614
|
+
this.config.port,
|
|
615
|
+
this.config.host,
|
|
616
|
+
async () => {
|
|
617
|
+
const protocol = this.config.https ? 'https' : 'http';
|
|
618
|
+
const url = `${protocol}://${this.config.host}:${this.config.port}`;
|
|
619
|
+
getLogger().info(`Server running on ${url}`);
|
|
620
|
+
if (this.config.healthCheck?.path) {
|
|
621
|
+
getLogger().info(`Health check available at ${url}${this.normalizePath(this.config.healthCheck.path, this.config.healthCheck.withGlobalPrefix)}`);
|
|
622
|
+
}
|
|
623
|
+
if (this.config.metrics?.enable && this.config.metrics.path) {
|
|
624
|
+
getLogger().info(`Metrics available at ${url}${this.normalizePath(this.config.metrics.path, this.config.metrics.withGlobalPrefix)}`);
|
|
625
|
+
}
|
|
626
|
+
if (this.config.openApi?.enable) {
|
|
627
|
+
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 = {
|
|
636
|
+
...this.config.https,
|
|
637
|
+
key: fs.readFileSync(this.config.https.key),
|
|
638
|
+
cert: fs.readFileSync(this.config.https.cert)
|
|
639
|
+
};
|
|
640
|
+
if (this.config.https.ca) {
|
|
641
|
+
httpsOptions.ca = fs.readFileSync(this.config.https.ca);
|
|
642
|
+
}
|
|
643
|
+
if (this.config.https.passphrase) {
|
|
644
|
+
httpsOptions.passphrase = this.config.https.passphrase;
|
|
645
|
+
}
|
|
646
|
+
this.server = https.createServer(httpsOptions, this.app).listen(...listenArgs);
|
|
842
647
|
}
|
|
843
|
-
|
|
648
|
+
else {
|
|
649
|
+
// Start the HTTP server
|
|
650
|
+
this.server = this.app.listen(...listenArgs);
|
|
651
|
+
}
|
|
652
|
+
// Track connections
|
|
653
|
+
this.server.on('connection', (conn) => {
|
|
654
|
+
this.connections.add(conn);
|
|
655
|
+
conn.on('close', () => this.connections.delete(conn));
|
|
656
|
+
});
|
|
657
|
+
// Handle server startup errors (port in use, permission denied, etc.)
|
|
658
|
+
this.server.on('error', err => {
|
|
659
|
+
getLogger().error({ err }, 'Server failed to start');
|
|
660
|
+
reject(err);
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
catch (error) {
|
|
664
|
+
reject(error);
|
|
665
|
+
}
|
|
844
666
|
});
|
|
845
|
-
}
|
|
667
|
+
}
|
|
846
668
|
/**
|
|
847
669
|
* Stop the HTTP server gracefully.
|
|
848
670
|
*
|
|
@@ -859,78 +681,47 @@ var ExpressServer = /** @class */ (function () {
|
|
|
859
681
|
* - Resources are properly cleaned up
|
|
860
682
|
* - Monitoring systems are notified
|
|
861
683
|
*/
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
case 1:
|
|
881
|
-
_a.sent();
|
|
882
|
-
shutdownTimeout = 10000;
|
|
883
|
-
serverClosePromise = new Promise(function (resolve, reject) {
|
|
884
|
-
_this.server.close(function (err) { return __awaiter(_this, void 0, void 0, function () {
|
|
885
|
-
return __generator(this, function (_a) {
|
|
886
|
-
switch (_a.label) {
|
|
887
|
-
case 0:
|
|
888
|
-
if (err) {
|
|
889
|
-
getLogger().error({ err: err }, 'Error while closing server');
|
|
890
|
-
reject(err);
|
|
891
|
-
return [2 /*return*/];
|
|
892
|
-
}
|
|
893
|
-
this.server = null;
|
|
894
|
-
this.isShuttingDown = false;
|
|
895
|
-
getLogger().info('Server stopped gracefully');
|
|
896
|
-
return [4 /*yield*/, this.runHook('afterStop')];
|
|
897
|
-
case 1:
|
|
898
|
-
_a.sent();
|
|
899
|
-
resolve();
|
|
900
|
-
return [2 /*return*/];
|
|
901
|
-
}
|
|
902
|
-
});
|
|
903
|
-
}); });
|
|
904
|
-
});
|
|
905
|
-
timeoutPromise = new Promise(function (_, reject) {
|
|
906
|
-
return setTimeout(function () { return reject(new Error('Shutdown timeout')); }, shutdownTimeout);
|
|
907
|
-
});
|
|
908
|
-
_a.label = 2;
|
|
909
|
-
case 2:
|
|
910
|
-
_a.trys.push([2, 4, 5, 7]);
|
|
911
|
-
return [4 /*yield*/, Promise.race([serverClosePromise, timeoutPromise])];
|
|
912
|
-
case 3:
|
|
913
|
-
_a.sent();
|
|
914
|
-
return [3 /*break*/, 7];
|
|
915
|
-
case 4:
|
|
916
|
-
err_3 = _a.sent();
|
|
917
|
-
getLogger().error({ err: err_3 }, 'Graceful shutdown timed out');
|
|
918
|
-
if (force) {
|
|
919
|
-
getLogger().warn('Forcing connection destroy due to shutdown timeout');
|
|
920
|
-
}
|
|
921
|
-
return [3 /*break*/, 7];
|
|
922
|
-
case 5:
|
|
923
|
-
// Always clean up connections
|
|
924
|
-
return [4 /*yield*/, this.destroyConnections()];
|
|
925
|
-
case 6:
|
|
926
|
-
// Always clean up connections
|
|
927
|
-
_a.sent();
|
|
928
|
-
return [7 /*endfinally*/];
|
|
929
|
-
case 7: return [2 /*return*/];
|
|
684
|
+
async stop(force = false) {
|
|
685
|
+
if (!this.server) {
|
|
686
|
+
getLogger().warn('Stop called but server is not running');
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
if (this.isShuttingDown) {
|
|
690
|
+
getLogger().warn('Stop called while shutdown is already in progress');
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
this.isShuttingDown = true;
|
|
694
|
+
await this.runHook('beforeStop', this.server);
|
|
695
|
+
const shutdownTimeout = 10_000; // 10s max wait
|
|
696
|
+
const serverClosePromise = new Promise((resolve, reject) => {
|
|
697
|
+
this.server.close(async (err) => {
|
|
698
|
+
if (err) {
|
|
699
|
+
getLogger().error({ err }, 'Error while closing server');
|
|
700
|
+
reject(err);
|
|
701
|
+
return;
|
|
930
702
|
}
|
|
703
|
+
this.server = null;
|
|
704
|
+
this.isShuttingDown = false;
|
|
705
|
+
getLogger().info('Server stopped gracefully');
|
|
706
|
+
await this.runHook('afterStop');
|
|
707
|
+
resolve();
|
|
931
708
|
});
|
|
932
709
|
});
|
|
933
|
-
|
|
710
|
+
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('Shutdown timeout')), shutdownTimeout));
|
|
711
|
+
try {
|
|
712
|
+
await Promise.race([serverClosePromise, timeoutPromise]);
|
|
713
|
+
}
|
|
714
|
+
catch (err) {
|
|
715
|
+
getLogger().error({ err }, 'Graceful shutdown timed out');
|
|
716
|
+
if (force) {
|
|
717
|
+
getLogger().warn('Forcing connection destroy due to shutdown timeout');
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
finally {
|
|
721
|
+
// Always clean up connections
|
|
722
|
+
await this.destroyConnections();
|
|
723
|
+
}
|
|
724
|
+
}
|
|
934
725
|
/**
|
|
935
726
|
* Enable graceful shutdown on OS signals for production deployment.
|
|
936
727
|
*
|
|
@@ -942,66 +733,46 @@ var ExpressServer = /** @class */ (function () {
|
|
|
942
733
|
*
|
|
943
734
|
* @param signals Array of process signals to listen for (default: SIGINT, SIGTERM)
|
|
944
735
|
*/
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
return [4 /*yield*/, this.stop()];
|
|
959
|
-
case 2:
|
|
960
|
-
_a.sent();
|
|
961
|
-
process.exit(0);
|
|
962
|
-
return [3 /*break*/, 8];
|
|
963
|
-
case 3:
|
|
964
|
-
err_4 = _a.sent();
|
|
965
|
-
getLogger().error({ err: err_4 }, 'Error during graceful shutdown, forcing stop...');
|
|
966
|
-
_a.label = 4;
|
|
967
|
-
case 4:
|
|
968
|
-
_a.trys.push([4, 6, , 7]);
|
|
969
|
-
return [4 /*yield*/, this.stop(true)];
|
|
970
|
-
case 5:
|
|
971
|
-
_a.sent(); // fallback to forced shutdown
|
|
972
|
-
process.exit(1);
|
|
973
|
-
return [3 /*break*/, 7];
|
|
974
|
-
case 6:
|
|
975
|
-
forceError_1 = _a.sent();
|
|
976
|
-
getLogger().fatal({ forceError: forceError_1 }, 'Forced shutdown failed, exiting hard');
|
|
977
|
-
process.exit(1);
|
|
978
|
-
return [3 /*break*/, 7];
|
|
979
|
-
case 7: return [3 /*break*/, 8];
|
|
980
|
-
case 8: return [2 /*return*/];
|
|
736
|
+
enableGracefulShutdown(signals = ['SIGINT', 'SIGTERM']) {
|
|
737
|
+
signals.forEach(signal => {
|
|
738
|
+
process.on(signal, async () => {
|
|
739
|
+
getLogger().info(`Received ${signal}, initiating graceful shutdown...`);
|
|
740
|
+
try {
|
|
741
|
+
await this.stop();
|
|
742
|
+
process.exit(0);
|
|
743
|
+
}
|
|
744
|
+
catch (err) {
|
|
745
|
+
getLogger().error({ err }, 'Error during graceful shutdown, forcing stop...');
|
|
746
|
+
try {
|
|
747
|
+
await this.stop(true); // fallback to forced shutdown
|
|
748
|
+
process.exit(1);
|
|
981
749
|
}
|
|
982
|
-
|
|
983
|
-
|
|
750
|
+
catch (forceError) {
|
|
751
|
+
getLogger().fatal({ forceError }, 'Forced shutdown failed, exiting hard');
|
|
752
|
+
process.exit(1);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
});
|
|
984
756
|
});
|
|
985
757
|
return this;
|
|
986
|
-
}
|
|
758
|
+
}
|
|
987
759
|
/**
|
|
988
760
|
* Set an externally created base router.
|
|
989
761
|
* This will override the internal rootRouter.
|
|
990
762
|
*/
|
|
991
|
-
|
|
763
|
+
setBaseRouter(router) {
|
|
992
764
|
this.externalRouter = router;
|
|
993
765
|
return this;
|
|
994
|
-
}
|
|
766
|
+
}
|
|
995
767
|
/**
|
|
996
768
|
* Create and register a new router (only used if not injecting one externally - use `setBaseRouter` instead).
|
|
997
769
|
*/
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
var path = this.normalizePath(prefix, true);
|
|
770
|
+
createRouter(prefix = '') {
|
|
771
|
+
const router = express.Router();
|
|
772
|
+
const path = this.normalizePath(prefix, true);
|
|
1002
773
|
this.rootRouter.use(path, router);
|
|
1003
774
|
return router;
|
|
1004
|
-
}
|
|
775
|
+
}
|
|
1005
776
|
/**
|
|
1006
777
|
* Register a new route handler with support for multiple HTTP methods.
|
|
1007
778
|
* The route is automatically registered under the globalPrefix if set.
|
|
@@ -1011,13 +782,9 @@ var ExpressServer = /** @class */ (function () {
|
|
|
1011
782
|
* @param handlers One or more Express request handlers (middleware + final handler)
|
|
1012
783
|
* @returns This instance for method chaining
|
|
1013
784
|
*/
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
handlers[_i - 2] = arguments[_i];
|
|
1018
|
-
}
|
|
1019
|
-
var fullPath = this.normalizePath(path, true);
|
|
1020
|
-
var methodMap = {
|
|
785
|
+
registerRoute(methods, path, ...handlers) {
|
|
786
|
+
const fullPath = this.normalizePath(path, true);
|
|
787
|
+
const methodMap = {
|
|
1021
788
|
get: this.app.get.bind(this.app),
|
|
1022
789
|
post: this.app.post.bind(this.app),
|
|
1023
790
|
put: this.app.put.bind(this.app),
|
|
@@ -1026,17 +793,17 @@ var ExpressServer = /** @class */ (function () {
|
|
|
1026
793
|
options: this.app.options.bind(this.app),
|
|
1027
794
|
head: this.app.head.bind(this.app)
|
|
1028
795
|
};
|
|
1029
|
-
methods.forEach(
|
|
1030
|
-
|
|
796
|
+
methods.forEach(m => {
|
|
797
|
+
const fn = methodMap[m];
|
|
1031
798
|
if (fn) {
|
|
1032
|
-
fn
|
|
799
|
+
fn(fullPath, ...handlers);
|
|
1033
800
|
}
|
|
1034
801
|
else {
|
|
1035
|
-
throw new Error(
|
|
802
|
+
throw new Error(`Unsupported HTTP method: ${m}`);
|
|
1036
803
|
}
|
|
1037
804
|
});
|
|
1038
805
|
return this;
|
|
1039
|
-
}
|
|
806
|
+
}
|
|
1040
807
|
/**
|
|
1041
808
|
* Register custom middleware with optional path restriction.
|
|
1042
809
|
*
|
|
@@ -1050,9 +817,9 @@ var ExpressServer = /** @class */ (function () {
|
|
|
1050
817
|
* @param middleware Middleware handler (required if path is provided)
|
|
1051
818
|
* @returns This instance for method chaining
|
|
1052
819
|
*/
|
|
1053
|
-
|
|
820
|
+
registerMiddleware(path, middleware) {
|
|
1054
821
|
if (typeof path === 'string') {
|
|
1055
|
-
|
|
822
|
+
const normalizedPath = this.normalizePath(path);
|
|
1056
823
|
if (normalizedPath) {
|
|
1057
824
|
this.app.use(normalizedPath, middleware);
|
|
1058
825
|
}
|
|
@@ -1064,7 +831,7 @@ var ExpressServer = /** @class */ (function () {
|
|
|
1064
831
|
this.app.use(path);
|
|
1065
832
|
}
|
|
1066
833
|
return this;
|
|
1067
|
-
}
|
|
834
|
+
}
|
|
1068
835
|
/**
|
|
1069
836
|
* Register one or more middleware functions to be applied globally.
|
|
1070
837
|
* This is a simpler alternative to registerMiddleware when you just want
|
|
@@ -1073,56 +840,40 @@ var ExpressServer = /** @class */ (function () {
|
|
|
1073
840
|
* @param middlewares One or more Express middleware functions
|
|
1074
841
|
* @returns This instance for method chaining
|
|
1075
842
|
*/
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
for (var _i = 0; _i < arguments.length; _i++) {
|
|
1080
|
-
middlewares[_i] = arguments[_i];
|
|
1081
|
-
}
|
|
1082
|
-
middlewares.forEach(function (middleware) {
|
|
1083
|
-
_this.app.use(middleware);
|
|
843
|
+
useMiddleware(...middlewares) {
|
|
844
|
+
middlewares.forEach(middleware => {
|
|
845
|
+
this.app.use(middleware);
|
|
1084
846
|
});
|
|
1085
847
|
return this;
|
|
1086
|
-
}
|
|
848
|
+
}
|
|
1087
849
|
/**
|
|
1088
850
|
* Get Prometheus registry (to add custom counters/histograms)
|
|
1089
851
|
*
|
|
1090
852
|
* @return {*} {client.Registry}
|
|
1091
853
|
*/
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
if (!((_a = this.config.metrics) === null || _a === void 0 ? void 0 : _a.enable)) {
|
|
854
|
+
getMetricsRegistry() {
|
|
855
|
+
if (!this.config.metrics?.enable) {
|
|
1095
856
|
throw new Error('Metrics are not enabled in the server configuration');
|
|
1096
857
|
}
|
|
1097
858
|
return this.register;
|
|
1098
|
-
}
|
|
859
|
+
}
|
|
1099
860
|
/**
|
|
1100
861
|
* Get server configuration
|
|
1101
862
|
*
|
|
1102
863
|
* @return {*} {ServerConfig}
|
|
1103
864
|
*/
|
|
1104
|
-
|
|
865
|
+
getConfig() {
|
|
1105
866
|
return this.config;
|
|
1106
|
-
}
|
|
867
|
+
}
|
|
1107
868
|
/**
|
|
1108
869
|
* Wait until server initialization (middleware + routes) has completed.
|
|
1109
870
|
* Useful for integration tests that inspect app before starting.
|
|
1110
871
|
*/
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
case 1:
|
|
1117
|
-
_a.sent();
|
|
1118
|
-
return [2 /*return*/];
|
|
1119
|
-
}
|
|
1120
|
-
});
|
|
1121
|
-
});
|
|
1122
|
-
};
|
|
1123
|
-
ExpressServer.prototype.normalizePath = function (path, withGlobalPrefix) {
|
|
1124
|
-
if (withGlobalPrefix === void 0) { withGlobalPrefix = false; }
|
|
1125
|
-
var sanitize = function (p) {
|
|
872
|
+
async waitUntilReady() {
|
|
873
|
+
await this.initPromise;
|
|
874
|
+
}
|
|
875
|
+
normalizePath(path, withGlobalPrefix = false) {
|
|
876
|
+
const sanitize = (p) => {
|
|
1126
877
|
return ('/' +
|
|
1127
878
|
p
|
|
1128
879
|
.trim()
|
|
@@ -1131,118 +882,82 @@ var ExpressServer = /** @class */ (function () {
|
|
|
1131
882
|
.replace(/\/+$/, '')); // remove trailing slash
|
|
1132
883
|
};
|
|
1133
884
|
// Resolve global prefix if enabled
|
|
1134
|
-
|
|
885
|
+
const prefix = withGlobalPrefix && this.globalPrefix ? sanitize(this.globalPrefix) : '';
|
|
1135
886
|
// If path is invalid, default to prefix or root
|
|
1136
887
|
if (typeof path !== 'string' || !path.trim()) {
|
|
1137
888
|
return prefix || '/';
|
|
1138
889
|
}
|
|
1139
890
|
return sanitize(prefix + '/' + path);
|
|
1140
|
-
}
|
|
1141
|
-
|
|
1142
|
-
var _a;
|
|
891
|
+
}
|
|
892
|
+
normalizeRouteForMetrics(req, res) {
|
|
1143
893
|
// Prevent high cardinality metrics by normalizing routes
|
|
1144
|
-
if (
|
|
894
|
+
if (req?.route?.path) {
|
|
1145
895
|
// Use Express route pattern instead of actual URL
|
|
1146
896
|
return req.route.path;
|
|
1147
897
|
}
|
|
1148
898
|
// Group common patterns
|
|
1149
899
|
if (res.statusCode === 404)
|
|
1150
900
|
return '/404';
|
|
1151
|
-
|
|
901
|
+
const path = (req.path || 'unknown').split('?')[0];
|
|
1152
902
|
// Replace IDs and UUIDs with placeholders
|
|
1153
903
|
return path
|
|
1154
904
|
.replace(/\/[0-9]+/g, '/:id')
|
|
1155
905
|
.replace(/\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/g, '/:uuid');
|
|
1156
|
-
}
|
|
906
|
+
}
|
|
1157
907
|
/**
|
|
1158
908
|
* Destroy all active connections (gracefully if possible).
|
|
1159
909
|
* If a connection does not close cleanly, it will be force-destroyed.
|
|
1160
910
|
*/
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
});
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
var _a;
|
|
1201
|
-
return __generator(this, function (_b) {
|
|
1202
|
-
switch (_b.label) {
|
|
1203
|
-
case 0: return [4 /*yield*/, fileExists(this.config.https.key)];
|
|
1204
|
-
case 1:
|
|
1205
|
-
if (!(_b.sent())) {
|
|
1206
|
-
getLogger().error("HTTPS key file not found: ".concat(this.config.https.key));
|
|
1207
|
-
process.exit(1);
|
|
1208
|
-
}
|
|
1209
|
-
return [4 /*yield*/, fileExists(this.config.https.cert)];
|
|
1210
|
-
case 2:
|
|
1211
|
-
if (!(_b.sent())) {
|
|
1212
|
-
getLogger().error("HTTPS cert file not found: ".concat(this.config.https.cert));
|
|
1213
|
-
process.exit(1);
|
|
1214
|
-
}
|
|
1215
|
-
_a = this.config.https.ca;
|
|
1216
|
-
if (!_a) return [3 /*break*/, 4];
|
|
1217
|
-
return [4 /*yield*/, fileExists(this.config.https.ca)];
|
|
1218
|
-
case 3:
|
|
1219
|
-
_a = !(_b.sent());
|
|
1220
|
-
_b.label = 4;
|
|
1221
|
-
case 4:
|
|
1222
|
-
if (_a) {
|
|
1223
|
-
getLogger().error("HTTPS CA file not found: ".concat(this.config.https.ca));
|
|
1224
|
-
process.exit(1);
|
|
1225
|
-
}
|
|
1226
|
-
return [2 /*return*/];
|
|
1227
|
-
}
|
|
1228
|
-
});
|
|
1229
|
-
});
|
|
1230
|
-
};
|
|
1231
|
-
ExpressServer.isBuiltServerConfig = function (config) {
|
|
911
|
+
async destroyConnections() {
|
|
912
|
+
const total = this.connections.size;
|
|
913
|
+
if (total === 0) {
|
|
914
|
+
getLogger().debug('No active connections to close');
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
const timeoutMs = 5000;
|
|
918
|
+
await Promise.race([
|
|
919
|
+
Promise.all(Array.from(this.connections).map(conn => new Promise(resolve => {
|
|
920
|
+
conn.end(() => {
|
|
921
|
+
if (!conn.destroyed)
|
|
922
|
+
conn.destroy();
|
|
923
|
+
resolve();
|
|
924
|
+
});
|
|
925
|
+
conn.on('error', () => {
|
|
926
|
+
conn.destroy();
|
|
927
|
+
resolve();
|
|
928
|
+
});
|
|
929
|
+
}))),
|
|
930
|
+
new Promise(resolve => setTimeout(resolve, timeoutMs))
|
|
931
|
+
]);
|
|
932
|
+
this.connections.clear();
|
|
933
|
+
getLogger().info(`Closed ${total} active connections`);
|
|
934
|
+
}
|
|
935
|
+
async validateHttpsFiles() {
|
|
936
|
+
if (!(await fileExists(this.config.https.key))) {
|
|
937
|
+
getLogger().error(`HTTPS key file not found: ${this.config.https.key}`);
|
|
938
|
+
process.exit(1);
|
|
939
|
+
}
|
|
940
|
+
if (!(await fileExists(this.config.https.cert))) {
|
|
941
|
+
getLogger().error(`HTTPS cert file not found: ${this.config.https.cert}`);
|
|
942
|
+
process.exit(1);
|
|
943
|
+
}
|
|
944
|
+
if (this.config.https.ca && !(await fileExists(this.config.https.ca))) {
|
|
945
|
+
getLogger().error(`HTTPS CA file not found: ${this.config.https.ca}`);
|
|
946
|
+
process.exit(1);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
static isBuiltServerConfig(config) {
|
|
1232
950
|
if (config[BUILD_MARKER]) {
|
|
1233
951
|
return true;
|
|
1234
952
|
}
|
|
1235
953
|
return false;
|
|
1236
|
-
}
|
|
1237
|
-
|
|
954
|
+
}
|
|
955
|
+
static optionalRequire(name) {
|
|
1238
956
|
try {
|
|
1239
957
|
return require(name);
|
|
1240
958
|
}
|
|
1241
|
-
catch
|
|
959
|
+
catch {
|
|
1242
960
|
return null;
|
|
1243
961
|
}
|
|
1244
|
-
}
|
|
1245
|
-
|
|
1246
|
-
}());
|
|
1247
|
-
export { ExpressServer };
|
|
1248
|
-
//# sourceMappingURL=server.js.map
|
|
962
|
+
}
|
|
963
|
+
}
|