@firebreak/vitals 1.2.3 → 2.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 +25 -1
- package/dist/checks/databricks.d.cts +1 -1
- package/dist/checks/databricks.d.ts +1 -1
- package/dist/checks/http.d.cts +1 -1
- package/dist/checks/http.d.ts +1 -1
- package/dist/checks/postgres.d.cts +1 -1
- package/dist/checks/postgres.d.ts +1 -1
- package/dist/checks/redis.d.cts +1 -1
- package/dist/checks/redis.d.ts +1 -1
- package/dist/{core-BJ2Z0rRi.d.cts → core-Bee03bJm.d.cts} +7 -1
- package/dist/{core-BJ2Z0rRi.d.ts → core-Bee03bJm.d.ts} +7 -1
- package/dist/handler-BvjN4Ot9.d.ts +20 -0
- package/dist/handler-TZOgZvY7.d.cts +20 -0
- package/dist/index.cjs +286 -29
- package/dist/index.d.cts +10 -3
- package/dist/index.d.ts +10 -3
- package/dist/index.mjs +285 -29
- package/dist/integrations/express.cjs +268 -52
- package/dist/integrations/express.d.cts +2 -2
- package/dist/integrations/express.d.ts +2 -2
- package/dist/integrations/express.mjs +268 -52
- package/dist/integrations/next.cjs +294 -51
- package/dist/integrations/next.d.cts +3 -3
- package/dist/integrations/next.d.ts +3 -3
- package/dist/integrations/next.mjs +294 -51
- package/package.json +1 -1
- package/dist/handler-Bvf66wzh.d.cts +0 -26
- package/dist/handler-R2U3ygLo.d.ts +0 -26
package/README.md
CHANGED
|
@@ -258,10 +258,34 @@ When a `token` is configured, the endpoint serves two tiers from a single route:
|
|
|
258
258
|
| No token provided | **Shallow** — `200` with `{ status: "ok", timestamp, ...metadata }` |
|
|
259
259
|
| Valid token provided | **Deep** — `200`/`503` with full check results + metadata |
|
|
260
260
|
| Invalid token provided | `403 Forbidden` |
|
|
261
|
-
| No token configured |
|
|
261
|
+
| No token configured | **Shallow** by default (see `deep` option below) |
|
|
262
262
|
|
|
263
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
264
|
|
|
265
|
+
#### The `deep` Option
|
|
266
|
+
|
|
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`:
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
createHealthcheckHandler({
|
|
271
|
+
registry,
|
|
272
|
+
deep: true, // no token → always return deep response
|
|
273
|
+
});
|
|
274
|
+
```
|
|
275
|
+
|
|
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 | — | — |
|
|
288
|
+
|
|
265
289
|
### Metadata
|
|
266
290
|
|
|
267
291
|
Attach static key-value pairs that appear in both shallow and deep responses:
|
package/dist/checks/http.d.cts
CHANGED
package/dist/checks/http.d.ts
CHANGED
package/dist/checks/redis.d.cts
CHANGED
package/dist/checks/redis.d.ts
CHANGED
|
@@ -26,6 +26,7 @@ interface HealthcheckResponse {
|
|
|
26
26
|
readonly status: StatusValue;
|
|
27
27
|
readonly timestamp: string;
|
|
28
28
|
readonly checks: Readonly<Record<string, CheckResult>>;
|
|
29
|
+
readonly cachedAt?: string;
|
|
29
30
|
}
|
|
30
31
|
interface HealthcheckResponseJson {
|
|
31
32
|
status: StatusLabel;
|
|
@@ -35,6 +36,7 @@ interface HealthcheckResponseJson {
|
|
|
35
36
|
latencyMs: number;
|
|
36
37
|
message: string;
|
|
37
38
|
}>;
|
|
39
|
+
cachedAt?: string;
|
|
38
40
|
}
|
|
39
41
|
declare function toJson(response: HealthcheckResponse): HealthcheckResponseJson;
|
|
40
42
|
declare function httpStatusCode(status: StatusValue): 200 | 503;
|
|
@@ -43,10 +45,13 @@ type AsyncCheckFn = () => Promise<CheckInput>;
|
|
|
43
45
|
type SyncCheckFn = () => CheckInput;
|
|
44
46
|
interface RegistryOptions {
|
|
45
47
|
defaultTimeout?: number;
|
|
48
|
+
cacheTtlMs?: number;
|
|
46
49
|
}
|
|
47
50
|
declare class HealthcheckRegistry {
|
|
48
51
|
private readonly checks;
|
|
49
52
|
private readonly defaultTimeout;
|
|
53
|
+
private readonly cacheTtlMs;
|
|
54
|
+
private cache;
|
|
50
55
|
constructor(options?: RegistryOptions);
|
|
51
56
|
add(name: string, fn: AsyncCheckFn, options?: {
|
|
52
57
|
timeout?: number;
|
|
@@ -58,8 +63,9 @@ declare class HealthcheckRegistry {
|
|
|
58
63
|
timeout?: number;
|
|
59
64
|
}): (fn: AsyncCheckFn) => AsyncCheckFn;
|
|
60
65
|
run(): Promise<HealthcheckResponse>;
|
|
66
|
+
private executeChecks;
|
|
61
67
|
private runSingle;
|
|
62
68
|
}
|
|
63
69
|
declare function syncCheck(fn: SyncCheckFn): AsyncCheckFn;
|
|
64
70
|
|
|
65
|
-
export { type AsyncCheckFn as A, type CheckInput as C,
|
|
71
|
+
export { type AsyncCheckFn as A, type CheckInput as C, type HealthcheckResponseJson as H, Status as S, type CheckResult as a, HealthcheckRegistry as b, type HealthcheckResponse as c, type StatusLabel as d, type StatusValue as e, type SyncCheckFn as f, statusToLabel as g, httpStatusCode as h, syncCheck as i, statusFromString as s, toJson as t };
|
|
@@ -26,6 +26,7 @@ interface HealthcheckResponse {
|
|
|
26
26
|
readonly status: StatusValue;
|
|
27
27
|
readonly timestamp: string;
|
|
28
28
|
readonly checks: Readonly<Record<string, CheckResult>>;
|
|
29
|
+
readonly cachedAt?: string;
|
|
29
30
|
}
|
|
30
31
|
interface HealthcheckResponseJson {
|
|
31
32
|
status: StatusLabel;
|
|
@@ -35,6 +36,7 @@ interface HealthcheckResponseJson {
|
|
|
35
36
|
latencyMs: number;
|
|
36
37
|
message: string;
|
|
37
38
|
}>;
|
|
39
|
+
cachedAt?: string;
|
|
38
40
|
}
|
|
39
41
|
declare function toJson(response: HealthcheckResponse): HealthcheckResponseJson;
|
|
40
42
|
declare function httpStatusCode(status: StatusValue): 200 | 503;
|
|
@@ -43,10 +45,13 @@ type AsyncCheckFn = () => Promise<CheckInput>;
|
|
|
43
45
|
type SyncCheckFn = () => CheckInput;
|
|
44
46
|
interface RegistryOptions {
|
|
45
47
|
defaultTimeout?: number;
|
|
48
|
+
cacheTtlMs?: number;
|
|
46
49
|
}
|
|
47
50
|
declare class HealthcheckRegistry {
|
|
48
51
|
private readonly checks;
|
|
49
52
|
private readonly defaultTimeout;
|
|
53
|
+
private readonly cacheTtlMs;
|
|
54
|
+
private cache;
|
|
50
55
|
constructor(options?: RegistryOptions);
|
|
51
56
|
add(name: string, fn: AsyncCheckFn, options?: {
|
|
52
57
|
timeout?: number;
|
|
@@ -58,8 +63,9 @@ declare class HealthcheckRegistry {
|
|
|
58
63
|
timeout?: number;
|
|
59
64
|
}): (fn: AsyncCheckFn) => AsyncCheckFn;
|
|
60
65
|
run(): Promise<HealthcheckResponse>;
|
|
66
|
+
private executeChecks;
|
|
61
67
|
private runSingle;
|
|
62
68
|
}
|
|
63
69
|
declare function syncCheck(fn: SyncCheckFn): AsyncCheckFn;
|
|
64
70
|
|
|
65
|
-
export { type AsyncCheckFn as A, type CheckInput as C,
|
|
71
|
+
export { type AsyncCheckFn as A, type CheckInput as C, type HealthcheckResponseJson as H, Status as S, type CheckResult as a, HealthcheckRegistry as b, type HealthcheckResponse as c, type StatusLabel as d, type StatusValue as e, type SyncCheckFn as f, statusToLabel as g, httpStatusCode as h, syncCheck as i, statusFromString as s, toJson as t };
|
|
@@ -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
|
@@ -25,6 +25,7 @@ __export(src_exports, {
|
|
|
25
25
|
createHealthcheckHandler: () => createHealthcheckHandler,
|
|
26
26
|
extractToken: () => extractToken,
|
|
27
27
|
httpStatusCode: () => httpStatusCode,
|
|
28
|
+
renderHealthcheckHtml: () => renderHealthcheckHtml,
|
|
28
29
|
statusFromString: () => statusFromString,
|
|
29
30
|
statusToLabel: () => statusToLabel,
|
|
30
31
|
syncCheck: () => syncCheck,
|
|
@@ -58,7 +59,7 @@ function statusFromString(value) {
|
|
|
58
59
|
return mapping[value.toLowerCase()] ?? Status.OUTAGE;
|
|
59
60
|
}
|
|
60
61
|
function toJson(response) {
|
|
61
|
-
|
|
62
|
+
const json = {
|
|
62
63
|
status: statusToLabel(response.status),
|
|
63
64
|
timestamp: response.timestamp,
|
|
64
65
|
checks: Object.fromEntries(
|
|
@@ -68,6 +69,10 @@ function toJson(response) {
|
|
|
68
69
|
])
|
|
69
70
|
)
|
|
70
71
|
};
|
|
72
|
+
if (response.cachedAt !== void 0) {
|
|
73
|
+
json.cachedAt = response.cachedAt;
|
|
74
|
+
}
|
|
75
|
+
return json;
|
|
71
76
|
}
|
|
72
77
|
function httpStatusCode(status) {
|
|
73
78
|
return status === Status.HEALTHY ? 200 : 503;
|
|
@@ -78,8 +83,11 @@ var import_node_perf_hooks = require("perf_hooks");
|
|
|
78
83
|
var HealthcheckRegistry = class {
|
|
79
84
|
checks = [];
|
|
80
85
|
defaultTimeout;
|
|
86
|
+
cacheTtlMs;
|
|
87
|
+
cache = null;
|
|
81
88
|
constructor(options) {
|
|
82
89
|
this.defaultTimeout = options?.defaultTimeout ?? 5e3;
|
|
90
|
+
this.cacheTtlMs = options?.cacheTtlMs ?? 0;
|
|
83
91
|
}
|
|
84
92
|
add(name, fn, options) {
|
|
85
93
|
if (this.checks.some((c) => c.name === name)) {
|
|
@@ -104,6 +112,27 @@ var HealthcheckRegistry = class {
|
|
|
104
112
|
};
|
|
105
113
|
}
|
|
106
114
|
async run() {
|
|
115
|
+
if (this.cacheTtlMs <= 0) {
|
|
116
|
+
return this.executeChecks();
|
|
117
|
+
}
|
|
118
|
+
if (this.cache !== null && Date.now() < this.cache.expiresAt) {
|
|
119
|
+
return {
|
|
120
|
+
status: this.cache.status,
|
|
121
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
122
|
+
checks: structuredClone(this.cache.checks),
|
|
123
|
+
cachedAt: this.cache.cachedAt
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
const response = await this.executeChecks();
|
|
127
|
+
this.cache = {
|
|
128
|
+
checks: structuredClone(response.checks),
|
|
129
|
+
status: response.status,
|
|
130
|
+
cachedAt: response.timestamp,
|
|
131
|
+
expiresAt: Date.now() + this.cacheTtlMs
|
|
132
|
+
};
|
|
133
|
+
return response;
|
|
134
|
+
}
|
|
135
|
+
async executeChecks() {
|
|
107
136
|
if (this.checks.length === 0) {
|
|
108
137
|
return {
|
|
109
138
|
status: Status.HEALTHY,
|
|
@@ -189,47 +218,274 @@ function extractToken(options) {
|
|
|
189
218
|
}
|
|
190
219
|
|
|
191
220
|
// src/handler.ts
|
|
192
|
-
var RESERVED_METADATA_KEYS = ["status", "timestamp", "checks"];
|
|
221
|
+
var RESERVED_METADATA_KEYS = ["status", "timestamp", "checks", "cachedAt"];
|
|
193
222
|
function createHealthcheckHandler(options) {
|
|
194
|
-
const { registry,
|
|
195
|
-
const token = rawToken || null;
|
|
223
|
+
const { registry, resolveDepth, metadata = {} } = options;
|
|
196
224
|
for (const key of Object.keys(metadata)) {
|
|
197
225
|
if (RESERVED_METADATA_KEYS.includes(key)) {
|
|
198
226
|
throw new Error(`Metadata key '${key}' is reserved. Use a different key name.`);
|
|
199
227
|
}
|
|
200
228
|
}
|
|
201
229
|
return async (req) => {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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 {
|
|
238
|
+
}
|
|
209
239
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
if (provided === null) {
|
|
216
|
-
const body = {
|
|
217
|
-
status: "ok",
|
|
218
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
219
|
-
...metadata
|
|
240
|
+
if (depth === "deep") {
|
|
241
|
+
const response = await registry.run();
|
|
242
|
+
return {
|
|
243
|
+
status: httpStatusCode(response.status),
|
|
244
|
+
body: { ...metadata, ...toJson(response) }
|
|
220
245
|
};
|
|
221
|
-
return { status: 200, body };
|
|
222
246
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
return {
|
|
228
|
-
status: httpStatusCode(response.status),
|
|
229
|
-
body: { ...metadata, ...toJson(response) }
|
|
247
|
+
const body = {
|
|
248
|
+
status: "ok",
|
|
249
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
250
|
+
...metadata
|
|
230
251
|
};
|
|
252
|
+
return { status: 200, body };
|
|
231
253
|
};
|
|
232
254
|
}
|
|
255
|
+
|
|
256
|
+
// src/html.ts
|
|
257
|
+
function escapeHtml(str) {
|
|
258
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
259
|
+
}
|
|
260
|
+
function statusColor(status) {
|
|
261
|
+
switch (status) {
|
|
262
|
+
case "healthy":
|
|
263
|
+
case "ok":
|
|
264
|
+
return "#22C55E";
|
|
265
|
+
case "degraded":
|
|
266
|
+
return "#F59E0B";
|
|
267
|
+
case "outage":
|
|
268
|
+
return "#ED1C24";
|
|
269
|
+
default:
|
|
270
|
+
return "#ED1C24";
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
var DEEP_KNOWN_KEYS = /* @__PURE__ */ new Set(["status", "timestamp", "checks", "cachedAt"]);
|
|
274
|
+
function isDeepResponse(body) {
|
|
275
|
+
return "checks" in body && "status" in body && body.status !== "ok";
|
|
276
|
+
}
|
|
277
|
+
function isShallowResponse(body) {
|
|
278
|
+
return "status" in body && body.status === "ok";
|
|
279
|
+
}
|
|
280
|
+
function isErrorResponse(body) {
|
|
281
|
+
return "error" in body && !("status" in body);
|
|
282
|
+
}
|
|
283
|
+
function renderStatusBadge(label, color) {
|
|
284
|
+
return `<span style="
|
|
285
|
+
display: inline-flex;
|
|
286
|
+
align-items: center;
|
|
287
|
+
gap: 8px;
|
|
288
|
+
padding: 4px 12px;
|
|
289
|
+
border-radius: 9999px;
|
|
290
|
+
background: ${color}1A;
|
|
291
|
+
font-family: 'Inter', system-ui, sans-serif;
|
|
292
|
+
font-size: 12px;
|
|
293
|
+
font-weight: 600;
|
|
294
|
+
letter-spacing: 0.05em;
|
|
295
|
+
text-transform: uppercase;
|
|
296
|
+
color: ${color};
|
|
297
|
+
"><span style="
|
|
298
|
+
width: 8px;
|
|
299
|
+
height: 8px;
|
|
300
|
+
border-radius: 50%;
|
|
301
|
+
background: ${color};
|
|
302
|
+
box-shadow: 0 0 6px ${color}80;
|
|
303
|
+
"></span>${escapeHtml(label)}</span>`;
|
|
304
|
+
}
|
|
305
|
+
function renderMetadataItems(items) {
|
|
306
|
+
if (items.length === 0) return "";
|
|
307
|
+
return `<div style="
|
|
308
|
+
display: flex;
|
|
309
|
+
flex-wrap: wrap;
|
|
310
|
+
gap: 24px;
|
|
311
|
+
margin-top: 16px;
|
|
312
|
+
">${items.map(
|
|
313
|
+
([label, value]) => `<div>
|
|
314
|
+
<div style="
|
|
315
|
+
font-family: 'Inter', system-ui, sans-serif;
|
|
316
|
+
font-size: 10px;
|
|
317
|
+
font-weight: 600;
|
|
318
|
+
letter-spacing: 0.05em;
|
|
319
|
+
text-transform: uppercase;
|
|
320
|
+
color: #A1A1AA;
|
|
321
|
+
margin-bottom: 4px;
|
|
322
|
+
">${escapeHtml(label)}</div>
|
|
323
|
+
<div style="
|
|
324
|
+
font-family: 'JetBrains Mono', monospace;
|
|
325
|
+
font-size: 13px;
|
|
326
|
+
color: #E4E4E7;
|
|
327
|
+
">${escapeHtml(value)}</div>
|
|
328
|
+
</div>`
|
|
329
|
+
).join("")}</div>`;
|
|
330
|
+
}
|
|
331
|
+
function renderCheckRow(name, check) {
|
|
332
|
+
const color = statusColor(check.status);
|
|
333
|
+
return `<div style="
|
|
334
|
+
display: flex;
|
|
335
|
+
align-items: center;
|
|
336
|
+
gap: 12px;
|
|
337
|
+
padding: 12px 0;
|
|
338
|
+
border-top: 1px solid #27272A;
|
|
339
|
+
">
|
|
340
|
+
<span style="
|
|
341
|
+
width: 8px;
|
|
342
|
+
height: 8px;
|
|
343
|
+
border-radius: 50%;
|
|
344
|
+
background: ${color};
|
|
345
|
+
box-shadow: 0 0 6px ${color}80;
|
|
346
|
+
flex-shrink: 0;
|
|
347
|
+
"></span>
|
|
348
|
+
<span style="
|
|
349
|
+
font-family: 'Inter', system-ui, sans-serif;
|
|
350
|
+
font-size: 12px;
|
|
351
|
+
text-transform: uppercase;
|
|
352
|
+
letter-spacing: 0.05em;
|
|
353
|
+
color: ${color};
|
|
354
|
+
font-weight: 600;
|
|
355
|
+
min-width: 72px;
|
|
356
|
+
">${escapeHtml(check.status)}</span>
|
|
357
|
+
<span style="
|
|
358
|
+
font-family: 'Inter', system-ui, sans-serif;
|
|
359
|
+
font-size: 14px;
|
|
360
|
+
font-weight: 500;
|
|
361
|
+
color: #E4E4E7;
|
|
362
|
+
flex: 1;
|
|
363
|
+
">${escapeHtml(name)}</span>
|
|
364
|
+
<span style="
|
|
365
|
+
font-family: 'JetBrains Mono', monospace;
|
|
366
|
+
font-size: 12px;
|
|
367
|
+
color: #A1A1AA;
|
|
368
|
+
min-width: 60px;
|
|
369
|
+
text-align: right;
|
|
370
|
+
">${escapeHtml(String(check.latencyMs))}ms</span>
|
|
371
|
+
<span style="
|
|
372
|
+
font-family: 'JetBrains Mono', monospace;
|
|
373
|
+
font-size: 12px;
|
|
374
|
+
color: #71717A;
|
|
375
|
+
max-width: 200px;
|
|
376
|
+
overflow: hidden;
|
|
377
|
+
text-overflow: ellipsis;
|
|
378
|
+
white-space: nowrap;
|
|
379
|
+
">${escapeHtml(check.message)}</span>
|
|
380
|
+
</div>`;
|
|
381
|
+
}
|
|
382
|
+
function renderPage(borderColor, content) {
|
|
383
|
+
return `<!DOCTYPE html>
|
|
384
|
+
<html lang="en">
|
|
385
|
+
<head>
|
|
386
|
+
<meta charset="utf-8">
|
|
387
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
388
|
+
<title>Healthcheck</title>
|
|
389
|
+
<style></style>
|
|
390
|
+
</head>
|
|
391
|
+
<body style="
|
|
392
|
+
margin: 0;
|
|
393
|
+
padding: 40px 20px;
|
|
394
|
+
background: #08080A;
|
|
395
|
+
min-height: 100vh;
|
|
396
|
+
display: flex;
|
|
397
|
+
justify-content: center;
|
|
398
|
+
align-items: flex-start;
|
|
399
|
+
box-sizing: border-box;
|
|
400
|
+
">
|
|
401
|
+
<div style="
|
|
402
|
+
width: 100%;
|
|
403
|
+
max-width: 560px;
|
|
404
|
+
background: #18181B;
|
|
405
|
+
border: 1px solid #27272A;
|
|
406
|
+
border-left: 3px solid ${borderColor};
|
|
407
|
+
border-radius: 8px;
|
|
408
|
+
padding: 24px;
|
|
409
|
+
">
|
|
410
|
+
${content}
|
|
411
|
+
</div>
|
|
412
|
+
</body>
|
|
413
|
+
</html>`;
|
|
414
|
+
}
|
|
415
|
+
function renderHealthcheckHtml(body) {
|
|
416
|
+
if (isErrorResponse(body)) {
|
|
417
|
+
const color = "#ED1C24";
|
|
418
|
+
return renderPage(
|
|
419
|
+
color,
|
|
420
|
+
`<div style="display: flex; align-items: center; justify-content: space-between;">
|
|
421
|
+
<span style="
|
|
422
|
+
font-family: 'Inter', system-ui, sans-serif;
|
|
423
|
+
font-size: 18px;
|
|
424
|
+
font-weight: 700;
|
|
425
|
+
color: #E4E4E7;
|
|
426
|
+
">Healthcheck</span>
|
|
427
|
+
${renderStatusBadge("ERROR", color)}
|
|
428
|
+
</div>
|
|
429
|
+
<div style="
|
|
430
|
+
margin-top: 16px;
|
|
431
|
+
font-family: 'JetBrains Mono', monospace;
|
|
432
|
+
font-size: 13px;
|
|
433
|
+
color: #EF4444;
|
|
434
|
+
">${escapeHtml(body.error)}</div>`
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
if (isShallowResponse(body)) {
|
|
438
|
+
const color = statusColor("ok");
|
|
439
|
+
const { status: _, timestamp, ...rest } = body;
|
|
440
|
+
const metadataItems = Object.entries(rest).map(
|
|
441
|
+
([k, v]) => [k, String(v)]
|
|
442
|
+
);
|
|
443
|
+
const allItems = [
|
|
444
|
+
["timestamp", timestamp],
|
|
445
|
+
...metadataItems
|
|
446
|
+
];
|
|
447
|
+
return renderPage(
|
|
448
|
+
color,
|
|
449
|
+
`<div style="display: flex; align-items: center; justify-content: space-between;">
|
|
450
|
+
<span style="
|
|
451
|
+
font-family: 'Inter', system-ui, sans-serif;
|
|
452
|
+
font-size: 18px;
|
|
453
|
+
font-weight: 700;
|
|
454
|
+
color: #E4E4E7;
|
|
455
|
+
">Healthcheck</span>
|
|
456
|
+
${renderStatusBadge("OK", color)}
|
|
457
|
+
</div>
|
|
458
|
+
${renderMetadataItems(allItems)}`
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
if (isDeepResponse(body)) {
|
|
462
|
+
const color = statusColor(body.status);
|
|
463
|
+
const { timestamp, checks } = body;
|
|
464
|
+
const metadataItems = Object.entries(body).filter(([k]) => !DEEP_KNOWN_KEYS.has(k)).map(([k, v]) => [k, String(v)]);
|
|
465
|
+
const allItems = [
|
|
466
|
+
["timestamp", timestamp],
|
|
467
|
+
...metadataItems
|
|
468
|
+
];
|
|
469
|
+
const checkRows = Object.entries(checks).sort(([a], [b]) => a.localeCompare(b)).map(([name, check]) => renderCheckRow(name, check)).join("");
|
|
470
|
+
return renderPage(
|
|
471
|
+
color,
|
|
472
|
+
`<div style="display: flex; align-items: center; justify-content: space-between;">
|
|
473
|
+
<span style="
|
|
474
|
+
font-family: 'Inter', system-ui, sans-serif;
|
|
475
|
+
font-size: 18px;
|
|
476
|
+
font-weight: 700;
|
|
477
|
+
color: #E4E4E7;
|
|
478
|
+
">Healthcheck</span>
|
|
479
|
+
${renderStatusBadge(body.status, color)}
|
|
480
|
+
</div>
|
|
481
|
+
${renderMetadataItems(allItems)}
|
|
482
|
+
<div style="margin-top: 20px;">
|
|
483
|
+
${checkRows}
|
|
484
|
+
</div>`
|
|
485
|
+
);
|
|
486
|
+
}
|
|
487
|
+
return renderPage("#ED1C24", `<pre style="color: #E4E4E7; font-family: 'JetBrains Mono', monospace;">${escapeHtml(JSON.stringify(body, null, 2))}</pre>`);
|
|
488
|
+
}
|
|
233
489
|
// Annotate the CommonJS export names for ESM import in node:
|
|
234
490
|
0 && (module.exports = {
|
|
235
491
|
HealthcheckRegistry,
|
|
@@ -237,6 +493,7 @@ function createHealthcheckHandler(options) {
|
|
|
237
493
|
createHealthcheckHandler,
|
|
238
494
|
extractToken,
|
|
239
495
|
httpStatusCode,
|
|
496
|
+
renderHealthcheckHtml,
|
|
240
497
|
statusFromString,
|
|
241
498
|
statusToLabel,
|
|
242
499
|
syncCheck,
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
export {
|
|
1
|
+
import { H as HealthcheckResponseJson } from './core-Bee03bJm.cjs';
|
|
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-TZOgZvY7.cjs';
|
|
4
|
+
export { H as HealthcheckHandlerOptions, a as HealthcheckHandlerResult, R as ResolveDepthFn, c as createHealthcheckHandler } from './handler-TZOgZvY7.cjs';
|
|
3
5
|
|
|
4
6
|
declare function verifyToken(provided: string, expected: string): boolean;
|
|
5
7
|
declare function extractToken(options: {
|
|
@@ -8,4 +10,9 @@ declare function extractToken(options: {
|
|
|
8
10
|
queryParamName?: string;
|
|
9
11
|
}): string | null;
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
type HealthcheckBody = HealthcheckResponseJson | ShallowResponseJson | {
|
|
14
|
+
error: string;
|
|
15
|
+
};
|
|
16
|
+
declare function renderHealthcheckHtml(body: HealthcheckBody): string;
|
|
17
|
+
|
|
18
|
+
export { HealthcheckResponseJson, ShallowResponseJson, extractToken, renderHealthcheckHtml, verifyToken };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
export {
|
|
1
|
+
import { H as HealthcheckResponseJson } from './core-Bee03bJm.js';
|
|
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-BvjN4Ot9.js';
|
|
4
|
+
export { H as HealthcheckHandlerOptions, a as HealthcheckHandlerResult, R as ResolveDepthFn, c as createHealthcheckHandler } from './handler-BvjN4Ot9.js';
|
|
3
5
|
|
|
4
6
|
declare function verifyToken(provided: string, expected: string): boolean;
|
|
5
7
|
declare function extractToken(options: {
|
|
@@ -8,4 +10,9 @@ declare function extractToken(options: {
|
|
|
8
10
|
queryParamName?: string;
|
|
9
11
|
}): string | null;
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
type HealthcheckBody = HealthcheckResponseJson | ShallowResponseJson | {
|
|
14
|
+
error: string;
|
|
15
|
+
};
|
|
16
|
+
declare function renderHealthcheckHtml(body: HealthcheckBody): string;
|
|
17
|
+
|
|
18
|
+
export { HealthcheckResponseJson, ShallowResponseJson, extractToken, renderHealthcheckHtml, verifyToken };
|