@firebreak/vitals 2.0.0 → 2.0.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/README.md +67 -52
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Vitals (Node.js)
|
|
2
2
|
|
|
3
|
-
Structured healthcheck endpoints for Node.js services with shallow and deep response tiers. Register health checks, run them concurrently with timeouts, and expose them via Express or Next.js.
|
|
3
|
+
Structured healthcheck endpoints for Node.js services with shallow and deep response tiers. Register health checks, run them concurrently with timeouts, and expose them via Express or Next.js. A configurable depth policy controls whether callers get a lightweight "alive" response or full diagnostic results.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -19,7 +19,7 @@ npm install express # for Express integration
|
|
|
19
19
|
## Quick Start (Express)
|
|
20
20
|
|
|
21
21
|
```typescript
|
|
22
|
-
import { HealthcheckRegistry } from '@firebreak/vitals';
|
|
22
|
+
import { HealthcheckRegistry, extractToken, verifyToken } from '@firebreak/vitals';
|
|
23
23
|
import { createHealthcheckMiddleware } from '@firebreak/vitals/express';
|
|
24
24
|
import { pgPoolCheck } from '@firebreak/vitals/checks/postgres';
|
|
25
25
|
import { ioredisClientCheck } from '@firebreak/vitals/checks/redis';
|
|
@@ -35,7 +35,14 @@ registry.add('api', httpCheck('https://api.example.com/health'));
|
|
|
35
35
|
const app = express();
|
|
36
36
|
app.get('/vitals', createHealthcheckMiddleware({
|
|
37
37
|
registry,
|
|
38
|
-
|
|
38
|
+
resolveDepth: (req) => {
|
|
39
|
+
const token = extractToken({
|
|
40
|
+
queryParams: req.queryParams as Record<string, string>,
|
|
41
|
+
authorizationHeader: req.authorizationHeader as string | null,
|
|
42
|
+
});
|
|
43
|
+
if (token && verifyToken(token, process.env.VITALS_TOKEN!)) return 'deep';
|
|
44
|
+
return 'shallow';
|
|
45
|
+
},
|
|
39
46
|
metadata: {
|
|
40
47
|
build: process.env.BUILD_SHA,
|
|
41
48
|
buildTimestamp: process.env.BUILD_TIMESTAMP,
|
|
@@ -43,19 +50,19 @@ app.get('/vitals', createHealthcheckMiddleware({
|
|
|
43
50
|
}));
|
|
44
51
|
```
|
|
45
52
|
|
|
46
|
-
Hit `/vitals` without a token to get a shallow response:
|
|
53
|
+
Hit `/vitals` without a valid token to get a shallow response:
|
|
47
54
|
|
|
48
55
|
```json
|
|
49
56
|
{ "status": "ok", "timestamp": "...", "build": "stg-45d76e5", "buildTimestamp": "2025-02-25T19:41:56Z" }
|
|
50
57
|
```
|
|
51
58
|
|
|
52
|
-
|
|
59
|
+
When `resolveDepth` returns `'deep'`, the full deep response with check results is returned.
|
|
53
60
|
|
|
54
61
|
## Quick Start (Next.js)
|
|
55
62
|
|
|
56
63
|
```typescript
|
|
57
64
|
// app/vitals/route.ts
|
|
58
|
-
import { HealthcheckRegistry } from '@firebreak/vitals';
|
|
65
|
+
import { HealthcheckRegistry, extractToken, verifyToken } from '@firebreak/vitals';
|
|
59
66
|
import { createNextHandler } from '@firebreak/vitals/next';
|
|
60
67
|
import { pgClientCheck } from '@firebreak/vitals/checks/postgres';
|
|
61
68
|
import { httpCheck } from '@firebreak/vitals/checks/http';
|
|
@@ -69,7 +76,14 @@ registry.add('api', httpCheck(`${process.env.API_BASE_URL}/health`));
|
|
|
69
76
|
|
|
70
77
|
export const GET = createNextHandler({
|
|
71
78
|
registry,
|
|
72
|
-
|
|
79
|
+
resolveDepth: (req) => {
|
|
80
|
+
const token = extractToken({
|
|
81
|
+
queryParams: req.queryParams as Record<string, string>,
|
|
82
|
+
authorizationHeader: req.authorizationHeader as string | null,
|
|
83
|
+
});
|
|
84
|
+
if (token && verifyToken(token, process.env.VITALS_TOKEN!)) return 'deep';
|
|
85
|
+
return 'shallow';
|
|
86
|
+
},
|
|
73
87
|
metadata: {
|
|
74
88
|
build: process.env.BUILD_SHA,
|
|
75
89
|
},
|
|
@@ -207,8 +221,14 @@ import { createHealthcheckMiddleware } from '@firebreak/vitals/express';
|
|
|
207
221
|
|
|
208
222
|
app.get('/vitals', createHealthcheckMiddleware({
|
|
209
223
|
registry,
|
|
210
|
-
|
|
211
|
-
|
|
224
|
+
resolveDepth: (req) => { // optional — omit for always-shallow
|
|
225
|
+
const token = extractToken({
|
|
226
|
+
queryParams: req.queryParams as Record<string, string>,
|
|
227
|
+
authorizationHeader: req.authorizationHeader as string | null,
|
|
228
|
+
});
|
|
229
|
+
if (token && verifyToken(token, 'my-secret-token')) return 'deep';
|
|
230
|
+
return 'shallow';
|
|
231
|
+
},
|
|
212
232
|
metadata: { // optional — included in shallow & deep responses
|
|
213
233
|
build: 'stg-45d76e5',
|
|
214
234
|
},
|
|
@@ -222,7 +242,14 @@ import { createNextHandler } from '@firebreak/vitals/next';
|
|
|
222
242
|
|
|
223
243
|
export const GET = createNextHandler({
|
|
224
244
|
registry,
|
|
225
|
-
|
|
245
|
+
resolveDepth: (req) => {
|
|
246
|
+
const token = extractToken({
|
|
247
|
+
queryParams: req.queryParams as Record<string, string>,
|
|
248
|
+
authorizationHeader: req.authorizationHeader as string | null,
|
|
249
|
+
});
|
|
250
|
+
if (token && verifyToken(token, process.env.VITALS_TOKEN!)) return 'deep';
|
|
251
|
+
return 'shallow';
|
|
252
|
+
},
|
|
226
253
|
metadata: { build: process.env.BUILD_SHA },
|
|
227
254
|
});
|
|
228
255
|
```
|
|
@@ -232,59 +259,52 @@ export const GET = createNextHandler({
|
|
|
232
259
|
For other frameworks, use the generic handler directly:
|
|
233
260
|
|
|
234
261
|
```typescript
|
|
235
|
-
import { createHealthcheckHandler } from '@firebreak/vitals';
|
|
262
|
+
import { createHealthcheckHandler, extractToken, verifyToken } from '@firebreak/vitals';
|
|
236
263
|
|
|
237
264
|
const handle = createHealthcheckHandler({
|
|
238
265
|
registry,
|
|
239
|
-
|
|
266
|
+
resolveDepth: (req) => {
|
|
267
|
+
const token = extractToken({
|
|
268
|
+
queryParams: req.queryParams as Record<string, string>,
|
|
269
|
+
authorizationHeader: req.authorizationHeader as string | null,
|
|
270
|
+
});
|
|
271
|
+
if (token && verifyToken(token, process.env.VITALS_TOKEN!)) return 'deep';
|
|
272
|
+
return 'shallow';
|
|
273
|
+
},
|
|
240
274
|
metadata: { build: 'stg-45d76e5' },
|
|
241
275
|
});
|
|
242
276
|
|
|
243
|
-
//
|
|
277
|
+
// resolveDepth returns 'shallow' → shallow response
|
|
244
278
|
const shallow = await handle({});
|
|
245
279
|
// { status: 200, body: { status: 'ok', timestamp: '...', build: 'stg-45d76e5' } }
|
|
246
280
|
|
|
247
|
-
//
|
|
281
|
+
// resolveDepth returns 'deep' → deep response
|
|
248
282
|
const deep = await handle({ queryParams: { token: '...' } });
|
|
249
283
|
// { status: 200, body: { status: 'healthy', timestamp: '...', build: 'stg-45d76e5', checks: { ... } } }
|
|
250
284
|
```
|
|
251
285
|
|
|
252
286
|
### Shallow / Deep Responses
|
|
253
287
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
| Request | Response |
|
|
257
|
-
|---------|----------|
|
|
258
|
-
| No token provided | **Shallow** — `200` with `{ status: "ok", timestamp, ...metadata }` |
|
|
259
|
-
| Valid token provided | **Deep** — `200`/`503` with full check results + metadata |
|
|
260
|
-
| Invalid token provided | `403 Forbidden` |
|
|
261
|
-
| No token configured | **Shallow** by default (see `deep` option below) |
|
|
288
|
+
The endpoint serves two tiers from a single route, controlled by the `resolveDepth` option:
|
|
262
289
|
|
|
263
|
-
|
|
290
|
+
| `resolveDepth` returns | Response |
|
|
291
|
+
|------------------------|----------|
|
|
292
|
+
| `'shallow'` (or omitted) | **Shallow** — `200` with `{ status: "ok", timestamp, ...metadata }` |
|
|
293
|
+
| `'deep'` | **Deep** — `200`/`503` with full check results + metadata |
|
|
264
294
|
|
|
265
|
-
|
|
295
|
+
When `resolveDepth` is not provided, the handler always returns a shallow response.
|
|
266
296
|
|
|
267
|
-
|
|
297
|
+
The `resolveDepth` function receives the request object (`{ ip, headers, queryParams, authorizationHeader }`) and returns `'deep'` or `'shallow'` (or a promise of either). This lets you implement any depth policy — token-based, IP-based, header-based, or always-deep for internal services:
|
|
268
298
|
|
|
269
299
|
```typescript
|
|
300
|
+
// Always deep (internal service behind a private network)
|
|
270
301
|
createHealthcheckHandler({
|
|
271
302
|
registry,
|
|
272
|
-
|
|
303
|
+
resolveDepth: () => 'deep',
|
|
273
304
|
});
|
|
274
305
|
```
|
|
275
306
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
> **Security note:** Using `deep: true` with an empty string token (e.g. `token: ''`) will throw an error at handler creation time. This prevents misconfigured environments (e.g. `VITALS_TOKEN=""`) from silently exposing deep healthcheck data. If you intend to run without authentication, explicitly omit the `token` option or set it to `null`.
|
|
279
|
-
|
|
280
|
-
When both `token` and `deep` are set, the `token` takes precedence — callers must still authenticate to see deep results:
|
|
281
|
-
|
|
282
|
-
| Configuration | No token in request | Valid token | Invalid token |
|
|
283
|
-
|---------------|---------------------|-------------|---------------|
|
|
284
|
-
| `token` set, `deep` unset | Shallow | Deep | 403 |
|
|
285
|
-
| `token` set, `deep: true` | Shallow | Deep | 403 |
|
|
286
|
-
| No `token`, `deep` unset | Shallow | — | — |
|
|
287
|
-
| No `token`, `deep: true` | Deep | — | — |
|
|
307
|
+
The shallow response lets load balancers and uptime monitors confirm the process is alive without needing a secret. The deep response is a superset of shallow — it includes everything shallow returns plus the `checks` object and a real health status.
|
|
288
308
|
|
|
289
309
|
### Metadata
|
|
290
310
|
|
|
@@ -293,7 +313,6 @@ Attach static key-value pairs that appear in both shallow and deep responses:
|
|
|
293
313
|
```typescript
|
|
294
314
|
createHealthcheckHandler({
|
|
295
315
|
registry,
|
|
296
|
-
token: process.env.VITALS_TOKEN,
|
|
297
316
|
metadata: {
|
|
298
317
|
build: 'stg-45d76e5',
|
|
299
318
|
buildTimestamp: '2025-02-25T19:41:56Z',
|
|
@@ -304,29 +323,26 @@ createHealthcheckHandler({
|
|
|
304
323
|
|
|
305
324
|
Metadata values can be `string`, `number`, or `boolean`. Keys `status`, `timestamp`, and `checks` are reserved — passing them throws at handler creation time.
|
|
306
325
|
|
|
307
|
-
###
|
|
308
|
-
|
|
309
|
-
Tokens can be provided via:
|
|
310
|
-
- Query parameter: `?token=my-secret-token`
|
|
311
|
-
- Bearer header: `Authorization: Bearer my-secret-token`
|
|
326
|
+
### Utilities
|
|
312
327
|
|
|
313
|
-
|
|
328
|
+
`extractToken` and `verifyToken` are exported helpers for use inside your `resolveDepth` function.
|
|
314
329
|
|
|
315
330
|
```typescript
|
|
316
331
|
import { verifyToken, extractToken } from '@firebreak/vitals';
|
|
317
332
|
|
|
318
|
-
|
|
319
|
-
|
|
333
|
+
// Extract a token from query params (?token=...) or Authorization header
|
|
320
334
|
const token = extractToken({
|
|
321
335
|
queryParams: { token: 'abc' },
|
|
322
336
|
authorizationHeader: 'Bearer abc',
|
|
323
|
-
queryParamName: 'token',
|
|
324
337
|
});
|
|
338
|
+
|
|
339
|
+
// Timing-safe SHA-256 comparison
|
|
340
|
+
verifyToken('provided-token', 'expected-token'); // boolean
|
|
325
341
|
```
|
|
326
342
|
|
|
327
343
|
## Response Format
|
|
328
344
|
|
|
329
|
-
**Shallow response** (
|
|
345
|
+
**Shallow response** (`resolveDepth` returns `'shallow'` or is omitted):
|
|
330
346
|
|
|
331
347
|
```json
|
|
332
348
|
{
|
|
@@ -337,7 +353,7 @@ const token = extractToken({
|
|
|
337
353
|
}
|
|
338
354
|
```
|
|
339
355
|
|
|
340
|
-
**Deep response** (
|
|
356
|
+
**Deep response** (`resolveDepth` returns `'deep'`):
|
|
341
357
|
|
|
342
358
|
```json
|
|
343
359
|
{
|
|
@@ -362,11 +378,10 @@ const token = extractToken({
|
|
|
362
378
|
|
|
363
379
|
| Condition | HTTP Code | Status |
|
|
364
380
|
|-----------|-----------|--------|
|
|
365
|
-
| Shallow
|
|
381
|
+
| Shallow | `200` | `"ok"` |
|
|
366
382
|
| Deep — healthy | `200` | `"healthy"` |
|
|
367
383
|
| Deep — degraded | `503` | `"degraded"` |
|
|
368
384
|
| Deep — outage | `503` | `"outage"` |
|
|
369
|
-
| Invalid token | `403` | — |
|
|
370
385
|
|
|
371
386
|
## Requirements
|
|
372
387
|
|