Initial commit
This commit is contained in:
152
di/src/Container.ts
Executable file
152
di/src/Container.ts
Executable file
@@ -0,0 +1,152 @@
|
||||
import AbstractFactory from './factory/AbstractFactory.ts'
|
||||
import Factory from './factory/Factory.ts'
|
||||
import SingletonFactory from './factory/SingletonFactory.ts'
|
||||
import FunctionFactory from './factory/FunctionFactory.ts'
|
||||
import NamedFactory from './factory/NamedFactory.ts'
|
||||
import { DependencyKey } from './type/DependencyKey.ts'
|
||||
import { collect, Collection } from '../../lib/src/collection/Collection.ts'
|
||||
import Instantiable, {isInstantiable} from './type/Instantiable.ts'
|
||||
|
||||
type MaybeFactory = AbstractFactory | undefined
|
||||
type MaybeDependency = any | undefined
|
||||
type ResolvedDependency = { param_index: number, key: DependencyKey, resolved: any }
|
||||
|
||||
interface InstanceRef {
|
||||
key: DependencyKey,
|
||||
value: any,
|
||||
}
|
||||
|
||||
class DuplicateFactoryKeyError extends Error {
|
||||
constructor(key: DependencyKey) {
|
||||
super(`A factory definition already exists with the key for ${key}.`)
|
||||
}
|
||||
}
|
||||
|
||||
class InvalidDependencyKeyError extends Error {
|
||||
constructor(key: DependencyKey) {
|
||||
super(`No such dependency is registered with this container: ${key}`)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Container {
|
||||
private factories: Collection<AbstractFactory> = new Collection<AbstractFactory>()
|
||||
private instances: Collection<InstanceRef> = new Collection<InstanceRef>()
|
||||
|
||||
register(dependency: Instantiable<any>) {
|
||||
if ( this.resolve(dependency) )
|
||||
throw new DuplicateFactoryKeyError(dependency)
|
||||
|
||||
const factory = new Factory(dependency)
|
||||
this.factories.push(factory)
|
||||
}
|
||||
|
||||
register_producer(name: string, producer: () => any) {
|
||||
if ( this.resolve(name) )
|
||||
throw new DuplicateFactoryKeyError(name)
|
||||
|
||||
const factory = new FunctionFactory(name, producer)
|
||||
this.factories.push(factory)
|
||||
}
|
||||
|
||||
register_named(name: string, dependency: Instantiable<any>) {
|
||||
if ( this.resolve(name) )
|
||||
throw new DuplicateFactoryKeyError(name)
|
||||
|
||||
const factory = new NamedFactory(name, dependency)
|
||||
this.factories.push(factory)
|
||||
}
|
||||
|
||||
register_singleton(key: string, value: any) {
|
||||
if ( this.resolve(key) )
|
||||
throw new DuplicateFactoryKeyError(key)
|
||||
|
||||
this.factories.push(new SingletonFactory(value, key))
|
||||
}
|
||||
|
||||
register_factory(factory: AbstractFactory) {
|
||||
if ( !this.factories.includes(factory) )
|
||||
this.factories.push(factory)
|
||||
}
|
||||
|
||||
has_instance(key: DependencyKey): boolean {
|
||||
return this.instances.where('key', '=', key).isNotEmpty()
|
||||
}
|
||||
|
||||
has_key(key: DependencyKey): boolean {
|
||||
return !!this.resolve(key)
|
||||
}
|
||||
|
||||
get_existing_instance(key: DependencyKey): MaybeDependency {
|
||||
const instances = this.instances.where('key', '=', key)
|
||||
if ( instances.isNotEmpty() ) return instances.first()
|
||||
}
|
||||
|
||||
resolve(key: DependencyKey): MaybeFactory {
|
||||
const factory = this.factories.firstWhere(item => item.match(key))
|
||||
if ( factory ) return factory
|
||||
}
|
||||
|
||||
resolve_and_create(key: DependencyKey, ...parameters: any[]): any {
|
||||
// If we've already instantiated this, just return that
|
||||
const instance = this.get_existing_instance(key)
|
||||
if ( typeof instance !== 'undefined' ) return instance.value
|
||||
|
||||
// Otherwise, attempt to create it
|
||||
const factory = this.resolve(key)
|
||||
if ( !factory )
|
||||
throw new InvalidDependencyKeyError(key)
|
||||
|
||||
// Produce and store a new instance
|
||||
const new_instance = this.produce_factory(factory, parameters)
|
||||
this.instances.push({
|
||||
key,
|
||||
value: new_instance,
|
||||
})
|
||||
|
||||
return new_instance
|
||||
}
|
||||
|
||||
protected produce_factory(factory: AbstractFactory, parameters: any[]) {
|
||||
// Create the dependencies for the factory
|
||||
const keys = factory.get_dependency_keys().filter(req => this.has_key(req.key))
|
||||
const dependencies = keys.map<ResolvedDependency>(req => {
|
||||
return {
|
||||
param_index: req.param_index,
|
||||
key: req.key,
|
||||
resolved: this.resolve_and_create(req.key),
|
||||
}
|
||||
}).sortBy('param_index')
|
||||
|
||||
// Build the arguments for the factory, using dependencies in the
|
||||
// correct param_index positions, or parameters of we don't have
|
||||
// the dependency.
|
||||
const construction_args = []
|
||||
let params = collect(parameters).reverse()
|
||||
for ( let i = 0; i <= dependencies.max('param_index'); i++ ) {
|
||||
const dep = dependencies.firstWhere('param_index', '=', i)
|
||||
if ( dep ) construction_args.push(dep.resolved)
|
||||
else construction_args.push(params.pop())
|
||||
}
|
||||
|
||||
// Produce a new instance
|
||||
return factory.produce(construction_args, params.reverse().all())
|
||||
}
|
||||
|
||||
make<T>(target: Instantiable<T>|DependencyKey, ...parameters: any[]): T {
|
||||
if ( isInstantiable(target) )
|
||||
return this.produce_factory(new Factory(target), parameters)
|
||||
else
|
||||
return this.resolve_and_create(target, ...parameters)
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
Container,
|
||||
InvalidDependencyKeyError,
|
||||
DuplicateFactoryKeyError,
|
||||
MaybeDependency,
|
||||
MaybeFactory,
|
||||
ResolvedDependency,
|
||||
InstanceRef,
|
||||
}
|
||||
73
di/src/decorator/Injection.ts
Executable file
73
di/src/decorator/Injection.ts
Executable file
@@ -0,0 +1,73 @@
|
||||
import {Reflect} from '../../../lib/src/external/reflect.ts'
|
||||
import {DEPENDENCY_KEYS_METADATA_KEY, DependencyKey} from '../type/DependencyKey.ts'
|
||||
import {collect, Collection} from '../../../lib/src/collection/Collection.ts'
|
||||
import {DependencyRequirement} from '../type/DependencyRequirement.ts'
|
||||
|
||||
const initDependencyMetadata = (target: Object): Collection<DependencyRequirement> => {
|
||||
const param_types = Reflect.getMetadata('design:paramtypes', target)
|
||||
return collect<DependencyKey>(param_types).map<DependencyRequirement>((type, index) => {
|
||||
return {
|
||||
param_index: index,
|
||||
key: type,
|
||||
overridden: false,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const Injectable = (): ClassDecorator => {
|
||||
return (target) => {
|
||||
const meta = initDependencyMetadata(target)
|
||||
const existing = Reflect.getMetadata(DEPENDENCY_KEYS_METADATA_KEY, target)
|
||||
const new_meta = new Collection<DependencyRequirement>()
|
||||
|
||||
if ( existing ) {
|
||||
const max_new = meta.max('param_index')
|
||||
const max_existing = existing.max('param_index')
|
||||
for ( let i = 0; i <= Math.max(max_new, max_existing); i++ ) {
|
||||
const existing_dr = existing.firstWhere('param_index', '=', i)
|
||||
const new_dr = meta.firstWhere('param_index', '=', i)
|
||||
|
||||
if ( existing_dr && !new_dr ) {
|
||||
new_meta.push(existing_dr)
|
||||
} else if ( new_dr && !existing_dr ) {
|
||||
new_meta.push(new_dr)
|
||||
} else if ( new_dr && existing_dr ) {
|
||||
if ( existing_dr.overridden ) {
|
||||
new_meta.push(existing_dr)
|
||||
} else {
|
||||
new_meta.push(new_dr)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
new_meta.concat(meta)
|
||||
}
|
||||
|
||||
Reflect.defineMetadata(DEPENDENCY_KEYS_METADATA_KEY, new_meta, target)
|
||||
}
|
||||
}
|
||||
|
||||
const Inject = (key: DependencyKey): ParameterDecorator => {
|
||||
return (target, property, param_index) => {
|
||||
if ( !Reflect.getMetadata(DEPENDENCY_KEYS_METADATA_KEY, target) ) {
|
||||
Reflect.defineMetadata(DEPENDENCY_KEYS_METADATA_KEY, initDependencyMetadata(target), target)
|
||||
}
|
||||
|
||||
const meta = Reflect.getMetadata(DEPENDENCY_KEYS_METADATA_KEY, target)
|
||||
const req = meta.firstWhere('param_index', '=', param_index)
|
||||
if ( req ) {
|
||||
req.key = key
|
||||
req.overridden = true
|
||||
} else {
|
||||
meta.push({
|
||||
param_index,
|
||||
key,
|
||||
overridden: true
|
||||
})
|
||||
}
|
||||
|
||||
Reflect.defineMetadata(DEPENDENCY_KEYS_METADATA_KEY, meta, target)
|
||||
}
|
||||
}
|
||||
|
||||
export { Inject, Injectable, initDependencyMetadata }
|
||||
13
di/src/decorator/Service.ts
Executable file
13
di/src/decorator/Service.ts
Executable file
@@ -0,0 +1,13 @@
|
||||
import { container } from '../global.ts'
|
||||
import { isInstantiable } from '../type/Instantiable.ts'
|
||||
|
||||
const Service = (name?: string): ClassDecorator => {
|
||||
return (target) => {
|
||||
if ( isInstantiable(target) ) {
|
||||
if ( name ) container.register_named(name, target)
|
||||
else container.register(target)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { Service }
|
||||
12
di/src/factory/AbstractFactory.ts
Normal file
12
di/src/factory/AbstractFactory.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import {Collection} from '../../../lib/src/collection/Collection.ts'
|
||||
import {DependencyRequirement} from '../type/DependencyRequirement.ts'
|
||||
|
||||
export default abstract class AbstractFactory {
|
||||
protected constructor(
|
||||
protected token: any
|
||||
) {}
|
||||
|
||||
abstract produce(dependencies: any[], parameters: any[]): any
|
||||
abstract match(something: any): boolean
|
||||
abstract get_dependency_keys(): Collection<DependencyRequirement>
|
||||
}
|
||||
28
di/src/factory/Factory.ts
Executable file
28
di/src/factory/Factory.ts
Executable file
@@ -0,0 +1,28 @@
|
||||
import Instantiable from '../type/Instantiable.ts'
|
||||
import { DEPENDENCY_KEYS_METADATA_KEY } from '../type/DependencyKey.ts'
|
||||
import { Reflect } from '../../../lib/src/external/reflect.ts'
|
||||
import { Collection } from '../../../lib/src/collection/Collection.ts'
|
||||
import { DependencyRequirement } from '../type/DependencyRequirement.ts'
|
||||
import AbstractFactory from './AbstractFactory.ts'
|
||||
|
||||
export default class Factory extends AbstractFactory {
|
||||
constructor(
|
||||
protected token: Instantiable<any>
|
||||
) {
|
||||
super(token)
|
||||
}
|
||||
|
||||
produce(dependencies: any[], parameters: any[]) {
|
||||
return new this.token(...dependencies, ...parameters)
|
||||
}
|
||||
|
||||
match(something: any) {
|
||||
return something === this.token || (something.toString && String(something) === this.token.name)
|
||||
}
|
||||
|
||||
get_dependency_keys(): Collection<DependencyRequirement> {
|
||||
const meta = Reflect.getMetadata(DEPENDENCY_KEYS_METADATA_KEY, this.token)
|
||||
if ( meta ) return meta
|
||||
return new Collection<DependencyRequirement>()
|
||||
}
|
||||
}
|
||||
24
di/src/factory/FunctionFactory.ts
Normal file
24
di/src/factory/FunctionFactory.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import AbstractFactory from './AbstractFactory.ts'
|
||||
import {DependencyRequirement} from '../type/DependencyRequirement.ts'
|
||||
import {Collection} from '../../../lib/src/collection/Collection.ts'
|
||||
|
||||
export default class FunctionFactory extends AbstractFactory {
|
||||
constructor(
|
||||
protected name: string,
|
||||
protected token: () => any,
|
||||
) {
|
||||
super(token)
|
||||
}
|
||||
|
||||
get_dependency_keys(): Collection<DependencyRequirement> {
|
||||
return new Collection<DependencyRequirement>()
|
||||
}
|
||||
|
||||
match(something: any) {
|
||||
return something === this.name
|
||||
}
|
||||
|
||||
produce(dependencies: any[], parameters: any[]): any {
|
||||
return this.token()
|
||||
}
|
||||
}
|
||||
15
di/src/factory/NamedFactory.ts
Normal file
15
di/src/factory/NamedFactory.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import Factory from './Factory.ts'
|
||||
import Instantiable from "../type/Instantiable.ts";
|
||||
|
||||
export default class NamedFactory extends Factory {
|
||||
constructor(
|
||||
protected name: string,
|
||||
protected token: Instantiable<any>,
|
||||
) {
|
||||
super(token)
|
||||
}
|
||||
|
||||
match(something: any) {
|
||||
return something === this.name
|
||||
}
|
||||
}
|
||||
24
di/src/factory/SingletonFactory.ts
Normal file
24
di/src/factory/SingletonFactory.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import Factory from './Factory.ts'
|
||||
import {Collection} from '../../../lib/src/collection/Collection.ts'
|
||||
import {DependencyRequirement} from '../type/DependencyRequirement.ts'
|
||||
|
||||
export default class SingletonFactory extends Factory {
|
||||
constructor(
|
||||
protected token: FunctionConstructor,
|
||||
protected key: string,
|
||||
) {
|
||||
super(token)
|
||||
}
|
||||
|
||||
produce(dependencies: any[], parameters: any[]) {
|
||||
return this.token
|
||||
}
|
||||
|
||||
match(something: any) {
|
||||
return something === this.key
|
||||
}
|
||||
|
||||
get_dependency_keys(): Collection<DependencyRequirement> {
|
||||
return new Collection<DependencyRequirement>()
|
||||
}
|
||||
}
|
||||
7
di/src/global.ts
Normal file
7
di/src/global.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { Container } from './Container.ts'
|
||||
import Instantiable from './type/Instantiable.ts'
|
||||
import { DependencyKey } from './type/DependencyKey.ts'
|
||||
|
||||
const container = new Container()
|
||||
const make = <T>(target: Instantiable<T>|DependencyKey, ...parameters: any[]) => container.make(target, ...parameters)
|
||||
export { container, make }
|
||||
4
di/src/type/DependencyKey.ts
Normal file
4
di/src/type/DependencyKey.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import Instantiable from './Instantiable.ts'
|
||||
const DEPENDENCY_KEYS_METADATA_KEY = 'daton:di:dependencyKeys.ts'
|
||||
type DependencyKey = Instantiable<any> | string
|
||||
export { DependencyKey, DEPENDENCY_KEYS_METADATA_KEY }
|
||||
9
di/src/type/DependencyRequirement.ts
Normal file
9
di/src/type/DependencyRequirement.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { DependencyKey } from './DependencyKey.ts'
|
||||
|
||||
interface DependencyRequirement {
|
||||
param_index: number,
|
||||
key: DependencyKey,
|
||||
overridden: boolean,
|
||||
}
|
||||
|
||||
export { DependencyRequirement }
|
||||
9
di/src/type/Instantiable.ts
Normal file
9
di/src/type/Instantiable.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export default interface Instantiable<T> {
|
||||
new(...args: any[]): T
|
||||
}
|
||||
|
||||
const isInstantiable = (what: any): what is Instantiable<any> => {
|
||||
return (typeof what === 'object' || typeof what === 'function') && 'constructor' in what && typeof what.constructor === 'function'
|
||||
}
|
||||
|
||||
export { isInstantiable }
|
||||
Reference in New Issue
Block a user