Centralize configure-able factory classes

This commit is contained in:
2022-09-26 11:34:23 -05:00
parent 5557aae543
commit c0595f3ef9
23 changed files with 262 additions and 682 deletions

View File

@@ -1,5 +1,5 @@
import 'reflect-metadata'
import {collect, Collection} from '../../util'
import {collect, Collection} from '../../util/collection/Collection'
import {logIfDebugging} from '../../util/support/debug'
import {
DEPENDENCY_KEYS_METADATA_KEY,

View File

@@ -1,5 +1,6 @@
import {DependencyKey, DependencyRequirement, PropertyDependency} from '../types'
import { Collection } from '../../util'
import {DependencyKey, DependencyRequirement, Instantiable, PropertyDependency} from '../types'
import {Collection, logIfDebugging} from '../../util'
import {getPropertyInjectionMetadata} from '../decorator/getPropertyInjectionMetadata'
/**
* Abstract base class for dependency container factories.
@@ -42,6 +43,22 @@ export abstract class AbstractFactory<T> {
*/
abstract getInjectedProperties(): Collection<PropertyDependency>
/** Helper method that returns all `@Inject()`'ed properties for a token and its prototypical ancestors. */
protected getInjectedPropertiesForPrototypeChain(token: Instantiable<any>): Collection<PropertyDependency> {
const meta = new Collection<PropertyDependency>()
do {
const loadedMeta = getPropertyInjectionMetadata(token)
if ( loadedMeta ) {
meta.concat(loadedMeta)
}
token = Object.getPrototypeOf(token)
logIfDebugging('extollo.di.injection', 'next currentToken:', token)
} while (token !== Function.prototype && token !== Object.prototype)
return meta
}
/**
* Get a human-readable name of the token this factory produces.
* This is meant for debugging output only.

View File

@@ -0,0 +1,84 @@
import {AbstractFactory} from './AbstractFactory'
import {Inject, Injectable} from '../decorator/injection'
import {Logging} from '../../service/Logging'
import {Config} from '../../service/Config'
import {
DEPENDENCY_KEYS_METADATA_KEY,
DependencyRequirement,
Instantiable,
isInstantiable,
PropertyDependency,
} from '../types'
import {Collection, ErrorWithContext, Maybe} from '../../util'
import 'reflect-metadata'
@Injectable()
export abstract class ConfiguredSingletonFactory<T> extends AbstractFactory<T> {
protected static loggedDefaultImplementationWarningOnce = false
@Inject()
protected readonly logging!: Logging
@Inject()
protected readonly config!: Config
constructor() {
super({})
}
protected abstract getConfigKey(): string
protected abstract getDefaultImplementation(): Instantiable<T>
protected abstract getAbstractImplementation(): any
protected getDefaultImplementationWarning(): Maybe<string> {
return undefined
}
produce(dependencies: any[], parameters: any[]): T {
return new (this.getImplementation())(...dependencies, ...parameters)
}
match(something: unknown): boolean {
return something === this.getAbstractImplementation()
}
getDependencyKeys(): Collection<DependencyRequirement> {
const meta = Reflect.getMetadata(DEPENDENCY_KEYS_METADATA_KEY, this.getImplementation())
if ( meta ) {
return meta
}
return new Collection<DependencyRequirement>()
}
getInjectedProperties(): Collection<PropertyDependency> {
return this.getInjectedPropertiesForPrototypeChain(this.getImplementation())
}
protected getImplementation(): Instantiable<T> {
const ctor = this.constructor as typeof ConfiguredSingletonFactory
const ImplementationClass = this.config.get(this.getConfigKey(), this.getDefaultImplementation())
if ( ImplementationClass === this.getDefaultImplementation() ) {
const warning = this.getDefaultImplementationWarning()
if ( warning && !ctor.loggedDefaultImplementationWarningOnce ) {
this.logging.warn(warning)
ctor.loggedDefaultImplementationWarningOnce = true
}
}
if (
!isInstantiable(ImplementationClass)
|| !(ImplementationClass.prototype instanceof this.getAbstractImplementation())
) {
throw new ErrorWithContext('Configured service clas does not properly extend from implementation base class.', {
configKey: this.getConfigKey(),
class: `${ImplementationClass}`,
mustExtendBase: `${this.getAbstractImplementation()}`,
})
}
return ImplementationClass as Instantiable<T>
}
}

View File

@@ -5,9 +5,8 @@ import {
Instantiable,
PropertyDependency,
} from '../types'
import {Collection, logIfDebugging} from '../../util'
import {Collection} from '../../util'
import 'reflect-metadata'
import {getPropertyInjectionMetadata} from '../decorator/getPropertyInjectionMetadata'
/**
* Standard static-class factory. The token of this factory is a reference to a
@@ -53,18 +52,6 @@ export class Factory<T> extends AbstractFactory<T> {
}
getInjectedProperties(): Collection<PropertyDependency> {
const meta = new Collection<PropertyDependency>()
let currentToken = this.token
do {
const loadedMeta = getPropertyInjectionMetadata(currentToken)
logIfDebugging('extollo.di.injection', 'Factory.getInjectedProperties() target:', currentToken, 'loaded:', loadedMeta)
if ( loadedMeta ) {
meta.concat(loadedMeta)
}
currentToken = Object.getPrototypeOf(currentToken)
} while (Object.getPrototypeOf(currentToken) !== Function.prototype && Object.getPrototypeOf(currentToken) !== Object.prototype)
return meta
return this.getInjectedPropertiesForPrototypeChain(this.token)
}
}

View File

@@ -7,12 +7,13 @@ export * from './factory/Factory'
export * from './factory/NamedFactory'
export * from './factory/SingletonFactory'
export * from './types'
export * from './ContainerBlueprint'
export * from './decorator/getPropertyInjectionMetadata'
export * from './decorator/injection'
export * from './Container'
export * from './ScopedContainer'
export * from './types'
export * from './decorator/injection'
export * from './decorator/getPropertyInjectionMetadata'
export * from './InjectionAware'
export * from './constructable'