ts-ioc-container 60.3.0 → 62.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 (52) hide show
  1. package/README.md +111 -65
  2. package/cjm/container/Container.js +16 -26
  3. package/cjm/container/EmptyContainer.js +3 -6
  4. package/cjm/hooks/HooksRunner.js +30 -22
  5. package/cjm/hooks/onConstruct.js +3 -11
  6. package/cjm/hooks/onResolved.js +8 -4
  7. package/cjm/hooks/onScopeDisposed.js +22 -0
  8. package/cjm/hooks/resolveHooks.js +3 -14
  9. package/cjm/index.js +8 -19
  10. package/cjm/injector/IInjector.js +12 -0
  11. package/cjm/provider/Provider.js +6 -6
  12. package/cjm/registration/IRegistration.js +1 -1
  13. package/esm/container/Container.js +16 -26
  14. package/esm/container/EmptyContainer.js +3 -6
  15. package/esm/hooks/HooksRunner.js +28 -21
  16. package/esm/hooks/onConstruct.js +4 -12
  17. package/esm/hooks/onResolved.js +8 -4
  18. package/esm/hooks/onScopeDisposed.js +17 -0
  19. package/esm/hooks/resolveHooks.js +2 -12
  20. package/esm/index.js +3 -6
  21. package/esm/injector/IInjector.js +12 -0
  22. package/esm/provider/Provider.js +6 -6
  23. package/esm/registration/IRegistration.js +2 -2
  24. package/package.json +1 -1
  25. package/typings/container/Container.d.ts +5 -8
  26. package/typings/container/EmptyContainer.d.ts +3 -5
  27. package/typings/container/IContainer.d.ts +3 -7
  28. package/typings/hooks/HooksRunner.d.ts +4 -2
  29. package/typings/hooks/onConstruct.d.ts +4 -6
  30. package/typings/hooks/onResolved.d.ts +4 -2
  31. package/typings/hooks/onScopeDisposed.d.ts +10 -0
  32. package/typings/hooks/resolveHooks.d.ts +6 -7
  33. package/typings/index.d.ts +7 -10
  34. package/typings/injector/IInjector.d.ts +9 -1
  35. package/typings/provider/IProvider.d.ts +3 -2
  36. package/typings/provider/Provider.d.ts +4 -4
  37. package/typings/registration/IRegistration.d.ts +3 -3
  38. package/cjm/errors/UnexpectedHookResultError.js +0 -12
  39. package/cjm/hooks/onConstructAsync.js +0 -28
  40. package/cjm/hooks/onContainerDisposed.js +0 -18
  41. package/cjm/hooks/onResolvedAsync.js +0 -24
  42. package/cjm/utils/promise.js +0 -5
  43. package/esm/errors/UnexpectedHookResultError.js +0 -8
  44. package/esm/hooks/onConstructAsync.js +0 -23
  45. package/esm/hooks/onContainerDisposed.js +0 -13
  46. package/esm/hooks/onResolvedAsync.js +0 -19
  47. package/esm/utils/promise.js +0 -1
  48. package/typings/errors/UnexpectedHookResultError.d.ts +0 -5
  49. package/typings/hooks/onConstructAsync.d.ts +0 -11
  50. package/typings/hooks/onContainerDisposed.d.ts +0 -9
  51. package/typings/hooks/onResolvedAsync.d.ts +0 -12
  52. package/typings/utils/promise.d.ts +0 -1
package/README.md CHANGED
@@ -18,7 +18,7 @@ provider pipelines, aliases, and custom injector strategies.
18
18
  - clean API for classes, keys, tokens, aliases, and scopes
19
19
  - no global container object; pass containers and scopes explicitly
20
20
  - supports tagged application, request, transaction, page, and widget scopes
21
- - decorator support with `@register`, `@inject`, `@onConstruct`, and `@onContainerDisposed`
21
+ - decorator support with `@register`, `@inject`, `@onConstruct`, and `@onScopeDisposed`
22
22
  - can [inject properties](#inject-property)
23
23
  - can inject [lazy dependencies](#lazy)
24
24
  - composable provider and registration pipelines
@@ -54,9 +54,9 @@ provider pipelines, aliases, and custom injector strategies.
54
54
  - [Scope](#scope) `scope`
55
55
  - [Module](#module)
56
56
  - [Hook](#hook) `@hook`
57
+ - [Hook domains](#hook-domains) `ScopeHook` `InjectorHook` `ProviderHook`
57
58
  - [OnConstruct](#onconstruct) `@onConstruct`
58
- - [OnConstructAsync](#onconstructasync) `@onConstructAsync`
59
- - [OnContainerDisposed](#oncontainerdisposed) `@onContainerDisposed`
59
+ - [OnScopeDisposed](#onscopedisposed) `@onScopeDisposed`
60
60
  - [Inject Property](#inject-property)
61
61
  - [Inject Method](#inject-method)
62
62
  - [Mock](#mock)
@@ -107,7 +107,7 @@ bundlers tree-shake unused exports.
107
107
 
108
108
  > [!NOTE]
109
109
  > The default `MetadataInjector` (and the `@inject` / `@onConstruct` /
110
- > `@onContainerDisposed` decorators) rely on `reflect-metadata`. It is declared as an
110
+ > `@onScopeDisposed` decorators) rely on `reflect-metadata`. It is declared as an
111
111
  > optional peer dependency — install it and import it once at your entrypoint.
112
112
  > `SimpleInjector` and `ProxyInjector` do not need it.
113
113
 
@@ -454,7 +454,7 @@ describe('Instances', function () {
454
454
  Sometimes you want to dispose a container or scope. For example, when a request, page, widget, or other local lifecycle ends.
455
455
 
456
456
  - container can be disposed
457
- - when container is disposed then it runs its `onContainerDisposed` hooks, unregisters its providers, removes its local instances, and detaches from its parent
457
+ - when container is disposed then it runs its `onScopeDisposed` hooks, unregisters its providers, removes its local instances, and detaches from its parent
458
458
 
459
459
  > [!IMPORTANT]
460
460
  > Dispose is local to the container being disposed. Child scopes are not disposed automatically; dispose them explicitly when their own lifecycle ends.
@@ -473,7 +473,7 @@ import { bindTo, Container, ContainerDisposedError, register, Registration as R,
473
473
  * - Cache entries cleared
474
474
  *
475
475
  * The container.dispose() method:
476
- * 1. Executes all onContainerDisposed hooks
476
+ * 1. Executes all onScopeDisposed hooks
477
477
  * 2. Clears all instances and registrations
478
478
  * 3. Detaches from parent scope
479
479
  * 4. Prevents further resolution
@@ -2379,7 +2379,7 @@ describe('Decorator Pattern', () => {
2379
2379
 
2380
2380
  ### On resolve
2381
2381
 
2382
- Sometimes you don't want to change the dependency, only to react to it. Use the `onResolve(...)` pipe — it appends a `DependencyHook` that receives the resolved dependency and the resolving scope.
2382
+ Sometimes you don't want to change the dependency, only to react to it. Use the `onResolve(...)` pipe — it appends a `ProviderHook` that receives the resolved dependency and the resolving scope.
2383
2383
 
2384
2384
  - `provider(onResolve((instance, scope) => tracker.track(instance)))`
2385
2385
 
@@ -2828,16 +2828,78 @@ Decorators are applied bottom-up, so `authorize` runs first, then `validate` and
2828
2828
  `(...prev) => [...prev].reverse()` to reorder, or `() => [onlyThisOne]` to
2829
2829
  replace the accumulated hooks.
2830
2830
 
2831
- `@onConstruct`, `@onConstructAsync` and `@onContainerDisposed` keep their
2832
- variadic signature and compensate for the bottom-up application order, so
2833
- stacked decorators run in declaration order: `@onConstruct(h1) @onConstruct(h2)`
2834
- runs `h1` before `h2`.
2831
+ `@onConstruct` and `@onScopeDisposed` keep their variadic signature and
2832
+ compensate for the bottom-up application order, so stacked decorators run in
2833
+ declaration order: `@onConstruct(h1) @onConstruct(h2)` runs `h1` before `h2`.
2834
+
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.
2852
+
2853
+ ### Hook domains
2854
+
2855
+ The decorators above declare hook *metadata* on a class. The imperative side —
2856
+ the callbacks the container machinery runs — is registered on whichever
2857
+ abstraction raises the event, one hook type per domain:
2858
+
2859
+ | Domain | Registered on | Type | Methods |
2860
+ | ------------ | ------------- | --------------- | -------------------------------------------------------- |
2861
+ | **Scope** | `IContainer` | `ScopeHook` | `onScopeCreated(...)`, `onScopeDisposed(...)` |
2862
+ | **Scope** | `IContainer` | `RegisteredHook`| `onRegistered(...)` |
2863
+ | **Injector** | `IInjector` | `InjectorHook` | `onConstructed(...)` |
2864
+ | **Provider** | `IProvider` | `ProviderHook` | `onResolved(...)`, or the [`onResolve`](#on-resolve) pipe |
2865
+
2866
+ A container never hands its injector out — the injector is configured first and
2867
+ passed in at construction:
2868
+
2869
+ ```typescript
2870
+ // Construction is the injector's event, not a scope's
2871
+ const injector = new MetadataInjector().onConstructed((instance, scope) => metrics.built(instance, scope));
2872
+
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
+ ```
2878
+
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(...)`:
2887
+
2888
+ ```typescript
2889
+ const injector = new MetadataInjector().useModule(new OnConstructModule());
2890
+ const container = new Container({ injector });
2891
+ ```
2892
+
2893
+ `OnDisposeModule` is a container module hooking `onScopeDisposed`, and
2894
+ `OnResolvedModule` is a container module reaching every provider through
2895
+ `onRegistered`.
2835
2896
 
2836
2897
  ### OnConstruct
2837
2898
 
2838
2899
  ```typescript
2839
2900
  import 'reflect-metadata';
2840
2901
  import {
2902
+ MetadataInjector,
2841
2903
  OnConstructModule,
2842
2904
  Container,
2843
2905
  type ExecutionContext,
@@ -2852,6 +2914,10 @@ const execute: HookFn = (ctx) => {
2852
2914
  ctx.invokeMethod({ args: ctx.resolveArgs() });
2853
2915
  };
2854
2916
 
2917
+ const executeAsync: HookFn = async (ctx) => {
2918
+ await ctx.invokeMethod({ args: ctx.resolveArgs() });
2919
+ };
2920
+
2855
2921
  describe('onConstruct', function () {
2856
2922
  it('should run initialization method after dependencies are resolved', function () {
2857
2923
  class DatabaseConnection {
@@ -2865,9 +2931,9 @@ describe('onConstruct', function () {
2865
2931
  }
2866
2932
  }
2867
2933
 
2868
- const container = new Container()
2869
- .useModule(new OnConstructModule())
2870
- .addRegistration(R.fromValue('postgres://localhost:5432').bindTo('ConnectionString'));
2934
+ const container = new Container({
2935
+ injector: new MetadataInjector().useModule(new OnConstructModule()),
2936
+ }).addRegistration(R.fromValue('postgres://localhost:5432').bindTo('ConnectionString'));
2871
2937
 
2872
2938
  const db = container.resolve(DatabaseConnection);
2873
2939
 
@@ -2886,11 +2952,13 @@ describe('onConstruct', function () {
2886
2952
  }
2887
2953
 
2888
2954
  let captured: { ex: unknown; context: ExecutionContext } | undefined;
2889
- const container = new Container().useModule(
2890
- new OnConstructModule((ex, context) => {
2891
- captured = { ex, context };
2892
- }),
2893
- );
2955
+ const container = new Container({
2956
+ injector: new MetadataInjector().useModule(
2957
+ new OnConstructModule((ex, context) => {
2958
+ captured = { ex, context };
2959
+ }),
2960
+ ),
2961
+ });
2894
2962
 
2895
2963
  expect(() => container.resolve(BrokenService)).not.toThrow();
2896
2964
  expect(captured?.ex).toBe(failure);
@@ -2907,7 +2975,7 @@ describe('onConstruct', function () {
2907
2975
  init() {}
2908
2976
  }
2909
2977
 
2910
- const container = new Container().useModule(new OnConstructModule());
2978
+ const container = new Container({ injector: new MetadataInjector().useModule(new OnConstructModule()) });
2911
2979
 
2912
2980
  expect(() => container.resolve(BrokenService)).toThrow(failure);
2913
2981
  });
@@ -2921,44 +2989,20 @@ describe('onConstruct', function () {
2921
2989
  }
2922
2990
 
2923
2991
  let scope: IContainer | undefined;
2924
- const container = new Container().useModule(
2925
- new OnConstructModule((_ex, context) => {
2926
- scope = context.scope;
2927
- }),
2928
- );
2992
+ const container = new Container({
2993
+ injector: new MetadataInjector().useModule(
2994
+ new OnConstructModule((_ex, context) => {
2995
+ scope = context.scope;
2996
+ }),
2997
+ ),
2998
+ });
2929
2999
  const child = container.createScope();
2930
3000
 
2931
3001
  child.resolve(BrokenService);
2932
3002
 
2933
3003
  expect(scope).toBe(child);
2934
3004
  });
2935
- });
2936
-
2937
- ```
2938
-
2939
- ### OnConstructAsync
2940
-
2941
- `@onConstructAsync` runs promise-returning initialization. Resolution stays
2942
- synchronous: `OnConstructAsyncModule` starts the hooks when the instance
2943
- is created and they settle afterwards, so `resolve` returns before they finish.
2944
-
2945
- ```typescript
2946
- import 'reflect-metadata';
2947
- import {
2948
- OnConstructAsyncModule,
2949
- Container,
2950
- type ExecutionContext,
2951
- type HookFn,
2952
- inject,
2953
- onConstructAsync,
2954
- Registration as R,
2955
- } from 'ts-ioc-container';
2956
-
2957
- const execute: HookFn = async (ctx) => {
2958
- await ctx.invokeMethod({ args: ctx.resolveArgs() });
2959
- };
2960
3005
 
2961
- describe('onConstructAsync', function () {
2962
3006
  it('should run an async initialization method after the instance is created', async function () {
2963
3007
  class DatabaseConnection {
2964
3008
  isConnected = false;
@@ -2973,7 +3017,7 @@ describe('onConstructAsync', function () {
2973
3017
  });
2974
3018
  }
2975
3019
 
2976
- @onConstructAsync(execute)
3020
+ @onConstruct(executeAsync)
2977
3021
  async connect(@inject('ConnectionString') connectionString: string) {
2978
3022
  await Promise.resolve();
2979
3023
  this.connectionString = connectionString;
@@ -2982,9 +3026,9 @@ describe('onConstructAsync', function () {
2982
3026
  }
2983
3027
  }
2984
3028
 
2985
- const container = new Container()
2986
- .useModule(new OnConstructAsyncModule())
2987
- .addRegistration(R.fromValue('postgres://localhost:5432').bindTo('ConnectionString'));
3029
+ const container = new Container({
3030
+ injector: new MetadataInjector().useModule(new OnConstructModule()),
3031
+ }).addRegistration(R.fromValue('postgres://localhost:5432').bindTo('ConnectionString'));
2988
3032
 
2989
3033
  const db = container.resolve(DatabaseConnection);
2990
3034
 
@@ -3001,16 +3045,18 @@ describe('onConstructAsync', function () {
3001
3045
  const failure = new Error('boom');
3002
3046
 
3003
3047
  class BrokenService {
3004
- @onConstructAsync(() => Promise.reject(failure))
3048
+ @onConstruct(() => Promise.reject(failure))
3005
3049
  init() {}
3006
3050
  }
3007
3051
 
3008
3052
  let captured: { ex: unknown; context: ExecutionContext } | undefined;
3009
- const container = new Container().useModule(
3010
- new OnConstructAsyncModule((ex, context) => {
3011
- captured = { ex, context };
3012
- }),
3013
- );
3053
+ const container = new Container({
3054
+ injector: new MetadataInjector().useModule(
3055
+ new OnConstructModule((ex, context) => {
3056
+ captured = { ex, context };
3057
+ }),
3058
+ ),
3059
+ });
3014
3060
 
3015
3061
  const child = container.createScope();
3016
3062
  child.resolve(BrokenService);
@@ -3024,7 +3070,7 @@ describe('onConstructAsync', function () {
3024
3070
 
3025
3071
  ```
3026
3072
 
3027
- ### OnContainerDisposed
3073
+ ### OnScopeDisposed
3028
3074
 
3029
3075
  ```typescript
3030
3076
  import 'reflect-metadata';
@@ -3034,7 +3080,7 @@ import {
3034
3080
  Container,
3035
3081
  type HookFn,
3036
3082
  inject,
3037
- onContainerDisposed,
3083
+ onScopeDisposed,
3038
3084
  register,
3039
3085
  Registration as R,
3040
3086
  singleton,
@@ -3063,13 +3109,13 @@ class Logger {
3063
3109
  this.messages.push(message);
3064
3110
  }
3065
3111
 
3066
- @onContainerDisposed(execute)
3112
+ @onScopeDisposed(execute)
3067
3113
  save() {
3068
3114
  this.logsRepo.saveLogs(this.messages);
3069
3115
  }
3070
3116
  }
3071
3117
 
3072
- describe('onContainerDisposed', function () {
3118
+ describe('onScopeDisposed', function () {
3073
3119
  it('should invoke hooks on all instances when container is disposed', function () {
3074
3120
  const container = new Container()
3075
3121
  .useModule(new OnDisposeModule())
@@ -19,10 +19,9 @@ class Container {
19
19
  providers = new Map();
20
20
  aliases = new AliasMap_1.AliasMap();
21
21
  injector;
22
- onConstructHookList = [];
23
- onDisposeHookList = [];
24
22
  onScopeCreatedHookList = [];
25
- onProviderRegisteredHookList = [];
23
+ onScopeDisposedHookList = [];
24
+ onRegisteredHookList = [];
26
25
  constructor(options = {}) {
27
26
  this.injector = options.injector ?? new MetadataInjector_1.MetadataInjector();
28
27
  this.parent = options.parent ?? new EmptyContainer_1.EmptyContainer();
@@ -32,8 +31,8 @@ class Container {
32
31
  this.validateContainer();
33
32
  this.providers.set(key, provider);
34
33
  this.aliases.setAliasesByKey(key, aliases);
35
- for (const onProviderRegistered of this.onProviderRegisteredHookList) {
36
- onProviderRegistered(provider, key, this);
34
+ for (const onRegistered of this.onRegisteredHookList) {
35
+ onRegistered(provider, key, this);
37
36
  }
38
37
  return this;
39
38
  }
@@ -78,10 +77,9 @@ class Container {
78
77
  createScope({ tags } = {}) {
79
78
  this.validateContainer();
80
79
  const scope = new Container({ injector: this.injector, parent: this, tags })
81
- .onConstruct(...this.onConstructHookList)
82
- .onInstanceDisposed(...this.onDisposeHookList)
83
80
  .onScopeCreated(...this.onScopeCreatedHookList)
84
- .onProviderRegistered(...this.onProviderRegisteredHookList);
81
+ .onScopeDisposed(...this.onScopeDisposedHookList)
82
+ .onRegistered(...this.onRegisteredHookList);
85
83
  for (const registration of this.getRegistrations()) {
86
84
  registration.applyTo(scope);
87
85
  }
@@ -103,8 +101,8 @@ class Container {
103
101
  dispose() {
104
102
  this.validateContainer();
105
103
  this.isDisposed = true;
106
- for (const hook of this.onDisposeHookList) {
107
- hook(this);
104
+ for (const onScopeDisposed of this.onScopeDisposedHookList) {
105
+ onScopeDisposed(this);
108
106
  }
109
107
  this.parent.removeScope(this);
110
108
  this.parent = new EmptyContainer_1.EmptyContainer();
@@ -115,10 +113,9 @@ class Container {
115
113
  this.aliases.destroy();
116
114
  this.instances.clear();
117
115
  this.registrations = [];
118
- this.onConstructHookList.length = 0;
119
- this.onDisposeHookList.length = 0;
120
116
  this.onScopeCreatedHookList.length = 0;
121
- this.onProviderRegisteredHookList.length = 0;
117
+ this.onScopeDisposedHookList.length = 0;
118
+ this.onRegisteredHookList.length = 0;
122
119
  }
123
120
  addRegistration(registration) {
124
121
  this.registrations.push(registration);
@@ -131,27 +128,20 @@ class Container {
131
128
  hasRegistration(key) {
132
129
  return this.registrations.some((r) => r.getKeyOrFail() === key) || this.parent.hasRegistration(key);
133
130
  }
134
- onConstruct(...hooks) {
135
- this.onConstructHookList.push(...hooks);
136
- return this;
137
- }
138
- onInstanceDisposed(...hooks) {
139
- this.onDisposeHookList.push(...hooks);
140
- return this;
141
- }
142
131
  onScopeCreated(...hooks) {
143
132
  this.onScopeCreatedHookList.push(...hooks);
144
133
  return this;
145
134
  }
146
- onProviderRegistered(...hooks) {
147
- this.onProviderRegisteredHookList.push(...hooks);
135
+ onScopeDisposed(...hooks) {
136
+ this.onScopeDisposedHookList.push(...hooks);
137
+ return this;
138
+ }
139
+ onRegistered(...hooks) {
140
+ this.onRegisteredHookList.push(...hooks);
148
141
  return this;
149
142
  }
150
143
  addInstance(instance) {
151
144
  this.instances.add(instance);
152
- for (const onConstruct of this.onConstructHookList) {
153
- onConstruct(instance, this);
154
- }
155
145
  }
156
146
  getScopes() {
157
147
  return [...this.scopes];
@@ -60,16 +60,13 @@ class EmptyContainer {
60
60
  resolveOneByAlias(alias, options) {
61
61
  throw new DependencyNotFoundError_1.DependencyNotFoundError(`Cannot find alias ${alias.toString()}`);
62
62
  }
63
- onInstanceDisposed(...hooks) {
64
- throw new MethodNotImplementedError_1.MethodNotImplementedError();
65
- }
66
- onConstruct(...hooks) {
63
+ onScopeCreated(...hooks) {
67
64
  throw new MethodNotImplementedError_1.MethodNotImplementedError();
68
65
  }
69
- onScopeCreated(...hooks) {
66
+ onScopeDisposed(...hooks) {
70
67
  throw new MethodNotImplementedError_1.MethodNotImplementedError();
71
68
  }
72
- onProviderRegistered(...hooks) {
69
+ onRegistered(...hooks) {
73
70
  throw new MethodNotImplementedError_1.MethodNotImplementedError();
74
71
  }
75
72
  }
@@ -1,10 +1,16 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.HooksRunner = void 0;
3
+ exports.runHooks = exports.HooksRunner = void 0;
4
4
  const HookContext_1 = require("./HookContext");
5
5
  const hook_1 = require("./hook");
6
- const UnexpectedHookResultError_1 = require("../errors/UnexpectedHookResultError");
7
- const promise_1 = require("../utils/promise");
6
+ const runFrom = (fns, index, context) => {
7
+ for (let i = index; i < fns.length; i++) {
8
+ const result = fns[i](context);
9
+ if (result instanceof Promise) {
10
+ return result.then(() => runFrom(fns, i + 1, context));
11
+ }
12
+ }
13
+ };
8
14
  class HooksRunner {
9
15
  key;
10
16
  constructor(key) {
@@ -15,28 +21,30 @@ class HooksRunner {
15
21
  }
16
22
  execute(target, { scope, createContext = HookContext_1.createHookContext, mapContext = (context) => context, predicate = () => true, }) {
17
23
  const hooks = Array.from((0, hook_1.getHooks)(target, this.key).entries()).filter(([methodName]) => predicate(methodName));
18
- const runMethodHooks = (methodName, executions) => {
19
- const context = mapContext(createContext(target, scope, methodName));
20
- for (const execute of executions) {
21
- const result = execute(context);
22
- if (result instanceof Promise) {
23
- throw new UnexpectedHookResultError_1.UnexpectedHookResultError(`Hook ${methodName} returned a promise, use runHooksAsync instead`);
24
- }
25
- }
26
- };
24
+ const pending = [];
27
25
  for (const [methodName, executions] of hooks) {
28
- runMethodHooks(methodName, executions.map(hook_1.toHookFn));
29
- }
30
- }
31
- async executeAsync(target, { scope, createContext = HookContext_1.createHookContext, mapContext = (context) => context, predicate = () => true, }) {
32
- const hooks = Array.from((0, hook_1.getHooks)(target, this.key).entries()).filter(([methodName]) => predicate(methodName));
33
- const runMethodHooks = async (methodName, executions) => {
34
26
  const context = mapContext(createContext(target, scope, methodName));
35
- for (const execute of executions) {
36
- await (0, promise_1.promisify)(execute(context));
27
+ const result = runFrom(executions.map(hook_1.toHookFn), 0, context);
28
+ if (result) {
29
+ pending.push(result);
37
30
  }
38
- };
39
- return Promise.all(hooks.map(([methodName, executions]) => runMethodHooks(methodName, executions.map(hook_1.toHookFn))));
31
+ }
32
+ return pending.length > 0 ? Promise.all(pending).then(() => undefined) : undefined;
40
33
  }
41
34
  }
42
35
  exports.HooksRunner = HooksRunner;
36
+ const runHooks = (runner, target, scope, onException) => {
37
+ const report = (ex) => {
38
+ if (!onException) {
39
+ throw ex;
40
+ }
41
+ onException(ex, { scope });
42
+ };
43
+ try {
44
+ runner.execute(target, { scope })?.catch(report);
45
+ }
46
+ catch (ex) {
47
+ report(ex);
48
+ }
49
+ };
50
+ exports.runHooks = runHooks;
@@ -11,17 +11,9 @@ class OnConstructModule {
11
11
  constructor(onException) {
12
12
  this.onException = onException;
13
13
  }
14
- applyTo(container) {
15
- container.onConstruct((instance, scope) => {
16
- try {
17
- exports.onConstructHooksRunner.execute(instance, { scope });
18
- }
19
- catch (ex) {
20
- if (!this.onException) {
21
- throw ex;
22
- }
23
- this.onException(ex, { scope });
24
- }
14
+ applyTo(injector) {
15
+ injector.onConstructed((instance, scope) => {
16
+ (0, HooksRunner_1.runHooks)(exports.onConstructHooksRunner, instance, scope, this.onException);
25
17
  });
26
18
  }
27
19
  }
@@ -7,14 +7,18 @@ const resolveHooks_1 = require("./resolveHooks");
7
7
  exports.onResolvedHooksRunner = new HooksRunner_1.HooksRunner('onResolved');
8
8
  exports.onResolved = (0, resolveHooks_1.resolvedHook)('onResolved');
9
9
  exports.onceResolved = (0, resolveHooks_1.onceResolvedHook)('onResolved');
10
- const runHooks = (0, resolveHooks_1.forEachResolvedObject)((0, resolveHooks_1.executeHooks)(exports.onResolvedHooksRunner));
10
+ const runHooks = (onException) => (0, resolveHooks_1.forEachResolvedObject)((0, resolveHooks_1.executeHooks)(exports.onResolvedHooksRunner, onException));
11
11
  class OnResolvedModule {
12
+ runHooks;
13
+ constructor(onException) {
14
+ this.runHooks = runHooks(onException);
15
+ }
12
16
  applyTo(container) {
13
- container.onProviderRegistered((provider) => {
14
- provider.onResolve(runHooks);
17
+ container.onRegistered((provider) => {
18
+ provider.onResolved(this.runHooks);
15
19
  });
16
20
  }
17
21
  }
18
22
  exports.OnResolvedModule = OnResolvedModule;
19
- const resolved = () => (0, IRegistration_1.registerPipe)((p) => p.onResolve(runHooks));
23
+ const resolved = (onException) => (0, IRegistration_1.registerPipe)((p) => p.onResolved(runHooks(onException)));
20
24
  exports.resolved = resolved;
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OnDisposeModule = exports.onScopeDisposed = exports.onScopeDisposedHooksRunner = void 0;
4
+ const hook_1 = require("./hook");
5
+ const HooksRunner_1 = require("./HooksRunner");
6
+ exports.onScopeDisposedHooksRunner = new HooksRunner_1.HooksRunner('onScopeDisposed');
7
+ const onScopeDisposed = (...fns) => (0, hook_1.hook)('onScopeDisposed', (0, hook_1.prependHooks)(...fns));
8
+ exports.onScopeDisposed = onScopeDisposed;
9
+ class OnDisposeModule {
10
+ onException;
11
+ constructor(onException) {
12
+ this.onException = onException;
13
+ }
14
+ applyTo(container) {
15
+ container.onScopeDisposed((scope) => {
16
+ for (const instance of scope.getInstances()) {
17
+ (0, HooksRunner_1.runHooks)(exports.onScopeDisposedHooksRunner, instance, scope, this.onException);
18
+ }
19
+ });
20
+ }
21
+ }
22
+ exports.OnDisposeModule = OnDisposeModule;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.executeHooksAsync = exports.executeHooks = exports.forEachResolvedObject = exports.onceResolvedHook = exports.resolvedHook = exports.invokeMethod = void 0;
3
+ exports.executeHooks = exports.forEachResolvedObject = exports.onceResolvedHook = exports.resolvedHook = exports.invokeMethod = void 0;
4
+ const HooksRunner_1 = require("./HooksRunner");
4
5
  const hook_1 = require("./hook");
5
6
  const basic_1 = require("../utils/basic");
6
7
  const invokeMethod = (context) => {
@@ -28,17 +29,5 @@ const forEachResolvedObject = (run) => (dependency, scope) => {
28
29
  }
29
30
  };
30
31
  exports.forEachResolvedObject = forEachResolvedObject;
31
- const executeHooks = (runner) => (dependency, scope) => runner.execute(dependency, { scope });
32
+ const executeHooks = (runner, onException) => (dependency, scope) => (0, HooksRunner_1.runHooks)(runner, dependency, scope, onException);
32
33
  exports.executeHooks = executeHooks;
33
- const executeHooksAsync = (runner, onException) => (dependency, scope) => {
34
- if (!runner.hasHooks(dependency)) {
35
- return;
36
- }
37
- runner.executeAsync(dependency, { scope }).catch((ex) => {
38
- if (!onException) {
39
- throw ex;
40
- }
41
- onException(ex, { scope });
42
- });
43
- };
44
- exports.executeHooksAsync = executeHooksAsync;