ts-ioc-container 64.0.0 → 66.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
@@ -2385,6 +2385,8 @@ Sometimes you don't want to change the dependency, only to react to it. Use the
2385
2385
 
2386
2386
  Hooks run after the whole `decorate(...)` chain, so they always observe the fully decorated dependency, and their return value is ignored — `onResolve` can never swap the dependency out. They fire per resolution, which means a `singleton()` provider runs them only on the resolve that fills the cache.
2387
2387
 
2388
+ This — or the `@onResolved` decorator over the same provider event — is the recommended way to react to a dependency; see [OnConstruct](#onconstruct) for why `@onConstruct` is the narrower tool.
2389
+
2388
2390
  ```typescript
2389
2391
  import 'reflect-metadata';
2390
2392
  import { Container, type IContainer, onResolve, register, Registration as R, singleton } from 'ts-ioc-container';
@@ -2845,10 +2847,14 @@ strategy decides the order, what is awaited, and where a failure goes
2845
2847
  | `SequentialAsync` | one after another | in order, or all at once (`methodStrategy`) | yes |
2846
2848
  | `ParallelAsync` | all at once | in order, or all at once (`methodStrategy`) | yes |
2847
2849
 
2850
+ The async strategies require `methodStrategy` (`'sequential'` or `'parallel'`):
2851
+ how the hooks of one member relate is named at the construction site rather
2852
+ than left to a default.
2853
+
2848
2854
  ```typescript
2849
2855
  const container = new Container()
2850
2856
  .useModule(new OnConstructModule(new SequentialSync({ key: 'onConstruct' })))
2851
- .useModule(new OnDisposeModule(new ParallelAsync({ key: 'onScopeDisposed' })));
2857
+ .useModule(new OnDisposeModule(new ParallelAsync({ key: 'onScopeDisposed', methodStrategy: 'parallel' })));
2852
2858
  ```
2853
2859
 
2854
2860
  Resolution and disposal stay synchronous under every strategy: a run stays
@@ -2873,10 +2879,10 @@ The decorators above declare hook *metadata* on a class. The imperative side —
2873
2879
  the callbacks the container machinery runs — is registered on whichever
2874
2880
  abstraction raises the event, one hook type per domain:
2875
2881
 
2876
- | Domain | Registered on | Type | Methods |
2882
+ | Domain | Registered on | Type | Where |
2877
2883
  | ------------ | ------------- | --------------- | -------------------------------------------------------- |
2878
- | **Scope** | `IContainer` | `ScopeHook` | `onScopeCreated(...)`, `onScopeDisposed(...)` |
2879
- | **Scope** | `IContainer` | `RegisteredHook`| `onRegistered(...)` |
2884
+ | **Scope** | `IContainer` | `ScopeHook` | `scopeCreated.subscribe(...)`, `scopeDisposed.subscribe(...)` |
2885
+ | **Scope** | `IContainer` | `RegisteredHook`| `registered.subscribe(...)` |
2880
2886
  | **Injector** | `IInjector` | `InjectorHook` | `onConstructed(...)` |
2881
2887
  | **Provider** | `IProvider` | `ProviderHook` | `onResolved(...)`, or the [`onResolve`](#on-resolve) pipe |
2882
2888
 
@@ -2888,21 +2894,44 @@ so a child inherits what its parent held then and later additions to either
2888
2894
  stay local.
2889
2895
 
2890
2896
  ```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));
2897
+ const container = new Container({ tags: ['application'] });
2898
+
2899
+ // Scope events are typed events on the container itself
2900
+ const stop = container.scopeCreated.subscribe((scope) => audit.scopeOpened(scope));
2901
+ container.scopeDisposed.subscribe((scope) => audit.scopeClosed(scope));
2902
+ container.registered.subscribe((provider, key) => audit.registered(key));
2903
+
2904
+ stop(); // detached; `container.scopeCreated.unsubscribe(fn)` does the same by reference
2895
2905
 
2896
2906
  // Construction is the injector's event, not a scope's
2897
2907
  container.getInjector().onConstructed((instance, scope) => metrics.built(instance, scope));
2898
2908
  ```
2899
2909
 
2910
+ The scope events are `scopeCreated`, `scopeDisposed` (`ITypedEvent<[IContainer]>`)
2911
+ and `registered` (`ITypedEvent<[IProvider, DependencyKey, IContainer]>`);
2912
+ `subscribe` returns the unsubscribe function. The exposed events carry no
2913
+ `emit` — only the container raises its own events.
2914
+
2915
+ `TypedEvent` itself is exported for your own events: `subscribe` / `unsubscribe`
2916
+ / `emit` / `dispose`, with `ITypedEvent` as the subscriber-only view to hand out.
2917
+
2900
2918
  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`.
2919
+ injector through `getInjector()`, `OnDisposeModule` subscribes to `scopeDisposed`,
2920
+ and `OnResolvedModule` reaches every provider through `registered`.
2903
2921
 
2904
2922
  ### OnConstruct
2905
2923
 
2924
+ > **Prefer `@onResolved` — or the [`onResolve`](#on-resolve) pipe — over
2925
+ > `@onConstruct`.** Construction is the *injector's* event, so `@onConstruct`
2926
+ > fires only for dependencies the injector builds: a `fromValue` constant or a
2927
+ > factory registration never triggers it. It also observes the instance before
2928
+ > the provider's `decorate(...)` chain wraps it, so a hook sees the bare
2929
+ > instance rather than what the caller receives. `@onResolved` runs on every
2930
+ > dependency leaving a provider, after the whole decorate chain — the same
2931
+ > ordering, awaiting and error handling, on what the caller actually gets.
2932
+ > Reach for `@onConstruct` only when you mean "this class was just constructed"
2933
+ > specifically.
2934
+
2906
2935
  ```typescript
2907
2936
  import 'reflect-metadata';
2908
2937
  import {
@@ -3027,7 +3056,7 @@ describe('onConstruct', function () {
3027
3056
 
3028
3057
  // An async strategy awaits the hooks; resolution itself still does not wait for them.
3029
3058
  const container = new Container()
3030
- .useModule(new OnConstructModule(new SequentialAsync({ key: 'onConstruct' })))
3059
+ .useModule(new OnConstructModule(new SequentialAsync({ key: 'onConstruct', methodStrategy: 'sequential' })))
3031
3060
  .addRegistration(R.fromValue('postgres://localhost:5432').bindTo('ConnectionString'));
3032
3061
 
3033
3062
  const db = container.resolve(DatabaseConnection);
@@ -3054,6 +3083,7 @@ describe('onConstruct', function () {
3054
3083
  new OnConstructModule(
3055
3084
  new SequentialAsync({
3056
3085
  key: 'onConstruct',
3086
+ methodStrategy: 'sequential',
3057
3087
  onError: (scope) => (ex) => {
3058
3088
  captured = { ex, scope };
3059
3089
  },
@@ -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;
@@ -4,7 +4,7 @@ exports.AsyncHookExecutionStrategy = void 0;
4
4
  const HookExecutionStrategy_1 = require("./HookExecutionStrategy");
5
5
  class AsyncHookExecutionStrategy extends HookExecutionStrategy_1.HookExecutionStrategy {
6
6
  methodStrategy;
7
- constructor({ methodStrategy = 'sequential', ...props }) {
7
+ constructor({ methodStrategy, ...props }) {
8
8
  super(props);
9
9
  this.methodStrategy = methodStrategy;
10
10
  }
@@ -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
+ }
@@ -1,7 +1,7 @@
1
1
  import { HookExecutionStrategy, runAtOnce, runInOrder, } from './HookExecutionStrategy.js';
2
2
  export class AsyncHookExecutionStrategy extends HookExecutionStrategy {
3
3
  methodStrategy;
4
- constructor({ methodStrategy = 'sequential', ...props }) {
4
+ constructor({ methodStrategy, ...props }) {
5
5
  super(props);
6
6
  this.methodStrategy = methodStrategy;
7
7
  }
@@ -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": "66.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
+ }
@@ -1,7 +1,7 @@
1
1
  import { HookExecutionStrategy, type HookExecutionStrategyProps, type MemberHooks } from './HookExecutionStrategy.js';
2
2
  export type MethodStrategy = 'sequential' | 'parallel';
3
3
  export type AsyncHookExecutionStrategyProps = HookExecutionStrategyProps & {
4
- methodStrategy?: MethodStrategy;
4
+ methodStrategy: MethodStrategy;
5
5
  };
6
6
  export declare abstract class AsyncHookExecutionStrategy extends HookExecutionStrategy {
7
7
  private readonly methodStrategy;
@@ -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
+ }