@firebreak/vitals 1.2.3 → 1.3.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/dist/index.mjs CHANGED
@@ -23,7 +23,7 @@ function statusFromString(value) {
23
23
  return mapping[value.toLowerCase()] ?? Status.OUTAGE;
24
24
  }
25
25
  function toJson(response) {
26
- return {
26
+ const json = {
27
27
  status: statusToLabel(response.status),
28
28
  timestamp: response.timestamp,
29
29
  checks: Object.fromEntries(
@@ -33,6 +33,10 @@ function toJson(response) {
33
33
  ])
34
34
  )
35
35
  };
36
+ if (response.cachedAt !== void 0) {
37
+ json.cachedAt = response.cachedAt;
38
+ }
39
+ return json;
36
40
  }
37
41
  function httpStatusCode(status) {
38
42
  return status === Status.HEALTHY ? 200 : 503;
@@ -43,8 +47,11 @@ import { performance } from "perf_hooks";
43
47
  var HealthcheckRegistry = class {
44
48
  checks = [];
45
49
  defaultTimeout;
50
+ cacheTtlMs;
51
+ cache = null;
46
52
  constructor(options) {
47
53
  this.defaultTimeout = options?.defaultTimeout ?? 5e3;
54
+ this.cacheTtlMs = options?.cacheTtlMs ?? 0;
48
55
  }
49
56
  add(name, fn, options) {
50
57
  if (this.checks.some((c) => c.name === name)) {
@@ -69,6 +76,27 @@ var HealthcheckRegistry = class {
69
76
  };
70
77
  }
71
78
  async run() {
79
+ if (this.cacheTtlMs <= 0) {
80
+ return this.executeChecks();
81
+ }
82
+ if (this.cache !== null && Date.now() < this.cache.expiresAt) {
83
+ return {
84
+ status: this.cache.status,
85
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
86
+ checks: structuredClone(this.cache.checks),
87
+ cachedAt: this.cache.cachedAt
88
+ };
89
+ }
90
+ const response = await this.executeChecks();
91
+ this.cache = {
92
+ checks: structuredClone(response.checks),
93
+ status: response.status,
94
+ cachedAt: response.timestamp,
95
+ expiresAt: Date.now() + this.cacheTtlMs
96
+ };
97
+ return response;
98
+ }
99
+ async executeChecks() {
72
100
  if (this.checks.length === 0) {
73
101
  return {
74
102
  status: Status.HEALTHY,
@@ -154,10 +182,16 @@ function extractToken(options) {
154
182
  }
155
183
 
156
184
  // src/handler.ts
157
- var RESERVED_METADATA_KEYS = ["status", "timestamp", "checks"];
185
+ var RESERVED_METADATA_KEYS = ["status", "timestamp", "checks", "cachedAt"];
158
186
  function createHealthcheckHandler(options) {
159
- const { registry, token: rawToken, queryParamName = "token", metadata = {} } = options;
160
- const token = rawToken || null;
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;
161
195
  for (const key of Object.keys(metadata)) {
162
196
  if (RESERVED_METADATA_KEYS.includes(key)) {
163
197
  throw new Error(`Metadata key '${key}' is reserved. Use a different key name.`);
@@ -165,6 +199,13 @@ function createHealthcheckHandler(options) {
165
199
  }
166
200
  return async (req) => {
167
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
+ };
208
+ }
168
209
  const body = {
169
210
  status: "ok",
170
211
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -195,12 +236,247 @@ function createHealthcheckHandler(options) {
195
236
  };
196
237
  };
197
238
  }
239
+
240
+ // src/html.ts
241
+ function escapeHtml(str) {
242
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
243
+ }
244
+ function statusColor(status) {
245
+ switch (status) {
246
+ case "healthy":
247
+ case "ok":
248
+ return "#22C55E";
249
+ case "degraded":
250
+ return "#F59E0B";
251
+ case "outage":
252
+ return "#ED1C24";
253
+ default:
254
+ return "#ED1C24";
255
+ }
256
+ }
257
+ var DEEP_KNOWN_KEYS = /* @__PURE__ */ new Set(["status", "timestamp", "checks", "cachedAt"]);
258
+ function isDeepResponse(body) {
259
+ return "checks" in body && "status" in body && body.status !== "ok";
260
+ }
261
+ function isShallowResponse(body) {
262
+ return "status" in body && body.status === "ok";
263
+ }
264
+ function isErrorResponse(body) {
265
+ return "error" in body && !("status" in body);
266
+ }
267
+ function renderStatusBadge(label, color) {
268
+ return `<span style="
269
+ display: inline-flex;
270
+ align-items: center;
271
+ gap: 8px;
272
+ padding: 4px 12px;
273
+ border-radius: 9999px;
274
+ background: ${color}1A;
275
+ font-family: 'Inter', system-ui, sans-serif;
276
+ font-size: 12px;
277
+ font-weight: 600;
278
+ letter-spacing: 0.05em;
279
+ text-transform: uppercase;
280
+ color: ${color};
281
+ "><span style="
282
+ width: 8px;
283
+ height: 8px;
284
+ border-radius: 50%;
285
+ background: ${color};
286
+ box-shadow: 0 0 6px ${color}80;
287
+ "></span>${escapeHtml(label)}</span>`;
288
+ }
289
+ function renderMetadataItems(items) {
290
+ if (items.length === 0) return "";
291
+ return `<div style="
292
+ display: flex;
293
+ flex-wrap: wrap;
294
+ gap: 24px;
295
+ margin-top: 16px;
296
+ ">${items.map(
297
+ ([label, value]) => `<div>
298
+ <div style="
299
+ font-family: 'Inter', system-ui, sans-serif;
300
+ font-size: 10px;
301
+ font-weight: 600;
302
+ letter-spacing: 0.05em;
303
+ text-transform: uppercase;
304
+ color: #A1A1AA;
305
+ margin-bottom: 4px;
306
+ ">${escapeHtml(label)}</div>
307
+ <div style="
308
+ font-family: 'JetBrains Mono', monospace;
309
+ font-size: 13px;
310
+ color: #E4E4E7;
311
+ ">${escapeHtml(value)}</div>
312
+ </div>`
313
+ ).join("")}</div>`;
314
+ }
315
+ function renderCheckRow(name, check) {
316
+ const color = statusColor(check.status);
317
+ return `<div style="
318
+ display: flex;
319
+ align-items: center;
320
+ gap: 12px;
321
+ padding: 12px 0;
322
+ border-top: 1px solid #27272A;
323
+ ">
324
+ <span style="
325
+ width: 8px;
326
+ height: 8px;
327
+ border-radius: 50%;
328
+ background: ${color};
329
+ box-shadow: 0 0 6px ${color}80;
330
+ flex-shrink: 0;
331
+ "></span>
332
+ <span style="
333
+ font-family: 'Inter', system-ui, sans-serif;
334
+ font-size: 12px;
335
+ text-transform: uppercase;
336
+ letter-spacing: 0.05em;
337
+ color: ${color};
338
+ font-weight: 600;
339
+ min-width: 72px;
340
+ ">${escapeHtml(check.status)}</span>
341
+ <span style="
342
+ font-family: 'Inter', system-ui, sans-serif;
343
+ font-size: 14px;
344
+ font-weight: 500;
345
+ color: #E4E4E7;
346
+ flex: 1;
347
+ ">${escapeHtml(name)}</span>
348
+ <span style="
349
+ font-family: 'JetBrains Mono', monospace;
350
+ font-size: 12px;
351
+ color: #A1A1AA;
352
+ min-width: 60px;
353
+ text-align: right;
354
+ ">${escapeHtml(String(check.latencyMs))}ms</span>
355
+ <span style="
356
+ font-family: 'JetBrains Mono', monospace;
357
+ font-size: 12px;
358
+ color: #71717A;
359
+ max-width: 200px;
360
+ overflow: hidden;
361
+ text-overflow: ellipsis;
362
+ white-space: nowrap;
363
+ ">${escapeHtml(check.message)}</span>
364
+ </div>`;
365
+ }
366
+ function renderPage(borderColor, content) {
367
+ return `<!DOCTYPE html>
368
+ <html lang="en">
369
+ <head>
370
+ <meta charset="utf-8">
371
+ <meta name="viewport" content="width=device-width, initial-scale=1">
372
+ <title>Healthcheck</title>
373
+ <style></style>
374
+ </head>
375
+ <body style="
376
+ margin: 0;
377
+ padding: 40px 20px;
378
+ background: #08080A;
379
+ min-height: 100vh;
380
+ display: flex;
381
+ justify-content: center;
382
+ align-items: flex-start;
383
+ box-sizing: border-box;
384
+ ">
385
+ <div style="
386
+ width: 100%;
387
+ max-width: 560px;
388
+ background: #18181B;
389
+ border: 1px solid #27272A;
390
+ border-left: 3px solid ${borderColor};
391
+ border-radius: 8px;
392
+ padding: 24px;
393
+ ">
394
+ ${content}
395
+ </div>
396
+ </body>
397
+ </html>`;
398
+ }
399
+ function renderHealthcheckHtml(body) {
400
+ if (isErrorResponse(body)) {
401
+ const color = "#ED1C24";
402
+ return renderPage(
403
+ color,
404
+ `<div style="display: flex; align-items: center; justify-content: space-between;">
405
+ <span style="
406
+ font-family: 'Inter', system-ui, sans-serif;
407
+ font-size: 18px;
408
+ font-weight: 700;
409
+ color: #E4E4E7;
410
+ ">Healthcheck</span>
411
+ ${renderStatusBadge("ERROR", color)}
412
+ </div>
413
+ <div style="
414
+ margin-top: 16px;
415
+ font-family: 'JetBrains Mono', monospace;
416
+ font-size: 13px;
417
+ color: #EF4444;
418
+ ">${escapeHtml(body.error)}</div>`
419
+ );
420
+ }
421
+ if (isShallowResponse(body)) {
422
+ const color = statusColor("ok");
423
+ const { status: _, timestamp, ...rest } = body;
424
+ const metadataItems = Object.entries(rest).map(
425
+ ([k, v]) => [k, String(v)]
426
+ );
427
+ const allItems = [
428
+ ["timestamp", timestamp],
429
+ ...metadataItems
430
+ ];
431
+ return renderPage(
432
+ color,
433
+ `<div style="display: flex; align-items: center; justify-content: space-between;">
434
+ <span style="
435
+ font-family: 'Inter', system-ui, sans-serif;
436
+ font-size: 18px;
437
+ font-weight: 700;
438
+ color: #E4E4E7;
439
+ ">Healthcheck</span>
440
+ ${renderStatusBadge("OK", color)}
441
+ </div>
442
+ ${renderMetadataItems(allItems)}`
443
+ );
444
+ }
445
+ if (isDeepResponse(body)) {
446
+ const color = statusColor(body.status);
447
+ const { timestamp, checks } = body;
448
+ const metadataItems = Object.entries(body).filter(([k]) => !DEEP_KNOWN_KEYS.has(k)).map(([k, v]) => [k, String(v)]);
449
+ const allItems = [
450
+ ["timestamp", timestamp],
451
+ ...metadataItems
452
+ ];
453
+ const checkRows = Object.entries(checks).sort(([a], [b]) => a.localeCompare(b)).map(([name, check]) => renderCheckRow(name, check)).join("");
454
+ return renderPage(
455
+ color,
456
+ `<div style="display: flex; align-items: center; justify-content: space-between;">
457
+ <span style="
458
+ font-family: 'Inter', system-ui, sans-serif;
459
+ font-size: 18px;
460
+ font-weight: 700;
461
+ color: #E4E4E7;
462
+ ">Healthcheck</span>
463
+ ${renderStatusBadge(body.status, color)}
464
+ </div>
465
+ ${renderMetadataItems(allItems)}
466
+ <div style="margin-top: 20px;">
467
+ ${checkRows}
468
+ </div>`
469
+ );
470
+ }
471
+ return renderPage("#ED1C24", `<pre style="color: #E4E4E7; font-family: 'JetBrains Mono', monospace;">${escapeHtml(JSON.stringify(body, null, 2))}</pre>`);
472
+ }
198
473
  export {
199
474
  HealthcheckRegistry,
200
475
  Status,
201
476
  createHealthcheckHandler,
202
477
  extractToken,
203
478
  httpStatusCode,
479
+ renderHealthcheckHtml,
204
480
  statusFromString,
205
481
  statusToLabel,
206
482
  syncCheck,
@@ -61,7 +61,7 @@ function statusToLabel(status) {
61
61
  return labels[status];
62
62
  }
63
63
  function toJson(response) {
64
- return {
64
+ const json = {
65
65
  status: statusToLabel(response.status),
66
66
  timestamp: response.timestamp,
67
67
  checks: Object.fromEntries(
@@ -71,16 +71,26 @@ function toJson(response) {
71
71
  ])
72
72
  )
73
73
  };
74
+ if (response.cachedAt !== void 0) {
75
+ json.cachedAt = response.cachedAt;
76
+ }
77
+ return json;
74
78
  }
75
79
  function httpStatusCode(status) {
76
80
  return status === Status.HEALTHY ? 200 : 503;
77
81
  }
78
82
 
79
83
  // src/handler.ts
80
- var RESERVED_METADATA_KEYS = ["status", "timestamp", "checks"];
84
+ var RESERVED_METADATA_KEYS = ["status", "timestamp", "checks", "cachedAt"];
81
85
  function createHealthcheckHandler(options) {
82
- const { registry, token: rawToken, queryParamName = "token", metadata = {} } = options;
83
- const token = rawToken || null;
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;
84
94
  for (const key of Object.keys(metadata)) {
85
95
  if (RESERVED_METADATA_KEYS.includes(key)) {
86
96
  throw new Error(`Metadata key '${key}' is reserved. Use a different key name.`);
@@ -88,6 +98,13 @@ function createHealthcheckHandler(options) {
88
98
  }
89
99
  return async (req) => {
90
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
+ };
107
+ }
91
108
  const body = {
92
109
  status: "ok",
93
110
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -119,6 +136,240 @@ function createHealthcheckHandler(options) {
119
136
  };
120
137
  }
121
138
 
139
+ // src/html.ts
140
+ function escapeHtml(str) {
141
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
142
+ }
143
+ function statusColor(status) {
144
+ switch (status) {
145
+ case "healthy":
146
+ case "ok":
147
+ return "#22C55E";
148
+ case "degraded":
149
+ return "#F59E0B";
150
+ case "outage":
151
+ return "#ED1C24";
152
+ default:
153
+ return "#ED1C24";
154
+ }
155
+ }
156
+ var DEEP_KNOWN_KEYS = /* @__PURE__ */ new Set(["status", "timestamp", "checks", "cachedAt"]);
157
+ function isDeepResponse(body) {
158
+ return "checks" in body && "status" in body && body.status !== "ok";
159
+ }
160
+ function isShallowResponse(body) {
161
+ return "status" in body && body.status === "ok";
162
+ }
163
+ function isErrorResponse(body) {
164
+ return "error" in body && !("status" in body);
165
+ }
166
+ function renderStatusBadge(label, color) {
167
+ return `<span style="
168
+ display: inline-flex;
169
+ align-items: center;
170
+ gap: 8px;
171
+ padding: 4px 12px;
172
+ border-radius: 9999px;
173
+ background: ${color}1A;
174
+ font-family: 'Inter', system-ui, sans-serif;
175
+ font-size: 12px;
176
+ font-weight: 600;
177
+ letter-spacing: 0.05em;
178
+ text-transform: uppercase;
179
+ color: ${color};
180
+ "><span style="
181
+ width: 8px;
182
+ height: 8px;
183
+ border-radius: 50%;
184
+ background: ${color};
185
+ box-shadow: 0 0 6px ${color}80;
186
+ "></span>${escapeHtml(label)}</span>`;
187
+ }
188
+ function renderMetadataItems(items) {
189
+ if (items.length === 0) return "";
190
+ return `<div style="
191
+ display: flex;
192
+ flex-wrap: wrap;
193
+ gap: 24px;
194
+ margin-top: 16px;
195
+ ">${items.map(
196
+ ([label, value]) => `<div>
197
+ <div style="
198
+ font-family: 'Inter', system-ui, sans-serif;
199
+ font-size: 10px;
200
+ font-weight: 600;
201
+ letter-spacing: 0.05em;
202
+ text-transform: uppercase;
203
+ color: #A1A1AA;
204
+ margin-bottom: 4px;
205
+ ">${escapeHtml(label)}</div>
206
+ <div style="
207
+ font-family: 'JetBrains Mono', monospace;
208
+ font-size: 13px;
209
+ color: #E4E4E7;
210
+ ">${escapeHtml(value)}</div>
211
+ </div>`
212
+ ).join("")}</div>`;
213
+ }
214
+ function renderCheckRow(name, check) {
215
+ const color = statusColor(check.status);
216
+ return `<div style="
217
+ display: flex;
218
+ align-items: center;
219
+ gap: 12px;
220
+ padding: 12px 0;
221
+ border-top: 1px solid #27272A;
222
+ ">
223
+ <span style="
224
+ width: 8px;
225
+ height: 8px;
226
+ border-radius: 50%;
227
+ background: ${color};
228
+ box-shadow: 0 0 6px ${color}80;
229
+ flex-shrink: 0;
230
+ "></span>
231
+ <span style="
232
+ font-family: 'Inter', system-ui, sans-serif;
233
+ font-size: 12px;
234
+ text-transform: uppercase;
235
+ letter-spacing: 0.05em;
236
+ color: ${color};
237
+ font-weight: 600;
238
+ min-width: 72px;
239
+ ">${escapeHtml(check.status)}</span>
240
+ <span style="
241
+ font-family: 'Inter', system-ui, sans-serif;
242
+ font-size: 14px;
243
+ font-weight: 500;
244
+ color: #E4E4E7;
245
+ flex: 1;
246
+ ">${escapeHtml(name)}</span>
247
+ <span style="
248
+ font-family: 'JetBrains Mono', monospace;
249
+ font-size: 12px;
250
+ color: #A1A1AA;
251
+ min-width: 60px;
252
+ text-align: right;
253
+ ">${escapeHtml(String(check.latencyMs))}ms</span>
254
+ <span style="
255
+ font-family: 'JetBrains Mono', monospace;
256
+ font-size: 12px;
257
+ color: #71717A;
258
+ max-width: 200px;
259
+ overflow: hidden;
260
+ text-overflow: ellipsis;
261
+ white-space: nowrap;
262
+ ">${escapeHtml(check.message)}</span>
263
+ </div>`;
264
+ }
265
+ function renderPage(borderColor, content) {
266
+ return `<!DOCTYPE html>
267
+ <html lang="en">
268
+ <head>
269
+ <meta charset="utf-8">
270
+ <meta name="viewport" content="width=device-width, initial-scale=1">
271
+ <title>Healthcheck</title>
272
+ <style></style>
273
+ </head>
274
+ <body style="
275
+ margin: 0;
276
+ padding: 40px 20px;
277
+ background: #08080A;
278
+ min-height: 100vh;
279
+ display: flex;
280
+ justify-content: center;
281
+ align-items: flex-start;
282
+ box-sizing: border-box;
283
+ ">
284
+ <div style="
285
+ width: 100%;
286
+ max-width: 560px;
287
+ background: #18181B;
288
+ border: 1px solid #27272A;
289
+ border-left: 3px solid ${borderColor};
290
+ border-radius: 8px;
291
+ padding: 24px;
292
+ ">
293
+ ${content}
294
+ </div>
295
+ </body>
296
+ </html>`;
297
+ }
298
+ function renderHealthcheckHtml(body) {
299
+ if (isErrorResponse(body)) {
300
+ const color = "#ED1C24";
301
+ return renderPage(
302
+ color,
303
+ `<div style="display: flex; align-items: center; justify-content: space-between;">
304
+ <span style="
305
+ font-family: 'Inter', system-ui, sans-serif;
306
+ font-size: 18px;
307
+ font-weight: 700;
308
+ color: #E4E4E7;
309
+ ">Healthcheck</span>
310
+ ${renderStatusBadge("ERROR", color)}
311
+ </div>
312
+ <div style="
313
+ margin-top: 16px;
314
+ font-family: 'JetBrains Mono', monospace;
315
+ font-size: 13px;
316
+ color: #EF4444;
317
+ ">${escapeHtml(body.error)}</div>`
318
+ );
319
+ }
320
+ if (isShallowResponse(body)) {
321
+ const color = statusColor("ok");
322
+ const { status: _, timestamp, ...rest } = body;
323
+ const metadataItems = Object.entries(rest).map(
324
+ ([k, v]) => [k, String(v)]
325
+ );
326
+ const allItems = [
327
+ ["timestamp", timestamp],
328
+ ...metadataItems
329
+ ];
330
+ return renderPage(
331
+ color,
332
+ `<div style="display: flex; align-items: center; justify-content: space-between;">
333
+ <span style="
334
+ font-family: 'Inter', system-ui, sans-serif;
335
+ font-size: 18px;
336
+ font-weight: 700;
337
+ color: #E4E4E7;
338
+ ">Healthcheck</span>
339
+ ${renderStatusBadge("OK", color)}
340
+ </div>
341
+ ${renderMetadataItems(allItems)}`
342
+ );
343
+ }
344
+ if (isDeepResponse(body)) {
345
+ const color = statusColor(body.status);
346
+ const { timestamp, checks } = body;
347
+ const metadataItems = Object.entries(body).filter(([k]) => !DEEP_KNOWN_KEYS.has(k)).map(([k, v]) => [k, String(v)]);
348
+ const allItems = [
349
+ ["timestamp", timestamp],
350
+ ...metadataItems
351
+ ];
352
+ const checkRows = Object.entries(checks).sort(([a], [b]) => a.localeCompare(b)).map(([name, check]) => renderCheckRow(name, check)).join("");
353
+ return renderPage(
354
+ color,
355
+ `<div style="display: flex; align-items: center; justify-content: space-between;">
356
+ <span style="
357
+ font-family: 'Inter', system-ui, sans-serif;
358
+ font-size: 18px;
359
+ font-weight: 700;
360
+ color: #E4E4E7;
361
+ ">Healthcheck</span>
362
+ ${renderStatusBadge(body.status, color)}
363
+ </div>
364
+ ${renderMetadataItems(allItems)}
365
+ <div style="margin-top: 20px;">
366
+ ${checkRows}
367
+ </div>`
368
+ );
369
+ }
370
+ return renderPage("#ED1C24", `<pre style="color: #E4E4E7; font-family: 'JetBrains Mono', monospace;">${escapeHtml(JSON.stringify(body, null, 2))}</pre>`);
371
+ }
372
+
122
373
  // src/integrations/express.ts
123
374
  function createHealthcheckMiddleware(options) {
124
375
  const handle = createHealthcheckHandler(options);
@@ -128,7 +379,12 @@ function createHealthcheckMiddleware(options) {
128
379
  queryParams: req.query,
129
380
  authorizationHeader: req.headers.authorization ?? null
130
381
  });
131
- res.status(result.status).json(result.body);
382
+ const preferred = req.accepts(["json", "html"]);
383
+ if (preferred === "html") {
384
+ res.status(result.status).type("html").send(renderHealthcheckHtml(result.body));
385
+ } else {
386
+ res.status(result.status).json(result.body);
387
+ }
132
388
  } catch {
133
389
  res.status(500).json({ error: "Internal Server Error" });
134
390
  }
@@ -1,6 +1,6 @@
1
1
  import { RequestHandler } from 'express';
2
- import { H as HealthcheckHandlerOptions } from '../handler-Bvf66wzh.cjs';
3
- import '../core-BJ2Z0rRi.cjs';
2
+ import { H as HealthcheckHandlerOptions } from '../handler-COH7lot9.cjs';
3
+ import '../core-Bee03bJm.cjs';
4
4
 
5
5
  type HealthcheckMiddlewareOptions = HealthcheckHandlerOptions;
6
6
  declare function createHealthcheckMiddleware(options: HealthcheckMiddlewareOptions): RequestHandler;
@@ -1,6 +1,6 @@
1
1
  import { RequestHandler } from 'express';
2
- import { H as HealthcheckHandlerOptions } from '../handler-R2U3ygLo.js';
3
- import '../core-BJ2Z0rRi.js';
2
+ import { H as HealthcheckHandlerOptions } from '../handler-CVYUad84.js';
3
+ import '../core-Bee03bJm.js';
4
4
 
5
5
  type HealthcheckMiddlewareOptions = HealthcheckHandlerOptions;
6
6
  declare function createHealthcheckMiddleware(options: HealthcheckMiddlewareOptions): RequestHandler;