UNPKG

application-services

Version:

Out of the box application environment and configuration service.

111 lines (106 loc) 4.11 kB
import { printStackTrace } from 'yerror'; import { autoProvider, singleton, location, } from 'knifecycle'; import { noop } from 'common-services'; const DEFAULT_SIGNALS = ['SIGTERM', 'SIGINT']; /* Architecture Note #1.5: Process The `process` service takes care of the process status. It returns nothing and should be injected only for its side effects. */ export default location(singleton(autoProvider(initProcess)), import.meta.url); /** * Instantiate the process service * @name initProcess * @function * @param {Object} services * The services `process` depends on * @param {Object} services.APP_ENV * The injected `APP_ENV` value * @param {Object} [services.PROCESS_NAME] * The process name to display * @param {Object} [services.SIGNALS] * The process signals that interrupt the process * @param {Object} [services.exit] * A `process.exit` like function * @param {Object} services.$instance * The Knifecycle instance * @param {Object} services.$fatalError * The Knifecycle fatal error manager * @param {Object} [services.log=noop] * An optional logging service * @return {Promise<Object>} * A promise of the process object */ async function initProcess({ ENV, APP_ENV, PROCESS_NAME = '', SIGNALS = DEFAULT_SIGNALS, log = noop, exit, $instance, $fatalError, }) { const signalsListeners = SIGNALS.map((signal) => [signal, terminate.bind(null, signal)]); let shuttingDown = false; /* Architecture Note #1.5.1: Process name It also set the process name with the actual NODE_ENV. */ global.process.title = `${PROCESS_NAME || global.process.title} - ${APP_ENV}:${ENV.NODE_ENV}`; /* Architecture Note #1.5.2: Signals handling It also handle SIGINT and SIGTERM signals to allow to gracefully shutdown the running process. The signals to handle can be customized by injecting the `SIGNALS` optional dependencies. */ signalsListeners.forEach(([signal, signalListener]) => { global.process.on(signal, signalListener); }); /* Architecture Note #1.5.3: Handling services fatal errors If an error occurs it attempts to gracefully exit to give it a chance to finish properly. */ $fatalError.errorPromise.catch((err) => { log('error', '💀 - Fatal error'); log('error-stack', printStackTrace(err)); terminate('FATAL'); }); /* Architecture Note #1.5.4: Uncaught exceptions If an uncaught exception occurs it also attempts to gracefully exit since a process should never be kept alive when an uncaught exception is raised. */ global.process.on('uncaughtException', catchUncaughtException); function catchUncaughtException(err) { log('error', '💀 - Uncaught Exception'); log('error-stack', printStackTrace(err)); terminate('ERR'); } function terminate(signal) { if (shuttingDown) { log('warning', `🚦 - ${signal} received again, shutdown now.`); exit(1); } else { log('warning', `🚦 - ${signal} received. Send it again to kill me instantly.`); shutdown(['ERR', 'FATAL'].includes(signal) ? 1 : 0); } } async function shutdown(code) { shuttingDown = true; log('warning', 'Shutting down now 🙏...'); await $instance.destroy(); try { log('warning', '😎 - Gracefull shutdown sucessfully done !'); exit(code); } catch (err) { log('error', '🤔 - Could not gracefully shutdown.'); log('error-stack', printStackTrace(err)); exit(code); } } async function dispose() { global.process.removeListener('uncaughtException', catchUncaughtException); signalsListeners.forEach(([signal, signalListener]) => { global.process.removeListener(signal, signalListener); }); } log('debug', '📇 - Process service initialized.'); return { service: global.process, dispose, }; } //# sourceMappingURL=process.js.map