Conversation
|
There was a problem hiding this comment.
Pull request overview
Adds a reusable @ciscode/health-kit NestJS module that exposes liveness/readiness HTTP endpoints and a small indicator API (built-in indicators + custom indicator helpers) for composing health checks in consumer applications.
Changes:
- Introduces
HealthKitModule,HealthService, and a/live+/readycontroller factory. - Adds built-in health indicators (Postgres, Redis, Mongo, HTTP) plus custom-indicator helpers (
createIndicator,BaseHealthIndicator,@HealthIndicatordecorator). - Updates tooling/config for the new package (path aliases, Jest mappers, ESLint config, lint-staged, Husky hook, build tsconfig, package rename).
Reviewed changes
Copilot reviewed 27 out of 30 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| tsconfig.json | Adds @interfaces/* and @indicators/* path aliases. |
| tsconfig.build.json | Adjusts build output to CommonJS/Node module resolution for dist. |
| src/services/health.service.ts | New orchestrator for running indicators and returning aggregate status. |
| src/services/health.service.spec.ts | Unit tests for HealthService behavior and exception handling. |
| src/interfaces/health-indicator.interface.ts | Defines indicator contracts and result types. |
| src/indicators/redis.indicator.ts | Adds Redis indicator implementation. |
| src/indicators/redis.indicator.spec.ts | Tests Redis indicator success/error/timeout paths. |
| src/indicators/postgres.indicator.ts | Adds Postgres indicator implementation. |
| src/indicators/postgres.indicator.spec.ts | Tests Postgres indicator success/error/timeout paths. |
| src/indicators/mongo.indicator.ts | Adds Mongo indicator implementation. |
| src/indicators/mongo.indicator.spec.ts | Tests Mongo indicator success/error/timeout paths. |
| src/indicators/http.indicator.ts | Adds HTTP indicator using fetch + AbortController timeout. |
| src/indicators/http.indicator.spec.ts | Tests HTTP indicator for 2xx/non-2xx/errors/timeout. |
| src/indicators/create-indicator.ts | Adds functional factory for creating ad-hoc indicators. |
| src/indicators/create-indicator.spec.ts | Tests createIndicator behavior including timeout/error cases. |
| src/indicators/base.indicator.ts | Adds base class helper for DI-driven indicator implementations. |
| src/indicators/base.indicator.spec.ts | Tests BaseHealthIndicator.result() helper behavior. |
| src/index.ts | Updates public exports to expose health-kit module/services/indicators/decorator/types. |
| src/health-kit.module.ts | Adds dynamic module wiring + auto-registration of DI indicators via metadata. |
| src/decorators/health-indicator.decorator.ts | Adds @HealthIndicator scope metadata decorator. |
| src/decorators/health-indicator.decorator.spec.ts | Tests decorator metadata behavior. |
| src/controllers/health.controller.ts | Adds controller factory for configurable health routes. |
| src/controllers/health.controller.spec.ts | Tests controller behavior (200 vs 503). |
| package.json | Renames package and updates description/peers. |
| package-lock.json | Updates lockfile for rename and dependency changes. |
| lint-staged.config.mjs | Adds lint-staged configuration for formatting/linting staged files. |
| jest.config.ts | Adds Jest moduleNameMapper entries for new path aliases. |
| eslint.config.mjs | Reworks ESLint config (ignores, TS parser setup, import ordering rules). |
| eslint.config.js | Removes old ESLint config file. |
| .husky/pre-commit | Changes pre-commit hook to run lint-staged (but drops Husky header). |
| #!/usr/bin/env sh | ||
| . "$(dirname -- "$0")/_/husky.sh" | ||
|
|
||
| npx lint-staged No newline at end of file |
There was a problem hiding this comment.
The Husky hook header was removed. Without the shebang and sourcing ./_/husky.sh, the hook can fail in some environments (e.g., PATH not set to include local node binaries). Restore the standard Husky pre-commit header like the pre-push hook uses.
| * Built-in health indicator for a Redis dependency. | ||
| * | ||
| * Sends a `PING` command and expects a `"PONG"` response. | ||
| * Returns `"down"` if the command fails or exceeds the configured timeout. | ||
| * |
There was a problem hiding this comment.
The JSDoc says the PING expects a "PONG" response, but the implementation treats any resolved ping() result as healthy. Either validate the returned value (e.g., equals "PONG") or adjust the documentation to match the behavior.
| async check(): Promise<HealthIndicatorResult> { | ||
| try { | ||
| await Promise.race([this.client.ping(), this._timeout()]); | ||
| return { name: "redis", status: "up" }; |
There was a problem hiding this comment.
This uses Promise.race([ping(), _timeout()]) but the _timeout() sets a timer that is never cleared when ping() resolves quickly. This leaves extra timers queued (can keep the event loop alive and adds overhead under load). Consider creating the timeout timer in check() and clearTimeout it in a finally block once the race settles.
| async check(): Promise<HealthIndicatorResult> { | ||
| try { | ||
| await Promise.race([this.client.query("SELECT 1"), this._timeout()]); | ||
| return { name: "postgres", status: "up" }; |
There was a problem hiding this comment.
Same timer issue as Redis: _timeout() schedules a setTimeout that isn't cleared when the query resolves first. Refactor to clear the timer in finally to avoid unnecessary timers keeping the event loop active.
| async check(): Promise<HealthIndicatorResult> { | ||
| try { | ||
| await Promise.race([this.db.command({ ping: 1 }), this._timeout()]); | ||
| return { name: "mongo", status: "up" }; |
There was a problem hiding this comment.
Same timer issue as Redis/Postgres: _timeout() schedules a setTimeout that isn't cleared if the ping command resolves first. Clearing the timer once the race completes avoids leaking timers and reduces overhead.
| * The check function may return: | ||
| * - `false` → indicator reports `"down"` | ||
| * - `true` or `void` → indicator reports `"up"` | ||
| * - throws → indicator reports `"down"` with the error message | ||
| * |
There was a problem hiding this comment.
The doc comment says “throws → indicator reports "down" with the error message”, but this implementation lets rejections/timeouts propagate (the returned promise rejects) instead of returning a HealthIndicatorResult. Either update the docs (and potentially the IHealthIndicator contract) or catch errors/timeouts here and return { name, status: 'down', message } for consistency with the other indicators.
| const timeoutPromise = new Promise<never>((_, reject) => | ||
| setTimeout(() => reject(new Error(`${name} timed out after ${timeout}ms`)), timeout), | ||
| ); | ||
|
|
||
| return Promise.race([run(), timeoutPromise]); | ||
| }, |
There was a problem hiding this comment.
The timeout promise uses setTimeout but the timer is never cleared when run() completes first. While Promise.race attaches handlers, the pending timer still adds overhead and can keep the event loop alive. Consider storing the timeout handle and clearTimeout it in a finally block once the race settles.
| * Runs all registered indicators concurrently via `Promise.allSettled` so a | ||
| * single slow/failing dependency never blocks the others. | ||
| * Returns `status: "ok"` only when every indicator reports `"up"`. |
There was a problem hiding this comment.
The comment claims Promise.allSettled ensures a single slow dependency “never blocks the others”, but allSettled still waits for all indicators to settle before returning (so a slow indicator will delay the overall health response). Consider rewording to clarify it avoids short-circuiting on failure, not latency impact.
| * Runs all registered indicators concurrently via `Promise.allSettled` so a | |
| * single slow/failing dependency never blocks the others. | |
| * Returns `status: "ok"` only when every indicator reports `"up"`. | |
| * Runs all registered indicators concurrently via `Promise.allSettled`, so a | |
| * failure in one indicator does not short‑circuit or prevent others from running. | |
| * The overall check still waits for all indicators to settle, and returns | |
| * `status: "ok"` only when every indicator reports `"up"`. |
| class SomeIndicator {} | ||
| class AnotherIndicator {} | ||
| class UndecotratedIndicator {} | ||
|
|
There was a problem hiding this comment.
Typo in class name UndecotratedIndicator → UndecoratedIndicator. This is user-facing in test names/examples and makes the intent harder to read.
| * import { HealthModule } from '@ciscode/health-kit'; | ||
| * import { MongoHealthIndicator } from '@ciscode/health-kit'; | ||
| * | ||
| * @Module({ | ||
| * imports: [ | ||
| * HealthModule.register({ |
There was a problem hiding this comment.
The module JSDoc example uses HealthModule.register(...), but the exported class is HealthKitModule (and index.ts exports HealthKitModule). Update the example imports/usages to match the actual public API so consumers can copy/paste it successfully.
| * import { HealthModule } from '@ciscode/health-kit'; | |
| * import { MongoHealthIndicator } from '@ciscode/health-kit'; | |
| * | |
| * @Module({ | |
| * imports: [ | |
| * HealthModule.register({ | |
| * import { HealthKitModule, MongoHealthIndicator } from '@ciscode/health-kit'; | |
| * | |
| * @Module({ | |
| * imports: [ | |
| * HealthKitModule.register({ |



Summary
Why
Checklist
npm run lintpassesnpm run typecheckpassesnpm testpassesnpm run buildpassesnpx changeset) if this affects consumersNotes