ts-ioc-container 67.0.0 → 68.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 +237 -92
- package/cjm/hooks/HookCollector.js +34 -0
- package/cjm/hooks/hook.js +3 -11
- package/cjm/index.js +10 -21
- package/cjm/utils/getConstructorChain.js +13 -0
- package/cjm/utils/memoize.js +18 -0
- package/esm/hooks/HookCollector.js +29 -0
- package/esm/hooks/hook.js +1 -9
- package/esm/index.js +3 -7
- package/esm/utils/getConstructorChain.js +9 -0
- package/esm/utils/memoize.js +14 -0
- package/package.json +1 -1
- package/typings/hooks/HookCollector.d.ts +28 -0
- package/typings/index.d.ts +3 -7
- package/typings/utils/getConstructorChain.d.ts +1 -0
- package/typings/utils/memoize.d.ts +1 -0
- package/cjm/hooks/HookExecutionStrategy.js +0 -48
- package/cjm/hooks/ParallelAsync.js +0 -11
- package/cjm/hooks/SequentialAsync.js +0 -11
- package/cjm/hooks/SequentialSync.js +0 -12
- package/cjm/hooks/onConstruct.js +0 -18
- package/cjm/hooks/onResolved.js +0 -27
- package/cjm/hooks/onScopeDisposed.js +0 -20
- package/esm/hooks/HookExecutionStrategy.js +0 -44
- package/esm/hooks/ParallelAsync.js +0 -7
- package/esm/hooks/SequentialAsync.js +0 -7
- package/esm/hooks/SequentialSync.js +0 -8
- package/esm/hooks/onConstruct.js +0 -13
- package/esm/hooks/onResolved.js +0 -21
- package/esm/hooks/onScopeDisposed.js +0 -15
- package/typings/hooks/HookExecutionStrategy.d.ts +0 -33
- package/typings/hooks/ParallelAsync.d.ts +0 -4
- package/typings/hooks/SequentialAsync.d.ts +0 -4
- package/typings/hooks/SequentialSync.d.ts +0 -4
- package/typings/hooks/onConstruct.d.ts +0 -9
- package/typings/hooks/onResolved.d.ts +0 -10
- package/typings/hooks/onScopeDisposed.d.ts +0 -9
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`,
|
|
21
|
+
- decorator support with `@register`, `@inject`, and `@hook` — lifecycle hook keys are yours to name
|
|
22
22
|
- can [inject properties](#inject-property)
|
|
23
23
|
- can inject [lazy dependencies](#lazy)
|
|
24
24
|
- composable provider and registration pipelines
|
|
@@ -55,8 +55,8 @@ provider pipelines, aliases, and custom injector strategies.
|
|
|
55
55
|
- [Module](#module)
|
|
56
56
|
- [Hook](#hook) `@hook`
|
|
57
57
|
- [Hook domains](#hook-domains) `ScopeHook` `InjectorHook` `ProviderHook`
|
|
58
|
-
- [
|
|
59
|
-
- [
|
|
58
|
+
- [Construct hooks](#construct-hooks) `onConstructed`
|
|
59
|
+
- [Scope disposal hooks](#scope-disposal-hooks) `scopeDisposed`
|
|
60
60
|
- [Inject Property](#inject-property)
|
|
61
61
|
- [Inject Method](#inject-method)
|
|
62
62
|
- [Mock](#mock)
|
|
@@ -106,8 +106,8 @@ bundlers tree-shake unused exports.
|
|
|
106
106
|
| Bun | ✅ | CJS + ESM | Runs the native ESM/CJS builds directly. |
|
|
107
107
|
|
|
108
108
|
> [!NOTE]
|
|
109
|
-
> The default `MetadataInjector` (and the `@inject` / `@
|
|
110
|
-
>
|
|
109
|
+
> The default `MetadataInjector` (and the `@inject` / `@hook` decorators) rely
|
|
110
|
+
> 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
|
|
|
@@ -2385,7 +2385,7 @@ Sometimes you don't want to change the dependency, only to react to it. Use the
|
|
|
2385
2385
|
|
|
2386
2386
|
Hooks run after the whole `decorate(...)` chain, so they always observe the fully decorated dependency, and their return value is ignored — `onResolve` can never swap the dependency out. They fire per resolution, which means a `singleton()` provider runs them only on the resolve that fills the cache.
|
|
2387
2387
|
|
|
2388
|
-
This — or
|
|
2388
|
+
This — or a hook key collected over the same provider event — is the recommended way to react to a dependency; see [Construct hooks](#construct-hooks) for why construction is the narrower event.
|
|
2389
2389
|
|
|
2390
2390
|
```typescript
|
|
2391
2391
|
import 'reflect-metadata';
|
|
@@ -2811,10 +2811,18 @@ describe('Container Modules', function () {
|
|
|
2811
2811
|
|
|
2812
2812
|
Sometimes you need to invoke methods after construct or dispose of class. This is what hooks are for.
|
|
2813
2813
|
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2814
|
+
`@hook(key, hook)` is the only hook decorator the library ships, and the key is
|
|
2815
|
+
yours: there is no `@onConstruct` or `@onScopeDisposed` in the package, because
|
|
2816
|
+
each is one line of your own code
|
|
2817
|
+
([ADR 0017](../../adr/0017-no-predefined-hook-keys.md)).
|
|
2818
|
+
|
|
2819
|
+
```typescript
|
|
2820
|
+
// yours, named in your vocabulary
|
|
2821
|
+
const onScopeDisposed = (fn: HookType) => hook('onScopeDisposed', fn);
|
|
2822
|
+
```
|
|
2823
|
+
|
|
2824
|
+
A decorated member takes **one** hook. Several hooks are combined at the
|
|
2825
|
+
declaration site, by the combinator that says how they relate:
|
|
2818
2826
|
|
|
2819
2827
|
```typescript
|
|
2820
2828
|
class OrderService {
|
|
@@ -2842,42 +2850,115 @@ A member carries exactly one hook per key, so decorating the same member twice
|
|
|
2842
2850
|
under one key replaces the earlier hook rather than adding to it — decorators
|
|
2843
2851
|
are applied bottom-up, so the topmost one is the one that stays.
|
|
2844
2852
|
|
|
2845
|
-
Every hook may be sync or async — one decorator
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
(
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2853
|
+
Every hook may be sync or async — the one decorator takes both, so there is no
|
|
2854
|
+
separate async form to reach for. *Running* them is not the library's job at
|
|
2855
|
+
all, and neither is deciding when to collect them
|
|
2856
|
+
([ADR 0016](../../adr/0016-collect-hooks-let-the-caller-run-them.md),
|
|
2857
|
+
[ADR 0018](../../adr/0018-no-hook-modules.md)). The library answers one
|
|
2858
|
+
question — which hooks does this object declare, and against what context do
|
|
2859
|
+
they run — and the container's events are where you ask it.
|
|
2860
|
+
|
|
2861
|
+
A **`HookCollector`** is keyed to one of your hook keys and answers a single
|
|
2862
|
+
question: `getActions(target, { scope })` returns one **`HookAction`** per
|
|
2863
|
+
decorated member — the hook resolved to a function, bound to the
|
|
2864
|
+
`IHookContext` it runs against (which carries the member's own `methodName`). It performs nothing, and how the
|
|
2865
|
+
hooks *within* one member relate was already settled by the combinator at the
|
|
2866
|
+
declaration site ([ADR 0015](../../adr/0015-one-hook-per-member.md)). It reads a
|
|
2867
|
+
class's metadata once and reuses it, so collecting on a hot event is cheap.
|
|
2868
|
+
|
|
2869
|
+
A **runner** performs them. Order, awaiting and failure handling are decided
|
|
2870
|
+
there and nowhere else; `toTask` turns an action into the `Task` that
|
|
2871
|
+
`runInOrder` and `runAtOnce` take. The library exports no type for it — it
|
|
2872
|
+
never calls a runner, nor is it handed one — so name the shape yourself:
|
|
2859
2873
|
|
|
2860
2874
|
```typescript
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2875
|
+
// yours, like the hook keys
|
|
2876
|
+
type HookRunner = (actions: HookAction[], context: ExecutionContext) => void;
|
|
2877
|
+
|
|
2878
|
+
// members one after another, never awaited: sync hooks finish before this returns
|
|
2879
|
+
const immediate: HookRunner = (actions) => {
|
|
2880
|
+
for (const { hook, context } of actions) {
|
|
2881
|
+
hook(context);
|
|
2882
|
+
}
|
|
2883
|
+
};
|
|
2884
|
+
|
|
2885
|
+
// members one after another, awaiting any that goes async; both kinds of failure reported
|
|
2886
|
+
const inOrder =
|
|
2887
|
+
(onError: (scope: IContainer) => (error: unknown) => void): HookRunner =>
|
|
2888
|
+
(actions, { scope }) => {
|
|
2889
|
+
try {
|
|
2890
|
+
runInOrder(actions.map(toTask))?.catch(onError(scope));
|
|
2891
|
+
} catch (ex) {
|
|
2892
|
+
onError(scope)(ex);
|
|
2893
|
+
}
|
|
2894
|
+
};
|
|
2895
|
+
|
|
2896
|
+
// every member started at once
|
|
2897
|
+
const atOnce: HookRunner = (actions) => {
|
|
2898
|
+
runAtOnce(actions.map(toTask));
|
|
2899
|
+
};
|
|
2900
|
+
|
|
2864
2901
|
```
|
|
2865
2902
|
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
afterwards. Instances that must expose readiness should publish it themselves,
|
|
2870
|
-
for example by storing the pending promise on the instance. The sync strategy
|
|
2871
|
-
never awaits — a hook that returns a promise under it is started but not
|
|
2872
|
-
observed.
|
|
2903
|
+
Then wire collecting to the event you want it on. There is no
|
|
2904
|
+
`OnConstructModule` in the package: an `IContainerModule` is one method, and
|
|
2905
|
+
these are the whole of what the modules this library used to ship contained.
|
|
2873
2906
|
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2907
|
+
```typescript
|
|
2908
|
+
const onConstructHooks = new HookCollector({ key: 'onConstruct' });
|
|
2909
|
+
const onScopeDisposedHooks = new HookCollector({ key: 'onScopeDisposed' });
|
|
2910
|
+
|
|
2911
|
+
// construction is the injector's event, and one injector backs the whole scope tree
|
|
2912
|
+
const constructModule = (run: HookRunner): IContainerModule => ({
|
|
2913
|
+
applyTo: (container) =>
|
|
2914
|
+
container.getInjector().onConstructed((instance, scope) => {
|
|
2915
|
+
run(onConstructHooks.getActions(instance, { scope }), { scope });
|
|
2916
|
+
}),
|
|
2917
|
+
});
|
|
2918
|
+
|
|
2919
|
+
// disposal is a scope event; collecting every instance into one list lets the
|
|
2920
|
+
// runner order the instances as well as the members
|
|
2921
|
+
const disposeModule = (run: HookRunner): IContainerModule => ({
|
|
2922
|
+
applyTo: (container) =>
|
|
2923
|
+
container.scopeDisposed.subscribe((scope) => {
|
|
2924
|
+
run(
|
|
2925
|
+
scope.getInstances().flatMap((instance) => onScopeDisposedHooks.getActions(instance, { scope })),
|
|
2926
|
+
{ scope },
|
|
2927
|
+
);
|
|
2928
|
+
}),
|
|
2929
|
+
});
|
|
2930
|
+
|
|
2931
|
+
const container = new Container().useModule(constructModule(inOrder(report))).useModule(disposeModule(atOnce));
|
|
2932
|
+
```
|
|
2877
2933
|
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2934
|
+
For resolve hooks, reach the providers through `registered.subscribe(...)` and
|
|
2935
|
+
`provider.onResolved(...)`, or pipe a single registration with
|
|
2936
|
+
[`onResolve`](#on-resolve). Narrowing is the collector's:
|
|
2937
|
+
`new HookCollector({ key: 'onConstruct', predicate })`.
|
|
2938
|
+
|
|
2939
|
+
Resolution and disposal stay synchronous unless the runner makes them
|
|
2940
|
+
otherwise: `runInOrder` and `runAtOnce` stay synchronous until a hook returns a
|
|
2941
|
+
promise, so sync hooks finish before `resolve` (or `dispose`) returns and async
|
|
2942
|
+
ones are started there and settle afterwards. Instances that must expose
|
|
2943
|
+
readiness should publish it themselves, for example by storing the pending
|
|
2944
|
+
promise on the instance.
|
|
2945
|
+
|
|
2946
|
+
Failures belong to the runner too — nothing in the library catches what a hook
|
|
2947
|
+
throws or rejects with. A runner which neither guards nor awaits lets a sync
|
|
2948
|
+
throw propagate out of `resolve` / `dispose` and drops an async rejection.
|
|
2949
|
+
|
|
2950
|
+
A custom key is collected and run the same way, with no module in between:
|
|
2951
|
+
|
|
2952
|
+
```typescript
|
|
2953
|
+
const workflow = new HookCollector({ key: 'workflow' });
|
|
2954
|
+
|
|
2955
|
+
container.getInjector().onConstructed((instance, scope) => {
|
|
2956
|
+
runInOrder(workflow.getActions(instance, { scope }).map(toTask));
|
|
2957
|
+
});
|
|
2958
|
+
```
|
|
2959
|
+
|
|
2960
|
+
`predicate`, `createExecutionContext` and `mapExecutionContext` can be set on
|
|
2961
|
+
the collector or overridden per `getActions` call.
|
|
2881
2962
|
|
|
2882
2963
|
### Hook domains
|
|
2883
2964
|
|
|
@@ -2921,22 +3002,22 @@ and `registered` (`ITypedEvent<[IProvider, DependencyKey, IContainer]>`);
|
|
|
2921
3002
|
`TypedEvent` itself is exported for your own events: `subscribe` / `unsubscribe`
|
|
2922
3003
|
/ `emit` / `dispose`, with `ITypedEvent` as the subscriber-only view to hand out.
|
|
2923
3004
|
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
and `OnResolvedModule` reaches every provider through `registered`.
|
|
3005
|
+
These four events are the whole surface hooks are wired to; the library ships no
|
|
3006
|
+
module over them ([ADR 0018](../../adr/0018-no-hook-modules.md)).
|
|
2927
3007
|
|
|
2928
|
-
###
|
|
3008
|
+
### Construct hooks
|
|
2929
3009
|
|
|
2930
|
-
> **Prefer
|
|
2931
|
-
>
|
|
2932
|
-
>
|
|
2933
|
-
>
|
|
3010
|
+
> **Prefer collecting on resolve — through `registered` / `onResolved`, or the
|
|
3011
|
+
> [`onResolve`](#on-resolve) pipe — over collecting on construction.**
|
|
3012
|
+
> Construction is the *injector's* event, so it fires only for dependencies the
|
|
3013
|
+
> injector builds: a `fromValue` constant or a factory registration never
|
|
3014
|
+
> triggers it. It also observes the instance before
|
|
2934
3015
|
> the provider's `decorate(...)` chain wraps it, so a hook sees the bare
|
|
2935
|
-
> instance rather than what the caller receives.
|
|
3016
|
+
> instance rather than what the caller receives. A resolve hook runs on every
|
|
2936
3017
|
> dependency leaving a provider, after the whole decorate chain — the same
|
|
2937
3018
|
> ordering, awaiting and error handling, on what the caller actually gets.
|
|
2938
|
-
> Reach for
|
|
2939
|
-
> specifically.
|
|
3019
|
+
> Reach for construct hooks only when you mean "this class was just
|
|
3020
|
+
> constructed" specifically.
|
|
2940
3021
|
|
|
2941
3022
|
```typescript
|
|
2942
3023
|
import 'reflect-metadata';
|
|
@@ -2944,12 +3025,16 @@ import {
|
|
|
2944
3025
|
Container,
|
|
2945
3026
|
type HookFn,
|
|
2946
3027
|
type IContainer,
|
|
3028
|
+
hook,
|
|
3029
|
+
HookCollector,
|
|
3030
|
+
type HookType,
|
|
3031
|
+
type IContainerModule,
|
|
2947
3032
|
inject,
|
|
2948
|
-
onConstruct,
|
|
2949
|
-
OnConstructModule,
|
|
2950
3033
|
Registration as R,
|
|
2951
|
-
|
|
2952
|
-
|
|
3034
|
+
runInOrder,
|
|
3035
|
+
toTask,
|
|
3036
|
+
type ExecutionContext,
|
|
3037
|
+
type HookAction,
|
|
2953
3038
|
} from 'ts-ioc-container';
|
|
2954
3039
|
|
|
2955
3040
|
const execute: HookFn = (ctx) => {
|
|
@@ -2960,6 +3045,37 @@ const executeAsync: HookFn = async (ctx) => {
|
|
|
2960
3045
|
await ctx.invokeMethod({ args: ctx.resolveArgs() });
|
|
2961
3046
|
};
|
|
2962
3047
|
|
|
3048
|
+
// The library ships no construct decorator: the key, the decorator which writes
|
|
3049
|
+
// it and the collector which reads it are all ours.
|
|
3050
|
+
const onConstruct = (fn: HookType) => hook('onConstruct', fn);
|
|
3051
|
+
const onConstructHooks = new HookCollector({ key: 'onConstruct' });
|
|
3052
|
+
|
|
3053
|
+
// Running the collected hooks is ours too, and so is naming the shape that does
|
|
3054
|
+
// it: the library neither calls a runner nor is handed one.
|
|
3055
|
+
type HookRunner = (actions: HookAction[], context: ExecutionContext) => void;
|
|
3056
|
+
|
|
3057
|
+
// This runner keeps the
|
|
3058
|
+
// actions in declaration order, stays synchronous until one returns a promise,
|
|
3059
|
+
// and reports a throw and a rejection alike.
|
|
3060
|
+
const run =
|
|
3061
|
+
(onError: (scope: IContainer) => (ex: unknown) => void = () => () => {}): HookRunner =>
|
|
3062
|
+
(actions, { scope }) => {
|
|
3063
|
+
try {
|
|
3064
|
+
runInOrder(actions.map(toTask))?.catch(onError(scope));
|
|
3065
|
+
} catch (ex) {
|
|
3066
|
+
onError(scope)(ex);
|
|
3067
|
+
}
|
|
3068
|
+
};
|
|
3069
|
+
|
|
3070
|
+
// Construction is the injector's event, and hanging the collection off it is
|
|
3071
|
+
// ours: the library ships no module for that, and a module is just an `applyTo`.
|
|
3072
|
+
const onConstructModule = (run: HookRunner): IContainerModule => ({
|
|
3073
|
+
applyTo: (container) =>
|
|
3074
|
+
container.getInjector().onConstructed((instance, scope) => {
|
|
3075
|
+
run(onConstructHooks.getActions(instance, { scope }), { scope });
|
|
3076
|
+
}),
|
|
3077
|
+
});
|
|
3078
|
+
|
|
2963
3079
|
describe('onConstruct', function () {
|
|
2964
3080
|
it('should run initialization method after dependencies are resolved', function () {
|
|
2965
3081
|
class DatabaseConnection {
|
|
@@ -2973,9 +3089,8 @@ describe('onConstruct', function () {
|
|
|
2973
3089
|
}
|
|
2974
3090
|
}
|
|
2975
3091
|
|
|
2976
|
-
// The module takes a strategy for how the hooks run; the strategy is keyed to the hooks it runs.
|
|
2977
3092
|
const container = new Container()
|
|
2978
|
-
.useModule(
|
|
3093
|
+
.useModule(onConstructModule(run()))
|
|
2979
3094
|
.addRegistration(R.fromValue('postgres://localhost:5432').bindTo('ConnectionString'));
|
|
2980
3095
|
|
|
2981
3096
|
const db = container.resolve(DatabaseConnection);
|
|
@@ -2984,7 +3099,7 @@ describe('onConstruct', function () {
|
|
|
2984
3099
|
expect(db.connectionString).toBe('postgres://localhost:5432');
|
|
2985
3100
|
});
|
|
2986
3101
|
|
|
2987
|
-
it('should forward hook exceptions to the
|
|
3102
|
+
it('should forward hook exceptions to the runner’s error handler with the scope', function () {
|
|
2988
3103
|
const failure = new Error('boom');
|
|
2989
3104
|
|
|
2990
3105
|
class BrokenService {
|
|
@@ -2996,12 +3111,9 @@ describe('onConstruct', function () {
|
|
|
2996
3111
|
|
|
2997
3112
|
let captured: { ex: unknown; scope: IContainer } | undefined;
|
|
2998
3113
|
const container = new Container().useModule(
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
onError: (scope) => (ex) => {
|
|
3003
|
-
captured = { ex, scope };
|
|
3004
|
-
},
|
|
3114
|
+
onConstructModule(
|
|
3115
|
+
run((scope) => (ex) => {
|
|
3116
|
+
captured = { ex, scope };
|
|
3005
3117
|
}),
|
|
3006
3118
|
),
|
|
3007
3119
|
);
|
|
@@ -3011,7 +3123,7 @@ describe('onConstruct', function () {
|
|
|
3011
3123
|
expect(captured?.scope).toBe(container);
|
|
3012
3124
|
});
|
|
3013
3125
|
|
|
3014
|
-
it('should expose the resolving scope to the
|
|
3126
|
+
it('should expose the resolving scope to the runner’s error handler', function () {
|
|
3015
3127
|
class BrokenService {
|
|
3016
3128
|
@onConstruct(() => {
|
|
3017
3129
|
throw new Error('boom');
|
|
@@ -3021,12 +3133,9 @@ describe('onConstruct', function () {
|
|
|
3021
3133
|
|
|
3022
3134
|
let scope: IContainer | undefined;
|
|
3023
3135
|
const container = new Container().useModule(
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
onError: (s) => () => {
|
|
3028
|
-
scope = s;
|
|
3029
|
-
},
|
|
3136
|
+
onConstructModule(
|
|
3137
|
+
run((s) => () => {
|
|
3138
|
+
scope = s;
|
|
3030
3139
|
}),
|
|
3031
3140
|
),
|
|
3032
3141
|
);
|
|
@@ -3060,9 +3169,9 @@ describe('onConstruct', function () {
|
|
|
3060
3169
|
}
|
|
3061
3170
|
}
|
|
3062
3171
|
|
|
3063
|
-
//
|
|
3172
|
+
// The runner awaits the hooks; resolution itself still does not wait for them.
|
|
3064
3173
|
const container = new Container()
|
|
3065
|
-
.useModule(
|
|
3174
|
+
.useModule(onConstructModule(run()))
|
|
3066
3175
|
.addRegistration(R.fromValue('postgres://localhost:5432').bindTo('ConnectionString'));
|
|
3067
3176
|
|
|
3068
3177
|
const db = container.resolve(DatabaseConnection);
|
|
@@ -3076,7 +3185,7 @@ describe('onConstruct', function () {
|
|
|
3076
3185
|
expect(db.connectionString).toBe('postgres://localhost:5432');
|
|
3077
3186
|
});
|
|
3078
3187
|
|
|
3079
|
-
it('should forward rejected hooks to the
|
|
3188
|
+
it('should forward rejected hooks to the runner’s error handler with the scope', async function () {
|
|
3080
3189
|
const failure = new Error('boom');
|
|
3081
3190
|
|
|
3082
3191
|
class BrokenService {
|
|
@@ -3086,12 +3195,9 @@ describe('onConstruct', function () {
|
|
|
3086
3195
|
|
|
3087
3196
|
let captured: { ex: unknown; scope: IContainer } | undefined;
|
|
3088
3197
|
const container = new Container().useModule(
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
onError: (scope) => (ex) => {
|
|
3093
|
-
captured = { ex, scope };
|
|
3094
|
-
},
|
|
3198
|
+
onConstructModule(
|
|
3199
|
+
run((scope) => (ex) => {
|
|
3200
|
+
captured = { ex, scope };
|
|
3095
3201
|
}),
|
|
3096
3202
|
),
|
|
3097
3203
|
);
|
|
@@ -3108,27 +3214,60 @@ describe('onConstruct', function () {
|
|
|
3108
3214
|
|
|
3109
3215
|
```
|
|
3110
3216
|
|
|
3111
|
-
###
|
|
3217
|
+
### Scope disposal hooks
|
|
3112
3218
|
|
|
3113
3219
|
```typescript
|
|
3114
3220
|
import 'reflect-metadata';
|
|
3115
3221
|
import {
|
|
3116
|
-
OnDisposeModule,
|
|
3117
3222
|
bindTo,
|
|
3118
3223
|
Container,
|
|
3119
3224
|
type HookFn,
|
|
3225
|
+
hook,
|
|
3226
|
+
HookCollector,
|
|
3227
|
+
type HookType,
|
|
3120
3228
|
inject,
|
|
3121
|
-
onScopeDisposed,
|
|
3122
3229
|
register,
|
|
3123
3230
|
Registration as R,
|
|
3124
|
-
SequentialSync,
|
|
3125
3231
|
singleton,
|
|
3232
|
+
type ExecutionContext,
|
|
3233
|
+
type HookAction,
|
|
3234
|
+
type IContainerModule,
|
|
3126
3235
|
} from 'ts-ioc-container';
|
|
3127
3236
|
|
|
3128
3237
|
const execute: HookFn = (ctx) => {
|
|
3129
3238
|
ctx.invokeMethod({ args: ctx.resolveArgs() });
|
|
3130
3239
|
};
|
|
3131
3240
|
|
|
3241
|
+
// The library ships no dispose decorator: the key, the decorator which writes it
|
|
3242
|
+
// and the collector which reads it are all ours.
|
|
3243
|
+
const onScopeDisposed = (fn: HookType) => hook('onScopeDisposed', fn);
|
|
3244
|
+
const onScopeDisposedHooks = new HookCollector({ key: 'onScopeDisposed' });
|
|
3245
|
+
|
|
3246
|
+
// Naming the shape which performs collected actions is ours: the library
|
|
3247
|
+
// neither calls a runner nor is handed one.
|
|
3248
|
+
type HookRunner = (actions: HookAction[], context: ExecutionContext) => void;
|
|
3249
|
+
|
|
3250
|
+
// This runner performs the collected hooks in order and never awaits.
|
|
3251
|
+
const run: HookRunner = (actions) => {
|
|
3252
|
+
for (const { hook, context } of actions) {
|
|
3253
|
+
hook(context);
|
|
3254
|
+
}
|
|
3255
|
+
};
|
|
3256
|
+
|
|
3257
|
+
// Disposal is a scope event, and hanging the collection off it is ours: the
|
|
3258
|
+
// library ships no module for that, and a module is just an `applyTo`. Every
|
|
3259
|
+
// instance of the scope is collected into one list, so the runner orders the
|
|
3260
|
+
// instances as well as the members.
|
|
3261
|
+
const onScopeDisposedModule = (run: HookRunner): IContainerModule => ({
|
|
3262
|
+
applyTo: (container) =>
|
|
3263
|
+
container.scopeDisposed.subscribe((scope) => {
|
|
3264
|
+
run(
|
|
3265
|
+
scope.getInstances().flatMap((instance) => onScopeDisposedHooks.getActions(instance, { scope })),
|
|
3266
|
+
{ scope },
|
|
3267
|
+
);
|
|
3268
|
+
}),
|
|
3269
|
+
});
|
|
3270
|
+
|
|
3132
3271
|
@register(bindTo('logsRepo'), singleton())
|
|
3133
3272
|
class LogsRepo {
|
|
3134
3273
|
savedLogs: string[] = [];
|
|
@@ -3157,7 +3296,7 @@ class Logger {
|
|
|
3157
3296
|
describe('onScopeDisposed', function () {
|
|
3158
3297
|
it('should invoke hooks on all instances when container is disposed', function () {
|
|
3159
3298
|
const container = new Container()
|
|
3160
|
-
.useModule(
|
|
3299
|
+
.useModule(onScopeDisposedModule(run))
|
|
3161
3300
|
.addRegistration(R.fromClass(Logger))
|
|
3162
3301
|
.addRegistration(R.fromClass(LogsRepo));
|
|
3163
3302
|
|
|
@@ -3177,7 +3316,7 @@ describe('onScopeDisposed', function () {
|
|
|
3177
3316
|
|
|
3178
3317
|
```typescript
|
|
3179
3318
|
import 'reflect-metadata';
|
|
3180
|
-
import { Container, hook, injectProp, Registration, sequential,
|
|
3319
|
+
import { Container, hook, HookCollector, injectProp, Registration, sequential, toTask } from 'ts-ioc-container';
|
|
3181
3320
|
|
|
3182
3321
|
/**
|
|
3183
3322
|
* UI Components - Property Injection
|
|
@@ -3192,8 +3331,8 @@ import { Container, hook, injectProp, Registration, sequential, SequentialSync }
|
|
|
3192
3331
|
|
|
3193
3332
|
describe('inject property', () => {
|
|
3194
3333
|
it('should inject property', () => {
|
|
3195
|
-
//
|
|
3196
|
-
const
|
|
3334
|
+
// Collector for the 'onInit' lifecycle hook
|
|
3335
|
+
const onInit = new HookCollector({ key: 'onInit' });
|
|
3197
3336
|
|
|
3198
3337
|
class UserViewModel {
|
|
3199
3338
|
// Inject 'GreetingService' into 'greeting' property during 'onInit'
|
|
@@ -3210,15 +3349,18 @@ describe('inject property', () => {
|
|
|
3210
3349
|
// 1. Create instance (dependencies not yet injected)
|
|
3211
3350
|
const viewModel = container.resolve(UserViewModel);
|
|
3212
3351
|
|
|
3213
|
-
// 2.
|
|
3214
|
-
|
|
3352
|
+
// 2. Collect the lifecycle hooks and run them to inject properties
|
|
3353
|
+
onInit
|
|
3354
|
+
.getActions(viewModel, { scope: container })
|
|
3355
|
+
.map(toTask)
|
|
3356
|
+
.forEach((task) => task());
|
|
3215
3357
|
|
|
3216
3358
|
expect(viewModel.greetingService).toBe('Hello');
|
|
3217
3359
|
expect(viewModel.display()).toBe('Hello User');
|
|
3218
3360
|
});
|
|
3219
3361
|
|
|
3220
3362
|
it('should read the applied instance property via getProperty', () => {
|
|
3221
|
-
const
|
|
3363
|
+
const onInit = new HookCollector({ key: 'onInit' });
|
|
3222
3364
|
|
|
3223
3365
|
let injectedValue: unknown;
|
|
3224
3366
|
|
|
@@ -3235,7 +3377,10 @@ describe('inject property', () => {
|
|
|
3235
3377
|
const container = new Container().addRegistration(Registration.fromValue('Hello').bindToKey('GreetingService'));
|
|
3236
3378
|
|
|
3237
3379
|
const viewModel = container.resolve(UserViewModel);
|
|
3238
|
-
|
|
3380
|
+
onInit
|
|
3381
|
+
.getActions(viewModel, { scope: container })
|
|
3382
|
+
.map(toTask)
|
|
3383
|
+
.forEach((task) => task());
|
|
3239
3384
|
|
|
3240
3385
|
expect(injectedValue).toBe('Hello');
|
|
3241
3386
|
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.toTask = exports.HookCollector = void 0;
|
|
4
|
+
const target_1 = require("../metadata/target");
|
|
5
|
+
const memoize_1 = require("../utils/memoize");
|
|
6
|
+
const hook_1 = require("./hook");
|
|
7
|
+
const HookContext_1 = require("./HookContext");
|
|
8
|
+
class HookCollector {
|
|
9
|
+
key;
|
|
10
|
+
options;
|
|
11
|
+
hooksOf = (0, memoize_1.memoize)(hook_1.getHooks);
|
|
12
|
+
constructor({ key, createExecutionContext = HookContext_1.createHookExecutionContext, mapExecutionContext = (context) => context, predicate = () => true, }) {
|
|
13
|
+
this.key = key;
|
|
14
|
+
this.options = { createExecutionContext, mapExecutionContext, predicate };
|
|
15
|
+
}
|
|
16
|
+
hasHooks(target) {
|
|
17
|
+
return (0, hook_1.hasHooks)(target, this.key);
|
|
18
|
+
}
|
|
19
|
+
getActions(target, { scope, createExecutionContext = this.options.createExecutionContext, mapExecutionContext = this.options.mapExecutionContext, predicate = this.options.predicate, }) {
|
|
20
|
+
const actions = [];
|
|
21
|
+
for (const [methodName, fn] of this.hooksOf((0, target_1.resolveConstructor)(target), this.key)) {
|
|
22
|
+
if (predicate(methodName)) {
|
|
23
|
+
actions.push({
|
|
24
|
+
hook: (0, hook_1.toHookFn)(fn),
|
|
25
|
+
context: mapExecutionContext(createExecutionContext(target, scope, methodName)),
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return actions;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
exports.HookCollector = HookCollector;
|
|
33
|
+
const toTask = ({ hook, context }) => () => hook(context);
|
|
34
|
+
exports.toTask = toTask;
|
package/cjm/hooks/hook.js
CHANGED
|
@@ -5,23 +5,15 @@ exports.getHooks = getHooks;
|
|
|
5
5
|
exports.hasHooks = hasHooks;
|
|
6
6
|
const basic_1 = require("../utils/basic");
|
|
7
7
|
const target_1 = require("../metadata/target");
|
|
8
|
+
const getConstructorChain_1 = require("../utils/getConstructorChain");
|
|
8
9
|
const isHookClassConstructor = (execute) => {
|
|
9
10
|
return basic_1.Is.constructor(execute) && execute.prototype.execute;
|
|
10
11
|
};
|
|
11
12
|
const toHookFn = (execute) => isHookClassConstructor(execute) ? (context) => context.scope.resolve(execute).execute(context) : execute;
|
|
12
13
|
exports.toHookFn = toHookFn;
|
|
13
|
-
const getConstructorChain = (ctor) => {
|
|
14
|
-
const chain = [];
|
|
15
|
-
let current = ctor;
|
|
16
|
-
while (typeof current === 'function' && current !== Function.prototype) {
|
|
17
|
-
chain.push(current);
|
|
18
|
-
current = Object.getPrototypeOf(current);
|
|
19
|
-
}
|
|
20
|
-
return chain;
|
|
21
|
-
};
|
|
22
14
|
function getHooks(target, key) {
|
|
23
15
|
const merged = new Map();
|
|
24
|
-
for (const ctor of getConstructorChain((0, target_1.resolveConstructor)(target)).reverse()) {
|
|
16
|
+
for (const ctor of (0, getConstructorChain_1.getConstructorChain)((0, target_1.resolveConstructor)(target)).reverse()) {
|
|
25
17
|
const ownHooks = Reflect.getOwnMetadata(key, ctor);
|
|
26
18
|
if (ownHooks) {
|
|
27
19
|
for (const [methodName, fn] of ownHooks) {
|
|
@@ -32,7 +24,7 @@ function getHooks(target, key) {
|
|
|
32
24
|
return merged;
|
|
33
25
|
}
|
|
34
26
|
function hasHooks(target, key) {
|
|
35
|
-
return getConstructorChain((0, target_1.resolveConstructor)(target)).some((ctor) => Reflect.hasOwnMetadata(key, ctor));
|
|
27
|
+
return (0, getConstructorChain_1.getConstructorChain)((0, target_1.resolveConstructor)(target)).some((ctor) => Reflect.hasOwnMetadata(key, ctor));
|
|
36
28
|
}
|
|
37
29
|
const hook = (key, fn) => (target, propertyKey) => {
|
|
38
30
|
const hooks = Reflect.hasOwnMetadata(key, target.constructor)
|
package/cjm/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
exports.
|
|
5
|
-
exports.Is = exports.unwrapProxy =
|
|
3
|
+
exports.sequential = exports.injectProp = exports.createHookExecutionContext = exports.createHookContextFactory = exports.HookContext = exports.toHookFn = exports.hasHooks = exports.hook = exports.getHooks = exports.TypedEventDisposedError = exports.UnsupportedTokenTypeError = exports.CannonSingletonApplyTwiceError = exports.ProviderDisposedError = exports.ContainerDisposedError = exports.MethodNotImplementedError = exports.DependencyMissingKeyError = exports.ContainerNotFoundError = exports.DependencyNotFoundError = exports.ContainerError = exports.Registration = exports.onResolve = exports.appendArgsFn = exports.appendArgs = exports.decorate = exports.singleton = exports.autoResolve = exports.lazy = exports.scopeAccess = exports.scope = exports.bindTo = exports.register = exports.toRegistrationFn = exports.toProviderFn = exports.toBindToken = exports.registerPipe = exports.isProviderPipe = exports.Provider = exports.ProxyInjector = exports.SimpleInjector = exports.resolveArgs = exports.argsFn = exports.args = exports.arg = exports.inject = exports.MetadataInjector = exports.Injector = exports.EmptyContainer = exports.AutoResolveModule = exports.Container = exports.isDependencyKey = void 0;
|
|
4
|
+
exports.ProxyRegistry = exports.pipe = exports.select = exports.TypedEvent = exports.getConstructorChain = exports.memoize = exports.once = exports.shallowCache = exports.debounce = exports.throttle = exports.runAtOnce = exports.runInOrder = exports.handleAsyncError = exports.handleError = exports.getMethodTags = exports.addMethodTag = exports.getMethodLabels = exports.addMethodLabel = exports.getMethodMeta = exports.addMethodMeta = exports.getParamTags = exports.addParamTag = exports.getParamLabels = exports.addParamLabel = exports.getParamMeta = exports.addParamMeta = exports.getClassTags = exports.addClassTag = exports.getClassLabels = exports.addClassLabel = exports.getClassMeta = exports.addClassMeta = exports.resolveConstructor = exports.GroupInstanceToken = exports.ConstantToken = exports.FunctionToken = exports.SingleToken = exports.ClassToken = exports.toSingleAlias = exports.SingleAliasToken = exports.toGroupAlias = exports.GroupAliasToken = exports.argToToken = exports.toMappedToken = exports.toToken = exports.InjectionToken = exports.toTask = exports.HookCollector = exports.oncePerInstance = exports.parallel = void 0;
|
|
5
|
+
exports.Is = exports.unwrapProxy = void 0;
|
|
6
6
|
var IContainer_1 = require("./container/IContainer");
|
|
7
7
|
Object.defineProperty(exports, "isDependencyKey", { enumerable: true, get: function () { return IContainer_1.isDependencyKey; } });
|
|
8
8
|
var Container_1 = require("./container/Container");
|
|
@@ -76,28 +76,13 @@ Object.defineProperty(exports, "createHookContextFactory", { enumerable: true, g
|
|
|
76
76
|
Object.defineProperty(exports, "createHookExecutionContext", { enumerable: true, get: function () { return HookContext_1.createHookExecutionContext; } });
|
|
77
77
|
var injectProp_1 = require("./hooks/injectProp");
|
|
78
78
|
Object.defineProperty(exports, "injectProp", { enumerable: true, get: function () { return injectProp_1.injectProp; } });
|
|
79
|
-
var onConstruct_1 = require("./hooks/onConstruct");
|
|
80
|
-
Object.defineProperty(exports, "onConstruct", { enumerable: true, get: function () { return onConstruct_1.onConstruct; } });
|
|
81
|
-
Object.defineProperty(exports, "OnConstructModule", { enumerable: true, get: function () { return onConstruct_1.OnConstructModule; } });
|
|
82
|
-
var onScopeDisposed_1 = require("./hooks/onScopeDisposed");
|
|
83
|
-
Object.defineProperty(exports, "onScopeDisposed", { enumerable: true, get: function () { return onScopeDisposed_1.onScopeDisposed; } });
|
|
84
|
-
Object.defineProperty(exports, "OnDisposeModule", { enumerable: true, get: function () { return onScopeDisposed_1.OnDisposeModule; } });
|
|
85
|
-
var onResolved_1 = require("./hooks/onResolved");
|
|
86
|
-
Object.defineProperty(exports, "onResolved", { enumerable: true, get: function () { return onResolved_1.onResolved; } });
|
|
87
|
-
Object.defineProperty(exports, "OnResolvedModule", { enumerable: true, get: function () { return onResolved_1.OnResolvedModule; } });
|
|
88
|
-
Object.defineProperty(exports, "resolved", { enumerable: true, get: function () { return onResolved_1.resolved; } });
|
|
89
79
|
var combinators_1 = require("./hooks/combinators");
|
|
90
80
|
Object.defineProperty(exports, "sequential", { enumerable: true, get: function () { return combinators_1.sequential; } });
|
|
91
81
|
Object.defineProperty(exports, "parallel", { enumerable: true, get: function () { return combinators_1.parallel; } });
|
|
92
82
|
Object.defineProperty(exports, "oncePerInstance", { enumerable: true, get: function () { return combinators_1.oncePerInstance; } });
|
|
93
|
-
var
|
|
94
|
-
Object.defineProperty(exports, "
|
|
95
|
-
|
|
96
|
-
Object.defineProperty(exports, "SequentialSync", { enumerable: true, get: function () { return SequentialSync_1.SequentialSync; } });
|
|
97
|
-
var SequentialAsync_1 = require("./hooks/SequentialAsync");
|
|
98
|
-
Object.defineProperty(exports, "SequentialAsync", { enumerable: true, get: function () { return SequentialAsync_1.SequentialAsync; } });
|
|
99
|
-
var ParallelAsync_1 = require("./hooks/ParallelAsync");
|
|
100
|
-
Object.defineProperty(exports, "ParallelAsync", { enumerable: true, get: function () { return ParallelAsync_1.ParallelAsync; } });
|
|
83
|
+
var HookCollector_1 = require("./hooks/HookCollector");
|
|
84
|
+
Object.defineProperty(exports, "HookCollector", { enumerable: true, get: function () { return HookCollector_1.HookCollector; } });
|
|
85
|
+
Object.defineProperty(exports, "toTask", { enumerable: true, get: function () { return HookCollector_1.toTask; } });
|
|
101
86
|
var InjectionToken_1 = require("./token/InjectionToken");
|
|
102
87
|
Object.defineProperty(exports, "InjectionToken", { enumerable: true, get: function () { return InjectionToken_1.InjectionToken; } });
|
|
103
88
|
var toToken_1 = require("./token/toToken");
|
|
@@ -157,6 +142,10 @@ var shallowCache_1 = require("./utils/shallowCache");
|
|
|
157
142
|
Object.defineProperty(exports, "shallowCache", { enumerable: true, get: function () { return shallowCache_1.shallowCache; } });
|
|
158
143
|
var once_1 = require("./utils/once");
|
|
159
144
|
Object.defineProperty(exports, "once", { enumerable: true, get: function () { return once_1.once; } });
|
|
145
|
+
var memoize_1 = require("./utils/memoize");
|
|
146
|
+
Object.defineProperty(exports, "memoize", { enumerable: true, get: function () { return memoize_1.memoize; } });
|
|
147
|
+
var getConstructorChain_1 = require("./utils/getConstructorChain");
|
|
148
|
+
Object.defineProperty(exports, "getConstructorChain", { enumerable: true, get: function () { return getConstructorChain_1.getConstructorChain; } });
|
|
160
149
|
var TypedEvent_1 = require("./utils/TypedEvent");
|
|
161
150
|
Object.defineProperty(exports, "TypedEvent", { enumerable: true, get: function () { return TypedEvent_1.TypedEvent; } });
|
|
162
151
|
var select_1 = require("./select");
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getConstructorChain = void 0;
|
|
4
|
+
const getConstructorChain = (ctor) => {
|
|
5
|
+
const chain = [];
|
|
6
|
+
let current = ctor;
|
|
7
|
+
while (typeof current === 'function' && current !== Function.prototype) {
|
|
8
|
+
chain.push(current);
|
|
9
|
+
current = Object.getPrototypeOf(current);
|
|
10
|
+
}
|
|
11
|
+
return chain;
|
|
12
|
+
};
|
|
13
|
+
exports.getConstructorChain = getConstructorChain;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.memoize = void 0;
|
|
4
|
+
const memoize = (fn) => {
|
|
5
|
+
const cache = new WeakMap();
|
|
6
|
+
return (target, key) => {
|
|
7
|
+
let byKey = cache.get(target);
|
|
8
|
+
if (!byKey) {
|
|
9
|
+
byKey = new Map();
|
|
10
|
+
cache.set(target, byKey);
|
|
11
|
+
}
|
|
12
|
+
if (!byKey.has(key)) {
|
|
13
|
+
byKey.set(key, fn(target, key));
|
|
14
|
+
}
|
|
15
|
+
return byKey.get(key);
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
exports.memoize = memoize;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { resolveConstructor } from '../metadata/target.js';
|
|
2
|
+
import { memoize } from '../utils/memoize.js';
|
|
3
|
+
import { getHooks, hasHooks, toHookFn } from './hook.js';
|
|
4
|
+
import { createHookExecutionContext } from './HookContext.js';
|
|
5
|
+
export class HookCollector {
|
|
6
|
+
key;
|
|
7
|
+
options;
|
|
8
|
+
hooksOf = memoize(getHooks);
|
|
9
|
+
constructor({ key, createExecutionContext = createHookExecutionContext, mapExecutionContext = (context) => context, predicate = () => true, }) {
|
|
10
|
+
this.key = key;
|
|
11
|
+
this.options = { createExecutionContext, mapExecutionContext, predicate };
|
|
12
|
+
}
|
|
13
|
+
hasHooks(target) {
|
|
14
|
+
return hasHooks(target, this.key);
|
|
15
|
+
}
|
|
16
|
+
getActions(target, { scope, createExecutionContext = this.options.createExecutionContext, mapExecutionContext = this.options.mapExecutionContext, predicate = this.options.predicate, }) {
|
|
17
|
+
const actions = [];
|
|
18
|
+
for (const [methodName, fn] of this.hooksOf(resolveConstructor(target), this.key)) {
|
|
19
|
+
if (predicate(methodName)) {
|
|
20
|
+
actions.push({
|
|
21
|
+
hook: toHookFn(fn),
|
|
22
|
+
context: mapExecutionContext(createExecutionContext(target, scope, methodName)),
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return actions;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export const toTask = ({ hook, context }) => () => hook(context);
|
package/esm/hooks/hook.js
CHANGED
|
@@ -1,18 +1,10 @@
|
|
|
1
1
|
import { Is } from '../utils/basic.js';
|
|
2
2
|
import { resolveConstructor } from '../metadata/target.js';
|
|
3
|
+
import { getConstructorChain } from '../utils/getConstructorChain.js';
|
|
3
4
|
const isHookClassConstructor = (execute) => {
|
|
4
5
|
return Is.constructor(execute) && execute.prototype.execute;
|
|
5
6
|
};
|
|
6
7
|
export const toHookFn = (execute) => isHookClassConstructor(execute) ? (context) => context.scope.resolve(execute).execute(context) : execute;
|
|
7
|
-
const getConstructorChain = (ctor) => {
|
|
8
|
-
const chain = [];
|
|
9
|
-
let current = ctor;
|
|
10
|
-
while (typeof current === 'function' && current !== Function.prototype) {
|
|
11
|
-
chain.push(current);
|
|
12
|
-
current = Object.getPrototypeOf(current);
|
|
13
|
-
}
|
|
14
|
-
return chain;
|
|
15
|
-
};
|
|
16
8
|
export function getHooks(target, key) {
|
|
17
9
|
const merged = new Map();
|
|
18
10
|
for (const ctor of getConstructorChain(resolveConstructor(target)).reverse()) {
|
package/esm/index.js
CHANGED
|
@@ -22,14 +22,8 @@ export { TypedEventDisposedError } from './errors/TypedEventDisposedError.js';
|
|
|
22
22
|
export { getHooks, hook, hasHooks, toHookFn, } from './hooks/hook.js';
|
|
23
23
|
export { HookContext, createHookContextFactory, createHookExecutionContext, } from './hooks/HookContext.js';
|
|
24
24
|
export { injectProp } from './hooks/injectProp.js';
|
|
25
|
-
export { onConstruct, OnConstructModule } from './hooks/onConstruct.js';
|
|
26
|
-
export { onScopeDisposed, OnDisposeModule } from './hooks/onScopeDisposed.js';
|
|
27
|
-
export { onResolved, OnResolvedModule, resolved } from './hooks/onResolved.js';
|
|
28
25
|
export { sequential, parallel, oncePerInstance } from './hooks/combinators.js';
|
|
29
|
-
export {
|
|
30
|
-
export { SequentialSync } from './hooks/SequentialSync.js';
|
|
31
|
-
export { SequentialAsync } from './hooks/SequentialAsync.js';
|
|
32
|
-
export { ParallelAsync } from './hooks/ParallelAsync.js';
|
|
26
|
+
export { HookCollector, toTask, } from './hooks/HookCollector.js';
|
|
33
27
|
export { InjectionToken } from './token/InjectionToken.js';
|
|
34
28
|
export { toToken, toMappedToken, argToToken } from './token/toToken.js';
|
|
35
29
|
export { GroupAliasToken, toGroupAlias } from './token/GroupAliasToken.js';
|
|
@@ -49,6 +43,8 @@ export { throttle } from './utils/throttle.js';
|
|
|
49
43
|
export { debounce } from './utils/debounce.js';
|
|
50
44
|
export { shallowCache } from './utils/shallowCache.js';
|
|
51
45
|
export { once } from './utils/once.js';
|
|
46
|
+
export { memoize } from './utils/memoize.js';
|
|
47
|
+
export { getConstructorChain } from './utils/getConstructorChain.js';
|
|
52
48
|
export { TypedEvent } from './utils/TypedEvent.js';
|
|
53
49
|
export { select } from './select.js';
|
|
54
50
|
export { pipe } from './utils/fp.js';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export const memoize = (fn) => {
|
|
2
|
+
const cache = new WeakMap();
|
|
3
|
+
return (target, key) => {
|
|
4
|
+
let byKey = cache.get(target);
|
|
5
|
+
if (!byKey) {
|
|
6
|
+
byKey = new Map();
|
|
7
|
+
cache.set(target, byKey);
|
|
8
|
+
}
|
|
9
|
+
if (!byKey.has(key)) {
|
|
10
|
+
byKey.set(key, fn(target, key));
|
|
11
|
+
}
|
|
12
|
+
return byKey.get(key);
|
|
13
|
+
};
|
|
14
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ts-ioc-container",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "68.0.0",
|
|
4
4
|
"description": "Fast, lightweight TypeScript dependency injection container with a clean API, scoped lifecycles, decorators, tokens, hooks, lazy injection, customizable providers, and no global container objects.",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public",
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ExecutionContext } from '../ExecutionContext.js';
|
|
2
|
+
import { type Task } from '../utils/task.js';
|
|
3
|
+
import { type Instance } from '../utils/basic.js';
|
|
4
|
+
import { type HookFn } from './hook.js';
|
|
5
|
+
import { type CreateHookExecutionContext, type IHookContext } from './HookContext.js';
|
|
6
|
+
export type MapHookExecutionContext = (context: IHookContext) => IHookContext;
|
|
7
|
+
export type HookAction = {
|
|
8
|
+
hook: HookFn;
|
|
9
|
+
context: IHookContext;
|
|
10
|
+
};
|
|
11
|
+
export type HookCollectorOptions = {
|
|
12
|
+
createExecutionContext?: CreateHookExecutionContext;
|
|
13
|
+
mapExecutionContext?: MapHookExecutionContext;
|
|
14
|
+
predicate?: (methodName: string) => boolean;
|
|
15
|
+
};
|
|
16
|
+
export type HookCollectionContext = ExecutionContext & HookCollectorOptions;
|
|
17
|
+
export type HookCollectorProps = HookCollectorOptions & {
|
|
18
|
+
key: string | symbol;
|
|
19
|
+
};
|
|
20
|
+
export declare class HookCollector {
|
|
21
|
+
private readonly key;
|
|
22
|
+
private readonly options;
|
|
23
|
+
private readonly hooksOf;
|
|
24
|
+
constructor({ key, createExecutionContext, mapExecutionContext, predicate, }: HookCollectorProps);
|
|
25
|
+
hasHooks(target: Instance): boolean;
|
|
26
|
+
getActions(target: Instance, { scope, createExecutionContext, mapExecutionContext, predicate, }: HookCollectionContext): HookAction[];
|
|
27
|
+
}
|
|
28
|
+
export declare const toTask: ({ hook, context }: HookAction) => Task;
|
package/typings/index.d.ts
CHANGED
|
@@ -23,14 +23,8 @@ export { TypedEventDisposedError } from './errors/TypedEventDisposedError.js';
|
|
|
23
23
|
export { getHooks, hook, hasHooks, toHookFn, type HookFn, type HookClass, type HookType, type InjectFn, type HooksOfClass, } from './hooks/hook.js';
|
|
24
24
|
export { HookContext, createHookContextFactory, createHookExecutionContext, type CreateHookExecutionContext, type IHookContext, } from './hooks/HookContext.js';
|
|
25
25
|
export { injectProp } from './hooks/injectProp.js';
|
|
26
|
-
export { onConstruct, OnConstructModule } from './hooks/onConstruct.js';
|
|
27
|
-
export { onScopeDisposed, OnDisposeModule } from './hooks/onScopeDisposed.js';
|
|
28
|
-
export { onResolved, OnResolvedModule, resolved } from './hooks/onResolved.js';
|
|
29
26
|
export { sequential, parallel, oncePerInstance, type ResolvedObjectHook } from './hooks/combinators.js';
|
|
30
|
-
export {
|
|
31
|
-
export { SequentialSync } from './hooks/SequentialSync.js';
|
|
32
|
-
export { SequentialAsync } from './hooks/SequentialAsync.js';
|
|
33
|
-
export { ParallelAsync } from './hooks/ParallelAsync.js';
|
|
27
|
+
export { HookCollector, toTask, type HookAction, type HookCollectionContext, type HookCollectorOptions, type HookCollectorProps, type MapHookExecutionContext, } from './hooks/HookCollector.js';
|
|
34
28
|
export { InjectionToken } from './token/InjectionToken.js';
|
|
35
29
|
export { type Injectable, toToken, toMappedToken, argToToken } from './token/toToken.js';
|
|
36
30
|
export { GroupAliasToken, toGroupAlias } from './token/GroupAliasToken.js';
|
|
@@ -50,6 +44,8 @@ export { throttle } from './utils/throttle.js';
|
|
|
50
44
|
export { debounce } from './utils/debounce.js';
|
|
51
45
|
export { shallowCache } from './utils/shallowCache.js';
|
|
52
46
|
export { once } from './utils/once.js';
|
|
47
|
+
export { memoize } from './utils/memoize.js';
|
|
48
|
+
export { getConstructorChain } from './utils/getConstructorChain.js';
|
|
53
49
|
export { TypedEvent, type ITypedEvent, type TypedEventListener, type Unsubscribe } from './utils/TypedEvent.js';
|
|
54
50
|
export { type ExecutionContext } from './ExecutionContext.js';
|
|
55
51
|
export { select } from './select.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const getConstructorChain: (ctor: unknown) => object[];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const memoize: <T extends object, K, R>(fn: (target: T, key: K) => R) => ((target: T, key: K) => R);
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.HookExecutionStrategy = void 0;
|
|
4
|
-
const target_1 = require("../metadata/target");
|
|
5
|
-
const hook_1 = require("./hook");
|
|
6
|
-
const HookContext_1 = require("./HookContext");
|
|
7
|
-
class HookExecutionStrategy {
|
|
8
|
-
key;
|
|
9
|
-
onError;
|
|
10
|
-
options;
|
|
11
|
-
hooksByClass = new WeakMap();
|
|
12
|
-
constructor({ key, onError, createExecutionContext = HookContext_1.createHookExecutionContext, mapExecutionContext = (context) => context, predicate = () => true, }) {
|
|
13
|
-
this.key = key;
|
|
14
|
-
this.onError = onError;
|
|
15
|
-
this.options = { createExecutionContext, mapExecutionContext, predicate };
|
|
16
|
-
}
|
|
17
|
-
hasHooks(target) {
|
|
18
|
-
return (0, hook_1.hasHooks)(target, this.key);
|
|
19
|
-
}
|
|
20
|
-
execute(target, { scope, ...overrides }) {
|
|
21
|
-
const report = (ex) => this.onError?.(scope)(ex);
|
|
22
|
-
try {
|
|
23
|
-
this.processHooks(this.collect(target, scope, overrides))?.catch(report);
|
|
24
|
-
}
|
|
25
|
-
catch (ex) {
|
|
26
|
-
report(ex);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
collect(target, scope, { createExecutionContext = this.options.createExecutionContext, mapExecutionContext = this.options.mapExecutionContext, predicate = this.options.predicate, }) {
|
|
30
|
-
const members = [];
|
|
31
|
-
for (const { methodName, hook } of this.hooksOf(target)) {
|
|
32
|
-
if (predicate(methodName)) {
|
|
33
|
-
members.push({ hook, context: mapExecutionContext(createExecutionContext(target, scope, methodName)) });
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
return members;
|
|
37
|
-
}
|
|
38
|
-
hooksOf(target) {
|
|
39
|
-
const Target = (0, target_1.resolveConstructor)(target);
|
|
40
|
-
let hooks = this.hooksByClass.get(Target);
|
|
41
|
-
if (!hooks) {
|
|
42
|
-
hooks = Array.from((0, hook_1.getHooks)(Target, this.key), ([methodName, fn]) => ({ methodName, hook: (0, hook_1.toHookFn)(fn) }));
|
|
43
|
-
this.hooksByClass.set(Target, hooks);
|
|
44
|
-
}
|
|
45
|
-
return hooks;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
exports.HookExecutionStrategy = HookExecutionStrategy;
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ParallelAsync = void 0;
|
|
4
|
-
const HookExecutionStrategy_1 = require("./HookExecutionStrategy");
|
|
5
|
-
const task_1 = require("../utils/task");
|
|
6
|
-
class ParallelAsync extends HookExecutionStrategy_1.HookExecutionStrategy {
|
|
7
|
-
processHooks(members) {
|
|
8
|
-
return (0, task_1.runAtOnce)(members.map(({ hook, context }) => () => hook(context)));
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
exports.ParallelAsync = ParallelAsync;
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.SequentialAsync = void 0;
|
|
4
|
-
const HookExecutionStrategy_1 = require("./HookExecutionStrategy");
|
|
5
|
-
const task_1 = require("../utils/task");
|
|
6
|
-
class SequentialAsync extends HookExecutionStrategy_1.HookExecutionStrategy {
|
|
7
|
-
processHooks(members) {
|
|
8
|
-
return (0, task_1.runInOrder)(members.map(({ hook, context }) => () => hook(context)));
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
exports.SequentialAsync = SequentialAsync;
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.SequentialSync = void 0;
|
|
4
|
-
const HookExecutionStrategy_1 = require("./HookExecutionStrategy");
|
|
5
|
-
class SequentialSync extends HookExecutionStrategy_1.HookExecutionStrategy {
|
|
6
|
-
processHooks(members) {
|
|
7
|
-
for (const { hook, context } of members) {
|
|
8
|
-
hook(context);
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
exports.SequentialSync = SequentialSync;
|
package/cjm/hooks/onConstruct.js
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.OnConstructModule = exports.onConstruct = void 0;
|
|
4
|
-
const hook_1 = require("./hook");
|
|
5
|
-
const onConstruct = (fn) => (0, hook_1.hook)('onConstruct', fn);
|
|
6
|
-
exports.onConstruct = onConstruct;
|
|
7
|
-
class OnConstructModule {
|
|
8
|
-
strategy;
|
|
9
|
-
constructor(strategy) {
|
|
10
|
-
this.strategy = strategy;
|
|
11
|
-
}
|
|
12
|
-
applyTo(container) {
|
|
13
|
-
container.getInjector().onConstructed((instance, scope) => {
|
|
14
|
-
this.strategy.execute(instance, { scope });
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
exports.OnConstructModule = OnConstructModule;
|
package/cjm/hooks/onResolved.js
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.resolved = exports.OnResolvedModule = exports.onResolved = void 0;
|
|
4
|
-
const IRegistration_1 = require("../registration/IRegistration");
|
|
5
|
-
const basic_1 = require("../utils/basic");
|
|
6
|
-
const hook_1 = require("./hook");
|
|
7
|
-
const onResolved = (fn) => (0, hook_1.hook)('onResolved', fn);
|
|
8
|
-
exports.onResolved = onResolved;
|
|
9
|
-
const runHooks = (strategy) => (dependency, scope) => {
|
|
10
|
-
if (basic_1.Is.object(dependency)) {
|
|
11
|
-
strategy.execute(dependency, { scope });
|
|
12
|
-
}
|
|
13
|
-
};
|
|
14
|
-
class OnResolvedModule {
|
|
15
|
-
runHooks;
|
|
16
|
-
constructor(strategy) {
|
|
17
|
-
this.runHooks = runHooks(strategy);
|
|
18
|
-
}
|
|
19
|
-
applyTo(container) {
|
|
20
|
-
container.registered.subscribe((provider) => {
|
|
21
|
-
provider.onResolved(this.runHooks);
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
exports.OnResolvedModule = OnResolvedModule;
|
|
26
|
-
const resolved = (strategy) => (0, IRegistration_1.registerPipe)((p) => p.onResolved(runHooks(strategy)));
|
|
27
|
-
exports.resolved = resolved;
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.OnDisposeModule = exports.onScopeDisposed = void 0;
|
|
4
|
-
const hook_1 = require("./hook");
|
|
5
|
-
const onScopeDisposed = (fn) => (0, hook_1.hook)('onScopeDisposed', fn);
|
|
6
|
-
exports.onScopeDisposed = onScopeDisposed;
|
|
7
|
-
class OnDisposeModule {
|
|
8
|
-
strategy;
|
|
9
|
-
constructor(strategy) {
|
|
10
|
-
this.strategy = strategy;
|
|
11
|
-
}
|
|
12
|
-
applyTo(container) {
|
|
13
|
-
container.scopeDisposed.subscribe((scope) => {
|
|
14
|
-
for (const instance of scope.getInstances()) {
|
|
15
|
-
this.strategy.execute(instance, { scope });
|
|
16
|
-
}
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
exports.OnDisposeModule = OnDisposeModule;
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { resolveConstructor } from '../metadata/target.js';
|
|
2
|
-
import { getHooks, hasHooks, toHookFn } from './hook.js';
|
|
3
|
-
import { createHookExecutionContext } from './HookContext.js';
|
|
4
|
-
export class HookExecutionStrategy {
|
|
5
|
-
key;
|
|
6
|
-
onError;
|
|
7
|
-
options;
|
|
8
|
-
hooksByClass = new WeakMap();
|
|
9
|
-
constructor({ key, onError, createExecutionContext = createHookExecutionContext, mapExecutionContext = (context) => context, predicate = () => true, }) {
|
|
10
|
-
this.key = key;
|
|
11
|
-
this.onError = onError;
|
|
12
|
-
this.options = { createExecutionContext, mapExecutionContext, predicate };
|
|
13
|
-
}
|
|
14
|
-
hasHooks(target) {
|
|
15
|
-
return hasHooks(target, this.key);
|
|
16
|
-
}
|
|
17
|
-
execute(target, { scope, ...overrides }) {
|
|
18
|
-
const report = (ex) => this.onError?.(scope)(ex);
|
|
19
|
-
try {
|
|
20
|
-
this.processHooks(this.collect(target, scope, overrides))?.catch(report);
|
|
21
|
-
}
|
|
22
|
-
catch (ex) {
|
|
23
|
-
report(ex);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
collect(target, scope, { createExecutionContext = this.options.createExecutionContext, mapExecutionContext = this.options.mapExecutionContext, predicate = this.options.predicate, }) {
|
|
27
|
-
const members = [];
|
|
28
|
-
for (const { methodName, hook } of this.hooksOf(target)) {
|
|
29
|
-
if (predicate(methodName)) {
|
|
30
|
-
members.push({ hook, context: mapExecutionContext(createExecutionContext(target, scope, methodName)) });
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
return members;
|
|
34
|
-
}
|
|
35
|
-
hooksOf(target) {
|
|
36
|
-
const Target = resolveConstructor(target);
|
|
37
|
-
let hooks = this.hooksByClass.get(Target);
|
|
38
|
-
if (!hooks) {
|
|
39
|
-
hooks = Array.from(getHooks(Target, this.key), ([methodName, fn]) => ({ methodName, hook: toHookFn(fn) }));
|
|
40
|
-
this.hooksByClass.set(Target, hooks);
|
|
41
|
-
}
|
|
42
|
-
return hooks;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { HookExecutionStrategy } from './HookExecutionStrategy.js';
|
|
2
|
-
import { runAtOnce } from '../utils/task.js';
|
|
3
|
-
export class ParallelAsync extends HookExecutionStrategy {
|
|
4
|
-
processHooks(members) {
|
|
5
|
-
return runAtOnce(members.map(({ hook, context }) => () => hook(context)));
|
|
6
|
-
}
|
|
7
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { HookExecutionStrategy } from './HookExecutionStrategy.js';
|
|
2
|
-
import { runInOrder } from '../utils/task.js';
|
|
3
|
-
export class SequentialAsync extends HookExecutionStrategy {
|
|
4
|
-
processHooks(members) {
|
|
5
|
-
return runInOrder(members.map(({ hook, context }) => () => hook(context)));
|
|
6
|
-
}
|
|
7
|
-
}
|
package/esm/hooks/onConstruct.js
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { hook } from './hook.js';
|
|
2
|
-
export const onConstruct = (fn) => hook('onConstruct', fn);
|
|
3
|
-
export class OnConstructModule {
|
|
4
|
-
strategy;
|
|
5
|
-
constructor(strategy) {
|
|
6
|
-
this.strategy = strategy;
|
|
7
|
-
}
|
|
8
|
-
applyTo(container) {
|
|
9
|
-
container.getInjector().onConstructed((instance, scope) => {
|
|
10
|
-
this.strategy.execute(instance, { scope });
|
|
11
|
-
});
|
|
12
|
-
}
|
|
13
|
-
}
|
package/esm/hooks/onResolved.js
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { registerPipe } from '../registration/IRegistration.js';
|
|
2
|
-
import { Is } from '../utils/basic.js';
|
|
3
|
-
import { hook } from './hook.js';
|
|
4
|
-
export const onResolved = (fn) => hook('onResolved', fn);
|
|
5
|
-
const runHooks = (strategy) => (dependency, scope) => {
|
|
6
|
-
if (Is.object(dependency)) {
|
|
7
|
-
strategy.execute(dependency, { scope });
|
|
8
|
-
}
|
|
9
|
-
};
|
|
10
|
-
export class OnResolvedModule {
|
|
11
|
-
runHooks;
|
|
12
|
-
constructor(strategy) {
|
|
13
|
-
this.runHooks = runHooks(strategy);
|
|
14
|
-
}
|
|
15
|
-
applyTo(container) {
|
|
16
|
-
container.registered.subscribe((provider) => {
|
|
17
|
-
provider.onResolved(this.runHooks);
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
export const resolved = (strategy) => registerPipe((p) => p.onResolved(runHooks(strategy)));
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { hook } from './hook.js';
|
|
2
|
-
export const onScopeDisposed = (fn) => hook('onScopeDisposed', fn);
|
|
3
|
-
export class OnDisposeModule {
|
|
4
|
-
strategy;
|
|
5
|
-
constructor(strategy) {
|
|
6
|
-
this.strategy = strategy;
|
|
7
|
-
}
|
|
8
|
-
applyTo(container) {
|
|
9
|
-
container.scopeDisposed.subscribe((scope) => {
|
|
10
|
-
for (const instance of scope.getInstances()) {
|
|
11
|
-
this.strategy.execute(instance, { scope });
|
|
12
|
-
}
|
|
13
|
-
});
|
|
14
|
-
}
|
|
15
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import type { IContainer } from '../container/IContainer.js';
|
|
2
|
-
import type { ExecutionContext } from '../ExecutionContext.js';
|
|
3
|
-
import { type Instance } from '../utils/basic.js';
|
|
4
|
-
import { type HookFn } from './hook.js';
|
|
5
|
-
import { type CreateHookExecutionContext, type IHookContext } from './HookContext.js';
|
|
6
|
-
export type MapHookExecutionContext = (context: IHookContext) => IHookContext;
|
|
7
|
-
export type OnErrorHandler = (scope: IContainer) => (error: unknown) => void;
|
|
8
|
-
export type HookExecutionOptions = {
|
|
9
|
-
createExecutionContext?: CreateHookExecutionContext;
|
|
10
|
-
mapExecutionContext?: MapHookExecutionContext;
|
|
11
|
-
predicate?: (methodName: string) => boolean;
|
|
12
|
-
};
|
|
13
|
-
export type HookExecutionContext = ExecutionContext & HookExecutionOptions;
|
|
14
|
-
export type HookExecutionStrategyProps = HookExecutionOptions & {
|
|
15
|
-
key: string | symbol;
|
|
16
|
-
onError?: OnErrorHandler;
|
|
17
|
-
};
|
|
18
|
-
export type MemberHook = {
|
|
19
|
-
hook: HookFn;
|
|
20
|
-
context: IHookContext;
|
|
21
|
-
};
|
|
22
|
-
export declare abstract class HookExecutionStrategy {
|
|
23
|
-
private readonly key;
|
|
24
|
-
private readonly onError?;
|
|
25
|
-
private readonly options;
|
|
26
|
-
private readonly hooksByClass;
|
|
27
|
-
constructor({ key, onError, createExecutionContext, mapExecutionContext, predicate, }: HookExecutionStrategyProps);
|
|
28
|
-
hasHooks(target: Instance): boolean;
|
|
29
|
-
execute(target: Instance, { scope, ...overrides }: HookExecutionContext): void;
|
|
30
|
-
protected abstract processHooks(members: MemberHook[]): void | Promise<void>;
|
|
31
|
-
private collect;
|
|
32
|
-
private hooksOf;
|
|
33
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { type HookType } from './hook.js';
|
|
2
|
-
import type { IContainer, IContainerModule } from '../container/IContainer.js';
|
|
3
|
-
import { type HookExecutionStrategy } from './HookExecutionStrategy.js';
|
|
4
|
-
export declare const onConstruct: (fn: HookType) => (target: object, propertyKey: string | symbol) => void;
|
|
5
|
-
export declare class OnConstructModule implements IContainerModule {
|
|
6
|
-
private readonly strategy;
|
|
7
|
-
constructor(strategy: HookExecutionStrategy);
|
|
8
|
-
applyTo(container: IContainer): void;
|
|
9
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type { IContainer, IContainerModule } from '../container/IContainer.js';
|
|
2
|
-
import { type HookExecutionStrategy } from './HookExecutionStrategy.js';
|
|
3
|
-
import { type HookType } from './hook.js';
|
|
4
|
-
export declare const onResolved: (fn: HookType) => (target: object, propertyKey: string | symbol) => void;
|
|
5
|
-
export declare class OnResolvedModule implements IContainerModule {
|
|
6
|
-
private readonly runHooks;
|
|
7
|
-
constructor(strategy: HookExecutionStrategy);
|
|
8
|
-
applyTo(container: IContainer): void;
|
|
9
|
-
}
|
|
10
|
-
export declare const resolved: <T = unknown>(strategy: HookExecutionStrategy) => import("../registration/IRegistration.js").ProviderPipe<T>;
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { type HookType } from './hook.js';
|
|
2
|
-
import type { IContainer, IContainerModule } from '../container/IContainer.js';
|
|
3
|
-
import { type HookExecutionStrategy } from './HookExecutionStrategy.js';
|
|
4
|
-
export declare const onScopeDisposed: (fn: HookType) => (target: object, propertyKey: string | symbol) => void;
|
|
5
|
-
export declare class OnDisposeModule implements IContainerModule {
|
|
6
|
-
private readonly strategy;
|
|
7
|
-
constructor(strategy: HookExecutionStrategy);
|
|
8
|
-
applyTo(container: IContainer): void;
|
|
9
|
-
}
|