garrettmills
f00233d49a
All checks were successful
continuous-integration/drone/push Build is passing
142 lines
5.0 KiB
TypeScript
142 lines
5.0 KiB
TypeScript
import {Container, MaybeDependency, MaybeFactory} from './Container'
|
|
import {DependencyKey, Instantiable, StaticClass} from './types'
|
|
import {AbstractFactory} from './factory/AbstractFactory'
|
|
|
|
/**
|
|
* A container that uses some parent container as a base, but
|
|
* can have other factories distinct from that parent.
|
|
*
|
|
* If an instance is not found in this container, it will be resolved from
|
|
* the parent container.
|
|
*
|
|
* However, if an instance IS found in this container, it will ALWAYS be
|
|
* resolved from this container, rather than the parent.
|
|
*
|
|
* This can be used to create scope-specific containers that can still resolve
|
|
* the global dependencies, while keeping scope-specific dependencies separate.
|
|
*
|
|
* @example
|
|
* The Request class from @extollo/lib is a ScopedContainer. It can resolve
|
|
* all dependencies that exist in the global Container, but it can also have
|
|
* request-specific services (like the Session) injected into it.
|
|
*
|
|
* @extends Container
|
|
*/
|
|
export class ScopedContainer extends Container {
|
|
/**
|
|
* Create a new scoped container based on a parent container instance.
|
|
* @param container
|
|
*/
|
|
public static fromParent(container: Container): ScopedContainer {
|
|
return new ScopedContainer(container)
|
|
}
|
|
|
|
private resolveParentScope = true
|
|
|
|
constructor(
|
|
private parentContainer: Container,
|
|
) {
|
|
super()
|
|
this.registerSingletonInstance<ScopedContainer>(ScopedContainer, this)
|
|
}
|
|
|
|
hasInstance(key: DependencyKey): boolean {
|
|
return super.hasInstance(key) || (this.resolveParentScope && this.parentContainer.hasInstance(key))
|
|
}
|
|
|
|
hasKey(key: DependencyKey): boolean {
|
|
return super.hasKey(key) || (this.resolveParentScope && this.parentContainer.hasKey(key))
|
|
}
|
|
|
|
getExistingInstance(key: DependencyKey): MaybeDependency {
|
|
const inst = super.getExistingInstance(key)
|
|
if ( inst ) {
|
|
return inst
|
|
}
|
|
|
|
if ( this.resolveParentScope ) {
|
|
return this.parentContainer.getExistingInstance(key)
|
|
}
|
|
}
|
|
|
|
resolve(key: DependencyKey): MaybeFactory<any> {
|
|
const factory = super.resolve(key)
|
|
if ( factory ) {
|
|
return factory
|
|
}
|
|
|
|
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<any>): 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<any>): 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<T>(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<T>(staticClass: StaticClass<T, any> | Instantiable<T>, instance: T): this {
|
|
return this.withoutParentScopes(() => super.registerSingletonInstance(staticClass, instance))
|
|
}
|
|
|
|
/**
|
|
* Register a given factory with the container.
|
|
* @param {AbstractFactory} factory
|
|
*/
|
|
registerFactory(factory: AbstractFactory<unknown>): 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<T>(closure: () => T): T {
|
|
const oldResolveParentScope = this.resolveParentScope
|
|
this.resolveParentScope = false
|
|
const value: T = closure()
|
|
this.resolveParentScope = oldResolveParentScope
|
|
return value
|
|
}
|
|
}
|