typedi
typedi copied to clipboard
feature: container inheritance rework
Description
This task aims at rethinking and rewriting how container inheritance and dependency lookup works between containers. This task should include rework or implementation of the following features
- allowing to set services globally, for a specific scope or as transient (we already support these)
- allow easily overwriting some of the dependencies of a given class (ref #186)
- either making it easy to create a container with parent lookup (preferred)
- or allowing specifying custom overrides when calling
container.get()
- allow containers to lookup missing dependencies in their parents
- allow containers to inherit dependency metadata from their parents
- allow containers to dispose of themselves, destroying all locally registered service
Currently a container can be requested with containerInstance.of(containerId)
function. This returns a new container if no container exists with the given ID or returns the existing one. There are several problems with this solution:
- caller has no idea if the container existed before or not
- there is no way to specify how a container should inherit services from a parent container
- currently, there is no lookup in parent containers besides checking the default container for
global
services
Proposed solution
The new implementation should:
- allow specifying whether an error should be raised when a container exists with given ID
- allow parent-child relations, where a dependency is looked up in the parent container if it's doesn't exist in the child
- this should not be limited to a single level, but should recursively look up, until the dependency is found or a container specifies a lookup strategy that doesn't allow further lookups in the parent container
- allow cloning the registered metadata from the parent container
- either with values, meaning the resolved value of
ExampleClass
service will be the same for both containers- technical note: this means the metadata is shared and we need to store which container uses which
- technical note: what happens if one container resets it's value?
- or with definition only, meaning a different instance will be created for the child container
- technical note: how inherit value only dependencies?
- either with values, meaning the resolved value of
Proposed API
// default container instance, always exists, cannot be destroyed
import { Container } from 'typedi';
Container.of('my-new-container', {
/**
* What should happen if an existing Container is found with the given ID.
* - `throw` means TypeDI will raise an error
* - `overwrite` - the previous container is disposed and a new container is created
* - `returnExisting` - returns the existing container if the same parameters where specified
*/
onConflict: 'throw' | 'overwrite' | 'returnExisting',
/**
* Controls what should happen when a type is requested what doesn't exists in the current container.
* - 'allow' means the parent container can be checked for the given type, if found the parent container will hold the instance
* - 'localOnly' - means types are checked locally only and if no type found a ServiceNotFoundError is thrown
*/
lookupStrategy: 'allow' | 'localOnly',
/**
* Enables or disables the lookup for global services. By default every requested type is first checked if it's
* a global (singleton) service in the default container. This check bypasses the lookup strategy, so if this behavior
* is not desired it can be disabled via this flag.
*/
allowGlobalLookup: boolean;
/**
* Controls how the child container inherits the service definitions from it's parent
* - `none` means no metadata is inherited
* - `definitionOnly` means metadata is inherited and if an instance already exists it's ignored
* - `definitionWithValues` means metadata and service instance both inherited, when the parent container is disposed
* the instance in this container is preserved
*/
inheritanceStrategy: 'none' | 'definitionOnly' | 'definitionWithValues',
}