@entwico/health-probes 1.1.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/LICENSE +21 -0
- package/README.md +320 -0
- package/dist/checks.d.ts +12 -0
- package/dist/checks.d.ts.map +1 -0
- package/dist/checks.js +63 -0
- package/dist/checks.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/paths.d.ts +4 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +13 -0
- package/dist/paths.js.map +1 -0
- package/dist/probes.d.ts +11 -0
- package/dist/probes.d.ts.map +1 -0
- package/dist/probes.js +87 -0
- package/dist/probes.js.map +1 -0
- package/dist/server.d.ts +9 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +66 -0
- package/dist/server.js.map +1 -0
- package/dist/types.d.ts +45 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +73 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 entwico GmbH
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
# health-probes
|
|
2
|
+
|
|
3
|
+
Health check probes for Node.js. Provides liveness, readiness, startup, and health status endpoints on a separate HTTP server. Framework-agnostic — works with any Node.js application.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @entwico/health-probes
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { checks, probes, server } from '@entwico/health-probes';
|
|
15
|
+
|
|
16
|
+
// start health server on a separate port
|
|
17
|
+
// default host is '127.0.0.1' for security reasons
|
|
18
|
+
// use host: '0.0.0.0' in k8s containers so the kubelet can reach the probes
|
|
19
|
+
server.start({ host: '0.0.0.0', port: 9090 });
|
|
20
|
+
|
|
21
|
+
// enable liveness immediately
|
|
22
|
+
probes.live.enable();
|
|
23
|
+
|
|
24
|
+
// initialize your app...
|
|
25
|
+
await connectToDatabase();
|
|
26
|
+
|
|
27
|
+
// register health checks
|
|
28
|
+
checks.register('database', () => db.ping());
|
|
29
|
+
|
|
30
|
+
// enable startup probe (initialization complete)
|
|
31
|
+
probes.startup.enable();
|
|
32
|
+
|
|
33
|
+
// enable readiness probe (ready for traffic)
|
|
34
|
+
probes.ready.enable();
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Graceful shutdown
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
// disable readiness first (stop receiving traffic)
|
|
41
|
+
probes.ready.disable();
|
|
42
|
+
|
|
43
|
+
await disconnectFromDatabase();
|
|
44
|
+
|
|
45
|
+
// stop health server
|
|
46
|
+
await server.stop();
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Probes
|
|
50
|
+
|
|
51
|
+
### Liveness Probe
|
|
52
|
+
|
|
53
|
+
Indicates if the process is running. If this fails, the orchestrator should restart the container.
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
probes.live.enable(); // returns 200 OK
|
|
57
|
+
probes.live.disable(); // returns 503 Service Unavailable
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Startup Probe
|
|
61
|
+
|
|
62
|
+
Indicates if the application has finished initializing.
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
probes.startup.enable();
|
|
66
|
+
probes.startup.disable();
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Readiness Probe
|
|
70
|
+
|
|
71
|
+
Indicates if the application is ready to receive traffic. When disabled or when required health checks fail, returns 503.
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
probes.ready.enable();
|
|
75
|
+
probes.ready.disable();
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
The readiness probe automatically runs all non-optional health checks and returns 503 if any fail.
|
|
79
|
+
|
|
80
|
+
### Health Status
|
|
81
|
+
|
|
82
|
+
Returns detailed JSON status of all probes and health checks. Useful for debugging and dashboards.
|
|
83
|
+
|
|
84
|
+
```json
|
|
85
|
+
{
|
|
86
|
+
"status": "healthy",
|
|
87
|
+
"probes": {
|
|
88
|
+
"live": true,
|
|
89
|
+
"startup": true,
|
|
90
|
+
"ready": true
|
|
91
|
+
},
|
|
92
|
+
"checks": {
|
|
93
|
+
"database": {
|
|
94
|
+
"status": "healthy",
|
|
95
|
+
"latency": 12
|
|
96
|
+
},
|
|
97
|
+
"redis": {
|
|
98
|
+
"status": "unhealthy",
|
|
99
|
+
"latency": 5003,
|
|
100
|
+
"error": "check \"redis\" timed out after 5000ms"
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Status values:
|
|
107
|
+
|
|
108
|
+
- `healthy` — all checks pass
|
|
109
|
+
- `degraded` — optional checks failing, required checks pass
|
|
110
|
+
- `unhealthy` — required checks failing
|
|
111
|
+
|
|
112
|
+
## Health Checks
|
|
113
|
+
|
|
114
|
+
Register health checks to verify dependencies are working:
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
import { checks } from '@entwico/health-probes';
|
|
118
|
+
|
|
119
|
+
// return result (recommended for boolean checks)
|
|
120
|
+
checks.register('database', () => ({
|
|
121
|
+
status: db.isConnected() ? 'healthy' : 'unhealthy',
|
|
122
|
+
error: db.isConnected() ? undefined : 'connection lost',
|
|
123
|
+
}));
|
|
124
|
+
|
|
125
|
+
// throw-based (classic pattern)
|
|
126
|
+
checks.register('cache', async () => {
|
|
127
|
+
await redis.ping(); // throws if fails
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// with options
|
|
131
|
+
checks.register({
|
|
132
|
+
name: 'external-api',
|
|
133
|
+
check: () => fetch('https://api.example.com/health').then(() => {}),
|
|
134
|
+
optional: true, // doesn't affect readiness, only health status
|
|
135
|
+
timeout: 10000, // custom timeout (default: 5000ms)
|
|
136
|
+
});
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
The check function can either:
|
|
140
|
+
|
|
141
|
+
- Return `HealthCheckResult` with status and optional error
|
|
142
|
+
- Return `void` (completing without error = healthy)
|
|
143
|
+
- Throw an error (= unhealthy with error message)
|
|
144
|
+
|
|
145
|
+
### Unregistering Checks
|
|
146
|
+
|
|
147
|
+
`register()` returns an unregister function:
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
const unregister = checks.register({
|
|
151
|
+
name: 'database',
|
|
152
|
+
check: () => db.ping(),
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// later...
|
|
156
|
+
unregister();
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Server Options
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
server.start({
|
|
163
|
+
host: '0.0.0.0', // default: '127.0.0.1'
|
|
164
|
+
port: 9090, // default: 9090
|
|
165
|
+
});
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Resolution order: **explicit option > environment variable > default**.
|
|
169
|
+
|
|
170
|
+
| Setting | Env Variable | Default |
|
|
171
|
+
| ------- | --------------- | ------------- |
|
|
172
|
+
| host | `HEALTH_HOST` | `127.0.0.1` |
|
|
173
|
+
| port | `HEALTH_PORT` | `9090` |
|
|
174
|
+
|
|
175
|
+
This means `server.start()` with no arguments will pick up `HEALTH_HOST` / `HEALTH_PORT` from the environment if set.
|
|
176
|
+
|
|
177
|
+
### Custom Paths
|
|
178
|
+
|
|
179
|
+
By default, endpoints use Kubernetes-style paths (`/livez`, `/readyz`, `/startupz`, `/healthz`). You can customize them:
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
import { SimplePaths } from '@entwico/health-probes';
|
|
183
|
+
|
|
184
|
+
// use simple paths: /live, /ready, /startup, /health
|
|
185
|
+
server.start({ paths: SimplePaths });
|
|
186
|
+
|
|
187
|
+
// or mix and match
|
|
188
|
+
server.start({
|
|
189
|
+
paths: {
|
|
190
|
+
live: '/healthcheck',
|
|
191
|
+
ready: '/ready',
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Built-in path presets:
|
|
197
|
+
|
|
198
|
+
| Preset | live | startup | ready | health |
|
|
199
|
+
| -------------------- | -------- | ----------- | --------- | ---------- |
|
|
200
|
+
| `K8sPaths` (default) | `/livez` | `/startupz` | `/readyz` | `/healthz` |
|
|
201
|
+
| `SimplePaths` | `/live` | `/startup` | `/ready` | `/health` |
|
|
202
|
+
|
|
203
|
+
## Kubernetes Configuration
|
|
204
|
+
|
|
205
|
+
```yaml
|
|
206
|
+
apiVersion: v1
|
|
207
|
+
kind: Pod
|
|
208
|
+
spec:
|
|
209
|
+
containers:
|
|
210
|
+
- name: app
|
|
211
|
+
ports:
|
|
212
|
+
- containerPort: 3000 # app
|
|
213
|
+
- containerPort: 9090 # health
|
|
214
|
+
livenessProbe:
|
|
215
|
+
httpGet:
|
|
216
|
+
path: /livez
|
|
217
|
+
port: 9090
|
|
218
|
+
initialDelaySeconds: 0
|
|
219
|
+
periodSeconds: 10
|
|
220
|
+
startupProbe:
|
|
221
|
+
httpGet:
|
|
222
|
+
path: /startupz
|
|
223
|
+
port: 9090
|
|
224
|
+
failureThreshold: 30
|
|
225
|
+
periodSeconds: 2
|
|
226
|
+
readinessProbe:
|
|
227
|
+
httpGet:
|
|
228
|
+
path: /readyz
|
|
229
|
+
port: 9090
|
|
230
|
+
periodSeconds: 5
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## API Reference
|
|
234
|
+
|
|
235
|
+
### Server
|
|
236
|
+
|
|
237
|
+
```ts
|
|
238
|
+
import { server } from '@entwico/health-probes';
|
|
239
|
+
|
|
240
|
+
server.start(options?: HealthServerOptions): void;
|
|
241
|
+
server.stop(): Promise<void>;
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Probes
|
|
245
|
+
|
|
246
|
+
```ts
|
|
247
|
+
import { probes } from '@entwico/health-probes';
|
|
248
|
+
|
|
249
|
+
probes.live.enable(): void;
|
|
250
|
+
probes.live.disable(): void;
|
|
251
|
+
probes.live.get(): Promise<HealthProbeResult>;
|
|
252
|
+
probes.live.response(): Promise<Response>;
|
|
253
|
+
|
|
254
|
+
// same for startup, ready
|
|
255
|
+
|
|
256
|
+
probes.health.get(): Promise<HealthResult>;
|
|
257
|
+
probes.health.response(): Promise<Response>;
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Checks
|
|
261
|
+
|
|
262
|
+
```ts
|
|
263
|
+
import { checks } from '@entwico/health-probes';
|
|
264
|
+
|
|
265
|
+
checks.register(name: string, check: CheckFn): () => void;
|
|
266
|
+
checks.register(check: HealthCheck): () => void;
|
|
267
|
+
|
|
268
|
+
// CheckFn = () => Promise<HealthCheckResult | void> | HealthCheckResult | void
|
|
269
|
+
checks.getChecks(): HealthCheck[];
|
|
270
|
+
checks.runAll(): Promise<Record<string, HealthCheckResult>>;
|
|
271
|
+
checks.runRequired(): Promise<boolean>;
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
## Types
|
|
275
|
+
|
|
276
|
+
```ts
|
|
277
|
+
interface ProbePaths {
|
|
278
|
+
live?: string;
|
|
279
|
+
startup?: string;
|
|
280
|
+
ready?: string;
|
|
281
|
+
health?: string;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
interface HealthServerOptions {
|
|
285
|
+
host?: string; // env: HEALTH_HOST, default: '127.0.0.1'
|
|
286
|
+
port?: number; // env: HEALTH_PORT, default: 9090
|
|
287
|
+
paths?: ProbePaths; // default: K8sPaths
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
interface HealthCheck {
|
|
291
|
+
name: string;
|
|
292
|
+
check: () => Promise<HealthCheckResult | void> | HealthCheckResult | void;
|
|
293
|
+
optional?: boolean; // default: false
|
|
294
|
+
timeout?: number; // default: 5000
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
interface HealthCheckResult {
|
|
298
|
+
status: 'healthy' | 'unhealthy';
|
|
299
|
+
latency?: number;
|
|
300
|
+
error?: string;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
interface HealthProbeResult {
|
|
304
|
+
passing: boolean;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
interface HealthResult {
|
|
308
|
+
status: 'healthy' | 'degraded' | 'unhealthy';
|
|
309
|
+
probes: {
|
|
310
|
+
live: boolean;
|
|
311
|
+
startup: boolean;
|
|
312
|
+
ready: boolean;
|
|
313
|
+
};
|
|
314
|
+
checks: Record<string, HealthCheckResult>;
|
|
315
|
+
}
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
## License
|
|
319
|
+
|
|
320
|
+
MIT
|
package/dist/checks.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { HealthCheck, HealthCheckResult } from './types.js';
|
|
2
|
+
export declare class HealthChecks {
|
|
3
|
+
private readonly registered;
|
|
4
|
+
register(name: string, check: () => Promise<HealthCheckResult | void> | HealthCheckResult | void): () => void;
|
|
5
|
+
register(check: HealthCheck): () => void;
|
|
6
|
+
private run;
|
|
7
|
+
runAll(): Promise<Record<string, HealthCheckResult>>;
|
|
8
|
+
runRequired(): Promise<boolean>;
|
|
9
|
+
getChecks(): HealthCheck[];
|
|
10
|
+
}
|
|
11
|
+
export declare const checks: HealthChecks;
|
|
12
|
+
//# sourceMappingURL=checks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checks.d.ts","sourceRoot":"","sources":["../src/checks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAOjE,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAkC;IAM7D,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,GAAG,iBAAiB,GAAG,IAAI,GAAG,MAAM,IAAI;IAC7G,QAAQ,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,IAAI;YAqB1B,GAAG;IA8BX,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;IAkBpD,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IAqBrC,SAAS,IAAI,WAAW,EAAE;CAG3B;AAED,eAAO,MAAM,MAAM,cAAqB,CAAC"}
|
package/dist/checks.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
const DEFAULT_TIMEOUT = 5000;
|
|
2
|
+
export class HealthChecks {
|
|
3
|
+
registered = new Map();
|
|
4
|
+
register(nameOrCheck, checkFn) {
|
|
5
|
+
const check = typeof nameOrCheck === 'string' ? { name: nameOrCheck, check: checkFn } : nameOrCheck;
|
|
6
|
+
if (this.registered.has(check.name)) {
|
|
7
|
+
console.warn(`[health] overwriting existing check "${check.name}"`);
|
|
8
|
+
}
|
|
9
|
+
this.registered.set(check.name, check);
|
|
10
|
+
return () => {
|
|
11
|
+
this.registered.delete(check.name);
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
async run(check) {
|
|
15
|
+
const timeout = check.timeout ?? DEFAULT_TIMEOUT;
|
|
16
|
+
const start = performance.now();
|
|
17
|
+
try {
|
|
18
|
+
const result = await Promise.race([
|
|
19
|
+
Promise.resolve(check.check()),
|
|
20
|
+
new Promise((_, reject) => {
|
|
21
|
+
setTimeout(() => reject(new Error(`check "${check.name}" timed out after ${timeout}ms`)), timeout);
|
|
22
|
+
}),
|
|
23
|
+
]);
|
|
24
|
+
const latency = Math.round(performance.now() - start);
|
|
25
|
+
if (result) {
|
|
26
|
+
return { ...result, latency };
|
|
27
|
+
}
|
|
28
|
+
return { status: 'healthy', latency };
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
const latency = Math.round(performance.now() - start);
|
|
32
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
33
|
+
return { status: 'unhealthy', latency, error: errorMessage };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
async runAll() {
|
|
37
|
+
const results = {};
|
|
38
|
+
const checks = [...this.registered.values()];
|
|
39
|
+
const checkPromises = checks.map(async (check) => {
|
|
40
|
+
const result = await this.run(check);
|
|
41
|
+
results[check.name] = result;
|
|
42
|
+
});
|
|
43
|
+
await Promise.all(checkPromises);
|
|
44
|
+
return results;
|
|
45
|
+
}
|
|
46
|
+
async runRequired() {
|
|
47
|
+
const requiredChecks = [...this.registered.values()].filter((check) => !check.optional);
|
|
48
|
+
if (requiredChecks.length === 0) {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
const checkPromises = requiredChecks.map(async (check) => {
|
|
52
|
+
const result = await this.run(check);
|
|
53
|
+
return result.status === 'healthy';
|
|
54
|
+
});
|
|
55
|
+
const results = await Promise.all(checkPromises);
|
|
56
|
+
return results.every(Boolean);
|
|
57
|
+
}
|
|
58
|
+
getChecks() {
|
|
59
|
+
return [...this.registered.values()];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
export const checks = new HealthChecks();
|
|
63
|
+
//# sourceMappingURL=checks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checks.js","sourceRoot":"","sources":["../src/checks.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { server } from './server.js';
|
|
2
|
+
export { probes } from './probes.js';
|
|
3
|
+
export { checks } from './checks.js';
|
|
4
|
+
export { HealthServer } from './server.js';
|
|
5
|
+
export { HealthProbes } from './probes.js';
|
|
6
|
+
export { HealthChecks } from './checks.js';
|
|
7
|
+
export { K8sPaths, SimplePaths } from './paths.js';
|
|
8
|
+
export type { HealthCheck, HealthCheckResult, HealthProbe, HealthProbeResult, HealthResult, HealthServerOptions, HealthStatusProbe, ProbePaths, } from './types.js';
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGrC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3C,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAGnD,YAAY,EACV,WAAW,EACX,iBAAiB,EACjB,WAAW,EACX,iBAAiB,EACjB,YAAY,EACZ,mBAAmB,EACnB,iBAAiB,EACjB,UAAU,GACX,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { server } from './server.js';
|
|
2
|
+
export { probes } from './probes.js';
|
|
3
|
+
export { checks } from './checks.js';
|
|
4
|
+
export { HealthServer } from './server.js';
|
|
5
|
+
export { HealthProbes } from './probes.js';
|
|
6
|
+
export { HealthChecks } from './checks.js';
|
|
7
|
+
export { K8sPaths, SimplePaths } from './paths.js';
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGrC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3C,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/paths.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAM7C,eAAO,MAAM,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAKzC,CAAC;AAKF,eAAO,MAAM,WAAW,EAAE,QAAQ,CAAC,UAAU,CAK5C,CAAC"}
|
package/dist/paths.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const K8sPaths = {
|
|
2
|
+
live: '/livez',
|
|
3
|
+
startup: '/startupz',
|
|
4
|
+
ready: '/readyz',
|
|
5
|
+
health: '/healthz',
|
|
6
|
+
};
|
|
7
|
+
export const SimplePaths = {
|
|
8
|
+
live: '/live',
|
|
9
|
+
startup: '/startup',
|
|
10
|
+
ready: '/ready',
|
|
11
|
+
health: '/health',
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=paths.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAMA,MAAM,CAAC,MAAM,QAAQ,GAAyB;IAC5C,IAAI,EAAE,QAAQ;IACd,OAAO,EAAE,WAAW;IACpB,KAAK,EAAE,SAAS;IAChB,MAAM,EAAE,UAAU;CACnB,CAAC;AAKF,MAAM,CAAC,MAAM,WAAW,GAAyB;IAC/C,IAAI,EAAE,OAAO;IACb,OAAO,EAAE,UAAU;IACnB,KAAK,EAAE,QAAQ;IACf,MAAM,EAAE,SAAS;CAClB,CAAC"}
|
package/dist/probes.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { HealthProbe, HealthStatusProbe } from './types.js';
|
|
2
|
+
export declare class HealthProbes {
|
|
3
|
+
private state;
|
|
4
|
+
private createProbeResponse;
|
|
5
|
+
readonly live: HealthProbe;
|
|
6
|
+
readonly startup: HealthProbe;
|
|
7
|
+
readonly ready: HealthProbe;
|
|
8
|
+
readonly health: HealthStatusProbe;
|
|
9
|
+
}
|
|
10
|
+
export declare const probes: HealthProbes;
|
|
11
|
+
//# sourceMappingURL=probes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"probes.d.ts","sourceRoot":"","sources":["../src/probes.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAmC,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAKlG,qBAAa,YAAY;IACvB,OAAO,CAAC,KAAK,CAIX;IAEF,OAAO,CAAC,mBAAmB;IAO3B,QAAQ,CAAC,IAAI,EAAE,WAAW,CAaxB;IAEF,QAAQ,CAAC,OAAO,EAAE,WAAW,CAa3B;IAEF,QAAQ,CAAC,KAAK,EAAE,WAAW,CAazB;IAEF,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAiChC;CACH;AAED,eAAO,MAAM,MAAM,cAAqB,CAAC"}
|
package/dist/probes.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { checks } from './checks.js';
|
|
2
|
+
export class HealthProbes {
|
|
3
|
+
state = {
|
|
4
|
+
live: false,
|
|
5
|
+
startup: false,
|
|
6
|
+
ready: false,
|
|
7
|
+
};
|
|
8
|
+
createProbeResponse(result) {
|
|
9
|
+
return new Response(result.passing ? 'OK' : 'Service Unavailable', {
|
|
10
|
+
status: result.passing ? 200 : 503,
|
|
11
|
+
headers: { 'Content-Type': 'text/plain' },
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
live = {
|
|
15
|
+
enable: () => {
|
|
16
|
+
this.state.live = true;
|
|
17
|
+
},
|
|
18
|
+
disable: () => {
|
|
19
|
+
this.state.live = false;
|
|
20
|
+
},
|
|
21
|
+
get: async () => {
|
|
22
|
+
return { passing: this.state.live };
|
|
23
|
+
},
|
|
24
|
+
response: async () => {
|
|
25
|
+
return this.createProbeResponse(await this.live.get());
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
startup = {
|
|
29
|
+
enable: () => {
|
|
30
|
+
this.state.startup = true;
|
|
31
|
+
},
|
|
32
|
+
disable: () => {
|
|
33
|
+
this.state.startup = false;
|
|
34
|
+
},
|
|
35
|
+
get: async () => {
|
|
36
|
+
return { passing: this.state.startup };
|
|
37
|
+
},
|
|
38
|
+
response: async () => {
|
|
39
|
+
return this.createProbeResponse(await this.startup.get());
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
ready = {
|
|
43
|
+
enable: () => {
|
|
44
|
+
this.state.ready = true;
|
|
45
|
+
},
|
|
46
|
+
disable: () => {
|
|
47
|
+
this.state.ready = false;
|
|
48
|
+
},
|
|
49
|
+
get: async () => {
|
|
50
|
+
return { passing: this.state.ready ? await checks.runRequired() : false };
|
|
51
|
+
},
|
|
52
|
+
response: async () => {
|
|
53
|
+
return this.createProbeResponse(await this.ready.get());
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
health = {
|
|
57
|
+
get: async () => {
|
|
58
|
+
const checkResults = await checks.runAll();
|
|
59
|
+
const registered = checks.getChecks();
|
|
60
|
+
const hasUnhealthy = Object.values(checkResults).some((c) => c.status === 'unhealthy');
|
|
61
|
+
const hasRequiredUnhealthy = registered
|
|
62
|
+
.filter((check) => !check.optional)
|
|
63
|
+
.some((check) => checkResults[check.name]?.status !== 'healthy');
|
|
64
|
+
let status = 'healthy';
|
|
65
|
+
if (hasRequiredUnhealthy) {
|
|
66
|
+
status = 'unhealthy';
|
|
67
|
+
}
|
|
68
|
+
else if (hasUnhealthy) {
|
|
69
|
+
status = 'degraded';
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
status,
|
|
73
|
+
probes: { live: this.state.live, startup: this.state.startup, ready: this.state.ready },
|
|
74
|
+
checks: checkResults,
|
|
75
|
+
};
|
|
76
|
+
},
|
|
77
|
+
response: async () => {
|
|
78
|
+
const result = await this.health.get();
|
|
79
|
+
return new Response(JSON.stringify(result, null, 2), {
|
|
80
|
+
status: 200,
|
|
81
|
+
headers: { 'Content-Type': 'application/json' },
|
|
82
|
+
});
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
export const probes = new HealthProbes();
|
|
87
|
+
//# sourceMappingURL=probes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"probes.js","sourceRoot":"","sources":["../src/probes.ts"],"names":[],"mappings":"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"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { HealthServerOptions } from './types.js';
|
|
2
|
+
export declare class HealthServer {
|
|
3
|
+
private instance;
|
|
4
|
+
start(options?: HealthServerOptions): void;
|
|
5
|
+
stop(): Promise<void>;
|
|
6
|
+
private handleRequest;
|
|
7
|
+
}
|
|
8
|
+
export declare const server: HealthServer;
|
|
9
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,mBAAmB,EAAc,MAAM,YAAY,CAAC;AAKlE,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAuB;IAKvC,KAAK,CAAC,OAAO,GAAE,mBAAwB,GAAG,IAAI;IAqC9C,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YAmBP,aAAa;CAc5B;AAED,eAAO,MAAM,MAAM,cAAqB,CAAC"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { createServer } from 'node:http';
|
|
2
|
+
import { K8sPaths } from './paths.js';
|
|
3
|
+
import { probes } from './probes.js';
|
|
4
|
+
export class HealthServer {
|
|
5
|
+
instance = null;
|
|
6
|
+
start(options = {}) {
|
|
7
|
+
if (this.instance) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
const host = options.host ?? process.env['HEALTH_HOST'] ?? '127.0.0.1';
|
|
11
|
+
const port = options.port ?? (process.env['HEALTH_PORT'] ? Number(process.env['HEALTH_PORT']) : 9090);
|
|
12
|
+
const paths = { ...K8sPaths, ...options.paths };
|
|
13
|
+
const instance = createServer(async (req, res) => {
|
|
14
|
+
try {
|
|
15
|
+
const url = new URL(req.url ?? '/', `http://${req.headers.host ?? 'localhost'}`);
|
|
16
|
+
const response = await this.handleRequest(url.pathname, paths);
|
|
17
|
+
res.statusCode = response.status;
|
|
18
|
+
for (const [key, value] of response.headers.entries()) {
|
|
19
|
+
res.setHeader(key, value);
|
|
20
|
+
}
|
|
21
|
+
const body = await response.text();
|
|
22
|
+
res.end(body);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
res.statusCode = 500;
|
|
26
|
+
res.setHeader('Content-Type', 'text/plain');
|
|
27
|
+
res.end('Internal Server Error');
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
instance.listen(port, host);
|
|
31
|
+
this.instance = instance;
|
|
32
|
+
}
|
|
33
|
+
stop() {
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
if (!this.instance) {
|
|
36
|
+
resolve();
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
this.instance.close((err) => {
|
|
40
|
+
if (err) {
|
|
41
|
+
reject(err);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
this.instance = null;
|
|
45
|
+
resolve();
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
async handleRequest(pathname, paths) {
|
|
51
|
+
switch (pathname) {
|
|
52
|
+
case paths.live:
|
|
53
|
+
return probes.live.response();
|
|
54
|
+
case paths.startup:
|
|
55
|
+
return probes.startup.response();
|
|
56
|
+
case paths.ready:
|
|
57
|
+
return probes.ready.response();
|
|
58
|
+
case paths.health:
|
|
59
|
+
return probes.health.response();
|
|
60
|
+
default:
|
|
61
|
+
return new Response('Not Found', { status: 404 });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
export const server = new HealthServer();
|
|
66
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export interface ProbePaths {
|
|
2
|
+
live?: string | undefined;
|
|
3
|
+
startup?: string | undefined;
|
|
4
|
+
ready?: string | undefined;
|
|
5
|
+
health?: string | undefined;
|
|
6
|
+
}
|
|
7
|
+
export interface HealthServerOptions {
|
|
8
|
+
host?: string | undefined;
|
|
9
|
+
port?: number | undefined;
|
|
10
|
+
paths?: ProbePaths | undefined;
|
|
11
|
+
}
|
|
12
|
+
export interface HealthCheck {
|
|
13
|
+
name: string;
|
|
14
|
+
check: () => Promise<HealthCheckResult | void> | HealthCheckResult | void;
|
|
15
|
+
optional?: boolean | undefined;
|
|
16
|
+
timeout?: number | undefined;
|
|
17
|
+
}
|
|
18
|
+
export interface HealthCheckResult {
|
|
19
|
+
status: 'healthy' | 'unhealthy';
|
|
20
|
+
latency?: number | undefined;
|
|
21
|
+
error?: string | undefined;
|
|
22
|
+
}
|
|
23
|
+
export interface HealthProbeResult {
|
|
24
|
+
passing: boolean;
|
|
25
|
+
}
|
|
26
|
+
export interface HealthResult {
|
|
27
|
+
status: 'healthy' | 'degraded' | 'unhealthy';
|
|
28
|
+
probes: {
|
|
29
|
+
live: boolean;
|
|
30
|
+
startup: boolean;
|
|
31
|
+
ready: boolean;
|
|
32
|
+
};
|
|
33
|
+
checks: Record<string, HealthCheckResult>;
|
|
34
|
+
}
|
|
35
|
+
export interface HealthProbe {
|
|
36
|
+
enable(): void;
|
|
37
|
+
disable(): void;
|
|
38
|
+
get(): Promise<HealthProbeResult>;
|
|
39
|
+
response(): Promise<Response>;
|
|
40
|
+
}
|
|
41
|
+
export interface HealthStatusProbe {
|
|
42
|
+
get(): Promise<HealthResult>;
|
|
43
|
+
response(): Promise<Response>;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,UAAU;IACzB,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7B;AAKD,MAAM,WAAW,mBAAmB;IAMlC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAO1B,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAM1B,KAAK,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC;CAChC;AAKD,MAAM,WAAW,WAAW;IAI1B,IAAI,EAAE,MAAM,CAAC;IAOb,KAAK,EAAE,MAAM,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,GAAG,iBAAiB,GAAG,IAAI,CAAC;IAO1E,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAM/B,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC9B;AAKD,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,SAAS,GAAG,WAAW,CAAC;IAChC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC5B;AAKD,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,OAAO,CAAC;CAClB;AAKD,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,SAAS,GAAG,UAAU,GAAG,WAAW,CAAC;IAC7C,MAAM,EAAE;QACN,IAAI,EAAE,OAAO,CAAC;QACd,OAAO,EAAE,OAAO,CAAC;QACjB,KAAK,EAAE,OAAO,CAAC;KAChB,CAAC;IACF,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;CAC3C;AAKD,MAAM,WAAW,WAAW;IAI1B,MAAM,IAAI,IAAI,CAAC;IAKf,OAAO,IAAI,IAAI,CAAC;IAKhB,GAAG,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAKlC,QAAQ,IAAI,OAAO,CAAC,QAAQ,CAAC,CAAC;CAC/B;AAKD,MAAM,WAAW,iBAAiB;IAIhC,GAAG,IAAI,OAAO,CAAC,YAAY,CAAC,CAAC;IAK7B,QAAQ,IAAI,OAAO,CAAC,QAAQ,CAAC,CAAC;CAC/B"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@entwico/health-probes",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Kubernetes-style health check probes for Node.js — framework-agnostic /livez, /readyz, /startupz, and /healthz endpoints",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "admin@entwico.com",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"module": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"README.md",
|
|
20
|
+
"LICENSE"
|
|
21
|
+
],
|
|
22
|
+
"keywords": [
|
|
23
|
+
"health",
|
|
24
|
+
"healthcheck",
|
|
25
|
+
"probes",
|
|
26
|
+
"kubernetes",
|
|
27
|
+
"k8s",
|
|
28
|
+
"livez",
|
|
29
|
+
"readyz",
|
|
30
|
+
"startupz",
|
|
31
|
+
"healthz",
|
|
32
|
+
"typescript"
|
|
33
|
+
],
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "https://github.com/entwico/health-probes"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsc --project tsconfig.build.json",
|
|
40
|
+
"clean": "rm -rf dist",
|
|
41
|
+
"prebuild": "npm run clean",
|
|
42
|
+
"prepublishOnly": "npm run build && npm run test && npm run lint",
|
|
43
|
+
"validate": "run-s lint typecheck test",
|
|
44
|
+
"lint": "eslint . --report-unused-disable-directives --max-warnings 0",
|
|
45
|
+
"typecheck": "tsc",
|
|
46
|
+
"test": "npx tsx --test --experimental-test-coverage src/index.test.ts",
|
|
47
|
+
"prepare": "husky",
|
|
48
|
+
"semantic-release": "semantic-release"
|
|
49
|
+
},
|
|
50
|
+
"publishConfig": {
|
|
51
|
+
"access": "public",
|
|
52
|
+
"provenance": true
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@commitlint/cli": "^20.4.1",
|
|
56
|
+
"@commitlint/config-conventional": "^20.4.1",
|
|
57
|
+
"@semantic-release/changelog": "^6.0.3",
|
|
58
|
+
"@semantic-release/git": "^10.0.1",
|
|
59
|
+
"@types/node": "^25.2.0",
|
|
60
|
+
"@typescript-eslint/eslint-plugin": "^8.54.0",
|
|
61
|
+
"eslint": "^9.39.2",
|
|
62
|
+
"eslint-config-prettier": "^10.1.8",
|
|
63
|
+
"eslint-import-resolver-typescript": "^4.4.4",
|
|
64
|
+
"eslint-plugin-import": "^2.32.0",
|
|
65
|
+
"eslint-plugin-prettier": "^5.5.5",
|
|
66
|
+
"husky": "^9.1.7",
|
|
67
|
+
"npm-run-all": "^4.1.5",
|
|
68
|
+
"semantic-release": "^25.0.3",
|
|
69
|
+
"tsx": "^4.21.0",
|
|
70
|
+
"typescript": "^5.9.3",
|
|
71
|
+
"typescript-eslint": "^8.54.0"
|
|
72
|
+
}
|
|
73
|
+
}
|