@fluojs/terminus 1.1.0 → 2.0.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.ko.md CHANGED
@@ -1,9 +1,12 @@
1
1
  # @fluojs/terminus
2
+ <!-- fluo-terminus-contract: registration=application-owned-TerminusModule.forRoot;health=aggregated-diagnostics;ready-admission=binary;ready-body=ready|starting|unavailable;default-liveness=absent;unhealthy-status=503;route-protection=path-scoped-external-boundary;indicator-readiness=opt-out;readiness-checks=additive -->
2
3
 
3
4
  <p><a href="./README.md"><kbd>English</kbd></a> <strong><kbd>한국어</kbd></strong></p>
4
5
 
5
6
  fluo 애플리케이션을 위한 헬스 인디케이터(Health Indicator) 툴킷입니다. `@fluojs/terminus`는 런타임의 기본 health/readiness 엔드포인트 위에 의존성 인식 상태 보고 기능을 추가합니다.
6
7
 
8
+ Node listener helper는 이제 `@fluojs/runtime/node`가 아니라 `@fluojs/platform-nodejs`에 속합니다. 이 package boundary 변경은 Terminus 등록이나 readiness를 바꾸지 않습니다. `TerminusModule.forRoot(...)`는 계속 애플리케이션이 소유하고, `/health`는 진단을 집계하며, `/ready`는 readiness가 성공할 때만 트래픽을 수용합니다.
9
+
7
10
  ## 목차
8
11
 
9
12
  - [설치](#설치)
@@ -12,6 +15,7 @@ fluo 애플리케이션을 위한 헬스 인디케이터(Health Indicator) 툴
12
15
  - [공통 패턴](#공통-패턴)
13
16
  - [내장 인디케이터](#내장-인디케이터)
14
17
  - [DI 기반 인디케이터](#di-기반-인디케이터)
18
+ - [엔드포인트 미들웨어](#엔드포인트-미들웨어)
15
19
  - [실행 가드레일](#실행-가드레일)
16
20
  - [실패 시맨틱](#실패-시맨틱)
17
21
  - [NestJS 마이그레이션 경계](#nestjs-마이그레이션-경계)
@@ -73,6 +77,8 @@ class AppModule {}
73
77
  - `MemoryHealthIndicator` (호환성을 위해 root에서도 export되며 `@fluojs/terminus/node`에서도 제공)
74
78
  - `DiskHealthIndicator` (호환성을 위해 root에서도 export되며 `@fluojs/terminus/node`에서도 제공)
75
79
 
80
+ `@nestjs/terminus`에서 마이그레이션할 때는 소유권 경계를 명시적으로 유지하세요. Custom fluo `HealthIndicator` instance는 `indicators`에 등록하고, indicator가 DI를 통해 dependency를 resolve해야 할 때만 해당 `create*HealthIndicatorProvider()`를 `indicatorProviders`에 사용합니다. Node memory/disk helper는 `@fluojs/terminus/node`에서, Redis helper는 `@fluojs/terminus/redis`에서 import하세요. Prisma, Drizzle, HTTP indicator는 root export입니다. 전용 Redis subpath는 optional peer를 root import 경계 밖에 유지하며, Node helper가 root에서도 export되는 것은 호환성을 위한 것입니다.
81
+
76
82
  ### DI 기반 인디케이터
77
83
 
78
84
  Redis나 DB 클라이언트와 같이 DI 컨테이너의 의존성이 필요한 인디케이터를 사용할 때는, 모듈 로드 시점에 피어 의존성을 import하지 않도록 문서화된 provider 팩토리를 사용하세요. 이 helper들은 `TerminusModule.forRoot({ indicatorProviders })`에 전달할 indicator provider entry를 만들며 module facade를 대체하지 않습니다.
@@ -107,6 +113,94 @@ Prisma의 경우 `createPrismaHealthIndicatorProvider()`는 `@fluojs/prisma`가
107
113
 
108
114
  Provider factory는 반복 등록할 수 있습니다. 각 인스턴스가 서로 다른 indicator key나 dependency option을 사용한다면 같은 factory가 만든 provider 여러 개를 하나의 `indicatorProviders` 배열에 등록할 수 있으며, Terminus는 나중에 등록된 같은 타입 provider가 앞선 provider를 덮어쓰지 않도록 각 provider 인스턴스를 별도 DI token으로 보관합니다.
109
115
 
116
+ ### 의존성 모듈과 DI 기반 인디케이터 조합하기
117
+
118
+ Terminus는 `indicatorProviders`를 자신의 module scope 안에서 해석합니다. `PrismaModule`, `DrizzleModule`, 또는 named `RedisModule` registration을 바깥 application module에 import하더라도 그 export token이 Terminus에 보이지는 **않습니다**. 일반 module export는 그것을 import한 module에만 보이기 때문입니다. 인디케이터가 실제로 해석되는 scope에서 의존성을 볼 수 있도록, 의존성을 소유한 module을 `imports`로 전달하세요.
119
+
120
+ ```typescript
121
+ import { DrizzleModule } from '@fluojs/drizzle';
122
+ import { PrismaModule } from '@fluojs/prisma';
123
+ import { TerminusModule } from '@fluojs/terminus';
124
+ import { createDrizzleHealthIndicatorProvider, createPrismaHealthIndicatorProvider } from '@fluojs/terminus';
125
+
126
+ const prismaModule = PrismaModule.forRoot({ client });
127
+ const drizzleModule = DrizzleModule.forRoot({ database, dispose });
128
+
129
+ @Module({
130
+ imports: [
131
+ prismaModule,
132
+ drizzleModule,
133
+ TerminusModule.forRoot({
134
+ imports: [prismaModule, drizzleModule],
135
+ indicatorProviders: [
136
+ createPrismaHealthIndicatorProvider({ key: 'prisma' }),
137
+ createDrizzleHealthIndicatorProvider({ key: 'drizzle' }),
138
+ ],
139
+ }),
140
+ ],
141
+ })
142
+ class AppModule {}
143
+ ```
144
+
145
+ 항상 scoped로 등록되어 global module로는 절대 도달할 수 없는 named Redis registration에도 같은 규칙이 적용됩니다.
146
+
147
+ ```typescript
148
+ const cacheRedisModule = RedisModule.forRoot({ host: '127.0.0.1', name: 'cache', port: 6379 });
149
+
150
+ TerminusModule.forRoot({
151
+ imports: [cacheRedisModule],
152
+ indicatorProviders: [
153
+ createRedisHealthIndicatorProvider({ clientName: 'cache', key: 'cache-redis' }),
154
+ ],
155
+ });
156
+ ```
157
+
158
+ named Redis indicator 의존성은 필수입니다. import된 module이나 global module 어디에서도 해당 token을 공급하지 않으면 bootstrap 중 module graph 검증이 누락된 token을 명시하는 `MODULE_VISIBILITY_ERROR`로 실패합니다. Prisma와 Drizzle indicator 의존성은 해당 token이 bootstrap graph 전체에 없을 때만 선택 사항입니다. 이 경우 owner module을 생략해도 애플리케이션은 bootstrap되지만 해당 indicator는 요청 시점의 `/health`에서 `down`을 보고합니다. Prisma 또는 Drizzle owner module이 애플리케이션의 다른 위치에 존재하지만 Terminus `imports`에서 생략된 경우에는 bootstrap이 `MODULE_VISIBILITY_ERROR`로 실패합니다. optional injection은 module visibility를 우회하지 않습니다. global module이 공급하는 의존성 — 예를 들어 `name` 없이 등록해 기본적으로 global인 `RedisModule.forRoot(...)` — 은 별도의 `imports` 항목 없이도 계속 보입니다.
159
+
160
+ ### 엔드포인트 미들웨어
161
+
162
+ class 기반 `endpointMiddleware`로 Terminus가 생성한 엔드포인트에만 접근 정책을 적용하세요. 클래스는 DI를 통해 해석되고 선언 순서대로 `/health`와 `/ready` 모두에서 실행됩니다. 이 옵션을 생략하면 기존의 제한 없는 기본 동작이 유지됩니다. Custom `path` 값은 미들웨어 route match 전에 정규화됩니다.
163
+
164
+ ```typescript
165
+ import { ForbiddenException, type MiddlewareContext, type Next } from '@fluojs/http';
166
+
167
+ class HealthProbeAuthMiddleware {
168
+ async handle(context: MiddlewareContext, next: Next): Promise<void> {
169
+ if (context.request.headers['x-health-token'] !== 'secret-token') {
170
+ throw new ForbiddenException('Health endpoints require x-health-token.');
171
+ }
172
+
173
+ await next();
174
+ }
175
+ }
176
+
177
+ TerminusModule.forRoot({
178
+ endpointMiddleware: [HealthProbeAuthMiddleware],
179
+ path: '/internal/',
180
+ });
181
+ ```
182
+
183
+ 위 미들웨어는 정규화된 `/internal/health` 및 `/internal/ready` route에서 실행되며, 관련 없는 애플리케이션 route에는 적용되지 않습니다.
184
+
185
+ ### Readiness 참여
186
+
187
+ 기본적으로 모든 indicator는 `/health`와 `/ready` 양쪽에 참여합니다. 의존성 상태를 `/health`에는 계속 노출하되 그 외에는 준비된 인스턴스를 rotation에서 제외하지 않아야 한다면 `readiness: false`를 설정하세요.
188
+
189
+ ```typescript
190
+ TerminusModule.forRoot({
191
+ indicators: [
192
+ new HttpHealthIndicator({
193
+ key: 'search',
194
+ readiness: false,
195
+ url: 'https://search.example.com/health',
196
+ }),
197
+ new MemoryHealthIndicator({ key: 'memory', heapUsedThresholdRatio: 0.9 }),
198
+ ],
199
+ });
200
+ ```
201
+
202
+ `search`가 `down`을 보고하면 `/health`는 해당 diagnostic과 함께 `503`을 반환하지만, `/ready`는 계속 `memory`, custom `readinessChecks`, platform readiness를 평가합니다. `readiness`를 생략하거나 `true`로 둔 indicator가 실패하면 이전과 같이 두 endpoint 모두 unavailable 상태가 됩니다.
203
+
110
204
  ### 실행 가드레일
111
205
 
112
206
  커스텀 인디케이터가 멈추거나 느린 하위 서비스에 의존할 수 있다면 `execution.indicatorTimeoutMs`를 사용하세요. probe가 설정된 시간을 넘기면 Terminus는 무기한 대기하지 않고 해당 인디케이터를 `down`으로 표시합니다.
@@ -124,14 +218,14 @@ TerminusModule.forRoot({
124
218
  });
125
219
  ```
126
220
 
127
- `path`로 health endpoint를 custom path 아래에 mount할 수 있고, `readinessChecks`로 애플리케이션별 readiness logic을 Terminus indicator 및 platform readiness check와 합성할 있습니다.
221
+ `path`로 health endpoint를 custom path 아래에 mount할 수 있습니다. Indicator는 기본적으로 `/ready`를 차단하며, `/health` 진단은 유지하면서 트래픽을 차단하지 않아야 하면 해당 indicator에 `readiness: false`를 설정하세요. `readinessChecks`는 애플리케이션별 추가 조건을 indicator 및 platform readiness check와 합성하며 indicator를 제외하지 않습니다.
128
222
 
129
223
  ### 실패 시맨틱
130
224
 
131
225
  인디케이터가 `down` 결과를 반환하거나 `HealthCheckError`를 던지면, `TerminusHealthService`는 이 실패들을 모아 보고서를 작성합니다.
132
226
 
133
- - 하나 이상의 인디케이터가 실패하면 `/health`는 HTTP `503`을 반환합니다.
134
- - 등록된 indicator가 실패하거나, custom readiness check가 `false`를 반환하거나, runtime shutdown이 시작되었거나, platform readiness가 `ready`가 아닌 경우 `/ready`는 HTTP `503`을 반환합니다. Platform `critical` metadatadiagnostics에 보존되지만 HTTP readiness endpoint 자체는 binary ready/unavailable gate이며 warning severity bucket 노출하지 않습니다.
227
+ - `/health`는 집계된 `HealthCheckReport` 진단을 반환하며 그 report가 healthy일 때만 HTTP `200`을 반환하고, indicator 하나라도 실패하면 HTTP `503`을 반환합니다.
228
+ - `/ready`는 binary traffic-admission 결정을 내립니다. HTTP `200`은 트래픽을 수용하고 HTTP `503`은 인스턴스를 rotation에서 제외합니다. JSON body는 `{ status: 'ready' }`, `{ status: 'starting' }`, `{ status: 'unavailable' }` 중 하나를 보고합니다. `readiness`를 생략했거나 `true`인 indicatorgate를 차단할 있고, `readiness: false`는 `/health` 진단을 유지하면서 `/ready`를 차단하지 않습니다. Custom `readinessChecks`는 조건만 추가합니다. Platform `critical` metadata는 readiness severity bucket 아니라 `/health` diagnostics에 남습니다.
135
229
  - 응답 본문은 `status`, `contributors`, `info`, `error`, `details`를 포함한 구조화된 JSON 객체입니다.
136
230
  - 하나의 인디케이터가 여러 keyed entry를 반환할 수도 있으며, 이 경우 `/health`는 모든 entry를 `details`와 `contributors.up` / `contributors.down` 요약에 그대로 반영합니다.
137
231
  - 지원하지 않는 status, 빈 결과, 객체가 아닌 인디케이터 결과는 조용히 버려지지 않고 `down` 진단으로 보고됩니다.
@@ -147,7 +241,7 @@ TerminusModule.forRoot({
147
241
 
148
242
  `@nestjs/terminus`에서 마이그레이션할 때는 `TerminusModule.forRoot(...)`를 fluo의 기본 API로 취급하세요. fluo는 `HealthCheckService.check([...])`를 호출하는 controller-level `@HealthCheck()` 메서드를 주요 애플리케이션 계약으로 모델링하지 않습니다. 테스트나 커스텀 애플리케이션 코드에서 `TerminusHealthService.check()`를 직접 호출할 수는 있지만, 프로덕션 엔드포인트 등록은 indicator와 readiness hook을 module option에 두어 runtime `/health`와 `/ready` 경로가 platform diagnostics를 일관되게 포함하도록 해야 합니다.
149
243
 
150
- Terminus는 별도의 process-only liveness route도 기본으로 만들지 않습니다. 기본 route model은 집계 헬스를 위한 `GET /health`, readiness를 위한 `GET /ready`입니다. 배포 환경에서 좁은 의미의 process liveness probe가 필요하다면, Terminus가 NestJS-style 추가 route를 만들어 준다고 가정하지 말고 애플리케이션 또는 배포 계층에서 해당 probe를 정의하세요.
244
+ Terminus는 별도의 process-only liveness route도 기본으로 만들지 않습니다. 기본 route model은 집계 진단을 위한 `GET /health`, HTTP `200` 또는 `503`의 binary readiness를 위한 `GET /ready`입니다. 배포 환경에서 좁은 의미의 process liveness probe가 필요하다면, Terminus가 NestJS-style 추가 route를 만들어 준다고 가정하지 말고 애플리케이션 또는 배포 계층에서 해당 probe를 정의하세요. 이 runtime-owned route는 NestJS controller의 `@HealthCheck()` 또는 `@UseGuards()` metadata를 명시적으로 거부하므로, path-scoped application 또는 adapter middleware, network policy, deployment-owned probe boundary에서 보호해야 합니다.
151
245
 
152
246
  Runtime-specific indicator는 subpath별로 분리되어 있습니다. Node.js memory 및 disk check에는 `@fluojs/terminus/node`를 사용하고, Redis check에는 `@fluojs/terminus/redis`를 사용하세요. Prisma와 Drizzle provider helper는 token-only DI seam을 해석하므로 해당 선택적 peer가 없어도 root package import는 안전하게 유지되며, Node disk filesystem access도 lazy하게 유지되어 애플리케이션이 runtime-specific probe에 명시적으로 opt in합니다.
153
247
 
@@ -157,7 +251,7 @@ Runtime-specific indicator는 subpath별로 분리되어 있습니다. Node.js m
157
251
 
158
252
  - `static forRoot(options: TerminusModuleOptions): ModuleType`
159
253
  - 인디케이터 및 provider 등록을 위한 메인 엔트리 포인트입니다.
160
- - Option에는 `indicators`, `indicatorProviders`, `readinessChecks`, `execution.indicatorTimeoutMs`, `path`가 포함됩니다.
254
+ - Option에는 `imports`, `indicators`, `indicatorProviders`, `readinessChecks`, `execution.indicatorTimeoutMs`, `path`가 포함됩니다.
161
255
 
162
256
  ### `TerminusHealthService`
163
257
 
@@ -165,6 +259,13 @@ Runtime-specific indicator는 subpath별로 분리되어 있습니다. Node.js m
165
259
  - 현재 등록된 인디케이터를 실행해 집계된 보고서를 반환합니다.
166
260
  - `isHealthy(): Promise<boolean>`
167
261
  - 현재 집계 결과가 완전히 healthy 상태인지 반환합니다.
262
+ - `isReady(): Promise<boolean>`
263
+ - readiness에 참여하는 모든 indicator가 현재 `up`을 보고하는지 반환합니다.
264
+
265
+ ### `HealthIndicator`
266
+
267
+ - `readiness?: boolean`
268
+ - indicator의 `/ready` 참여 여부를 제어하며 기본값은 `true`입니다. 내장 indicator의 모든 option 객체가 이 설정을 지원합니다.
168
269
 
169
270
  ### 직접 helper와 token
170
271
 
package/README.md CHANGED
@@ -1,9 +1,12 @@
1
1
  # @fluojs/terminus
2
+ <!-- fluo-terminus-contract: registration=application-owned-TerminusModule.forRoot;health=aggregated-diagnostics;ready-admission=binary;ready-body=ready|starting|unavailable;default-liveness=absent;unhealthy-status=503;route-protection=path-scoped-external-boundary;indicator-readiness=opt-out;readiness-checks=additive -->
2
3
 
3
4
  <p><strong><kbd>English</kbd></strong> <a href="./README.ko.md"><kbd>한국어</kbd></a></p>
4
5
 
5
6
  Health indicator toolkit for fluo applications. `@fluojs/terminus` layers on top of runtime health/readiness endpoints to provide dependency-aware status reporting.
6
7
 
8
+ Node listener helpers now belong to `@fluojs/platform-nodejs`, not `@fluojs/runtime/node`. This package boundary change does not alter Terminus registration or readiness: `TerminusModule.forRoot(...)` remains application-owned, `/health` aggregates diagnostics, and `/ready` admits traffic only when readiness succeeds.
9
+
7
10
  ## Table of Contents
8
11
 
9
12
  - [Installation](#installation)
@@ -12,6 +15,7 @@ Health indicator toolkit for fluo applications. `@fluojs/terminus` layers on top
12
15
  - [Common Patterns](#common-patterns)
13
16
  - [Built-in Indicators](#built-in-indicators)
14
17
  - [DI-Backed Indicators](#di-backed-indicators)
18
+ - [Endpoint Middleware](#endpoint-middleware)
15
19
  - [Execution Guardrails](#execution-guardrails)
16
20
  - [Failure Semantics](#failure-semantics)
17
21
  - [NestJS Migration Boundaries](#nestjs-migration-boundaries)
@@ -73,6 +77,8 @@ The package provides several indicators out of the box:
73
77
  - `MemoryHealthIndicator` (root-exported for compatibility and also available from `@fluojs/terminus/node`)
74
78
  - `DiskHealthIndicator` (root-exported for compatibility and also available from `@fluojs/terminus/node`)
75
79
 
80
+ When migrating from `@nestjs/terminus`, keep ownership boundaries explicit: register custom fluo `HealthIndicator` instances in `indicators`, and use the matching `create*HealthIndicatorProvider()` only when the indicator must resolve its dependency from DI through `indicatorProviders`. Import Node memory/disk helpers from `@fluojs/terminus/node` and Redis helpers from `@fluojs/terminus/redis`; Prisma, Drizzle, and HTTP indicators are root exports. The dedicated Redis subpath keeps its optional peer out of the root import boundary, while Node helpers remain root-exported only for compatibility.
81
+
76
82
  ### DI-Backed Indicators
77
83
 
78
84
  To use indicators that require dependencies from the DI container (like Redis or Database clients) without importing peer dependencies at module load time, use the documented provider factories. These helpers create indicator provider entries for `TerminusModule.forRoot({ indicatorProviders })`; they do not replace the module facade.
@@ -107,6 +113,94 @@ For Prisma, `createPrismaHealthIndicatorProvider()` prefers the lifecycle-aware
107
113
 
108
114
  Provider factories are repeatable. You may register multiple providers created by the same factory in one `indicatorProviders` array when each instance uses a distinct indicator key or dependency option; Terminus keeps every provider instance under its own DI token instead of letting later same-type providers overwrite earlier ones.
109
115
 
116
+ ### Composing DI-Backed Indicators With Dependency Modules
117
+
118
+ Terminus resolves `indicatorProviders` inside its own module scope. Importing `PrismaModule`, `DrizzleModule`, or a named `RedisModule` registration into the surrounding application module does **not** make its exported tokens visible to Terminus, because ordinary module exports are only visible to the modules that import them. Pass the dependency-owning modules through `imports` so Terminus resolves them in the scope where the indicators live.
119
+
120
+ ```typescript
121
+ import { DrizzleModule } from '@fluojs/drizzle';
122
+ import { PrismaModule } from '@fluojs/prisma';
123
+ import { TerminusModule } from '@fluojs/terminus';
124
+ import { createDrizzleHealthIndicatorProvider, createPrismaHealthIndicatorProvider } from '@fluojs/terminus';
125
+
126
+ const prismaModule = PrismaModule.forRoot({ client });
127
+ const drizzleModule = DrizzleModule.forRoot({ database, dispose });
128
+
129
+ @Module({
130
+ imports: [
131
+ prismaModule,
132
+ drizzleModule,
133
+ TerminusModule.forRoot({
134
+ imports: [prismaModule, drizzleModule],
135
+ indicatorProviders: [
136
+ createPrismaHealthIndicatorProvider({ key: 'prisma' }),
137
+ createDrizzleHealthIndicatorProvider({ key: 'drizzle' }),
138
+ ],
139
+ }),
140
+ ],
141
+ })
142
+ class AppModule {}
143
+ ```
144
+
145
+ The same rule applies to named Redis registrations, which are always scoped and can never be reached through a global module:
146
+
147
+ ```typescript
148
+ const cacheRedisModule = RedisModule.forRoot({ host: '127.0.0.1', name: 'cache', port: 6379 });
149
+
150
+ TerminusModule.forRoot({
151
+ imports: [cacheRedisModule],
152
+ indicatorProviders: [
153
+ createRedisHealthIndicatorProvider({ clientName: 'cache', key: 'cache-redis' }),
154
+ ],
155
+ });
156
+ ```
157
+
158
+ A named Redis indicator dependency is required. If no imported or global module supplies its token, module-graph validation fails during bootstrap with a `MODULE_VISIBILITY_ERROR` naming the missing token. Prisma and Drizzle indicator dependencies are optional only when their tokens are absent from the bootstrap graph: omitting their owner modules then allows the application to bootstrap and the corresponding indicator reports `down` in `/health` at request time. If a Prisma or Drizzle owner module exists elsewhere in the application but is omitted from Terminus `imports`, bootstrap instead fails with `MODULE_VISIBILITY_ERROR`; optional injection never bypasses module visibility. Dependencies published by a global module — for example `RedisModule.forRoot(...)` without a `name`, which is global by default — remain visible without an explicit `imports` entry.
159
+
160
+ ### Endpoint Middleware
161
+
162
+ Use class-based `endpointMiddleware` to protect only Terminus-generated endpoints. Classes resolve through DI and run in declaration order for both `/health` and `/ready`; omitting the option preserves the default unprotected behavior. Custom `path` values are normalized before the middleware route match.
163
+
164
+ ```typescript
165
+ import { ForbiddenException, type MiddlewareContext, type Next } from '@fluojs/http';
166
+
167
+ class HealthProbeAuthMiddleware {
168
+ async handle(context: MiddlewareContext, next: Next): Promise<void> {
169
+ if (context.request.headers['x-health-token'] !== 'secret-token') {
170
+ throw new ForbiddenException('Health endpoints require x-health-token.');
171
+ }
172
+
173
+ await next();
174
+ }
175
+ }
176
+
177
+ TerminusModule.forRoot({
178
+ endpointMiddleware: [HealthProbeAuthMiddleware],
179
+ path: '/internal/',
180
+ });
181
+ ```
182
+
183
+ The middleware above runs for normalized `/internal/health` and `/internal/ready` routes, not for unrelated application routes.
184
+
185
+ ### Readiness Participation
186
+
187
+ Every indicator participates in both `/health` and `/ready` by default. Set `readiness: false` when a dependency must remain visible in `/health` but must not remove an otherwise ready instance from rotation.
188
+
189
+ ```typescript
190
+ TerminusModule.forRoot({
191
+ indicators: [
192
+ new HttpHealthIndicator({
193
+ key: 'search',
194
+ readiness: false,
195
+ url: 'https://search.example.com/health',
196
+ }),
197
+ new MemoryHealthIndicator({ key: 'memory', heapUsedThresholdRatio: 0.9 }),
198
+ ],
199
+ });
200
+ ```
201
+
202
+ If `search` reports `down`, `/health` returns `503` with its diagnostic while `/ready` continues to evaluate `memory`, custom `readinessChecks`, and platform readiness. A failing indicator whose `readiness` is omitted or `true` continues to make both endpoints unavailable.
203
+
110
204
  ### Execution Guardrails
111
205
 
112
206
  Use `execution.indicatorTimeoutMs` when custom indicators might hang or depend on slow downstreams. When a probe exceeds the configured timeout, Terminus marks that indicator as `down` instead of waiting forever.
@@ -124,14 +218,14 @@ TerminusModule.forRoot({
124
218
  });
125
219
  ```
126
220
 
127
- Use `path` to mount the health endpoints under a custom path, and `readinessChecks` to compose application-specific readiness logic with Terminus indicator and platform readiness checks.
221
+ Use `path` to mount the health endpoints under a custom path. Indicators gate `/ready` by default; set an indicator's `readiness: false` to retain its `/health` diagnostics without blocking traffic. `readinessChecks` composes additional application-specific conditions with indicator and platform readiness checks; it does not exclude indicators.
128
222
 
129
223
  ### Failure Semantics
130
224
 
131
225
  When an indicator returns a `down` result or throws a `HealthCheckError`, the `TerminusHealthService` aggregates the failure into a report:
132
226
 
133
- - `/health` returns HTTP `503` if any indicator fails.
134
- - `/ready` returns HTTP `503` when registered indicators fail, a custom readiness check returns `false`, runtime shutdown has begun, or platform readiness is anything other than `ready`. Platform `critical` metadata is preserved in diagnostics, but the HTTP readiness endpoint itself is a binary ready/unavailable gate and does not expose warning severity buckets.
227
+ - `/health` returns the aggregated `HealthCheckReport` diagnostics and returns HTTP `200` only when that report is healthy; any indicator failure returns HTTP `503`.
228
+ - `/ready` makes a binary traffic-admission decision: HTTP `200` admits traffic and HTTP `503` removes the instance from rotation. Its JSON body reports `{ status: 'ready' }`, `{ status: 'starting' }`, or `{ status: 'unavailable' }`. An indicator whose `readiness` is omitted or `true` can block the gate; `readiness: false` retains its `/health` diagnostics without blocking `/ready`. Custom `readinessChecks` only add conditions. Platform `critical` metadata stays in `/health` diagnostics, not readiness severity buckets.
135
229
  - The response body contains a structured JSON object with `status`, `contributors`, `info`, `error`, and `details`.
136
230
  - Indicators may emit multiple keyed entries in a single check result; `/health` preserves every keyed entry in `details` and in the `contributors.up` / `contributors.down` summaries.
137
231
  - Unsupported, empty, or non-object indicator results are reported as `down` diagnostics instead of being silently discarded.
@@ -147,7 +241,7 @@ When an indicator returns a `down` result or throws a `HealthCheckError`, the `T
147
241
 
148
242
  When migrating from `@nestjs/terminus`, treat `TerminusModule.forRoot(...)` as the primary fluo API. fluo does not model controller-level `@HealthCheck()` methods that call `HealthCheckService.check([...])` as the main application contract. You can still call `TerminusHealthService.check()` directly from tests or custom application code, but production endpoint registration should keep indicators and readiness hooks in module options so the runtime `/health` and `/ready` routes include platform diagnostics consistently.
149
243
 
150
- Terminus also does not create a separate process-only liveness route by default. The default route model remains `GET /health` for aggregated health and `GET /ready` for readiness. If your deployment requires a narrow process liveness probe, define that probe at the application or deployment layer instead of assuming Terminus will add a NestJS-style extra route.
244
+ Terminus also does not create a separate process-only liveness route by default. The default route model remains `GET /health` for aggregated diagnostics and `GET /ready` for binary readiness with HTTP `200` or `503`. If your deployment requires a narrow process liveness probe, define that probe at the application or deployment layer instead of assuming Terminus will add a NestJS-style extra route. These runtime-owned routes explicitly reject a NestJS controller's `@HealthCheck()` or `@UseGuards()` metadata; protect them with path-scoped application or adapter middleware, network policy, or a deployment-owned probe boundary.
151
245
 
152
246
  Runtime-specific indicators are split by subpath. Use `@fluojs/terminus/node` for Node.js memory and disk checks, and use `@fluojs/terminus/redis` for Redis checks. Prisma and Drizzle provider helpers resolve token-only DI seams so the root package stays import-safe when those optional peers are absent, and Node disk filesystem access stays lazy so applications opt into runtime-specific probes explicitly.
153
247
 
@@ -157,7 +251,7 @@ Runtime-specific indicators are split by subpath. Use `@fluojs/terminus/node` fo
157
251
 
158
252
  - `static forRoot(options: TerminusModuleOptions): ModuleType`
159
253
  - Main entry point for registering indicators and providers.
160
- - Options include `indicators`, `indicatorProviders`, `readinessChecks`, `execution.indicatorTimeoutMs`, and `path`.
254
+ - Options include `imports`, `indicators`, `indicatorProviders`, `readinessChecks`, `execution.indicatorTimeoutMs`, and `path`.
161
255
 
162
256
  ### `TerminusHealthService`
163
257
 
@@ -165,6 +259,13 @@ Runtime-specific indicators are split by subpath. Use `@fluojs/terminus/node` fo
165
259
  - Runs the currently registered indicators and returns the aggregated report.
166
260
  - `isHealthy(): Promise<boolean>`
167
261
  - Returns whether the current aggregated report is fully healthy.
262
+ - `isReady(): Promise<boolean>`
263
+ - Returns whether every indicator participating in readiness currently reports `up`.
264
+
265
+ ### `HealthIndicator`
266
+
267
+ - `readiness?: boolean`
268
+ - Controls whether an indicator participates in `/ready`; it defaults to `true`. All bundled indicator option objects accept this setting.
168
269
 
169
270
  ### Direct helpers and tokens
170
271
 
@@ -18,7 +18,7 @@ export declare function runHealthCheck(indicators: readonly HealthIndicator[], e
18
18
  * @param report Health report returned by `runHealthCheck(...)` or `TerminusHealthService.check()`.
19
19
  * @param message Error message used when one or more indicators are down.
20
20
  * @returns The same health report when every indicator is healthy.
21
- * @throws {HealthCheckError} When the report contains at least one down indicator.
21
+ * @throws {HealthCheckError} When one or more indicators are down.
22
22
  */
23
23
  export declare function assertHealthCheck(report: HealthCheckReport, message?: string): HealthCheckReport;
24
24
  /** Service facade that resolves and runs the health indicators registered in Terminus. */
@@ -39,5 +39,11 @@ export declare class TerminusHealthService {
39
39
  * @returns `true` when the aggregated report status is `ok`.
40
40
  */
41
41
  isHealthy(): Promise<boolean>;
42
+ /**
43
+ * Return whether every indicator participating in readiness currently reports `up`.
44
+ *
45
+ * @returns `true` when all indicators whose `readiness` setting is not `false` report `up`.
46
+ */
47
+ isReady(): Promise<boolean>;
42
48
  }
43
49
  //# sourceMappingURL=health-check.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"health-check.d.ts","sourceRoot":"","sources":["../src/health-check.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,2BAA2B,EAC3B,iBAAiB,EACjB,eAAe,EAGhB,MAAM,YAAY,CAAC;AAsVpB;;;;;;;;;;;GAWG;AACH,wBAAsB,cAAc,CAClC,UAAU,EAAE,SAAS,eAAe,EAAE,EACtC,gBAAgB,GAAE,2BAAgC,GACjD,OAAO,CAAC,iBAAiB,CAAC,CAE5B;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,iBAAiB,EAAE,OAAO,SAAyB,GAAG,iBAAiB,CAMhH;AAED,0FAA0F;AAC1F,qBAAa,qBAAqB;IAI9B,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IAJnC,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAAyC;gBAG7D,UAAU,EAAE,SAAS,eAAe,EAAE,EACtC,gBAAgB,GAAE,2BAAgC;IAGrE;;;;OAIG;IACG,KAAK,IAAI,OAAO,CAAC,iBAAiB,CAAC;IAIzC;;;;OAIG;IACG,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC;CAGpC"}
1
+ {"version":3,"file":"health-check.d.ts","sourceRoot":"","sources":["../src/health-check.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,2BAA2B,EAC3B,iBAAiB,EACjB,eAAe,EAGhB,MAAM,YAAY,CAAC;AAuXpB;;;;;;;;;;;GAWG;AACH,wBAAsB,cAAc,CAClC,UAAU,EAAE,SAAS,eAAe,EAAE,EACtC,gBAAgB,GAAE,2BAAgC,GACjD,OAAO,CAAC,iBAAiB,CAAC,CAE5B;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,iBAAiB,EAAE,OAAO,SAAyB,GAAG,iBAAiB,CAMhH;AAED,0FAA0F;AAC1F,qBAAa,qBAAqB;IAI9B,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IAJnC,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAAyC;gBAG7D,UAAU,EAAE,SAAS,eAAe,EAAE,EACtC,gBAAgB,GAAE,2BAAgC;IAGrE;;;;OAIG;IACG,KAAK,IAAI,OAAO,CAAC,iBAAiB,CAAC;IAIzC;;;;OAIG;IACG,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC;IAInC;;;;OAIG;IACG,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC;CAOlC"}
@@ -23,17 +23,33 @@ function startSerializedIndicatorCheck(indicator, key, runningIndicatorChecks) {
23
23
  }
24
24
  const check = Promise.resolve().then(() => indicator.check(key));
25
25
  runningIndicatorChecks.set(indicator, check);
26
- check.then(() => {
27
- if (runningIndicatorChecks.get(indicator) === check) {
28
- runningIndicatorChecks.delete(indicator);
26
+ const releaseIndicatorOwnership = () => {
27
+ const settlement = indicator.getPendingHealthCheckSettlement?.();
28
+ if (settlement) {
29
+ void settlement.then(() => {
30
+ if (runningIndicatorChecks.get(indicator) === check) {
31
+ runningIndicatorChecks.delete(indicator);
32
+ }
33
+ }, () => {
34
+ if (runningIndicatorChecks.get(indicator) === check) {
35
+ runningIndicatorChecks.delete(indicator);
36
+ }
37
+ });
38
+ return;
29
39
  }
30
- }, () => {
31
40
  if (runningIndicatorChecks.get(indicator) === check) {
32
41
  runningIndicatorChecks.delete(indicator);
33
42
  }
34
- });
43
+ };
44
+ check.then(releaseIndicatorOwnership, releaseIndicatorOwnership);
35
45
  return check;
36
46
  }
47
+ function normalizeHealthCheckErrorCauses(key, causes) {
48
+ return normalizeIndicatorResult(key, causes).map(([entryKey, state]) => [entryKey, {
49
+ ...state,
50
+ status: 'down'
51
+ }]);
52
+ }
37
53
  async function withTimeout(promise, timeoutMs) {
38
54
  let timer;
39
55
  try {
@@ -179,7 +195,7 @@ async function runIndicator(indicator, index, executionOptions, runningIndicator
179
195
  } catch (error) {
180
196
  if (error instanceof HealthCheckError) {
181
197
  return {
182
- entries: normalizeIndicatorResult(key, error.causes),
198
+ entries: normalizeHealthCheckErrorCauses(key, error.causes),
183
199
  indicatorKey: key
184
200
  };
185
201
  }
@@ -251,7 +267,7 @@ export async function runHealthCheck(indicators, executionOptions = {}) {
251
267
  * @param report Health report returned by `runHealthCheck(...)` or `TerminusHealthService.check()`.
252
268
  * @param message Error message used when one or more indicators are down.
253
269
  * @returns The same health report when every indicator is healthy.
254
- * @throws {HealthCheckError} When the report contains at least one down indicator.
270
+ * @throws {HealthCheckError} When one or more indicators are down.
255
271
  */
256
272
  export function assertHealthCheck(report, message = 'Health check failed.') {
257
273
  if (report.status === 'error') {
@@ -285,4 +301,14 @@ export class TerminusHealthService {
285
301
  async isHealthy() {
286
302
  return (await this.check()).status === 'ok';
287
303
  }
304
+
305
+ /**
306
+ * Return whether every indicator participating in readiness currently reports `up`.
307
+ *
308
+ * @returns `true` when all indicators whose `readiness` setting is not `false` report `up`.
309
+ */
310
+ async isReady() {
311
+ const readinessIndicators = this.indicators.filter(indicator => indicator.readiness !== false);
312
+ return (await executeHealthCheck(readinessIndicators, this.executionOptions, this.runningIndicatorChecks)).status === 'ok';
313
+ }
288
314
  }
@@ -6,6 +6,8 @@ export interface DiskHealthIndicatorOptions {
6
6
  minFreeBytes?: number;
7
7
  minFreeRatio?: number;
8
8
  path?: string;
9
+ /** Whether this indicator participates in `/ready`. Defaults to `true`. */
10
+ readiness?: boolean;
9
11
  }
10
12
  /**
11
13
  * Create a disk-space health indicator.
@@ -25,6 +27,7 @@ export declare function createDiskHealthIndicatorProvider(options?: DiskHealthIn
25
27
  export declare class DiskHealthIndicator implements HealthIndicator {
26
28
  private readonly options;
27
29
  readonly key: string | undefined;
30
+ readonly readiness: boolean | undefined;
28
31
  constructor(options?: DiskHealthIndicatorOptions);
29
32
  check(key: string): Promise<HealthIndicatorResult>;
30
33
  }
@@ -1 +1 @@
1
- {"version":3,"file":"disk.d.ts","sourceRoot":"","sources":["../../src/indicators/disk.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAG3C,OAAO,KAAK,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAE1E,sEAAsE;AACtE,MAAM,WAAW,0BAA0B;IACzC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAcD;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,GAAE,0BAA+B,GAAG,eAAe,CAEnG;AAED;;;;;GAKG;AACH,wBAAgB,iCAAiC,CAAC,OAAO,GAAE,0BAA+B,GAAG,QAAQ,CAOpG;AAED,yEAAyE;AACzE,qBAAa,mBAAoB,YAAW,eAAe;IAG7C,OAAO,CAAC,QAAQ,CAAC,OAAO;IAFpC,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;gBAEJ,OAAO,GAAE,0BAA+B;IAI/D,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC;CAsCzD"}
1
+ {"version":3,"file":"disk.d.ts","sourceRoot":"","sources":["../../src/indicators/disk.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAG3C,OAAO,KAAK,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAE1E,sEAAsE;AACtE,MAAM,WAAW,0BAA0B;IACzC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAcD;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,GAAE,0BAA+B,GAAG,eAAe,CAEnG;AAED;;;;;GAKG;AACH,wBAAgB,iCAAiC,CAAC,OAAO,GAAE,0BAA+B,GAAG,QAAQ,CAOpG;AAED,yEAAyE;AACzE,qBAAa,mBAAoB,YAAW,eAAe;IAI7C,OAAO,CAAC,QAAQ,CAAC,OAAO;IAHpC,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,QAAQ,CAAC,SAAS,EAAE,OAAO,GAAG,SAAS,CAAC;gBAEX,OAAO,GAAE,0BAA+B;IAK/D,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC;CAsCzD"}
@@ -40,9 +40,11 @@ export function createDiskHealthIndicatorProvider(options = {}) {
40
40
  /** Health indicator that inspects free space for one filesystem path. */
41
41
  export class DiskHealthIndicator {
42
42
  key;
43
+ readiness;
43
44
  constructor(options = {}) {
44
45
  this.options = options;
45
46
  this.key = options.key;
47
+ this.readiness = options.readiness;
46
48
  }
47
49
  async check(key) {
48
50
  const indicatorKey = resolveIndicatorKey('disk', this.options.key ?? key);
@@ -25,6 +25,8 @@ export interface DrizzleHealthIndicatorOptions {
25
25
  key?: string;
26
26
  ping?: () => Promise<unknown> | unknown;
27
27
  query?: unknown;
28
+ /** Whether this indicator participates in `/ready`. Defaults to `true`. */
29
+ readiness?: boolean;
28
30
  timeoutMs?: number;
29
31
  }
30
32
  /**
@@ -45,8 +47,11 @@ export declare function createDrizzleHealthIndicatorProvider(options?: Omit<Driz
45
47
  export declare class DrizzleHealthIndicator implements HealthIndicator {
46
48
  private readonly options;
47
49
  readonly key: string | undefined;
50
+ readonly readiness: boolean | undefined;
51
+ private pendingProbeSettlement;
48
52
  constructor(options?: DrizzleHealthIndicatorOptions);
49
53
  check(key: string): Promise<HealthIndicatorResult>;
54
+ getPendingHealthCheckSettlement(): Promise<void> | undefined;
50
55
  }
51
56
  export {};
52
57
  //# sourceMappingURL=drizzle.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"drizzle.d.ts","sourceRoot":"","sources":["../../src/indicators/drizzle.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,QAAQ,EAAE,MAAM,YAAY,CAAC;AAGrD,OAAO,KAAK,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAK1E,UAAU,kBAAkB;IAC1B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAChD;AAED,UAAU,4BAA4B;IACpC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,MAAM,EAAE;QACN,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,SAAS,GAAG,UAAU,GAAG,WAAW,CAAC;KAC9C,CAAC;IACF,SAAS,EAAE;QACT,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,OAAO,GAAG,WAAW,CAAC;KAC/B,CAAC;CACH;AAED,UAAU,yBAAyB;IACjC,4BAA4B,CAAC,EAAE,MAAM,4BAA4B,CAAC;IAClE,OAAO,CAAC,EAAE,MAAM,kBAAkB,GAAG,OAAO,CAAC;CAC9C;AAED,gEAAgE;AAChE,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,EAAE,kBAAkB,CAAC;IAC9B,cAAc,CAAC,EAAE,yBAAyB,CAAC;IAC3C,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IACxC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA8DD;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAAC,OAAO,GAAE,6BAAkC,GAAG,eAAe,CAEzG;AAED;;;;;GAKG;AACH,wBAAgB,oCAAoC,CAAC,OAAO,GAAE,IAAI,CAAC,6BAA6B,EAAE,UAAU,GAAG,gBAAgB,CAAM,GAAG,QAAQ,CAkB/I;AAED,iHAAiH;AACjH,qBAAa,sBAAuB,YAAW,eAAe;IAGhD,OAAO,CAAC,QAAQ,CAAC,OAAO;IAFpC,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;gBAEJ,OAAO,GAAE,6BAAkC;IAIlE,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC;CAiCzD"}
1
+ {"version":3,"file":"drizzle.d.ts","sourceRoot":"","sources":["../../src/indicators/drizzle.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,QAAQ,EAAE,MAAM,YAAY,CAAC;AAErD,OAAO,KAAK,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAM1E,UAAU,kBAAkB;IAC1B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAChD;AAED,UAAU,4BAA4B;IACpC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,MAAM,EAAE;QACN,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,SAAS,GAAG,UAAU,GAAG,WAAW,CAAC;KAC9C,CAAC;IACF,SAAS,EAAE;QACT,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,OAAO,GAAG,WAAW,CAAC;KAC/B,CAAC;CACH;AAED,UAAU,yBAAyB;IACjC,4BAA4B,CAAC,EAAE,MAAM,4BAA4B,CAAC;IAClE,OAAO,CAAC,EAAE,MAAM,kBAAkB,GAAG,OAAO,CAAC;CAC9C;AAED,gEAAgE;AAChE,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,EAAE,kBAAkB,CAAC;IAC9B,cAAc,CAAC,EAAE,yBAAyB,CAAC;IAC3C,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IACxC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,2EAA2E;IAC3E,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA8DD;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAAC,OAAO,GAAE,6BAAkC,GAAG,eAAe,CAEzG;AAED;;;;;GAKG;AACH,wBAAgB,oCAAoC,CAAC,OAAO,GAAE,IAAI,CAAC,6BAA6B,EAAE,UAAU,GAAG,gBAAgB,CAAM,GAAG,QAAQ,CAkB/I;AAED,iHAAiH;AACjH,qBAAa,sBAAuB,YAAW,eAAe;IAKhD,OAAO,CAAC,QAAQ,CAAC,OAAO;IAJpC,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,QAAQ,CAAC,SAAS,EAAE,OAAO,GAAG,SAAS,CAAC;IACxC,OAAO,CAAC,sBAAsB,CAA4B;gBAE7B,OAAO,GAAE,6BAAkC;IAKlE,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC;IAoCxD,+BAA+B,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,SAAS;CAG7D"}
@@ -1,5 +1,5 @@
1
1
  import { optional } from '@fluojs/di';
2
- import { createDownResult, createUpResult, resolveIndicatorKey, resolveIndicatorTimeoutMs, throwHealthCheckError, withIndicatorTimeout } from './utils.js';
2
+ import { createDownResult, createUpResult, resolveIndicatorKey, resolveIndicatorTimeoutMs, throwHealthCheckError, waitForIndicatorProbeSettlement, withIndicatorTimeout } from './utils.js';
3
3
  const DRIZZLE_DATABASE = Symbol.for('fluo.drizzle.database');
4
4
  const DRIZZLE_HANDLE_PROVIDER = Symbol.for('fluo.drizzle.handle-provider');
5
5
 
@@ -12,7 +12,7 @@ async function runDrizzlePing(options) {
12
12
  await options.ping();
13
13
  return;
14
14
  }
15
- const database = options.database ?? resolveCurrentDatabase(options.handleProvider);
15
+ const database = resolveCurrentDatabase(options.handleProvider) ?? options.database;
16
16
  if (!database || typeof database.execute !== 'function') {
17
17
  throw new Error('Drizzle indicator requires an execute-capable database handle or a ping callback.');
18
18
  }
@@ -79,9 +79,12 @@ export function createDrizzleHealthIndicatorProvider(options = {}) {
79
79
  /** Health indicator that maps Drizzle lifecycle state and probes connectivity with an execute-capable handle. */
80
80
  export class DrizzleHealthIndicator {
81
81
  key;
82
+ readiness;
83
+ pendingProbeSettlement;
82
84
  constructor(options = {}) {
83
85
  this.options = options;
84
86
  this.key = options.key;
87
+ this.readiness = options.readiness;
85
88
  }
86
89
  async check(key) {
87
90
  const indicatorKey = resolveIndicatorKey('drizzle', this.options.key ?? key);
@@ -92,7 +95,9 @@ export class DrizzleHealthIndicator {
92
95
  if (lifecycleDownResult) {
93
96
  throwHealthCheckError('Drizzle health check failed.', lifecycleDownResult);
94
97
  }
95
- await withIndicatorTimeout(runDrizzlePing(this.options), timeoutMs, indicatorKey);
98
+ const probe = runDrizzlePing(this.options);
99
+ this.pendingProbeSettlement = waitForIndicatorProbeSettlement(probe);
100
+ await withIndicatorTimeout(probe, timeoutMs, indicatorKey);
96
101
  return createUpResult(indicatorKey, snapshot ? {
97
102
  details: snapshot.details,
98
103
  healthStatus: snapshot.health.status,
@@ -105,4 +110,7 @@ export class DrizzleHealthIndicator {
105
110
  throwHealthCheckError('Drizzle health check failed.', createDownResult(indicatorKey, error instanceof Error ? error.message : 'Drizzle health check failed.'));
106
111
  }
107
112
  }
113
+ getPendingHealthCheckSettlement() {
114
+ return this.pendingProbeSettlement;
115
+ }
108
116
  }
@@ -6,6 +6,8 @@ export interface HttpHealthIndicatorOptions {
6
6
  headers?: Record<string, string>;
7
7
  key?: string;
8
8
  method?: string;
9
+ /** Whether this indicator participates in `/ready`. Defaults to `true`. */
10
+ readiness?: boolean;
9
11
  timeoutMs?: number;
10
12
  url: string;
11
13
  }
@@ -27,6 +29,7 @@ export declare function createHttpHealthIndicatorProvider(options: HttpHealthInd
27
29
  export declare class HttpHealthIndicator implements HealthIndicator {
28
30
  private readonly options;
29
31
  readonly key: string | undefined;
32
+ readonly readiness: boolean | undefined;
30
33
  constructor(options: HttpHealthIndicatorOptions);
31
34
  check(key: string): Promise<HealthIndicatorResult>;
32
35
  }
@@ -1 +1 @@
1
- {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/indicators/http.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAI3C,OAAO,KAAK,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAE1E,wDAAwD;AACxD,MAAM,WAAW,0BAA0B;IACzC,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,GAAG,CAAC,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC;IAC5E,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;CACb;AA8BD;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,0BAA0B,GAAG,eAAe,CAE9F;AAED;;;;;GAKG;AACH,wBAAgB,iCAAiC,CAAC,OAAO,EAAE,0BAA0B,GAAG,QAAQ,CAO/F;AAED,6EAA6E;AAC7E,qBAAa,mBAAoB,YAAW,eAAe;IAG7C,OAAO,CAAC,QAAQ,CAAC,OAAO;IAFpC,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;gBAEJ,OAAO,EAAE,0BAA0B;IAI1D,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC;CAmDzD"}
1
+ {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/indicators/http.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAI3C,OAAO,KAAK,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAE1E,wDAAwD;AACxD,MAAM,WAAW,0BAA0B;IACzC,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,GAAG,CAAC,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC;IAC5E,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2EAA2E;IAC3E,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;CACb;AA8BD;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,0BAA0B,GAAG,eAAe,CAE9F;AAED;;;;;GAKG;AACH,wBAAgB,iCAAiC,CAAC,OAAO,EAAE,0BAA0B,GAAG,QAAQ,CAO/F;AAED,6EAA6E;AAC7E,qBAAa,mBAAoB,YAAW,eAAe;IAI7C,OAAO,CAAC,QAAQ,CAAC,OAAO;IAHpC,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,QAAQ,CAAC,SAAS,EAAE,OAAO,GAAG,SAAS,CAAC;gBAEX,OAAO,EAAE,0BAA0B;IAK1D,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC;CAmDzD"}
@@ -51,9 +51,11 @@ export function createHttpHealthIndicatorProvider(options) {
51
51
  /** Health indicator that probes an upstream HTTP endpoint with `fetch()`. */
52
52
  export class HttpHealthIndicator {
53
53
  key;
54
+ readiness;
54
55
  constructor(options) {
55
56
  this.options = options;
56
57
  this.key = options.key;
58
+ this.readiness = options.readiness;
57
59
  }
58
60
  async check(key) {
59
61
  const indicatorKey = resolveIndicatorKey('http', this.options.key ?? key);
@@ -16,6 +16,8 @@ export interface MemoryHealthIndicatorOptions {
16
16
  heapUsedThresholdRatio?: number;
17
17
  key?: string;
18
18
  memoryUsage?: MemoryUsageSampler;
19
+ /** Whether this indicator participates in `/ready`. Defaults to `true`. */
20
+ readiness?: boolean;
19
21
  rssThresholdBytes?: number;
20
22
  }
21
23
  /**
@@ -36,6 +38,7 @@ export declare function createMemoryHealthIndicatorProvider(options?: MemoryHeal
36
38
  export declare class MemoryHealthIndicator implements HealthIndicator {
37
39
  private readonly options;
38
40
  readonly key: string | undefined;
41
+ readonly readiness: boolean | undefined;
39
42
  constructor(options?: MemoryHealthIndicatorOptions);
40
43
  check(key: string): Promise<HealthIndicatorResult>;
41
44
  }
@@ -1 +1 @@
1
- {"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../src/indicators/memory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAI3C,OAAO,KAAK,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAE1E,mDAAmD;AACnD,MAAM,WAAW,mBAAmB;IAClC,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,8EAA8E;AAC9E,MAAM,MAAM,kBAAkB,GAAG,MAAM,mBAAmB,CAAC;AAE3D,4DAA4D;AAC5D,MAAM,WAAW,4BAA4B;IAC3C,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAYD;;;;;GAKG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,GAAE,4BAAiC,GAAG,eAAe,CAEvG;AAED;;;;;GAKG;AACH,wBAAgB,mCAAmC,CAAC,OAAO,GAAE,4BAAiC,GAAG,QAAQ,CAOxG;AAED,qEAAqE;AACrE,qBAAa,qBAAsB,YAAW,eAAe;IAG/C,OAAO,CAAC,QAAQ,CAAC,OAAO;IAFpC,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;gBAEJ,OAAO,GAAE,4BAAiC;IAIjE,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC;CAiCzD"}
1
+ {"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../src/indicators/memory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAI3C,OAAO,KAAK,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAE1E,mDAAmD;AACnD,MAAM,WAAW,mBAAmB;IAClC,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,8EAA8E;AAC9E,MAAM,MAAM,kBAAkB,GAAG,MAAM,mBAAmB,CAAC;AAE3D,4DAA4D;AAC5D,MAAM,WAAW,4BAA4B;IAC3C,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjC,2EAA2E;IAC3E,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAYD;;;;;GAKG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,GAAE,4BAAiC,GAAG,eAAe,CAEvG;AAED;;;;;GAKG;AACH,wBAAgB,mCAAmC,CAAC,OAAO,GAAE,4BAAiC,GAAG,QAAQ,CAOxG;AAED,qEAAqE;AACrE,qBAAa,qBAAsB,YAAW,eAAe;IAI/C,OAAO,CAAC,QAAQ,CAAC,OAAO;IAHpC,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,QAAQ,CAAC,SAAS,EAAE,OAAO,GAAG,SAAS,CAAC;gBAEX,OAAO,GAAE,4BAAiC;IAKjE,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC;CAiCzD"}
@@ -42,9 +42,11 @@ export function createMemoryHealthIndicatorProvider(options = {}) {
42
42
  /** Health indicator that checks local process heap and RSS usage. */
43
43
  export class MemoryHealthIndicator {
44
44
  key;
45
+ readiness;
45
46
  constructor(options = {}) {
46
47
  this.options = options;
47
48
  this.key = options.key;
49
+ this.readiness = options.readiness;
48
50
  }
49
51
  async check(key) {
50
52
  const indicatorKey = resolveIndicatorKey('memory', this.options.key ?? key);
@@ -34,6 +34,8 @@ export interface PrismaHealthIndicatorOptions {
34
34
  name?: string;
35
35
  /** Custom ping callback for manual probes or tests. Lifecycle state is only mapped when `service` is available. */
36
36
  ping?: () => Promise<unknown> | unknown;
37
+ /** Whether this indicator participates in `/ready`. Defaults to `true`. */
38
+ readiness?: boolean;
37
39
  /** Lifecycle-aware Prisma service/facade handle, usually resolved from `getPrismaServiceToken(name)`. */
38
40
  service?: PrismaServiceLike;
39
41
  /** Explicit Prisma service token to resolve when using `createPrismaHealthIndicatorProvider(...)`. */
@@ -64,8 +66,11 @@ export declare function createPrismaHealthIndicatorProvider(options?: Omit<Prism
64
66
  export declare class PrismaHealthIndicator implements HealthIndicator {
65
67
  private readonly options;
66
68
  readonly key: string | undefined;
69
+ readonly readiness: boolean | undefined;
70
+ private pendingProbeSettlement;
67
71
  constructor(options?: PrismaHealthIndicatorOptions);
68
72
  check(key: string): Promise<HealthIndicatorResult>;
73
+ getPendingHealthCheckSettlement(): Promise<void> | undefined;
69
74
  }
70
75
  export {};
71
76
  //# sourceMappingURL=prisma.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"prisma.d.ts","sourceRoot":"","sources":["../../src/indicators/prisma.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAY,KAAK,QAAQ,EAAE,MAAM,YAAY,CAAC;AAGrD,OAAO,KAAK,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAK1E,UAAU,gBAAgB;IACxB,WAAW,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACvD,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACxD,SAAS,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACrD,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACvD;AAED,UAAU,2BAA2B;IACnC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,MAAM,EAAE;QACN,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,SAAS,GAAG,UAAU,GAAG,WAAW,CAAC;KAC9C,CAAC;IACF,SAAS,EAAE;QACT,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,OAAO,GAAG,WAAW,CAAC;KAC/B,CAAC;CACH;AAED,UAAU,iBAAiB;IACzB,4BAA4B,CAAC,EAAE,MAAM,2BAA2B,CAAC;IACjE,OAAO,CAAC,EAAE,MAAM,gBAAgB,GAAG,OAAO,CAAC;CAC5C;AAED,+DAA+D;AAC/D,MAAM,WAAW,4BAA4B;IAC3C,qFAAqF;IACrF,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,kGAAkG;IAClG,WAAW,CAAC,EAAE,KAAK,CAAC;IACpB,gGAAgG;IAChG,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,kGAAkG;IAClG,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mHAAmH;IACnH,IAAI,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IACxC,yGAAyG;IACzG,OAAO,CAAC,EAAE,iBAAiB,CAAC;IAC5B,sGAAsG;IACtG,YAAY,CAAC,EAAE,KAAK,CAAC;IACrB,2EAA2E;IAC3E,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA+HD;;;;;GAKG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,GAAE,4BAAiC,GAAG,eAAe,CAEvG;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mCAAmC,CACjD,OAAO,GAAE,IAAI,CAAC,4BAA4B,EAAE,QAAQ,GAAG,SAAS,CAAM,GACrE,QAAQ,CA8BV;AAED,uGAAuG;AACvG,qBAAa,qBAAsB,YAAW,eAAe;IAG/C,OAAO,CAAC,QAAQ,CAAC,OAAO;IAFpC,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;gBAEJ,OAAO,GAAE,4BAAiC;IAIjE,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC;CA2BzD"}
1
+ {"version":3,"file":"prisma.d.ts","sourceRoot":"","sources":["../../src/indicators/prisma.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAY,KAAK,QAAQ,EAAE,MAAM,YAAY,CAAC;AAErD,OAAO,KAAK,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAM1E,UAAU,gBAAgB;IACxB,WAAW,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACvD,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACxD,SAAS,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACrD,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACvD;AAED,UAAU,2BAA2B;IACnC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,MAAM,EAAE;QACN,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,SAAS,GAAG,UAAU,GAAG,WAAW,CAAC;KAC9C,CAAC;IACF,SAAS,EAAE;QACT,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,OAAO,GAAG,WAAW,CAAC;KAC/B,CAAC;CACH;AAED,UAAU,iBAAiB;IACzB,4BAA4B,CAAC,EAAE,MAAM,2BAA2B,CAAC;IACjE,OAAO,CAAC,EAAE,MAAM,gBAAgB,GAAG,OAAO,CAAC;CAC5C;AAED,+DAA+D;AAC/D,MAAM,WAAW,4BAA4B;IAC3C,qFAAqF;IACrF,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,kGAAkG;IAClG,WAAW,CAAC,EAAE,KAAK,CAAC;IACpB,gGAAgG;IAChG,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,kGAAkG;IAClG,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mHAAmH;IACnH,IAAI,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IACxC,2EAA2E;IAC3E,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,yGAAyG;IACzG,OAAO,CAAC,EAAE,iBAAiB,CAAC;IAC5B,sGAAsG;IACtG,YAAY,CAAC,EAAE,KAAK,CAAC;IACrB,2EAA2E;IAC3E,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA+HD;;;;;GAKG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,GAAE,4BAAiC,GAAG,eAAe,CAEvG;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mCAAmC,CACjD,OAAO,GAAE,IAAI,CAAC,4BAA4B,EAAE,QAAQ,GAAG,SAAS,CAAM,GACrE,QAAQ,CA8BV;AAED,uGAAuG;AACvG,qBAAa,qBAAsB,YAAW,eAAe;IAK/C,OAAO,CAAC,QAAQ,CAAC,OAAO;IAJpC,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,QAAQ,CAAC,SAAS,EAAE,OAAO,GAAG,SAAS,CAAC;IACxC,OAAO,CAAC,sBAAsB,CAA4B;gBAE7B,OAAO,GAAE,4BAAiC;IAKjE,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC;IA8BxD,+BAA+B,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,SAAS;CAG7D"}
@@ -1,5 +1,5 @@
1
1
  import { optional } from '@fluojs/di';
2
- import { createDownResult, createUpResult, resolveIndicatorKey, resolveIndicatorTimeoutMs, throwHealthCheckError, withIndicatorTimeout } from './utils.js';
2
+ import { createDownResult, createUpResult, resolveIndicatorKey, resolveIndicatorTimeoutMs, throwHealthCheckError, waitForIndicatorProbeSettlement, withIndicatorTimeout } from './utils.js';
3
3
  const PRISMA_CLIENT = Symbol.for('fluo.prisma.client');
4
4
  const PRISMA_SERVICE = Symbol.for('fluo.prisma.service');
5
5
 
@@ -137,9 +137,12 @@ export function createPrismaHealthIndicatorProvider(options = {}) {
137
137
  /** Health indicator that maps Prisma lifecycle status and probes connectivity with a trivial query. */
138
138
  export class PrismaHealthIndicator {
139
139
  key;
140
+ readiness;
141
+ pendingProbeSettlement;
140
142
  constructor(options = {}) {
141
143
  this.options = options;
142
144
  this.key = options.key;
145
+ this.readiness = options.readiness;
143
146
  }
144
147
  async check(key) {
145
148
  const indicatorKey = resolveIndicatorKey('prisma', this.options.key ?? key);
@@ -150,7 +153,9 @@ export class PrismaHealthIndicator {
150
153
  if (lifecycleDownResult) {
151
154
  throwHealthCheckError('Prisma health check failed.', lifecycleDownResult);
152
155
  }
153
- await withIndicatorTimeout(runPrismaPing(this.options), timeoutMs, indicatorKey);
156
+ const probe = runPrismaPing(this.options);
157
+ this.pendingProbeSettlement = waitForIndicatorProbeSettlement(probe);
158
+ await withIndicatorTimeout(probe, timeoutMs, indicatorKey);
154
159
  return createUpResult(indicatorKey, createPrismaLifecycleUpDetails(snapshot));
155
160
  } catch (error) {
156
161
  if (error instanceof Error && error.name === 'HealthCheckError') {
@@ -159,4 +164,7 @@ export class PrismaHealthIndicator {
159
164
  throwHealthCheckError('Prisma health check failed.', createDownResult(indicatorKey, error instanceof Error ? error.message : 'Prisma health check failed.'));
160
165
  }
161
166
  }
167
+ getPendingHealthCheckSettlement() {
168
+ return this.pendingProbeSettlement;
169
+ }
162
170
  }
@@ -22,6 +22,8 @@ export interface RedisHealthIndicatorOptions {
22
22
  key?: string;
23
23
  /** Custom ping callback for manual probes or tests. Lifecycle state is only mapped when `client.status` is available. */
24
24
  ping?: () => Promise<unknown> | unknown;
25
+ /** Whether this indicator participates in `/ready`. Defaults to `true`. */
26
+ readiness?: boolean;
25
27
  /** Maximum time to wait for the ping operation. Defaults to `2_000` ms. */
26
28
  timeoutMs?: number;
27
29
  }
@@ -39,6 +41,10 @@ export declare function createRedisHealthIndicator(options?: RedisHealthIndicato
39
41
  * default-vs-named client lifecycle boundary from `@fluojs/redis` while keeping the
40
42
  * Redis-specific peer dependency isolated to `@fluojs/terminus/redis`.
41
43
  *
44
+ * The Redis client token is a required dependency of the indicator, so it must be visible in
45
+ * the Terminus module scope. Pass the owning module through `TerminusModule.forRoot({ imports })`;
46
+ * a missing registration fails at bootstrap rather than degrading `/health` and `/ready` afterwards.
47
+ *
42
48
  * @param options Optional named-client hint, timeout, key override, or custom ping callback.
43
49
  * @returns A factory provider that exposes `RedisHealthIndicator` from the DI container.
44
50
  */
@@ -47,8 +53,11 @@ export declare function createRedisHealthIndicatorProvider(options?: Omit<RedisH
47
53
  export declare class RedisHealthIndicator implements HealthIndicator {
48
54
  private readonly options;
49
55
  readonly key: string | undefined;
56
+ readonly readiness: boolean | undefined;
57
+ private pendingProbeSettlement;
50
58
  constructor(options?: RedisHealthIndicatorOptions);
51
59
  check(key: string): Promise<HealthIndicatorResult>;
60
+ getPendingHealthCheckSettlement(): Promise<void> | undefined;
52
61
  }
53
62
  export {};
54
63
  //# sourceMappingURL=redis.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"redis.d.ts","sourceRoot":"","sources":["../../src/indicators/redis.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAA+E,KAAK,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAG1I,OAAO,KAAK,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAE1E,UAAU,eAAe;IACvB,IAAI,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9B,MAAM,CAAC,EAAE,uBAAuB,CAAC,QAAQ,CAAC,CAAC;CAC5C;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,2BAA2B;IAC1C,oGAAoG;IACpG,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB,gGAAgG;IAChG,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,+FAA+F;IAC/F,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,yHAAyH;IACzH,IAAI,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IACxC,2EAA2E;IAC3E,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAoED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,GAAE,2BAAgC,GAAG,eAAe,CAErG;AAED;;;;;;;;;GASG;AACH,wBAAgB,kCAAkC,CAAC,OAAO,GAAE,IAAI,CAAC,2BAA2B,EAAE,QAAQ,CAAM,GAAG,QAAQ,CAQtH;AAED,4GAA4G;AAC5G,qBAAa,oBAAqB,YAAW,eAAe;IAG9C,OAAO,CAAC,QAAQ,CAAC,OAAO;IAFpC,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;gBAEJ,OAAO,GAAE,2BAAgC;IAIhE,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC;CAwBzD"}
1
+ {"version":3,"file":"redis.d.ts","sourceRoot":"","sources":["../../src/indicators/redis.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAA+E,KAAK,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAE1I,OAAO,KAAK,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAG1E,UAAU,eAAe;IACvB,IAAI,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9B,MAAM,CAAC,EAAE,uBAAuB,CAAC,QAAQ,CAAC,CAAC;CAC5C;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,2BAA2B;IAC1C,oGAAoG;IACpG,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB,gGAAgG;IAChG,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,+FAA+F;IAC/F,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,yHAAyH;IACzH,IAAI,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IACxC,2EAA2E;IAC3E,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,2EAA2E;IAC3E,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAoED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,GAAE,2BAAgC,GAAG,eAAe,CAErG;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kCAAkC,CAAC,OAAO,GAAE,IAAI,CAAC,2BAA2B,EAAE,QAAQ,CAAM,GAAG,QAAQ,CAQtH;AAED,4GAA4G;AAC5G,qBAAa,oBAAqB,YAAW,eAAe;IAK9C,OAAO,CAAC,QAAQ,CAAC,OAAO;IAJpC,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,QAAQ,CAAC,SAAS,EAAE,OAAO,GAAG,SAAS,CAAC;IACxC,OAAO,CAAC,sBAAsB,CAA4B;gBAE7B,OAAO,GAAE,2BAAgC;IAKhE,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC;IA2BxD,+BAA+B,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,SAAS;CAG7D"}
@@ -1,5 +1,5 @@
1
1
  import { createRedisPlatformStatusSnapshot, getRedisClientToken, getRedisComponentId } from '@fluojs/redis';
2
- import { createDownResult, createUpResult, resolveIndicatorKey, resolveIndicatorTimeoutMs, throwHealthCheckError, withIndicatorTimeout } from './utils.js';
2
+ import { createDownResult, createUpResult, resolveIndicatorKey, resolveIndicatorTimeoutMs, throwHealthCheckError, waitForIndicatorProbeSettlement, withIndicatorTimeout } from './utils.js';
3
3
 
4
4
  /**
5
5
  * Options for probing Redis connectivity.
@@ -74,6 +74,10 @@ export function createRedisHealthIndicator(options = {}) {
74
74
  * default-vs-named client lifecycle boundary from `@fluojs/redis` while keeping the
75
75
  * Redis-specific peer dependency isolated to `@fluojs/terminus/redis`.
76
76
  *
77
+ * The Redis client token is a required dependency of the indicator, so it must be visible in
78
+ * the Terminus module scope. Pass the owning module through `TerminusModule.forRoot({ imports })`;
79
+ * a missing registration fails at bootstrap rather than degrading `/health` and `/ready` afterwards.
80
+ *
77
81
  * @param options Optional named-client hint, timeout, key override, or custom ping callback.
78
82
  * @returns A factory provider that exposes `RedisHealthIndicator` from the DI container.
79
83
  */
@@ -92,9 +96,12 @@ export function createRedisHealthIndicatorProvider(options = {}) {
92
96
  /** Health indicator that maps Redis lifecycle status and checks reachability with a ping-like operation. */
93
97
  export class RedisHealthIndicator {
94
98
  key;
99
+ readiness;
100
+ pendingProbeSettlement;
95
101
  constructor(options = {}) {
96
102
  this.options = options;
97
103
  this.key = options.key;
104
+ this.readiness = options.readiness;
98
105
  }
99
106
  async check(key) {
100
107
  const indicatorKey = resolveIndicatorKey('redis', this.options.key ?? key);
@@ -104,7 +111,9 @@ export class RedisHealthIndicator {
104
111
  if (lifecycleDownResult) {
105
112
  throwHealthCheckError('Redis health check failed.', lifecycleDownResult);
106
113
  }
107
- await withIndicatorTimeout(runRedisPing(this.options), timeoutMs, indicatorKey);
114
+ const probe = runRedisPing(this.options);
115
+ this.pendingProbeSettlement = waitForIndicatorProbeSettlement(probe);
116
+ await withIndicatorTimeout(probe, timeoutMs, indicatorKey);
108
117
  return createUpResult(indicatorKey, createRedisLifecycleUpDetails(this.options));
109
118
  } catch (error) {
110
119
  if (error instanceof Error && error.name === 'HealthCheckError') {
@@ -113,4 +122,7 @@ export class RedisHealthIndicator {
113
122
  throwHealthCheckError('Redis health check failed.', createDownResult(indicatorKey, error instanceof Error ? error.message : 'Redis health check failed.'));
114
123
  }
115
124
  }
125
+ getPendingHealthCheckSettlement() {
126
+ return this.pendingProbeSettlement;
127
+ }
116
128
  }
@@ -39,6 +39,13 @@ export declare function createDownResult(key: string, message: string, details?:
39
39
  * @returns The original promise result when it finishes in time.
40
40
  */
41
41
  export declare function withIndicatorTimeout<T>(promise: Promise<T>, timeoutMs: number, indicatorName: string): Promise<T>;
42
+ /**
43
+ * Convert a probe promise into a settlement signal that never rejects.
44
+ *
45
+ * @param promise Probe promise whose completion releases its execution ownership.
46
+ * @returns A promise that resolves after the probe either fulfills or rejects.
47
+ */
48
+ export declare function waitForIndicatorProbeSettlement(promise: Promise<unknown>): Promise<void>;
42
49
  /**
43
50
  * Resolve the effective key used in reports for one indicator execution.
44
51
  *
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/indicators/utils.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAEzD,6EAA6E;AAC7E,MAAM,WAAW,uBAAuB;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAUD;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,gBAAgB,EAAE,MAAM,EACxB,aAAa,EAAE,MAAM,GACpB,MAAM,CAER;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAAG,qBAAqB,CAOxG;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACpC,qBAAqB,CAQvB;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,EACpC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EACnB,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,CAAC,CAAC,CAyBZ;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,MAAM,EACnB,GAAG,EAAE,MAAM,GAAG,SAAS,GACtB,MAAM,CAER;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,qBAAqB,GAAG,KAAK,CAE3F"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/indicators/utils.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAEzD,6EAA6E;AAC7E,MAAM,WAAW,uBAAuB;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAUD;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,gBAAgB,EAAE,MAAM,EACxB,aAAa,EAAE,MAAM,GACpB,MAAM,CAER;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAAG,qBAAqB,CAOxG;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACpC,qBAAqB,CAQvB;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,EACpC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EACnB,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,CAAC,CAAC,CAyBZ;AAED;;;;;GAKG;AACH,wBAAgB,+BAA+B,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAKxF;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,MAAM,EACnB,GAAG,EAAE,MAAM,GAAG,SAAS,GACtB,MAAM,CAER;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,qBAAqB,GAAG,KAAK,CAE3F"}
@@ -85,6 +85,16 @@ export function withIndicatorTimeout(promise, timeoutMs, indicatorName) {
85
85
  });
86
86
  }
87
87
 
88
+ /**
89
+ * Convert a probe promise into a settlement signal that never rejects.
90
+ *
91
+ * @param promise Probe promise whose completion releases its execution ownership.
92
+ * @returns A promise that resolves after the probe either fulfills or rejects.
93
+ */
94
+ export function waitForIndicatorProbeSettlement(promise) {
95
+ return promise.then(() => undefined, () => undefined);
96
+ }
97
+
88
98
  /**
89
99
  * Resolve the effective key used in reports for one indicator execution.
90
100
  *
package/dist/module.d.ts CHANGED
@@ -5,6 +5,13 @@ export declare class TerminusModule {
5
5
  /**
6
6
  * Register Terminus health indicators and readiness hooks.
7
7
  *
8
+ * DI-backed indicator providers resolve inside the Terminus module scope. A named Redis token
9
+ * is required, so its owner module must be listed in `imports`; otherwise bootstrap fails with
10
+ * `MODULE_VISIBILITY_ERROR`. Prisma and Drizzle owner tokens are optional only when absent from
11
+ * the bootstrap graph: omitting their modules then lets the application bootstrap and their
12
+ * indicators report `down` on health checks. Existing sibling owner tokens still require
13
+ * `imports`; optional injection never bypasses module visibility.
14
+ *
8
15
  * @example
9
16
  * ```ts
10
17
  * import { MemoryHealthIndicator } from '@fluojs/terminus/node';
@@ -14,7 +21,17 @@ export declare class TerminusModule {
14
21
  * });
15
22
  * ```
16
23
  *
17
- * @param options Terminus health indicator and readiness configuration.
24
+ * @example
25
+ * ```ts
26
+ * const prismaModule = PrismaModule.forRoot({ client });
27
+ *
28
+ * TerminusModule.forRoot({
29
+ * imports: [prismaModule],
30
+ * indicatorProviders: [createPrismaHealthIndicatorProvider({ key: 'prisma' })],
31
+ * });
32
+ * ```
33
+ *
34
+ * @param options Terminus health indicator, dependency import, and readiness configuration.
18
35
  * @returns A runtime module exposing health endpoints and `TerminusHealthService`.
19
36
  */
20
37
  static forRoot(options?: TerminusModuleOptions): ModuleType;
@@ -1 +1 @@
1
- {"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAGA,OAAO,EAGL,KAAK,UAAU,EAKhB,MAAM,iBAAiB,CAAC;AAIzB,OAAO,KAAK,EAA4D,qBAAqB,EAAE,MAAM,YAAY,CAAC;AA6RlH,uFAAuF;AACvF,qBAAa,cAAc;IACzB;;;;;;;;;;;;;;OAcG;IACH,MAAM,CAAC,OAAO,CAAC,OAAO,GAAE,qBAA0B,GAAG,UAAU;CAGhE"}
1
+ {"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAGA,OAAO,EAGL,KAAK,UAAU,EAKhB,MAAM,iBAAiB,CAAC;AAIzB,OAAO,KAAK,EAA4D,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAkSlH,uFAAuF;AACvF,qBAAa,cAAc;IACzB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA+BG;IACH,MAAM,CAAC,OAAO,CAAC,OAAO,GAAE,qBAA0B,GAAG,UAAU;CAGhE"}
package/dist/module.js CHANGED
@@ -23,6 +23,7 @@ function createTerminusProviders(options = {}) {
23
23
  execution: {
24
24
  ...(options.execution ?? {})
25
25
  },
26
+ imports: [...(options.imports ?? [])],
26
27
  indicators: copyIndicators(options.indicators),
27
28
  indicatorProviders: copyProviders(options.indicatorProviders),
28
29
  readinessChecks: [...(options.readinessChecks ?? [])]
@@ -149,7 +150,9 @@ function withPlatformDiagnostics(report, health, readiness) {
149
150
  }
150
151
  function createTerminusRuntimeModule(options = {}) {
151
152
  const readinessChecks = [...(options.readinessChecks ?? [])];
153
+ const terminusImports = [...(options.imports ?? [])];
152
154
  const healthModule = HealthModule.forRoot({
155
+ endpointMiddleware: options.endpointMiddleware,
153
156
  healthCheck: async ctx => {
154
157
  const healthService = await ctx.container.resolve(TerminusHealthService);
155
158
  const platformShell = await ctx.container.resolve(PLATFORM_SHELL);
@@ -169,9 +172,11 @@ function createTerminusRuntimeModule(options = {}) {
169
172
  class TerminusRuntimeModule {}
170
173
  return defineModule(TerminusRuntimeModule, {
171
174
  exports: [TERMINUS_HEALTH_INDICATORS, TERMINUS_INDICATOR_PROVIDER_TOKENS, TerminusHealthService],
172
- imports: [healthModule],
175
+ imports: [healthModule, ...terminusImports],
173
176
  providers: [...createTerminusProviders({
174
177
  execution: options.execution,
178
+ endpointMiddleware: options.endpointMiddleware,
179
+ imports: terminusImports,
175
180
  indicatorProviders: options.indicatorProviders,
176
181
  indicators: options.indicators,
177
182
  path: options.path,
@@ -185,7 +190,7 @@ function createTerminusRuntimeModule(options = {}) {
185
190
  onApplicationBootstrap() {
186
191
  healthModule.addReadinessCheck(async ctx => {
187
192
  const platformShell = await ctx.container.resolve(PLATFORM_SHELL);
188
- const [indicatorHealthy, readiness] = await Promise.all([healthService.isHealthy(), platformShell.ready()]);
193
+ const [indicatorHealthy, readiness] = await Promise.all([healthService.isReady(), platformShell.ready()]);
189
194
  return indicatorHealthy && readiness.status === 'ready';
190
195
  });
191
196
  }
@@ -200,6 +205,13 @@ export class TerminusModule {
200
205
  /**
201
206
  * Register Terminus health indicators and readiness hooks.
202
207
  *
208
+ * DI-backed indicator providers resolve inside the Terminus module scope. A named Redis token
209
+ * is required, so its owner module must be listed in `imports`; otherwise bootstrap fails with
210
+ * `MODULE_VISIBILITY_ERROR`. Prisma and Drizzle owner tokens are optional only when absent from
211
+ * the bootstrap graph: omitting their modules then lets the application bootstrap and their
212
+ * indicators report `down` on health checks. Existing sibling owner tokens still require
213
+ * `imports`; optional injection never bypasses module visibility.
214
+ *
203
215
  * @example
204
216
  * ```ts
205
217
  * import { MemoryHealthIndicator } from '@fluojs/terminus/node';
@@ -209,7 +221,17 @@ export class TerminusModule {
209
221
  * });
210
222
  * ```
211
223
  *
212
- * @param options Terminus health indicator and readiness configuration.
224
+ * @example
225
+ * ```ts
226
+ * const prismaModule = PrismaModule.forRoot({ client });
227
+ *
228
+ * TerminusModule.forRoot({
229
+ * imports: [prismaModule],
230
+ * indicatorProviders: [createPrismaHealthIndicatorProvider({ key: 'prisma' })],
231
+ * });
232
+ * ```
233
+ *
234
+ * @param options Terminus health indicator, dependency import, and readiness configuration.
213
235
  * @returns A runtime module exposing health endpoints and `TerminusHealthService`.
214
236
  */
215
237
  static forRoot(options = {}) {
@@ -5,7 +5,11 @@ interface NodeMemoryUsageSnapshot {
5
5
  heapUsed: number;
6
6
  rss: number;
7
7
  }
8
- /** Read Node.js process memory usage through the Terminus Node runtime seam. */
8
+ /**
9
+ * Read Node.js process memory usage through the Terminus Node runtime seam.
10
+ *
11
+ * @returns A snapshot of the current Node.js process memory usage.
12
+ */
9
13
  export declare function readNodeMemoryUsage(): NodeMemoryUsageSnapshot;
10
14
  export {};
11
15
  //# sourceMappingURL=node-runtime.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"node-runtime.d.ts","sourceRoot":"","sources":["../src/node-runtime.ts"],"names":[],"mappings":"AAAA,UAAU,uBAAuB;IAC/B,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;CACb;AAkBD,gFAAgF;AAChF,wBAAgB,mBAAmB,IAAI,uBAAuB,CAQ7D"}
1
+ {"version":3,"file":"node-runtime.d.ts","sourceRoot":"","sources":["../src/node-runtime.ts"],"names":[],"mappings":"AAAA,UAAU,uBAAuB;IAC/B,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;CACb;AAkBD;;;;GAIG;AACH,wBAAgB,mBAAmB,IAAI,uBAAuB,CAQ7D"}
@@ -3,7 +3,11 @@ function resolveNodeProcess() {
3
3
  return runtimeGlobal.process;
4
4
  }
5
5
 
6
- /** Read Node.js process memory usage through the Terminus Node runtime seam. */
6
+ /**
7
+ * Read Node.js process memory usage through the Terminus Node runtime seam.
8
+ *
9
+ * @returns A snapshot of the current Node.js process memory usage.
10
+ */
7
11
  export function readNodeMemoryUsage() {
8
12
  const memoryUsage = resolveNodeProcess()?.memoryUsage;
9
13
  if (typeof memoryUsage !== 'function') {
package/dist/types.d.ts CHANGED
@@ -1,5 +1,7 @@
1
+ import type { Constructor } from '@fluojs/core';
1
2
  import type { Provider } from '@fluojs/di';
2
- import type { PlatformHealthReport, PlatformReadinessReport, ReadinessCheck } from '@fluojs/runtime';
3
+ import type { Middleware } from '@fluojs/http';
4
+ import type { ModuleType, PlatformHealthReport, PlatformReadinessReport, ReadinessCheck } from '@fluojs/runtime';
3
5
  /** Status values returned by one health indicator execution. */
4
6
  export type HealthIndicatorStatus = 'up' | 'down';
5
7
  /** One indicator state payload stored under its resolved key. */
@@ -14,6 +16,8 @@ export type HealthIndicatorResult = {
14
16
  export interface HealthIndicator {
15
17
  check(key: string): Promise<HealthIndicatorResult>;
16
18
  key?: string;
19
+ /** Whether this indicator participates in `/ready`. Defaults to `true`. */
20
+ readiness?: boolean;
17
21
  }
18
22
  /** Structured health report returned by Terminus aggregation helpers. */
19
23
  export interface HealthCheckReport {
@@ -44,6 +48,18 @@ export interface HealthCheckExecutionOptions {
44
48
  */
45
49
  export interface TerminusModuleOptions {
46
50
  execution?: HealthCheckExecutionOptions;
51
+ /** Class-based middleware applied only to the generated `/health` and `/ready` endpoints. */
52
+ endpointMiddleware?: readonly Constructor<Middleware>[];
53
+ /**
54
+ * Modules whose exported tokens must be visible to `indicatorProviders`.
55
+ *
56
+ * Terminus registers `indicatorProviders` inside its own module scope, so a
57
+ * dependency-owning module such as `PrismaModule`, `DrizzleModule`, or a named
58
+ * `RedisModule` registration must be imported here for its exported tokens to
59
+ * resolve. Importing the module into the parent application module alone does
60
+ * not make its exports visible to Terminus.
61
+ */
62
+ imports?: readonly ModuleType[];
47
63
  indicators?: readonly HealthIndicator[];
48
64
  indicatorProviders?: readonly Provider[];
49
65
  path?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,KAAK,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAErG,gEAAgE;AAChE,MAAM,MAAM,qBAAqB,GAAG,IAAI,GAAG,MAAM,CAAC;AAElD,iEAAiE;AACjE,MAAM,MAAM,oBAAoB,GAAG;IACjC,MAAM,EAAE,qBAAqB,CAAC;CAC/B,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE5B,qDAAqD;AACrD,MAAM,MAAM,qBAAqB,GAAG;IAClC,CAAC,GAAG,EAAE,MAAM,GAAG,oBAAoB,CAAC;CACrC,CAAC;AAEF,iFAAiF;AACjF,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAAC;IACnD,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,yEAAyE;AACzE,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE;QACZ,IAAI,EAAE,MAAM,EAAE,CAAC;QACf,EAAE,EAAE,MAAM,EAAE,CAAC;KACd,CAAC;IACF,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC;IAC9C,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC;IAC5C,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC;IAC3C,QAAQ,CAAC,EAAE;QACT,MAAM,EAAE,oBAAoB,CAAC;QAC7B,SAAS,EAAE,uBAAuB,CAAC;KACpC,CAAC;IACF,MAAM,EAAE,IAAI,GAAG,OAAO,CAAC;CACxB;AAED,mFAAmF;AACnF,MAAM,WAAW,2BAA2B;IAC1C;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,SAAS,CAAC,EAAE,2BAA2B,CAAC;IACxC,UAAU,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;IACxC,kBAAkB,CAAC,EAAE,SAAS,QAAQ,EAAE,CAAC;IACzC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,eAAe,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;CAC7C"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,KAAK,EAAE,UAAU,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEjH,gEAAgE;AAChE,MAAM,MAAM,qBAAqB,GAAG,IAAI,GAAG,MAAM,CAAC;AAElD,iEAAiE;AACjE,MAAM,MAAM,oBAAoB,GAAG;IACjC,MAAM,EAAE,qBAAqB,CAAC;CAC/B,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE5B,qDAAqD;AACrD,MAAM,MAAM,qBAAqB,GAAG;IAClC,CAAC,GAAG,EAAE,MAAM,GAAG,oBAAoB,CAAC;CACrC,CAAC;AAEF,iFAAiF;AACjF,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAAC;IACnD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,2EAA2E;IAC3E,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,yEAAyE;AACzE,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE;QACZ,IAAI,EAAE,MAAM,EAAE,CAAC;QACf,EAAE,EAAE,MAAM,EAAE,CAAC;KACd,CAAC;IACF,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC;IAC9C,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC;IAC5C,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC;IAC3C,QAAQ,CAAC,EAAE;QACT,MAAM,EAAE,oBAAoB,CAAC;QAC7B,SAAS,EAAE,uBAAuB,CAAC;KACpC,CAAC;IACF,MAAM,EAAE,IAAI,GAAG,OAAO,CAAC;CACxB;AAED,mFAAmF;AACnF,MAAM,WAAW,2BAA2B;IAC1C;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,SAAS,CAAC,EAAE,2BAA2B,CAAC;IACxC,6FAA6F;IAC7F,kBAAkB,CAAC,EAAE,SAAS,WAAW,CAAC,UAAU,CAAC,EAAE,CAAC;IACxD;;;;;;;;OAQG;IACH,OAAO,CAAC,EAAE,SAAS,UAAU,EAAE,CAAC;IAChC,UAAU,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;IACxC,kBAAkB,CAAC,EAAE,SAAS,QAAQ,EAAE,CAAC;IACzC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,eAAe,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;CAC7C"}
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  "liveness",
10
10
  "health-check"
11
11
  ],
12
- "version": "1.1.0",
12
+ "version": "2.0.0",
13
13
  "private": false,
14
14
  "license": "MIT",
15
15
  "repository": {
@@ -18,7 +18,7 @@
18
18
  "directory": "packages/terminus"
19
19
  },
20
20
  "engines": {
21
- "node": ">=20.0.0"
21
+ "node": ">=24.0.0 <27"
22
22
  },
23
23
  "publishConfig": {
24
24
  "access": "public"
@@ -44,15 +44,15 @@
44
44
  "dist"
45
45
  ],
46
46
  "dependencies": {
47
- "@fluojs/core": "^1.1.0",
48
- "@fluojs/di": "^2.0.0",
49
- "@fluojs/http": "^2.0.1",
50
- "@fluojs/runtime": "^2.0.1"
47
+ "@fluojs/core": "^2.0.0",
48
+ "@fluojs/di": "^3.0.0",
49
+ "@fluojs/http": "^3.0.0",
50
+ "@fluojs/runtime": "^3.0.0"
51
51
  },
52
52
  "peerDependencies": {
53
- "@fluojs/drizzle": "^1.1.1",
54
- "@fluojs/prisma": "^1.1.1",
55
- "@fluojs/redis": "^1.1.0"
53
+ "@fluojs/drizzle": "^2.0.0",
54
+ "@fluojs/prisma": "^2.0.0",
55
+ "@fluojs/redis": "^2.0.0"
56
56
  },
57
57
  "peerDependenciesMeta": {
58
58
  "@fluojs/drizzle": {
@@ -66,8 +66,8 @@
66
66
  }
67
67
  },
68
68
  "devDependencies": {
69
- "vitest": "^3.2.4",
70
- "@fluojs/testing": "^2.0.0"
69
+ "vitest": "^4.1.11",
70
+ "@fluojs/testing": "^3.0.0"
71
71
  },
72
72
  "scripts": {
73
73
  "prebuild": "node ../../tooling/scripts/clean-dist.mjs",