Add request container lifecycle handling
This commit is contained in:
@@ -8,7 +8,7 @@ import {
|
||||
TypedDependencyKey,
|
||||
} from './types'
|
||||
import {AbstractFactory} from './factory/AbstractFactory'
|
||||
import {collect, Collection, ErrorWithContext, globalRegistry, logIfDebugging} from '../util'
|
||||
import {Awaitable, collect, Collection, ErrorWithContext, globalRegistry, hasOwnProperty, logIfDebugging} from '../util'
|
||||
import {Factory} from './factory/Factory'
|
||||
import {DuplicateFactoryKeyError} from './error/DuplicateFactoryKeyError'
|
||||
import {ClosureFactory} from './factory/ClosureFactory'
|
||||
@@ -21,6 +21,32 @@ 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'
|
||||
&& what !== null
|
||||
&& hasOwnProperty(what, 'awareOfContainerLifecycle')
|
||||
&& what.awareOfContainerLifecycle,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A container of resolve-able dependencies that are created via inversion-of-control.
|
||||
*/
|
||||
@@ -113,6 +139,12 @@ export class Container {
|
||||
*/
|
||||
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()
|
||||
|
||||
constructor() {
|
||||
this.registerSingletonInstance<Container>(Container, this)
|
||||
this.registerSingleton('injector', this)
|
||||
@@ -133,7 +165,14 @@ export class Container {
|
||||
* @param key
|
||||
*/
|
||||
release(key: DependencyKey): this {
|
||||
this.instances = this.instances.filter(x => x.key !== key)
|
||||
this.instances = this.instances.filter(x => {
|
||||
if ( x.key === key && isAwareOfContainerLifecycle(x.value) ) {
|
||||
x.value.onContainerRelease?.()
|
||||
}
|
||||
|
||||
return x.key !== key
|
||||
})
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
@@ -365,6 +404,10 @@ export class Container {
|
||||
value: newInstance,
|
||||
})
|
||||
|
||||
if ( isAwareOfContainerLifecycle(newInstance) ) {
|
||||
newInstance.onContainerRealize?.()
|
||||
}
|
||||
|
||||
this.waitingResolveCallbacks = this.waitingResolveCallbacks.filter(waiter => {
|
||||
if ( waiter.key === key ) {
|
||||
waiter.callback(newInstance)
|
||||
@@ -421,6 +464,10 @@ export class Container {
|
||||
}
|
||||
})
|
||||
|
||||
if ( isAwareOfContainerLifecycle(inst) ) {
|
||||
this.waitingLifecycleCallbacks.push(new WeakRef<AwareOfContainerLifecycle>(inst))
|
||||
}
|
||||
|
||||
return inst
|
||||
}
|
||||
|
||||
@@ -521,7 +568,10 @@ export class Container {
|
||||
*/
|
||||
makeNew<T>(key: TypedDependencyKey<T>, ...parameters: any[]): T {
|
||||
if ( isInstantiable(key) ) {
|
||||
return this.produceFactory(new Factory(key), parameters)
|
||||
const result = this.produceFactory(new Factory(key), parameters)
|
||||
if ( isAwareOfContainerLifecycle(result) ) {
|
||||
result.onContainerRealize?.()
|
||||
}
|
||||
}
|
||||
|
||||
throw new TypeError(`Invalid or unknown make target: ${key}`)
|
||||
@@ -541,6 +591,21 @@ export class Container {
|
||||
return factory.getDependencyKeys().pluck('key')
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform any cleanup necessary to destroy this container instance.
|
||||
*/
|
||||
destroy(): void {
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user