@catbee/utils 2.0.4 → 2.1.1
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 +1 -0
- package/cache/index.cjs +36 -16
- package/cache/index.d.ts +6 -2
- package/cache/index.mjs +36 -16
- package/context-store/index.cjs +97 -76
- package/context-store/index.d.ts +74 -57
- package/context-store/index.mjs +97 -77
- package/healthz-server/index.cjs +356 -0
- package/healthz-server/index.d.ts +291 -0
- package/healthz-server/index.mjs +352 -0
- package/index.cjs +7 -0
- package/index.d.ts +1 -0
- package/index.mjs +1 -0
- package/logger/index.cjs +174 -76
- package/logger/index.d.ts +29 -18
- package/logger/index.mjs +174 -77
- package/package.json +12 -7
- package/server/index.cjs +216 -74
- package/server/index.d.ts +72 -14
- package/server/index.mjs +215 -74
package/server/index.cjs
CHANGED
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
'use strict';
|
|
26
26
|
|
|
27
27
|
var express = require('express');
|
|
28
|
+
var http = require('http');
|
|
28
29
|
var https = require('https');
|
|
29
30
|
var httpStatusCodes = require('@catbee/utils/http-status-codes');
|
|
30
31
|
var response = require('@catbee/utils/response');
|
|
@@ -42,6 +43,7 @@ var id = require('@catbee/utils/id');
|
|
|
42
43
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
43
44
|
|
|
44
45
|
var express__default = /*#__PURE__*/_interopDefault(express);
|
|
46
|
+
var http__default = /*#__PURE__*/_interopDefault(http);
|
|
45
47
|
var https__default = /*#__PURE__*/_interopDefault(https);
|
|
46
48
|
|
|
47
49
|
var __defProp = Object.defineProperty;
|
|
@@ -689,6 +691,15 @@ var DependencyErrors = {
|
|
|
689
691
|
"cookie-parser": getDependencyErrorMessage("cookie-parser"),
|
|
690
692
|
"@scalar/express-api-reference": getDependencyErrorMessage("@scalar/express-api-reference")
|
|
691
693
|
};
|
|
694
|
+
var SUPPORTED_HTTP_METHODS = /* @__PURE__ */ new Set([
|
|
695
|
+
"get",
|
|
696
|
+
"post",
|
|
697
|
+
"put",
|
|
698
|
+
"delete",
|
|
699
|
+
"patch",
|
|
700
|
+
"options",
|
|
701
|
+
"head"
|
|
702
|
+
]);
|
|
692
703
|
var ExpressServer = class {
|
|
693
704
|
static {
|
|
694
705
|
__name(this, "ExpressServer");
|
|
@@ -701,11 +712,11 @@ var ExpressServer = class {
|
|
|
701
712
|
hooks;
|
|
702
713
|
/** Global API prefix (from config) */
|
|
703
714
|
globalPrefix;
|
|
704
|
-
/**
|
|
715
|
+
/** Primary root router mounted to the application */
|
|
705
716
|
rootRouter;
|
|
706
|
-
/**
|
|
707
|
-
|
|
708
|
-
/**
|
|
717
|
+
/** Set of registered sub-routers to prevent duplicate mounting */
|
|
718
|
+
mountedRouters = /* @__PURE__ */ new Set();
|
|
719
|
+
/** Express app instance */
|
|
709
720
|
app;
|
|
710
721
|
/** Set of active WebSocket connections */
|
|
711
722
|
connections = /* @__PURE__ */ new Set();
|
|
@@ -713,6 +724,8 @@ var ExpressServer = class {
|
|
|
713
724
|
isShuttingDown = false;
|
|
714
725
|
/** Flag indicating if graceful shutdown handlers are registered */
|
|
715
726
|
gracefulShutdownRegistered = false;
|
|
727
|
+
/** Map of registered signal listeners for clean teardown */
|
|
728
|
+
signalListeners = /* @__PURE__ */ new Map();
|
|
716
729
|
/**
|
|
717
730
|
* Collection of registered health check functions.
|
|
718
731
|
* These are executed when the health check endpoint is accessed.
|
|
@@ -720,6 +733,8 @@ var ExpressServer = class {
|
|
|
720
733
|
healthChecks = [];
|
|
721
734
|
/** Promise that resolves when initialization (middleware + routes) is complete */
|
|
722
735
|
initPromise;
|
|
736
|
+
/** In-flight start promise to protect against concurrent start() calls */
|
|
737
|
+
startPromise;
|
|
723
738
|
/**
|
|
724
739
|
* Initializes server with intelligent defaults and security best practices.
|
|
725
740
|
* All settings can be customized via config and hooks.
|
|
@@ -822,6 +837,7 @@ var ExpressServer = class {
|
|
|
822
837
|
this.setupBodyParsingMiddleware();
|
|
823
838
|
this.setupCookieParsingMiddleware();
|
|
824
839
|
await this.setupOpenApiMiddleware();
|
|
840
|
+
this.setupResponseHook();
|
|
825
841
|
}
|
|
826
842
|
/**
|
|
827
843
|
* Set up basic middleware (trust proxy, request ID, context).
|
|
@@ -876,10 +892,15 @@ var ExpressServer = class {
|
|
|
876
892
|
* Set up global headers middleware.
|
|
877
893
|
*/
|
|
878
894
|
setupGlobalHeaders() {
|
|
895
|
+
const hasCustomHeaders = Boolean(this.config.globalHeaders && Object.keys(this.config.globalHeaders).length > 0);
|
|
896
|
+
const isMicroservice = Boolean(this.config.isMicroservice);
|
|
897
|
+
const hasServiceVersion = Boolean(this.config.serviceVersion?.enable);
|
|
898
|
+
if (!hasCustomHeaders && !isMicroservice && !hasServiceVersion) {
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
879
901
|
this.app.use((_req, res, next) => {
|
|
880
902
|
if (this.config.globalHeaders) {
|
|
881
|
-
for (const key
|
|
882
|
-
const value = this.config.globalHeaders[key];
|
|
903
|
+
for (const [key, value] of Object.entries(this.config.globalHeaders)) {
|
|
883
904
|
res.setHeader(key, typeof value === "function" ? value() : value);
|
|
884
905
|
}
|
|
885
906
|
}
|
|
@@ -1074,6 +1095,11 @@ var ExpressServer = class {
|
|
|
1074
1095
|
}, "Failed to mount OpenAPI docs");
|
|
1075
1096
|
}
|
|
1076
1097
|
}
|
|
1098
|
+
}
|
|
1099
|
+
/**
|
|
1100
|
+
* Set up response preprocessing hook (applies global prefix if set).
|
|
1101
|
+
*/
|
|
1102
|
+
setupResponseHook() {
|
|
1077
1103
|
if (this.hooks.onResponse) {
|
|
1078
1104
|
this.app.use(this.globalPrefix, this.hooks.onResponse);
|
|
1079
1105
|
}
|
|
@@ -1092,8 +1118,7 @@ var ExpressServer = class {
|
|
|
1092
1118
|
this.app.get(healthCheckPath, async (_req, res) => {
|
|
1093
1119
|
return this.handleHealthCheckRequest(res);
|
|
1094
1120
|
});
|
|
1095
|
-
|
|
1096
|
-
this.app.use(this.globalPrefix, routerToUse);
|
|
1121
|
+
this.app.use(this.globalPrefix, this.rootRouter);
|
|
1097
1122
|
await this.runHook("afterRoutes", this.app);
|
|
1098
1123
|
this.app.use((req, res) => {
|
|
1099
1124
|
const status = httpStatusCodes.HttpStatusCodes.NOT_FOUND;
|
|
@@ -1228,11 +1253,15 @@ var ExpressServer = class {
|
|
|
1228
1253
|
* Start the HTTP server and begin listening for requests.
|
|
1229
1254
|
*
|
|
1230
1255
|
* This method:
|
|
1256
|
+
* - Protects against concurrent start() invocations
|
|
1257
|
+
* - Awaits server initialization (middleware + routes)
|
|
1231
1258
|
* - Executes beforeStart hooks
|
|
1259
|
+
* - Creates the HTTP/HTTPS server instance
|
|
1260
|
+
* - Sets up error handling and connection tracking BEFORE listening
|
|
1261
|
+
* - Executes onServerCreated hook BEFORE listening
|
|
1232
1262
|
* - Binds to the configured host/port
|
|
1233
|
-
* -
|
|
1234
|
-
* -
|
|
1235
|
-
* - Logs startup information
|
|
1263
|
+
* - Executes afterStart hooks on successful listen
|
|
1264
|
+
* - Cleans up server reference and listeners on startup failure
|
|
1236
1265
|
*
|
|
1237
1266
|
* @returns Promise resolving to the running HTTP server instance
|
|
1238
1267
|
* @throws Error if server fails to start or port is already in use
|
|
@@ -1242,33 +1271,68 @@ var ExpressServer = class {
|
|
|
1242
1271
|
logger.getLogger().warn("Server is already running, returning existing instance");
|
|
1243
1272
|
return this.server;
|
|
1244
1273
|
}
|
|
1274
|
+
if (this.startPromise) {
|
|
1275
|
+
return this.startPromise;
|
|
1276
|
+
}
|
|
1277
|
+
this.startPromise = this.doStart();
|
|
1278
|
+
try {
|
|
1279
|
+
return await this.startPromise;
|
|
1280
|
+
} finally {
|
|
1281
|
+
this.startPromise = void 0;
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
/**
|
|
1285
|
+
* Internal implementation of server startup.
|
|
1286
|
+
*/
|
|
1287
|
+
async doStart() {
|
|
1245
1288
|
await this.initPromise;
|
|
1246
1289
|
await this.runHook("beforeStart", this.app);
|
|
1290
|
+
const server = this.createServerInstance();
|
|
1291
|
+
this.server = server;
|
|
1247
1292
|
return new Promise((resolve, reject) => {
|
|
1248
|
-
|
|
1293
|
+
let isListening = false;
|
|
1294
|
+
server.on("error", (err) => {
|
|
1295
|
+
if (!isListening) {
|
|
1296
|
+
logger.getLogger().error({
|
|
1297
|
+
err
|
|
1298
|
+
}, "Server failed to start");
|
|
1299
|
+
try {
|
|
1300
|
+
server.removeAllListeners();
|
|
1301
|
+
server.close();
|
|
1302
|
+
} catch {
|
|
1303
|
+
}
|
|
1304
|
+
this.server = null;
|
|
1305
|
+
this.connections.clear();
|
|
1306
|
+
reject(err);
|
|
1307
|
+
} else {
|
|
1308
|
+
logger.getLogger().error({
|
|
1309
|
+
err
|
|
1310
|
+
}, "Server runtime error");
|
|
1311
|
+
}
|
|
1312
|
+
});
|
|
1313
|
+
this.setupConnectionTracking();
|
|
1314
|
+
Promise.resolve(this.runHook("onServerCreated", server)).then(() => {
|
|
1249
1315
|
const onListening = /* @__PURE__ */ __name(async () => {
|
|
1316
|
+
isListening = true;
|
|
1250
1317
|
this.logServerStartInfo();
|
|
1251
|
-
|
|
1252
|
-
resolve(
|
|
1318
|
+
await this.runHook("afterStart", server);
|
|
1319
|
+
resolve(server);
|
|
1253
1320
|
}, "onListening");
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
}
|
|
1321
|
+
const listenArgs = [
|
|
1322
|
+
this.config.port,
|
|
1323
|
+
this.config.host,
|
|
1324
|
+
onListening
|
|
1325
|
+
];
|
|
1326
|
+
server.listen(...listenArgs);
|
|
1327
|
+
}).catch((err) => {
|
|
1328
|
+
server.emit("error", err instanceof Error ? err : new Error(String(err)));
|
|
1329
|
+
});
|
|
1261
1330
|
});
|
|
1262
1331
|
}
|
|
1263
1332
|
/**
|
|
1264
|
-
* Create HTTP or HTTPS server instance.
|
|
1333
|
+
* Create HTTP or HTTPS server instance (without listening).
|
|
1265
1334
|
*/
|
|
1266
|
-
createServerInstance(
|
|
1267
|
-
const listenArgs = [
|
|
1268
|
-
this.config.port,
|
|
1269
|
-
this.config.host,
|
|
1270
|
-
onListening
|
|
1271
|
-
];
|
|
1335
|
+
createServerInstance() {
|
|
1272
1336
|
if (this.config.https) {
|
|
1273
1337
|
const httpsOptions = {
|
|
1274
1338
|
...this.config.https,
|
|
@@ -1281,9 +1345,9 @@ var ExpressServer = class {
|
|
|
1281
1345
|
if (this.config.https.passphrase) {
|
|
1282
1346
|
httpsOptions.passphrase = this.config.https.passphrase;
|
|
1283
1347
|
}
|
|
1284
|
-
return https__default.default.createServer(httpsOptions, this.app)
|
|
1348
|
+
return https__default.default.createServer(httpsOptions, this.app);
|
|
1285
1349
|
}
|
|
1286
|
-
return this.app
|
|
1350
|
+
return http__default.default.createServer(this.app);
|
|
1287
1351
|
}
|
|
1288
1352
|
/**
|
|
1289
1353
|
* Set up connection tracking for graceful shutdown.
|
|
@@ -1295,17 +1359,6 @@ var ExpressServer = class {
|
|
|
1295
1359
|
});
|
|
1296
1360
|
}
|
|
1297
1361
|
/**
|
|
1298
|
-
* Set up error handling for server startup.
|
|
1299
|
-
*/
|
|
1300
|
-
setupServerErrorHandling(reject) {
|
|
1301
|
-
this.server.on("error", (err) => {
|
|
1302
|
-
logger.getLogger().error({
|
|
1303
|
-
err
|
|
1304
|
-
}, "Server failed to start");
|
|
1305
|
-
reject(err);
|
|
1306
|
-
});
|
|
1307
|
-
}
|
|
1308
|
-
/**
|
|
1309
1362
|
* Log server startup information.
|
|
1310
1363
|
*/
|
|
1311
1364
|
logServerStartInfo() {
|
|
@@ -1367,6 +1420,7 @@ var ExpressServer = class {
|
|
|
1367
1420
|
afterStopCalled = true;
|
|
1368
1421
|
await this.runHook("afterStop");
|
|
1369
1422
|
}, "runAfterStop");
|
|
1423
|
+
server.closeIdleConnections?.();
|
|
1370
1424
|
const serverClosePromise = new Promise((resolve, reject) => {
|
|
1371
1425
|
server.close(async (err) => {
|
|
1372
1426
|
if (timer) clearTimeout(timer);
|
|
@@ -1429,7 +1483,7 @@ var ExpressServer = class {
|
|
|
1429
1483
|
}
|
|
1430
1484
|
let signalHandled = false;
|
|
1431
1485
|
signals.forEach((signal) => {
|
|
1432
|
-
|
|
1486
|
+
const handler = /* @__PURE__ */ __name(async () => {
|
|
1433
1487
|
if (signalHandled) {
|
|
1434
1488
|
logger.getLogger().warn(`Ignoring duplicate ${signal}`);
|
|
1435
1489
|
return;
|
|
@@ -1437,6 +1491,7 @@ var ExpressServer = class {
|
|
|
1437
1491
|
signalHandled = true;
|
|
1438
1492
|
logger.getLogger().info(`Received ${signal}, initiating graceful shutdown...`);
|
|
1439
1493
|
try {
|
|
1494
|
+
this.disableGracefulShutdown();
|
|
1440
1495
|
await this.stop(true);
|
|
1441
1496
|
process.exit(0);
|
|
1442
1497
|
} catch (err) {
|
|
@@ -1445,12 +1500,29 @@ var ExpressServer = class {
|
|
|
1445
1500
|
}, "Shutdown failed");
|
|
1446
1501
|
process.exit(1);
|
|
1447
1502
|
}
|
|
1448
|
-
});
|
|
1503
|
+
}, "handler");
|
|
1504
|
+
this.signalListeners.set(signal, handler);
|
|
1505
|
+
process.on(signal, handler);
|
|
1449
1506
|
});
|
|
1450
1507
|
this.gracefulShutdownRegistered = true;
|
|
1451
1508
|
return this;
|
|
1452
1509
|
}
|
|
1453
1510
|
/**
|
|
1511
|
+
* Unregister graceful shutdown signal listeners.
|
|
1512
|
+
* Useful for testing and dynamic server lifecycles to prevent memory and listener leaks.
|
|
1513
|
+
*/
|
|
1514
|
+
disableGracefulShutdown() {
|
|
1515
|
+
if (!this.gracefulShutdownRegistered) {
|
|
1516
|
+
return this;
|
|
1517
|
+
}
|
|
1518
|
+
for (const [signal, handler] of this.signalListeners.entries()) {
|
|
1519
|
+
process.removeListener(signal, handler);
|
|
1520
|
+
}
|
|
1521
|
+
this.signalListeners.clear();
|
|
1522
|
+
this.gracefulShutdownRegistered = false;
|
|
1523
|
+
return this;
|
|
1524
|
+
}
|
|
1525
|
+
/**
|
|
1454
1526
|
* Destroy all active connections (gracefully if possible).
|
|
1455
1527
|
* If a connection does not close cleanly, it will be force-destroyed.
|
|
1456
1528
|
*/
|
|
@@ -1463,32 +1535,56 @@ var ExpressServer = class {
|
|
|
1463
1535
|
];
|
|
1464
1536
|
await Promise.allSettled(sockets.map((socket) => new Promise((resolve) => {
|
|
1465
1537
|
socket.end();
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
clearTimeout(timer);
|
|
1538
|
+
let timer;
|
|
1539
|
+
const cleanup = /* @__PURE__ */ __name(() => {
|
|
1540
|
+
if (timer) clearTimeout(timer);
|
|
1541
|
+
socket.removeListener("close", onClose);
|
|
1542
|
+
socket.removeListener("error", onError);
|
|
1472
1543
|
resolve();
|
|
1473
|
-
});
|
|
1474
|
-
|
|
1475
|
-
|
|
1544
|
+
}, "cleanup");
|
|
1545
|
+
const onClose = /* @__PURE__ */ __name(() => cleanup(), "onClose");
|
|
1546
|
+
const onError = /* @__PURE__ */ __name(() => {
|
|
1476
1547
|
socket.destroy();
|
|
1477
|
-
|
|
1478
|
-
});
|
|
1548
|
+
cleanup();
|
|
1549
|
+
}, "onError");
|
|
1550
|
+
timer = setTimeout(() => {
|
|
1551
|
+
socket.destroy();
|
|
1552
|
+
cleanup();
|
|
1553
|
+
}, 1e3);
|
|
1554
|
+
socket.once("close", onClose);
|
|
1555
|
+
socket.once("error", onError);
|
|
1479
1556
|
})));
|
|
1480
1557
|
this.connections.clear();
|
|
1481
1558
|
logger.getLogger().info(`Closed ${sockets.length} active connection(s)`);
|
|
1482
1559
|
}
|
|
1483
1560
|
/**
|
|
1484
|
-
*
|
|
1485
|
-
*
|
|
1561
|
+
* Mount a base router onto the server's root router.
|
|
1562
|
+
*
|
|
1563
|
+
* Note: This attaches the supplied router to the root router pipeline.
|
|
1564
|
+
* Duplicate mounting of the same router instance is ignored.
|
|
1565
|
+
*
|
|
1566
|
+
* @param router The Express router instance to mount
|
|
1567
|
+
* @returns This instance for method chaining
|
|
1486
1568
|
*/
|
|
1487
|
-
|
|
1488
|
-
this.
|
|
1569
|
+
addBaseRouter(router) {
|
|
1570
|
+
if (this.mountedRouters.has(router)) {
|
|
1571
|
+
return this;
|
|
1572
|
+
}
|
|
1573
|
+
this.mountedRouters.add(router);
|
|
1574
|
+
this.rootRouter.use(router);
|
|
1489
1575
|
return this;
|
|
1490
1576
|
}
|
|
1491
1577
|
/**
|
|
1578
|
+
* Alias for `addBaseRouter` (maintained for backward compatibility).
|
|
1579
|
+
* Mounts the supplied router onto the server's root router.
|
|
1580
|
+
*
|
|
1581
|
+
* @param router The Express router instance to mount
|
|
1582
|
+
* @returns This instance for method chaining
|
|
1583
|
+
*/
|
|
1584
|
+
setBaseRouter(router) {
|
|
1585
|
+
return this.addBaseRouter(router);
|
|
1586
|
+
}
|
|
1587
|
+
/**
|
|
1492
1588
|
* Create and register a new router (only used if not injecting one externally - use `setBaseRouter` instead).
|
|
1493
1589
|
*/
|
|
1494
1590
|
createRouter(prefix = "") {
|
|
@@ -1508,27 +1604,73 @@ var ExpressServer = class {
|
|
|
1508
1604
|
*/
|
|
1509
1605
|
registerRoute(methods, path, ...handlers) {
|
|
1510
1606
|
const fullPath = this.normalizePath(path, true);
|
|
1511
|
-
const routerToUse = this.
|
|
1512
|
-
const methodMap = {
|
|
1513
|
-
get: routerToUse.get.bind(routerToUse),
|
|
1514
|
-
post: routerToUse.post.bind(routerToUse),
|
|
1515
|
-
put: routerToUse.put.bind(routerToUse),
|
|
1516
|
-
delete: routerToUse.delete.bind(routerToUse),
|
|
1517
|
-
patch: routerToUse.patch.bind(routerToUse),
|
|
1518
|
-
options: routerToUse.options.bind(routerToUse),
|
|
1519
|
-
head: routerToUse.head.bind(routerToUse)
|
|
1520
|
-
};
|
|
1607
|
+
const routerToUse = this.rootRouter;
|
|
1521
1608
|
methods.forEach((m) => {
|
|
1522
|
-
const
|
|
1523
|
-
if (
|
|
1524
|
-
fn(fullPath, ...handlers);
|
|
1525
|
-
} else {
|
|
1609
|
+
const method = m.toLowerCase();
|
|
1610
|
+
if (!SUPPORTED_HTTP_METHODS.has(method) || typeof routerToUse[method] !== "function") {
|
|
1526
1611
|
throw new Error(`Unsupported HTTP method: ${m}`);
|
|
1527
1612
|
}
|
|
1613
|
+
routerToUse[method](fullPath, ...handlers);
|
|
1528
1614
|
});
|
|
1529
1615
|
return this;
|
|
1530
1616
|
}
|
|
1531
1617
|
/**
|
|
1618
|
+
* Register a GET route handler.
|
|
1619
|
+
*/
|
|
1620
|
+
get(path, ...handlers) {
|
|
1621
|
+
return this.registerRoute([
|
|
1622
|
+
"get"
|
|
1623
|
+
], path, ...handlers);
|
|
1624
|
+
}
|
|
1625
|
+
/**
|
|
1626
|
+
* Register a POST route handler.
|
|
1627
|
+
*/
|
|
1628
|
+
post(path, ...handlers) {
|
|
1629
|
+
return this.registerRoute([
|
|
1630
|
+
"post"
|
|
1631
|
+
], path, ...handlers);
|
|
1632
|
+
}
|
|
1633
|
+
/**
|
|
1634
|
+
* Register a PUT route handler.
|
|
1635
|
+
*/
|
|
1636
|
+
put(path, ...handlers) {
|
|
1637
|
+
return this.registerRoute([
|
|
1638
|
+
"put"
|
|
1639
|
+
], path, ...handlers);
|
|
1640
|
+
}
|
|
1641
|
+
/**
|
|
1642
|
+
* Register a DELETE route handler.
|
|
1643
|
+
*/
|
|
1644
|
+
delete(path, ...handlers) {
|
|
1645
|
+
return this.registerRoute([
|
|
1646
|
+
"delete"
|
|
1647
|
+
], path, ...handlers);
|
|
1648
|
+
}
|
|
1649
|
+
/**
|
|
1650
|
+
* Register a PATCH route handler.
|
|
1651
|
+
*/
|
|
1652
|
+
patch(path, ...handlers) {
|
|
1653
|
+
return this.registerRoute([
|
|
1654
|
+
"patch"
|
|
1655
|
+
], path, ...handlers);
|
|
1656
|
+
}
|
|
1657
|
+
/**
|
|
1658
|
+
* Register an OPTIONS route handler.
|
|
1659
|
+
*/
|
|
1660
|
+
options(path, ...handlers) {
|
|
1661
|
+
return this.registerRoute([
|
|
1662
|
+
"options"
|
|
1663
|
+
], path, ...handlers);
|
|
1664
|
+
}
|
|
1665
|
+
/**
|
|
1666
|
+
* Register a HEAD route handler.
|
|
1667
|
+
*/
|
|
1668
|
+
head(path, ...handlers) {
|
|
1669
|
+
return this.registerRoute([
|
|
1670
|
+
"head"
|
|
1671
|
+
], path, ...handlers);
|
|
1672
|
+
}
|
|
1673
|
+
/**
|
|
1532
1674
|
* Register custom middleware with optional path restriction.
|
|
1533
1675
|
*
|
|
1534
1676
|
* Use this for:
|
|
@@ -1542,7 +1684,7 @@ var ExpressServer = class {
|
|
|
1542
1684
|
* @returns This instance for method chaining
|
|
1543
1685
|
*/
|
|
1544
1686
|
registerMiddleware(path, middleware) {
|
|
1545
|
-
const routerToUse = this.
|
|
1687
|
+
const routerToUse = this.rootRouter;
|
|
1546
1688
|
if (typeof path === "string") {
|
|
1547
1689
|
const normalizedPath = this.normalizePath(path);
|
|
1548
1690
|
if (normalizedPath) {
|
|
@@ -1565,7 +1707,7 @@ var ExpressServer = class {
|
|
|
1565
1707
|
*/
|
|
1566
1708
|
useMiddleware(...middlewares) {
|
|
1567
1709
|
middlewares.forEach((middleware) => {
|
|
1568
|
-
|
|
1710
|
+
this.rootRouter.use(middleware);
|
|
1569
1711
|
});
|
|
1570
1712
|
return this;
|
|
1571
1713
|
}
|
package/server/index.d.ts
CHANGED
|
@@ -62,11 +62,11 @@ declare class ExpressServer {
|
|
|
62
62
|
protected hooks: CatbeeServerHooks;
|
|
63
63
|
/** Global API prefix (from config) */
|
|
64
64
|
protected globalPrefix: string;
|
|
65
|
-
/**
|
|
65
|
+
/** Primary root router mounted to the application */
|
|
66
66
|
private readonly rootRouter;
|
|
67
|
-
/**
|
|
68
|
-
private
|
|
69
|
-
/**
|
|
67
|
+
/** Set of registered sub-routers to prevent duplicate mounting */
|
|
68
|
+
private readonly mountedRouters;
|
|
69
|
+
/** Express app instance */
|
|
70
70
|
private readonly app;
|
|
71
71
|
/** Set of active WebSocket connections */
|
|
72
72
|
private readonly connections;
|
|
@@ -74,6 +74,8 @@ declare class ExpressServer {
|
|
|
74
74
|
private isShuttingDown;
|
|
75
75
|
/** Flag indicating if graceful shutdown handlers are registered */
|
|
76
76
|
private gracefulShutdownRegistered;
|
|
77
|
+
/** Map of registered signal listeners for clean teardown */
|
|
78
|
+
private readonly signalListeners;
|
|
77
79
|
/**
|
|
78
80
|
* Collection of registered health check functions.
|
|
79
81
|
* These are executed when the health check endpoint is accessed.
|
|
@@ -81,6 +83,8 @@ declare class ExpressServer {
|
|
|
81
83
|
private readonly healthChecks;
|
|
82
84
|
/** Promise that resolves when initialization (middleware + routes) is complete */
|
|
83
85
|
private readonly initPromise;
|
|
86
|
+
/** In-flight start promise to protect against concurrent start() calls */
|
|
87
|
+
private startPromise?;
|
|
84
88
|
/**
|
|
85
89
|
* Initializes server with intelligent defaults and security best practices.
|
|
86
90
|
* All settings can be customized via config and hooks.
|
|
@@ -178,6 +182,10 @@ declare class ExpressServer {
|
|
|
178
182
|
* Set up OpenAPI documentation middleware.
|
|
179
183
|
*/
|
|
180
184
|
private setupOpenApiMiddleware;
|
|
185
|
+
/**
|
|
186
|
+
* Set up response preprocessing hook (applies global prefix if set).
|
|
187
|
+
*/
|
|
188
|
+
private setupResponseHook;
|
|
181
189
|
/**
|
|
182
190
|
* Configure server routes and error handling.
|
|
183
191
|
* Sets up in following order:
|
|
@@ -238,28 +246,32 @@ declare class ExpressServer {
|
|
|
238
246
|
* Start the HTTP server and begin listening for requests.
|
|
239
247
|
*
|
|
240
248
|
* This method:
|
|
249
|
+
* - Protects against concurrent start() invocations
|
|
250
|
+
* - Awaits server initialization (middleware + routes)
|
|
241
251
|
* - Executes beforeStart hooks
|
|
252
|
+
* - Creates the HTTP/HTTPS server instance
|
|
253
|
+
* - Sets up error handling and connection tracking BEFORE listening
|
|
254
|
+
* - Executes onServerCreated hook BEFORE listening
|
|
242
255
|
* - Binds to the configured host/port
|
|
243
|
-
* -
|
|
244
|
-
* -
|
|
245
|
-
* - Logs startup information
|
|
256
|
+
* - Executes afterStart hooks on successful listen
|
|
257
|
+
* - Cleans up server reference and listeners on startup failure
|
|
246
258
|
*
|
|
247
259
|
* @returns Promise resolving to the running HTTP server instance
|
|
248
260
|
* @throws Error if server fails to start or port is already in use
|
|
249
261
|
*/
|
|
250
262
|
start(): Promise<http.Server | https.Server>;
|
|
251
263
|
/**
|
|
252
|
-
*
|
|
264
|
+
* Internal implementation of server startup.
|
|
265
|
+
*/
|
|
266
|
+
private doStart;
|
|
267
|
+
/**
|
|
268
|
+
* Create HTTP or HTTPS server instance (without listening).
|
|
253
269
|
*/
|
|
254
270
|
private createServerInstance;
|
|
255
271
|
/**
|
|
256
272
|
* Set up connection tracking for graceful shutdown.
|
|
257
273
|
*/
|
|
258
274
|
private setupConnectionTracking;
|
|
259
|
-
/**
|
|
260
|
-
* Set up error handling for server startup.
|
|
261
|
-
*/
|
|
262
|
-
private setupServerErrorHandling;
|
|
263
275
|
/**
|
|
264
276
|
* Log server startup information.
|
|
265
277
|
*/
|
|
@@ -297,14 +309,32 @@ declare class ExpressServer {
|
|
|
297
309
|
* @param signals Array of process signals to listen for (default: SIGINT, SIGTERM)
|
|
298
310
|
*/
|
|
299
311
|
enableGracefulShutdown(signals?: NodeJS.Signals[]): this;
|
|
312
|
+
/**
|
|
313
|
+
* Unregister graceful shutdown signal listeners.
|
|
314
|
+
* Useful for testing and dynamic server lifecycles to prevent memory and listener leaks.
|
|
315
|
+
*/
|
|
316
|
+
disableGracefulShutdown(): this;
|
|
300
317
|
/**
|
|
301
318
|
* Destroy all active connections (gracefully if possible).
|
|
302
319
|
* If a connection does not close cleanly, it will be force-destroyed.
|
|
303
320
|
*/
|
|
304
321
|
private destroyConnections;
|
|
305
322
|
/**
|
|
306
|
-
*
|
|
307
|
-
*
|
|
323
|
+
* Mount a base router onto the server's root router.
|
|
324
|
+
*
|
|
325
|
+
* Note: This attaches the supplied router to the root router pipeline.
|
|
326
|
+
* Duplicate mounting of the same router instance is ignored.
|
|
327
|
+
*
|
|
328
|
+
* @param router The Express router instance to mount
|
|
329
|
+
* @returns This instance for method chaining
|
|
330
|
+
*/
|
|
331
|
+
addBaseRouter(router: Router): this;
|
|
332
|
+
/**
|
|
333
|
+
* Alias for `addBaseRouter` (maintained for backward compatibility).
|
|
334
|
+
* Mounts the supplied router onto the server's root router.
|
|
335
|
+
*
|
|
336
|
+
* @param router The Express router instance to mount
|
|
337
|
+
* @returns This instance for method chaining
|
|
308
338
|
*/
|
|
309
339
|
setBaseRouter(router: Router): this;
|
|
310
340
|
/**
|
|
@@ -321,6 +351,34 @@ declare class ExpressServer {
|
|
|
321
351
|
* @returns This instance for method chaining
|
|
322
352
|
*/
|
|
323
353
|
registerRoute(methods: Array<keyof Pick<Express, 'get' | 'post' | 'put' | 'delete' | 'patch' | 'options' | 'head'>>, path: string, ...handlers: Array<express.RequestHandler>): this;
|
|
354
|
+
/**
|
|
355
|
+
* Register a GET route handler.
|
|
356
|
+
*/
|
|
357
|
+
get(path: string, ...handlers: express.RequestHandler[]): this;
|
|
358
|
+
/**
|
|
359
|
+
* Register a POST route handler.
|
|
360
|
+
*/
|
|
361
|
+
post(path: string, ...handlers: express.RequestHandler[]): this;
|
|
362
|
+
/**
|
|
363
|
+
* Register a PUT route handler.
|
|
364
|
+
*/
|
|
365
|
+
put(path: string, ...handlers: express.RequestHandler[]): this;
|
|
366
|
+
/**
|
|
367
|
+
* Register a DELETE route handler.
|
|
368
|
+
*/
|
|
369
|
+
delete(path: string, ...handlers: express.RequestHandler[]): this;
|
|
370
|
+
/**
|
|
371
|
+
* Register a PATCH route handler.
|
|
372
|
+
*/
|
|
373
|
+
patch(path: string, ...handlers: express.RequestHandler[]): this;
|
|
374
|
+
/**
|
|
375
|
+
* Register an OPTIONS route handler.
|
|
376
|
+
*/
|
|
377
|
+
options(path: string, ...handlers: express.RequestHandler[]): this;
|
|
378
|
+
/**
|
|
379
|
+
* Register a HEAD route handler.
|
|
380
|
+
*/
|
|
381
|
+
head(path: string, ...handlers: express.RequestHandler[]): this;
|
|
324
382
|
/**
|
|
325
383
|
* Register custom middleware with optional path restriction.
|
|
326
384
|
*
|