proxydi 0.0.11 → 0.0.13

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 CHANGED
@@ -5,11 +5,27 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
- ## [[0.0.10](https://www.npmjs.com/package/proxydi/v/0.0.11)] - 2025-03-03
8
+ ## [[0.0.13](https://www.npmjs.com/package/proxydi/v/0.0.13)] - 2025-03-04
9
9
 
10
10
  ### Added
11
11
 
12
- - resolveInjectables() method [#12](https://github.com/proxy-di/proxydi/pull/13)
12
+ - inject, resolve register by class [#15](https://github.com/proxy-di/proxydi/pull/15)
13
+
14
+ ### Changed
15
+
16
+ - remove event requires dependency
17
+
18
+ ## [[0.0.12](https://www.npmjs.com/package/proxydi/v/0.0.12)] - 2025-03-04
19
+
20
+ ### Added
21
+
22
+ - Experimental @middleware [#14](https://github.com/proxy-di/proxydi/pull/14)
23
+
24
+ ## [[0.0.11](https://www.npmjs.com/package/proxydi/v/0.0.11)] - 2025-03-03
25
+
26
+ ### Added
27
+
28
+ - resolveInjectables() method [#13](https://github.com/proxy-di/proxydi/pull/13)
13
29
 
14
30
  ## [[0.0.10](https://www.npmjs.com/package/proxydi/v/0.0.10)] - 2025-03-02
15
31
 
package/README.md CHANGED
@@ -17,6 +17,7 @@ Core features:
17
17
  Eperimemntal features:
18
18
 
19
19
  - Construtor injections (see unit tests for examples)
20
+ - Middleware listeners (see unit tests for examples)
20
21
  - Matches dependencies by unique identifiers or automatically using class names and property names
21
22
 
22
23
  ## Quick start
@@ -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.
@@ -44,15 +45,9 @@ export declare class ProxyDiContainer implements IProxyDiContainer {
44
45
  * @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
46
  * @returns Dependency instance, registered in container
46
47
  */
47
- register<T>(DependencyClass: DependencyClass<T>, dependencyId: DependencyId): T & ContainerizedDependency;
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.
package/dist/index.cjs CHANGED
@@ -1,5 +1,40 @@
1
1
  'use strict';
2
2
 
3
+ const injectableClasses = {};
4
+ const constructorInjections = {};
5
+ function injectable(dependencyOrDependencies, autoInjecions) {
6
+ return function (value, context) {
7
+ if ((context === null || context === undefined ? undefined : context.kind) !== 'class') {
8
+ throw new Error('@injectable decorator should decorate classes');
9
+ }
10
+ const name = dependencyOrDependencies
11
+ ? Array.isArray(dependencyOrDependencies)
12
+ ? context.name
13
+ : dependencyOrDependencies
14
+ : context.name;
15
+ const injectToConstructor = dependencyOrDependencies
16
+ ? Array.isArray(dependencyOrDependencies)
17
+ ? dependencyOrDependencies
18
+ : autoInjecions
19
+ : autoInjecions;
20
+ if (injectableClasses[name]) {
21
+ throw new Error(`ProxyDi has already regisered dependency ${String(name)} by @injectable`);
22
+ }
23
+ injectableClasses[name] = value;
24
+ if (injectToConstructor) {
25
+ constructorInjections[name] = injectToConstructor;
26
+ }
27
+ };
28
+ }
29
+ function findInjectableId(injectable) {
30
+ for (const [id, DependencyClass] of Object.entries(injectableClasses)) {
31
+ if (DependencyClass === injectable) {
32
+ return id;
33
+ }
34
+ }
35
+ throw new Error(`Class is not @injectable: ${injectable.name}`);
36
+ }
37
+
3
38
  const INJECTIONS = Symbol('injections');
4
39
  /**
5
40
  * This symbol constant defines a property name.
@@ -28,7 +63,11 @@ const IS_INSTANCE_PROXY = Symbol('isInstanceProxy');
28
63
  const inject = (dependencyId) => {
29
64
  return function (_value, context) {
30
65
  if ((context === null || context === undefined ? undefined : context.kind) === 'field') {
31
- const id = dependencyId ? dependencyId : context.name;
66
+ const id = dependencyId
67
+ ? typeof dependencyId === 'function'
68
+ ? findInjectableId(dependencyId)
69
+ : dependencyId
70
+ : context.name;
32
71
  const injection = {
33
72
  property: context.name,
34
73
  dependencyId: id,
@@ -47,41 +86,6 @@ const inject = (dependencyId) => {
47
86
  };
48
87
  };
49
88
 
50
- const injectableClasses = {};
51
- const constructorInjections = {};
52
- function injectable(dependencyOrDependencies, autoInjecions) {
53
- return function (value, context) {
54
- if ((context === null || context === undefined ? undefined : context.kind) !== 'class') {
55
- throw new Error('@injectable decorator should decorate classes');
56
- }
57
- const name = dependencyOrDependencies
58
- ? Array.isArray(dependencyOrDependencies)
59
- ? context.name
60
- : dependencyOrDependencies
61
- : context.name;
62
- const injectToConstructor = dependencyOrDependencies
63
- ? Array.isArray(dependencyOrDependencies)
64
- ? dependencyOrDependencies
65
- : autoInjecions
66
- : autoInjecions;
67
- if (injectableClasses[name]) {
68
- throw new Error(`ProxyDi has already regisered dependency ${String(name)} by @injectable`);
69
- }
70
- injectableClasses[name] = value;
71
- if (injectToConstructor) {
72
- constructorInjections[name] = injectToConstructor;
73
- }
74
- };
75
- }
76
- function findInjectableId(injectable) {
77
- for (const [id, DependencyClass] of Object.entries(injectableClasses)) {
78
- if (DependencyClass === injectable) {
79
- return id;
80
- }
81
- }
82
- throw new Error(`Class is not @injectable: ${injectable.name}`);
83
- }
84
-
85
89
  const DEFAULT_SETTINGS = {
86
90
  allowRegisterAnything: false,
87
91
  allowRewriteDependencies: false,
@@ -190,6 +194,72 @@ function makeConstructorDependencyProxy(container, dependencyId) {
190
194
  });
191
195
  }
192
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, dependency) {
245
+ var _a;
246
+ this.listeners.remove.forEach((listener) => listener(container, dependencyId, dependency));
247
+ (_a = this.parent) === null || _a === undefined ? undefined : _a.onRemove(container, dependencyId, dependency);
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
+
193
263
  /**
194
264
  * A dependency injection container
195
265
  */
@@ -210,35 +280,55 @@ class ProxyDiContainer {
210
280
  */
211
281
  this.parentDependencyProxies = {};
212
282
  this.id = ProxyDiContainer.idCounter++;
283
+ this.middlewareListener = new MiddlewareListener(parent === null || parent === undefined ? undefined : parent.middlewareListener);
213
284
  if (parent) {
214
285
  this.parent = parent;
215
286
  this.parent.addChild(this);
216
287
  }
217
288
  this.settings = Object.assign(Object.assign({}, DEFAULT_SETTINGS), settings);
218
289
  }
219
- register(dependency, dependencyId) {
220
- if (this.dependencies[dependencyId]) {
290
+ register(dependency, dependecyId) {
291
+ var _a;
292
+ let id = dependecyId;
293
+ if (!id) {
294
+ if (typeof dependency === 'function') {
295
+ try {
296
+ id = findInjectableId(dependency);
297
+ }
298
+ catch (_b) {
299
+ id = dependency.name;
300
+ }
301
+ }
302
+ }
303
+ if (this.dependencies[id]) {
221
304
  if (!this.settings.allowRewriteDependencies) {
222
- throw new Error(`ProxyDi already has dependency for ${String(dependencyId)}`);
305
+ throw new Error(`ProxyDi already has dependency for ${String(id)}`);
223
306
  }
224
307
  }
225
- let dependencyInstance;
308
+ let instance;
226
309
  const isClass = typeof dependency === 'function';
227
310
  if (isClass) {
228
- dependencyInstance = this.createInstance(dependency, dependencyId);
311
+ instance = this.createInstance(dependency, id);
229
312
  }
230
313
  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
- }
314
+ instance = dependency;
236
315
  }
237
- if (typeof dependencyInstance === 'object') {
238
- dependencyInstance[PROXYDI_CONTAINER] = this;
316
+ const isObject = typeof instance === 'object';
317
+ if (!isObject && !this.settings.allowRegisterAnything) {
318
+ throw new Error(`Can't register as dependency (allowRegisterAnything is off for this contatiner): ${instance}`);
239
319
  }
240
- this.registerImpl(dependencyInstance, dependencyId);
241
- return dependencyInstance;
320
+ if (isObject) {
321
+ instance[PROXYDI_CONTAINER] = this;
322
+ instance[DEPENDENCY_ID] = id;
323
+ }
324
+ this.injectDependenciesTo(instance);
325
+ this.dependencies[id] = instance;
326
+ const constructorName = (_a = instance.constructor) === null || _a === undefined ? undefined : _a.name;
327
+ if (constructorName && middlewaresClasses[constructorName]) {
328
+ this.middlewareListener.add(instance);
329
+ }
330
+ this.middlewareListener.onRegister(this, id, instance);
331
+ return instance;
242
332
  }
243
333
  createInstance(Dependency, dependencyId) {
244
334
  const paramIds = constructorInjections[dependencyId] || [];
@@ -249,18 +339,6 @@ class ProxyDiContainer {
249
339
  }
250
340
  return new Dependency(...params);
251
341
  }
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
342
  /**
265
343
  * 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
344
  * @param dependencyId The identifier of the dependency.
@@ -274,7 +352,13 @@ class ProxyDiContainer {
274
352
  }
275
353
  resolve(dependency) {
276
354
  if (typeof dependency === 'function') {
277
- const id = findInjectableId(dependency);
355
+ let id;
356
+ try {
357
+ id = findInjectableId(dependency);
358
+ }
359
+ catch (_a) {
360
+ id = dependency.name;
361
+ }
278
362
  return this.resolve(id);
279
363
  }
280
364
  if (!this.isKnown(dependency)) {
@@ -349,11 +433,16 @@ class ProxyDiContainer {
349
433
  * @param dependencyOrId The dependency instance or dependency identifier to remove.
350
434
  */
351
435
  remove(dependencyOrId) {
436
+ var _a;
352
437
  const id = isDependency(dependencyOrId)
353
438
  ? dependencyOrId[DEPENDENCY_ID]
354
439
  : dependencyOrId;
355
440
  const dependency = this.dependencies[id];
356
441
  if (dependency) {
442
+ const constructorName = (_a = dependency.constructor) === null || _a === undefined ? undefined : _a.name;
443
+ if (constructorName && middlewaresClasses[constructorName]) {
444
+ this.middlewareListener.remove(dependency);
445
+ }
357
446
  const dependencyInjects = dependency[INJECTIONS]
358
447
  ? dependency[INJECTIONS]
359
448
  : {};
@@ -362,6 +451,7 @@ class ProxyDiContainer {
362
451
  });
363
452
  delete dependency[DEPENDENCY_ID];
364
453
  delete this.dependencies[id];
454
+ this.middlewareListener.onRemove(this, id, dependency);
365
455
  }
366
456
  }
367
457
  /**
@@ -477,4 +567,5 @@ exports.PROXYDI_CONTAINER = PROXYDI_CONTAINER;
477
567
  exports.ProxyDiContainer = ProxyDiContainer;
478
568
  exports.inject = inject;
479
569
  exports.injectable = injectable;
570
+ exports.middleware = middleware;
480
571
  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 { middleware } from './middleware/middleware';
5
+ export { MiddlewareRegisteringListener, MiddlewareRemovingListener, } from './middleware/MiddlewareListener';
4
6
  export { Injection, DependencyId, DependencyClass, ContainerSettings, PROXYDI_CONTAINER, DEPENDENCY_ID, } from './types';
5
7
  export { resolveAll } from './resolveAll';
package/dist/index.js CHANGED
@@ -1,3 +1,38 @@
1
+ const injectableClasses = {};
2
+ const constructorInjections = {};
3
+ function injectable(dependencyOrDependencies, autoInjecions) {
4
+ return function (value, context) {
5
+ if ((context === null || context === undefined ? undefined : context.kind) !== 'class') {
6
+ throw new Error('@injectable decorator should decorate classes');
7
+ }
8
+ const name = dependencyOrDependencies
9
+ ? Array.isArray(dependencyOrDependencies)
10
+ ? context.name
11
+ : dependencyOrDependencies
12
+ : context.name;
13
+ const injectToConstructor = dependencyOrDependencies
14
+ ? Array.isArray(dependencyOrDependencies)
15
+ ? dependencyOrDependencies
16
+ : autoInjecions
17
+ : autoInjecions;
18
+ if (injectableClasses[name]) {
19
+ throw new Error(`ProxyDi has already regisered dependency ${String(name)} by @injectable`);
20
+ }
21
+ injectableClasses[name] = value;
22
+ if (injectToConstructor) {
23
+ constructorInjections[name] = injectToConstructor;
24
+ }
25
+ };
26
+ }
27
+ function findInjectableId(injectable) {
28
+ for (const [id, DependencyClass] of Object.entries(injectableClasses)) {
29
+ if (DependencyClass === injectable) {
30
+ return id;
31
+ }
32
+ }
33
+ throw new Error(`Class is not @injectable: ${injectable.name}`);
34
+ }
35
+
1
36
  const INJECTIONS = Symbol('injections');
2
37
  /**
3
38
  * This symbol constant defines a property name.
@@ -26,7 +61,11 @@ const IS_INSTANCE_PROXY = Symbol('isInstanceProxy');
26
61
  const inject = (dependencyId) => {
27
62
  return function (_value, context) {
28
63
  if ((context === null || context === undefined ? undefined : context.kind) === 'field') {
29
- const id = dependencyId ? dependencyId : context.name;
64
+ const id = dependencyId
65
+ ? typeof dependencyId === 'function'
66
+ ? findInjectableId(dependencyId)
67
+ : dependencyId
68
+ : context.name;
30
69
  const injection = {
31
70
  property: context.name,
32
71
  dependencyId: id,
@@ -45,41 +84,6 @@ const inject = (dependencyId) => {
45
84
  };
46
85
  };
47
86
 
48
- const injectableClasses = {};
49
- const constructorInjections = {};
50
- function injectable(dependencyOrDependencies, autoInjecions) {
51
- return function (value, context) {
52
- if ((context === null || context === undefined ? undefined : context.kind) !== 'class') {
53
- throw new Error('@injectable decorator should decorate classes');
54
- }
55
- const name = dependencyOrDependencies
56
- ? Array.isArray(dependencyOrDependencies)
57
- ? context.name
58
- : dependencyOrDependencies
59
- : context.name;
60
- const injectToConstructor = dependencyOrDependencies
61
- ? Array.isArray(dependencyOrDependencies)
62
- ? dependencyOrDependencies
63
- : autoInjecions
64
- : autoInjecions;
65
- if (injectableClasses[name]) {
66
- throw new Error(`ProxyDi has already regisered dependency ${String(name)} by @injectable`);
67
- }
68
- injectableClasses[name] = value;
69
- if (injectToConstructor) {
70
- constructorInjections[name] = injectToConstructor;
71
- }
72
- };
73
- }
74
- function findInjectableId(injectable) {
75
- for (const [id, DependencyClass] of Object.entries(injectableClasses)) {
76
- if (DependencyClass === injectable) {
77
- return id;
78
- }
79
- }
80
- throw new Error(`Class is not @injectable: ${injectable.name}`);
81
- }
82
-
83
87
  const DEFAULT_SETTINGS = {
84
88
  allowRegisterAnything: false,
85
89
  allowRewriteDependencies: false,
@@ -188,6 +192,72 @@ function makeConstructorDependencyProxy(container, dependencyId) {
188
192
  });
189
193
  }
190
194
 
195
+ const middlewaresClasses = {};
196
+ function middleware() {
197
+ return function (value, context) {
198
+ if ((context === null || context === undefined ? undefined : context.kind) !== 'class') {
199
+ throw new Error('@middleware decorator should decorate classes');
200
+ }
201
+ const name = context.name;
202
+ if (middlewaresClasses[name]) {
203
+ throw new Error(`ProxyDi has already regisered middleware ${String(name)} by @middleware`);
204
+ }
205
+ middlewaresClasses[name] = value;
206
+ };
207
+ }
208
+
209
+ class MiddlewareListener {
210
+ constructor(parent) {
211
+ this.parent = parent;
212
+ this.listeners = {
213
+ register: [],
214
+ remove: [],
215
+ };
216
+ }
217
+ add(middleware) {
218
+ if (isRegistingMiddleware(middleware)) {
219
+ middleware.onRegister && this.on('register', middleware.onRegister);
220
+ }
221
+ if (isRemovingMiddleware(middleware)) {
222
+ middleware.onRemove && this.on('remove', middleware.onRemove);
223
+ }
224
+ }
225
+ remove(middleware) {
226
+ if (isRegistingMiddleware(middleware)) {
227
+ middleware.onRegister &&
228
+ this.off('register', middleware.onRegister);
229
+ }
230
+ if (isRemovingMiddleware(middleware)) {
231
+ middleware.onRemove && this.off('remove', middleware.onRemove);
232
+ }
233
+ }
234
+ on(event, listener) {
235
+ this.listeners[event].push(listener);
236
+ }
237
+ onRegister(container, dependencyId, dependency) {
238
+ var _a;
239
+ this.listeners.register.forEach((listener) => listener(container, dependencyId, dependency));
240
+ (_a = this.parent) === null || _a === undefined ? undefined : _a.onRegister(container, dependencyId, dependency);
241
+ }
242
+ onRemove(container, dependencyId, dependency) {
243
+ var _a;
244
+ this.listeners.remove.forEach((listener) => listener(container, dependencyId, dependency));
245
+ (_a = this.parent) === null || _a === undefined ? undefined : _a.onRemove(container, dependencyId, dependency);
246
+ }
247
+ off(event, listener) {
248
+ const index = this.listeners[event].indexOf(listener);
249
+ if (index !== -1) {
250
+ this.listeners[event].splice(index, 1);
251
+ }
252
+ }
253
+ }
254
+ function isRegistingMiddleware(middleware) {
255
+ return middleware.onRegister;
256
+ }
257
+ function isRemovingMiddleware(middleware) {
258
+ return middleware.onRemove;
259
+ }
260
+
191
261
  /**
192
262
  * A dependency injection container
193
263
  */
@@ -208,35 +278,55 @@ class ProxyDiContainer {
208
278
  */
209
279
  this.parentDependencyProxies = {};
210
280
  this.id = ProxyDiContainer.idCounter++;
281
+ this.middlewareListener = new MiddlewareListener(parent === null || parent === undefined ? undefined : parent.middlewareListener);
211
282
  if (parent) {
212
283
  this.parent = parent;
213
284
  this.parent.addChild(this);
214
285
  }
215
286
  this.settings = Object.assign(Object.assign({}, DEFAULT_SETTINGS), settings);
216
287
  }
217
- register(dependency, dependencyId) {
218
- if (this.dependencies[dependencyId]) {
288
+ register(dependency, dependecyId) {
289
+ var _a;
290
+ let id = dependecyId;
291
+ if (!id) {
292
+ if (typeof dependency === 'function') {
293
+ try {
294
+ id = findInjectableId(dependency);
295
+ }
296
+ catch (_b) {
297
+ id = dependency.name;
298
+ }
299
+ }
300
+ }
301
+ if (this.dependencies[id]) {
219
302
  if (!this.settings.allowRewriteDependencies) {
220
- throw new Error(`ProxyDi already has dependency for ${String(dependencyId)}`);
303
+ throw new Error(`ProxyDi already has dependency for ${String(id)}`);
221
304
  }
222
305
  }
223
- let dependencyInstance;
306
+ let instance;
224
307
  const isClass = typeof dependency === 'function';
225
308
  if (isClass) {
226
- dependencyInstance = this.createInstance(dependency, dependencyId);
309
+ instance = this.createInstance(dependency, id);
227
310
  }
228
311
  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
- }
312
+ instance = dependency;
234
313
  }
235
- if (typeof dependencyInstance === 'object') {
236
- dependencyInstance[PROXYDI_CONTAINER] = this;
314
+ const isObject = typeof instance === 'object';
315
+ if (!isObject && !this.settings.allowRegisterAnything) {
316
+ throw new Error(`Can't register as dependency (allowRegisterAnything is off for this contatiner): ${instance}`);
237
317
  }
238
- this.registerImpl(dependencyInstance, dependencyId);
239
- return dependencyInstance;
318
+ if (isObject) {
319
+ instance[PROXYDI_CONTAINER] = this;
320
+ instance[DEPENDENCY_ID] = id;
321
+ }
322
+ this.injectDependenciesTo(instance);
323
+ this.dependencies[id] = instance;
324
+ const constructorName = (_a = instance.constructor) === null || _a === undefined ? undefined : _a.name;
325
+ if (constructorName && middlewaresClasses[constructorName]) {
326
+ this.middlewareListener.add(instance);
327
+ }
328
+ this.middlewareListener.onRegister(this, id, instance);
329
+ return instance;
240
330
  }
241
331
  createInstance(Dependency, dependencyId) {
242
332
  const paramIds = constructorInjections[dependencyId] || [];
@@ -247,18 +337,6 @@ class ProxyDiContainer {
247
337
  }
248
338
  return new Dependency(...params);
249
339
  }
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
340
  /**
263
341
  * 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
342
  * @param dependencyId The identifier of the dependency.
@@ -272,7 +350,13 @@ class ProxyDiContainer {
272
350
  }
273
351
  resolve(dependency) {
274
352
  if (typeof dependency === 'function') {
275
- const id = findInjectableId(dependency);
353
+ let id;
354
+ try {
355
+ id = findInjectableId(dependency);
356
+ }
357
+ catch (_a) {
358
+ id = dependency.name;
359
+ }
276
360
  return this.resolve(id);
277
361
  }
278
362
  if (!this.isKnown(dependency)) {
@@ -347,11 +431,16 @@ class ProxyDiContainer {
347
431
  * @param dependencyOrId The dependency instance or dependency identifier to remove.
348
432
  */
349
433
  remove(dependencyOrId) {
434
+ var _a;
350
435
  const id = isDependency(dependencyOrId)
351
436
  ? dependencyOrId[DEPENDENCY_ID]
352
437
  : dependencyOrId;
353
438
  const dependency = this.dependencies[id];
354
439
  if (dependency) {
440
+ const constructorName = (_a = dependency.constructor) === null || _a === undefined ? undefined : _a.name;
441
+ if (constructorName && middlewaresClasses[constructorName]) {
442
+ this.middlewareListener.remove(dependency);
443
+ }
355
444
  const dependencyInjects = dependency[INJECTIONS]
356
445
  ? dependency[INJECTIONS]
357
446
  : {};
@@ -360,6 +449,7 @@ class ProxyDiContainer {
360
449
  });
361
450
  delete dependency[DEPENDENCY_ID];
362
451
  delete this.dependencies[id];
452
+ this.middlewareListener.onRemove(this, id, dependency);
363
453
  }
364
454
  }
365
455
  /**
@@ -470,4 +560,4 @@ function recursiveResolveAll(container, dependencyId) {
470
560
  return all;
471
561
  }
472
562
 
473
- export { DEPENDENCY_ID, PROXYDI_CONTAINER, ProxyDiContainer, inject, injectable, resolveAll };
563
+ export { DEPENDENCY_ID, PROXYDI_CONTAINER, ProxyDiContainer, inject, injectable, middleware, resolveAll };
package/dist/index.umd.js CHANGED
@@ -4,6 +4,41 @@
4
4
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.YourLibraryName = {}));
5
5
  })(this, (function (exports) { 'use strict';
6
6
 
7
+ const injectableClasses = {};
8
+ const constructorInjections = {};
9
+ function injectable(dependencyOrDependencies, autoInjecions) {
10
+ return function (value, context) {
11
+ if ((context === null || context === undefined ? undefined : context.kind) !== 'class') {
12
+ throw new Error('@injectable decorator should decorate classes');
13
+ }
14
+ const name = dependencyOrDependencies
15
+ ? Array.isArray(dependencyOrDependencies)
16
+ ? context.name
17
+ : dependencyOrDependencies
18
+ : context.name;
19
+ const injectToConstructor = dependencyOrDependencies
20
+ ? Array.isArray(dependencyOrDependencies)
21
+ ? dependencyOrDependencies
22
+ : autoInjecions
23
+ : autoInjecions;
24
+ if (injectableClasses[name]) {
25
+ throw new Error(`ProxyDi has already regisered dependency ${String(name)} by @injectable`);
26
+ }
27
+ injectableClasses[name] = value;
28
+ if (injectToConstructor) {
29
+ constructorInjections[name] = injectToConstructor;
30
+ }
31
+ };
32
+ }
33
+ function findInjectableId(injectable) {
34
+ for (const [id, DependencyClass] of Object.entries(injectableClasses)) {
35
+ if (DependencyClass === injectable) {
36
+ return id;
37
+ }
38
+ }
39
+ throw new Error(`Class is not @injectable: ${injectable.name}`);
40
+ }
41
+
7
42
  const INJECTIONS = Symbol('injections');
8
43
  /**
9
44
  * This symbol constant defines a property name.
@@ -32,7 +67,11 @@
32
67
  const inject = (dependencyId) => {
33
68
  return function (_value, context) {
34
69
  if ((context === null || context === undefined ? undefined : context.kind) === 'field') {
35
- const id = dependencyId ? dependencyId : context.name;
70
+ const id = dependencyId
71
+ ? typeof dependencyId === 'function'
72
+ ? findInjectableId(dependencyId)
73
+ : dependencyId
74
+ : context.name;
36
75
  const injection = {
37
76
  property: context.name,
38
77
  dependencyId: id,
@@ -51,41 +90,6 @@
51
90
  };
52
91
  };
53
92
 
54
- const injectableClasses = {};
55
- const constructorInjections = {};
56
- function injectable(dependencyOrDependencies, autoInjecions) {
57
- return function (value, context) {
58
- if ((context === null || context === undefined ? undefined : context.kind) !== 'class') {
59
- throw new Error('@injectable decorator should decorate classes');
60
- }
61
- const name = dependencyOrDependencies
62
- ? Array.isArray(dependencyOrDependencies)
63
- ? context.name
64
- : dependencyOrDependencies
65
- : context.name;
66
- const injectToConstructor = dependencyOrDependencies
67
- ? Array.isArray(dependencyOrDependencies)
68
- ? dependencyOrDependencies
69
- : autoInjecions
70
- : autoInjecions;
71
- if (injectableClasses[name]) {
72
- throw new Error(`ProxyDi has already regisered dependency ${String(name)} by @injectable`);
73
- }
74
- injectableClasses[name] = value;
75
- if (injectToConstructor) {
76
- constructorInjections[name] = injectToConstructor;
77
- }
78
- };
79
- }
80
- function findInjectableId(injectable) {
81
- for (const [id, DependencyClass] of Object.entries(injectableClasses)) {
82
- if (DependencyClass === injectable) {
83
- return id;
84
- }
85
- }
86
- throw new Error(`Class is not @injectable: ${injectable.name}`);
87
- }
88
-
89
93
  const DEFAULT_SETTINGS = {
90
94
  allowRegisterAnything: false,
91
95
  allowRewriteDependencies: false,
@@ -194,6 +198,72 @@
194
198
  });
195
199
  }
196
200
 
201
+ const middlewaresClasses = {};
202
+ function middleware() {
203
+ return function (value, context) {
204
+ if ((context === null || context === undefined ? undefined : context.kind) !== 'class') {
205
+ throw new Error('@middleware decorator should decorate classes');
206
+ }
207
+ const name = context.name;
208
+ if (middlewaresClasses[name]) {
209
+ throw new Error(`ProxyDi has already regisered middleware ${String(name)} by @middleware`);
210
+ }
211
+ middlewaresClasses[name] = value;
212
+ };
213
+ }
214
+
215
+ class MiddlewareListener {
216
+ constructor(parent) {
217
+ this.parent = parent;
218
+ this.listeners = {
219
+ register: [],
220
+ remove: [],
221
+ };
222
+ }
223
+ add(middleware) {
224
+ if (isRegistingMiddleware(middleware)) {
225
+ middleware.onRegister && this.on('register', middleware.onRegister);
226
+ }
227
+ if (isRemovingMiddleware(middleware)) {
228
+ middleware.onRemove && this.on('remove', middleware.onRemove);
229
+ }
230
+ }
231
+ remove(middleware) {
232
+ if (isRegistingMiddleware(middleware)) {
233
+ middleware.onRegister &&
234
+ this.off('register', middleware.onRegister);
235
+ }
236
+ if (isRemovingMiddleware(middleware)) {
237
+ middleware.onRemove && this.off('remove', middleware.onRemove);
238
+ }
239
+ }
240
+ on(event, listener) {
241
+ this.listeners[event].push(listener);
242
+ }
243
+ onRegister(container, dependencyId, dependency) {
244
+ var _a;
245
+ this.listeners.register.forEach((listener) => listener(container, dependencyId, dependency));
246
+ (_a = this.parent) === null || _a === undefined ? undefined : _a.onRegister(container, dependencyId, dependency);
247
+ }
248
+ onRemove(container, dependencyId, dependency) {
249
+ var _a;
250
+ this.listeners.remove.forEach((listener) => listener(container, dependencyId, dependency));
251
+ (_a = this.parent) === null || _a === undefined ? undefined : _a.onRemove(container, dependencyId, dependency);
252
+ }
253
+ off(event, listener) {
254
+ const index = this.listeners[event].indexOf(listener);
255
+ if (index !== -1) {
256
+ this.listeners[event].splice(index, 1);
257
+ }
258
+ }
259
+ }
260
+ function isRegistingMiddleware(middleware) {
261
+ return middleware.onRegister;
262
+ }
263
+ function isRemovingMiddleware(middleware) {
264
+ return middleware.onRemove;
265
+ }
266
+
197
267
  /**
198
268
  * A dependency injection container
199
269
  */
@@ -214,35 +284,55 @@
214
284
  */
215
285
  this.parentDependencyProxies = {};
216
286
  this.id = ProxyDiContainer.idCounter++;
287
+ this.middlewareListener = new MiddlewareListener(parent === null || parent === undefined ? undefined : parent.middlewareListener);
217
288
  if (parent) {
218
289
  this.parent = parent;
219
290
  this.parent.addChild(this);
220
291
  }
221
292
  this.settings = Object.assign(Object.assign({}, DEFAULT_SETTINGS), settings);
222
293
  }
223
- register(dependency, dependencyId) {
224
- if (this.dependencies[dependencyId]) {
294
+ register(dependency, dependecyId) {
295
+ var _a;
296
+ let id = dependecyId;
297
+ if (!id) {
298
+ if (typeof dependency === 'function') {
299
+ try {
300
+ id = findInjectableId(dependency);
301
+ }
302
+ catch (_b) {
303
+ id = dependency.name;
304
+ }
305
+ }
306
+ }
307
+ if (this.dependencies[id]) {
225
308
  if (!this.settings.allowRewriteDependencies) {
226
- throw new Error(`ProxyDi already has dependency for ${String(dependencyId)}`);
309
+ throw new Error(`ProxyDi already has dependency for ${String(id)}`);
227
310
  }
228
311
  }
229
- let dependencyInstance;
312
+ let instance;
230
313
  const isClass = typeof dependency === 'function';
231
314
  if (isClass) {
232
- dependencyInstance = this.createInstance(dependency, dependencyId);
315
+ instance = this.createInstance(dependency, id);
233
316
  }
234
317
  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
- }
318
+ instance = dependency;
240
319
  }
241
- if (typeof dependencyInstance === 'object') {
242
- dependencyInstance[PROXYDI_CONTAINER] = this;
320
+ const isObject = typeof instance === 'object';
321
+ if (!isObject && !this.settings.allowRegisterAnything) {
322
+ throw new Error(`Can't register as dependency (allowRegisterAnything is off for this contatiner): ${instance}`);
243
323
  }
244
- this.registerImpl(dependencyInstance, dependencyId);
245
- return dependencyInstance;
324
+ if (isObject) {
325
+ instance[PROXYDI_CONTAINER] = this;
326
+ instance[DEPENDENCY_ID] = id;
327
+ }
328
+ this.injectDependenciesTo(instance);
329
+ this.dependencies[id] = instance;
330
+ const constructorName = (_a = instance.constructor) === null || _a === undefined ? undefined : _a.name;
331
+ if (constructorName && middlewaresClasses[constructorName]) {
332
+ this.middlewareListener.add(instance);
333
+ }
334
+ this.middlewareListener.onRegister(this, id, instance);
335
+ return instance;
246
336
  }
247
337
  createInstance(Dependency, dependencyId) {
248
338
  const paramIds = constructorInjections[dependencyId] || [];
@@ -253,18 +343,6 @@
253
343
  }
254
344
  return new Dependency(...params);
255
345
  }
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
346
  /**
269
347
  * 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
348
  * @param dependencyId The identifier of the dependency.
@@ -278,7 +356,13 @@
278
356
  }
279
357
  resolve(dependency) {
280
358
  if (typeof dependency === 'function') {
281
- const id = findInjectableId(dependency);
359
+ let id;
360
+ try {
361
+ id = findInjectableId(dependency);
362
+ }
363
+ catch (_a) {
364
+ id = dependency.name;
365
+ }
282
366
  return this.resolve(id);
283
367
  }
284
368
  if (!this.isKnown(dependency)) {
@@ -353,11 +437,16 @@
353
437
  * @param dependencyOrId The dependency instance or dependency identifier to remove.
354
438
  */
355
439
  remove(dependencyOrId) {
440
+ var _a;
356
441
  const id = isDependency(dependencyOrId)
357
442
  ? dependencyOrId[DEPENDENCY_ID]
358
443
  : dependencyOrId;
359
444
  const dependency = this.dependencies[id];
360
445
  if (dependency) {
446
+ const constructorName = (_a = dependency.constructor) === null || _a === undefined ? undefined : _a.name;
447
+ if (constructorName && middlewaresClasses[constructorName]) {
448
+ this.middlewareListener.remove(dependency);
449
+ }
361
450
  const dependencyInjects = dependency[INJECTIONS]
362
451
  ? dependency[INJECTIONS]
363
452
  : {};
@@ -366,6 +455,7 @@
366
455
  });
367
456
  delete dependency[DEPENDENCY_ID];
368
457
  delete this.dependencies[id];
458
+ this.middlewareListener.onRemove(this, id, dependency);
369
459
  }
370
460
  }
371
461
  /**
@@ -481,6 +571,7 @@
481
571
  exports.ProxyDiContainer = ProxyDiContainer;
482
572
  exports.inject = inject;
483
573
  exports.injectable = injectable;
574
+ exports.middleware = middleware;
484
575
  exports.resolveAll = resolveAll;
485
576
 
486
577
  }));
package/dist/inject.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { DependencyId } from './types';
1
+ import { DependencyId, DependencyClass } from './types';
2
2
  /**
3
3
  * Registers an injection for dependency injection.
4
4
  *
@@ -7,4 +7,4 @@ import { DependencyId } from './types';
7
7
  *
8
8
  * The decorated field will receive its dependency from the same container as the injection owner.
9
9
  */
10
- export declare const inject: (dependencyId?: DependencyId) => (_value: unknown, context: ClassFieldDecoratorContext) => void;
10
+ export declare const inject: (dependencyId?: DependencyId | DependencyClass<any>) => (_value: unknown, context: ClassFieldDecoratorContext) => void;
@@ -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, dependency: any) => 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, dependency: any): 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, dependency: any): 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "proxydi",
3
- "version": "0.0.11",
3
+ "version": "0.0.13",
4
4
  "description": "A typed hierarchical DI container that resolves circular dependencies via Proxy",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",