@piggly/fastify-chassis
Version:
An ESM/CommonJS toolkit to help you to do common operations in your back-end applications with Fastify and NodeJS.
196 lines • 6.64 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.GracefulShutdownService = void 0;
const tslib_1 = require("tslib");
const ddd_toolkit_1 = require("@piggly/ddd-toolkit");
const event_bus_1 = tslib_1.__importDefault(require("@piggly/event-bus"));
const debug_1 = tslib_1.__importDefault(require("debug"));
/**
* @file Handle graceful shutdown of the service.
* @copyright Piggly Lab 2025
* @since 7.5.0
* @author Caique Araujo <caique@piggly.com.br>
*/
class GracefulShutdownService {
/**
* The cleanup service to use.
* @since 7.5.0
* @author Caique Araujo <caique@piggly.com.br>
*/
cleanup;
/**
* Whether the service is shutting down.
* @since 7.5.0
* @author Caique Araujo <caique@piggly.com.br>
*/
is_shutting_down = false;
/**
* The timeout in milliseconds.
* @since 7.5.0
* @author Caique Araujo <caique@piggly.com.br>
*/
timeout;
/**
* Constructor.
* @param {CleanUpService} cleanup The cleanup service to use.
* @param {number} timeout The timeout in milliseconds. Default is 30000.
* @since 7.5.0
* @author Caique Araujo <caique@piggly.com.br>
*/
constructor(cleanup, timeout = 30000) {
this.timeout = timeout;
this.cleanup = cleanup;
}
/**
* Get the cleanup service.
*
* @returns {CleanUpService}
* @throws {Error} If cleanup service is not provided.
* @since 7.5.0
* @author Caique Araujo <caique@piggly.com.br>
*/
get handlers() {
if (!this.cleanup) {
throw new Error('Cleanup service must be provided');
}
return this.cleanup;
}
/**
* Fetch the cleanup service.
* Useful when you want to delay the cleanup to a later time.
*
* E.g: You want to listen for shutdown signals and it can
* happen before the cleanup service is ready.
*
* If "cleanup" is not provided, it will try to load the cleanup service
* from the ServiceProvider.
*
* @param {CleanUpService} cleanup The cleanup service to use.
* @since 7.5.0
* @author Caique Araujo <caique@piggly.com.br>
*/
fetch(cleanup) {
this.cleanup = cleanup ?? ddd_toolkit_1.ServiceProvider.get('CleanUpService');
(0, debug_1.default)('app:shutdown')(`Fetched cleanup service: ${this.cleanup === undefined ? 'NO' : 'YES'}`);
}
/**
* Listen for shutdown signals.
*
* @since 7.5.0
* @author Caique Araujo <caique@piggly.com.br>
*/
listen() {
process.on('SIGINT', () => this._shutdown('SIGINT'));
process.on('SIGTERM', () => this._shutdown('SIGTERM'));
process.on('SIGHUP', () => this._shutdown('SIGHUP'));
(0, debug_1.default)('app:shutdown')('Listening for shutdown signals...');
}
/**
* Listen for uncaught exceptions and rejections.
*
* @since 7.5.0
* @author Caique Araujo <caique@piggly.com.br>
*/
listenUncaught() {
const handler = async (reason, origin) => {
(0, debug_1.default)('app:uncaught')(reason, origin);
(0, debug_1.default)('app:shutdown:uncaught')('Uncaught exception, shutting down...');
await event_bus_1.default.instance.cleanup();
const logger = ddd_toolkit_1.LoggerService.softResolve();
logger.error('UNCAUGHT_EXCEPTION_ERROR', reason, origin);
logger.flush();
await logger.cleanup();
await this._shutdown('UNCAUGHT_EXCEPTION');
};
process.on('uncaughtException', handler);
process.on('unhandledRejection', handler);
}
/**
* Listen for user signals.
*
* @since 7.5.0
* @author Caique Araujo <caique@piggly.com.br>
*/
listenUSR() {
process.on('SIGUSR1', () => this._shutdown('SIGUSR1'));
process.on('SIGUSR2', () => this._shutdown('SIGUSR2'));
}
/**
* Shutdown the service.
*
* @returns {Promise<void>}
* @since 7.5.0
* @author Caique Araujo <caique@piggly.com.br>
*/
async shutdown() {
await this._shutdown('MANUAL');
}
/**
* Clean up the service.
*
* @returns {Promise<boolean>}
* @since 7.5.0
* @author Caique Araujo <caique@piggly.com.br>
*/
async _clean() {
try {
ddd_toolkit_1.LoggerService.softResolve().flush();
await Promise.all([
event_bus_1.default.instance.cleanup(),
ddd_toolkit_1.LoggerService.softResolve().cleanup(),
]);
if (this.cleanup) {
const response = await this.cleanup.softClean();
return response.success;
}
return true;
}
catch (error) {
(0, debug_1.default)('app:shutdown:error')(error);
return false;
}
}
/**
* Shutdown the service.
*
* @param {string} signal The signal that triggered the shutdown.
* @returns {Promise<void>}
* @since 7.5.0
* @author Caique Araujo <caique@piggly.com.br>
*/
async _shutdown(signal) {
if (this.is_shutting_down) {
(0, debug_1.default)('app:shutdown')('Already shutting down, skipping...');
return;
}
this.is_shutting_down = true;
(0, debug_1.default)('app:shutdown')(`Received ${signal} signal, shutting down...`);
const timeout = setTimeout(() => {
(0, debug_1.default)('app:shutdown')('Graceful shutdown timed out, forcing exit...');
process.exit(1);
}, this.timeout);
const response = await this._clean();
clearTimeout(timeout);
if (!response) {
(0, debug_1.default)('app:shutdown')('Failed to clean up, forcing exit...');
process.exit(1);
}
(0, debug_1.default)('app:shutdown')('Shutdown completed successfully');
process.exit(0);
}
/**
* Resolve the graceful shutdown service.
* It will load the CleanUpService from the ServiceProvider.
*
* @param {number} timeout The timeout in milliseconds. Default is 30000.
* @returns {GracefulShutdownService}
* @since 7.5.0
* @author Caique Araujo <caique@piggly.com.br>
*/
static resolve(timeout = 30000) {
const service = new GracefulShutdownService(ddd_toolkit_1.ServiceProvider.get('CleanUpService'), timeout);
return service;
}
}
exports.GracefulShutdownService = GracefulShutdownService;
//# sourceMappingURL=GracefulShutdownService.js.map