@fluojs/terminus 1.0.5 → 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-마이그레이션-경계)
@@ -25,10 +29,12 @@ fluo 애플리케이션을 위한 헬스 인디케이터(Health Indicator) 툴
25
29
  pnpm add @fluojs/terminus
26
30
  ```
27
31
 
28
- Redis indicator subpath를 사용할 때만 선택적 peer 설치하세요.
32
+ 사용하는 indicator provider seam에 필요한 선택적 peer 설치하세요.
29
33
 
30
34
  ```bash
31
35
  pnpm add @fluojs/redis ioredis
36
+ pnpm add @fluojs/prisma @prisma/client
37
+ pnpm add @fluojs/drizzle drizzle-orm
32
38
  ```
33
39
 
34
40
  ## 사용 시점
@@ -71,6 +77,8 @@ class AppModule {}
71
77
  - `MemoryHealthIndicator` (호환성을 위해 root에서도 export되며 `@fluojs/terminus/node`에서도 제공)
72
78
  - `DiskHealthIndicator` (호환성을 위해 root에서도 export되며 `@fluojs/terminus/node`에서도 제공)
73
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
+
74
82
  ### DI 기반 인디케이터
75
83
 
76
84
  Redis나 DB 클라이언트와 같이 DI 컨테이너의 의존성이 필요한 인디케이터를 사용할 때는, 모듈 로드 시점에 피어 의존성을 import하지 않도록 문서화된 provider 팩토리를 사용하세요. 이 helper들은 `TerminusModule.forRoot({ indicatorProviders })`에 전달할 indicator provider entry를 만들며 module facade를 대체하지 않습니다.
@@ -101,13 +109,103 @@ TerminusModule.forRoot({
101
109
 
102
110
  Drizzle의 경우 `createDrizzleHealthIndicatorProvider()`는 `@fluojs/drizzle`이 노출하는 lifecycle-aware `DrizzleDatabase` wrapper를 우선 사용합니다. Drizzle이 종료 중이거나 중지되었거나 `DrizzleDatabase.createPlatformStatusSnapshot()` 기준으로 준비되지 않은 상태이면 SQL probe를 실행하기 전에 해당 indicator를 `down`으로 보고합니다. legacy raw `DRIZZLE_DATABASE` handle만 등록된 경우에는 기존 lightweight SQL probe 동작을 유지합니다.
103
111
 
112
+ Prisma의 경우 `createPrismaHealthIndicatorProvider()`는 `@fluojs/prisma`가 노출하는 lifecycle-aware `PrismaService` / `PrismaServiceFacade` token을 우선 사용하고, probe 전에 `createPlatformStatusSnapshot()`을 확인한 뒤 `current()`를 호출해 ambient transaction/lifecycle seam이 health probe에 보이도록 합니다. 기본 Prisma registration을 대상으로 하려면 `name`을 생략하고, `PrismaModule.forRoot({ name })`으로 등록한 named registration을 대상으로 하려면 `name`을 전달하세요. 수동 provider graph에서는 명시적인 `serviceToken` / `clientToken` 값을 전달할 수 있습니다. raw Prisma client token만 등록된 경우에는 root package에서 optional Prisma peer를 import하지 않으면서 기존 lightweight query probe 동작을 유지합니다.
113
+
104
114
  Provider factory는 반복 등록할 수 있습니다. 각 인스턴스가 서로 다른 indicator key나 dependency option을 사용한다면 같은 factory가 만든 provider 여러 개를 하나의 `indicatorProviders` 배열에 등록할 수 있으며, Terminus는 나중에 등록된 같은 타입 provider가 앞선 provider를 덮어쓰지 않도록 각 provider 인스턴스를 별도 DI token으로 보관합니다.
105
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
+
106
204
  ### 실행 가드레일
107
205
 
108
206
  커스텀 인디케이터가 멈추거나 느린 하위 서비스에 의존할 수 있다면 `execution.indicatorTimeoutMs`를 사용하세요. probe가 설정된 시간을 넘기면 Terminus는 무기한 대기하지 않고 해당 인디케이터를 `down`으로 표시합니다.
109
207
 
110
- Terminus는 같은 indicator instance에 대한 check도 직렬화합니다. Timeout된 probe나 느린 probe가 아직 실행 중일 때 다른 `/health` 또는 `/ready` 요청이 들어오면, Terminus는 같은 downstream에 겹치는 probe를 새로 시작하지 않고 해당 indicator를 새 요청에서 `down`으로 보고합니다. Built-in HTTP indicator는 자체 timeout이 만료되면 `fetch` 요청을 abort하지만, 다른 driver와 custom callback은 cancellation을 노출하지 않을 수 있으므로 원래 promise가 settle될 때까지 overlap을 막는 방식으로 보호합니다.
208
+ Terminus는 각 `TerminusHealthService` / application container 안에서 같은 indicator instance에 대한 check도 직렬화합니다. Timeout된 probe나 느린 probe가 아직 실행 중일 때 같은 container의 다른 `/health` 또는 `/ready` 요청이 들어오면, Terminus는 같은 downstream에 겹치는 probe를 새로 시작하지 않고 해당 indicator를 새 요청에서 `down`으로 보고합니다. 테스트나 multi-app process가 같은 indicator object를 재사용하더라도 별도 application container는 독립적인 in-flight state를 유지합니다. Built-in HTTP indicator는 자체 timeout이 만료되면 `fetch` 요청을 abort하지만, 다른 driver와 custom callback은 cancellation을 노출하지 않을 수 있으므로 원래 promise가 settle될 때까지 overlap을 막는 방식으로 보호합니다.
111
209
 
112
210
  ```typescript
113
211
  TerminusModule.forRoot({
@@ -120,14 +218,14 @@ TerminusModule.forRoot({
120
218
  });
121
219
  ```
122
220
 
123
- `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를 제외하지 않습니다.
124
222
 
125
223
  ### 실패 시맨틱
126
224
 
127
225
  인디케이터가 `down` 결과를 반환하거나 `HealthCheckError`를 던지면, `TerminusHealthService`는 이 실패들을 모아 보고서를 작성합니다.
128
226
 
129
- - 하나 이상의 인디케이터가 실패하면 `/health`는 HTTP `503`을 반환합니다.
130
- - 등록된 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에 남습니다.
131
229
  - 응답 본문은 `status`, `contributors`, `info`, `error`, `details`를 포함한 구조화된 JSON 객체입니다.
132
230
  - 하나의 인디케이터가 여러 keyed entry를 반환할 수도 있으며, 이 경우 `/health`는 모든 entry를 `details`와 `contributors.up` / `contributors.down` 요약에 그대로 반영합니다.
133
231
  - 지원하지 않는 status, 빈 결과, 객체가 아닌 인디케이터 결과는 조용히 버려지지 않고 `down` 진단으로 보고됩니다.
@@ -135,6 +233,7 @@ TerminusModule.forRoot({
135
233
  - 같은 실행에서 이미 보고된 key를 다른 인디케이터가 다시 사용하면, Terminus는 먼저 기록된 entry를 유지하고 데이터를 조용히 덮어쓰는 대신 결정적인 `*-duplicate-key-error` contributor를 추가합니다.
136
234
  - 플랫폼 health/readiness 실패는 `/health` 응답에서 결정적인 `fluo-platform-health`, `fluo-platform-readiness` contributor로 노출됩니다. 이 key들은 platform diagnostic용으로 예약되어 있으며, platform failure 중 user indicator가 같은 key를 반환하면 Terminus는 platform payload를 예약 key 아래에 유지하고 runtime state를 떨어뜨리지 않도록 결정적인 `*-user-key-collision` diagnostic을 추가합니다.
137
235
  - Runtime diagnostics가 있으면 `/health` response에 platform health/readiness detail을 담은 `platform` block이 포함될 수 있습니다.
236
+ - DI provider로 생성한 Prisma indicator는 query보다 먼저 `@fluojs/prisma` service lifecycle readiness/health state를 반영하므로, raw client handle이 아직 호출 가능하더라도 종료 중이거나 중지되었거나 아직 연결되지 않은 통합은 `/health`와 `/ready`를 unavailable로 표시합니다.
138
237
  - DI provider로 생성한 Drizzle indicator는 SQL probe보다 먼저 Drizzle lifecycle readiness/health state를 반영하므로, underlying driver가 raw ping을 아직 받을 수 있어도 종료 중이거나 중지된 통합은 `/health`와 `/ready`를 unavailable로 표시합니다.
139
238
  - Redis subpath로 생성한 Redis indicator는 `PING` 전에 `@fluojs/redis` client lifecycle state를 반영하므로, 종료 중이거나 연결이 끊긴 Redis client는 command 실행 전에도 `/health`와 `/ready`를 unavailable로 표시합니다.
140
239
 
@@ -142,9 +241,9 @@ TerminusModule.forRoot({
142
241
 
143
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를 일관되게 포함하도록 해야 합니다.
144
243
 
145
- 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에서 보호해야 합니다.
146
245
 
147
- Runtime-specific indicator는 subpath별로 분리되어 있습니다. Node.js memory 및 disk check에는 `@fluojs/terminus/node`를 사용하고, Redis check에는 `@fluojs/terminus/redis`를 사용하세요. Root packageRedis optional-peer import를 root entrypoint 밖에 두고 Node disk filesystem access도 lazy하게 유지하므로, 애플리케이션은 runtime-specific probe 명시적으로 opt in합니다.
246
+ Runtime-specific indicator는 subpath별로 분리되어 있습니다. Node.js memory 및 disk check에는 `@fluojs/terminus/node`를 사용하고, Redis check에는 `@fluojs/terminus/redis`를 사용하세요. Prisma와 Drizzle provider helpertoken-only DI seam을 해석하므로 해당 선택적 peer 없어도 root package import는 안전하게 유지되며, Node disk filesystem access도 lazy하게 유지되어 애플리케이션이 runtime-specific probe 명시적으로 opt in합니다.
148
247
 
149
248
  ## 공개 API 개요
150
249
 
@@ -152,7 +251,7 @@ Runtime-specific indicator는 subpath별로 분리되어 있습니다. Node.js m
152
251
 
153
252
  - `static forRoot(options: TerminusModuleOptions): ModuleType`
154
253
  - 인디케이터 및 provider 등록을 위한 메인 엔트리 포인트입니다.
155
- - Option에는 `indicators`, `indicatorProviders`, `readinessChecks`, `execution.indicatorTimeoutMs`, `path`가 포함됩니다.
254
+ - Option에는 `imports`, `indicators`, `indicatorProviders`, `readinessChecks`, `execution.indicatorTimeoutMs`, `path`가 포함됩니다.
156
255
 
157
256
  ### `TerminusHealthService`
158
257
 
@@ -160,11 +259,18 @@ Runtime-specific indicator는 subpath별로 분리되어 있습니다. Node.js m
160
259
  - 현재 등록된 인디케이터를 실행해 집계된 보고서를 반환합니다.
161
260
  - `isHealthy(): Promise<boolean>`
162
261
  - 현재 집계 결과가 완전히 healthy 상태인지 반환합니다.
262
+ - `isReady(): Promise<boolean>`
263
+ - readiness에 참여하는 모든 indicator가 현재 `up`을 보고하는지 반환합니다.
264
+
265
+ ### `HealthIndicator`
266
+
267
+ - `readiness?: boolean`
268
+ - indicator의 `/ready` 참여 여부를 제어하며 기본값은 `true`입니다. 내장 indicator의 모든 option 객체가 이 설정을 지원합니다.
163
269
 
164
270
  ### 직접 helper와 token
165
271
 
166
272
  - `runHealthCheck(...)`, `assertHealthCheck(...)`: 직접 aggregation/testing helper입니다.
167
- - `TERMINUS_HEALTH_INDICATORS`, `TERMINUS_INDICATOR_PROVIDER_TOKENS`: 등록된 indicator와 provider token을 위한 DI token입니다.
273
+ - `TERMINUS_HEALTH_INDICATORS`, `TERMINUS_INDICATOR_PROVIDER_TOKENS`: 등록된 indicator와 provider token을 위한 DI token입니다. `TerminusModule.forRoot(...)`는 두 token을 모두 export하므로 downstream module은 Terminus 내부를 재구성하지 않고도 구성된 indicator/provider-token set을 확인할 수 있습니다.
168
274
  - Built-in indicator는 `create*HealthIndicator()` 및 `create*HealthIndicatorProvider()` helper도 노출합니다. Provider helper는 `indicatorProviders`를 위한 의도적인 DI composition 예외이며, 애플리케이션 등록은 계속 `TerminusModule.forRoot(...)`를 사용해야 합니다.
169
275
 
170
276
  ### `@fluojs/terminus/redis`
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)
@@ -25,10 +29,12 @@ Health indicator toolkit for fluo applications. `@fluojs/terminus` layers on top
25
29
  pnpm add @fluojs/terminus
26
30
  ```
27
31
 
28
- Install the optional peer only when you use the Redis indicator subpath:
32
+ Install optional peers only for the indicator provider seams you enable:
29
33
 
30
34
  ```bash
31
35
  pnpm add @fluojs/redis ioredis
36
+ pnpm add @fluojs/prisma @prisma/client
37
+ pnpm add @fluojs/drizzle drizzle-orm
32
38
  ```
33
39
 
34
40
  ## When to Use
@@ -71,6 +77,8 @@ The package provides several indicators out of the box:
71
77
  - `MemoryHealthIndicator` (root-exported for compatibility and also available from `@fluojs/terminus/node`)
72
78
  - `DiskHealthIndicator` (root-exported for compatibility and also available from `@fluojs/terminus/node`)
73
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
+
74
82
  ### DI-Backed Indicators
75
83
 
76
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.
@@ -101,13 +109,103 @@ Redis indicators created through `@fluojs/terminus/redis` are lifecycle-aware wh
101
109
 
102
110
  For Drizzle, `createDrizzleHealthIndicatorProvider()` prefers the lifecycle-aware `DrizzleDatabase` wrapper exported by `@fluojs/drizzle`. The indicator reports `down` before probing SQL whenever Drizzle is shutting down, stopped, or otherwise not ready according to `DrizzleDatabase.createPlatformStatusSnapshot()`. If only the legacy raw `DRIZZLE_DATABASE` handle is registered, the provider keeps the previous lightweight SQL probe behavior.
103
111
 
112
+ For Prisma, `createPrismaHealthIndicatorProvider()` prefers the lifecycle-aware `PrismaService` / `PrismaServiceFacade` token exported by `@fluojs/prisma`, checks `createPlatformStatusSnapshot()` before probing, and then calls `current()` so ambient transaction/lifecycle seams stay visible to the health probe. Omit `name` to target the default Prisma registration, pass `name` to target `PrismaModule.forRoot({ name })`, or pass explicit `serviceToken` / `clientToken` values for manual provider graphs. If only a raw Prisma client token is registered, the provider keeps the previous lightweight query probe behavior without importing the optional Prisma peer from the root package.
113
+
104
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.
105
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
+
106
204
  ### Execution Guardrails
107
205
 
108
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.
109
207
 
110
- Terminus also serializes checks per indicator instance. If a timed-out or otherwise slow probe is still running when another `/health` or `/ready` request arrives, Terminus reports that indicator as `down` for the new request instead of starting an overlapping probe against the same downstream. Built-in HTTP indicators abort their `fetch` request when their own timeout expires; other drivers and custom callbacks may not expose cancellation, so they are protected from overlap until the original promise settles.
208
+ Terminus also serializes checks per indicator instance inside each `TerminusHealthService` / application container. If a timed-out or otherwise slow probe is still running when another `/health` or `/ready` request arrives for the same container, Terminus reports that indicator as `down` for the new request instead of starting an overlapping probe against the same downstream. Separate application containers keep independent in-flight state even when tests or multi-app processes reuse the same indicator object. Built-in HTTP indicators abort their `fetch` request when their own timeout expires; other drivers and custom callbacks may not expose cancellation, so they are protected from overlap until the original promise settles.
111
209
 
112
210
  ```typescript
113
211
  TerminusModule.forRoot({
@@ -120,14 +218,14 @@ TerminusModule.forRoot({
120
218
  });
121
219
  ```
122
220
 
123
- 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.
124
222
 
125
223
  ### Failure Semantics
126
224
 
127
225
  When an indicator returns a `down` result or throws a `HealthCheckError`, the `TerminusHealthService` aggregates the failure into a report:
128
226
 
129
- - `/health` returns HTTP `503` if any indicator fails.
130
- - `/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.
131
229
  - The response body contains a structured JSON object with `status`, `contributors`, `info`, `error`, and `details`.
132
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.
133
231
  - Unsupported, empty, or non-object indicator results are reported as `down` diagnostics instead of being silently discarded.
@@ -135,6 +233,7 @@ When an indicator returns a `down` result or throws a `HealthCheckError`, the `T
135
233
  - If an indicator reuses a key that was already reported earlier in the same run, Terminus keeps the first entry and adds a deterministic `*-duplicate-key-error` contributor instead of silently overwriting data.
136
234
  - Platform health/readiness failures are surfaced as deterministic `fluo-platform-health` and `fluo-platform-readiness` contributors in `/health` responses. These keys are reserved for platform diagnostics; if a user indicator returns one of them during a platform failure, Terminus keeps the platform payload under the reserved key and adds a deterministic `*-user-key-collision` diagnostic instead of dropping runtime state.
137
235
  - `/health` responses may include a `platform` block with platform health/readiness details when runtime diagnostics are available.
236
+ - Prisma indicators created through the DI provider map `@fluojs/prisma` service lifecycle readiness/health state before querying, so shutdown, stopped, or not-yet-connected integrations mark `/health` and `/ready` unavailable even when the raw client handle is still callable.
138
237
  - Drizzle indicators created through the DI provider map Drizzle lifecycle readiness/health state before SQL probing, so shutdown or stopped integrations mark `/health` and `/ready` as unavailable even if the underlying driver still accepts a raw ping.
139
238
  - Redis indicators created through the Redis subpath map `@fluojs/redis` client lifecycle state before `PING`, so shutdown or disconnected Redis clients mark `/health` and `/ready` as unavailable even before command execution.
140
239
 
@@ -142,9 +241,9 @@ When an indicator returns a `down` result or throws a `HealthCheckError`, the `T
142
241
 
143
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.
144
243
 
145
- 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.
146
245
 
147
- 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. The root package keeps Redis optional-peer imports out of the root entrypoint and keeps Node disk filesystem access lazy so applications opt into runtime-specific probes explicitly.
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.
148
247
 
149
248
  ## Public API Overview
150
249
 
@@ -152,7 +251,7 @@ Runtime-specific indicators are split by subpath. Use `@fluojs/terminus/node` fo
152
251
 
153
252
  - `static forRoot(options: TerminusModuleOptions): ModuleType`
154
253
  - Main entry point for registering indicators and providers.
155
- - Options include `indicators`, `indicatorProviders`, `readinessChecks`, `execution.indicatorTimeoutMs`, and `path`.
254
+ - Options include `imports`, `indicators`, `indicatorProviders`, `readinessChecks`, `execution.indicatorTimeoutMs`, and `path`.
156
255
 
157
256
  ### `TerminusHealthService`
158
257
 
@@ -160,11 +259,18 @@ Runtime-specific indicators are split by subpath. Use `@fluojs/terminus/node` fo
160
259
  - Runs the currently registered indicators and returns the aggregated report.
161
260
  - `isHealthy(): Promise<boolean>`
162
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.
163
269
 
164
270
  ### Direct helpers and tokens
165
271
 
166
272
  - `runHealthCheck(...)`, `assertHealthCheck(...)`: Direct aggregation/testing helpers.
167
- - `TERMINUS_HEALTH_INDICATORS`, `TERMINUS_INDICATOR_PROVIDER_TOKENS`: DI tokens for registered indicators and provider tokens.
273
+ - `TERMINUS_HEALTH_INDICATORS`, `TERMINUS_INDICATOR_PROVIDER_TOKENS`: DI tokens for registered indicators and provider tokens. `TerminusModule.forRoot(...)` exports both tokens so downstream modules can inspect the composed indicator/provider-token set without rebuilding Terminus internals.
168
274
  - Built-in indicators also expose `create*HealthIndicator()` and `create*HealthIndicatorProvider()` helpers. Provider helpers are intentional DI-composition exceptions for `indicatorProviders`, while application registration should still go through `TerminusModule.forRoot(...)`.
169
275
 
170
276
  ### `@fluojs/terminus/redis`
@@ -2,6 +2,11 @@ import type { HealthCheckExecutionOptions, HealthCheckReport, HealthIndicator }
2
2
  /**
3
3
  * Run every registered health indicator and aggregate their results.
4
4
  *
5
+ * @remarks
6
+ * Direct helper calls receive an isolated execution scope. Use `TerminusHealthService`
7
+ * when repeated checks should serialize overlapping probes for a container-owned
8
+ * indicator set.
9
+ *
5
10
  * @param indicators Indicator instances to execute for the current health probe.
6
11
  * @param executionOptions Optional timeout guardrails for indicator execution.
7
12
  * @returns A structured report containing `info`, `error`, and full `details` maps.
@@ -13,13 +18,14 @@ export declare function runHealthCheck(indicators: readonly HealthIndicator[], e
13
18
  * @param report Health report returned by `runHealthCheck(...)` or `TerminusHealthService.check()`.
14
19
  * @param message Error message used when one or more indicators are down.
15
20
  * @returns The same health report when every indicator is healthy.
16
- * @throws {HealthCheckError} When the report contains at least one down indicator.
21
+ * @throws {HealthCheckError} When one or more indicators are down.
17
22
  */
18
23
  export declare function assertHealthCheck(report: HealthCheckReport, message?: string): HealthCheckReport;
19
24
  /** Service facade that resolves and runs the health indicators registered in Terminus. */
20
25
  export declare class TerminusHealthService {
21
26
  private readonly indicators;
22
27
  private readonly executionOptions;
28
+ private readonly runningIndicatorChecks;
23
29
  constructor(indicators: readonly HealthIndicator[], executionOptions?: HealthCheckExecutionOptions);
24
30
  /**
25
31
  * Execute all registered indicators once.
@@ -33,5 +39,11 @@ export declare class TerminusHealthService {
33
39
  * @returns `true` when the aggregated report status is `ok`.
34
40
  */
35
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>;
36
48
  }
37
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;AAqTpB;;;;;;GAMG;AACH,wBAAsB,cAAc,CAClC,UAAU,EAAE,SAAS,eAAe,EAAE,EACtC,gBAAgB,GAAE,2BAAgC,GACjD,OAAO,CAAC,iBAAiB,CAAC,CAoB5B;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,iBAAiB,EAAE,OAAO,SAAyB,GAAG,iBAAiB,CAMhH;AAED,0FAA0F;AAC1F,qBAAa,qBAAqB;IAE9B,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,gBAAgB;gBADhB,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"}