@firebreak/vitals 1.3.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 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. Unauthenticated callers get a lightweight "alive" response with optional metadata; authenticated callers get full diagnostic results.
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
- token: process.env.VITALS_TOKEN,
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
- Provide the token (`?token=...` or `Authorization: Bearer ...`) to get the full deep response with check results.
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
- token: process.env.VITALS_TOKEN,
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
- token: 'my-secret-token', // optional — omit to disable auth
211
- queryParamName: 'token', // default: 'token'
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
- token: process.env.VITALS_TOKEN,
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
- token: process.env.VITALS_TOKEN,
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
- // No token → shallow response
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
- // Valid token → deep response
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
- 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 | **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
- 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.
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
- #### The `deep` Option
295
+ When `resolveDepth` is not provided, the handler always returns a shallow response.
266
296
 
267
- When no `token` is configured, the handler returns a **shallow** response by default. To expose full healthcheck results without requiring token authentication, set `deep: true`:
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
- deep: true, // no token → always return deep response
303
+ resolveDepth: () => 'deep',
273
304
  });
274
305
  ```
275
306
 
276
- This is useful for internal services behind a private network where token auth is unnecessary. The `deep` option defaults to `false`.
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
- ### Authentication
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
- Tokens are compared using timing-safe SHA-256 comparison.
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
- verifyToken('provided-token', 'expected-token'); // boolean
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** (no token provided):
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** (valid token provided):
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 (no token) | `200` | `"ok"` |
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
 
@@ -0,0 +1,20 @@
1
+ import { b as HealthcheckRegistry, H as HealthcheckResponseJson } from './core-Bee03bJm.js';
2
+
3
+ type ResolveDepthFn = (req: Record<string, unknown>) => 'deep' | 'shallow' | Promise<'deep' | 'shallow'>;
4
+ interface HealthcheckHandlerOptions {
5
+ registry: HealthcheckRegistry;
6
+ resolveDepth?: ResolveDepthFn;
7
+ metadata?: Record<string, string | number | boolean>;
8
+ }
9
+ interface ShallowResponseJson {
10
+ status: 'ok';
11
+ timestamp: string;
12
+ [key: string]: string | number | boolean;
13
+ }
14
+ interface HealthcheckHandlerResult {
15
+ status: number;
16
+ body: HealthcheckResponseJson | ShallowResponseJson;
17
+ }
18
+ declare function createHealthcheckHandler(options: HealthcheckHandlerOptions): (req: Record<string, unknown>) => Promise<HealthcheckHandlerResult>;
19
+
20
+ export { type HealthcheckHandlerOptions as H, type ResolveDepthFn as R, type ShallowResponseJson as S, type HealthcheckHandlerResult as a, createHealthcheckHandler as c };
@@ -0,0 +1,20 @@
1
+ import { b as HealthcheckRegistry, H as HealthcheckResponseJson } from './core-Bee03bJm.cjs';
2
+
3
+ type ResolveDepthFn = (req: Record<string, unknown>) => 'deep' | 'shallow' | Promise<'deep' | 'shallow'>;
4
+ interface HealthcheckHandlerOptions {
5
+ registry: HealthcheckRegistry;
6
+ resolveDepth?: ResolveDepthFn;
7
+ metadata?: Record<string, string | number | boolean>;
8
+ }
9
+ interface ShallowResponseJson {
10
+ status: 'ok';
11
+ timestamp: string;
12
+ [key: string]: string | number | boolean;
13
+ }
14
+ interface HealthcheckHandlerResult {
15
+ status: number;
16
+ body: HealthcheckResponseJson | ShallowResponseJson;
17
+ }
18
+ declare function createHealthcheckHandler(options: HealthcheckHandlerOptions): (req: Record<string, unknown>) => Promise<HealthcheckHandlerResult>;
19
+
20
+ export { type HealthcheckHandlerOptions as H, type ResolveDepthFn as R, type ShallowResponseJson as S, type HealthcheckHandlerResult as a, createHealthcheckHandler as c };
package/dist/index.cjs CHANGED
@@ -220,56 +220,36 @@ function extractToken(options) {
220
220
  // src/handler.ts
221
221
  var RESERVED_METADATA_KEYS = ["status", "timestamp", "checks", "cachedAt"];
222
222
  function createHealthcheckHandler(options) {
223
- const { registry, token: rawToken, deep = false, queryParamName = "token", metadata = {} } = options;
224
- const isEmptyToken = typeof rawToken === "string" && rawToken.trim() === "";
225
- if (deep && isEmptyToken) {
226
- throw new Error(
227
- "Cannot use `deep: true` with an empty string token. This would expose deep healthcheck data without authentication. Either set a non-empty token or explicitly set `token: null`."
228
- );
229
- }
230
- const token = rawToken == null || isEmptyToken ? null : rawToken;
223
+ const { registry, resolveDepth, metadata = {} } = options;
231
224
  for (const key of Object.keys(metadata)) {
232
225
  if (RESERVED_METADATA_KEYS.includes(key)) {
233
226
  throw new Error(`Metadata key '${key}' is reserved. Use a different key name.`);
234
227
  }
235
228
  }
236
229
  return async (req) => {
237
- if (token === null) {
238
- if (deep) {
239
- const response2 = await registry.run();
240
- return {
241
- status: httpStatusCode(response2.status),
242
- body: { ...metadata, ...toJson(response2) }
243
- };
230
+ let depth = "shallow";
231
+ if (resolveDepth) {
232
+ try {
233
+ const result = await resolveDepth(req);
234
+ if (result === "deep" || result === "shallow") {
235
+ depth = result;
236
+ }
237
+ } catch {
244
238
  }
245
- const body = {
246
- status: "ok",
247
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
248
- ...metadata
249
- };
250
- return { status: 200, body };
251
239
  }
252
- const provided = extractToken({
253
- queryParams: req.queryParams,
254
- authorizationHeader: req.authorizationHeader,
255
- queryParamName
256
- });
257
- if (provided === null) {
258
- const body = {
259
- status: "ok",
260
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
261
- ...metadata
240
+ if (depth === "deep") {
241
+ const response = await registry.run();
242
+ return {
243
+ status: httpStatusCode(response.status),
244
+ body: { ...metadata, ...toJson(response) }
262
245
  };
263
- return { status: 200, body };
264
- }
265
- if (!verifyToken(provided, token)) {
266
- return { status: 403, body: { error: "Forbidden" } };
267
246
  }
268
- const response = await registry.run();
269
- return {
270
- status: httpStatusCode(response.status),
271
- body: { ...metadata, ...toJson(response) }
247
+ const body = {
248
+ status: "ok",
249
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
250
+ ...metadata
272
251
  };
252
+ return { status: 200, body };
273
253
  };
274
254
  }
275
255
 
package/dist/index.d.cts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { H as HealthcheckResponseJson } from './core-Bee03bJm.cjs';
2
2
  export { A as AsyncCheckFn, C as CheckInput, a as CheckResult, b as HealthcheckRegistry, c as HealthcheckResponse, S as Status, d as StatusLabel, e as StatusValue, f as SyncCheckFn, h as httpStatusCode, s as statusFromString, g as statusToLabel, i as syncCheck, t as toJson } from './core-Bee03bJm.cjs';
3
- import { S as ShallowResponseJson } from './handler-COH7lot9.cjs';
4
- export { H as HealthcheckHandlerOptions, a as HealthcheckHandlerResult, b as HealthcheckRequest, c as createHealthcheckHandler } from './handler-COH7lot9.cjs';
3
+ import { S as ShallowResponseJson } from './handler-TZOgZvY7.cjs';
4
+ export { H as HealthcheckHandlerOptions, a as HealthcheckHandlerResult, R as ResolveDepthFn, c as createHealthcheckHandler } from './handler-TZOgZvY7.cjs';
5
5
 
6
6
  declare function verifyToken(provided: string, expected: string): boolean;
7
7
  declare function extractToken(options: {
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { H as HealthcheckResponseJson } from './core-Bee03bJm.js';
2
2
  export { A as AsyncCheckFn, C as CheckInput, a as CheckResult, b as HealthcheckRegistry, c as HealthcheckResponse, S as Status, d as StatusLabel, e as StatusValue, f as SyncCheckFn, h as httpStatusCode, s as statusFromString, g as statusToLabel, i as syncCheck, t as toJson } from './core-Bee03bJm.js';
3
- import { S as ShallowResponseJson } from './handler-CVYUad84.js';
4
- export { H as HealthcheckHandlerOptions, a as HealthcheckHandlerResult, b as HealthcheckRequest, c as createHealthcheckHandler } from './handler-CVYUad84.js';
3
+ import { S as ShallowResponseJson } from './handler-BvjN4Ot9.js';
4
+ export { H as HealthcheckHandlerOptions, a as HealthcheckHandlerResult, R as ResolveDepthFn, c as createHealthcheckHandler } from './handler-BvjN4Ot9.js';
5
5
 
6
6
  declare function verifyToken(provided: string, expected: string): boolean;
7
7
  declare function extractToken(options: {
package/dist/index.mjs CHANGED
@@ -184,56 +184,36 @@ function extractToken(options) {
184
184
  // src/handler.ts
185
185
  var RESERVED_METADATA_KEYS = ["status", "timestamp", "checks", "cachedAt"];
186
186
  function createHealthcheckHandler(options) {
187
- const { registry, token: rawToken, deep = false, queryParamName = "token", metadata = {} } = options;
188
- const isEmptyToken = typeof rawToken === "string" && rawToken.trim() === "";
189
- if (deep && isEmptyToken) {
190
- throw new Error(
191
- "Cannot use `deep: true` with an empty string token. This would expose deep healthcheck data without authentication. Either set a non-empty token or explicitly set `token: null`."
192
- );
193
- }
194
- const token = rawToken == null || isEmptyToken ? null : rawToken;
187
+ const { registry, resolveDepth, metadata = {} } = options;
195
188
  for (const key of Object.keys(metadata)) {
196
189
  if (RESERVED_METADATA_KEYS.includes(key)) {
197
190
  throw new Error(`Metadata key '${key}' is reserved. Use a different key name.`);
198
191
  }
199
192
  }
200
193
  return async (req) => {
201
- if (token === null) {
202
- if (deep) {
203
- const response2 = await registry.run();
204
- return {
205
- status: httpStatusCode(response2.status),
206
- body: { ...metadata, ...toJson(response2) }
207
- };
194
+ let depth = "shallow";
195
+ if (resolveDepth) {
196
+ try {
197
+ const result = await resolveDepth(req);
198
+ if (result === "deep" || result === "shallow") {
199
+ depth = result;
200
+ }
201
+ } catch {
208
202
  }
209
- const body = {
210
- status: "ok",
211
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
212
- ...metadata
213
- };
214
- return { status: 200, body };
215
203
  }
216
- const provided = extractToken({
217
- queryParams: req.queryParams,
218
- authorizationHeader: req.authorizationHeader,
219
- queryParamName
220
- });
221
- if (provided === null) {
222
- const body = {
223
- status: "ok",
224
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
225
- ...metadata
204
+ if (depth === "deep") {
205
+ const response = await registry.run();
206
+ return {
207
+ status: httpStatusCode(response.status),
208
+ body: { ...metadata, ...toJson(response) }
226
209
  };
227
- return { status: 200, body };
228
- }
229
- if (!verifyToken(provided, token)) {
230
- return { status: 403, body: { error: "Forbidden" } };
231
210
  }
232
- const response = await registry.run();
233
- return {
234
- status: httpStatusCode(response.status),
235
- body: { ...metadata, ...toJson(response) }
211
+ const body = {
212
+ status: "ok",
213
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
214
+ ...metadata
236
215
  };
216
+ return { status: 200, body };
237
217
  };
238
218
  }
239
219
 
@@ -24,28 +24,6 @@ __export(express_exports, {
24
24
  });
25
25
  module.exports = __toCommonJS(express_exports);
26
26
 
27
- // src/auth.ts
28
- var import_node_crypto = require("crypto");
29
- function verifyToken(provided, expected) {
30
- if (!provided || !expected) return false;
31
- const providedHash = (0, import_node_crypto.createHash)("sha256").update(provided).digest();
32
- const expectedHash = (0, import_node_crypto.createHash)("sha256").update(expected).digest();
33
- return (0, import_node_crypto.timingSafeEqual)(providedHash, expectedHash);
34
- }
35
- function extractToken(options) {
36
- const { queryParams, authorizationHeader, queryParamName = "token" } = options;
37
- if (queryParams) {
38
- const value = queryParams[queryParamName];
39
- const str = Array.isArray(value) ? value[0] : value;
40
- if (str) return str;
41
- }
42
- if (authorizationHeader?.startsWith("Bearer ")) {
43
- const token = authorizationHeader.slice("Bearer ".length);
44
- if (token) return token;
45
- }
46
- return null;
47
- }
48
-
49
27
  // src/types.ts
50
28
  var Status = {
51
29
  HEALTHY: 2,
@@ -83,56 +61,36 @@ function httpStatusCode(status) {
83
61
  // src/handler.ts
84
62
  var RESERVED_METADATA_KEYS = ["status", "timestamp", "checks", "cachedAt"];
85
63
  function createHealthcheckHandler(options) {
86
- const { registry, token: rawToken, deep = false, queryParamName = "token", metadata = {} } = options;
87
- const isEmptyToken = typeof rawToken === "string" && rawToken.trim() === "";
88
- if (deep && isEmptyToken) {
89
- throw new Error(
90
- "Cannot use `deep: true` with an empty string token. This would expose deep healthcheck data without authentication. Either set a non-empty token or explicitly set `token: null`."
91
- );
92
- }
93
- const token = rawToken == null || isEmptyToken ? null : rawToken;
64
+ const { registry, resolveDepth, metadata = {} } = options;
94
65
  for (const key of Object.keys(metadata)) {
95
66
  if (RESERVED_METADATA_KEYS.includes(key)) {
96
67
  throw new Error(`Metadata key '${key}' is reserved. Use a different key name.`);
97
68
  }
98
69
  }
99
70
  return async (req) => {
100
- if (token === null) {
101
- if (deep) {
102
- const response2 = await registry.run();
103
- return {
104
- status: httpStatusCode(response2.status),
105
- body: { ...metadata, ...toJson(response2) }
106
- };
71
+ let depth = "shallow";
72
+ if (resolveDepth) {
73
+ try {
74
+ const result = await resolveDepth(req);
75
+ if (result === "deep" || result === "shallow") {
76
+ depth = result;
77
+ }
78
+ } catch {
107
79
  }
108
- const body = {
109
- status: "ok",
110
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
111
- ...metadata
112
- };
113
- return { status: 200, body };
114
80
  }
115
- const provided = extractToken({
116
- queryParams: req.queryParams,
117
- authorizationHeader: req.authorizationHeader,
118
- queryParamName
119
- });
120
- if (provided === null) {
121
- const body = {
122
- status: "ok",
123
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
124
- ...metadata
81
+ if (depth === "deep") {
82
+ const response = await registry.run();
83
+ return {
84
+ status: httpStatusCode(response.status),
85
+ body: { ...metadata, ...toJson(response) }
125
86
  };
126
- return { status: 200, body };
127
- }
128
- if (!verifyToken(provided, token)) {
129
- return { status: 403, body: { error: "Forbidden" } };
130
87
  }
131
- const response = await registry.run();
132
- return {
133
- status: httpStatusCode(response.status),
134
- body: { ...metadata, ...toJson(response) }
88
+ const body = {
89
+ status: "ok",
90
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
91
+ ...metadata
135
92
  };
93
+ return { status: 200, body };
136
94
  };
137
95
  }
138
96
 
@@ -376,6 +334,8 @@ function createHealthcheckMiddleware(options) {
376
334
  return async (req, res) => {
377
335
  try {
378
336
  const result = await handle({
337
+ ip: req.ip,
338
+ headers: req.headers,
379
339
  queryParams: req.query,
380
340
  authorizationHeader: req.headers.authorization ?? null
381
341
  });
@@ -1,5 +1,5 @@
1
1
  import { RequestHandler } from 'express';
2
- import { H as HealthcheckHandlerOptions } from '../handler-COH7lot9.cjs';
2
+ import { H as HealthcheckHandlerOptions } from '../handler-TZOgZvY7.cjs';
3
3
  import '../core-Bee03bJm.cjs';
4
4
 
5
5
  type HealthcheckMiddlewareOptions = HealthcheckHandlerOptions;
@@ -1,5 +1,5 @@
1
1
  import { RequestHandler } from 'express';
2
- import { H as HealthcheckHandlerOptions } from '../handler-CVYUad84.js';
2
+ import { H as HealthcheckHandlerOptions } from '../handler-BvjN4Ot9.js';
3
3
  import '../core-Bee03bJm.js';
4
4
 
5
5
  type HealthcheckMiddlewareOptions = HealthcheckHandlerOptions;
@@ -1,25 +1,3 @@
1
- // src/auth.ts
2
- import { createHash, timingSafeEqual } from "crypto";
3
- function verifyToken(provided, expected) {
4
- if (!provided || !expected) return false;
5
- const providedHash = createHash("sha256").update(provided).digest();
6
- const expectedHash = createHash("sha256").update(expected).digest();
7
- return timingSafeEqual(providedHash, expectedHash);
8
- }
9
- function extractToken(options) {
10
- const { queryParams, authorizationHeader, queryParamName = "token" } = options;
11
- if (queryParams) {
12
- const value = queryParams[queryParamName];
13
- const str = Array.isArray(value) ? value[0] : value;
14
- if (str) return str;
15
- }
16
- if (authorizationHeader?.startsWith("Bearer ")) {
17
- const token = authorizationHeader.slice("Bearer ".length);
18
- if (token) return token;
19
- }
20
- return null;
21
- }
22
-
23
1
  // src/types.ts
24
2
  var Status = {
25
3
  HEALTHY: 2,
@@ -57,56 +35,36 @@ function httpStatusCode(status) {
57
35
  // src/handler.ts
58
36
  var RESERVED_METADATA_KEYS = ["status", "timestamp", "checks", "cachedAt"];
59
37
  function createHealthcheckHandler(options) {
60
- const { registry, token: rawToken, deep = false, queryParamName = "token", metadata = {} } = options;
61
- const isEmptyToken = typeof rawToken === "string" && rawToken.trim() === "";
62
- if (deep && isEmptyToken) {
63
- throw new Error(
64
- "Cannot use `deep: true` with an empty string token. This would expose deep healthcheck data without authentication. Either set a non-empty token or explicitly set `token: null`."
65
- );
66
- }
67
- const token = rawToken == null || isEmptyToken ? null : rawToken;
38
+ const { registry, resolveDepth, metadata = {} } = options;
68
39
  for (const key of Object.keys(metadata)) {
69
40
  if (RESERVED_METADATA_KEYS.includes(key)) {
70
41
  throw new Error(`Metadata key '${key}' is reserved. Use a different key name.`);
71
42
  }
72
43
  }
73
44
  return async (req) => {
74
- if (token === null) {
75
- if (deep) {
76
- const response2 = await registry.run();
77
- return {
78
- status: httpStatusCode(response2.status),
79
- body: { ...metadata, ...toJson(response2) }
80
- };
45
+ let depth = "shallow";
46
+ if (resolveDepth) {
47
+ try {
48
+ const result = await resolveDepth(req);
49
+ if (result === "deep" || result === "shallow") {
50
+ depth = result;
51
+ }
52
+ } catch {
81
53
  }
82
- const body = {
83
- status: "ok",
84
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
85
- ...metadata
86
- };
87
- return { status: 200, body };
88
54
  }
89
- const provided = extractToken({
90
- queryParams: req.queryParams,
91
- authorizationHeader: req.authorizationHeader,
92
- queryParamName
93
- });
94
- if (provided === null) {
95
- const body = {
96
- status: "ok",
97
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
98
- ...metadata
55
+ if (depth === "deep") {
56
+ const response = await registry.run();
57
+ return {
58
+ status: httpStatusCode(response.status),
59
+ body: { ...metadata, ...toJson(response) }
99
60
  };
100
- return { status: 200, body };
101
- }
102
- if (!verifyToken(provided, token)) {
103
- return { status: 403, body: { error: "Forbidden" } };
104
61
  }
105
- const response = await registry.run();
106
- return {
107
- status: httpStatusCode(response.status),
108
- body: { ...metadata, ...toJson(response) }
62
+ const body = {
63
+ status: "ok",
64
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
65
+ ...metadata
109
66
  };
67
+ return { status: 200, body };
110
68
  };
111
69
  }
112
70
 
@@ -350,6 +308,8 @@ function createHealthcheckMiddleware(options) {
350
308
  return async (req, res) => {
351
309
  try {
352
310
  const result = await handle({
311
+ ip: req.ip,
312
+ headers: req.headers,
353
313
  queryParams: req.query,
354
314
  authorizationHeader: req.headers.authorization ?? null
355
315
  });
@@ -24,28 +24,6 @@ __export(next_exports, {
24
24
  });
25
25
  module.exports = __toCommonJS(next_exports);
26
26
 
27
- // src/auth.ts
28
- var import_node_crypto = require("crypto");
29
- function verifyToken(provided, expected) {
30
- if (!provided || !expected) return false;
31
- const providedHash = (0, import_node_crypto.createHash)("sha256").update(provided).digest();
32
- const expectedHash = (0, import_node_crypto.createHash)("sha256").update(expected).digest();
33
- return (0, import_node_crypto.timingSafeEqual)(providedHash, expectedHash);
34
- }
35
- function extractToken(options) {
36
- const { queryParams, authorizationHeader, queryParamName = "token" } = options;
37
- if (queryParams) {
38
- const value = queryParams[queryParamName];
39
- const str = Array.isArray(value) ? value[0] : value;
40
- if (str) return str;
41
- }
42
- if (authorizationHeader?.startsWith("Bearer ")) {
43
- const token = authorizationHeader.slice("Bearer ".length);
44
- if (token) return token;
45
- }
46
- return null;
47
- }
48
-
49
27
  // src/types.ts
50
28
  var Status = {
51
29
  HEALTHY: 2,
@@ -83,56 +61,36 @@ function httpStatusCode(status) {
83
61
  // src/handler.ts
84
62
  var RESERVED_METADATA_KEYS = ["status", "timestamp", "checks", "cachedAt"];
85
63
  function createHealthcheckHandler(options) {
86
- const { registry, token: rawToken, deep = false, queryParamName = "token", metadata = {} } = options;
87
- const isEmptyToken = typeof rawToken === "string" && rawToken.trim() === "";
88
- if (deep && isEmptyToken) {
89
- throw new Error(
90
- "Cannot use `deep: true` with an empty string token. This would expose deep healthcheck data without authentication. Either set a non-empty token or explicitly set `token: null`."
91
- );
92
- }
93
- const token = rawToken == null || isEmptyToken ? null : rawToken;
64
+ const { registry, resolveDepth, metadata = {} } = options;
94
65
  for (const key of Object.keys(metadata)) {
95
66
  if (RESERVED_METADATA_KEYS.includes(key)) {
96
67
  throw new Error(`Metadata key '${key}' is reserved. Use a different key name.`);
97
68
  }
98
69
  }
99
70
  return async (req) => {
100
- if (token === null) {
101
- if (deep) {
102
- const response2 = await registry.run();
103
- return {
104
- status: httpStatusCode(response2.status),
105
- body: { ...metadata, ...toJson(response2) }
106
- };
71
+ let depth = "shallow";
72
+ if (resolveDepth) {
73
+ try {
74
+ const result = await resolveDepth(req);
75
+ if (result === "deep" || result === "shallow") {
76
+ depth = result;
77
+ }
78
+ } catch {
107
79
  }
108
- const body = {
109
- status: "ok",
110
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
111
- ...metadata
112
- };
113
- return { status: 200, body };
114
80
  }
115
- const provided = extractToken({
116
- queryParams: req.queryParams,
117
- authorizationHeader: req.authorizationHeader,
118
- queryParamName
119
- });
120
- if (provided === null) {
121
- const body = {
122
- status: "ok",
123
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
124
- ...metadata
81
+ if (depth === "deep") {
82
+ const response = await registry.run();
83
+ return {
84
+ status: httpStatusCode(response.status),
85
+ body: { ...metadata, ...toJson(response) }
125
86
  };
126
- return { status: 200, body };
127
- }
128
- if (!verifyToken(provided, token)) {
129
- return { status: 403, body: { error: "Forbidden" } };
130
87
  }
131
- const response = await registry.run();
132
- return {
133
- status: httpStatusCode(response.status),
134
- body: { ...metadata, ...toJson(response) }
88
+ const body = {
89
+ status: "ok",
90
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
91
+ ...metadata
135
92
  };
93
+ return { status: 200, body };
136
94
  };
137
95
  }
138
96
 
@@ -404,6 +362,8 @@ function createNextHandler(options) {
404
362
  const url = new URL(request.url);
405
363
  const queryParams = Object.fromEntries(url.searchParams);
406
364
  const result = await handle({
365
+ ip: request.headers.get("x-forwarded-for"),
366
+ headers: Object.fromEntries(request.headers),
407
367
  queryParams,
408
368
  authorizationHeader: request.headers.get("authorization")
409
369
  });
@@ -1,4 +1,4 @@
1
- import { H as HealthcheckHandlerOptions } from '../handler-COH7lot9.cjs';
1
+ import { H as HealthcheckHandlerOptions } from '../handler-TZOgZvY7.cjs';
2
2
  import '../core-Bee03bJm.cjs';
3
3
 
4
4
  type NextHealthcheckOptions = HealthcheckHandlerOptions;
@@ -15,7 +15,7 @@ type NextHealthcheckOptions = HealthcheckHandlerOptions;
15
15
  *
16
16
  * export const GET = createNextHandler({
17
17
  * registry,
18
- * token: process.env.VITALS_TOKEN,
18
+ * resolveDepth: (req) => req.ip === '10.0.0.1' ? 'deep' : 'shallow',
19
19
  * });
20
20
  * ```
21
21
  */
@@ -1,4 +1,4 @@
1
- import { H as HealthcheckHandlerOptions } from '../handler-CVYUad84.js';
1
+ import { H as HealthcheckHandlerOptions } from '../handler-BvjN4Ot9.js';
2
2
  import '../core-Bee03bJm.js';
3
3
 
4
4
  type NextHealthcheckOptions = HealthcheckHandlerOptions;
@@ -15,7 +15,7 @@ type NextHealthcheckOptions = HealthcheckHandlerOptions;
15
15
  *
16
16
  * export const GET = createNextHandler({
17
17
  * registry,
18
- * token: process.env.VITALS_TOKEN,
18
+ * resolveDepth: (req) => req.ip === '10.0.0.1' ? 'deep' : 'shallow',
19
19
  * });
20
20
  * ```
21
21
  */
@@ -1,25 +1,3 @@
1
- // src/auth.ts
2
- import { createHash, timingSafeEqual } from "crypto";
3
- function verifyToken(provided, expected) {
4
- if (!provided || !expected) return false;
5
- const providedHash = createHash("sha256").update(provided).digest();
6
- const expectedHash = createHash("sha256").update(expected).digest();
7
- return timingSafeEqual(providedHash, expectedHash);
8
- }
9
- function extractToken(options) {
10
- const { queryParams, authorizationHeader, queryParamName = "token" } = options;
11
- if (queryParams) {
12
- const value = queryParams[queryParamName];
13
- const str = Array.isArray(value) ? value[0] : value;
14
- if (str) return str;
15
- }
16
- if (authorizationHeader?.startsWith("Bearer ")) {
17
- const token = authorizationHeader.slice("Bearer ".length);
18
- if (token) return token;
19
- }
20
- return null;
21
- }
22
-
23
1
  // src/types.ts
24
2
  var Status = {
25
3
  HEALTHY: 2,
@@ -57,56 +35,36 @@ function httpStatusCode(status) {
57
35
  // src/handler.ts
58
36
  var RESERVED_METADATA_KEYS = ["status", "timestamp", "checks", "cachedAt"];
59
37
  function createHealthcheckHandler(options) {
60
- const { registry, token: rawToken, deep = false, queryParamName = "token", metadata = {} } = options;
61
- const isEmptyToken = typeof rawToken === "string" && rawToken.trim() === "";
62
- if (deep && isEmptyToken) {
63
- throw new Error(
64
- "Cannot use `deep: true` with an empty string token. This would expose deep healthcheck data without authentication. Either set a non-empty token or explicitly set `token: null`."
65
- );
66
- }
67
- const token = rawToken == null || isEmptyToken ? null : rawToken;
38
+ const { registry, resolveDepth, metadata = {} } = options;
68
39
  for (const key of Object.keys(metadata)) {
69
40
  if (RESERVED_METADATA_KEYS.includes(key)) {
70
41
  throw new Error(`Metadata key '${key}' is reserved. Use a different key name.`);
71
42
  }
72
43
  }
73
44
  return async (req) => {
74
- if (token === null) {
75
- if (deep) {
76
- const response2 = await registry.run();
77
- return {
78
- status: httpStatusCode(response2.status),
79
- body: { ...metadata, ...toJson(response2) }
80
- };
45
+ let depth = "shallow";
46
+ if (resolveDepth) {
47
+ try {
48
+ const result = await resolveDepth(req);
49
+ if (result === "deep" || result === "shallow") {
50
+ depth = result;
51
+ }
52
+ } catch {
81
53
  }
82
- const body = {
83
- status: "ok",
84
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
85
- ...metadata
86
- };
87
- return { status: 200, body };
88
54
  }
89
- const provided = extractToken({
90
- queryParams: req.queryParams,
91
- authorizationHeader: req.authorizationHeader,
92
- queryParamName
93
- });
94
- if (provided === null) {
95
- const body = {
96
- status: "ok",
97
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
98
- ...metadata
55
+ if (depth === "deep") {
56
+ const response = await registry.run();
57
+ return {
58
+ status: httpStatusCode(response.status),
59
+ body: { ...metadata, ...toJson(response) }
99
60
  };
100
- return { status: 200, body };
101
- }
102
- if (!verifyToken(provided, token)) {
103
- return { status: 403, body: { error: "Forbidden" } };
104
61
  }
105
- const response = await registry.run();
106
- return {
107
- status: httpStatusCode(response.status),
108
- body: { ...metadata, ...toJson(response) }
62
+ const body = {
63
+ status: "ok",
64
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
65
+ ...metadata
109
66
  };
67
+ return { status: 200, body };
110
68
  };
111
69
  }
112
70
 
@@ -378,6 +336,8 @@ function createNextHandler(options) {
378
336
  const url = new URL(request.url);
379
337
  const queryParams = Object.fromEntries(url.searchParams);
380
338
  const result = await handle({
339
+ ip: request.headers.get("x-forwarded-for"),
340
+ headers: Object.fromEntries(request.headers),
381
341
  queryParams,
382
342
  authorizationHeader: request.headers.get("authorization")
383
343
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@firebreak/vitals",
3
- "version": "1.3.0",
3
+ "version": "2.0.1",
4
4
  "description": "Deep healthcheck endpoints following the HEALTHCHECK_SPEC format",
5
5
  "type": "module",
6
6
  "exports": {
@@ -1,27 +0,0 @@
1
- import { b as HealthcheckRegistry, H as HealthcheckResponseJson } from './core-Bee03bJm.cjs';
2
-
3
- interface HealthcheckHandlerOptions {
4
- registry: HealthcheckRegistry;
5
- token?: string | null;
6
- deep?: boolean;
7
- queryParamName?: string;
8
- metadata?: Record<string, string | number | boolean>;
9
- }
10
- interface HealthcheckRequest {
11
- queryParams?: Record<string, string | string[] | undefined>;
12
- authorizationHeader?: string | null;
13
- }
14
- interface ShallowResponseJson {
15
- status: 'ok';
16
- timestamp: string;
17
- [key: string]: string | number | boolean;
18
- }
19
- interface HealthcheckHandlerResult {
20
- status: number;
21
- body: HealthcheckResponseJson | ShallowResponseJson | {
22
- error: string;
23
- };
24
- }
25
- declare function createHealthcheckHandler(options: HealthcheckHandlerOptions): (req: HealthcheckRequest) => Promise<HealthcheckHandlerResult>;
26
-
27
- export { type HealthcheckHandlerOptions as H, type ShallowResponseJson as S, type HealthcheckHandlerResult as a, type HealthcheckRequest as b, createHealthcheckHandler as c };
@@ -1,27 +0,0 @@
1
- import { b as HealthcheckRegistry, H as HealthcheckResponseJson } from './core-Bee03bJm.js';
2
-
3
- interface HealthcheckHandlerOptions {
4
- registry: HealthcheckRegistry;
5
- token?: string | null;
6
- deep?: boolean;
7
- queryParamName?: string;
8
- metadata?: Record<string, string | number | boolean>;
9
- }
10
- interface HealthcheckRequest {
11
- queryParams?: Record<string, string | string[] | undefined>;
12
- authorizationHeader?: string | null;
13
- }
14
- interface ShallowResponseJson {
15
- status: 'ok';
16
- timestamp: string;
17
- [key: string]: string | number | boolean;
18
- }
19
- interface HealthcheckHandlerResult {
20
- status: number;
21
- body: HealthcheckResponseJson | ShallowResponseJson | {
22
- error: string;
23
- };
24
- }
25
- declare function createHealthcheckHandler(options: HealthcheckHandlerOptions): (req: HealthcheckRequest) => Promise<HealthcheckHandlerResult>;
26
-
27
- export { type HealthcheckHandlerOptions as H, type ShallowResponseJson as S, type HealthcheckHandlerResult as a, type HealthcheckRequest as b, createHealthcheckHandler as c };