Add push$ to Collection; make Container listen for retroactive blueprint changes
This commit is contained in:
parent
a173393697
commit
f1791b1d76
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@extollo/lib",
|
"name": "@extollo/lib",
|
||||||
"version": "0.14.2",
|
"version": "0.14.3",
|
||||||
"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",
|
||||||
|
@ -14,7 +14,7 @@ import {
|
|||||||
Collection,
|
Collection,
|
||||||
globalRegistry,
|
globalRegistry,
|
||||||
hasOwnProperty,
|
hasOwnProperty,
|
||||||
logIfDebugging,
|
logIfDebugging, Unsubscribe,
|
||||||
} from '../util'
|
} from '../util'
|
||||||
import {ErrorWithContext, withErrorContext} from '../util/error/ErrorWithContext'
|
import {ErrorWithContext, withErrorContext} from '../util/error/ErrorWithContext'
|
||||||
import {Factory} from './factory/Factory'
|
import {Factory} from './factory/Factory'
|
||||||
@ -100,6 +100,8 @@ export class Container {
|
|||||||
.then(value => listener.callback(value))
|
.then(value => listener.callback(value))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
container.subscribeToBlueprintChanges(ContainerBlueprint.getContainerBlueprint())
|
||||||
|
|
||||||
return container
|
return container
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,11 +155,33 @@ export class Container {
|
|||||||
*/
|
*/
|
||||||
protected waitingLifecycleCallbacks: Collection<WeakRef<AwareOfContainerLifecycle>> = new Collection()
|
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() {
|
constructor() {
|
||||||
this.registerSingletonInstance<Container>(Container, this)
|
this.registerSingletonInstance<Container>(Container, this)
|
||||||
this.registerSingleton('injector', 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.
|
* Purge all factories and instances of the given key from this container.
|
||||||
* @param key
|
* @param key
|
||||||
@ -622,6 +646,8 @@ export class Container {
|
|||||||
* Perform any cleanup necessary to destroy this container instance.
|
* Perform any cleanup necessary to destroy this container instance.
|
||||||
*/
|
*/
|
||||||
destroy(): void {
|
destroy(): void {
|
||||||
|
this.blueprintSubscribers.mapCall('unsubscribe')
|
||||||
|
|
||||||
this.waitingLifecycleCallbacks
|
this.waitingLifecycleCallbacks
|
||||||
.mapCall('deref')
|
.mapCall('deref')
|
||||||
.whereDefined()
|
.whereDefined()
|
||||||
|
@ -3,6 +3,8 @@ import NamedFactory from './factory/NamedFactory'
|
|||||||
import {AbstractFactory} from './factory/AbstractFactory'
|
import {AbstractFactory} from './factory/AbstractFactory'
|
||||||
import {Factory} from './factory/Factory'
|
import {Factory} from './factory/Factory'
|
||||||
import {ClosureFactory} from './factory/ClosureFactory'
|
import {ClosureFactory} from './factory/ClosureFactory'
|
||||||
|
import {Collection, collect} from '../util/collection/Collection'
|
||||||
|
import {Subscription, Unsubscribe} from '../util/support/BehaviorSubject'
|
||||||
|
|
||||||
/** Simple type alias for a callback to a container's onResolve method. */
|
/** Simple type alias for a callback to a container's onResolve method. */
|
||||||
export type ContainerResolutionCallback<T> = (() => unknown) | ((t: T) => unknown)
|
export type ContainerResolutionCallback<T> = (() => unknown) | ((t: T) => unknown)
|
||||||
@ -25,11 +27,11 @@ export class ContainerBlueprint {
|
|||||||
return this.instance
|
return this.instance
|
||||||
}
|
}
|
||||||
|
|
||||||
protected factories: (() => AbstractFactory<any>)[] = []
|
protected factories: Collection<(() => AbstractFactory<any>)> = collect()
|
||||||
|
|
||||||
protected constructableFactories: StaticClass<AbstractFactory<any>, any>[] = []
|
protected constructableFactories: Collection<StaticClass<AbstractFactory<any>, any>> = collect()
|
||||||
|
|
||||||
protected resolutionCallbacks: ({key: TypedDependencyKey<any>, callback: ContainerResolutionCallback<any>})[] = []
|
protected resolutionCallbacks: Collection<{key: TypedDependencyKey<any>, callback: ContainerResolutionCallback<any>}> = collect()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register some factory class with the container. Should take no construction params.
|
* Register some factory class with the container. Should take no construction params.
|
||||||
@ -74,7 +76,16 @@ export class ContainerBlueprint {
|
|||||||
* Get an array of factory instances in the blueprint.
|
* Get an array of factory instances in the blueprint.
|
||||||
*/
|
*/
|
||||||
resolve(): AbstractFactory<any>[] {
|
resolve(): AbstractFactory<any>[] {
|
||||||
return this.factories.map(x => x())
|
return this.factories.map(x => x()).all()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribe to new factories being registered.
|
||||||
|
* Used by `Container` implementations to listen for factories being registered after the container is realized.
|
||||||
|
* @param sub
|
||||||
|
*/
|
||||||
|
resolve$(sub: Subscription<() => AbstractFactory<any>>): Unsubscribe {
|
||||||
|
return this.factories.push$(sub)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -94,14 +105,32 @@ export class ContainerBlueprint {
|
|||||||
* Get an array of static Factory classes that need to be instantiated by
|
* Get an array of static Factory classes that need to be instantiated by
|
||||||
* the container itself.
|
* the container itself.
|
||||||
*/
|
*/
|
||||||
resolveConstructable(): StaticClass<AbstractFactory<any>, any> {
|
resolveConstructable(): StaticClass<AbstractFactory<any>, any>[] {
|
||||||
return [...this.constructableFactories]
|
return this.constructableFactories.all()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribe to new constructable factories being registered.
|
||||||
|
* Used by `Container` implementations to listen for factories registered after the container is realized.
|
||||||
|
* @param sub
|
||||||
|
*/
|
||||||
|
resolveConstructable$(sub: Subscription<StaticClass<AbstractFactory<any>, any>>): Unsubscribe {
|
||||||
|
return this.constructableFactories.push$(sub)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get an array of DependencyKey-callback pairs to register with new containers.
|
* Get an array of DependencyKey-callback pairs to register with new containers.
|
||||||
*/
|
*/
|
||||||
resolveResolutionCallbacks(): ({key: TypedDependencyKey<any>, callback: ContainerResolutionCallback<any>})[] {
|
resolveResolutionCallbacks(): ({key: TypedDependencyKey<any>, callback: ContainerResolutionCallback<any>})[] {
|
||||||
return [...this.resolutionCallbacks]
|
return this.resolutionCallbacks.all()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribe to new resolution callbacks being registered.
|
||||||
|
* Used by `Container` implementations to listen for callbacks registered after the container is realized.
|
||||||
|
* @param sub
|
||||||
|
*/
|
||||||
|
resolveResolutionCallbacks$(sub: Subscription<{key: TypedDependencyKey<any>, callback: ContainerResolutionCallback<any>}>): Unsubscribe {
|
||||||
|
return this.resolutionCallbacks.push$(sub)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -166,6 +166,7 @@ export const Singleton = (name?: string): ClassDecorator => {
|
|||||||
...(name ? { name } : {}),
|
...(name ? { name } : {}),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logIfDebugging('extollo.di.singleton', 'Registering singleton target:', target, 'injectionType:', injectionType)
|
||||||
Reflect.defineMetadata(DEPENDENCY_KEYS_SERVICE_TYPE_KEY, injectionType, target)
|
Reflect.defineMetadata(DEPENDENCY_KEYS_SERVICE_TYPE_KEY, injectionType, target)
|
||||||
Injectable()(target)
|
Injectable()(target)
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import {AsyncPipe, Pipeline} from '../support/Pipe'
|
import {AsyncPipe, Pipeline} from '../support/Pipe'
|
||||||
|
import {Unsubscribe, Subscription} from '../support/BehaviorSubject'
|
||||||
|
|
||||||
type CollectionItem<T> = T
|
type CollectionItem<T> = T
|
||||||
type MaybeCollectionItem<T> = CollectionItem<T> | undefined
|
type MaybeCollectionItem<T> = CollectionItem<T> | undefined
|
||||||
@ -50,6 +51,8 @@ export {
|
|||||||
class Collection<T> {
|
class Collection<T> {
|
||||||
private storedItems: CollectionItem<T>[] = []
|
private storedItems: CollectionItem<T>[] = []
|
||||||
|
|
||||||
|
private pushSubscribers: Subscription<T>[] = []
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new collection from an array of items.
|
* Create a new collection from an array of items.
|
||||||
* @param items
|
* @param items
|
||||||
@ -966,6 +969,7 @@ class Collection<T> {
|
|||||||
*/
|
*/
|
||||||
prepend(item: CollectionItem<T>): Collection<T> {
|
prepend(item: CollectionItem<T>): Collection<T> {
|
||||||
this.storedItems = [item, ...this.storedItems]
|
this.storedItems = [item, ...this.storedItems]
|
||||||
|
this.callPushSubscribers(item)
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -975,9 +979,33 @@ class Collection<T> {
|
|||||||
*/
|
*/
|
||||||
push(item: CollectionItem<T>): Collection<T> {
|
push(item: CollectionItem<T>): Collection<T> {
|
||||||
this.storedItems.push(item)
|
this.storedItems.push(item)
|
||||||
|
this.callPushSubscribers(item)
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribe to listen for items being added to the collection.
|
||||||
|
* @param sub
|
||||||
|
*/
|
||||||
|
push$(sub: Subscription<T>): Unsubscribe {
|
||||||
|
this.pushSubscribers.push(sub)
|
||||||
|
return {
|
||||||
|
unsubscribe: () => this.pushSubscribers = this.pushSubscribers.filter(x => x !== sub),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Helper to notify subscribers that an item has been pushed to the collection. */
|
||||||
|
private callPushSubscribers(item: T): void {
|
||||||
|
this.pushSubscribers
|
||||||
|
.forEach(sub => {
|
||||||
|
if ( typeof sub === 'object' ) {
|
||||||
|
sub?.next?.(item)
|
||||||
|
} else {
|
||||||
|
sub(item)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Push the given items to the end of this collection.
|
* Push the given items to the end of this collection.
|
||||||
* Unlike `merge()`, this mutates the current collection's items.
|
* Unlike `merge()`, this mutates the current collection's items.
|
||||||
@ -987,6 +1015,7 @@ class Collection<T> {
|
|||||||
const concats = items instanceof Collection ? items.all() : items
|
const concats = items instanceof Collection ? items.all() : items
|
||||||
for ( const item of concats ) {
|
for ( const item of concats ) {
|
||||||
this.storedItems.push(item)
|
this.storedItems.push(item)
|
||||||
|
this.callPushSubscribers(item)
|
||||||
}
|
}
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user