proxydi 0.0.9 → 0.0.10

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/README.md CHANGED
@@ -12,9 +12,12 @@ Core features:
12
12
  - Automatically resolves circular dependencies with no performance impact
13
13
  - Resolves dependencies in the context of a particular container
14
14
  - Supports hierarchical containers with the ability to resolve dependencies in both directions
15
- - Matches dependencies by unique identifiers or automatically using class names and property names
16
15
  - Currently under active development, the API may change until version 0.1.0
17
16
 
17
+ Eperimemntal features:
18
+ - Construtor injections (see unit tests for examples)
19
+ - Matches dependencies by unique identifiers or automatically using class names and property names
20
+
18
21
  ## Quick start
19
22
 
20
23
  > If you are using React, you should use the React wrapper for ProxyDi instead of this library directly: [@proxydi/react](https://github.com/proxy-di/proxydi-react)
@@ -218,7 +221,7 @@ class UnderwaterShield {
218
221
 
219
222
  In this example, the shield perk increases character health if it is underwater. The perk receives both level and character using the @inject() decorator, the same way as we have seen so far.
220
223
 
221
- ## Resolving dependencies from children
224
+ ### Resolving dependencies from children
222
225
 
223
226
  Backward bonus of containers hierarchy, each top level dependency is free to use all dependency from the bottom:
224
227
 
@@ -46,6 +46,7 @@ export declare class ProxyDiContainer implements IProxyDiContainer {
46
46
  */
47
47
  register<T>(DependencyClass: DependencyClass<T>, dependencyId: DependencyId): T & ContainerizedDependency;
48
48
  register<T>(dependency: T extends new (...args: any[]) => any ? never : T, dependencyId: DependencyId): T & ContainerizedDependency;
49
+ private createInstance;
49
50
  /**
50
51
  * Internal method that implements registeration of dependency and prepare it for injection.
51
52
  * @param dependencyId The unique identifier of the dependency.
@@ -65,7 +66,7 @@ export declare class ProxyDiContainer implements IProxyDiContainer {
65
66
  * @throws Error if the dependency cannot be found or is not auto injectable.
66
67
  */
67
68
  resolve<T>(dependencyId: DependencyId): T & ContainerizedDependency;
68
- resolve<T extends new (...args: any[]) => any>(SomeClass: T): InstanceType<T> & ContainerizedDependency;
69
+ resolve<T extends DependencyClass<any>>(SomeClass: T): InstanceType<T> & ContainerizedDependency;
69
70
  /**
70
71
  * Injects dependencies to the given object based on its defined injections metadata. Does not affect the container.
71
72
  * @param injectionsOwner The object to inject dependencies into.
package/dist/index.cjs CHANGED
@@ -1,10 +1,20 @@
1
1
  'use strict';
2
2
 
3
3
  const INJECTIONS = Symbol('injections');
4
+ /**
5
+ * This symbol constant defines a property name.
6
+ * This property is present in each dependency instance that was registered in ProxyDiContainer.
7
+ * The property stores the dependency identifier that should be used to resolve dependency from the container where it was registered.
8
+ */
4
9
  const DEPENDENCY_ID = Symbol('DependencyId');
10
+ /**
11
+ * This symbol constant defines a property name.
12
+ * This property is present in each dependency instance that was registered in ProxyDiContainer.
13
+ * The property stores a reference to the ProxyDiContainer in which the dependency was registered.
14
+ */
15
+ const PROXYDY_CONTAINER = Symbol('proxyDiContainer');
5
16
  const IS_INJECTION_PROXY = Symbol('isInjectionProxy');
6
17
  const INJECTION_OWNER = Symbol('injectionOwner');
7
- const PROXYDY_CONTAINER = Symbol('proxyDiContainer');
8
18
  const IS_INSTANCE_PROXY = Symbol('isInstanceProxy');
9
19
 
10
20
  /**
@@ -37,31 +47,40 @@ const inject = (dependencyId) => {
37
47
  };
38
48
  };
39
49
 
40
- const autoInjectableClasses = {};
41
- /**
42
- * Registers a class as an auto-injectable for dependency injection.
43
- *
44
- * @param dependencyId - Optional dependency identifier. If omitted, the class name is used.
45
- * @returns A class decorator function.
46
- *
47
- * Note: During dependency resolution, any container that does not have an instance for the specified dependency identifier
48
- * will create an instance of the decorated class. However, if a container already has an instance with that identifier
49
- * prior to resolution, the decorated class will be ignored by that container.
50
- */
51
- const autoInjectable = (dependencyId) => {
50
+ const injectableClasses = {};
51
+ const constructorInjections = {};
52
+ function injectable(dependencyOrDependencies, autoInjecions) {
52
53
  return function (value, context) {
53
- if ((context === null || context === undefined ? undefined : context.kind) === 'class') {
54
- const name = dependencyId ? dependencyId : context.name;
55
- if (autoInjectableClasses[name]) {
56
- throw new Error(`ProxyDi autoInjectable already has dependency ID: ${String(name)}`);
57
- }
58
- autoInjectableClasses[name] = value;
54
+ if ((context === null || context === undefined ? undefined : context.kind) !== 'class') {
55
+ throw new Error('@injectable decorator should decorate classes');
59
56
  }
60
- else {
61
- throw new Error('@autoInjectable decorator should decorate classes');
57
+ const name = dependencyOrDependencies
58
+ ? Array.isArray(dependencyOrDependencies)
59
+ ? context.name
60
+ : dependencyOrDependencies
61
+ : context.name;
62
+ const injectToConstructor = dependencyOrDependencies
63
+ ? Array.isArray(dependencyOrDependencies)
64
+ ? dependencyOrDependencies
65
+ : autoInjecions
66
+ : autoInjecions;
67
+ if (injectableClasses[name]) {
68
+ throw new Error(`ProxyDi has already regisered dependency ${String(name)} by @injectable`);
69
+ }
70
+ injectableClasses[name] = value;
71
+ if (injectToConstructor) {
72
+ constructorInjections[name] = injectToConstructor;
62
73
  }
63
74
  };
64
- };
75
+ }
76
+ function findInjectableId(injectable) {
77
+ for (const [id, DependencyClass] of Object.entries(injectableClasses)) {
78
+ if (DependencyClass === injectable) {
79
+ return id;
80
+ }
81
+ }
82
+ throw new Error(`Class is not @injectable: ${injectable.name}`);
83
+ }
65
84
 
66
85
  const DEFAULT_SETTINGS = {
67
86
  allowRegisterAnything: false,
@@ -128,6 +147,49 @@ function makeDependencyProxy(dependency) {
128
147
  });
129
148
  }
130
149
 
150
+ function makeConstructorDependencyProxy(container, dependencyId) {
151
+ // let values: Record<string | symbol, any> = {};
152
+ // let wasResolved = false;
153
+ function getDependency() {
154
+ if (container.isKnown(dependencyId)) {
155
+ const dependency = container.resolve(dependencyId);
156
+ // if (!wasResolved) {
157
+ // for (const key in values) {
158
+ // dependency[key] = values[key];
159
+ // }
160
+ // values = {};
161
+ // wasResolved = true;
162
+ // }
163
+ return dependency;
164
+ }
165
+ else {
166
+ throw new Error(`Unknown dependency: ${String(dependencyId)}`);
167
+ }
168
+ }
169
+ return new Proxy({}, {
170
+ get: function (target, prop, receiver) {
171
+ // if (values[prop]) {
172
+ // return values[prop];
173
+ // }
174
+ const dependency = getDependency();
175
+ return Reflect.get(dependency, prop, receiver);
176
+ },
177
+ set: function (_target, prop, value) {
178
+ // if (!container.isKnown(dependencyId)) {
179
+ // values[prop] = value;
180
+ // return true;
181
+ // } else {
182
+ const dependency = getDependency();
183
+ return Reflect.set(dependency, prop, value);
184
+ //}
185
+ },
186
+ has: function (_target, prop) {
187
+ const dependency = getDependency();
188
+ return Reflect.has(dependency, prop);
189
+ },
190
+ });
191
+ }
192
+
131
193
  /**
132
194
  * A dependency injection container
133
195
  */
@@ -163,7 +225,7 @@ class ProxyDiContainer {
163
225
  let dependencyInstance;
164
226
  const isClass = typeof dependency === 'function';
165
227
  if (isClass) {
166
- dependencyInstance = new dependency();
228
+ dependencyInstance = this.createInstance(dependency, dependencyId);
167
229
  }
168
230
  else {
169
231
  dependencyInstance = dependency;
@@ -178,6 +240,15 @@ class ProxyDiContainer {
178
240
  this.registerImpl(dependencyInstance, dependencyId);
179
241
  return dependencyInstance;
180
242
  }
243
+ createInstance(Dependency, dependencyId) {
244
+ const paramIds = constructorInjections[dependencyId] || [];
245
+ const params = [];
246
+ for (const id of paramIds) {
247
+ const param = makeConstructorDependencyProxy(this, id);
248
+ params.push(param);
249
+ }
250
+ return new Dependency(...params);
251
+ }
181
252
  /**
182
253
  * Internal method that implements registeration of dependency and prepare it for injection.
183
254
  * @param dependencyId The unique identifier of the dependency.
@@ -199,39 +270,34 @@ class ProxyDiContainer {
199
270
  return !!(this.parentDependencyProxies[dependencyId] ||
200
271
  this.dependencies[dependencyId] ||
201
272
  (this.parent && this.parent.isKnown(dependencyId)) ||
202
- autoInjectableClasses[dependencyId]);
273
+ injectableClasses[dependencyId]);
203
274
  }
204
- resolve(param) {
205
- if (typeof param === 'function') {
206
- for (const [dependencyId, DependencyClass] of Object.entries(autoInjectableClasses)) {
207
- if (DependencyClass === param) {
208
- return this.resolve(dependencyId);
209
- }
210
- }
211
- throw new Error(`Class is not auto injectable: ${param.name}`);
275
+ resolve(dependency) {
276
+ if (typeof dependency === 'function') {
277
+ const id = findInjectableId(dependency);
278
+ return this.resolve(id);
212
279
  }
213
- if (!this.isKnown(param)) {
214
- throw new Error(`Can't resolve unknown dependency: ${String(param)}`);
280
+ if (!this.isKnown(dependency)) {
281
+ throw new Error(`Can't resolve unknown dependency: ${String(dependency)}`);
215
282
  }
216
- const proxy = this.parentDependencyProxies[param];
283
+ const proxy = this.parentDependencyProxies[dependency];
217
284
  if (proxy) {
218
285
  return proxy;
219
286
  }
220
- const dependency = this.findDependency(param);
221
- if (dependency) {
222
- if (dependency[PROXYDY_CONTAINER] !== this &&
223
- typeof dependency === 'object' &&
287
+ const instance = this.findDependency(dependency);
288
+ if (instance) {
289
+ if (instance[PROXYDY_CONTAINER] !== this &&
290
+ typeof instance === 'object' &&
224
291
  this.settings.resolveInContainerContext) {
225
- const proxy = makeDependencyProxy(dependency);
292
+ const proxy = makeDependencyProxy(instance);
226
293
  this.injectDependenciesTo(proxy);
227
- this.parentDependencyProxies[param] = proxy;
294
+ this.parentDependencyProxies[dependency] = proxy;
228
295
  return proxy;
229
296
  }
230
- return dependency;
297
+ return instance;
231
298
  }
232
- const AutoInjectableClass = autoInjectableClasses[param];
233
- const autoDependency = new AutoInjectableClass();
234
- return this.register(autoDependency, param);
299
+ const InjectableClass = injectableClasses[dependency];
300
+ return this.register(InjectableClass, dependency);
235
301
  }
236
302
  /**
237
303
  * Injects dependencies to the given object based on its defined injections metadata. Does not affect the container.
@@ -376,12 +442,8 @@ function isDependency(dependencyOrId) {
376
442
 
377
443
  function resolveAll(instance, dependencyId) {
378
444
  if (typeof dependencyId === 'function') {
379
- for (const [id, DependencyClass] of Object.entries(autoInjectableClasses)) {
380
- if (DependencyClass === dependencyId) {
381
- return resolveAll(instance, id);
382
- }
383
- }
384
- throw new Error(`Class is not auto injectable: ${dependencyId.name}`);
445
+ const id = findInjectableId(dependencyId);
446
+ return resolveAll(instance, id);
385
447
  }
386
448
  const container = instance[PROXYDY_CONTAINER];
387
449
  if (!container) {
@@ -403,6 +465,6 @@ function recursiveResolveAll(container, dependencyId) {
403
465
  exports.DEPENDENCY_ID = DEPENDENCY_ID;
404
466
  exports.PROXYDY_CONTAINER = PROXYDY_CONTAINER;
405
467
  exports.ProxyDiContainer = ProxyDiContainer;
406
- exports.autoInjectable = autoInjectable;
407
468
  exports.inject = inject;
469
+ exports.injectable = injectable;
408
470
  exports.resolveAll = resolveAll;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { inject } from './inject';
2
2
  export { ProxyDiContainer } from './ProxyDiContainer';
3
- export { autoInjectable } from './autoInjectable';
3
+ export { injectable } from './injectable';
4
4
  export { Injection, DependencyId, DependencyClass, ContainerSettings, PROXYDY_CONTAINER, DEPENDENCY_ID, } from './types';
5
5
  export { resolveAll } from './resolveAll';
package/dist/index.js CHANGED
@@ -1,8 +1,18 @@
1
1
  const INJECTIONS = Symbol('injections');
2
+ /**
3
+ * This symbol constant defines a property name.
4
+ * This property is present in each dependency instance that was registered in ProxyDiContainer.
5
+ * The property stores the dependency identifier that should be used to resolve dependency from the container where it was registered.
6
+ */
2
7
  const DEPENDENCY_ID = Symbol('DependencyId');
8
+ /**
9
+ * This symbol constant defines a property name.
10
+ * This property is present in each dependency instance that was registered in ProxyDiContainer.
11
+ * The property stores a reference to the ProxyDiContainer in which the dependency was registered.
12
+ */
13
+ const PROXYDY_CONTAINER = Symbol('proxyDiContainer');
3
14
  const IS_INJECTION_PROXY = Symbol('isInjectionProxy');
4
15
  const INJECTION_OWNER = Symbol('injectionOwner');
5
- const PROXYDY_CONTAINER = Symbol('proxyDiContainer');
6
16
  const IS_INSTANCE_PROXY = Symbol('isInstanceProxy');
7
17
 
8
18
  /**
@@ -35,31 +45,40 @@ const inject = (dependencyId) => {
35
45
  };
36
46
  };
37
47
 
38
- const autoInjectableClasses = {};
39
- /**
40
- * Registers a class as an auto-injectable for dependency injection.
41
- *
42
- * @param dependencyId - Optional dependency identifier. If omitted, the class name is used.
43
- * @returns A class decorator function.
44
- *
45
- * Note: During dependency resolution, any container that does not have an instance for the specified dependency identifier
46
- * will create an instance of the decorated class. However, if a container already has an instance with that identifier
47
- * prior to resolution, the decorated class will be ignored by that container.
48
- */
49
- const autoInjectable = (dependencyId) => {
48
+ const injectableClasses = {};
49
+ const constructorInjections = {};
50
+ function injectable(dependencyOrDependencies, autoInjecions) {
50
51
  return function (value, context) {
51
- if ((context === null || context === undefined ? undefined : context.kind) === 'class') {
52
- const name = dependencyId ? dependencyId : context.name;
53
- if (autoInjectableClasses[name]) {
54
- throw new Error(`ProxyDi autoInjectable already has dependency ID: ${String(name)}`);
55
- }
56
- autoInjectableClasses[name] = value;
52
+ if ((context === null || context === undefined ? undefined : context.kind) !== 'class') {
53
+ throw new Error('@injectable decorator should decorate classes');
57
54
  }
58
- else {
59
- throw new Error('@autoInjectable decorator should decorate classes');
55
+ const name = dependencyOrDependencies
56
+ ? Array.isArray(dependencyOrDependencies)
57
+ ? context.name
58
+ : dependencyOrDependencies
59
+ : context.name;
60
+ const injectToConstructor = dependencyOrDependencies
61
+ ? Array.isArray(dependencyOrDependencies)
62
+ ? dependencyOrDependencies
63
+ : autoInjecions
64
+ : autoInjecions;
65
+ if (injectableClasses[name]) {
66
+ throw new Error(`ProxyDi has already regisered dependency ${String(name)} by @injectable`);
67
+ }
68
+ injectableClasses[name] = value;
69
+ if (injectToConstructor) {
70
+ constructorInjections[name] = injectToConstructor;
60
71
  }
61
72
  };
62
- };
73
+ }
74
+ function findInjectableId(injectable) {
75
+ for (const [id, DependencyClass] of Object.entries(injectableClasses)) {
76
+ if (DependencyClass === injectable) {
77
+ return id;
78
+ }
79
+ }
80
+ throw new Error(`Class is not @injectable: ${injectable.name}`);
81
+ }
63
82
 
64
83
  const DEFAULT_SETTINGS = {
65
84
  allowRegisterAnything: false,
@@ -126,6 +145,49 @@ function makeDependencyProxy(dependency) {
126
145
  });
127
146
  }
128
147
 
148
+ function makeConstructorDependencyProxy(container, dependencyId) {
149
+ // let values: Record<string | symbol, any> = {};
150
+ // let wasResolved = false;
151
+ function getDependency() {
152
+ if (container.isKnown(dependencyId)) {
153
+ const dependency = container.resolve(dependencyId);
154
+ // if (!wasResolved) {
155
+ // for (const key in values) {
156
+ // dependency[key] = values[key];
157
+ // }
158
+ // values = {};
159
+ // wasResolved = true;
160
+ // }
161
+ return dependency;
162
+ }
163
+ else {
164
+ throw new Error(`Unknown dependency: ${String(dependencyId)}`);
165
+ }
166
+ }
167
+ return new Proxy({}, {
168
+ get: function (target, prop, receiver) {
169
+ // if (values[prop]) {
170
+ // return values[prop];
171
+ // }
172
+ const dependency = getDependency();
173
+ return Reflect.get(dependency, prop, receiver);
174
+ },
175
+ set: function (_target, prop, value) {
176
+ // if (!container.isKnown(dependencyId)) {
177
+ // values[prop] = value;
178
+ // return true;
179
+ // } else {
180
+ const dependency = getDependency();
181
+ return Reflect.set(dependency, prop, value);
182
+ //}
183
+ },
184
+ has: function (_target, prop) {
185
+ const dependency = getDependency();
186
+ return Reflect.has(dependency, prop);
187
+ },
188
+ });
189
+ }
190
+
129
191
  /**
130
192
  * A dependency injection container
131
193
  */
@@ -161,7 +223,7 @@ class ProxyDiContainer {
161
223
  let dependencyInstance;
162
224
  const isClass = typeof dependency === 'function';
163
225
  if (isClass) {
164
- dependencyInstance = new dependency();
226
+ dependencyInstance = this.createInstance(dependency, dependencyId);
165
227
  }
166
228
  else {
167
229
  dependencyInstance = dependency;
@@ -176,6 +238,15 @@ class ProxyDiContainer {
176
238
  this.registerImpl(dependencyInstance, dependencyId);
177
239
  return dependencyInstance;
178
240
  }
241
+ createInstance(Dependency, dependencyId) {
242
+ const paramIds = constructorInjections[dependencyId] || [];
243
+ const params = [];
244
+ for (const id of paramIds) {
245
+ const param = makeConstructorDependencyProxy(this, id);
246
+ params.push(param);
247
+ }
248
+ return new Dependency(...params);
249
+ }
179
250
  /**
180
251
  * Internal method that implements registeration of dependency and prepare it for injection.
181
252
  * @param dependencyId The unique identifier of the dependency.
@@ -197,39 +268,34 @@ class ProxyDiContainer {
197
268
  return !!(this.parentDependencyProxies[dependencyId] ||
198
269
  this.dependencies[dependencyId] ||
199
270
  (this.parent && this.parent.isKnown(dependencyId)) ||
200
- autoInjectableClasses[dependencyId]);
271
+ injectableClasses[dependencyId]);
201
272
  }
202
- resolve(param) {
203
- if (typeof param === 'function') {
204
- for (const [dependencyId, DependencyClass] of Object.entries(autoInjectableClasses)) {
205
- if (DependencyClass === param) {
206
- return this.resolve(dependencyId);
207
- }
208
- }
209
- throw new Error(`Class is not auto injectable: ${param.name}`);
273
+ resolve(dependency) {
274
+ if (typeof dependency === 'function') {
275
+ const id = findInjectableId(dependency);
276
+ return this.resolve(id);
210
277
  }
211
- if (!this.isKnown(param)) {
212
- throw new Error(`Can't resolve unknown dependency: ${String(param)}`);
278
+ if (!this.isKnown(dependency)) {
279
+ throw new Error(`Can't resolve unknown dependency: ${String(dependency)}`);
213
280
  }
214
- const proxy = this.parentDependencyProxies[param];
281
+ const proxy = this.parentDependencyProxies[dependency];
215
282
  if (proxy) {
216
283
  return proxy;
217
284
  }
218
- const dependency = this.findDependency(param);
219
- if (dependency) {
220
- if (dependency[PROXYDY_CONTAINER] !== this &&
221
- typeof dependency === 'object' &&
285
+ const instance = this.findDependency(dependency);
286
+ if (instance) {
287
+ if (instance[PROXYDY_CONTAINER] !== this &&
288
+ typeof instance === 'object' &&
222
289
  this.settings.resolveInContainerContext) {
223
- const proxy = makeDependencyProxy(dependency);
290
+ const proxy = makeDependencyProxy(instance);
224
291
  this.injectDependenciesTo(proxy);
225
- this.parentDependencyProxies[param] = proxy;
292
+ this.parentDependencyProxies[dependency] = proxy;
226
293
  return proxy;
227
294
  }
228
- return dependency;
295
+ return instance;
229
296
  }
230
- const AutoInjectableClass = autoInjectableClasses[param];
231
- const autoDependency = new AutoInjectableClass();
232
- return this.register(autoDependency, param);
297
+ const InjectableClass = injectableClasses[dependency];
298
+ return this.register(InjectableClass, dependency);
233
299
  }
234
300
  /**
235
301
  * Injects dependencies to the given object based on its defined injections metadata. Does not affect the container.
@@ -374,12 +440,8 @@ function isDependency(dependencyOrId) {
374
440
 
375
441
  function resolveAll(instance, dependencyId) {
376
442
  if (typeof dependencyId === 'function') {
377
- for (const [id, DependencyClass] of Object.entries(autoInjectableClasses)) {
378
- if (DependencyClass === dependencyId) {
379
- return resolveAll(instance, id);
380
- }
381
- }
382
- throw new Error(`Class is not auto injectable: ${dependencyId.name}`);
443
+ const id = findInjectableId(dependencyId);
444
+ return resolveAll(instance, id);
383
445
  }
384
446
  const container = instance[PROXYDY_CONTAINER];
385
447
  if (!container) {
@@ -398,4 +460,4 @@ function recursiveResolveAll(container, dependencyId) {
398
460
  return all;
399
461
  }
400
462
 
401
- export { DEPENDENCY_ID, PROXYDY_CONTAINER, ProxyDiContainer, autoInjectable, inject, resolveAll };
463
+ export { DEPENDENCY_ID, PROXYDY_CONTAINER, ProxyDiContainer, inject, injectable, resolveAll };
package/dist/index.umd.js CHANGED
@@ -5,10 +5,20 @@
5
5
  })(this, (function (exports) { 'use strict';
6
6
 
7
7
  const INJECTIONS = Symbol('injections');
8
+ /**
9
+ * This symbol constant defines a property name.
10
+ * This property is present in each dependency instance that was registered in ProxyDiContainer.
11
+ * The property stores the dependency identifier that should be used to resolve dependency from the container where it was registered.
12
+ */
8
13
  const DEPENDENCY_ID = Symbol('DependencyId');
14
+ /**
15
+ * This symbol constant defines a property name.
16
+ * This property is present in each dependency instance that was registered in ProxyDiContainer.
17
+ * The property stores a reference to the ProxyDiContainer in which the dependency was registered.
18
+ */
19
+ const PROXYDY_CONTAINER = Symbol('proxyDiContainer');
9
20
  const IS_INJECTION_PROXY = Symbol('isInjectionProxy');
10
21
  const INJECTION_OWNER = Symbol('injectionOwner');
11
- const PROXYDY_CONTAINER = Symbol('proxyDiContainer');
12
22
  const IS_INSTANCE_PROXY = Symbol('isInstanceProxy');
13
23
 
14
24
  /**
@@ -41,31 +51,40 @@
41
51
  };
42
52
  };
43
53
 
44
- const autoInjectableClasses = {};
45
- /**
46
- * Registers a class as an auto-injectable for dependency injection.
47
- *
48
- * @param dependencyId - Optional dependency identifier. If omitted, the class name is used.
49
- * @returns A class decorator function.
50
- *
51
- * Note: During dependency resolution, any container that does not have an instance for the specified dependency identifier
52
- * will create an instance of the decorated class. However, if a container already has an instance with that identifier
53
- * prior to resolution, the decorated class will be ignored by that container.
54
- */
55
- const autoInjectable = (dependencyId) => {
54
+ const injectableClasses = {};
55
+ const constructorInjections = {};
56
+ function injectable(dependencyOrDependencies, autoInjecions) {
56
57
  return function (value, context) {
57
- if ((context === null || context === undefined ? undefined : context.kind) === 'class') {
58
- const name = dependencyId ? dependencyId : context.name;
59
- if (autoInjectableClasses[name]) {
60
- throw new Error(`ProxyDi autoInjectable already has dependency ID: ${String(name)}`);
61
- }
62
- autoInjectableClasses[name] = value;
58
+ if ((context === null || context === undefined ? undefined : context.kind) !== 'class') {
59
+ throw new Error('@injectable decorator should decorate classes');
63
60
  }
64
- else {
65
- throw new Error('@autoInjectable decorator should decorate classes');
61
+ const name = dependencyOrDependencies
62
+ ? Array.isArray(dependencyOrDependencies)
63
+ ? context.name
64
+ : dependencyOrDependencies
65
+ : context.name;
66
+ const injectToConstructor = dependencyOrDependencies
67
+ ? Array.isArray(dependencyOrDependencies)
68
+ ? dependencyOrDependencies
69
+ : autoInjecions
70
+ : autoInjecions;
71
+ if (injectableClasses[name]) {
72
+ throw new Error(`ProxyDi has already regisered dependency ${String(name)} by @injectable`);
73
+ }
74
+ injectableClasses[name] = value;
75
+ if (injectToConstructor) {
76
+ constructorInjections[name] = injectToConstructor;
66
77
  }
67
78
  };
68
- };
79
+ }
80
+ function findInjectableId(injectable) {
81
+ for (const [id, DependencyClass] of Object.entries(injectableClasses)) {
82
+ if (DependencyClass === injectable) {
83
+ return id;
84
+ }
85
+ }
86
+ throw new Error(`Class is not @injectable: ${injectable.name}`);
87
+ }
69
88
 
70
89
  const DEFAULT_SETTINGS = {
71
90
  allowRegisterAnything: false,
@@ -132,6 +151,49 @@
132
151
  });
133
152
  }
134
153
 
154
+ function makeConstructorDependencyProxy(container, dependencyId) {
155
+ // let values: Record<string | symbol, any> = {};
156
+ // let wasResolved = false;
157
+ function getDependency() {
158
+ if (container.isKnown(dependencyId)) {
159
+ const dependency = container.resolve(dependencyId);
160
+ // if (!wasResolved) {
161
+ // for (const key in values) {
162
+ // dependency[key] = values[key];
163
+ // }
164
+ // values = {};
165
+ // wasResolved = true;
166
+ // }
167
+ return dependency;
168
+ }
169
+ else {
170
+ throw new Error(`Unknown dependency: ${String(dependencyId)}`);
171
+ }
172
+ }
173
+ return new Proxy({}, {
174
+ get: function (target, prop, receiver) {
175
+ // if (values[prop]) {
176
+ // return values[prop];
177
+ // }
178
+ const dependency = getDependency();
179
+ return Reflect.get(dependency, prop, receiver);
180
+ },
181
+ set: function (_target, prop, value) {
182
+ // if (!container.isKnown(dependencyId)) {
183
+ // values[prop] = value;
184
+ // return true;
185
+ // } else {
186
+ const dependency = getDependency();
187
+ return Reflect.set(dependency, prop, value);
188
+ //}
189
+ },
190
+ has: function (_target, prop) {
191
+ const dependency = getDependency();
192
+ return Reflect.has(dependency, prop);
193
+ },
194
+ });
195
+ }
196
+
135
197
  /**
136
198
  * A dependency injection container
137
199
  */
@@ -167,7 +229,7 @@
167
229
  let dependencyInstance;
168
230
  const isClass = typeof dependency === 'function';
169
231
  if (isClass) {
170
- dependencyInstance = new dependency();
232
+ dependencyInstance = this.createInstance(dependency, dependencyId);
171
233
  }
172
234
  else {
173
235
  dependencyInstance = dependency;
@@ -182,6 +244,15 @@
182
244
  this.registerImpl(dependencyInstance, dependencyId);
183
245
  return dependencyInstance;
184
246
  }
247
+ createInstance(Dependency, dependencyId) {
248
+ const paramIds = constructorInjections[dependencyId] || [];
249
+ const params = [];
250
+ for (const id of paramIds) {
251
+ const param = makeConstructorDependencyProxy(this, id);
252
+ params.push(param);
253
+ }
254
+ return new Dependency(...params);
255
+ }
185
256
  /**
186
257
  * Internal method that implements registeration of dependency and prepare it for injection.
187
258
  * @param dependencyId The unique identifier of the dependency.
@@ -203,39 +274,34 @@
203
274
  return !!(this.parentDependencyProxies[dependencyId] ||
204
275
  this.dependencies[dependencyId] ||
205
276
  (this.parent && this.parent.isKnown(dependencyId)) ||
206
- autoInjectableClasses[dependencyId]);
277
+ injectableClasses[dependencyId]);
207
278
  }
208
- resolve(param) {
209
- if (typeof param === 'function') {
210
- for (const [dependencyId, DependencyClass] of Object.entries(autoInjectableClasses)) {
211
- if (DependencyClass === param) {
212
- return this.resolve(dependencyId);
213
- }
214
- }
215
- throw new Error(`Class is not auto injectable: ${param.name}`);
279
+ resolve(dependency) {
280
+ if (typeof dependency === 'function') {
281
+ const id = findInjectableId(dependency);
282
+ return this.resolve(id);
216
283
  }
217
- if (!this.isKnown(param)) {
218
- throw new Error(`Can't resolve unknown dependency: ${String(param)}`);
284
+ if (!this.isKnown(dependency)) {
285
+ throw new Error(`Can't resolve unknown dependency: ${String(dependency)}`);
219
286
  }
220
- const proxy = this.parentDependencyProxies[param];
287
+ const proxy = this.parentDependencyProxies[dependency];
221
288
  if (proxy) {
222
289
  return proxy;
223
290
  }
224
- const dependency = this.findDependency(param);
225
- if (dependency) {
226
- if (dependency[PROXYDY_CONTAINER] !== this &&
227
- typeof dependency === 'object' &&
291
+ const instance = this.findDependency(dependency);
292
+ if (instance) {
293
+ if (instance[PROXYDY_CONTAINER] !== this &&
294
+ typeof instance === 'object' &&
228
295
  this.settings.resolveInContainerContext) {
229
- const proxy = makeDependencyProxy(dependency);
296
+ const proxy = makeDependencyProxy(instance);
230
297
  this.injectDependenciesTo(proxy);
231
- this.parentDependencyProxies[param] = proxy;
298
+ this.parentDependencyProxies[dependency] = proxy;
232
299
  return proxy;
233
300
  }
234
- return dependency;
301
+ return instance;
235
302
  }
236
- const AutoInjectableClass = autoInjectableClasses[param];
237
- const autoDependency = new AutoInjectableClass();
238
- return this.register(autoDependency, param);
303
+ const InjectableClass = injectableClasses[dependency];
304
+ return this.register(InjectableClass, dependency);
239
305
  }
240
306
  /**
241
307
  * Injects dependencies to the given object based on its defined injections metadata. Does not affect the container.
@@ -380,12 +446,8 @@
380
446
 
381
447
  function resolveAll(instance, dependencyId) {
382
448
  if (typeof dependencyId === 'function') {
383
- for (const [id, DependencyClass] of Object.entries(autoInjectableClasses)) {
384
- if (DependencyClass === dependencyId) {
385
- return resolveAll(instance, id);
386
- }
387
- }
388
- throw new Error(`Class is not auto injectable: ${dependencyId.name}`);
449
+ const id = findInjectableId(dependencyId);
450
+ return resolveAll(instance, id);
389
451
  }
390
452
  const container = instance[PROXYDY_CONTAINER];
391
453
  if (!container) {
@@ -407,8 +469,8 @@
407
469
  exports.DEPENDENCY_ID = DEPENDENCY_ID;
408
470
  exports.PROXYDY_CONTAINER = PROXYDY_CONTAINER;
409
471
  exports.ProxyDiContainer = ProxyDiContainer;
410
- exports.autoInjectable = autoInjectable;
411
472
  exports.inject = inject;
473
+ exports.injectable = injectable;
412
474
  exports.resolveAll = resolveAll;
413
475
 
414
476
  }));
@@ -0,0 +1,17 @@
1
+ import { DependencyClass, DependencyId } from './types';
2
+ export declare const injectableClasses: Record<DependencyId, DependencyClass<any>>;
3
+ export declare const constructorInjections: Record<DependencyId, DependencyId[]>;
4
+ /**
5
+ * Registers a class as an automatically injectable for dependency injection container.
6
+ *
7
+ * @param dependencyId - Optional dependency identifier. If omitted, the class name is used.
8
+ * @param autoInjecions - injections that should be injected to dependency constructor while it creates
9
+ * @returns A class decorator function.
10
+ *
11
+ * Note: During dependency resolution, any container that does not have an instance for the specified dependency identifier
12
+ * will create an instance of the decorated class. However, if a container already has an instance with that identifier
13
+ * prior to resolution, the decorated class will be ignored by that container.
14
+ */
15
+ export declare function injectable(autoInjecions: DependencyId[]): any;
16
+ export declare function injectable(dependencyId?: DependencyId, autoInjecions?: DependencyId[]): any;
17
+ export declare function findInjectableId(injectable: DependencyClass<any>): DependencyId;
@@ -0,0 +1,2 @@
1
+ import { DependencyId, IProxyDiContainer } from './types';
2
+ export declare function makeConstructorDependencyProxy<T>(container: IProxyDiContainer, dependencyId: DependencyId): T;
package/dist/types.d.ts CHANGED
@@ -21,7 +21,18 @@ export type IProxyDiContainer = {
21
21
  destroy: () => void;
22
22
  };
23
23
  export declare const INJECTIONS: unique symbol;
24
+ /**
25
+ * This symbol constant defines a property name.
26
+ * This property is present in each dependency instance that was registered in ProxyDiContainer.
27
+ * The property stores the dependency identifier that should be used to resolve dependency from the container where it was registered.
28
+ */
24
29
  export declare const DEPENDENCY_ID: unique symbol;
30
+ /**
31
+ * This symbol constant defines a property name.
32
+ * This property is present in each dependency instance that was registered in ProxyDiContainer.
33
+ * The property stores a reference to the ProxyDiContainer in which the dependency was registered.
34
+ */
35
+ export declare const PROXYDY_CONTAINER: unique symbol;
25
36
  export type Injections = Record<string | symbol, Injection>;
26
37
  export type Dependency = {
27
38
  [INJECTIONS]: Injections;
@@ -31,7 +42,7 @@ export type Dependency = {
31
42
  */
32
43
  export type ContainerizedDependency = Dependency & {
33
44
  /**
34
- * Unique identifier that could use to resolve this instance
45
+ * Unique identifier that could use to resolve this instance from container where it was registered
35
46
  */
36
47
  [DEPENDENCY_ID]: DependencyId;
37
48
  /**
@@ -46,7 +57,6 @@ export type ContainerSettings = {
46
57
  };
47
58
  export declare const IS_INJECTION_PROXY: unique symbol;
48
59
  export declare const INJECTION_OWNER: unique symbol;
49
- export declare const PROXYDY_CONTAINER: unique symbol;
50
60
  export declare const IS_INSTANCE_PROXY: unique symbol;
51
61
  export type InjectionProxy = {
52
62
  [IS_INJECTION_PROXY]: true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "proxydi",
3
- "version": "0.0.9",
3
+ "version": "0.0.10",
4
4
  "description": "A typed hierarchical DI container that resolves circular dependencies via Proxy",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",