application-services
Version:
Out of the box application environment and configuration service.
112 lines (107 loc) • 4.09 kB
JavaScript
import { printStackTrace } from 'yerror';
import process from 'node:process';
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.
*/
/**
* 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.
*/
process.title = `${PROCESS_NAME || 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]) => {
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.
*/
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() {
process.removeListener('uncaughtException', catchUncaughtException);
signalsListeners.forEach(([signal, signalListener]) => {
process.removeListener(signal, signalListener);
});
}
log('debug', '📇 - Process service initialized.');
return {
service: process,
dispose,
};
}
export default location(singleton(autoProvider(initProcess)), import.meta.url);
//# sourceMappingURL=process.js.map