proxydi 0.0.5 → 0.0.6

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,16 +1,16 @@
1
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&v=0.0.5)
3
+ [![Coverage Status](https://coveralls.io/repos/github/proxy-di/proxydi/badge.png)](https://coveralls.io/github/proxy-di/proxydi)
4
4
 
5
5
  A typed hierarchical DI container that resolves circular dependencies via Proxy.
6
6
 
7
7
  Core features:
8
8
 
9
- - Uses Stage 3 decorators (supported in TypeScript 5.x and babel-plugin-proposal-decorators)
10
- - Automatically resolves circular dependencies
9
+ - 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))
10
+ - Automatically resolves circular dependencies with no persormance impact
11
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
12
+ - Matches dependencies by unique identifiers or automatically using class names and property names
13
+ - Currently under active development, the API may change until version 0.1.0
14
14
 
15
15
  # Quick start
16
16
 
@@ -20,6 +20,8 @@ Install the `proxydi` package in your JavaScript or TypeScript project:
20
20
  npm i proxydi
21
21
  ```
22
22
 
23
+ ## TypeScript set up
24
+
23
25
  If you are using TypeScript, ensure that `experimentalDecorators` is set to `false` in your `tsconfig.json`. This enables support for Stage 3 decorators:
24
26
 
25
27
  ```jsonc
@@ -28,11 +30,31 @@ If you are using TypeScript, ensure that `experimentalDecorators` is set to `fal
28
30
  "compilerOptions": {
29
31
  // ...
30
32
  "experimentalDecorators": false,
33
+ "strictPropertyInitialization": false,
31
34
  },
32
35
  //...
33
36
  }
34
37
  ```
35
38
 
39
+ Changing `strictPropertyInitialization` is not necessary, but if you leave it at the default value, you will need to slightly modify the examples. More about this later.
40
+
41
+ ## Babel set up
42
+
43
+ For Babel projects, ensure that @babel/plugin-proposal-decorators is configured exactly as follows:
44
+
45
+ ```jsonc
46
+ // .babelrc
47
+ {
48
+ // ...
49
+ "plugins": [
50
+ // other plugins
51
+ ["@babel/plugin-proposal-decorators", { "version": "2023-11" }],
52
+ ],
53
+ }
54
+ ```
55
+
56
+ ## Usage
57
+
36
58
  The process of using ProxyDi consists of 3 stages:
37
59
 
38
60
  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.
@@ -65,7 +87,7 @@ container.newDependency(Actor, 'Actor');
65
87
 
66
88
  ```typescript
67
89
  const actor = container.resolve<Actor>('Actor');
68
- console.log(actor.greet());
90
+ console.log(actor.play());
69
91
  ```
70
92
 
71
93
  And the result is:
@@ -95,11 +117,11 @@ console.log(actor.play());
95
117
  > 007, I have a new mission for you
96
118
  ```
97
119
 
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.
120
+ 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 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.
99
121
 
100
- So, ProxyDi is a just tool to link dependencies. And nothing more. But we
122
+ So, ProxyDi is just a tool to link dependencies. And nothing more.
101
123
 
102
- # Сircular dependencies
124
+ # Circular dependencies
103
125
 
104
126
  There are several rough edges in traditional DI container implementations that ProxyDi addresses. The first of these is circular dependencies.
105
127
 
@@ -121,7 +143,7 @@ class Actor {
121
143
 
122
144
  play() {
123
145
  const line = this.role.greet();
124
- // Here actor asks director how to perform the line
146
+ // Here, the actor asks the director how to perform the line.
125
147
  return this.director.direct(line);
126
148
  }
127
149
 
@@ -143,4 +165,40 @@ console.log(actor.play());
143
165
  > Bond... James Bond!
144
166
  ```
145
167
 
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.
168
+ 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 on your part.
169
+
170
+ # Rewriting dependencies
171
+
172
+ 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:
173
+
174
+ ```typescript
175
+ const container = new ProxyDiContainer();
176
+ container.newDependency(Actor, 'Actor');
177
+ container.newDependency(Actor, 'Actor'); // !!! Error here
178
+ ```
179
+
180
+ However, there is an option that allows you to do these kinds of things:
181
+
182
+ ```typescript
183
+ const container = new ProxyDiContainer({ allowRewriteDependencies: true });
184
+ container.newDependency(Actor, 'Actor');
185
+
186
+ const actor = container.resolve<Actor>('Actor');
187
+ const wrapper = new ActorWrapper(actor);
188
+
189
+ container.registerDependency(wrapper, 'Actor'); // No error is thrown here now
190
+ ```
191
+
192
+ ## Injection proxy performance
193
+
194
+ As mentioned before, ProxyDi uses `Proxy` for each field marked by the @inject() decorator. This makes it possible to resolve circular dependencies. By default, these proxies are replaced by the actual dependency instances from the container during their first use, so the performance impact on your application is minimal.
195
+
196
+ However, if you allow rewriting dependencies in the container, these proxies remain in use to keep injections updated. As a result, every time you access a dependency field, there is a significant performance impact. In our tests, property access via Proxy is up to 100 times slower. For this reason, we recommend not allowing rewriting dependencies in production and keeping the container’s default behavior.
197
+
198
+ ## Baking injections
199
+
200
+ There is a container method `bakeInjections()` that bakes all injections and freezes the current container’s dependencies. After calling this method, the container will deny any attempts to rewrite dependencies. It also bakes the dependencies in all its children.
201
+
202
+ After the container has been baked, the performance impact becomes zero. Therefore, you should use this method even for containers with default settings, ensuring that your application don't have to wait for the first use of each injection before they are baked.
203
+
204
+ To be continued...
@@ -1,5 +1,5 @@
1
1
  import { ContainerizedDependency, Injection, IProxyDiContainer } from './types';
2
- export declare const makeInjectionProxy: <T>(inject: Injection, injectionOwner: ContainerizedDependency, container: IProxyDiContainer) => T;
2
+ export declare const makeInjectionProxy: <T>(injection: Injection, injectionOwner: ContainerizedDependency, container: IProxyDiContainer) => T;
3
3
  export declare function makeDependencyProxy(dependency: any): any;
4
4
  export declare function isInjectionProxy(value: any): boolean;
5
5
  export declare function isInstanceProxy(value: any): boolean;
@@ -10,7 +10,7 @@ export declare class ProxyDiContainer implements IProxyDiContainer {
10
10
  */
11
11
  private dependencies;
12
12
  private parentDependencyProxies;
13
- private settings;
13
+ readonly settings: Required<ContainerSettings>;
14
14
  constructor(settings?: ContainerSettings, parent?: ProxyDiContainer);
15
15
  registerDependency<T>(dependency: Instanced<T>, dependencyId: DependencyId): void;
16
16
  newDependency<T>(DependencyClass: DependencyClass<T>, dependencyId: DependencyId): void;
@@ -19,6 +19,7 @@ export declare class ProxyDiContainer implements IProxyDiContainer {
19
19
  resolveAutoInjectable<T extends new () => any>(SomeClass: T): InstanceType<T>;
20
20
  resolve<T>(dependencyId: DependencyId): T & ContainerizedDependency;
21
21
  injectDependenciesTo(injectionsOwner: any): void;
22
+ bakeInjections(): void;
22
23
  createChildContainer(): ProxyDiContainer;
23
24
  removeDependency(dependencyOrId: DependencyId | ContainerizedDependency): void;
24
25
  destroy(): void;
package/dist/index.cjs CHANGED
@@ -47,13 +47,17 @@ class InjectionProxy {
47
47
  }
48
48
  }
49
49
  _a = IS_INJECTION_PROXY;
50
- const makeInjectionProxy = (inject, injectionOwner, container) => {
50
+ const makeInjectionProxy = (injection, injectionOwner, container) => {
51
51
  function getDependency() {
52
- if (container.isKnown(inject.dependencyId)) {
53
- return container.resolve(inject.dependencyId);
52
+ if (container.isKnown(injection.dependencyId)) {
53
+ const dependency = container.resolve(injection.dependencyId);
54
+ if (!container.settings.allowRewriteDependencies) {
55
+ injection.set(injectionOwner, dependency);
56
+ }
57
+ return dependency;
54
58
  }
55
59
  else {
56
- throw new Error(`Unknown dependency: ${String(inject.dependencyId)}`);
60
+ throw new Error(`Unknown dependency: ${String(injection.dependencyId)}`);
57
61
  }
58
62
  }
59
63
  return new Proxy(new InjectionProxy(injectionOwner, container), {
@@ -114,10 +118,13 @@ class ProxyDiContainer {
114
118
  throw new Error(`ProxyDi already has dependency for ${String(dependencyId)}`);
115
119
  }
116
120
  }
117
- if (!(typeof dependency === 'object') &&
118
- !this.settings.allowRegisterAnything) {
121
+ const isObject = typeof dependency === 'object';
122
+ if (!isObject && !this.settings.allowRegisterAnything) {
119
123
  throw new Error(`Can't register as dependency (allowRegisterAnything is off for this contatiner): ${dependency}`);
120
124
  }
125
+ if (isObject) {
126
+ dependency[PROXYDY_CONTAINER] = this;
127
+ }
121
128
  this.registerDependencyImpl(dependencyId, dependency);
122
129
  }
123
130
  newDependency(DependencyClass, dependencyId) {
@@ -127,6 +134,7 @@ class ProxyDiContainer {
127
134
  }
128
135
  }
129
136
  const dependency = new DependencyClass();
137
+ dependency[PROXYDY_CONTAINER] = this;
130
138
  this.registerDependencyImpl(dependencyId, dependency);
131
139
  }
132
140
  registerDependencyImpl(dependencyId, dependency) {
@@ -177,11 +185,24 @@ class ProxyDiContainer {
177
185
  }
178
186
  injectDependenciesTo(injectionsOwner) {
179
187
  const dependencyInjects = injectionsOwner[INJECTIONS] || {};
180
- Object.values(dependencyInjects).forEach((inject) => {
181
- const dependencyProxy = makeInjectionProxy(inject, injectionsOwner, this);
182
- inject.set(injectionsOwner, dependencyProxy);
188
+ Object.values(dependencyInjects).forEach((injection) => {
189
+ const dependencyProxy = makeInjectionProxy(injection, injectionsOwner, this);
190
+ injection.set(injectionsOwner, dependencyProxy);
183
191
  });
184
192
  }
193
+ bakeInjections() {
194
+ for (const dependency of Object.values(this.dependencies)) {
195
+ const dependencyInjects = dependency[INJECTIONS] || {};
196
+ Object.values(dependencyInjects).forEach((inject) => {
197
+ const value = this.resolve(inject.dependencyId);
198
+ inject.set(dependency, value);
199
+ });
200
+ }
201
+ this.settings.allowRewriteDependencies = false;
202
+ for (const child of Object.values(this.children)) {
203
+ child.bakeInjections();
204
+ }
205
+ }
185
206
  createChildContainer() {
186
207
  return new ProxyDiContainer(this.settings, this);
187
208
  }
package/dist/index.js CHANGED
@@ -45,13 +45,17 @@ class InjectionProxy {
45
45
  }
46
46
  }
47
47
  _a = IS_INJECTION_PROXY;
48
- const makeInjectionProxy = (inject, injectionOwner, container) => {
48
+ const makeInjectionProxy = (injection, injectionOwner, container) => {
49
49
  function getDependency() {
50
- if (container.isKnown(inject.dependencyId)) {
51
- return container.resolve(inject.dependencyId);
50
+ if (container.isKnown(injection.dependencyId)) {
51
+ const dependency = container.resolve(injection.dependencyId);
52
+ if (!container.settings.allowRewriteDependencies) {
53
+ injection.set(injectionOwner, dependency);
54
+ }
55
+ return dependency;
52
56
  }
53
57
  else {
54
- throw new Error(`Unknown dependency: ${String(inject.dependencyId)}`);
58
+ throw new Error(`Unknown dependency: ${String(injection.dependencyId)}`);
55
59
  }
56
60
  }
57
61
  return new Proxy(new InjectionProxy(injectionOwner, container), {
@@ -112,10 +116,13 @@ class ProxyDiContainer {
112
116
  throw new Error(`ProxyDi already has dependency for ${String(dependencyId)}`);
113
117
  }
114
118
  }
115
- if (!(typeof dependency === 'object') &&
116
- !this.settings.allowRegisterAnything) {
119
+ const isObject = typeof dependency === 'object';
120
+ if (!isObject && !this.settings.allowRegisterAnything) {
117
121
  throw new Error(`Can't register as dependency (allowRegisterAnything is off for this contatiner): ${dependency}`);
118
122
  }
123
+ if (isObject) {
124
+ dependency[PROXYDY_CONTAINER] = this;
125
+ }
119
126
  this.registerDependencyImpl(dependencyId, dependency);
120
127
  }
121
128
  newDependency(DependencyClass, dependencyId) {
@@ -125,6 +132,7 @@ class ProxyDiContainer {
125
132
  }
126
133
  }
127
134
  const dependency = new DependencyClass();
135
+ dependency[PROXYDY_CONTAINER] = this;
128
136
  this.registerDependencyImpl(dependencyId, dependency);
129
137
  }
130
138
  registerDependencyImpl(dependencyId, dependency) {
@@ -175,11 +183,24 @@ class ProxyDiContainer {
175
183
  }
176
184
  injectDependenciesTo(injectionsOwner) {
177
185
  const dependencyInjects = injectionsOwner[INJECTIONS] || {};
178
- Object.values(dependencyInjects).forEach((inject) => {
179
- const dependencyProxy = makeInjectionProxy(inject, injectionsOwner, this);
180
- inject.set(injectionsOwner, dependencyProxy);
186
+ Object.values(dependencyInjects).forEach((injection) => {
187
+ const dependencyProxy = makeInjectionProxy(injection, injectionsOwner, this);
188
+ injection.set(injectionsOwner, dependencyProxy);
181
189
  });
182
190
  }
191
+ bakeInjections() {
192
+ for (const dependency of Object.values(this.dependencies)) {
193
+ const dependencyInjects = dependency[INJECTIONS] || {};
194
+ Object.values(dependencyInjects).forEach((inject) => {
195
+ const value = this.resolve(inject.dependencyId);
196
+ inject.set(dependency, value);
197
+ });
198
+ }
199
+ this.settings.allowRewriteDependencies = false;
200
+ for (const child of Object.values(this.children)) {
201
+ child.bakeInjections();
202
+ }
203
+ }
183
204
  createChildContainer() {
184
205
  return new ProxyDiContainer(this.settings, this);
185
206
  }
package/dist/index.umd.js CHANGED
@@ -51,13 +51,17 @@
51
51
  }
52
52
  }
53
53
  _a = IS_INJECTION_PROXY;
54
- const makeInjectionProxy = (inject, injectionOwner, container) => {
54
+ const makeInjectionProxy = (injection, injectionOwner, container) => {
55
55
  function getDependency() {
56
- if (container.isKnown(inject.dependencyId)) {
57
- return container.resolve(inject.dependencyId);
56
+ if (container.isKnown(injection.dependencyId)) {
57
+ const dependency = container.resolve(injection.dependencyId);
58
+ if (!container.settings.allowRewriteDependencies) {
59
+ injection.set(injectionOwner, dependency);
60
+ }
61
+ return dependency;
58
62
  }
59
63
  else {
60
- throw new Error(`Unknown dependency: ${String(inject.dependencyId)}`);
64
+ throw new Error(`Unknown dependency: ${String(injection.dependencyId)}`);
61
65
  }
62
66
  }
63
67
  return new Proxy(new InjectionProxy(injectionOwner, container), {
@@ -118,10 +122,13 @@
118
122
  throw new Error(`ProxyDi already has dependency for ${String(dependencyId)}`);
119
123
  }
120
124
  }
121
- if (!(typeof dependency === 'object') &&
122
- !this.settings.allowRegisterAnything) {
125
+ const isObject = typeof dependency === 'object';
126
+ if (!isObject && !this.settings.allowRegisterAnything) {
123
127
  throw new Error(`Can't register as dependency (allowRegisterAnything is off for this contatiner): ${dependency}`);
124
128
  }
129
+ if (isObject) {
130
+ dependency[PROXYDY_CONTAINER] = this;
131
+ }
125
132
  this.registerDependencyImpl(dependencyId, dependency);
126
133
  }
127
134
  newDependency(DependencyClass, dependencyId) {
@@ -131,6 +138,7 @@
131
138
  }
132
139
  }
133
140
  const dependency = new DependencyClass();
141
+ dependency[PROXYDY_CONTAINER] = this;
134
142
  this.registerDependencyImpl(dependencyId, dependency);
135
143
  }
136
144
  registerDependencyImpl(dependencyId, dependency) {
@@ -181,11 +189,24 @@
181
189
  }
182
190
  injectDependenciesTo(injectionsOwner) {
183
191
  const dependencyInjects = injectionsOwner[INJECTIONS] || {};
184
- Object.values(dependencyInjects).forEach((inject) => {
185
- const dependencyProxy = makeInjectionProxy(inject, injectionsOwner, this);
186
- inject.set(injectionsOwner, dependencyProxy);
192
+ Object.values(dependencyInjects).forEach((injection) => {
193
+ const dependencyProxy = makeInjectionProxy(injection, injectionsOwner, this);
194
+ injection.set(injectionsOwner, dependencyProxy);
187
195
  });
188
196
  }
197
+ bakeInjections() {
198
+ for (const dependency of Object.values(this.dependencies)) {
199
+ const dependencyInjects = dependency[INJECTIONS] || {};
200
+ Object.values(dependencyInjects).forEach((inject) => {
201
+ const value = this.resolve(inject.dependencyId);
202
+ inject.set(dependency, value);
203
+ });
204
+ }
205
+ this.settings.allowRewriteDependencies = false;
206
+ for (const child of Object.values(this.children)) {
207
+ child.bakeInjections();
208
+ }
209
+ }
189
210
  createChildContainer() {
190
211
  return new ProxyDiContainer(this.settings, this);
191
212
  }
package/dist/types.d.ts CHANGED
@@ -12,6 +12,7 @@ export type Injection = {
12
12
  };
13
13
  export type IProxyDiContainer = {
14
14
  id: number;
15
+ settings: Required<ContainerSettings>;
15
16
  isKnown: (dependencyId: DependencyId) => boolean;
16
17
  injectDependenciesTo: (dependency: any) => void;
17
18
  registerDependency: <T>(instance: T extends {
@@ -21,6 +22,7 @@ export type IProxyDiContainer = {
21
22
  resolve: <T>(serviceId: DependencyId) => T & ContainerizedDependency;
22
23
  createChildContainer: () => IProxyDiContainer;
23
24
  removeDependency: (serviceId: DependencyId | ContainerizedDependency) => void;
25
+ bakeInjections(): void;
24
26
  destroy: () => void;
25
27
  };
26
28
  export declare const INJECTIONS: unique symbol;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "proxydi",
3
- "version": "0.0.5",
3
+ "version": "0.0.6",
4
4
  "description": "A typed hierarchical DI container that resolves circular dependencies via Proxy",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",