Centralize configure-able factory classes

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

View File

@ -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",

View File

@ -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<AuthenticatableRepository> {
protected get config(): Config {
return Container.getContainer().make<Config>(Config)
export class AuthenticatableRepositoryFactory extends ConfiguredSingletonFactory<AuthenticatableRepository> {
protected getConfigKey(): string {
return 'auth.storage'
}
produce(): AuthenticatableRepository {
return new (this.getAuthenticatableRepositoryClass())()
protected getDefaultImplementation(): Instantiable<AuthenticatableRepository> {
return ORMUserRepository
}
match(something: unknown): boolean {
return something === AuthenticatableRepository
}
getDependencyKeys(): Collection<DependencyRequirement> {
const meta = Reflect.getMetadata(DEPENDENCY_KEYS_METADATA_KEY, this.getAuthenticatableRepositoryClass())
if ( meta ) {
return meta
}
return new Collection<DependencyRequirement>()
}
getInjectedProperties(): Collection<PropertyDependency> {
const meta = new Collection<PropertyDependency>()
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<AuthenticatableRepository> {
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
}
}

View File

@ -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<ClientRepository> {
protected get config(): Config {
return Container.getContainer().make<Config>(Config)
export class ClientRepositoryFactory extends ConfiguredSingletonFactory<ClientRepository> {
protected getConfigKey(): string {
return 'oauth2.repository.client'
}
produce(): ClientRepository {
return new (this.getClientRepositoryClass())()
protected getDefaultImplementation(): Instantiable<ClientRepository> {
return ConfigClientRepository
}
match(something: unknown): boolean {
return something === ClientRepository
}
getDependencyKeys(): Collection<DependencyRequirement> {
const meta = Reflect.getMetadata(DEPENDENCY_KEYS_METADATA_KEY, this.getClientRepositoryClass())
if ( meta ) {
return meta
}
return new Collection<DependencyRequirement>()
}
getInjectedProperties(): Collection<PropertyDependency> {
const meta = new Collection<PropertyDependency>()
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<ClientRepository>
*/
protected getClientRepositoryClass(): Instantiable<ClientRepository> {
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
}
}

View File

@ -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<RedemptionCodeRepository> {
protected get config(): Config {
return Container.getContainer().make<Config>(Config)
export class RedemptionCodeRepositoryFactory extends ConfiguredSingletonFactory<RedemptionCodeRepository> {
protected getConfigKey(): string {
return 'oauth2.repository.client'
}
produce(): RedemptionCodeRepository {
return new (this.getRedemptionCodeRepositoryClass())()
protected getDefaultImplementation(): Instantiable<RedemptionCodeRepository> {
return CacheRedemptionCodeRepository
}
match(something: unknown): boolean {
return something === RedemptionCodeRepository
}
getDependencyKeys(): Collection<DependencyRequirement> {
const meta = Reflect.getMetadata(DEPENDENCY_KEYS_METADATA_KEY, this.getRedemptionCodeRepositoryClass())
if ( meta ) {
return meta
}
return new Collection<DependencyRequirement>()
}
getInjectedProperties(): Collection<PropertyDependency> {
const meta = new Collection<PropertyDependency>()
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<RedemptionCodeRepository>
*/
protected getRedemptionCodeRepositoryClass(): Instantiable<RedemptionCodeRepository> {
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
}
}

View File

@ -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<ScopeRepository> {
protected get config(): Config {
return Container.getContainer().make<Config>(Config)
export class ScopeRepositoryFactory extends ConfiguredSingletonFactory<ScopeRepository> {
protected getConfigKey(): string {
return 'oauth2.repository.scope'
}
produce(): ScopeRepository {
return new (this.getScopeRepositoryClass())()
protected getDefaultImplementation(): Instantiable<ScopeRepository> {
return ConfigScopeRepository
}
match(something: unknown): boolean {
return something === ScopeRepository
}
getDependencyKeys(): Collection<DependencyRequirement> {
const meta = Reflect.getMetadata(DEPENDENCY_KEYS_METADATA_KEY, this.getScopeRepositoryClass())
if ( meta ) {
return meta
}
return new Collection<DependencyRequirement>()
}
getInjectedProperties(): Collection<PropertyDependency> {
const meta = new Collection<PropertyDependency>()
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<ScopeRepository>
*/
protected getScopeRepositoryClass(): Instantiable<ScopeRepository> {
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
}
}

View File

@ -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<TokenRepository> {
protected get config(): Config {
return Container.getContainer().make<Config>(Config)
export class TokenRepositoryFactory extends ConfiguredSingletonFactory<TokenRepository> {
protected getConfigKey(): string {
return 'oauth2.repository.token'
}
produce(): TokenRepository {
return new (this.getTokenRepositoryClass())()
protected getDefaultImplementation(): Instantiable<TokenRepository> {
return ORMTokenRepository
}
match(something: unknown): boolean {
return something === TokenRepository
}
getDependencyKeys(): Collection<DependencyRequirement> {
const meta = Reflect.getMetadata(DEPENDENCY_KEYS_METADATA_KEY, this.getTokenRepositoryClass())
if ( meta ) {
return meta
}
return new Collection<DependencyRequirement>()
}
getInjectedProperties(): Collection<PropertyDependency> {
const meta = new Collection<PropertyDependency>()
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<TokenRepository>
*/
protected getTokenRepositoryClass(): Instantiable<TokenRepository> {
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
}
}

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'

View File

@ -20,7 +20,7 @@ export class InjectSessionHTTPModule extends HTTPKernelModule {
}
public async apply(request: Request): Promise<Request> {
request.registerFactory(new SessionFactory())
request.registerFactory(request.make(SessionFactory))
const session = <Session> request.make(Session)
const id = request.cookies.get('extollo.session')

View File

@ -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<Session> {
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>(Logging)
this.config = Container.getContainer().make<Config>(Config)
export class SessionFactory extends ConfiguredSingletonFactory<Session> {
protected getConfigKey(): string {
return 'server.session.driver'
}
produce(): Session {
return new (this.getSessionClass())()
protected getDefaultImplementation(): Instantiable<Session> {
return MemorySession
}
match(something: unknown): boolean {
return something === Session
protected getAbstractImplementation(): any {
return Session
}
getDependencyKeys(): Collection<DependencyRequirement> {
const meta = Reflect.getMetadata(DEPENDENCY_KEYS_METADATA_KEY, this.getSessionClass())
if ( meta ) {
return meta
}
return new Collection<DependencyRequirement>()
}
getInjectedProperties(): Collection<PropertyDependency> {
const meta = new Collection<PropertyDependency>()
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<Session>
*/
protected getSessionClass(): Instantiable<Session> {
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<string> {
return 'You are using the default memory-based session driver. It is recommended you configure a persistent session driver instead.'
}
}

View File

@ -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<Migrator> {
@Inject()
protected readonly logging!: Logging
@Inject()
protected readonly config!: Config
constructor() {
super({})
export class MigratorFactory extends ConfiguredSingletonFactory<Migrator> {
protected getConfigKey(): string {
return 'database.migrations.driver'
}
produce(): Migrator {
return new (this.getMigratorClass())()
protected getDefaultImplementation(): Instantiable<Migrator> {
return DatabaseMigrator
}
match(something: unknown): boolean {
return something === Migrator
}
getDependencyKeys(): Collection<DependencyRequirement> {
const meta = Reflect.getMetadata(DEPENDENCY_KEYS_METADATA_KEY, this.getMigratorClass())
if ( meta ) {
return meta
}
return new Collection<DependencyRequirement>()
}
getInjectedProperties(): Collection<PropertyDependency> {
const meta = new Collection<PropertyDependency>()
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<Migrator>
*/
protected getMigratorClass(): Instantiable<Migrator> {
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
}
}

View File

@ -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<T extends TreeModel<T>> extends Model<T> {
public static readonly parentIdField = 'parent_id'
/** @override to include the tree fields */
public static query<T2 extends Model<T2>>(): ModelBuilder<T2> {
return super.query<T2>()
.fields(this.rightTreeField, this.leftTreeField, this.parentIdField)
}
/**
* @override to eager-load the subtree by default
* @protected
@ -52,6 +59,13 @@ export abstract class TreeModel<T extends TreeModel<T>> extends Model<T> {
protected removedChildren: Collection<TreeModel<T>> = collect()
/** @override to include the tree fields */
public query(): ModelBuilder<T> {
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<number> {
const ctor = this.constructor as typeof TreeModel

View File

@ -1,5 +1,5 @@
import {Canonical} from './Canonical'
import {Singleton} from '../di'
import {Singleton} from '../di/decorator/injection'
import {Maybe} from '../util'
/**

View File

@ -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

View File

@ -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<Queue> {
/** 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),
]
export class QueueFactory extends ConfiguredSingletonFactory<Queue> {
protected getConfigKey(): string {
return 'server.queue.driver'
}
produce(): Queue {
return new (this.getQueueClass())()
protected getDefaultImplementation(): Instantiable<Queue> {
return SyncQueue
}
match(something: unknown): boolean {
return something === Queue
protected getAbstractImplementation(): any {
return Queue
}
getDependencyKeys(): Collection<DependencyRequirement> {
const meta = Reflect.getMetadata(DEPENDENCY_KEYS_METADATA_KEY, this.getQueueClass())
if ( meta ) {
return meta
}
return new Collection<DependencyRequirement>()
}
getInjectedProperties(): Collection<PropertyDependency> {
const meta = new Collection<PropertyDependency>()
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<Queue>.
* @protected
*/
protected getQueueClass(): StaticInstantiable<Queue> {
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<string> {
return 'You are using the default synchronous queue driver. It is recommended you configure a background queue driver instead.'
}
}

View File

@ -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<Cache> {
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>(Logging)
this.config = Container.getContainer().make<Config>(Config)
@FactoryProducer()
export class CacheFactory extends ConfiguredSingletonFactory<Cache> {
protected getConfigKey(): string {
return 'server.cache.driver'
}
produce(): Cache {
return new (this.getCacheClass())()
protected getDefaultImplementation(): Instantiable<Cache> {
return InMemCache
}
match(something: unknown): boolean {
return something === Cache
protected getAbstractImplementation(): any {
return Cache
}
getDependencyKeys(): Collection<DependencyRequirement> {
const meta = Reflect.getMetadata(DEPENDENCY_KEYS_METADATA_KEY, this.getCacheClass())
if ( meta ) {
return meta
}
return new Collection<DependencyRequirement>()
}
getInjectedProperties(): Collection<PropertyDependency> {
const meta = new Collection<PropertyDependency>()
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<Cache>.
* @protected
*/
protected getCacheClass(): StaticClass<Cache, Instantiable<Cache>> {
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<string> {
return 'You are using the default memory-based cache driver. It is recommended you configure a persistent cache driver instead.'
}
}

View File

@ -1,5 +1,5 @@
import {Collection} from './Collection'
import {InjectionAware} from '../../di'
import {InjectionAware} from '../../di/InjectionAware'
export type MaybeIterationItem<T> = { done: boolean, value?: T }
export type ChunkCallback<T> = (items: Collection<T>) => any

View File

@ -2,6 +2,8 @@ import {RequestInfo, RequestInit, Response} from 'node-fetch'
import {unsafeESMImport} from './unsafe'
export const fetch = (url: RequestInfo, init?: RequestInit): Promise<Response> => unsafeESMImport('node-fetch').then(({default: nodeFetch}) => nodeFetch(url, init))
export * from './support/operator'
export * from './cache/Cache'
export * from './cache/InMemCache'

View File

@ -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<T>(value: T, ...ops: ((t: T) => unknown)[]): T {
ops.forEach(op => op(value))
return value
}

View File

@ -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<ViewEngine> {
protected readonly logging: Logging
protected readonly config: Config
constructor() {
super({})
this.logging = Container.getContainer().make<Logging>(Logging)
this.config = Container.getContainer().make<Config>(Config)
@FactoryProducer()
export class ViewEngineFactory extends ConfiguredSingletonFactory<ViewEngine> {
protected getConfigKey(): string {
return 'server.view_engine.driver'
}
produce(): ViewEngine {
return new (this.getViewEngineClass())()
protected getDefaultImplementation(): Instantiable<ViewEngine> {
return PugViewEngine
}
match(something: unknown): boolean {
return something === ViewEngine
}
getDependencyKeys(): Collection<DependencyRequirement> {
const meta = Reflect.getMetadata(DEPENDENCY_KEYS_METADATA_KEY, this.getViewEngineClass())
if ( meta ) {
return meta
}
return new Collection<DependencyRequirement>()
}
getInjectedProperties(): Collection<PropertyDependency> {
const meta = new Collection<PropertyDependency>()
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<ViewEngine, Instantiable<ViewEngine>> {
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
}
}