garrettmills
9a55623370
All checks were successful
continuous-integration/drone/push Build is passing
672 lines
23 KiB
TypeScript
672 lines
23 KiB
TypeScript
import {
|
|
DependencyKey,
|
|
InstanceRef,
|
|
Instantiable,
|
|
isInstantiable,
|
|
StaticClass,
|
|
StaticInstantiable,
|
|
TypedDependencyKey,
|
|
} from './types'
|
|
import {AbstractFactory} from './factory/AbstractFactory'
|
|
import {
|
|
Awaitable,
|
|
collect,
|
|
Collection,
|
|
globalRegistry,
|
|
hasOwnProperty,
|
|
logIfDebugging, Unsubscribe,
|
|
} from '../util'
|
|
import {ErrorWithContext, withErrorContext} from '../util/error/ErrorWithContext'
|
|
import {Factory} from './factory/Factory'
|
|
import {DuplicateFactoryKeyError} from './error/DuplicateFactoryKeyError'
|
|
import {ClosureFactory} from './factory/ClosureFactory'
|
|
import NamedFactory from './factory/NamedFactory'
|
|
import SingletonFactory from './factory/SingletonFactory'
|
|
import {InvalidDependencyKeyError} from './error/InvalidDependencyKeyError'
|
|
import {ContainerBlueprint, ContainerResolutionCallback} from './ContainerBlueprint'
|
|
|
|
export type MaybeFactory<T> = AbstractFactory<T> | undefined
|
|
export type MaybeDependency = any | undefined
|
|
export type ResolvedDependency = { paramIndex: number, key: DependencyKey, resolved: any }
|
|
|
|
/**
|
|
* Singletons that implement this interface receive callbacks for
|
|
* structural container events.
|
|
*/
|
|
export interface AwareOfContainerLifecycle {
|
|
awareOfContainerLifecycle: true
|
|
|
|
/** Called when this key is realized by its parent container. */
|
|
onContainerRealize?(): Awaitable<unknown>
|
|
|
|
/** Called before the parent container of this instance is destroyed. */
|
|
onContainerDestroy?(): Awaitable<unknown>
|
|
|
|
/** Called before an instance of a key is released from the container. */
|
|
onContainerRelease?(): Awaitable<unknown>
|
|
}
|
|
|
|
export function isAwareOfContainerLifecycle(what: unknown): what is AwareOfContainerLifecycle {
|
|
return Boolean(
|
|
(typeof what === 'object' || typeof what === 'function')
|
|
&& what !== null
|
|
&& hasOwnProperty(what, 'awareOfContainerLifecycle')
|
|
&& what.awareOfContainerLifecycle,
|
|
)
|
|
}
|
|
|
|
/**
|
|
* A container of resolve-able dependencies that are created via inversion-of-control.
|
|
*/
|
|
export class Container {
|
|
/**
|
|
* Set to true when we're realizing a container.
|
|
* Used to prevent infinite recursion when `getContainer()` is accidentally called
|
|
* from somewhere within the `realizeContainer()` call.
|
|
*/
|
|
protected static realizingContainer = false
|
|
|
|
/**
|
|
* List of dependency keys currently being `make`'d as a reverse stack.
|
|
* This is used to detect dependency cycles.
|
|
* @protected
|
|
*/
|
|
protected static makeStack?: Collection<DependencyKey>
|
|
|
|
/**
|
|
* The 100 most recent dependency keys that were `make`'d. Used to help with
|
|
* debugging cyclic dependency errors.
|
|
* @protected
|
|
*/
|
|
protected static makeHistory?: Collection<DependencyKey>
|
|
|
|
/**
|
|
* Given a Container instance, apply the ContainerBlueprint to it.
|
|
* @param container
|
|
*/
|
|
public static realizeContainer<T extends Container>(container: T): T {
|
|
ContainerBlueprint.getContainerBlueprint()
|
|
.resolve()
|
|
.map(factory => container.registerFactory(factory))
|
|
|
|
ContainerBlueprint.getContainerBlueprint()
|
|
.resolveConstructable()
|
|
.map((factory: StaticClass<AbstractFactory<any>, any>) => container.registerFactory(container.make(factory)))
|
|
|
|
ContainerBlueprint.getContainerBlueprint()
|
|
.resolveResolutionCallbacks()
|
|
.map((listener: {key: TypedDependencyKey<any>, callback: ContainerResolutionCallback<any>}) => {
|
|
container.onResolve(listener.key)
|
|
.then(value => listener.callback(value))
|
|
})
|
|
|
|
container.subscribeToBlueprintChanges(ContainerBlueprint.getContainerBlueprint())
|
|
|
|
return container
|
|
}
|
|
|
|
/**
|
|
* Get the global instance of this container.
|
|
*/
|
|
public static getContainer(): Container {
|
|
const existing = <Container | undefined> globalRegistry.getGlobal('extollo/injector')
|
|
if ( !existing ) {
|
|
if ( this.realizingContainer ) {
|
|
throw new ErrorWithContext('Attempted getContainer call during container realization.')
|
|
}
|
|
|
|
this.realizingContainer = true
|
|
const container = Container.realizeContainer(new Container())
|
|
globalRegistry.setGlobal('extollo/injector', container)
|
|
this.realizingContainer = false
|
|
return container
|
|
}
|
|
|
|
return existing
|
|
}
|
|
|
|
/**
|
|
* Collection of factories registered with this container.
|
|
* @type Collection<AbstractFactory>
|
|
*/
|
|
protected factories: Collection<AbstractFactory<unknown>> = new Collection<AbstractFactory<unknown>>()
|
|
|
|
/**
|
|
* Collection of singleton instances produced by this container.
|
|
* @type Collection<InstanceRef>
|
|
*/
|
|
protected instances: Collection<InstanceRef> = new Collection<InstanceRef>()
|
|
|
|
/**
|
|
* Collection of static-class overrides registered with this container.
|
|
* @protected
|
|
*/
|
|
protected staticOverrides: Collection<{ base: StaticInstantiable<any>, override: StaticInstantiable<any> }> = new Collection<{base: StaticInstantiable<any>; override: StaticInstantiable<any>}>()
|
|
|
|
/**
|
|
* Collection of callbacks waiting for a dependency key to be resolved.
|
|
* @protected
|
|
*/
|
|
protected waitingResolveCallbacks: Collection<{ key: DependencyKey, callback: (t: unknown) => unknown }> = new Collection<{key: DependencyKey; callback:(t: unknown) => unknown}>()
|
|
|
|
/**
|
|
* Collection of created objects that should have lifecycle events called on them, if they still exist.
|
|
* @protected
|
|
*/
|
|
protected waitingLifecycleCallbacks: Collection<WeakRef<AwareOfContainerLifecycle>> = new Collection()
|
|
|
|
/**
|
|
* Collection of subscriptions to ContainerBlueprint events.
|
|
* We keep this around so we can remove the subscriptions when the container is destroyed.
|
|
* @protected
|
|
*/
|
|
protected blueprintSubscribers: Collection<Unsubscribe> = new Collection()
|
|
|
|
constructor() {
|
|
this.registerSingletonInstance<Container>(Container, this)
|
|
this.registerSingleton('injector', this)
|
|
}
|
|
|
|
/** Make the container listen to changes in the given blueprint. */
|
|
private subscribeToBlueprintChanges(blueprint: ContainerBlueprint): void {
|
|
this.blueprintSubscribers.push(
|
|
blueprint.resolve$(factory => this.registerFactory(factory())),
|
|
)
|
|
|
|
this.blueprintSubscribers.push(
|
|
blueprint.resolveConstructable$(factoryClass => this.registerFactory(this.make(factoryClass))),
|
|
)
|
|
|
|
this.blueprintSubscribers.push(
|
|
blueprint.resolveResolutionCallbacks$(listener => this.onResolve(listener.key).then(value => listener.callback(value))),
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Purge all factories and instances of the given key from this container.
|
|
* @param key
|
|
*/
|
|
purge(key: DependencyKey): this {
|
|
this.factories = this.factories.filter(x => !x.match(key))
|
|
this.release(key)
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Remove all stored instances of the given key from this container.
|
|
* @param key
|
|
*/
|
|
release(key: DependencyKey): this {
|
|
this.instances = this.instances.filter(x => {
|
|
if ( x.key === key && isAwareOfContainerLifecycle(x.value) ) {
|
|
x.value.onContainerRelease?.()
|
|
}
|
|
|
|
return x.key !== key
|
|
})
|
|
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Register a basic instantiable class as a standard Factory with this container.
|
|
* @param {Instantiable} dependency
|
|
*/
|
|
register(dependency: Instantiable<any>): this {
|
|
if ( this.resolve(dependency) ) {
|
|
throw new DuplicateFactoryKeyError(dependency)
|
|
}
|
|
|
|
const factory = new Factory(dependency)
|
|
this.factories.push(factory)
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Register a static class as an override of some base class.
|
|
* @param base
|
|
* @param override
|
|
*/
|
|
registerStaticOverride<T>(base: StaticInstantiable<T>, override: StaticInstantiable<T>): this {
|
|
if ( this.hasStaticOverride(base) ) {
|
|
throw new DuplicateFactoryKeyError(base)
|
|
}
|
|
|
|
this.staticOverrides.push({
|
|
base,
|
|
override,
|
|
})
|
|
|
|
return this
|
|
}
|
|
|
|
/** Returns true if a static override exists for the given base class. */
|
|
hasStaticOverride<T>(base: StaticInstantiable<T>): boolean {
|
|
return this.staticOverrides.where('base', '=', base).isNotEmpty()
|
|
}
|
|
|
|
/**
|
|
* Get the static class overriding the base class.
|
|
* @param base
|
|
*/
|
|
getStaticOverride<T>(base: StaticInstantiable<T>): StaticInstantiable<T> {
|
|
const override = this.staticOverrides.firstWhere('base', '=', base)
|
|
if ( override ) {
|
|
return override.override
|
|
}
|
|
|
|
return base
|
|
}
|
|
|
|
/**
|
|
* Get the registered instance of the static override of a given class.
|
|
* @param base
|
|
* @param parameters
|
|
*/
|
|
makeByStaticOverride<T>(base: StaticInstantiable<T>, ...parameters: any[]): T {
|
|
const key = this.getStaticOverride(base)
|
|
return this.make(key, ...parameters)
|
|
}
|
|
|
|
/**
|
|
* Register the given function as a factory within the container.
|
|
* @param {string} name - unique name to identify the factory in the container
|
|
* @param {function} producer - factory to produce a value
|
|
*/
|
|
registerProducer(name: DependencyKey, producer: () => any): this {
|
|
if ( this.resolve(name) ) {
|
|
throw new DuplicateFactoryKeyError(name)
|
|
}
|
|
|
|
const factory = new ClosureFactory(name, producer)
|
|
this.factories.push(factory)
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Register a basic instantiable class as a standard Factory with this container,
|
|
* identified by a string name rather than static class.
|
|
* @param {string} name - unique name to identify the factory in the container
|
|
* @param {Instantiable} dependency
|
|
*/
|
|
registerNamed(name: string, dependency: Instantiable<any>): this {
|
|
if ( this.resolve(name) ) {
|
|
throw new DuplicateFactoryKeyError(name)
|
|
}
|
|
|
|
const factory = new NamedFactory(name, dependency)
|
|
this.factories.push(factory)
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Register a value as a singleton in the container. It will not be instantiated, but
|
|
* can be injected by its unique name.
|
|
* @param {string} key - unique name to identify the singleton in the container
|
|
* @param value
|
|
*/
|
|
registerSingleton<T>(key: DependencyKey, value: T): this {
|
|
if ( this.resolve(key) ) {
|
|
throw new DuplicateFactoryKeyError(key)
|
|
}
|
|
|
|
this.factories.push(new SingletonFactory(key, value))
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Register a static class to the container along with its already-instantiated
|
|
* instance that will be used to resolve the class.
|
|
* @param staticClass
|
|
* @param instance
|
|
*/
|
|
registerSingletonInstance<T>(staticClass: StaticClass<T, any> | Instantiable<T>, instance: T): this {
|
|
if ( this.resolve(staticClass) ) {
|
|
throw new DuplicateFactoryKeyError(staticClass)
|
|
}
|
|
|
|
this.register(staticClass)
|
|
this.instances.push({
|
|
key: staticClass,
|
|
value: instance,
|
|
})
|
|
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Register a given factory with the container.
|
|
* @param {AbstractFactory} factory
|
|
*/
|
|
registerFactory(factory: AbstractFactory<unknown>): this {
|
|
if ( !this.factories.includes(factory) ) {
|
|
this.factories.push(factory)
|
|
}
|
|
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Returns true if the container has an already-produced value for the given key.
|
|
* @param {DependencyKey} key
|
|
*/
|
|
hasInstance(key: DependencyKey): boolean {
|
|
return this.instances.where('key', '=', key).isNotEmpty()
|
|
}
|
|
|
|
/**
|
|
* Get a Promise that resolves the first time the given dependency key is resolved
|
|
* by the application. If it has already been resolved, the Promise will resolve immediately.
|
|
* @param key
|
|
*/
|
|
onResolve<T>(key: TypedDependencyKey<T>): Promise<T> {
|
|
if ( this.hasInstance(key) ) {
|
|
return new Promise<T>(res => res(this.make<T>(key)))
|
|
}
|
|
|
|
// Otherwise, we haven't instantiated an instance with this key yet,
|
|
// so put it onto the waitlist.
|
|
return new Promise<T>(res => {
|
|
this.waitingResolveCallbacks.push({
|
|
key,
|
|
callback: (res as (t: unknown) => unknown),
|
|
})
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Returns true if the container has a factory for the given key.
|
|
* @param {DependencyKey} key
|
|
*/
|
|
hasKey(key: DependencyKey): boolean {
|
|
return Boolean(this.resolve(key))
|
|
}
|
|
|
|
/**
|
|
* Get the already-produced value for the given key, if one exists.
|
|
* @param {DependencyKey} key
|
|
*/
|
|
getExistingInstance(key: DependencyKey): MaybeDependency {
|
|
const instances = this.instances.where('key', '=', key)
|
|
if ( instances.isNotEmpty() ) {
|
|
return instances.first()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Find the factory for the given key, if one is registered with this container.
|
|
* @param {DependencyKey} key
|
|
*/
|
|
resolve(key: DependencyKey): MaybeFactory<unknown> {
|
|
const factory = this.factories.firstWhere(item => item.match(key))
|
|
if ( factory ) {
|
|
return factory
|
|
} else {
|
|
logIfDebugging('extollo.di.injector', 'unable to resolve factory', key, factory, this.factories)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Resolve the dependency key. If a singleton value for that key already exists in this container,
|
|
* return that value. Otherwise, use the factory and given parameters to produce and return the value.
|
|
* @param {DependencyKey} key
|
|
* @param {...any} parameters
|
|
*/
|
|
resolveAndCreate(key: DependencyKey, ...parameters: any[]): any {
|
|
logIfDebugging('extollo.di.injector', 'resolveAndCreate', key, {parameters})
|
|
|
|
// If we've already instantiated this, just return that
|
|
const instance = this.getExistingInstance(key)
|
|
logIfDebugging('extollo.di.injector', 'resolveAndCreate existing instance?', instance)
|
|
if ( typeof instance !== 'undefined' ) {
|
|
return instance.value
|
|
}
|
|
|
|
// Otherwise, attempt to create it
|
|
const factory = this.resolve(key)
|
|
logIfDebugging('extollo.di.injector', 'resolveAndCreate factory', factory)
|
|
if ( !factory ) {
|
|
throw new InvalidDependencyKeyError(key)
|
|
}
|
|
|
|
// Produce and store a new instance
|
|
const newInstance = this.produceFactory(factory, parameters)
|
|
this.instances.push({
|
|
key,
|
|
value: newInstance,
|
|
})
|
|
|
|
if ( isAwareOfContainerLifecycle(newInstance) ) {
|
|
newInstance.onContainerRealize?.()
|
|
}
|
|
|
|
this.waitingResolveCallbacks = this.waitingResolveCallbacks.filter(waiter => {
|
|
if ( waiter.key === key ) {
|
|
waiter.callback(newInstance)
|
|
return false
|
|
}
|
|
|
|
return true
|
|
})
|
|
|
|
return newInstance
|
|
}
|
|
|
|
/**
|
|
* Given a factory and manually-provided parameters, resolve the dependencies for the
|
|
* factory and produce its value.
|
|
* @param {AbstractFactory} factory
|
|
* @param {array} parameters
|
|
*/
|
|
protected produceFactory<T>(factory: AbstractFactory<T>, parameters: any[]): T {
|
|
logIfDebugging('extollo.di.injector', 'Make stack', Container.makeStack)
|
|
|
|
// Create the dependencies for the factory
|
|
const keys = factory.getDependencyKeys().filter(req => this.hasKey(req.key))
|
|
const dependencies = keys.map<ResolvedDependency>(req => {
|
|
return withErrorContext(() => {
|
|
return {
|
|
paramIndex: req.paramIndex,
|
|
key: req.key,
|
|
resolved: this.resolveAndCreate(req.key),
|
|
}
|
|
}, {
|
|
producingToken: factory.getTokenName(),
|
|
constructorDependency: req,
|
|
})
|
|
}).sortBy('paramIndex')
|
|
|
|
// Build the arguments for the factory, using dependencies in the
|
|
// correct paramIndex positions, or parameters of we don't have
|
|
// the dependency.
|
|
const constructorArguments = []
|
|
const params = collect(parameters).reverse()
|
|
for ( let i = 0; i <= dependencies.max('paramIndex'); i++ ) {
|
|
const dep = dependencies.firstWhere('paramIndex', '=', i)
|
|
if ( dep ) {
|
|
constructorArguments.push(dep.resolved)
|
|
} else {
|
|
constructorArguments.push(params.pop())
|
|
}
|
|
}
|
|
|
|
// Produce a new instance
|
|
const inst = factory.produce(constructorArguments, params.reverse().all())
|
|
|
|
logIfDebugging('extollo.di.injector', 'Resolving dependencies for factory', factory)
|
|
factory.getInjectedProperties().each(dependency => {
|
|
logIfDebugging('extollo.di.injector', 'Resolving injected dependency:', dependency)
|
|
if ( dependency.key && inst ) {
|
|
withErrorContext(() => {
|
|
(inst as any)[dependency.property] = this.resolveAndCreate(dependency.key)
|
|
}, {
|
|
producingToken: factory.getTokenName(),
|
|
propertyDependency: dependency,
|
|
})
|
|
}
|
|
})
|
|
|
|
if ( isAwareOfContainerLifecycle(inst) ) {
|
|
this.waitingLifecycleCallbacks.push(new WeakRef<AwareOfContainerLifecycle>(inst))
|
|
}
|
|
|
|
return inst
|
|
}
|
|
|
|
/**
|
|
* Create an instance of the given target. The target can either be a DependencyKey registered with
|
|
* this container (in which case, the singleton value will be returned), or an instantiable class.
|
|
*
|
|
* If the instantiable class has the Injectable decorator, its injectable parameters will be automatically
|
|
* injected into the instance.
|
|
* @param {DependencyKey} target
|
|
* @param {...any} parameters
|
|
*/
|
|
make<T>(target: DependencyKey, ...parameters: any[]): T {
|
|
if ( !Container.makeStack ) {
|
|
Container.makeStack = new Collection()
|
|
}
|
|
|
|
if ( !Container.makeHistory ) {
|
|
Container.makeHistory = new Collection()
|
|
}
|
|
|
|
Container.makeStack.push(target)
|
|
|
|
if ( Container.makeHistory.length > 100 ) {
|
|
Container.makeHistory = Container.makeHistory.slice(1, 100)
|
|
}
|
|
Container.makeHistory.push(target)
|
|
|
|
this.checkForMakeCycles()
|
|
|
|
try {
|
|
const result = withErrorContext(() => {
|
|
if (this.hasKey(target)) {
|
|
const realized = this.resolveAndCreate(target, ...parameters)
|
|
Container.makeStack?.pop()
|
|
return realized
|
|
} else if (typeof target !== 'string' && isInstantiable(target)) {
|
|
const realized = this.produceFactory(new Factory(target), parameters)
|
|
Container.makeStack?.pop()
|
|
return realized
|
|
}
|
|
}, {
|
|
makeStack: Container.makeStack.map(x => typeof x === 'string' ? x : (x?.name || 'unknown')).toArray(),
|
|
})
|
|
|
|
if ( result ) {
|
|
return result
|
|
}
|
|
} catch (e: unknown) {
|
|
Container.makeStack.pop()
|
|
throw e
|
|
}
|
|
|
|
Container.makeStack.pop()
|
|
throw new TypeError(`Invalid or unknown make target: ${target}`)
|
|
}
|
|
|
|
/**
|
|
* Check the `makeStack` for duplicates and throw an error if a dependency cycle is
|
|
* detected. This is used to prevent infinite mutual recursion when cyclic dependencies
|
|
* occur.
|
|
* @protected
|
|
*/
|
|
protected checkForMakeCycles(): void {
|
|
if ( !Container.makeStack ) {
|
|
Container.makeStack = new Collection()
|
|
}
|
|
|
|
if ( !Container.makeHistory ) {
|
|
Container.makeHistory = new Collection()
|
|
}
|
|
|
|
if ( Container.makeStack.unique().length !== Container.makeStack.length ) {
|
|
const displayKey = (key: DependencyKey) => {
|
|
if ( typeof key === 'string' ) {
|
|
return 'key: `' + key + '`'
|
|
} else {
|
|
return `key: ${key.name}`
|
|
}
|
|
}
|
|
|
|
const makeStack = Container.makeStack
|
|
.reverse()
|
|
.map(displayKey)
|
|
|
|
const makeHistory = Container.makeHistory
|
|
.reverse()
|
|
.map(displayKey)
|
|
|
|
console.error('Make Stack:') // eslint-disable-line no-console
|
|
console.error(makeStack.join('\n')) // eslint-disable-line no-console
|
|
console.error('Make History:') // eslint-disable-line no-console
|
|
console.error(makeHistory.join('\n')) // eslint-disable-line no-console
|
|
throw new ErrorWithContext('Cyclic dependency chain detected', {
|
|
makeStack,
|
|
makeHistory,
|
|
})
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create a new instance of the dependency key using this container, ignoring any pre-existing instances
|
|
* in this container.
|
|
* @param key
|
|
* @param parameters
|
|
*/
|
|
makeNew<T>(key: TypedDependencyKey<T>, ...parameters: any[]): T {
|
|
if ( isInstantiable(key) ) {
|
|
const result = this.produceFactory(new Factory(key), parameters)
|
|
if ( isAwareOfContainerLifecycle(result) ) {
|
|
result.onContainerRealize?.()
|
|
}
|
|
return result
|
|
}
|
|
|
|
throw new TypeError(`Invalid or unknown make target: ${key}`)
|
|
}
|
|
|
|
/**
|
|
* Get a collection of dependency keys required by the given target, if it is registered with this container.
|
|
* @param {DependencyKey} target
|
|
*/
|
|
getDependencies(target: DependencyKey): Collection<DependencyKey> {
|
|
const factory = this.resolve(target)
|
|
|
|
if ( !factory ) {
|
|
throw new InvalidDependencyKeyError(target)
|
|
}
|
|
|
|
return factory.getDependencyKeys().pluck('key')
|
|
}
|
|
|
|
/**
|
|
* Perform any cleanup necessary to destroy this container instance.
|
|
*/
|
|
destroy(): void {
|
|
this.blueprintSubscribers.mapCall('unsubscribe')
|
|
|
|
this.waitingLifecycleCallbacks
|
|
.mapCall('deref')
|
|
.whereDefined()
|
|
.each(inst => {
|
|
if ( isAwareOfContainerLifecycle(inst) ) {
|
|
inst.onContainerRelease?.()
|
|
inst.onContainerDestroy?.()
|
|
}
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Given a different container, copy the factories and instances from this container over to it.
|
|
* @param container
|
|
*/
|
|
cloneTo(container: Container): this {
|
|
container.factories = this.factories.clone()
|
|
container.instances = this.instances.clone()
|
|
return this
|
|
}
|
|
}
|