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 +41 -11
- package/cjm/container/AutoResolveModule.js +1 -1
- package/cjm/container/Container.js +17 -31
- package/cjm/container/EmptyContainer.js +3 -3
- package/cjm/errors/TypedEventDisposedError.js +17 -0
- package/cjm/hooks/AsyncHookExecutionStrategy.js +1 -1
- package/cjm/hooks/onResolved.js +1 -1
- package/cjm/hooks/onScopeDisposed.js +1 -1
- package/cjm/index.js +7 -3
- package/cjm/utils/TypedEvent.js +35 -0
- package/esm/container/AutoResolveModule.js +1 -1
- package/esm/container/Container.js +17 -31
- package/esm/container/EmptyContainer.js +3 -3
- package/esm/errors/TypedEventDisposedError.js +13 -0
- package/esm/hooks/AsyncHookExecutionStrategy.js +1 -1
- package/esm/hooks/onResolved.js +1 -1
- package/esm/hooks/onScopeDisposed.js +1 -1
- package/esm/index.js +2 -0
- package/esm/utils/TypedEvent.js +31 -0
- package/package.json +1 -1
- package/typings/container/Container.d.ts +8 -7
- package/typings/container/EmptyContainer.d.ts +5 -4
- package/typings/container/IContainer.d.ts +4 -3
- package/typings/errors/TypedEventDisposedError.d.ts +6 -0
- package/typings/hooks/AsyncHookExecutionStrategy.d.ts +1 -1
- package/typings/index.d.ts +2 -0
- package/typings/utils/TypedEvent.d.ts +17 -0
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 |
|
|
2882
|
+
| Domain | Registered on | Type | Where |
|
|
2877
2883
|
| ------------ | ------------- | --------------- | -------------------------------------------------------- |
|
|
2878
|
-
| **Scope** | `IContainer` | `ScopeHook` | `
|
|
2879
|
-
| **Scope** | `IContainer` | `RegisteredHook`| `
|
|
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
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
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`
|
|
2902
|
-
`OnResolvedModule` reaches every provider through `
|
|
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.
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
117
|
-
this.
|
|
118
|
-
this.
|
|
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
|
-
|
|
66
|
+
get scopeCreated() {
|
|
67
67
|
throw new MethodNotImplementedError_1.MethodNotImplementedError();
|
|
68
68
|
}
|
|
69
|
-
|
|
69
|
+
get scopeDisposed() {
|
|
70
70
|
throw new MethodNotImplementedError_1.MethodNotImplementedError();
|
|
71
71
|
}
|
|
72
|
-
|
|
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
|
|
7
|
+
constructor({ methodStrategy, ...props }) {
|
|
8
8
|
super(props);
|
|
9
9
|
this.methodStrategy = methodStrategy;
|
|
10
10
|
}
|
package/cjm/hooks/onResolved.js
CHANGED
|
@@ -10,7 +10,7 @@ class OnDisposeModule {
|
|
|
10
10
|
this.strategy = strategy;
|
|
11
11
|
}
|
|
12
12
|
applyTo(container) {
|
|
13
|
-
container.
|
|
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.
|
|
4
|
-
exports.
|
|
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;
|
|
@@ -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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
114
|
-
this.
|
|
115
|
-
this.
|
|
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
|
-
|
|
63
|
+
get scopeCreated() {
|
|
64
64
|
throw new MethodNotImplementedError();
|
|
65
65
|
}
|
|
66
|
-
|
|
66
|
+
get scopeDisposed() {
|
|
67
67
|
throw new MethodNotImplementedError();
|
|
68
68
|
}
|
|
69
|
-
|
|
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
|
|
4
|
+
constructor({ methodStrategy, ...props }) {
|
|
5
5
|
super(props);
|
|
6
6
|
this.methodStrategy = methodStrategy;
|
|
7
7
|
}
|
package/esm/hooks/onResolved.js
CHANGED
|
@@ -6,7 +6,7 @@ export class OnDisposeModule {
|
|
|
6
6
|
this.strategy = strategy;
|
|
7
7
|
}
|
|
8
8
|
applyTo(container) {
|
|
9
|
-
container.
|
|
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": "
|
|
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
|
|
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
|
|
17
|
-
private readonly
|
|
18
|
-
private readonly
|
|
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
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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[];
|
|
@@ -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
|
|
4
|
+
methodStrategy: MethodStrategy;
|
|
5
5
|
};
|
|
6
6
|
export declare abstract class AsyncHookExecutionStrategy extends HookExecutionStrategy {
|
|
7
7
|
private readonly methodStrategy;
|
package/typings/index.d.ts
CHANGED
|
@@ -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
|
+
}
|