@visulima/health-check 1.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/CHANGELOG.md +6 -0
- package/LICENSE.md +0 -0
- package/README.md +204 -0
- package/dist/index.d.ts +71 -0
- package/dist/index.js +249 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +249 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +127 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
## @visulima/health-check 1.0.0 (2022-11-16)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* **health-check:** adding new health-check package ([#17](https://github.com/visulima/visulima/issues/17)) ([2e2a89f](https://github.com/visulima/visulima/commit/2e2a89fe85214237c9f55bf00d76b69de691ee3e))
|
package/LICENSE.md
ADDED
|
File without changes
|
package/README.md
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<h3>Visulima health-check</h3>
|
|
3
|
+
<p>
|
|
4
|
+
A library built to provide support for defining service health for node services. It allows you to register async health checks for your dependencies and the service itself, provides a health endpoint that exposes their status, and health metrics.
|
|
5
|
+
|
|
6
|
+
It’s built on top of
|
|
7
|
+
|
|
8
|
+
[pingman](https://github.com/dopecodez/pingman),
|
|
9
|
+
[node:http](https://nodejs.org/api/http.html),
|
|
10
|
+
[cacheable-lookup](https://github.com/szmarczak/cacheable-lookup),
|
|
11
|
+
[node:process.env](https://nodejs.org/docs/latest/api/process.html#process_process_env)
|
|
12
|
+
|
|
13
|
+
</p>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<br />
|
|
17
|
+
|
|
18
|
+
<div align="center">
|
|
19
|
+
|
|
20
|
+
[![typescript-image]][typescript-url] [![npm-image]][npm-url] [![license-image]][license-url]
|
|
21
|
+
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<div align="center">
|
|
25
|
+
<sub>Built with ❤︎ by <a href="https://twitter.com/_prisis_">Daniel Bannert</a></sub>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
npm install @visulima/health-check
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
yarn add @visulima/health-check
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
pnpm add @visulima/health-check
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Usecases for API health check endpoints
|
|
43
|
+
|
|
44
|
+
Keeping the API health check endpoints generic allows to use them for multiple purposes. In this section, we will review of the everyday use cases of an API health check endpoint
|
|
45
|
+
|
|
46
|
+
- Container orchestrators and API load balancers can use API health check endpoint to find out about the process status
|
|
47
|
+
- Usage of memory, disk, and other server resources can be monitored via API health check endpoints
|
|
48
|
+
- Health checks can test APIs dependencies, such as databases and external service endpoints, to confirm availability and normal functioning.
|
|
49
|
+
|
|
50
|
+
## Usage
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
import { healthCheckHandler, HealthCheck as HealthCheck, nodeEnvironmentCheck } from "@visulima/health-check";
|
|
54
|
+
|
|
55
|
+
const HealthCheckService = new HealthCheck();
|
|
56
|
+
|
|
57
|
+
HealthCheckService.addChecker("node-env", nodeEnvironmentCheck);
|
|
58
|
+
|
|
59
|
+
export default healthCheckHandler(HealthCheckService); // returns a http handler
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### API health check endpoint types
|
|
63
|
+
|
|
64
|
+
There are at least three different types of API health check endpoints designed to serve specific purposes.
|
|
65
|
+
|
|
66
|
+
- _The readiness endpoint_, often available via `/health/ready`, returns the readiness state to accept incoming requests from the gateway or the upstream proxy. Readiness signals that the app is running normally but isn’t ready to receive requests yet.
|
|
67
|
+
|
|
68
|
+
- _The liveness endpoint_, often available via `/health/live`, returns the liveness of a microservice. If the check does not return the expected response, it means that the process is unhealthy or dead and should be replaced as soon as possible.
|
|
69
|
+
|
|
70
|
+
- _The generic health check endpoint_, often available via `/health`, returns the status of the service and the dependencies.
|
|
71
|
+
|
|
72
|
+
Consider the following example: an API that loads JSON-based data into memory to serve requests.
|
|
73
|
+
|
|
74
|
+
The `/health/ready` continues to respond with `NOT_READY` signal while the API is loading the JSON file since the API cannot serve any request without the file in memory. Therefore, it may take time for the API to process the entire file.
|
|
75
|
+
|
|
76
|
+
The `/health/live` immediately signals `LIVE`, even though the app is not ready, to prevent the container orchestrator layer from restarting the app.
|
|
77
|
+
|
|
78
|
+
> ### Consider protecting your health check endpoint
|
|
79
|
+
>
|
|
80
|
+
> Most ping endpoints are publicly available because they don’t provide much internal or sensitive information. On the other hand, API health check endpoints expose information about your service, so it’s a good idea to protect this endpoint. You only need to make sure that your API monitoring tool supports sending API access keys.
|
|
81
|
+
|
|
82
|
+
### Built-in Checks
|
|
83
|
+
|
|
84
|
+
The library comes with a set of built-in checks. Currently implemented checks are as follows:
|
|
85
|
+
|
|
86
|
+
#### Node Environment Check
|
|
87
|
+
|
|
88
|
+
This check verifies that the node environment is set to production.
|
|
89
|
+
This check is useful for ensuring that the node environment is set to production in production environments or only that the NODE_ENV is set.
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
import { nodeEnvironmentCheck } from "@visulima/health-check";
|
|
93
|
+
|
|
94
|
+
nodeEnvironmentCheck(); // check if NODE_ENV is set
|
|
95
|
+
nodeEnvironmentCheck("production"); // check if NODE_ENV is set to production
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
#### HTTP built-in check
|
|
99
|
+
|
|
100
|
+
The HTTP check allows you to trigger an HTTP request to one of your dependencies, and verify the response status, and optionally the content of the response body.
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
import { httpCheck } from "@visulima/health-check";
|
|
104
|
+
|
|
105
|
+
httpCheck("https://example.com", {
|
|
106
|
+
fetchOptions: {
|
|
107
|
+
method: "GET",
|
|
108
|
+
headers: {
|
|
109
|
+
"Content-Type": "application/json",
|
|
110
|
+
},
|
|
111
|
+
// ... any other options you want to pass to node-fetch
|
|
112
|
+
},
|
|
113
|
+
expected: {
|
|
114
|
+
status: 200,
|
|
115
|
+
body: "OK",
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
#### DNS built-in check(s)
|
|
121
|
+
|
|
122
|
+
The DNS checks allow you to perform lookup to a given hostname / domain name / CNAME / etc, and validate that it resolves to at least the minimum number of required results.
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
import { dnsCheck } from "@visulima/health-check";
|
|
126
|
+
|
|
127
|
+
dnsCheck("example.com", ["1.1.1.1"], {
|
|
128
|
+
family: "all",
|
|
129
|
+
// ... other options for cacheable-lookup
|
|
130
|
+
});
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
#### Ping built-in check(s)
|
|
134
|
+
|
|
135
|
+
The ping checks allow you to verifies that a resource is still alive and reachable. For example, you can use it as a DB ping check to verify that your DB is still alive and reachable.
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
import { pingCheck } from "@visulima/health-check";
|
|
139
|
+
|
|
140
|
+
pingCheck("example.com", {
|
|
141
|
+
timeout: 1000,
|
|
142
|
+
// ... other options for pingman
|
|
143
|
+
});
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
#### Custom Checks
|
|
147
|
+
|
|
148
|
+
The library provides Check interface that you can implement to create your own custom checks.
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
type Checker = () => Promise<HealthReportEntry>;
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Shape of health report entry. Each checker must
|
|
155
|
+
* return an object with similar shape.
|
|
156
|
+
*/
|
|
157
|
+
type HealthReportEntry = {
|
|
158
|
+
displayName: string;
|
|
159
|
+
health: {
|
|
160
|
+
healthy: boolean;
|
|
161
|
+
message?: string;
|
|
162
|
+
timestamp: string;
|
|
163
|
+
};
|
|
164
|
+
meta?: any;
|
|
165
|
+
};
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
#### Expose Health Endpoint
|
|
169
|
+
|
|
170
|
+
The library provides an HTTP handler function for serving health stats in JSON format. You can register it using your favorite HTTP implementation like so:
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
import { handleHealthCheck } from "@visulima/health-check";
|
|
174
|
+
|
|
175
|
+
export default handleHealthCheck();
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Supported Node.js Versions
|
|
179
|
+
|
|
180
|
+
Libraries in this ecosystem make the best effort to track
|
|
181
|
+
[Node.js’ release schedule](https://github.com/nodejs/release#release-schedule). Here’s [a
|
|
182
|
+
post on why we think this is important](https://medium.com/the-node-js-collection/maintainers-should-consider-following-node-js-release-schedule-ab08ed4de71a).
|
|
183
|
+
|
|
184
|
+
## Contributing
|
|
185
|
+
|
|
186
|
+
If you would like to help take a look at the [list of issues](https://github.com/visulima/visulima/issues) and check our [Contributing](.github/CONTRIBUTING.md) guild.
|
|
187
|
+
|
|
188
|
+
> **Note:** please note that this project is released with a Contributor Code of Conduct. By participating in this project you agree to abide by its terms.
|
|
189
|
+
|
|
190
|
+
## Credits
|
|
191
|
+
|
|
192
|
+
- [Daniel Bannert](https://github.com/prisis)
|
|
193
|
+
- [All Contributors](https://github.com/visulima/visulima/graphs/contributors)
|
|
194
|
+
|
|
195
|
+
## License
|
|
196
|
+
|
|
197
|
+
The visulima health-check is open-sourced software licensed under the [MIT][license-url]
|
|
198
|
+
|
|
199
|
+
[typescript-image]: https://img.shields.io/badge/Typescript-294E80.svg?style=for-the-badge&logo=typescript
|
|
200
|
+
[typescript-url]: "typescript"
|
|
201
|
+
[license-image]: https://img.shields.io/npm/l/@visulima/health-check?color=blueviolet&style=for-the-badge
|
|
202
|
+
[license-url]: LICENSE.md "license"
|
|
203
|
+
[npm-image]: https://img.shields.io/npm/v/@visulima/health-check/alpha.svg?style=for-the-badge&logo=npm
|
|
204
|
+
[npm-url]: https://www.npmjs.com/package/@visulima/health-check/v/alpha "npm"
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
+
import { IPFamily, Options } from 'cacheable-lookup';
|
|
3
|
+
import { extendedPingOptions } from 'pingman';
|
|
4
|
+
|
|
5
|
+
type Checker = () => Promise<HealthReportEntry>;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Shape of health report entry. Each checker must
|
|
9
|
+
* return an object with similar shape.
|
|
10
|
+
*/
|
|
11
|
+
type HealthReportEntry = {
|
|
12
|
+
displayName: string;
|
|
13
|
+
health: {
|
|
14
|
+
healthy: boolean;
|
|
15
|
+
message?: string;
|
|
16
|
+
timestamp: string;
|
|
17
|
+
};
|
|
18
|
+
meta?: any;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The shape of entire report
|
|
23
|
+
*/
|
|
24
|
+
type HealthReport = {
|
|
25
|
+
[service: string]: HealthReportEntry;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Shape of health check contract
|
|
30
|
+
*/
|
|
31
|
+
interface HealthCheck {
|
|
32
|
+
servicesList: string[];
|
|
33
|
+
addChecker(service: string, checker: Checker): void;
|
|
34
|
+
isLive(): Promise<boolean>;
|
|
35
|
+
getReport(): Promise<{ healthy: boolean; report: HealthReport }>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
declare const _default$1: (healthCheck: HealthCheck, sendHeader?: boolean | undefined) => <Request_1 extends IncomingMessage, Response_1 extends ServerResponse<IncomingMessage>>(_: Request_1, response: Response_1) => Promise<void>;
|
|
39
|
+
|
|
40
|
+
declare const _default: <Request_1 extends IncomingMessage, Response_1 extends ServerResponse<IncomingMessage>>(healthCheck: HealthCheck) => (_request: Request_1, response: Response_1) => Promise<void>;
|
|
41
|
+
|
|
42
|
+
declare class Healthcheck implements HealthCheck {
|
|
43
|
+
private healthCheckers;
|
|
44
|
+
get servicesList(): string[];
|
|
45
|
+
private invokeChecker;
|
|
46
|
+
addChecker(service: string, checker: Checker): void;
|
|
47
|
+
getReport(): Promise<{
|
|
48
|
+
healthy: boolean;
|
|
49
|
+
report: HealthReport;
|
|
50
|
+
}>;
|
|
51
|
+
isLive(): Promise<boolean>;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
declare const dnsCheck: (host: string, expectedAddresses?: string[], options?: {
|
|
55
|
+
family?: IPFamily | "all";
|
|
56
|
+
hints?: number;
|
|
57
|
+
} & Options) => Checker;
|
|
58
|
+
|
|
59
|
+
declare const httpCheck: (host: RequestInfo | URL, options?: {
|
|
60
|
+
fetchOptions?: RequestInit;
|
|
61
|
+
expected?: {
|
|
62
|
+
status?: number;
|
|
63
|
+
body?: string;
|
|
64
|
+
};
|
|
65
|
+
}) => Checker;
|
|
66
|
+
|
|
67
|
+
declare const nodeEnvironmentCheck: (expectedEnv?: string) => Checker;
|
|
68
|
+
|
|
69
|
+
declare const pingCheck: (host: string, options?: extendedPingOptions) => Checker;
|
|
70
|
+
|
|
71
|
+
export { Checker, Healthcheck as HealthCheck, dnsCheck, _default$1 as healthCheckHandler, _default as healthReadyHandler, httpCheck, nodeEnvironmentCheck as nodeEnvCheck, pingCheck };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } }// src/handler/healthcheck.ts
|
|
2
|
+
var _httpstatuscodes = require('http-status-codes');
|
|
3
|
+
var healthcheck_default = (healthCheck, sendHeader = true) => async (_, response) => {
|
|
4
|
+
const { healthy, report } = await healthCheck.getReport();
|
|
5
|
+
const payload = {
|
|
6
|
+
status: healthy ? "ok" : "error",
|
|
7
|
+
message: healthy ? "Health check successful" : "Health check failed",
|
|
8
|
+
appName: _nullishCoalesce(process.env.APP_NAME, () => ( "unknown")),
|
|
9
|
+
appVersion: _nullishCoalesce(process.env.APP_VERSION, () => ( "unknown")),
|
|
10
|
+
timestamp: new Date().toISOString(),
|
|
11
|
+
reports: report
|
|
12
|
+
};
|
|
13
|
+
response.statusCode = healthy ? _httpstatuscodes.StatusCodes.OK : _httpstatuscodes.StatusCodes.SERVICE_UNAVAILABLE;
|
|
14
|
+
if (sendHeader) {
|
|
15
|
+
response.setHeader("Content-Type", "application/json");
|
|
16
|
+
}
|
|
17
|
+
response.end(JSON.stringify(payload));
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// src/handler/readyhandler.ts
|
|
21
|
+
|
|
22
|
+
var readyhandler_default = (healthCheck) => async (_request, response) => {
|
|
23
|
+
const { healthy } = await healthCheck.getReport();
|
|
24
|
+
response.statusCode = healthy ? _httpstatuscodes.StatusCodes.NO_CONTENT : _httpstatuscodes.StatusCodes.SERVICE_UNAVAILABLE;
|
|
25
|
+
response.end();
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// src/healthcheck.ts
|
|
29
|
+
var Healthcheck = class {
|
|
30
|
+
constructor() {
|
|
31
|
+
this.healthCheckers = {};
|
|
32
|
+
}
|
|
33
|
+
get servicesList() {
|
|
34
|
+
return Object.keys(this.healthCheckers);
|
|
35
|
+
}
|
|
36
|
+
async invokeChecker(service, reportSheet) {
|
|
37
|
+
const checker = this.healthCheckers[service];
|
|
38
|
+
let report;
|
|
39
|
+
try {
|
|
40
|
+
report = await checker();
|
|
41
|
+
report.displayName = report.displayName || service;
|
|
42
|
+
} catch (error) {
|
|
43
|
+
report = {
|
|
44
|
+
displayName: service,
|
|
45
|
+
health: { healthy: false, message: error.message, timestamp: new Date().toISOString() },
|
|
46
|
+
meta: { fatal: true }
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
reportSheet[service] = report;
|
|
50
|
+
return report.health.healthy;
|
|
51
|
+
}
|
|
52
|
+
addChecker(service, checker) {
|
|
53
|
+
this.healthCheckers[service] = checker;
|
|
54
|
+
}
|
|
55
|
+
async getReport() {
|
|
56
|
+
const report = {};
|
|
57
|
+
await Promise.all(Object.keys(this.healthCheckers).map((service) => this.invokeChecker(service, report)));
|
|
58
|
+
const unhealthyService = Object.keys(report).find((service) => !report[service].health.healthy);
|
|
59
|
+
return { healthy: !unhealthyService, report };
|
|
60
|
+
}
|
|
61
|
+
async isLive() {
|
|
62
|
+
const { healthy } = await this.getReport();
|
|
63
|
+
return healthy;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
var healthcheck_default2 = Healthcheck;
|
|
67
|
+
|
|
68
|
+
// src/checks/dns-check.ts
|
|
69
|
+
var _cacheablelookup = require('cacheable-lookup'); var _cacheablelookup2 = _interopRequireDefault(_cacheablelookup);
|
|
70
|
+
var DISPLAY_NAME = "DNS check for";
|
|
71
|
+
var dnsCheck = (host, expectedAddresses, options) => async () => {
|
|
72
|
+
const { hints, family = "all", ...config } = options || {};
|
|
73
|
+
const cacheable = new (0, _cacheablelookup2.default)(config);
|
|
74
|
+
try {
|
|
75
|
+
const meta = await cacheable.lookupAsync(host.replace(/^https?:\/\//, ""), {
|
|
76
|
+
hints,
|
|
77
|
+
...family === "all" ? { all: true } : { family }
|
|
78
|
+
});
|
|
79
|
+
if (Array.isArray(expectedAddresses) && !(expectedAddresses == null ? void 0 : expectedAddresses.includes(meta.address))) {
|
|
80
|
+
return {
|
|
81
|
+
displayName: `${DISPLAY_NAME} ${host}`,
|
|
82
|
+
health: {
|
|
83
|
+
healthy: false,
|
|
84
|
+
message: `${DISPLAY_NAME} ${host} returned address ${meta.address} instead of ${expectedAddresses.join(", ")}.`,
|
|
85
|
+
timestamp: new Date().toISOString()
|
|
86
|
+
},
|
|
87
|
+
meta: {
|
|
88
|
+
host,
|
|
89
|
+
addresses: meta
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
displayName: `${DISPLAY_NAME} ${host}`,
|
|
95
|
+
health: {
|
|
96
|
+
healthy: true,
|
|
97
|
+
message: `${DISPLAY_NAME} ${host} were resolved.`,
|
|
98
|
+
timestamp: new Date().toISOString()
|
|
99
|
+
},
|
|
100
|
+
meta: {
|
|
101
|
+
host,
|
|
102
|
+
addresses: meta
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
} catch (error) {
|
|
106
|
+
return {
|
|
107
|
+
displayName: `${DISPLAY_NAME} ${host}`,
|
|
108
|
+
health: {
|
|
109
|
+
healthy: false,
|
|
110
|
+
message: error.message,
|
|
111
|
+
timestamp: new Date().toISOString()
|
|
112
|
+
},
|
|
113
|
+
meta: {
|
|
114
|
+
host
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
var dns_check_default = dnsCheck;
|
|
120
|
+
|
|
121
|
+
// src/checks/http-check.ts
|
|
122
|
+
var _assert = require('assert');
|
|
123
|
+
var DISPLAY_NAME2 = "HTTP check for";
|
|
124
|
+
var httpCheck = (host, options) => async () => {
|
|
125
|
+
var _a, _b, _c, _d, _e;
|
|
126
|
+
try {
|
|
127
|
+
const response = await fetch(host, (options == null ? void 0 : options.fetchOptions) || {});
|
|
128
|
+
if (typeof ((_a = options == null ? void 0 : options.expected) == null ? void 0 : _a.status) !== "undefined" && ((_b = options == null ? void 0 : options.expected) == null ? void 0 : _b.status) !== response.status) {
|
|
129
|
+
throw new Error(`${DISPLAY_NAME2} ${host} returned status ${response.status} instead of ${options == null ? void 0 : options.expected.status}`);
|
|
130
|
+
}
|
|
131
|
+
if (typeof ((_c = options == null ? void 0 : options.expected) == null ? void 0 : _c.body) !== "undefined") {
|
|
132
|
+
const textBody = await response.text();
|
|
133
|
+
try {
|
|
134
|
+
_assert.deepStrictEqual.call(void 0, textBody, options == null ? void 0 : options.expected.body);
|
|
135
|
+
} catch (e) {
|
|
136
|
+
throw new Error(`${DISPLAY_NAME2} ${host} returned body ${JSON.stringify(textBody)} instead of ${JSON.stringify(options == null ? void 0 : options.expected.body)}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
displayName: `${DISPLAY_NAME2} ${host}`,
|
|
141
|
+
health: {
|
|
142
|
+
healthy: true,
|
|
143
|
+
message: `${DISPLAY_NAME2} ${host} was successful.`,
|
|
144
|
+
timestamp: new Date().toISOString()
|
|
145
|
+
},
|
|
146
|
+
meta: {
|
|
147
|
+
host,
|
|
148
|
+
method: ((_d = options == null ? void 0 : options.fetchOptions) == null ? void 0 : _d.method) || "GET",
|
|
149
|
+
status: response.status
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
} catch (error) {
|
|
153
|
+
return {
|
|
154
|
+
displayName: `${DISPLAY_NAME2} ${host}`,
|
|
155
|
+
health: {
|
|
156
|
+
healthy: false,
|
|
157
|
+
message: error.message,
|
|
158
|
+
timestamp: new Date().toISOString()
|
|
159
|
+
},
|
|
160
|
+
meta: {
|
|
161
|
+
host,
|
|
162
|
+
method: ((_e = options == null ? void 0 : options.fetchOptions) == null ? void 0 : _e.method) || "GET"
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
var http_check_default = httpCheck;
|
|
168
|
+
|
|
169
|
+
// src/checks/node-environment-check.ts
|
|
170
|
+
var DISPLAY_NAME3 = "Node Environment Check";
|
|
171
|
+
var nodeEnvironmentCheck = (expectedEnv) => async () => {
|
|
172
|
+
const environment = "development";
|
|
173
|
+
let errorMessage;
|
|
174
|
+
if (typeof environment !== "undefined" && typeof expectedEnv !== "undefined" && environment !== expectedEnv) {
|
|
175
|
+
errorMessage = `NODE_ENV environment variable is set to "${environment}" instead of "${expectedEnv}".`;
|
|
176
|
+
} else if (typeof environment === "undefined") {
|
|
177
|
+
errorMessage = ["Missing NODE_ENV environment variable.", "It can make some parts of the application misbehave"].join(" ");
|
|
178
|
+
}
|
|
179
|
+
if (typeof errorMessage !== "undefined") {
|
|
180
|
+
return {
|
|
181
|
+
displayName: DISPLAY_NAME3,
|
|
182
|
+
health: {
|
|
183
|
+
healthy: false,
|
|
184
|
+
message: errorMessage,
|
|
185
|
+
timestamp: new Date().toISOString()
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
return {
|
|
190
|
+
displayName: DISPLAY_NAME3,
|
|
191
|
+
health: {
|
|
192
|
+
healthy: true,
|
|
193
|
+
timestamp: new Date().toISOString()
|
|
194
|
+
},
|
|
195
|
+
meta: {
|
|
196
|
+
env: environment
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
};
|
|
200
|
+
var node_environment_check_default = nodeEnvironmentCheck;
|
|
201
|
+
|
|
202
|
+
// src/checks/ping-check.ts
|
|
203
|
+
var _pingman = require('pingman'); var _pingman2 = _interopRequireDefault(_pingman);
|
|
204
|
+
var DISPLAY_NAME4 = "Ping check for";
|
|
205
|
+
var pingCheck = (host, options) => async () => {
|
|
206
|
+
try {
|
|
207
|
+
const response = await _pingman2.default.call(void 0, host.replace(/^https?:\/\//, ""), options);
|
|
208
|
+
if (!response.alive) {
|
|
209
|
+
return {
|
|
210
|
+
displayName: `${DISPLAY_NAME4} ${host}`,
|
|
211
|
+
health: {
|
|
212
|
+
healthy: false,
|
|
213
|
+
message: `Ping failed for ${host}.`,
|
|
214
|
+
timestamp: new Date().toISOString()
|
|
215
|
+
},
|
|
216
|
+
meta: response
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
return {
|
|
220
|
+
displayName: `${DISPLAY_NAME4} ${host}`,
|
|
221
|
+
health: {
|
|
222
|
+
healthy: true,
|
|
223
|
+
message: `${DISPLAY_NAME4} ${host} was successful.`,
|
|
224
|
+
timestamp: new Date().toISOString()
|
|
225
|
+
},
|
|
226
|
+
meta: response
|
|
227
|
+
};
|
|
228
|
+
} catch (error) {
|
|
229
|
+
return {
|
|
230
|
+
displayName: `${DISPLAY_NAME4} ${host}`,
|
|
231
|
+
health: {
|
|
232
|
+
healthy: false,
|
|
233
|
+
message: error.message,
|
|
234
|
+
timestamp: new Date().toISOString()
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
var ping_check_default = pingCheck;
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
exports.HealthCheck = healthcheck_default2; exports.dnsCheck = dns_check_default; exports.healthCheckHandler = healthcheck_default; exports.healthReadyHandler = readyhandler_default; exports.httpCheck = http_check_default; exports.nodeEnvCheck = node_environment_check_default; exports.pingCheck = ping_check_default;
|
|
249
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/handler/healthcheck.ts","../src/handler/readyhandler.ts","../src/healthcheck.ts","../src/checks/dns-check.ts","../src/checks/http-check.ts","../src/checks/node-environment-check.ts","../src/checks/ping-check.ts"],"names":["StatusCodes","healthcheck_default","DISPLAY_NAME"],"mappings":"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","sourcesContent":["import { StatusCodes } from \"http-status-codes\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\n\nimport type { HealthCheck, HealthReport } from \"../types\";\n\nexport default (healthCheck: HealthCheck, sendHeader: boolean | undefined = true) =>\n async <Request extends IncomingMessage, Response extends ServerResponse>(_: Request, response: Response) => {\n const { healthy, report } = await healthCheck.getReport();\n\n const payload: HealthCheckApiPayload = {\n status: healthy ? \"ok\" : \"error\",\n message: healthy ? \"Health check successful\" : \"Health check failed\",\n appName: process.env.APP_NAME ?? \"unknown\",\n appVersion: process.env.APP_VERSION ?? \"unknown\",\n timestamp: new Date().toISOString(),\n reports: report,\n };\n\n response.statusCode = healthy ? StatusCodes.OK : StatusCodes.SERVICE_UNAVAILABLE;\n\n if (sendHeader) {\n response.setHeader(\"Content-Type\", \"application/json\");\n }\n\n response.end(JSON.stringify(payload));\n };\n\nexport type HealthCheckApiPayload = {\n status: \"ok\" | \"error\";\n message: string;\n appName: string;\n appVersion: string;\n timestamp: string;\n reports: HealthReport;\n};\n","import type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { StatusCodes } from \"http-status-codes\";\n\nimport type { HealthCheck } from \"../types\";\n\nexport default <Request extends IncomingMessage, Response extends ServerResponse>(healthCheck: HealthCheck) => async (_request: Request, response: Response) => {\n const { healthy } = await healthCheck.getReport();\n\n response.statusCode = healthy ? StatusCodes.NO_CONTENT : StatusCodes.SERVICE_UNAVAILABLE;\n response.end()\n};\n","import {\n Checker, HealthCheck as HealthcheckInterface, HealthReport, HealthReportEntry,\n} from \"./types\";\n\nclass Healthcheck implements HealthcheckInterface {\n /**\n * A copy of registered checkers\n */\n private healthCheckers: { [service: string]: Checker } = {};\n\n /**\n * Returns an array of registered services names\n */\n public get servicesList(): string[] {\n return Object.keys(this.healthCheckers);\n }\n\n /**\n * Invokes a given checker to collect the report metrics.\n */\n private async invokeChecker(service: string, reportSheet: HealthReport): Promise<boolean> {\n const checker = this.healthCheckers[service] as Checker;\n\n let report: HealthReportEntry;\n\n try {\n report = await checker();\n\n report.displayName = report.displayName || service;\n } catch (error: any) {\n report = {\n displayName: service,\n health: { healthy: false, message: error.message, timestamp: new Date().toISOString() },\n meta: { fatal: true },\n };\n }\n\n // eslint-disable-next-line no-param-reassign\n reportSheet[service] = report;\n\n return report.health.healthy;\n }\n\n public addChecker(service: string, checker: Checker): void {\n this.healthCheckers[service] = checker;\n }\n\n /**\n * Returns the health check reports. The health checks are performed when\n * this method is invoked.\n */\n public async getReport(): Promise<{ healthy: boolean; report: HealthReport }> {\n const report: HealthReport = {};\n\n // eslint-disable-next-line compat/compat\n await Promise.all(Object.keys(this.healthCheckers).map((service) => this.invokeChecker(service, report)));\n\n /**\n * Finding unhealthy service to know if system is healthy or not\n */\n const unhealthyService = Object.keys(report).find((service) => !(report[service] as HealthReportEntry).health.healthy);\n\n return { healthy: !unhealthyService, report };\n }\n\n public async isLive(): Promise<boolean> {\n const { healthy } = await this.getReport();\n\n return healthy;\n }\n}\n\nexport default Healthcheck;\n","import type { IPFamily, Options } from \"cacheable-lookup\";\nimport CacheableLookup from \"cacheable-lookup\";\n\nimport type { Checker } from \"../types\";\n\nconst DISPLAY_NAME = \"DNS check for\";\n\n/**\n * Register the `dns` checker to ensure that a domain is reachable.\n */\nconst dnsCheck = (\n host: string,\n expectedAddresses?: string[],\n options?: {\n family?: IPFamily | \"all\";\n hints?: number;\n } & Options,\n): Checker => async () => {\n const { hints, family = \"all\", ...config } = options || {};\n\n const cacheable = new CacheableLookup(config);\n\n try {\n const meta = await cacheable.lookupAsync(host.replace(/^https?:\\/\\//, \"\"), {\n hints,\n ...(family === \"all\" ? { all: true } : { family: family as IPFamily }),\n });\n\n if (Array.isArray(expectedAddresses) && !expectedAddresses?.includes(meta.address)) {\n return {\n displayName: `${DISPLAY_NAME} ${host}`,\n health: {\n healthy: false,\n message: `${DISPLAY_NAME} ${host} returned address ${meta.address} instead of ${expectedAddresses.join(\", \")}.`,\n timestamp: new Date().toISOString(),\n },\n meta: {\n host,\n addresses: meta,\n },\n };\n }\n\n return {\n displayName: `${DISPLAY_NAME} ${host}`,\n health: {\n healthy: true,\n message: `${DISPLAY_NAME} ${host} were resolved.`,\n timestamp: new Date().toISOString(),\n },\n meta: {\n host,\n addresses: meta,\n },\n };\n } catch (error: any) {\n return {\n displayName: `${DISPLAY_NAME} ${host}`,\n health: {\n healthy: false,\n message: error.message,\n timestamp: new Date().toISOString(),\n },\n meta: {\n host,\n },\n };\n }\n};\n\nexport default dnsCheck;\n","import { deepStrictEqual } from \"node:assert\";\n\nimport type { Checker } from \"../types\";\n\nconst DISPLAY_NAME = \"HTTP check for\";\n\n/**\n * Register the `http` checker to ensure http body and status is correct.\n */\nconst httpCheck =\n (host: RequestInfo | URL, options?: {\n fetchOptions?: RequestInit,\n expected?: { status?: number; body?: string }\n }): Checker =>\n async () => {\n try {\n // eslint-disable-next-line compat/compat\n const response = await fetch(host, options?.fetchOptions || {});\n\n if (typeof options?.expected?.status !== \"undefined\" && options?.expected?.status !== response.status) {\n throw new Error(`${DISPLAY_NAME} ${host} returned status ${response.status} instead of ${options?.expected.status}`);\n }\n\n if (typeof options?.expected?.body !== \"undefined\") {\n const textBody = await response.text();\n\n try {\n deepStrictEqual(textBody, options?.expected.body);\n } catch {\n throw new Error(`${DISPLAY_NAME} ${host} returned body ${JSON.stringify(textBody)} instead of ${JSON.stringify(options?.expected.body)}`);\n }\n }\n\n return {\n displayName: `${DISPLAY_NAME} ${host}`,\n health: {\n healthy: true,\n message: `${DISPLAY_NAME} ${host} was successful.`,\n timestamp: new Date().toISOString(),\n },\n meta: {\n host,\n method: options?.fetchOptions?.method || \"GET\",\n status: response.status,\n },\n };\n } catch (error: any) {\n return {\n displayName: `${DISPLAY_NAME} ${host}`,\n health: {\n healthy: false,\n message: error.message,\n timestamp: new Date().toISOString(),\n },\n meta: {\n host,\n method: options?.fetchOptions?.method || \"GET\",\n },\n };\n }\n };\n\nexport default httpCheck;\n","import type { Checker } from \"../types\";\n\nconst DISPLAY_NAME = \"Node Environment Check\";\n\n/**\n * Register the `env` checker to ensure that `NODE_ENV` environment\n * variable is defined.\n */\nconst nodeEnvironmentCheck = (expectedEnv?: string): Checker => async () => {\n const environment = process.env.NODE_ENV;\n\n let errorMessage: string | undefined;\n\n if (typeof environment !== \"undefined\" && typeof expectedEnv !== \"undefined\" && environment !== expectedEnv) {\n errorMessage = `NODE_ENV environment variable is set to \"${environment}\" instead of \"${expectedEnv}\".`;\n } else if (typeof environment === \"undefined\") {\n errorMessage = [\"Missing NODE_ENV environment variable.\", \"It can make some parts of the application misbehave\"].join(\" \");\n }\n\n if (typeof errorMessage !== \"undefined\") {\n return {\n displayName: DISPLAY_NAME,\n health: {\n healthy: false,\n message: errorMessage,\n timestamp: new Date().toISOString(),\n },\n };\n }\n\n return {\n displayName: DISPLAY_NAME,\n health: {\n healthy: true,\n timestamp: new Date().toISOString(),\n },\n meta: {\n env: environment,\n },\n };\n};\n\nexport default nodeEnvironmentCheck;\n","import type { extendedPingOptions } from \"pingman\";\nimport ping from \"pingman\";\n\nimport type { Checker } from \"../types\";\n\nconst DISPLAY_NAME = \"Ping check for\";\n\n/**\n * Register the `ping` checker to ensure that a domain is reachable.\n */\nconst pingCheck = (host: string, options?: extendedPingOptions): Checker => async () => {\n try {\n const response = await ping(host.replace(/^https?:\\/\\//, \"\"), options);\n\n if (!response.alive) {\n return {\n displayName: `${DISPLAY_NAME} ${host}`,\n health: {\n healthy: false,\n message: `Ping failed for ${host}.`,\n timestamp: new Date().toISOString(),\n },\n meta: response,\n };\n }\n\n return {\n displayName: `${DISPLAY_NAME} ${host}`,\n health: {\n healthy: true,\n message: `${DISPLAY_NAME} ${host} was successful.`,\n timestamp: new Date().toISOString(),\n },\n meta: response,\n };\n } catch (error: any) {\n return {\n displayName: `${DISPLAY_NAME} ${host}`,\n health: {\n healthy: false,\n message: error.message,\n timestamp: new Date().toISOString(),\n },\n };\n }\n};\n\nexport default pingCheck;\n"]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
// src/handler/healthcheck.ts
|
|
2
|
+
import { StatusCodes } from "http-status-codes";
|
|
3
|
+
var healthcheck_default = (healthCheck, sendHeader = true) => async (_, response) => {
|
|
4
|
+
const { healthy, report } = await healthCheck.getReport();
|
|
5
|
+
const payload = {
|
|
6
|
+
status: healthy ? "ok" : "error",
|
|
7
|
+
message: healthy ? "Health check successful" : "Health check failed",
|
|
8
|
+
appName: process.env.APP_NAME ?? "unknown",
|
|
9
|
+
appVersion: process.env.APP_VERSION ?? "unknown",
|
|
10
|
+
timestamp: new Date().toISOString(),
|
|
11
|
+
reports: report
|
|
12
|
+
};
|
|
13
|
+
response.statusCode = healthy ? StatusCodes.OK : StatusCodes.SERVICE_UNAVAILABLE;
|
|
14
|
+
if (sendHeader) {
|
|
15
|
+
response.setHeader("Content-Type", "application/json");
|
|
16
|
+
}
|
|
17
|
+
response.end(JSON.stringify(payload));
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// src/handler/readyhandler.ts
|
|
21
|
+
import { StatusCodes as StatusCodes2 } from "http-status-codes";
|
|
22
|
+
var readyhandler_default = (healthCheck) => async (_request, response) => {
|
|
23
|
+
const { healthy } = await healthCheck.getReport();
|
|
24
|
+
response.statusCode = healthy ? StatusCodes2.NO_CONTENT : StatusCodes2.SERVICE_UNAVAILABLE;
|
|
25
|
+
response.end();
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// src/healthcheck.ts
|
|
29
|
+
var Healthcheck = class {
|
|
30
|
+
constructor() {
|
|
31
|
+
this.healthCheckers = {};
|
|
32
|
+
}
|
|
33
|
+
get servicesList() {
|
|
34
|
+
return Object.keys(this.healthCheckers);
|
|
35
|
+
}
|
|
36
|
+
async invokeChecker(service, reportSheet) {
|
|
37
|
+
const checker = this.healthCheckers[service];
|
|
38
|
+
let report;
|
|
39
|
+
try {
|
|
40
|
+
report = await checker();
|
|
41
|
+
report.displayName = report.displayName || service;
|
|
42
|
+
} catch (error) {
|
|
43
|
+
report = {
|
|
44
|
+
displayName: service,
|
|
45
|
+
health: { healthy: false, message: error.message, timestamp: new Date().toISOString() },
|
|
46
|
+
meta: { fatal: true }
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
reportSheet[service] = report;
|
|
50
|
+
return report.health.healthy;
|
|
51
|
+
}
|
|
52
|
+
addChecker(service, checker) {
|
|
53
|
+
this.healthCheckers[service] = checker;
|
|
54
|
+
}
|
|
55
|
+
async getReport() {
|
|
56
|
+
const report = {};
|
|
57
|
+
await Promise.all(Object.keys(this.healthCheckers).map((service) => this.invokeChecker(service, report)));
|
|
58
|
+
const unhealthyService = Object.keys(report).find((service) => !report[service].health.healthy);
|
|
59
|
+
return { healthy: !unhealthyService, report };
|
|
60
|
+
}
|
|
61
|
+
async isLive() {
|
|
62
|
+
const { healthy } = await this.getReport();
|
|
63
|
+
return healthy;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
var healthcheck_default2 = Healthcheck;
|
|
67
|
+
|
|
68
|
+
// src/checks/dns-check.ts
|
|
69
|
+
import CacheableLookup from "cacheable-lookup";
|
|
70
|
+
var DISPLAY_NAME = "DNS check for";
|
|
71
|
+
var dnsCheck = (host, expectedAddresses, options) => async () => {
|
|
72
|
+
const { hints, family = "all", ...config } = options || {};
|
|
73
|
+
const cacheable = new CacheableLookup(config);
|
|
74
|
+
try {
|
|
75
|
+
const meta = await cacheable.lookupAsync(host.replace(/^https?:\/\//, ""), {
|
|
76
|
+
hints,
|
|
77
|
+
...family === "all" ? { all: true } : { family }
|
|
78
|
+
});
|
|
79
|
+
if (Array.isArray(expectedAddresses) && !(expectedAddresses == null ? void 0 : expectedAddresses.includes(meta.address))) {
|
|
80
|
+
return {
|
|
81
|
+
displayName: `${DISPLAY_NAME} ${host}`,
|
|
82
|
+
health: {
|
|
83
|
+
healthy: false,
|
|
84
|
+
message: `${DISPLAY_NAME} ${host} returned address ${meta.address} instead of ${expectedAddresses.join(", ")}.`,
|
|
85
|
+
timestamp: new Date().toISOString()
|
|
86
|
+
},
|
|
87
|
+
meta: {
|
|
88
|
+
host,
|
|
89
|
+
addresses: meta
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
displayName: `${DISPLAY_NAME} ${host}`,
|
|
95
|
+
health: {
|
|
96
|
+
healthy: true,
|
|
97
|
+
message: `${DISPLAY_NAME} ${host} were resolved.`,
|
|
98
|
+
timestamp: new Date().toISOString()
|
|
99
|
+
},
|
|
100
|
+
meta: {
|
|
101
|
+
host,
|
|
102
|
+
addresses: meta
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
} catch (error) {
|
|
106
|
+
return {
|
|
107
|
+
displayName: `${DISPLAY_NAME} ${host}`,
|
|
108
|
+
health: {
|
|
109
|
+
healthy: false,
|
|
110
|
+
message: error.message,
|
|
111
|
+
timestamp: new Date().toISOString()
|
|
112
|
+
},
|
|
113
|
+
meta: {
|
|
114
|
+
host
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
var dns_check_default = dnsCheck;
|
|
120
|
+
|
|
121
|
+
// src/checks/http-check.ts
|
|
122
|
+
import { deepStrictEqual } from "assert";
|
|
123
|
+
var DISPLAY_NAME2 = "HTTP check for";
|
|
124
|
+
var httpCheck = (host, options) => async () => {
|
|
125
|
+
var _a, _b, _c, _d, _e;
|
|
126
|
+
try {
|
|
127
|
+
const response = await fetch(host, (options == null ? void 0 : options.fetchOptions) || {});
|
|
128
|
+
if (typeof ((_a = options == null ? void 0 : options.expected) == null ? void 0 : _a.status) !== "undefined" && ((_b = options == null ? void 0 : options.expected) == null ? void 0 : _b.status) !== response.status) {
|
|
129
|
+
throw new Error(`${DISPLAY_NAME2} ${host} returned status ${response.status} instead of ${options == null ? void 0 : options.expected.status}`);
|
|
130
|
+
}
|
|
131
|
+
if (typeof ((_c = options == null ? void 0 : options.expected) == null ? void 0 : _c.body) !== "undefined") {
|
|
132
|
+
const textBody = await response.text();
|
|
133
|
+
try {
|
|
134
|
+
deepStrictEqual(textBody, options == null ? void 0 : options.expected.body);
|
|
135
|
+
} catch {
|
|
136
|
+
throw new Error(`${DISPLAY_NAME2} ${host} returned body ${JSON.stringify(textBody)} instead of ${JSON.stringify(options == null ? void 0 : options.expected.body)}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
displayName: `${DISPLAY_NAME2} ${host}`,
|
|
141
|
+
health: {
|
|
142
|
+
healthy: true,
|
|
143
|
+
message: `${DISPLAY_NAME2} ${host} was successful.`,
|
|
144
|
+
timestamp: new Date().toISOString()
|
|
145
|
+
},
|
|
146
|
+
meta: {
|
|
147
|
+
host,
|
|
148
|
+
method: ((_d = options == null ? void 0 : options.fetchOptions) == null ? void 0 : _d.method) || "GET",
|
|
149
|
+
status: response.status
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
} catch (error) {
|
|
153
|
+
return {
|
|
154
|
+
displayName: `${DISPLAY_NAME2} ${host}`,
|
|
155
|
+
health: {
|
|
156
|
+
healthy: false,
|
|
157
|
+
message: error.message,
|
|
158
|
+
timestamp: new Date().toISOString()
|
|
159
|
+
},
|
|
160
|
+
meta: {
|
|
161
|
+
host,
|
|
162
|
+
method: ((_e = options == null ? void 0 : options.fetchOptions) == null ? void 0 : _e.method) || "GET"
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
var http_check_default = httpCheck;
|
|
168
|
+
|
|
169
|
+
// src/checks/node-environment-check.ts
|
|
170
|
+
var DISPLAY_NAME3 = "Node Environment Check";
|
|
171
|
+
var nodeEnvironmentCheck = (expectedEnv) => async () => {
|
|
172
|
+
const environment = "development";
|
|
173
|
+
let errorMessage;
|
|
174
|
+
if (typeof environment !== "undefined" && typeof expectedEnv !== "undefined" && environment !== expectedEnv) {
|
|
175
|
+
errorMessage = `NODE_ENV environment variable is set to "${environment}" instead of "${expectedEnv}".`;
|
|
176
|
+
} else if (typeof environment === "undefined") {
|
|
177
|
+
errorMessage = ["Missing NODE_ENV environment variable.", "It can make some parts of the application misbehave"].join(" ");
|
|
178
|
+
}
|
|
179
|
+
if (typeof errorMessage !== "undefined") {
|
|
180
|
+
return {
|
|
181
|
+
displayName: DISPLAY_NAME3,
|
|
182
|
+
health: {
|
|
183
|
+
healthy: false,
|
|
184
|
+
message: errorMessage,
|
|
185
|
+
timestamp: new Date().toISOString()
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
return {
|
|
190
|
+
displayName: DISPLAY_NAME3,
|
|
191
|
+
health: {
|
|
192
|
+
healthy: true,
|
|
193
|
+
timestamp: new Date().toISOString()
|
|
194
|
+
},
|
|
195
|
+
meta: {
|
|
196
|
+
env: environment
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
};
|
|
200
|
+
var node_environment_check_default = nodeEnvironmentCheck;
|
|
201
|
+
|
|
202
|
+
// src/checks/ping-check.ts
|
|
203
|
+
import ping from "pingman";
|
|
204
|
+
var DISPLAY_NAME4 = "Ping check for";
|
|
205
|
+
var pingCheck = (host, options) => async () => {
|
|
206
|
+
try {
|
|
207
|
+
const response = await ping(host.replace(/^https?:\/\//, ""), options);
|
|
208
|
+
if (!response.alive) {
|
|
209
|
+
return {
|
|
210
|
+
displayName: `${DISPLAY_NAME4} ${host}`,
|
|
211
|
+
health: {
|
|
212
|
+
healthy: false,
|
|
213
|
+
message: `Ping failed for ${host}.`,
|
|
214
|
+
timestamp: new Date().toISOString()
|
|
215
|
+
},
|
|
216
|
+
meta: response
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
return {
|
|
220
|
+
displayName: `${DISPLAY_NAME4} ${host}`,
|
|
221
|
+
health: {
|
|
222
|
+
healthy: true,
|
|
223
|
+
message: `${DISPLAY_NAME4} ${host} was successful.`,
|
|
224
|
+
timestamp: new Date().toISOString()
|
|
225
|
+
},
|
|
226
|
+
meta: response
|
|
227
|
+
};
|
|
228
|
+
} catch (error) {
|
|
229
|
+
return {
|
|
230
|
+
displayName: `${DISPLAY_NAME4} ${host}`,
|
|
231
|
+
health: {
|
|
232
|
+
healthy: false,
|
|
233
|
+
message: error.message,
|
|
234
|
+
timestamp: new Date().toISOString()
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
var ping_check_default = pingCheck;
|
|
240
|
+
export {
|
|
241
|
+
healthcheck_default2 as HealthCheck,
|
|
242
|
+
dns_check_default as dnsCheck,
|
|
243
|
+
healthcheck_default as healthCheckHandler,
|
|
244
|
+
readyhandler_default as healthReadyHandler,
|
|
245
|
+
http_check_default as httpCheck,
|
|
246
|
+
node_environment_check_default as nodeEnvCheck,
|
|
247
|
+
ping_check_default as pingCheck
|
|
248
|
+
};
|
|
249
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/handler/healthcheck.ts","../src/handler/readyhandler.ts","../src/healthcheck.ts","../src/checks/dns-check.ts","../src/checks/http-check.ts","../src/checks/node-environment-check.ts","../src/checks/ping-check.ts"],"sourcesContent":["import { StatusCodes } from \"http-status-codes\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\n\nimport type { HealthCheck, HealthReport } from \"../types\";\n\nexport default (healthCheck: HealthCheck, sendHeader: boolean | undefined = true) =>\n async <Request extends IncomingMessage, Response extends ServerResponse>(_: Request, response: Response) => {\n const { healthy, report } = await healthCheck.getReport();\n\n const payload: HealthCheckApiPayload = {\n status: healthy ? \"ok\" : \"error\",\n message: healthy ? \"Health check successful\" : \"Health check failed\",\n appName: process.env.APP_NAME ?? \"unknown\",\n appVersion: process.env.APP_VERSION ?? \"unknown\",\n timestamp: new Date().toISOString(),\n reports: report,\n };\n\n response.statusCode = healthy ? StatusCodes.OK : StatusCodes.SERVICE_UNAVAILABLE;\n\n if (sendHeader) {\n response.setHeader(\"Content-Type\", \"application/json\");\n }\n\n response.end(JSON.stringify(payload));\n };\n\nexport type HealthCheckApiPayload = {\n status: \"ok\" | \"error\";\n message: string;\n appName: string;\n appVersion: string;\n timestamp: string;\n reports: HealthReport;\n};\n","import type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { StatusCodes } from \"http-status-codes\";\n\nimport type { HealthCheck } from \"../types\";\n\nexport default <Request extends IncomingMessage, Response extends ServerResponse>(healthCheck: HealthCheck) => async (_request: Request, response: Response) => {\n const { healthy } = await healthCheck.getReport();\n\n response.statusCode = healthy ? StatusCodes.NO_CONTENT : StatusCodes.SERVICE_UNAVAILABLE;\n response.end()\n};\n","import {\n Checker, HealthCheck as HealthcheckInterface, HealthReport, HealthReportEntry,\n} from \"./types\";\n\nclass Healthcheck implements HealthcheckInterface {\n /**\n * A copy of registered checkers\n */\n private healthCheckers: { [service: string]: Checker } = {};\n\n /**\n * Returns an array of registered services names\n */\n public get servicesList(): string[] {\n return Object.keys(this.healthCheckers);\n }\n\n /**\n * Invokes a given checker to collect the report metrics.\n */\n private async invokeChecker(service: string, reportSheet: HealthReport): Promise<boolean> {\n const checker = this.healthCheckers[service] as Checker;\n\n let report: HealthReportEntry;\n\n try {\n report = await checker();\n\n report.displayName = report.displayName || service;\n } catch (error: any) {\n report = {\n displayName: service,\n health: { healthy: false, message: error.message, timestamp: new Date().toISOString() },\n meta: { fatal: true },\n };\n }\n\n // eslint-disable-next-line no-param-reassign\n reportSheet[service] = report;\n\n return report.health.healthy;\n }\n\n public addChecker(service: string, checker: Checker): void {\n this.healthCheckers[service] = checker;\n }\n\n /**\n * Returns the health check reports. The health checks are performed when\n * this method is invoked.\n */\n public async getReport(): Promise<{ healthy: boolean; report: HealthReport }> {\n const report: HealthReport = {};\n\n // eslint-disable-next-line compat/compat\n await Promise.all(Object.keys(this.healthCheckers).map((service) => this.invokeChecker(service, report)));\n\n /**\n * Finding unhealthy service to know if system is healthy or not\n */\n const unhealthyService = Object.keys(report).find((service) => !(report[service] as HealthReportEntry).health.healthy);\n\n return { healthy: !unhealthyService, report };\n }\n\n public async isLive(): Promise<boolean> {\n const { healthy } = await this.getReport();\n\n return healthy;\n }\n}\n\nexport default Healthcheck;\n","import type { IPFamily, Options } from \"cacheable-lookup\";\nimport CacheableLookup from \"cacheable-lookup\";\n\nimport type { Checker } from \"../types\";\n\nconst DISPLAY_NAME = \"DNS check for\";\n\n/**\n * Register the `dns` checker to ensure that a domain is reachable.\n */\nconst dnsCheck = (\n host: string,\n expectedAddresses?: string[],\n options?: {\n family?: IPFamily | \"all\";\n hints?: number;\n } & Options,\n): Checker => async () => {\n const { hints, family = \"all\", ...config } = options || {};\n\n const cacheable = new CacheableLookup(config);\n\n try {\n const meta = await cacheable.lookupAsync(host.replace(/^https?:\\/\\//, \"\"), {\n hints,\n ...(family === \"all\" ? { all: true } : { family: family as IPFamily }),\n });\n\n if (Array.isArray(expectedAddresses) && !expectedAddresses?.includes(meta.address)) {\n return {\n displayName: `${DISPLAY_NAME} ${host}`,\n health: {\n healthy: false,\n message: `${DISPLAY_NAME} ${host} returned address ${meta.address} instead of ${expectedAddresses.join(\", \")}.`,\n timestamp: new Date().toISOString(),\n },\n meta: {\n host,\n addresses: meta,\n },\n };\n }\n\n return {\n displayName: `${DISPLAY_NAME} ${host}`,\n health: {\n healthy: true,\n message: `${DISPLAY_NAME} ${host} were resolved.`,\n timestamp: new Date().toISOString(),\n },\n meta: {\n host,\n addresses: meta,\n },\n };\n } catch (error: any) {\n return {\n displayName: `${DISPLAY_NAME} ${host}`,\n health: {\n healthy: false,\n message: error.message,\n timestamp: new Date().toISOString(),\n },\n meta: {\n host,\n },\n };\n }\n};\n\nexport default dnsCheck;\n","import { deepStrictEqual } from \"node:assert\";\n\nimport type { Checker } from \"../types\";\n\nconst DISPLAY_NAME = \"HTTP check for\";\n\n/**\n * Register the `http` checker to ensure http body and status is correct.\n */\nconst httpCheck =\n (host: RequestInfo | URL, options?: {\n fetchOptions?: RequestInit,\n expected?: { status?: number; body?: string }\n }): Checker =>\n async () => {\n try {\n // eslint-disable-next-line compat/compat\n const response = await fetch(host, options?.fetchOptions || {});\n\n if (typeof options?.expected?.status !== \"undefined\" && options?.expected?.status !== response.status) {\n throw new Error(`${DISPLAY_NAME} ${host} returned status ${response.status} instead of ${options?.expected.status}`);\n }\n\n if (typeof options?.expected?.body !== \"undefined\") {\n const textBody = await response.text();\n\n try {\n deepStrictEqual(textBody, options?.expected.body);\n } catch {\n throw new Error(`${DISPLAY_NAME} ${host} returned body ${JSON.stringify(textBody)} instead of ${JSON.stringify(options?.expected.body)}`);\n }\n }\n\n return {\n displayName: `${DISPLAY_NAME} ${host}`,\n health: {\n healthy: true,\n message: `${DISPLAY_NAME} ${host} was successful.`,\n timestamp: new Date().toISOString(),\n },\n meta: {\n host,\n method: options?.fetchOptions?.method || \"GET\",\n status: response.status,\n },\n };\n } catch (error: any) {\n return {\n displayName: `${DISPLAY_NAME} ${host}`,\n health: {\n healthy: false,\n message: error.message,\n timestamp: new Date().toISOString(),\n },\n meta: {\n host,\n method: options?.fetchOptions?.method || \"GET\",\n },\n };\n }\n };\n\nexport default httpCheck;\n","import type { Checker } from \"../types\";\n\nconst DISPLAY_NAME = \"Node Environment Check\";\n\n/**\n * Register the `env` checker to ensure that `NODE_ENV` environment\n * variable is defined.\n */\nconst nodeEnvironmentCheck = (expectedEnv?: string): Checker => async () => {\n const environment = process.env.NODE_ENV;\n\n let errorMessage: string | undefined;\n\n if (typeof environment !== \"undefined\" && typeof expectedEnv !== \"undefined\" && environment !== expectedEnv) {\n errorMessage = `NODE_ENV environment variable is set to \"${environment}\" instead of \"${expectedEnv}\".`;\n } else if (typeof environment === \"undefined\") {\n errorMessage = [\"Missing NODE_ENV environment variable.\", \"It can make some parts of the application misbehave\"].join(\" \");\n }\n\n if (typeof errorMessage !== \"undefined\") {\n return {\n displayName: DISPLAY_NAME,\n health: {\n healthy: false,\n message: errorMessage,\n timestamp: new Date().toISOString(),\n },\n };\n }\n\n return {\n displayName: DISPLAY_NAME,\n health: {\n healthy: true,\n timestamp: new Date().toISOString(),\n },\n meta: {\n env: environment,\n },\n };\n};\n\nexport default nodeEnvironmentCheck;\n","import type { extendedPingOptions } from \"pingman\";\nimport ping from \"pingman\";\n\nimport type { Checker } from \"../types\";\n\nconst DISPLAY_NAME = \"Ping check for\";\n\n/**\n * Register the `ping` checker to ensure that a domain is reachable.\n */\nconst pingCheck = (host: string, options?: extendedPingOptions): Checker => async () => {\n try {\n const response = await ping(host.replace(/^https?:\\/\\//, \"\"), options);\n\n if (!response.alive) {\n return {\n displayName: `${DISPLAY_NAME} ${host}`,\n health: {\n healthy: false,\n message: `Ping failed for ${host}.`,\n timestamp: new Date().toISOString(),\n },\n meta: response,\n };\n }\n\n return {\n displayName: `${DISPLAY_NAME} ${host}`,\n health: {\n healthy: true,\n message: `${DISPLAY_NAME} ${host} was successful.`,\n timestamp: new Date().toISOString(),\n },\n meta: response,\n };\n } catch (error: any) {\n return {\n displayName: `${DISPLAY_NAME} ${host}`,\n health: {\n healthy: false,\n message: error.message,\n timestamp: new Date().toISOString(),\n },\n };\n }\n};\n\nexport default pingCheck;\n"],"mappings":"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","names":["StatusCodes","healthcheck_default","DISPLAY_NAME","DISPLAY_NAME","DISPLAY_NAME"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@visulima/health-check",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A library built to provide support for defining service health for node services. It allows you to register async health checks for your dependencies and the service itself, provides a health endpoint that exposes their status, and health metrics.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"anolilab",
|
|
7
|
+
"visulima",
|
|
8
|
+
"blitz",
|
|
9
|
+
"blitzjs",
|
|
10
|
+
"nextjs",
|
|
11
|
+
"health",
|
|
12
|
+
"check",
|
|
13
|
+
"checks",
|
|
14
|
+
"dns-check",
|
|
15
|
+
"http-check",
|
|
16
|
+
"ping-check"
|
|
17
|
+
],
|
|
18
|
+
"homepage": "https://visulima.com/packages/health-check",
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "https://github.com/visulima/visulima.git",
|
|
22
|
+
"directory": "packages/health-check"
|
|
23
|
+
},
|
|
24
|
+
"funding": [
|
|
25
|
+
{
|
|
26
|
+
"type": "github",
|
|
27
|
+
"url": "https://github.com/sponsors/prisis"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"type": "consulting",
|
|
31
|
+
"url": "https://anolilab.com/support"
|
|
32
|
+
}
|
|
33
|
+
],
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"author": {
|
|
36
|
+
"name": "Daniel Bannert",
|
|
37
|
+
"email": "d.bannert@anolilab.de"
|
|
38
|
+
},
|
|
39
|
+
"sideEffects": false,
|
|
40
|
+
"exports": {
|
|
41
|
+
".": {
|
|
42
|
+
"types": "./dist/index.d.ts",
|
|
43
|
+
"require": "./dist/index.js",
|
|
44
|
+
"import": "./dist/index.mjs"
|
|
45
|
+
},
|
|
46
|
+
"./package.json": "./package.json"
|
|
47
|
+
},
|
|
48
|
+
"main": "dist/index.js",
|
|
49
|
+
"module": "dist/index.mjs",
|
|
50
|
+
"source": "src/index.ts",
|
|
51
|
+
"types": "dist/index.d.ts",
|
|
52
|
+
"files": [
|
|
53
|
+
"dist/**",
|
|
54
|
+
"README.md",
|
|
55
|
+
"CHANGELOG.md",
|
|
56
|
+
"LICENSE.md"
|
|
57
|
+
],
|
|
58
|
+
"scripts": {
|
|
59
|
+
"build": "cross-env NODE_ENV=development tsup",
|
|
60
|
+
"build:prod": "cross-env NODE_ENV=production tsup",
|
|
61
|
+
"clean": "rimraf node_modules dist",
|
|
62
|
+
"coverage": "vitest run --coverage",
|
|
63
|
+
"dev": "pnpm predev && pnpm run build --watch",
|
|
64
|
+
"lint:eslint": "cross-env NO_LOGS=true eslint --ext js,jsx,ts,tsx --max-warnings=0 --config .eslintrc.cjs",
|
|
65
|
+
"lint:eslint:fix": "pnpm run lint:eslint --fix",
|
|
66
|
+
"test": "vitest"
|
|
67
|
+
},
|
|
68
|
+
"dependencies": {
|
|
69
|
+
"cacheable-lookup": "^7.0.0",
|
|
70
|
+
"http-status-codes": "^2.2.0",
|
|
71
|
+
"pingman": "^1.1.5"
|
|
72
|
+
},
|
|
73
|
+
"devDependencies": {
|
|
74
|
+
"@anolilab/eslint-config": "^4.0.9",
|
|
75
|
+
"@anolilab/semantic-release-preset": "^2.0.7",
|
|
76
|
+
"@rushstack/eslint-plugin-security": "^0.5.0",
|
|
77
|
+
"@types/node": "^18.8.4",
|
|
78
|
+
"@types/react": "^18.0.21",
|
|
79
|
+
"@types/react-dom": "^18.0.6",
|
|
80
|
+
"@typescript-eslint/eslint-plugin": "^5.40.0",
|
|
81
|
+
"@typescript-eslint/parser": "^5.40.0",
|
|
82
|
+
"cross-env": "^7.0.3",
|
|
83
|
+
"cross-fetch": "^3.1.5",
|
|
84
|
+
"eslint": "^8.25.0",
|
|
85
|
+
"eslint-plugin-compat": "^4.0.2",
|
|
86
|
+
"eslint-plugin-eslint-comments": "^3.2.0",
|
|
87
|
+
"eslint-plugin-import": "^2.26.0",
|
|
88
|
+
"eslint-plugin-json": "^3.1.0",
|
|
89
|
+
"eslint-plugin-jsx-a11y": "^6.6.1",
|
|
90
|
+
"eslint-plugin-markdown": "^3.0.0",
|
|
91
|
+
"eslint-plugin-no-loops": "^0.3.0",
|
|
92
|
+
"eslint-plugin-no-secrets": "^0.8.9",
|
|
93
|
+
"eslint-plugin-node": "^11.1.0",
|
|
94
|
+
"eslint-plugin-optimize-regex": "^1.2.1",
|
|
95
|
+
"eslint-plugin-promise": "^6.0.1",
|
|
96
|
+
"eslint-plugin-radar": "^0.2.1",
|
|
97
|
+
"eslint-plugin-react": "7.31.10",
|
|
98
|
+
"eslint-plugin-react-hooks": "4.6.0",
|
|
99
|
+
"eslint-plugin-simple-import-sort": "^8.0.0",
|
|
100
|
+
"eslint-plugin-sort-keys-fix": "^1.1.2",
|
|
101
|
+
"eslint-plugin-testing-library": "^5.7.2",
|
|
102
|
+
"eslint-plugin-unicorn": "^44.0.2",
|
|
103
|
+
"eslint-plugin-you-dont-need-lodash-underscore": "^6.12.0",
|
|
104
|
+
"eslint-plugin-you-dont-need-momentjs": "^1.6.0",
|
|
105
|
+
"next": "^12.3.1",
|
|
106
|
+
"next-test-api-route-handler": "^4.0.0-canary.1",
|
|
107
|
+
"node-mocks-http": "^1.11.0",
|
|
108
|
+
"prettier": "^2.7.1",
|
|
109
|
+
"react": "^18.2.0",
|
|
110
|
+
"react-dom": "^18.2.0",
|
|
111
|
+
"read-pkg": "^7.1.0",
|
|
112
|
+
"rimraf": "^3.0.2",
|
|
113
|
+
"semantic-release": "^19.0.5",
|
|
114
|
+
"tsup": "^6.2.3",
|
|
115
|
+
"typescript": "^4.8.4",
|
|
116
|
+
"vitest": "^0.24.1"
|
|
117
|
+
},
|
|
118
|
+
"optionalDependencies": {
|
|
119
|
+
"next": "^12.3.1"
|
|
120
|
+
},
|
|
121
|
+
"engines": {
|
|
122
|
+
"node": ">=16"
|
|
123
|
+
},
|
|
124
|
+
"publishConfig": {
|
|
125
|
+
"access": "public"
|
|
126
|
+
}
|
|
127
|
+
}
|