@ -1,15 +1,62 @@
/** Manages service definitions, instances, and deferred injection. */
class Container {
constructor ( definitions = { } ) {
/ * *
* Static service definitions from which instances are created when
* the services are requested . Should be in service name - > service
* definition pairs .
* @ type object
* /
this . definitions = definitions
/ * *
* Instantiated services . If a service has already been requested , it is
* stored here so that the single instance can be reused .
* @ type object
* /
this . instances = { }
/ * *
* Already injected static service definitions . These are used to resolve
* circular dependencies .
* @ type object
* /
this . statics = { }
/ * *
* Instance of the dependency injector this container is associated with .
* If this is specified , services will be injected with other services when
* they are instantiated .
* @ type { boolean | DependencyInjector }
* /
this . di = false
/ * *
* Array of static class definitions with deferred services . These static
* definitions are waiting for a service to be registered with this container
* so it can be injected into the prototype and instances .
* @ type { * [ ] }
* /
this . deferred _classes = [ ]
}
/ * *
* Check if a service definition exists in this container .
* @ param { string } service - the name of the service
* @ returns { boolean } - true if the service definition exists in this container
* /
has ( service ) {
return ! ! this . definitions [ service ]
}
/ * *
* Fetch a service by name . If no name is provided , return the service
* proxy container . This container has getters for all the services by
* name .
* @ param { string } service - the name of the service
* @ returns { Service | * } - the service instance of service container proxy
* /
service ( service = false ) {
if ( service === false ) {
return new Proxy ( { } , {
@ -20,14 +67,16 @@ class Container {
}
if ( ! this . definitions [ service ] ) {
throw new Error ( 'No such service registered with this container .' )
throw new Error ( 'No such service registered with this container : '+ service )
}
// Store the static reference first.
// This allows us to resolve circular dependencies.
if ( ! this . statics [ service ] ) {
this . statics [ service ] = this . definitions [ service ]
this . di . make ( this . statics [ service ] )
if ( this . di ) {
this . di . make ( this . statics [ service ] )
}
}
if ( ! this . instances [ service ] ) {
@ -38,19 +87,80 @@ class Container {
return this . instances [ service ]
}
/ * *
* Register a class definition as a service . When requested , the service
* for this class will be created from the class ' instance .
* @ param { string } service _name - the referential name of the service
* @ param { * } service _class - the service class definition
* /
register ( service _name , service _class ) {
this . definitions [ service _name ] = service _class
this . _process _deferral ( service _name , this . service ( service _name ) )
}
/ * *
* Register a class instance as a service . When requested , the provided
* instance will be returned as the instance of the service . The instance ' s
* constructor is saved as the service definition .
* @ param { string } service _name - the referential name of the service
* @ param { Service } service _instance - the service class instance
* /
register _as _instance ( service _name , service _instance ) {
this . definitions [ service _name ] = service _instance . constructor
this . instances [ service _name ] = service _instance
this . _process _deferral ( service _name , service _instance )
}
/ * *
* Process deferred classes that need the provided service name and instance .
* @ param { string } service _name - the referential name of the service
* @ param { Service } service _instance - the instance of the service
* @ private
* /
_process _deferral ( service _name , service _instance ) {
const new _deferrals = [ ]
for ( const Class of this . deferred _classes ) {
if ( Class . _di _deferred _services . includes ( service _name ) ) {
Class . _ _deferral _callback ( service _name , service _instance )
}
if ( Class . _ _has _deferred _services ) {
new _deferrals . push ( Class )
}
}
this . deferred _classes = new _deferrals
}
/ * *
* Defer a static class to have its missing services filled in as they
* become available in the service container . The class should extend
* from Injectable .
* @ param { * } Class - the static class to be deferred
* /
defer ( Class ) {
if ( ! this . _ _is _deferrable ( Class ) ) {
throw new Error ( 'Cannot defer non-deferrable class: ' + Class . name )
}
this . deferred _classes . push ( Class )
}
_ _is _injectable ( Class ) {
console . log ( 'WARNING: use of flitter-di/Container#__is_injectable is deprecated and will be removed in the future' )
/ * *
* Checks if a class is deferrable . That is , does it have the requirements
* for functioning with the defer logic . In almost all cases , these should be
* satisfied by having the Class extend from Injectable .
* @ param { * } Class - the static class to check
* @ returns { boolean } - true if the class is deferrable
* @ private
* /
_ _is _deferrable ( Class ) {
return (
Class . services
&& Array . isArray ( Class . services )
Array . isArray ( Class . _di _deferred _services )
&& Array . isArray ( Class . _di _deferred _instances )
&& '_di_allow_defer' in Class
&& typeof Class . _ _deferral _callback === 'function'
&& '__has_deferred_services' in Class
)
}
}