@@ -4,82 +4,29 @@ import "reflect-metadata";
4
4
/* --------------------------------- TYPES --------------------------------------- */
5
5
/* ------------------------------------------------------------------------------- */
6
6
7
- /**
8
- * Alias for constructor of type of T
9
- */
10
7
type Class < T > = new ( ...args : any [ ] ) => T
11
-
12
- /**
13
- * Life time style of component.
14
- * Singleton: the same instance returned on each resolve.
15
- * Transient: different instance returned on each resolve (default registration)
16
- */
17
8
type LifetimeScope = "Singleton" | "Transient"
18
-
19
- /**
20
- * Internal use, interface which contains of index and name
21
- */
22
- interface IndexNameType {
23
- index : number ,
24
- name : string
25
- }
26
-
27
- /**
28
- * Internal use, Abstraction of resolver type
29
- */
30
- interface Resolver {
31
- /**
32
- * Resolve a registered component model
33
- * @param config ComponentModel that will be resolved
34
- */
35
- resolve < T > ( config : ComponentModel ) : T
36
- }
37
-
38
- /**
39
- * Alias type for ResolverBase constructor
40
- */
41
9
type ResolverConstructor = new ( kernel : Kernel , cache : { [ key : string ] : any } ) => Resolver
42
10
43
- /**
44
- * Abstraction of container which only expose resolve<T>() method
45
- */
46
- interface Kernel {
47
- /**
48
- * Resolve a registered component
49
- * @param type Type or Name of the component that will be resolved
50
- */
51
- resolve < T > ( type : Class < T > | string ) : T
52
- }
11
+ interface IndexNameType { index : number , name : string }
12
+ interface Resolver { resolve < T > ( config : ComponentModel ) : T }
13
+ interface Kernel { resolve < T > ( type : Class < T > | string ) : T }
14
+ interface AutoFactory < T > { get ( ) : T }
15
+ interface Analyzer { analyze ( path : ( string | Class < any > ) [ ] , model ?: ComponentModel ) : string | undefined }
53
16
54
- /**
55
- * ComponentModel modifier that will be exposed on fluent registration
56
- */
57
17
interface ComponentModelModifier < T > {
58
- /**
59
- * Set a component model as singleton life style, default lifestyle is transient
60
- */
61
- singleton ( ) : ComponentModelModifier < T >
18
+ singleton ( ) : ComponentModelModifier < T > ,
62
19
onCreated ( callback : ( instance : T , kernel : Kernel ) => T ) : ComponentModelModifier < T >
63
20
}
64
21
65
- /**
66
- * Abstraction of ComponentModel
67
- */
68
22
interface ComponentModel {
69
23
kind : string ,
70
24
name : string ,
71
- scope : LifetimeScope
25
+ scope : LifetimeScope ,
26
+ analyzed : boolean
72
27
onCreatedCallback ?: ( instance : any , kernel : Kernel ) => any
73
28
}
74
29
75
- /**
76
- * Factory that returned registered component
77
- */
78
- interface AutoFactory < T > {
79
- get ( ) : T
80
- }
81
-
82
-
83
30
/* ------------------------------------------------------------------------------- */
84
31
/* ----------------------------- CONSTANTS/CACHE --------------------------------- */
85
32
/* ------------------------------------------------------------------------------- */
@@ -89,10 +36,6 @@ interface AutoFactory<T> {
89
36
*/
90
37
const NAME_DECORATOR_KEY = "my-own-ioc-container:named-type"
91
38
92
- /**
93
- * Registry of Resolvers will be used by Container. This constant retrieve value
94
- * from @resolver decorator
95
- */
96
39
const RESOLVERS : { [ kind : string ] : ResolverConstructor } = { }
97
40
98
41
@@ -139,6 +82,10 @@ function traverseConstructorParameters(target: Class<any>): (string | Class<any>
139
82
return traverseConstructorParameters ( Object . getPrototypeOf ( target ) )
140
83
}
141
84
85
+ function getComponentName ( component : string | Class < any > ) {
86
+ return typeof component == "string" ? component : component . prototype . constructor . name
87
+ }
88
+
142
89
/* ------------------------------------------------------------------------------- */
143
90
/* --------------------------------- DECORATORS ---------------------------------- */
144
91
/* ------------------------------------------------------------------------------- */
@@ -161,10 +108,6 @@ namespace inject {
161
108
}
162
109
}
163
110
164
- /**
165
- * Only for internal use. Register resolver thus automatically added to the Container
166
- * @param kind Kind of resolver, will automatically match with ComponentModel.kind
167
- */
168
111
function resolver ( kind : string ) {
169
112
return ( target : ResolverConstructor ) => {
170
113
RESOLVERS [ kind ] = target
@@ -178,13 +121,13 @@ function resolver(kind: string) {
178
121
abstract class ComponentModelBase < T > implements ComponentModel , ComponentModelModifier < T > {
179
122
abstract kind : string ;
180
123
abstract name : string ;
124
+ analyzed : boolean = false ;
181
125
scope : LifetimeScope = "Transient"
182
126
onCreatedCallback ?: ( instance : any , kernel : Kernel ) => any ;
183
127
singleton ( ) : ComponentModelModifier < T > {
184
128
this . scope = "Singleton"
185
129
return this
186
130
}
187
-
188
131
onCreated ( callback : ( instance : T , kernel : Kernel ) => T ) : ComponentModelModifier < T > {
189
132
this . onCreatedCallback = callback
190
133
return this
@@ -209,23 +152,90 @@ abstract class ResolverBase implements Resolver {
209
152
}
210
153
}
211
154
155
+ class DependencyGraphAnalyzer {
156
+ constructor ( private models : ComponentModel [ ] ) { }
157
+
158
+ private getModelByNameOrType ( type : string | Class < any > ) : ComponentModel | undefined {
159
+ const filter = ( x : ComponentModel ) =>
160
+ typeof type == "function" && x instanceof TypeComponentModel ?
161
+ x . type == type : x . name == type
162
+ return this . models . filter ( filter ) [ 0 ]
163
+ }
164
+
165
+ private traverseAnalyze ( path : ( string | Class < any > ) [ ] , model ?: ComponentModel ) : string | undefined {
166
+ /*
167
+ Traverse component model recursively to get issue in dependency graph
168
+ path: is traversal path for example:
169
+ class A {}
170
+ class B { constructor(a:A){} }
171
+ class C {}
172
+ class D { constructor(b:B, c:C){} }
173
+ if we traverse through D then the path will be:
174
+ 1: [D, B, A]
175
+ 2: [D, C]
176
+
177
+ model: the current model
178
+ */
179
+ if ( model && model . analyzed ) return
180
+ const curName = getComponentName ( path [ path . length - 1 ] )
181
+ const curPath = path . map ( x => getComponentName ( x ) ) . join ( " -> " )
182
+ if ( ! model ) return `Trying to resolve ${ curPath } but ${ curName } is not registered in container`
183
+ else {
184
+ if ( this . hasCircularDependency ( path , model ) ) return `Circular dependency detected on: ${ curPath } `
185
+ if ( model instanceof TypeComponentModel ) {
186
+ for ( let dependency of model . dependencies ) {
187
+ path . push ( dependency )
188
+ const analysis = this . traverseAnalyze ( path , this . getModelByNameOrType ( dependency ) )
189
+ if ( analysis ) return analysis
190
+ }
191
+ }
192
+ }
193
+ }
194
+
195
+ private hasCircularDependency ( path : ( string | Class < any > ) [ ] , model : ComponentModel ) {
196
+ /*
197
+ Graph has circular dependency if the model is inside the path exclude the last path.
198
+ Example:
199
+ path: [Computer, LGMonitor], model: {type: LGMonitor} -> Not Circular
200
+ path: [Computer, Computer], model: {type: Computer} -> Circular
201
+ path: [Computer, LGMonitor, Computer], model: {type: Computer} -> Circular
202
+ path: [Computer], model: {type:Computer} -> Not Circular
203
+ */
204
+ const matchName = ( x : string | Class < any > ) => ( typeof x == "string" && x == model . name )
205
+ const matchType = ( x : string | Class < any > ) => ( typeof x == "function" && model instanceof TypeComponentModel && x == model . type )
206
+ //exclude the last path
207
+ const testPath = path . slice ( 0 , - 1 )
208
+ //check if the model is inside path
209
+ return ( testPath . some ( matchName ) || testPath . some ( matchType ) )
210
+ }
211
+
212
+ analyze ( request : string | Class < any > ) {
213
+ const model = this . getModelByNameOrType ( request )
214
+ const analysis = this . traverseAnalyze ( [ request ] , model )
215
+ if ( analysis ) throw new Error ( analysis )
216
+ }
217
+ }
218
+
212
219
class Container implements Kernel {
213
220
private singletonCache : { [ name : string ] : any } = { }
214
221
private models : ComponentModel [ ] = [ ]
215
222
private resolver : { [ kind : string ] : Resolver } = { }
223
+ private analyzer : DependencyGraphAnalyzer
216
224
217
225
constructor ( ) {
218
226
//setup all registered RESOLVERS
219
227
//Resolver should be marked with @resolver decorator
220
228
Object . keys ( RESOLVERS ) . forEach ( x => {
221
229
this . resolver [ x ] = new RESOLVERS [ x ] ( this , this . singletonCache )
222
230
} )
231
+ this . analyzer = new DependencyGraphAnalyzer ( this . models )
223
232
}
224
233
225
- private resolveModel < T > ( model : ComponentModel ) : T {
226
- const resolver = this . resolver [ model . kind ]
227
- if ( ! resolver ) throw new Error ( `No resolver registered for component model kind of ${ model . kind } ` )
228
- return resolver . resolve ( model )
234
+ private getModelByNameOrType ( type : string | Class < any > ) : ComponentModel | undefined {
235
+ const filter = ( x : ComponentModel ) =>
236
+ typeof type == "function" && x instanceof TypeComponentModel ?
237
+ x . type == type : x . name == type
238
+ return this . models . filter ( filter ) [ 0 ]
229
239
}
230
240
231
241
/**
@@ -260,21 +270,19 @@ class Container implements Kernel {
260
270
}
261
271
}
262
272
273
+ private resolveModel < T > ( model : ComponentModel ) : T {
274
+ const resolver = this . resolver [ model . kind ]
275
+ if ( ! resolver ) throw new Error ( `No resolver registered for component model kind of ${ model . kind } ` )
276
+ return resolver . resolve ( model )
277
+ }
278
+
263
279
/**
264
- * Resolve a registered component
265
- * @param type Type or Name of the component that will be resolved
266
- */
280
+ * Resolve a registered component
281
+ * @param type Type or Name of the component that will be resolved
282
+ */
267
283
resolve < T > ( type : Class < T > | string ) : T {
268
- if ( typeof type == "string" ) {
269
- const model = this . models . filter ( x => x . name == type ) [ 0 ] ;
270
- if ( ! model ) throw Error ( `Trying to resolve ${ type } , but its not registered in the container` )
271
- return this . resolveModel ( model )
272
- }
273
- else {
274
- const model = this . models . filter ( x => x . kind == "Type" && x instanceof TypeComponentModel && x . type == type ) [ 0 ]
275
- if ( ! model ) throw Error ( `Trying to resolve type of ${ type . prototype . constructor . name } , but its not registered in the container` )
276
- return this . resolveModel ( model )
277
- }
284
+ this . analyzer . analyze ( type )
285
+ return this . resolveModel ( this . getModelByNameOrType ( type ) ! )
278
286
}
279
287
}
280
288
@@ -386,5 +394,9 @@ export {
386
394
inject ,
387
395
Container ,
388
396
ComponentRegistrar ,
389
- ComponentModel
397
+ ComponentModel ,
398
+ DependencyGraphAnalyzer ,
399
+ TypeComponentModel ,
400
+ InstanceComponentModel ,
401
+ AutoFactoryComponentModel
390
402
}
0 commit comments