@catbee/utils 2.2.1 → 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.
@@ -26,28 +26,42 @@
26
26
  * A health check function that can be synchronous or asynchronous.
27
27
  * Receives an AbortSignal that is triggered when `checkTimeoutMs` is exceeded.
28
28
  *
29
- * > **Cancellation Note**: Cancellation is cooperative. The check function must listen to
30
- * > `signal.aborted` or pass `signal` to underlying asynchronous APIs (e.g. database drivers, `fetch`).
31
- * > Synchronous CPU-bound loops or operations that ignore `signal` cannot be forcibly stopped by JavaScript.
29
+ * > **Cancellation & Timeout Semantics**:
30
+ * > When `checkTimeoutMs` expires, the HTTP probe response immediately fast-fails with 503
31
+ * > and aborts the supplied `AbortSignal`.
32
+ * >
33
+ * > However, runtime cancellation in JavaScript is **strictly cooperative**. The probe server cannot
34
+ * > preemptively interrupt executing JavaScript or force-cancel asynchronous work that does not listen
35
+ * > to the signal. If a check executes asynchronous tasks without passing `signal`
36
+ * > (e.g., `await db.query(...)` without `{ signal }`), that task will continue running in the background.
37
+ * > To prevent background resource leaks, always pass `signal` to underlying drivers/clients
38
+ * > (e.g. `fetch(url, { signal })`, database clients, HTTP clients) or check `signal?.aborted`.
32
39
  *
33
- * - Return `true` (or resolve to true) to signal healthy.
40
+ * - Return `true` (or resolve to true, or resolve void without error) to signal healthy.
34
41
  * - Return `false` (or resolve to false) to signal unhealthy.
35
42
  * - Throw an error (or reject) to signal unhealthy with an error message.
36
43
  */
37
- type HealthCheckFn = (signal?: AbortSignal) => boolean | Promise<boolean>;
44
+ type HealthCheckFn = (signal?: AbortSignal) => boolean | void | Promise<boolean | void>;
38
45
  /**
39
46
  * A readiness check function.
40
47
  * Receives an AbortSignal that is triggered when `checkTimeoutMs` is exceeded.
41
48
  *
42
- * > **Cancellation Note**: Cancellation is cooperative. The check function must listen to
43
- * > `signal.aborted` or pass `signal` to underlying asynchronous APIs (e.g. database drivers, `fetch`).
44
- * > Synchronous CPU-bound loops or operations that ignore `signal` cannot be forcibly stopped by JavaScript.
49
+ * > **Cancellation & Timeout Semantics**:
50
+ * > When `checkTimeoutMs` expires, the HTTP probe response immediately fast-fails with 503
51
+ * > and aborts the supplied `AbortSignal`.
52
+ * >
53
+ * > However, runtime cancellation in JavaScript is **strictly cooperative**. The probe server cannot
54
+ * > preemptively interrupt executing JavaScript or force-cancel asynchronous work that does not listen
55
+ * > to the signal. If a check executes asynchronous tasks without passing `signal`
56
+ * > (e.g., `await db.query(...)` without `{ signal }`), that task will continue running in the background.
57
+ * > To prevent background resource leaks, always pass `signal` to underlying drivers/clients
58
+ * > (e.g. `fetch(url, { signal })`, database clients, HTTP clients) or check `signal?.aborted`.
45
59
  *
46
- * - Return `true` (or resolve to true) to signal ready.
60
+ * - Return `true` (or resolve to true, or resolve void without error) to signal ready.
47
61
  * - Return `false` (or resolve to false) to signal not ready.
48
62
  * - Throw an error (or reject) to signal not ready with an error.
49
63
  */
50
- type ReadinessCheckFn = (signal?: AbortSignal) => boolean | Promise<boolean>;
64
+ type ReadinessCheckFn = (signal?: AbortSignal) => boolean | void | Promise<boolean | void>;
51
65
  /**
52
66
  * A named health check with an associated check function.
53
67
  */
@@ -56,10 +70,11 @@ interface NamedCheck {
56
70
  name: string;
57
71
  /**
58
72
  * Check function — return false or throw to indicate failure.
73
+ * Resolving void without throwing is considered healthy/ready.
59
74
  * Receives an AbortSignal that is triggered when the check times out.
60
- * Cancellation via the signal is cooperative.
75
+ * Cancellation via the signal is cooperative; pass `signal` to underlying operations.
61
76
  */
62
- check: (signal?: AbortSignal) => boolean | Promise<boolean>;
77
+ check: (signal?: AbortSignal) => boolean | void | Promise<boolean | void>;
63
78
  }
64
79
  /**
65
80
  * Result of a single named health check.
@@ -148,14 +163,27 @@ interface CatbeeHealthzServerConfig {
148
163
  /**
149
164
  * Custom liveness check function. Runs *in addition to* `checks`.
150
165
  * Return `false` or throw to indicate unhealthy.
166
+ * Can also be provided via `onLivenessCheck`.
151
167
  */
152
168
  onHealthCheck?: HealthCheckFn;
169
+ /**
170
+ * Symmetrical alias for `onHealthCheck` — custom liveness check function.
171
+ * Runs *in addition to* `checks` on `/healthz`.
172
+ * Return `false` or throw to indicate unhealthy.
173
+ */
174
+ onLivenessCheck?: HealthCheckFn;
153
175
  /**
154
176
  * Custom readiness check function. Runs *in addition to* `readinessChecks`.
155
177
  * Return `false` or throw to indicate not ready.
156
178
  */
157
179
  onReadinessCheck?: ReadinessCheckFn;
158
- /** Timeout (ms) per individual check before it's considered failed
180
+ /**
181
+ * Timeout (ms) per individual check before it's considered failed.
182
+ *
183
+ * When exceeded, the probe immediately fast-fails with status 503 and triggers
184
+ * `abort()` on the check's `AbortSignal`. Note that cancellation is cooperative;
185
+ * checks should forward `signal` to downstream clients (e.g. database drivers, fetch)
186
+ * to avoid orphaned background execution.
159
187
  * - **default**: `5000`
160
188
  * - **env**: `HEALTHZ_CHECK_TIMEOUT_MS`
161
189
  */
@@ -194,8 +222,9 @@ interface HealthzAddressInfo {
194
222
  * "omitted" (-> fall back to `checks`) is distinguishable from
195
223
  * "explicitly empty" (-> run nothing).
196
224
  */
197
- interface ResolvedHealthzConfig extends Required<Omit<CatbeeHealthzServerConfig, 'onHealthCheck' | 'onReadinessCheck' | 'readinessChecks'>> {
225
+ interface ResolvedHealthzConfig extends Required<Omit<CatbeeHealthzServerConfig, 'onHealthCheck' | 'onLivenessCheck' | 'onReadinessCheck' | 'readinessChecks'>> {
198
226
  onHealthCheck?: CatbeeHealthzServerConfig['onHealthCheck'];
227
+ onLivenessCheck?: CatbeeHealthzServerConfig['onLivenessCheck'];
199
228
  onReadinessCheck?: CatbeeHealthzServerConfig['onReadinessCheck'];
200
229
  readinessChecks?: CatbeeHealthzServerConfig['readinessChecks'];
201
230
  }
@@ -259,6 +288,8 @@ declare class HealthzServer {
259
288
  private readonly server;
260
289
  private readonly config;
261
290
  private readonly startedAt;
291
+ /** Running address info for the Healthz probe server */
292
+ private addressInfo;
262
293
  /** Whether the health HTTP server is currently running and listening on its port */
263
294
  private running;
264
295
  /** Whether application startup has completed (for `/startupz`) */
@@ -266,6 +297,9 @@ declare class HealthzServer {
266
297
  /** Whether the service is ready to receive traffic (for `/readyz`) */
267
298
  private ready;
268
299
  private shuttingDown;
300
+ private readonly livenessChecks;
301
+ private readonly readinessChecks;
302
+ private stoppingPromise?;
269
303
  private readonly onSigterm;
270
304
  private readonly onSigint;
271
305
  private constructor();
@@ -279,33 +313,96 @@ declare class HealthzServer {
279
313
  */
280
314
  static start(opts?: CatbeeHealthzServerConfig): Promise<HealthzAddressInfo | null>;
281
315
  /** Whether the Healthz HTTP probe server is currently running and listening on its port. */
316
+ isRunning(): boolean;
317
+ /** Whether the Healthz HTTP probe server is currently running and listening on its port. */
282
318
  static isRunning(): boolean;
283
319
  /** Mark application startup as completed (switches `/startupz` to 200). */
320
+ markStartupComplete(): this;
321
+ /** Mark application startup as completed (switches `/startupz` to 200). */
284
322
  static markStartupComplete(): void;
323
+ /** Set application startup completion status. */
324
+ setStartupComplete(complete: boolean): this;
325
+ /** Set application startup completion status. */
326
+ static setStartupComplete(complete: boolean): void;
327
+ /** Whether application startup has completed (for `/startupz`). */
328
+ isStartupComplete(): boolean;
285
329
  /** Whether application startup has completed (for `/startupz`). */
286
330
  static isStartupComplete(): boolean;
287
331
  /** Mark the service as ready / not-ready for traffic. */
332
+ setReady(ready: boolean): this;
333
+ /** Mark the service as ready / not-ready for traffic. */
288
334
  static setReady(ready: boolean): void;
289
335
  /** Whether the service is currently marked as ready. */
336
+ isReady(): boolean;
337
+ /** Whether the service is currently marked as ready. */
290
338
  static isReady(): boolean;
339
+ /** Get address info of this health server instance. */
340
+ getAddress(): HealthzAddressInfo | null;
341
+ /** Get address info of the active singleton health server. */
342
+ static getAddress(): HealthzAddressInfo | null;
343
+ /** Get port the health server is listening on. */
344
+ getPort(): number | undefined;
345
+ /** Get port the active singleton health server is listening on. */
346
+ static getPort(): number | undefined;
347
+ /** Get configured host. */
348
+ getHost(): string | undefined;
349
+ /** Get configured host of the active singleton health server. */
350
+ static getHost(): string | undefined;
351
+ /** Get full URL for this health server instance. */
352
+ getUrl(): string | undefined;
353
+ /** Get full URL for the active singleton health server. */
354
+ static getUrl(): string | undefined;
355
+ /** Gracefully stop this health server instance. */
356
+ stop(): Promise<void>;
291
357
  /** Gracefully stop the health-check server. */
292
358
  static stop(): Promise<void>;
293
359
  static getInstance(): HealthzServer | undefined;
294
360
  /**
295
361
  * Register a named check on this HealthzServer instance dynamically.
362
+ * If a check with the same name already exists in the target probe, it is updated in-place.
296
363
  *
297
364
  * @param check - The named check to register
298
365
  * @param type - Which probe to attach this check to ('readiness', 'liveness', or 'both')
299
366
  * @returns This instance for chaining
300
367
  */
301
368
  registerCheck(check: NamedCheck, type?: 'liveness' | 'readiness' | 'both'): this;
369
+ /**
370
+ * Unregister a named check on this HealthzServer instance.
371
+ *
372
+ * @param name - The name of the check to remove
373
+ * @param type - Which probe to remove from ('liveness', 'readiness', or 'both')
374
+ * @returns This instance for chaining
375
+ */
376
+ unregisterCheck(name: string, type?: 'liveness' | 'readiness' | 'both'): this;
377
+ /**
378
+ * Get all registered checks on this instance.
379
+ */
380
+ getChecks(): {
381
+ liveness: NamedCheck[];
382
+ readiness: NamedCheck[];
383
+ };
302
384
  /**
303
385
  * Register a named check on the active singleton HealthzServer instance (if started).
386
+ * If a check with the same name already exists in the target probe, it is updated in-place.
304
387
  *
305
388
  * @param check - The named check to register
306
389
  * @param type - Which probe to attach this check to ('readiness', 'liveness', or 'both')
307
390
  */
308
391
  static registerCheck(check: NamedCheck, type?: 'liveness' | 'readiness' | 'both'): void;
392
+ /**
393
+ * Unregister a named check on the active singleton HealthzServer instance (if started).
394
+ *
395
+ * @param name - Name of the check to remove
396
+ * @param type - Which probe to remove from ('liveness', 'readiness', or 'both')
397
+ */
398
+ static unregisterCheck(name: string, type?: 'liveness' | 'readiness' | 'both'): void;
399
+ /**
400
+ * Get all registered checks on the active singleton HealthzServer instance.
401
+ */
402
+ static getChecks(): {
403
+ liveness: NamedCheck[];
404
+ readiness: NamedCheck[];
405
+ };
309
406
  private handleRequest;
310
407
  /** `/healthz` — Liveness probe */
311
408
  private handleLiveness;
@@ -316,6 +413,13 @@ declare class HealthzServer {
316
413
  private runChecks;
317
414
  private sendProbe;
318
415
  private sendJson;
416
+ /**
417
+ * Executes a check function with timeout and cooperative cancellation.
418
+ *
419
+ * Note on cancellation: The AbortController aborts when `ms` expires, which signals cooperative
420
+ * consumers (e.g., fetch, pg, ioredis) to terminate their work. The attached .catch() on checkPromise
421
+ * prevents unhandled rejections if the check promise rejects after the timeout has already resolved.
422
+ */
319
423
  private executeCheck;
320
424
  private initiateShutdown;
321
425
  private cleanup;