/**
 * A DI container for ES 6
 */ type LogMethod = ( ... args : any [ ] ) => void ; interface Logger { error : LogMethod ; debug : LogMethod ; } type ClassOf < T > = new ( ) => T ; type InjectableType < T > = ClassOf < T > | typeof Injector | string ; export type Provider < T > = ( injector : Injector , obj : object , field : string ) => T ; function getInjectableTypeKey ( type ) { return ! type ? '<undefined>' : type . name ? type . name : type ; } function getLocation ( obj , property ) { const objKey = getInjectableTypeKey ( obj . constructor ) ; return objKey + '.' + property ; } function validateType ( type , obj , prop ) { if ( ! type ) { throw new Error ( 'Type is undefined ' + getLocation ( obj , prop ) ) ; } } export function inject ( type ) { return function injectClassProperty ( obj , prop ) { validateType ( type , obj , prop ) ; Object . defineProperty ( obj , prop , { get ( ) { const i = ( this as any ) . __injector ; if ( ! i ) { throw new Error ( 'No injector defined @ ' + getLocation ( obj , prop ) + ' did you forget @injected ?' ) ; } return i . getInstance ( type , this , prop ) ; } , } ) ; } ; } /**
 * Explicit demarcation for a singleton scope
 */ export function singleton < T extends { new ( ... args ) : { } ; } > ( constructor ) { let instance ; ( constructor as any ) . __provider = function SingletonProvider ( injector ) { if ( ! instance ) { instance = instance = new constructor ( ) ; instance . __injector = injector ; if ( instance . __postConstruct ) { instance . __postConstruct . apply ( instance ) ; } } return instance ; } ; return constructor ; } /**
 * Create an instance of this type for each injection point
 */ export function newInstance < T extends { new ( ... args ) : { } ; } > ( constructor ) { ( constructor as any ) . __provider = function InstancePerObjectProvider ( injector , obj , prop ) { const o = obj as any ; if ( ! o . __instances ) { o . __instances = { } ; } let instance = o . __instances [ prop ] ; if ( ! instance ) { instance = o . __instances [ prop ] = new constructor ( ) as T ; instance . __injector = injector ; if ( instance . __postConstruct ) { instance . __postConstruct . apply ( instance ) ; } } return instance ; } ; return constructor ; } /**
 * Called after object is constructed; use this to access injected properties
 */ export function postConstruct ( target , prop , descriptor ) { const method = function ( this ) { this [ prop ] . apply ( this ) ; } ; if ( target . __postConstruct ) { const existing = target . __postConstruct ; target . __postConstruct = function ( this ) { existing . apply ( this ) ; method . apply ( this ) ; } ; } else { target . __postConstruct = method ; } } interface Bindable < T > { param { { new ( ) : T } } constructor to : ( constructor : { new ( ) : T ; } ) => void ; toProvider : ( provider : Provider < T > ) => void ; toInstance : ( instance : T ) => void ; } export class ConsoleLogger implements Logger { error = console . error . bind ( console ) ; debug = console . debug . bind ( console ) ; } export class Injector { /**
     * Holds instance providers
     */ providers = { } ; /**
     * This may be an injector with a parent, which can get delegated to
     */ parent ? : Injector ; /**
     * The logger to use, this defaults to `ConsoleLogger`
     */ logger : Logger ; constructor ( config = new ConsoleLogger ( ) ) { if ( config instanceof Injector ) { this . logger = config . logger ; this . parent = config ; } else { this . logger = config ; } } bind < T > ( type ) { const injector = this ; return { to : function RegisterType ( constructor ) { injector . registerProvider ( type , ( injector ) => injector . getInstance ( constructor ) ) ; } , toProvider : function RegisterProvider ( provider ) { injector . registerProvider ( type , provider ) ; } , toInstance : function RegisterInstance ( instance ) { injector . registerProvider ( type , ( ) => instance ) ; } , } ; } private registerProvider < T > ( type , provider ) { const key = getInjectableTypeKey ( type ) ; this . providers [ key ] = provider ; } getInstance < T > ( type , obj = { } , property = '' ) { if ( type === Injector ) { return this as any ; } const key = getInjectableTypeKey ( type ) ; let provider = this . providers [ key ] ; if ( ! provider ) { if ( this . parent && this . parent . providers [ key ] ) { return this . parent . getInstance ( type , obj , property ) ; } provider = ( type as any ) . __provider ; if ( ! provider ) { this . logger . debug ( 'No provider for: ' , getInjectableTypeKey ( type ) , '@' , getLocation ( obj , property ) ) ; let instance ; if ( typeof type === 'string' ) { instance = process . env [ type ] ; } else { instance = new ( type as ClassOf < any > ) ( ) ; instance . __injector = this ; if ( instance . __postConstruct ) { instance . __postConstruct . apply ( instance ) ; } } this . providers [ key ] = ( ) => instance ; return instance ; } } try { const instance = provider ( this , obj , property ) ; instance . __injector = this ; return instance ; } catch ( e ) { this . logger . error ( 'An error occurred while attempting to inject' + getInjectableTypeKey ( type ) + '@' + getLocation ( obj , property ) , e ) ; throw e ; } } }