@firebreak/vitals 1.2.0 → 1.2.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.
Files changed (2) hide show
  1. package/README.md +85 -14
  2. package/package.json +37 -9
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Vitals (Node.js)
2
2
 
3
- Structured deep healthcheck endpoints for Node.js services. 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. Unauthenticated callers get a lightweight "alive" response with optional metadata; authenticated callers get full diagnostic results.
4
4
 
5
5
  ## Install
6
6
 
@@ -36,9 +36,21 @@ const app = express();
36
36
  app.get('/vitals', createHealthcheckMiddleware({
37
37
  registry,
38
38
  token: process.env.VITALS_TOKEN,
39
+ metadata: {
40
+ build: process.env.BUILD_SHA,
41
+ buildTimestamp: process.env.BUILD_TIMESTAMP,
42
+ },
39
43
  }));
40
44
  ```
41
45
 
46
+ Hit `/vitals` without a token to get a shallow response:
47
+
48
+ ```json
49
+ { "status": "ok", "timestamp": "...", "build": "stg-45d76e5", "buildTimestamp": "2025-02-25T19:41:56Z" }
50
+ ```
51
+
52
+ Provide the token (`?token=...` or `Authorization: Bearer ...`) to get the full deep response with check results.
53
+
42
54
  ## Quick Start (Next.js)
43
55
 
44
56
  ```typescript
@@ -58,6 +70,9 @@ registry.add('api', httpCheck(`${process.env.API_BASE_URL}/health`));
58
70
  export const GET = createNextHandler({
59
71
  registry,
60
72
  token: process.env.VITALS_TOKEN,
73
+ metadata: {
74
+ build: process.env.BUILD_SHA,
75
+ },
61
76
  });
62
77
  ```
63
78
 
@@ -194,6 +209,9 @@ app.get('/vitals', createHealthcheckMiddleware({
194
209
  registry,
195
210
  token: 'my-secret-token', // optional — omit to disable auth
196
211
  queryParamName: 'token', // default: 'token'
212
+ metadata: { // optional — included in shallow & deep responses
213
+ build: 'stg-45d76e5',
214
+ },
197
215
  }));
198
216
  ```
199
217
 
@@ -205,6 +223,7 @@ import { createNextHandler } from '@firebreak/vitals/next';
205
223
  export const GET = createNextHandler({
206
224
  registry,
207
225
  token: process.env.VITALS_TOKEN,
226
+ metadata: { build: process.env.BUILD_SHA },
208
227
  });
209
228
  ```
210
229
 
@@ -215,19 +234,55 @@ For other frameworks, use the generic handler directly:
215
234
  ```typescript
216
235
  import { createHealthcheckHandler } from '@firebreak/vitals';
217
236
 
218
- const handle = createHealthcheckHandler({ registry, token: process.env.VITALS_TOKEN });
237
+ const handle = createHealthcheckHandler({
238
+ registry,
239
+ token: process.env.VITALS_TOKEN,
240
+ metadata: { build: 'stg-45d76e5' },
241
+ });
242
+
243
+ // No token → shallow response
244
+ const shallow = await handle({});
245
+ // { status: 200, body: { status: 'ok', timestamp: '...', build: 'stg-45d76e5' } }
219
246
 
220
- // In any framework:
221
- const result = await handle({
222
- queryParams: { token: 'abc' },
223
- authorizationHeader: 'Bearer abc',
247
+ // Valid token → deep response
248
+ const deep = await handle({ queryParams: { token: '...' } });
249
+ // { status: 200, body: { status: 'healthy', timestamp: '...', build: 'stg-45d76e5', checks: { ... } } }
250
+ ```
251
+
252
+ ### Shallow / Deep Responses
253
+
254
+ When a `token` is configured, the endpoint serves two tiers from a single route:
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 | Deep response always (backwards-compatible) |
262
+
263
+ 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.
264
+
265
+ ### Metadata
266
+
267
+ Attach static key-value pairs that appear in both shallow and deep responses:
268
+
269
+ ```typescript
270
+ createHealthcheckHandler({
271
+ registry,
272
+ token: process.env.VITALS_TOKEN,
273
+ metadata: {
274
+ build: 'stg-45d76e5',
275
+ buildTimestamp: '2025-02-25T19:41:56Z',
276
+ region: 'us-east-1',
277
+ },
224
278
  });
225
- // result = { status: 200, body: { status: 'healthy', ... } }
226
279
  ```
227
280
 
281
+ Metadata values can be `string`, `number`, or `boolean`. Keys `status`, `timestamp`, and `checks` are reserved — passing them throws at handler creation time.
282
+
228
283
  ### Authentication
229
284
 
230
- When a token is configured, requests must provide it via:
285
+ Tokens can be provided via:
231
286
  - Query parameter: `?token=my-secret-token`
232
287
  - Bearer header: `Authorization: Bearer my-secret-token`
233
288
 
@@ -247,10 +302,25 @@ const token = extractToken({
247
302
 
248
303
  ## Response Format
249
304
 
305
+ **Shallow response** (no token provided):
306
+
307
+ ```json
308
+ {
309
+ "status": "ok",
310
+ "timestamp": "2025-02-26T12:00:00.000Z",
311
+ "build": "stg-45d76e5",
312
+ "buildTimestamp": "2025-02-25T19:41:56Z"
313
+ }
314
+ ```
315
+
316
+ **Deep response** (valid token provided):
317
+
250
318
  ```json
251
319
  {
252
320
  "status": "healthy",
253
321
  "timestamp": "2025-02-26T12:00:00.000Z",
322
+ "build": "stg-45d76e5",
323
+ "buildTimestamp": "2025-02-25T19:41:56Z",
254
324
  "checks": {
255
325
  "postgres": {
256
326
  "status": "healthy",
@@ -266,12 +336,13 @@ const token = extractToken({
266
336
  }
267
337
  ```
268
338
 
269
- | Overall Status | HTTP Code |
270
- |---------------|-----------|
271
- | `healthy` | `200` |
272
- | `degraded` | `503` |
273
- | `outage` | `503` |
274
- | Auth failure | `403` |
339
+ | Condition | HTTP Code | Status |
340
+ |-----------|-----------|--------|
341
+ | Shallow (no token) | `200` | `"ok"` |
342
+ | Deep — healthy | `200` | `"healthy"` |
343
+ | Deep — degraded | `503` | `"degraded"` |
344
+ | Deep outage | `503` | `"outage"` |
345
+ | Invalid token | `403` | — |
275
346
 
276
347
  ## Requirements
277
348
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@firebreak/vitals",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "description": "Deep healthcheck endpoints following the HEALTHCHECK_SPEC format",
5
5
  "type": "module",
6
6
  "exports": {
@@ -88,9 +88,9 @@
88
88
  "lint": "tsc --noEmit"
89
89
  },
90
90
  "peerDependencies": {
91
- "pg": ">=8.0.0",
91
+ "express": ">=4.0.0",
92
92
  "ioredis": ">=5.0.0",
93
- "express": ">=4.0.0"
93
+ "pg": ">=8.0.0"
94
94
  },
95
95
  "peerDependenciesMeta": {
96
96
  "pg": {
@@ -104,17 +104,45 @@
104
104
  }
105
105
  },
106
106
  "devDependencies": {
107
- "tsup": "^8.0.0",
108
- "typescript": "^5.5.0",
109
- "vitest": "^2.0.0",
110
- "@types/node": "^22.0.0",
111
107
  "@types/express": "^5.0.0",
108
+ "@types/node": "^22.0.0",
112
109
  "@types/pg": "^8.0.0",
113
- "ioredis": "^5.0.0",
110
+ "@types/supertest": "^6.0.0",
114
111
  "express": "^4.21.0",
112
+ "ioredis": "^5.0.0",
115
113
  "pg": "^8.13.0",
116
114
  "supertest": "^7.0.0",
117
- "@types/supertest": "^6.0.0"
115
+ "tsup": "^8.0.0",
116
+ "typescript": "^5.5.0",
117
+ "vitest": "^2.0.0"
118
+ },
119
+ "overrides": {
120
+ "array-flatten": "npm:@socketregistry/array-flatten@^1",
121
+ "es-define-property": "npm:@socketregistry/es-define-property@^1",
122
+ "es-set-tostringtag": "npm:@socketregistry/es-set-tostringtag@^1",
123
+ "function-bind": "npm:@socketregistry/function-bind@^1",
124
+ "gopd": "npm:@socketregistry/gopd@^1",
125
+ "has-symbols": "npm:@socketregistry/has-symbols@^1",
126
+ "has-tostringtag": "npm:@socketregistry/has-tostringtag@^1",
127
+ "hasown": "npm:@socketregistry/hasown@^1",
128
+ "object-assign": "npm:@socketregistry/object-assign@^1",
129
+ "safe-buffer": "npm:@socketregistry/safe-buffer@^1",
130
+ "safer-buffer": "npm:@socketregistry/safer-buffer@^1",
131
+ "side-channel": "npm:@socketregistry/side-channel@^1"
132
+ },
133
+ "resolutions": {
134
+ "array-flatten": "npm:@socketregistry/array-flatten@^1",
135
+ "es-define-property": "npm:@socketregistry/es-define-property@^1",
136
+ "es-set-tostringtag": "npm:@socketregistry/es-set-tostringtag@^1",
137
+ "function-bind": "npm:@socketregistry/function-bind@^1",
138
+ "gopd": "npm:@socketregistry/gopd@^1",
139
+ "has-symbols": "npm:@socketregistry/has-symbols@^1",
140
+ "has-tostringtag": "npm:@socketregistry/has-tostringtag@^1",
141
+ "hasown": "npm:@socketregistry/hasown@^1",
142
+ "object-assign": "npm:@socketregistry/object-assign@^1",
143
+ "safe-buffer": "npm:@socketregistry/safe-buffer@^1",
144
+ "safer-buffer": "npm:@socketregistry/safer-buffer@^1",
145
+ "side-channel": "npm:@socketregistry/side-channel@^1"
118
146
  },
119
147
  "engines": {
120
148
  "node": ">=20.0.0"