Add request container lifecycle handling
This commit is contained in:
parent
514a578260
commit
78cb26fcb2
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@extollo/lib",
|
"name": "@extollo/lib",
|
||||||
"version": "0.9.15",
|
"version": "0.9.16",
|
||||||
"description": "The framework library that lifts up your code.",
|
"description": "The framework library that lifts up your code.",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"types": "lib/index.d.ts",
|
"types": "lib/index.d.ts",
|
||||||
|
@ -8,7 +8,7 @@ import {
|
|||||||
TypedDependencyKey,
|
TypedDependencyKey,
|
||||||
} from './types'
|
} from './types'
|
||||||
import {AbstractFactory} from './factory/AbstractFactory'
|
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 {Factory} from './factory/Factory'
|
||||||
import {DuplicateFactoryKeyError} from './error/DuplicateFactoryKeyError'
|
import {DuplicateFactoryKeyError} from './error/DuplicateFactoryKeyError'
|
||||||
import {ClosureFactory} from './factory/ClosureFactory'
|
import {ClosureFactory} from './factory/ClosureFactory'
|
||||||
@ -21,6 +21,32 @@ export type MaybeFactory<T> = AbstractFactory<T> | undefined
|
|||||||
export type MaybeDependency = any | undefined
|
export type MaybeDependency = any | undefined
|
||||||
export type ResolvedDependency = { paramIndex: number, key: DependencyKey, resolved: any }
|
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.
|
* 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}>();
|
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() {
|
constructor() {
|
||||||
this.registerSingletonInstance<Container>(Container, this)
|
this.registerSingletonInstance<Container>(Container, this)
|
||||||
this.registerSingleton('injector', this)
|
this.registerSingleton('injector', this)
|
||||||
@ -133,7 +165,14 @@ export class Container {
|
|||||||
* @param key
|
* @param key
|
||||||
*/
|
*/
|
||||||
release(key: DependencyKey): this {
|
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
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -365,6 +404,10 @@ export class Container {
|
|||||||
value: newInstance,
|
value: newInstance,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if ( isAwareOfContainerLifecycle(newInstance) ) {
|
||||||
|
newInstance.onContainerRealize?.()
|
||||||
|
}
|
||||||
|
|
||||||
this.waitingResolveCallbacks = this.waitingResolveCallbacks.filter(waiter => {
|
this.waitingResolveCallbacks = this.waitingResolveCallbacks.filter(waiter => {
|
||||||
if ( waiter.key === key ) {
|
if ( waiter.key === key ) {
|
||||||
waiter.callback(newInstance)
|
waiter.callback(newInstance)
|
||||||
@ -421,6 +464,10 @@ export class Container {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if ( isAwareOfContainerLifecycle(inst) ) {
|
||||||
|
this.waitingLifecycleCallbacks.push(new WeakRef<AwareOfContainerLifecycle>(inst))
|
||||||
|
}
|
||||||
|
|
||||||
return inst
|
return inst
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -521,7 +568,10 @@ export class Container {
|
|||||||
*/
|
*/
|
||||||
makeNew<T>(key: TypedDependencyKey<T>, ...parameters: any[]): T {
|
makeNew<T>(key: TypedDependencyKey<T>, ...parameters: any[]): T {
|
||||||
if ( isInstantiable(key) ) {
|
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}`)
|
throw new TypeError(`Invalid or unknown make target: ${key}`)
|
||||||
@ -541,6 +591,21 @@ export class Container {
|
|||||||
return factory.getDependencyKeys().pluck('key')
|
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.
|
* Given a different container, copy the factories and instances from this container over to it.
|
||||||
* @param container
|
* @param container
|
||||||
|
@ -131,6 +131,9 @@ export class HTTPServer extends Unit {
|
|||||||
if ( extolloReq.response.canSend() ) {
|
if ( extolloReq.response.canSend() ) {
|
||||||
await extolloReq.response.send()
|
await extolloReq.response.send()
|
||||||
}
|
}
|
||||||
|
}).finally(() => {
|
||||||
|
this.logging.verbose('Destroying request container...')
|
||||||
|
extolloReq.destroy()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import {Inject, Singleton, StaticInstantiable} from '../../di'
|
import {AwareOfContainerLifecycle, Inject, Singleton, StaticInstantiable} from '../../di'
|
||||||
import {
|
import {
|
||||||
BusConnectorConfig,
|
BusConnectorConfig,
|
||||||
BusSubscriber,
|
BusSubscriber,
|
||||||
@ -25,7 +25,9 @@ export interface BusInternalSubscription {
|
|||||||
* Propagating event bus implementation.
|
* Propagating event bus implementation.
|
||||||
*/
|
*/
|
||||||
@Singleton()
|
@Singleton()
|
||||||
export class Bus<TEvent extends Event = Event> extends Unit implements EventBus<TEvent> {
|
export class Bus<TEvent extends Event = Event> extends Unit implements EventBus<TEvent>, AwareOfContainerLifecycle {
|
||||||
|
awareOfContainerLifecycle: true = true
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
protected readonly logging!: Logging
|
protected readonly logging!: Logging
|
||||||
|
|
||||||
@ -228,4 +230,8 @@ export class Bus<TEvent extends Event = Event> extends Unit implements EventBus<
|
|||||||
|
|
||||||
this.isUp = false
|
this.isUp = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onContainerDestroy(): Awaitable<void> {
|
||||||
|
this.down()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import {Inject, Injectable, StaticInstantiable} from '../../di'
|
import {AwareOfContainerLifecycle, Inject, Injectable, StaticInstantiable} from '../../di'
|
||||||
import {BusSubscriber, Event, EventBus, EventHandler, EventHandlerReturn, EventHandlerSubscription} from './types'
|
import {BusSubscriber, Event, EventBus, EventHandler, EventHandlerReturn, EventHandlerSubscription} from './types'
|
||||||
import {Awaitable, Collection, ifDebugging, Pipeline, uuid4} from '../../util'
|
import {Awaitable, Collection, ifDebugging, Pipeline, uuid4} from '../../util'
|
||||||
import {Logging} from '../../service/Logging'
|
import {Logging} from '../../service/Logging'
|
||||||
@ -10,7 +10,9 @@ import {CanonicalItemClass} from '../CanonicalReceiver'
|
|||||||
* Non-connectable event bus implementation. Can forward events to the main Bus instance.
|
* Non-connectable event bus implementation. Can forward events to the main Bus instance.
|
||||||
*/
|
*/
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class LocalBus<TEvent extends Event = Event> extends CanonicalItemClass implements EventBus<TEvent> {
|
export class LocalBus<TEvent extends Event = Event> extends CanonicalItemClass implements EventBus<TEvent>, AwareOfContainerLifecycle {
|
||||||
|
awareOfContainerLifecycle: true = true
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
protected readonly logging!: Logging
|
protected readonly logging!: Logging
|
||||||
|
|
||||||
@ -135,4 +137,8 @@ export class LocalBus<TEvent extends Event = Event> extends CanonicalItemClass i
|
|||||||
|
|
||||||
this.isUp = false
|
this.isUp = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onContainerRelease(): Awaitable<void> {
|
||||||
|
this.down()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import {BusSubscriber, Event, EventBus, EventHandler, EventHandlerReturn, EventHandlerSubscription} from './types'
|
import {BusSubscriber, Event, EventBus, EventHandler, EventHandlerReturn, EventHandlerSubscription} from './types'
|
||||||
import {Container, Inject, Injectable, StaticInstantiable} from '../../di'
|
import {AwareOfContainerLifecycle, Container, Inject, Injectable, StaticInstantiable} from '../../di'
|
||||||
import {Awaitable, Collection, Pipeline, uuid4} from '../../util'
|
import {Awaitable, Collection, Pipeline, uuid4} from '../../util'
|
||||||
import {Redis} from '../redis/Redis'
|
import {Redis} from '../redis/Redis'
|
||||||
import {Serialization} from './serial/Serialization'
|
import {Serialization} from './serial/Serialization'
|
||||||
@ -11,7 +11,9 @@ import {getEventName} from './getEventName'
|
|||||||
* Event bus implementation that does pub/sub over a Redis connection.
|
* Event bus implementation that does pub/sub over a Redis connection.
|
||||||
*/
|
*/
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RedisBus implements EventBus {
|
export class RedisBus implements EventBus, AwareOfContainerLifecycle {
|
||||||
|
awareOfContainerLifecycle: true = true
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
protected readonly redis!: Redis
|
protected readonly redis!: Redis
|
||||||
|
|
||||||
@ -125,8 +127,11 @@ export class RedisBus implements EventBus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
down(): Awaitable<void> {
|
down(): Awaitable<void> {
|
||||||
// The Redis service will clean up the connections when the framework exits,
|
this.subscriberConnection?.disconnect()
|
||||||
// so we don't need to do anything here.
|
this.publisherConnection?.disconnect()
|
||||||
return undefined
|
}
|
||||||
|
|
||||||
|
onContainerRelease(): Awaitable<void> {
|
||||||
|
this.down()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,10 @@
|
|||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"target": "es6",
|
"target": "es6",
|
||||||
"sourceMap": true
|
"sourceMap": true,
|
||||||
|
"lib": ["ESNext"]
|
||||||
},
|
},
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"node_modules"
|
"node_modules"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,8 @@
|
|||||||
"outDir": "./lib",
|
"outDir": "./lib",
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"emitDecoratorMetadata": true
|
"emitDecoratorMetadata": true,
|
||||||
|
"lib": ["ESNext"]
|
||||||
},
|
},
|
||||||
"include": ["src"],
|
"include": ["src"],
|
||||||
"exclude": ["node_modules"]
|
"exclude": ["node_modules"]
|
||||||
|
Loading…
Reference in New Issue
Block a user