@fluojs/terminus 1.0.4 → 1.1.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 +20 -4
- package/README.md +20 -4
- package/dist/health-check.d.ts +6 -0
- package/dist/health-check.d.ts.map +1 -1
- package/dist/health-check.js +55 -13
- package/dist/indicators/drizzle.js +2 -2
- package/dist/indicators/http.d.ts.map +1 -1
- package/dist/indicators/http.js +39 -26
- package/dist/indicators/prisma.d.ts +40 -7
- package/dist/indicators/prisma.d.ts.map +1 -1
- package/dist/indicators/prisma.js +96 -14
- package/dist/indicators/redis.js +2 -2
- package/dist/indicators/utils.d.ts +10 -0
- package/dist/indicators/utils.d.ts.map +1 -1
- package/dist/indicators/utils.js +28 -2
- package/dist/module.d.ts.map +1 -1
- package/dist/module.js +27 -18
- package/package.json +9 -9
package/README.ko.md
CHANGED
|
@@ -14,6 +14,7 @@ fluo 애플리케이션을 위한 헬스 인디케이터(Health Indicator) 툴
|
|
|
14
14
|
- [DI 기반 인디케이터](#di-기반-인디케이터)
|
|
15
15
|
- [실행 가드레일](#실행-가드레일)
|
|
16
16
|
- [실패 시맨틱](#실패-시맨틱)
|
|
17
|
+
- [NestJS 마이그레이션 경계](#nestjs-마이그레이션-경계)
|
|
17
18
|
- [공개 API 개요](#공개-api-개요)
|
|
18
19
|
- [관련 패키지](#관련-패키지)
|
|
19
20
|
- [예제 소스](#예제-소스)
|
|
@@ -24,10 +25,12 @@ fluo 애플리케이션을 위한 헬스 인디케이터(Health Indicator) 툴
|
|
|
24
25
|
pnpm add @fluojs/terminus
|
|
25
26
|
```
|
|
26
27
|
|
|
27
|
-
|
|
28
|
+
사용하는 indicator provider seam에 필요한 선택적 peer만 설치하세요.
|
|
28
29
|
|
|
29
30
|
```bash
|
|
30
31
|
pnpm add @fluojs/redis ioredis
|
|
32
|
+
pnpm add @fluojs/prisma @prisma/client
|
|
33
|
+
pnpm add @fluojs/drizzle drizzle-orm
|
|
31
34
|
```
|
|
32
35
|
|
|
33
36
|
## 사용 시점
|
|
@@ -100,12 +103,16 @@ TerminusModule.forRoot({
|
|
|
100
103
|
|
|
101
104
|
Drizzle의 경우 `createDrizzleHealthIndicatorProvider()`는 `@fluojs/drizzle`이 노출하는 lifecycle-aware `DrizzleDatabase` wrapper를 우선 사용합니다. Drizzle이 종료 중이거나 중지되었거나 `DrizzleDatabase.createPlatformStatusSnapshot()` 기준으로 준비되지 않은 상태이면 SQL probe를 실행하기 전에 해당 indicator를 `down`으로 보고합니다. legacy raw `DRIZZLE_DATABASE` handle만 등록된 경우에는 기존 lightweight SQL probe 동작을 유지합니다.
|
|
102
105
|
|
|
106
|
+
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 동작을 유지합니다.
|
|
107
|
+
|
|
103
108
|
Provider factory는 반복 등록할 수 있습니다. 각 인스턴스가 서로 다른 indicator key나 dependency option을 사용한다면 같은 factory가 만든 provider 여러 개를 하나의 `indicatorProviders` 배열에 등록할 수 있으며, Terminus는 나중에 등록된 같은 타입 provider가 앞선 provider를 덮어쓰지 않도록 각 provider 인스턴스를 별도 DI token으로 보관합니다.
|
|
104
109
|
|
|
105
110
|
### 실행 가드레일
|
|
106
111
|
|
|
107
112
|
커스텀 인디케이터가 멈추거나 느린 하위 서비스에 의존할 수 있다면 `execution.indicatorTimeoutMs`를 사용하세요. probe가 설정된 시간을 넘기면 Terminus는 무기한 대기하지 않고 해당 인디케이터를 `down`으로 표시합니다.
|
|
108
113
|
|
|
114
|
+
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을 막는 방식으로 보호합니다.
|
|
115
|
+
|
|
109
116
|
```typescript
|
|
110
117
|
TerminusModule.forRoot({
|
|
111
118
|
execution: {
|
|
@@ -121,7 +128,7 @@ TerminusModule.forRoot({
|
|
|
121
128
|
|
|
122
129
|
### 실패 시맨틱
|
|
123
130
|
|
|
124
|
-
인디케이터가
|
|
131
|
+
인디케이터가 `down` 결과를 반환하거나 `HealthCheckError`를 던지면, `TerminusHealthService`는 이 실패들을 모아 보고서를 작성합니다.
|
|
125
132
|
|
|
126
133
|
- 하나 이상의 인디케이터가 실패하면 `/health`는 HTTP `503`을 반환합니다.
|
|
127
134
|
- 등록된 indicator가 실패하거나, custom readiness check가 `false`를 반환하거나, runtime shutdown이 시작되었거나, platform readiness가 `ready`가 아닌 경우 `/ready`는 HTTP `503`을 반환합니다. Platform `critical` metadata는 diagnostics에 보존되지만 HTTP readiness endpoint 자체는 binary ready/unavailable gate이며 warning severity bucket을 노출하지 않습니다.
|
|
@@ -130,11 +137,20 @@ TerminusModule.forRoot({
|
|
|
130
137
|
- 지원하지 않는 status, 빈 결과, 객체가 아닌 인디케이터 결과는 조용히 버려지지 않고 `down` 진단으로 보고됩니다.
|
|
131
138
|
- Blank indicator result key는 healthy entry로 기여하지 않고 `down` 진단으로 보고됩니다.
|
|
132
139
|
- 같은 실행에서 이미 보고된 key를 다른 인디케이터가 다시 사용하면, Terminus는 먼저 기록된 entry를 유지하고 데이터를 조용히 덮어쓰는 대신 결정적인 `*-duplicate-key-error` contributor를 추가합니다.
|
|
133
|
-
- 플랫폼 health/readiness 실패는 `/health` 응답에서 결정적인 `fluo-platform-health`, `fluo-platform-readiness` contributor로 노출됩니다.
|
|
140
|
+
- 플랫폼 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을 추가합니다.
|
|
134
141
|
- Runtime diagnostics가 있으면 `/health` response에 platform health/readiness detail을 담은 `platform` block이 포함될 수 있습니다.
|
|
142
|
+
- DI provider로 생성한 Prisma indicator는 query보다 먼저 `@fluojs/prisma` service lifecycle readiness/health state를 반영하므로, raw client handle이 아직 호출 가능하더라도 종료 중이거나 중지되었거나 아직 연결되지 않은 통합은 `/health`와 `/ready`를 unavailable로 표시합니다.
|
|
135
143
|
- DI provider로 생성한 Drizzle indicator는 SQL probe보다 먼저 Drizzle lifecycle readiness/health state를 반영하므로, underlying driver가 raw ping을 아직 받을 수 있어도 종료 중이거나 중지된 통합은 `/health`와 `/ready`를 unavailable로 표시합니다.
|
|
136
144
|
- Redis subpath로 생성한 Redis indicator는 `PING` 전에 `@fluojs/redis` client lifecycle state를 반영하므로, 종료 중이거나 연결이 끊긴 Redis client는 command 실행 전에도 `/health`와 `/ready`를 unavailable로 표시합니다.
|
|
137
145
|
|
|
146
|
+
## NestJS 마이그레이션 경계
|
|
147
|
+
|
|
148
|
+
`@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
|
+
|
|
150
|
+
Terminus는 별도의 process-only liveness route도 기본으로 만들지 않습니다. 기본 route model은 집계 헬스를 위한 `GET /health`, readiness를 위한 `GET /ready`입니다. 배포 환경에서 좁은 의미의 process liveness probe가 필요하다면, Terminus가 NestJS-style 추가 route를 만들어 준다고 가정하지 말고 애플리케이션 또는 배포 계층에서 해당 probe를 정의하세요.
|
|
151
|
+
|
|
152
|
+
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
|
+
|
|
138
154
|
## 공개 API 개요
|
|
139
155
|
|
|
140
156
|
### `TerminusModule`
|
|
@@ -153,7 +169,7 @@ TerminusModule.forRoot({
|
|
|
153
169
|
### 직접 helper와 token
|
|
154
170
|
|
|
155
171
|
- `runHealthCheck(...)`, `assertHealthCheck(...)`: 직접 aggregation/testing helper입니다.
|
|
156
|
-
- `TERMINUS_HEALTH_INDICATORS`, `TERMINUS_INDICATOR_PROVIDER_TOKENS`: 등록된 indicator와 provider token을 위한 DI token입니다.
|
|
172
|
+
- `TERMINUS_HEALTH_INDICATORS`, `TERMINUS_INDICATOR_PROVIDER_TOKENS`: 등록된 indicator와 provider token을 위한 DI token입니다. `TerminusModule.forRoot(...)`는 두 token을 모두 export하므로 downstream module은 Terminus 내부를 재구성하지 않고도 구성된 indicator/provider-token set을 확인할 수 있습니다.
|
|
157
173
|
- Built-in indicator는 `create*HealthIndicator()` 및 `create*HealthIndicatorProvider()` helper도 노출합니다. Provider helper는 `indicatorProviders`를 위한 의도적인 DI composition 예외이며, 애플리케이션 등록은 계속 `TerminusModule.forRoot(...)`를 사용해야 합니다.
|
|
158
174
|
|
|
159
175
|
### `@fluojs/terminus/redis`
|
package/README.md
CHANGED
|
@@ -14,6 +14,7 @@ Health indicator toolkit for fluo applications. `@fluojs/terminus` layers on top
|
|
|
14
14
|
- [DI-Backed Indicators](#di-backed-indicators)
|
|
15
15
|
- [Execution Guardrails](#execution-guardrails)
|
|
16
16
|
- [Failure Semantics](#failure-semantics)
|
|
17
|
+
- [NestJS Migration Boundaries](#nestjs-migration-boundaries)
|
|
17
18
|
- [Public API Overview](#public-api-overview)
|
|
18
19
|
- [Related Packages](#related-packages)
|
|
19
20
|
- [Example Sources](#example-sources)
|
|
@@ -24,10 +25,12 @@ Health indicator toolkit for fluo applications. `@fluojs/terminus` layers on top
|
|
|
24
25
|
pnpm add @fluojs/terminus
|
|
25
26
|
```
|
|
26
27
|
|
|
27
|
-
Install
|
|
28
|
+
Install optional peers only for the indicator provider seams you enable:
|
|
28
29
|
|
|
29
30
|
```bash
|
|
30
31
|
pnpm add @fluojs/redis ioredis
|
|
32
|
+
pnpm add @fluojs/prisma @prisma/client
|
|
33
|
+
pnpm add @fluojs/drizzle drizzle-orm
|
|
31
34
|
```
|
|
32
35
|
|
|
33
36
|
## When to Use
|
|
@@ -100,12 +103,16 @@ Redis indicators created through `@fluojs/terminus/redis` are lifecycle-aware wh
|
|
|
100
103
|
|
|
101
104
|
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.
|
|
102
105
|
|
|
106
|
+
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.
|
|
107
|
+
|
|
103
108
|
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.
|
|
104
109
|
|
|
105
110
|
### Execution Guardrails
|
|
106
111
|
|
|
107
112
|
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.
|
|
108
113
|
|
|
114
|
+
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.
|
|
115
|
+
|
|
109
116
|
```typescript
|
|
110
117
|
TerminusModule.forRoot({
|
|
111
118
|
execution: {
|
|
@@ -121,7 +128,7 @@ Use `path` to mount the health endpoints under a custom path, and `readinessChec
|
|
|
121
128
|
|
|
122
129
|
### Failure Semantics
|
|
123
130
|
|
|
124
|
-
When an indicator
|
|
131
|
+
When an indicator returns a `down` result or throws a `HealthCheckError`, the `TerminusHealthService` aggregates the failure into a report:
|
|
125
132
|
|
|
126
133
|
- `/health` returns HTTP `503` if any indicator fails.
|
|
127
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.
|
|
@@ -130,11 +137,20 @@ When an indicator fails, it throws a `HealthCheckError`. The `TerminusHealthServ
|
|
|
130
137
|
- Unsupported, empty, or non-object indicator results are reported as `down` diagnostics instead of being silently discarded.
|
|
131
138
|
- Blank indicator result keys are reported as `down` diagnostics instead of contributing healthy entries.
|
|
132
139
|
- 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.
|
|
133
|
-
- Platform health/readiness failures are surfaced as deterministic `fluo-platform-health` and `fluo-platform-readiness` contributors in `/health` responses.
|
|
140
|
+
- 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.
|
|
134
141
|
- `/health` responses may include a `platform` block with platform health/readiness details when runtime diagnostics are available.
|
|
142
|
+
- 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.
|
|
135
143
|
- 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.
|
|
136
144
|
- 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.
|
|
137
145
|
|
|
146
|
+
## NestJS Migration Boundaries
|
|
147
|
+
|
|
148
|
+
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
|
+
|
|
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.
|
|
151
|
+
|
|
152
|
+
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
|
+
|
|
138
154
|
## Public API Overview
|
|
139
155
|
|
|
140
156
|
### `TerminusModule`
|
|
@@ -153,7 +169,7 @@ When an indicator fails, it throws a `HealthCheckError`. The `TerminusHealthServ
|
|
|
153
169
|
### Direct helpers and tokens
|
|
154
170
|
|
|
155
171
|
- `runHealthCheck(...)`, `assertHealthCheck(...)`: Direct aggregation/testing helpers.
|
|
156
|
-
- `TERMINUS_HEALTH_INDICATORS`, `TERMINUS_INDICATOR_PROVIDER_TOKENS`: DI tokens for registered indicators and provider tokens.
|
|
172
|
+
- `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.
|
|
157
173
|
- 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(...)`.
|
|
158
174
|
|
|
159
175
|
### `@fluojs/terminus/redis`
|
package/dist/health-check.d.ts
CHANGED
|
@@ -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.
|
|
@@ -20,6 +25,7 @@ export declare function assertHealthCheck(report: HealthCheckReport, message?: s
|
|
|
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.
|
|
@@ -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;
|
|
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"}
|
package/dist/health-check.js
CHANGED
|
@@ -8,6 +8,32 @@ function normalizeIndicatorTimeoutMs(value) {
|
|
|
8
8
|
function createTimeoutMessage(timeoutMs) {
|
|
9
9
|
return `Health indicator timed out after ${String(timeoutMs)}ms.`;
|
|
10
10
|
}
|
|
11
|
+
function createInFlightResult(key) {
|
|
12
|
+
return {
|
|
13
|
+
[key]: {
|
|
14
|
+
message: 'A previous health indicator probe is still running; Terminus will not start an overlapping probe for the same indicator instance.',
|
|
15
|
+
status: 'down'
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function startSerializedIndicatorCheck(indicator, key, runningIndicatorChecks) {
|
|
20
|
+
const runningCheck = runningIndicatorChecks.get(indicator);
|
|
21
|
+
if (runningCheck) {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
const check = Promise.resolve().then(() => indicator.check(key));
|
|
25
|
+
runningIndicatorChecks.set(indicator, check);
|
|
26
|
+
check.then(() => {
|
|
27
|
+
if (runningIndicatorChecks.get(indicator) === check) {
|
|
28
|
+
runningIndicatorChecks.delete(indicator);
|
|
29
|
+
}
|
|
30
|
+
}, () => {
|
|
31
|
+
if (runningIndicatorChecks.get(indicator) === check) {
|
|
32
|
+
runningIndicatorChecks.delete(indicator);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
return check;
|
|
36
|
+
}
|
|
11
37
|
async function withTimeout(promise, timeoutMs) {
|
|
12
38
|
let timer;
|
|
13
39
|
try {
|
|
@@ -134,11 +160,18 @@ function createDuplicateKeyFailure(indicatorKey, duplicateKeys, seenKeys) {
|
|
|
134
160
|
status: 'down'
|
|
135
161
|
}];
|
|
136
162
|
}
|
|
137
|
-
async function runIndicator(indicator, index, executionOptions) {
|
|
163
|
+
async function runIndicator(indicator, index, executionOptions, runningIndicatorChecks) {
|
|
138
164
|
const key = inferIndicatorKey(indicator, index);
|
|
139
165
|
const indicatorTimeoutMs = normalizeIndicatorTimeoutMs(executionOptions.indicatorTimeoutMs);
|
|
166
|
+
const runningCheck = startSerializedIndicatorCheck(indicator, key, runningIndicatorChecks);
|
|
167
|
+
if (!runningCheck) {
|
|
168
|
+
return {
|
|
169
|
+
entries: Object.entries(createInFlightResult(key)),
|
|
170
|
+
indicatorKey: key
|
|
171
|
+
};
|
|
172
|
+
}
|
|
140
173
|
try {
|
|
141
|
-
const result = indicatorTimeoutMs === undefined ? await
|
|
174
|
+
const result = indicatorTimeoutMs === undefined ? await runningCheck : await withTimeout(runningCheck, indicatorTimeoutMs);
|
|
142
175
|
return {
|
|
143
176
|
entries: normalizeIndicatorResult(key, result),
|
|
144
177
|
indicatorKey: key
|
|
@@ -178,16 +211,8 @@ function aggregateIndicatorEntries(checks) {
|
|
|
178
211
|
}
|
|
179
212
|
return aggregatedEntries;
|
|
180
213
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
* Run every registered health indicator and aggregate their results.
|
|
184
|
-
*
|
|
185
|
-
* @param indicators Indicator instances to execute for the current health probe.
|
|
186
|
-
* @param executionOptions Optional timeout guardrails for indicator execution.
|
|
187
|
-
* @returns A structured report containing `info`, `error`, and full `details` maps.
|
|
188
|
-
*/
|
|
189
|
-
export async function runHealthCheck(indicators, executionOptions = {}) {
|
|
190
|
-
const checks = aggregateIndicatorEntries(await Promise.all(indicators.map((indicator, index) => runIndicator(indicator, index, executionOptions))));
|
|
214
|
+
async function executeHealthCheck(indicators, executionOptions = {}, runningIndicatorChecks) {
|
|
215
|
+
const checks = aggregateIndicatorEntries(await Promise.all(indicators.map((indicator, index) => runIndicator(indicator, index, executionOptions, runningIndicatorChecks))));
|
|
191
216
|
const details = Object.fromEntries(checks);
|
|
192
217
|
const infoEntries = checks.filter(([, result]) => result.status === 'up');
|
|
193
218
|
const errorEntries = checks.filter(([, result]) => result.status === 'down');
|
|
@@ -204,6 +229,22 @@ export async function runHealthCheck(indicators, executionOptions = {}) {
|
|
|
204
229
|
};
|
|
205
230
|
}
|
|
206
231
|
|
|
232
|
+
/**
|
|
233
|
+
* Run every registered health indicator and aggregate their results.
|
|
234
|
+
*
|
|
235
|
+
* @remarks
|
|
236
|
+
* Direct helper calls receive an isolated execution scope. Use `TerminusHealthService`
|
|
237
|
+
* when repeated checks should serialize overlapping probes for a container-owned
|
|
238
|
+
* indicator set.
|
|
239
|
+
*
|
|
240
|
+
* @param indicators Indicator instances to execute for the current health probe.
|
|
241
|
+
* @param executionOptions Optional timeout guardrails for indicator execution.
|
|
242
|
+
* @returns A structured report containing `info`, `error`, and full `details` maps.
|
|
243
|
+
*/
|
|
244
|
+
export async function runHealthCheck(indicators, executionOptions = {}) {
|
|
245
|
+
return executeHealthCheck(indicators, executionOptions, new WeakMap());
|
|
246
|
+
}
|
|
247
|
+
|
|
207
248
|
/**
|
|
208
249
|
* Assert that an aggregated health report is fully healthy.
|
|
209
250
|
*
|
|
@@ -221,6 +262,7 @@ export function assertHealthCheck(report, message = 'Health check failed.') {
|
|
|
221
262
|
|
|
222
263
|
/** Service facade that resolves and runs the health indicators registered in Terminus. */
|
|
223
264
|
export class TerminusHealthService {
|
|
265
|
+
runningIndicatorChecks = new WeakMap();
|
|
224
266
|
constructor(indicators, executionOptions = {}) {
|
|
225
267
|
this.indicators = indicators;
|
|
226
268
|
this.executionOptions = executionOptions;
|
|
@@ -232,7 +274,7 @@ export class TerminusHealthService {
|
|
|
232
274
|
* @returns The aggregated health report for this check cycle.
|
|
233
275
|
*/
|
|
234
276
|
async check() {
|
|
235
|
-
return
|
|
277
|
+
return executeHealthCheck(this.indicators, this.executionOptions, this.runningIndicatorChecks);
|
|
236
278
|
}
|
|
237
279
|
|
|
238
280
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { optional } from '@fluojs/di';
|
|
2
|
-
import { createDownResult, createUpResult, resolveIndicatorKey, throwHealthCheckError, withIndicatorTimeout } from './utils.js';
|
|
2
|
+
import { createDownResult, createUpResult, resolveIndicatorKey, resolveIndicatorTimeoutMs, throwHealthCheckError, 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
|
|
|
@@ -85,8 +85,8 @@ export class DrizzleHealthIndicator {
|
|
|
85
85
|
}
|
|
86
86
|
async check(key) {
|
|
87
87
|
const indicatorKey = resolveIndicatorKey('drizzle', this.options.key ?? key);
|
|
88
|
-
const timeoutMs = this.options.timeoutMs ?? DEFAULT_DRIZZLE_TIMEOUT_MS;
|
|
89
88
|
try {
|
|
89
|
+
const timeoutMs = resolveIndicatorTimeoutMs(this.options.timeoutMs, DEFAULT_DRIZZLE_TIMEOUT_MS, indicatorKey);
|
|
90
90
|
const snapshot = createDrizzleLifecycleSnapshot(this.options.handleProvider);
|
|
91
91
|
const lifecycleDownResult = snapshot ? createDrizzleLifecycleDownResult(indicatorKey, snapshot) : undefined;
|
|
92
92
|
if (lifecycleDownResult) {
|
|
@@ -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;
|
|
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"}
|
package/dist/indicators/http.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createDownResult, createUpResult, resolveIndicatorKey, throwHealthCheckError } from './utils.js';
|
|
1
|
+
import { createDownResult, createUpResult, resolveIndicatorKey, resolveIndicatorTimeoutMs, throwHealthCheckError } from './utils.js';
|
|
2
2
|
import { HealthCheckError } from '../errors.js';
|
|
3
3
|
|
|
4
4
|
/** Options for probing one upstream HTTP dependency. */
|
|
@@ -16,6 +16,13 @@ function isExpectedStatus(status, expected) {
|
|
|
16
16
|
}
|
|
17
17
|
return status >= 200 && status < 300;
|
|
18
18
|
}
|
|
19
|
+
function cancelResponseBody(response) {
|
|
20
|
+
const body = response.body;
|
|
21
|
+
if (!body) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
void body.cancel().catch(() => undefined);
|
|
25
|
+
}
|
|
19
26
|
|
|
20
27
|
/**
|
|
21
28
|
* Create an HTTP-backed health indicator.
|
|
@@ -50,39 +57,45 @@ export class HttpHealthIndicator {
|
|
|
50
57
|
}
|
|
51
58
|
async check(key) {
|
|
52
59
|
const indicatorKey = resolveIndicatorKey('http', this.options.key ?? key);
|
|
53
|
-
const timeoutMs = this.options.timeoutMs ?? DEFAULT_HTTP_TIMEOUT_MS;
|
|
54
60
|
const method = this.options.method ?? 'GET';
|
|
55
|
-
const abortController = new AbortController();
|
|
56
|
-
const startedAt = Date.now();
|
|
57
|
-
const timeout = setTimeout(() => {
|
|
58
|
-
abortController.abort(new Error(`HTTP health check timed out after ${String(timeoutMs)}ms.`));
|
|
59
|
-
}, timeoutMs);
|
|
60
61
|
try {
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
})
|
|
62
|
+
const timeoutMs = resolveIndicatorTimeoutMs(this.options.timeoutMs, DEFAULT_HTTP_TIMEOUT_MS, indicatorKey);
|
|
63
|
+
const abortController = new AbortController();
|
|
64
|
+
const startedAt = Date.now();
|
|
65
|
+
const timeout = setTimeout(() => {
|
|
66
|
+
abortController.abort(new Error(`HTTP health check timed out after ${String(timeoutMs)}ms.`));
|
|
67
|
+
}, timeoutMs);
|
|
68
|
+
try {
|
|
69
|
+
const response = await fetch(this.options.url, {
|
|
70
|
+
headers: this.options.headers,
|
|
71
|
+
method,
|
|
72
|
+
signal: abortController.signal
|
|
73
|
+
});
|
|
74
|
+
const responseTimeMs = Date.now() - startedAt;
|
|
75
|
+
try {
|
|
76
|
+
if (!isExpectedStatus(response.status, this.options.expectedStatus)) {
|
|
77
|
+
throwHealthCheckError('HTTP health check failed.', createDownResult(indicatorKey, `Unexpected status code ${String(response.status)} from ${this.options.url}.`, {
|
|
78
|
+
responseTimeMs,
|
|
79
|
+
statusCode: response.status,
|
|
80
|
+
url: this.options.url
|
|
81
|
+
}));
|
|
82
|
+
}
|
|
83
|
+
return createUpResult(indicatorKey, {
|
|
84
|
+
responseTimeMs,
|
|
85
|
+
statusCode: response.status,
|
|
86
|
+
url: this.options.url
|
|
87
|
+
});
|
|
88
|
+
} finally {
|
|
89
|
+
cancelResponseBody(response);
|
|
90
|
+
}
|
|
91
|
+
} finally {
|
|
92
|
+
clearTimeout(timeout);
|
|
73
93
|
}
|
|
74
|
-
return createUpResult(indicatorKey, {
|
|
75
|
-
responseTimeMs,
|
|
76
|
-
statusCode: response.status,
|
|
77
|
-
url: this.options.url
|
|
78
|
-
});
|
|
79
94
|
} catch (error) {
|
|
80
95
|
if (error instanceof HealthCheckError) {
|
|
81
96
|
throw error;
|
|
82
97
|
}
|
|
83
98
|
throwHealthCheckError('HTTP health check failed.', createDownResult(indicatorKey, error instanceof Error ? error.message : `HTTP health check failed for ${this.options.url}.`));
|
|
84
|
-
} finally {
|
|
85
|
-
clearTimeout(timeout);
|
|
86
99
|
}
|
|
87
100
|
}
|
|
88
101
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Token } from '@fluojs/core';
|
|
2
|
+
import { type Provider } from '@fluojs/di';
|
|
2
3
|
import type { HealthIndicator, HealthIndicatorResult } from '../types.js';
|
|
3
4
|
interface PrismaClientLike {
|
|
4
5
|
$executeRaw?: (...args: unknown[]) => Promise<unknown>;
|
|
@@ -6,28 +7,60 @@ interface PrismaClientLike {
|
|
|
6
7
|
$queryRaw?: (...args: unknown[]) => Promise<unknown>;
|
|
7
8
|
$queryRawUnsafe?: (query: string) => Promise<unknown>;
|
|
8
9
|
}
|
|
10
|
+
interface PrismaLifecycleSnapshotLike {
|
|
11
|
+
details?: Record<string, unknown>;
|
|
12
|
+
health: {
|
|
13
|
+
reason?: string;
|
|
14
|
+
status: 'healthy' | 'degraded' | 'unhealthy';
|
|
15
|
+
};
|
|
16
|
+
readiness: {
|
|
17
|
+
reason?: string;
|
|
18
|
+
status: 'ready' | 'not-ready';
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
interface PrismaServiceLike {
|
|
22
|
+
createPlatformStatusSnapshot?: () => PrismaLifecycleSnapshotLike;
|
|
23
|
+
current?: () => PrismaClientLike | unknown;
|
|
24
|
+
}
|
|
9
25
|
/** Options for probing Prisma-backed database connectivity. */
|
|
10
26
|
export interface PrismaHealthIndicatorOptions {
|
|
27
|
+
/** Raw Prisma client to probe when no lifecycle-aware service facade is supplied. */
|
|
11
28
|
client?: PrismaClientLike;
|
|
29
|
+
/** Explicit raw-client token to resolve when using `createPrismaHealthIndicatorProvider(...)`. */
|
|
30
|
+
clientToken?: Token;
|
|
31
|
+
/** Indicator result key override. Defaults to the key passed to `check(...)`, then `prisma`. */
|
|
12
32
|
key?: string;
|
|
33
|
+
/** Named Prisma registration to resolve when using `createPrismaHealthIndicatorProvider(...)`. */
|
|
34
|
+
name?: string;
|
|
35
|
+
/** Custom ping callback for manual probes or tests. Lifecycle state is only mapped when `service` is available. */
|
|
13
36
|
ping?: () => Promise<unknown> | unknown;
|
|
37
|
+
/** Lifecycle-aware Prisma service/facade handle, usually resolved from `getPrismaServiceToken(name)`. */
|
|
38
|
+
service?: PrismaServiceLike;
|
|
39
|
+
/** Explicit Prisma service token to resolve when using `createPrismaHealthIndicatorProvider(...)`. */
|
|
40
|
+
serviceToken?: Token;
|
|
41
|
+
/** Maximum time to wait for the ping operation. Defaults to `2_000` ms. */
|
|
14
42
|
timeoutMs?: number;
|
|
15
43
|
}
|
|
16
44
|
/**
|
|
17
45
|
* Create a Prisma health indicator.
|
|
18
46
|
*
|
|
19
|
-
* @param options Optional Prisma client, ping callback, timeout, and key override.
|
|
20
|
-
* @returns A health indicator that
|
|
47
|
+
* @param options Optional lifecycle-aware service facade, Prisma client, ping callback, timeout, and key override.
|
|
48
|
+
* @returns A health indicator that checks Prisma lifecycle state before executing a lightweight round trip.
|
|
21
49
|
*/
|
|
22
50
|
export declare function createPrismaHealthIndicator(options?: PrismaHealthIndicatorOptions): HealthIndicator;
|
|
23
51
|
/**
|
|
24
|
-
* Create a Terminus indicator provider collection entry that resolves
|
|
52
|
+
* Create a Terminus indicator provider collection entry that resolves Prisma from DI.
|
|
53
|
+
*
|
|
54
|
+
* The provider prefers `getPrismaServiceToken(options.name)` so `@fluojs/prisma`
|
|
55
|
+
* lifecycle snapshots participate in health/readiness diagnostics. It falls back
|
|
56
|
+
* to the matching raw client token for compatibility with manual provider graphs.
|
|
57
|
+
* Explicit `serviceToken` and `clientToken` values override the name-derived tokens.
|
|
25
58
|
*
|
|
26
|
-
* @param options Optional timeout, key override, or custom ping callback.
|
|
59
|
+
* @param options Optional name hint, explicit tokens, timeout, key override, or custom ping callback.
|
|
27
60
|
* @returns A factory provider with a unique internal DI token for `TerminusModule` indicatorProviders.
|
|
28
61
|
*/
|
|
29
|
-
export declare function createPrismaHealthIndicatorProvider(options?: Omit<PrismaHealthIndicatorOptions, 'client'>): Provider;
|
|
30
|
-
/** Health indicator that
|
|
62
|
+
export declare function createPrismaHealthIndicatorProvider(options?: Omit<PrismaHealthIndicatorOptions, 'client' | 'service'>): Provider;
|
|
63
|
+
/** Health indicator that maps Prisma lifecycle status and probes connectivity with a trivial query. */
|
|
31
64
|
export declare class PrismaHealthIndicator implements HealthIndicator {
|
|
32
65
|
private readonly options;
|
|
33
66
|
readonly key: string | undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prisma.d.ts","sourceRoot":"","sources":["../../src/indicators/prisma.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;
|
|
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,5 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { optional } from '@fluojs/di';
|
|
2
|
+
import { createDownResult, createUpResult, resolveIndicatorKey, resolveIndicatorTimeoutMs, throwHealthCheckError, withIndicatorTimeout } from './utils.js';
|
|
2
3
|
const PRISMA_CLIENT = Symbol.for('fluo.prisma.client');
|
|
4
|
+
const PRISMA_SERVICE = Symbol.for('fluo.prisma.service');
|
|
3
5
|
|
|
4
6
|
/** Options for probing Prisma-backed database connectivity. */
|
|
5
7
|
|
|
@@ -9,7 +11,7 @@ async function runPrismaPing(options) {
|
|
|
9
11
|
await options.ping();
|
|
10
12
|
return;
|
|
11
13
|
}
|
|
12
|
-
const client = options.client;
|
|
14
|
+
const client = resolveCurrentPrismaClient(options.service) ?? options.client;
|
|
13
15
|
if (!client) {
|
|
14
16
|
throw new Error('Prisma indicator requires either a client or ping callback.');
|
|
15
17
|
}
|
|
@@ -31,36 +33,108 @@ async function runPrismaPing(options) {
|
|
|
31
33
|
}
|
|
32
34
|
throw new Error('Prisma indicator requires a client with query/execute capabilities or a ping callback.');
|
|
33
35
|
}
|
|
36
|
+
function normalizePrismaRegistrationName(name) {
|
|
37
|
+
if (name === undefined) {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
const normalizedName = name.trim();
|
|
41
|
+
if (normalizedName.length === 0) {
|
|
42
|
+
throw new Error('Prisma health indicator registration name must be a non-empty string when provided.');
|
|
43
|
+
}
|
|
44
|
+
return normalizedName;
|
|
45
|
+
}
|
|
46
|
+
function getPrismaClientToken(name) {
|
|
47
|
+
const normalizedName = normalizePrismaRegistrationName(name);
|
|
48
|
+
return normalizedName === undefined ? PRISMA_CLIENT : Symbol.for(`fluo.prisma.client:${normalizedName}`);
|
|
49
|
+
}
|
|
50
|
+
function getPrismaServiceToken(name) {
|
|
51
|
+
const normalizedName = normalizePrismaRegistrationName(name);
|
|
52
|
+
return normalizedName === undefined ? PRISMA_SERVICE : Symbol.for(`fluo.prisma.service:${normalizedName}`);
|
|
53
|
+
}
|
|
54
|
+
function resolveCurrentPrismaClient(service) {
|
|
55
|
+
if (!service || typeof service.current !== 'function') {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
return service.current();
|
|
59
|
+
}
|
|
60
|
+
function createPrismaLifecycleSnapshot(service) {
|
|
61
|
+
if (!service || typeof service.createPlatformStatusSnapshot !== 'function') {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
return service.createPlatformStatusSnapshot();
|
|
65
|
+
}
|
|
66
|
+
function createPrismaLifecycleDownResult(indicatorKey, snapshot) {
|
|
67
|
+
const healthStatus = snapshot.health.status;
|
|
68
|
+
const readinessStatus = snapshot.readiness.status;
|
|
69
|
+
if (healthStatus === 'healthy' && readinessStatus === 'ready') {
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
const message = snapshot.readiness.reason ?? snapshot.health.reason ?? `Prisma lifecycle reported health=${healthStatus} readiness=${readinessStatus}.`;
|
|
73
|
+
return createDownResult(indicatorKey, message, {
|
|
74
|
+
details: snapshot.details,
|
|
75
|
+
healthStatus,
|
|
76
|
+
readinessStatus
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
function createPrismaLifecycleUpDetails(snapshot) {
|
|
80
|
+
if (!snapshot) {
|
|
81
|
+
return {};
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
details: snapshot.details,
|
|
85
|
+
healthStatus: snapshot.health.status,
|
|
86
|
+
readinessStatus: snapshot.readiness.status
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function toPrismaService(value) {
|
|
90
|
+
return typeof value === 'object' && value !== null ? value : undefined;
|
|
91
|
+
}
|
|
34
92
|
|
|
35
93
|
/**
|
|
36
94
|
* Create a Prisma health indicator.
|
|
37
95
|
*
|
|
38
|
-
* @param options Optional Prisma client, ping callback, timeout, and key override.
|
|
39
|
-
* @returns A health indicator that
|
|
96
|
+
* @param options Optional lifecycle-aware service facade, Prisma client, ping callback, timeout, and key override.
|
|
97
|
+
* @returns A health indicator that checks Prisma lifecycle state before executing a lightweight round trip.
|
|
40
98
|
*/
|
|
41
99
|
export function createPrismaHealthIndicator(options = {}) {
|
|
42
100
|
return new PrismaHealthIndicator(options);
|
|
43
101
|
}
|
|
44
102
|
|
|
45
103
|
/**
|
|
46
|
-
* Create a Terminus indicator provider collection entry that resolves
|
|
104
|
+
* Create a Terminus indicator provider collection entry that resolves Prisma from DI.
|
|
47
105
|
*
|
|
48
|
-
*
|
|
106
|
+
* The provider prefers `getPrismaServiceToken(options.name)` so `@fluojs/prisma`
|
|
107
|
+
* lifecycle snapshots participate in health/readiness diagnostics. It falls back
|
|
108
|
+
* to the matching raw client token for compatibility with manual provider graphs.
|
|
109
|
+
* Explicit `serviceToken` and `clientToken` values override the name-derived tokens.
|
|
110
|
+
*
|
|
111
|
+
* @param options Optional name hint, explicit tokens, timeout, key override, or custom ping callback.
|
|
49
112
|
* @returns A factory provider with a unique internal DI token for `TerminusModule` indicatorProviders.
|
|
50
113
|
*/
|
|
51
114
|
export function createPrismaHealthIndicatorProvider(options = {}) {
|
|
52
115
|
const indicatorProviderToken = Symbol('fluo.terminus.prisma-health-indicator');
|
|
116
|
+
const hasExplicitServiceToken = options.serviceToken !== undefined;
|
|
117
|
+
const hasExplicitClientToken = options.clientToken !== undefined;
|
|
118
|
+
const serviceToken = hasExplicitServiceToken || !hasExplicitClientToken ? options.serviceToken ?? getPrismaServiceToken(options.name) : undefined;
|
|
119
|
+
const clientToken = hasExplicitClientToken || !hasExplicitServiceToken ? options.clientToken ?? getPrismaClientToken(options.name) : undefined;
|
|
120
|
+
const inject = [...(serviceToken === undefined ? [] : [optional(serviceToken)]), ...(clientToken === undefined ? [] : [optional(clientToken)])];
|
|
53
121
|
return {
|
|
54
|
-
inject
|
|
122
|
+
inject,
|
|
55
123
|
provide: indicatorProviderToken,
|
|
56
|
-
useFactory:
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
124
|
+
useFactory: (...resolvedDependencies) => {
|
|
125
|
+
const resolvedService = serviceToken === undefined ? undefined : resolvedDependencies[0];
|
|
126
|
+
const resolvedClientIndex = serviceToken === undefined ? 0 : 1;
|
|
127
|
+
const resolvedClient = clientToken === undefined ? undefined : resolvedDependencies[resolvedClientIndex];
|
|
128
|
+
return new PrismaHealthIndicator({
|
|
129
|
+
...options,
|
|
130
|
+
client: resolvedClient,
|
|
131
|
+
service: toPrismaService(resolvedService)
|
|
132
|
+
});
|
|
133
|
+
}
|
|
60
134
|
};
|
|
61
135
|
}
|
|
62
136
|
|
|
63
|
-
/** Health indicator that
|
|
137
|
+
/** Health indicator that maps Prisma lifecycle status and probes connectivity with a trivial query. */
|
|
64
138
|
export class PrismaHealthIndicator {
|
|
65
139
|
key;
|
|
66
140
|
constructor(options = {}) {
|
|
@@ -69,11 +143,19 @@ export class PrismaHealthIndicator {
|
|
|
69
143
|
}
|
|
70
144
|
async check(key) {
|
|
71
145
|
const indicatorKey = resolveIndicatorKey('prisma', this.options.key ?? key);
|
|
72
|
-
const timeoutMs = this.options.timeoutMs ?? DEFAULT_PRISMA_TIMEOUT_MS;
|
|
73
146
|
try {
|
|
147
|
+
const timeoutMs = resolveIndicatorTimeoutMs(this.options.timeoutMs, DEFAULT_PRISMA_TIMEOUT_MS, indicatorKey);
|
|
148
|
+
const snapshot = createPrismaLifecycleSnapshot(this.options.service);
|
|
149
|
+
const lifecycleDownResult = snapshot ? createPrismaLifecycleDownResult(indicatorKey, snapshot) : undefined;
|
|
150
|
+
if (lifecycleDownResult) {
|
|
151
|
+
throwHealthCheckError('Prisma health check failed.', lifecycleDownResult);
|
|
152
|
+
}
|
|
74
153
|
await withIndicatorTimeout(runPrismaPing(this.options), timeoutMs, indicatorKey);
|
|
75
|
-
return createUpResult(indicatorKey);
|
|
154
|
+
return createUpResult(indicatorKey, createPrismaLifecycleUpDetails(snapshot));
|
|
76
155
|
} catch (error) {
|
|
156
|
+
if (error instanceof Error && error.name === 'HealthCheckError') {
|
|
157
|
+
throw error;
|
|
158
|
+
}
|
|
77
159
|
throwHealthCheckError('Prisma health check failed.', createDownResult(indicatorKey, error instanceof Error ? error.message : 'Prisma health check failed.'));
|
|
78
160
|
}
|
|
79
161
|
}
|
package/dist/indicators/redis.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createRedisPlatformStatusSnapshot, getRedisClientToken, getRedisComponentId } from '@fluojs/redis';
|
|
2
|
-
import { createDownResult, createUpResult, resolveIndicatorKey, throwHealthCheckError, withIndicatorTimeout } from './utils.js';
|
|
2
|
+
import { createDownResult, createUpResult, resolveIndicatorKey, resolveIndicatorTimeoutMs, throwHealthCheckError, withIndicatorTimeout } from './utils.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Options for probing Redis connectivity.
|
|
@@ -98,8 +98,8 @@ export class RedisHealthIndicator {
|
|
|
98
98
|
}
|
|
99
99
|
async check(key) {
|
|
100
100
|
const indicatorKey = resolveIndicatorKey('redis', this.options.key ?? key);
|
|
101
|
-
const timeoutMs = this.options.timeoutMs ?? DEFAULT_REDIS_TIMEOUT_MS;
|
|
102
101
|
try {
|
|
102
|
+
const timeoutMs = resolveIndicatorTimeoutMs(this.options.timeoutMs, DEFAULT_REDIS_TIMEOUT_MS, indicatorKey);
|
|
103
103
|
const lifecycleDownResult = createRedisLifecycleDownResult(indicatorKey, this.options);
|
|
104
104
|
if (lifecycleDownResult) {
|
|
105
105
|
throwHealthCheckError('Redis health check failed.', lifecycleDownResult);
|
|
@@ -3,6 +3,16 @@ import type { HealthIndicatorResult } from '../types.js';
|
|
|
3
3
|
export interface IndicatorTimeoutOptions {
|
|
4
4
|
timeoutMs?: number;
|
|
5
5
|
}
|
|
6
|
+
/**
|
|
7
|
+
* Resolve an indicator timeout budget before it reaches timeout scheduling.
|
|
8
|
+
*
|
|
9
|
+
* @param timeoutMs Caller-provided timeout budget in milliseconds.
|
|
10
|
+
* @param defaultTimeoutMs Default timeout budget used when `timeoutMs` is omitted.
|
|
11
|
+
* @param indicatorName Indicator name used in timeout validation errors.
|
|
12
|
+
* @returns A positive integer timeout budget in milliseconds.
|
|
13
|
+
* @throws {Error} When the resolved timeout is zero, negative, `NaN`, or infinite.
|
|
14
|
+
*/
|
|
15
|
+
export declare function resolveIndicatorTimeoutMs(timeoutMs: number | undefined, defaultTimeoutMs: number, indicatorName: string): number;
|
|
6
16
|
/**
|
|
7
17
|
* Create an `up` indicator result payload.
|
|
8
18
|
*
|
|
@@ -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;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,
|
|
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"}
|
package/dist/indicators/utils.js
CHANGED
|
@@ -2,6 +2,26 @@ import { HealthCheckError } from '../errors.js';
|
|
|
2
2
|
|
|
3
3
|
/** Timeout settings shared by indicators that call external dependencies. */
|
|
4
4
|
|
|
5
|
+
function normalizePositiveFiniteTimeoutMs(timeoutMs, indicatorName) {
|
|
6
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
|
7
|
+
throw new Error(`${indicatorName} health indicator timeoutMs must be a positive finite number.`);
|
|
8
|
+
}
|
|
9
|
+
return Math.max(1, Math.floor(timeoutMs));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Resolve an indicator timeout budget before it reaches timeout scheduling.
|
|
14
|
+
*
|
|
15
|
+
* @param timeoutMs Caller-provided timeout budget in milliseconds.
|
|
16
|
+
* @param defaultTimeoutMs Default timeout budget used when `timeoutMs` is omitted.
|
|
17
|
+
* @param indicatorName Indicator name used in timeout validation errors.
|
|
18
|
+
* @returns A positive integer timeout budget in milliseconds.
|
|
19
|
+
* @throws {Error} When the resolved timeout is zero, negative, `NaN`, or infinite.
|
|
20
|
+
*/
|
|
21
|
+
export function resolveIndicatorTimeoutMs(timeoutMs, defaultTimeoutMs, indicatorName) {
|
|
22
|
+
return normalizePositiveFiniteTimeoutMs(timeoutMs ?? defaultTimeoutMs, indicatorName);
|
|
23
|
+
}
|
|
24
|
+
|
|
5
25
|
/**
|
|
6
26
|
* Create an `up` indicator result payload.
|
|
7
27
|
*
|
|
@@ -45,10 +65,16 @@ export function createDownResult(key, message, details = {}) {
|
|
|
45
65
|
* @returns The original promise result when it finishes in time.
|
|
46
66
|
*/
|
|
47
67
|
export function withIndicatorTimeout(promise, timeoutMs, indicatorName) {
|
|
68
|
+
let normalizedTimeoutMs;
|
|
69
|
+
try {
|
|
70
|
+
normalizedTimeoutMs = normalizePositiveFiniteTimeoutMs(timeoutMs, indicatorName);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
return Promise.reject(error);
|
|
73
|
+
}
|
|
48
74
|
return new Promise((resolve, reject) => {
|
|
49
75
|
const timer = setTimeout(() => {
|
|
50
|
-
reject(new Error(`${indicatorName} health indicator timed out after ${String(
|
|
51
|
-
},
|
|
76
|
+
reject(new Error(`${indicatorName} health indicator timed out after ${String(normalizedTimeoutMs)}ms.`));
|
|
77
|
+
}, normalizedTimeoutMs);
|
|
52
78
|
promise.then(value => {
|
|
53
79
|
clearTimeout(timer);
|
|
54
80
|
resolve(value);
|
package/dist/module.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAGA,OAAO,EAGL,KAAK,UAAU,
|
|
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"}
|
package/dist/module.js
CHANGED
|
@@ -83,7 +83,7 @@ function createPlatformReadinessDiagnostic(readiness) {
|
|
|
83
83
|
};
|
|
84
84
|
}
|
|
85
85
|
function createPlatformDiagnosticCollisionKey(diagnosticKey, seenKeys) {
|
|
86
|
-
const baseKey = `${diagnosticKey}-
|
|
86
|
+
const baseKey = `${diagnosticKey}-user-key-collision`;
|
|
87
87
|
if (!seenKeys.has(baseKey)) {
|
|
88
88
|
return baseKey;
|
|
89
89
|
}
|
|
@@ -95,42 +95,51 @@ function createPlatformDiagnosticCollisionKey(diagnosticKey, seenKeys) {
|
|
|
95
95
|
}
|
|
96
96
|
return candidate;
|
|
97
97
|
}
|
|
98
|
-
function appendPlatformDiagnostic(entries,
|
|
98
|
+
function appendPlatformDiagnostic(entries, collisionEntries, diagnosticKey, diagnostic) {
|
|
99
99
|
if (diagnostic === undefined) {
|
|
100
100
|
return;
|
|
101
101
|
}
|
|
102
|
-
if (
|
|
103
|
-
|
|
104
|
-
|
|
102
|
+
if (diagnosticKey in collisionEntries) {
|
|
103
|
+
const collisionKey = createPlatformDiagnosticCollisionKey(diagnosticKey, new Set([...Object.keys(entries), ...Object.keys(collisionEntries)]));
|
|
104
|
+
entries[collisionKey] = {
|
|
105
|
+
message: `User health result key "${diagnosticKey}" is reserved for Terminus platform diagnostics; the platform diagnostic remains available under the reserved key.`,
|
|
106
|
+
status: 'down'
|
|
107
|
+
};
|
|
105
108
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
entries[diagnosticKey] = diagnostic;
|
|
110
|
+
}
|
|
111
|
+
function withoutKeys(entries, keys) {
|
|
112
|
+
return Object.fromEntries(Object.entries(entries).filter(([key]) => !keys.has(key)));
|
|
113
|
+
}
|
|
114
|
+
function remapContributors(contributors, reservedKeys, platformDiagnosticKeys) {
|
|
115
|
+
return contributors.filter(key => !reservedKeys.has(key) && !platformDiagnosticKeys.includes(key));
|
|
111
116
|
}
|
|
112
117
|
function withPlatformDiagnostics(report, health, readiness) {
|
|
113
118
|
const platformDiagnostics = {};
|
|
114
119
|
const healthDiagnostic = createPlatformHealthDiagnostic(health);
|
|
115
120
|
const readinessDiagnostic = createPlatformReadinessDiagnostic(readiness);
|
|
116
|
-
|
|
117
|
-
appendPlatformDiagnostic(platformDiagnostics,
|
|
118
|
-
appendPlatformDiagnostic(platformDiagnostics, existingKeys, 'fluo-platform-readiness', readinessDiagnostic);
|
|
121
|
+
appendPlatformDiagnostic(platformDiagnostics, report.details, 'fluo-platform-health', healthDiagnostic);
|
|
122
|
+
appendPlatformDiagnostic(platformDiagnostics, report.details, 'fluo-platform-readiness', readinessDiagnostic);
|
|
119
123
|
const platformDiagnosticKeys = Object.keys(platformDiagnostics);
|
|
124
|
+
const platformReservedKeys = new Set([...(healthDiagnostic ? ['fluo-platform-health'] : []), ...(readinessDiagnostic ? ['fluo-platform-readiness'] : [])]);
|
|
125
|
+
const reportDetails = withoutKeys(report.details, platformReservedKeys);
|
|
126
|
+
const reportInfo = withoutKeys(report.info, platformReservedKeys);
|
|
127
|
+
const reportError = withoutKeys(report.error, platformReservedKeys);
|
|
120
128
|
return {
|
|
121
129
|
...report,
|
|
122
130
|
contributors: {
|
|
123
|
-
down: [...report.contributors.down, ...platformDiagnosticKeys],
|
|
124
|
-
up:
|
|
131
|
+
down: [...remapContributors(report.contributors.down, platformReservedKeys, platformDiagnosticKeys), ...platformDiagnosticKeys],
|
|
132
|
+
up: remapContributors(report.contributors.up, platformReservedKeys, platformDiagnosticKeys)
|
|
125
133
|
},
|
|
126
134
|
details: {
|
|
127
|
-
...
|
|
135
|
+
...reportDetails,
|
|
128
136
|
...platformDiagnostics
|
|
129
137
|
},
|
|
130
138
|
error: {
|
|
131
|
-
...
|
|
139
|
+
...reportError,
|
|
132
140
|
...platformDiagnostics
|
|
133
141
|
},
|
|
142
|
+
info: reportInfo,
|
|
134
143
|
platform: {
|
|
135
144
|
health,
|
|
136
145
|
readiness
|
|
@@ -159,7 +168,7 @@ function createTerminusRuntimeModule(options = {}) {
|
|
|
159
168
|
const TERMINUS_READINESS_REGISTRAR = Symbol('fluo.terminus.readiness-registrar');
|
|
160
169
|
class TerminusRuntimeModule {}
|
|
161
170
|
return defineModule(TerminusRuntimeModule, {
|
|
162
|
-
exports: [TERMINUS_HEALTH_INDICATORS, TerminusHealthService],
|
|
171
|
+
exports: [TERMINUS_HEALTH_INDICATORS, TERMINUS_INDICATOR_PROVIDER_TOKENS, TerminusHealthService],
|
|
163
172
|
imports: [healthModule],
|
|
164
173
|
providers: [...createTerminusProviders({
|
|
165
174
|
execution: options.execution,
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"liveness",
|
|
10
10
|
"health-check"
|
|
11
11
|
],
|
|
12
|
-
"version": "1.0
|
|
12
|
+
"version": "1.1.0",
|
|
13
13
|
"private": false,
|
|
14
14
|
"license": "MIT",
|
|
15
15
|
"repository": {
|
|
@@ -44,15 +44,15 @@
|
|
|
44
44
|
"dist"
|
|
45
45
|
],
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@fluojs/core": "^1.0
|
|
48
|
-
"@fluojs/di": "^
|
|
49
|
-
"@fluojs/http": "^
|
|
50
|
-
"@fluojs/runtime": "^
|
|
47
|
+
"@fluojs/core": "^1.1.0",
|
|
48
|
+
"@fluojs/di": "^2.0.0",
|
|
49
|
+
"@fluojs/http": "^2.0.1",
|
|
50
|
+
"@fluojs/runtime": "^2.0.1"
|
|
51
51
|
},
|
|
52
52
|
"peerDependencies": {
|
|
53
|
-
"@fluojs/drizzle": "^1.
|
|
54
|
-
"@fluojs/prisma": "^1.
|
|
55
|
-
"@fluojs/redis": "^1.0
|
|
53
|
+
"@fluojs/drizzle": "^1.1.1",
|
|
54
|
+
"@fluojs/prisma": "^1.1.1",
|
|
55
|
+
"@fluojs/redis": "^1.1.0"
|
|
56
56
|
},
|
|
57
57
|
"peerDependenciesMeta": {
|
|
58
58
|
"@fluojs/drizzle": {
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|
|
69
69
|
"vitest": "^3.2.4",
|
|
70
|
-
"@fluojs/testing": "^
|
|
70
|
+
"@fluojs/testing": "^2.0.0"
|
|
71
71
|
},
|
|
72
72
|
"scripts": {
|
|
73
73
|
"prebuild": "node ../../tooling/scripts/clean-dist.mjs",
|