let Enumerable: any = require('linq'); import {MetaUtils} from '../core/metadata/utils'; import {Decorators} from '../core/constants'; import {DecoratorType} from '../core/enums/decorator-type'; import {MetaData} from '../core/metadata/metadata'; import {ClassType, NodeModuleType} from '../core/utils/types'; import {IInjectParams} from './decorators/interfaces/inject-params'; import * as Utils from '../core/utils'; let _extSources: Array<(any) => Object> = []; let _depInstMap = new Map(); let _serviceMap = new Map, Object>(); export function extSources(extSources?: Array<(any) => Object>) { if (extSources !== undefined) { _extSources = extSources; } return _extSources; } export function serviceMap(serviceMap?: Map, Object>) { if (serviceMap !== undefined) { _serviceMap = serviceMap; } return _serviceMap; } //var services: Array<{ fn: Function, params: {} }> = []; //var serviceInstances: Array<{fn: Function, inst: any}> = []; function generateToken(fn: Function) { return fn.toString(); } class DependencyNode { parents: Map, boolean>; current: any; children: Map, boolean>; constructor(data) { this.parents = new Map, boolean>(); this.children = new Map, boolean>();; this.current = data; } } let dependencyRoot: Map, DependencyNode> = new Map(); let dependencyOrder: Map, number>; class DI { private stack: Array<{ parent: any; children: Array }> = []; public resolveDependencies(cls: ClassType): T { if (_serviceMap.has(cls)) { return this.resolveServiceDependency(cls, _serviceMap.get(cls)); } return this.getFromExtSources(cls); } private getFromExtSources(cls: ClassType): T { let inst; _extSources.forEach(func => { if (!inst) { inst = func.apply(this, [cls]); } }); return inst; } private getDependencyOrderString(cls?: ClassType) { let arr = []; dependencyOrder.forEach((value: number, key: ClassType) => { arr.push(key.name); }); cls && arr.push(cls.name); return arr.join('=>'); } private resolveServiceDependency(cls: ClassType, service): T { let inst; if (!service.singleton) { inst = this.instantiateClass(cls); } inst = this.getInstance(cls); if (inst) { return inst; } else { inst = this.instantiateClass(cls); } return inst; } private getInstance(cls: ClassType): T { return _depInstMap.get(cls); } private getDependencies(cls: ClassType): Array { return Enumerable.from(MetaUtils.getMetaData(cls.prototype, Decorators.INJECT)); } private publicDeps(deps: Array): Array { return Enumerable.from(deps) .where((x: MetaData) => x.decoratorType === DecoratorType.PROPERTY) .toArray(); } private constructorDeps(deps: Array): Array { return Enumerable.from(deps) .where((x: MetaData) => x.decoratorType === DecoratorType.PARAM) .toArray(); } private resolveConstructorDeps(deps): Array { let resolvedDeps = []; Enumerable.from(deps) .orderBy((x: MetaData) => x.paramIndex) .forEach((x: MetaData) => { let type = (x.params).type; if ((type).default) { type = (type).default; } if (!type) { console.log(x); throw 'no type found'; } resolvedDeps.push(this.resolveDependencies(type)); }); return resolvedDeps; } private getType(params: any): ClassType { let type = (params).type; if ((type).__esModule) { type = (type).default; } return type; } private resolvePropDeps(inst: any, propDeps: Array) { Enumerable.from(propDeps) .forEach((x: MetaData) => { inst[x.propertyKey] = this.resolveDependencies((x.params).type); }); } //private instantiateClass(fn: T): T {} private getCycle(parent: ClassType, child: ClassType) { let arr = Enumerable.from(this.stack) .select(x => x.parent.name) .toArray(); arr.push(parent.name, child.name); return arr.join(" => "); } // todo: cyclic dependency private instantiateClass(cls: ClassType): T { console.log('get dependencies: ' + cls.name); let allDependencies = this.getDependencies(cls); let deps = this.constructorDeps(allDependencies); let str = Enumerable.from(deps) .select((x: MetaData) => this.getType(x.params) ? this.getType(x.params).name : ' @ ') .toArray() .join(","); console.log(" " + str); if (!dependencyRoot.get(cls)) { dependencyRoot.set(cls, new DependencyNode(cls)); } Enumerable.from(deps) .forEach((x: MetaData) => { let type = this.getType(x.params); if (!dependencyRoot.get(type)) { dependencyRoot.set(type, new DependencyNode(type)); } dependencyRoot.get(cls).children.set(type, true); if (dependencyRoot.get(type).children.get(cls)) { let cycleDepStr = this.getCycle(cls, type); throw Error('Cycle found: ' + cycleDepStr); } dependencyRoot.get(type).parents.set(cls, true); }); this.stack.push({ parent: cls, children: deps }); let injectedProps = this.publicDeps(allDependencies); let resolvedDeps = this.resolveConstructorDeps(deps); let inst = Utils.activator(cls, resolvedDeps); this.resolvePropDeps(inst, injectedProps); _depInstMap.set(cls, inst); this.stack.pop(); return inst; } } export interface IContainer { addService(cls: ClassType, params: any); resolve(cls: ClassType): T; resolve(module: NodeModuleType): T; addSource(source: (any) => Object); } export class Container { /** * Registers all the services, i.e. classes having @service decorator, in the DI container. * DI container will inject only those services that are registered with this method. * @param {class} cls Typescript class having @service decorator * @param {Object} params Decorator params */ public static addService(cls: ClassType, params: any) { _serviceMap.set(cls, params); } /** * * @param cls */ public static resolve(key: ClassType|NodeModuleType): T { let srvKey: ClassType = key; if ((>key).__esModule) { srvKey = (>key).default; } dependencyOrder = dependencyOrder || new Map, number>(); let di = new DI(); return di.resolveDependencies(srvKey); } public static addSource(source: (any) => Object) { _extSources.push(source); //source.forEach((x, y) => { // if (_depInstMap.has(y)) { // throw 'key already present in the map'; // } // _depInstMap.set(y, x); //}); } }