@firebreak/vitals 2.2.0 → 3.0.0
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 +55 -54
- package/dist/checks/databricks.cjs +2 -1
- package/dist/checks/databricks.d.cts +3 -2
- package/dist/checks/databricks.d.ts +3 -2
- package/dist/checks/databricks.mjs +2 -1
- package/dist/handler-B1Omp5VP.d.ts +21 -0
- package/dist/handler-MWB6zBBG.d.cts +21 -0
- package/dist/index.cjs +22 -23
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.mjs +22 -23
- package/dist/integrations/express.cjs +20 -21
- package/dist/integrations/express.d.cts +1 -1
- package/dist/integrations/express.d.ts +1 -1
- package/dist/integrations/express.mjs +20 -21
- package/dist/integrations/next.cjs +20 -21
- package/dist/integrations/next.d.cts +2 -2
- package/dist/integrations/next.d.ts +2 -2
- package/dist/integrations/next.mjs +20 -21
- package/package.json +3 -1
- package/dist/handler-C3eTDuSQ.d.cts +0 -21
- package/dist/handler-DaFJivGK.d.ts +0 -21
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Vitals (Node.js)
|
|
2
2
|
|
|
3
|
-
Structured healthcheck endpoints for Node.js services
|
|
3
|
+
Structured healthcheck endpoints for Node.js services. Register health checks, run them concurrently with timeouts, and expose them via Express or Next.js. Health checks always run — a configurable detail policy controls whether callers see a summary status or full diagnostic results.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -35,13 +35,13 @@ 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
|
+
resolveDetail: (req) => {
|
|
39
39
|
const token = extractToken({
|
|
40
40
|
queryParams: req.queryParams as Record<string, string>,
|
|
41
41
|
authorizationHeader: req.authorizationHeader as string | null,
|
|
42
42
|
});
|
|
43
|
-
if (token && verifyToken(token, process.env.VITALS_TOKEN!)) return '
|
|
44
|
-
return '
|
|
43
|
+
if (token && verifyToken(token, process.env.VITALS_TOKEN!)) return 'full';
|
|
44
|
+
return 'summary';
|
|
45
45
|
},
|
|
46
46
|
metadata: {
|
|
47
47
|
build: process.env.BUILD_SHA,
|
|
@@ -50,13 +50,13 @@ app.get('/vitals', createHealthcheckMiddleware({
|
|
|
50
50
|
}));
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
Hit `/vitals` without a valid token to get a
|
|
53
|
+
Hit `/vitals` without a valid token to get a summary response with real health status:
|
|
54
54
|
|
|
55
55
|
```json
|
|
56
|
-
{ "status": "
|
|
56
|
+
{ "status": "healthy", "timestamp": "...", "build": "stg-45d76e5", "buildTimestamp": "2025-02-25T19:41:56Z" }
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
-
When `
|
|
59
|
+
When `resolveDetail` returns `'full'`, the full response with check results is returned.
|
|
60
60
|
|
|
61
61
|
## Quick Start (Next.js)
|
|
62
62
|
|
|
@@ -76,13 +76,13 @@ registry.add('api', httpCheck(`${process.env.API_BASE_URL}/health`));
|
|
|
76
76
|
|
|
77
77
|
export const GET = createNextHandler({
|
|
78
78
|
registry,
|
|
79
|
-
|
|
79
|
+
resolveDetail: (req) => {
|
|
80
80
|
const token = extractToken({
|
|
81
81
|
queryParams: req.queryParams as Record<string, string>,
|
|
82
82
|
authorizationHeader: req.authorizationHeader as string | null,
|
|
83
83
|
});
|
|
84
|
-
if (token && verifyToken(token, process.env.VITALS_TOKEN!)) return '
|
|
85
|
-
return '
|
|
84
|
+
if (token && verifyToken(token, process.env.VITALS_TOKEN!)) return 'full';
|
|
85
|
+
return 'summary';
|
|
86
86
|
},
|
|
87
87
|
metadata: {
|
|
88
88
|
build: process.env.BUILD_SHA,
|
|
@@ -221,15 +221,15 @@ import { createHealthcheckMiddleware } from '@firebreak/vitals/express';
|
|
|
221
221
|
|
|
222
222
|
app.get('/vitals', createHealthcheckMiddleware({
|
|
223
223
|
registry,
|
|
224
|
-
|
|
224
|
+
resolveDetail: (req) => { // optional — omit for always-summary
|
|
225
225
|
const token = extractToken({
|
|
226
226
|
queryParams: req.queryParams as Record<string, string>,
|
|
227
227
|
authorizationHeader: req.authorizationHeader as string | null,
|
|
228
228
|
});
|
|
229
|
-
if (token && verifyToken(token, 'my-secret-token')) return '
|
|
230
|
-
return '
|
|
229
|
+
if (token && verifyToken(token, 'my-secret-token')) return 'full';
|
|
230
|
+
return 'summary';
|
|
231
231
|
},
|
|
232
|
-
metadata: { // optional — included in
|
|
232
|
+
metadata: { // optional — included in summary & full responses
|
|
233
233
|
build: 'stg-45d76e5',
|
|
234
234
|
},
|
|
235
235
|
}));
|
|
@@ -242,13 +242,13 @@ import { createNextHandler } from '@firebreak/vitals/next';
|
|
|
242
242
|
|
|
243
243
|
export const GET = createNextHandler({
|
|
244
244
|
registry,
|
|
245
|
-
|
|
245
|
+
resolveDetail: (req) => {
|
|
246
246
|
const token = extractToken({
|
|
247
247
|
queryParams: req.queryParams as Record<string, string>,
|
|
248
248
|
authorizationHeader: req.authorizationHeader as string | null,
|
|
249
249
|
});
|
|
250
|
-
if (token && verifyToken(token, process.env.VITALS_TOKEN!)) return '
|
|
251
|
-
return '
|
|
250
|
+
if (token && verifyToken(token, process.env.VITALS_TOKEN!)) return 'full';
|
|
251
|
+
return 'summary';
|
|
252
252
|
},
|
|
253
253
|
metadata: { build: process.env.BUILD_SHA },
|
|
254
254
|
});
|
|
@@ -263,52 +263,52 @@ import { createHealthcheckHandler, extractToken, verifyToken } from '@firebreak/
|
|
|
263
263
|
|
|
264
264
|
const handle = createHealthcheckHandler({
|
|
265
265
|
registry,
|
|
266
|
-
|
|
266
|
+
resolveDetail: (req) => {
|
|
267
267
|
const token = extractToken({
|
|
268
268
|
queryParams: req.queryParams as Record<string, string>,
|
|
269
269
|
authorizationHeader: req.authorizationHeader as string | null,
|
|
270
270
|
});
|
|
271
|
-
if (token && verifyToken(token, process.env.VITALS_TOKEN!)) return '
|
|
272
|
-
return '
|
|
271
|
+
if (token && verifyToken(token, process.env.VITALS_TOKEN!)) return 'full';
|
|
272
|
+
return 'summary';
|
|
273
273
|
},
|
|
274
274
|
metadata: { build: 'stg-45d76e5' },
|
|
275
275
|
});
|
|
276
276
|
|
|
277
|
-
//
|
|
278
|
-
const
|
|
279
|
-
// { status: 200, body: { status: '
|
|
277
|
+
// resolveDetail returns 'summary' → summary response (real status, no checks)
|
|
278
|
+
const summary = await handle({});
|
|
279
|
+
// { status: 200, body: { status: 'healthy', timestamp: '...', build: 'stg-45d76e5' } }
|
|
280
280
|
|
|
281
|
-
//
|
|
282
|
-
const
|
|
281
|
+
// resolveDetail returns 'full' → full response with check details
|
|
282
|
+
const full = await handle({ queryParams: { token: '...' } });
|
|
283
283
|
// { status: 200, body: { status: 'healthy', timestamp: '...', build: 'stg-45d76e5', checks: { ... } } }
|
|
284
284
|
```
|
|
285
285
|
|
|
286
|
-
###
|
|
286
|
+
### Summary / Full Responses
|
|
287
287
|
|
|
288
|
-
The
|
|
288
|
+
Health checks always run. The `resolveDetail` option controls how much detail is exposed in the response:
|
|
289
289
|
|
|
290
|
-
| `
|
|
291
|
-
|
|
292
|
-
| `'
|
|
293
|
-
| `'
|
|
290
|
+
| `resolveDetail` returns | Response |
|
|
291
|
+
|-------------------------|----------|
|
|
292
|
+
| `'summary'` (or omitted) | **Summary** — real HTTP status with `{ status: "healthy"/"degraded"/"outage", timestamp, ...metadata }` |
|
|
293
|
+
| `'full'` | **Full** — real HTTP status with check results + metadata |
|
|
294
294
|
|
|
295
|
-
When `
|
|
295
|
+
When `resolveDetail` is not provided, the handler returns a summary response.
|
|
296
296
|
|
|
297
|
-
The `
|
|
297
|
+
The `resolveDetail` function receives the request object (`{ ip, headers, queryParams, authorizationHeader }`) and returns `'full'` or `'summary'` (or a promise of either). This lets you implement any detail policy — token-based, IP-based, header-based, or always-full for internal services:
|
|
298
298
|
|
|
299
299
|
```typescript
|
|
300
|
-
// Always
|
|
300
|
+
// Always full (internal service behind a private network)
|
|
301
301
|
createHealthcheckHandler({
|
|
302
302
|
registry,
|
|
303
|
-
|
|
303
|
+
resolveDetail: () => 'full',
|
|
304
304
|
});
|
|
305
305
|
```
|
|
306
306
|
|
|
307
|
-
|
|
307
|
+
Both summary and full responses reflect real health status and return the correct HTTP status code (200 for healthy, 503 for degraded/outage). The summary response lets load balancers and uptime monitors get real health status without exposing internal check details. The full response adds the `checks` object with per-check diagnostics.
|
|
308
308
|
|
|
309
309
|
### Metadata
|
|
310
310
|
|
|
311
|
-
Attach static key-value pairs that appear in both
|
|
311
|
+
Attach static key-value pairs that appear in both summary and full responses:
|
|
312
312
|
|
|
313
313
|
```typescript
|
|
314
314
|
createHealthcheckHandler({
|
|
@@ -323,9 +323,9 @@ createHealthcheckHandler({
|
|
|
323
323
|
|
|
324
324
|
Metadata values can be `string`, `number`, or `boolean`. Keys `status`, `timestamp`, `checks`, and `cachedAt` are reserved -- passing them throws at handler creation time.
|
|
325
325
|
|
|
326
|
-
#### `
|
|
326
|
+
#### `summaryMetadata`
|
|
327
327
|
|
|
328
|
-
By default,
|
|
328
|
+
By default, summary responses include all `metadata` fields. Use `summaryMetadata` to override what appears in summary responses while keeping `metadata` for full responses:
|
|
329
329
|
|
|
330
330
|
```typescript
|
|
331
331
|
createHealthcheckHandler({
|
|
@@ -335,8 +335,8 @@ createHealthcheckHandler({
|
|
|
335
335
|
buildTimestamp: '2025-02-25T19:41:56Z',
|
|
336
336
|
region: 'us-east-1',
|
|
337
337
|
},
|
|
338
|
-
//
|
|
339
|
-
|
|
338
|
+
// Summary responses only include status + timestamp (no build info)
|
|
339
|
+
summaryMetadata: {},
|
|
340
340
|
});
|
|
341
341
|
```
|
|
342
342
|
|
|
@@ -346,16 +346,16 @@ You can also include a subset of fields:
|
|
|
346
346
|
createHealthcheckHandler({
|
|
347
347
|
registry,
|
|
348
348
|
metadata: { build: 'stg-45d76e5', region: 'us-east-1', version: '1.0.0' },
|
|
349
|
-
//
|
|
350
|
-
|
|
349
|
+
// Summary responses include only version
|
|
350
|
+
summaryMetadata: { version: '1.0.0' },
|
|
351
351
|
});
|
|
352
352
|
```
|
|
353
353
|
|
|
354
|
-
When `
|
|
354
|
+
When `summaryMetadata` is omitted, summary responses fall back to using `metadata`.
|
|
355
355
|
|
|
356
356
|
### Utilities
|
|
357
357
|
|
|
358
|
-
`extractToken` and `verifyToken` are exported helpers for use inside your `
|
|
358
|
+
`extractToken` and `verifyToken` are exported helpers for use inside your `resolveDetail` function.
|
|
359
359
|
|
|
360
360
|
```typescript
|
|
361
361
|
import { verifyToken, extractToken } from '@firebreak/vitals';
|
|
@@ -372,18 +372,18 @@ verifyToken('provided-token', 'expected-token'); // boolean
|
|
|
372
372
|
|
|
373
373
|
## Response Format
|
|
374
374
|
|
|
375
|
-
**
|
|
375
|
+
**Summary response** (`resolveDetail` returns `'summary'` or is omitted):
|
|
376
376
|
|
|
377
377
|
```json
|
|
378
378
|
{
|
|
379
|
-
"status": "
|
|
379
|
+
"status": "healthy",
|
|
380
380
|
"timestamp": "2025-02-26T12:00:00.000Z",
|
|
381
381
|
"build": "stg-45d76e5",
|
|
382
382
|
"buildTimestamp": "2025-02-25T19:41:56Z"
|
|
383
383
|
}
|
|
384
384
|
```
|
|
385
385
|
|
|
386
|
-
**
|
|
386
|
+
**Full response** (`resolveDetail` returns `'full'`):
|
|
387
387
|
|
|
388
388
|
```json
|
|
389
389
|
{
|
|
@@ -406,12 +406,13 @@ verifyToken('provided-token', 'expected-token'); // boolean
|
|
|
406
406
|
}
|
|
407
407
|
```
|
|
408
408
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
|
412
|
-
|
|
413
|
-
|
|
|
414
|
-
|
|
|
409
|
+
Both summary and full responses return real HTTP status codes:
|
|
410
|
+
|
|
411
|
+
| Status | HTTP Code |
|
|
412
|
+
|--------|-----------|
|
|
413
|
+
| `"healthy"` | `200` |
|
|
414
|
+
| `"degraded"` | `503` |
|
|
415
|
+
| `"outage"` | `503` |
|
|
415
416
|
|
|
416
417
|
## Requirements
|
|
417
418
|
|
|
@@ -32,11 +32,12 @@ var Status = {
|
|
|
32
32
|
};
|
|
33
33
|
|
|
34
34
|
// src/checks/databricks.ts
|
|
35
|
-
function databricksCheck(
|
|
35
|
+
function databricksCheck(clientOrFactory) {
|
|
36
36
|
return async () => {
|
|
37
37
|
let session;
|
|
38
38
|
let op;
|
|
39
39
|
try {
|
|
40
|
+
const client = typeof clientOrFactory === "function" ? await clientOrFactory() : clientOrFactory;
|
|
40
41
|
session = await client.openSession();
|
|
41
42
|
op = await session.executeStatement("SELECT 1");
|
|
42
43
|
return { status: Status.HEALTHY, message: "" };
|
|
@@ -14,10 +14,11 @@ interface DatabricksSession {
|
|
|
14
14
|
interface DatabricksOperation {
|
|
15
15
|
close(): Promise<void>;
|
|
16
16
|
}
|
|
17
|
+
type DatabricksClientOrFactory = DatabricksClient | (() => DatabricksClient | Promise<DatabricksClient>);
|
|
17
18
|
/**
|
|
18
19
|
* Creates a healthcheck function that opens a session on an existing
|
|
19
20
|
* Databricks SQL client, runs `SELECT 1`, and closes the session.
|
|
20
21
|
*/
|
|
21
|
-
declare function databricksCheck(
|
|
22
|
+
declare function databricksCheck(clientOrFactory: DatabricksClientOrFactory): AsyncCheckFn;
|
|
22
23
|
|
|
23
|
-
export { AsyncCheckFn, type DatabricksClient, type DatabricksOperation, type DatabricksSession, databricksCheck };
|
|
24
|
+
export { AsyncCheckFn, type DatabricksClient, type DatabricksClientOrFactory, type DatabricksOperation, type DatabricksSession, databricksCheck };
|
|
@@ -14,10 +14,11 @@ interface DatabricksSession {
|
|
|
14
14
|
interface DatabricksOperation {
|
|
15
15
|
close(): Promise<void>;
|
|
16
16
|
}
|
|
17
|
+
type DatabricksClientOrFactory = DatabricksClient | (() => DatabricksClient | Promise<DatabricksClient>);
|
|
17
18
|
/**
|
|
18
19
|
* Creates a healthcheck function that opens a session on an existing
|
|
19
20
|
* Databricks SQL client, runs `SELECT 1`, and closes the session.
|
|
20
21
|
*/
|
|
21
|
-
declare function databricksCheck(
|
|
22
|
+
declare function databricksCheck(clientOrFactory: DatabricksClientOrFactory): AsyncCheckFn;
|
|
22
23
|
|
|
23
|
-
export { AsyncCheckFn, type DatabricksClient, type DatabricksOperation, type DatabricksSession, databricksCheck };
|
|
24
|
+
export { AsyncCheckFn, type DatabricksClient, type DatabricksClientOrFactory, type DatabricksOperation, type DatabricksSession, databricksCheck };
|
|
@@ -6,11 +6,12 @@ var Status = {
|
|
|
6
6
|
};
|
|
7
7
|
|
|
8
8
|
// src/checks/databricks.ts
|
|
9
|
-
function databricksCheck(
|
|
9
|
+
function databricksCheck(clientOrFactory) {
|
|
10
10
|
return async () => {
|
|
11
11
|
let session;
|
|
12
12
|
let op;
|
|
13
13
|
try {
|
|
14
|
+
const client = typeof clientOrFactory === "function" ? await clientOrFactory() : clientOrFactory;
|
|
14
15
|
session = await client.openSession();
|
|
15
16
|
op = await session.executeStatement("SELECT 1");
|
|
16
17
|
return { status: Status.HEALTHY, message: "" };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { b as HealthcheckRegistry, H as HealthcheckResponseJson, d as StatusLabel } from './core-Bee03bJm.js';
|
|
2
|
+
|
|
3
|
+
type ResolveDetailFn = (req: Record<string, unknown>) => 'full' | 'summary' | Promise<'full' | 'summary'>;
|
|
4
|
+
interface HealthcheckHandlerOptions {
|
|
5
|
+
registry: HealthcheckRegistry;
|
|
6
|
+
resolveDetail?: ResolveDetailFn;
|
|
7
|
+
metadata?: Record<string, string | number | boolean>;
|
|
8
|
+
summaryMetadata?: Record<string, string | number | boolean>;
|
|
9
|
+
}
|
|
10
|
+
interface SummaryResponseJson {
|
|
11
|
+
status: StatusLabel;
|
|
12
|
+
timestamp: string;
|
|
13
|
+
[key: string]: string | number | boolean;
|
|
14
|
+
}
|
|
15
|
+
interface HealthcheckHandlerResult {
|
|
16
|
+
status: number;
|
|
17
|
+
body: HealthcheckResponseJson | SummaryResponseJson;
|
|
18
|
+
}
|
|
19
|
+
declare function createHealthcheckHandler(options: HealthcheckHandlerOptions): (req: Record<string, unknown>) => Promise<HealthcheckHandlerResult>;
|
|
20
|
+
|
|
21
|
+
export { type HealthcheckHandlerOptions as H, type ResolveDetailFn as R, type SummaryResponseJson as S, type HealthcheckHandlerResult as a, createHealthcheckHandler as c };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { b as HealthcheckRegistry, H as HealthcheckResponseJson, d as StatusLabel } from './core-Bee03bJm.cjs';
|
|
2
|
+
|
|
3
|
+
type ResolveDetailFn = (req: Record<string, unknown>) => 'full' | 'summary' | Promise<'full' | 'summary'>;
|
|
4
|
+
interface HealthcheckHandlerOptions {
|
|
5
|
+
registry: HealthcheckRegistry;
|
|
6
|
+
resolveDetail?: ResolveDetailFn;
|
|
7
|
+
metadata?: Record<string, string | number | boolean>;
|
|
8
|
+
summaryMetadata?: Record<string, string | number | boolean>;
|
|
9
|
+
}
|
|
10
|
+
interface SummaryResponseJson {
|
|
11
|
+
status: StatusLabel;
|
|
12
|
+
timestamp: string;
|
|
13
|
+
[key: string]: string | number | boolean;
|
|
14
|
+
}
|
|
15
|
+
interface HealthcheckHandlerResult {
|
|
16
|
+
status: number;
|
|
17
|
+
body: HealthcheckResponseJson | SummaryResponseJson;
|
|
18
|
+
}
|
|
19
|
+
declare function createHealthcheckHandler(options: HealthcheckHandlerOptions): (req: Record<string, unknown>) => Promise<HealthcheckHandlerResult>;
|
|
20
|
+
|
|
21
|
+
export { type HealthcheckHandlerOptions as H, type ResolveDetailFn as R, type SummaryResponseJson as S, type HealthcheckHandlerResult as a, createHealthcheckHandler as c };
|
package/dist/index.cjs
CHANGED
|
@@ -207,8 +207,8 @@ function extractToken(options) {
|
|
|
207
207
|
const { queryParams, authorizationHeader, queryParamName = "token" } = options;
|
|
208
208
|
if (queryParams) {
|
|
209
209
|
const value = queryParams[queryParamName];
|
|
210
|
-
const
|
|
211
|
-
if (
|
|
210
|
+
const raw = Array.isArray(value) ? value[0] : value;
|
|
211
|
+
if (raw) return String(raw);
|
|
212
212
|
}
|
|
213
213
|
if (authorizationHeader?.startsWith("Bearer ")) {
|
|
214
214
|
const token = authorizationHeader.slice("Bearer ".length);
|
|
@@ -220,8 +220,8 @@ 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,
|
|
224
|
-
for (const bag of [metadata,
|
|
223
|
+
const { registry, resolveDetail, metadata = {}, summaryMetadata } = options;
|
|
224
|
+
for (const bag of [metadata, summaryMetadata ?? {}]) {
|
|
225
225
|
for (const key of Object.keys(bag)) {
|
|
226
226
|
if (RESERVED_METADATA_KEYS.includes(key)) {
|
|
227
227
|
throw new Error(`Metadata key '${key}' is reserved. Use a different key name.`);
|
|
@@ -229,29 +229,29 @@ function createHealthcheckHandler(options) {
|
|
|
229
229
|
}
|
|
230
230
|
}
|
|
231
231
|
return async (req) => {
|
|
232
|
-
let
|
|
233
|
-
if (
|
|
232
|
+
let detail = "summary";
|
|
233
|
+
if (resolveDetail) {
|
|
234
234
|
try {
|
|
235
|
-
const result = await
|
|
236
|
-
if (result === "
|
|
237
|
-
|
|
235
|
+
const result = await resolveDetail(req);
|
|
236
|
+
if (result === "full" || result === "summary") {
|
|
237
|
+
detail = result;
|
|
238
238
|
}
|
|
239
239
|
} catch {
|
|
240
240
|
}
|
|
241
241
|
}
|
|
242
|
-
|
|
243
|
-
|
|
242
|
+
const response = await registry.run();
|
|
243
|
+
if (detail === "full") {
|
|
244
244
|
return {
|
|
245
245
|
status: httpStatusCode(response.status),
|
|
246
246
|
body: { ...metadata, ...toJson(response) }
|
|
247
247
|
};
|
|
248
248
|
}
|
|
249
249
|
const body = {
|
|
250
|
-
status:
|
|
251
|
-
timestamp:
|
|
252
|
-
...
|
|
250
|
+
status: statusToLabel(response.status),
|
|
251
|
+
timestamp: response.timestamp,
|
|
252
|
+
...summaryMetadata ?? metadata
|
|
253
253
|
};
|
|
254
|
-
return { status:
|
|
254
|
+
return { status: httpStatusCode(response.status), body };
|
|
255
255
|
};
|
|
256
256
|
}
|
|
257
257
|
|
|
@@ -262,7 +262,6 @@ function escapeHtml(str) {
|
|
|
262
262
|
function statusColor(status) {
|
|
263
263
|
switch (status) {
|
|
264
264
|
case "healthy":
|
|
265
|
-
case "ok":
|
|
266
265
|
return "#22C55E";
|
|
267
266
|
case "degraded":
|
|
268
267
|
return "#F59E0B";
|
|
@@ -274,10 +273,10 @@ function statusColor(status) {
|
|
|
274
273
|
}
|
|
275
274
|
var DEEP_KNOWN_KEYS = /* @__PURE__ */ new Set(["status", "timestamp", "checks", "cachedAt"]);
|
|
276
275
|
function isDeepResponse(body) {
|
|
277
|
-
return "checks" in body && "status" in body
|
|
276
|
+
return "checks" in body && "status" in body;
|
|
278
277
|
}
|
|
279
|
-
function
|
|
280
|
-
return "status" in body && body
|
|
278
|
+
function isSummaryResponse(body) {
|
|
279
|
+
return "status" in body && !("checks" in body) && !("error" in body);
|
|
281
280
|
}
|
|
282
281
|
function isErrorResponse(body) {
|
|
283
282
|
return "error" in body && !("status" in body);
|
|
@@ -436,9 +435,9 @@ function renderHealthcheckHtml(body) {
|
|
|
436
435
|
">${escapeHtml(body.error)}</div>`
|
|
437
436
|
);
|
|
438
437
|
}
|
|
439
|
-
if (
|
|
440
|
-
const color = statusColor(
|
|
441
|
-
const { status
|
|
438
|
+
if (isSummaryResponse(body)) {
|
|
439
|
+
const color = statusColor(body.status);
|
|
440
|
+
const { status, timestamp, ...rest } = body;
|
|
442
441
|
const metadataItems = Object.entries(rest).map(
|
|
443
442
|
([k, v]) => [k, String(v)]
|
|
444
443
|
);
|
|
@@ -455,7 +454,7 @@ function renderHealthcheckHtml(body) {
|
|
|
455
454
|
font-weight: 700;
|
|
456
455
|
color: #E4E4E7;
|
|
457
456
|
">Healthcheck</span>
|
|
458
|
-
${renderStatusBadge(
|
|
457
|
+
${renderStatusBadge(status, color)}
|
|
459
458
|
</div>
|
|
460
459
|
${renderMetadataItems(allItems)}`
|
|
461
460
|
);
|
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
|
|
4
|
-
export { H as HealthcheckHandlerOptions, a as HealthcheckHandlerResult, R as
|
|
3
|
+
import { S as SummaryResponseJson } from './handler-MWB6zBBG.cjs';
|
|
4
|
+
export { H as HealthcheckHandlerOptions, a as HealthcheckHandlerResult, R as ResolveDetailFn, c as createHealthcheckHandler } from './handler-MWB6zBBG.cjs';
|
|
5
5
|
|
|
6
6
|
declare function verifyToken(provided: string, expected: string): boolean;
|
|
7
7
|
declare function extractToken(options: {
|
|
@@ -10,9 +10,9 @@ declare function extractToken(options: {
|
|
|
10
10
|
queryParamName?: string;
|
|
11
11
|
}): string | null;
|
|
12
12
|
|
|
13
|
-
type HealthcheckBody = HealthcheckResponseJson |
|
|
13
|
+
type HealthcheckBody = HealthcheckResponseJson | SummaryResponseJson | {
|
|
14
14
|
error: string;
|
|
15
15
|
};
|
|
16
16
|
declare function renderHealthcheckHtml(body: HealthcheckBody): string;
|
|
17
17
|
|
|
18
|
-
export { HealthcheckResponseJson,
|
|
18
|
+
export { HealthcheckResponseJson, SummaryResponseJson, extractToken, renderHealthcheckHtml, verifyToken };
|
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
|
|
4
|
-
export { H as HealthcheckHandlerOptions, a as HealthcheckHandlerResult, R as
|
|
3
|
+
import { S as SummaryResponseJson } from './handler-B1Omp5VP.js';
|
|
4
|
+
export { H as HealthcheckHandlerOptions, a as HealthcheckHandlerResult, R as ResolveDetailFn, c as createHealthcheckHandler } from './handler-B1Omp5VP.js';
|
|
5
5
|
|
|
6
6
|
declare function verifyToken(provided: string, expected: string): boolean;
|
|
7
7
|
declare function extractToken(options: {
|
|
@@ -10,9 +10,9 @@ declare function extractToken(options: {
|
|
|
10
10
|
queryParamName?: string;
|
|
11
11
|
}): string | null;
|
|
12
12
|
|
|
13
|
-
type HealthcheckBody = HealthcheckResponseJson |
|
|
13
|
+
type HealthcheckBody = HealthcheckResponseJson | SummaryResponseJson | {
|
|
14
14
|
error: string;
|
|
15
15
|
};
|
|
16
16
|
declare function renderHealthcheckHtml(body: HealthcheckBody): string;
|
|
17
17
|
|
|
18
|
-
export { HealthcheckResponseJson,
|
|
18
|
+
export { HealthcheckResponseJson, SummaryResponseJson, extractToken, renderHealthcheckHtml, verifyToken };
|
package/dist/index.mjs
CHANGED
|
@@ -171,8 +171,8 @@ function extractToken(options) {
|
|
|
171
171
|
const { queryParams, authorizationHeader, queryParamName = "token" } = options;
|
|
172
172
|
if (queryParams) {
|
|
173
173
|
const value = queryParams[queryParamName];
|
|
174
|
-
const
|
|
175
|
-
if (
|
|
174
|
+
const raw = Array.isArray(value) ? value[0] : value;
|
|
175
|
+
if (raw) return String(raw);
|
|
176
176
|
}
|
|
177
177
|
if (authorizationHeader?.startsWith("Bearer ")) {
|
|
178
178
|
const token = authorizationHeader.slice("Bearer ".length);
|
|
@@ -184,8 +184,8 @@ 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,
|
|
188
|
-
for (const bag of [metadata,
|
|
187
|
+
const { registry, resolveDetail, metadata = {}, summaryMetadata } = options;
|
|
188
|
+
for (const bag of [metadata, summaryMetadata ?? {}]) {
|
|
189
189
|
for (const key of Object.keys(bag)) {
|
|
190
190
|
if (RESERVED_METADATA_KEYS.includes(key)) {
|
|
191
191
|
throw new Error(`Metadata key '${key}' is reserved. Use a different key name.`);
|
|
@@ -193,29 +193,29 @@ function createHealthcheckHandler(options) {
|
|
|
193
193
|
}
|
|
194
194
|
}
|
|
195
195
|
return async (req) => {
|
|
196
|
-
let
|
|
197
|
-
if (
|
|
196
|
+
let detail = "summary";
|
|
197
|
+
if (resolveDetail) {
|
|
198
198
|
try {
|
|
199
|
-
const result = await
|
|
200
|
-
if (result === "
|
|
201
|
-
|
|
199
|
+
const result = await resolveDetail(req);
|
|
200
|
+
if (result === "full" || result === "summary") {
|
|
201
|
+
detail = result;
|
|
202
202
|
}
|
|
203
203
|
} catch {
|
|
204
204
|
}
|
|
205
205
|
}
|
|
206
|
-
|
|
207
|
-
|
|
206
|
+
const response = await registry.run();
|
|
207
|
+
if (detail === "full") {
|
|
208
208
|
return {
|
|
209
209
|
status: httpStatusCode(response.status),
|
|
210
210
|
body: { ...metadata, ...toJson(response) }
|
|
211
211
|
};
|
|
212
212
|
}
|
|
213
213
|
const body = {
|
|
214
|
-
status:
|
|
215
|
-
timestamp:
|
|
216
|
-
...
|
|
214
|
+
status: statusToLabel(response.status),
|
|
215
|
+
timestamp: response.timestamp,
|
|
216
|
+
...summaryMetadata ?? metadata
|
|
217
217
|
};
|
|
218
|
-
return { status:
|
|
218
|
+
return { status: httpStatusCode(response.status), body };
|
|
219
219
|
};
|
|
220
220
|
}
|
|
221
221
|
|
|
@@ -226,7 +226,6 @@ function escapeHtml(str) {
|
|
|
226
226
|
function statusColor(status) {
|
|
227
227
|
switch (status) {
|
|
228
228
|
case "healthy":
|
|
229
|
-
case "ok":
|
|
230
229
|
return "#22C55E";
|
|
231
230
|
case "degraded":
|
|
232
231
|
return "#F59E0B";
|
|
@@ -238,10 +237,10 @@ function statusColor(status) {
|
|
|
238
237
|
}
|
|
239
238
|
var DEEP_KNOWN_KEYS = /* @__PURE__ */ new Set(["status", "timestamp", "checks", "cachedAt"]);
|
|
240
239
|
function isDeepResponse(body) {
|
|
241
|
-
return "checks" in body && "status" in body
|
|
240
|
+
return "checks" in body && "status" in body;
|
|
242
241
|
}
|
|
243
|
-
function
|
|
244
|
-
return "status" in body && body
|
|
242
|
+
function isSummaryResponse(body) {
|
|
243
|
+
return "status" in body && !("checks" in body) && !("error" in body);
|
|
245
244
|
}
|
|
246
245
|
function isErrorResponse(body) {
|
|
247
246
|
return "error" in body && !("status" in body);
|
|
@@ -400,9 +399,9 @@ function renderHealthcheckHtml(body) {
|
|
|
400
399
|
">${escapeHtml(body.error)}</div>`
|
|
401
400
|
);
|
|
402
401
|
}
|
|
403
|
-
if (
|
|
404
|
-
const color = statusColor(
|
|
405
|
-
const { status
|
|
402
|
+
if (isSummaryResponse(body)) {
|
|
403
|
+
const color = statusColor(body.status);
|
|
404
|
+
const { status, timestamp, ...rest } = body;
|
|
406
405
|
const metadataItems = Object.entries(rest).map(
|
|
407
406
|
([k, v]) => [k, String(v)]
|
|
408
407
|
);
|
|
@@ -419,7 +418,7 @@ function renderHealthcheckHtml(body) {
|
|
|
419
418
|
font-weight: 700;
|
|
420
419
|
color: #E4E4E7;
|
|
421
420
|
">Healthcheck</span>
|
|
422
|
-
${renderStatusBadge(
|
|
421
|
+
${renderStatusBadge(status, color)}
|
|
423
422
|
</div>
|
|
424
423
|
${renderMetadataItems(allItems)}`
|
|
425
424
|
);
|
|
@@ -61,8 +61,8 @@ function httpStatusCode(status) {
|
|
|
61
61
|
// src/handler.ts
|
|
62
62
|
var RESERVED_METADATA_KEYS = ["status", "timestamp", "checks", "cachedAt"];
|
|
63
63
|
function createHealthcheckHandler(options) {
|
|
64
|
-
const { registry,
|
|
65
|
-
for (const bag of [metadata,
|
|
64
|
+
const { registry, resolveDetail, metadata = {}, summaryMetadata } = options;
|
|
65
|
+
for (const bag of [metadata, summaryMetadata ?? {}]) {
|
|
66
66
|
for (const key of Object.keys(bag)) {
|
|
67
67
|
if (RESERVED_METADATA_KEYS.includes(key)) {
|
|
68
68
|
throw new Error(`Metadata key '${key}' is reserved. Use a different key name.`);
|
|
@@ -70,29 +70,29 @@ function createHealthcheckHandler(options) {
|
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
return async (req) => {
|
|
73
|
-
let
|
|
74
|
-
if (
|
|
73
|
+
let detail = "summary";
|
|
74
|
+
if (resolveDetail) {
|
|
75
75
|
try {
|
|
76
|
-
const result = await
|
|
77
|
-
if (result === "
|
|
78
|
-
|
|
76
|
+
const result = await resolveDetail(req);
|
|
77
|
+
if (result === "full" || result === "summary") {
|
|
78
|
+
detail = result;
|
|
79
79
|
}
|
|
80
80
|
} catch {
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
const response = await registry.run();
|
|
84
|
+
if (detail === "full") {
|
|
85
85
|
return {
|
|
86
86
|
status: httpStatusCode(response.status),
|
|
87
87
|
body: { ...metadata, ...toJson(response) }
|
|
88
88
|
};
|
|
89
89
|
}
|
|
90
90
|
const body = {
|
|
91
|
-
status:
|
|
92
|
-
timestamp:
|
|
93
|
-
...
|
|
91
|
+
status: statusToLabel(response.status),
|
|
92
|
+
timestamp: response.timestamp,
|
|
93
|
+
...summaryMetadata ?? metadata
|
|
94
94
|
};
|
|
95
|
-
return { status:
|
|
95
|
+
return { status: httpStatusCode(response.status), body };
|
|
96
96
|
};
|
|
97
97
|
}
|
|
98
98
|
|
|
@@ -103,7 +103,6 @@ function escapeHtml(str) {
|
|
|
103
103
|
function statusColor(status) {
|
|
104
104
|
switch (status) {
|
|
105
105
|
case "healthy":
|
|
106
|
-
case "ok":
|
|
107
106
|
return "#22C55E";
|
|
108
107
|
case "degraded":
|
|
109
108
|
return "#F59E0B";
|
|
@@ -115,10 +114,10 @@ function statusColor(status) {
|
|
|
115
114
|
}
|
|
116
115
|
var DEEP_KNOWN_KEYS = /* @__PURE__ */ new Set(["status", "timestamp", "checks", "cachedAt"]);
|
|
117
116
|
function isDeepResponse(body) {
|
|
118
|
-
return "checks" in body && "status" in body
|
|
117
|
+
return "checks" in body && "status" in body;
|
|
119
118
|
}
|
|
120
|
-
function
|
|
121
|
-
return "status" in body && body
|
|
119
|
+
function isSummaryResponse(body) {
|
|
120
|
+
return "status" in body && !("checks" in body) && !("error" in body);
|
|
122
121
|
}
|
|
123
122
|
function isErrorResponse(body) {
|
|
124
123
|
return "error" in body && !("status" in body);
|
|
@@ -277,9 +276,9 @@ function renderHealthcheckHtml(body) {
|
|
|
277
276
|
">${escapeHtml(body.error)}</div>`
|
|
278
277
|
);
|
|
279
278
|
}
|
|
280
|
-
if (
|
|
281
|
-
const color = statusColor(
|
|
282
|
-
const { status
|
|
279
|
+
if (isSummaryResponse(body)) {
|
|
280
|
+
const color = statusColor(body.status);
|
|
281
|
+
const { status, timestamp, ...rest } = body;
|
|
283
282
|
const metadataItems = Object.entries(rest).map(
|
|
284
283
|
([k, v]) => [k, String(v)]
|
|
285
284
|
);
|
|
@@ -296,7 +295,7 @@ function renderHealthcheckHtml(body) {
|
|
|
296
295
|
font-weight: 700;
|
|
297
296
|
color: #E4E4E7;
|
|
298
297
|
">Healthcheck</span>
|
|
299
|
-
${renderStatusBadge(
|
|
298
|
+
${renderStatusBadge(status, color)}
|
|
300
299
|
</div>
|
|
301
300
|
${renderMetadataItems(allItems)}`
|
|
302
301
|
);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { RequestHandler } from 'express';
|
|
2
|
-
import { H as HealthcheckHandlerOptions } from '../handler-
|
|
2
|
+
import { H as HealthcheckHandlerOptions } from '../handler-MWB6zBBG.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-
|
|
2
|
+
import { H as HealthcheckHandlerOptions } from '../handler-B1Omp5VP.js';
|
|
3
3
|
import '../core-Bee03bJm.js';
|
|
4
4
|
|
|
5
5
|
type HealthcheckMiddlewareOptions = HealthcheckHandlerOptions;
|
|
@@ -35,8 +35,8 @@ function httpStatusCode(status) {
|
|
|
35
35
|
// src/handler.ts
|
|
36
36
|
var RESERVED_METADATA_KEYS = ["status", "timestamp", "checks", "cachedAt"];
|
|
37
37
|
function createHealthcheckHandler(options) {
|
|
38
|
-
const { registry,
|
|
39
|
-
for (const bag of [metadata,
|
|
38
|
+
const { registry, resolveDetail, metadata = {}, summaryMetadata } = options;
|
|
39
|
+
for (const bag of [metadata, summaryMetadata ?? {}]) {
|
|
40
40
|
for (const key of Object.keys(bag)) {
|
|
41
41
|
if (RESERVED_METADATA_KEYS.includes(key)) {
|
|
42
42
|
throw new Error(`Metadata key '${key}' is reserved. Use a different key name.`);
|
|
@@ -44,29 +44,29 @@ function createHealthcheckHandler(options) {
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
return async (req) => {
|
|
47
|
-
let
|
|
48
|
-
if (
|
|
47
|
+
let detail = "summary";
|
|
48
|
+
if (resolveDetail) {
|
|
49
49
|
try {
|
|
50
|
-
const result = await
|
|
51
|
-
if (result === "
|
|
52
|
-
|
|
50
|
+
const result = await resolveDetail(req);
|
|
51
|
+
if (result === "full" || result === "summary") {
|
|
52
|
+
detail = result;
|
|
53
53
|
}
|
|
54
54
|
} catch {
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
const response = await registry.run();
|
|
58
|
+
if (detail === "full") {
|
|
59
59
|
return {
|
|
60
60
|
status: httpStatusCode(response.status),
|
|
61
61
|
body: { ...metadata, ...toJson(response) }
|
|
62
62
|
};
|
|
63
63
|
}
|
|
64
64
|
const body = {
|
|
65
|
-
status:
|
|
66
|
-
timestamp:
|
|
67
|
-
...
|
|
65
|
+
status: statusToLabel(response.status),
|
|
66
|
+
timestamp: response.timestamp,
|
|
67
|
+
...summaryMetadata ?? metadata
|
|
68
68
|
};
|
|
69
|
-
return { status:
|
|
69
|
+
return { status: httpStatusCode(response.status), body };
|
|
70
70
|
};
|
|
71
71
|
}
|
|
72
72
|
|
|
@@ -77,7 +77,6 @@ function escapeHtml(str) {
|
|
|
77
77
|
function statusColor(status) {
|
|
78
78
|
switch (status) {
|
|
79
79
|
case "healthy":
|
|
80
|
-
case "ok":
|
|
81
80
|
return "#22C55E";
|
|
82
81
|
case "degraded":
|
|
83
82
|
return "#F59E0B";
|
|
@@ -89,10 +88,10 @@ function statusColor(status) {
|
|
|
89
88
|
}
|
|
90
89
|
var DEEP_KNOWN_KEYS = /* @__PURE__ */ new Set(["status", "timestamp", "checks", "cachedAt"]);
|
|
91
90
|
function isDeepResponse(body) {
|
|
92
|
-
return "checks" in body && "status" in body
|
|
91
|
+
return "checks" in body && "status" in body;
|
|
93
92
|
}
|
|
94
|
-
function
|
|
95
|
-
return "status" in body && body
|
|
93
|
+
function isSummaryResponse(body) {
|
|
94
|
+
return "status" in body && !("checks" in body) && !("error" in body);
|
|
96
95
|
}
|
|
97
96
|
function isErrorResponse(body) {
|
|
98
97
|
return "error" in body && !("status" in body);
|
|
@@ -251,9 +250,9 @@ function renderHealthcheckHtml(body) {
|
|
|
251
250
|
">${escapeHtml(body.error)}</div>`
|
|
252
251
|
);
|
|
253
252
|
}
|
|
254
|
-
if (
|
|
255
|
-
const color = statusColor(
|
|
256
|
-
const { status
|
|
253
|
+
if (isSummaryResponse(body)) {
|
|
254
|
+
const color = statusColor(body.status);
|
|
255
|
+
const { status, timestamp, ...rest } = body;
|
|
257
256
|
const metadataItems = Object.entries(rest).map(
|
|
258
257
|
([k, v]) => [k, String(v)]
|
|
259
258
|
);
|
|
@@ -270,7 +269,7 @@ function renderHealthcheckHtml(body) {
|
|
|
270
269
|
font-weight: 700;
|
|
271
270
|
color: #E4E4E7;
|
|
272
271
|
">Healthcheck</span>
|
|
273
|
-
${renderStatusBadge(
|
|
272
|
+
${renderStatusBadge(status, color)}
|
|
274
273
|
</div>
|
|
275
274
|
${renderMetadataItems(allItems)}`
|
|
276
275
|
);
|
|
@@ -61,8 +61,8 @@ function httpStatusCode(status) {
|
|
|
61
61
|
// src/handler.ts
|
|
62
62
|
var RESERVED_METADATA_KEYS = ["status", "timestamp", "checks", "cachedAt"];
|
|
63
63
|
function createHealthcheckHandler(options) {
|
|
64
|
-
const { registry,
|
|
65
|
-
for (const bag of [metadata,
|
|
64
|
+
const { registry, resolveDetail, metadata = {}, summaryMetadata } = options;
|
|
65
|
+
for (const bag of [metadata, summaryMetadata ?? {}]) {
|
|
66
66
|
for (const key of Object.keys(bag)) {
|
|
67
67
|
if (RESERVED_METADATA_KEYS.includes(key)) {
|
|
68
68
|
throw new Error(`Metadata key '${key}' is reserved. Use a different key name.`);
|
|
@@ -70,29 +70,29 @@ function createHealthcheckHandler(options) {
|
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
return async (req) => {
|
|
73
|
-
let
|
|
74
|
-
if (
|
|
73
|
+
let detail = "summary";
|
|
74
|
+
if (resolveDetail) {
|
|
75
75
|
try {
|
|
76
|
-
const result = await
|
|
77
|
-
if (result === "
|
|
78
|
-
|
|
76
|
+
const result = await resolveDetail(req);
|
|
77
|
+
if (result === "full" || result === "summary") {
|
|
78
|
+
detail = result;
|
|
79
79
|
}
|
|
80
80
|
} catch {
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
const response = await registry.run();
|
|
84
|
+
if (detail === "full") {
|
|
85
85
|
return {
|
|
86
86
|
status: httpStatusCode(response.status),
|
|
87
87
|
body: { ...metadata, ...toJson(response) }
|
|
88
88
|
};
|
|
89
89
|
}
|
|
90
90
|
const body = {
|
|
91
|
-
status:
|
|
92
|
-
timestamp:
|
|
93
|
-
...
|
|
91
|
+
status: statusToLabel(response.status),
|
|
92
|
+
timestamp: response.timestamp,
|
|
93
|
+
...summaryMetadata ?? metadata
|
|
94
94
|
};
|
|
95
|
-
return { status:
|
|
95
|
+
return { status: httpStatusCode(response.status), body };
|
|
96
96
|
};
|
|
97
97
|
}
|
|
98
98
|
|
|
@@ -103,7 +103,6 @@ function escapeHtml(str) {
|
|
|
103
103
|
function statusColor(status) {
|
|
104
104
|
switch (status) {
|
|
105
105
|
case "healthy":
|
|
106
|
-
case "ok":
|
|
107
106
|
return "#22C55E";
|
|
108
107
|
case "degraded":
|
|
109
108
|
return "#F59E0B";
|
|
@@ -115,10 +114,10 @@ function statusColor(status) {
|
|
|
115
114
|
}
|
|
116
115
|
var DEEP_KNOWN_KEYS = /* @__PURE__ */ new Set(["status", "timestamp", "checks", "cachedAt"]);
|
|
117
116
|
function isDeepResponse(body) {
|
|
118
|
-
return "checks" in body && "status" in body
|
|
117
|
+
return "checks" in body && "status" in body;
|
|
119
118
|
}
|
|
120
|
-
function
|
|
121
|
-
return "status" in body && body
|
|
119
|
+
function isSummaryResponse(body) {
|
|
120
|
+
return "status" in body && !("checks" in body) && !("error" in body);
|
|
122
121
|
}
|
|
123
122
|
function isErrorResponse(body) {
|
|
124
123
|
return "error" in body && !("status" in body);
|
|
@@ -277,9 +276,9 @@ function renderHealthcheckHtml(body) {
|
|
|
277
276
|
">${escapeHtml(body.error)}</div>`
|
|
278
277
|
);
|
|
279
278
|
}
|
|
280
|
-
if (
|
|
281
|
-
const color = statusColor(
|
|
282
|
-
const { status
|
|
279
|
+
if (isSummaryResponse(body)) {
|
|
280
|
+
const color = statusColor(body.status);
|
|
281
|
+
const { status, timestamp, ...rest } = body;
|
|
283
282
|
const metadataItems = Object.entries(rest).map(
|
|
284
283
|
([k, v]) => [k, String(v)]
|
|
285
284
|
);
|
|
@@ -296,7 +295,7 @@ function renderHealthcheckHtml(body) {
|
|
|
296
295
|
font-weight: 700;
|
|
297
296
|
color: #E4E4E7;
|
|
298
297
|
">Healthcheck</span>
|
|
299
|
-
${renderStatusBadge(
|
|
298
|
+
${renderStatusBadge(status, color)}
|
|
300
299
|
</div>
|
|
301
300
|
${renderMetadataItems(allItems)}`
|
|
302
301
|
);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { H as HealthcheckHandlerOptions } from '../handler-
|
|
1
|
+
import { H as HealthcheckHandlerOptions } from '../handler-MWB6zBBG.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
|
-
*
|
|
18
|
+
* resolveDetail: (req) => req.ip === '10.0.0.1' ? 'full' : 'summary',
|
|
19
19
|
* });
|
|
20
20
|
* ```
|
|
21
21
|
*/
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { H as HealthcheckHandlerOptions } from '../handler-
|
|
1
|
+
import { H as HealthcheckHandlerOptions } from '../handler-B1Omp5VP.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
|
-
*
|
|
18
|
+
* resolveDetail: (req) => req.ip === '10.0.0.1' ? 'full' : 'summary',
|
|
19
19
|
* });
|
|
20
20
|
* ```
|
|
21
21
|
*/
|
|
@@ -35,8 +35,8 @@ function httpStatusCode(status) {
|
|
|
35
35
|
// src/handler.ts
|
|
36
36
|
var RESERVED_METADATA_KEYS = ["status", "timestamp", "checks", "cachedAt"];
|
|
37
37
|
function createHealthcheckHandler(options) {
|
|
38
|
-
const { registry,
|
|
39
|
-
for (const bag of [metadata,
|
|
38
|
+
const { registry, resolveDetail, metadata = {}, summaryMetadata } = options;
|
|
39
|
+
for (const bag of [metadata, summaryMetadata ?? {}]) {
|
|
40
40
|
for (const key of Object.keys(bag)) {
|
|
41
41
|
if (RESERVED_METADATA_KEYS.includes(key)) {
|
|
42
42
|
throw new Error(`Metadata key '${key}' is reserved. Use a different key name.`);
|
|
@@ -44,29 +44,29 @@ function createHealthcheckHandler(options) {
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
return async (req) => {
|
|
47
|
-
let
|
|
48
|
-
if (
|
|
47
|
+
let detail = "summary";
|
|
48
|
+
if (resolveDetail) {
|
|
49
49
|
try {
|
|
50
|
-
const result = await
|
|
51
|
-
if (result === "
|
|
52
|
-
|
|
50
|
+
const result = await resolveDetail(req);
|
|
51
|
+
if (result === "full" || result === "summary") {
|
|
52
|
+
detail = result;
|
|
53
53
|
}
|
|
54
54
|
} catch {
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
const response = await registry.run();
|
|
58
|
+
if (detail === "full") {
|
|
59
59
|
return {
|
|
60
60
|
status: httpStatusCode(response.status),
|
|
61
61
|
body: { ...metadata, ...toJson(response) }
|
|
62
62
|
};
|
|
63
63
|
}
|
|
64
64
|
const body = {
|
|
65
|
-
status:
|
|
66
|
-
timestamp:
|
|
67
|
-
...
|
|
65
|
+
status: statusToLabel(response.status),
|
|
66
|
+
timestamp: response.timestamp,
|
|
67
|
+
...summaryMetadata ?? metadata
|
|
68
68
|
};
|
|
69
|
-
return { status:
|
|
69
|
+
return { status: httpStatusCode(response.status), body };
|
|
70
70
|
};
|
|
71
71
|
}
|
|
72
72
|
|
|
@@ -77,7 +77,6 @@ function escapeHtml(str) {
|
|
|
77
77
|
function statusColor(status) {
|
|
78
78
|
switch (status) {
|
|
79
79
|
case "healthy":
|
|
80
|
-
case "ok":
|
|
81
80
|
return "#22C55E";
|
|
82
81
|
case "degraded":
|
|
83
82
|
return "#F59E0B";
|
|
@@ -89,10 +88,10 @@ function statusColor(status) {
|
|
|
89
88
|
}
|
|
90
89
|
var DEEP_KNOWN_KEYS = /* @__PURE__ */ new Set(["status", "timestamp", "checks", "cachedAt"]);
|
|
91
90
|
function isDeepResponse(body) {
|
|
92
|
-
return "checks" in body && "status" in body
|
|
91
|
+
return "checks" in body && "status" in body;
|
|
93
92
|
}
|
|
94
|
-
function
|
|
95
|
-
return "status" in body && body
|
|
93
|
+
function isSummaryResponse(body) {
|
|
94
|
+
return "status" in body && !("checks" in body) && !("error" in body);
|
|
96
95
|
}
|
|
97
96
|
function isErrorResponse(body) {
|
|
98
97
|
return "error" in body && !("status" in body);
|
|
@@ -251,9 +250,9 @@ function renderHealthcheckHtml(body) {
|
|
|
251
250
|
">${escapeHtml(body.error)}</div>`
|
|
252
251
|
);
|
|
253
252
|
}
|
|
254
|
-
if (
|
|
255
|
-
const color = statusColor(
|
|
256
|
-
const { status
|
|
253
|
+
if (isSummaryResponse(body)) {
|
|
254
|
+
const color = statusColor(body.status);
|
|
255
|
+
const { status, timestamp, ...rest } = body;
|
|
257
256
|
const metadataItems = Object.entries(rest).map(
|
|
258
257
|
([k, v]) => [k, String(v)]
|
|
259
258
|
);
|
|
@@ -270,7 +269,7 @@ function renderHealthcheckHtml(body) {
|
|
|
270
269
|
font-weight: 700;
|
|
271
270
|
color: #E4E4E7;
|
|
272
271
|
">Healthcheck</span>
|
|
273
|
-
${renderStatusBadge(
|
|
272
|
+
${renderStatusBadge(status, color)}
|
|
274
273
|
</div>
|
|
275
274
|
${renderMetadataItems(allItems)}`
|
|
276
275
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@firebreak/vitals",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "Deep healthcheck endpoints following the HEALTHCHECK_SPEC format",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -107,6 +107,7 @@
|
|
|
107
107
|
"build": "tsup",
|
|
108
108
|
"test": "vitest run",
|
|
109
109
|
"test:watch": "vitest",
|
|
110
|
+
"test:coverage": "vitest run --coverage",
|
|
110
111
|
"lint": "tsc --noEmit"
|
|
111
112
|
},
|
|
112
113
|
"peerDependencies": {
|
|
@@ -130,6 +131,7 @@
|
|
|
130
131
|
"@types/node": "^22.0.0",
|
|
131
132
|
"@types/pg": "^8.0.0",
|
|
132
133
|
"@types/supertest": "^6.0.0",
|
|
134
|
+
"@vitest/coverage-v8": "^2.1.9",
|
|
133
135
|
"express": "^4.21.0",
|
|
134
136
|
"ioredis": "^5.0.0",
|
|
135
137
|
"pg": "^8.13.0",
|
|
@@ -1,21 +0,0 @@
|
|
|
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
|
-
shallowMetadata?: Record<string, string | number | boolean>;
|
|
9
|
-
}
|
|
10
|
-
interface ShallowResponseJson {
|
|
11
|
-
status: 'ok';
|
|
12
|
-
timestamp: string;
|
|
13
|
-
[key: string]: string | number | boolean;
|
|
14
|
-
}
|
|
15
|
-
interface HealthcheckHandlerResult {
|
|
16
|
-
status: number;
|
|
17
|
-
body: HealthcheckResponseJson | ShallowResponseJson;
|
|
18
|
-
}
|
|
19
|
-
declare function createHealthcheckHandler(options: HealthcheckHandlerOptions): (req: Record<string, unknown>) => Promise<HealthcheckHandlerResult>;
|
|
20
|
-
|
|
21
|
-
export { type HealthcheckHandlerOptions as H, type ResolveDepthFn as R, type ShallowResponseJson as S, type HealthcheckHandlerResult as a, createHealthcheckHandler as c };
|
|
@@ -1,21 +0,0 @@
|
|
|
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
|
-
shallowMetadata?: Record<string, string | number | boolean>;
|
|
9
|
-
}
|
|
10
|
-
interface ShallowResponseJson {
|
|
11
|
-
status: 'ok';
|
|
12
|
-
timestamp: string;
|
|
13
|
-
[key: string]: string | number | boolean;
|
|
14
|
-
}
|
|
15
|
-
interface HealthcheckHandlerResult {
|
|
16
|
-
status: number;
|
|
17
|
-
body: HealthcheckResponseJson | ShallowResponseJson;
|
|
18
|
-
}
|
|
19
|
-
declare function createHealthcheckHandler(options: HealthcheckHandlerOptions): (req: Record<string, unknown>) => Promise<HealthcheckHandlerResult>;
|
|
20
|
-
|
|
21
|
-
export { type HealthcheckHandlerOptions as H, type ResolveDepthFn as R, type ShallowResponseJson as S, type HealthcheckHandlerResult as a, createHealthcheckHandler as c };
|