From dab3d006c86f014bd148d5d57e32c8b58b53cbeb Mon Sep 17 00:00:00 2001 From: garrettmills Date: Fri, 4 Jun 2021 01:03:10 -0500 Subject: [PATCH] Containers - add ability to purge/release factories; override factories in scoped --- src/di/Container.ts | 19 +++++++++ src/di/ScopedContainer.ts | 86 ++++++++++++++++++++++++++++++++++++--- 2 files changed, 100 insertions(+), 5 deletions(-) diff --git a/src/di/Container.ts b/src/di/Container.ts index 418adeb..263f9c8 100644 --- a/src/di/Container.ts +++ b/src/di/Container.ts @@ -47,6 +47,25 @@ export class Container { this.registerSingleton('injector', this) } + /** + * 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 => x.key !== key) + return this + } + /** * Register a basic instantiable class as a standard Factory with this container. * @param {Instantiable} dependency diff --git a/src/di/ScopedContainer.ts b/src/di/ScopedContainer.ts index ef3026c..f91606d 100644 --- a/src/di/ScopedContainer.ts +++ b/src/di/ScopedContainer.ts @@ -1,5 +1,6 @@ import {Container, MaybeDependency, MaybeFactory} from './Container' -import {DependencyKey} from './types' +import {DependencyKey, Instantiable} from './types' +import {AbstractFactory} from './factory/AbstractFactory' /** * A container that uses some parent container as a base, but @@ -30,6 +31,8 @@ export class ScopedContainer extends Container { return new ScopedContainer(container) } + private resolveParentScope = true + constructor( private parentContainer: Container, ) { @@ -38,11 +41,11 @@ export class ScopedContainer extends Container { } hasInstance(key: DependencyKey): boolean { - return super.hasInstance(key) || this.parentContainer.hasInstance(key) + return super.hasInstance(key) || (this.resolveParentScope && this.parentContainer.hasInstance(key)) } hasKey(key: DependencyKey): boolean { - return super.hasKey(key) || this.parentContainer.hasKey(key) + return super.hasKey(key) || (this.resolveParentScope && this.parentContainer.hasKey(key)) } getExistingInstance(key: DependencyKey): MaybeDependency { @@ -51,7 +54,9 @@ export class ScopedContainer extends Container { return inst } - return this.parentContainer.getExistingInstance(key) + if ( this.resolveParentScope ) { + return this.parentContainer.getExistingInstance(key) + } } resolve(key: DependencyKey): MaybeFactory { @@ -60,6 +65,77 @@ export class ScopedContainer extends Container { return factory } - return this.parentContainer?.resolve(key) + if ( this.resolveParentScope ) { + return this.parentContainer.resolve(key) + } + } + + /** + * Register a basic instantiable class as a standard Factory with this container. + * @param {Instantiable} dependency + */ + register(dependency: Instantiable): this { + return this.withoutParentScopes(() => super.register(dependency)) + } + + /** + * 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 { + return this.withoutParentScopes(() => super.registerProducer(name, producer)) + } + + /** + * 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): this { + return this.withoutParentScopes(() => super.registerNamed(name, dependency)) + } + + /** + * 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(key: DependencyKey, value: T): this { + return this.withoutParentScopes(() => super.registerSingleton(key, value)) + } + + /** + * 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(staticClass: Instantiable, instance: T): this { + return this.withoutParentScopes(() => super.registerSingletonInstance(staticClass, instance)) + } + + /** + * Register a given factory with the container. + * @param {AbstractFactory} factory + */ + registerFactory(factory: AbstractFactory): this { + return this.withoutParentScopes(() => super.registerFactory(factory)) + } + + /** + * Execute a closure on this container, disabling parent-resolution. + * Effectively, the closure will have access to this container as if + * it were NOT a scoped container, and only contained its factories. + * @param closure + */ + withoutParentScopes(closure: () => T): T { + const oldResolveParentScope = this.resolveParentScope + this.resolveParentScope = false + const value: T = closure() + this.resolveParentScope = oldResolveParentScope + return value } }