proxydi 0.0.8 → 0.0.9
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 +48 -14
- package/dist/ProxyDiContainer.d.ts +3 -3
- package/dist/index.cjs +28 -3
- package/dist/index.d.ts +2 -1
- package/dist/index.js +28 -4
- package/dist/index.umd.js +28 -3
- package/dist/resolveAll.d.ts +8 -0
- package/dist/types.d.ts +2 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -9,8 +9,9 @@ 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
|
+
- Supports hierarchical containers with the ability to resolve dependencies in both directions
|
|
14
15
|
- Matches dependencies by unique identifiers or automatically using class names and property names
|
|
15
16
|
- Currently under active development, the API may change until version 0.1.0
|
|
16
17
|
|
|
@@ -78,7 +79,7 @@ class Actor {
|
|
|
78
79
|
```
|
|
79
80
|
|
|
80
81
|
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)
|
|
82
|
+
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
83
|
|
|
83
84
|
```typescript
|
|
84
85
|
class Agent007 implements Character {
|
|
@@ -124,7 +125,7 @@ console.log(actor.play());
|
|
|
124
125
|
> 007, I have a new mission for you
|
|
125
126
|
```
|
|
126
127
|
|
|
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
|
|
128
|
+
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
129
|
|
|
129
130
|
> 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
131
|
|
|
@@ -174,7 +175,7 @@ console.log(actor.play());
|
|
|
174
175
|
|
|
175
176
|
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
177
|
|
|
177
|
-
But take a look, our approach is still the same - we just link dependencies
|
|
178
|
+
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
179
|
|
|
179
180
|
## Hierarchy of containers
|
|
180
181
|
|
|
@@ -182,7 +183,7 @@ Another tricky part of DI containers is the ability to create multiple instances
|
|
|
182
183
|
|
|
183
184
|
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
185
|
|
|
185
|
-
For example, imagine you are working on a game level, there are many characters on this level, each character could have many perks.
|
|
186
|
+
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
187
|
|
|
187
188
|
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
189
|
|
|
@@ -199,7 +200,9 @@ const perk = perksContainer.register(new UnderwaterShield(10), 'perk');
|
|
|
199
200
|
|
|
200
201
|
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
202
|
|
|
202
|
-
|
|
203
|
+
### Resolving dependencies from parents
|
|
204
|
+
|
|
205
|
+
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
206
|
|
|
204
207
|
```typescript
|
|
205
208
|
class UnderwaterShield {
|
|
@@ -208,15 +211,38 @@ class UnderwaterShield {
|
|
|
208
211
|
|
|
209
212
|
constructor(private amount: number) {}
|
|
210
213
|
|
|
211
|
-
|
|
212
|
-
this.character.on('hit', this.act);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
act = () =>
|
|
214
|
+
activate = () =>
|
|
216
215
|
this.level.isUnderwater && (this.character.health += this.amount);
|
|
217
216
|
}
|
|
218
217
|
```
|
|
219
218
|
|
|
219
|
+
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
|
+
|
|
221
|
+
## Resolving dependencies from children
|
|
222
|
+
|
|
223
|
+
Backward bonus of containers hierarchy, each top level dependency is free to use all dependency from the bottom:
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
class Character {
|
|
227
|
+
public health = 100;
|
|
228
|
+
|
|
229
|
+
hit(abount: number) {
|
|
230
|
+
this.health -= abount;
|
|
231
|
+
|
|
232
|
+
const perks = resolveAll<Perk>(this, 'perk');
|
|
233
|
+
perks.forEach((perk) => perk.activate());
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
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.
|
|
239
|
+
|
|
240
|
+
### Reference to the container
|
|
241
|
+
|
|
242
|
+
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.
|
|
243
|
+
|
|
244
|
+
Despite this explanation is a little bit complicated, the example is still simple, the character just activates all its perks.
|
|
245
|
+
|
|
220
246
|
## Rewriting dependencies
|
|
221
247
|
|
|
222
248
|
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 +279,18 @@ Therefore, after the container has been baked, the performance impact becomes ze
|
|
|
253
279
|
|
|
254
280
|
To be continued...
|
|
255
281
|
|
|
256
|
-
##
|
|
282
|
+
## Motivation
|
|
257
283
|
|
|
258
|
-
|
|
284
|
+
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.
|
|
285
|
+
|
|
286
|
+
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.
|
|
287
|
+
|
|
288
|
+
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
289
|
|
|
260
290
|
## Contributing
|
|
261
291
|
|
|
262
|
-
Contribution documentation is not ready yet but is planned. Feel free to contribute even now though! :)
|
|
292
|
+
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! :)
|
|
293
|
+
|
|
294
|
+
## License
|
|
295
|
+
|
|
296
|
+
This project is licensed under the terms of the MIT License. See the [LICENSE](./LICENSE) file for details.
|
|
@@ -44,8 +44,8 @@ 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
49
|
/**
|
|
50
50
|
* Internal method that implements registeration of dependency and prepare it for injection.
|
|
51
51
|
* @param dependencyId The unique identifier of the dependency.
|
|
@@ -65,7 +65,7 @@ export declare class ProxyDiContainer implements IProxyDiContainer {
|
|
|
65
65
|
* @throws Error if the dependency cannot be found or is not auto injectable.
|
|
66
66
|
*/
|
|
67
67
|
resolve<T>(dependencyId: DependencyId): T & ContainerizedDependency;
|
|
68
|
-
resolve<T extends new (...args: any[]) => any>(SomeClass: T): InstanceType<T
|
|
68
|
+
resolve<T extends new (...args: any[]) => any>(SomeClass: T): InstanceType<T> & ContainerizedDependency;
|
|
69
69
|
/**
|
|
70
70
|
* Injects dependencies to the given object based on its defined injections metadata. Does not affect the container.
|
|
71
71
|
* @param injectionsOwner The object to inject dependencies into.
|
package/dist/index.cjs
CHANGED
|
@@ -231,9 +231,7 @@ class ProxyDiContainer {
|
|
|
231
231
|
}
|
|
232
232
|
const AutoInjectableClass = autoInjectableClasses[param];
|
|
233
233
|
const autoDependency = new AutoInjectableClass();
|
|
234
|
-
this.
|
|
235
|
-
this.dependencies[param] = autoDependency;
|
|
236
|
-
return autoDependency;
|
|
234
|
+
return this.register(autoDependency, param);
|
|
237
235
|
}
|
|
238
236
|
/**
|
|
239
237
|
* Injects dependencies to the given object based on its defined injections metadata. Does not affect the container.
|
|
@@ -376,8 +374,35 @@ function isDependency(dependencyOrId) {
|
|
|
376
374
|
!!dependencyOrId[DEPENDENCY_ID]);
|
|
377
375
|
}
|
|
378
376
|
|
|
377
|
+
function resolveAll(instance, dependencyId) {
|
|
378
|
+
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}`);
|
|
385
|
+
}
|
|
386
|
+
const container = instance[PROXYDY_CONTAINER];
|
|
387
|
+
if (!container) {
|
|
388
|
+
throw new Error('Instance is not registered in any container');
|
|
389
|
+
}
|
|
390
|
+
return recursiveResolveAll(container, dependencyId);
|
|
391
|
+
}
|
|
392
|
+
function recursiveResolveAll(container, dependencyId) {
|
|
393
|
+
let all = container.isKnown(dependencyId)
|
|
394
|
+
? [container.resolve(dependencyId)]
|
|
395
|
+
: [];
|
|
396
|
+
for (const child of container.children) {
|
|
397
|
+
const allChild = recursiveResolveAll(child, dependencyId);
|
|
398
|
+
all = all.concat(allChild);
|
|
399
|
+
}
|
|
400
|
+
return all;
|
|
401
|
+
}
|
|
402
|
+
|
|
379
403
|
exports.DEPENDENCY_ID = DEPENDENCY_ID;
|
|
380
404
|
exports.PROXYDY_CONTAINER = PROXYDY_CONTAINER;
|
|
381
405
|
exports.ProxyDiContainer = ProxyDiContainer;
|
|
382
406
|
exports.autoInjectable = autoInjectable;
|
|
383
407
|
exports.inject = inject;
|
|
408
|
+
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
3
|
export { autoInjectable } from './autoInjectable';
|
|
4
|
-
export { Injection, DependencyId, DependencyClass, ContainerSettings
|
|
4
|
+
export { Injection, DependencyId, DependencyClass, ContainerSettings, PROXYDY_CONTAINER, DEPENDENCY_ID, } from './types';
|
|
5
|
+
export { resolveAll } from './resolveAll';
|
package/dist/index.js
CHANGED
|
@@ -229,9 +229,7 @@ class ProxyDiContainer {
|
|
|
229
229
|
}
|
|
230
230
|
const AutoInjectableClass = autoInjectableClasses[param];
|
|
231
231
|
const autoDependency = new AutoInjectableClass();
|
|
232
|
-
this.
|
|
233
|
-
this.dependencies[param] = autoDependency;
|
|
234
|
-
return autoDependency;
|
|
232
|
+
return this.register(autoDependency, param);
|
|
235
233
|
}
|
|
236
234
|
/**
|
|
237
235
|
* Injects dependencies to the given object based on its defined injections metadata. Does not affect the container.
|
|
@@ -374,4 +372,30 @@ function isDependency(dependencyOrId) {
|
|
|
374
372
|
!!dependencyOrId[DEPENDENCY_ID]);
|
|
375
373
|
}
|
|
376
374
|
|
|
377
|
-
|
|
375
|
+
function resolveAll(instance, dependencyId) {
|
|
376
|
+
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}`);
|
|
383
|
+
}
|
|
384
|
+
const container = instance[PROXYDY_CONTAINER];
|
|
385
|
+
if (!container) {
|
|
386
|
+
throw new Error('Instance is not registered in any container');
|
|
387
|
+
}
|
|
388
|
+
return recursiveResolveAll(container, dependencyId);
|
|
389
|
+
}
|
|
390
|
+
function recursiveResolveAll(container, dependencyId) {
|
|
391
|
+
let all = container.isKnown(dependencyId)
|
|
392
|
+
? [container.resolve(dependencyId)]
|
|
393
|
+
: [];
|
|
394
|
+
for (const child of container.children) {
|
|
395
|
+
const allChild = recursiveResolveAll(child, dependencyId);
|
|
396
|
+
all = all.concat(allChild);
|
|
397
|
+
}
|
|
398
|
+
return all;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
export { DEPENDENCY_ID, PROXYDY_CONTAINER, ProxyDiContainer, autoInjectable, inject, resolveAll };
|
package/dist/index.umd.js
CHANGED
|
@@ -235,9 +235,7 @@
|
|
|
235
235
|
}
|
|
236
236
|
const AutoInjectableClass = autoInjectableClasses[param];
|
|
237
237
|
const autoDependency = new AutoInjectableClass();
|
|
238
|
-
this.
|
|
239
|
-
this.dependencies[param] = autoDependency;
|
|
240
|
-
return autoDependency;
|
|
238
|
+
return this.register(autoDependency, param);
|
|
241
239
|
}
|
|
242
240
|
/**
|
|
243
241
|
* Injects dependencies to the given object based on its defined injections metadata. Does not affect the container.
|
|
@@ -380,10 +378,37 @@
|
|
|
380
378
|
!!dependencyOrId[DEPENDENCY_ID]);
|
|
381
379
|
}
|
|
382
380
|
|
|
381
|
+
function resolveAll(instance, dependencyId) {
|
|
382
|
+
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}`);
|
|
389
|
+
}
|
|
390
|
+
const container = instance[PROXYDY_CONTAINER];
|
|
391
|
+
if (!container) {
|
|
392
|
+
throw new Error('Instance is not registered in any container');
|
|
393
|
+
}
|
|
394
|
+
return recursiveResolveAll(container, dependencyId);
|
|
395
|
+
}
|
|
396
|
+
function recursiveResolveAll(container, dependencyId) {
|
|
397
|
+
let all = container.isKnown(dependencyId)
|
|
398
|
+
? [container.resolve(dependencyId)]
|
|
399
|
+
: [];
|
|
400
|
+
for (const child of container.children) {
|
|
401
|
+
const allChild = recursiveResolveAll(child, dependencyId);
|
|
402
|
+
all = all.concat(allChild);
|
|
403
|
+
}
|
|
404
|
+
return all;
|
|
405
|
+
}
|
|
406
|
+
|
|
383
407
|
exports.DEPENDENCY_ID = DEPENDENCY_ID;
|
|
384
408
|
exports.PROXYDY_CONTAINER = PROXYDY_CONTAINER;
|
|
385
409
|
exports.ProxyDiContainer = ProxyDiContainer;
|
|
386
410
|
exports.autoInjectable = autoInjectable;
|
|
387
411
|
exports.inject = inject;
|
|
412
|
+
exports.resolveAll = resolveAll;
|
|
388
413
|
|
|
389
414
|
}));
|
|
@@ -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,6 +14,8 @@ 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;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "proxydi",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.9",
|
|
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
|
+
}
|