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.
Files changed (53) hide show
  1. package/README.md +110 -94
  2. package/cjm/container/AutoResolveModule.js +1 -1
  3. package/cjm/container/Container.js +20 -31
  4. package/cjm/container/EmptyContainer.js +6 -3
  5. package/cjm/errors/TypedEventDisposedError.js +17 -0
  6. package/cjm/hooks/AsyncHookExecutionStrategy.js +15 -0
  7. package/cjm/hooks/HookContext.js +4 -4
  8. package/cjm/hooks/HookExecutionStrategy.js +68 -0
  9. package/cjm/hooks/ParallelAsync.js +17 -0
  10. package/cjm/hooks/SequentialAsync.js +15 -0
  11. package/cjm/hooks/SequentialSync.js +14 -0
  12. package/cjm/hooks/onConstruct.js +7 -9
  13. package/cjm/hooks/onResolved.js +14 -10
  14. package/cjm/hooks/onScopeDisposed.js +6 -8
  15. package/cjm/hooks/resolveHooks.js +1 -18
  16. package/cjm/index.js +18 -11
  17. package/cjm/utils/TypedEvent.js +35 -0
  18. package/esm/container/AutoResolveModule.js +1 -1
  19. package/esm/container/Container.js +20 -31
  20. package/esm/container/EmptyContainer.js +6 -3
  21. package/esm/errors/TypedEventDisposedError.js +13 -0
  22. package/esm/hooks/AsyncHookExecutionStrategy.js +11 -0
  23. package/esm/hooks/HookContext.js +2 -2
  24. package/esm/hooks/HookExecutionStrategy.js +62 -0
  25. package/esm/hooks/ParallelAsync.js +13 -0
  26. package/esm/hooks/SequentialAsync.js +11 -0
  27. package/esm/hooks/SequentialSync.js +10 -0
  28. package/esm/hooks/onConstruct.js +6 -8
  29. package/esm/hooks/onResolved.js +12 -9
  30. package/esm/hooks/onScopeDisposed.js +5 -7
  31. package/esm/hooks/resolveHooks.js +1 -14
  32. package/esm/index.js +12 -6
  33. package/esm/utils/TypedEvent.js +31 -0
  34. package/package.json +1 -1
  35. package/typings/container/Container.d.ts +9 -7
  36. package/typings/container/EmptyContainer.d.ts +7 -4
  37. package/typings/container/IContainer.d.ts +6 -4
  38. package/typings/errors/TypedEventDisposedError.d.ts +6 -0
  39. package/typings/hooks/AsyncHookExecutionStrategy.d.ts +10 -0
  40. package/typings/hooks/HookContext.d.ts +3 -3
  41. package/typings/hooks/HookExecutionStrategy.d.ts +35 -0
  42. package/typings/hooks/ParallelAsync.d.ts +5 -0
  43. package/typings/hooks/SequentialAsync.d.ts +5 -0
  44. package/typings/hooks/SequentialSync.d.ts +4 -0
  45. package/typings/hooks/onConstruct.d.ts +7 -8
  46. package/typings/hooks/onResolved.d.ts +5 -5
  47. package/typings/hooks/onScopeDisposed.d.ts +4 -5
  48. package/typings/hooks/resolveHooks.d.ts +0 -6
  49. package/typings/index.d.ts +12 -6
  50. package/typings/utils/TypedEvent.d.ts +17 -0
  51. package/cjm/hooks/HooksRunner.js +0 -50
  52. package/esm/hooks/HooksRunner.js +0 -45
  53. 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. A `HooksRunner` runs a hook chain
2837
- eagerly and stays synchronous until a hook returns a promise, then awaits the
2838
- rest of that chain. Sync hooks therefore finish before `resolve` (or `dispose`)
2839
- returns, exactly as before; async ones are started there and settle afterwards,
2840
- so `resolve` returns before they finish. Instances that must expose readiness
2841
- should publish it themselves, for example by storing the pending promise on the
2842
- instance.
2843
-
2844
- `HooksRunner.execute` mirrors that: it returns `undefined` when nothing went
2845
- async and a promise otherwise, so a caller running its own hooks can `await` the
2846
- result either way.
2847
-
2848
- Every module takes an optional `onException` handler, and it catches both kinds
2849
- of failure — what a sync hook threw and what an async hook rejected with.
2850
- Without one, a sync hook throws out of the call and an async hook surfaces as an
2851
- unhandled promise rejection.
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 | Methods |
2876
+ | Domain | Registered on | Type | Where |
2860
2877
  | ------------ | ------------- | --------------- | -------------------------------------------------------- |
2861
- | **Scope** | `IContainer` | `ScopeHook` | `onScopeCreated(...)`, `onScopeDisposed(...)` |
2862
- | **Scope** | `IContainer` | `RegisteredHook`| `onRegistered(...)` |
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 never hands its injector out — the injector is configured first and
2867
- passed in at construction:
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
- // Construction is the injector's event, not a scope's
2871
- const injector = new MetadataInjector().onConstructed((instance, scope) => metrics.built(instance, scope));
2891
+ const container = new Container({ tags: ['application'] });
2872
2892
 
2873
- const container = new Container({ injector, tags: ['application'] })
2874
- .onScopeCreated((scope) => audit.scopeOpened(scope))
2875
- .onScopeDisposed((scope) => audit.scopeClosed(scope))
2876
- .onRegistered((provider, key) => audit.registered(key));
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
- A container passes its injector to every scope it creates, so **one injector**
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
- ```typescript
2889
- const injector = new MetadataInjector().useModule(new OnConstructModule());
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
- `OnDisposeModule` is a container module hooking `onScopeDisposed`, and
2894
- `OnResolvedModule` is a container module reaching every provider through
2895
- `onRegistered`.
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
- const container = new Container({
2935
- injector: new MetadataInjector().useModule(new OnConstructModule()),
2936
- }).addRegistration(R.fromValue('postgres://localhost:5432').bindTo('ConnectionString'));
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 onException handler with the execution context', function () {
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; context: ExecutionContext } | undefined;
2955
- const container = new Container({
2956
- injector: new MetadataInjector().useModule(
2957
- new OnConstructModule((ex, context) => {
2958
- captured = { ex, context };
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?.context.scope).toBe(container);
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 through the execution context', function () {
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
- injector: new MetadataInjector().useModule(
2994
- new OnConstructModule((_ex, context) => {
2995
- scope = context.scope;
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
- const container = new Container({
3030
- injector: new MetadataInjector().useModule(new OnConstructModule()),
3031
- }).addRegistration(R.fromValue('postgres://localhost:5432').bindTo('ConnectionString'));
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 onException handler with the execution context', async function () {
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; context: ExecutionContext } | undefined;
3053
- const container = new Container({
3054
- injector: new MetadataInjector().useModule(
3055
- new OnConstructModule((ex, context) => {
3056
- captured = { ex, context };
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?.context.scope).toBe(child);
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, HooksRunner, injectProp, Registration } from 'ts-ioc-container';
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
- // Runner for the 'onInit' lifecycle hook
3157
- const onInitHookRunner = new HooksRunner('onInit');
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
- onInitHookRunner.execute(viewModel, { scope: container });
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 onInitHookRunner = new HooksRunner('onInit');
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
- onInitHookRunner.execute(viewModel, { scope: container });
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.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);
@@ -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
- onScopeCreated(...hooks) {
66
+ get scopeCreated() {
64
67
  throw new MethodNotImplementedError_1.MethodNotImplementedError();
65
68
  }
66
- onScopeDisposed(...hooks) {
69
+ get scopeDisposed() {
67
70
  throw new MethodNotImplementedError_1.MethodNotImplementedError();
68
71
  }
69
- onRegistered(...hooks) {
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;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createHookContextFactory = exports.createHookContext = exports.HookContext = void 0;
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 createHookContext = (Target, scope, methodName = 'constructor') => new HookContext(Target, scope, methodName);
39
- exports.createHookContext = createHookContext;
40
- const createHookContextFactory = ({ args = [] } = {}) => (Target, scope, methodName) => (0, exports.createHookContext)(Target, scope, methodName).setInitialArgs(...args);
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;