import { Binding } from "../bindings/binding" ; import * as ERROR_MSGS from "../constants/error_msgs" ; import { BindingScopeEnum , TargetTypeEnum } from "../constants/literal_types" ; import * as METADATA_KEY from "../constants/metadata_keys" ; import { interfaces } from "../interfaces/interfaces" ; import { MetadataReader } from "../planning/metadata_reader" ; import { createMockRequest , getBindingDictionary , plan } from "../planning/planner" ; import { resolve } from "../resolution/resolver" ; import { BindingToSyntax } from "../syntax/binding_to_syntax" ; import { isPromise , isPromiseOrContainsPromise } from "../utils/async" ; import { id } from "../utils/id" ; import { getServiceIdentifierAsString } from "../utils/serialization" ; import { ContainerSnapshot } from "./container_snapshot" ; import { Lookup } from "./lookup" ; import { ModuleActivationStore } from "./module_activation_store" ; type GetArgs < T > = Omit < interfaces . NextArgs < T > , 'contextInterceptor' | 'targetType' > ; class Container implements interfaces . Container { public id : number ; public parent : interfaces . Container | null ; public readonly options : interfaces . ContainerOptions ; private _middleware : interfaces . Next | null ; private _bindingDictionary : interfaces . Lookup < interfaces . Binding < unknown > > ; private _activations : interfaces . Lookup < interfaces . BindingActivation < unknown > > ; private _deactivations : interfaces . Lookup < interfaces . BindingDeactivation < unknown > > ; private _snapshots : interfaces . ContainerSnapshot [ ] ; private _metadataReader : interfaces . MetadataReader ; private _moduleActivationStore : interfaces . ModuleActivationStore ; public static merge ( container1 , container2 , ... containers ) { const container = new Container ( ) ; const targetContainers = [ container1 , container2 , ... containers ] . map ( ( targetContainer ) => getBindingDictionary ( targetContainer ) ) ; const bindingDictionary = getBindingDictionary ( container ) ; function copyDictionary ( origin , destination ) { origin . traverse ( ( _key , value ) => { value . forEach ( ( binding ) => { destination . add ( binding . serviceIdentifier , binding . clone ( ) ) ; } ) ; } ) ; } targetContainers . forEach ( ( targetBindingDictionary ) => { copyDictionary ( targetBindingDictionary , bindingDictionary ) ; } ) ; return container ; } public constructor ( containerOptions ? ) { const options = containerOptions || { } ; if ( typeof options !== "object" ) { throw new Error ( `${ ERROR_MSGS . CONTAINER_OPTIONS_MUST_BE_AN_OBJECT }` ) ; } if ( options . defaultScope === undefined ) { options . defaultScope = BindingScopeEnum . Transient ; } else if ( options . defaultScope !== BindingScopeEnum . Singleton && options . defaultScope !== BindingScopeEnum . Transient && options . defaultScope !== BindingScopeEnum . Request ) { throw new Error ( `${ ERROR_MSGS . CONTAINER_OPTIONS_INVALID_DEFAULT_SCOPE }` ) ; } if ( options . autoBindInjectable === undefined ) { options . autoBindInjectable = false ; } else if ( typeof options . autoBindInjectable !== "boolean" ) { throw new Error ( `${ ERROR_MSGS . CONTAINER_OPTIONS_INVALID_AUTO_BIND_INJECTABLE }` ) ; } if ( options . skipBaseClassChecks === undefined ) { options . skipBaseClassChecks = false ; } else if ( typeof options . skipBaseClassChecks !== "boolean" ) { throw new Error ( `${ ERROR_MSGS . CONTAINER_OPTIONS_INVALID_SKIP_BASE_CHECK }` ) ; } this . options = { autoBindInjectable : options . autoBindInjectable , defaultScope : options . defaultScope , skipBaseClassChecks : options . skipBaseClassChecks } ; this . id = id ( ) ; this . _bindingDictionary = new Lookup < interfaces . Binding < unknown > > ( ) ; this . _snapshots = [ ] ; this . _middleware = null ; this . _activations = new Lookup < interfaces . BindingActivation < unknown > > ( ) ; this . _deactivations = new Lookup < interfaces . BindingDeactivation < unknown > > ( ) ; this . parent = null ; this . _metadataReader = new MetadataReader ( ) ; this . _moduleActivationStore = new ModuleActivationStore ( ) ; } public load ( ... modules ) { const getHelpers = this . _getContainerModuleHelpersFactory ( ) ; for ( const currentModule of modules ) { const containerModuleHelpers = getHelpers ( currentModule . id ) ; currentModule . registry ( containerModuleHelpers . bindFunction , containerModuleHelpers . unbindFunction , containerModuleHelpers . isboundFunction , containerModuleHelpers . rebindFunction , containerModuleHelpers . unbindAsyncFunction , containerModuleHelpers . onActivationFunction , containerModuleHelpers . onDeactivationFunction ) ; } } public async loadAsync ( ... modules ) { const getHelpers = this . _getContainerModuleHelpersFactory ( ) ; for ( const currentModule of modules ) { const containerModuleHelpers = getHelpers ( currentModule . id ) ; await currentModule . registry ( containerModuleHelpers . bindFunction , containerModuleHelpers . unbindFunction , containerModuleHelpers . isboundFunction , containerModuleHelpers . rebindFunction , containerModuleHelpers . unbindAsyncFunction , containerModuleHelpers . onActivationFunction , containerModuleHelpers . onDeactivationFunction ) ; } } public unload ( ... modules ) { modules . forEach ( ( module ) => { const deactivations = this . _removeModuleBindings ( module . id ) ; this . _deactivateSingletons ( deactivations ) ; this . _removeModuleHandlers ( module . id ) ; } ) ; } public async unloadAsync ( ... modules ) { for ( const module of modules ) { const deactivations = this . _removeModuleBindings ( module . id ) ; await this . _deactivateSingletonsAsync ( deactivations ) ; this . _removeModuleHandlers ( module . id ) ; } } public bind < T > ( serviceIdentifier ) { const scope = this . options . defaultScope || BindingScopeEnum . Transient ; const binding = new Binding < T > ( serviceIdentifier , scope ) ; this . _bindingDictionary . add ( serviceIdentifier , binding as Binding < unknown > ) ; return new BindingToSyntax < T > ( binding ) ; } public rebind < T > ( serviceIdentifier ) { this . unbind ( serviceIdentifier ) ; return this . bind ( serviceIdentifier ) ; } public async rebindAsync < T > ( serviceIdentifier ) { await this . unbindAsync ( serviceIdentifier ) ; return this . bind ( serviceIdentifier ) ; } public unbind ( serviceIdentifier ) { if ( this . _bindingDictionary . hasKey ( serviceIdentifier ) ) { const bindings = this . _bindingDictionary . get ( serviceIdentifier ) ; this . _deactivateSingletons ( bindings ) ; } this . _removeServiceFromDictionary ( serviceIdentifier ) ; } public async unbindAsync ( serviceIdentifier ) { if ( this . _bindingDictionary . hasKey ( serviceIdentifier ) ) { const bindings = this . _bindingDictionary . get ( serviceIdentifier ) ; await this . _deactivateSingletonsAsync ( bindings ) ; } this . _removeServiceFromDictionary ( serviceIdentifier ) ; } public unbindAll ( ) { this . _bindingDictionary . traverse ( ( _key , value ) => { this . _deactivateSingletons ( value ) ; } ) ; this . _bindingDictionary = new Lookup < Binding < unknown > > ( ) ; } public async unbindAllAsync ( ) { const promises = [ ] ; this . _bindingDictionary . traverse ( ( _key , value ) => { promises . push ( this . _deactivateSingletonsAsync ( value ) ) ; } ) ; await Promise . all ( promises ) ; this . _bindingDictionary = new Lookup < Binding < unknown > > ( ) ; } public onActivation < T > ( serviceIdentifier , onActivation ) { this . _activations . add ( serviceIdentifier , onActivation as interfaces . BindingActivation < unknown > ) ; } public onDeactivation < T > ( serviceIdentifier , onDeactivation ) { this . _deactivations . add ( serviceIdentifier , onDeactivation as interfaces . BindingDeactivation < unknown > ) ; } public isBound ( serviceIdentifier ) { let bound = this . _bindingDictionary . hasKey ( serviceIdentifier ) ; if ( ! bound && this . parent ) { bound = this . parent . isBound ( serviceIdentifier ) ; } return bound ; } public isCurrentBound < T > ( serviceIdentifier ) { return this . _bindingDictionary . hasKey ( serviceIdentifier ) ; } public isBoundNamed ( serviceIdentifier , named ) { return this . isBoundTagged ( serviceIdentifier , METADATA_KEY . NAMED_TAG , named ) ; } public isBoundTagged ( serviceIdentifier , key , value ) { let bound = false ; if ( this . _bindingDictionary . hasKey ( serviceIdentifier ) ) { const bindings = this . _bindingDictionary . get ( serviceIdentifier ) ; const request = createMockRequest ( this , serviceIdentifier , key , value ) ; bound = bindings . some ( ( b ) => b . constraint ( request ) ) ; } if ( ! bound && this . parent ) { bound = this . parent . isBoundTagged ( serviceIdentifier , key , value ) ; } return bound ; } public snapshot ( ) { this . _snapshots . push ( ContainerSnapshot . of ( this . _bindingDictionary . clone ( ) , this . _middleware , this . _activations . clone ( ) , this . _deactivations . clone ( ) , this . _moduleActivationStore . clone ( ) ) ) ; } public restore ( ) { const snapshot = this . _snapshots . pop ( ) ; if ( snapshot === undefined ) { throw new Error ( ERROR_MSGS . NO_MORE_SNAPSHOTS_AVAILABLE ) ; } this . _bindingDictionary = snapshot . bindings ; this . _activations = snapshot . activations ; this . _deactivations = snapshot . deactivations ; this . _middleware = snapshot . middleware ; this . _moduleActivationStore = snapshot . moduleActivationStore ; } public createChild ( containerOptions ? ) { const child = new Container ( containerOptions || this . options ) ; child . parent = this ; return child ; } public applyMiddleware ( ... middlewares ) { const initial = ( this . _middleware ) ? this . _middleware : this . _planAndResolve ( ) ; this . _middleware = middlewares . reduce ( ( prev , curr ) => curr ( prev ) , initial ) ; } public applyCustomMetadataReader ( metadataReader ) { this . _metadataReader = metadataReader ; } public get < T > ( serviceIdentifier ) { const getArgs = this . _getNotAllArgs ( serviceIdentifier , false ) ; return this . _getButThrowIfAsync < T > ( getArgs ) as T ; } public async getAsync < T > ( serviceIdentifier ) { const getArgs = this . _getNotAllArgs ( serviceIdentifier , false ) ; return this . _get < T > ( getArgs ) as Promise < T > | T ; } public getTagged < T > ( serviceIdentifier , key , value ) { const getArgs = this . _getNotAllArgs ( serviceIdentifier , false , key , value ) ; return this . _getButThrowIfAsync < T > ( getArgs ) as T ; } public async getTaggedAsync < T > ( serviceIdentifier , key , value ) { const getArgs = this . _getNotAllArgs ( serviceIdentifier , false , key , value ) ; return this . _get < T > ( getArgs ) as Promise < T > | T ; } public getNamed < T > ( serviceIdentifier , named ) { return this . getTagged < T > ( serviceIdentifier , METADATA_KEY . NAMED_TAG , named ) ; } public getNamedAsync < T > ( serviceIdentifier , named ) { return this . getTaggedAsync < T > ( serviceIdentifier , METADATA_KEY . NAMED_TAG , named ) ; } public getAll < T > ( serviceIdentifier ) { const getArgs = this . _getAllArgs ( serviceIdentifier ) ; return this . _getButThrowIfAsync < T > ( getArgs ) as T [ ] ; } public getAllAsync < T > ( serviceIdentifier ) { const getArgs = this . _getAllArgs ( serviceIdentifier ) ; return this . _getAll ( getArgs ) ; } public getAllTagged < T > ( serviceIdentifier , key , value ) { const getArgs = this . _getNotAllArgs ( serviceIdentifier , true , key , value ) ; return this . _getButThrowIfAsync < T > ( getArgs ) as T [ ] ; } public getAllTaggedAsync < T > ( serviceIdentifier , key , value ) { const getArgs = this . _getNotAllArgs ( serviceIdentifier , true , key , value ) ; return this . _getAll ( getArgs ) ; } public getAllNamed < T > ( serviceIdentifier , named ) { return this . getAllTagged < T > ( serviceIdentifier , METADATA_KEY . NAMED_TAG , named ) ; } public getAllNamedAsync < T > ( serviceIdentifier , named ) { return this . getAllTaggedAsync < T > ( serviceIdentifier , METADATA_KEY . NAMED_TAG , named ) ; } public resolve < T > ( constructorFunction ) { const isBound = this . isBound ( constructorFunction ) ; if ( ! isBound ) { this . bind < T > ( constructorFunction ) . toSelf ( ) ; } const resolved = this . get < T > ( constructorFunction ) ; if ( ! isBound ) { this . unbind ( constructorFunction ) ; } return resolved ; } private _preDestroy ( constructor , instance ) { if ( Reflect . hasMetadata ( METADATA_KEY . PRE_DESTROY , constructor ) ) { const data = Reflect . getMetadata ( METADATA_KEY . PRE_DESTROY , constructor ) ; return instance [ data . value as string ] ( ) ; } } private _removeModuleHandlers ( moduleId ) { const moduleActivationsHandlers = this . _moduleActivationStore . remove ( moduleId ) ; this . _activations . removeIntersection ( moduleActivationsHandlers . onActivations ) ; this . _deactivations . removeIntersection ( moduleActivationsHandlers . onDeactivations ) ; } private _removeModuleBindings ( moduleId ) { return this . _bindingDictionary . removeByCondition ( binding => binding . moduleId === moduleId ) ; } private _deactivate < T > ( binding , instance ) { const constructor = Object . getPrototypeOf ( instance ) . constructor ; try { if ( this . _deactivations . hasKey ( binding . serviceIdentifier ) ) { const result = this . _deactivateContainer ( instance , this . _deactivations . get ( binding . serviceIdentifier ) . values ( ) ) ; if ( isPromise ( result ) ) { return this . _handleDeactivationError ( result . then ( ( ) => this . _propagateContainerDeactivationThenBindingAndPreDestroyAsync ( binding , instance , constructor ) ) , constructor ) ; } } const propagateDeactivationResult = this . _propagateContainerDeactivationThenBindingAndPreDestroy ( binding , instance , constructor ) ; if ( isPromise ( propagateDeactivationResult ) ) { return this . _handleDeactivationError ( propagateDeactivationResult , constructor ) ; } } catch ( ex ) { throw new Error ( ERROR_MSGS . ON_DEACTIVATION_ERROR ( constructor . name , ex . message ) ) ; } } private async _handleDeactivationError ( asyncResult , constructor ) { try { await asyncResult ; } catch ( ex ) { throw new Error ( ERROR_MSGS . ON_DEACTIVATION_ERROR ( constructor . name , ex . message ) ) ; } } private _deactivateContainer < T > ( instance , deactivationsIterator ) { let deactivation = deactivationsIterator . next ( ) ; while ( deactivation . value ) { const result = deactivation . value ( instance ) ; if ( isPromise ( result ) ) { return result . then ( ( ) => this . _deactivateContainerAsync ( instance , deactivationsIterator ) ) ; } deactivation = deactivationsIterator . next ( ) ; } } private async _deactivateContainerAsync < T > ( instance , deactivationsIterator ) { let deactivation = deactivationsIterator . next ( ) ; while ( deactivation . value ) { await deactivation . value ( instance ) ; deactivation = deactivationsIterator . next ( ) ; } } private _getContainerModuleHelpersFactory ( ) { const setModuleId = ( bindingToSyntax , moduleId ) => { bindingToSyntax . _binding . moduleId = moduleId ; } ; const getBindFunction = ( moduleId ) => ( serviceIdentifier ) => { const bindingToSyntax = this . bind ( serviceIdentifier ) ; setModuleId ( bindingToSyntax , moduleId ) ; return bindingToSyntax ; } ; const getUnbindFunction = ( ) => ( serviceIdentifier ) => { return this . unbind ( serviceIdentifier ) ; } ; const getUnbindAsyncFunction = ( ) => ( serviceIdentifier ) => { return this . unbindAsync ( serviceIdentifier ) ; } ; const getIsboundFunction = ( ) => ( serviceIdentifier ) => { return this . isBound ( serviceIdentifier ) ; } ; const getRebindFunction = ( moduleId ) => ( serviceIdentifier ) => { const bindingToSyntax = this . rebind ( serviceIdentifier ) ; setModuleId ( bindingToSyntax , moduleId ) ; return bindingToSyntax ; } ; const getOnActivationFunction = ( moduleId ) => ( serviceIdentifier , onActivation ) => { this . _moduleActivationStore . addActivation ( moduleId , serviceIdentifier , onActivation ) ; this . onActivation ( serviceIdentifier , onActivation ) ; } ; const getOnDeactivationFunction = ( moduleId ) => ( serviceIdentifier , onDeactivation ) => { this . _moduleActivationStore . addDeactivation ( moduleId , serviceIdentifier , onDeactivation ) ; this . onDeactivation ( serviceIdentifier , onDeactivation ) ; } ; return ( mId ) => ( { bindFunction : getBindFunction ( mId ) , isboundFunction : getIsboundFunction ( ) , onActivationFunction : getOnActivationFunction ( mId ) , onDeactivationFunction : getOnDeactivationFunction ( mId ) , rebindFunction : getRebindFunction ( mId ) , unbindFunction : getUnbindFunction ( ) , unbindAsyncFunction : getUnbindAsyncFunction ( ) } ) ; } private _getAll < T > ( getArgs ) { return Promise . all ( this . _get < T > ( getArgs ) as ( Promise < T > | T ) [ ] ) ; } private _get < T > ( getArgs ) { const planAndResolveArgs = { ... getArgs , contextInterceptor : ( context ) => context , targetType : TargetTypeEnum . Variable } ; if ( this . _middleware ) { const middlewareResult = this . _middleware ( planAndResolveArgs ) ; if ( middlewareResult === undefined || middlewareResult === null ) { throw new Error ( ERROR_MSGS . INVALID_MIDDLEWARE_RETURN ) ; } return middlewareResult ; } return this . _planAndResolve < T > ( ) ( planAndResolveArgs ) ; } private _getButThrowIfAsync < T > ( getArgs ) { const result = this . _get < T > ( getArgs ) ; if ( isPromiseOrContainsPromise < T > ( result ) ) { throw new Error ( ERROR_MSGS . LAZY_IN_SYNC ( getArgs . serviceIdentifier ) ) ; } return result as ( T | T [ ] ) ; } private _getAllArgs < T > ( serviceIdentifier ) { const getAllArgs = { avoidConstraints : true , isMultiInject : true , serviceIdentifier , } ; return getAllArgs ; } private _getNotAllArgs < T > ( serviceIdentifier , isMultiInject , key ? , value ? ) { const getNotAllArgs = { avoidConstraints : false , isMultiInject , serviceIdentifier , key , value , } ; return getNotAllArgs ; } private _planAndResolve < T = unknown > ( ) { return ( args ) => { let context = plan ( this . _metadataReader , this , args . isMultiInject , args . targetType , args . serviceIdentifier , args . key , args . value , args . avoidConstraints ) ; context = args . contextInterceptor ( context ) ; const result = resolve < T > ( context ) ; return result ; } ; } private _deactivateIfSingleton ( binding ) { if ( ! binding . activated ) { return ; } if ( isPromise ( binding . cache ) ) { return binding . cache . then ( ( resolved ) => this . _deactivate ( binding , resolved ) ) ; } return this . _deactivate ( binding , binding . cache ) ; } private _deactivateSingletons ( bindings ) { for ( const binding of bindings ) { const result = this . _deactivateIfSingleton ( binding ) ; if ( isPromise ( result ) ) { throw new Error ( ERROR_MSGS . ASYNC_UNBIND_REQUIRED ) ; } } } private async _deactivateSingletonsAsync ( bindings ) { await Promise . all ( bindings . map ( b => this . _deactivateIfSingleton ( b ) ) ) ; } private _propagateContainerDeactivationThenBindingAndPreDestroy < T > ( binding , instance , constructor ) { if ( this . parent ) { return this . _deactivate . bind ( this . parent ) ( binding , instance ) ; } else { return this . _bindingDeactivationAndPreDestroy ( binding , instance , constructor ) ; } } private async _propagateContainerDeactivationThenBindingAndPreDestroyAsync < T > ( binding , instance , constructor ) { if ( this . parent ) { await this . _deactivate . bind ( this . parent ) ( binding , instance ) ; } else { await this . _bindingDeactivationAndPreDestroyAsync ( binding , instance , constructor ) ; } } private _removeServiceFromDictionary ( serviceIdentifier ) { try { this . _bindingDictionary . remove ( serviceIdentifier ) ; } catch ( e ) { throw new Error ( `${ ERROR_MSGS . CANNOT_UNBIND } ${ getServiceIdentifierAsString ( serviceIdentifier ) }` ) ; } } private _bindingDeactivationAndPreDestroy < T > ( binding , instance , constructor ) { if ( typeof binding . onDeactivation === "function" ) { const result = binding . onDeactivation ( instance ) ; if ( isPromise ( result ) ) { return result . then ( ( ) => this . _preDestroy ( constructor , instance ) ) ; } } return this . _preDestroy ( constructor , instance ) ; } private async _bindingDeactivationAndPreDestroyAsync < T > ( binding , instance , constructor ) { if ( typeof binding . onDeactivation === "function" ) { await binding . onDeactivation ( instance ) ; } await this . _preDestroy ( constructor , instance ) ; } } export { Container } ;