@fluojs/terminus 1.0.5 → 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 +9 -4
- package/README.md +9 -4
- package/dist/health-check.d.ts +6 -0
- package/dist/health-check.d.ts.map +1 -1
- package/dist/health-check.js +23 -15
- 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 +1 -1
- package/package.json +9 -9
package/README.ko.md
CHANGED
|
@@ -25,10 +25,12 @@ fluo 애플리케이션을 위한 헬스 인디케이터(Health Indicator) 툴
|
|
|
25
25
|
pnpm add @fluojs/terminus
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
사용하는 indicator provider seam에 필요한 선택적 peer만 설치하세요.
|
|
29
29
|
|
|
30
30
|
```bash
|
|
31
31
|
pnpm add @fluojs/redis ioredis
|
|
32
|
+
pnpm add @fluojs/prisma @prisma/client
|
|
33
|
+
pnpm add @fluojs/drizzle drizzle-orm
|
|
32
34
|
```
|
|
33
35
|
|
|
34
36
|
## 사용 시점
|
|
@@ -101,13 +103,15 @@ TerminusModule.forRoot({
|
|
|
101
103
|
|
|
102
104
|
Drizzle의 경우 `createDrizzleHealthIndicatorProvider()`는 `@fluojs/drizzle`이 노출하는 lifecycle-aware `DrizzleDatabase` wrapper를 우선 사용합니다. Drizzle이 종료 중이거나 중지되었거나 `DrizzleDatabase.createPlatformStatusSnapshot()` 기준으로 준비되지 않은 상태이면 SQL probe를 실행하기 전에 해당 indicator를 `down`으로 보고합니다. legacy raw `DRIZZLE_DATABASE` handle만 등록된 경우에는 기존 lightweight SQL probe 동작을 유지합니다.
|
|
103
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
|
+
|
|
104
108
|
Provider factory는 반복 등록할 수 있습니다. 각 인스턴스가 서로 다른 indicator key나 dependency option을 사용한다면 같은 factory가 만든 provider 여러 개를 하나의 `indicatorProviders` 배열에 등록할 수 있으며, Terminus는 나중에 등록된 같은 타입 provider가 앞선 provider를 덮어쓰지 않도록 각 provider 인스턴스를 별도 DI token으로 보관합니다.
|
|
105
109
|
|
|
106
110
|
### 실행 가드레일
|
|
107
111
|
|
|
108
112
|
커스텀 인디케이터가 멈추거나 느린 하위 서비스에 의존할 수 있다면 `execution.indicatorTimeoutMs`를 사용하세요. probe가 설정된 시간을 넘기면 Terminus는 무기한 대기하지 않고 해당 인디케이터를 `down`으로 표시합니다.
|
|
109
113
|
|
|
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을 막는 방식으로 보호합니다.
|
|
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을 막는 방식으로 보호합니다.
|
|
111
115
|
|
|
112
116
|
```typescript
|
|
113
117
|
TerminusModule.forRoot({
|
|
@@ -135,6 +139,7 @@ TerminusModule.forRoot({
|
|
|
135
139
|
- 같은 실행에서 이미 보고된 key를 다른 인디케이터가 다시 사용하면, Terminus는 먼저 기록된 entry를 유지하고 데이터를 조용히 덮어쓰는 대신 결정적인 `*-duplicate-key-error` contributor를 추가합니다.
|
|
136
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을 추가합니다.
|
|
137
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로 표시합니다.
|
|
138
143
|
- DI provider로 생성한 Drizzle indicator는 SQL probe보다 먼저 Drizzle lifecycle readiness/health state를 반영하므로, underlying driver가 raw ping을 아직 받을 수 있어도 종료 중이거나 중지된 통합은 `/health`와 `/ready`를 unavailable로 표시합니다.
|
|
139
144
|
- Redis subpath로 생성한 Redis indicator는 `PING` 전에 `@fluojs/redis` client lifecycle state를 반영하므로, 종료 중이거나 연결이 끊긴 Redis client는 command 실행 전에도 `/health`와 `/ready`를 unavailable로 표시합니다.
|
|
140
145
|
|
|
@@ -144,7 +149,7 @@ TerminusModule.forRoot({
|
|
|
144
149
|
|
|
145
150
|
Terminus는 별도의 process-only liveness route도 기본으로 만들지 않습니다. 기본 route model은 집계 헬스를 위한 `GET /health`, readiness를 위한 `GET /ready`입니다. 배포 환경에서 좁은 의미의 process liveness probe가 필요하다면, Terminus가 NestJS-style 추가 route를 만들어 준다고 가정하지 말고 애플리케이션 또는 배포 계층에서 해당 probe를 정의하세요.
|
|
146
151
|
|
|
147
|
-
Runtime-specific indicator는 subpath별로 분리되어 있습니다. Node.js memory 및 disk check에는 `@fluojs/terminus/node`를 사용하고, Redis check에는 `@fluojs/terminus/redis`를 사용하세요.
|
|
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합니다.
|
|
148
153
|
|
|
149
154
|
## 공개 API 개요
|
|
150
155
|
|
|
@@ -164,7 +169,7 @@ Runtime-specific indicator는 subpath별로 분리되어 있습니다. Node.js m
|
|
|
164
169
|
### 직접 helper와 token
|
|
165
170
|
|
|
166
171
|
- `runHealthCheck(...)`, `assertHealthCheck(...)`: 직접 aggregation/testing helper입니다.
|
|
167
|
-
- `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을 확인할 수 있습니다.
|
|
168
173
|
- Built-in indicator는 `create*HealthIndicator()` 및 `create*HealthIndicatorProvider()` helper도 노출합니다. Provider helper는 `indicatorProviders`를 위한 의도적인 DI composition 예외이며, 애플리케이션 등록은 계속 `TerminusModule.forRoot(...)`를 사용해야 합니다.
|
|
169
174
|
|
|
170
175
|
### `@fluojs/terminus/redis`
|
package/README.md
CHANGED
|
@@ -25,10 +25,12 @@ Health indicator toolkit for fluo applications. `@fluojs/terminus` layers on top
|
|
|
25
25
|
pnpm add @fluojs/terminus
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
Install
|
|
28
|
+
Install optional peers only for the indicator provider seams you enable:
|
|
29
29
|
|
|
30
30
|
```bash
|
|
31
31
|
pnpm add @fluojs/redis ioredis
|
|
32
|
+
pnpm add @fluojs/prisma @prisma/client
|
|
33
|
+
pnpm add @fluojs/drizzle drizzle-orm
|
|
32
34
|
```
|
|
33
35
|
|
|
34
36
|
## When to Use
|
|
@@ -101,13 +103,15 @@ Redis indicators created through `@fluojs/terminus/redis` are lifecycle-aware wh
|
|
|
101
103
|
|
|
102
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.
|
|
103
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
|
+
|
|
104
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.
|
|
105
109
|
|
|
106
110
|
### Execution Guardrails
|
|
107
111
|
|
|
108
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.
|
|
109
113
|
|
|
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.
|
|
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.
|
|
111
115
|
|
|
112
116
|
```typescript
|
|
113
117
|
TerminusModule.forRoot({
|
|
@@ -135,6 +139,7 @@ When an indicator returns a `down` result or throws a `HealthCheckError`, the `T
|
|
|
135
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.
|
|
136
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.
|
|
137
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.
|
|
138
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.
|
|
139
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.
|
|
140
145
|
|
|
@@ -144,7 +149,7 @@ When migrating from `@nestjs/terminus`, treat `TerminusModule.forRoot(...)` as t
|
|
|
144
149
|
|
|
145
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.
|
|
146
151
|
|
|
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.
|
|
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.
|
|
148
153
|
|
|
149
154
|
## Public API Overview
|
|
150
155
|
|
|
@@ -164,7 +169,7 @@ Runtime-specific indicators are split by subpath. Use `@fluojs/terminus/node` fo
|
|
|
164
169
|
### Direct helpers and tokens
|
|
165
170
|
|
|
166
171
|
- `runHealthCheck(...)`, `assertHealthCheck(...)`: Direct aggregation/testing helpers.
|
|
167
|
-
- `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.
|
|
168
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(...)`.
|
|
169
174
|
|
|
170
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
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { HealthCheckError } from './errors.js';
|
|
2
|
-
const runningIndicatorChecks = new WeakMap();
|
|
3
2
|
function normalizeIndicatorTimeoutMs(value) {
|
|
4
3
|
if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) {
|
|
5
4
|
return undefined;
|
|
@@ -17,7 +16,7 @@ function createInFlightResult(key) {
|
|
|
17
16
|
}
|
|
18
17
|
};
|
|
19
18
|
}
|
|
20
|
-
function startSerializedIndicatorCheck(indicator, key) {
|
|
19
|
+
function startSerializedIndicatorCheck(indicator, key, runningIndicatorChecks) {
|
|
21
20
|
const runningCheck = runningIndicatorChecks.get(indicator);
|
|
22
21
|
if (runningCheck) {
|
|
23
22
|
return undefined;
|
|
@@ -161,10 +160,10 @@ function createDuplicateKeyFailure(indicatorKey, duplicateKeys, seenKeys) {
|
|
|
161
160
|
status: 'down'
|
|
162
161
|
}];
|
|
163
162
|
}
|
|
164
|
-
async function runIndicator(indicator, index, executionOptions) {
|
|
163
|
+
async function runIndicator(indicator, index, executionOptions, runningIndicatorChecks) {
|
|
165
164
|
const key = inferIndicatorKey(indicator, index);
|
|
166
165
|
const indicatorTimeoutMs = normalizeIndicatorTimeoutMs(executionOptions.indicatorTimeoutMs);
|
|
167
|
-
const runningCheck = startSerializedIndicatorCheck(indicator, key);
|
|
166
|
+
const runningCheck = startSerializedIndicatorCheck(indicator, key, runningIndicatorChecks);
|
|
168
167
|
if (!runningCheck) {
|
|
169
168
|
return {
|
|
170
169
|
entries: Object.entries(createInFlightResult(key)),
|
|
@@ -212,16 +211,8 @@ function aggregateIndicatorEntries(checks) {
|
|
|
212
211
|
}
|
|
213
212
|
return aggregatedEntries;
|
|
214
213
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
* Run every registered health indicator and aggregate their results.
|
|
218
|
-
*
|
|
219
|
-
* @param indicators Indicator instances to execute for the current health probe.
|
|
220
|
-
* @param executionOptions Optional timeout guardrails for indicator execution.
|
|
221
|
-
* @returns A structured report containing `info`, `error`, and full `details` maps.
|
|
222
|
-
*/
|
|
223
|
-
export async function runHealthCheck(indicators, executionOptions = {}) {
|
|
224
|
-
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))));
|
|
225
216
|
const details = Object.fromEntries(checks);
|
|
226
217
|
const infoEntries = checks.filter(([, result]) => result.status === 'up');
|
|
227
218
|
const errorEntries = checks.filter(([, result]) => result.status === 'down');
|
|
@@ -238,6 +229,22 @@ export async function runHealthCheck(indicators, executionOptions = {}) {
|
|
|
238
229
|
};
|
|
239
230
|
}
|
|
240
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
|
+
|
|
241
248
|
/**
|
|
242
249
|
* Assert that an aggregated health report is fully healthy.
|
|
243
250
|
*
|
|
@@ -255,6 +262,7 @@ export function assertHealthCheck(report, message = 'Health check failed.') {
|
|
|
255
262
|
|
|
256
263
|
/** Service facade that resolves and runs the health indicators registered in Terminus. */
|
|
257
264
|
export class TerminusHealthService {
|
|
265
|
+
runningIndicatorChecks = new WeakMap();
|
|
258
266
|
constructor(indicators, executionOptions = {}) {
|
|
259
267
|
this.indicators = indicators;
|
|
260
268
|
this.executionOptions = executionOptions;
|
|
@@ -266,7 +274,7 @@ export class TerminusHealthService {
|
|
|
266
274
|
* @returns The aggregated health report for this check cycle.
|
|
267
275
|
*/
|
|
268
276
|
async check() {
|
|
269
|
-
return
|
|
277
|
+
return executeHealthCheck(this.indicators, this.executionOptions, this.runningIndicatorChecks);
|
|
270
278
|
}
|
|
271
279
|
|
|
272
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
|
@@ -168,7 +168,7 @@ function createTerminusRuntimeModule(options = {}) {
|
|
|
168
168
|
const TERMINUS_READINESS_REGISTRAR = Symbol('fluo.terminus.readiness-registrar');
|
|
169
169
|
class TerminusRuntimeModule {}
|
|
170
170
|
return defineModule(TerminusRuntimeModule, {
|
|
171
|
-
exports: [TERMINUS_HEALTH_INDICATORS, TerminusHealthService],
|
|
171
|
+
exports: [TERMINUS_HEALTH_INDICATORS, TERMINUS_INDICATOR_PROVIDER_TOKENS, TerminusHealthService],
|
|
172
172
|
imports: [healthModule],
|
|
173
173
|
providers: [...createTerminusProviders({
|
|
174
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.1.
|
|
54
|
-
"@fluojs/prisma": "^1.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",
|