import logger from '@mds-core/mds-logger' ; import { hours , minutes , seconds , NotFoundError , ValidationError , ConflictError } from '@mds-core/mds-utils' ; import { Nullable } from '@mds-core/mds-types' ; import retry , { Options as RetryOptions } from 'async-retry' ; import { ServiceResultType , ServiceErrorDescriptor , ServiceErrorType , ProcessController } from '../@types' ; type ProcessMonitorOptions = Partial < Omit < RetryOptions , 'onRetry' > & { interval : number ; signals : NodeJS . Signals [ ] ; } > ; const ProcessMonitor = async ( controller , options = { } ) : Promise < ProcessController > => { const { interval = hours ( 1 ) , signals = [ 'SIGINT' , 'SIGTERM' ] , retries = 15 , minTimeout = seconds ( 2 ) , maxTimeout = minutes ( 2 ) , ... retryOptions } = options ; const { env : { npm_package_name , npm_package_version , npm_package_git_commit } } = process ; const version = `${ npm_package_name } v${ npm_package_version } (${ npm_package_git_commit ?? 'local' })` ; try { await retry ( async ( ) => { logger . info ( `Initializing process ${ version }` ) ; await controller . start ( ) ; } , { retries , minTimeout , maxTimeout , ... retryOptions , onRetry : ( error , attempt ) => { logger . error ( `Initializing process ${ version } failed: ${ error . message }, Retrying ${ attempt } of ${ retries }....` ) ; } } ) ; } catch ( error ) { logger . error ( `Initializing process ${ version } failed: ${ error . message }, Exiting...` ) ; await controller . stop ( ) ; process . exit ( 1 ) ; } logger . info ( `Monitoring process ${ version } for ${ signals . join ( ', ' ) }` ) ; const timeout = setInterval ( ( ) => { logger . info ( `Monitoring process ${ version } for ${ signals . join ( ', ' ) }` ) ; } , interval ) ; const terminate = async ( signal ) => { clearInterval ( timeout ) ; logger . info ( `Terminating process ${ version } on ${ signal }` ) ; await controller . stop ( ) ; process . exit ( 0 ) ; } ; signals . forEach ( signal => process . on ( signal , async ( ) => { await terminate ( signal ) ; } ) ) ; return { start : async ( ) => undefined , stop : async ( ) => terminate ( 'SIGUSR1' ) } ; } ; export const ProcessManager = ( controller , options = { } ) => ( { monitor : ( ) => { ProcessMonitor ( controller , options ) ; } , controller : ( ) : ProcessController => { let monitor = null ; return { start : async ( ) => { if ( ! monitor ) { monitor = await ProcessMonitor ( controller , options ) ; } } , stop : async ( ) => { if ( monitor ) { await monitor . stop ( ) ; monitor = null ; } } } ; } } ) ; export const ServiceResult = < R > ( result ) : ServiceResultType < R > => ( { error : null , result } ) ; export const ServiceError = < E extends string > ( error ) : ServiceErrorType < E > => ( { error : { isServiceError : true , ... error } } ) ; export const ServiceException = ( message , error ? ) => { const details = ( error instanceof Error && error . message ) || undefined ; if ( error instanceof NotFoundError ) { return ServiceError ( { type : 'NotFoundError' , message , details } ) ; } if ( error instanceof ValidationError ) { return ServiceError ( { type : 'ValidationError' , message , details } ) ; } if ( error instanceof ConflictError ) { return ServiceError ( { type : 'ConflictError' , message , details } ) ; } return ServiceError ( { type : 'ServiceException' , message , details } ) ; } ;