@fluojs/terminus 1.0.0-beta.1
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/LICENSE +21 -0
- package/README.ko.md +148 -0
- package/README.md +148 -0
- package/dist/errors.d.ts +6 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +8 -0
- package/dist/health-check.d.ts +37 -0
- package/dist/health-check.d.ts.map +1 -0
- package/dist/health-check.js +191 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/indicators/disk.d.ts +31 -0
- package/dist/indicators/disk.d.ts.map +1 -0
- package/dist/indicators/disk.js +73 -0
- package/dist/indicators/drizzle.d.ts +36 -0
- package/dist/indicators/drizzle.d.ts.map +1 -0
- package/dist/indicators/drizzle.js +64 -0
- package/dist/indicators/http.d.ts +33 -0
- package/dist/indicators/http.d.ts.map +1 -0
- package/dist/indicators/http.js +87 -0
- package/dist/indicators/index.d.ts +6 -0
- package/dist/indicators/index.d.ts.map +1 -0
- package/dist/indicators/index.js +5 -0
- package/dist/indicators/memory.d.ts +31 -0
- package/dist/indicators/memory.d.ts.map +1 -0
- package/dist/indicators/memory.js +68 -0
- package/dist/indicators/prisma.d.ts +38 -0
- package/dist/indicators/prisma.d.ts.map +1 -0
- package/dist/indicators/prisma.js +79 -0
- package/dist/indicators/redis.d.ts +36 -0
- package/dist/indicators/redis.d.ts.map +1 -0
- package/dist/indicators/redis.js +64 -0
- package/dist/indicators/utils.d.ts +48 -0
- package/dist/indicators/utils.d.ts.map +1 -0
- package/dist/indicators/utils.js +82 -0
- package/dist/module.d.ts +20 -0
- package/dist/module.d.ts.map +1 -0
- package/dist/module.js +138 -0
- package/dist/redis.d.ts +2 -0
- package/dist/redis.d.ts.map +1 -0
- package/dist/redis.js +1 -0
- package/dist/tokens.d.ts +9 -0
- package/dist/tokens.d.ts.map +1 -0
- package/dist/tokens.js +9 -0
- package/dist/types.d.ts +52 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/package.json +74 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 fluo contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.ko.md
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# @fluojs/terminus
|
|
2
|
+
|
|
3
|
+
<p><a href="./README.md"><kbd>English</kbd></a> <strong><kbd>한국어</kbd></strong></p>
|
|
4
|
+
|
|
5
|
+
fluo 애플리케이션을 위한 헬스 인디케이터(Health Indicator) 툴킷입니다. `@fluojs/terminus`는 런타임의 기본 health/readiness 엔드포인트 위에 의존성 인식 상태 보고 기능을 추가합니다.
|
|
6
|
+
|
|
7
|
+
## 목차
|
|
8
|
+
|
|
9
|
+
- [설치](#설치)
|
|
10
|
+
- [사용 시점](#사용-시점)
|
|
11
|
+
- [빠른 시작](#빠른-시작)
|
|
12
|
+
- [공통 패턴](#공통-패턴)
|
|
13
|
+
- [내장 인디케이터](#내장-인디케이터)
|
|
14
|
+
- [DI 기반 인디케이터](#di-기반-인디케이터)
|
|
15
|
+
- [실패 시맨틱](#실패-시맨틱)
|
|
16
|
+
- [공개 API 개요](#공개-api-개요)
|
|
17
|
+
- [관련 패키지](#관련-패키지)
|
|
18
|
+
- [예제 소스](#예제-소스)
|
|
19
|
+
|
|
20
|
+
## 설치
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pnpm add @fluojs/terminus
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## 사용 시점
|
|
27
|
+
|
|
28
|
+
- 외부 의존성(데이터베이스, Redis, API 등)의 상태를 애플리케이션 헬스 체크 결과에 포함해야 할 때.
|
|
29
|
+
- 표준 모니터링 패턴에 맞는 구조화된 JSON 헬스 보고서가 필요할 때.
|
|
30
|
+
- 핵심 하위 서비스에 접속할 수 없는 경우 `/ready` 체크가 실패하도록 설정해야 할 때.
|
|
31
|
+
|
|
32
|
+
## 빠른 시작
|
|
33
|
+
|
|
34
|
+
`TerminusModule.forRoot()`를 통해 헬스 인디케이터를 등록합니다.
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
import { Module } from '@fluojs/core';
|
|
38
|
+
import { HttpHealthIndicator, MemoryHealthIndicator, TerminusModule } from '@fluojs/terminus';
|
|
39
|
+
|
|
40
|
+
@Module({
|
|
41
|
+
imports: [
|
|
42
|
+
TerminusModule.forRoot({
|
|
43
|
+
indicators: [
|
|
44
|
+
new HttpHealthIndicator({ key: 'upstream-api', url: 'https://example.com/health' }),
|
|
45
|
+
new MemoryHealthIndicator({ key: 'memory', heapUsedThresholdRatio: 0.9 }),
|
|
46
|
+
],
|
|
47
|
+
}),
|
|
48
|
+
],
|
|
49
|
+
})
|
|
50
|
+
class AppModule {}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## 공통 패턴
|
|
54
|
+
|
|
55
|
+
### 내장 인디케이터
|
|
56
|
+
|
|
57
|
+
패키지에서 기본으로 제공하는 인디케이터들은 다음과 같습니다.
|
|
58
|
+
|
|
59
|
+
- `PrismaHealthIndicator` / `DrizzleHealthIndicator`
|
|
60
|
+
- `RedisHealthIndicator` (`@fluojs/terminus/redis`에서 제공)
|
|
61
|
+
- `HttpHealthIndicator`
|
|
62
|
+
- `MemoryHealthIndicator`
|
|
63
|
+
- `DiskHealthIndicator`
|
|
64
|
+
|
|
65
|
+
### DI 기반 인디케이터
|
|
66
|
+
|
|
67
|
+
Redis나 DB 클라이언트와 같이 DI 컨테이너의 의존성이 필요한 인디케이터를 사용할 때는, 모듈 로드 시점에 피어 의존성을 import하지 않도록 제공되는 provider 팩토리를 사용하세요.
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
import { TerminusModule } from '@fluojs/terminus';
|
|
71
|
+
import { createRedisHealthIndicatorProvider } from '@fluojs/terminus/redis';
|
|
72
|
+
|
|
73
|
+
TerminusModule.forRoot({
|
|
74
|
+
indicatorProviders: [
|
|
75
|
+
createRedisHealthIndicatorProvider({ key: 'redis' })
|
|
76
|
+
],
|
|
77
|
+
});
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`clientName`을 생략하면 기본 Redis 클라이언트를 계속 점검합니다. 이름 있는 Redis 연결을 점검하려면 `clientName`을 전달해 기본 클라이언트 대신 해당 named token을 해석하게 하세요.
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
TerminusModule.forRoot({
|
|
84
|
+
indicatorProviders: [
|
|
85
|
+
createRedisHealthIndicatorProvider({ key: 'redis' }),
|
|
86
|
+
createRedisHealthIndicatorProvider({ clientName: 'cache', key: 'cache-redis' }),
|
|
87
|
+
],
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### 실행 가드레일
|
|
92
|
+
|
|
93
|
+
커스텀 인디케이터가 멈추거나 느린 하위 서비스에 의존할 수 있다면 `execution.indicatorTimeoutMs`를 사용하세요. probe가 설정된 시간을 넘기면 Terminus는 무기한 대기하지 않고 해당 인디케이터를 `down`으로 표시합니다.
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
TerminusModule.forRoot({
|
|
97
|
+
execution: {
|
|
98
|
+
indicatorTimeoutMs: 1_500,
|
|
99
|
+
},
|
|
100
|
+
indicators: [
|
|
101
|
+
new HttpHealthIndicator({ key: 'upstream-api', url: 'https://example.com/health' }),
|
|
102
|
+
],
|
|
103
|
+
});
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### 실패 시맨틱
|
|
107
|
+
|
|
108
|
+
인디케이터가 실패하면 `HealthCheckError`를 던집니다. `TerminusHealthService`는 이 실패들을 모아 보고서를 작성합니다.
|
|
109
|
+
|
|
110
|
+
- 하나 이상의 인디케이터가 실패하면 `/health`는 HTTP `503`을 반환합니다.
|
|
111
|
+
- 준비 상태(readiness)와 관련된 인디케이터가 실패하면 `/ready`는 HTTP `503`을 반환합니다.
|
|
112
|
+
- 응답 본문은 `status`, `contributors`, `info`, `error`, `details`를 포함한 구조화된 JSON 객체입니다.
|
|
113
|
+
- 하나의 인디케이터가 여러 keyed entry를 반환할 수도 있으며, 이 경우 `/health`는 모든 entry를 `details`와 `contributors.up` / `contributors.down` 요약에 그대로 반영합니다.
|
|
114
|
+
- 같은 실행에서 이미 보고된 key를 다른 인디케이터가 다시 사용하면, Terminus는 먼저 기록된 entry를 유지하고 데이터를 조용히 덮어쓰는 대신 결정적인 `*-duplicate-key-error` contributor를 추가합니다.
|
|
115
|
+
|
|
116
|
+
## 공개 API 개요
|
|
117
|
+
|
|
118
|
+
### `TerminusModule`
|
|
119
|
+
|
|
120
|
+
- `static forRoot(options: TerminusModuleOptions): ModuleType`
|
|
121
|
+
- 인디케이터 및 provider 등록을 위한 메인 엔트리 포인트입니다.
|
|
122
|
+
|
|
123
|
+
### `TerminusHealthService`
|
|
124
|
+
|
|
125
|
+
- `check(): Promise<HealthCheckReport>`
|
|
126
|
+
- 현재 등록된 인디케이터를 실행해 집계된 보고서를 반환합니다.
|
|
127
|
+
- `isHealthy(): Promise<boolean>`
|
|
128
|
+
- 현재 집계 결과가 완전히 healthy 상태인지 반환합니다.
|
|
129
|
+
|
|
130
|
+
### `@fluojs/terminus/redis`
|
|
131
|
+
|
|
132
|
+
- `RedisHealthIndicator`, `createRedisHealthIndicator()`, `createRedisHealthIndicatorProvider()`
|
|
133
|
+
- Redis 전용 인디케이터 헬퍼는 선택적 Redis 피어가 설치되지 않은 환경에서도 루트 패키지 import가 안전하도록 전용 subpath에서 export됩니다.
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
### `HealthCheckError`
|
|
137
|
+
|
|
138
|
+
- 커스텀 인디케이터 내부에서 "down" 상태를 알리기 위해 이 에러를 발생시킵니다.
|
|
139
|
+
|
|
140
|
+
## 관련 패키지
|
|
141
|
+
|
|
142
|
+
- `@fluojs/metrics`: 가시성(Observability) 확보를 위해 자주 함께 사용됩니다.
|
|
143
|
+
- `@fluojs/prisma` / `@fluojs/drizzle` / `@fluojs/redis`: 특정 인디케이터를 위한 피어 의존성입니다.
|
|
144
|
+
|
|
145
|
+
## 예제 소스
|
|
146
|
+
|
|
147
|
+
- `examples/ops-metrics-terminus/src/app.ts`: 헬스 체크와 메트릭의 엔드투엔드 통합 예제.
|
|
148
|
+
- `packages/terminus/src/health-check.test.ts`: 집계 및 단언(assertion) 흐름 예제.
|
package/README.md
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# @fluojs/terminus
|
|
2
|
+
|
|
3
|
+
<p><strong><kbd>English</kbd></strong> <a href="./README.ko.md"><kbd>한국어</kbd></a></p>
|
|
4
|
+
|
|
5
|
+
Health indicator toolkit for fluo applications. `@fluojs/terminus` layers on top of runtime health/readiness endpoints to provide dependency-aware status reporting.
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
- [Installation](#installation)
|
|
10
|
+
- [When to Use](#when-to-use)
|
|
11
|
+
- [Quick Start](#quick-start)
|
|
12
|
+
- [Common Patterns](#common-patterns)
|
|
13
|
+
- [Built-in Indicators](#built-in-indicators)
|
|
14
|
+
- [DI-Backed Indicators](#di-backed-indicators)
|
|
15
|
+
- [Failure Semantics](#failure-semantics)
|
|
16
|
+
- [Public API Overview](#public-api-overview)
|
|
17
|
+
- [Related Packages](#related-packages)
|
|
18
|
+
- [Example Sources](#example-sources)
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pnpm add @fluojs/terminus
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## When to Use
|
|
27
|
+
|
|
28
|
+
- When you need to monitor external dependencies (databases, Redis, APIs) as part of your application's health status.
|
|
29
|
+
- When you want a structured JSON health report that aligns with standard monitoring patterns.
|
|
30
|
+
- When you need your `/ready` check to fail if critical downstream services are unreachable.
|
|
31
|
+
|
|
32
|
+
## Quick Start
|
|
33
|
+
|
|
34
|
+
Import `TerminusModule.forRoot()` to register health indicators.
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
import { Module } from '@fluojs/core';
|
|
38
|
+
import { HttpHealthIndicator, MemoryHealthIndicator, TerminusModule } from '@fluojs/terminus';
|
|
39
|
+
|
|
40
|
+
@Module({
|
|
41
|
+
imports: [
|
|
42
|
+
TerminusModule.forRoot({
|
|
43
|
+
indicators: [
|
|
44
|
+
new HttpHealthIndicator({ key: 'upstream-api', url: 'https://example.com/health' }),
|
|
45
|
+
new MemoryHealthIndicator({ key: 'memory', heapUsedThresholdRatio: 0.9 }),
|
|
46
|
+
],
|
|
47
|
+
}),
|
|
48
|
+
],
|
|
49
|
+
})
|
|
50
|
+
class AppModule {}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Common Patterns
|
|
54
|
+
|
|
55
|
+
### Built-in Indicators
|
|
56
|
+
|
|
57
|
+
The package provides several indicators out of the box:
|
|
58
|
+
|
|
59
|
+
- `PrismaHealthIndicator` / `DrizzleHealthIndicator`
|
|
60
|
+
- `RedisHealthIndicator` (from `@fluojs/terminus/redis`)
|
|
61
|
+
- `HttpHealthIndicator`
|
|
62
|
+
- `MemoryHealthIndicator`
|
|
63
|
+
- `DiskHealthIndicator`
|
|
64
|
+
|
|
65
|
+
### DI-Backed Indicators
|
|
66
|
+
|
|
67
|
+
To use indicators that require dependencies from the DI container (like Redis or Database clients) without importing peer dependencies at module load time, use the provider factories.
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
import { TerminusModule } from '@fluojs/terminus';
|
|
71
|
+
import { createRedisHealthIndicatorProvider } from '@fluojs/terminus/redis';
|
|
72
|
+
|
|
73
|
+
TerminusModule.forRoot({
|
|
74
|
+
indicatorProviders: [
|
|
75
|
+
createRedisHealthIndicatorProvider({ key: 'redis' })
|
|
76
|
+
],
|
|
77
|
+
});
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Omit `clientName` to keep probing the default Redis client. If the indicator should use a named Redis connection, pass `clientName` so the provider resolves that named client token instead of the default one.
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
TerminusModule.forRoot({
|
|
84
|
+
indicatorProviders: [
|
|
85
|
+
createRedisHealthIndicatorProvider({ key: 'redis' }),
|
|
86
|
+
createRedisHealthIndicatorProvider({ clientName: 'cache', key: 'cache-redis' }),
|
|
87
|
+
],
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Execution Guardrails
|
|
92
|
+
|
|
93
|
+
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.
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
TerminusModule.forRoot({
|
|
97
|
+
execution: {
|
|
98
|
+
indicatorTimeoutMs: 1_500,
|
|
99
|
+
},
|
|
100
|
+
indicators: [
|
|
101
|
+
new HttpHealthIndicator({ key: 'upstream-api', url: 'https://example.com/health' }),
|
|
102
|
+
],
|
|
103
|
+
});
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Failure Semantics
|
|
107
|
+
|
|
108
|
+
When an indicator fails, it throws a `HealthCheckError`. The `TerminusHealthService` aggregates these failures into a report:
|
|
109
|
+
|
|
110
|
+
- `/health` returns HTTP `503` if any indicator fails.
|
|
111
|
+
- `/ready` returns HTTP `503` if any indicator associated with readiness fails.
|
|
112
|
+
- The response body contains a structured JSON object with `status`, `contributors`, `info`, `error`, and `details`.
|
|
113
|
+
- Indicators may emit multiple keyed entries in a single check result; `/health` preserves every keyed entry in `details` and in the `contributors.up` / `contributors.down` summaries.
|
|
114
|
+
- 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.
|
|
115
|
+
|
|
116
|
+
## Public API Overview
|
|
117
|
+
|
|
118
|
+
### `TerminusModule`
|
|
119
|
+
|
|
120
|
+
- `static forRoot(options: TerminusModuleOptions): ModuleType`
|
|
121
|
+
- Main entry point for registering indicators and providers.
|
|
122
|
+
|
|
123
|
+
### `TerminusHealthService`
|
|
124
|
+
|
|
125
|
+
- `check(): Promise<HealthCheckReport>`
|
|
126
|
+
- Runs the currently registered indicators and returns the aggregated report.
|
|
127
|
+
- `isHealthy(): Promise<boolean>`
|
|
128
|
+
- Returns whether the current aggregated report is fully healthy.
|
|
129
|
+
|
|
130
|
+
### `@fluojs/terminus/redis`
|
|
131
|
+
|
|
132
|
+
- `RedisHealthIndicator`, `createRedisHealthIndicator()`, `createRedisHealthIndicatorProvider()`
|
|
133
|
+
- Redis-specific indicator helpers are exported from the dedicated subpath so the root package stays import-safe without the optional Redis peer installed.
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
### `HealthCheckError`
|
|
137
|
+
|
|
138
|
+
- Throw this error within custom indicators to signal a "down" state.
|
|
139
|
+
|
|
140
|
+
## Related Packages
|
|
141
|
+
|
|
142
|
+
- `@fluojs/metrics`: Often used together for observability.
|
|
143
|
+
- `@fluojs/prisma` / `@fluojs/drizzle` / `@fluojs/redis`: Peer dependencies for specific indicators.
|
|
144
|
+
|
|
145
|
+
## Example Sources
|
|
146
|
+
|
|
147
|
+
- `examples/ops-metrics-terminus/src/app.ts`: End-to-end integration of health and metrics.
|
|
148
|
+
- `packages/terminus/src/health-check.test.ts`: Demonstrates aggregation and assertion flow.
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAExD,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,MAAM,EAAE,qBAAqB,CAAC;gBAE3B,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,qBAAqB;CAK3D"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { HealthCheckExecutionOptions, HealthCheckReport, HealthIndicator } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Run every registered health indicator and aggregate their results.
|
|
4
|
+
*
|
|
5
|
+
* @param indicators Indicator instances to execute for the current health probe.
|
|
6
|
+
* @param executionOptions Optional timeout guardrails for indicator execution.
|
|
7
|
+
* @returns A structured report containing `info`, `error`, and full `details` maps.
|
|
8
|
+
*/
|
|
9
|
+
export declare function runHealthCheck(indicators: readonly HealthIndicator[], executionOptions?: HealthCheckExecutionOptions): Promise<HealthCheckReport>;
|
|
10
|
+
/**
|
|
11
|
+
* Assert that an aggregated health report is fully healthy.
|
|
12
|
+
*
|
|
13
|
+
* @param report Health report returned by `runHealthCheck(...)` or `TerminusHealthService.check()`.
|
|
14
|
+
* @param message Error message used when one or more indicators are down.
|
|
15
|
+
* @returns The same health report when every indicator is healthy.
|
|
16
|
+
* @throws {HealthCheckError} When the report contains at least one down indicator.
|
|
17
|
+
*/
|
|
18
|
+
export declare function assertHealthCheck(report: HealthCheckReport, message?: string): HealthCheckReport;
|
|
19
|
+
/** Service facade that resolves and runs the health indicators registered in Terminus. */
|
|
20
|
+
export declare class TerminusHealthService {
|
|
21
|
+
private readonly indicators;
|
|
22
|
+
private readonly executionOptions;
|
|
23
|
+
constructor(indicators: readonly HealthIndicator[], executionOptions?: HealthCheckExecutionOptions);
|
|
24
|
+
/**
|
|
25
|
+
* Execute all registered indicators once.
|
|
26
|
+
*
|
|
27
|
+
* @returns The aggregated health report for this check cycle.
|
|
28
|
+
*/
|
|
29
|
+
check(): Promise<HealthCheckReport>;
|
|
30
|
+
/**
|
|
31
|
+
* Return whether every registered indicator currently reports `up`.
|
|
32
|
+
*
|
|
33
|
+
* @returns `true` when the aggregated report status is `ok`.
|
|
34
|
+
*/
|
|
35
|
+
isHealthy(): Promise<boolean>;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=health-check.d.ts.map
|
|
@@ -0,0 +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;AA0LpB;;;;;;GAMG;AACH,wBAAsB,cAAc,CAClC,UAAU,EAAE,SAAS,eAAe,EAAE,EACtC,gBAAgB,GAAE,2BAAgC,GACjD,OAAO,CAAC,iBAAiB,CAAC,CAoB5B;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,iBAAiB,EAAE,OAAO,SAAyB,GAAG,iBAAiB,CAMhH;AAED,0FAA0F;AAC1F,qBAAa,qBAAqB;IAE9B,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,gBAAgB;gBADhB,UAAU,EAAE,SAAS,eAAe,EAAE,EACtC,gBAAgB,GAAE,2BAAgC;IAGrE;;;;OAIG;IACG,KAAK,IAAI,OAAO,CAAC,iBAAiB,CAAC;IAIzC;;;;OAIG;IACG,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC;CAGpC"}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { HealthCheckError } from './errors.js';
|
|
2
|
+
function normalizeIndicatorTimeoutMs(value) {
|
|
3
|
+
if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) {
|
|
4
|
+
return undefined;
|
|
5
|
+
}
|
|
6
|
+
return Math.floor(value);
|
|
7
|
+
}
|
|
8
|
+
function createTimeoutMessage(timeoutMs) {
|
|
9
|
+
return `Health indicator timed out after ${String(timeoutMs)}ms.`;
|
|
10
|
+
}
|
|
11
|
+
async function withTimeout(promise, timeoutMs) {
|
|
12
|
+
let timer;
|
|
13
|
+
try {
|
|
14
|
+
return await Promise.race([promise, new Promise((_, reject) => {
|
|
15
|
+
timer = setTimeout(() => {
|
|
16
|
+
reject(new Error(createTimeoutMessage(timeoutMs)));
|
|
17
|
+
}, timeoutMs);
|
|
18
|
+
})]);
|
|
19
|
+
} finally {
|
|
20
|
+
if (timer !== undefined) {
|
|
21
|
+
clearTimeout(timer);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function toFailureResult(key, error) {
|
|
26
|
+
return {
|
|
27
|
+
[key]: {
|
|
28
|
+
message: error instanceof Error ? error.message : 'Unknown health indicator error.',
|
|
29
|
+
status: 'down'
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function hasIndicatorStatus(value) {
|
|
34
|
+
if (typeof value !== 'object' || value === null) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
const status = value.status;
|
|
38
|
+
return status === 'up' || status === 'down';
|
|
39
|
+
}
|
|
40
|
+
function normalizeIndicatorResult(key, result) {
|
|
41
|
+
const normalizedEntries = Object.entries(result).filter(([, entryValue]) => hasIndicatorStatus(entryValue));
|
|
42
|
+
if (normalizedEntries.length > 0) {
|
|
43
|
+
return Object.fromEntries(normalizedEntries);
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
[key]: {
|
|
47
|
+
message: 'Indicator returned an unsupported status value.',
|
|
48
|
+
status: 'down'
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function inferIndicatorKey(indicator, index) {
|
|
53
|
+
const candidateKey = indicator.key;
|
|
54
|
+
if (typeof candidateKey === 'string' && candidateKey.trim().length > 0) {
|
|
55
|
+
return candidateKey;
|
|
56
|
+
}
|
|
57
|
+
const constructorName = indicator.constructor?.name;
|
|
58
|
+
if (typeof constructorName === 'string' && constructorName.trim().length > 0) {
|
|
59
|
+
return constructorName.replace(/HealthIndicator$/, '').replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
|
|
60
|
+
}
|
|
61
|
+
return `indicator-${String(index + 1)}`;
|
|
62
|
+
}
|
|
63
|
+
function createDuplicateKeyFailureKey(indicatorKey, seenKeys) {
|
|
64
|
+
const baseKey = `${indicatorKey}-duplicate-key-error`;
|
|
65
|
+
if (!seenKeys.has(baseKey)) {
|
|
66
|
+
return baseKey;
|
|
67
|
+
}
|
|
68
|
+
let suffix = 2;
|
|
69
|
+
let candidate = `${baseKey}-${String(suffix)}`;
|
|
70
|
+
while (seenKeys.has(candidate)) {
|
|
71
|
+
suffix += 1;
|
|
72
|
+
candidate = `${baseKey}-${String(suffix)}`;
|
|
73
|
+
}
|
|
74
|
+
return candidate;
|
|
75
|
+
}
|
|
76
|
+
function createDuplicateKeyFailure(indicatorKey, duplicateKeys, seenKeys) {
|
|
77
|
+
return [createDuplicateKeyFailureKey(indicatorKey, seenKeys), {
|
|
78
|
+
message: `Indicator produced duplicate result key(s): ${duplicateKeys.join(', ')}.`,
|
|
79
|
+
status: 'down'
|
|
80
|
+
}];
|
|
81
|
+
}
|
|
82
|
+
async function runIndicator(indicator, index, executionOptions) {
|
|
83
|
+
const key = inferIndicatorKey(indicator, index);
|
|
84
|
+
const indicatorTimeoutMs = normalizeIndicatorTimeoutMs(executionOptions.indicatorTimeoutMs);
|
|
85
|
+
try {
|
|
86
|
+
const result = indicatorTimeoutMs === undefined ? await indicator.check(key) : await withTimeout(indicator.check(key), indicatorTimeoutMs);
|
|
87
|
+
return {
|
|
88
|
+
entries: Object.entries(normalizeIndicatorResult(key, result)),
|
|
89
|
+
indicatorKey: key
|
|
90
|
+
};
|
|
91
|
+
} catch (error) {
|
|
92
|
+
if (error instanceof HealthCheckError) {
|
|
93
|
+
return {
|
|
94
|
+
entries: Object.entries(normalizeIndicatorResult(key, error.causes)),
|
|
95
|
+
indicatorKey: key
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
entries: Object.entries(toFailureResult(key, error)),
|
|
100
|
+
indicatorKey: key
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function aggregateIndicatorEntries(checks) {
|
|
105
|
+
const aggregatedEntries = [];
|
|
106
|
+
const seenKeys = new Set();
|
|
107
|
+
for (const check of checks) {
|
|
108
|
+
const duplicateKeys = [];
|
|
109
|
+
for (const entry of check.entries) {
|
|
110
|
+
const [entryKey] = entry;
|
|
111
|
+
if (seenKeys.has(entryKey)) {
|
|
112
|
+
duplicateKeys.push(entryKey);
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
seenKeys.add(entryKey);
|
|
116
|
+
aggregatedEntries.push(entry);
|
|
117
|
+
}
|
|
118
|
+
if (duplicateKeys.length > 0) {
|
|
119
|
+
const duplicateFailure = createDuplicateKeyFailure(check.indicatorKey, duplicateKeys, seenKeys);
|
|
120
|
+
seenKeys.add(duplicateFailure[0]);
|
|
121
|
+
aggregatedEntries.push(duplicateFailure);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return aggregatedEntries;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Run every registered health indicator and aggregate their results.
|
|
129
|
+
*
|
|
130
|
+
* @param indicators Indicator instances to execute for the current health probe.
|
|
131
|
+
* @param executionOptions Optional timeout guardrails for indicator execution.
|
|
132
|
+
* @returns A structured report containing `info`, `error`, and full `details` maps.
|
|
133
|
+
*/
|
|
134
|
+
export async function runHealthCheck(indicators, executionOptions = {}) {
|
|
135
|
+
const checks = aggregateIndicatorEntries(await Promise.all(indicators.map((indicator, index) => runIndicator(indicator, index, executionOptions))));
|
|
136
|
+
const details = Object.fromEntries(checks);
|
|
137
|
+
const infoEntries = checks.filter(([, result]) => result.status === 'up');
|
|
138
|
+
const errorEntries = checks.filter(([, result]) => result.status === 'down');
|
|
139
|
+
return {
|
|
140
|
+
checkedAt: new Date().toISOString(),
|
|
141
|
+
contributors: {
|
|
142
|
+
down: errorEntries.map(([key]) => key),
|
|
143
|
+
up: infoEntries.map(([key]) => key)
|
|
144
|
+
},
|
|
145
|
+
details,
|
|
146
|
+
error: Object.fromEntries(errorEntries),
|
|
147
|
+
info: Object.fromEntries(infoEntries),
|
|
148
|
+
status: errorEntries.length === 0 ? 'ok' : 'error'
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Assert that an aggregated health report is fully healthy.
|
|
154
|
+
*
|
|
155
|
+
* @param report Health report returned by `runHealthCheck(...)` or `TerminusHealthService.check()`.
|
|
156
|
+
* @param message Error message used when one or more indicators are down.
|
|
157
|
+
* @returns The same health report when every indicator is healthy.
|
|
158
|
+
* @throws {HealthCheckError} When the report contains at least one down indicator.
|
|
159
|
+
*/
|
|
160
|
+
export function assertHealthCheck(report, message = 'Health check failed.') {
|
|
161
|
+
if (report.status === 'error') {
|
|
162
|
+
throw new HealthCheckError(message, report.error);
|
|
163
|
+
}
|
|
164
|
+
return report;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** Service facade that resolves and runs the health indicators registered in Terminus. */
|
|
168
|
+
export class TerminusHealthService {
|
|
169
|
+
constructor(indicators, executionOptions = {}) {
|
|
170
|
+
this.indicators = indicators;
|
|
171
|
+
this.executionOptions = executionOptions;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Execute all registered indicators once.
|
|
176
|
+
*
|
|
177
|
+
* @returns The aggregated health report for this check cycle.
|
|
178
|
+
*/
|
|
179
|
+
async check() {
|
|
180
|
+
return runHealthCheck(this.indicators, this.executionOptions);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Return whether every registered indicator currently reports `up`.
|
|
185
|
+
*
|
|
186
|
+
* @returns `true` when the aggregated report status is `ok`.
|
|
187
|
+
*/
|
|
188
|
+
async isHealthy() {
|
|
189
|
+
return (await this.check()).status === 'ok';
|
|
190
|
+
}
|
|
191
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Provider } from '@fluojs/di';
|
|
2
|
+
import type { HealthIndicator, HealthIndicatorResult } from '../types.js';
|
|
3
|
+
/** Options for checking free disk bytes and free-ratio thresholds. */
|
|
4
|
+
export interface DiskHealthIndicatorOptions {
|
|
5
|
+
key?: string;
|
|
6
|
+
minFreeBytes?: number;
|
|
7
|
+
minFreeRatio?: number;
|
|
8
|
+
path?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Create a disk-space health indicator.
|
|
12
|
+
*
|
|
13
|
+
* @param options Optional filesystem path and free-space thresholds.
|
|
14
|
+
* @returns A health indicator backed by `statfs()`.
|
|
15
|
+
*/
|
|
16
|
+
export declare function createDiskHealthIndicator(options?: DiskHealthIndicatorOptions): HealthIndicator;
|
|
17
|
+
/**
|
|
18
|
+
* Create a provider that registers a `DiskHealthIndicator` instance.
|
|
19
|
+
*
|
|
20
|
+
* @param options Optional filesystem path and free-space thresholds.
|
|
21
|
+
* @returns A value provider that exposes `DiskHealthIndicator` from the DI container.
|
|
22
|
+
*/
|
|
23
|
+
export declare function createDiskHealthIndicatorProvider(options?: DiskHealthIndicatorOptions): Provider;
|
|
24
|
+
/** Health indicator that inspects free space for one filesystem path. */
|
|
25
|
+
export declare class DiskHealthIndicator implements HealthIndicator {
|
|
26
|
+
private readonly options;
|
|
27
|
+
readonly key: string | undefined;
|
|
28
|
+
constructor(options?: DiskHealthIndicatorOptions);
|
|
29
|
+
check(key: string): Promise<HealthIndicatorResult>;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=disk.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"disk.d.ts","sourceRoot":"","sources":["../../src/indicators/disk.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAG3C,OAAO,KAAK,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAE1E,sEAAsE;AACtE,MAAM,WAAW,0BAA0B;IACzC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAQD;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,GAAE,0BAA+B,GAAG,eAAe,CAEnG;AAED;;;;;GAKG;AACH,wBAAgB,iCAAiC,CAAC,OAAO,GAAE,0BAA+B,GAAG,QAAQ,CAKpG;AAED,yEAAyE;AACzE,qBAAa,mBAAoB,YAAW,eAAe;IAG7C,OAAO,CAAC,QAAQ,CAAC,OAAO;IAFpC,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;gBAEJ,OAAO,GAAE,0BAA+B;IAI/D,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC;CAsCzD"}
|