"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.gracefulExit = exports.asyncExitHook = void 0; const node_process_1 = __importDefault(require("node:process")); const asyncCallbacks = new Set(); const callbacks = new Set(); let isCalled = false; let isRegistered = false; async function exit(shouldManuallyExit, isSynchronous, signal) { if (isCalled) { return; } isCalled = true; if (asyncCallbacks.size > 0 && isSynchronous) { console.error([ 'SYNCHRONOUS TERMINATION NOTICE:', 'When explicitly exiting the process via process.exit or via a parent process,', 'asynchronous tasks in your exitHooks will not run. Either remove these tasks,', 'use gracefulExit() instead of process.exit(), or ensure your parent process', 'sends a SIGINT to the process running this code.', ].join(' ')); } const exitCode = 128 + signal; const done = (force = false) => { if (force || shouldManuallyExit) { node_process_1.default.exit(exitCode); // eslint-disable-line unicorn/no-process-exit } }; for (const callback of callbacks) { callback(exitCode); } if (isSynchronous) { done(); return; } const promises = []; let forceAfter = 0; for (const [callback, wait] of asyncCallbacks) { forceAfter = Math.max(forceAfter, wait); promises.push(Promise.resolve(callback(exitCode))); } // Force exit if we exceeded our wait value const asyncTimer = setTimeout(() => { done(true); }, forceAfter); await Promise.all(promises); clearTimeout(asyncTimer); done(); } function addHook(options) { const { onExit, minimumWait, isSynchronous } = options; const asyncCallbackConfig = [onExit, minimumWait]; if (isSynchronous) { callbacks.add(onExit); } else { asyncCallbacks.add(asyncCallbackConfig); } if (!isRegistered) { isRegistered = true; // Exit cases that support asynchronous handling node_process_1.default.once('beforeExit', exit.bind(undefined, true, false, -128)); node_process_1.default.once('SIGINT', exit.bind(undefined, true, false, 2)); node_process_1.default.once('SIGTERM', exit.bind(undefined, true, false, 15)); // Explicit exit events. Calling will force an immediate exit and run all // synchronous hooks. Explicit exits must not extend the node process // artificially. Will log errors if asynchronous calls exist. node_process_1.default.once('exit', exit.bind(undefined, false, true, 0)); // PM2 Cluster shutdown message. Caught to support async handlers with pm2, // needed because explicitly calling process.exit() doesn't trigger the // beforeExit event, and the exit event cannot support async handlers, // since the event loop is never called after it. node_process_1.default.on('message', message => { if (message === 'shutdown') { void exit(true, true, -128); } }); } return () => { if (isSynchronous) { callbacks.delete(onExit); } else { asyncCallbacks.delete(asyncCallbackConfig); } }; } function exitHook(onExit) { if (typeof onExit !== 'function') { throw new TypeError('onExit must be a function'); } return addHook({ onExit, isSynchronous: true, }); } exports.default = exitHook; const defaultOptions = { minimumWait: 500, }; function asyncExitHook(onExit, options) { if (typeof onExit !== 'function') { throw new TypeError('onExit must be a function'); } const minimumWait = resolveMinimumWait(options); return addHook({ onExit, minimumWait, isSynchronous: false, }); } exports.asyncExitHook = asyncExitHook; function gracefulExit(signal = 0) { void exit(true, false, -128 + signal); } exports.gracefulExit = gracefulExit; function validateMinimumWait(value) { if (typeof value !== 'number' || value <= 0) { throw new TypeError('minimumWait must be set to a positive numeric value'); } return value; } function resolveMinimumWait(options = defaultOptions) { if (typeof options === 'number') { return validateMinimumWait(options); } if (typeof options === 'object' && options !== null) { const { minimumWait } = { ...defaultOptions, ...options }; return validateMinimumWait(minimumWait); } throw new TypeError('minimumWait must be set to a positive numeric value'); }