Initial commit

This commit is contained in:
garrettmills
2020-06-15 20:35:30 -05:00
commit 6c4696227b
19 changed files with 3225 additions and 0 deletions

5
di/module.ts Normal file
View File

@@ -0,0 +1,5 @@
export { container, make } from './src/global.ts'
export { Inject, Injectable } from './src/decorator/Injection.ts'
export { Service } from './src/decorator/Service.ts'
export { default as AbstractFactory } from './src/factory/AbstractFactory.ts'
export { Container, InvalidDependencyKeyError, DuplicateFactoryKeyError, MaybeDependency, MaybeFactory } from './src/Container.ts'

152
di/src/Container.ts Executable file
View 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
View 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
View 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 }

View 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
View 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>()
}
}

View 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()
}
}

View 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
}
}

View 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
View 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 }

View 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 }

View File

@@ -0,0 +1,9 @@
import { DependencyKey } from './DependencyKey.ts'
interface DependencyRequirement {
param_index: number,
key: DependencyKey,
overridden: boolean,
}
export { DependencyRequirement }

View 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 }