UNPKG

7.84 kBPlain TextView Raw
1let Enumerable: any = require('linq');
2import {MetaUtils} from '../core/metadata/utils';
3import {Decorators} from '../core/constants';
4import {DecoratorType} from '../core/enums/decorator-type';
5import {MetaData} from '../core/metadata/metadata';
6import {ClassType, NodeModuleType} from '../core/utils/types';
7import {IInjectParams} from './decorators/interfaces/inject-params';
8
9import * as Utils from '../core/utils';
10
11let _extSources: Array<(any) => Object> = [];
12let _depInstMap = new Map();
13let _serviceMap = new Map<ClassType<any>, Object>();
14
15export function extSources(extSources?: Array<(any) => Object>) {
16 if (extSources !== undefined) {
17 _extSources = extSources;
18 }
19 return _extSources;
20}
21
22export function serviceMap(serviceMap?: Map<ClassType<any>, Object>) {
23 if (serviceMap !== undefined) {
24 _serviceMap = serviceMap;
25 }
26 return _serviceMap;
27}
28//var services: Array<{ fn: Function, params: {} }> = [];
29//var serviceInstances: Array<{fn: Function, inst: any}> = [];
30
31function generateToken(fn: Function) {
32 return fn.toString();
33}
34
35class DependencyNode {
36 parents: Map<ClassType<any>, boolean>;
37 current: any;
38 children: Map<ClassType<any>, boolean>;
39 constructor(data) {
40 this.parents = new Map<ClassType<any>, boolean>();
41 this.children = new Map<ClassType<any>, boolean>();;
42 this.current = data;
43 }
44}
45
46let dependencyRoot: Map<ClassType<any>, DependencyNode> = new Map();
47
48let dependencyOrder: Map<ClassType<any>, number>;
49
50class DI {
51 private stack: Array<{ parent: any; children: Array<any> }> = [];
52
53 public resolveDependencies<T>(cls: ClassType<T>): T {
54 if (_serviceMap.has(cls)) {
55 return this.resolveServiceDependency<T>(cls, _serviceMap.get(cls));
56 }
57 return this.getFromExtSources<T>(cls);
58 }
59
60 private getFromExtSources<T>(cls: ClassType<any>): T {
61 let inst;
62 _extSources.forEach(func => {
63 if (!inst) {
64 inst = func.apply(this, [cls]);
65 }
66 });
67 return inst;
68 }
69
70 private getDependencyOrderString(cls?: ClassType<any>) {
71 let arr = [];
72 dependencyOrder.forEach((value: number, key: ClassType<any>) => {
73 arr.push(key.name);
74 });
75 cls && arr.push(cls.name);
76 return arr.join('=>');
77 }
78
79 private resolveServiceDependency<T>(cls: ClassType<T>, service): T {
80 let inst;
81 if (!service.singleton) {
82 inst = this.instantiateClass<T>(cls);
83 }
84 inst = this.getInstance(cls);
85 if (inst) {
86 return inst;
87 } else {
88 inst = this.instantiateClass<T>(cls);
89 }
90 return inst;
91 }
92
93 private getInstance<T>(cls: ClassType<T>): T {
94 return _depInstMap.get(cls);
95 }
96
97 private getDependencies(cls: ClassType<any>): Array<MetaData> {
98 return Enumerable.from(MetaUtils.getMetaData(cls.prototype, Decorators.INJECT));
99 }
100
101 private publicDeps(deps: Array<MetaData>): Array<MetaData> {
102 return Enumerable.from(deps)
103 .where((x: MetaData) => x.decoratorType === DecoratorType.PROPERTY)
104 .toArray();
105 }
106
107 private constructorDeps(deps: Array<MetaData>): Array<MetaData> {
108 return Enumerable.from(deps)
109 .where((x: MetaData) => x.decoratorType === DecoratorType.PARAM)
110 .toArray();
111 }
112
113 private resolveConstructorDeps(deps): Array<any> {
114 let resolvedDeps = [];
115 Enumerable.from(deps)
116 .orderBy((x: MetaData) => x.paramIndex)
117 .forEach((x: MetaData) => {
118 let type = (<IInjectParams>x.params).type;
119
120 if ((<any>type).default) {
121 type = (<any>type).default;
122 }
123 if (!type) {
124 console.log(x);
125 throw 'no type found';
126 }
127 resolvedDeps.push(this.resolveDependencies(type));
128 });
129 return resolvedDeps;
130 }
131
132 private getType(params: any): ClassType<any> {
133 let type = (<IInjectParams>params).type;
134 if ((<any>type).__esModule) {
135 type = (<any>type).default;
136 }
137 return type;
138 }
139
140 private resolvePropDeps(inst: any, propDeps: Array<any>) {
141 Enumerable.from(propDeps)
142 .forEach((x: MetaData) => {
143 inst[x.propertyKey] = this.resolveDependencies((<IInjectParams>x.params).type);
144 });
145 }
146 //private instantiateClass<T extends Function>(fn: T): T {}
147
148 private getCycle<T>(parent: ClassType<T>, child: ClassType<T>) {
149 let arr = Enumerable.from(this.stack)
150 .select(x => x.parent.name)
151 .toArray();
152 arr.push(parent.name, child.name);
153 return arr.join(" => ");
154 }
155
156 // todo: cyclic dependency
157 private instantiateClass<T>(cls: ClassType<T>): T {
158 console.log('get dependencies: ' + cls.name);
159
160 let allDependencies = this.getDependencies(cls);
161 let deps = this.constructorDeps(allDependencies);
162 let str = Enumerable.from(deps)
163 .select((x: MetaData) => this.getType(x.params) ? this.getType(x.params).name : ' @ ')
164 .toArray()
165 .join(",");
166 console.log(" " + str);
167
168 if (!dependencyRoot.get(cls)) {
169 dependencyRoot.set(cls, new DependencyNode(cls));
170 }
171
172 Enumerable.from(deps)
173 .forEach((x: MetaData) => {
174
175 let type = this.getType(x.params);
176 if (!dependencyRoot.get(type)) {
177 dependencyRoot.set(type, new DependencyNode(type));
178 }
179 dependencyRoot.get(cls).children.set(type, true);
180
181 if (dependencyRoot.get(type).children.get(cls)) {
182 let cycleDepStr = this.getCycle(cls, type);
183 throw Error('Cycle found: ' + cycleDepStr);
184 }
185
186 dependencyRoot.get(type).parents.set(cls, true);
187 });
188
189 this.stack.push({ parent: cls, children: deps });
190 let injectedProps = this.publicDeps(allDependencies);
191
192 let resolvedDeps = this.resolveConstructorDeps(deps);
193 let inst = Utils.activator<T>(cls, resolvedDeps);
194 this.resolvePropDeps(inst, injectedProps);
195
196 _depInstMap.set(cls, inst);
197
198 this.stack.pop();
199 return inst;
200 }
201}
202
203export interface IContainer {
204 addService<T>(cls: ClassType<T>, params: any);
205 resolve<T>(cls: ClassType<T>): T;
206 resolve<T>(module: NodeModuleType<T>): T;
207 addSource(source: (any) => Object);
208}
209
210export class Container {
211 /**
212 * Registers all the services, i.e. classes having @service decorator, in the DI container.
213 * DI container will inject only those services that are registered with this method.
214 * @param {class} cls Typescript class having @service decorator
215 * @param {Object} params Decorator params
216 */
217 public static addService<T>(cls: ClassType<T>, params: any) {
218 _serviceMap.set(cls, params);
219 }
220
221 /**
222 *
223 * @param cls
224 */
225 public static resolve<T>(key: ClassType<T>|NodeModuleType<T>): T {
226 let srvKey: ClassType<T> = <any>key;
227 if ((<NodeModuleType<any>>key).__esModule) {
228 srvKey = (<NodeModuleType<any>>key).default;
229 }
230 dependencyOrder = dependencyOrder || new Map<ClassType<any>, number>();
231 let di = new DI();
232 return di.resolveDependencies<T>(srvKey);
233 }
234
235 public static addSource(source: (any) => Object) {
236 _extSources.push(source);
237 //source.forEach((x, y) => {
238 // if (_depInstMap.has(y)) {
239 // throw 'key already present in the map';
240 // }
241 // _depInstMap.set(y, x);
242 //});
243 }
244}
\No newline at end of file