proxydi 0.0.10 → 0.0.12

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/CHANGELOG.md ADDED
@@ -0,0 +1,91 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [[0.0.12](https://www.npmjs.com/package/proxydi/v/0.0.12)] - 2025-03-04
9
+
10
+ ### Added
11
+
12
+ - Experimental @middleware [#14](https://github.com/proxy-di/proxydi/pull/14)
13
+
14
+ ## [[0.0.11](https://www.npmjs.com/package/proxydi/v/0.0.11)] - 2025-03-03
15
+
16
+ ### Added
17
+
18
+ - resolveInjectables() method [#13](https://github.com/proxy-di/proxydi/pull/13)
19
+
20
+ ## [[0.0.10](https://www.npmjs.com/package/proxydi/v/0.0.10)] - 2025-03-02
21
+
22
+ ### Changed
23
+
24
+ - Renamed @autoInjectable to @injectable
25
+
26
+ ### Added
27
+
28
+ - Experimental constructor injections via @injectable decorator [#12](https://github.com/proxy-di/proxydi/pull/12)
29
+
30
+ ## [[0.0.9](https://www.npmjs.com/package/proxydi/v/0.0.9)] - 2025-02-27
31
+
32
+ ### Added
33
+
34
+ - resolveAll() function to find all dependencies of a given kind [#11](https://github.com/proxy-di/proxydi/pull/11)
35
+
36
+ ## [[0.0.8](https://www.npmjs.com/package/proxydi/v/0.0.8)] - 2025-02-26
37
+
38
+ ### Changed
39
+
40
+ - API improvements [#10](https://github.com/proxy-di/proxydi/pull/10)
41
+
42
+ ### Added
43
+
44
+ - Added access to child containers
45
+ - PROXYDI_CONTAINER and DEPENDENCY_ID now public
46
+ - Enhanced hierarchy documentation
47
+
48
+ ## [[0.0.7](https://www.npmjs.com/package/proxydi/v/0.0.7)] - 2025-02-21
49
+
50
+ ### Added
51
+
52
+ - TypeDoc integration [#7](https://github.com/proxy-di/proxydi/pull/7)
53
+ - Action to publish documentation on GitHub Pages
54
+
55
+ ## [[0.0.6](https://www.npmjs.com/package/proxydi/v/0.0.6)] - 2025-02-21
56
+
57
+ ### Changed
58
+
59
+ - Improved API [#6](https://github.com/proxy-di/proxydi/pull/6)
60
+
61
+ ## [[0.0.5](https://www.npmjs.com/package/proxydi/v/0.0.5)] - 2025-02-20
62
+
63
+ ### Changed
64
+
65
+ - Baking dependencies [#5](https://github.com/proxy-di/proxydi/pull/5)
66
+ - 100% test coverage
67
+ - Changed API with new terminology
68
+
69
+ ## [[0.0.4](https://www.npmjs.com/package/proxydi/v/0.0.4)] - 2025-02-11
70
+
71
+ ### Changed
72
+
73
+ - Implemented proxy per instance instead of per container [#2](https://github.com/proxy-di/proxydi/pull/2)
74
+ - Ability to register anything as an instance
75
+ - Changed settings to object format
76
+ - Added settings presets
77
+
78
+ ### Fixed
79
+
80
+ - Made injectables known
81
+
82
+ ## [[0.0.3](https://www.npmjs.com/package/proxydi/v/0.0.3)] - 2025-02-07
83
+
84
+ ### Added
85
+
86
+ - Draft DI-container implementation
87
+ - @injectable decorator
88
+ - Basic project setup with CI/CD
89
+ - Testing framework integration
90
+ - Basic package configuration
91
+ - Repository initialization
package/README.md CHANGED
@@ -15,7 +15,9 @@ Core features:
15
15
  - Currently under active development, the API may change until version 0.1.0
16
16
 
17
17
  Eperimemntal features:
18
+
18
19
  - Construtor injections (see unit tests for examples)
20
+ - Middleware listeners (see unit tests for examples)
19
21
  - Matches dependencies by unique identifiers or automatically using class names and property names
20
22
 
21
23
  ## Quick start
@@ -242,10 +244,67 @@ In this example, the character activates all its perks, which are registered in
242
244
 
243
245
  ### Reference to the container
244
246
 
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.
247
+ Here you should be wondering, how [resolveAll()](https://proxy-di.github.io/proxydi/functions/resolveAll.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
248
 
247
249
  Despite this explanation is a little bit complicated, the example is still simple, the character just activates all its perks.
248
250
 
251
+ ## Injectable classes
252
+
253
+ Look at the following example:
254
+
255
+ ```typescript
256
+ @injectable()
257
+ class GameEngine {
258
+ start = () => console.log('Game started');
259
+ }
260
+
261
+ const container = new ProxyDiContainer();
262
+
263
+ const gameEngine = container.resolve(GameEngine);
264
+ gameEngine.start();
265
+ ```
266
+
267
+ ```shell
268
+ > Game started
269
+ ```
270
+
271
+ Two things happen here:
272
+
273
+ 1. We use the [@injectable()](https://proxy-di.github.io/proxydi/functions/injectable.html) decorator to mark the class as injectable. This allows us to resolve the dependency without registering it in the container.
274
+
275
+ 2. To resolve the dependency instance, we pass the injectable class itself as the dependency identifier. This is possible because ProxyDi automatically uses the class name as the dependency identifier when none is explicitly provided. As an additional bonus, the `gameEngine` has a type `GameEngine`.
276
+
277
+ ### Implicitly resolving injectable() dependencies
278
+
279
+ There are a few important nuances about the `@injectable()` decorator to keep in mind. The first is how these dependencies are resolved. Consider this example:
280
+
281
+ ```typescript
282
+ const parent = new ProxyDiContainer();
283
+ const child = parent.createChildContainer();
284
+
285
+ const engine1 = child.resolve(GameEngine);
286
+ const engine2 = parent.resolve(GameEngine);
287
+
288
+ engine1 === engine2; // false
289
+ ```
290
+
291
+ Here we create a hierarchy of containers and resolve the same dependency from the child container first and then from the parent container. The result is two different instances of the GameEngine class. This happens because during the first resolution, the child container doesn't find GameEngine in either itself or its parent container, so it creates a new instance and stores it in itself. When the parent container resolves GameEngine, it creates another instance since he knowns nothing about child depenencies
292
+
293
+ There are two ways to avoid this behavior:
294
+
295
+ 1. Don't use the `@injectable()` decorator and always register dependencies explicitly
296
+ 2. Use the `registerInjectables()` method to create instances for all injectable dependencies in the container. In this case every time when you resolve injectable dependency, you will get the same instance:
297
+
298
+ ```typescript
299
+ const parent = new ProxyDiContainer().registerInjectables();
300
+ const child = parent.createChildContainer();
301
+
302
+ const engine1 = child.resolve(GameEngine);
303
+ const engine2 = parent.resolve(GameEngine);
304
+
305
+ engine1 === engine2; // true now
306
+ ```
307
+
249
308
  ## Rewriting dependencies
250
309
 
251
310
  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:
@@ -29,6 +29,7 @@ export declare class ProxyDiContainer implements IProxyDiContainer {
29
29
  * Settings that control the behavior of the container and it's children
30
30
  */
31
31
  readonly settings: Required<ContainerSettings>;
32
+ private middlewareListener;
32
33
  /**
33
34
  * Creates a new instance of ProxyDiContainer.
34
35
  * @param settings Optional container settings to override defaults.
@@ -47,12 +48,6 @@ export declare class ProxyDiContainer implements IProxyDiContainer {
47
48
  register<T>(DependencyClass: DependencyClass<T>, dependencyId: DependencyId): T & ContainerizedDependency;
48
49
  register<T>(dependency: T extends new (...args: any[]) => any ? never : T, dependencyId: DependencyId): T & ContainerizedDependency;
49
50
  private createInstance;
50
- /**
51
- * Internal method that implements registeration of dependency and prepare it for injection.
52
- * @param dependencyId The unique identifier of the dependency.
53
- * @param dependency The dependency instance.
54
- */
55
- private registerImpl;
56
51
  /**
57
52
  * Checks if a dependency with the given ID is known to the container or its ancestors which means that it can be resolved by this container
58
53
  * @param dependencyId The identifier of the dependency.
@@ -72,6 +67,11 @@ export declare class ProxyDiContainer implements IProxyDiContainer {
72
67
  * @param injectionsOwner The object to inject dependencies into.
73
68
  */
74
69
  injectDependenciesTo(injectionsOwner: any): void;
70
+ /**
71
+ * Creates instances for all injectable classes and registers them in this container.
72
+ * @returns This container to allow use along with constructor.
73
+ */
74
+ registerInjectables(): this;
75
75
  /**
76
76
  * Finalizes dependency injections, prevents further rewriting of dependencies,
77
77
  * and recursively bakes injections for child containers.
package/dist/index.cjs CHANGED
@@ -12,7 +12,7 @@ const DEPENDENCY_ID = Symbol('DependencyId');
12
12
  * This property is present in each dependency instance that was registered in ProxyDiContainer.
13
13
  * The property stores a reference to the ProxyDiContainer in which the dependency was registered.
14
14
  */
15
- const PROXYDY_CONTAINER = Symbol('proxyDiContainer');
15
+ const PROXYDI_CONTAINER = Symbol('proxyDiContainer');
16
16
  const IS_INJECTION_PROXY = Symbol('isInjectionProxy');
17
17
  const INJECTION_OWNER = Symbol('injectionOwner');
18
18
  const IS_INSTANCE_PROXY = Symbol('isInstanceProxy');
@@ -93,7 +93,7 @@ class InjectionProxy {
93
93
  constructor(onwer, container) {
94
94
  this[_a] = true;
95
95
  this[INJECTION_OWNER] = onwer;
96
- this[PROXYDY_CONTAINER] = container;
96
+ this[PROXYDI_CONTAINER] = container;
97
97
  }
98
98
  }
99
99
  _a = IS_INJECTION_PROXY;
@@ -190,6 +190,72 @@ function makeConstructorDependencyProxy(container, dependencyId) {
190
190
  });
191
191
  }
192
192
 
193
+ const middlewaresClasses = {};
194
+ function middleware() {
195
+ return function (value, context) {
196
+ if ((context === null || context === undefined ? undefined : context.kind) !== 'class') {
197
+ throw new Error('@middleware decorator should decorate classes');
198
+ }
199
+ const name = context.name;
200
+ if (middlewaresClasses[name]) {
201
+ throw new Error(`ProxyDi has already regisered middleware ${String(name)} by @middleware`);
202
+ }
203
+ middlewaresClasses[name] = value;
204
+ };
205
+ }
206
+
207
+ class MiddlewareListener {
208
+ constructor(parent) {
209
+ this.parent = parent;
210
+ this.listeners = {
211
+ register: [],
212
+ remove: [],
213
+ };
214
+ }
215
+ add(middleware) {
216
+ if (isRegistingMiddleware(middleware)) {
217
+ middleware.onRegister && this.on('register', middleware.onRegister);
218
+ }
219
+ if (isRemovingMiddleware(middleware)) {
220
+ middleware.onRemove && this.on('remove', middleware.onRemove);
221
+ }
222
+ }
223
+ remove(middleware) {
224
+ if (isRegistingMiddleware(middleware)) {
225
+ middleware.onRegister &&
226
+ this.off('register', middleware.onRegister);
227
+ }
228
+ if (isRemovingMiddleware(middleware)) {
229
+ middleware.onRemove && this.off('remove', middleware.onRemove);
230
+ }
231
+ }
232
+ on(event, listener) {
233
+ this.listeners[event].push(listener);
234
+ }
235
+ onRegister(container, dependencyId, dependency) {
236
+ var _a;
237
+ this.listeners.register.forEach((listener) => listener(container, dependencyId, dependency));
238
+ (_a = this.parent) === null || _a === undefined ? undefined : _a.onRegister(container, dependencyId, dependency);
239
+ }
240
+ onRemove(container, dependencyId) {
241
+ var _a;
242
+ this.listeners.remove.forEach((listener) => listener(container, dependencyId));
243
+ (_a = this.parent) === null || _a === undefined ? undefined : _a.onRemove(container, dependencyId);
244
+ }
245
+ off(event, listener) {
246
+ const index = this.listeners[event].indexOf(listener);
247
+ if (index !== -1) {
248
+ this.listeners[event].splice(index, 1);
249
+ }
250
+ }
251
+ }
252
+ function isRegistingMiddleware(middleware) {
253
+ return middleware.onRegister;
254
+ }
255
+ function isRemovingMiddleware(middleware) {
256
+ return middleware.onRemove;
257
+ }
258
+
193
259
  /**
194
260
  * A dependency injection container
195
261
  */
@@ -210,6 +276,7 @@ class ProxyDiContainer {
210
276
  */
211
277
  this.parentDependencyProxies = {};
212
278
  this.id = ProxyDiContainer.idCounter++;
279
+ this.middlewareListener = new MiddlewareListener(parent === null || parent === undefined ? undefined : parent.middlewareListener);
213
280
  if (parent) {
214
281
  this.parent = parent;
215
282
  this.parent.addChild(this);
@@ -217,28 +284,36 @@ class ProxyDiContainer {
217
284
  this.settings = Object.assign(Object.assign({}, DEFAULT_SETTINGS), settings);
218
285
  }
219
286
  register(dependency, dependencyId) {
287
+ var _a;
220
288
  if (this.dependencies[dependencyId]) {
221
289
  if (!this.settings.allowRewriteDependencies) {
222
290
  throw new Error(`ProxyDi already has dependency for ${String(dependencyId)}`);
223
291
  }
224
292
  }
225
- let dependencyInstance;
293
+ let instance;
226
294
  const isClass = typeof dependency === 'function';
227
295
  if (isClass) {
228
- dependencyInstance = this.createInstance(dependency, dependencyId);
296
+ instance = this.createInstance(dependency, dependencyId);
229
297
  }
230
298
  else {
231
- dependencyInstance = dependency;
232
- if (!(typeof dependencyInstance === 'object') &&
233
- !this.settings.allowRegisterAnything) {
234
- throw new Error(`Can't register as dependency (allowRegisterAnything is off for this contatiner): ${dependencyInstance}`);
235
- }
299
+ instance = dependency;
300
+ }
301
+ const isObject = typeof instance === 'object';
302
+ if (!isObject && !this.settings.allowRegisterAnything) {
303
+ throw new Error(`Can't register as dependency (allowRegisterAnything is off for this contatiner): ${instance}`);
236
304
  }
237
- if (typeof dependencyInstance === 'object') {
238
- dependencyInstance[PROXYDY_CONTAINER] = this;
305
+ if (isObject) {
306
+ instance[PROXYDI_CONTAINER] = this;
307
+ instance[DEPENDENCY_ID] = dependencyId;
239
308
  }
240
- this.registerImpl(dependencyInstance, dependencyId);
241
- return dependencyInstance;
309
+ this.injectDependenciesTo(instance);
310
+ this.dependencies[dependencyId] = instance;
311
+ const constructorName = (_a = instance.constructor) === null || _a === undefined ? undefined : _a.name;
312
+ if (constructorName && middlewaresClasses[constructorName]) {
313
+ this.middlewareListener.add(instance);
314
+ }
315
+ this.middlewareListener.onRegister(this, dependencyId, instance);
316
+ return instance;
242
317
  }
243
318
  createInstance(Dependency, dependencyId) {
244
319
  const paramIds = constructorInjections[dependencyId] || [];
@@ -249,18 +324,6 @@ class ProxyDiContainer {
249
324
  }
250
325
  return new Dependency(...params);
251
326
  }
252
- /**
253
- * Internal method that implements registeration of dependency and prepare it for injection.
254
- * @param dependencyId The unique identifier of the dependency.
255
- * @param dependency The dependency instance.
256
- */
257
- registerImpl(dependency, dependencyId) {
258
- this.injectDependenciesTo(dependency);
259
- if (typeof dependency === 'object') {
260
- dependency[DEPENDENCY_ID] = dependencyId;
261
- }
262
- this.dependencies[dependencyId] = dependency;
263
- }
264
327
  /**
265
328
  * Checks if a dependency with the given ID is known to the container or its ancestors which means that it can be resolved by this container
266
329
  * @param dependencyId The identifier of the dependency.
@@ -286,7 +349,7 @@ class ProxyDiContainer {
286
349
  }
287
350
  const instance = this.findDependency(dependency);
288
351
  if (instance) {
289
- if (instance[PROXYDY_CONTAINER] !== this &&
352
+ if (instance[PROXYDI_CONTAINER] !== this &&
290
353
  typeof instance === 'object' &&
291
354
  this.settings.resolveInContainerContext) {
292
355
  const proxy = makeDependencyProxy(instance);
@@ -310,6 +373,16 @@ class ProxyDiContainer {
310
373
  injection.set(injectionsOwner, dependencyProxy);
311
374
  });
312
375
  }
376
+ /**
377
+ * Creates instances for all injectable classes and registers them in this container.
378
+ * @returns This container to allow use along with constructor.
379
+ */
380
+ registerInjectables() {
381
+ for (const [dependencyId, InjectableClass] of Object.entries(injectableClasses)) {
382
+ this.register(InjectableClass, dependencyId);
383
+ }
384
+ return this;
385
+ }
313
386
  /**
314
387
  * Finalizes dependency injections, prevents further rewriting of dependencies,
315
388
  * and recursively bakes injections for child containers.
@@ -339,11 +412,16 @@ class ProxyDiContainer {
339
412
  * @param dependencyOrId The dependency instance or dependency identifier to remove.
340
413
  */
341
414
  remove(dependencyOrId) {
415
+ var _a;
342
416
  const id = isDependency(dependencyOrId)
343
417
  ? dependencyOrId[DEPENDENCY_ID]
344
418
  : dependencyOrId;
345
419
  const dependency = this.dependencies[id];
346
420
  if (dependency) {
421
+ const constructorName = (_a = dependency.constructor) === null || _a === undefined ? undefined : _a.name;
422
+ if (constructorName && middlewaresClasses[constructorName]) {
423
+ this.middlewareListener.remove(dependency);
424
+ }
347
425
  const dependencyInjects = dependency[INJECTIONS]
348
426
  ? dependency[INJECTIONS]
349
427
  : {};
@@ -352,6 +430,7 @@ class ProxyDiContainer {
352
430
  });
353
431
  delete dependency[DEPENDENCY_ID];
354
432
  delete this.dependencies[id];
433
+ this.middlewareListener.onRemove(this, id);
355
434
  }
356
435
  }
357
436
  /**
@@ -445,7 +524,7 @@ function resolveAll(instance, dependencyId) {
445
524
  const id = findInjectableId(dependencyId);
446
525
  return resolveAll(instance, id);
447
526
  }
448
- const container = instance[PROXYDY_CONTAINER];
527
+ const container = instance[PROXYDI_CONTAINER];
449
528
  if (!container) {
450
529
  throw new Error('Instance is not registered in any container');
451
530
  }
@@ -463,8 +542,9 @@ function recursiveResolveAll(container, dependencyId) {
463
542
  }
464
543
 
465
544
  exports.DEPENDENCY_ID = DEPENDENCY_ID;
466
- exports.PROXYDY_CONTAINER = PROXYDY_CONTAINER;
545
+ exports.PROXYDI_CONTAINER = PROXYDI_CONTAINER;
467
546
  exports.ProxyDiContainer = ProxyDiContainer;
468
547
  exports.inject = inject;
469
548
  exports.injectable = injectable;
549
+ exports.middleware = middleware;
470
550
  exports.resolveAll = resolveAll;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  export { inject } from './inject';
2
2
  export { ProxyDiContainer } from './ProxyDiContainer';
3
3
  export { injectable } from './injectable';
4
- export { Injection, DependencyId, DependencyClass, ContainerSettings, PROXYDY_CONTAINER, DEPENDENCY_ID, } from './types';
4
+ export { middleware } from './middleware/middleware';
5
+ export { MiddlewareRegisteringListener, MiddlewareRemovingListener, } from './middleware/MiddlewareListener';
6
+ export { Injection, DependencyId, DependencyClass, ContainerSettings, PROXYDI_CONTAINER, DEPENDENCY_ID, } from './types';
5
7
  export { resolveAll } from './resolveAll';
package/dist/index.js CHANGED
@@ -10,7 +10,7 @@ const DEPENDENCY_ID = Symbol('DependencyId');
10
10
  * This property is present in each dependency instance that was registered in ProxyDiContainer.
11
11
  * The property stores a reference to the ProxyDiContainer in which the dependency was registered.
12
12
  */
13
- const PROXYDY_CONTAINER = Symbol('proxyDiContainer');
13
+ const PROXYDI_CONTAINER = Symbol('proxyDiContainer');
14
14
  const IS_INJECTION_PROXY = Symbol('isInjectionProxy');
15
15
  const INJECTION_OWNER = Symbol('injectionOwner');
16
16
  const IS_INSTANCE_PROXY = Symbol('isInstanceProxy');
@@ -91,7 +91,7 @@ class InjectionProxy {
91
91
  constructor(onwer, container) {
92
92
  this[_a] = true;
93
93
  this[INJECTION_OWNER] = onwer;
94
- this[PROXYDY_CONTAINER] = container;
94
+ this[PROXYDI_CONTAINER] = container;
95
95
  }
96
96
  }
97
97
  _a = IS_INJECTION_PROXY;
@@ -188,6 +188,72 @@ function makeConstructorDependencyProxy(container, dependencyId) {
188
188
  });
189
189
  }
190
190
 
191
+ const middlewaresClasses = {};
192
+ function middleware() {
193
+ return function (value, context) {
194
+ if ((context === null || context === undefined ? undefined : context.kind) !== 'class') {
195
+ throw new Error('@middleware decorator should decorate classes');
196
+ }
197
+ const name = context.name;
198
+ if (middlewaresClasses[name]) {
199
+ throw new Error(`ProxyDi has already regisered middleware ${String(name)} by @middleware`);
200
+ }
201
+ middlewaresClasses[name] = value;
202
+ };
203
+ }
204
+
205
+ class MiddlewareListener {
206
+ constructor(parent) {
207
+ this.parent = parent;
208
+ this.listeners = {
209
+ register: [],
210
+ remove: [],
211
+ };
212
+ }
213
+ add(middleware) {
214
+ if (isRegistingMiddleware(middleware)) {
215
+ middleware.onRegister && this.on('register', middleware.onRegister);
216
+ }
217
+ if (isRemovingMiddleware(middleware)) {
218
+ middleware.onRemove && this.on('remove', middleware.onRemove);
219
+ }
220
+ }
221
+ remove(middleware) {
222
+ if (isRegistingMiddleware(middleware)) {
223
+ middleware.onRegister &&
224
+ this.off('register', middleware.onRegister);
225
+ }
226
+ if (isRemovingMiddleware(middleware)) {
227
+ middleware.onRemove && this.off('remove', middleware.onRemove);
228
+ }
229
+ }
230
+ on(event, listener) {
231
+ this.listeners[event].push(listener);
232
+ }
233
+ onRegister(container, dependencyId, dependency) {
234
+ var _a;
235
+ this.listeners.register.forEach((listener) => listener(container, dependencyId, dependency));
236
+ (_a = this.parent) === null || _a === undefined ? undefined : _a.onRegister(container, dependencyId, dependency);
237
+ }
238
+ onRemove(container, dependencyId) {
239
+ var _a;
240
+ this.listeners.remove.forEach((listener) => listener(container, dependencyId));
241
+ (_a = this.parent) === null || _a === undefined ? undefined : _a.onRemove(container, dependencyId);
242
+ }
243
+ off(event, listener) {
244
+ const index = this.listeners[event].indexOf(listener);
245
+ if (index !== -1) {
246
+ this.listeners[event].splice(index, 1);
247
+ }
248
+ }
249
+ }
250
+ function isRegistingMiddleware(middleware) {
251
+ return middleware.onRegister;
252
+ }
253
+ function isRemovingMiddleware(middleware) {
254
+ return middleware.onRemove;
255
+ }
256
+
191
257
  /**
192
258
  * A dependency injection container
193
259
  */
@@ -208,6 +274,7 @@ class ProxyDiContainer {
208
274
  */
209
275
  this.parentDependencyProxies = {};
210
276
  this.id = ProxyDiContainer.idCounter++;
277
+ this.middlewareListener = new MiddlewareListener(parent === null || parent === undefined ? undefined : parent.middlewareListener);
211
278
  if (parent) {
212
279
  this.parent = parent;
213
280
  this.parent.addChild(this);
@@ -215,28 +282,36 @@ class ProxyDiContainer {
215
282
  this.settings = Object.assign(Object.assign({}, DEFAULT_SETTINGS), settings);
216
283
  }
217
284
  register(dependency, dependencyId) {
285
+ var _a;
218
286
  if (this.dependencies[dependencyId]) {
219
287
  if (!this.settings.allowRewriteDependencies) {
220
288
  throw new Error(`ProxyDi already has dependency for ${String(dependencyId)}`);
221
289
  }
222
290
  }
223
- let dependencyInstance;
291
+ let instance;
224
292
  const isClass = typeof dependency === 'function';
225
293
  if (isClass) {
226
- dependencyInstance = this.createInstance(dependency, dependencyId);
294
+ instance = this.createInstance(dependency, dependencyId);
227
295
  }
228
296
  else {
229
- dependencyInstance = dependency;
230
- if (!(typeof dependencyInstance === 'object') &&
231
- !this.settings.allowRegisterAnything) {
232
- throw new Error(`Can't register as dependency (allowRegisterAnything is off for this contatiner): ${dependencyInstance}`);
233
- }
297
+ instance = dependency;
298
+ }
299
+ const isObject = typeof instance === 'object';
300
+ if (!isObject && !this.settings.allowRegisterAnything) {
301
+ throw new Error(`Can't register as dependency (allowRegisterAnything is off for this contatiner): ${instance}`);
234
302
  }
235
- if (typeof dependencyInstance === 'object') {
236
- dependencyInstance[PROXYDY_CONTAINER] = this;
303
+ if (isObject) {
304
+ instance[PROXYDI_CONTAINER] = this;
305
+ instance[DEPENDENCY_ID] = dependencyId;
237
306
  }
238
- this.registerImpl(dependencyInstance, dependencyId);
239
- return dependencyInstance;
307
+ this.injectDependenciesTo(instance);
308
+ this.dependencies[dependencyId] = instance;
309
+ const constructorName = (_a = instance.constructor) === null || _a === undefined ? undefined : _a.name;
310
+ if (constructorName && middlewaresClasses[constructorName]) {
311
+ this.middlewareListener.add(instance);
312
+ }
313
+ this.middlewareListener.onRegister(this, dependencyId, instance);
314
+ return instance;
240
315
  }
241
316
  createInstance(Dependency, dependencyId) {
242
317
  const paramIds = constructorInjections[dependencyId] || [];
@@ -247,18 +322,6 @@ class ProxyDiContainer {
247
322
  }
248
323
  return new Dependency(...params);
249
324
  }
250
- /**
251
- * Internal method that implements registeration of dependency and prepare it for injection.
252
- * @param dependencyId The unique identifier of the dependency.
253
- * @param dependency The dependency instance.
254
- */
255
- registerImpl(dependency, dependencyId) {
256
- this.injectDependenciesTo(dependency);
257
- if (typeof dependency === 'object') {
258
- dependency[DEPENDENCY_ID] = dependencyId;
259
- }
260
- this.dependencies[dependencyId] = dependency;
261
- }
262
325
  /**
263
326
  * Checks if a dependency with the given ID is known to the container or its ancestors which means that it can be resolved by this container
264
327
  * @param dependencyId The identifier of the dependency.
@@ -284,7 +347,7 @@ class ProxyDiContainer {
284
347
  }
285
348
  const instance = this.findDependency(dependency);
286
349
  if (instance) {
287
- if (instance[PROXYDY_CONTAINER] !== this &&
350
+ if (instance[PROXYDI_CONTAINER] !== this &&
288
351
  typeof instance === 'object' &&
289
352
  this.settings.resolveInContainerContext) {
290
353
  const proxy = makeDependencyProxy(instance);
@@ -308,6 +371,16 @@ class ProxyDiContainer {
308
371
  injection.set(injectionsOwner, dependencyProxy);
309
372
  });
310
373
  }
374
+ /**
375
+ * Creates instances for all injectable classes and registers them in this container.
376
+ * @returns This container to allow use along with constructor.
377
+ */
378
+ registerInjectables() {
379
+ for (const [dependencyId, InjectableClass] of Object.entries(injectableClasses)) {
380
+ this.register(InjectableClass, dependencyId);
381
+ }
382
+ return this;
383
+ }
311
384
  /**
312
385
  * Finalizes dependency injections, prevents further rewriting of dependencies,
313
386
  * and recursively bakes injections for child containers.
@@ -337,11 +410,16 @@ class ProxyDiContainer {
337
410
  * @param dependencyOrId The dependency instance or dependency identifier to remove.
338
411
  */
339
412
  remove(dependencyOrId) {
413
+ var _a;
340
414
  const id = isDependency(dependencyOrId)
341
415
  ? dependencyOrId[DEPENDENCY_ID]
342
416
  : dependencyOrId;
343
417
  const dependency = this.dependencies[id];
344
418
  if (dependency) {
419
+ const constructorName = (_a = dependency.constructor) === null || _a === undefined ? undefined : _a.name;
420
+ if (constructorName && middlewaresClasses[constructorName]) {
421
+ this.middlewareListener.remove(dependency);
422
+ }
345
423
  const dependencyInjects = dependency[INJECTIONS]
346
424
  ? dependency[INJECTIONS]
347
425
  : {};
@@ -350,6 +428,7 @@ class ProxyDiContainer {
350
428
  });
351
429
  delete dependency[DEPENDENCY_ID];
352
430
  delete this.dependencies[id];
431
+ this.middlewareListener.onRemove(this, id);
353
432
  }
354
433
  }
355
434
  /**
@@ -443,7 +522,7 @@ function resolveAll(instance, dependencyId) {
443
522
  const id = findInjectableId(dependencyId);
444
523
  return resolveAll(instance, id);
445
524
  }
446
- const container = instance[PROXYDY_CONTAINER];
525
+ const container = instance[PROXYDI_CONTAINER];
447
526
  if (!container) {
448
527
  throw new Error('Instance is not registered in any container');
449
528
  }
@@ -460,4 +539,4 @@ function recursiveResolveAll(container, dependencyId) {
460
539
  return all;
461
540
  }
462
541
 
463
- export { DEPENDENCY_ID, PROXYDY_CONTAINER, ProxyDiContainer, inject, injectable, resolveAll };
542
+ export { DEPENDENCY_ID, PROXYDI_CONTAINER, ProxyDiContainer, inject, injectable, middleware, resolveAll };
package/dist/index.umd.js CHANGED
@@ -16,7 +16,7 @@
16
16
  * This property is present in each dependency instance that was registered in ProxyDiContainer.
17
17
  * The property stores a reference to the ProxyDiContainer in which the dependency was registered.
18
18
  */
19
- const PROXYDY_CONTAINER = Symbol('proxyDiContainer');
19
+ const PROXYDI_CONTAINER = Symbol('proxyDiContainer');
20
20
  const IS_INJECTION_PROXY = Symbol('isInjectionProxy');
21
21
  const INJECTION_OWNER = Symbol('injectionOwner');
22
22
  const IS_INSTANCE_PROXY = Symbol('isInstanceProxy');
@@ -97,7 +97,7 @@
97
97
  constructor(onwer, container) {
98
98
  this[_a] = true;
99
99
  this[INJECTION_OWNER] = onwer;
100
- this[PROXYDY_CONTAINER] = container;
100
+ this[PROXYDI_CONTAINER] = container;
101
101
  }
102
102
  }
103
103
  _a = IS_INJECTION_PROXY;
@@ -194,6 +194,72 @@
194
194
  });
195
195
  }
196
196
 
197
+ const middlewaresClasses = {};
198
+ function middleware() {
199
+ return function (value, context) {
200
+ if ((context === null || context === undefined ? undefined : context.kind) !== 'class') {
201
+ throw new Error('@middleware decorator should decorate classes');
202
+ }
203
+ const name = context.name;
204
+ if (middlewaresClasses[name]) {
205
+ throw new Error(`ProxyDi has already regisered middleware ${String(name)} by @middleware`);
206
+ }
207
+ middlewaresClasses[name] = value;
208
+ };
209
+ }
210
+
211
+ class MiddlewareListener {
212
+ constructor(parent) {
213
+ this.parent = parent;
214
+ this.listeners = {
215
+ register: [],
216
+ remove: [],
217
+ };
218
+ }
219
+ add(middleware) {
220
+ if (isRegistingMiddleware(middleware)) {
221
+ middleware.onRegister && this.on('register', middleware.onRegister);
222
+ }
223
+ if (isRemovingMiddleware(middleware)) {
224
+ middleware.onRemove && this.on('remove', middleware.onRemove);
225
+ }
226
+ }
227
+ remove(middleware) {
228
+ if (isRegistingMiddleware(middleware)) {
229
+ middleware.onRegister &&
230
+ this.off('register', middleware.onRegister);
231
+ }
232
+ if (isRemovingMiddleware(middleware)) {
233
+ middleware.onRemove && this.off('remove', middleware.onRemove);
234
+ }
235
+ }
236
+ on(event, listener) {
237
+ this.listeners[event].push(listener);
238
+ }
239
+ onRegister(container, dependencyId, dependency) {
240
+ var _a;
241
+ this.listeners.register.forEach((listener) => listener(container, dependencyId, dependency));
242
+ (_a = this.parent) === null || _a === undefined ? undefined : _a.onRegister(container, dependencyId, dependency);
243
+ }
244
+ onRemove(container, dependencyId) {
245
+ var _a;
246
+ this.listeners.remove.forEach((listener) => listener(container, dependencyId));
247
+ (_a = this.parent) === null || _a === undefined ? undefined : _a.onRemove(container, dependencyId);
248
+ }
249
+ off(event, listener) {
250
+ const index = this.listeners[event].indexOf(listener);
251
+ if (index !== -1) {
252
+ this.listeners[event].splice(index, 1);
253
+ }
254
+ }
255
+ }
256
+ function isRegistingMiddleware(middleware) {
257
+ return middleware.onRegister;
258
+ }
259
+ function isRemovingMiddleware(middleware) {
260
+ return middleware.onRemove;
261
+ }
262
+
197
263
  /**
198
264
  * A dependency injection container
199
265
  */
@@ -214,6 +280,7 @@
214
280
  */
215
281
  this.parentDependencyProxies = {};
216
282
  this.id = ProxyDiContainer.idCounter++;
283
+ this.middlewareListener = new MiddlewareListener(parent === null || parent === undefined ? undefined : parent.middlewareListener);
217
284
  if (parent) {
218
285
  this.parent = parent;
219
286
  this.parent.addChild(this);
@@ -221,28 +288,36 @@
221
288
  this.settings = Object.assign(Object.assign({}, DEFAULT_SETTINGS), settings);
222
289
  }
223
290
  register(dependency, dependencyId) {
291
+ var _a;
224
292
  if (this.dependencies[dependencyId]) {
225
293
  if (!this.settings.allowRewriteDependencies) {
226
294
  throw new Error(`ProxyDi already has dependency for ${String(dependencyId)}`);
227
295
  }
228
296
  }
229
- let dependencyInstance;
297
+ let instance;
230
298
  const isClass = typeof dependency === 'function';
231
299
  if (isClass) {
232
- dependencyInstance = this.createInstance(dependency, dependencyId);
300
+ instance = this.createInstance(dependency, dependencyId);
233
301
  }
234
302
  else {
235
- dependencyInstance = dependency;
236
- if (!(typeof dependencyInstance === 'object') &&
237
- !this.settings.allowRegisterAnything) {
238
- throw new Error(`Can't register as dependency (allowRegisterAnything is off for this contatiner): ${dependencyInstance}`);
239
- }
303
+ instance = dependency;
304
+ }
305
+ const isObject = typeof instance === 'object';
306
+ if (!isObject && !this.settings.allowRegisterAnything) {
307
+ throw new Error(`Can't register as dependency (allowRegisterAnything is off for this contatiner): ${instance}`);
240
308
  }
241
- if (typeof dependencyInstance === 'object') {
242
- dependencyInstance[PROXYDY_CONTAINER] = this;
309
+ if (isObject) {
310
+ instance[PROXYDI_CONTAINER] = this;
311
+ instance[DEPENDENCY_ID] = dependencyId;
243
312
  }
244
- this.registerImpl(dependencyInstance, dependencyId);
245
- return dependencyInstance;
313
+ this.injectDependenciesTo(instance);
314
+ this.dependencies[dependencyId] = instance;
315
+ const constructorName = (_a = instance.constructor) === null || _a === undefined ? undefined : _a.name;
316
+ if (constructorName && middlewaresClasses[constructorName]) {
317
+ this.middlewareListener.add(instance);
318
+ }
319
+ this.middlewareListener.onRegister(this, dependencyId, instance);
320
+ return instance;
246
321
  }
247
322
  createInstance(Dependency, dependencyId) {
248
323
  const paramIds = constructorInjections[dependencyId] || [];
@@ -253,18 +328,6 @@
253
328
  }
254
329
  return new Dependency(...params);
255
330
  }
256
- /**
257
- * Internal method that implements registeration of dependency and prepare it for injection.
258
- * @param dependencyId The unique identifier of the dependency.
259
- * @param dependency The dependency instance.
260
- */
261
- registerImpl(dependency, dependencyId) {
262
- this.injectDependenciesTo(dependency);
263
- if (typeof dependency === 'object') {
264
- dependency[DEPENDENCY_ID] = dependencyId;
265
- }
266
- this.dependencies[dependencyId] = dependency;
267
- }
268
331
  /**
269
332
  * Checks if a dependency with the given ID is known to the container or its ancestors which means that it can be resolved by this container
270
333
  * @param dependencyId The identifier of the dependency.
@@ -290,7 +353,7 @@
290
353
  }
291
354
  const instance = this.findDependency(dependency);
292
355
  if (instance) {
293
- if (instance[PROXYDY_CONTAINER] !== this &&
356
+ if (instance[PROXYDI_CONTAINER] !== this &&
294
357
  typeof instance === 'object' &&
295
358
  this.settings.resolveInContainerContext) {
296
359
  const proxy = makeDependencyProxy(instance);
@@ -314,6 +377,16 @@
314
377
  injection.set(injectionsOwner, dependencyProxy);
315
378
  });
316
379
  }
380
+ /**
381
+ * Creates instances for all injectable classes and registers them in this container.
382
+ * @returns This container to allow use along with constructor.
383
+ */
384
+ registerInjectables() {
385
+ for (const [dependencyId, InjectableClass] of Object.entries(injectableClasses)) {
386
+ this.register(InjectableClass, dependencyId);
387
+ }
388
+ return this;
389
+ }
317
390
  /**
318
391
  * Finalizes dependency injections, prevents further rewriting of dependencies,
319
392
  * and recursively bakes injections for child containers.
@@ -343,11 +416,16 @@
343
416
  * @param dependencyOrId The dependency instance or dependency identifier to remove.
344
417
  */
345
418
  remove(dependencyOrId) {
419
+ var _a;
346
420
  const id = isDependency(dependencyOrId)
347
421
  ? dependencyOrId[DEPENDENCY_ID]
348
422
  : dependencyOrId;
349
423
  const dependency = this.dependencies[id];
350
424
  if (dependency) {
425
+ const constructorName = (_a = dependency.constructor) === null || _a === undefined ? undefined : _a.name;
426
+ if (constructorName && middlewaresClasses[constructorName]) {
427
+ this.middlewareListener.remove(dependency);
428
+ }
351
429
  const dependencyInjects = dependency[INJECTIONS]
352
430
  ? dependency[INJECTIONS]
353
431
  : {};
@@ -356,6 +434,7 @@
356
434
  });
357
435
  delete dependency[DEPENDENCY_ID];
358
436
  delete this.dependencies[id];
437
+ this.middlewareListener.onRemove(this, id);
359
438
  }
360
439
  }
361
440
  /**
@@ -449,7 +528,7 @@
449
528
  const id = findInjectableId(dependencyId);
450
529
  return resolveAll(instance, id);
451
530
  }
452
- const container = instance[PROXYDY_CONTAINER];
531
+ const container = instance[PROXYDI_CONTAINER];
453
532
  if (!container) {
454
533
  throw new Error('Instance is not registered in any container');
455
534
  }
@@ -467,10 +546,11 @@
467
546
  }
468
547
 
469
548
  exports.DEPENDENCY_ID = DEPENDENCY_ID;
470
- exports.PROXYDY_CONTAINER = PROXYDY_CONTAINER;
549
+ exports.PROXYDI_CONTAINER = PROXYDI_CONTAINER;
471
550
  exports.ProxyDiContainer = ProxyDiContainer;
472
551
  exports.inject = inject;
473
552
  exports.injectable = injectable;
553
+ exports.middleware = middleware;
474
554
  exports.resolveAll = resolveAll;
475
555
 
476
556
  }));
@@ -0,0 +1,29 @@
1
+ import { ProxyDiContainer } from '../ProxyDiContainer';
2
+ import { DependencyId } from '../types';
3
+ export interface MiddlewareListenerEvent {
4
+ register: (container: ProxyDiContainer, dependencyId: DependencyId, dependency: any) => void;
5
+ remove: (container: ProxyDiContainer, dependencyId: DependencyId) => void;
6
+ }
7
+ /**
8
+ * Describe the middleware that able to listen to the registering of a dependency in containers hierarchy
9
+ */
10
+ export interface MiddlewareRegisteringListener {
11
+ onRegister(container: ProxyDiContainer, dependencyId: DependencyId, dependency: any): void;
12
+ }
13
+ /**
14
+ * Describe the middleware that able to listen to the removing of a dependency in containers hierarchy
15
+ */
16
+ export interface MiddlewareRemovingListener {
17
+ onRemove(container: ProxyDiContainer, dependencyId: DependencyId): void;
18
+ }
19
+ export declare class MiddlewareListener {
20
+ private parent?;
21
+ private listeners;
22
+ constructor(parent?: MiddlewareListener | undefined);
23
+ add(middleware: any): void;
24
+ remove(middleware: any): void;
25
+ private on;
26
+ onRegister(container: ProxyDiContainer, dependencyId: DependencyId, dependency: any): void;
27
+ onRemove(container: ProxyDiContainer, dependencyId: DependencyId): void;
28
+ private off;
29
+ }
@@ -0,0 +1,3 @@
1
+ import { DependencyClass, DependencyId } from '../types';
2
+ export declare const middlewaresClasses: Record<DependencyId, DependencyClass<any>>;
3
+ export declare function middleware(): any;
package/dist/types.d.ts CHANGED
@@ -12,7 +12,7 @@ export type IProxyDiContainer = {
12
12
  isKnown: (dependencyId: DependencyId) => boolean;
13
13
  injectDependenciesTo: (dependency: any) => void;
14
14
  register: (dependency: any, dependencyId: DependencyId) => any;
15
- resolve: <T>(dependencyId: DependencyId) => T & ContainerizedDependency;
15
+ resolve: <T>(dependencyId: DependencyId | DependencyClass<any>) => T & ContainerizedDependency;
16
16
  createChildContainer: () => IProxyDiContainer;
17
17
  children: IProxyDiContainer[];
18
18
  getChild(id: number): IProxyDiContainer;
@@ -32,7 +32,7 @@ export declare const DEPENDENCY_ID: unique symbol;
32
32
  * This property is present in each dependency instance that was registered in ProxyDiContainer.
33
33
  * The property stores a reference to the ProxyDiContainer in which the dependency was registered.
34
34
  */
35
- export declare const PROXYDY_CONTAINER: unique symbol;
35
+ export declare const PROXYDI_CONTAINER: unique symbol;
36
36
  export type Injections = Record<string | symbol, Injection>;
37
37
  export type Dependency = {
38
38
  [INJECTIONS]: Injections;
@@ -48,7 +48,7 @@ export type ContainerizedDependency = Dependency & {
48
48
  /**
49
49
  * ProxyDi container in which this instance was registered
50
50
  */
51
- [PROXYDY_CONTAINER]: IProxyDiContainer;
51
+ [PROXYDI_CONTAINER]: IProxyDiContainer;
52
52
  };
53
53
  export type ContainerSettings = {
54
54
  allowRegisterAnything?: boolean;
@@ -61,5 +61,5 @@ export declare const IS_INSTANCE_PROXY: unique symbol;
61
61
  export type InjectionProxy = {
62
62
  [IS_INJECTION_PROXY]: true;
63
63
  [INJECTION_OWNER]: ContainerizedDependency;
64
- [PROXYDY_CONTAINER]: IProxyDiContainer;
64
+ [PROXYDI_CONTAINER]: IProxyDiContainer;
65
65
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "proxydi",
3
- "version": "0.0.10",
3
+ "version": "0.0.12",
4
4
  "description": "A typed hierarchical DI container that resolves circular dependencies via Proxy",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",