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 extends AbstractFactory { protected static loggedDefaultImplementationWarningOnce = false @Inject() protected readonly logging!: Logging @Inject() protected readonly config!: Config constructor() { super({}) } protected abstract getConfigKey(): string protected abstract getDefaultImplementation(): Instantiable protected abstract getAbstractImplementation(): any protected getDefaultImplementationWarning(): Maybe { return undefined } produce(dependencies: any[], parameters: any[]): T { return new (this.getImplementation())(...dependencies, ...parameters) } match(something: unknown): boolean { return something === this.getAbstractImplementation() } getDependencyKeys(): Collection { const meta = Reflect.getMetadata(DEPENDENCY_KEYS_METADATA_KEY, this.getImplementation()) if ( meta ) { return meta } return new Collection() } getInjectedProperties(): Collection { return this.getInjectedPropertiesForPrototypeChain(this.getImplementation()) } protected getImplementation(): Instantiable { 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 } }