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", "name": "@extollo/lib",
"version": "0.14.4", "version": "0.14.5",
"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",

View File

@ -1,73 +1,23 @@
import { import {Instantiable, FactoryProducer} from '../../di'
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 {AuthenticatableRepository} from '../types' import {AuthenticatableRepository} from '../types'
import {ORMUserRepository} from './orm/ORMUserRepository' import {ORMUserRepository} from './orm/ORMUserRepository'
import {ConfiguredSingletonFactory} from '../../di/factory/ConfiguredSingletonFactory'
/** /**
* A dependency injection factory that matches the abstract ClientRepository class * A dependency injection factory that matches the abstract ClientRepository class
* and produces an instance of the configured repository driver implementation. * and produces an instance of the configured repository driver implementation.
*/ */
@FactoryProducer() @FactoryProducer()
export class AuthenticatableRepositoryFactory extends AbstractFactory<AuthenticatableRepository> { export class AuthenticatableRepositoryFactory extends ConfiguredSingletonFactory<AuthenticatableRepository> {
protected get config(): Config { protected getConfigKey(): string {
return Container.getContainer().make<Config>(Config) return 'auth.storage'
} }
produce(): AuthenticatableRepository { protected getDefaultImplementation(): Instantiable<AuthenticatableRepository> {
return new (this.getAuthenticatableRepositoryClass())() return ORMUserRepository
} }
match(something: unknown): boolean { protected getAbstractImplementation(): any {
return something === AuthenticatableRepository return 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
} }
} }

View File

@ -1,74 +1,23 @@
import { import {Instantiable, FactoryProducer} from '../../../di'
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 {ClientRepository} from '../types' import {ClientRepository} from '../types'
import {ConfigClientRepository} from './ConfigClientRepository' import {ConfigClientRepository} from './ConfigClientRepository'
import {ConfiguredSingletonFactory} from '../../../di/factory/ConfiguredSingletonFactory'
/** /**
* A dependency injection factory that matches the abstract ClientRepository class * A dependency injection factory that matches the abstract ClientRepository class
* and produces an instance of the configured repository driver implementation. * and produces an instance of the configured repository driver implementation.
*/ */
@FactoryProducer() @FactoryProducer()
export class ClientRepositoryFactory extends AbstractFactory<ClientRepository> { export class ClientRepositoryFactory extends ConfiguredSingletonFactory<ClientRepository> {
protected get config(): Config { protected getConfigKey(): string {
return Container.getContainer().make<Config>(Config) return 'oauth2.repository.client'
} }
produce(): ClientRepository { protected getDefaultImplementation(): Instantiable<ClientRepository> {
return new (this.getClientRepositoryClass())() return ConfigClientRepository
} }
match(something: unknown): boolean { protected getAbstractImplementation(): any {
return something === ClientRepository return 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
} }
} }

View File

@ -1,74 +1,23 @@
import { import {Instantiable, FactoryProducer} from '../../../di'
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 {RedemptionCodeRepository} from '../types' import {RedemptionCodeRepository} from '../types'
import {CacheRedemptionCodeRepository} from './CacheRedemptionCodeRepository' import {CacheRedemptionCodeRepository} from './CacheRedemptionCodeRepository'
import {ConfiguredSingletonFactory} from '../../../di/factory/ConfiguredSingletonFactory'
/** /**
* A dependency injection factory that matches the abstract RedemptionCodeRepository class * A dependency injection factory that matches the abstract RedemptionCodeRepository class
* and produces an instance of the configured repository driver implementation. * and produces an instance of the configured repository driver implementation.
*/ */
@FactoryProducer() @FactoryProducer()
export class RedemptionCodeRepositoryFactory extends AbstractFactory<RedemptionCodeRepository> { export class RedemptionCodeRepositoryFactory extends ConfiguredSingletonFactory<RedemptionCodeRepository> {
protected get config(): Config { protected getConfigKey(): string {
return Container.getContainer().make<Config>(Config) return 'oauth2.repository.client'
} }
produce(): RedemptionCodeRepository { protected getDefaultImplementation(): Instantiable<RedemptionCodeRepository> {
return new (this.getRedemptionCodeRepositoryClass())() return CacheRedemptionCodeRepository
} }
match(something: unknown): boolean { protected getAbstractImplementation(): any {
return something === RedemptionCodeRepository return 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
} }
} }

View File

@ -1,74 +1,23 @@
import { import {Instantiable, FactoryProducer} from '../../../di'
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 {ScopeRepository} from '../types' import {ScopeRepository} from '../types'
import {ConfigScopeRepository} from './ConfigScopeRepository' import {ConfigScopeRepository} from './ConfigScopeRepository'
import {ConfiguredSingletonFactory} from '../../../di/factory/ConfiguredSingletonFactory'
/** /**
* A dependency injection factory that matches the abstract ScopeRepository class * A dependency injection factory that matches the abstract ScopeRepository class
* and produces an instance of the configured repository driver implementation. * and produces an instance of the configured repository driver implementation.
*/ */
@FactoryProducer() @FactoryProducer()
export class ScopeRepositoryFactory extends AbstractFactory<ScopeRepository> { export class ScopeRepositoryFactory extends ConfiguredSingletonFactory<ScopeRepository> {
protected get config(): Config { protected getConfigKey(): string {
return Container.getContainer().make<Config>(Config) return 'oauth2.repository.scope'
} }
produce(): ScopeRepository { protected getDefaultImplementation(): Instantiable<ScopeRepository> {
return new (this.getScopeRepositoryClass())() return ConfigScopeRepository
} }
match(something: unknown): boolean { protected getAbstractImplementation(): any {
return something === ScopeRepository return 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
} }
} }

View File

@ -1,74 +1,23 @@
import { import {Instantiable, FactoryProducer} from '../../../di'
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 {TokenRepository} from '../types' import {TokenRepository} from '../types'
import {ORMTokenRepository} from './ORMTokenRepository' import {ORMTokenRepository} from './ORMTokenRepository'
import {ConfiguredSingletonFactory} from '../../../di/factory/ConfiguredSingletonFactory'
/** /**
* A dependency injection factory that matches the abstract TokenRepository class * A dependency injection factory that matches the abstract TokenRepository class
* and produces an instance of the configured repository driver implementation. * and produces an instance of the configured repository driver implementation.
*/ */
@FactoryProducer() @FactoryProducer()
export class TokenRepositoryFactory extends AbstractFactory<TokenRepository> { export class TokenRepositoryFactory extends ConfiguredSingletonFactory<TokenRepository> {
protected get config(): Config { protected getConfigKey(): string {
return Container.getContainer().make<Config>(Config) return 'oauth2.repository.token'
} }
produce(): TokenRepository { protected getDefaultImplementation(): Instantiable<TokenRepository> {
return new (this.getTokenRepositoryClass())() return ORMTokenRepository
} }
match(something: unknown): boolean { protected getAbstractImplementation(): any {
return something === TokenRepository return 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
} }
} }

View File

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

View File

@ -1,5 +1,6 @@
import {DependencyKey, DependencyRequirement, PropertyDependency} from '../types' import {DependencyKey, DependencyRequirement, Instantiable, PropertyDependency} from '../types'
import { Collection } from '../../util' import {Collection, logIfDebugging} from '../../util'
import {getPropertyInjectionMetadata} from '../decorator/getPropertyInjectionMetadata'
/** /**
* Abstract base class for dependency container factories. * Abstract base class for dependency container factories.
@ -42,6 +43,22 @@ export abstract class AbstractFactory<T> {
*/ */
abstract getInjectedProperties(): Collection<PropertyDependency> 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. * Get a human-readable name of the token this factory produces.
* This is meant for debugging output only. * 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, Instantiable,
PropertyDependency, PropertyDependency,
} from '../types' } from '../types'
import {Collection, logIfDebugging} from '../../util' import {Collection} from '../../util'
import 'reflect-metadata' import 'reflect-metadata'
import {getPropertyInjectionMetadata} from '../decorator/getPropertyInjectionMetadata'
/** /**
* Standard static-class factory. The token of this factory is a reference to a * 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> { getInjectedProperties(): Collection<PropertyDependency> {
const meta = new Collection<PropertyDependency>() return this.getInjectedPropertiesForPrototypeChain(this.token)
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
} }
} }

View File

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

View File

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

View File

@ -1,87 +1,23 @@
import { import {Instantiable} from '../../di'
AbstractFactory, import {Maybe} from '../../util'
Container,
DependencyRequirement,
PropertyDependency,
isInstantiable,
DEPENDENCY_KEYS_METADATA_KEY,
Instantiable, getPropertyInjectionMetadata,
} from '../../di'
import {Collection, ErrorWithContext} from '../../util'
import {MemorySession} from './MemorySession' import {MemorySession} from './MemorySession'
import {Session} from './Session' import {Session} from './Session'
import {Logging} from '../../service/Logging' import {ConfiguredSingletonFactory} from '../../di/factory/ConfiguredSingletonFactory'
import {Config} from '../../service/Config'
/** export class SessionFactory extends ConfiguredSingletonFactory<Session> {
* A dependency injection factory that matches the abstract Session class protected getConfigKey(): string {
* and produces an instance of the configured session driver implementation. return 'server.session.driver'
*/
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)
} }
produce(): Session { protected getDefaultImplementation(): Instantiable<Session> {
return new (this.getSessionClass())() return MemorySession
} }
match(something: unknown): boolean { protected getAbstractImplementation(): any {
return something === Session return Session
} }
getDependencyKeys(): Collection<DependencyRequirement> { protected getDefaultImplementationWarning(): Maybe<string> {
const meta = Reflect.getMetadata(DEPENDENCY_KEYS_METADATA_KEY, this.getSessionClass()) return 'You are using the default memory-based session driver. It is recommended you configure a persistent session driver instead.'
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
} }
} }

View File

@ -1,86 +1,23 @@
import { import {Instantiable, FactoryProducer} from '../../di'
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 {Migrator} from './Migrator' import {Migrator} from './Migrator'
import {DatabaseMigrator} from './DatabaseMigrator' import {DatabaseMigrator} from './DatabaseMigrator'
import {ConfiguredSingletonFactory} from '../../di/factory/ConfiguredSingletonFactory'
/** /**
* A dependency injection factory that matches the abstract Migrator class * A dependency injection factory that matches the abstract Migrator class
* and produces an instance of the configured session driver implementation. * and produces an instance of the configured session driver implementation.
*/ */
@Injectable()
@FactoryProducer() @FactoryProducer()
export class MigratorFactory extends AbstractFactory<Migrator> { export class MigratorFactory extends ConfiguredSingletonFactory<Migrator> {
@Inject() protected getConfigKey(): string {
protected readonly logging!: Logging return 'database.migrations.driver'
@Inject()
protected readonly config!: Config
constructor() {
super({})
} }
produce(): Migrator { protected getDefaultImplementation(): Instantiable<Migrator> {
return new (this.getMigratorClass())() return DatabaseMigrator
} }
match(something: unknown): boolean { protected getAbstractImplementation(): any {
return something === Migrator return 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
} }
} }

View File

@ -3,6 +3,7 @@ import {collect, Collection, ErrorWithContext, Maybe} from '../../util'
import {HasSubtree} from './relation/HasSubtree' import {HasSubtree} from './relation/HasSubtree'
import {Related} from './relation/decorators' import {Related} from './relation/decorators'
import {HasTreeParent} from './relation/HasTreeParent' import {HasTreeParent} from './relation/HasTreeParent'
import {ModelBuilder} from './ModelBuilder'
/** /**
* Model implementation with helpers for querying tree-structured data. * 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' 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 * @override to eager-load the subtree by default
* @protected * @protected
@ -52,6 +59,13 @@ export abstract class TreeModel<T extends TreeModel<T>> extends Model<T> {
protected removedChildren: Collection<TreeModel<T>> = collect() 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. */ /** Get the left tree number for this model. */
public leftTreeNum(): Maybe<number> { public leftTreeNum(): Maybe<number> {
const ctor = this.constructor as typeof TreeModel const ctor = this.constructor as typeof TreeModel

View File

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

View File

@ -1,5 +1,7 @@
import {DebuggingTraceIsNotAnError, Logger, LoggingLevel, LogMessage} from '../util' import {Singleton} from '../di/decorator/injection'
import {Singleton} from '../di' 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 * A singleton service that manages loggers registered in the application, and

View File

@ -1,86 +1,28 @@
import { import {FactoryProducer, Instantiable} from '../../../di'
AbstractFactory, import {Maybe} from '../../../util'
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 {Queue} from './Queue' import {Queue} from './Queue'
import {SyncQueue} from './SyncQueue' import {SyncQueue} from './SyncQueue'
import {ConfiguredSingletonFactory} from '../../../di/factory/ConfiguredSingletonFactory'
/** /**
* Dependency container factory that matches the abstract Queue token, but * 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. * produces an instance of whatever Queue driver is configured in the `server.queue.driver` config.
*/ */
@FactoryProducer() @FactoryProducer()
export class QueueFactory extends AbstractFactory<Queue> { export class QueueFactory extends ConfiguredSingletonFactory<Queue> {
/** true if we have printed the synchronous queue driver warning once. */ protected getConfigKey(): string {
private static loggedSyncQueueWarningOnce = false return 'server.queue.driver'
private di(): [Logging, Config] {
return [
Container.getContainer().make(Logging),
Container.getContainer().make(Config),
]
} }
produce(): Queue { protected getDefaultImplementation(): Instantiable<Queue> {
return new (this.getQueueClass())() return SyncQueue
} }
match(something: unknown): boolean { protected getAbstractImplementation(): any {
return something === Queue return Queue
} }
getDependencyKeys(): Collection<DependencyRequirement> { protected getDefaultImplementationWarning(): Maybe<string> {
const meta = Reflect.getMetadata(DEPENDENCY_KEYS_METADATA_KEY, this.getQueueClass()) return 'You are using the default synchronous queue driver. It is recommended you configure a background queue driver instead.'
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
} }
} }

View File

@ -1,86 +1,27 @@
import { import {Instantiable, FactoryProducer} from '../../di'
AbstractFactory, import {InMemCache, Maybe} from '../../util'
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 {Cache} 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 * 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. * produces an instance of whatever Cache driver is configured in the `server.cache.driver` config.
*/ */
export class CacheFactory extends AbstractFactory<Cache> { @FactoryProducer()
protected readonly logging: Logging export class CacheFactory extends ConfiguredSingletonFactory<Cache> {
protected getConfigKey(): string {
protected readonly config: Config return 'server.cache.driver'
/** 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)
} }
produce(): Cache { protected getDefaultImplementation(): Instantiable<Cache> {
return new (this.getCacheClass())() return InMemCache
} }
match(something: unknown): boolean { protected getAbstractImplementation(): any {
return something === Cache return Cache
} }
getDependencyKeys(): Collection<DependencyRequirement> { protected getDefaultImplementationWarning(): Maybe<string> {
const meta = Reflect.getMetadata(DEPENDENCY_KEYS_METADATA_KEY, this.getCacheClass()) return 'You are using the default memory-based cache driver. It is recommended you configure a persistent cache driver instead.'
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
} }
} }

View File

@ -1,5 +1,5 @@
import {Collection} from './Collection' import {Collection} from './Collection'
import {InjectionAware} from '../../di' import {InjectionAware} from '../../di/InjectionAware'
export type MaybeIterationItem<T> = { done: boolean, value?: T } export type MaybeIterationItem<T> = { done: boolean, value?: T }
export type ChunkCallback<T> = (items: Collection<T>) => any 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' import {unsafeESMImport} from './unsafe'
export const fetch = (url: RequestInfo, init?: RequestInit): Promise<Response> => unsafeESMImport('node-fetch').then(({default: nodeFetch}) => nodeFetch(url, init)) 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/Cache'
export * from './cache/InMemCache' 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 { import {Instantiable, FactoryProducer} from '../di'
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 {ViewEngine} from './ViewEngine' import {ViewEngine} from './ViewEngine'
import {PugViewEngine} from './PugViewEngine' import {PugViewEngine} from './PugViewEngine'
import {ConfiguredSingletonFactory} from '../di/factory/ConfiguredSingletonFactory'
/** /**
* Dependency factory whose token matches the abstract ViewEngine class, but produces * Dependency factory whose token matches the abstract ViewEngine class, but produces
* a particular ViewEngine implementation based on the configuration. * a particular ViewEngine implementation based on the configuration.
*/ */
export class ViewEngineFactory extends AbstractFactory<ViewEngine> { @FactoryProducer()
protected readonly logging: Logging export class ViewEngineFactory extends ConfiguredSingletonFactory<ViewEngine> {
protected getConfigKey(): string {
protected readonly config: Config return 'server.view_engine.driver'
constructor() {
super({})
this.logging = Container.getContainer().make<Logging>(Logging)
this.config = Container.getContainer().make<Config>(Config)
} }
produce(): ViewEngine { protected getDefaultImplementation(): Instantiable<ViewEngine> {
return new (this.getViewEngineClass())() return PugViewEngine
} }
match(something: unknown): boolean { protected getAbstractImplementation(): any {
return something === ViewEngine return 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
} }
} }