diff --git a/package.json b/package.json index 460e7c4..0df99de 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@extollo/lib", - "version": "0.14.4", + "version": "0.14.5", "description": "The framework library that lifts up your code.", "main": "lib/index.js", "types": "lib/index.d.ts", diff --git a/src/auth/repository/AuthenticatableRepositoryFactory.ts b/src/auth/repository/AuthenticatableRepositoryFactory.ts index 48897bb..48df40f 100644 --- a/src/auth/repository/AuthenticatableRepositoryFactory.ts +++ b/src/auth/repository/AuthenticatableRepositoryFactory.ts @@ -1,73 +1,23 @@ -import { - AbstractFactory, - Container, - DependencyRequirement, - PropertyDependency, - isInstantiable, - DEPENDENCY_KEYS_METADATA_KEY, - Instantiable, FactoryProducer, getPropertyInjectionMetadata, -} from '../../di' -import {Collection, ErrorWithContext} from '../../util' -import {Config} from '../../service/Config' +import {Instantiable, FactoryProducer} from '../../di' import {AuthenticatableRepository} from '../types' import {ORMUserRepository} from './orm/ORMUserRepository' +import {ConfiguredSingletonFactory} from '../../di/factory/ConfiguredSingletonFactory' /** * A dependency injection factory that matches the abstract ClientRepository class * and produces an instance of the configured repository driver implementation. */ @FactoryProducer() -export class AuthenticatableRepositoryFactory extends AbstractFactory { - protected get config(): Config { - return Container.getContainer().make(Config) +export class AuthenticatableRepositoryFactory extends ConfiguredSingletonFactory { + protected getConfigKey(): string { + return 'auth.storage' } - produce(): AuthenticatableRepository { - return new (this.getAuthenticatableRepositoryClass())() + protected getDefaultImplementation(): Instantiable { + return ORMUserRepository } - match(something: unknown): boolean { - return something === AuthenticatableRepository - } - - getDependencyKeys(): Collection { - const meta = Reflect.getMetadata(DEPENDENCY_KEYS_METADATA_KEY, this.getAuthenticatableRepositoryClass()) - if ( meta ) { - return meta - } - return new Collection() - } - - getInjectedProperties(): Collection { - const meta = new Collection() - let currentToken = this.getAuthenticatableRepositoryClass() - - do { - const loadedMeta = getPropertyInjectionMetadata(currentToken) - if ( loadedMeta ) { - meta.concat(loadedMeta) - } - currentToken = Object.getPrototypeOf(currentToken) - } while (Object.getPrototypeOf(currentToken) !== Function.prototype && Object.getPrototypeOf(currentToken) !== Object.prototype) - - return meta - } - - /** - * Return the instantiable class of the configured user repository backend. - * @protected - */ - protected getAuthenticatableRepositoryClass(): Instantiable { - const AuthenticatableRepositoryClass = this.config.get('auth.storage', ORMUserRepository) - - if ( !isInstantiable(AuthenticatableRepositoryClass) || !(AuthenticatableRepositoryClass.prototype instanceof AuthenticatableRepository) ) { - const e = new ErrorWithContext('Provided client repository class does not extend from @extollo/lib.AuthenticatableRepository') - e.context = { - configKey: 'auth.storage', - class: AuthenticatableRepositoryClass.toString(), - } - } - - return AuthenticatableRepositoryClass + protected getAbstractImplementation(): any { + return AuthenticatableRepository } } diff --git a/src/auth/server/repositories/ClientRepositoryFactory.ts b/src/auth/server/repositories/ClientRepositoryFactory.ts index 656cfe5..962cf82 100644 --- a/src/auth/server/repositories/ClientRepositoryFactory.ts +++ b/src/auth/server/repositories/ClientRepositoryFactory.ts @@ -1,74 +1,23 @@ -import { - AbstractFactory, - Container, - DependencyRequirement, - PropertyDependency, - isInstantiable, - DEPENDENCY_KEYS_METADATA_KEY, - Instantiable, FactoryProducer, getPropertyInjectionMetadata, -} from '../../../di' -import {Collection, ErrorWithContext} from '../../../util' -import {Config} from '../../../service/Config' +import {Instantiable, FactoryProducer} from '../../../di' import {ClientRepository} from '../types' import {ConfigClientRepository} from './ConfigClientRepository' +import {ConfiguredSingletonFactory} from '../../../di/factory/ConfiguredSingletonFactory' /** * A dependency injection factory that matches the abstract ClientRepository class * and produces an instance of the configured repository driver implementation. */ @FactoryProducer() -export class ClientRepositoryFactory extends AbstractFactory { - protected get config(): Config { - return Container.getContainer().make(Config) +export class ClientRepositoryFactory extends ConfiguredSingletonFactory { + protected getConfigKey(): string { + return 'oauth2.repository.client' } - produce(): ClientRepository { - return new (this.getClientRepositoryClass())() + protected getDefaultImplementation(): Instantiable { + return ConfigClientRepository } - match(something: unknown): boolean { - return something === ClientRepository - } - - getDependencyKeys(): Collection { - const meta = Reflect.getMetadata(DEPENDENCY_KEYS_METADATA_KEY, this.getClientRepositoryClass()) - if ( meta ) { - return meta - } - return new Collection() - } - - getInjectedProperties(): Collection { - const meta = new Collection() - let currentToken = this.getClientRepositoryClass() - - do { - const loadedMeta = getPropertyInjectionMetadata(currentToken) - if ( loadedMeta ) { - meta.concat(loadedMeta) - } - currentToken = Object.getPrototypeOf(currentToken) - } while (Object.getPrototypeOf(currentToken) !== Function.prototype && Object.getPrototypeOf(currentToken) !== Object.prototype) - - return meta - } - - /** - * Return the instantiable class of the configured client repository backend. - * @protected - * @return Instantiable - */ - protected getClientRepositoryClass(): Instantiable { - const ClientRepositoryClass = this.config.get('oauth2.repository.client', ConfigClientRepository) - - if ( !isInstantiable(ClientRepositoryClass) || !(ClientRepositoryClass.prototype instanceof ClientRepository) ) { - const e = new ErrorWithContext('Provided client repository class does not extend from @extollo/lib.ClientRepository') - e.context = { - configKey: 'oauth2.repository.client', - class: ClientRepositoryClass.toString(), - } - } - - return ClientRepositoryClass + protected getAbstractImplementation(): any { + return ClientRepository } } diff --git a/src/auth/server/repositories/RedemptionCodeRepositoryFactory.ts b/src/auth/server/repositories/RedemptionCodeRepositoryFactory.ts index 7afe739..8441f74 100644 --- a/src/auth/server/repositories/RedemptionCodeRepositoryFactory.ts +++ b/src/auth/server/repositories/RedemptionCodeRepositoryFactory.ts @@ -1,74 +1,23 @@ -import { - AbstractFactory, - Container, - DependencyRequirement, - PropertyDependency, - isInstantiable, - DEPENDENCY_KEYS_METADATA_KEY, - Instantiable, FactoryProducer, getPropertyInjectionMetadata, -} from '../../../di' -import {Collection, ErrorWithContext} from '../../../util' -import {Config} from '../../../service/Config' +import {Instantiable, FactoryProducer} from '../../../di' import {RedemptionCodeRepository} from '../types' import {CacheRedemptionCodeRepository} from './CacheRedemptionCodeRepository' +import {ConfiguredSingletonFactory} from '../../../di/factory/ConfiguredSingletonFactory' /** * A dependency injection factory that matches the abstract RedemptionCodeRepository class * and produces an instance of the configured repository driver implementation. */ @FactoryProducer() -export class RedemptionCodeRepositoryFactory extends AbstractFactory { - protected get config(): Config { - return Container.getContainer().make(Config) +export class RedemptionCodeRepositoryFactory extends ConfiguredSingletonFactory { + protected getConfigKey(): string { + return 'oauth2.repository.client' } - produce(): RedemptionCodeRepository { - return new (this.getRedemptionCodeRepositoryClass())() + protected getDefaultImplementation(): Instantiable { + return CacheRedemptionCodeRepository } - match(something: unknown): boolean { - return something === RedemptionCodeRepository - } - - getDependencyKeys(): Collection { - const meta = Reflect.getMetadata(DEPENDENCY_KEYS_METADATA_KEY, this.getRedemptionCodeRepositoryClass()) - if ( meta ) { - return meta - } - return new Collection() - } - - getInjectedProperties(): Collection { - const meta = new Collection() - let currentToken = this.getRedemptionCodeRepositoryClass() - - do { - const loadedMeta = getPropertyInjectionMetadata(currentToken) - if ( loadedMeta ) { - meta.concat(loadedMeta) - } - currentToken = Object.getPrototypeOf(currentToken) - } while (Object.getPrototypeOf(currentToken) !== Function.prototype && Object.getPrototypeOf(currentToken) !== Object.prototype) - - return meta - } - - /** - * Return the instantiable class of the configured client repository backend. - * @protected - * @return Instantiable - */ - protected getRedemptionCodeRepositoryClass(): Instantiable { - const RedemptionCodeRepositoryClass = this.config.get('oauth2.repository.client', CacheRedemptionCodeRepository) - - if ( !isInstantiable(RedemptionCodeRepositoryClass) || !(RedemptionCodeRepositoryClass.prototype instanceof RedemptionCodeRepository) ) { - const e = new ErrorWithContext('Provided client repository class does not extend from @extollo/lib.RedemptionCodeRepository') - e.context = { - configKey: 'oauth2.repository.client', - class: RedemptionCodeRepositoryClass.toString(), - } - } - - return RedemptionCodeRepositoryClass + protected getAbstractImplementation(): any { + return RedemptionCodeRepository } } diff --git a/src/auth/server/repositories/ScopeRepositoryFactory.ts b/src/auth/server/repositories/ScopeRepositoryFactory.ts index 36f22cf..c821fd3 100644 --- a/src/auth/server/repositories/ScopeRepositoryFactory.ts +++ b/src/auth/server/repositories/ScopeRepositoryFactory.ts @@ -1,74 +1,23 @@ -import { - AbstractFactory, - Container, - DependencyRequirement, - PropertyDependency, - isInstantiable, - DEPENDENCY_KEYS_METADATA_KEY, - Instantiable, FactoryProducer, getPropertyInjectionMetadata, -} from '../../../di' -import {Collection, ErrorWithContext} from '../../../util' -import {Config} from '../../../service/Config' +import {Instantiable, FactoryProducer} from '../../../di' import {ScopeRepository} from '../types' import {ConfigScopeRepository} from './ConfigScopeRepository' +import {ConfiguredSingletonFactory} from '../../../di/factory/ConfiguredSingletonFactory' /** * A dependency injection factory that matches the abstract ScopeRepository class * and produces an instance of the configured repository driver implementation. */ @FactoryProducer() -export class ScopeRepositoryFactory extends AbstractFactory { - protected get config(): Config { - return Container.getContainer().make(Config) +export class ScopeRepositoryFactory extends ConfiguredSingletonFactory { + protected getConfigKey(): string { + return 'oauth2.repository.scope' } - produce(): ScopeRepository { - return new (this.getScopeRepositoryClass())() + protected getDefaultImplementation(): Instantiable { + return ConfigScopeRepository } - match(something: unknown): boolean { - return something === ScopeRepository - } - - getDependencyKeys(): Collection { - const meta = Reflect.getMetadata(DEPENDENCY_KEYS_METADATA_KEY, this.getScopeRepositoryClass()) - if ( meta ) { - return meta - } - return new Collection() - } - - getInjectedProperties(): Collection { - const meta = new Collection() - let currentToken = this.getScopeRepositoryClass() - - do { - const loadedMeta = getPropertyInjectionMetadata(currentToken) - if ( loadedMeta ) { - meta.concat(loadedMeta) - } - currentToken = Object.getPrototypeOf(currentToken) - } while (Object.getPrototypeOf(currentToken) !== Function.prototype && Object.getPrototypeOf(currentToken) !== Object.prototype) - - return meta - } - - /** - * Return the instantiable class of the configured scope repository backend. - * @protected - * @return Instantiable - */ - protected getScopeRepositoryClass(): Instantiable { - const ScopeRepositoryClass = this.config.get('oauth2.repository.scope', ConfigScopeRepository) - - if ( !isInstantiable(ScopeRepositoryClass) || !(ScopeRepositoryClass.prototype instanceof ScopeRepository) ) { - const e = new ErrorWithContext('Provided client repository class does not extend from @extollo/lib.ScopeRepository') - e.context = { - configKey: 'oauth2.repository.client', - class: ScopeRepositoryClass.toString(), - } - } - - return ScopeRepositoryClass + protected getAbstractImplementation(): any { + return ScopeRepository } } diff --git a/src/auth/server/repositories/TokenRepositoryFactory.ts b/src/auth/server/repositories/TokenRepositoryFactory.ts index 1b437a6..efb0106 100644 --- a/src/auth/server/repositories/TokenRepositoryFactory.ts +++ b/src/auth/server/repositories/TokenRepositoryFactory.ts @@ -1,74 +1,23 @@ -import { - AbstractFactory, - Container, - DependencyRequirement, - PropertyDependency, - isInstantiable, - DEPENDENCY_KEYS_METADATA_KEY, - Instantiable, FactoryProducer, getPropertyInjectionMetadata, -} from '../../../di' -import {Collection, ErrorWithContext} from '../../../util' -import {Config} from '../../../service/Config' +import {Instantiable, FactoryProducer} from '../../../di' import {TokenRepository} from '../types' import {ORMTokenRepository} from './ORMTokenRepository' +import {ConfiguredSingletonFactory} from '../../../di/factory/ConfiguredSingletonFactory' /** * A dependency injection factory that matches the abstract TokenRepository class * and produces an instance of the configured repository driver implementation. */ @FactoryProducer() -export class TokenRepositoryFactory extends AbstractFactory { - protected get config(): Config { - return Container.getContainer().make(Config) +export class TokenRepositoryFactory extends ConfiguredSingletonFactory { + protected getConfigKey(): string { + return 'oauth2.repository.token' } - produce(): TokenRepository { - return new (this.getTokenRepositoryClass())() + protected getDefaultImplementation(): Instantiable { + return ORMTokenRepository } - match(something: unknown): boolean { - return something === TokenRepository - } - - getDependencyKeys(): Collection { - const meta = Reflect.getMetadata(DEPENDENCY_KEYS_METADATA_KEY, this.getTokenRepositoryClass()) - if ( meta ) { - return meta - } - return new Collection() - } - - getInjectedProperties(): Collection { - const meta = new Collection() - let currentToken = this.getTokenRepositoryClass() - - do { - const loadedMeta = getPropertyInjectionMetadata(currentToken) - if ( loadedMeta ) { - meta.concat(loadedMeta) - } - currentToken = Object.getPrototypeOf(currentToken) - } while (Object.getPrototypeOf(currentToken) !== Function.prototype && Object.getPrototypeOf(currentToken) !== Object.prototype) - - return meta - } - - /** - * Return the instantiable class of the configured token repository backend. - * @protected - * @return Instantiable - */ - protected getTokenRepositoryClass(): Instantiable { - const TokenRepositoryClass = this.config.get('oauth2.repository.token', ORMTokenRepository) - - if ( !isInstantiable(TokenRepositoryClass) || !(TokenRepositoryClass.prototype instanceof TokenRepository) ) { - const e = new ErrorWithContext('Provided token repository class does not extend from @extollo/lib.TokenRepository') - e.context = { - configKey: 'oauth2.repository.client', - class: TokenRepositoryClass.toString(), - } - } - - return TokenRepositoryClass + protected getAbstractImplementation(): any { + return TokenRepository } } diff --git a/src/di/decorator/injection.ts b/src/di/decorator/injection.ts index 8682018..e1be61d 100644 --- a/src/di/decorator/injection.ts +++ b/src/di/decorator/injection.ts @@ -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, diff --git a/src/di/factory/AbstractFactory.ts b/src/di/factory/AbstractFactory.ts index ef28ca7..ce7d0cf 100644 --- a/src/di/factory/AbstractFactory.ts +++ b/src/di/factory/AbstractFactory.ts @@ -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 { */ abstract getInjectedProperties(): Collection + /** Helper method that returns all `@Inject()`'ed properties for a token and its prototypical ancestors. */ + protected getInjectedPropertiesForPrototypeChain(token: Instantiable): Collection { + const meta = new Collection() + + 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. diff --git a/src/di/factory/ConfiguredSingletonFactory.ts b/src/di/factory/ConfiguredSingletonFactory.ts new file mode 100644 index 0000000..b3cb4a0 --- /dev/null +++ b/src/di/factory/ConfiguredSingletonFactory.ts @@ -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 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 + } +} diff --git a/src/di/factory/Factory.ts b/src/di/factory/Factory.ts index ca16d0b..558451a 100644 --- a/src/di/factory/Factory.ts +++ b/src/di/factory/Factory.ts @@ -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 extends AbstractFactory { } getInjectedProperties(): Collection { - const meta = new Collection() - 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) } } diff --git a/src/di/index.ts b/src/di/index.ts index a8ece7c..3f7ce47 100644 --- a/src/di/index.ts +++ b/src/di/index.ts @@ -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' diff --git a/src/http/kernel/module/InjectSessionHTTPModule.ts b/src/http/kernel/module/InjectSessionHTTPModule.ts index 02f5ea3..3ff936e 100644 --- a/src/http/kernel/module/InjectSessionHTTPModule.ts +++ b/src/http/kernel/module/InjectSessionHTTPModule.ts @@ -20,7 +20,7 @@ export class InjectSessionHTTPModule extends HTTPKernelModule { } public async apply(request: Request): Promise { - request.registerFactory(new SessionFactory()) + request.registerFactory(request.make(SessionFactory)) const session = request.make(Session) const id = request.cookies.get('extollo.session') diff --git a/src/http/session/SessionFactory.ts b/src/http/session/SessionFactory.ts index e43baf5..c019cdb 100644 --- a/src/http/session/SessionFactory.ts +++ b/src/http/session/SessionFactory.ts @@ -1,87 +1,23 @@ -import { - AbstractFactory, - Container, - DependencyRequirement, - PropertyDependency, - isInstantiable, - DEPENDENCY_KEYS_METADATA_KEY, - Instantiable, getPropertyInjectionMetadata, -} from '../../di' -import {Collection, ErrorWithContext} from '../../util' +import {Instantiable} from '../../di' +import {Maybe} from '../../util' import {MemorySession} from './MemorySession' import {Session} from './Session' -import {Logging} from '../../service/Logging' -import {Config} from '../../service/Config' +import {ConfiguredSingletonFactory} from '../../di/factory/ConfiguredSingletonFactory' -/** - * A dependency injection factory that matches the abstract Session class - * and produces an instance of the configured session driver implementation. - */ -export class SessionFactory extends AbstractFactory { - protected readonly logging: Logging - - protected readonly config: Config - - /** True if we have printed the memory session warning at least once. */ - private static loggedMemorySessionWarningOnce = false - - constructor() { - super({}) - this.logging = Container.getContainer().make(Logging) - this.config = Container.getContainer().make(Config) +export class SessionFactory extends ConfiguredSingletonFactory { + protected getConfigKey(): string { + return 'server.session.driver' } - produce(): Session { - return new (this.getSessionClass())() + protected getDefaultImplementation(): Instantiable { + return MemorySession } - match(something: unknown): boolean { - return something === Session + protected getAbstractImplementation(): any { + return Session } - getDependencyKeys(): Collection { - const meta = Reflect.getMetadata(DEPENDENCY_KEYS_METADATA_KEY, this.getSessionClass()) - if ( meta ) { - return meta - } - return new Collection() - } - - getInjectedProperties(): Collection { - const meta = new Collection() - let currentToken = this.getSessionClass() - - do { - const loadedMeta = getPropertyInjectionMetadata(currentToken) - if ( loadedMeta ) { - meta.concat(loadedMeta) - } - currentToken = Object.getPrototypeOf(currentToken) - } while (Object.getPrototypeOf(currentToken) !== Function.prototype && Object.getPrototypeOf(currentToken) !== Object.prototype) - - return meta - } - - /** - * Return the instantiable class of the configured session backend. - * @protected - * @return Instantiable - */ - protected getSessionClass(): Instantiable { - const SessionClass = this.config.get('server.session.driver', MemorySession) - if ( SessionClass === MemorySession && !SessionFactory.loggedMemorySessionWarningOnce ) { - this.logging.warn(`You are using the default memory-based session driver. It is recommended you configure a persistent session driver instead.`) - SessionFactory.loggedMemorySessionWarningOnce = true - } - - if ( !isInstantiable(SessionClass) || !(SessionClass.prototype instanceof Session) ) { - const e = new ErrorWithContext('Provided session class does not extend from @extollo/lib.Session') - e.context = { - configKey: 'server.session.driver', - class: SessionClass.toString(), - } - } - - return SessionClass + protected getDefaultImplementationWarning(): Maybe { + return 'You are using the default memory-based session driver. It is recommended you configure a persistent session driver instead.' } } diff --git a/src/orm/migrations/MigratorFactory.ts b/src/orm/migrations/MigratorFactory.ts index fa2dc01..79b2ccf 100644 --- a/src/orm/migrations/MigratorFactory.ts +++ b/src/orm/migrations/MigratorFactory.ts @@ -1,86 +1,23 @@ -import { - AbstractFactory, - DependencyRequirement, - PropertyDependency, - isInstantiable, - DEPENDENCY_KEYS_METADATA_KEY, - Instantiable, - Injectable, - Inject, - FactoryProducer, - getPropertyInjectionMetadata, -} from '../../di' -import {Collection, ErrorWithContext} from '../../util' -import {Logging} from '../../service/Logging' -import {Config} from '../../service/Config' +import {Instantiable, FactoryProducer} from '../../di' import {Migrator} from './Migrator' import {DatabaseMigrator} from './DatabaseMigrator' +import {ConfiguredSingletonFactory} from '../../di/factory/ConfiguredSingletonFactory' /** * A dependency injection factory that matches the abstract Migrator class * and produces an instance of the configured session driver implementation. */ -@Injectable() @FactoryProducer() -export class MigratorFactory extends AbstractFactory { - @Inject() - protected readonly logging!: Logging - - @Inject() - protected readonly config!: Config - - constructor() { - super({}) - } - - produce(): Migrator { - return new (this.getMigratorClass())() +export class MigratorFactory extends ConfiguredSingletonFactory { + protected getConfigKey(): string { + return 'database.migrations.driver' } - match(something: unknown): boolean { - return something === Migrator - } - - getDependencyKeys(): Collection { - const meta = Reflect.getMetadata(DEPENDENCY_KEYS_METADATA_KEY, this.getMigratorClass()) - if ( meta ) { - return meta - } - - return new Collection() + protected getDefaultImplementation(): Instantiable { + return DatabaseMigrator } - getInjectedProperties(): Collection { - const meta = new Collection() - let currentToken = this.getMigratorClass() - - do { - const loadedMeta = getPropertyInjectionMetadata(currentToken) - if ( loadedMeta ) { - meta.concat(loadedMeta) - } - currentToken = Object.getPrototypeOf(currentToken) - } while (Object.getPrototypeOf(currentToken) !== Function.prototype && Object.getPrototypeOf(currentToken) !== Object.prototype) - - return meta - } - - /** - * Return the instantiable class of the configured migrator backend. - * @protected - * @return Instantiable - */ - protected getMigratorClass(): Instantiable { - const MigratorClass = this.config.get('database.migrations.driver', DatabaseMigrator) - - if ( !isInstantiable(MigratorClass) || !(MigratorClass.prototype instanceof Migrator) ) { - const e = new ErrorWithContext('Provided migration driver class does not extend from @extollo/lib.Migrator') - e.context = { - configKey: 'database.migrations.driver', - class: MigratorClass.toString(), - } - } - - return MigratorClass + protected getAbstractImplementation(): any { + return Migrator } } diff --git a/src/orm/model/TreeModel.ts b/src/orm/model/TreeModel.ts index 8166175..5274a25 100644 --- a/src/orm/model/TreeModel.ts +++ b/src/orm/model/TreeModel.ts @@ -3,6 +3,7 @@ import {collect, Collection, ErrorWithContext, Maybe} from '../../util' import {HasSubtree} from './relation/HasSubtree' import {Related} from './relation/decorators' import {HasTreeParent} from './relation/HasTreeParent' +import {ModelBuilder} from './ModelBuilder' /** * Model implementation with helpers for querying tree-structured data. @@ -44,6 +45,12 @@ export abstract class TreeModel> extends Model { public static readonly parentIdField = 'parent_id' + /** @override to include the tree fields */ + public static query>(): ModelBuilder { + return super.query() + .fields(this.rightTreeField, this.leftTreeField, this.parentIdField) + } + /** * @override to eager-load the subtree by default * @protected @@ -52,6 +59,13 @@ export abstract class TreeModel> extends Model { protected removedChildren: Collection> = collect() + /** @override to include the tree fields */ + public query(): ModelBuilder { + const ctor = this.constructor as typeof TreeModel + return super.query() + .fields(ctor.leftTreeField, ctor.rightTreeField, ctor.parentIdField) + } + /** Get the left tree number for this model. */ public leftTreeNum(): Maybe { const ctor = this.constructor as typeof TreeModel diff --git a/src/service/Canon.ts b/src/service/Canon.ts index 2584f8a..42acf09 100644 --- a/src/service/Canon.ts +++ b/src/service/Canon.ts @@ -1,5 +1,5 @@ import {Canonical} from './Canonical' -import {Singleton} from '../di' +import {Singleton} from '../di/decorator/injection' import {Maybe} from '../util' /** diff --git a/src/service/Logging.ts b/src/service/Logging.ts index 6ce8d3e..b59e886 100644 --- a/src/service/Logging.ts +++ b/src/service/Logging.ts @@ -1,5 +1,7 @@ -import {DebuggingTraceIsNotAnError, Logger, LoggingLevel, LogMessage} from '../util' -import {Singleton} from '../di' +import {Singleton} from '../di/decorator/injection' +import {DebuggingTraceIsNotAnError} from '../util/error/DebuggingTraceIsNotAnError' +import {LoggingLevel, LogMessage} from '../util/logging/types' +import {Logger} from '../util/logging/Logger' /** * A singleton service that manages loggers registered in the application, and diff --git a/src/support/bus/queue/QueueFactory.ts b/src/support/bus/queue/QueueFactory.ts index f68221d..568e4fd 100644 --- a/src/support/bus/queue/QueueFactory.ts +++ b/src/support/bus/queue/QueueFactory.ts @@ -1,86 +1,28 @@ -import { - AbstractFactory, - Container, - DependencyRequirement, - PropertyDependency, - isInstantiable, - DEPENDENCY_KEYS_METADATA_KEY, - StaticInstantiable, - FactoryProducer, getPropertyInjectionMetadata, -} from '../../../di' -import {Collection, ErrorWithContext} from '../../../util' -import {Logging} from '../../../service/Logging' -import {Config} from '../../../service/Config' +import {FactoryProducer, Instantiable} from '../../../di' +import {Maybe} from '../../../util' import {Queue} from './Queue' import {SyncQueue} from './SyncQueue' +import {ConfiguredSingletonFactory} from '../../../di/factory/ConfiguredSingletonFactory' /** * Dependency container factory that matches the abstract Queue token, but * produces an instance of whatever Queue driver is configured in the `server.queue.driver` config. */ @FactoryProducer() -export class QueueFactory extends AbstractFactory { - /** true if we have printed the synchronous queue driver warning once. */ - private static loggedSyncQueueWarningOnce = false - - private di(): [Logging, Config] { - return [ - Container.getContainer().make(Logging), - Container.getContainer().make(Config), - ] - } - - produce(): Queue { - return new (this.getQueueClass())() +export class QueueFactory extends ConfiguredSingletonFactory { + protected getConfigKey(): string { + return 'server.queue.driver' } - match(something: unknown): boolean { - return something === Queue + protected getDefaultImplementation(): Instantiable { + return SyncQueue } - getDependencyKeys(): Collection { - const meta = Reflect.getMetadata(DEPENDENCY_KEYS_METADATA_KEY, this.getQueueClass()) - if ( meta ) { - return meta - } - return new Collection() + protected getAbstractImplementation(): any { + return Queue } - getInjectedProperties(): Collection { - const meta = new Collection() - let currentToken = this.getQueueClass() - - do { - const loadedMeta = getPropertyInjectionMetadata(currentToken) - if ( loadedMeta ) { - meta.concat(loadedMeta) - } - currentToken = Object.getPrototypeOf(currentToken) - } while (Object.getPrototypeOf(currentToken) !== Function.prototype && Object.getPrototypeOf(currentToken) !== Object.prototype) - - return meta - } - - /** - * Get the configured queue driver and return some Instantiable. - * @protected - */ - protected getQueueClass(): StaticInstantiable { - const [logging, config] = this.di() - const QueueClass = config.get('server.queue.driver', SyncQueue) - if ( QueueClass === SyncQueue && !QueueFactory.loggedSyncQueueWarningOnce ) { - logging.warn(`You are using the default synchronous queue driver. It is recommended you configure a background queue driver instead.`) - QueueFactory.loggedSyncQueueWarningOnce = true - } - - if ( !isInstantiable(QueueClass) || !(QueueClass.prototype instanceof Queue) ) { - const e = new ErrorWithContext('Provided queue class does not extend from @extollo/lib.Queue') - e.context = { - configKey: 'server.queue.driver', - class: QueueClass.toString(), - } - } - - return QueueClass + protected getDefaultImplementationWarning(): Maybe { + return 'You are using the default synchronous queue driver. It is recommended you configure a background queue driver instead.' } } diff --git a/src/support/cache/CacheFactory.ts b/src/support/cache/CacheFactory.ts index c656885..6139363 100644 --- a/src/support/cache/CacheFactory.ts +++ b/src/support/cache/CacheFactory.ts @@ -1,86 +1,27 @@ -import { - AbstractFactory, - Container, - DependencyRequirement, - PropertyDependency, - isInstantiable, - DEPENDENCY_KEYS_METADATA_KEY, - StaticClass, Instantiable, getPropertyInjectionMetadata, -} from '../../di' -import {Collection, ErrorWithContext} from '../../util' -import {Logging} from '../../service/Logging' -import {Config} from '../../service/Config' +import {Instantiable, FactoryProducer} from '../../di' +import {InMemCache, Maybe} from '../../util' import {Cache} from '../../util' -import {MemoryCache} from './MemoryCache' +import {ConfiguredSingletonFactory} from '../../di/factory/ConfiguredSingletonFactory' /** * Dependency container factory that matches the abstract Cache token, but * produces an instance of whatever Cache driver is configured in the `server.cache.driver` config. */ -export class CacheFactory extends AbstractFactory { - protected readonly logging: Logging - - protected readonly config: Config - - /** true if we have printed the memory-based cache driver warning once. */ - private static loggedMemoryCacheWarningOnce = false - - constructor() { - super({}) - this.logging = Container.getContainer().make(Logging) - this.config = Container.getContainer().make(Config) +@FactoryProducer() +export class CacheFactory extends ConfiguredSingletonFactory { + protected getConfigKey(): string { + return 'server.cache.driver' } - produce(): Cache { - return new (this.getCacheClass())() + protected getDefaultImplementation(): Instantiable { + return InMemCache } - match(something: unknown): boolean { - return something === Cache + protected getAbstractImplementation(): any { + return Cache } - getDependencyKeys(): Collection { - const meta = Reflect.getMetadata(DEPENDENCY_KEYS_METADATA_KEY, this.getCacheClass()) - if ( meta ) { - return meta - } - return new Collection() - } - - getInjectedProperties(): Collection { - const meta = new Collection() - let currentToken = this.getCacheClass() - - do { - const loadedMeta = getPropertyInjectionMetadata(currentToken) - if ( loadedMeta ) { - meta.concat(loadedMeta) - } - currentToken = Object.getPrototypeOf(currentToken) - } while (Object.getPrototypeOf(currentToken) !== Function.prototype && Object.getPrototypeOf(currentToken) !== Object.prototype) - - return meta - } - - /** - * Get the configured cache driver and return some Instantiable. - * @protected - */ - protected getCacheClass(): StaticClass> { - const CacheClass = this.config.get('server.cache.driver', MemoryCache) - if ( CacheClass === MemoryCache && !CacheFactory.loggedMemoryCacheWarningOnce ) { - this.logging.warn(`You are using the default memory-based cache driver. It is recommended you configure a persistent cache driver instead.`) - CacheFactory.loggedMemoryCacheWarningOnce = true - } - - if ( !isInstantiable(CacheClass) || !(CacheClass.prototype instanceof Cache) ) { - const e = new ErrorWithContext('Provided session class does not extend from @extollo/lib.Cache') - e.context = { - configKey: 'server.cache.driver', - class: CacheClass.toString(), - } - } - - return CacheClass + protected getDefaultImplementationWarning(): Maybe { + return 'You are using the default memory-based cache driver. It is recommended you configure a persistent cache driver instead.' } } diff --git a/src/util/collection/Iterable.ts b/src/util/collection/Iterable.ts index 7d24e06..06caf71 100644 --- a/src/util/collection/Iterable.ts +++ b/src/util/collection/Iterable.ts @@ -1,5 +1,5 @@ import {Collection} from './Collection' -import {InjectionAware} from '../../di' +import {InjectionAware} from '../../di/InjectionAware' export type MaybeIterationItem = { done: boolean, value?: T } export type ChunkCallback = (items: Collection) => any diff --git a/src/util/index.ts b/src/util/index.ts index 651db2f..cc9e89d 100644 --- a/src/util/index.ts +++ b/src/util/index.ts @@ -2,6 +2,8 @@ import {RequestInfo, RequestInit, Response} from 'node-fetch' import {unsafeESMImport} from './unsafe' export const fetch = (url: RequestInfo, init?: RequestInit): Promise => unsafeESMImport('node-fetch').then(({default: nodeFetch}) => nodeFetch(url, init)) +export * from './support/operator' + export * from './cache/Cache' export * from './cache/InMemCache' diff --git a/src/util/support/operator.ts b/src/util/support/operator.ts new file mode 100644 index 0000000..35b7043 --- /dev/null +++ b/src/util/support/operator.ts @@ -0,0 +1,27 @@ +/** + * Apply a series of operators to a value, returning the original value. + * + * Helpful for values/methods that don't support chaining. + * + * @example + * ```ts + * const inOneHour = () => tap(new Date, d => d.setMinutes(d.getMinutes() + 60)) + * ``` + * + * This is equivalent to: + * + * ```ts + * const inOneHour = () => { + * const d = new Date + * d.setMinutes(d.getMinutes() + 60) + * return d + * } + * ``` + * + * @param value + * @param ops + */ +export function tap(value: T, ...ops: ((t: T) => unknown)[]): T { + ops.forEach(op => op(value)) + return value +} diff --git a/src/views/ViewEngineFactory.ts b/src/views/ViewEngineFactory.ts index b220137..0b69fab 100644 --- a/src/views/ViewEngineFactory.ts +++ b/src/views/ViewEngineFactory.ts @@ -1,79 +1,23 @@ -import { - AbstractFactory, - Container, - DependencyRequirement, - PropertyDependency, - isInstantiable, - DEPENDENCY_KEYS_METADATA_KEY, - StaticClass, Instantiable, getPropertyInjectionMetadata, -} from '../di' -import {Collection, ErrorWithContext} from '../util' -import {Logging} from '../service/Logging' -import {Config} from '../service/Config' +import {Instantiable, FactoryProducer} from '../di' import {ViewEngine} from './ViewEngine' import {PugViewEngine} from './PugViewEngine' +import {ConfiguredSingletonFactory} from '../di/factory/ConfiguredSingletonFactory' /** * Dependency factory whose token matches the abstract ViewEngine class, but produces * a particular ViewEngine implementation based on the configuration. */ -export class ViewEngineFactory extends AbstractFactory { - protected readonly logging: Logging - - protected readonly config: Config - - constructor() { - super({}) - this.logging = Container.getContainer().make(Logging) - this.config = Container.getContainer().make(Config) - } - - produce(): ViewEngine { - return new (this.getViewEngineClass())() - } - - match(something: unknown): boolean { - return something === ViewEngine +@FactoryProducer() +export class ViewEngineFactory extends ConfiguredSingletonFactory { + protected getConfigKey(): string { + return 'server.view_engine.driver' } - getDependencyKeys(): Collection { - const meta = Reflect.getMetadata(DEPENDENCY_KEYS_METADATA_KEY, this.getViewEngineClass()) - if ( meta ) { - return meta - } - return new Collection() + protected getDefaultImplementation(): Instantiable { + return PugViewEngine } - getInjectedProperties(): Collection { - const meta = new Collection() - let currentToken = this.getViewEngineClass() - - do { - const loadedMeta = getPropertyInjectionMetadata(currentToken) - if ( loadedMeta ) { - meta.concat(loadedMeta) - } - currentToken = Object.getPrototypeOf(currentToken) - } while (Object.getPrototypeOf(currentToken) !== Function.prototype && Object.getPrototypeOf(currentToken) !== Object.prototype) - - return meta - } - - /** - * Using the config, get the implementation of the ViewEngine that should be used in the application. - * @protected - */ - protected getViewEngineClass(): StaticClass> { - const ViewEngineClass = this.config.get('server.view_engine.driver', PugViewEngine) - - if ( !isInstantiable(ViewEngineClass) || !(ViewEngineClass.prototype instanceof ViewEngine) ) { - const e = new ErrorWithContext('Provided session class does not extend from @extollo/lib.ViewEngine') - e.context = { - configKey: 'server.view_engine.driver', - class: ViewEngineClass.toString(), - } - } - - return ViewEngineClass + protected getAbstractImplementation(): any { + return ViewEngine } }