@sebspark/health-check 0.1.4 → 1.0.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/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +245 -38
- package/dist/index.mjs +244 -38
- package/package.json +2 -2
package/dist/index.d.mts
CHANGED
|
@@ -375,4 +375,4 @@ declare class HealthMonitor {
|
|
|
375
375
|
dispose(): void;
|
|
376
376
|
}
|
|
377
377
|
|
|
378
|
-
export { type CheckError, type DependencyCheck, type DependencyStatusValue, type Freshness, HealthMonitor, type HealthMonitorConfig, type HealthSummary, type Impact, type Liveness, type Mode, type Observed, type Process, type ReadinessPayload, type ReadinessSummary, type Status, type StatusValue, type System, TimeoutError, UnknownError };
|
|
378
|
+
export { type AsyncConfig, type CheckError, type DependencyCheck, DependencyMonitor, type DependencyMonitorConfig, type DependencyStatusValue, type Freshness, HealthMonitor, type HealthMonitorConfig, type HealthSummary, type Impact, type Liveness, type Mode, type Observed, type Process, type ReadinessPayload, type ReadinessSummary, type Status, type StatusValue, type SyncInlineConfig, type SyncPolledConfig, type System, TimeoutError, UnknownError };
|
package/dist/index.d.ts
CHANGED
|
@@ -375,4 +375,4 @@ declare class HealthMonitor {
|
|
|
375
375
|
dispose(): void;
|
|
376
376
|
}
|
|
377
377
|
|
|
378
|
-
export { type CheckError, type DependencyCheck, type DependencyStatusValue, type Freshness, HealthMonitor, type HealthMonitorConfig, type HealthSummary, type Impact, type Liveness, type Mode, type Observed, type Process, type ReadinessPayload, type ReadinessSummary, type Status, type StatusValue, type System, TimeoutError, UnknownError };
|
|
378
|
+
export { type AsyncConfig, type CheckError, type DependencyCheck, DependencyMonitor, type DependencyMonitorConfig, type DependencyStatusValue, type Freshness, HealthMonitor, type HealthMonitorConfig, type HealthSummary, type Impact, type Liveness, type Mode, type Observed, type Process, type ReadinessPayload, type ReadinessSummary, type Status, type StatusValue, type SyncInlineConfig, type SyncPolledConfig, type System, TimeoutError, UnknownError };
|
package/dist/index.js
CHANGED
|
@@ -30,50 +30,15 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
+
DependencyMonitor: () => DependencyMonitor,
|
|
33
34
|
HealthMonitor: () => HealthMonitor,
|
|
34
35
|
TimeoutError: () => TimeoutError,
|
|
35
36
|
UnknownError: () => UnknownError
|
|
36
37
|
});
|
|
37
38
|
module.exports = __toCommonJS(index_exports);
|
|
38
39
|
|
|
39
|
-
// src/
|
|
40
|
-
var
|
|
41
|
-
|
|
42
|
-
// src/static-checks.ts
|
|
43
|
-
var import_node_os = __toESM(require("os"));
|
|
44
|
-
var ping = () => ({ status: "ok" });
|
|
45
|
-
var liveness = () => {
|
|
46
|
-
const cpus = import_node_os.default.cpus();
|
|
47
|
-
const total = import_node_os.default.totalmem();
|
|
48
|
-
const free = import_node_os.default.freemem();
|
|
49
|
-
return {
|
|
50
|
-
status: "ok",
|
|
51
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
52
|
-
system: {
|
|
53
|
-
hostname: import_node_os.default.hostname(),
|
|
54
|
-
platform: import_node_os.default.platform(),
|
|
55
|
-
release: import_node_os.default.release(),
|
|
56
|
-
arch: import_node_os.default.arch(),
|
|
57
|
-
uptime: import_node_os.default.uptime(),
|
|
58
|
-
loadavg: import_node_os.default.loadavg(),
|
|
59
|
-
totalmem: total,
|
|
60
|
-
freemem: free,
|
|
61
|
-
memUsedRatio: total > 0 ? (total - free) / total : 0,
|
|
62
|
-
cpus: {
|
|
63
|
-
count: cpus.length,
|
|
64
|
-
model: cpus[0]?.model,
|
|
65
|
-
speedMHz: cpus[0]?.speed
|
|
66
|
-
}
|
|
67
|
-
},
|
|
68
|
-
process: {
|
|
69
|
-
pid: process.pid,
|
|
70
|
-
node: process.versions.node,
|
|
71
|
-
uptime: process.uptime(),
|
|
72
|
-
memory: process.memoryUsage()
|
|
73
|
-
// rss, heapTotal, heapUsed, external, arrayBuffers
|
|
74
|
-
}
|
|
75
|
-
};
|
|
76
|
-
};
|
|
40
|
+
// src/dependency-monitor.ts
|
|
41
|
+
var import_node_perf_hooks = require("perf_hooks");
|
|
77
42
|
|
|
78
43
|
// src/types.ts
|
|
79
44
|
var TimeoutError = class extends Error {
|
|
@@ -90,6 +55,17 @@ var UnknownError = class extends Error {
|
|
|
90
55
|
};
|
|
91
56
|
|
|
92
57
|
// src/timing.ts
|
|
58
|
+
var wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
59
|
+
var runTimeoutTimer = async (ms) => {
|
|
60
|
+
await wait(ms);
|
|
61
|
+
throw new TimeoutError();
|
|
62
|
+
};
|
|
63
|
+
var runAgainstTimeout = (promise, ms) => {
|
|
64
|
+
if (ms > 0) {
|
|
65
|
+
return Promise.race([promise, runTimeoutTimer(ms)]);
|
|
66
|
+
}
|
|
67
|
+
return promise;
|
|
68
|
+
};
|
|
93
69
|
var throttle = (fn, ms) => {
|
|
94
70
|
let current = null;
|
|
95
71
|
let clearHandle = null;
|
|
@@ -115,6 +91,236 @@ var throttle = (fn, ms) => {
|
|
|
115
91
|
};
|
|
116
92
|
return wrapped;
|
|
117
93
|
};
|
|
94
|
+
var singleFlight = (fn) => throttle(fn, 0);
|
|
95
|
+
|
|
96
|
+
// src/dependency-monitor.ts
|
|
97
|
+
var DependencyMonitor = class {
|
|
98
|
+
/** Immutable configuration for this monitor. */
|
|
99
|
+
config;
|
|
100
|
+
/** Operational mode determined from the configuration. */
|
|
101
|
+
mode;
|
|
102
|
+
/** Last computed status (starts as `"unknown"` until first result). */
|
|
103
|
+
status;
|
|
104
|
+
/** Optional error details from the last failure. */
|
|
105
|
+
error;
|
|
106
|
+
/** Last observed metrics (e.g., latency). */
|
|
107
|
+
observed;
|
|
108
|
+
/** Freshness timestamps for last check/success. */
|
|
109
|
+
freshness;
|
|
110
|
+
/** Indicates whether this monitor has been disposed. */
|
|
111
|
+
isDisposed = false;
|
|
112
|
+
/** Internal timer handle for polling/retry. */
|
|
113
|
+
timeout;
|
|
114
|
+
/**
|
|
115
|
+
* Create a new {@link DependencyMonitor}.
|
|
116
|
+
* @param config Monitor configuration (inline, polled, or async).
|
|
117
|
+
*/
|
|
118
|
+
constructor(config) {
|
|
119
|
+
this.config = config;
|
|
120
|
+
this.mode = config.asyncCall ? "async" : config.pollRate ? "polled" : "inline";
|
|
121
|
+
this.status = "unknown";
|
|
122
|
+
if (this.mode === "inline") {
|
|
123
|
+
this.check = singleFlight(this.check.bind(this));
|
|
124
|
+
}
|
|
125
|
+
if (this.mode !== "inline") {
|
|
126
|
+
this.doCheck();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/** Impact of this dependency (critical/non_critical). */
|
|
130
|
+
get impact() {
|
|
131
|
+
return this.config.impact;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Run one check cycle depending on the configured mode.
|
|
135
|
+
* - Inline: no-op here (runs in {@link check})
|
|
136
|
+
* - Polled/Async: invoked automatically and re-scheduled as needed
|
|
137
|
+
*/
|
|
138
|
+
async doCheck() {
|
|
139
|
+
if (this.timeout) {
|
|
140
|
+
clearTimeout(this.timeout);
|
|
141
|
+
}
|
|
142
|
+
if (this.isDisposed) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (this.config.syncCall) {
|
|
146
|
+
await this.doSyncCheck();
|
|
147
|
+
} else {
|
|
148
|
+
this.doAsyncCheck();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Execute a synchronous (inline/polled) check and update internal state.
|
|
153
|
+
* Classification:
|
|
154
|
+
* - OK if `"ok"` and latency ≤ `healthyLimitMs`
|
|
155
|
+
* - Degraded if `"degraded"` or latency between `(healthyLimitMs, timeoutLimitMs]`
|
|
156
|
+
* - Error if `"error"`, `Error`, or timeout exceeded
|
|
157
|
+
*/
|
|
158
|
+
async doSyncCheck() {
|
|
159
|
+
const start = import_node_perf_hooks.performance.now();
|
|
160
|
+
const { syncCall, timeoutLimitMs } = this.config;
|
|
161
|
+
try {
|
|
162
|
+
const response = await runAgainstTimeout(syncCall(), timeoutLimitMs);
|
|
163
|
+
const duration = import_node_perf_hooks.performance.now() - start;
|
|
164
|
+
this.handleDependencyResponse(response, duration);
|
|
165
|
+
} catch (err) {
|
|
166
|
+
this.handleDependencyError(err);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Execute an asynchronous check (async mode).
|
|
171
|
+
* - Initializes timers to escalate `unknown` → `degraded` → `error`
|
|
172
|
+
* if no callback result arrives within the thresholds.
|
|
173
|
+
* - On callback, clears timers and classifies the result with latency.
|
|
174
|
+
*/
|
|
175
|
+
async doAsyncCheck() {
|
|
176
|
+
const { asyncCall, healthyLimitMs, timeoutLimitMs } = this.config;
|
|
177
|
+
const start = import_node_perf_hooks.performance.now();
|
|
178
|
+
let callActive = true;
|
|
179
|
+
let healthTimeout;
|
|
180
|
+
let serviceTimeout;
|
|
181
|
+
asyncCall((response) => {
|
|
182
|
+
if (!callActive) return;
|
|
183
|
+
callActive = false;
|
|
184
|
+
clearTimeout(healthTimeout);
|
|
185
|
+
clearTimeout(serviceTimeout);
|
|
186
|
+
const duration = import_node_perf_hooks.performance.now() - start;
|
|
187
|
+
this.handleDependencyResponse(response, duration);
|
|
188
|
+
});
|
|
189
|
+
healthTimeout = setTimeout(() => {
|
|
190
|
+
if (callActive) this.status = "degraded";
|
|
191
|
+
}, healthyLimitMs);
|
|
192
|
+
serviceTimeout = setTimeout(() => {
|
|
193
|
+
if (!callActive) return;
|
|
194
|
+
callActive = false;
|
|
195
|
+
clearTimeout(healthTimeout);
|
|
196
|
+
this.handleDependencyError(new TimeoutError());
|
|
197
|
+
}, timeoutLimitMs);
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Normalize a successful dependency response into status + metadata.
|
|
201
|
+
* @param response Returned status value or Error from the dependency
|
|
202
|
+
* @param duration Measured latency in milliseconds
|
|
203
|
+
*/
|
|
204
|
+
handleDependencyResponse(response, duration) {
|
|
205
|
+
if (response instanceof Error) {
|
|
206
|
+
return this.handleDependencyError(response);
|
|
207
|
+
}
|
|
208
|
+
if (response === "error") {
|
|
209
|
+
return this.handleDependencyError(new UnknownError());
|
|
210
|
+
}
|
|
211
|
+
this.error = void 0;
|
|
212
|
+
this.status = response === "ok" && duration <= this.config.healthyLimitMs ? "ok" : "degraded";
|
|
213
|
+
const end = /* @__PURE__ */ new Date();
|
|
214
|
+
this.freshness = {
|
|
215
|
+
lastChecked: end.toISOString(),
|
|
216
|
+
lastSuccess: end.toISOString()
|
|
217
|
+
};
|
|
218
|
+
this.observed = { latencyMs: duration };
|
|
219
|
+
if (this.config.pollRate && !this.isDisposed) {
|
|
220
|
+
const delay = this.config.pollRate;
|
|
221
|
+
this.timeout = setTimeout(() => this.doCheck(), delay);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Normalize a failed dependency result into `"error"` and update metadata.
|
|
226
|
+
* @param error Error thrown/returned or synthesized (e.g., timeout)
|
|
227
|
+
*/
|
|
228
|
+
handleDependencyError(error) {
|
|
229
|
+
this.status = "error";
|
|
230
|
+
this.observed = void 0;
|
|
231
|
+
this.error = {
|
|
232
|
+
code: error.code || "UNKNOWN",
|
|
233
|
+
message: error.message
|
|
234
|
+
};
|
|
235
|
+
const end = /* @__PURE__ */ new Date();
|
|
236
|
+
if (this.freshness) {
|
|
237
|
+
this.freshness.lastChecked = end.toISOString();
|
|
238
|
+
} else {
|
|
239
|
+
this.freshness = { lastChecked: end.toISOString(), lastSuccess: null };
|
|
240
|
+
}
|
|
241
|
+
if ((this.config.retryRate || this.config.pollRate) && !this.isDisposed) {
|
|
242
|
+
const delay = this.config.retryRate || this.config.pollRate;
|
|
243
|
+
this.timeout = setTimeout(() => this.doCheck(), delay);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Get a snapshot of the dependency’s current health.
|
|
248
|
+
* - Inline mode: executes the `syncCall` (single-flight guarded)
|
|
249
|
+
* - Polled/Async: returns the cached result
|
|
250
|
+
* @throws if the monitor has been disposed
|
|
251
|
+
*/
|
|
252
|
+
async check() {
|
|
253
|
+
if (this.isDisposed) {
|
|
254
|
+
throw new Error("DependencyMonitor has been disposed");
|
|
255
|
+
}
|
|
256
|
+
if (this.mode === "inline") {
|
|
257
|
+
await this.doCheck();
|
|
258
|
+
}
|
|
259
|
+
const result = {
|
|
260
|
+
status: this.status,
|
|
261
|
+
impact: this.config.impact,
|
|
262
|
+
mode: this.mode,
|
|
263
|
+
freshness: this.freshness,
|
|
264
|
+
observed: this.observed,
|
|
265
|
+
error: this.error
|
|
266
|
+
};
|
|
267
|
+
return result;
|
|
268
|
+
}
|
|
269
|
+
/** Symbol-based disposer for use with `using`. */
|
|
270
|
+
[Symbol.dispose]() {
|
|
271
|
+
this.dispose();
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Dispose the monitor:
|
|
275
|
+
* - Clears any scheduled timers
|
|
276
|
+
* - Prevents further checks from running
|
|
277
|
+
*/
|
|
278
|
+
dispose() {
|
|
279
|
+
this.isDisposed = true;
|
|
280
|
+
if (this.timeout) {
|
|
281
|
+
clearTimeout(this.timeout);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
// src/health-monitor.ts
|
|
287
|
+
var import_express = require("express");
|
|
288
|
+
|
|
289
|
+
// src/static-checks.ts
|
|
290
|
+
var import_node_os = __toESM(require("os"));
|
|
291
|
+
var ping = () => ({ status: "ok" });
|
|
292
|
+
var liveness = () => {
|
|
293
|
+
const cpus = import_node_os.default.cpus();
|
|
294
|
+
const total = import_node_os.default.totalmem();
|
|
295
|
+
const free = import_node_os.default.freemem();
|
|
296
|
+
return {
|
|
297
|
+
status: "ok",
|
|
298
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
299
|
+
system: {
|
|
300
|
+
hostname: import_node_os.default.hostname(),
|
|
301
|
+
platform: import_node_os.default.platform(),
|
|
302
|
+
release: import_node_os.default.release(),
|
|
303
|
+
arch: import_node_os.default.arch(),
|
|
304
|
+
uptime: import_node_os.default.uptime(),
|
|
305
|
+
loadavg: import_node_os.default.loadavg(),
|
|
306
|
+
totalmem: total,
|
|
307
|
+
freemem: free,
|
|
308
|
+
memUsedRatio: total > 0 ? (total - free) / total : 0,
|
|
309
|
+
cpus: {
|
|
310
|
+
count: cpus.length,
|
|
311
|
+
model: cpus[0]?.model,
|
|
312
|
+
speedMHz: cpus[0]?.speed
|
|
313
|
+
}
|
|
314
|
+
},
|
|
315
|
+
process: {
|
|
316
|
+
pid: process.pid,
|
|
317
|
+
node: process.versions.node,
|
|
318
|
+
uptime: process.uptime(),
|
|
319
|
+
memory: process.memoryUsage()
|
|
320
|
+
// rss, heapTotal, heapUsed, external, arrayBuffers
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
};
|
|
118
324
|
|
|
119
325
|
// src/health-monitor.ts
|
|
120
326
|
var HealthMonitor = class {
|
|
@@ -360,6 +566,7 @@ var HealthMonitor = class {
|
|
|
360
566
|
};
|
|
361
567
|
// Annotate the CommonJS export names for ESM import in node:
|
|
362
568
|
0 && (module.exports = {
|
|
569
|
+
DependencyMonitor,
|
|
363
570
|
HealthMonitor,
|
|
364
571
|
TimeoutError,
|
|
365
572
|
UnknownError
|
package/dist/index.mjs
CHANGED
|
@@ -1,41 +1,5 @@
|
|
|
1
|
-
// src/
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
// src/static-checks.ts
|
|
5
|
-
import os from "os";
|
|
6
|
-
var ping = () => ({ status: "ok" });
|
|
7
|
-
var liveness = () => {
|
|
8
|
-
const cpus = os.cpus();
|
|
9
|
-
const total = os.totalmem();
|
|
10
|
-
const free = os.freemem();
|
|
11
|
-
return {
|
|
12
|
-
status: "ok",
|
|
13
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
14
|
-
system: {
|
|
15
|
-
hostname: os.hostname(),
|
|
16
|
-
platform: os.platform(),
|
|
17
|
-
release: os.release(),
|
|
18
|
-
arch: os.arch(),
|
|
19
|
-
uptime: os.uptime(),
|
|
20
|
-
loadavg: os.loadavg(),
|
|
21
|
-
totalmem: total,
|
|
22
|
-
freemem: free,
|
|
23
|
-
memUsedRatio: total > 0 ? (total - free) / total : 0,
|
|
24
|
-
cpus: {
|
|
25
|
-
count: cpus.length,
|
|
26
|
-
model: cpus[0]?.model,
|
|
27
|
-
speedMHz: cpus[0]?.speed
|
|
28
|
-
}
|
|
29
|
-
},
|
|
30
|
-
process: {
|
|
31
|
-
pid: process.pid,
|
|
32
|
-
node: process.versions.node,
|
|
33
|
-
uptime: process.uptime(),
|
|
34
|
-
memory: process.memoryUsage()
|
|
35
|
-
// rss, heapTotal, heapUsed, external, arrayBuffers
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
};
|
|
1
|
+
// src/dependency-monitor.ts
|
|
2
|
+
import { performance } from "perf_hooks";
|
|
39
3
|
|
|
40
4
|
// src/types.ts
|
|
41
5
|
var TimeoutError = class extends Error {
|
|
@@ -52,6 +16,17 @@ var UnknownError = class extends Error {
|
|
|
52
16
|
};
|
|
53
17
|
|
|
54
18
|
// src/timing.ts
|
|
19
|
+
var wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
20
|
+
var runTimeoutTimer = async (ms) => {
|
|
21
|
+
await wait(ms);
|
|
22
|
+
throw new TimeoutError();
|
|
23
|
+
};
|
|
24
|
+
var runAgainstTimeout = (promise, ms) => {
|
|
25
|
+
if (ms > 0) {
|
|
26
|
+
return Promise.race([promise, runTimeoutTimer(ms)]);
|
|
27
|
+
}
|
|
28
|
+
return promise;
|
|
29
|
+
};
|
|
55
30
|
var throttle = (fn, ms) => {
|
|
56
31
|
let current = null;
|
|
57
32
|
let clearHandle = null;
|
|
@@ -77,6 +52,236 @@ var throttle = (fn, ms) => {
|
|
|
77
52
|
};
|
|
78
53
|
return wrapped;
|
|
79
54
|
};
|
|
55
|
+
var singleFlight = (fn) => throttle(fn, 0);
|
|
56
|
+
|
|
57
|
+
// src/dependency-monitor.ts
|
|
58
|
+
var DependencyMonitor = class {
|
|
59
|
+
/** Immutable configuration for this monitor. */
|
|
60
|
+
config;
|
|
61
|
+
/** Operational mode determined from the configuration. */
|
|
62
|
+
mode;
|
|
63
|
+
/** Last computed status (starts as `"unknown"` until first result). */
|
|
64
|
+
status;
|
|
65
|
+
/** Optional error details from the last failure. */
|
|
66
|
+
error;
|
|
67
|
+
/** Last observed metrics (e.g., latency). */
|
|
68
|
+
observed;
|
|
69
|
+
/** Freshness timestamps for last check/success. */
|
|
70
|
+
freshness;
|
|
71
|
+
/** Indicates whether this monitor has been disposed. */
|
|
72
|
+
isDisposed = false;
|
|
73
|
+
/** Internal timer handle for polling/retry. */
|
|
74
|
+
timeout;
|
|
75
|
+
/**
|
|
76
|
+
* Create a new {@link DependencyMonitor}.
|
|
77
|
+
* @param config Monitor configuration (inline, polled, or async).
|
|
78
|
+
*/
|
|
79
|
+
constructor(config) {
|
|
80
|
+
this.config = config;
|
|
81
|
+
this.mode = config.asyncCall ? "async" : config.pollRate ? "polled" : "inline";
|
|
82
|
+
this.status = "unknown";
|
|
83
|
+
if (this.mode === "inline") {
|
|
84
|
+
this.check = singleFlight(this.check.bind(this));
|
|
85
|
+
}
|
|
86
|
+
if (this.mode !== "inline") {
|
|
87
|
+
this.doCheck();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/** Impact of this dependency (critical/non_critical). */
|
|
91
|
+
get impact() {
|
|
92
|
+
return this.config.impact;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Run one check cycle depending on the configured mode.
|
|
96
|
+
* - Inline: no-op here (runs in {@link check})
|
|
97
|
+
* - Polled/Async: invoked automatically and re-scheduled as needed
|
|
98
|
+
*/
|
|
99
|
+
async doCheck() {
|
|
100
|
+
if (this.timeout) {
|
|
101
|
+
clearTimeout(this.timeout);
|
|
102
|
+
}
|
|
103
|
+
if (this.isDisposed) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (this.config.syncCall) {
|
|
107
|
+
await this.doSyncCheck();
|
|
108
|
+
} else {
|
|
109
|
+
this.doAsyncCheck();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Execute a synchronous (inline/polled) check and update internal state.
|
|
114
|
+
* Classification:
|
|
115
|
+
* - OK if `"ok"` and latency ≤ `healthyLimitMs`
|
|
116
|
+
* - Degraded if `"degraded"` or latency between `(healthyLimitMs, timeoutLimitMs]`
|
|
117
|
+
* - Error if `"error"`, `Error`, or timeout exceeded
|
|
118
|
+
*/
|
|
119
|
+
async doSyncCheck() {
|
|
120
|
+
const start = performance.now();
|
|
121
|
+
const { syncCall, timeoutLimitMs } = this.config;
|
|
122
|
+
try {
|
|
123
|
+
const response = await runAgainstTimeout(syncCall(), timeoutLimitMs);
|
|
124
|
+
const duration = performance.now() - start;
|
|
125
|
+
this.handleDependencyResponse(response, duration);
|
|
126
|
+
} catch (err) {
|
|
127
|
+
this.handleDependencyError(err);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Execute an asynchronous check (async mode).
|
|
132
|
+
* - Initializes timers to escalate `unknown` → `degraded` → `error`
|
|
133
|
+
* if no callback result arrives within the thresholds.
|
|
134
|
+
* - On callback, clears timers and classifies the result with latency.
|
|
135
|
+
*/
|
|
136
|
+
async doAsyncCheck() {
|
|
137
|
+
const { asyncCall, healthyLimitMs, timeoutLimitMs } = this.config;
|
|
138
|
+
const start = performance.now();
|
|
139
|
+
let callActive = true;
|
|
140
|
+
let healthTimeout;
|
|
141
|
+
let serviceTimeout;
|
|
142
|
+
asyncCall((response) => {
|
|
143
|
+
if (!callActive) return;
|
|
144
|
+
callActive = false;
|
|
145
|
+
clearTimeout(healthTimeout);
|
|
146
|
+
clearTimeout(serviceTimeout);
|
|
147
|
+
const duration = performance.now() - start;
|
|
148
|
+
this.handleDependencyResponse(response, duration);
|
|
149
|
+
});
|
|
150
|
+
healthTimeout = setTimeout(() => {
|
|
151
|
+
if (callActive) this.status = "degraded";
|
|
152
|
+
}, healthyLimitMs);
|
|
153
|
+
serviceTimeout = setTimeout(() => {
|
|
154
|
+
if (!callActive) return;
|
|
155
|
+
callActive = false;
|
|
156
|
+
clearTimeout(healthTimeout);
|
|
157
|
+
this.handleDependencyError(new TimeoutError());
|
|
158
|
+
}, timeoutLimitMs);
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Normalize a successful dependency response into status + metadata.
|
|
162
|
+
* @param response Returned status value or Error from the dependency
|
|
163
|
+
* @param duration Measured latency in milliseconds
|
|
164
|
+
*/
|
|
165
|
+
handleDependencyResponse(response, duration) {
|
|
166
|
+
if (response instanceof Error) {
|
|
167
|
+
return this.handleDependencyError(response);
|
|
168
|
+
}
|
|
169
|
+
if (response === "error") {
|
|
170
|
+
return this.handleDependencyError(new UnknownError());
|
|
171
|
+
}
|
|
172
|
+
this.error = void 0;
|
|
173
|
+
this.status = response === "ok" && duration <= this.config.healthyLimitMs ? "ok" : "degraded";
|
|
174
|
+
const end = /* @__PURE__ */ new Date();
|
|
175
|
+
this.freshness = {
|
|
176
|
+
lastChecked: end.toISOString(),
|
|
177
|
+
lastSuccess: end.toISOString()
|
|
178
|
+
};
|
|
179
|
+
this.observed = { latencyMs: duration };
|
|
180
|
+
if (this.config.pollRate && !this.isDisposed) {
|
|
181
|
+
const delay = this.config.pollRate;
|
|
182
|
+
this.timeout = setTimeout(() => this.doCheck(), delay);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Normalize a failed dependency result into `"error"` and update metadata.
|
|
187
|
+
* @param error Error thrown/returned or synthesized (e.g., timeout)
|
|
188
|
+
*/
|
|
189
|
+
handleDependencyError(error) {
|
|
190
|
+
this.status = "error";
|
|
191
|
+
this.observed = void 0;
|
|
192
|
+
this.error = {
|
|
193
|
+
code: error.code || "UNKNOWN",
|
|
194
|
+
message: error.message
|
|
195
|
+
};
|
|
196
|
+
const end = /* @__PURE__ */ new Date();
|
|
197
|
+
if (this.freshness) {
|
|
198
|
+
this.freshness.lastChecked = end.toISOString();
|
|
199
|
+
} else {
|
|
200
|
+
this.freshness = { lastChecked: end.toISOString(), lastSuccess: null };
|
|
201
|
+
}
|
|
202
|
+
if ((this.config.retryRate || this.config.pollRate) && !this.isDisposed) {
|
|
203
|
+
const delay = this.config.retryRate || this.config.pollRate;
|
|
204
|
+
this.timeout = setTimeout(() => this.doCheck(), delay);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Get a snapshot of the dependency’s current health.
|
|
209
|
+
* - Inline mode: executes the `syncCall` (single-flight guarded)
|
|
210
|
+
* - Polled/Async: returns the cached result
|
|
211
|
+
* @throws if the monitor has been disposed
|
|
212
|
+
*/
|
|
213
|
+
async check() {
|
|
214
|
+
if (this.isDisposed) {
|
|
215
|
+
throw new Error("DependencyMonitor has been disposed");
|
|
216
|
+
}
|
|
217
|
+
if (this.mode === "inline") {
|
|
218
|
+
await this.doCheck();
|
|
219
|
+
}
|
|
220
|
+
const result = {
|
|
221
|
+
status: this.status,
|
|
222
|
+
impact: this.config.impact,
|
|
223
|
+
mode: this.mode,
|
|
224
|
+
freshness: this.freshness,
|
|
225
|
+
observed: this.observed,
|
|
226
|
+
error: this.error
|
|
227
|
+
};
|
|
228
|
+
return result;
|
|
229
|
+
}
|
|
230
|
+
/** Symbol-based disposer for use with `using`. */
|
|
231
|
+
[Symbol.dispose]() {
|
|
232
|
+
this.dispose();
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Dispose the monitor:
|
|
236
|
+
* - Clears any scheduled timers
|
|
237
|
+
* - Prevents further checks from running
|
|
238
|
+
*/
|
|
239
|
+
dispose() {
|
|
240
|
+
this.isDisposed = true;
|
|
241
|
+
if (this.timeout) {
|
|
242
|
+
clearTimeout(this.timeout);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
// src/health-monitor.ts
|
|
248
|
+
import { Router } from "express";
|
|
249
|
+
|
|
250
|
+
// src/static-checks.ts
|
|
251
|
+
import os from "os";
|
|
252
|
+
var ping = () => ({ status: "ok" });
|
|
253
|
+
var liveness = () => {
|
|
254
|
+
const cpus = os.cpus();
|
|
255
|
+
const total = os.totalmem();
|
|
256
|
+
const free = os.freemem();
|
|
257
|
+
return {
|
|
258
|
+
status: "ok",
|
|
259
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
260
|
+
system: {
|
|
261
|
+
hostname: os.hostname(),
|
|
262
|
+
platform: os.platform(),
|
|
263
|
+
release: os.release(),
|
|
264
|
+
arch: os.arch(),
|
|
265
|
+
uptime: os.uptime(),
|
|
266
|
+
loadavg: os.loadavg(),
|
|
267
|
+
totalmem: total,
|
|
268
|
+
freemem: free,
|
|
269
|
+
memUsedRatio: total > 0 ? (total - free) / total : 0,
|
|
270
|
+
cpus: {
|
|
271
|
+
count: cpus.length,
|
|
272
|
+
model: cpus[0]?.model,
|
|
273
|
+
speedMHz: cpus[0]?.speed
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
process: {
|
|
277
|
+
pid: process.pid,
|
|
278
|
+
node: process.versions.node,
|
|
279
|
+
uptime: process.uptime(),
|
|
280
|
+
memory: process.memoryUsage()
|
|
281
|
+
// rss, heapTotal, heapUsed, external, arrayBuffers
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
};
|
|
80
285
|
|
|
81
286
|
// src/health-monitor.ts
|
|
82
287
|
var HealthMonitor = class {
|
|
@@ -321,6 +526,7 @@ var HealthMonitor = class {
|
|
|
321
526
|
}
|
|
322
527
|
};
|
|
323
528
|
export {
|
|
529
|
+
DependencyMonitor,
|
|
324
530
|
HealthMonitor,
|
|
325
531
|
TimeoutError,
|
|
326
532
|
UnknownError
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sebspark/health-check",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"dist"
|
|
10
10
|
],
|
|
11
11
|
"scripts": {
|
|
12
|
-
"build": "tsup-node src/index.ts --format esm,cjs --dts",
|
|
12
|
+
"build": "tsup-node src/index.ts --format esm,cjs --target node22 --dts",
|
|
13
13
|
"dev": "tsc --watch --noEmit",
|
|
14
14
|
"lint": "biome check .",
|
|
15
15
|
"test": "vitest run --passWithNoTests --coverage",
|