@firebreak/vitals 1.0.2 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +115 -46
- package/dist/checks/databricks.cjs +57 -0
- package/dist/checks/databricks.d.cts +23 -0
- package/dist/checks/databricks.d.ts +23 -0
- package/dist/checks/databricks.mjs +30 -0
- package/dist/checks/http.cjs +63 -0
- package/dist/checks/http.d.cts +17 -0
- package/dist/checks/http.d.ts +17 -0
- package/dist/checks/http.mjs +36 -0
- package/dist/checks/postgres.cjs +4 -11
- package/dist/checks/postgres.d.cts +1 -1
- package/dist/checks/postgres.d.ts +1 -1
- package/dist/checks/postgres.mjs +4 -13
- package/dist/checks/redis.cjs +4 -11
- package/dist/checks/redis.d.cts +1 -1
- package/dist/checks/redis.d.ts +1 -1
- package/dist/checks/redis.mjs +4 -13
- package/dist/{core-9-MXAO0I.d.cts → core-BJ2Z0rRi.d.cts} +13 -3
- package/dist/{core-9-MXAO0I.d.ts → core-BJ2Z0rRi.d.ts} +13 -3
- package/dist/handler-Bccbso4b.d.cts +25 -0
- package/dist/handler-D0nYVQvu.d.ts +25 -0
- package/dist/index.cjs +24 -0
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.mjs +23 -0
- package/dist/integrations/express.cjs +28 -14
- package/dist/integrations/express.d.cts +3 -6
- package/dist/integrations/express.d.ts +3 -6
- package/dist/integrations/express.mjs +28 -14
- package/dist/integrations/next.cjs +121 -0
- package/dist/integrations/next.d.cts +24 -0
- package/dist/integrations/next.d.ts +24 -0
- package/dist/integrations/next.mjs +94 -0
- package/package.json +32 -2
package/dist/checks/redis.cjs
CHANGED
|
@@ -34,7 +34,6 @@ __export(redis_exports, {
|
|
|
34
34
|
ioredisClientCheck: () => ioredisClientCheck
|
|
35
35
|
});
|
|
36
36
|
module.exports = __toCommonJS(redis_exports);
|
|
37
|
-
var import_node_perf_hooks = require("perf_hooks");
|
|
38
37
|
|
|
39
38
|
// src/types.ts
|
|
40
39
|
var Status = {
|
|
@@ -46,7 +45,6 @@ var Status = {
|
|
|
46
45
|
// src/checks/redis.ts
|
|
47
46
|
function ioredisCheck(url, options) {
|
|
48
47
|
return async () => {
|
|
49
|
-
const start = import_node_perf_hooks.performance.now();
|
|
50
48
|
const { default: RedisClient } = await import("ioredis");
|
|
51
49
|
const client = new RedisClient(url, {
|
|
52
50
|
lazyConnect: true,
|
|
@@ -60,12 +58,10 @@ function ioredisCheck(url, options) {
|
|
|
60
58
|
if (result !== "PONG") {
|
|
61
59
|
throw new Error(`PING returned ${result}`);
|
|
62
60
|
}
|
|
63
|
-
|
|
64
|
-
return { status: Status.HEALTHY, latencyMs, message: "" };
|
|
61
|
+
return { status: Status.HEALTHY, message: "" };
|
|
65
62
|
} catch (error) {
|
|
66
|
-
const latencyMs = Math.round(import_node_perf_hooks.performance.now() - start);
|
|
67
63
|
const message = error instanceof Error ? error.message : String(error);
|
|
68
|
-
return { status: Status.OUTAGE,
|
|
64
|
+
return { status: Status.OUTAGE, message };
|
|
69
65
|
} finally {
|
|
70
66
|
client.disconnect();
|
|
71
67
|
}
|
|
@@ -73,18 +69,15 @@ function ioredisCheck(url, options) {
|
|
|
73
69
|
}
|
|
74
70
|
function ioredisClientCheck(client) {
|
|
75
71
|
return async () => {
|
|
76
|
-
const start = import_node_perf_hooks.performance.now();
|
|
77
72
|
try {
|
|
78
73
|
const result = await client.ping();
|
|
79
74
|
if (result !== "PONG") {
|
|
80
75
|
throw new Error(`PING returned ${result}`);
|
|
81
76
|
}
|
|
82
|
-
|
|
83
|
-
return { status: Status.HEALTHY, latencyMs, message: "" };
|
|
77
|
+
return { status: Status.HEALTHY, message: "" };
|
|
84
78
|
} catch (error) {
|
|
85
|
-
const latencyMs = Math.round(import_node_perf_hooks.performance.now() - start);
|
|
86
79
|
const message = error instanceof Error ? error.message : String(error);
|
|
87
|
-
return { status: Status.OUTAGE,
|
|
80
|
+
return { status: Status.OUTAGE, message };
|
|
88
81
|
}
|
|
89
82
|
};
|
|
90
83
|
}
|
package/dist/checks/redis.d.cts
CHANGED
package/dist/checks/redis.d.ts
CHANGED
package/dist/checks/redis.mjs
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
// src/checks/redis.ts
|
|
2
|
-
import { performance } from "perf_hooks";
|
|
3
|
-
|
|
4
1
|
// src/types.ts
|
|
5
2
|
var Status = {
|
|
6
3
|
HEALTHY: 2,
|
|
@@ -11,7 +8,6 @@ var Status = {
|
|
|
11
8
|
// src/checks/redis.ts
|
|
12
9
|
function ioredisCheck(url, options) {
|
|
13
10
|
return async () => {
|
|
14
|
-
const start = performance.now();
|
|
15
11
|
const { default: RedisClient } = await import("ioredis");
|
|
16
12
|
const client = new RedisClient(url, {
|
|
17
13
|
lazyConnect: true,
|
|
@@ -25,12 +21,10 @@ function ioredisCheck(url, options) {
|
|
|
25
21
|
if (result !== "PONG") {
|
|
26
22
|
throw new Error(`PING returned ${result}`);
|
|
27
23
|
}
|
|
28
|
-
|
|
29
|
-
return { status: Status.HEALTHY, latencyMs, message: "" };
|
|
24
|
+
return { status: Status.HEALTHY, message: "" };
|
|
30
25
|
} catch (error) {
|
|
31
|
-
const latencyMs = Math.round(performance.now() - start);
|
|
32
26
|
const message = error instanceof Error ? error.message : String(error);
|
|
33
|
-
return { status: Status.OUTAGE,
|
|
27
|
+
return { status: Status.OUTAGE, message };
|
|
34
28
|
} finally {
|
|
35
29
|
client.disconnect();
|
|
36
30
|
}
|
|
@@ -38,18 +32,15 @@ function ioredisCheck(url, options) {
|
|
|
38
32
|
}
|
|
39
33
|
function ioredisClientCheck(client) {
|
|
40
34
|
return async () => {
|
|
41
|
-
const start = performance.now();
|
|
42
35
|
try {
|
|
43
36
|
const result = await client.ping();
|
|
44
37
|
if (result !== "PONG") {
|
|
45
38
|
throw new Error(`PING returned ${result}`);
|
|
46
39
|
}
|
|
47
|
-
|
|
48
|
-
return { status: Status.HEALTHY, latencyMs, message: "" };
|
|
40
|
+
return { status: Status.HEALTHY, message: "" };
|
|
49
41
|
} catch (error) {
|
|
50
|
-
const latencyMs = Math.round(performance.now() - start);
|
|
51
42
|
const message = error instanceof Error ? error.message : String(error);
|
|
52
|
-
return { status: Status.OUTAGE,
|
|
43
|
+
return { status: Status.OUTAGE, message };
|
|
53
44
|
}
|
|
54
45
|
};
|
|
55
46
|
}
|
|
@@ -7,6 +7,16 @@ type StatusValue = (typeof Status)[keyof typeof Status];
|
|
|
7
7
|
type StatusLabel = 'healthy' | 'degraded' | 'outage';
|
|
8
8
|
declare function statusToLabel(status: StatusValue): StatusLabel;
|
|
9
9
|
declare function statusFromString(value: string): StatusValue;
|
|
10
|
+
/**
|
|
11
|
+
* What a check function returns. `latencyMs` is optional because the registry
|
|
12
|
+
* always measures wall-clock latency and overwrites this value.
|
|
13
|
+
*/
|
|
14
|
+
interface CheckInput {
|
|
15
|
+
readonly status: StatusValue;
|
|
16
|
+
readonly latencyMs?: number;
|
|
17
|
+
readonly message: string;
|
|
18
|
+
}
|
|
19
|
+
/** The final result stored per-check after the registry measures latency. */
|
|
10
20
|
interface CheckResult {
|
|
11
21
|
readonly status: StatusValue;
|
|
12
22
|
readonly latencyMs: number;
|
|
@@ -29,8 +39,8 @@ interface HealthcheckResponseJson {
|
|
|
29
39
|
declare function toJson(response: HealthcheckResponse): HealthcheckResponseJson;
|
|
30
40
|
declare function httpStatusCode(status: StatusValue): 200 | 503;
|
|
31
41
|
|
|
32
|
-
type AsyncCheckFn = () => Promise<
|
|
33
|
-
type SyncCheckFn = () =>
|
|
42
|
+
type AsyncCheckFn = () => Promise<CheckInput>;
|
|
43
|
+
type SyncCheckFn = () => CheckInput;
|
|
34
44
|
interface RegistryOptions {
|
|
35
45
|
defaultTimeout?: number;
|
|
36
46
|
}
|
|
@@ -52,4 +62,4 @@ declare class HealthcheckRegistry {
|
|
|
52
62
|
}
|
|
53
63
|
declare function syncCheck(fn: SyncCheckFn): AsyncCheckFn;
|
|
54
64
|
|
|
55
|
-
export { type AsyncCheckFn as A, type
|
|
65
|
+
export { type AsyncCheckFn as A, type CheckInput as C, HealthcheckRegistry as H, Status as S, type CheckResult as a, type HealthcheckResponse as b, type HealthcheckResponseJson 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 };
|
|
@@ -7,6 +7,16 @@ type StatusValue = (typeof Status)[keyof typeof Status];
|
|
|
7
7
|
type StatusLabel = 'healthy' | 'degraded' | 'outage';
|
|
8
8
|
declare function statusToLabel(status: StatusValue): StatusLabel;
|
|
9
9
|
declare function statusFromString(value: string): StatusValue;
|
|
10
|
+
/**
|
|
11
|
+
* What a check function returns. `latencyMs` is optional because the registry
|
|
12
|
+
* always measures wall-clock latency and overwrites this value.
|
|
13
|
+
*/
|
|
14
|
+
interface CheckInput {
|
|
15
|
+
readonly status: StatusValue;
|
|
16
|
+
readonly latencyMs?: number;
|
|
17
|
+
readonly message: string;
|
|
18
|
+
}
|
|
19
|
+
/** The final result stored per-check after the registry measures latency. */
|
|
10
20
|
interface CheckResult {
|
|
11
21
|
readonly status: StatusValue;
|
|
12
22
|
readonly latencyMs: number;
|
|
@@ -29,8 +39,8 @@ interface HealthcheckResponseJson {
|
|
|
29
39
|
declare function toJson(response: HealthcheckResponse): HealthcheckResponseJson;
|
|
30
40
|
declare function httpStatusCode(status: StatusValue): 200 | 503;
|
|
31
41
|
|
|
32
|
-
type AsyncCheckFn = () => Promise<
|
|
33
|
-
type SyncCheckFn = () =>
|
|
42
|
+
type AsyncCheckFn = () => Promise<CheckInput>;
|
|
43
|
+
type SyncCheckFn = () => CheckInput;
|
|
34
44
|
interface RegistryOptions {
|
|
35
45
|
defaultTimeout?: number;
|
|
36
46
|
}
|
|
@@ -52,4 +62,4 @@ declare class HealthcheckRegistry {
|
|
|
52
62
|
}
|
|
53
63
|
declare function syncCheck(fn: SyncCheckFn): AsyncCheckFn;
|
|
54
64
|
|
|
55
|
-
export { type AsyncCheckFn as A, type
|
|
65
|
+
export { type AsyncCheckFn as A, type CheckInput as C, HealthcheckRegistry as H, Status as S, type CheckResult as a, type HealthcheckResponse as b, type HealthcheckResponseJson 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,25 @@
|
|
|
1
|
+
import { H as HealthcheckRegistry, c as HealthcheckResponseJson } from './core-BJ2Z0rRi.cjs';
|
|
2
|
+
|
|
3
|
+
interface HealthcheckHandlerOptions {
|
|
4
|
+
registry: HealthcheckRegistry;
|
|
5
|
+
token?: string | null;
|
|
6
|
+
queryParamName?: string;
|
|
7
|
+
}
|
|
8
|
+
interface HealthcheckRequest {
|
|
9
|
+
queryParams?: Record<string, string | string[] | undefined>;
|
|
10
|
+
authorizationHeader?: string | null;
|
|
11
|
+
}
|
|
12
|
+
interface HealthcheckHandlerResult {
|
|
13
|
+
status: number;
|
|
14
|
+
body: HealthcheckResponseJson | {
|
|
15
|
+
error: string;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Framework-agnostic healthcheck handler. Takes a simple request object
|
|
20
|
+
* and returns a status code + JSON body. Framework integrations (Express,
|
|
21
|
+
* Next.js, etc.) are thin wrappers around this function.
|
|
22
|
+
*/
|
|
23
|
+
declare function createHealthcheckHandler(options: HealthcheckHandlerOptions): (req: HealthcheckRequest) => Promise<HealthcheckHandlerResult>;
|
|
24
|
+
|
|
25
|
+
export { type HealthcheckHandlerOptions as H, type HealthcheckHandlerResult as a, type HealthcheckRequest as b, createHealthcheckHandler as c };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { H as HealthcheckRegistry, c as HealthcheckResponseJson } from './core-BJ2Z0rRi.js';
|
|
2
|
+
|
|
3
|
+
interface HealthcheckHandlerOptions {
|
|
4
|
+
registry: HealthcheckRegistry;
|
|
5
|
+
token?: string | null;
|
|
6
|
+
queryParamName?: string;
|
|
7
|
+
}
|
|
8
|
+
interface HealthcheckRequest {
|
|
9
|
+
queryParams?: Record<string, string | string[] | undefined>;
|
|
10
|
+
authorizationHeader?: string | null;
|
|
11
|
+
}
|
|
12
|
+
interface HealthcheckHandlerResult {
|
|
13
|
+
status: number;
|
|
14
|
+
body: HealthcheckResponseJson | {
|
|
15
|
+
error: string;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Framework-agnostic healthcheck handler. Takes a simple request object
|
|
20
|
+
* and returns a status code + JSON body. Framework integrations (Express,
|
|
21
|
+
* Next.js, etc.) are thin wrappers around this function.
|
|
22
|
+
*/
|
|
23
|
+
declare function createHealthcheckHandler(options: HealthcheckHandlerOptions): (req: HealthcheckRequest) => Promise<HealthcheckHandlerResult>;
|
|
24
|
+
|
|
25
|
+
export { type HealthcheckHandlerOptions as H, type HealthcheckHandlerResult as a, type HealthcheckRequest as b, createHealthcheckHandler as c };
|
package/dist/index.cjs
CHANGED
|
@@ -22,6 +22,7 @@ var src_exports = {};
|
|
|
22
22
|
__export(src_exports, {
|
|
23
23
|
HealthcheckRegistry: () => HealthcheckRegistry,
|
|
24
24
|
Status: () => Status,
|
|
25
|
+
createHealthcheckHandler: () => createHealthcheckHandler,
|
|
25
26
|
extractToken: () => extractToken,
|
|
26
27
|
httpStatusCode: () => httpStatusCode,
|
|
27
28
|
statusFromString: () => statusFromString,
|
|
@@ -186,10 +187,33 @@ function extractToken(options) {
|
|
|
186
187
|
}
|
|
187
188
|
return null;
|
|
188
189
|
}
|
|
190
|
+
|
|
191
|
+
// src/handler.ts
|
|
192
|
+
function createHealthcheckHandler(options) {
|
|
193
|
+
const { registry, token = null, queryParamName = "token" } = options;
|
|
194
|
+
return async (req) => {
|
|
195
|
+
if (token !== null) {
|
|
196
|
+
const provided = extractToken({
|
|
197
|
+
queryParams: req.queryParams,
|
|
198
|
+
authorizationHeader: req.authorizationHeader,
|
|
199
|
+
queryParamName
|
|
200
|
+
});
|
|
201
|
+
if (provided === null || !verifyToken(provided, token)) {
|
|
202
|
+
return { status: 403, body: { error: "Forbidden" } };
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
const response = await registry.run();
|
|
206
|
+
return {
|
|
207
|
+
status: httpStatusCode(response.status),
|
|
208
|
+
body: toJson(response)
|
|
209
|
+
};
|
|
210
|
+
};
|
|
211
|
+
}
|
|
189
212
|
// Annotate the CommonJS export names for ESM import in node:
|
|
190
213
|
0 && (module.exports = {
|
|
191
214
|
HealthcheckRegistry,
|
|
192
215
|
Status,
|
|
216
|
+
createHealthcheckHandler,
|
|
193
217
|
extractToken,
|
|
194
218
|
httpStatusCode,
|
|
195
219
|
statusFromString,
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export { A as AsyncCheckFn, C as CheckResult, H as HealthcheckRegistry,
|
|
1
|
+
export { A as AsyncCheckFn, C as CheckInput, a as CheckResult, H as HealthcheckRegistry, b as HealthcheckResponse, c as HealthcheckResponseJson, 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-BJ2Z0rRi.cjs';
|
|
2
|
+
export { H as HealthcheckHandlerOptions, a as HealthcheckHandlerResult, b as HealthcheckRequest, c as createHealthcheckHandler } from './handler-Bccbso4b.cjs';
|
|
2
3
|
|
|
3
4
|
declare function verifyToken(provided: string, expected: string): boolean;
|
|
4
5
|
declare function extractToken(options: {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export { A as AsyncCheckFn, C as CheckResult, H as HealthcheckRegistry,
|
|
1
|
+
export { A as AsyncCheckFn, C as CheckInput, a as CheckResult, H as HealthcheckRegistry, b as HealthcheckResponse, c as HealthcheckResponseJson, 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-BJ2Z0rRi.js';
|
|
2
|
+
export { H as HealthcheckHandlerOptions, a as HealthcheckHandlerResult, b as HealthcheckRequest, c as createHealthcheckHandler } from './handler-D0nYVQvu.js';
|
|
2
3
|
|
|
3
4
|
declare function verifyToken(provided: string, expected: string): boolean;
|
|
4
5
|
declare function extractToken(options: {
|
package/dist/index.mjs
CHANGED
|
@@ -152,9 +152,32 @@ function extractToken(options) {
|
|
|
152
152
|
}
|
|
153
153
|
return null;
|
|
154
154
|
}
|
|
155
|
+
|
|
156
|
+
// src/handler.ts
|
|
157
|
+
function createHealthcheckHandler(options) {
|
|
158
|
+
const { registry, token = null, queryParamName = "token" } = options;
|
|
159
|
+
return async (req) => {
|
|
160
|
+
if (token !== null) {
|
|
161
|
+
const provided = extractToken({
|
|
162
|
+
queryParams: req.queryParams,
|
|
163
|
+
authorizationHeader: req.authorizationHeader,
|
|
164
|
+
queryParamName
|
|
165
|
+
});
|
|
166
|
+
if (provided === null || !verifyToken(provided, token)) {
|
|
167
|
+
return { status: 403, body: { error: "Forbidden" } };
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
const response = await registry.run();
|
|
171
|
+
return {
|
|
172
|
+
status: httpStatusCode(response.status),
|
|
173
|
+
body: toJson(response)
|
|
174
|
+
};
|
|
175
|
+
};
|
|
176
|
+
}
|
|
155
177
|
export {
|
|
156
178
|
HealthcheckRegistry,
|
|
157
179
|
Status,
|
|
180
|
+
createHealthcheckHandler,
|
|
158
181
|
extractToken,
|
|
159
182
|
httpStatusCode,
|
|
160
183
|
statusFromString,
|
|
@@ -76,24 +76,38 @@ function httpStatusCode(status) {
|
|
|
76
76
|
return status === Status.HEALTHY ? 200 : 503;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
// src/handler.ts
|
|
80
|
+
function createHealthcheckHandler(options) {
|
|
81
|
+
const { registry, token = null, queryParamName = "token" } = options;
|
|
82
|
+
return async (req) => {
|
|
83
|
+
if (token !== null) {
|
|
84
|
+
const provided = extractToken({
|
|
85
|
+
queryParams: req.queryParams,
|
|
86
|
+
authorizationHeader: req.authorizationHeader,
|
|
87
|
+
queryParamName
|
|
88
|
+
});
|
|
89
|
+
if (provided === null || !verifyToken(provided, token)) {
|
|
90
|
+
return { status: 403, body: { error: "Forbidden" } };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const response = await registry.run();
|
|
94
|
+
return {
|
|
95
|
+
status: httpStatusCode(response.status),
|
|
96
|
+
body: toJson(response)
|
|
97
|
+
};
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
79
101
|
// src/integrations/express.ts
|
|
80
102
|
function createHealthcheckMiddleware(options) {
|
|
81
|
-
const
|
|
103
|
+
const handle = createHealthcheckHandler(options);
|
|
82
104
|
return async (req, res) => {
|
|
83
105
|
try {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
});
|
|
90
|
-
if (provided === null || !verifyToken(provided, token)) {
|
|
91
|
-
res.status(403).json({ error: "Forbidden" });
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
const response = await registry.run();
|
|
96
|
-
res.status(httpStatusCode(response.status)).json(toJson(response));
|
|
106
|
+
const result = await handle({
|
|
107
|
+
queryParams: req.query,
|
|
108
|
+
authorizationHeader: req.headers.authorization ?? null
|
|
109
|
+
});
|
|
110
|
+
res.status(result.status).json(result.body);
|
|
97
111
|
} catch {
|
|
98
112
|
res.status(500).json({ error: "Internal Server Error" });
|
|
99
113
|
}
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import { RequestHandler } from 'express';
|
|
2
|
-
import { H as
|
|
2
|
+
import { H as HealthcheckHandlerOptions } from '../handler-Bccbso4b.cjs';
|
|
3
|
+
import '../core-BJ2Z0rRi.cjs';
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
registry: HealthcheckRegistry;
|
|
6
|
-
token?: string | null;
|
|
7
|
-
queryParamName?: string;
|
|
8
|
-
}
|
|
5
|
+
type HealthcheckMiddlewareOptions = HealthcheckHandlerOptions;
|
|
9
6
|
declare function createHealthcheckMiddleware(options: HealthcheckMiddlewareOptions): RequestHandler;
|
|
10
7
|
|
|
11
8
|
export { type HealthcheckMiddlewareOptions, createHealthcheckMiddleware };
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import { RequestHandler } from 'express';
|
|
2
|
-
import { H as
|
|
2
|
+
import { H as HealthcheckHandlerOptions } from '../handler-D0nYVQvu.js';
|
|
3
|
+
import '../core-BJ2Z0rRi.js';
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
registry: HealthcheckRegistry;
|
|
6
|
-
token?: string | null;
|
|
7
|
-
queryParamName?: string;
|
|
8
|
-
}
|
|
5
|
+
type HealthcheckMiddlewareOptions = HealthcheckHandlerOptions;
|
|
9
6
|
declare function createHealthcheckMiddleware(options: HealthcheckMiddlewareOptions): RequestHandler;
|
|
10
7
|
|
|
11
8
|
export { type HealthcheckMiddlewareOptions, createHealthcheckMiddleware };
|
|
@@ -50,24 +50,38 @@ function httpStatusCode(status) {
|
|
|
50
50
|
return status === Status.HEALTHY ? 200 : 503;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
// src/handler.ts
|
|
54
|
+
function createHealthcheckHandler(options) {
|
|
55
|
+
const { registry, token = null, queryParamName = "token" } = options;
|
|
56
|
+
return async (req) => {
|
|
57
|
+
if (token !== null) {
|
|
58
|
+
const provided = extractToken({
|
|
59
|
+
queryParams: req.queryParams,
|
|
60
|
+
authorizationHeader: req.authorizationHeader,
|
|
61
|
+
queryParamName
|
|
62
|
+
});
|
|
63
|
+
if (provided === null || !verifyToken(provided, token)) {
|
|
64
|
+
return { status: 403, body: { error: "Forbidden" } };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const response = await registry.run();
|
|
68
|
+
return {
|
|
69
|
+
status: httpStatusCode(response.status),
|
|
70
|
+
body: toJson(response)
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
53
75
|
// src/integrations/express.ts
|
|
54
76
|
function createHealthcheckMiddleware(options) {
|
|
55
|
-
const
|
|
77
|
+
const handle = createHealthcheckHandler(options);
|
|
56
78
|
return async (req, res) => {
|
|
57
79
|
try {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
});
|
|
64
|
-
if (provided === null || !verifyToken(provided, token)) {
|
|
65
|
-
res.status(403).json({ error: "Forbidden" });
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
const response = await registry.run();
|
|
70
|
-
res.status(httpStatusCode(response.status)).json(toJson(response));
|
|
80
|
+
const result = await handle({
|
|
81
|
+
queryParams: req.query,
|
|
82
|
+
authorizationHeader: req.headers.authorization ?? null
|
|
83
|
+
});
|
|
84
|
+
res.status(result.status).json(result.body);
|
|
71
85
|
} catch {
|
|
72
86
|
res.status(500).json({ error: "Internal Server Error" });
|
|
73
87
|
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/integrations/next.ts
|
|
21
|
+
var next_exports = {};
|
|
22
|
+
__export(next_exports, {
|
|
23
|
+
createNextHandler: () => createNextHandler
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(next_exports);
|
|
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
|
+
// src/types.ts
|
|
50
|
+
var Status = {
|
|
51
|
+
HEALTHY: 2,
|
|
52
|
+
DEGRADED: 1,
|
|
53
|
+
OUTAGE: 0
|
|
54
|
+
};
|
|
55
|
+
function statusToLabel(status) {
|
|
56
|
+
const labels = {
|
|
57
|
+
[Status.HEALTHY]: "healthy",
|
|
58
|
+
[Status.DEGRADED]: "degraded",
|
|
59
|
+
[Status.OUTAGE]: "outage"
|
|
60
|
+
};
|
|
61
|
+
return labels[status];
|
|
62
|
+
}
|
|
63
|
+
function toJson(response) {
|
|
64
|
+
return {
|
|
65
|
+
status: statusToLabel(response.status),
|
|
66
|
+
timestamp: response.timestamp,
|
|
67
|
+
checks: Object.fromEntries(
|
|
68
|
+
Object.entries(response.checks).map(([name, result]) => [
|
|
69
|
+
name,
|
|
70
|
+
{ status: statusToLabel(result.status), latencyMs: result.latencyMs, message: result.message }
|
|
71
|
+
])
|
|
72
|
+
)
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function httpStatusCode(status) {
|
|
76
|
+
return status === Status.HEALTHY ? 200 : 503;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// src/handler.ts
|
|
80
|
+
function createHealthcheckHandler(options) {
|
|
81
|
+
const { registry, token = null, queryParamName = "token" } = options;
|
|
82
|
+
return async (req) => {
|
|
83
|
+
if (token !== null) {
|
|
84
|
+
const provided = extractToken({
|
|
85
|
+
queryParams: req.queryParams,
|
|
86
|
+
authorizationHeader: req.authorizationHeader,
|
|
87
|
+
queryParamName
|
|
88
|
+
});
|
|
89
|
+
if (provided === null || !verifyToken(provided, token)) {
|
|
90
|
+
return { status: 403, body: { error: "Forbidden" } };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const response = await registry.run();
|
|
94
|
+
return {
|
|
95
|
+
status: httpStatusCode(response.status),
|
|
96
|
+
body: toJson(response)
|
|
97
|
+
};
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// src/integrations/next.ts
|
|
102
|
+
function createNextHandler(options) {
|
|
103
|
+
const handle = createHealthcheckHandler(options);
|
|
104
|
+
return async (request) => {
|
|
105
|
+
try {
|
|
106
|
+
const url = new URL(request.url);
|
|
107
|
+
const queryParams = Object.fromEntries(url.searchParams);
|
|
108
|
+
const result = await handle({
|
|
109
|
+
queryParams,
|
|
110
|
+
authorizationHeader: request.headers.get("authorization")
|
|
111
|
+
});
|
|
112
|
+
return Response.json(result.body, { status: result.status });
|
|
113
|
+
} catch {
|
|
114
|
+
return Response.json({ error: "Internal Server Error" }, { status: 500 });
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
119
|
+
0 && (module.exports = {
|
|
120
|
+
createNextHandler
|
|
121
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { H as HealthcheckHandlerOptions } from '../handler-Bccbso4b.cjs';
|
|
2
|
+
import '../core-BJ2Z0rRi.cjs';
|
|
3
|
+
|
|
4
|
+
type NextHealthcheckOptions = HealthcheckHandlerOptions;
|
|
5
|
+
/**
|
|
6
|
+
* Creates a Next.js App Router GET handler for the healthcheck endpoint.
|
|
7
|
+
*
|
|
8
|
+
* Usage in `app/vitals/route.ts`:
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { HealthcheckRegistry } from '@firebreak/vitals';
|
|
11
|
+
* import { createNextHandler } from '@firebreak/vitals/next';
|
|
12
|
+
*
|
|
13
|
+
* const registry = new HealthcheckRegistry();
|
|
14
|
+
* // ... register checks ...
|
|
15
|
+
*
|
|
16
|
+
* export const GET = createNextHandler({
|
|
17
|
+
* registry,
|
|
18
|
+
* token: process.env.VITALS_TOKEN,
|
|
19
|
+
* });
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
declare function createNextHandler(options: NextHealthcheckOptions): (request: Request) => Promise<Response>;
|
|
23
|
+
|
|
24
|
+
export { type NextHealthcheckOptions, createNextHandler };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { H as HealthcheckHandlerOptions } from '../handler-D0nYVQvu.js';
|
|
2
|
+
import '../core-BJ2Z0rRi.js';
|
|
3
|
+
|
|
4
|
+
type NextHealthcheckOptions = HealthcheckHandlerOptions;
|
|
5
|
+
/**
|
|
6
|
+
* Creates a Next.js App Router GET handler for the healthcheck endpoint.
|
|
7
|
+
*
|
|
8
|
+
* Usage in `app/vitals/route.ts`:
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { HealthcheckRegistry } from '@firebreak/vitals';
|
|
11
|
+
* import { createNextHandler } from '@firebreak/vitals/next';
|
|
12
|
+
*
|
|
13
|
+
* const registry = new HealthcheckRegistry();
|
|
14
|
+
* // ... register checks ...
|
|
15
|
+
*
|
|
16
|
+
* export const GET = createNextHandler({
|
|
17
|
+
* registry,
|
|
18
|
+
* token: process.env.VITALS_TOKEN,
|
|
19
|
+
* });
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
declare function createNextHandler(options: NextHealthcheckOptions): (request: Request) => Promise<Response>;
|
|
23
|
+
|
|
24
|
+
export { type NextHealthcheckOptions, createNextHandler };
|