import { Binding , Context , ContextView , inject , invokeMethod , sortBindingsByPhase , } from '@loopback/context' ; import { CoreBindings , CoreTags } from './keys' ; import { LifeCycleObserver , lifeCycleObserverFilter } from './lifecycle' ; import debugFactory from 'debug' ; const debug = debugFactory ( 'loopback:core:lifecycle' ) ; /**
 * A group of life cycle observers
 */ export type LifeCycleObserverGroup = { /**
     * Observer group name
     */ group : string ; /**
     * Bindings for observers within the group
     */ bindings : Readonly < Binding < LifeCycleObserver > > [ ] ; } ; export type LifeCycleObserverOptions = { /**
     * Control the order of observer groups for notifications. For example,
     * with `['datasource', 'server']`, the observers in `datasource` group are
     * notified before those in `server` group during `start`. Please note that
     * observers are notified in the reverse order during `stop`.
     */ orderedGroups : string [ ] ; /**
     * Override and disable lifecycle observer groups. This setting applies to
     * both ordered groups (i.e. those defined in `orderedGroups`) and unordered
     * groups.
     */ disabledGroups ? : string [ ] ; /**
     * Notify observers of the same group in parallel, default to `true`
     */ parallel ? : boolean ; } ; export const DEFAULT_ORDERED_GROUPS = [ 'server' ] ; /**
 * A context-based registry for life cycle observers
 */ export class LifeCycleObserverRegistry implements LifeCycleObserver { constructor ( @ inject . context ( ) protected readonly context , @ inject . view ( lifeCycleObserverFilter ) protected readonly observersView , @ inject ( CoreBindings . LIFE_CYCLE_OBSERVER_OPTIONS , { optional : true } ) protected readonly options = { parallel : true , orderedGroups : DEFAULT_ORDERED_GROUPS , } ) { } setOrderedGroups ( groups ) { this . options . orderedGroups = groups ; } /**
     * Get observer groups ordered by the group
     */ public getObserverGroupsByOrder ( ) { const bindings = this . observersView . bindings ; const groups = this . sortObserverBindingsByGroup ( bindings ) ; if ( debug . enabled ) { debug ( 'Observer groups: %j' , groups . map ( g => ( { group : g . group , bindings : g . bindings . map ( b => b . key ) , } ) ) ) ; } return groups ; } param binding protected getObserverGroup ( binding ) { let group = binding . tagMap [ CoreTags . LIFE_CYCLE_OBSERVER_GROUP ] ; if ( ! group ) { group = this . options . orderedGroups . find ( g => binding . tagMap [ g ] === g ) ; } group = group || '' ; debug ( 'Binding %s is configured with observer group %s' , binding . key , group ) ; return group ; } param bindings protected sortObserverBindingsByGroup ( bindings ) { const groupMap = new Map ( ) ; sortBindingsByPhase ( bindings , CoreTags . LIFE_CYCLE_OBSERVER_GROUP , this . options . orderedGroups ) ; for ( const binding of bindings ) { const group = this . getObserverGroup ( binding ) ; let bindingsInGroup = groupMap . get ( group ) ; if ( bindingsInGroup == null ) { bindingsInGroup = [ ] ; groupMap . set ( group , bindingsInGroup ) ; } bindingsInGroup . push ( binding ) ; } const groups = [ ] ; for ( const [ group , bindingsInGroup ] of groupMap ) { groups . push ( { group , bindings : bindingsInGroup } ) ; } return groups ; } param group param event protected async notifyObservers ( observers , bindings , event ) { if ( ! this . options . parallel ) { let index = 0 ; for ( const observer of observers ) { debug ( 'Invoking %s observer for binding %s' , event , bindings [ index ] . key ) ; index ++ ; await this . invokeObserver ( observer , event ) ; } return ; } const notifiers = observers . map ( ( observer , index ) => { debug ( 'Invoking %s observer for binding %s' , event , bindings [ index ] . key ) ; return this . invokeObserver ( observer , event ) ; } ) ; await Promise . all ( notifiers ) ; } param observer param event protected async invokeObserver ( observer , event ) { if ( typeof observer [ event ] === 'function' ) { await invokeMethod ( observer , event , this . context , [ undefined ] , { skipInterceptors : true , } ) ; } } param events param groups protected async notifyGroups ( events , groups , reverse = false ) { const observers = await this . observersView . values ( ) ; const bindings = this . observersView . bindings ; const found = observers . some ( observer => events . some ( e => typeof observer [ e ] === 'function' ) ) ; if ( ! found ) return ; if ( reverse ) { groups = [ ... groups ] . reverse ( ) ; } for ( const group of groups ) { if ( this . options . disabledGroups ?. includes ( group . group ) ) { debug ( 'Notification skipped (Group is disabled): %s' , group . group ) ; continue ; } const observersForGroup = [ ] ; const bindingsInGroup = reverse ? group . bindings . reverse ( ) : group . bindings ; for ( const binding of bindingsInGroup ) { const index = bindings . indexOf ( binding ) ; observersForGroup . push ( observers [ index ] ) ; } for ( const event of events ) { debug ( 'Beginning notification %s of %s...' , event ) ; await this . notifyObservers ( observersForGroup , group . bindings , event ) ; debug ( 'Finished notification %s of %s' , event ) ; } } } /**
     * Notify all life cycle observers by group of `init`
     */ public async init ( ) { debug ( 'Initializing the %s...' ) ; const groups = this . getObserverGroupsByOrder ( ) ; await this . notifyGroups ( [ 'init' ] , groups ) ; } /**
     * Notify all life cycle observers by group of `start`
     */ public async start ( ) { debug ( 'Starting the %s...' ) ; const groups = this . getObserverGroupsByOrder ( ) ; await this . notifyGroups ( [ 'start' ] , groups ) ; } /**
     * Notify all life cycle observers by group of `stop`
     */ public async stop ( ) { debug ( 'Stopping the %s...' ) ; const groups = this . getObserverGroupsByOrder ( ) ; await this . notifyGroups ( [ 'stop' ] , groups , true ) ; } }