ts-ioc-container 64.0.0 → 65.0.0

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
@@ -2873,10 +2873,10 @@ The decorators above declare hook *metadata* on a class. The imperative side —
2873
2873
  the callbacks the container machinery runs — is registered on whichever
2874
2874
  abstraction raises the event, one hook type per domain:
2875
2875
 
2876
- | Domain | Registered on | Type | Methods |
2876
+ | Domain | Registered on | Type | Where |
2877
2877
  | ------------ | ------------- | --------------- | -------------------------------------------------------- |
2878
- | **Scope** | `IContainer` | `ScopeHook` | `onScopeCreated(...)`, `onScopeDisposed(...)` |
2879
- | **Scope** | `IContainer` | `RegisteredHook`| `onRegistered(...)` |
2878
+ | **Scope** | `IContainer` | `ScopeHook` | `scopeCreated.subscribe(...)`, `scopeDisposed.subscribe(...)` |
2879
+ | **Scope** | `IContainer` | `RegisteredHook`| `registered.subscribe(...)` |
2880
2880
  | **Injector** | `IInjector` | `InjectorHook` | `onConstructed(...)` |
2881
2881
  | **Provider** | `IProvider` | `ProviderHook` | `onResolved(...)`, or the [`onResolve`](#on-resolve) pipe |
2882
2882
 
@@ -2888,18 +2888,30 @@ so a child inherits what its parent held then and later additions to either
2888
2888
  stay local.
2889
2889
 
2890
2890
  ```typescript
2891
- const container = new Container({ tags: ['application'] })
2892
- .onScopeCreated((scope) => audit.scopeOpened(scope))
2893
- .onScopeDisposed((scope) => audit.scopeClosed(scope))
2894
- .onRegistered((provider, key) => audit.registered(key));
2891
+ const container = new Container({ tags: ['application'] });
2892
+
2893
+ // Scope events are typed events on the container itself
2894
+ const stop = container.scopeCreated.subscribe((scope) => audit.scopeOpened(scope));
2895
+ container.scopeDisposed.subscribe((scope) => audit.scopeClosed(scope));
2896
+ container.registered.subscribe((provider, key) => audit.registered(key));
2897
+
2898
+ stop(); // detached; `container.scopeCreated.unsubscribe(fn)` does the same by reference
2895
2899
 
2896
2900
  // Construction is the injector's event, not a scope's
2897
2901
  container.getInjector().onConstructed((instance, scope) => metrics.built(instance, scope));
2898
2902
  ```
2899
2903
 
2904
+ The scope events are `scopeCreated`, `scopeDisposed` (`ITypedEvent<[IContainer]>`)
2905
+ and `registered` (`ITypedEvent<[IProvider, DependencyKey, IContainer]>`);
2906
+ `subscribe` returns the unsubscribe function. The exposed events carry no
2907
+ `emit` — only the container raises its own events.
2908
+
2909
+ `TypedEvent` itself is exported for your own events: `subscribe` / `unsubscribe`
2910
+ / `emit` / `dispose`, with `ITypedEvent` as the subscriber-only view to hand out.
2911
+
2900
2912
  The built-in modules are all container modules: `OnConstructModule` reaches the
2901
- injector through `getInjector()`, `OnDisposeModule` hooks `onScopeDisposed`, and
2902
- `OnResolvedModule` reaches every provider through `onRegistered`.
2913
+ injector through `getInjector()`, `OnDisposeModule` subscribes to `scopeDisposed`,
2914
+ and `OnResolvedModule` reaches every provider through `registered`.
2903
2915
 
2904
2916
  ### OnConstruct
2905
2917
 
@@ -7,7 +7,7 @@ class AutoResolveModule {
7
7
  this.options = options;
8
8
  }
9
9
  applyTo(container) {
10
- container.onScopeCreated((scope) => scope.autoResolve(this.options));
10
+ container.scopeCreated.subscribe((scope) => scope.autoResolve(this.options));
11
11
  }
12
12
  }
13
13
  exports.AutoResolveModule = AutoResolveModule;
@@ -9,6 +9,7 @@ const ProxyRegistry_1 = require("../utils/ProxyRegistry");
9
9
  const DependencyNotFoundError_1 = require("../errors/DependencyNotFoundError");
10
10
  const basic_1 = require("../utils/basic");
11
11
  const array_1 = require("../utils/array");
12
+ const TypedEvent_1 = require("../utils/TypedEvent");
12
13
  class Container {
13
14
  isDisposed = false;
14
15
  parent;
@@ -19,9 +20,12 @@ class Container {
19
20
  providers = new Map();
20
21
  aliases = new AliasMap_1.AliasMap();
21
22
  injector;
22
- onScopeCreatedHookList = [];
23
- onScopeDisposedHookList = [];
24
- onRegisteredHookList = [];
23
+ scopeCreatedEvent = new TypedEvent_1.TypedEvent();
24
+ scopeDisposedEvent = new TypedEvent_1.TypedEvent();
25
+ registeredEvent = new TypedEvent_1.TypedEvent();
26
+ scopeCreated = this.scopeCreatedEvent;
27
+ scopeDisposed = this.scopeDisposedEvent;
28
+ registered = this.registeredEvent;
25
29
  constructor(options = {}) {
26
30
  this.injector = options.injector ?? new MetadataInjector_1.MetadataInjector();
27
31
  this.parent = options.parent ?? new EmptyContainer_1.EmptyContainer();
@@ -31,9 +35,7 @@ class Container {
31
35
  this.validateContainer();
32
36
  this.providers.set(key, provider);
33
37
  this.aliases.setAliasesByKey(key, aliases);
34
- for (const onRegistered of this.onRegisteredHookList) {
35
- onRegistered(provider, key, this);
36
- }
38
+ this.registeredEvent.emit(provider, key, this);
37
39
  return this;
38
40
  }
39
41
  resolve(target, { args = [], child = this, lazy } = {}) {
@@ -76,17 +78,15 @@ class Container {
76
78
  }
77
79
  createScope({ tags } = {}) {
78
80
  this.validateContainer();
79
- const scope = new Container({ injector: this.injector, parent: this, tags })
80
- .onScopeCreated(...this.onScopeCreatedHookList)
81
- .onScopeDisposed(...this.onScopeDisposedHookList)
82
- .onRegistered(...this.onRegisteredHookList);
81
+ const scope = new Container({ injector: this.injector, parent: this, tags });
82
+ this.scopeCreatedEvent.getListeners().forEach((hook) => scope.scopeCreatedEvent.subscribe(hook));
83
+ this.scopeDisposedEvent.getListeners().forEach((hook) => scope.scopeDisposedEvent.subscribe(hook));
84
+ this.registeredEvent.getListeners().forEach((hook) => scope.registeredEvent.subscribe(hook));
83
85
  for (const registration of this.getRegistrations()) {
84
86
  registration.applyTo(scope);
85
87
  }
86
88
  this.scopes.push(scope);
87
- for (const onScopeCreated of this.onScopeCreatedHookList) {
88
- onScopeCreated(scope);
89
- }
89
+ this.scopeCreatedEvent.emit(scope);
90
90
  return scope;
91
91
  }
92
92
  autoResolve({ args = [] } = {}) {
@@ -101,9 +101,7 @@ class Container {
101
101
  dispose() {
102
102
  this.validateContainer();
103
103
  this.isDisposed = true;
104
- for (const onScopeDisposed of this.onScopeDisposedHookList) {
105
- onScopeDisposed(this);
106
- }
104
+ this.scopeDisposedEvent.emit(this);
107
105
  this.parent.removeScope(this);
108
106
  this.parent = new EmptyContainer_1.EmptyContainer();
109
107
  for (const provider of this.providers.values()) {
@@ -113,9 +111,9 @@ class Container {
113
111
  this.aliases.destroy();
114
112
  this.instances.clear();
115
113
  this.registrations = [];
116
- this.onScopeCreatedHookList.length = 0;
117
- this.onScopeDisposedHookList.length = 0;
118
- this.onRegisteredHookList.length = 0;
114
+ this.scopeCreatedEvent.dispose();
115
+ this.scopeDisposedEvent.dispose();
116
+ this.registeredEvent.dispose();
119
117
  }
120
118
  addRegistration(registration) {
121
119
  this.registrations.push(registration);
@@ -131,18 +129,6 @@ class Container {
131
129
  hasRegistration(key) {
132
130
  return this.registrations.some((r) => r.getKeyOrFail() === key) || this.parent.hasRegistration(key);
133
131
  }
134
- onScopeCreated(...hooks) {
135
- this.onScopeCreatedHookList.push(...hooks);
136
- return this;
137
- }
138
- onScopeDisposed(...hooks) {
139
- this.onScopeDisposedHookList.push(...hooks);
140
- return this;
141
- }
142
- onRegistered(...hooks) {
143
- this.onRegisteredHookList.push(...hooks);
144
- return this;
145
- }
146
132
  addInstance(instance) {
147
133
  this.instances.add(instance);
148
134
  }
@@ -63,13 +63,13 @@ class EmptyContainer {
63
63
  resolveOneByAlias(alias, options) {
64
64
  throw new DependencyNotFoundError_1.DependencyNotFoundError(`Cannot find alias ${alias.toString()}`);
65
65
  }
66
- onScopeCreated(...hooks) {
66
+ get scopeCreated() {
67
67
  throw new MethodNotImplementedError_1.MethodNotImplementedError();
68
68
  }
69
- onScopeDisposed(...hooks) {
69
+ get scopeDisposed() {
70
70
  throw new MethodNotImplementedError_1.MethodNotImplementedError();
71
71
  }
72
- onRegistered(...hooks) {
72
+ get registered() {
73
73
  throw new MethodNotImplementedError_1.MethodNotImplementedError();
74
74
  }
75
75
  }
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TypedEventDisposedError = void 0;
4
+ const ContainerError_1 = require("./ContainerError");
5
+ class TypedEventDisposedError extends ContainerError_1.ContainerError {
6
+ name = 'TypedEventDisposedError';
7
+ static assert(isTrue, failMessage) {
8
+ if (!isTrue) {
9
+ throw new TypedEventDisposedError(failMessage);
10
+ }
11
+ }
12
+ constructor(message) {
13
+ super(message);
14
+ Object.setPrototypeOf(this, TypedEventDisposedError.prototype);
15
+ }
16
+ }
17
+ exports.TypedEventDisposedError = TypedEventDisposedError;
@@ -17,7 +17,7 @@ class OnResolvedModule {
17
17
  this.runHooks = runHooks(strategy);
18
18
  }
19
19
  applyTo(container) {
20
- container.onRegistered((provider) => {
20
+ container.registered.subscribe((provider) => {
21
21
  provider.onResolved(this.runHooks);
22
22
  });
23
23
  }
@@ -10,7 +10,7 @@ class OnDisposeModule {
10
10
  this.strategy = strategy;
11
11
  }
12
12
  applyTo(container) {
13
- container.onScopeDisposed((scope) => {
13
+ container.scopeDisposed.subscribe((scope) => {
14
14
  for (const instance of scope.getInstances()) {
15
15
  this.strategy.execute(instance, { scope });
16
16
  }
package/cjm/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createHookExecutionContext = exports.createHookContextFactory = exports.HookContext = exports.prepend = exports.append = exports.prependHooks = exports.appendHooks = exports.hasHooks = exports.hook = exports.getHooks = exports.UnsupportedTokenTypeError = exports.CannonSingletonApplyTwiceError = exports.ProviderDisposedError = exports.ContainerDisposedError = exports.MethodNotImplementedError = exports.DependencyMissingKeyError = exports.ContainerNotFoundError = exports.DependencyNotFoundError = exports.ContainerError = exports.Registration = exports.onResolve = exports.appendArgsFn = exports.appendArgs = exports.decorate = exports.singleton = exports.autoResolve = exports.lazy = exports.scopeAccess = exports.scope = exports.bindTo = exports.register = exports.toRegistrationFn = exports.toProviderFn = exports.toBindToken = exports.registerPipe = exports.isProviderPipe = exports.Provider = exports.ProxyInjector = exports.SimpleInjector = exports.resolveArgs = exports.argsFn = exports.args = exports.arg = exports.inject = exports.MetadataInjector = exports.Injector = exports.EmptyContainer = exports.AutoResolveModule = exports.Container = exports.isDependencyKey = void 0;
4
- exports.debounce = exports.throttle = exports.handleAsyncError = exports.handleError = exports.getMethodTags = exports.addMethodTag = exports.getMethodLabels = exports.addMethodLabel = exports.getMethodMeta = exports.addMethodMeta = exports.getParamTags = exports.addParamTag = exports.getParamLabels = exports.addParamLabel = exports.getParamMeta = exports.addParamMeta = exports.getClassTags = exports.addClassTag = exports.getClassLabels = exports.addClassLabel = exports.getClassMeta = exports.addClassMeta = exports.resolveConstructor = exports.GroupInstanceToken = exports.ConstantToken = exports.FunctionToken = exports.SingleToken = exports.ClassToken = exports.toSingleAlias = exports.SingleAliasToken = exports.toGroupAlias = exports.GroupAliasToken = exports.argToToken = exports.toMappedToken = exports.toToken = exports.InjectionToken = exports.ParallelAsync = exports.SequentialAsync = exports.SequentialSync = exports.AsyncHookExecutionStrategy = exports.HookExecutionStrategy = exports.oncePerInstance = exports.resolved = exports.OnResolvedModule = exports.onResolved = exports.OnDisposeModule = exports.onScopeDisposed = exports.OnConstructModule = exports.onConstruct = exports.injectProp = void 0;
5
- exports.Is = exports.unwrapProxy = exports.ProxyRegistry = exports.pipe = exports.select = exports.once = exports.shallowCache = void 0;
3
+ exports.createHookContextFactory = exports.HookContext = exports.prepend = exports.append = exports.prependHooks = exports.appendHooks = exports.hasHooks = exports.hook = exports.getHooks = exports.TypedEventDisposedError = exports.UnsupportedTokenTypeError = exports.CannonSingletonApplyTwiceError = exports.ProviderDisposedError = exports.ContainerDisposedError = exports.MethodNotImplementedError = exports.DependencyMissingKeyError = exports.ContainerNotFoundError = exports.DependencyNotFoundError = exports.ContainerError = exports.Registration = exports.onResolve = exports.appendArgsFn = exports.appendArgs = exports.decorate = exports.singleton = exports.autoResolve = exports.lazy = exports.scopeAccess = exports.scope = exports.bindTo = exports.register = exports.toRegistrationFn = exports.toProviderFn = exports.toBindToken = exports.registerPipe = exports.isProviderPipe = exports.Provider = exports.ProxyInjector = exports.SimpleInjector = exports.resolveArgs = exports.argsFn = exports.args = exports.arg = exports.inject = exports.MetadataInjector = exports.Injector = exports.EmptyContainer = exports.AutoResolveModule = exports.Container = exports.isDependencyKey = void 0;
4
+ exports.throttle = exports.handleAsyncError = exports.handleError = exports.getMethodTags = exports.addMethodTag = exports.getMethodLabels = exports.addMethodLabel = exports.getMethodMeta = exports.addMethodMeta = exports.getParamTags = exports.addParamTag = exports.getParamLabels = exports.addParamLabel = exports.getParamMeta = exports.addParamMeta = exports.getClassTags = exports.addClassTag = exports.getClassLabels = exports.addClassLabel = exports.getClassMeta = exports.addClassMeta = exports.resolveConstructor = exports.GroupInstanceToken = exports.ConstantToken = exports.FunctionToken = exports.SingleToken = exports.ClassToken = exports.toSingleAlias = exports.SingleAliasToken = exports.toGroupAlias = exports.GroupAliasToken = exports.argToToken = exports.toMappedToken = exports.toToken = exports.InjectionToken = exports.ParallelAsync = exports.SequentialAsync = exports.SequentialSync = exports.AsyncHookExecutionStrategy = exports.HookExecutionStrategy = exports.oncePerInstance = exports.resolved = exports.OnResolvedModule = exports.onResolved = exports.OnDisposeModule = exports.onScopeDisposed = exports.OnConstructModule = exports.onConstruct = exports.injectProp = exports.createHookExecutionContext = void 0;
5
+ exports.Is = exports.unwrapProxy = exports.ProxyRegistry = exports.pipe = exports.select = exports.TypedEvent = exports.once = exports.shallowCache = exports.debounce = void 0;
6
6
  var IContainer_1 = require("./container/IContainer");
7
7
  Object.defineProperty(exports, "isDependencyKey", { enumerable: true, get: function () { return IContainer_1.isDependencyKey; } });
8
8
  var Container_1 = require("./container/Container");
@@ -63,6 +63,8 @@ var CannonSingletonApplyTwiceError_1 = require("./errors/CannonSingletonApplyTwi
63
63
  Object.defineProperty(exports, "CannonSingletonApplyTwiceError", { enumerable: true, get: function () { return CannonSingletonApplyTwiceError_1.CannonSingletonApplyTwiceError; } });
64
64
  var UnsupportedTokenTypeError_1 = require("./errors/UnsupportedTokenTypeError");
65
65
  Object.defineProperty(exports, "UnsupportedTokenTypeError", { enumerable: true, get: function () { return UnsupportedTokenTypeError_1.UnsupportedTokenTypeError; } });
66
+ var TypedEventDisposedError_1 = require("./errors/TypedEventDisposedError");
67
+ Object.defineProperty(exports, "TypedEventDisposedError", { enumerable: true, get: function () { return TypedEventDisposedError_1.TypedEventDisposedError; } });
66
68
  var hook_1 = require("./hooks/hook");
67
69
  Object.defineProperty(exports, "getHooks", { enumerable: true, get: function () { return hook_1.getHooks; } });
68
70
  Object.defineProperty(exports, "hook", { enumerable: true, get: function () { return hook_1.hook; } });
@@ -155,6 +157,8 @@ var shallowCache_1 = require("./utils/shallowCache");
155
157
  Object.defineProperty(exports, "shallowCache", { enumerable: true, get: function () { return shallowCache_1.shallowCache; } });
156
158
  var once_1 = require("./utils/once");
157
159
  Object.defineProperty(exports, "once", { enumerable: true, get: function () { return once_1.once; } });
160
+ var TypedEvent_1 = require("./utils/TypedEvent");
161
+ Object.defineProperty(exports, "TypedEvent", { enumerable: true, get: function () { return TypedEvent_1.TypedEvent; } });
158
162
  var select_1 = require("./select");
159
163
  Object.defineProperty(exports, "select", { enumerable: true, get: function () { return select_1.select; } });
160
164
  var fp_1 = require("./utils/fp");
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TypedEvent = void 0;
4
+ const TypedEventDisposedError_1 = require("../errors/TypedEventDisposedError");
5
+ class TypedEvent {
6
+ listeners = new Set();
7
+ isDisposed = false;
8
+ subscribe(listener) {
9
+ this.validate();
10
+ this.listeners.add(listener);
11
+ return () => this.unsubscribe(listener);
12
+ }
13
+ unsubscribe(listener) {
14
+ this.listeners.delete(listener);
15
+ }
16
+ emit(...args) {
17
+ this.validate();
18
+ for (const listener of [...this.listeners]) {
19
+ if (this.listeners.has(listener)) {
20
+ listener(...args);
21
+ }
22
+ }
23
+ }
24
+ getListeners() {
25
+ return [...this.listeners];
26
+ }
27
+ dispose() {
28
+ this.listeners.clear();
29
+ this.isDisposed = true;
30
+ }
31
+ validate() {
32
+ TypedEventDisposedError_1.TypedEventDisposedError.assert(!this.isDisposed, 'TypedEvent is already disposed');
33
+ }
34
+ }
35
+ exports.TypedEvent = TypedEvent;
@@ -4,6 +4,6 @@ export class AutoResolveModule {
4
4
  this.options = options;
5
5
  }
6
6
  applyTo(container) {
7
- container.onScopeCreated((scope) => scope.autoResolve(this.options));
7
+ container.scopeCreated.subscribe((scope) => scope.autoResolve(this.options));
8
8
  }
9
9
  }
@@ -6,6 +6,7 @@ import { unwrapProxy } from '../utils/ProxyRegistry.js';
6
6
  import { DependencyNotFoundError } from '../errors/DependencyNotFoundError.js';
7
7
  import { Is } from '../utils/basic.js';
8
8
  import { Filter as F } from '../utils/array.js';
9
+ import { TypedEvent } from '../utils/TypedEvent.js';
9
10
  export class Container {
10
11
  isDisposed = false;
11
12
  parent;
@@ -16,9 +17,12 @@ export class Container {
16
17
  providers = new Map();
17
18
  aliases = new AliasMap();
18
19
  injector;
19
- onScopeCreatedHookList = [];
20
- onScopeDisposedHookList = [];
21
- onRegisteredHookList = [];
20
+ scopeCreatedEvent = new TypedEvent();
21
+ scopeDisposedEvent = new TypedEvent();
22
+ registeredEvent = new TypedEvent();
23
+ scopeCreated = this.scopeCreatedEvent;
24
+ scopeDisposed = this.scopeDisposedEvent;
25
+ registered = this.registeredEvent;
22
26
  constructor(options = {}) {
23
27
  this.injector = options.injector ?? new MetadataInjector();
24
28
  this.parent = options.parent ?? new EmptyContainer();
@@ -28,9 +32,7 @@ export class Container {
28
32
  this.validateContainer();
29
33
  this.providers.set(key, provider);
30
34
  this.aliases.setAliasesByKey(key, aliases);
31
- for (const onRegistered of this.onRegisteredHookList) {
32
- onRegistered(provider, key, this);
33
- }
35
+ this.registeredEvent.emit(provider, key, this);
34
36
  return this;
35
37
  }
36
38
  resolve(target, { args = [], child = this, lazy } = {}) {
@@ -73,17 +75,15 @@ export class Container {
73
75
  }
74
76
  createScope({ tags } = {}) {
75
77
  this.validateContainer();
76
- const scope = new Container({ injector: this.injector, parent: this, tags })
77
- .onScopeCreated(...this.onScopeCreatedHookList)
78
- .onScopeDisposed(...this.onScopeDisposedHookList)
79
- .onRegistered(...this.onRegisteredHookList);
78
+ const scope = new Container({ injector: this.injector, parent: this, tags });
79
+ this.scopeCreatedEvent.getListeners().forEach((hook) => scope.scopeCreatedEvent.subscribe(hook));
80
+ this.scopeDisposedEvent.getListeners().forEach((hook) => scope.scopeDisposedEvent.subscribe(hook));
81
+ this.registeredEvent.getListeners().forEach((hook) => scope.registeredEvent.subscribe(hook));
80
82
  for (const registration of this.getRegistrations()) {
81
83
  registration.applyTo(scope);
82
84
  }
83
85
  this.scopes.push(scope);
84
- for (const onScopeCreated of this.onScopeCreatedHookList) {
85
- onScopeCreated(scope);
86
- }
86
+ this.scopeCreatedEvent.emit(scope);
87
87
  return scope;
88
88
  }
89
89
  autoResolve({ args = [] } = {}) {
@@ -98,9 +98,7 @@ export class Container {
98
98
  dispose() {
99
99
  this.validateContainer();
100
100
  this.isDisposed = true;
101
- for (const onScopeDisposed of this.onScopeDisposedHookList) {
102
- onScopeDisposed(this);
103
- }
101
+ this.scopeDisposedEvent.emit(this);
104
102
  this.parent.removeScope(this);
105
103
  this.parent = new EmptyContainer();
106
104
  for (const provider of this.providers.values()) {
@@ -110,9 +108,9 @@ export class Container {
110
108
  this.aliases.destroy();
111
109
  this.instances.clear();
112
110
  this.registrations = [];
113
- this.onScopeCreatedHookList.length = 0;
114
- this.onScopeDisposedHookList.length = 0;
115
- this.onRegisteredHookList.length = 0;
111
+ this.scopeCreatedEvent.dispose();
112
+ this.scopeDisposedEvent.dispose();
113
+ this.registeredEvent.dispose();
116
114
  }
117
115
  addRegistration(registration) {
118
116
  this.registrations.push(registration);
@@ -128,18 +126,6 @@ export class Container {
128
126
  hasRegistration(key) {
129
127
  return this.registrations.some((r) => r.getKeyOrFail() === key) || this.parent.hasRegistration(key);
130
128
  }
131
- onScopeCreated(...hooks) {
132
- this.onScopeCreatedHookList.push(...hooks);
133
- return this;
134
- }
135
- onScopeDisposed(...hooks) {
136
- this.onScopeDisposedHookList.push(...hooks);
137
- return this;
138
- }
139
- onRegistered(...hooks) {
140
- this.onRegisteredHookList.push(...hooks);
141
- return this;
142
- }
143
129
  addInstance(instance) {
144
130
  this.instances.add(instance);
145
131
  }
@@ -60,13 +60,13 @@ export class EmptyContainer {
60
60
  resolveOneByAlias(alias, options) {
61
61
  throw new DependencyNotFoundError(`Cannot find alias ${alias.toString()}`);
62
62
  }
63
- onScopeCreated(...hooks) {
63
+ get scopeCreated() {
64
64
  throw new MethodNotImplementedError();
65
65
  }
66
- onScopeDisposed(...hooks) {
66
+ get scopeDisposed() {
67
67
  throw new MethodNotImplementedError();
68
68
  }
69
- onRegistered(...hooks) {
69
+ get registered() {
70
70
  throw new MethodNotImplementedError();
71
71
  }
72
72
  }
@@ -0,0 +1,13 @@
1
+ import { ContainerError } from './ContainerError.js';
2
+ export class TypedEventDisposedError extends ContainerError {
3
+ name = 'TypedEventDisposedError';
4
+ static assert(isTrue, failMessage) {
5
+ if (!isTrue) {
6
+ throw new TypedEventDisposedError(failMessage);
7
+ }
8
+ }
9
+ constructor(message) {
10
+ super(message);
11
+ Object.setPrototypeOf(this, TypedEventDisposedError.prototype);
12
+ }
13
+ }
@@ -13,7 +13,7 @@ export class OnResolvedModule {
13
13
  this.runHooks = runHooks(strategy);
14
14
  }
15
15
  applyTo(container) {
16
- container.onRegistered((provider) => {
16
+ container.registered.subscribe((provider) => {
17
17
  provider.onResolved(this.runHooks);
18
18
  });
19
19
  }
@@ -6,7 +6,7 @@ export class OnDisposeModule {
6
6
  this.strategy = strategy;
7
7
  }
8
8
  applyTo(container) {
9
- container.onScopeDisposed((scope) => {
9
+ container.scopeDisposed.subscribe((scope) => {
10
10
  for (const instance of scope.getInstances()) {
11
11
  this.strategy.execute(instance, { scope });
12
12
  }
package/esm/index.js CHANGED
@@ -18,6 +18,7 @@ export { ContainerDisposedError } from './errors/ContainerDisposedError.js';
18
18
  export { ProviderDisposedError } from './errors/ProviderDisposedError.js';
19
19
  export { CannonSingletonApplyTwiceError } from './errors/CannonSingletonApplyTwiceError.js';
20
20
  export { UnsupportedTokenTypeError } from './errors/UnsupportedTokenTypeError.js';
21
+ export { TypedEventDisposedError } from './errors/TypedEventDisposedError.js';
21
22
  export { getHooks, hook, hasHooks, appendHooks, prependHooks, append, prepend, } from './hooks/hook.js';
22
23
  export { HookContext, createHookContextFactory, createHookExecutionContext, } from './hooks/HookContext.js';
23
24
  export { injectProp } from './hooks/injectProp.js';
@@ -48,6 +49,7 @@ export { throttle } from './utils/throttle.js';
48
49
  export { debounce } from './utils/debounce.js';
49
50
  export { shallowCache } from './utils/shallowCache.js';
50
51
  export { once } from './utils/once.js';
52
+ export { TypedEvent } from './utils/TypedEvent.js';
51
53
  export { select } from './select.js';
52
54
  export { pipe } from './utils/fp.js';
53
55
  export { ProxyRegistry, unwrapProxy } from './utils/ProxyRegistry.js';
@@ -0,0 +1,31 @@
1
+ import { TypedEventDisposedError } from '../errors/TypedEventDisposedError.js';
2
+ export class TypedEvent {
3
+ listeners = new Set();
4
+ isDisposed = false;
5
+ subscribe(listener) {
6
+ this.validate();
7
+ this.listeners.add(listener);
8
+ return () => this.unsubscribe(listener);
9
+ }
10
+ unsubscribe(listener) {
11
+ this.listeners.delete(listener);
12
+ }
13
+ emit(...args) {
14
+ this.validate();
15
+ for (const listener of [...this.listeners]) {
16
+ if (this.listeners.has(listener)) {
17
+ listener(...args);
18
+ }
19
+ }
20
+ }
21
+ getListeners() {
22
+ return [...this.listeners];
23
+ }
24
+ dispose() {
25
+ this.listeners.clear();
26
+ this.isDisposed = true;
27
+ }
28
+ validate() {
29
+ TypedEventDisposedError.assert(!this.isDisposed, 'TypedEvent is already disposed');
30
+ }
31
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-ioc-container",
3
- "version": "64.0.0",
3
+ "version": "65.0.0",
4
4
  "description": "Fast, lightweight TypeScript dependency injection container with a clean API, scoped lifecycles, decorators, tokens, hooks, lazy injection, customizable providers, and no global container objects.",
5
5
  "publishConfig": {
6
6
  "access": "public",
@@ -1,8 +1,9 @@
1
- import { type AutoResolveOptions, type CreateScopeOptions, type DependencyKey, type IContainer, type IContainerModule, type RegisteredHook, type RegisterOptions, ResolveManyOptions, type ResolveOneOptions, type ScopeHook, type Tag } from './IContainer.js';
1
+ import { type AutoResolveOptions, type CreateScopeOptions, type DependencyKey, type IContainer, type IContainerModule, type RegisterOptions, ResolveManyOptions, type ResolveOneOptions, type Tag } from './IContainer.js';
2
2
  import { type IInjector } from '../injector/IInjector.js';
3
3
  import { type IProvider } from '../provider/IProvider.js';
4
4
  import { type IRegistration } from '../registration/IRegistration.js';
5
5
  import { constructor, Instance } from '../utils/basic.js';
6
+ import { type ITypedEvent } from '../utils/TypedEvent.js';
6
7
  export declare class Container implements IContainer {
7
8
  isDisposed: boolean;
8
9
  private parent;
@@ -13,9 +14,12 @@ export declare class Container implements IContainer {
13
14
  private readonly providers;
14
15
  private readonly aliases;
15
16
  private readonly injector;
16
- private readonly onScopeCreatedHookList;
17
- private readonly onScopeDisposedHookList;
18
- private readonly onRegisteredHookList;
17
+ private readonly scopeCreatedEvent;
18
+ private readonly scopeDisposedEvent;
19
+ private readonly registeredEvent;
20
+ readonly scopeCreated: ITypedEvent<[IContainer]>;
21
+ readonly scopeDisposed: ITypedEvent<[IContainer]>;
22
+ readonly registered: ITypedEvent<[IProvider, DependencyKey, IContainer]>;
19
23
  constructor(options?: {
20
24
  injector?: IInjector;
21
25
  parent?: IContainer;
@@ -32,9 +36,6 @@ export declare class Container implements IContainer {
32
36
  getRegistrations(): IRegistration[];
33
37
  getInjector(): IInjector;
34
38
  hasRegistration(key: DependencyKey): boolean;
35
- onScopeCreated(...hooks: ScopeHook[]): this;
36
- onScopeDisposed(...hooks: ScopeHook[]): this;
37
- onRegistered(...hooks: RegisteredHook[]): this;
38
39
  addInstance(instance: Instance): void;
39
40
  getScopes(): IContainer[];
40
41
  hasInstance(instance: Instance): boolean;
@@ -1,8 +1,9 @@
1
- import { type AutoResolveOptions, type DependencyKey, type IContainer, type IContainerModule, type ScopeHook, type RegisteredHook, type ResolveManyOptions, type ResolveOneOptions, type Tag } from './IContainer.js';
1
+ import { type AutoResolveOptions, type DependencyKey, type IContainer, type IContainerModule, type ResolveManyOptions, type ResolveOneOptions, type Tag } from './IContainer.js';
2
2
  import { type IProvider } from '../provider/IProvider.js';
3
3
  import { type IRegistration } from '../registration/IRegistration.js';
4
4
  import { type constructor, type Instance } from '../utils/basic.js';
5
5
  import { type IInjector } from '../injector/IInjector.js';
6
+ import { type ITypedEvent } from '../utils/TypedEvent.js';
6
7
  export declare class EmptyContainer implements IContainer {
7
8
  get isDisposed(): boolean;
8
9
  addInstance(instance: Instance): void;
@@ -25,7 +26,7 @@ export declare class EmptyContainer implements IContainer {
25
26
  resolve<T>(key: constructor<T> | DependencyKey, options?: ResolveOneOptions): T;
26
27
  resolveByAlias<T>(alias: DependencyKey, options?: ResolveManyOptions): T[];
27
28
  resolveOneByAlias<T>(alias: DependencyKey, options?: ResolveOneOptions): T;
28
- onScopeCreated(...hooks: ScopeHook[]): this;
29
- onScopeDisposed(...hooks: ScopeHook[]): this;
30
- onRegistered(...hooks: RegisteredHook[]): this;
29
+ get scopeCreated(): ITypedEvent<[IContainer]>;
30
+ get scopeDisposed(): ITypedEvent<[IContainer]>;
31
+ get registered(): ITypedEvent<[IProvider, DependencyKey, IContainer]>;
31
32
  }
@@ -2,6 +2,7 @@ import { type IProvider, ProviderOptions } from '../provider/IProvider.js';
2
2
  import { type IRegistration } from '../registration/IRegistration.js';
3
3
  import { IInjector, type WithArgs } from '../injector/IInjector.js';
4
4
  import { type constructor, Instance } from '../utils/basic.js';
5
+ import { type ITypedEvent } from '../utils/TypedEvent.js';
5
6
  export type DependencyKey = string | symbol;
6
7
  export declare function isDependencyKey(target: unknown): target is DependencyKey;
7
8
  export type Tag = string;
@@ -35,9 +36,9 @@ export type ScopeHook = (scope: IContainer) => void;
35
36
  export type RegisteredHook = (provider: IProvider, key: DependencyKey, scope: IContainer) => void;
36
37
  export interface IContainer extends Tagged {
37
38
  readonly isDisposed: boolean;
38
- onScopeCreated(...hooks: ScopeHook[]): this;
39
- onScopeDisposed(...hooks: ScopeHook[]): this;
40
- onRegistered(...hooks: RegisteredHook[]): this;
39
+ readonly scopeCreated: ITypedEvent<[IContainer]>;
40
+ readonly scopeDisposed: ITypedEvent<[IContainer]>;
41
+ readonly registered: ITypedEvent<[IProvider, DependencyKey, IContainer]>;
41
42
  register(key: DependencyKey, value: IProvider, options?: RegisterOptions): this;
42
43
  addRegistration(registration: IRegistration): this;
43
44
  getRegistrations(): IRegistration[];
@@ -0,0 +1,6 @@
1
+ import { ContainerError } from './ContainerError.js';
2
+ export declare class TypedEventDisposedError extends ContainerError {
3
+ name: string;
4
+ static assert(isTrue: boolean, failMessage: string): void;
5
+ constructor(message: string);
6
+ }
@@ -19,6 +19,7 @@ export { ContainerDisposedError } from './errors/ContainerDisposedError.js';
19
19
  export { ProviderDisposedError } from './errors/ProviderDisposedError.js';
20
20
  export { CannonSingletonApplyTwiceError } from './errors/CannonSingletonApplyTwiceError.js';
21
21
  export { UnsupportedTokenTypeError } from './errors/UnsupportedTokenTypeError.js';
22
+ export { TypedEventDisposedError } from './errors/TypedEventDisposedError.js';
22
23
  export { getHooks, hook, hasHooks, appendHooks, prependHooks, append, prepend, type HookFn, type HookClass, type HookType, type MapHooksFn, type InjectFn, type HooksOfClass, } from './hooks/hook.js';
23
24
  export { HookContext, createHookContextFactory, createHookExecutionContext, type CreateHookExecutionContext, type IHookContext, } from './hooks/HookContext.js';
24
25
  export { injectProp } from './hooks/injectProp.js';
@@ -49,6 +50,7 @@ export { throttle } from './utils/throttle.js';
49
50
  export { debounce } from './utils/debounce.js';
50
51
  export { shallowCache } from './utils/shallowCache.js';
51
52
  export { once } from './utils/once.js';
53
+ export { TypedEvent, type ITypedEvent, type TypedEventListener, type Unsubscribe } from './utils/TypedEvent.js';
52
54
  export { type ExecutionContext } from './ExecutionContext.js';
53
55
  export { select } from './select.js';
54
56
  export { pipe, type MapFn } from './utils/fp.js';
@@ -0,0 +1,17 @@
1
+ export type TypedEventListener<TArgs extends unknown[]> = (...args: TArgs) => void;
2
+ export type Unsubscribe = () => void;
3
+ export interface ITypedEvent<TArgs extends unknown[]> {
4
+ subscribe(listener: TypedEventListener<TArgs>): Unsubscribe;
5
+ unsubscribe(listener: TypedEventListener<TArgs>): void;
6
+ dispose(): void;
7
+ }
8
+ export declare class TypedEvent<TArgs extends unknown[] = []> implements ITypedEvent<TArgs> {
9
+ private readonly listeners;
10
+ private isDisposed;
11
+ subscribe(listener: TypedEventListener<TArgs>): Unsubscribe;
12
+ unsubscribe(listener: TypedEventListener<TArgs>): void;
13
+ emit(...args: TArgs): void;
14
+ getListeners(): TypedEventListener<TArgs>[];
15
+ dispose(): void;
16
+ private validate;
17
+ }