@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.
@@ -24,28 +24,6 @@ __export(express_exports, {
24
24
  });
25
25
  module.exports = __toCommonJS(express_exports);
26
26
 
27
- // src/auth.ts
28
- var import_node_crypto = require("crypto");
29
- function verifyToken(provided, expected) {
30
- if (!provided || !expected) return false;
31
- const providedHash = (0, import_node_crypto.createHash)("sha256").update(provided).digest();
32
- const expectedHash = (0, import_node_crypto.createHash)("sha256").update(expected).digest();
33
- return (0, import_node_crypto.timingSafeEqual)(providedHash, expectedHash);
34
- }
35
- function extractToken(options) {
36
- const { queryParams, authorizationHeader, queryParamName = "token" } = options;
37
- if (queryParams) {
38
- const value = queryParams[queryParamName];
39
- const str = Array.isArray(value) ? value[0] : value;
40
- if (str) return str;
41
- }
42
- if (authorizationHeader?.startsWith("Bearer ")) {
43
- const token = authorizationHeader.slice("Bearer ".length);
44
- if (token) return token;
45
- }
46
- return null;
47
- }
48
-
49
27
  // src/types.ts
50
28
  var Status = {
51
29
  HEALTHY: 2,
@@ -61,7 +39,7 @@ function statusToLabel(status) {
61
39
  return labels[status];
62
40
  }
63
41
  function toJson(response) {
64
- return {
42
+ const json = {
65
43
  status: statusToLabel(response.status),
66
44
  timestamp: response.timestamp,
67
45
  checks: Object.fromEntries(
@@ -71,64 +49,302 @@ function toJson(response) {
71
49
  ])
72
50
  )
73
51
  };
52
+ if (response.cachedAt !== void 0) {
53
+ json.cachedAt = response.cachedAt;
54
+ }
55
+ return json;
74
56
  }
75
57
  function httpStatusCode(status) {
76
58
  return status === Status.HEALTHY ? 200 : 503;
77
59
  }
78
60
 
79
61
  // src/handler.ts
80
- var RESERVED_METADATA_KEYS = ["status", "timestamp", "checks"];
62
+ var RESERVED_METADATA_KEYS = ["status", "timestamp", "checks", "cachedAt"];
81
63
  function createHealthcheckHandler(options) {
82
- const { registry, token: rawToken, queryParamName = "token", metadata = {} } = options;
83
- const token = rawToken || null;
64
+ const { registry, resolveDepth, metadata = {} } = options;
84
65
  for (const key of Object.keys(metadata)) {
85
66
  if (RESERVED_METADATA_KEYS.includes(key)) {
86
67
  throw new Error(`Metadata key '${key}' is reserved. Use a different key name.`);
87
68
  }
88
69
  }
89
70
  return async (req) => {
90
- if (token === null) {
91
- const body = {
92
- status: "ok",
93
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
94
- ...metadata
95
- };
96
- return { status: 200, body };
71
+ let depth = "shallow";
72
+ if (resolveDepth) {
73
+ try {
74
+ const result = await resolveDepth(req);
75
+ if (result === "deep" || result === "shallow") {
76
+ depth = result;
77
+ }
78
+ } catch {
79
+ }
97
80
  }
98
- const provided = extractToken({
99
- queryParams: req.queryParams,
100
- authorizationHeader: req.authorizationHeader,
101
- queryParamName
102
- });
103
- if (provided === null) {
104
- const body = {
105
- status: "ok",
106
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
107
- ...metadata
81
+ if (depth === "deep") {
82
+ const response = await registry.run();
83
+ return {
84
+ status: httpStatusCode(response.status),
85
+ body: { ...metadata, ...toJson(response) }
108
86
  };
109
- return { status: 200, body };
110
- }
111
- if (!verifyToken(provided, token)) {
112
- return { status: 403, body: { error: "Forbidden" } };
113
87
  }
114
- const response = await registry.run();
115
- return {
116
- status: httpStatusCode(response.status),
117
- body: { ...metadata, ...toJson(response) }
88
+ const body = {
89
+ status: "ok",
90
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
91
+ ...metadata
118
92
  };
93
+ return { status: 200, body };
119
94
  };
120
95
  }
121
96
 
97
+ // src/html.ts
98
+ function escapeHtml(str) {
99
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
100
+ }
101
+ function statusColor(status) {
102
+ switch (status) {
103
+ case "healthy":
104
+ case "ok":
105
+ return "#22C55E";
106
+ case "degraded":
107
+ return "#F59E0B";
108
+ case "outage":
109
+ return "#ED1C24";
110
+ default:
111
+ return "#ED1C24";
112
+ }
113
+ }
114
+ var DEEP_KNOWN_KEYS = /* @__PURE__ */ new Set(["status", "timestamp", "checks", "cachedAt"]);
115
+ function isDeepResponse(body) {
116
+ return "checks" in body && "status" in body && body.status !== "ok";
117
+ }
118
+ function isShallowResponse(body) {
119
+ return "status" in body && body.status === "ok";
120
+ }
121
+ function isErrorResponse(body) {
122
+ return "error" in body && !("status" in body);
123
+ }
124
+ function renderStatusBadge(label, color) {
125
+ return `<span style="
126
+ display: inline-flex;
127
+ align-items: center;
128
+ gap: 8px;
129
+ padding: 4px 12px;
130
+ border-radius: 9999px;
131
+ background: ${color}1A;
132
+ font-family: 'Inter', system-ui, sans-serif;
133
+ font-size: 12px;
134
+ font-weight: 600;
135
+ letter-spacing: 0.05em;
136
+ text-transform: uppercase;
137
+ color: ${color};
138
+ "><span style="
139
+ width: 8px;
140
+ height: 8px;
141
+ border-radius: 50%;
142
+ background: ${color};
143
+ box-shadow: 0 0 6px ${color}80;
144
+ "></span>${escapeHtml(label)}</span>`;
145
+ }
146
+ function renderMetadataItems(items) {
147
+ if (items.length === 0) return "";
148
+ return `<div style="
149
+ display: flex;
150
+ flex-wrap: wrap;
151
+ gap: 24px;
152
+ margin-top: 16px;
153
+ ">${items.map(
154
+ ([label, value]) => `<div>
155
+ <div style="
156
+ font-family: 'Inter', system-ui, sans-serif;
157
+ font-size: 10px;
158
+ font-weight: 600;
159
+ letter-spacing: 0.05em;
160
+ text-transform: uppercase;
161
+ color: #A1A1AA;
162
+ margin-bottom: 4px;
163
+ ">${escapeHtml(label)}</div>
164
+ <div style="
165
+ font-family: 'JetBrains Mono', monospace;
166
+ font-size: 13px;
167
+ color: #E4E4E7;
168
+ ">${escapeHtml(value)}</div>
169
+ </div>`
170
+ ).join("")}</div>`;
171
+ }
172
+ function renderCheckRow(name, check) {
173
+ const color = statusColor(check.status);
174
+ return `<div style="
175
+ display: flex;
176
+ align-items: center;
177
+ gap: 12px;
178
+ padding: 12px 0;
179
+ border-top: 1px solid #27272A;
180
+ ">
181
+ <span style="
182
+ width: 8px;
183
+ height: 8px;
184
+ border-radius: 50%;
185
+ background: ${color};
186
+ box-shadow: 0 0 6px ${color}80;
187
+ flex-shrink: 0;
188
+ "></span>
189
+ <span style="
190
+ font-family: 'Inter', system-ui, sans-serif;
191
+ font-size: 12px;
192
+ text-transform: uppercase;
193
+ letter-spacing: 0.05em;
194
+ color: ${color};
195
+ font-weight: 600;
196
+ min-width: 72px;
197
+ ">${escapeHtml(check.status)}</span>
198
+ <span style="
199
+ font-family: 'Inter', system-ui, sans-serif;
200
+ font-size: 14px;
201
+ font-weight: 500;
202
+ color: #E4E4E7;
203
+ flex: 1;
204
+ ">${escapeHtml(name)}</span>
205
+ <span style="
206
+ font-family: 'JetBrains Mono', monospace;
207
+ font-size: 12px;
208
+ color: #A1A1AA;
209
+ min-width: 60px;
210
+ text-align: right;
211
+ ">${escapeHtml(String(check.latencyMs))}ms</span>
212
+ <span style="
213
+ font-family: 'JetBrains Mono', monospace;
214
+ font-size: 12px;
215
+ color: #71717A;
216
+ max-width: 200px;
217
+ overflow: hidden;
218
+ text-overflow: ellipsis;
219
+ white-space: nowrap;
220
+ ">${escapeHtml(check.message)}</span>
221
+ </div>`;
222
+ }
223
+ function renderPage(borderColor, content) {
224
+ return `<!DOCTYPE html>
225
+ <html lang="en">
226
+ <head>
227
+ <meta charset="utf-8">
228
+ <meta name="viewport" content="width=device-width, initial-scale=1">
229
+ <title>Healthcheck</title>
230
+ <style></style>
231
+ </head>
232
+ <body style="
233
+ margin: 0;
234
+ padding: 40px 20px;
235
+ background: #08080A;
236
+ min-height: 100vh;
237
+ display: flex;
238
+ justify-content: center;
239
+ align-items: flex-start;
240
+ box-sizing: border-box;
241
+ ">
242
+ <div style="
243
+ width: 100%;
244
+ max-width: 560px;
245
+ background: #18181B;
246
+ border: 1px solid #27272A;
247
+ border-left: 3px solid ${borderColor};
248
+ border-radius: 8px;
249
+ padding: 24px;
250
+ ">
251
+ ${content}
252
+ </div>
253
+ </body>
254
+ </html>`;
255
+ }
256
+ function renderHealthcheckHtml(body) {
257
+ if (isErrorResponse(body)) {
258
+ const color = "#ED1C24";
259
+ return renderPage(
260
+ color,
261
+ `<div style="display: flex; align-items: center; justify-content: space-between;">
262
+ <span style="
263
+ font-family: 'Inter', system-ui, sans-serif;
264
+ font-size: 18px;
265
+ font-weight: 700;
266
+ color: #E4E4E7;
267
+ ">Healthcheck</span>
268
+ ${renderStatusBadge("ERROR", color)}
269
+ </div>
270
+ <div style="
271
+ margin-top: 16px;
272
+ font-family: 'JetBrains Mono', monospace;
273
+ font-size: 13px;
274
+ color: #EF4444;
275
+ ">${escapeHtml(body.error)}</div>`
276
+ );
277
+ }
278
+ if (isShallowResponse(body)) {
279
+ const color = statusColor("ok");
280
+ const { status: _, timestamp, ...rest } = body;
281
+ const metadataItems = Object.entries(rest).map(
282
+ ([k, v]) => [k, String(v)]
283
+ );
284
+ const allItems = [
285
+ ["timestamp", timestamp],
286
+ ...metadataItems
287
+ ];
288
+ return renderPage(
289
+ color,
290
+ `<div style="display: flex; align-items: center; justify-content: space-between;">
291
+ <span style="
292
+ font-family: 'Inter', system-ui, sans-serif;
293
+ font-size: 18px;
294
+ font-weight: 700;
295
+ color: #E4E4E7;
296
+ ">Healthcheck</span>
297
+ ${renderStatusBadge("OK", color)}
298
+ </div>
299
+ ${renderMetadataItems(allItems)}`
300
+ );
301
+ }
302
+ if (isDeepResponse(body)) {
303
+ const color = statusColor(body.status);
304
+ const { timestamp, checks } = body;
305
+ const metadataItems = Object.entries(body).filter(([k]) => !DEEP_KNOWN_KEYS.has(k)).map(([k, v]) => [k, String(v)]);
306
+ const allItems = [
307
+ ["timestamp", timestamp],
308
+ ...metadataItems
309
+ ];
310
+ const checkRows = Object.entries(checks).sort(([a], [b]) => a.localeCompare(b)).map(([name, check]) => renderCheckRow(name, check)).join("");
311
+ return renderPage(
312
+ color,
313
+ `<div style="display: flex; align-items: center; justify-content: space-between;">
314
+ <span style="
315
+ font-family: 'Inter', system-ui, sans-serif;
316
+ font-size: 18px;
317
+ font-weight: 700;
318
+ color: #E4E4E7;
319
+ ">Healthcheck</span>
320
+ ${renderStatusBadge(body.status, color)}
321
+ </div>
322
+ ${renderMetadataItems(allItems)}
323
+ <div style="margin-top: 20px;">
324
+ ${checkRows}
325
+ </div>`
326
+ );
327
+ }
328
+ return renderPage("#ED1C24", `<pre style="color: #E4E4E7; font-family: 'JetBrains Mono', monospace;">${escapeHtml(JSON.stringify(body, null, 2))}</pre>`);
329
+ }
330
+
122
331
  // src/integrations/express.ts
123
332
  function createHealthcheckMiddleware(options) {
124
333
  const handle = createHealthcheckHandler(options);
125
334
  return async (req, res) => {
126
335
  try {
127
336
  const result = await handle({
337
+ ip: req.ip,
338
+ headers: req.headers,
128
339
  queryParams: req.query,
129
340
  authorizationHeader: req.headers.authorization ?? null
130
341
  });
131
- res.status(result.status).json(result.body);
342
+ const preferred = req.accepts(["json", "html"]);
343
+ if (preferred === "html") {
344
+ res.status(result.status).type("html").send(renderHealthcheckHtml(result.body));
345
+ } else {
346
+ res.status(result.status).json(result.body);
347
+ }
132
348
  } catch {
133
349
  res.status(500).json({ error: "Internal Server Error" });
134
350
  }
@@ -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-TZOgZvY7.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-BvjN4Ot9.js';
3
+ import '../core-Bee03bJm.js';
4
4
 
5
5
  type HealthcheckMiddlewareOptions = HealthcheckHandlerOptions;
6
6
  declare function createHealthcheckMiddleware(options: HealthcheckMiddlewareOptions): RequestHandler;