kubernetes-health
Version:
Helper library for implementing Kubernetes heath checks and graceful HTTP shutdown in Node applications
184 lines • 8.71 kB
JavaScript
"use strict";
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
if (kind === "m") throw new TypeError("Private method is not writable");
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var _Health_options, _Health_events, _Health_isReady, _Health_isTerminating, _Health_beforeLiveTasks, _Health_beforeReadyTasks, _Health_beforeTerminationTasks, _Health_pendingTasks;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Health = exports.defaultOptions = void 0;
const events_1 = require("events");
const _utils_1 = require("./_utils");
/** Default values for Health options, if not specified */
exports.defaultOptions = Object.freeze({
forciblyTerminate: () => process.exit(1),
shutdownDelaySeconds: 5,
shutdownSignals: ['SIGTERM', 'SIGHUP', 'SIGINT'],
terminationGracePeriodSeconds: 30,
});
/**
* The `Health` class represents the health status if an application. It manages transitions between application
* state, and automatically registers shutdown handlers to that run when the process receives a shutdown signal.
*/
class Health {
constructor(options) {
_Health_options.set(this, void 0);
_Health_events.set(this, new events_1.EventEmitter());
_Health_isReady.set(this, false);
_Health_isTerminating.set(this, false);
_Health_beforeLiveTasks.set(this, []);
_Health_beforeReadyTasks.set(this, []);
_Health_beforeTerminationTasks.set(this, []);
_Health_pendingTasks.set(this, []
/**
* `true` if the application is live, `false` otherwise
*/
);
__classPrivateFieldSet(this, _Health_options, { ...exports.defaultOptions, ...options }, "f");
for (const signal of __classPrivateFieldGet(this, _Health_options, "f").shutdownSignals) {
process.on(signal, () => {
this.shutdown();
});
}
}
/**
* `true` if the application is live, `false` otherwise
*/
get isLive() {
return __classPrivateFieldGet(this, _Health_beforeLiveTasks, "f").length === 0;
}
/**
* `true` if the application is ready, `false` otherwise
*/
get isReady() {
return !__classPrivateFieldGet(this, _Health_isTerminating, "f") && __classPrivateFieldGet(this, _Health_isReady, "f") && __classPrivateFieldGet(this, _Health_beforeReadyTasks, "f").length === 0;
}
/**
* The overall application status, transitions between the following status states:
*
* - `Pending`
* - `Live`
* - `Ready`
* - `NotReady`
* - `Terminating`
*/
get status() {
if (__classPrivateFieldGet(this, _Health_isTerminating, "f"))
return 'Terminating';
if (__classPrivateFieldGet(this, _Health_beforeLiveTasks, "f").length !== 0)
return 'Pending';
if (__classPrivateFieldGet(this, _Health_beforeReadyTasks, "f").length !== 0)
return 'Live';
if (__classPrivateFieldGet(this, _Health_isReady, "f"))
return 'Ready';
return 'NotReady';
}
/**
* Add (register) a task that must complete before the application is considered live
*
* @param task the task to execute
*/
beforeLive(task) {
__classPrivateFieldGet(this, _Health_beforeLiveTasks, "f").push(task);
task().then(() => {
__classPrivateFieldGet(this, _Health_beforeLiveTasks, "f").splice(__classPrivateFieldGet(this, _Health_beforeLiveTasks, "f").indexOf(task), 1);
});
}
/**
* Add (register) a task that must complete before the application is considered ready
*
* @param task the task to execute
*/
beforeReady(task) {
__classPrivateFieldGet(this, _Health_beforeReadyTasks, "f").push(task);
task().then(() => {
__classPrivateFieldGet(this, _Health_beforeReadyTasks, "f").splice(__classPrivateFieldGet(this, _Health_beforeReadyTasks, "f").indexOf(task), 1);
});
}
/**
* Add (register) a task that must complete before the application terminates
*
* @param task the task to execute
*/
beforeTermination(handler) {
__classPrivateFieldGet(this, _Health_beforeTerminationTasks, "f").push(handler);
}
/**
* Create a new pending task that must be completed before the process terminates.
*
* @returns a `PendingTask` that can be marked as completed
*/
createPendingTask() {
let isComplete = false;
const task = Object.freeze({
markComplete: () => {
if (isComplete)
return;
isComplete = true;
__classPrivateFieldGet(this, _Health_pendingTasks, "f").splice(__classPrivateFieldGet(this, _Health_pendingTasks, "f").indexOf(task), 1);
__classPrivateFieldGet(this, _Health_events, "f").emit('taskComplete');
},
});
__classPrivateFieldGet(this, _Health_pendingTasks, "f").push(task);
return task;
}
/**
* Mark the health instance as ready
*/
markReady() {
if (__classPrivateFieldGet(this, _Health_isTerminating, "f"))
return;
__classPrivateFieldSet(this, _Health_isReady, true, "f");
}
/**
* Mark the health instance as not ready
*/
markNotReady() {
if (__classPrivateFieldGet(this, _Health_isTerminating, "f"))
return;
__classPrivateFieldSet(this, _Health_isReady, false, "f");
}
/**
* Signals that the process should shut down. Waits for any pending tasks and triggers all shutdown handlers.
* If a `terminationGracePeriodSeconds` is defined, the process will be forcibly terminated after that amount
* of time if it does not gracefully exit before then.
*/
async shutdown() {
if (__classPrivateFieldGet(this, _Health_isTerminating, "f"))
return;
__classPrivateFieldSet(this, _Health_isTerminating, true, "f");
if (__classPrivateFieldGet(this, _Health_options, "f").terminationGracePeriodSeconds > 0) {
setTimeout(() => {
__classPrivateFieldGet(this, _Health_options, "f").forciblyTerminate();
}, __classPrivateFieldGet(this, _Health_options, "f").terminationGracePeriodSeconds * 1000).unref();
}
if (__classPrivateFieldGet(this, _Health_options, "f").shutdownDelaySeconds > 0) {
await (0, _utils_1.sleep)(__classPrivateFieldGet(this, _Health_options, "f").shutdownDelaySeconds * 1000);
}
if (__classPrivateFieldGet(this, _Health_pendingTasks, "f").length > 0) {
await new Promise((resolve) => {
const checkPendingTasks = () => {
if (__classPrivateFieldGet(this, _Health_pendingTasks, "f").length === 0) {
__classPrivateFieldGet(this, _Health_events, "f").off('taskComplete', checkPendingTasks);
resolve();
}
};
__classPrivateFieldGet(this, _Health_events, "f").on('taskComplete', checkPendingTasks);
checkPendingTasks();
});
}
await Promise.allSettled(__classPrivateFieldGet(this, _Health_beforeTerminationTasks, "f"));
setTimeout(() => {
__classPrivateFieldGet(this, _Health_options, "f").forciblyTerminate();
}, 1000).unref();
}
}
exports.Health = Health;
_Health_options = new WeakMap(), _Health_events = new WeakMap(), _Health_isReady = new WeakMap(), _Health_isTerminating = new WeakMap(), _Health_beforeLiveTasks = new WeakMap(), _Health_beforeReadyTasks = new WeakMap(), _Health_beforeTerminationTasks = new WeakMap(), _Health_pendingTasks = new WeakMap();
//# sourceMappingURL=Health.js.map