proxydi 0.0.4 → 0.0.5
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 +100 -24
- package/dist/Proxy.utils.d.ts +5 -3
- package/dist/ProxyDI.d.ts +16 -16
- package/dist/ProxyDiContainer.d.ts +28 -0
- package/dist/autoInjectable.d.ts +13 -0
- package/dist/autoInjectableService.d.ts +13 -0
- package/dist/index.cjs +180 -149
- package/dist/index.d.ts +3 -3
- package/dist/index.js +179 -148
- package/dist/index.umd.js +180 -149
- package/dist/inject.d.ts +10 -2
- package/dist/presets.d.ts +3 -3
- package/dist/types.d.ts +37 -34
- package/package.json +3 -2
- package/.github/workflows/coverage_badge.yml +0 -32
package/README.md
CHANGED
|
@@ -1,25 +1,29 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ProxyDi
|
|
2
2
|
|
|
3
|
-
[](https://coveralls.io/github/proxy-di/proxydi?branch=main)
|
|
3
|
+
[](https://coveralls.io/github/proxy-di/proxydi?branch=main&v=0.0.5)
|
|
4
4
|
|
|
5
|
-
A typed DI container that resolves circular dependencies via Proxy.
|
|
5
|
+
A typed hierarchical DI container that resolves circular dependencies via Proxy.
|
|
6
6
|
|
|
7
|
-
Core
|
|
7
|
+
Core features:
|
|
8
8
|
|
|
9
9
|
- Uses Stage 3 decorators (supported in TypeScript 5.x and babel-plugin-proposal-decorators)
|
|
10
10
|
- Automatically resolves circular dependencies
|
|
11
|
-
-
|
|
12
|
-
-
|
|
11
|
+
- Resolves dependencies in the context of a particular container
|
|
12
|
+
- Matches services by unique identifiers, class, or property names
|
|
13
|
+
- Currently in active development, API may change until version 0.1.0
|
|
13
14
|
|
|
14
15
|
# Quick start
|
|
15
16
|
|
|
17
|
+
Install the `proxydi` package in your JavaScript or TypeScript project:
|
|
18
|
+
|
|
16
19
|
```shell
|
|
17
20
|
npm i proxydi
|
|
18
21
|
```
|
|
19
22
|
|
|
20
|
-
If you are using TypeScript
|
|
23
|
+
If you are using TypeScript, ensure that `experimentalDecorators` is set to `false` in your `tsconfig.json`. This enables support for Stage 3 decorators:
|
|
21
24
|
|
|
22
25
|
```jsonc
|
|
26
|
+
// tsconfig.json
|
|
23
27
|
{
|
|
24
28
|
"compilerOptions": {
|
|
25
29
|
// ...
|
|
@@ -29,42 +33,114 @@ If you are using TypeScript be sure that your tsconfig.json set exactly `false`
|
|
|
29
33
|
}
|
|
30
34
|
```
|
|
31
35
|
|
|
32
|
-
|
|
36
|
+
The process of using ProxyDi consists of 3 stages:
|
|
33
37
|
|
|
34
|
-
|
|
35
|
-
import { inject } from 'proxydi';
|
|
38
|
+
1. Use the @inject decorator to define the dependencies to be resolved by the ProxyDi container. In this example, we define an interface for characters and ask ProxyDi to resolve the `Role` dependency for actors.
|
|
36
39
|
|
|
37
|
-
|
|
40
|
+
```typescript
|
|
41
|
+
interface Character {
|
|
38
42
|
greet(): string;
|
|
39
43
|
}
|
|
40
44
|
|
|
41
|
-
class
|
|
42
|
-
@inject()
|
|
45
|
+
class Actor {
|
|
46
|
+
@inject('Role') role: Character;
|
|
47
|
+
|
|
48
|
+
play = () => this.role.greet();
|
|
43
49
|
}
|
|
44
50
|
```
|
|
45
51
|
|
|
46
|
-
2.
|
|
52
|
+
2. Next, set up the ProxyDi container and fill it with dependencies. Let's define an agent 007 role and prepare our first container:
|
|
47
53
|
|
|
48
54
|
```typescript
|
|
49
|
-
|
|
55
|
+
class Agent007 implements Character {
|
|
56
|
+
greet = () => 'Bond... James Bond';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const container = new ProxyDiContainer();
|
|
60
|
+
container.newDependency(Agent007, 'Role');
|
|
61
|
+
container.newDependency(Actor, 'Actor');
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
3. At the last stage, take dependencies from the ProxyDi container and just use them. Let our actor play its role:
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
const actor = container.resolve<Actor>('Actor');
|
|
68
|
+
console.log(actor.greet());
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
And the result is:
|
|
72
|
+
|
|
73
|
+
```shell
|
|
74
|
+
> Bond... James Bond
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
# The Reason
|
|
78
|
+
|
|
79
|
+
To illustrate why we should use a container for this simple purpose, let's create a container for another character and ask the actor to play the new role:
|
|
50
80
|
|
|
51
|
-
|
|
52
|
-
|
|
81
|
+
```typescript
|
|
82
|
+
class M implements Character {
|
|
83
|
+
greet = () => '007, I have a new mission for you';
|
|
53
84
|
}
|
|
54
85
|
|
|
55
|
-
const container = new
|
|
56
|
-
container.
|
|
86
|
+
const container = new ProxyDiContainer();
|
|
87
|
+
container.newDependency(M, 'Role');
|
|
88
|
+
container.newDependency(Actor, 'Actor');
|
|
89
|
+
|
|
90
|
+
const actor = container.resolve<Actor>('Actor');
|
|
91
|
+
console.log(actor.play());
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
```shell
|
|
95
|
+
> 007, I have a new mission for you
|
|
57
96
|
```
|
|
58
97
|
|
|
59
|
-
|
|
98
|
+
In this example, we changed the behavior of the actor by changing the role dependency in the ProxyDi container. This is the goal of the [Dependency inversion principle](https://en.wikipedia.org/wiki/Dependency_inversion_principle) in SOLID. Continuing our metaphor, the actor can play any role, but it is not he who decides what role he will play. This is a film director's decision, and here we just cosplay him by setting up our containers.
|
|
99
|
+
|
|
100
|
+
So, ProxyDi is a just tool to link dependencies. And nothing more. But we
|
|
101
|
+
|
|
102
|
+
# Сircular dependencies
|
|
103
|
+
|
|
104
|
+
There are several rough edges in traditional DI container implementations that ProxyDi addresses. The first of these is circular dependencies.
|
|
105
|
+
|
|
106
|
+
Let's illustrate this with a movie set metaphor. During filming, the actor and director work together in a continuous feedback loop:
|
|
60
107
|
|
|
61
108
|
```typescript
|
|
62
|
-
|
|
63
|
-
|
|
109
|
+
class Director {
|
|
110
|
+
@inject('Actor') private actor: Actor;
|
|
111
|
+
private passionLevel = 1;
|
|
112
|
+
|
|
113
|
+
direct(line: string) {
|
|
114
|
+
return this.actor.perform(line, this.passionLevel);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
class Actor {
|
|
119
|
+
@inject('Role') private role: Character;
|
|
120
|
+
@inject('Director') private director: Director;
|
|
121
|
+
|
|
122
|
+
play() {
|
|
123
|
+
const line = this.role.greet();
|
|
124
|
+
// Here actor asks director how to perform the line
|
|
125
|
+
return this.director.direct(line);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
perform(line: string, loudness: number = 0) {
|
|
129
|
+
return line + '!'.repeat(loudness);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const container = new ProxyDiContainer();
|
|
134
|
+
container.newDependency(Actor, 'Actor');
|
|
135
|
+
container.newDependency(Director, 'Director');
|
|
136
|
+
container.newDependency(Agent007, 'Role');
|
|
137
|
+
|
|
138
|
+
const actor = container.resolve<Actor>('Actor');
|
|
139
|
+
console.log(actor.play());
|
|
64
140
|
```
|
|
65
141
|
|
|
66
142
|
```shell
|
|
67
|
-
>
|
|
143
|
+
> Bond... James Bond!
|
|
68
144
|
```
|
|
69
145
|
|
|
70
|
-
|
|
146
|
+
In traditional DI containers, this scene would be tricky to shoot - the Director calls Actor's methods while Actor simultaneously needs Director's guidance. But ProxyDi handles it elegantly using JavaScript Proxies without any worries from you.
|
package/dist/Proxy.utils.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export declare const
|
|
3
|
-
export declare function
|
|
1
|
+
import { ContainerizedDependency, Injection, IProxyDiContainer } from './types';
|
|
2
|
+
export declare const makeInjectionProxy: <T>(inject: Injection, injectionOwner: ContainerizedDependency, container: IProxyDiContainer) => T;
|
|
3
|
+
export declare function makeDependencyProxy(dependency: any): any;
|
|
4
|
+
export declare function isInjectionProxy(value: any): boolean;
|
|
5
|
+
export declare function isInstanceProxy(value: any): boolean;
|
package/dist/ProxyDI.d.ts
CHANGED
|
@@ -1,27 +1,27 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
export declare class
|
|
1
|
+
import { IProxyDiContainer as IProxyDiContainer, ContainerizedServiceInstance, ServiceInstanced, ServiceClass } from './types';
|
|
2
|
+
import { ProxyDiSettings, ServiceId } from './types';
|
|
3
|
+
export declare class ProxyDiContainer implements IProxyDiContainer {
|
|
4
4
|
private static idCounter;
|
|
5
5
|
readonly id: number;
|
|
6
|
-
readonly parent?:
|
|
6
|
+
readonly parent?: ProxyDiContainer;
|
|
7
7
|
private children;
|
|
8
|
+
/**
|
|
9
|
+
* Holds instances of services registered in particular this container
|
|
10
|
+
*/
|
|
8
11
|
private serviceInstances;
|
|
9
|
-
private
|
|
12
|
+
private parentInstanceProxies;
|
|
10
13
|
private settings;
|
|
11
|
-
constructor(settings?:
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
registerClass<T>(serviceId: ServiceId, serviceClass: ServiceClass<T>): void;
|
|
14
|
+
constructor(settings?: ProxyDiSettings, parent?: ProxyDiContainer);
|
|
15
|
+
registerService<T>(serviceId: ServiceId, instance: ServiceInstanced<T>): void;
|
|
16
|
+
private registerInstanceImplementation;
|
|
17
|
+
createService<T>(serviceId: ServiceId, ServiceClass: ServiceClass<T>): void;
|
|
16
18
|
isKnown(serviceId: ServiceId): boolean;
|
|
17
|
-
resolve<T>(serviceId: ServiceId): T;
|
|
18
|
-
injectDependencies(
|
|
19
|
-
createChildContainer():
|
|
20
|
-
|
|
21
|
-
removeClass(serviceId: ServiceId): void;
|
|
19
|
+
resolve<T>(serviceId: ServiceId): T & ContainerizedServiceInstance;
|
|
20
|
+
injectDependencies(injectionsOwner: any): void;
|
|
21
|
+
createChildContainer(): ProxyDiContainer;
|
|
22
|
+
removeService(serviceId: ServiceId | ContainerizedServiceInstance): void;
|
|
22
23
|
destroy(): void;
|
|
23
24
|
private findInstance;
|
|
24
|
-
private findServiceClass;
|
|
25
25
|
private addChild;
|
|
26
26
|
private removeChild;
|
|
27
27
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { IProxyDiContainer as IProxyDiContainer, ContainerizedDependency as ContainerizedDependency, Instanced, DependencyClass } from './types';
|
|
2
|
+
import { ContainerSettings as ContainerSettings, DependencyId } from './types';
|
|
3
|
+
export declare class ProxyDiContainer implements IProxyDiContainer {
|
|
4
|
+
private static idCounter;
|
|
5
|
+
readonly id: number;
|
|
6
|
+
readonly parent?: ProxyDiContainer;
|
|
7
|
+
private children;
|
|
8
|
+
/**
|
|
9
|
+
* Holds dependencies of this particular container
|
|
10
|
+
*/
|
|
11
|
+
private dependencies;
|
|
12
|
+
private parentDependencyProxies;
|
|
13
|
+
private settings;
|
|
14
|
+
constructor(settings?: ContainerSettings, parent?: ProxyDiContainer);
|
|
15
|
+
registerDependency<T>(dependency: Instanced<T>, dependencyId: DependencyId): void;
|
|
16
|
+
newDependency<T>(DependencyClass: DependencyClass<T>, dependencyId: DependencyId): void;
|
|
17
|
+
private registerDependencyImpl;
|
|
18
|
+
isKnown(dependencyId: DependencyId): boolean;
|
|
19
|
+
resolveAutoInjectable<T extends new () => any>(SomeClass: T): InstanceType<T>;
|
|
20
|
+
resolve<T>(dependencyId: DependencyId): T & ContainerizedDependency;
|
|
21
|
+
injectDependenciesTo(injectionsOwner: any): void;
|
|
22
|
+
createChildContainer(): ProxyDiContainer;
|
|
23
|
+
removeDependency(dependencyOrId: DependencyId | ContainerizedDependency): void;
|
|
24
|
+
destroy(): void;
|
|
25
|
+
private findDependency;
|
|
26
|
+
private addChild;
|
|
27
|
+
private removeChild;
|
|
28
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { InstancedDependency, DependencyId } from './types';
|
|
2
|
+
export declare const autoInjectableClasses: Record<DependencyId, InstancedDependency<any>>;
|
|
3
|
+
/**
|
|
4
|
+
* Registers a class as an auto-injectable for dependency injection.
|
|
5
|
+
*
|
|
6
|
+
* @param dependencyId - Optional dependency identifier. If omitted, the class name is used.
|
|
7
|
+
* @returns A class decorator function.
|
|
8
|
+
*
|
|
9
|
+
* Note: During dependency resolution, any container that does not have an instance for the specified dependency identifier
|
|
10
|
+
* will create an instance of the decorated class. However, if a container already has an instance with that identifier
|
|
11
|
+
* prior to resolution, the decorated class will be ignored by that container.
|
|
12
|
+
*/
|
|
13
|
+
export declare const autoInjectable: (dependencyId?: DependencyId) => (value: InstancedDependency<any>, context: ClassDecoratorContext) => void;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { InstancedServiceClass, ServiceId } from './types';
|
|
2
|
+
export declare const autoInjectableServices: Record<ServiceId, InstancedServiceClass<any>>;
|
|
3
|
+
/**
|
|
4
|
+
* Decorator that registers a class as an auto-injectable service for dependency injection.
|
|
5
|
+
*
|
|
6
|
+
* @param serviceId - Optional service identifier. If omitted, the class name is used.
|
|
7
|
+
* @returns A class decorator function.
|
|
8
|
+
*
|
|
9
|
+
* Note: During dependency resolution, any container that does not have an instance for the specified service identifier
|
|
10
|
+
* will create an instance of the decorated class. However, if a container already has an instance with that identifier
|
|
11
|
+
* prior to resolution, the decorated class will be ignored by that container.
|
|
12
|
+
*/
|
|
13
|
+
export declare const autoInjectableService: (serviceId?: ServiceId) => (value: InstancedServiceClass<any>, context: ClassDecoratorContext) => void;
|