@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/README.md CHANGED
@@ -55,7 +55,7 @@ const config = new ServerConfigBuilder()
55
55
  .withCors({ origin: '*' })
56
56
  .enableRateLimit({ max: 50, windowMs: 60000 })
57
57
  .enableRequestLogging({ ignorePaths: ['/healthz', '/metrics'] })
58
- .withHealthCheck({ path: '/health', detailed: true })
58
+ .enableHealthzServer({ port: 8282 })
59
59
  .enableOpenApi('./openapi.yaml', { mountPath: '/docs' })
60
60
  .withGlobalHeaders({ 'X-Powered-By': 'Catbee' })
61
61
  .withGlobalPrefix('/api')
package/config/index.cjs CHANGED
@@ -27,6 +27,7 @@
27
27
  var env = require('@catbee/utils/env');
28
28
  var id = require('@catbee/utils/id');
29
29
  var object = require('@catbee/utils/object');
30
+ var healthzServer = require('@catbee/utils/healthz-server');
30
31
 
31
32
  var __defProp = Object.defineProperty;
32
33
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
@@ -78,10 +79,9 @@ var defaultServerConfig = {
78
79
  verbose: env.Env.getBoolean("SERVER_OPENAPI_VERBOSE", false),
79
80
  withGlobalPrefix: env.Env.getBoolean("SERVER_OPENAPI_WITH_GLOBAL_PREFIX", false)
80
81
  },
81
- healthCheck: {
82
- path: env.Env.get("SERVER_HEALTH_CHECK_PATH", "/healthz"),
83
- detailed: env.Env.getBoolean("SERVER_HEALTH_CHECK_DETAILED_OUTPUT", true),
84
- withGlobalPrefix: env.Env.getBoolean("SERVER_HEALTH_CHECK_WITH_GLOBAL_PREFIX", false)
82
+ healthzServer: {
83
+ enable: env.Env.getBoolean("SERVER_HEALTHZ_ENABLE", env.Env.getBoolean("HEALTHZ_ENABLE", false)),
84
+ ...healthzServer.getDefaultHealthzConfig()
85
85
  },
86
86
  requestTimeout: env.Env.getDuration("SERVER_REQUEST_TIMEOUT_MS", 0),
87
87
  responseTime: {
@@ -98,8 +98,7 @@ var defaultServerConfig = {
98
98
  enable: env.Env.getBoolean("SERVER_SERVICE_VERSION_ENABLE", false),
99
99
  headerName: env.Env.get("SERVER_SERVICE_VERSION_HEADER_NAME", "x-service-version"),
100
100
  version: env.Env.get("${npm_package_version}", "0.0.0")
101
- },
102
- skipHealthzChecksValidation: env.Env.getBoolean("SERVER_SKIP_HEALTHZ_CHECKS_VALIDATION", false)
101
+ }
103
102
  };
104
103
  var defaultCatbeeConfig = {
105
104
  logger: {
package/config/index.mjs CHANGED
@@ -25,6 +25,7 @@
25
25
  import { Env } from '@catbee/utils/env';
26
26
  import { uuid } from '@catbee/utils/id';
27
27
  import { deepClone, deepObjMerge } from '@catbee/utils/object';
28
+ import { getDefaultHealthzConfig } from '@catbee/utils/healthz-server';
28
29
 
29
30
  var __defProp = Object.defineProperty;
30
31
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
@@ -76,10 +77,9 @@ var defaultServerConfig = {
76
77
  verbose: Env.getBoolean("SERVER_OPENAPI_VERBOSE", false),
77
78
  withGlobalPrefix: Env.getBoolean("SERVER_OPENAPI_WITH_GLOBAL_PREFIX", false)
78
79
  },
79
- healthCheck: {
80
- path: Env.get("SERVER_HEALTH_CHECK_PATH", "/healthz"),
81
- detailed: Env.getBoolean("SERVER_HEALTH_CHECK_DETAILED_OUTPUT", true),
82
- withGlobalPrefix: Env.getBoolean("SERVER_HEALTH_CHECK_WITH_GLOBAL_PREFIX", false)
80
+ healthzServer: {
81
+ enable: Env.getBoolean("SERVER_HEALTHZ_ENABLE", Env.getBoolean("HEALTHZ_ENABLE", false)),
82
+ ...getDefaultHealthzConfig()
83
83
  },
84
84
  requestTimeout: Env.getDuration("SERVER_REQUEST_TIMEOUT_MS", 0),
85
85
  responseTime: {
@@ -96,8 +96,7 @@ var defaultServerConfig = {
96
96
  enable: Env.getBoolean("SERVER_SERVICE_VERSION_ENABLE", false),
97
97
  headerName: Env.get("SERVER_SERVICE_VERSION_HEADER_NAME", "x-service-version"),
98
98
  version: Env.get("${npm_package_version}", "0.0.0")
99
- },
100
- skipHealthzChecksValidation: Env.getBoolean("SERVER_SKIP_HEALTHZ_CHECKS_VALIDATION", false)
99
+ }
101
100
  };
102
101
  var defaultCatbeeConfig = {
103
102
  logger: {
@@ -36,10 +36,11 @@ function getDefaultHealthzConfig() {
36
36
  healthzPath: env.Env.get("HEALTHZ_PATH", "") || env.Env.get("SERVER_HEALTHZ_PATH", "") || env.Env.get("SERVER_HEALTH_CHECK_PATH", "/healthz"),
37
37
  readyzPath: env.Env.get("HEALTHZ_READYZ_PATH", "") || env.Env.get("SERVER_READYZ_PATH", "/readyz"),
38
38
  startupzPath: env.Env.get("HEALTHZ_STARTUPZ_PATH", "") || env.Env.get("SERVER_STARTUPZ_PATH", "/startupz"),
39
- detailed: env.Env.getBoolean("HEALTHZ_DETAILED", env.Env.getBoolean("SERVER_HEALTH_CHECK_DETAILED_OUTPUT", true)),
39
+ detailed: env.Env.getBoolean("HEALTHZ_DETAILED", env.Env.getBoolean("SERVER_HEALTHZ_DETAILED", env.Env.getBoolean("SERVER_HEALTH_CHECK_DETAILED_OUTPUT", true))),
40
40
  checks: [],
41
- checkTimeoutMs: env.Env.getDuration("HEALTHZ_CHECK_TIMEOUT_MS", 5e3),
42
- shutdownDelayMs: env.Env.getDuration("HEALTHZ_SHUTDOWN_DELAY_MS", 5e3)
41
+ checkTimeoutMs: env.Env.getDuration("HEALTHZ_CHECK_TIMEOUT_MS", env.Env.getDuration("SERVER_HEALTHZ_CHECK_TIMEOUT_MS", 5e3)),
42
+ shutdownDelayMs: env.Env.getDuration("HEALTHZ_SHUTDOWN_DELAY_MS", env.Env.getDuration("SERVER_HEALTHZ_SHUTDOWN_DELAY_MS", 5e3)),
43
+ handleSignals: true
43
44
  };
44
45
  }
45
46
  __name(getDefaultHealthzConfig, "getDefaultHealthzConfig");
@@ -66,8 +67,10 @@ var HealthzServer = class _HealthzServer {
66
67
  server;
67
68
  config;
68
69
  startedAt = Date.now();
69
- /** Whether the health server has successfully started listening (for `/startupz`) */
70
- started = false;
70
+ /** Whether the health HTTP server is currently running and listening on its port */
71
+ running = false;
72
+ /** Whether application startup has completed (for `/startupz`) */
73
+ startupComplete = false;
71
74
  /** Whether the service is ready to receive traffic (for `/readyz`) */
72
75
  ready = false;
73
76
  shuttingDown = false;
@@ -76,8 +79,16 @@ var HealthzServer = class _HealthzServer {
76
79
  constructor(config) {
77
80
  this.config = config;
78
81
  this.server = http.createServer((req, res) => this.handleRequest(req, res));
79
- process.once("SIGTERM", this.onSigterm);
80
- process.once("SIGINT", this.onSigint);
82
+ if (this.config.handleSignals !== false) {
83
+ process.once("SIGTERM", this.onSigterm);
84
+ process.once("SIGINT", this.onSigint);
85
+ }
86
+ }
87
+ /**
88
+ * Returns default Healthz server configuration resolved from environment variables.
89
+ */
90
+ static getDefaultConfig() {
91
+ return getDefaultHealthzConfig();
81
92
  }
82
93
  /**
83
94
  * Boot the health-check server.
@@ -100,7 +111,7 @@ var HealthzServer = class _HealthzServer {
100
111
  port: config.port
101
112
  }, () => {
102
113
  instance.server.off("error", onError);
103
- instance.started = true;
114
+ instance.running = true;
104
115
  const addr = instance.server.address();
105
116
  if (!addr || typeof addr === "string") {
106
117
  instance.cleanup();
@@ -116,9 +127,19 @@ var HealthzServer = class _HealthzServer {
116
127
  });
117
128
  });
118
129
  }
119
- /** Whether the health-check server is currently running and started. */
120
- static isStarted() {
121
- return _global[SINGLETON_KEY]?.started ?? false;
130
+ /** Whether the Healthz HTTP probe server is currently running and listening on its port. */
131
+ static isRunning() {
132
+ return _global[SINGLETON_KEY]?.running ?? false;
133
+ }
134
+ /** Mark application startup as completed (switches `/startupz` to 200). */
135
+ static markStartupComplete() {
136
+ const instance = _global[SINGLETON_KEY];
137
+ if (!instance) return;
138
+ instance.startupComplete = true;
139
+ }
140
+ /** Whether application startup has completed (for `/startupz`). */
141
+ static isStartupComplete() {
142
+ return _global[SINGLETON_KEY]?.startupComplete ?? false;
122
143
  }
123
144
  /** Mark the service as ready / not-ready for traffic. */
124
145
  static setReady(ready) {
@@ -148,6 +169,34 @@ var HealthzServer = class _HealthzServer {
148
169
  static getInstance() {
149
170
  return _global[SINGLETON_KEY];
150
171
  }
172
+ /**
173
+ * Register a named check on this HealthzServer instance dynamically.
174
+ *
175
+ * @param check - The named check to register
176
+ * @param type - Which probe to attach this check to ('readiness', 'liveness', or 'both')
177
+ * @returns This instance for chaining
178
+ */
179
+ registerCheck(check, type = "readiness") {
180
+ if (type === "liveness" || type === "both") {
181
+ this.config.checks.push(check);
182
+ }
183
+ if (type === "readiness" || type === "both") {
184
+ this.config.readinessChecks ??= [
185
+ ...this.config.checks
186
+ ];
187
+ this.config.readinessChecks.push(check);
188
+ }
189
+ return this;
190
+ }
191
+ /**
192
+ * Register a named check on the active singleton HealthzServer instance (if started).
193
+ *
194
+ * @param check - The named check to register
195
+ * @param type - Which probe to attach this check to ('readiness', 'liveness', or 'both')
196
+ */
197
+ static registerCheck(check, type = "readiness") {
198
+ _global[SINGLETON_KEY]?.registerCheck(check, type);
199
+ }
151
200
  async handleRequest(req, res) {
152
201
  const isHead = req.method === "HEAD";
153
202
  if (req.method !== "GET" && !isHead) {
@@ -251,7 +300,7 @@ var HealthzServer = class _HealthzServer {
251
300
  }
252
301
  /** `/startupz` — Startup probe */
253
302
  handleStartup(res, isHead = false) {
254
- if (this.started) {
303
+ if (this.startupComplete) {
255
304
  this.sendProbe(res, 200, "ok", [], isHead);
256
305
  } else {
257
306
  this.sendProbe(res, 503, "unhealthy", [
@@ -259,7 +308,7 @@ var HealthzServer = class _HealthzServer {
259
308
  name: "startup",
260
309
  ok: false,
261
310
  durationMs: 0,
262
- error: "Service has not started yet"
311
+ error: "Application startup not complete"
263
312
  }
264
313
  ], isHead);
265
314
  }
@@ -346,7 +395,8 @@ var HealthzServer = class _HealthzServer {
346
395
  process.off("SIGTERM", this.onSigterm);
347
396
  process.off("SIGINT", this.onSigint);
348
397
  this.ready = false;
349
- this.started = false;
398
+ this.startupComplete = false;
399
+ this.running = false;
350
400
  this.shuttingDown = false;
351
401
  }
352
402
  };
@@ -169,6 +169,13 @@ interface CatbeeHealthzServerConfig {
169
169
  * - **env**: `HEALTHZ_SHUTDOWN_DELAY_MS`
170
170
  */
171
171
  shutdownDelayMs?: number;
172
+ /**
173
+ * Whether the HealthzServer should register its own SIGTERM/SIGINT process signal listeners.
174
+ * When managed by an orchestrating server (such as Catbee ExpressServer), set this to `false`
175
+ * to prevent signal listener conflicts and allow coordinated teardown.
176
+ * - **default**: `true`
177
+ */
178
+ handleSignals?: boolean;
172
179
  }
173
180
  /**
174
181
  * Address information returned after the server starts listening.
@@ -179,6 +186,29 @@ interface HealthzAddressInfo {
179
186
  port: number;
180
187
  }
181
188
 
189
+ /**
190
+ * Resolved configuration with all defaults applied.
191
+ * Internal-only — consumers interact with `CatbeeHealthzServerConfig`.
192
+ *
193
+ * `readinessChecks` is kept as `NamedCheck[] | undefined` so that
194
+ * "omitted" (-> fall back to `checks`) is distinguishable from
195
+ * "explicitly empty" (-> run nothing).
196
+ */
197
+ interface ResolvedHealthzConfig extends Required<Omit<CatbeeHealthzServerConfig, 'onHealthCheck' | 'onReadinessCheck' | 'readinessChecks'>> {
198
+ onHealthCheck?: CatbeeHealthzServerConfig['onHealthCheck'];
199
+ onReadinessCheck?: CatbeeHealthzServerConfig['onReadinessCheck'];
200
+ readinessChecks?: CatbeeHealthzServerConfig['readinessChecks'];
201
+ }
202
+ /**
203
+ * Loads default Healthz server configuration from environment variables.
204
+ */
205
+ declare function getDefaultHealthzConfig(): ResolvedHealthzConfig;
206
+ /**
207
+ * Merge user-supplied configuration with environment-resolved defaults.
208
+ * Undefined user values do not override resolved defaults.
209
+ */
210
+ declare function resolveConfig(userConfig?: CatbeeHealthzServerConfig): ResolvedHealthzConfig;
211
+
182
212
  /**
183
213
  * Standalone HTTP health-check server designed for Kubernetes probes
184
214
  * and microservice orchestration.
@@ -189,7 +219,7 @@ interface HealthzAddressInfo {
189
219
  * |-----------|-------------|------------------|-----------|
190
220
  * | Liveness | `/healthz` | `livenessProbe` | Runs configured checks; 200 = alive, 503 = unhealthy |
191
221
  * | Readiness | `/readyz` | `readinessProbe` | Checks readiness flag + readiness checks; 503 while not ready |
192
- * | Startup | `/startupz` | `startupProbe` | 200 once the server has successfully started listening; 503 before that |
222
+ * | Startup | `/startupz` | `startupProbe` | 200 once application startup completes (`markStartupComplete()`); 503 while booting |
193
223
  *
194
224
  * ### Kubernetes Probe Best Practices:
195
225
  * - **Liveness (`/healthz`)**: Keep these checks extremely lightweight (e.g. process is responsive,
@@ -199,7 +229,8 @@ interface HealthzAddressInfo {
199
229
  * - **Readiness (`/readyz`)**: Place external dependency checks (DB, Redis, downstream APIs) here via
200
230
  * `readinessChecks`. If a dependency fails, Kubernetes temporarily pulls the pod from service endpoints
201
231
  * without restarting the container, allowing it to recover gracefully.
202
- * - **Startup (`/startupz`)**: Verifies the health server process has started listening.
232
+ * - **Startup (`/startupz`)**: Verifies the application has completed its startup sequence
233
+ * (signaled via `markStartupComplete()`). Protects slow-starting applications from premature liveness kills.
203
234
  *
204
235
  * @example
205
236
  * ```ts
@@ -207,11 +238,9 @@ interface HealthzAddressInfo {
207
238
  *
208
239
  * const addr = await HealthzServer.start({
209
240
  * port: 8282,
210
- * // Keep liveness lightweight:
211
241
  * checks: [
212
242
  * { name: 'process', check: () => true },
213
243
  * ],
214
- * // Place dependency checks on readiness:
215
244
  * readinessChecks: [
216
245
  * { name: 'database', check: (signal) => db.ping({ signal }) },
217
246
  * { name: 'redis', check: (signal) => redis.ping({ signal }) },
@@ -219,6 +248,9 @@ interface HealthzAddressInfo {
219
248
  * shutdownDelayMs: 10_000,
220
249
  * });
221
250
  *
251
+ * // Signal application startup complete (switches /startupz to 200):
252
+ * HealthzServer.markStartupComplete();
253
+ *
222
254
  * // Signal readiness after all background services and migrations are ready:
223
255
  * HealthzServer.setReady(true);
224
256
  * ```
@@ -227,21 +259,31 @@ declare class HealthzServer {
227
259
  private readonly server;
228
260
  private readonly config;
229
261
  private readonly startedAt;
230
- /** Whether the health server has successfully started listening (for `/startupz`) */
231
- private started;
262
+ /** Whether the health HTTP server is currently running and listening on its port */
263
+ private running;
264
+ /** Whether application startup has completed (for `/startupz`) */
265
+ private startupComplete;
232
266
  /** Whether the service is ready to receive traffic (for `/readyz`) */
233
267
  private ready;
234
268
  private shuttingDown;
235
269
  private readonly onSigterm;
236
270
  private readonly onSigint;
237
271
  private constructor();
272
+ /**
273
+ * Returns default Healthz server configuration resolved from environment variables.
274
+ */
275
+ static getDefaultConfig(): ResolvedHealthzConfig;
238
276
  /**
239
277
  * Boot the health-check server.
240
278
  * Returns `null` if a server is already running in this process.
241
279
  */
242
280
  static start(opts?: CatbeeHealthzServerConfig): Promise<HealthzAddressInfo | null>;
243
- /** Whether the health-check server is currently running and started. */
244
- static isStarted(): boolean;
281
+ /** Whether the Healthz HTTP probe server is currently running and listening on its port. */
282
+ static isRunning(): boolean;
283
+ /** Mark application startup as completed (switches `/startupz` to 200). */
284
+ static markStartupComplete(): void;
285
+ /** Whether application startup has completed (for `/startupz`). */
286
+ static isStartupComplete(): boolean;
245
287
  /** Mark the service as ready / not-ready for traffic. */
246
288
  static setReady(ready: boolean): void;
247
289
  /** Whether the service is currently marked as ready. */
@@ -249,6 +291,21 @@ declare class HealthzServer {
249
291
  /** Gracefully stop the health-check server. */
250
292
  static stop(): Promise<void>;
251
293
  static getInstance(): HealthzServer | undefined;
294
+ /**
295
+ * Register a named check on this HealthzServer instance dynamically.
296
+ *
297
+ * @param check - The named check to register
298
+ * @param type - Which probe to attach this check to ('readiness', 'liveness', or 'both')
299
+ * @returns This instance for chaining
300
+ */
301
+ registerCheck(check: NamedCheck, type?: 'liveness' | 'readiness' | 'both'): this;
302
+ /**
303
+ * Register a named check on the active singleton HealthzServer instance (if started).
304
+ *
305
+ * @param check - The named check to register
306
+ * @param type - Which probe to attach this check to ('readiness', 'liveness', or 'both')
307
+ */
308
+ static registerCheck(check: NamedCheck, type?: 'liveness' | 'readiness' | 'both'): void;
252
309
  private handleRequest;
253
310
  /** `/healthz` — Liveness probe */
254
311
  private handleLiveness;
@@ -264,28 +321,5 @@ declare class HealthzServer {
264
321
  private cleanup;
265
322
  }
266
323
 
267
- /**
268
- * Resolved configuration with all defaults applied.
269
- * Internal-only — consumers interact with `CatbeeHealthzServerConfig`.
270
- *
271
- * `readinessChecks` is kept as `NamedCheck[] | undefined` so that
272
- * "omitted" (-> fall back to `checks`) is distinguishable from
273
- * "explicitly empty" (-> run nothing).
274
- */
275
- interface ResolvedHealthzConfig extends Required<Omit<CatbeeHealthzServerConfig, 'onHealthCheck' | 'onReadinessCheck' | 'readinessChecks'>> {
276
- onHealthCheck?: CatbeeHealthzServerConfig['onHealthCheck'];
277
- onReadinessCheck?: CatbeeHealthzServerConfig['onReadinessCheck'];
278
- readinessChecks?: CatbeeHealthzServerConfig['readinessChecks'];
279
- }
280
- /**
281
- * Loads default Healthz server configuration from environment variables.
282
- */
283
- declare function getDefaultHealthzConfig(): ResolvedHealthzConfig;
284
- /**
285
- * Merge user-supplied configuration with environment-resolved defaults.
286
- * Undefined user values do not override resolved defaults.
287
- */
288
- declare function resolveConfig(userConfig?: CatbeeHealthzServerConfig): ResolvedHealthzConfig;
289
-
290
324
  export { HealthzServer, getDefaultHealthzConfig, resolveConfig };
291
325
  export type { CatbeeHealthzServerConfig, CheckResult, HealthCheckFn, HealthzAddressInfo, NamedCheck, ProbeResponse, ProbeStatus, ReadinessCheckFn, ResolvedHealthzConfig };
@@ -34,10 +34,11 @@ function getDefaultHealthzConfig() {
34
34
  healthzPath: Env.get("HEALTHZ_PATH", "") || Env.get("SERVER_HEALTHZ_PATH", "") || Env.get("SERVER_HEALTH_CHECK_PATH", "/healthz"),
35
35
  readyzPath: Env.get("HEALTHZ_READYZ_PATH", "") || Env.get("SERVER_READYZ_PATH", "/readyz"),
36
36
  startupzPath: Env.get("HEALTHZ_STARTUPZ_PATH", "") || Env.get("SERVER_STARTUPZ_PATH", "/startupz"),
37
- detailed: Env.getBoolean("HEALTHZ_DETAILED", Env.getBoolean("SERVER_HEALTH_CHECK_DETAILED_OUTPUT", true)),
37
+ detailed: Env.getBoolean("HEALTHZ_DETAILED", Env.getBoolean("SERVER_HEALTHZ_DETAILED", Env.getBoolean("SERVER_HEALTH_CHECK_DETAILED_OUTPUT", true))),
38
38
  checks: [],
39
- checkTimeoutMs: Env.getDuration("HEALTHZ_CHECK_TIMEOUT_MS", 5e3),
40
- shutdownDelayMs: Env.getDuration("HEALTHZ_SHUTDOWN_DELAY_MS", 5e3)
39
+ checkTimeoutMs: Env.getDuration("HEALTHZ_CHECK_TIMEOUT_MS", Env.getDuration("SERVER_HEALTHZ_CHECK_TIMEOUT_MS", 5e3)),
40
+ shutdownDelayMs: Env.getDuration("HEALTHZ_SHUTDOWN_DELAY_MS", Env.getDuration("SERVER_HEALTHZ_SHUTDOWN_DELAY_MS", 5e3)),
41
+ handleSignals: true
41
42
  };
42
43
  }
43
44
  __name(getDefaultHealthzConfig, "getDefaultHealthzConfig");
@@ -64,8 +65,10 @@ var HealthzServer = class _HealthzServer {
64
65
  server;
65
66
  config;
66
67
  startedAt = Date.now();
67
- /** Whether the health server has successfully started listening (for `/startupz`) */
68
- started = false;
68
+ /** Whether the health HTTP server is currently running and listening on its port */
69
+ running = false;
70
+ /** Whether application startup has completed (for `/startupz`) */
71
+ startupComplete = false;
69
72
  /** Whether the service is ready to receive traffic (for `/readyz`) */
70
73
  ready = false;
71
74
  shuttingDown = false;
@@ -74,8 +77,16 @@ var HealthzServer = class _HealthzServer {
74
77
  constructor(config) {
75
78
  this.config = config;
76
79
  this.server = createServer((req, res) => this.handleRequest(req, res));
77
- process.once("SIGTERM", this.onSigterm);
78
- process.once("SIGINT", this.onSigint);
80
+ if (this.config.handleSignals !== false) {
81
+ process.once("SIGTERM", this.onSigterm);
82
+ process.once("SIGINT", this.onSigint);
83
+ }
84
+ }
85
+ /**
86
+ * Returns default Healthz server configuration resolved from environment variables.
87
+ */
88
+ static getDefaultConfig() {
89
+ return getDefaultHealthzConfig();
79
90
  }
80
91
  /**
81
92
  * Boot the health-check server.
@@ -98,7 +109,7 @@ var HealthzServer = class _HealthzServer {
98
109
  port: config.port
99
110
  }, () => {
100
111
  instance.server.off("error", onError);
101
- instance.started = true;
112
+ instance.running = true;
102
113
  const addr = instance.server.address();
103
114
  if (!addr || typeof addr === "string") {
104
115
  instance.cleanup();
@@ -114,9 +125,19 @@ var HealthzServer = class _HealthzServer {
114
125
  });
115
126
  });
116
127
  }
117
- /** Whether the health-check server is currently running and started. */
118
- static isStarted() {
119
- return _global[SINGLETON_KEY]?.started ?? false;
128
+ /** Whether the Healthz HTTP probe server is currently running and listening on its port. */
129
+ static isRunning() {
130
+ return _global[SINGLETON_KEY]?.running ?? false;
131
+ }
132
+ /** Mark application startup as completed (switches `/startupz` to 200). */
133
+ static markStartupComplete() {
134
+ const instance = _global[SINGLETON_KEY];
135
+ if (!instance) return;
136
+ instance.startupComplete = true;
137
+ }
138
+ /** Whether application startup has completed (for `/startupz`). */
139
+ static isStartupComplete() {
140
+ return _global[SINGLETON_KEY]?.startupComplete ?? false;
120
141
  }
121
142
  /** Mark the service as ready / not-ready for traffic. */
122
143
  static setReady(ready) {
@@ -146,6 +167,34 @@ var HealthzServer = class _HealthzServer {
146
167
  static getInstance() {
147
168
  return _global[SINGLETON_KEY];
148
169
  }
170
+ /**
171
+ * Register a named check on this HealthzServer instance dynamically.
172
+ *
173
+ * @param check - The named check to register
174
+ * @param type - Which probe to attach this check to ('readiness', 'liveness', or 'both')
175
+ * @returns This instance for chaining
176
+ */
177
+ registerCheck(check, type = "readiness") {
178
+ if (type === "liveness" || type === "both") {
179
+ this.config.checks.push(check);
180
+ }
181
+ if (type === "readiness" || type === "both") {
182
+ this.config.readinessChecks ??= [
183
+ ...this.config.checks
184
+ ];
185
+ this.config.readinessChecks.push(check);
186
+ }
187
+ return this;
188
+ }
189
+ /**
190
+ * Register a named check on the active singleton HealthzServer instance (if started).
191
+ *
192
+ * @param check - The named check to register
193
+ * @param type - Which probe to attach this check to ('readiness', 'liveness', or 'both')
194
+ */
195
+ static registerCheck(check, type = "readiness") {
196
+ _global[SINGLETON_KEY]?.registerCheck(check, type);
197
+ }
149
198
  async handleRequest(req, res) {
150
199
  const isHead = req.method === "HEAD";
151
200
  if (req.method !== "GET" && !isHead) {
@@ -249,7 +298,7 @@ var HealthzServer = class _HealthzServer {
249
298
  }
250
299
  /** `/startupz` — Startup probe */
251
300
  handleStartup(res, isHead = false) {
252
- if (this.started) {
301
+ if (this.startupComplete) {
253
302
  this.sendProbe(res, 200, "ok", [], isHead);
254
303
  } else {
255
304
  this.sendProbe(res, 503, "unhealthy", [
@@ -257,7 +306,7 @@ var HealthzServer = class _HealthzServer {
257
306
  name: "startup",
258
307
  ok: false,
259
308
  durationMs: 0,
260
- error: "Service has not started yet"
309
+ error: "Application startup not complete"
261
310
  }
262
311
  ], isHead);
263
312
  }
@@ -344,7 +393,8 @@ var HealthzServer = class _HealthzServer {
344
393
  process.off("SIGTERM", this.onSigterm);
345
394
  process.off("SIGINT", this.onSigint);
346
395
  this.ready = false;
347
- this.started = false;
396
+ this.startupComplete = false;
397
+ this.running = false;
348
398
  this.shuttingDown = false;
349
399
  }
350
400
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@catbee/utils",
3
- "version": "2.1.1",
3
+ "version": "2.2.1",
4
4
  "description": "A modular, production-grade utility toolkit for Node.js and TypeScript, designed for robust, scalable applications (including Express-based services). All utilities are tree-shakable and can be imported independently.",
5
5
  "publishConfig": {
6
6
  "access": "public"