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 CHANGED
@@ -1,25 +1,29 @@
1
- # ProxyDI
1
+ # ProxyDi
2
2
 
3
- [![Coverage Status](https://coveralls.io/repos/github/proxy-di/proxydi/badge.svg?branch=main)](https://coveralls.io/github/proxy-di/proxydi?branch=main)
3
+ [![Coverage Status](https://coveralls.io/repos/github/proxy-di/proxydi/badge.svg?branch=main)](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 notes:
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
- - Matches services by unique identifiers, class or property names
12
- - Currently in active development
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 be sure that your tsconfig.json set exactly `false` value for `experimentalDecorators`. This could be not obvious, but this setup turns on support of Stage 3 decorators.
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
- 1. @inject your dependencies
36
+ The process of using ProxyDi consists of 3 stages:
33
37
 
34
- ```typescript
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
- interface Personality {
40
+ ```typescript
41
+ interface Character {
38
42
  greet(): string;
39
43
  }
40
44
 
41
- class Agent {
42
- @inject() personality: Personality;
45
+ class Actor {
46
+ @inject('Role') role: Character;
47
+
48
+ play = () => this.role.greet();
43
49
  }
44
50
  ```
45
51
 
46
- 2. Create, fill ProxyDI container
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
- import { ProxyDI } from 'proxydi';
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
- class Jarvis implements Personality {
52
- greet: () => 'Hello, sir!';
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 ProxyDI();
56
- container.registerClass('personality', Jarvis);
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
- 3. Use dependencies
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
- const agent = container.resolve(Agent);
63
- console.log(agent.personality.greet());
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
- > Hello, sir!
143
+ > Bond... James Bond!
68
144
  ```
69
145
 
70
- To be continued...
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.
@@ -1,3 +1,5 @@
1
- import { ServiceId } from './types';
2
- export declare const makeProxy: <T>(serviceId: ServiceId, instance: any) => T;
3
- export declare function isProxy(value: any): boolean;
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 { ProxyDI as IProxyDI, ProxydiedInstance } from './types';
2
- import { ProxyDISettings, ServiceClass, ServiceId } from './types';
3
- export declare class ProxyDI implements IProxyDI {
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?: ProxyDI;
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 serviceClasses;
12
+ private parentInstanceProxies;
10
13
  private settings;
11
- constructor(settings?: ProxyDISettings, parent?: ProxyDI);
12
- registerInstance<T>(serviceId: ServiceId, instance: T extends {
13
- new (...args: any[]): any;
14
- } ? never : T): void;
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(instance: any): void;
19
- createChildContainer(): ProxyDI;
20
- removeInstance(serviceId: ServiceId | ProxydiedInstance): void;
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;