proxydi 0.0.8 → 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 +52 -15
- package/dist/ProxyDiContainer.d.ts +4 -3
- package/dist/index.cjs +135 -48
- package/dist/index.d.ts +3 -2
- package/dist/index.js +134 -48
- package/dist/index.umd.js +135 -48
- package/dist/injectable.d.ts +17 -0
- package/dist/proxy.constuctor.d.ts +2 -0
- package/dist/resolveAll.d.ts +8 -0
- package/dist/types.d.ts +14 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -9,11 +9,15 @@ A typed hierarchical DI container that resolves circular dependencies via Proxy.
|
|
|
9
9
|
Core features:
|
|
10
10
|
|
|
11
11
|
- Uses Stage 3 decorators, supported in TypeScript 5.x ([examples repository](https://github.com/proxy-di/node-ts-examples)) and Babel via babel-plugin-proposal-decorators ([examples repository](https://github.com/proxy-di/node-babel-examples))
|
|
12
|
-
- Automatically resolves circular dependencies with no
|
|
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
15
|
- Currently under active development, the API may change until version 0.1.0
|
|
16
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
|
+
|
|
17
21
|
## Quick start
|
|
18
22
|
|
|
19
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)
|
|
@@ -78,7 +82,7 @@ class Actor {
|
|
|
78
82
|
```
|
|
79
83
|
|
|
80
84
|
2. Next, create the [ProxyDiContainer](https://proxy-di.github.io/proxydi/classes/ProxyDiContainer.html)
|
|
81
|
-
and fill it with dependencies using [register()](https://proxy-di.github.io/proxydi/classes/ProxyDiContainer.html#register)
|
|
85
|
+
and fill it with dependencies using [register()](https://proxy-di.github.io/proxydi/classes/ProxyDiContainer.html#register) method. For example, let's define an agent 007 role and prepare our first container:
|
|
82
86
|
|
|
83
87
|
```typescript
|
|
84
88
|
class Agent007 implements Character {
|
|
@@ -124,7 +128,7 @@ console.log(actor.play());
|
|
|
124
128
|
> 007, I have a new mission for you
|
|
125
129
|
```
|
|
126
130
|
|
|
127
|
-
In this example, we changed the behavior of the actor by changing the role dependency in the ProxyDi container. This is exactly the goal of the [Dependency inversion principle](https://en.wikipedia.org/wiki/Dependency_inversion_principle) in SOLID approach to design
|
|
131
|
+
In this example, we changed the behavior of the actor by changing the role dependency in the ProxyDi container. This is exactly the goal of the [Dependency inversion principle](https://en.wikipedia.org/wiki/Dependency_inversion_principle) in the SOLID approach to software design. Continuing our metaphor, the actor can play any role, but he is not the one who decides which role he will play. This is a film director's decision, and here we just cosplay him by setting up our containers.
|
|
128
132
|
|
|
129
133
|
> So, ProxyDi is just a tool to link dependencies, allowing them to freely communicate with each other without worrying about which specific dependency they're dealing with. And nothing more.
|
|
130
134
|
|
|
@@ -174,7 +178,7 @@ console.log(actor.play());
|
|
|
174
178
|
|
|
175
179
|
In traditional DI containers, this scene would be tricky to shoot - the Director calls Actor's methods while Actor simultaneously needs Director's guidance.
|
|
176
180
|
|
|
177
|
-
But take a look, our approach is still the same - we just link dependencies
|
|
181
|
+
But take a look, our approach is still the same - we just link dependencies with @inject and use them freely without any worries. ProxyDi handles this tricky issue as elegantly as possible. It does this using JavaScript Proxies, more about Proxies and their impact on performance [later](#injection-proxy-performance).
|
|
178
182
|
|
|
179
183
|
## Hierarchy of containers
|
|
180
184
|
|
|
@@ -182,7 +186,7 @@ Another tricky part of DI containers is the ability to create multiple instances
|
|
|
182
186
|
|
|
183
187
|
Instead, it suggests you to use a hierarchy of containers by using [createChildContainer()](https://proxy-di.github.io/proxydi/classes/ProxyDiContainer.html#createchildcontainer) method. Child container inherits all parent settings and can resolve exactly the same dependencies as their parent (but parent container does not have access to dependencies registered in its children).
|
|
184
188
|
|
|
185
|
-
For example, imagine you are working on a game level, there are many characters on this level, each character could have many perks.
|
|
189
|
+
For example, imagine you are working on a game level, there are many characters on this level, and each character could have many perks.
|
|
186
190
|
|
|
187
191
|
With ProxyDi we can present all these stuff in a hierarchy of containers. The most top container holds information about game level, the most bottom ones hold information about perks:
|
|
188
192
|
|
|
@@ -199,7 +203,9 @@ const perk = perksContainer.register(new UnderwaterShield(10), 'perk');
|
|
|
199
203
|
|
|
200
204
|
This is not how I propose to design games, ECS pattern does it better, but the goal was to demonstrate that with this approach instead of creating bunches of instances to represent your project entities, you can create bunches of containers each of them containing instances related to each other.
|
|
201
205
|
|
|
202
|
-
|
|
206
|
+
### Resolving dependencies from parents
|
|
207
|
+
|
|
208
|
+
As a bonus, each bottom level dependency is free to use any dependency from the top just as if they were registered in its own container:
|
|
203
209
|
|
|
204
210
|
```typescript
|
|
205
211
|
class UnderwaterShield {
|
|
@@ -208,15 +214,38 @@ class UnderwaterShield {
|
|
|
208
214
|
|
|
209
215
|
constructor(private amount: number) {}
|
|
210
216
|
|
|
211
|
-
|
|
212
|
-
this.character.on('hit', this.act);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
act = () =>
|
|
217
|
+
activate = () =>
|
|
216
218
|
this.level.isUnderwater && (this.character.health += this.amount);
|
|
217
219
|
}
|
|
218
220
|
```
|
|
219
221
|
|
|
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.
|
|
223
|
+
|
|
224
|
+
### Resolving dependencies from children
|
|
225
|
+
|
|
226
|
+
Backward bonus of containers hierarchy, each top level dependency is free to use all dependency from the bottom:
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
class Character {
|
|
230
|
+
public health = 100;
|
|
231
|
+
|
|
232
|
+
hit(abount: number) {
|
|
233
|
+
this.health -= abount;
|
|
234
|
+
|
|
235
|
+
const perks = resolveAll<Perk>(this, 'perk');
|
|
236
|
+
perks.forEach((perk) => perk.activate());
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
In this example, the character activates all its perks, which are registered in all children containers. The way it do this job need a little bit more explanation.
|
|
242
|
+
|
|
243
|
+
### Reference to the container
|
|
244
|
+
|
|
245
|
+
Here you should be wondering, how [resolveAll()](https://proxy-di.github.io/proxydi/functions/resolveAll.html.html) function knows about the container, to which character belongs. The answer - each time when dependency is registered in the ProxyDiContainer, it saves a reference to itself in this dependency instance. So, when you call resolveAll() function, it just takes this reference from the instance and then recursively resolves all asked dependencies from this container and all its children and children of children and so on.
|
|
246
|
+
|
|
247
|
+
Despite this explanation is a little bit complicated, the example is still simple, the character just activates all its perks.
|
|
248
|
+
|
|
220
249
|
## Rewriting dependencies
|
|
221
250
|
|
|
222
251
|
By default, ProxyDi doesn't allow rewriting dependencies in the container. After a dependency becomes known to the container, any attempt to register a new dependency with the same dependency ID will throw an Error:
|
|
@@ -253,10 +282,18 @@ Therefore, after the container has been baked, the performance impact becomes ze
|
|
|
253
282
|
|
|
254
283
|
To be continued...
|
|
255
284
|
|
|
256
|
-
##
|
|
285
|
+
## Motivation
|
|
257
286
|
|
|
258
|
-
|
|
287
|
+
The world and software changes, they become more complex over time. But the main imperative in software development stays the same - managing the complexity. Despite the tendency that software is written more often by artificial intelligence than humans, complexity stays complexity. The less complex conceptions any kind of intelligence should operate, the more efficient it will be.
|
|
288
|
+
|
|
289
|
+
The main goal of ProxyDi is to decrease complexity in the very small field of connecting different entities of software code between each other, no matter by whom this code was written. This is my attempt to make the linking of dependencies as transparent and simple as possible for developers.
|
|
290
|
+
|
|
291
|
+
Also, I'm tired of moving this-like code from one project to another. I hope that ProxyDi will be a good enough solution for this problem not only for me. To be honest, this is the 4th attempt to create an "ideal" DI container and my 1st that uses Stage 3 decorators. I hope, this one will be the last one. With your help :) For TS/JS technology stack, of course!
|
|
259
292
|
|
|
260
293
|
## Contributing
|
|
261
294
|
|
|
262
|
-
Contribution documentation is not ready yet but is planned. Feel free to contribute even now though! :)
|
|
295
|
+
Any reviews, comments, ideas, issues, and pull requests are welcome. Contribution documentation is not ready yet but is planned. Feel free to contribute even now though! :)
|
|
296
|
+
|
|
297
|
+
## License
|
|
298
|
+
|
|
299
|
+
This project is licensed under the terms of the MIT License. See the [LICENSE](./LICENSE) file for details.
|
|
@@ -44,8 +44,9 @@ export declare class ProxyDiContainer implements IProxyDiContainer {
|
|
|
44
44
|
* @throws Error if dependency is already registered and rewriting is not allowed or if invalid dependency (not object) is provided and this it now allowed.
|
|
45
45
|
* @returns Dependency instance, registered in container
|
|
46
46
|
*/
|
|
47
|
-
register<T>(DependencyClass: DependencyClass<T>, dependencyId: DependencyId): T;
|
|
48
|
-
register<T>(dependency: T, dependencyId: DependencyId): T;
|
|
47
|
+
register<T>(DependencyClass: DependencyClass<T>, dependencyId: DependencyId): T & ContainerizedDependency;
|
|
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
|
|
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
|
|
41
|
-
|
|
42
|
-
|
|
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)
|
|
54
|
-
|
|
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
|
-
|
|
61
|
-
|
|
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 =
|
|
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,41 +270,34 @@ class ProxyDiContainer {
|
|
|
199
270
|
return !!(this.parentDependencyProxies[dependencyId] ||
|
|
200
271
|
this.dependencies[dependencyId] ||
|
|
201
272
|
(this.parent && this.parent.isKnown(dependencyId)) ||
|
|
202
|
-
|
|
273
|
+
injectableClasses[dependencyId]);
|
|
203
274
|
}
|
|
204
|
-
resolve(
|
|
205
|
-
if (typeof
|
|
206
|
-
|
|
207
|
-
|
|
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(
|
|
214
|
-
throw new Error(`Can't resolve unknown dependency: ${String(
|
|
280
|
+
if (!this.isKnown(dependency)) {
|
|
281
|
+
throw new Error(`Can't resolve unknown dependency: ${String(dependency)}`);
|
|
215
282
|
}
|
|
216
|
-
const proxy = this.parentDependencyProxies[
|
|
283
|
+
const proxy = this.parentDependencyProxies[dependency];
|
|
217
284
|
if (proxy) {
|
|
218
285
|
return proxy;
|
|
219
286
|
}
|
|
220
|
-
const
|
|
221
|
-
if (
|
|
222
|
-
if (
|
|
223
|
-
typeof
|
|
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(
|
|
292
|
+
const proxy = makeDependencyProxy(instance);
|
|
226
293
|
this.injectDependenciesTo(proxy);
|
|
227
|
-
this.parentDependencyProxies[
|
|
294
|
+
this.parentDependencyProxies[dependency] = proxy;
|
|
228
295
|
return proxy;
|
|
229
296
|
}
|
|
230
|
-
return
|
|
297
|
+
return instance;
|
|
231
298
|
}
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
this.registerImpl(autoDependency, param);
|
|
235
|
-
this.dependencies[param] = autoDependency;
|
|
236
|
-
return autoDependency;
|
|
299
|
+
const InjectableClass = injectableClasses[dependency];
|
|
300
|
+
return this.register(InjectableClass, dependency);
|
|
237
301
|
}
|
|
238
302
|
/**
|
|
239
303
|
* Injects dependencies to the given object based on its defined injections metadata. Does not affect the container.
|
|
@@ -376,8 +440,31 @@ function isDependency(dependencyOrId) {
|
|
|
376
440
|
!!dependencyOrId[DEPENDENCY_ID]);
|
|
377
441
|
}
|
|
378
442
|
|
|
443
|
+
function resolveAll(instance, dependencyId) {
|
|
444
|
+
if (typeof dependencyId === 'function') {
|
|
445
|
+
const id = findInjectableId(dependencyId);
|
|
446
|
+
return resolveAll(instance, id);
|
|
447
|
+
}
|
|
448
|
+
const container = instance[PROXYDY_CONTAINER];
|
|
449
|
+
if (!container) {
|
|
450
|
+
throw new Error('Instance is not registered in any container');
|
|
451
|
+
}
|
|
452
|
+
return recursiveResolveAll(container, dependencyId);
|
|
453
|
+
}
|
|
454
|
+
function recursiveResolveAll(container, dependencyId) {
|
|
455
|
+
let all = container.isKnown(dependencyId)
|
|
456
|
+
? [container.resolve(dependencyId)]
|
|
457
|
+
: [];
|
|
458
|
+
for (const child of container.children) {
|
|
459
|
+
const allChild = recursiveResolveAll(child, dependencyId);
|
|
460
|
+
all = all.concat(allChild);
|
|
461
|
+
}
|
|
462
|
+
return all;
|
|
463
|
+
}
|
|
464
|
+
|
|
379
465
|
exports.DEPENDENCY_ID = DEPENDENCY_ID;
|
|
380
466
|
exports.PROXYDY_CONTAINER = PROXYDY_CONTAINER;
|
|
381
467
|
exports.ProxyDiContainer = ProxyDiContainer;
|
|
382
|
-
exports.autoInjectable = autoInjectable;
|
|
383
468
|
exports.inject = inject;
|
|
469
|
+
exports.injectable = injectable;
|
|
470
|
+
exports.resolveAll = resolveAll;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { inject } from './inject';
|
|
2
2
|
export { ProxyDiContainer } from './ProxyDiContainer';
|
|
3
|
-
export {
|
|
4
|
-
export { Injection, DependencyId, DependencyClass, ContainerSettings
|
|
3
|
+
export { injectable } from './injectable';
|
|
4
|
+
export { Injection, DependencyId, DependencyClass, ContainerSettings, PROXYDY_CONTAINER, DEPENDENCY_ID, } from './types';
|
|
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
|
|
39
|
-
|
|
40
|
-
|
|
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)
|
|
52
|
-
|
|
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
|
-
|
|
59
|
-
|
|
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 =
|
|
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,41 +268,34 @@ class ProxyDiContainer {
|
|
|
197
268
|
return !!(this.parentDependencyProxies[dependencyId] ||
|
|
198
269
|
this.dependencies[dependencyId] ||
|
|
199
270
|
(this.parent && this.parent.isKnown(dependencyId)) ||
|
|
200
|
-
|
|
271
|
+
injectableClasses[dependencyId]);
|
|
201
272
|
}
|
|
202
|
-
resolve(
|
|
203
|
-
if (typeof
|
|
204
|
-
|
|
205
|
-
|
|
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(
|
|
212
|
-
throw new Error(`Can't resolve unknown dependency: ${String(
|
|
278
|
+
if (!this.isKnown(dependency)) {
|
|
279
|
+
throw new Error(`Can't resolve unknown dependency: ${String(dependency)}`);
|
|
213
280
|
}
|
|
214
|
-
const proxy = this.parentDependencyProxies[
|
|
281
|
+
const proxy = this.parentDependencyProxies[dependency];
|
|
215
282
|
if (proxy) {
|
|
216
283
|
return proxy;
|
|
217
284
|
}
|
|
218
|
-
const
|
|
219
|
-
if (
|
|
220
|
-
if (
|
|
221
|
-
typeof
|
|
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(
|
|
290
|
+
const proxy = makeDependencyProxy(instance);
|
|
224
291
|
this.injectDependenciesTo(proxy);
|
|
225
|
-
this.parentDependencyProxies[
|
|
292
|
+
this.parentDependencyProxies[dependency] = proxy;
|
|
226
293
|
return proxy;
|
|
227
294
|
}
|
|
228
|
-
return
|
|
295
|
+
return instance;
|
|
229
296
|
}
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
this.registerImpl(autoDependency, param);
|
|
233
|
-
this.dependencies[param] = autoDependency;
|
|
234
|
-
return autoDependency;
|
|
297
|
+
const InjectableClass = injectableClasses[dependency];
|
|
298
|
+
return this.register(InjectableClass, dependency);
|
|
235
299
|
}
|
|
236
300
|
/**
|
|
237
301
|
* Injects dependencies to the given object based on its defined injections metadata. Does not affect the container.
|
|
@@ -374,4 +438,26 @@ function isDependency(dependencyOrId) {
|
|
|
374
438
|
!!dependencyOrId[DEPENDENCY_ID]);
|
|
375
439
|
}
|
|
376
440
|
|
|
377
|
-
|
|
441
|
+
function resolveAll(instance, dependencyId) {
|
|
442
|
+
if (typeof dependencyId === 'function') {
|
|
443
|
+
const id = findInjectableId(dependencyId);
|
|
444
|
+
return resolveAll(instance, id);
|
|
445
|
+
}
|
|
446
|
+
const container = instance[PROXYDY_CONTAINER];
|
|
447
|
+
if (!container) {
|
|
448
|
+
throw new Error('Instance is not registered in any container');
|
|
449
|
+
}
|
|
450
|
+
return recursiveResolveAll(container, dependencyId);
|
|
451
|
+
}
|
|
452
|
+
function recursiveResolveAll(container, dependencyId) {
|
|
453
|
+
let all = container.isKnown(dependencyId)
|
|
454
|
+
? [container.resolve(dependencyId)]
|
|
455
|
+
: [];
|
|
456
|
+
for (const child of container.children) {
|
|
457
|
+
const allChild = recursiveResolveAll(child, dependencyId);
|
|
458
|
+
all = all.concat(allChild);
|
|
459
|
+
}
|
|
460
|
+
return all;
|
|
461
|
+
}
|
|
462
|
+
|
|
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
|
|
45
|
-
|
|
46
|
-
|
|
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)
|
|
58
|
-
|
|
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
|
-
|
|
65
|
-
|
|
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 =
|
|
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,41 +274,34 @@
|
|
|
203
274
|
return !!(this.parentDependencyProxies[dependencyId] ||
|
|
204
275
|
this.dependencies[dependencyId] ||
|
|
205
276
|
(this.parent && this.parent.isKnown(dependencyId)) ||
|
|
206
|
-
|
|
277
|
+
injectableClasses[dependencyId]);
|
|
207
278
|
}
|
|
208
|
-
resolve(
|
|
209
|
-
if (typeof
|
|
210
|
-
|
|
211
|
-
|
|
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(
|
|
218
|
-
throw new Error(`Can't resolve unknown dependency: ${String(
|
|
284
|
+
if (!this.isKnown(dependency)) {
|
|
285
|
+
throw new Error(`Can't resolve unknown dependency: ${String(dependency)}`);
|
|
219
286
|
}
|
|
220
|
-
const proxy = this.parentDependencyProxies[
|
|
287
|
+
const proxy = this.parentDependencyProxies[dependency];
|
|
221
288
|
if (proxy) {
|
|
222
289
|
return proxy;
|
|
223
290
|
}
|
|
224
|
-
const
|
|
225
|
-
if (
|
|
226
|
-
if (
|
|
227
|
-
typeof
|
|
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(
|
|
296
|
+
const proxy = makeDependencyProxy(instance);
|
|
230
297
|
this.injectDependenciesTo(proxy);
|
|
231
|
-
this.parentDependencyProxies[
|
|
298
|
+
this.parentDependencyProxies[dependency] = proxy;
|
|
232
299
|
return proxy;
|
|
233
300
|
}
|
|
234
|
-
return
|
|
301
|
+
return instance;
|
|
235
302
|
}
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
this.registerImpl(autoDependency, param);
|
|
239
|
-
this.dependencies[param] = autoDependency;
|
|
240
|
-
return autoDependency;
|
|
303
|
+
const InjectableClass = injectableClasses[dependency];
|
|
304
|
+
return this.register(InjectableClass, dependency);
|
|
241
305
|
}
|
|
242
306
|
/**
|
|
243
307
|
* Injects dependencies to the given object based on its defined injections metadata. Does not affect the container.
|
|
@@ -380,10 +444,33 @@
|
|
|
380
444
|
!!dependencyOrId[DEPENDENCY_ID]);
|
|
381
445
|
}
|
|
382
446
|
|
|
447
|
+
function resolveAll(instance, dependencyId) {
|
|
448
|
+
if (typeof dependencyId === 'function') {
|
|
449
|
+
const id = findInjectableId(dependencyId);
|
|
450
|
+
return resolveAll(instance, id);
|
|
451
|
+
}
|
|
452
|
+
const container = instance[PROXYDY_CONTAINER];
|
|
453
|
+
if (!container) {
|
|
454
|
+
throw new Error('Instance is not registered in any container');
|
|
455
|
+
}
|
|
456
|
+
return recursiveResolveAll(container, dependencyId);
|
|
457
|
+
}
|
|
458
|
+
function recursiveResolveAll(container, dependencyId) {
|
|
459
|
+
let all = container.isKnown(dependencyId)
|
|
460
|
+
? [container.resolve(dependencyId)]
|
|
461
|
+
: [];
|
|
462
|
+
for (const child of container.children) {
|
|
463
|
+
const allChild = recursiveResolveAll(child, dependencyId);
|
|
464
|
+
all = all.concat(allChild);
|
|
465
|
+
}
|
|
466
|
+
return all;
|
|
467
|
+
}
|
|
468
|
+
|
|
383
469
|
exports.DEPENDENCY_ID = DEPENDENCY_ID;
|
|
384
470
|
exports.PROXYDY_CONTAINER = PROXYDY_CONTAINER;
|
|
385
471
|
exports.ProxyDiContainer = ProxyDiContainer;
|
|
386
|
-
exports.autoInjectable = autoInjectable;
|
|
387
472
|
exports.inject = inject;
|
|
473
|
+
exports.injectable = injectable;
|
|
474
|
+
exports.resolveAll = resolveAll;
|
|
388
475
|
|
|
389
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,8 @@
|
|
|
1
|
+
import { ContainerizedDependency, DependencyId } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Resolves all dependencies from this container and it's children either by its dependency ID or through a class constructor for auto-injectable classes.
|
|
4
|
+
* @param param The dependency ID or class constructor.
|
|
5
|
+
* @returns Array with all founded dependence instances, could be empty
|
|
6
|
+
*/
|
|
7
|
+
export declare function resolveAll<T>(instance: any, dependencyId: DependencyId): (T & ContainerizedDependency)[];
|
|
8
|
+
export declare function resolveAll<T extends new (...args: any[]) => any>(instance: any, SomeClass: T): (InstanceType<T> & ContainerizedDependency)[];
|
package/dist/types.d.ts
CHANGED
|
@@ -14,12 +14,25 @@ export type IProxyDiContainer = {
|
|
|
14
14
|
register: (dependency: any, dependencyId: DependencyId) => any;
|
|
15
15
|
resolve: <T>(dependencyId: DependencyId) => T & ContainerizedDependency;
|
|
16
16
|
createChildContainer: () => IProxyDiContainer;
|
|
17
|
+
children: IProxyDiContainer[];
|
|
18
|
+
getChild(id: number): IProxyDiContainer;
|
|
17
19
|
remove: (dependencyId: DependencyId | ContainerizedDependency) => void;
|
|
18
20
|
bakeInjections(): void;
|
|
19
21
|
destroy: () => void;
|
|
20
22
|
};
|
|
21
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
|
+
*/
|
|
22
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;
|
|
23
36
|
export type Injections = Record<string | symbol, Injection>;
|
|
24
37
|
export type Dependency = {
|
|
25
38
|
[INJECTIONS]: Injections;
|
|
@@ -29,7 +42,7 @@ export type Dependency = {
|
|
|
29
42
|
*/
|
|
30
43
|
export type ContainerizedDependency = Dependency & {
|
|
31
44
|
/**
|
|
32
|
-
* 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
|
|
33
46
|
*/
|
|
34
47
|
[DEPENDENCY_ID]: DependencyId;
|
|
35
48
|
/**
|
|
@@ -44,7 +57,6 @@ export type ContainerSettings = {
|
|
|
44
57
|
};
|
|
45
58
|
export declare const IS_INJECTION_PROXY: unique symbol;
|
|
46
59
|
export declare const INJECTION_OWNER: unique symbol;
|
|
47
|
-
export declare const PROXYDY_CONTAINER: unique symbol;
|
|
48
60
|
export declare const IS_INSTANCE_PROXY: unique symbol;
|
|
49
61
|
export type InjectionProxy = {
|
|
50
62
|
[IS_INJECTION_PROXY]: true;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "proxydi",
|
|
3
|
-
"version": "0.0.
|
|
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",
|
|
@@ -57,4 +57,4 @@
|
|
|
57
57
|
"typescript": "^5.7.3",
|
|
58
58
|
"vitest": "^3.0.4"
|
|
59
59
|
}
|
|
60
|
-
}
|
|
60
|
+
}
|