@firebreak/vitals 1.0.0 → 1.0.2
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.md +209 -0
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# Vitals (Node.js)
|
|
2
|
+
|
|
3
|
+
Structured deep healthcheck endpoints for Node.js services. Register health checks, run them concurrently with timeouts, and expose them via Express.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install vitals
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Peer dependencies are optional — install only what you need:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install pg # for PostgreSQL checks
|
|
15
|
+
npm install ioredis # for Redis checks
|
|
16
|
+
npm install express # for Express integration
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import { HealthcheckRegistry, Status } from 'vitals';
|
|
23
|
+
import { createHealthcheckMiddleware } from 'vitals/express';
|
|
24
|
+
import { pgPoolCheck } from 'vitals/checks/postgres';
|
|
25
|
+
import { ioredisClientCheck } from 'vitals/checks/redis';
|
|
26
|
+
import express from 'express';
|
|
27
|
+
|
|
28
|
+
const registry = new HealthcheckRegistry({ defaultTimeout: 5000 });
|
|
29
|
+
|
|
30
|
+
// Add checks using an existing connection pool / client
|
|
31
|
+
registry.add('postgres', pgPoolCheck(pool));
|
|
32
|
+
registry.add('redis', ioredisClientCheck(redisClient));
|
|
33
|
+
|
|
34
|
+
// Or add a custom check
|
|
35
|
+
registry.add('api', async () => {
|
|
36
|
+
const start = Date.now();
|
|
37
|
+
const res = await fetch('https://api.example.com/ping');
|
|
38
|
+
return {
|
|
39
|
+
status: res.ok ? Status.HEALTHY : Status.OUTAGE,
|
|
40
|
+
latencyMs: Date.now() - start,
|
|
41
|
+
message: res.ok ? '' : `HTTP ${res.status}`,
|
|
42
|
+
};
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Mount as Express middleware
|
|
46
|
+
const app = express();
|
|
47
|
+
app.get('/healthcheck/deep', createHealthcheckMiddleware({
|
|
48
|
+
registry,
|
|
49
|
+
token: process.env.HEALTHCHECK_TOKEN,
|
|
50
|
+
}));
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## API
|
|
54
|
+
|
|
55
|
+
### `HealthcheckRegistry`
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
const registry = new HealthcheckRegistry({ defaultTimeout: 5000 });
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**`registry.add(name, checkFn, options?)`**
|
|
62
|
+
|
|
63
|
+
Register a named async check function. Options: `{ timeout?: number }`.
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
registry.add('service', async () => ({
|
|
67
|
+
status: Status.HEALTHY,
|
|
68
|
+
latencyMs: 0,
|
|
69
|
+
message: '',
|
|
70
|
+
}), { timeout: 3000 });
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**`registry.check(name, checkFn?, options?)`**
|
|
74
|
+
|
|
75
|
+
Decorator-style registration. Can be used as a method decorator or called directly.
|
|
76
|
+
|
|
77
|
+
**`registry.run()`**
|
|
78
|
+
|
|
79
|
+
Execute all registered checks concurrently. Returns a `HealthcheckResponse` with the worst overall status.
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
const response = await registry.run();
|
|
83
|
+
// { status: 2, timestamp: '...', checks: { service: { status: 2, latencyMs: 12, message: '' } } }
|
|
84
|
+
|
|
85
|
+
const json = toJson(response);
|
|
86
|
+
// { status: 'healthy', timestamp: '...', checks: { ... } }
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Status
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
import { Status, statusToLabel, statusFromString, httpStatusCode } from 'vitals';
|
|
93
|
+
|
|
94
|
+
Status.HEALTHY // 2
|
|
95
|
+
Status.DEGRADED // 1
|
|
96
|
+
Status.OUTAGE // 0
|
|
97
|
+
|
|
98
|
+
statusToLabel(Status.HEALTHY) // 'healthy'
|
|
99
|
+
statusFromString('degraded') // Status.DEGRADED
|
|
100
|
+
httpStatusCode(Status.HEALTHY) // 200
|
|
101
|
+
httpStatusCode(Status.OUTAGE) // 503
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Built-in Checks
|
|
105
|
+
|
|
106
|
+
#### PostgreSQL
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
import { pgClientCheck, pgPoolCheck } from 'vitals/checks/postgres';
|
|
110
|
+
|
|
111
|
+
// Fresh connection each time (good for validating connectivity)
|
|
112
|
+
registry.add('pg', pgClientCheck('postgresql://localhost:5432/mydb'));
|
|
113
|
+
|
|
114
|
+
// Existing pg.Pool (good for production — reuses connections)
|
|
115
|
+
registry.add('pg', pgPoolCheck(pool));
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
#### Redis
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
import { ioredisCheck, ioredisClientCheck } from 'vitals/checks/redis';
|
|
122
|
+
|
|
123
|
+
// Fresh connection each time
|
|
124
|
+
registry.add('redis', ioredisCheck('redis://localhost:6379'));
|
|
125
|
+
|
|
126
|
+
// Existing ioredis client
|
|
127
|
+
registry.add('redis', ioredisClientCheck(client));
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Sync Checks
|
|
131
|
+
|
|
132
|
+
Wrap synchronous functions to use as health checks:
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
import { syncCheck, Status } from 'vitals';
|
|
136
|
+
|
|
137
|
+
registry.add('disk', syncCheck(() => {
|
|
138
|
+
const free = checkDiskSpace('/');
|
|
139
|
+
return {
|
|
140
|
+
status: free > 1_000_000_000 ? Status.HEALTHY : Status.DEGRADED,
|
|
141
|
+
latencyMs: 0,
|
|
142
|
+
message: `${free} bytes free`,
|
|
143
|
+
};
|
|
144
|
+
}));
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Express Integration
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
import { createHealthcheckMiddleware } from 'vitals/express';
|
|
151
|
+
|
|
152
|
+
app.get('/healthcheck/deep', createHealthcheckMiddleware({
|
|
153
|
+
registry,
|
|
154
|
+
token: 'my-secret-token', // optional — omit to disable auth
|
|
155
|
+
queryParamName: 'token', // default: 'token'
|
|
156
|
+
}));
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
When a token is configured, requests must provide it via:
|
|
160
|
+
- Query parameter: `?token=my-secret-token`
|
|
161
|
+
- Bearer header: `Authorization: Bearer my-secret-token`
|
|
162
|
+
|
|
163
|
+
### Authentication
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
import { verifyToken, extractToken } from 'vitals';
|
|
167
|
+
|
|
168
|
+
// Timing-safe token comparison
|
|
169
|
+
verifyToken('provided-token', 'expected-token'); // boolean
|
|
170
|
+
|
|
171
|
+
// Extract token from request
|
|
172
|
+
const token = extractToken({
|
|
173
|
+
queryParams: { token: 'abc' },
|
|
174
|
+
authorizationHeader: 'Bearer abc',
|
|
175
|
+
queryParamName: 'token',
|
|
176
|
+
});
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Response Format
|
|
180
|
+
|
|
181
|
+
```json
|
|
182
|
+
{
|
|
183
|
+
"status": "healthy",
|
|
184
|
+
"timestamp": "2025-02-26T12:00:00.000Z",
|
|
185
|
+
"checks": {
|
|
186
|
+
"postgres": {
|
|
187
|
+
"status": "healthy",
|
|
188
|
+
"latencyMs": 4.2,
|
|
189
|
+
"message": ""
|
|
190
|
+
},
|
|
191
|
+
"redis": {
|
|
192
|
+
"status": "healthy",
|
|
193
|
+
"latencyMs": 1.1,
|
|
194
|
+
"message": ""
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
| Overall Status | HTTP Code |
|
|
201
|
+
|---------------|-----------|
|
|
202
|
+
| `healthy` | `200` |
|
|
203
|
+
| `degraded` | `503` |
|
|
204
|
+
| `outage` | `503` |
|
|
205
|
+
| Auth failure | `403` |
|
|
206
|
+
|
|
207
|
+
## Requirements
|
|
208
|
+
|
|
209
|
+
- Node.js >= 20.0.0
|