@catbee/utils 2.2.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/server/index.cjs CHANGED
@@ -39,6 +39,7 @@ var fs = require('@catbee/utils/fs');
39
39
  var validation = require('@catbee/utils/validation');
40
40
  var async = require('@catbee/utils/async');
41
41
  var id = require('@catbee/utils/id');
42
+ var string = require('@catbee/utils/string');
42
43
  var healthzServer = require('@catbee/utils/healthz-server');
43
44
 
44
45
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
@@ -738,8 +739,12 @@ var ExpressServer = class {
738
739
  app;
739
740
  /** Set of active WebSocket connections */
740
741
  connections = /* @__PURE__ */ new Set();
741
- /** Flag indicating if the server is shutting down */
742
+ /** Flag indicating if the server is shutting down (rejects traffic with 503) */
742
743
  isShuttingDown = false;
744
+ /** Flag indicating if the server is in graceful drain delay (sets Connection: close while servicing traffic) */
745
+ isDraining = false;
746
+ /** Internal readiness state (used when HealthzServer is not enabled) */
747
+ internalReady = false;
743
748
  /** Flag indicating if graceful shutdown handlers are registered */
744
749
  gracefulShutdownRegistered = false;
745
750
  /** Map of registered signal listeners for clean teardown */
@@ -750,10 +755,14 @@ var ExpressServer = class {
750
755
  healthzChecks = [];
751
756
  /** Named checks queued for Healthz readiness probe */
752
757
  healthzReadinessChecks = [];
758
+ /** Whether readiness checks were explicitly provided or registered */
759
+ hasExplicitReadinessChecks = false;
753
760
  /** Promise that resolves when initialization (middleware + routes) is complete */
754
761
  initPromise;
755
762
  /** In-flight start promise to protect against concurrent start() calls */
756
763
  startPromise;
764
+ /** In-flight stop promise to protect against concurrent stop() calls */
765
+ stopPromise;
757
766
  /**
758
767
  * Initializes server with intelligent defaults and security best practices.
759
768
  * All settings can be customized via config and hooks.
@@ -794,7 +803,8 @@ var ExpressServer = class {
794
803
  if (this.config.healthzServer.checks) {
795
804
  this.healthzChecks.push(...this.config.healthzServer.checks);
796
805
  }
797
- if (this.config.healthzServer.readinessChecks) {
806
+ if (this.config.healthzServer.readinessChecks !== void 0) {
807
+ this.hasExplicitReadinessChecks = true;
798
808
  this.healthzReadinessChecks.push(...this.config.healthzServer.readinessChecks);
799
809
  }
800
810
  }
@@ -892,6 +902,9 @@ var ExpressServer = class {
892
902
  res.status(httpStatusCodes.HttpStatusCodes.SERVICE_UNAVAILABLE).json(new exception.ServiceUnavailableException("Server is shutting down"));
893
903
  return;
894
904
  }
905
+ if (this.isDraining) {
906
+ res.setHeader("Connection", "close");
907
+ }
895
908
  next();
896
909
  });
897
910
  }
@@ -1205,21 +1218,96 @@ var ExpressServer = class {
1205
1218
  check
1206
1219
  };
1207
1220
  if (probeType === "liveness" || probeType === "both") {
1208
- this.healthzChecks.push(namedCheck);
1221
+ const idx = this.healthzChecks.findIndex((c) => c.name === name);
1222
+ if (idx !== -1) {
1223
+ this.healthzChecks[idx] = namedCheck;
1224
+ } else {
1225
+ this.healthzChecks.push(namedCheck);
1226
+ }
1209
1227
  }
1210
1228
  if (probeType === "readiness" || probeType === "both") {
1211
- this.healthzReadinessChecks.push(namedCheck);
1229
+ this.hasExplicitReadinessChecks = true;
1230
+ const idx = this.healthzReadinessChecks.findIndex((c) => c.name === name);
1231
+ if (idx !== -1) {
1232
+ this.healthzReadinessChecks[idx] = namedCheck;
1233
+ } else {
1234
+ this.healthzReadinessChecks.push(namedCheck);
1235
+ }
1212
1236
  }
1213
1237
  healthzServer.HealthzServer.registerCheck(namedCheck, probeType);
1214
1238
  return this;
1215
1239
  }
1216
1240
  /**
1241
+ * Unregister a health check by name.
1242
+ *
1243
+ * @param name Name of the check to remove
1244
+ * @param options Target probe type ('readiness' | 'liveness' | 'both') or options object
1245
+ * @returns This instance for method chaining
1246
+ */
1247
+ unregisterHealthCheck(name, options) {
1248
+ const probeType = typeof options === "string" ? options : options?.type ?? "both";
1249
+ if (probeType === "liveness" || probeType === "both") {
1250
+ const idx = this.healthzChecks.findIndex((c) => c.name === name);
1251
+ if (idx !== -1) this.healthzChecks.splice(idx, 1);
1252
+ }
1253
+ if (probeType === "readiness" || probeType === "both") {
1254
+ const idx = this.healthzReadinessChecks.findIndex((c) => c.name === name);
1255
+ if (idx !== -1) this.healthzReadinessChecks.splice(idx, 1);
1256
+ }
1257
+ healthzServer.HealthzServer.unregisterCheck(name, probeType);
1258
+ return this;
1259
+ }
1260
+ /**
1261
+ * Get all registered Healthz checks queued for this Express server.
1262
+ */
1263
+ getHealthzChecks() {
1264
+ return {
1265
+ liveness: [
1266
+ ...this.healthzChecks
1267
+ ],
1268
+ readiness: [
1269
+ ...this.healthzReadinessChecks
1270
+ ]
1271
+ };
1272
+ }
1273
+ /**
1274
+ * Register a readiness health check.
1275
+ *
1276
+ * @param name Unique identifier for the check (used in detailed responses)
1277
+ * @param check Function returning boolean or Promise<boolean> indicating health (supports optional AbortSignal)
1278
+ * @returns This instance for method chaining
1279
+ */
1280
+ registerReadinessCheck(name, check) {
1281
+ return this.registerHealthCheck(name, check, "readiness");
1282
+ }
1283
+ /**
1284
+ * Register a liveness health check.
1285
+ *
1286
+ * @param name Unique identifier for the check (used in detailed responses)
1287
+ * @param check Function returning boolean or Promise<boolean> indicating health (supports optional AbortSignal)
1288
+ * @returns This instance for method chaining
1289
+ */
1290
+ registerLivenessCheck(name, check) {
1291
+ return this.registerHealthCheck(name, check, "liveness");
1292
+ }
1293
+ /**
1294
+ * Register a readiness and liveness health check.
1295
+ *
1296
+ * @param name Unique identifier for the check (used in detailed responses)
1297
+ * @param check Function returning boolean or Promise<boolean> indicating health (supports optional AbortSignal)
1298
+ * @returns This instance for method chaining
1299
+ */
1300
+ registerReadinessAndLivenessChecks(name, check) {
1301
+ return this.registerHealthCheck(name, check, "both");
1302
+ }
1303
+ /**
1217
1304
  * Mark the service as ready / not-ready for traffic on the Healthz probe server.
1218
1305
  *
1219
1306
  * @param ready Whether the service is ready to receive traffic
1220
1307
  * @returns This instance for method chaining
1221
1308
  */
1222
1309
  setReady(ready) {
1310
+ this.internalReady = ready;
1223
1311
  healthzServer.HealthzServer.setReady(ready);
1224
1312
  return this;
1225
1313
  }
@@ -1227,7 +1315,28 @@ var ExpressServer = class {
1227
1315
  * Whether the service is currently marked as ready for traffic on the Healthz probe server.
1228
1316
  */
1229
1317
  isReady() {
1230
- return healthzServer.HealthzServer.isReady();
1318
+ if (this.isHealthzServerEnabled()) {
1319
+ return healthzServer.HealthzServer.isReady();
1320
+ }
1321
+ return this.internalReady && this.isRunning();
1322
+ }
1323
+ /**
1324
+ * Mark application startup as completed on the Healthz probe server (switches `/startupz` to 200).
1325
+ *
1326
+ * @returns This instance for method chaining
1327
+ */
1328
+ markStartupComplete() {
1329
+ healthzServer.HealthzServer.markStartupComplete();
1330
+ return this;
1331
+ }
1332
+ /**
1333
+ * Whether application startup has completed on the Healthz probe server.
1334
+ */
1335
+ isStartupComplete() {
1336
+ if (this.isHealthzServerEnabled()) {
1337
+ return healthzServer.HealthzServer.isStartupComplete();
1338
+ }
1339
+ return this.isRunning();
1231
1340
  }
1232
1341
  /**
1233
1342
  * Get the running HealthzServer instance (if started).
@@ -1248,7 +1357,7 @@ var ExpressServer = class {
1248
1357
  * @returns `true` when ready, otherwise `false`.
1249
1358
  */
1250
1359
  ready() {
1251
- return healthzServer.HealthzServer.isReady();
1360
+ return this.isReady();
1252
1361
  }
1253
1362
  /**
1254
1363
  * Get the underlying Express application instance.
@@ -1305,92 +1414,108 @@ var ExpressServer = class {
1305
1414
  */
1306
1415
  async doStart() {
1307
1416
  await this.initPromise;
1308
- await this.runHook("beforeStart", this.app);
1309
- const server = this.createServerInstance();
1310
- this.server = server;
1311
- return new Promise((resolve, reject) => {
1312
- let isListening = false;
1313
- server.on("error", (err) => {
1314
- if (!isListening) {
1315
- logger.getLogger().error({
1316
- err
1317
- }, "Server failed to start");
1318
- try {
1319
- server.removeAllListeners();
1320
- server.close();
1321
- if (healthzServer.HealthzServer.isStarted()) {
1322
- healthzServer.HealthzServer.stop().catch(() => {
1323
- });
1324
- }
1325
- } catch {
1326
- }
1327
- this.server = null;
1328
- this.connections.clear();
1329
- reject(err);
1330
- } else {
1331
- logger.getLogger().error({
1332
- err
1333
- }, "Server runtime error");
1334
- }
1335
- });
1336
- this.setupConnectionTracking();
1337
- Promise.resolve(this.runHook("onServerCreated", server)).then(() => {
1338
- const onListening = /* @__PURE__ */ __name(async () => {
1339
- try {
1340
- if (this.isHealthzServerEnabled()) {
1341
- const healthzConfig = {
1342
- ...typeof this.config.healthzServer === "object" ? this.config.healthzServer : {},
1343
- handleSignals: false,
1344
- checks: [
1345
- ...this.healthzChecks
1346
- ],
1347
- readinessChecks: [
1348
- ...this.healthzReadinessChecks
1349
- ]
1350
- };
1351
- const addr = await healthzServer.HealthzServer.start(healthzConfig);
1352
- if (!addr) {
1353
- throw new Error("Healthz probe server failed to start (already running in this process)");
1354
- }
1355
- this.healthzAddress = addr;
1356
- }
1357
- this.logServerStartInfo();
1358
- await this.runHook("afterStart", server);
1359
- if (this.isHealthzServerEnabled()) {
1360
- healthzServer.HealthzServer.setReady(true);
1361
- }
1362
- isListening = true;
1363
- resolve(server);
1364
- } catch (err) {
1365
- const error = err instanceof Error ? err : new Error(String(err));
1417
+ if (this.isHealthzServerEnabled()) {
1418
+ const healthzConfig = {
1419
+ ...typeof this.config.healthzServer === "object" ? this.config.healthzServer : {},
1420
+ handleSignals: false,
1421
+ checks: [
1422
+ ...this.healthzChecks
1423
+ ],
1424
+ ...this.hasExplicitReadinessChecks ? {
1425
+ readinessChecks: [
1426
+ ...this.healthzReadinessChecks
1427
+ ]
1428
+ } : {}
1429
+ };
1430
+ const addr = await healthzServer.HealthzServer.start(healthzConfig);
1431
+ if (!addr) {
1432
+ throw new Error("Healthz probe server failed to start (already running in this process)");
1433
+ }
1434
+ this.healthzAddress = addr;
1435
+ }
1436
+ try {
1437
+ await this.runHook("beforeStart", this.app);
1438
+ const server = this.createServerInstance();
1439
+ this.server = server;
1440
+ return await new Promise((resolve, reject) => {
1441
+ let isListening = false;
1442
+ server.on("error", async (err) => {
1443
+ if (!isListening) {
1366
1444
  logger.getLogger().error({
1367
- err: error
1368
- }, "Server startup failed");
1369
- if (healthzServer.HealthzServer.isStarted()) {
1370
- await healthzServer.HealthzServer.stop().catch(() => {
1371
- });
1372
- }
1445
+ err
1446
+ }, "Server failed to start");
1373
1447
  try {
1374
1448
  server.removeAllListeners();
1375
1449
  server.close();
1450
+ if (healthzServer.HealthzServer.isRunning()) {
1451
+ await healthzServer.HealthzServer.stop().catch(() => {
1452
+ });
1453
+ }
1376
1454
  } catch {
1377
1455
  }
1378
1456
  this.server = null;
1379
1457
  this.healthzAddress = null;
1380
1458
  this.connections.clear();
1381
- reject(error);
1459
+ reject(err);
1460
+ } else {
1461
+ logger.getLogger().error({
1462
+ err
1463
+ }, "Server runtime error");
1382
1464
  }
1383
- }, "onListening");
1384
- const listenArgs = [
1385
- this.config.port,
1386
- this.config.host,
1387
- onListening
1388
- ];
1389
- server.listen(...listenArgs);
1390
- }).catch((err) => {
1391
- server.emit("error", err instanceof Error ? err : new Error(String(err)));
1465
+ });
1466
+ this.setupConnectionTracking();
1467
+ Promise.resolve(this.runHook("onServerCreated", server)).then(() => {
1468
+ const onListening = /* @__PURE__ */ __name(async () => {
1469
+ try {
1470
+ this.logServerStartInfo();
1471
+ await this.runHook("afterStart", server);
1472
+ this.internalReady = true;
1473
+ if (this.isHealthzServerEnabled()) {
1474
+ healthzServer.HealthzServer.markStartupComplete();
1475
+ healthzServer.HealthzServer.setReady(true);
1476
+ }
1477
+ isListening = true;
1478
+ resolve(server);
1479
+ } catch (err) {
1480
+ const error = err instanceof Error ? err : new Error(String(err));
1481
+ logger.getLogger().error({
1482
+ err: error
1483
+ }, "Server startup failed");
1484
+ if (healthzServer.HealthzServer.isRunning()) {
1485
+ await healthzServer.HealthzServer.stop().catch(() => {
1486
+ });
1487
+ }
1488
+ try {
1489
+ server.removeAllListeners();
1490
+ server.close();
1491
+ } catch {
1492
+ }
1493
+ this.server = null;
1494
+ this.healthzAddress = null;
1495
+ this.connections.clear();
1496
+ reject(error);
1497
+ }
1498
+ }, "onListening");
1499
+ const listenArgs = [
1500
+ this.config.port,
1501
+ this.config.host,
1502
+ onListening
1503
+ ];
1504
+ server.listen(...listenArgs);
1505
+ }).catch((err) => {
1506
+ server.emit("error", err instanceof Error ? err : new Error(String(err)));
1507
+ });
1392
1508
  });
1393
- });
1509
+ } catch (err) {
1510
+ if (healthzServer.HealthzServer.isRunning()) {
1511
+ await healthzServer.HealthzServer.stop().catch(() => {
1512
+ });
1513
+ }
1514
+ this.healthzAddress = null;
1515
+ this.server = null;
1516
+ this.connections.clear();
1517
+ throw err;
1518
+ }
1394
1519
  }
1395
1520
  /**
1396
1521
  * Create HTTP or HTTPS server instance (without listening).
@@ -1431,7 +1556,8 @@ var ExpressServer = class {
1431
1556
  const url = `${protocol}://${host}:${port}`;
1432
1557
  logger.getLogger().info(`Server running on ${url}`);
1433
1558
  if (this.healthzAddress) {
1434
- logger.getLogger().info(`Healthz probe server running on http://${this.healthzAddress.address}:${this.healthzAddress.port}`);
1559
+ const healthzHost = this.formatHostForUrl(this.healthzAddress.address);
1560
+ logger.getLogger().info(`Healthz server running on http://${healthzHost}:${this.healthzAddress.port}`);
1435
1561
  }
1436
1562
  if (this.config.openApi?.enable) {
1437
1563
  logger.getLogger().info(`API docs available at ${url}${this.normalizePath(this.config.openApi.mountPath, this.config.openApi.withGlobalPrefix)}`);
@@ -1454,16 +1580,26 @@ var ExpressServer = class {
1454
1580
  * - Monitoring systems are notified
1455
1581
  */
1456
1582
  async stop(force = false) {
1457
- if (!this.server && !healthzServer.HealthzServer.isStarted()) {
1583
+ if (!this.server && !healthzServer.HealthzServer.isRunning()) {
1458
1584
  logger.getLogger().warn("Stop called but server is not running");
1459
1585
  return;
1460
1586
  }
1461
- if (this.isShuttingDown) {
1462
- logger.getLogger().warn("Shutdown already in progress");
1463
- return;
1587
+ if (this.stopPromise) {
1588
+ return this.stopPromise;
1589
+ }
1590
+ this.stopPromise = this.doStop(force);
1591
+ try {
1592
+ await this.stopPromise;
1593
+ } finally {
1594
+ this.stopPromise = void 0;
1464
1595
  }
1465
- this.isShuttingDown = true;
1466
- if (healthzServer.HealthzServer.isStarted()) {
1596
+ }
1597
+ /**
1598
+ * Internal implementation of server shutdown.
1599
+ */
1600
+ async doStop(force = false) {
1601
+ this.internalReady = false;
1602
+ if (healthzServer.HealthzServer.isRunning()) {
1467
1603
  healthzServer.HealthzServer.setReady(false);
1468
1604
  }
1469
1605
  if (this.server) {
@@ -1471,19 +1607,23 @@ var ExpressServer = class {
1471
1607
  }
1472
1608
  try {
1473
1609
  const shutdownDelay = this.getHealthzShutdownDelay();
1474
- if (shutdownDelay > 0 && !force && healthzServer.HealthzServer.isStarted()) {
1610
+ if (shutdownDelay > 0 && !force && healthzServer.HealthzServer.isRunning()) {
1475
1611
  logger.getLogger().info(`Waiting ${shutdownDelay}ms for load balancer to drain traffic...`);
1612
+ this.isDraining = true;
1476
1613
  await new Promise((resolve) => setTimeout(resolve, shutdownDelay));
1477
1614
  }
1615
+ this.isDraining = false;
1616
+ this.isShuttingDown = true;
1478
1617
  if (this.server) {
1479
1618
  await this.gracefulShutdown(force);
1480
1619
  }
1481
1620
  } finally {
1482
- if (healthzServer.HealthzServer.isStarted()) {
1621
+ if (healthzServer.HealthzServer.isRunning()) {
1483
1622
  await healthzServer.HealthzServer.stop();
1484
1623
  this.healthzAddress = null;
1485
1624
  }
1486
1625
  this.isShuttingDown = false;
1626
+ this.isDraining = false;
1487
1627
  }
1488
1628
  }
1489
1629
  /**
@@ -1570,10 +1710,11 @@ var ExpressServer = class {
1570
1710
  signalHandled = true;
1571
1711
  logger.getLogger().info(`Received ${signal}, initiating graceful shutdown...`);
1572
1712
  try {
1573
- this.disableGracefulShutdown();
1574
1713
  await this.stop(false);
1714
+ this.disableGracefulShutdown();
1575
1715
  process.exit(0);
1576
1716
  } catch (err) {
1717
+ this.disableGracefulShutdown();
1577
1718
  logger.getLogger().fatal({
1578
1719
  err
1579
1720
  }, "Shutdown failed");
@@ -1931,7 +2072,8 @@ var ExpressServer = class {
1931
2072
  }
1932
2073
  normalizePath(path, withGlobalPrefix = false) {
1933
2074
  const sanitize = /* @__PURE__ */ __name((p) => {
1934
- return "/" + p.trim().replace(/^\/+/, "").replace(/\/{2,}/g, "/").replace(/\/+$/, "");
2075
+ const inner = string.trimChars(p.trim(), "/");
2076
+ return inner ? "/" + inner.replace(/\/{2,}/g, "/") : "/";
1935
2077
  }, "sanitize");
1936
2078
  const prefix = withGlobalPrefix && this.globalPrefix ? sanitize(this.globalPrefix) : "";
1937
2079
  if (typeof path !== "string" || !path.trim()) {
package/server/index.d.ts CHANGED
@@ -26,7 +26,7 @@ import express, { Express, Router } from 'express';
26
26
  import http from 'node:http';
27
27
  import https from 'node:https';
28
28
  import { CatbeeServerConfig, CatbeeServerHooks } from '@catbee/utils/types';
29
- import { HealthzServer, HealthzAddressInfo, CatbeeHealthzServerConfig } from '@catbee/utils/healthz-server';
29
+ import { NamedCheck, HealthzServer, HealthzAddressInfo, CatbeeHealthzServerConfig } from '@catbee/utils/healthz-server';
30
30
 
31
31
  /**
32
32
  * Map of critical dependencies to their error messages.
@@ -71,8 +71,12 @@ declare class ExpressServer {
71
71
  private readonly app;
72
72
  /** Set of active WebSocket connections */
73
73
  private readonly connections;
74
- /** Flag indicating if the server is shutting down */
74
+ /** Flag indicating if the server is shutting down (rejects traffic with 503) */
75
75
  private isShuttingDown;
76
+ /** Flag indicating if the server is in graceful drain delay (sets Connection: close while servicing traffic) */
77
+ private isDraining;
78
+ /** Internal readiness state (used when HealthzServer is not enabled) */
79
+ private internalReady;
76
80
  /** Flag indicating if graceful shutdown handlers are registered */
77
81
  private gracefulShutdownRegistered;
78
82
  /** Map of registered signal listeners for clean teardown */
@@ -83,10 +87,14 @@ declare class ExpressServer {
83
87
  private readonly healthzChecks;
84
88
  /** Named checks queued for Healthz readiness probe */
85
89
  private readonly healthzReadinessChecks;
90
+ /** Whether readiness checks were explicitly provided or registered */
91
+ private hasExplicitReadinessChecks;
86
92
  /** Promise that resolves when initialization (middleware + routes) is complete */
87
93
  private readonly initPromise;
88
94
  /** In-flight start promise to protect against concurrent start() calls */
89
95
  private startPromise?;
96
+ /** In-flight stop promise to protect against concurrent stop() calls */
97
+ private stopPromise?;
90
98
  /**
91
99
  * Initializes server with intelligent defaults and security best practices.
92
100
  * All settings can be customized via config and hooks.
@@ -223,9 +231,50 @@ declare class ExpressServer {
223
231
  * @param options Target probe type ('readiness' | 'liveness' | 'both') or options object
224
232
  * @returns This instance for method chaining
225
233
  */
226
- registerHealthCheck(name: string, check: (signal?: AbortSignal) => Promise<boolean> | boolean, options?: 'readiness' | 'liveness' | 'both' | {
234
+ registerHealthCheck(name: string, check: (signal?: AbortSignal) => Promise<boolean | void> | boolean | void, options?: 'readiness' | 'liveness' | 'both' | {
227
235
  type?: 'readiness' | 'liveness' | 'both';
228
236
  }): this;
237
+ /**
238
+ * Unregister a health check by name.
239
+ *
240
+ * @param name Name of the check to remove
241
+ * @param options Target probe type ('readiness' | 'liveness' | 'both') or options object
242
+ * @returns This instance for method chaining
243
+ */
244
+ unregisterHealthCheck(name: string, options?: 'readiness' | 'liveness' | 'both' | {
245
+ type?: 'readiness' | 'liveness' | 'both';
246
+ }): this;
247
+ /**
248
+ * Get all registered Healthz checks queued for this Express server.
249
+ */
250
+ getHealthzChecks(): {
251
+ liveness: NamedCheck[];
252
+ readiness: NamedCheck[];
253
+ };
254
+ /**
255
+ * Register a readiness health check.
256
+ *
257
+ * @param name Unique identifier for the check (used in detailed responses)
258
+ * @param check Function returning boolean or Promise<boolean> indicating health (supports optional AbortSignal)
259
+ * @returns This instance for method chaining
260
+ */
261
+ registerReadinessCheck(name: string, check: (signal?: AbortSignal) => Promise<boolean | void> | boolean | void): this;
262
+ /**
263
+ * Register a liveness health check.
264
+ *
265
+ * @param name Unique identifier for the check (used in detailed responses)
266
+ * @param check Function returning boolean or Promise<boolean> indicating health (supports optional AbortSignal)
267
+ * @returns This instance for method chaining
268
+ */
269
+ registerLivenessCheck(name: string, check: (signal?: AbortSignal) => Promise<boolean | void> | boolean | void): this;
270
+ /**
271
+ * Register a readiness and liveness health check.
272
+ *
273
+ * @param name Unique identifier for the check (used in detailed responses)
274
+ * @param check Function returning boolean or Promise<boolean> indicating health (supports optional AbortSignal)
275
+ * @returns This instance for method chaining
276
+ */
277
+ registerReadinessAndLivenessChecks(name: string, check: (signal?: AbortSignal) => Promise<boolean | void> | boolean | void): this;
229
278
  /**
230
279
  * Mark the service as ready / not-ready for traffic on the Healthz probe server.
231
280
  *
@@ -237,6 +286,16 @@ declare class ExpressServer {
237
286
  * Whether the service is currently marked as ready for traffic on the Healthz probe server.
238
287
  */
239
288
  isReady(): boolean;
289
+ /**
290
+ * Mark application startup as completed on the Healthz probe server (switches `/startupz` to 200).
291
+ *
292
+ * @returns This instance for method chaining
293
+ */
294
+ markStartupComplete(): this;
295
+ /**
296
+ * Whether application startup has completed on the Healthz probe server.
297
+ */
298
+ isStartupComplete(): boolean;
240
299
  /**
241
300
  * Get the running HealthzServer instance (if started).
242
301
  */
@@ -317,6 +376,10 @@ declare class ExpressServer {
317
376
  * - Monitoring systems are notified
318
377
  */
319
378
  stop(force?: boolean): Promise<void>;
379
+ /**
380
+ * Internal implementation of server shutdown.
381
+ */
382
+ private doStop;
320
383
  /**
321
384
  * Perform graceful server shutdown with timeout.
322
385
  */