import { Binding , BindingFromClassOptions , BindingScope , Constructor , Context , createBindingFromClass , DynamicValueProviderClass , generateUniqueId , Interceptor , InterceptorBindingOptions , JSONObject , Provider , registerInterceptor , ValueOrPromise , } from '@loopback/context' ; import assert from 'assert' ; import debugFactory from 'debug' ; import { once } from 'events' ; import { Component , mountComponent } from './component' ; import { CoreBindings , CoreTags } from './keys' ; import { asLifeCycleObserver , isLifeCycleObserverClass , LifeCycleObserver , } from './lifecycle' ; import { LifeCycleObserverRegistry } from './lifecycle-registry' ; import { Server } from './server' ; import { createServiceBinding , ServiceOptions } from './service' ; const debug = debugFactory ( 'loopback:core:application' ) ; const debugShutdown = debugFactory ( 'loopback:core:application:shutdown' ) ; const debugWarning = debugFactory ( 'loopback:core:application:warning' ) ; param configOrParent param parent function buildConstructorArgs ( configOrParent ? , parent ? ) { let name ; let parentCtx ; if ( configOrParent instanceof Context ) { parentCtx = configOrParent ; name = undefined ; } else { parentCtx = parent ; name = configOrParent ?. name ; } return [ parentCtx , name ] ; } /**
 * Application is the container for various types of artifacts, such as
 * components, servers, controllers, repositories, datasources, connectors,
 * and models.
 */ export class Application extends Context implements LifeCycleObserver { public readonly options : ApplicationConfig ; /**
     * A flag to indicate that the application is being shut down
     */ private _isShuttingDown = false ; private _shutdownOptions : ShutdownOptions ; private _signalListener : ( signal : string ) => Promise < void > ; private _initialized = false ; /**
     * State of the application
     */ private _state = 'created' ; /**
     * Get the state of the application. The initial state is `created` and it can
     * transition as follows by `start` and `stop`:
     *
     * 1. start
     *   - !started -> starting -> started
     *   - started -> started (no-op)
     * 2. stop
     *   - (started | initialized) -> stopping -> stopped
     *   - ! (started || initialized) -> stopped (no-op)
     *
     * Two types of states are expected:
     * - stable, such as `started` and `stopped`
     * - in process, such as `booting` and `starting`
     *
     * Operations such as `start` and `stop` can only be called at a stable state.
     * The logic should immediately set the state to a new one indicating work in
     * process, such as `starting` and `stopping`.
     */ public get state ( ) { return this . _state ; } param parent constructor ( parent ) ; param config param parent constructor ( config ? , parent ? ) ; constructor ( configOrParent ? , parent ? ) { super ( ... buildConstructorArgs ( configOrParent , parent ) ) ; this . scope = BindingScope . APPLICATION ; this . options = configOrParent instanceof Context ? { } : configOrParent ?? { } ; this . _debug = debug ; this . bind ( CoreBindings . LIFE_CYCLE_OBSERVER_REGISTRY ) . toClass ( LifeCycleObserverRegistry ) . inScope ( BindingScope . SINGLETON ) ; this . bind ( CoreBindings . APPLICATION_INSTANCE ) . to ( this ) ; this . bind ( CoreBindings . APPLICATION_CONFIG ) . to ( this . options ) ; this . configure ( CoreBindings . APPLICATION_INSTANCE ) . toAlias ( CoreBindings . APPLICATION_CONFIG ) ; this . _shutdownOptions = { signals : [ 'SIGTERM' ] , ... this . options . shutdown } ; } param controllerCtor param name returns example controller < T > ( controllerCtor , nameOrOptions ? ) { this . debug ( 'Adding controller %s' , nameOrOptions ?? controllerCtor . name ) ; const binding = createBindingFromClass ( controllerCtor , { namespace : CoreBindings . CONTROLLERS , type : CoreTags . CONTROLLER , defaultScope : BindingScope . TRANSIENT , ... toOptions ( nameOrOptions ) , } ) ; this . add ( binding ) ; return binding ; } example param server param nameOrOptions returns public server < T extends Server > ( ctor , nameOrOptions ? ) { this . debug ( 'Adding server %s' , nameOrOptions ?? ctor . name ) ; const binding = createBindingFromClass ( ctor , { namespace : CoreBindings . SERVERS , type : CoreTags . SERVER , defaultScope : BindingScope . SINGLETON , ... toOptions ( nameOrOptions ) , } ) . apply ( asLifeCycleObserver ) ; this . add ( binding ) ; return binding ; } remarks param ctors returns public servers < T extends Server > ( ctors ) { return ctors . map ( ctor => this . server ( ctor ) ) ; } typeParam param ctor returns public async getServer < T extends Server > ( target ) { let key ; if ( typeof target === 'string' ) { key = `${ CoreBindings . SERVERS }.${ target }` ; } else { const ctor = target as Constructor < T > ; key = `${ CoreBindings . SERVERS }.${ ctor . name }` ; } return this . get < T > ( key ) ; } param op protected assertNotInProcess ( op ) { assert ( ! this . _state . endsWith ( 'ing' ) , `Cannot ${ op } the application as it is ${ this . _state }.` ) ; } param op param states protected assertInStates ( op , ... states ) { assert ( states . includes ( this . _state ) , `Cannot ${ op } the application as it is ${ this . _state }. Valid states are ${ states }.` ) ; } param state protected setState ( state ) { const oldState = this . _state ; this . _state = state ; if ( oldState !== state ) { this . emit ( 'stateChanged' , { from : oldState , to : this . _state } ) ; this . emit ( state ) ; } } protected async awaitState ( state ) { await once ( this , state ) ; } /**
     * Initialize the application, and all of its registered observers. The
     * application state is checked to ensure the integrity of `initialize`.
     *
     * If the application is already initialized, no operation is performed.
     *
     * This method is automatically invoked by `start()` if the application is not
     * initialized.
     */ public async init ( ) { if ( this . _initialized ) return ; if ( this . _state === 'initializing' ) return this . awaitState ( 'initialized' ) ; this . assertNotInProcess ( 'initialize' ) ; this . setState ( 'initializing' ) ; const registry = await this . getLifeCycleObserverRegistry ( ) ; await registry . init ( ) ; this . _initialized = true ; this . setState ( 'initialized' ) ; } param fn returns public onInit ( fn ) { const key = [ CoreBindings . LIFE_CYCLE_OBSERVERS , fn . name || '<onInit>' , generateUniqueId ( ) , ] . join ( '.' ) ; return this . bind < LifeCycleObserver > ( key ) . to ( { init : fn } ) . apply ( asLifeCycleObserver ) ; } /**
     * Start the application, and all of its registered observers. The application
     * state is checked to ensure the integrity of `start`.
     *
     * If the application is not initialized, it calls first `init()` to
     * initialize the application. This only happens if `start()` is called for
     * the first time.
     *
     * If the application is already started, no operation is performed.
     */ public async start ( ) { if ( ! this . _initialized ) await this . init ( ) ; if ( this . _state === 'starting' ) return this . awaitState ( 'started' ) ; this . assertNotInProcess ( 'start' ) ; if ( this . _state === 'started' ) return ; this . setState ( 'starting' ) ; this . setupShutdown ( ) ; const registry = await this . getLifeCycleObserverRegistry ( ) ; await registry . start ( ) ; this . setState ( 'started' ) ; } param fn returns public onStart ( fn ) { const key = [ CoreBindings . LIFE_CYCLE_OBSERVERS , fn . name || '<onStart>' , generateUniqueId ( ) , ] . join ( '.' ) ; return this . bind < LifeCycleObserver > ( key ) . to ( { start : fn } ) . apply ( asLifeCycleObserver ) ; } /**
     * Stop the application instance and all of its registered observers. The
     * application state is checked to ensure the integrity of `stop`.
     *
     * If the application is already stopped or not started, no operation is
     * performed.
     */ public async stop ( ) { if ( this . _state === 'stopping' ) return this . awaitState ( 'stopped' ) ; this . assertNotInProcess ( 'stop' ) ; if ( this . _state !== 'started' && this . _state !== 'initialized' ) return ; this . setState ( 'stopping' ) ; if ( ! this . _isShuttingDown ) { this . removeSignalListener ( ) ; } const registry = await this . getLifeCycleObserverRegistry ( ) ; await registry . stop ( ) ; this . setState ( 'stopped' ) ; } param fn returns public onStop ( fn ) { const key = [ CoreBindings . LIFE_CYCLE_OBSERVERS , fn . name || '<onStop>' , generateUniqueId ( ) , ] . join ( '.' ) ; return this . bind < LifeCycleObserver > ( key ) . to ( { stop : fn } ) . apply ( asLifeCycleObserver ) ; } private async getLifeCycleObserverRegistry ( ) { return this . get ( CoreBindings . LIFE_CYCLE_OBSERVER_REGISTRY ) ; } param componentCtor param nameOrOptions example public component < T extends Component = Component > ( componentCtor , nameOrOptions ? ) { this . debug ( 'Adding component: %s' , nameOrOptions ?? componentCtor . name ) ; const binding = createBindingFromClass ( componentCtor , { namespace : CoreBindings . COMPONENTS , type : CoreTags . COMPONENT , defaultScope : BindingScope . SINGLETON , ... toOptions ( nameOrOptions ) , } ) ; if ( isLifeCycleObserverClass ( componentCtor ) ) { binding . apply ( asLifeCycleObserver ) ; } this . add ( binding ) ; const instance = this . getSync < Component > ( binding . key ) ; mountComponent ( this , instance ) ; return binding ; } param metadata public setMetadata ( metadata ) { this . bind ( CoreBindings . APPLICATION_METADATA ) . to ( metadata ) ; } param ctor param nameOrOptions public lifeCycleObserver < T extends LifeCycleObserver > ( ctor , nameOrOptions ? ) { this . debug ( 'Adding life cycle observer %s' , nameOrOptions ?? ctor . name ) ; const binding = createBindingFromClass ( ctor , { namespace : CoreBindings . LIFE_CYCLE_OBSERVERS , type : CoreTags . LIFE_CYCLE_OBSERVER , defaultScope : BindingScope . SINGLETON , ... toOptions ( nameOrOptions ) , } ) . apply ( asLifeCycleObserver ) ; this . add ( binding ) ; return binding ; } param cls example injectable inject inject public service < S > ( cls , nameOrOptions ? ) { const options = toOptions ( nameOrOptions ) ; const binding = createServiceBinding ( cls , options ) ; this . add ( binding ) ; return binding ; } param interceptor param nameOrOptions public interceptor ( interceptor , nameOrOptions ? ) { const options = toOptions ( nameOrOptions ) ; return registerInterceptor ( this , interceptor , options ) ; } /**
     * Set up signals that are captured to shutdown the application
     */ protected setupShutdown ( ) { if ( this . _signalListener != null ) { this . registerSignalListener ( ) ; return this . _signalListener ; } const gracePeriod = this . _shutdownOptions . gracePeriod ; this . _signalListener = async ( signal ) => { const kill = ( ) => { this . removeSignalListener ( ) ; process . kill ( process . pid , signal ) ; } ; debugShutdown ( '[%s] Signal %s received for process %d' , this . name , signal , process . pid ) ; if ( ! this . _isShuttingDown ) { this . _isShuttingDown = true ; let timer ; if ( typeof gracePeriod === 'number' && ! isNaN ( gracePeriod ) ) { timer = setTimeout ( kill , gracePeriod ) ; } try { await this . stop ( ) ; } finally { if ( timer != null ) clearTimeout ( timer ) ; kill ( ) ; } } } ; this . registerSignalListener ( ) ; return this . _signalListener ; } private registerSignalListener ( ) { const { signals = [ ] } = this . _shutdownOptions ; debugShutdown ( '[%s] Registering signal listeners on the process %d' , this . name , process . pid , signals ) ; signals . forEach ( sig => { if ( process . getMaxListeners ( ) <= process . listenerCount ( sig ) ) { if ( debugWarning . enabled ) { debugWarning ( '[%s] %d %s listeners are added to process %d' , this . name , process . listenerCount ( sig ) , sig , process . pid , new Error ( 'MaxListenersExceededWarning' ) ) ; } } process . on ( sig , this . _signalListener ) ; } ) ; } private removeSignalListener ( ) { if ( this . _signalListener == null ) return ; const { signals = [ ] } = this . _shutdownOptions ; debugShutdown ( '[%s] Removing signal listeners on the process %d' , this . name , process . pid , signals ) ; signals . forEach ( sig => process . removeListener ( sig , this . _signalListener ) ) ; } } param nameOrOptions function toOptions ( nameOrOptions ? ) { if ( typeof nameOrOptions === 'string' ) { return { name : nameOrOptions } ; } return nameOrOptions ?? { } ; } /**
 * Options to set up application shutdown
 */ export type ShutdownOptions = { /**
     * An array of signals to be trapped for graceful shutdown
     */ signals ? : NodeJS . Signals [ ] ; /**
     * Period in milliseconds to wait for the grace shutdown to finish before
     * exiting the process
     */ gracePeriod ? : number ; } ; /**
 * Configuration for application
 */ export interface ApplicationConfig { /**
     * Name of the application context
     */ name ? : string ; /**
     * Configuration for signals that shut down the application
     */ shutdown ? : ShutdownOptions ; /**
     * Other properties
     */ [ prop ] : any ; } export type ControllerClass < T = any > = Constructor < T > ; export type ServiceOrProviderClass < T = any > = Constructor < T | Provider < T > > | DynamicValueProviderClass < T > ; /**
 * Type description for `package.json`
 */ export interface ApplicationMetadata extends JSONObject { name : string ; version : string ; description : string ; }