ts-ioc-container 63.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 +110 -94
- package/cjm/container/AutoResolveModule.js +1 -1
- package/cjm/container/Container.js +20 -31
- package/cjm/container/EmptyContainer.js +6 -3
- package/cjm/errors/TypedEventDisposedError.js +17 -0
- package/cjm/hooks/AsyncHookExecutionStrategy.js +15 -0
- package/cjm/hooks/HookContext.js +4 -4
- package/cjm/hooks/HookExecutionStrategy.js +68 -0
- package/cjm/hooks/ParallelAsync.js +17 -0
- package/cjm/hooks/SequentialAsync.js +15 -0
- package/cjm/hooks/SequentialSync.js +14 -0
- package/cjm/hooks/onConstruct.js +7 -9
- package/cjm/hooks/onResolved.js +14 -10
- package/cjm/hooks/onScopeDisposed.js +6 -8
- package/cjm/hooks/resolveHooks.js +1 -18
- package/cjm/index.js +18 -11
- package/cjm/utils/TypedEvent.js +35 -0
- package/esm/container/AutoResolveModule.js +1 -1
- package/esm/container/Container.js +20 -31
- package/esm/container/EmptyContainer.js +6 -3
- package/esm/errors/TypedEventDisposedError.js +13 -0
- package/esm/hooks/AsyncHookExecutionStrategy.js +11 -0
- package/esm/hooks/HookContext.js +2 -2
- package/esm/hooks/HookExecutionStrategy.js +62 -0
- package/esm/hooks/ParallelAsync.js +13 -0
- package/esm/hooks/SequentialAsync.js +11 -0
- package/esm/hooks/SequentialSync.js +10 -0
- package/esm/hooks/onConstruct.js +6 -8
- package/esm/hooks/onResolved.js +12 -9
- package/esm/hooks/onScopeDisposed.js +5 -7
- package/esm/hooks/resolveHooks.js +1 -14
- package/esm/index.js +12 -6
- package/esm/utils/TypedEvent.js +31 -0
- package/package.json +1 -1
- package/typings/container/Container.d.ts +9 -7
- package/typings/container/EmptyContainer.d.ts +7 -4
- package/typings/container/IContainer.d.ts +6 -4
- package/typings/errors/TypedEventDisposedError.d.ts +6 -0
- package/typings/hooks/AsyncHookExecutionStrategy.d.ts +10 -0
- package/typings/hooks/HookContext.d.ts +3 -3
- package/typings/hooks/HookExecutionStrategy.d.ts +35 -0
- package/typings/hooks/ParallelAsync.d.ts +5 -0
- package/typings/hooks/SequentialAsync.d.ts +5 -0
- package/typings/hooks/SequentialSync.d.ts +4 -0
- package/typings/hooks/onConstruct.d.ts +7 -8
- package/typings/hooks/onResolved.d.ts +5 -5
- package/typings/hooks/onScopeDisposed.d.ts +4 -5
- package/typings/hooks/resolveHooks.d.ts +0 -6
- package/typings/index.d.ts +12 -6
- package/typings/utils/TypedEvent.d.ts +17 -0
- package/cjm/hooks/HooksRunner.js +0 -50
- package/esm/hooks/HooksRunner.js +0 -45
- package/typings/hooks/HooksRunner.d.ts +0 -19
package/README.md
CHANGED
|
@@ -2833,22 +2833,39 @@ compensate for the bottom-up application order, so stacked decorators run in
|
|
|
2833
2833
|
declaration order: `@onConstruct(h1) @onConstruct(h2)` runs `h1` before `h2`.
|
|
2834
2834
|
|
|
2835
2835
|
Every hook may be sync or async — one decorator and one module take both, so
|
|
2836
|
-
there is no separate async form to reach for.
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
`
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2836
|
+
there is no separate async form to reach for. *How* the hooks run is a separate
|
|
2837
|
+
choice: each module takes a `HookExecutionStrategy`, keyed to the hooks it runs
|
|
2838
|
+
(`onConstruct`, `onScopeDisposed`, `onResolved`, or a custom key), and the
|
|
2839
|
+
strategy decides the order, what is awaited, and where a failure goes
|
|
2840
|
+
([ADR 0014](../../adr/0014-hook-execution-strategy.md)):
|
|
2841
|
+
|
|
2842
|
+
| Strategy | Members (decorated methods) | Hooks of one member | Awaits |
|
|
2843
|
+
| ----------------- | --------------------------- | ------------------------------------------- | ------ |
|
|
2844
|
+
| `SequentialSync` | one after another | in declaration order | no |
|
|
2845
|
+
| `SequentialAsync` | one after another | in order, or all at once (`methodStrategy`) | yes |
|
|
2846
|
+
| `ParallelAsync` | all at once | in order, or all at once (`methodStrategy`) | yes |
|
|
2847
|
+
|
|
2848
|
+
```typescript
|
|
2849
|
+
const container = new Container()
|
|
2850
|
+
.useModule(new OnConstructModule(new SequentialSync({ key: 'onConstruct' })))
|
|
2851
|
+
.useModule(new OnDisposeModule(new ParallelAsync({ key: 'onScopeDisposed' })));
|
|
2852
|
+
```
|
|
2853
|
+
|
|
2854
|
+
Resolution and disposal stay synchronous under every strategy: a run stays
|
|
2855
|
+
synchronous until a hook returns a promise, so sync hooks finish before
|
|
2856
|
+
`resolve` (or `dispose`) returns, and async ones are started there and settle
|
|
2857
|
+
afterwards. Instances that must expose readiness should publish it themselves,
|
|
2858
|
+
for example by storing the pending promise on the instance. The sync strategy
|
|
2859
|
+
never awaits — a hook that returns a promise under it is started but not
|
|
2860
|
+
observed.
|
|
2861
|
+
|
|
2862
|
+
`strategy.execute(instance, { scope })` runs the hooks of a custom key by hand;
|
|
2863
|
+
`predicate`, `createExecutionContext` and `mapExecutionContext` can be set on
|
|
2864
|
+
the strategy or per call.
|
|
2865
|
+
|
|
2866
|
+
A strategy takes an optional `onError: (scope) => (error) => void`, which
|
|
2867
|
+
receives both kinds of failure — what a sync hook threw and what an async hook
|
|
2868
|
+
rejected with. Without one, failures are dropped.
|
|
2852
2869
|
|
|
2853
2870
|
### Hook domains
|
|
2854
2871
|
|
|
@@ -2856,58 +2873,60 @@ The decorators above declare hook *metadata* on a class. The imperative side —
|
|
|
2856
2873
|
the callbacks the container machinery runs — is registered on whichever
|
|
2857
2874
|
abstraction raises the event, one hook type per domain:
|
|
2858
2875
|
|
|
2859
|
-
| Domain | Registered on | Type |
|
|
2876
|
+
| Domain | Registered on | Type | Where |
|
|
2860
2877
|
| ------------ | ------------- | --------------- | -------------------------------------------------------- |
|
|
2861
|
-
| **Scope** | `IContainer` | `ScopeHook` | `
|
|
2862
|
-
| **Scope** | `IContainer` | `RegisteredHook`| `
|
|
2878
|
+
| **Scope** | `IContainer` | `ScopeHook` | `scopeCreated.subscribe(...)`, `scopeDisposed.subscribe(...)` |
|
|
2879
|
+
| **Scope** | `IContainer` | `RegisteredHook`| `registered.subscribe(...)` |
|
|
2863
2880
|
| **Injector** | `IInjector` | `InjectorHook` | `onConstructed(...)` |
|
|
2864
2881
|
| **Provider** | `IProvider` | `ProviderHook` | `onResolved(...)`, or the [`onResolve`](#on-resolve) pipe |
|
|
2865
2882
|
|
|
2866
|
-
A container
|
|
2867
|
-
|
|
2883
|
+
A container passes its injector to every scope it creates, so **one injector**
|
|
2884
|
+
backs the whole scope tree, and `container.getInjector()` hands it out: an
|
|
2885
|
+
`onConstructed` hook registered through it covers every scope, whenever it was
|
|
2886
|
+
added. Scope hooks, by contrast, are copied into a child at `createScope` time,
|
|
2887
|
+
so a child inherits what its parent held then and later additions to either
|
|
2888
|
+
stay local.
|
|
2868
2889
|
|
|
2869
2890
|
```typescript
|
|
2870
|
-
|
|
2871
|
-
const injector = new MetadataInjector().onConstructed((instance, scope) => metrics.built(instance, scope));
|
|
2891
|
+
const container = new Container({ tags: ['application'] });
|
|
2872
2892
|
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
```
|
|
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));
|
|
2878
2897
|
|
|
2879
|
-
|
|
2880
|
-
backs the whole scope tree and an `onConstructed` hook covers all of it, whenever
|
|
2881
|
-
it was added. Scope hooks, by contrast, are copied into a child at `createScope`
|
|
2882
|
-
time, so a child inherits what its parent held then and later additions to either
|
|
2883
|
-
stay local.
|
|
2884
|
-
|
|
2885
|
-
The built-in modules follow the same split. `OnConstructModule` is an
|
|
2886
|
-
**injector** module (`IInjectorModule`), applied with `injector.useModule(...)`:
|
|
2898
|
+
stop(); // detached; `container.scopeCreated.unsubscribe(fn)` does the same by reference
|
|
2887
2899
|
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
const container = new Container({ injector });
|
|
2900
|
+
// Construction is the injector's event, not a scope's
|
|
2901
|
+
container.getInjector().onConstructed((instance, scope) => metrics.built(instance, scope));
|
|
2891
2902
|
```
|
|
2892
2903
|
|
|
2893
|
-
|
|
2894
|
-
`
|
|
2895
|
-
`
|
|
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
|
+
|
|
2912
|
+
The built-in modules are all container modules: `OnConstructModule` reaches the
|
|
2913
|
+
injector through `getInjector()`, `OnDisposeModule` subscribes to `scopeDisposed`,
|
|
2914
|
+
and `OnResolvedModule` reaches every provider through `registered`.
|
|
2896
2915
|
|
|
2897
2916
|
### OnConstruct
|
|
2898
2917
|
|
|
2899
2918
|
```typescript
|
|
2900
2919
|
import 'reflect-metadata';
|
|
2901
2920
|
import {
|
|
2902
|
-
MetadataInjector,
|
|
2903
2921
|
OnConstructModule,
|
|
2904
2922
|
Container,
|
|
2905
|
-
type ExecutionContext,
|
|
2906
2923
|
type HookFn,
|
|
2907
2924
|
type IContainer,
|
|
2908
2925
|
inject,
|
|
2909
2926
|
onConstruct,
|
|
2910
2927
|
Registration as R,
|
|
2928
|
+
SequentialAsync,
|
|
2929
|
+
SequentialSync,
|
|
2911
2930
|
} from 'ts-ioc-container';
|
|
2912
2931
|
|
|
2913
2932
|
const execute: HookFn = (ctx) => {
|
|
@@ -2931,9 +2950,10 @@ describe('onConstruct', function () {
|
|
|
2931
2950
|
}
|
|
2932
2951
|
}
|
|
2933
2952
|
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2953
|
+
// The module takes a strategy for how the hooks run; the strategy is keyed to the hooks it runs.
|
|
2954
|
+
const container = new Container()
|
|
2955
|
+
.useModule(new OnConstructModule(new SequentialSync({ key: 'onConstruct' })))
|
|
2956
|
+
.addRegistration(R.fromValue('postgres://localhost:5432').bindTo('ConnectionString'));
|
|
2937
2957
|
|
|
2938
2958
|
const db = container.resolve(DatabaseConnection);
|
|
2939
2959
|
|
|
@@ -2941,7 +2961,7 @@ describe('onConstruct', function () {
|
|
|
2941
2961
|
expect(db.connectionString).toBe('postgres://localhost:5432');
|
|
2942
2962
|
});
|
|
2943
2963
|
|
|
2944
|
-
it('should forward hook exceptions to the
|
|
2964
|
+
it('should forward hook exceptions to the onError handler with the scope', function () {
|
|
2945
2965
|
const failure = new Error('boom');
|
|
2946
2966
|
|
|
2947
2967
|
class BrokenService {
|
|
@@ -2951,36 +2971,24 @@ describe('onConstruct', function () {
|
|
|
2951
2971
|
init() {}
|
|
2952
2972
|
}
|
|
2953
2973
|
|
|
2954
|
-
let captured: { ex: unknown;
|
|
2955
|
-
const container = new Container(
|
|
2956
|
-
|
|
2957
|
-
new
|
|
2958
|
-
|
|
2974
|
+
let captured: { ex: unknown; scope: IContainer } | undefined;
|
|
2975
|
+
const container = new Container().useModule(
|
|
2976
|
+
new OnConstructModule(
|
|
2977
|
+
new SequentialSync({
|
|
2978
|
+
key: 'onConstruct',
|
|
2979
|
+
onError: (scope) => (ex) => {
|
|
2980
|
+
captured = { ex, scope };
|
|
2981
|
+
},
|
|
2959
2982
|
}),
|
|
2960
2983
|
),
|
|
2961
|
-
|
|
2984
|
+
);
|
|
2962
2985
|
|
|
2963
2986
|
expect(() => container.resolve(BrokenService)).not.toThrow();
|
|
2964
2987
|
expect(captured?.ex).toBe(failure);
|
|
2965
|
-
expect(captured?.
|
|
2966
|
-
});
|
|
2967
|
-
|
|
2968
|
-
it('should rethrow hook exceptions when no onException handler is provided', function () {
|
|
2969
|
-
const failure = new Error('boom');
|
|
2970
|
-
|
|
2971
|
-
class BrokenService {
|
|
2972
|
-
@onConstruct(() => {
|
|
2973
|
-
throw failure;
|
|
2974
|
-
})
|
|
2975
|
-
init() {}
|
|
2976
|
-
}
|
|
2977
|
-
|
|
2978
|
-
const container = new Container({ injector: new MetadataInjector().useModule(new OnConstructModule()) });
|
|
2979
|
-
|
|
2980
|
-
expect(() => container.resolve(BrokenService)).toThrow(failure);
|
|
2988
|
+
expect(captured?.scope).toBe(container);
|
|
2981
2989
|
});
|
|
2982
2990
|
|
|
2983
|
-
it('should expose the resolving scope
|
|
2991
|
+
it('should expose the resolving scope to the onError handler', function () {
|
|
2984
2992
|
class BrokenService {
|
|
2985
2993
|
@onConstruct(() => {
|
|
2986
2994
|
throw new Error('boom');
|
|
@@ -2989,13 +2997,16 @@ describe('onConstruct', function () {
|
|
|
2989
2997
|
}
|
|
2990
2998
|
|
|
2991
2999
|
let scope: IContainer | undefined;
|
|
2992
|
-
const container = new Container(
|
|
2993
|
-
|
|
2994
|
-
new
|
|
2995
|
-
|
|
3000
|
+
const container = new Container().useModule(
|
|
3001
|
+
new OnConstructModule(
|
|
3002
|
+
new SequentialSync({
|
|
3003
|
+
key: 'onConstruct',
|
|
3004
|
+
onError: (s) => () => {
|
|
3005
|
+
scope = s;
|
|
3006
|
+
},
|
|
2996
3007
|
}),
|
|
2997
3008
|
),
|
|
2998
|
-
|
|
3009
|
+
);
|
|
2999
3010
|
const child = container.createScope();
|
|
3000
3011
|
|
|
3001
3012
|
child.resolve(BrokenService);
|
|
@@ -3026,9 +3037,10 @@ describe('onConstruct', function () {
|
|
|
3026
3037
|
}
|
|
3027
3038
|
}
|
|
3028
3039
|
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3040
|
+
// An async strategy awaits the hooks; resolution itself still does not wait for them.
|
|
3041
|
+
const container = new Container()
|
|
3042
|
+
.useModule(new OnConstructModule(new SequentialAsync({ key: 'onConstruct' })))
|
|
3043
|
+
.addRegistration(R.fromValue('postgres://localhost:5432').bindTo('ConnectionString'));
|
|
3032
3044
|
|
|
3033
3045
|
const db = container.resolve(DatabaseConnection);
|
|
3034
3046
|
|
|
@@ -3041,7 +3053,7 @@ describe('onConstruct', function () {
|
|
|
3041
3053
|
expect(db.connectionString).toBe('postgres://localhost:5432');
|
|
3042
3054
|
});
|
|
3043
3055
|
|
|
3044
|
-
it('should forward rejected hooks to the
|
|
3056
|
+
it('should forward rejected hooks to the onError handler with the scope', async function () {
|
|
3045
3057
|
const failure = new Error('boom');
|
|
3046
3058
|
|
|
3047
3059
|
class BrokenService {
|
|
@@ -3049,14 +3061,17 @@ describe('onConstruct', function () {
|
|
|
3049
3061
|
init() {}
|
|
3050
3062
|
}
|
|
3051
3063
|
|
|
3052
|
-
let captured: { ex: unknown;
|
|
3053
|
-
const container = new Container(
|
|
3054
|
-
|
|
3055
|
-
new
|
|
3056
|
-
|
|
3064
|
+
let captured: { ex: unknown; scope: IContainer } | undefined;
|
|
3065
|
+
const container = new Container().useModule(
|
|
3066
|
+
new OnConstructModule(
|
|
3067
|
+
new SequentialAsync({
|
|
3068
|
+
key: 'onConstruct',
|
|
3069
|
+
onError: (scope) => (ex) => {
|
|
3070
|
+
captured = { ex, scope };
|
|
3071
|
+
},
|
|
3057
3072
|
}),
|
|
3058
3073
|
),
|
|
3059
|
-
|
|
3074
|
+
);
|
|
3060
3075
|
|
|
3061
3076
|
const child = container.createScope();
|
|
3062
3077
|
child.resolve(BrokenService);
|
|
@@ -3064,7 +3079,7 @@ describe('onConstruct', function () {
|
|
|
3064
3079
|
await vi.waitFor(() => expect(captured).toBeDefined());
|
|
3065
3080
|
|
|
3066
3081
|
expect(captured?.ex).toBe(failure);
|
|
3067
|
-
expect(captured?.
|
|
3082
|
+
expect(captured?.scope).toBe(child);
|
|
3068
3083
|
});
|
|
3069
3084
|
});
|
|
3070
3085
|
|
|
@@ -3083,6 +3098,7 @@ import {
|
|
|
3083
3098
|
onScopeDisposed,
|
|
3084
3099
|
register,
|
|
3085
3100
|
Registration as R,
|
|
3101
|
+
SequentialSync,
|
|
3086
3102
|
singleton,
|
|
3087
3103
|
} from 'ts-ioc-container';
|
|
3088
3104
|
|
|
@@ -3118,7 +3134,7 @@ class Logger {
|
|
|
3118
3134
|
describe('onScopeDisposed', function () {
|
|
3119
3135
|
it('should invoke hooks on all instances when container is disposed', function () {
|
|
3120
3136
|
const container = new Container()
|
|
3121
|
-
.useModule(new OnDisposeModule())
|
|
3137
|
+
.useModule(new OnDisposeModule(new SequentialSync({ key: 'onScopeDisposed' })))
|
|
3122
3138
|
.addRegistration(R.fromClass(Logger))
|
|
3123
3139
|
.addRegistration(R.fromClass(LogsRepo));
|
|
3124
3140
|
|
|
@@ -3138,7 +3154,7 @@ describe('onScopeDisposed', function () {
|
|
|
3138
3154
|
|
|
3139
3155
|
```typescript
|
|
3140
3156
|
import 'reflect-metadata';
|
|
3141
|
-
import { append, Container, hook,
|
|
3157
|
+
import { append, Container, hook, SequentialSync, injectProp, Registration } from 'ts-ioc-container';
|
|
3142
3158
|
|
|
3143
3159
|
/**
|
|
3144
3160
|
* UI Components - Property Injection
|
|
@@ -3153,8 +3169,8 @@ import { append, Container, hook, HooksRunner, injectProp, Registration } from '
|
|
|
3153
3169
|
|
|
3154
3170
|
describe('inject property', () => {
|
|
3155
3171
|
it('should inject property', () => {
|
|
3156
|
-
//
|
|
3157
|
-
const
|
|
3172
|
+
// Strategy for the 'onInit' lifecycle hook
|
|
3173
|
+
const onInitStrategy = new SequentialSync({ key: 'onInit' });
|
|
3158
3174
|
|
|
3159
3175
|
class UserViewModel {
|
|
3160
3176
|
// Inject 'GreetingService' into 'greeting' property during 'onInit'
|
|
@@ -3172,14 +3188,14 @@ describe('inject property', () => {
|
|
|
3172
3188
|
const viewModel = container.resolve(UserViewModel);
|
|
3173
3189
|
|
|
3174
3190
|
// 2. Run lifecycle hooks to inject properties
|
|
3175
|
-
|
|
3191
|
+
onInitStrategy.execute(viewModel, { scope: container });
|
|
3176
3192
|
|
|
3177
3193
|
expect(viewModel.greetingService).toBe('Hello');
|
|
3178
3194
|
expect(viewModel.display()).toBe('Hello User');
|
|
3179
3195
|
});
|
|
3180
3196
|
|
|
3181
3197
|
it('should read the applied instance property via getProperty', () => {
|
|
3182
|
-
const
|
|
3198
|
+
const onInitStrategy = new SequentialSync({ key: 'onInit' });
|
|
3183
3199
|
|
|
3184
3200
|
let injectedValue: unknown;
|
|
3185
3201
|
|
|
@@ -3196,7 +3212,7 @@ describe('inject property', () => {
|
|
|
3196
3212
|
const container = new Container().addRegistration(Registration.fromValue('Hello').bindToKey('GreetingService'));
|
|
3197
3213
|
|
|
3198
3214
|
const viewModel = container.resolve(UserViewModel);
|
|
3199
|
-
|
|
3215
|
+
onInitStrategy.execute(viewModel, { scope: container });
|
|
3200
3216
|
|
|
3201
3217
|
expect(injectedValue).toBe('Hello');
|
|
3202
3218
|
});
|
|
@@ -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);
|
|
@@ -125,21 +123,12 @@ class Container {
|
|
|
125
123
|
getRegistrations() {
|
|
126
124
|
return [...this.parent.getRegistrations(), ...this.registrations];
|
|
127
125
|
}
|
|
126
|
+
getInjector() {
|
|
127
|
+
return this.injector;
|
|
128
|
+
}
|
|
128
129
|
hasRegistration(key) {
|
|
129
130
|
return this.registrations.some((r) => r.getKeyOrFail() === key) || this.parent.hasRegistration(key);
|
|
130
131
|
}
|
|
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
132
|
addInstance(instance) {
|
|
144
133
|
this.instances.add(instance);
|
|
145
134
|
}
|
|
@@ -23,6 +23,9 @@ class EmptyContainer {
|
|
|
23
23
|
createScope() {
|
|
24
24
|
throw new MethodNotImplementedError_1.MethodNotImplementedError();
|
|
25
25
|
}
|
|
26
|
+
getInjector() {
|
|
27
|
+
throw new MethodNotImplementedError_1.MethodNotImplementedError();
|
|
28
|
+
}
|
|
26
29
|
autoResolve(options) {
|
|
27
30
|
throw new MethodNotImplementedError_1.MethodNotImplementedError();
|
|
28
31
|
}
|
|
@@ -60,13 +63,13 @@ class EmptyContainer {
|
|
|
60
63
|
resolveOneByAlias(alias, options) {
|
|
61
64
|
throw new DependencyNotFoundError_1.DependencyNotFoundError(`Cannot find alias ${alias.toString()}`);
|
|
62
65
|
}
|
|
63
|
-
|
|
66
|
+
get scopeCreated() {
|
|
64
67
|
throw new MethodNotImplementedError_1.MethodNotImplementedError();
|
|
65
68
|
}
|
|
66
|
-
|
|
69
|
+
get scopeDisposed() {
|
|
67
70
|
throw new MethodNotImplementedError_1.MethodNotImplementedError();
|
|
68
71
|
}
|
|
69
|
-
|
|
72
|
+
get registered() {
|
|
70
73
|
throw new MethodNotImplementedError_1.MethodNotImplementedError();
|
|
71
74
|
}
|
|
72
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;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AsyncHookExecutionStrategy = void 0;
|
|
4
|
+
const HookExecutionStrategy_1 = require("./HookExecutionStrategy");
|
|
5
|
+
class AsyncHookExecutionStrategy extends HookExecutionStrategy_1.HookExecutionStrategy {
|
|
6
|
+
methodStrategy;
|
|
7
|
+
constructor({ methodStrategy = 'sequential', ...props }) {
|
|
8
|
+
super(props);
|
|
9
|
+
this.methodStrategy = methodStrategy;
|
|
10
|
+
}
|
|
11
|
+
runMember({ hooks, context }) {
|
|
12
|
+
return this.methodStrategy === 'parallel' ? (0, HookExecutionStrategy_1.runAtOnce)(hooks, context) : (0, HookExecutionStrategy_1.runInOrder)(hooks, context);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
exports.AsyncHookExecutionStrategy = AsyncHookExecutionStrategy;
|
package/cjm/hooks/HookContext.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createHookContextFactory = exports.
|
|
3
|
+
exports.createHookContextFactory = exports.createHookExecutionContext = exports.HookContext = void 0;
|
|
4
4
|
const MetadataInjector_1 = require("../injector/MetadataInjector");
|
|
5
5
|
class HookContext {
|
|
6
6
|
instance;
|
|
@@ -35,7 +35,7 @@ class HookContext {
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
exports.HookContext = HookContext;
|
|
38
|
-
const
|
|
39
|
-
exports.
|
|
40
|
-
const createHookContextFactory = ({ args = [] } = {}) => (Target, scope, methodName) => (0, exports.
|
|
38
|
+
const createHookExecutionContext = (Target, scope, methodName = 'constructor') => new HookContext(Target, scope, methodName);
|
|
39
|
+
exports.createHookExecutionContext = createHookExecutionContext;
|
|
40
|
+
const createHookContextFactory = ({ args = [] } = {}) => (Target, scope, methodName) => (0, exports.createHookExecutionContext)(Target, scope, methodName).setInitialArgs(...args);
|
|
41
41
|
exports.createHookContextFactory = createHookContextFactory;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HookExecutionStrategy = exports.runAtOnce = exports.runInOrder = void 0;
|
|
4
|
+
const target_1 = require("../metadata/target");
|
|
5
|
+
const hook_1 = require("./hook");
|
|
6
|
+
const HookContext_1 = require("./HookContext");
|
|
7
|
+
const runInOrder = (hooks, context, from = 0) => {
|
|
8
|
+
for (let i = from; i < hooks.length; i++) {
|
|
9
|
+
const result = hooks[i](context);
|
|
10
|
+
if (result instanceof Promise) {
|
|
11
|
+
return result.then(() => (0, exports.runInOrder)(hooks, context, i + 1));
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
exports.runInOrder = runInOrder;
|
|
16
|
+
const runAtOnce = (hooks, context) => {
|
|
17
|
+
const pending = [];
|
|
18
|
+
for (const hook of hooks) {
|
|
19
|
+
const result = hook(context);
|
|
20
|
+
if (result instanceof Promise) {
|
|
21
|
+
pending.push(result);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return pending.length > 0 ? Promise.all(pending).then(() => undefined) : undefined;
|
|
25
|
+
};
|
|
26
|
+
exports.runAtOnce = runAtOnce;
|
|
27
|
+
class HookExecutionStrategy {
|
|
28
|
+
key;
|
|
29
|
+
onError;
|
|
30
|
+
options;
|
|
31
|
+
hooksByClass = new WeakMap();
|
|
32
|
+
constructor({ key, onError, createExecutionContext = HookContext_1.createHookExecutionContext, mapExecutionContext = (context) => context, predicate = () => true, }) {
|
|
33
|
+
this.key = key;
|
|
34
|
+
this.onError = onError;
|
|
35
|
+
this.options = { createExecutionContext, mapExecutionContext, predicate };
|
|
36
|
+
}
|
|
37
|
+
hasHooks(target) {
|
|
38
|
+
return (0, hook_1.hasHooks)(target, this.key);
|
|
39
|
+
}
|
|
40
|
+
execute(target, { scope, ...overrides }) {
|
|
41
|
+
const report = (ex) => this.onError?.(scope)(ex);
|
|
42
|
+
try {
|
|
43
|
+
this.processHooks(this.collect(target, scope, overrides))?.catch(report);
|
|
44
|
+
}
|
|
45
|
+
catch (ex) {
|
|
46
|
+
report(ex);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
collect(target, scope, { createExecutionContext = this.options.createExecutionContext, mapExecutionContext = this.options.mapExecutionContext, predicate = this.options.predicate, }) {
|
|
50
|
+
const members = [];
|
|
51
|
+
for (const { methodName, hooks } of this.hooksOf(target)) {
|
|
52
|
+
if (predicate(methodName)) {
|
|
53
|
+
members.push({ hooks, context: mapExecutionContext(createExecutionContext(target, scope, methodName)) });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return members;
|
|
57
|
+
}
|
|
58
|
+
hooksOf(target) {
|
|
59
|
+
const Target = (0, target_1.resolveConstructor)(target);
|
|
60
|
+
let hooks = this.hooksByClass.get(Target);
|
|
61
|
+
if (!hooks) {
|
|
62
|
+
hooks = Array.from((0, hook_1.getHooks)(Target, this.key), ([methodName, fns]) => ({ methodName, hooks: fns.map(hook_1.toHookFn) }));
|
|
63
|
+
this.hooksByClass.set(Target, hooks);
|
|
64
|
+
}
|
|
65
|
+
return hooks;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
exports.HookExecutionStrategy = HookExecutionStrategy;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ParallelAsync = void 0;
|
|
4
|
+
const AsyncHookExecutionStrategy_1 = require("./AsyncHookExecutionStrategy");
|
|
5
|
+
class ParallelAsync extends AsyncHookExecutionStrategy_1.AsyncHookExecutionStrategy {
|
|
6
|
+
processHooks(members) {
|
|
7
|
+
const pending = [];
|
|
8
|
+
for (const member of members) {
|
|
9
|
+
const result = this.runMember(member);
|
|
10
|
+
if (result) {
|
|
11
|
+
pending.push(result);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return pending.length > 0 ? Promise.all(pending).then(() => undefined) : undefined;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
exports.ParallelAsync = ParallelAsync;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SequentialAsync = void 0;
|
|
4
|
+
const AsyncHookExecutionStrategy_1 = require("./AsyncHookExecutionStrategy");
|
|
5
|
+
class SequentialAsync extends AsyncHookExecutionStrategy_1.AsyncHookExecutionStrategy {
|
|
6
|
+
processHooks(members, from = 0) {
|
|
7
|
+
for (let i = from; i < members.length; i++) {
|
|
8
|
+
const result = this.runMember(members[i]);
|
|
9
|
+
if (result) {
|
|
10
|
+
return result.then(() => this.processHooks(members, i + 1));
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
exports.SequentialAsync = SequentialAsync;
|