@spinajs/di 1.1.7 → 1.2.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/container.js CHANGED
@@ -2,7 +2,6 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Container = void 0;
4
4
  const exceptions_1 = require("@spinajs/exceptions");
5
- const _ = require("lodash");
6
5
  require("reflect-metadata");
7
6
  const array_1 = require("./array");
8
7
  const decorators_1 = require("./decorators");
@@ -10,17 +9,18 @@ const enums_1 = require("./enums");
10
9
  const helpers_1 = require("./helpers");
11
10
  const interfaces_1 = require("./interfaces");
12
11
  const events_1 = require("events");
13
- const exceptions_2 = require("./exceptions");
12
+ const binder_1 = require("./binder");
13
+ const registry_1 = require("./registry");
14
+ const container_cache_1 = require("./container-cache");
14
15
  /**
15
16
  * Dependency injection container implementation
16
17
  */
17
18
  class Container extends events_1.EventEmitter {
18
19
  constructor(parent) {
19
20
  super();
20
- this.registry = new Map();
21
- this.cache = new Map();
21
+ this.registry = new registry_1.Registry(this);
22
+ this.cache = new container_cache_1.ContainerCache(this);
22
23
  this.parent = parent || undefined;
23
- this.registerSelf();
24
24
  }
25
25
  /**
26
26
  * Returns container cache - map object with resolved classes as singletons
@@ -31,6 +31,9 @@ class Container extends events_1.EventEmitter {
31
31
  get Registry() {
32
32
  return this.registry;
33
33
  }
34
+ get Parent() {
35
+ return this.parent;
36
+ }
34
37
  /**
35
38
  * Clears container registry and cache. shorthand for container.clearCache() && container.clearRegistry()
36
39
  */
@@ -42,54 +45,23 @@ class Container extends events_1.EventEmitter {
42
45
  */
43
46
  clearCache() {
44
47
  this.cache.clear();
45
- this.cache = new Map();
46
48
  }
47
49
  /**
48
50
  * Clears container resolved types
49
51
  */
50
52
  clearRegistry() {
51
- this.registry.clear();
52
- this.registerSelf();
53
+ this.Registry.clear();
53
54
  }
54
55
  /**
55
56
  * Register class/interface to DI.
56
57
  * @param type - interface object to register
57
- * @throws { InvalidArgument } if type is null or undefined
58
+ * @throws {@link InvalidArgument} if type is null or undefined
58
59
  */
59
60
  register(implementation) {
60
- if (_.isNil(implementation)) {
61
+ if (!implementation) {
61
62
  throw new exceptions_1.InvalidArgument('argument `type` cannot be null or undefined');
62
63
  }
63
- const self = this;
64
- return {
65
- _impl: null,
66
- as(type) {
67
- this._impl = implementation;
68
- const tname = typeof type === 'string' ? type : type.name;
69
- if (!self._hasRegisteredType(tname, implementation)) {
70
- if (self.registry.has(tname)) {
71
- self.registry.get(tname).push(implementation);
72
- }
73
- else {
74
- self.registry.set(tname, [implementation]);
75
- }
76
- }
77
- return this;
78
- },
79
- asSelf() {
80
- this._impl = implementation;
81
- self.registry.set(implementation.name, [implementation]);
82
- return this;
83
- },
84
- singleInstance() {
85
- const descriptor = {
86
- inject: [],
87
- resolver: enums_1.ResolveType.Singleton,
88
- };
89
- this._impl[decorators_1.DI_DESCRIPTION_SYMBOL] = descriptor;
90
- return this;
91
- },
92
- };
64
+ return new binder_1.Binder(implementation, this);
93
65
  }
94
66
  /**
95
67
  * Creates child DI container.
@@ -99,294 +71,276 @@ class Container extends events_1.EventEmitter {
99
71
  return new Container(this);
100
72
  }
101
73
  get(service, parent = true) {
102
- const self = this;
103
- const identifier = typeof service === 'string'
104
- ? this.Registry.get(service) || service
105
- : service instanceof array_1.TypedArray
106
- ? this.Registry.get(service.Type.name)
107
- : this.Registry.get(service.name) || service.name;
108
- if (!identifier) {
109
- return null;
110
- }
111
- if (typeof identifier === 'string') {
112
- return _get(identifier);
113
- }
114
- /**
115
- * When we try to get type by factory func, always return null
116
- * It's technically an arror becouse factory func in in charge now
117
- * of managing intances of created objects (eg. creating cache)
118
- *
119
- * We do not track of any instances created by factory funcions.
120
- */
121
- const isFactory = !(0, helpers_1.isConstructor)(identifier[identifier.length - 1]) && _.isFunction(identifier[identifier.length - 1]);
122
- if (isFactory) {
123
- return null;
124
- }
74
+ // get value registered as TypedArray ( mean to return all created instances )
125
75
  if (service instanceof array_1.TypedArray) {
126
- return identifier.map(t => _get(t.name));
127
- }
128
- return _get(identifier[identifier.length - 1].name);
129
- function _get(i) {
130
- if (self.cache.has(i)) {
131
- return self.cache.get(i);
132
- }
133
- else if (self.parent && parent) {
134
- return self.parent.get(i, parent);
135
- }
136
- return null;
137
- }
138
- }
139
- getRegistered(service, parent = true) {
140
- if (!service) {
141
- throw new exceptions_1.InvalidArgument('argument "service" cannot be null or empty');
142
- }
143
- const name = typeof service === 'string' ? service : service.constructor.name;
144
- if (this.registry.has(name)) {
145
- return this.registry.get(name);
146
- }
147
- if (this.parent && parent) {
148
- return this.parent.getRegistered(service, parent);
76
+ return this.cache.get((0, helpers_1.getTypeName)(service.Type));
149
77
  }
150
- return null;
78
+ const r = this.cache.get(service, parent);
79
+ return r[r.length - 1];
151
80
  }
152
81
  hasRegistered(service, parent = true) {
153
- if (this.registry.has(typeof service === 'string' ? service : service.name)) {
154
- return true;
155
- }
156
- else if (this.parent && parent) {
157
- return this.parent.hasRegistered(service);
158
- }
159
- return false;
82
+ return this.Registry.hasRegistered(service, parent);
160
83
  }
161
84
  /**
162
85
  * Checks if service is already resolved and exists in container cache.
163
86
  * NOTE: check is only valid for classes that are singletons.
164
87
  *
165
88
  * @param service - service name or class to check
166
- * @returns { boolean } - true if service instance already exists, otherwise false.
167
- * @throws { InvalidArgument } when service is null or empty
89
+ * @returns true if service instance already exists, otherwise false.
90
+ * @throws {@link InvalidArgument} when service is null or empty
168
91
  */
169
- has(service, parent = true) {
170
- if (!service) {
171
- throw new exceptions_1.InvalidArgument('argument cannot be null or empty');
172
- }
173
- const name = typeof service === 'string' ? service : service.name;
174
- if (this.cache.has(name)) {
175
- return true;
176
- }
177
- if (this.parent && parent) {
178
- return this.parent.has(name);
179
- }
180
- return false;
92
+ isResolved(service, parent = true) {
93
+ return this.Cache.has(service, parent);
181
94
  }
182
95
  /**
183
96
  *
184
- * @param type type to resolve
185
- * @param options options passed to constructor / factory
186
- * @param check strict check if serivice is registered in container before resolving. Default behavior is to not check and resolve
97
+ * @param type - type to resolve
98
+ * @param options - options passed to constructor / factory
99
+ * @param check - strict check if serivice is registered in container before resolving. Default behavior is to not check and resolve
187
100
  */
188
101
  resolve(type, options, check) {
189
- const sourceType = type instanceof array_1.TypedArray ? type.Type : type;
190
- if (_.isNil(type)) {
102
+ if (!type) {
191
103
  throw new exceptions_1.InvalidArgument('argument `type` cannot be null or undefined');
192
104
  }
193
- if ((typeof options === 'boolean' && options === true) || check === true) {
194
- if (!this.hasRegistered(typeof sourceType === 'string' ? sourceType : sourceType.name)) {
195
- throw new Error(`Type ${sourceType} is not registered at container`);
196
- }
197
- }
105
+ const sourceType = type instanceof array_1.TypedArray ? type.Type : type;
106
+ const sourceName = (0, helpers_1.getTypeName)(type);
198
107
  const opt = typeof options === 'boolean' ? null : options;
199
- const isArray = type.constructor.name === 'TypedArray';
200
- const targetType = isArray
201
- ? this.getRegistered(type.Type.name) || [type.Type]
202
- : typeof type === 'string'
203
- ? this.getRegistered(type)
204
- : this.getRegistered(type.name) || [type];
205
- if (!targetType) {
206
- throw new Error(`cannot resolve type ${type} becouse is not registered in container`);
207
- }
208
- if (typeof sourceType === 'string') {
209
- return this.resolveType(sourceType, targetType[targetType.length - 1], opt);
108
+ const setCache = (r) => {
109
+ this.Cache.add(type, r);
110
+ return r;
111
+ };
112
+ const emit = (target) => {
113
+ // firs event to emit that particular type was resolved
114
+ this.emit(`di.resolved.${(0, helpers_1.getTypeName)(target)}`, this, target);
115
+ // emit that source type was resolved
116
+ this.emit(`di.resolved.${sourceName}`, this, target);
117
+ };
118
+ if (options === true || check === true) {
119
+ if (!this.hasRegistered(sourceType)) {
120
+ throw new Error(`Type ${sourceName} is not registered at container`);
121
+ }
210
122
  }
211
- if (isArray) {
212
- const resolved = targetType.map(r => this.resolveArrayType(sourceType, r, opt));
213
- if (resolved.some(r => r instanceof Promise)) {
214
- return Promise.all(resolved);
123
+ if ((0, helpers_1.isTypedArray)(type)) {
124
+ // special case for arrays
125
+ // if we have in cache, retunr all we got
126
+ // TODO: fix this and every time check if theres is any
127
+ // new registerd type
128
+ if (this.Cache.has(type)) {
129
+ return this.Cache.get(type);
130
+ }
131
+ // if its array type, resolve all registered types or throw exception
132
+ const targetType = this.getRegisteredTypes(type);
133
+ if (!targetType) {
134
+ return [];
215
135
  }
136
+ const resolved = targetType.map((r) => this.resolveType(type, r, opt));
137
+ if (resolved.some((r) => r instanceof Promise)) {
138
+ return Promise.all(resolved).then((value) => {
139
+ value.forEach((v) => {
140
+ setCache(v);
141
+ emit(v);
142
+ });
143
+ return value;
144
+ });
145
+ }
146
+ // special case, we dont want to cache multiple times
147
+ resolved.forEach((v) => {
148
+ setCache(v);
149
+ emit(v);
150
+ });
216
151
  return resolved;
217
152
  }
218
- return this.resolveType(sourceType, targetType[targetType.length - 1], opt);
219
- }
220
- resolveArrayType(sourceType, targetType, options) {
221
- const tname = typeof sourceType === 'string' ? sourceType : sourceType.name;
222
- if (!this.Registry.has(tname)) {
223
- throw new exceptions_2.ResolveException(`Cannot resolve array of type ${tname}, no types are registered in container.`);
153
+ else {
154
+ // finaly resolve single type:
155
+ // 1. last registered type OR
156
+ // 2. if non is registered - type itself
157
+ let targetType = this.getRegisteredTypes(type);
158
+ if (!targetType) {
159
+ // if nothing is register under string identifier, then return null
160
+ if (typeof type === 'string') {
161
+ return null;
162
+ }
163
+ else {
164
+ targetType = [type];
165
+ }
166
+ }
167
+ // resolve last registered type ( newest )
168
+ const rValue = this.resolveType(sourceType, targetType[targetType.length - 1], opt);
169
+ if ((0, helpers_1.isPromise)(rValue)) {
170
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call
171
+ return rValue.then((v) => {
172
+ setCache(v);
173
+ emit(v);
174
+ return v;
175
+ });
176
+ }
177
+ setCache(rValue);
178
+ emit(rValue);
179
+ return rValue;
224
180
  }
225
- return this.resolveType(sourceType, targetType, options);
181
+ }
182
+ getRegisteredTypes(service, parent) {
183
+ return this.Registry.getTypes(service, parent);
226
184
  }
227
185
  resolveType(sourceType, targetType, options) {
228
- const self = this;
229
- const descriptor = _extractDescriptor(targetType);
230
- const isFactory = !(0, helpers_1.isConstructor)(targetType) && _.isFunction(targetType);
231
186
  /**
232
187
  * If its a factory func, always resolve as new instance
233
188
  */
234
- if (isFactory) {
235
- return _getNewInstance(targetType);
236
- }
189
+ if ((0, helpers_1.isFactory)(targetType)) {
190
+ return this.getNewInstance(targetType, null, options);
191
+ }
192
+ // we now know its not factory func
193
+ // but typescript complains about this
194
+ // becouse isFactory is custom type check
195
+ const tType = targetType;
196
+ const sName = (0, helpers_1.getTypeName)(sourceType);
197
+ const descriptor = this.extractDescriptor(tType);
198
+ // check if is singleton,
199
+ // resolving strategy per container is treatead as singleton
200
+ // in this particular container
201
+ const isSingletonInChild = descriptor.resolver === enums_1.ResolveType.PerChildContainer;
202
+ const isSingleton = descriptor.resolver === enums_1.ResolveType.Singleton;
203
+ const getCachedInstance = (e, parent) => {
204
+ if (this.isResolved(e, parent)) {
205
+ return this.get(e, parent);
206
+ }
207
+ return null;
208
+ };
209
+ const resolve = (d, t, i) => {
210
+ if (d.resolver === enums_1.ResolveType.NewInstance) {
211
+ return this.getNewInstance(t, i, options);
212
+ }
213
+ this.Registry.register(sName, t);
214
+ return getCachedInstance(tType, d.resolver === enums_1.ResolveType.Singleton ? true : false) || this.getNewInstance(t, i, options);
215
+ };
237
216
  // check cache if needed
238
- if (descriptor.resolver === enums_1.ResolveType.Singleton || descriptor.resolver === enums_1.ResolveType.PerChildContainer) {
239
- if (this.has(targetType, descriptor.resolver === enums_1.ResolveType.Singleton)) {
240
- return this.get(targetType);
217
+ if (isSingletonInChild || isSingleton) {
218
+ // if its singleton ( not per child container )
219
+ // check also in parent containers
220
+ // ------- IMPORTANT ------------
221
+ // TODO: in future allow to check in runtime if target type is cashed,
222
+ // now, if for example we resolve array of some type,
223
+ // when we later register another type of base class used in typed array
224
+ // we will not resolve it, becaouse contaienr will not check
225
+ // if in cache this new type exists ( only check if type in array exists )
226
+ const cached = getCachedInstance(sourceType, isSingleton);
227
+ if (cached) {
228
+ return cached;
241
229
  }
242
230
  }
243
- const deps = _resolveDeps(descriptor.inject);
231
+ const deps = this.resolveDependencies(descriptor.inject);
244
232
  if (deps instanceof Promise) {
245
- return deps
246
- .then(resolvedDependencies => {
247
- return _resolve(descriptor, targetType, resolvedDependencies);
248
- }).then(_setCache);
233
+ return deps.then((resolvedDependencies) => {
234
+ return resolve(descriptor, tType, resolvedDependencies);
235
+ });
249
236
  }
250
237
  else {
251
- const resInstance = _resolve(descriptor, targetType, deps);
238
+ const resInstance = resolve(descriptor, tType, deps);
252
239
  if (resInstance instanceof Promise) {
253
- return resInstance.then(_setCache);
240
+ return resInstance;
254
241
  }
255
- _setCache(resInstance);
256
242
  return resInstance;
257
243
  }
258
- function _getNameOfResolvedType() {
259
- return targetType.name;
260
- }
261
- function _setCache(r) {
262
- const checkParent = descriptor.resolver === enums_1.ResolveType.Singleton;
263
- const toCheck = _getNameOfResolvedType();
264
- if (!self.has(toCheck, checkParent)) {
265
- self.Cache.set(toCheck, r);
266
- }
267
- return r;
244
+ }
245
+ getNewInstance(typeToCreate, a, options) {
246
+ let args = [null];
247
+ let newInstance = null;
248
+ /**
249
+ * If type is not Constructable, we assume its factory function,
250
+ * just call it with `this` container.
251
+ */
252
+ if ((0, helpers_1.isFactory)(typeToCreate)) {
253
+ newInstance = typeToCreate(this, ...(options !== null && options !== void 0 ? options : []));
268
254
  }
269
- function _resolve(d, t, i) {
270
- const tname = typeof sourceType === 'string' ? sourceType : sourceType.name;
271
- if (d.resolver === enums_1.ResolveType.NewInstance) {
272
- return _getNewInstance(t, i);
255
+ else {
256
+ if (a.constructor.name === 'Array') {
257
+ args = args.concat(a.filter((i) => !i.autoinject).map((i) => i.instance));
273
258
  }
274
- if (!self.Registry.has(tname)) {
275
- self.Registry.set(tname, [t]);
259
+ if (options && options.length !== 0) {
260
+ args = args.concat(options);
276
261
  }
277
- else {
278
- if (!self._hasRegisteredType(sourceType, t)) {
279
- self.Registry.set(tname, self.Registry.get(tname).concat(t));
280
- }
262
+ /* eslint-disable */
263
+ newInstance = new (Function.prototype.bind.apply(typeToCreate, args))();
264
+ for (const ai of a.filter((i) => i.autoinject)) {
265
+ // TYPE HACK to tell typescript we dont care type
266
+ /* eslint-disable */
267
+ newInstance[`${ai.autoinjectKey}`] = ai.instance;
281
268
  }
282
- return (_getCachedInstance(targetType, d.resolver === enums_1.ResolveType.Singleton ? true : false) || _getNewInstance(t, i));
283
- }
284
- function _extractDescriptor(type) {
285
- const descriptor = {
286
- inject: [],
287
- resolver: enums_1.ResolveType.Singleton,
288
- };
289
- reduce(type);
290
- descriptor.inject = _.uniqWith(descriptor.inject, (a, b) => {
291
- return a.inject.name === b.inject.name;
292
- });
293
- return descriptor;
294
- function reduce(t) {
295
- if (!t) {
296
- return;
297
- }
298
- reduce(t.prototype);
299
- reduce(t.__proto__);
300
- if (t[decorators_1.DI_DESCRIPTION_SYMBOL]) {
301
- descriptor.inject = descriptor.inject.concat(t[decorators_1.DI_DESCRIPTION_SYMBOL].inject);
302
- descriptor.resolver = t[decorators_1.DI_DESCRIPTION_SYMBOL].resolver;
303
- }
269
+ if ((0, helpers_1.isAsyncModule)(newInstance)) {
270
+ return new Promise((res, rej) => {
271
+ newInstance
272
+ .resolveAsync()
273
+ .then(() => { })
274
+ .then(() => {
275
+ res(newInstance);
276
+ })
277
+ .catch((err) => rej(err));
278
+ });
304
279
  }
305
- }
306
- function _resolveDeps(toInject) {
307
- const dependencies = toInject.map(t => {
308
- const promiseOrVal = self.resolve(t.inject);
309
- if (promiseOrVal instanceof Promise) {
310
- return new Promise((res, _) => {
311
- res(promiseOrVal);
312
- }).then((val) => {
313
- return {
314
- autoinject: t.autoinject,
315
- autoinjectKey: t.autoinjectKey,
316
- instance: val,
317
- };
318
- });
280
+ else {
281
+ if (newInstance instanceof interfaces_1.SyncModule) {
282
+ newInstance.resolve();
319
283
  }
320
- return {
321
- autoinject: t.autoinject,
322
- autoinjectKey: t.autoinjectKey,
323
- instance: promiseOrVal,
324
- };
325
- });
326
- if (dependencies.some(p => p instanceof Promise)) {
327
- return Promise.all(dependencies);
328
284
  }
329
- return dependencies;
330
285
  }
331
- function _getCachedInstance(e, parent) {
332
- if (self.has(e, parent)) {
333
- return self.get(e, parent);
286
+ return newInstance;
287
+ }
288
+ hasRegisteredType(source, type, parent) {
289
+ return this.Registry.hasRegisteredType(source, type, parent);
290
+ }
291
+ resolveDependencies(toInject) {
292
+ const dependencies = toInject.map((t) => {
293
+ const promiseOrVal = this.resolve(t.inject);
294
+ if (promiseOrVal instanceof Promise) {
295
+ return new Promise((res, _) => {
296
+ res(promiseOrVal);
297
+ }).then((val) => {
298
+ return {
299
+ autoinject: t.autoinject,
300
+ autoinjectKey: t.autoinjectKey,
301
+ instance: val,
302
+ };
303
+ });
334
304
  }
335
- return null;
305
+ return {
306
+ autoinject: t.autoinject,
307
+ autoinjectKey: t.autoinjectKey,
308
+ instance: promiseOrVal,
309
+ };
310
+ });
311
+ if (dependencies.some((p) => p instanceof Promise)) {
312
+ return Promise.all(dependencies);
336
313
  }
337
- function _getNewInstance(typeToCreate, a) {
338
- let args = [null];
339
- let newInstance = null;
340
- /**
341
- * If type is not Constructable, we assume its factory function,
342
- * just call it with `this` container.
343
- */
344
- if (!(0, helpers_1.isConstructor)(typeToCreate) && _.isFunction(typeToCreate)) {
345
- newInstance = typeToCreate(self, ...[].concat(options));
314
+ return dependencies;
315
+ }
316
+ extractDescriptor(type) {
317
+ const descriptor = {
318
+ inject: [],
319
+ resolver: enums_1.ResolveType.Singleton,
320
+ };
321
+ reduce(type);
322
+ descriptor.inject = (0, helpers_1.uniqBy)(descriptor.inject, (a, b) => {
323
+ if (a.inject instanceof array_1.TypedArray && b.inject instanceof array_1.TypedArray) {
324
+ return (0, helpers_1.getTypeName)(a.inject.Type) === (0, helpers_1.getTypeName)(b.inject.Type);
346
325
  }
347
326
  else {
348
- if (_.isArray(a)) {
349
- args = args.concat(a.filter(i => !i.autoinject).map(i => i.instance));
350
- }
351
- if (!_.isEmpty(options)) {
352
- args = args.concat(options);
353
- }
354
- newInstance = new (Function.prototype.bind.apply(typeToCreate, args))();
355
- for (const ai of a.filter(i => i.autoinject)) {
356
- newInstance[ai.autoinjectKey] = ai.instance;
357
- }
358
- if (newInstance instanceof interfaces_1.AsyncModule) {
359
- return new Promise(res => {
360
- newInstance.resolveAsync(self).then(() => {
361
- self.emit(`di.resolved.${_getNameOfResolvedType()}`);
362
- }).then(() => {
363
- res(newInstance);
364
- });
365
- });
366
- }
367
- else {
368
- if (newInstance instanceof interfaces_1.SyncModule) {
369
- newInstance.resolve(self);
370
- }
371
- self.emit(`di.resolved.${_getNameOfResolvedType()}`);
327
+ return a.inject.name === b.inject.name;
328
+ }
329
+ });
330
+ return descriptor;
331
+ function reduce(t) {
332
+ if (t) {
333
+ // for descriptors defined on class declarations
334
+ reduce(t.prototype);
335
+ // for descriptors defined on class properties eg. @Autoinject()
336
+ reduce(t.__proto__);
337
+ if (t[`${decorators_1.DI_DESCRIPTION_SYMBOL}`]) {
338
+ descriptor.inject = descriptor.inject.concat(t[`${decorators_1.DI_DESCRIPTION_SYMBOL}`].inject);
339
+ descriptor.resolver = t[`${decorators_1.DI_DESCRIPTION_SYMBOL}`].resolver;
372
340
  }
373
341
  }
374
- return newInstance;
375
342
  }
376
343
  }
377
- _hasRegisteredType(source, type) {
378
- const sourceName = typeof source === 'string' ? source : source.name;
379
- const targetName = typeof type === 'string' ? type : type.name;
380
- if (this.registry.has(sourceName)) {
381
- return this.registry.get(sourceName).find(s => s.name === targetName) !== undefined;
382
- }
383
- return false;
384
- }
385
- //
386
- // allows container instance to be resolved
387
- registerSelf() {
388
- this.cache.set('Container', this);
389
- }
390
344
  }
391
345
  exports.Container = Container;
392
346
  //# sourceMappingURL=container.js.map