import { DependencyCollection } from './collection' ; import { IdleValue } from './idle' ; import { getSingletonDependencies } from './singleton' ; import { Ctor , DependencyKey , DependencyValue , FactoryItem , Disposable , InitPromise , isClassItem , isFactoryItem , isValueItem , Identifier , isInitPromise } from './typings' ; import { assertRecursionNotTrappedInACircle , completeInitialization , getDependencies , getDependencyKeyName , requireInitialization } from './utils' ; export class Injector implements Disposable { private readonly parent ? : Injector ; private readonly collection : DependencyCollection ; constructor ( collection ? , parent ? ) { const _collection = collection || new DependencyCollection ( ) ; if ( ! parent ) { const newDependencies = getSingletonDependencies ( ) ; newDependencies . forEach ( ( d ) => { if ( ! _collection . has ( d [ 0 ] ) ) { _collection . add ( d [ 0 ] , d [ 1 ] ) ; } } ) ; } this . collection = _collection ; this . parent = parent ; } dispose ( ) { this . collection . dispose ( ) ; } add < T > ( ctor ) ; add < T > ( key , item ) ; add < T > ( ctorOrKey , item ? ) { this . collection . add ( ctorOrKey as any , item as any ) ; } /**
     * create a child Initializer to build layered injection system
     */ createChild ( dependencies = new DependencyCollection ( ) ) { return new Injector ( dependencies , this ) ; } /**
     * get a dependency or create one in the current injector
     */ getOrInit < T > ( key ) ; getOrInit < T > ( key , optional ) ; getOrInit < T > ( key , optional ? ) { const thing = this . getDependencyOrIdentifierPair ( key ) ; if ( typeof thing === 'undefined' ) { if ( ! optional ) { throw new Error ( `[wedi] "${ getDependencyKeyName ( key ) }" is not provided by any injector.` ) ; } return null ; } else if ( isInitPromise ( thing ) ) { return this . createAndCacheInstance ( key , thing ) ; } else if ( isValueItem ( thing ) ) { return thing . useValue ; } else if ( isFactoryItem ( thing ) ) { return this . invokeDependencyFactory ( key as Identifier < T > , thing ) ; } else if ( isClassItem ( thing ) ) { return this . createAndCacheInstance ( key , new InitPromise ( thing . useClass , ! ! thing . lazyInstantiation ) ) ; } else { return thing as T ; } } param ctor createInstance < T > ( ctor , ... extraParams ) { const theCtor = ctor instanceof InitPromise ? ctor . ctor : ctor ; const dependencies = getDependencies ( theCtor ) . sort ( ( a , b ) => a . index - b . index ) ; const resolvedArgs = [ ] ; let args = [ ... extraParams ] ; for ( const dependency of dependencies ) { const thing = this . getOrInit ( dependency . id , true ) ; if ( thing === null && ! dependency . optional ) { throw new Error ( `[wedi] "${ theCtor . name }" relies on a not provided dependency "${ getDependencyKeyName ( dependency . id ) }".` ) ; } resolvedArgs . push ( thing ) ; } const firstDependencyArgIndex = dependencies . length > 0 ? dependencies [ 0 ] . index : args . length ; if ( args . length !== firstDependencyArgIndex ) { console . warn ( `[wedi] expected ${ firstDependencyArgIndex } non-injected parameters ` + `but ${ args . length } parameters are provided.` ) ; const delta = firstDependencyArgIndex - args . length ; if ( delta > 0 ) { args = [ ... args , ... new Array ( delta ) . fill ( undefined ) ] ; } else { args = args . slice ( 0 , firstDependencyArgIndex ) ; } } return new theCtor ( ... args , ... resolvedArgs ) ; } private getDependencyOrIdentifierPair < T > ( id ) { return ( this . collection . get ( id ) || ( this . parent ? this . parent . getDependencyOrIdentifierPair ( id ) : undefined ) ) ; } private putDependencyBack < T > ( key , value ) { if ( this . collection . get ( key ) ) { this . collection . add ( key , value ) ; } else { this . parent ! . putDependencyBack ( key , value ) ; } } private createAndCacheInstance < T > ( dKey , initPromise ) { requireInitialization ( ) ; assertRecursionNotTrappedInACircle ( dKey ) ; const ctor = initPromise . ctor ; let thing ; if ( initPromise . lazyInstantiation ) { const idle = new IdleValue < T > ( ( ) => this . doCreateInstance ( dKey , ctor ) ) ; thing = new Proxy ( Object . create ( null ) , { get ( target , key ) { if ( key in target ) { return target [ key ] ; } const obj = idle . getValue ( ) ; let prop = ( obj as any ) [ key ] ; if ( typeof prop !== 'function' ) { return prop ; } prop = prop . bind ( obj ) ; target [ key ] = prop ; return prop ; } , set ( _target , key , value ) { ; ( idle . getValue ( ) as any ) [ key ] = value ; return true ; } } ) as T ; } else { thing = this . doCreateInstance ( dKey , ctor ) ; } completeInitialization ( ) ; return thing ; } private doCreateInstance < T > ( id , ctor ) { const thing = this . createInstance ( ctor ) ; this . putDependencyBack ( id , thing ) ; return thing ; } private invokeDependencyFactory < T > ( id , factory ) { const dependencies = factory . deps ?. map ( ( dp ) => this . getOrInit ( dp , true ) ) || [ ] ; const thing = factory . useFactory . call ( null , dependencies ) ; this . collection . add ( id , { useValue : thing } ) ; return thing ; } }