@catbee/utils 2.1.1 → 2.2.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/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 healthzServer = require('@catbee/utils/healthz-server');
42
43
 
43
44
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
44
45
 
@@ -307,36 +308,53 @@ var ServerConfigBuilder = class {
307
308
  return this.setEnabled("requestLogging", false);
308
309
  }
309
310
  /**
310
- * Configures server health check endpoint.
311
+ * Configures the dedicated Healthz probe HTTP server for Kubernetes.
311
312
  *
312
- * @param opts - Health check configuration options
313
+ * @param opts - Healthz server configuration options or boolean toggle
313
314
  * @returns The builder instance for chaining
314
- * @default - { path: '/healthz', detailed: true, withGlobalPrefix: false }
315
315
  *
316
316
  * @example
317
317
  * ```typescript
318
- * builder.withHealthCheck({
319
- * path: '/health',
320
- * detailed: true
318
+ * builder.withHealthzServer({
319
+ * port: 8282,
320
+ * shutdownDelayMs: 5000,
321
+ * readinessChecks: [
322
+ * { name: 'db', check: () => checkDb() }
323
+ * ]
321
324
  * })
322
325
  * ```
323
326
  */
324
- withHealthCheck(opts) {
325
- this.mergeConfig("healthCheck", opts);
327
+ withHealthzServer(opts) {
328
+ if (typeof opts === "boolean") {
329
+ this.config.healthzServer = opts;
330
+ } else {
331
+ const current = object.isPlainObject(this.config.healthzServer) ? object.deepClone(this.config.healthzServer) : {};
332
+ this.config.healthzServer = object.deepObjMerge({}, current, {
333
+ enable: true,
334
+ ...opts
335
+ });
336
+ }
326
337
  return this;
327
338
  }
328
339
  /**
329
- * Enables health check endpoint with default or custom settings
330
- * @param opts - Optional health check configuration
340
+ * Enables the dedicated Healthz probe HTTP server.
341
+ *
342
+ * @param opts - Optional Healthz server configuration options
331
343
  * @returns The builder instance for chaining
344
+ */
345
+ enableHealthzServer(opts = {}) {
346
+ return this.withHealthzServer({
347
+ ...opts,
348
+ enable: true
349
+ });
350
+ }
351
+ /**
352
+ * Disables the dedicated Healthz probe HTTP server.
332
353
  *
333
- * @example
334
- * ```typescript
335
- * builder.disableHealthCheck()
336
- * ```
354
+ * @returns The builder instance for chaining
337
355
  */
338
- disableHealthCheck() {
339
- return this.setEnabled("healthCheck", false);
356
+ disableHealthzServer() {
357
+ return this.withHealthzServer(false);
340
358
  }
341
359
  /**
342
360
  * Configures OpenAPI/Swagger documentation for the API.
@@ -726,11 +744,12 @@ var ExpressServer = class {
726
744
  gracefulShutdownRegistered = false;
727
745
  /** Map of registered signal listeners for clean teardown */
728
746
  signalListeners = /* @__PURE__ */ new Map();
729
- /**
730
- * Collection of registered health check functions.
731
- * These are executed when the health check endpoint is accessed.
732
- */
733
- healthChecks = [];
747
+ /** Running address info for the Healthz probe server */
748
+ healthzAddress;
749
+ /** Named checks queued for Healthz liveness probe */
750
+ healthzChecks = [];
751
+ /** Named checks queued for Healthz readiness probe */
752
+ healthzReadinessChecks = [];
734
753
  /** Promise that resolves when initialization (middleware + routes) is complete */
735
754
  initPromise;
736
755
  /** In-flight start promise to protect against concurrent start() calls */
@@ -765,8 +784,19 @@ var ExpressServer = class {
765
784
  logger.getLogger().error(msg);
766
785
  throw new Error(msg);
767
786
  }
768
- if (config$1?.healthCheck?.checks) {
769
- this.healthChecks.push(...config$1.healthCheck.checks);
787
+ if (typeof this.config.healthzServer === "boolean") {
788
+ this.config.healthzServer = {
789
+ ...healthzServer.HealthzServer.getDefaultConfig(),
790
+ enable: this.config.healthzServer
791
+ };
792
+ }
793
+ if (this.config.healthzServer && typeof this.config.healthzServer === "object") {
794
+ if (this.config.healthzServer.checks) {
795
+ this.healthzChecks.push(...this.config.healthzServer.checks);
796
+ }
797
+ if (this.config.healthzServer.readinessChecks) {
798
+ this.healthzReadinessChecks.push(...this.config.healthzServer.readinessChecks);
799
+ }
770
800
  }
771
801
  this.globalPrefix = this.normalizePath(this.config.globalPrefix ?? "", false);
772
802
  this.hooks = hooks;
@@ -1114,10 +1144,6 @@ var ExpressServer = class {
1114
1144
  * 4. Error handler
1115
1145
  */
1116
1146
  async setupRoutes() {
1117
- const healthCheckPath = this.normalizePath(this.config.healthCheck?.path || "/healthz", this.config.healthCheck?.withGlobalPrefix);
1118
- this.app.get(healthCheckPath, async (_req, res) => {
1119
- return this.handleHealthCheckRequest(res);
1120
- });
1121
1147
  this.app.use(this.globalPrefix, this.rootRouter);
1122
1148
  await this.runHook("afterRoutes", this.app);
1123
1149
  this.app.use((req, res) => {
@@ -1141,60 +1167,25 @@ var ExpressServer = class {
1141
1167
  });
1142
1168
  }
1143
1169
  /**
1144
- * Execute health check and return response.
1170
+ * Whether the Healthz probe server is enabled.
1145
1171
  */
1146
- async handleHealthCheckRequest(res) {
1147
- try {
1148
- if (!this.healthChecks.length || config.getCatbeeServerGlobalConfig().skipHealthzChecksValidation) {
1149
- return res.status(httpStatusCodes.HttpStatusCodes.OK).json(new response.SuccessResponse("OK"));
1150
- }
1151
- const results = await this.executeHealthChecks();
1152
- const allOk = results.every((r) => r.status);
1153
- const status = allOk ? httpStatusCodes.HttpStatusCodes.OK : httpStatusCodes.HttpStatusCodes.SERVICE_UNAVAILABLE;
1154
- const response$1 = new response.SuccessResponse(allOk ? "OK" : "Service unavailable");
1155
- if (!allOk) response$1.error = true;
1156
- if (this.config.healthCheck?.detailed) response$1.data = {
1157
- checks: results
1158
- };
1159
- return res.status(status).json(response$1);
1160
- } catch {
1161
- return res.status(httpStatusCodes.HttpStatusCodes.INTERNAL_SERVER_ERROR).json(new exception.InternalServerErrorException("Health check failed"));
1172
+ isHealthzServerEnabled() {
1173
+ if (typeof this.config.healthzServer === "boolean") {
1174
+ return this.config.healthzServer;
1162
1175
  }
1176
+ return this.config.healthzServer?.enable === true;
1163
1177
  }
1164
1178
  /**
1165
- * Execute all registered health checks and return results.
1179
+ * Get the graceful shutdown delay in milliseconds configured for HealthzServer.
1166
1180
  */
1167
- async executeHealthChecks() {
1168
- const checkResults = await Promise.allSettled(this.healthChecks.map(async ({ name, check }) => {
1169
- try {
1170
- const status = await Promise.resolve(check());
1171
- return {
1172
- name,
1173
- status,
1174
- error: null
1175
- };
1176
- } catch (error) {
1177
- return {
1178
- name,
1179
- status: false,
1180
- error: error.message
1181
- };
1182
- }
1183
- }));
1184
- return checkResults.map((result) => {
1185
- if (result.status === "fulfilled") return result.value;
1186
- return {
1187
- name: "unknown",
1188
- status: false,
1189
- error: result.reason
1190
- };
1191
- });
1181
+ getHealthzShutdownDelay() {
1182
+ return typeof this.config.healthzServer === "object" ? this.config.healthzServer.shutdownDelayMs ?? 0 : 0;
1192
1183
  }
1193
1184
  /**
1194
- * Register a new health check function for monitoring service dependencies.
1185
+ * Register a new health check function for monitoring service dependencies on the Healthz probe server.
1195
1186
  *
1196
- * Health checks are executed when the health endpoint is accessed and
1197
- * help determine if the service is ready to handle requests.
1187
+ * By default, external dependencies (DB, Redis, etc.) are registered as `readiness` checks.
1188
+ * Can also be registered as `liveness` or `both`.
1198
1189
  *
1199
1190
  * Examples:
1200
1191
  * - Database connectivity
@@ -1203,33 +1194,76 @@ var ExpressServer = class {
1203
1194
  * - Memory/CPU usage checks
1204
1195
  *
1205
1196
  * @param name Unique identifier for the check (used in detailed responses)
1206
- * @param check Function returning boolean or Promise<boolean> indicating health
1197
+ * @param check Function returning boolean or Promise<boolean> indicating health (supports optional AbortSignal)
1198
+ * @param options Target probe type ('readiness' | 'liveness' | 'both') or options object
1207
1199
  * @returns This instance for method chaining
1208
1200
  */
1209
- registerHealthCheck(name, check) {
1210
- this.healthChecks.push({
1201
+ registerHealthCheck(name, check, options) {
1202
+ const probeType = typeof options === "string" ? options : options?.type ?? "readiness";
1203
+ const namedCheck = {
1211
1204
  name,
1212
1205
  check
1213
- });
1206
+ };
1207
+ if (probeType === "liveness" || probeType === "both") {
1208
+ this.healthzChecks.push(namedCheck);
1209
+ }
1210
+ if (probeType === "readiness" || probeType === "both") {
1211
+ this.healthzReadinessChecks.push(namedCheck);
1212
+ }
1213
+ healthzServer.HealthzServer.registerCheck(namedCheck, probeType);
1214
1214
  return this;
1215
1215
  }
1216
1216
  /**
1217
- * Run registered health checks and return whether the service is ready.
1218
- * Useful for readiness probes in deployment tooling.
1217
+ * Mark the service as ready / not-ready for traffic on the Healthz probe server.
1219
1218
  *
1220
- * @returns Promise resolving to `true` when all checks pass, otherwise `false`.
1219
+ * @param ready Whether the service is ready to receive traffic
1220
+ * @returns This instance for method chaining
1221
1221
  */
1222
- async ready() {
1223
- try {
1224
- if (!this.healthChecks.length || config.getCatbeeServerGlobalConfig().skipHealthzChecksValidation) return true;
1225
- const results = await this.executeHealthChecks();
1226
- return results.every((r) => r.status === true);
1227
- } catch (err) {
1228
- logger.getLogger().error({
1229
- err
1230
- }, "Error while running readiness checks");
1231
- return false;
1232
- }
1222
+ setReady(ready) {
1223
+ healthzServer.HealthzServer.setReady(ready);
1224
+ return this;
1225
+ }
1226
+ /**
1227
+ * Whether the service is currently marked as ready for traffic on the Healthz probe server.
1228
+ */
1229
+ isReady() {
1230
+ return healthzServer.HealthzServer.isReady();
1231
+ }
1232
+ /**
1233
+ * Mark application startup as completed on the Healthz probe server (switches `/startupz` to 200).
1234
+ *
1235
+ * @returns This instance for method chaining
1236
+ */
1237
+ markStartupComplete() {
1238
+ healthzServer.HealthzServer.markStartupComplete();
1239
+ return this;
1240
+ }
1241
+ /**
1242
+ * Whether application startup has completed on the Healthz probe server.
1243
+ */
1244
+ isStartupComplete() {
1245
+ return healthzServer.HealthzServer.isStartupComplete();
1246
+ }
1247
+ /**
1248
+ * Get the running HealthzServer instance (if started).
1249
+ */
1250
+ getHealthzServer() {
1251
+ return healthzServer.HealthzServer.getInstance();
1252
+ }
1253
+ /**
1254
+ * Get the address info of the running HealthzServer (if started).
1255
+ */
1256
+ getHealthzAddress() {
1257
+ return this.healthzAddress;
1258
+ }
1259
+ /**
1260
+ * Returns whether the service is currently marked as ready for traffic on the Healthz probe server.
1261
+ * Useful for readiness checks in deployment tooling.
1262
+ *
1263
+ * @returns `true` when ready, otherwise `false`.
1264
+ */
1265
+ ready() {
1266
+ return healthzServer.HealthzServer.isReady();
1233
1267
  }
1234
1268
  /**
1235
1269
  * Get the underlying Express application instance.
@@ -1286,48 +1320,105 @@ var ExpressServer = class {
1286
1320
  */
1287
1321
  async doStart() {
1288
1322
  await this.initPromise;
1289
- await this.runHook("beforeStart", this.app);
1290
- const server = this.createServerInstance();
1291
- this.server = server;
1292
- return new Promise((resolve, reject) => {
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 {
1323
+ if (this.isHealthzServerEnabled()) {
1324
+ const healthzConfig = {
1325
+ ...typeof this.config.healthzServer === "object" ? this.config.healthzServer : {},
1326
+ handleSignals: false,
1327
+ checks: [
1328
+ ...this.healthzChecks
1329
+ ],
1330
+ readinessChecks: [
1331
+ ...this.healthzReadinessChecks
1332
+ ]
1333
+ };
1334
+ const addr = await healthzServer.HealthzServer.start(healthzConfig);
1335
+ if (!addr) {
1336
+ throw new Error("Healthz probe server failed to start (already running in this process)");
1337
+ }
1338
+ this.healthzAddress = addr;
1339
+ }
1340
+ try {
1341
+ await this.runHook("beforeStart", this.app);
1342
+ const server = this.createServerInstance();
1343
+ this.server = server;
1344
+ return await new Promise((resolve, reject) => {
1345
+ let isListening = false;
1346
+ server.on("error", async (err) => {
1347
+ if (!isListening) {
1348
+ logger.getLogger().error({
1349
+ err
1350
+ }, "Server failed to start");
1351
+ try {
1352
+ server.removeAllListeners();
1353
+ server.close();
1354
+ if (healthzServer.HealthzServer.isRunning()) {
1355
+ await healthzServer.HealthzServer.stop().catch(() => {
1356
+ });
1357
+ }
1358
+ } catch {
1359
+ }
1360
+ this.server = null;
1361
+ this.healthzAddress = null;
1362
+ this.connections.clear();
1363
+ reject(err);
1364
+ } else {
1365
+ logger.getLogger().error({
1366
+ err
1367
+ }, "Server runtime error");
1303
1368
  }
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(() => {
1315
- const onListening = /* @__PURE__ */ __name(async () => {
1316
- isListening = true;
1317
- this.logServerStartInfo();
1318
- await this.runHook("afterStart", server);
1319
- resolve(server);
1320
- }, "onListening");
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)));
1369
+ });
1370
+ this.setupConnectionTracking();
1371
+ Promise.resolve(this.runHook("onServerCreated", server)).then(() => {
1372
+ const onListening = /* @__PURE__ */ __name(async () => {
1373
+ try {
1374
+ this.logServerStartInfo();
1375
+ await this.runHook("afterStart", server);
1376
+ if (this.isHealthzServerEnabled()) {
1377
+ healthzServer.HealthzServer.markStartupComplete();
1378
+ healthzServer.HealthzServer.setReady(true);
1379
+ }
1380
+ isListening = true;
1381
+ resolve(server);
1382
+ } catch (err) {
1383
+ const error = err instanceof Error ? err : new Error(String(err));
1384
+ logger.getLogger().error({
1385
+ err: error
1386
+ }, "Server startup failed");
1387
+ if (healthzServer.HealthzServer.isRunning()) {
1388
+ await healthzServer.HealthzServer.stop().catch(() => {
1389
+ });
1390
+ }
1391
+ try {
1392
+ server.removeAllListeners();
1393
+ server.close();
1394
+ } catch {
1395
+ }
1396
+ this.server = null;
1397
+ this.healthzAddress = null;
1398
+ this.connections.clear();
1399
+ reject(error);
1400
+ }
1401
+ }, "onListening");
1402
+ const listenArgs = [
1403
+ this.config.port,
1404
+ this.config.host,
1405
+ onListening
1406
+ ];
1407
+ server.listen(...listenArgs);
1408
+ }).catch((err) => {
1409
+ server.emit("error", err instanceof Error ? err : new Error(String(err)));
1410
+ });
1329
1411
  });
1330
- });
1412
+ } catch (err) {
1413
+ if (healthzServer.HealthzServer.isRunning()) {
1414
+ await healthzServer.HealthzServer.stop().catch(() => {
1415
+ });
1416
+ }
1417
+ this.healthzAddress = null;
1418
+ this.server = null;
1419
+ this.connections.clear();
1420
+ throw err;
1421
+ }
1331
1422
  }
1332
1423
  /**
1333
1424
  * Create HTTP or HTTPS server instance (without listening).
@@ -1367,8 +1458,8 @@ var ExpressServer = class {
1367
1458
  const host = this.formatHostForUrl(this.config.host || "localhost");
1368
1459
  const url = `${protocol}://${host}:${port}`;
1369
1460
  logger.getLogger().info(`Server running on ${url}`);
1370
- if (this.config.healthCheck && this.config.healthCheck?.path) {
1371
- logger.getLogger().info(`Health check available at ${url}${this.normalizePath(this.config.healthCheck.path, this.config.healthCheck.withGlobalPrefix)}`);
1461
+ if (this.healthzAddress) {
1462
+ logger.getLogger().info(`Healthz probe server running on http://${this.healthzAddress.address}:${this.healthzAddress.port}`);
1372
1463
  }
1373
1464
  if (this.config.openApi?.enable) {
1374
1465
  logger.getLogger().info(`API docs available at ${url}${this.normalizePath(this.config.openApi.mountPath, this.config.openApi.withGlobalPrefix)}`);
@@ -1391,7 +1482,7 @@ var ExpressServer = class {
1391
1482
  * - Monitoring systems are notified
1392
1483
  */
1393
1484
  async stop(force = false) {
1394
- if (!this.server) {
1485
+ if (!this.server && !healthzServer.HealthzServer.isRunning()) {
1395
1486
  logger.getLogger().warn("Stop called but server is not running");
1396
1487
  return;
1397
1488
  }
@@ -1400,10 +1491,26 @@ var ExpressServer = class {
1400
1491
  return;
1401
1492
  }
1402
1493
  this.isShuttingDown = true;
1403
- await this.runHook("beforeStop", this.server);
1494
+ if (healthzServer.HealthzServer.isRunning()) {
1495
+ healthzServer.HealthzServer.setReady(false);
1496
+ }
1497
+ if (this.server) {
1498
+ await this.runHook("beforeStop", this.server);
1499
+ }
1404
1500
  try {
1405
- await this.gracefulShutdown(force);
1501
+ const shutdownDelay = this.getHealthzShutdownDelay();
1502
+ if (shutdownDelay > 0 && !force && healthzServer.HealthzServer.isRunning()) {
1503
+ logger.getLogger().info(`Waiting ${shutdownDelay}ms for load balancer to drain traffic...`);
1504
+ await new Promise((resolve) => setTimeout(resolve, shutdownDelay));
1505
+ }
1506
+ if (this.server) {
1507
+ await this.gracefulShutdown(force);
1508
+ }
1406
1509
  } finally {
1510
+ if (healthzServer.HealthzServer.isRunning()) {
1511
+ await healthzServer.HealthzServer.stop();
1512
+ this.healthzAddress = null;
1513
+ }
1407
1514
  this.isShuttingDown = false;
1408
1515
  }
1409
1516
  }
@@ -1492,7 +1599,7 @@ var ExpressServer = class {
1492
1599
  logger.getLogger().info(`Received ${signal}, initiating graceful shutdown...`);
1493
1600
  try {
1494
1601
  this.disableGracefulShutdown();
1495
- await this.stop(true);
1602
+ await this.stop(false);
1496
1603
  process.exit(0);
1497
1604
  } catch (err) {
1498
1605
  logger.getLogger().fatal({
package/server/index.d.ts CHANGED
@@ -26,6 +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
30
 
30
31
  /**
31
32
  * Map of critical dependencies to their error messages.
@@ -76,11 +77,12 @@ declare class ExpressServer {
76
77
  private gracefulShutdownRegistered;
77
78
  /** Map of registered signal listeners for clean teardown */
78
79
  private readonly signalListeners;
79
- /**
80
- * Collection of registered health check functions.
81
- * These are executed when the health check endpoint is accessed.
82
- */
83
- private readonly healthChecks;
80
+ /** Running address info for the Healthz probe server */
81
+ private healthzAddress?;
82
+ /** Named checks queued for Healthz liveness probe */
83
+ private readonly healthzChecks;
84
+ /** Named checks queued for Healthz readiness probe */
85
+ private readonly healthzReadinessChecks;
84
86
  /** Promise that resolves when initialization (middleware + routes) is complete */
85
87
  private readonly initPromise;
86
88
  /** In-flight start promise to protect against concurrent start() calls */
@@ -197,18 +199,18 @@ declare class ExpressServer {
197
199
  */
198
200
  protected setupRoutes(): Promise<void>;
199
201
  /**
200
- * Execute health check and return response.
202
+ * Whether the Healthz probe server is enabled.
201
203
  */
202
- private handleHealthCheckRequest;
204
+ isHealthzServerEnabled(): boolean;
203
205
  /**
204
- * Execute all registered health checks and return results.
206
+ * Get the graceful shutdown delay in milliseconds configured for HealthzServer.
205
207
  */
206
- private executeHealthChecks;
208
+ private getHealthzShutdownDelay;
207
209
  /**
208
- * Register a new health check function for monitoring service dependencies.
210
+ * Register a new health check function for monitoring service dependencies on the Healthz probe server.
209
211
  *
210
- * Health checks are executed when the health endpoint is accessed and
211
- * help determine if the service is ready to handle requests.
212
+ * By default, external dependencies (DB, Redis, etc.) are registered as `readiness` checks.
213
+ * Can also be registered as `liveness` or `both`.
212
214
  *
213
215
  * Examples:
214
216
  * - Database connectivity
@@ -217,17 +219,49 @@ declare class ExpressServer {
217
219
  * - Memory/CPU usage checks
218
220
  *
219
221
  * @param name Unique identifier for the check (used in detailed responses)
220
- * @param check Function returning boolean or Promise<boolean> indicating health
222
+ * @param check Function returning boolean or Promise<boolean> indicating health (supports optional AbortSignal)
223
+ * @param options Target probe type ('readiness' | 'liveness' | 'both') or options object
224
+ * @returns This instance for method chaining
225
+ */
226
+ registerHealthCheck(name: string, check: (signal?: AbortSignal) => Promise<boolean> | boolean, options?: 'readiness' | 'liveness' | 'both' | {
227
+ type?: 'readiness' | 'liveness' | 'both';
228
+ }): this;
229
+ /**
230
+ * Mark the service as ready / not-ready for traffic on the Healthz probe server.
231
+ *
232
+ * @param ready Whether the service is ready to receive traffic
233
+ * @returns This instance for method chaining
234
+ */
235
+ setReady(ready: boolean): this;
236
+ /**
237
+ * Whether the service is currently marked as ready for traffic on the Healthz probe server.
238
+ */
239
+ isReady(): boolean;
240
+ /**
241
+ * Mark application startup as completed on the Healthz probe server (switches `/startupz` to 200).
242
+ *
221
243
  * @returns This instance for method chaining
222
244
  */
223
- registerHealthCheck(name: string, check: () => Promise<boolean> | boolean): this;
245
+ markStartupComplete(): this;
224
246
  /**
225
- * Run registered health checks and return whether the service is ready.
226
- * Useful for readiness probes in deployment tooling.
247
+ * Whether application startup has completed on the Healthz probe server.
248
+ */
249
+ isStartupComplete(): boolean;
250
+ /**
251
+ * Get the running HealthzServer instance (if started).
252
+ */
253
+ getHealthzServer(): HealthzServer | undefined;
254
+ /**
255
+ * Get the address info of the running HealthzServer (if started).
256
+ */
257
+ getHealthzAddress(): HealthzAddressInfo | null | undefined;
258
+ /**
259
+ * Returns whether the service is currently marked as ready for traffic on the Healthz probe server.
260
+ * Useful for readiness checks in deployment tooling.
227
261
  *
228
- * @returns Promise resolving to `true` when all checks pass, otherwise `false`.
262
+ * @returns `true` when ready, otherwise `false`.
229
263
  */
230
- ready(): Promise<boolean>;
264
+ ready(): boolean;
231
265
  /**
232
266
  * Get the underlying Express application instance.
233
267
  * Use this for advanced Express features not exposed by this wrapper.
@@ -715,32 +749,36 @@ declare class ServerConfigBuilder {
715
749
  */
716
750
  disableRequestLogging(): this;
717
751
  /**
718
- * Configures server health check endpoint.
752
+ * Configures the dedicated Healthz probe HTTP server for Kubernetes.
719
753
  *
720
- * @param opts - Health check configuration options
754
+ * @param opts - Healthz server configuration options or boolean toggle
721
755
  * @returns The builder instance for chaining
722
- * @default - { path: '/healthz', detailed: true, withGlobalPrefix: false }
723
756
  *
724
757
  * @example
725
758
  * ```typescript
726
- * builder.withHealthCheck({
727
- * path: '/health',
728
- * detailed: true
759
+ * builder.withHealthzServer({
760
+ * port: 8282,
761
+ * shutdownDelayMs: 5000,
762
+ * readinessChecks: [
763
+ * { name: 'db', check: () => checkDb() }
764
+ * ]
729
765
  * })
730
766
  * ```
731
767
  */
732
- withHealthCheck(opts: Partial<NonNullable<CatbeeServerConfig['healthCheck']>>): this;
768
+ withHealthzServer(opts: NonNullable<CatbeeServerConfig['healthzServer']>): this;
733
769
  /**
734
- * Enables health check endpoint with default or custom settings
735
- * @param opts - Optional health check configuration
770
+ * Enables the dedicated Healthz probe HTTP server.
771
+ *
772
+ * @param opts - Optional Healthz server configuration options
736
773
  * @returns The builder instance for chaining
774
+ */
775
+ enableHealthzServer(opts?: Partial<CatbeeHealthzServerConfig>): this;
776
+ /**
777
+ * Disables the dedicated Healthz probe HTTP server.
737
778
  *
738
- * @example
739
- * ```typescript
740
- * builder.disableHealthCheck()
741
- * ```
779
+ * @returns The builder instance for chaining
742
780
  */
743
- disableHealthCheck(): this;
781
+ disableHealthzServer(): this;
744
782
  /**
745
783
  * Configures OpenAPI/Swagger documentation for the API.
746
784
  *