@catbee/utils 2.1.1 → 2.2.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/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");
@@ -76,8 +77,16 @@ var HealthzServer = class _HealthzServer {
76
77
  constructor(config) {
77
78
  this.config = config;
78
79
  this.server = http.createServer((req, res) => this.handleRequest(req, res));
79
- process.once("SIGTERM", this.onSigterm);
80
- 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();
81
90
  }
82
91
  /**
83
92
  * Boot the health-check server.
@@ -148,6 +157,34 @@ var HealthzServer = class _HealthzServer {
148
157
  static getInstance() {
149
158
  return _global[SINGLETON_KEY];
150
159
  }
160
+ /**
161
+ * Register a named check on this HealthzServer instance dynamically.
162
+ *
163
+ * @param check - The named check to register
164
+ * @param type - Which probe to attach this check to ('readiness', 'liveness', or 'both')
165
+ * @returns This instance for chaining
166
+ */
167
+ registerCheck(check, type = "readiness") {
168
+ if (type === "liveness" || type === "both") {
169
+ this.config.checks.push(check);
170
+ }
171
+ if (type === "readiness" || type === "both") {
172
+ this.config.readinessChecks ??= [
173
+ ...this.config.checks
174
+ ];
175
+ this.config.readinessChecks.push(check);
176
+ }
177
+ return this;
178
+ }
179
+ /**
180
+ * Register a named check on the active singleton HealthzServer instance (if started).
181
+ *
182
+ * @param check - The named check to register
183
+ * @param type - Which probe to attach this check to ('readiness', 'liveness', or 'both')
184
+ */
185
+ static registerCheck(check, type = "readiness") {
186
+ _global[SINGLETON_KEY]?.registerCheck(check, type);
187
+ }
151
188
  async handleRequest(req, res) {
152
189
  const isHead = req.method === "HEAD";
153
190
  if (req.method !== "GET" && !isHead) {
@@ -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.
@@ -235,6 +265,10 @@ declare class HealthzServer {
235
265
  private readonly onSigterm;
236
266
  private readonly onSigint;
237
267
  private constructor();
268
+ /**
269
+ * Returns default Healthz server configuration resolved from environment variables.
270
+ */
271
+ static getDefaultConfig(): ResolvedHealthzConfig;
238
272
  /**
239
273
  * Boot the health-check server.
240
274
  * Returns `null` if a server is already running in this process.
@@ -249,6 +283,21 @@ declare class HealthzServer {
249
283
  /** Gracefully stop the health-check server. */
250
284
  static stop(): Promise<void>;
251
285
  static getInstance(): HealthzServer | undefined;
286
+ /**
287
+ * Register a named check on this HealthzServer instance dynamically.
288
+ *
289
+ * @param check - The named check to register
290
+ * @param type - Which probe to attach this check to ('readiness', 'liveness', or 'both')
291
+ * @returns This instance for chaining
292
+ */
293
+ registerCheck(check: NamedCheck, type?: 'liveness' | 'readiness' | 'both'): this;
294
+ /**
295
+ * Register a named check on the active singleton HealthzServer instance (if started).
296
+ *
297
+ * @param check - The named check to register
298
+ * @param type - Which probe to attach this check to ('readiness', 'liveness', or 'both')
299
+ */
300
+ static registerCheck(check: NamedCheck, type?: 'liveness' | 'readiness' | 'both'): void;
252
301
  private handleRequest;
253
302
  /** `/healthz` — Liveness probe */
254
303
  private handleLiveness;
@@ -264,28 +313,5 @@ declare class HealthzServer {
264
313
  private cleanup;
265
314
  }
266
315
 
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
316
  export { HealthzServer, getDefaultHealthzConfig, resolveConfig };
291
317
  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");
@@ -74,8 +75,16 @@ var HealthzServer = class _HealthzServer {
74
75
  constructor(config) {
75
76
  this.config = config;
76
77
  this.server = createServer((req, res) => this.handleRequest(req, res));
77
- process.once("SIGTERM", this.onSigterm);
78
- process.once("SIGINT", this.onSigint);
78
+ if (this.config.handleSignals !== false) {
79
+ process.once("SIGTERM", this.onSigterm);
80
+ process.once("SIGINT", this.onSigint);
81
+ }
82
+ }
83
+ /**
84
+ * Returns default Healthz server configuration resolved from environment variables.
85
+ */
86
+ static getDefaultConfig() {
87
+ return getDefaultHealthzConfig();
79
88
  }
80
89
  /**
81
90
  * Boot the health-check server.
@@ -146,6 +155,34 @@ var HealthzServer = class _HealthzServer {
146
155
  static getInstance() {
147
156
  return _global[SINGLETON_KEY];
148
157
  }
158
+ /**
159
+ * Register a named check on this HealthzServer instance dynamically.
160
+ *
161
+ * @param check - The named check to register
162
+ * @param type - Which probe to attach this check to ('readiness', 'liveness', or 'both')
163
+ * @returns This instance for chaining
164
+ */
165
+ registerCheck(check, type = "readiness") {
166
+ if (type === "liveness" || type === "both") {
167
+ this.config.checks.push(check);
168
+ }
169
+ if (type === "readiness" || type === "both") {
170
+ this.config.readinessChecks ??= [
171
+ ...this.config.checks
172
+ ];
173
+ this.config.readinessChecks.push(check);
174
+ }
175
+ return this;
176
+ }
177
+ /**
178
+ * Register a named check on the active singleton HealthzServer instance (if started).
179
+ *
180
+ * @param check - The named check to register
181
+ * @param type - Which probe to attach this check to ('readiness', 'liveness', or 'both')
182
+ */
183
+ static registerCheck(check, type = "readiness") {
184
+ _global[SINGLETON_KEY]?.registerCheck(check, type);
185
+ }
149
186
  async handleRequest(req, res) {
150
187
  const isHead = req.method === "HEAD";
151
188
  if (req.method !== "GET" && !isHead) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@catbee/utils",
3
- "version": "2.1.1",
3
+ "version": "2.2.0",
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"