ts-ioc-container 72.0.0 → 72.2.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/AGENTS.md +179 -0
- package/README.md +94 -0
- package/cjm/container/Container.js +2 -2
- package/cjm/container/EmptyContainer.js +17 -15
- package/cjm/errors/ArgumentNotFoundError.js +1 -0
- package/cjm/errors/CannonSingletonApplyTwiceError.js +2 -14
- package/cjm/errors/CannotApplySingletonTwiceError.js +18 -0
- package/cjm/errors/ContainerDisposedError.js +1 -0
- package/cjm/errors/ContainerError.js +1 -0
- package/cjm/errors/ContainerNotFoundError.js +1 -0
- package/cjm/errors/DependencyMissingKeyError.js +1 -0
- package/cjm/errors/DependencyNotFoundError.js +1 -0
- package/cjm/errors/MethodNotImplementedError.js +1 -0
- package/cjm/errors/ProviderDisposedError.js +1 -0
- package/cjm/errors/TypedEventDisposedError.js +1 -0
- package/cjm/errors/UnsupportedTokenTypeError.js +1 -0
- package/cjm/index.js +8 -3
- package/cjm/metadata/class.js +3 -1
- package/cjm/metadata/method.js +3 -1
- package/cjm/metadata/parameter.js +7 -1
- package/cjm/provider/Provider.js +6 -6
- package/cjm/registration/Registration.js +2 -2
- package/cjm/token/ConstantToken.js +4 -4
- package/cjm/token/FunctionToken.js +1 -1
- package/cjm/token/GroupInstanceToken.js +4 -4
- package/cjm/token/toToken.js +1 -1
- package/cjm/utils/TypedEvent.js +1 -1
- package/cjm/utils/array.js +1 -1
- package/esm/container/Container.js +2 -2
- package/esm/container/EmptyContainer.js +17 -15
- package/esm/errors/ArgumentNotFoundError.js +1 -0
- package/esm/errors/CannonSingletonApplyTwiceError.js +2 -13
- package/esm/errors/CannotApplySingletonTwiceError.js +14 -0
- package/esm/errors/ContainerDisposedError.js +1 -0
- package/esm/errors/ContainerError.js +1 -0
- package/esm/errors/ContainerNotFoundError.js +1 -0
- package/esm/errors/DependencyMissingKeyError.js +1 -0
- package/esm/errors/DependencyNotFoundError.js +1 -0
- package/esm/errors/MethodNotImplementedError.js +1 -0
- package/esm/errors/ProviderDisposedError.js +1 -0
- package/esm/errors/TypedEventDisposedError.js +1 -0
- package/esm/errors/UnsupportedTokenTypeError.js +1 -0
- package/esm/index.js +4 -3
- package/esm/metadata/class.js +1 -0
- package/esm/metadata/method.js +1 -0
- package/esm/metadata/parameter.js +5 -0
- package/esm/provider/Provider.js +6 -6
- package/esm/registration/Registration.js +2 -2
- package/esm/token/ConstantToken.js +4 -4
- package/esm/token/FunctionToken.js +1 -1
- package/esm/token/GroupInstanceToken.js +4 -4
- package/esm/token/toToken.js +1 -1
- package/esm/utils/TypedEvent.js +1 -1
- package/esm/utils/array.js +1 -1
- package/lib/ExecutionContext.ts +6 -0
- package/lib/container/AliasMap.ts +30 -0
- package/lib/container/AutoResolveModule.ts +24 -0
- package/lib/container/Container.ts +311 -0
- package/lib/container/EmptyContainer.ts +166 -0
- package/lib/container/IContainer.ts +156 -0
- package/lib/errors/ArgumentNotFoundError.ts +13 -0
- package/lib/errors/CannonSingletonApplyTwiceError.ts +6 -0
- package/lib/errors/CannotApplySingletonTwiceError.ts +22 -0
- package/lib/errors/ContainerDisposedError.ts +13 -0
- package/lib/errors/ContainerError.ts +15 -0
- package/lib/errors/ContainerNotFoundError.ts +13 -0
- package/lib/errors/DependencyMissingKeyError.ts +13 -0
- package/lib/errors/DependencyNotFoundError.ts +13 -0
- package/lib/errors/MethodNotImplementedError.ts +13 -0
- package/lib/errors/ProviderDisposedError.ts +22 -0
- package/lib/errors/TypedEventDisposedError.ts +22 -0
- package/lib/errors/UnsupportedTokenTypeError.ts +13 -0
- package/lib/hooks/HookCollector.ts +109 -0
- package/lib/hooks/HookContext.ts +85 -0
- package/lib/hooks/combinators.ts +51 -0
- package/lib/hooks/hook.ts +89 -0
- package/lib/hooks/injectProp.ts +14 -0
- package/lib/index.ts +181 -0
- package/lib/injector/IInjector.ts +88 -0
- package/lib/injector/MetadataInjector.ts +100 -0
- package/lib/injector/ProxyInjector.ts +31 -0
- package/lib/injector/SimpleInjector.ts +20 -0
- package/lib/metadata/class.ts +50 -0
- package/lib/metadata/method.ts +47 -0
- package/lib/metadata/parameter.ts +60 -0
- package/lib/metadata/target.ts +25 -0
- package/lib/provider/IProvider.ts +65 -0
- package/lib/provider/Provider.ts +174 -0
- package/lib/registration/IRegistration.ts +235 -0
- package/lib/registration/Registration.ts +122 -0
- package/lib/select.ts +33 -0
- package/lib/token/BindToken.ts +13 -0
- package/lib/token/ClassToken.ts +72 -0
- package/lib/token/ConstantToken.ts +50 -0
- package/lib/token/FunctionToken.ts +79 -0
- package/lib/token/GroupAliasToken.ts +92 -0
- package/lib/token/GroupInstanceToken.ts +70 -0
- package/lib/token/InjectionToken.ts +46 -0
- package/lib/token/SingleAliasToken.ts +84 -0
- package/lib/token/SingleToken.ts +93 -0
- package/lib/token/toToken.ts +46 -0
- package/lib/utils/ProxyRegistry.ts +149 -0
- package/lib/utils/TypedEvent.ts +96 -0
- package/lib/utils/array.ts +28 -0
- package/lib/utils/basic.ts +34 -0
- package/lib/utils/debounce.ts +18 -0
- package/lib/utils/errorHandler.ts +34 -0
- package/lib/utils/fp.ts +86 -0
- package/lib/utils/getConstructorChain.ts +17 -0
- package/lib/utils/memoize.ts +24 -0
- package/lib/utils/once.ts +24 -0
- package/lib/utils/shallowCache.ts +22 -0
- package/lib/utils/task.ts +33 -0
- package/lib/utils/throttle.ts +17 -0
- package/package.json +5 -3
- package/typings/ExecutionContext.d.ts +2 -0
- package/typings/ExecutionContext.d.ts.map +1 -0
- package/typings/container/AliasMap.d.ts +1 -0
- package/typings/container/AliasMap.d.ts.map +1 -0
- package/typings/container/AutoResolveModule.d.ts +16 -0
- package/typings/container/AutoResolveModule.d.ts.map +1 -0
- package/typings/container/Container.d.ts +60 -0
- package/typings/container/Container.d.ts.map +1 -0
- package/typings/container/EmptyContainer.d.ts +51 -0
- package/typings/container/EmptyContainer.d.ts.map +1 -0
- package/typings/container/IContainer.d.ts +75 -0
- package/typings/container/IContainer.d.ts.map +1 -0
- package/typings/errors/ArgumentNotFoundError.d.ts +3 -0
- package/typings/errors/ArgumentNotFoundError.d.ts.map +1 -0
- package/typings/errors/CannonSingletonApplyTwiceError.d.ts +6 -6
- package/typings/errors/CannonSingletonApplyTwiceError.d.ts.map +1 -0
- package/typings/errors/CannotApplySingletonTwiceError.d.ts +12 -0
- package/typings/errors/CannotApplySingletonTwiceError.d.ts.map +1 -0
- package/typings/errors/ContainerDisposedError.d.ts +3 -0
- package/typings/errors/ContainerDisposedError.d.ts.map +1 -0
- package/typings/errors/ContainerError.d.ts +7 -0
- package/typings/errors/ContainerError.d.ts.map +1 -0
- package/typings/errors/ContainerNotFoundError.d.ts +3 -0
- package/typings/errors/ContainerNotFoundError.d.ts.map +1 -0
- package/typings/errors/DependencyMissingKeyError.d.ts +3 -0
- package/typings/errors/DependencyMissingKeyError.d.ts.map +1 -0
- package/typings/errors/DependencyNotFoundError.d.ts +3 -0
- package/typings/errors/DependencyNotFoundError.d.ts.map +1 -0
- package/typings/errors/MethodNotImplementedError.d.ts +3 -0
- package/typings/errors/MethodNotImplementedError.d.ts.map +1 -0
- package/typings/errors/ProviderDisposedError.d.ts +6 -0
- package/typings/errors/ProviderDisposedError.d.ts.map +1 -0
- package/typings/errors/TypedEventDisposedError.d.ts +6 -0
- package/typings/errors/TypedEventDisposedError.d.ts.map +1 -0
- package/typings/errors/UnsupportedTokenTypeError.d.ts +3 -0
- package/typings/errors/UnsupportedTokenTypeError.d.ts.map +1 -0
- package/typings/hooks/HookCollector.d.ts +36 -0
- package/typings/hooks/HookCollector.d.ts.map +1 -0
- package/typings/hooks/HookContext.d.ts +10 -0
- package/typings/hooks/HookContext.d.ts.map +1 -0
- package/typings/hooks/combinators.d.ts +23 -0
- package/typings/hooks/combinators.d.ts.map +1 -0
- package/typings/hooks/hook.d.ts +39 -0
- package/typings/hooks/hook.d.ts.map +1 -0
- package/typings/hooks/injectProp.d.ts +9 -0
- package/typings/hooks/injectProp.d.ts.map +1 -0
- package/typings/index.d.ts +5 -3
- package/typings/index.d.ts.map +1 -0
- package/typings/injector/IInjector.d.ts +32 -0
- package/typings/injector/IInjector.d.ts.map +1 -0
- package/typings/injector/MetadataInjector.d.ts +58 -0
- package/typings/injector/MetadataInjector.d.ts.map +1 -0
- package/typings/injector/ProxyInjector.d.ts +13 -0
- package/typings/injector/ProxyInjector.d.ts.map +1 -0
- package/typings/injector/SimpleInjector.d.ts +13 -0
- package/typings/injector/SimpleInjector.d.ts.map +1 -0
- package/typings/metadata/class.d.ts +28 -0
- package/typings/metadata/class.d.ts.map +1 -0
- package/typings/metadata/method.d.ts +25 -0
- package/typings/metadata/method.d.ts.map +1 -0
- package/typings/metadata/parameter.d.ts +22 -0
- package/typings/metadata/parameter.d.ts.map +1 -0
- package/typings/metadata/target.d.ts +19 -0
- package/typings/metadata/target.d.ts.map +1 -0
- package/typings/provider/IProvider.d.ts +25 -0
- package/typings/provider/IProvider.d.ts.map +1 -0
- package/typings/provider/Provider.d.ts +30 -0
- package/typings/provider/Provider.d.ts.map +1 -0
- package/typings/registration/IRegistration.d.ts +137 -0
- package/typings/registration/IRegistration.d.ts.map +1 -0
- package/typings/registration/Registration.d.ts +23 -0
- package/typings/registration/Registration.d.ts.map +1 -0
- package/typings/select.d.ts +15 -0
- package/typings/select.d.ts.map +1 -0
- package/typings/token/BindToken.d.ts +3 -0
- package/typings/token/BindToken.d.ts.map +1 -0
- package/typings/token/ClassToken.d.ts +2 -0
- package/typings/token/ClassToken.d.ts.map +1 -0
- package/typings/token/ConstantToken.d.ts +14 -0
- package/typings/token/ConstantToken.d.ts.map +1 -0
- package/typings/token/FunctionToken.d.ts +10 -0
- package/typings/token/FunctionToken.d.ts.map +1 -0
- package/typings/token/GroupAliasToken.d.ts +14 -0
- package/typings/token/GroupAliasToken.d.ts.map +1 -0
- package/typings/token/GroupInstanceToken.d.ts +19 -0
- package/typings/token/GroupInstanceToken.d.ts.map +1 -0
- package/typings/token/InjectionToken.d.ts +8 -0
- package/typings/token/InjectionToken.d.ts.map +1 -0
- package/typings/token/SingleAliasToken.d.ts +6 -0
- package/typings/token/SingleAliasToken.d.ts.map +1 -0
- package/typings/token/SingleToken.d.ts +18 -0
- package/typings/token/SingleToken.d.ts.map +1 -0
- package/typings/token/toToken.d.ts +12 -0
- package/typings/token/toToken.d.ts.map +1 -0
- package/typings/utils/ProxyRegistry.d.ts +63 -0
- package/typings/utils/ProxyRegistry.d.ts.map +1 -0
- package/typings/utils/TypedEvent.d.ts +42 -0
- package/typings/utils/TypedEvent.d.ts.map +1 -0
- package/typings/utils/array.d.ts +7 -0
- package/typings/utils/array.d.ts.map +1 -0
- package/typings/utils/basic.d.ts +12 -0
- package/typings/utils/basic.d.ts.map +1 -0
- package/typings/utils/debounce.d.ts +2 -0
- package/typings/utils/debounce.d.ts.map +1 -0
- package/typings/utils/errorHandler.d.ts +4 -0
- package/typings/utils/errorHandler.d.ts.map +1 -0
- package/typings/utils/fp.d.ts +8 -0
- package/typings/utils/fp.d.ts.map +1 -0
- package/typings/utils/getConstructorChain.d.ts +9 -0
- package/typings/utils/getConstructorChain.d.ts.map +1 -0
- package/typings/utils/memoize.d.ts +10 -0
- package/typings/utils/memoize.d.ts.map +1 -0
- package/typings/utils/once.d.ts +2 -0
- package/typings/utils/once.d.ts.map +1 -0
- package/typings/utils/shallowCache.d.ts +2 -0
- package/typings/utils/shallowCache.d.ts.map +1 -0
- package/typings/utils/task.d.ts +13 -0
- package/typings/utils/task.d.ts.map +1 -0
- package/typings/utils/throttle.d.ts +2 -0
- package/typings/utils/throttle.d.ts.map +1 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { ContainerError } from './ContainerError';
|
|
2
|
+
|
|
3
|
+
/** Thrown when `singleton()` is applied twice to one provider. Code `IOC_SINGLETON_APPLIED_TWICE`. */
|
|
4
|
+
export class CannotApplySingletonTwiceError extends ContainerError {
|
|
5
|
+
name = 'CannotApplySingletonTwiceError';
|
|
6
|
+
readonly code = 'IOC_SINGLETON_APPLIED_TWICE';
|
|
7
|
+
|
|
8
|
+
constructor(message?: string) {
|
|
9
|
+
super(message);
|
|
10
|
+
|
|
11
|
+
Object.setPrototypeOf(this, CannotApplySingletonTwiceError.prototype);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @throws {CannotApplySingletonTwiceError} when `isTrue` is falsy.
|
|
16
|
+
*/
|
|
17
|
+
static assert(isTrue: boolean, failMessage: string) {
|
|
18
|
+
if (!isTrue) {
|
|
19
|
+
throw new CannotApplySingletonTwiceError(failMessage);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ContainerError } from './ContainerError';
|
|
2
|
+
|
|
3
|
+
/** Thrown when a disposed scope is used. Code `IOC_CONTAINER_DISPOSED`. */
|
|
4
|
+
export class ContainerDisposedError extends ContainerError {
|
|
5
|
+
name = 'ContainerDisposedError';
|
|
6
|
+
readonly code = 'IOC_CONTAINER_DISPOSED';
|
|
7
|
+
|
|
8
|
+
constructor(message: string) {
|
|
9
|
+
super(message);
|
|
10
|
+
|
|
11
|
+
Object.setPrototypeOf(this, ContainerDisposedError.prototype);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base class of every error the container throws. `code` is stable across
|
|
3
|
+
* versions (unlike `message`), so match on it — or on the subclass — rather
|
|
4
|
+
* than on the text. The codes are listed in the package's AGENTS.md.
|
|
5
|
+
*/
|
|
6
|
+
export abstract class ContainerError extends Error {
|
|
7
|
+
name = 'ContainerError';
|
|
8
|
+
readonly code: string = 'IOC_CONTAINER_ERROR';
|
|
9
|
+
|
|
10
|
+
protected constructor(message?: string) {
|
|
11
|
+
super(message);
|
|
12
|
+
|
|
13
|
+
Object.setPrototypeOf(this, ContainerError.prototype);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ContainerError } from './ContainerError';
|
|
2
|
+
|
|
3
|
+
/** Signals that no container is associated with a target. Code `IOC_CONTAINER_NOT_FOUND`. */
|
|
4
|
+
export class ContainerNotFoundError extends ContainerError {
|
|
5
|
+
name = 'ContainerNotFoundError';
|
|
6
|
+
readonly code = 'IOC_CONTAINER_NOT_FOUND';
|
|
7
|
+
|
|
8
|
+
constructor(message: string) {
|
|
9
|
+
super(message);
|
|
10
|
+
|
|
11
|
+
Object.setPrototypeOf(this, ContainerNotFoundError.prototype);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ContainerError } from './ContainerError';
|
|
2
|
+
|
|
3
|
+
/** Thrown when a registration has no binding key. Code `IOC_DEPENDENCY_MISSING_KEY`. */
|
|
4
|
+
export class DependencyMissingKeyError extends ContainerError {
|
|
5
|
+
name = 'DependencyMissingKeyError';
|
|
6
|
+
readonly code = 'IOC_DEPENDENCY_MISSING_KEY';
|
|
7
|
+
|
|
8
|
+
constructor(message: string) {
|
|
9
|
+
super(message);
|
|
10
|
+
|
|
11
|
+
Object.setPrototypeOf(this, DependencyMissingKeyError.prototype);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ContainerError } from './ContainerError';
|
|
2
|
+
|
|
3
|
+
/** Thrown when a key or alias is not found in the scope or any parent. Code `IOC_DEPENDENCY_NOT_FOUND`. */
|
|
4
|
+
export class DependencyNotFoundError extends ContainerError {
|
|
5
|
+
name = 'DependencyNotFoundError';
|
|
6
|
+
readonly code = 'IOC_DEPENDENCY_NOT_FOUND';
|
|
7
|
+
|
|
8
|
+
constructor(message: string) {
|
|
9
|
+
super(message);
|
|
10
|
+
|
|
11
|
+
Object.setPrototypeOf(this, DependencyNotFoundError.prototype);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ContainerError } from './ContainerError';
|
|
2
|
+
|
|
3
|
+
/** Thrown by a method the object does not support; the message names the class and method. Code `IOC_METHOD_NOT_IMPLEMENTED`. */
|
|
4
|
+
export class MethodNotImplementedError extends ContainerError {
|
|
5
|
+
name = 'MethodNotImplementedError';
|
|
6
|
+
readonly code = 'IOC_METHOD_NOT_IMPLEMENTED';
|
|
7
|
+
|
|
8
|
+
constructor(message?: string) {
|
|
9
|
+
super(message);
|
|
10
|
+
|
|
11
|
+
Object.setPrototypeOf(this, MethodNotImplementedError.prototype);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { ContainerError } from './ContainerError';
|
|
2
|
+
|
|
3
|
+
/** Thrown when a provider of a disposed scope is used. Code `IOC_PROVIDER_DISPOSED`. */
|
|
4
|
+
export class ProviderDisposedError extends ContainerError {
|
|
5
|
+
name = 'ProviderDisposedError';
|
|
6
|
+
readonly code = 'IOC_PROVIDER_DISPOSED';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @throws {ProviderDisposedError} when `isTrue` is falsy.
|
|
10
|
+
*/
|
|
11
|
+
static assert(isTrue: boolean, failMessage: string) {
|
|
12
|
+
if (!isTrue) {
|
|
13
|
+
throw new ProviderDisposedError(failMessage);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
constructor(message: string) {
|
|
18
|
+
super(message);
|
|
19
|
+
|
|
20
|
+
Object.setPrototypeOf(this, ProviderDisposedError.prototype);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { ContainerError } from './ContainerError';
|
|
2
|
+
|
|
3
|
+
/** Thrown when subscribing to an event of a disposed scope. Code `IOC_EVENT_DISPOSED`. */
|
|
4
|
+
export class TypedEventDisposedError extends ContainerError {
|
|
5
|
+
name = 'TypedEventDisposedError';
|
|
6
|
+
readonly code = 'IOC_EVENT_DISPOSED';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @throws {TypedEventDisposedError} when `isTrue` is falsy.
|
|
10
|
+
*/
|
|
11
|
+
static assert(isTrue: boolean, failMessage: string) {
|
|
12
|
+
if (!isTrue) {
|
|
13
|
+
throw new TypedEventDisposedError(failMessage);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
constructor(message: string) {
|
|
18
|
+
super(message);
|
|
19
|
+
|
|
20
|
+
Object.setPrototypeOf(this, TypedEventDisposedError.prototype);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ContainerError } from './ContainerError';
|
|
2
|
+
|
|
3
|
+
/** Thrown by `toToken` for a value that is not a token, key, class or function. Code `IOC_UNSUPPORTED_TOKEN_TYPE`. */
|
|
4
|
+
export class UnsupportedTokenTypeError extends ContainerError {
|
|
5
|
+
name = 'UnsupportedTokenTypeError';
|
|
6
|
+
readonly code = 'IOC_UNSUPPORTED_TOKEN_TYPE';
|
|
7
|
+
|
|
8
|
+
constructor(message: string) {
|
|
9
|
+
super(message);
|
|
10
|
+
|
|
11
|
+
Object.setPrototypeOf(this, UnsupportedTokenTypeError.prototype);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import type { ExecutionContext } from '../ExecutionContext';
|
|
2
|
+
import { resolveConstructor } from '../metadata/target';
|
|
3
|
+
import { type Task } from '../utils/task';
|
|
4
|
+
import { type Instance } from '../utils/basic';
|
|
5
|
+
import { memoize } from '../utils/memoize';
|
|
6
|
+
import { getHooks, hasHooks, type HookFn, toHookFn } from './hook';
|
|
7
|
+
import { createHookExecutionContext, type CreateHookExecutionContext, type IHookContext } from './HookContext';
|
|
8
|
+
|
|
9
|
+
/** Transforms each hook context before it is handed to its hook. */
|
|
10
|
+
export type MapHookExecutionContext = (context: IHookContext) => IHookContext;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* One decorated member's hook, resolved to a function and bound to the context
|
|
14
|
+
* it runs against — the model the library hands back, and the whole of what it
|
|
15
|
+
* says about a hook. The member it came from is `context.methodName`.
|
|
16
|
+
* Performing it is the caller's business (ADR 0016).
|
|
17
|
+
*/
|
|
18
|
+
export type HookAction = {
|
|
19
|
+
hook: HookFn;
|
|
20
|
+
context: IHookContext;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Shapes what is collected: how each action's hook context is built, and which
|
|
25
|
+
* members take part. A collector carries defaults for all three, so a call
|
|
26
|
+
* names only what it overrides.
|
|
27
|
+
*/
|
|
28
|
+
export type HookCollectorOptions = {
|
|
29
|
+
createExecutionContext?: CreateHookExecutionContext;
|
|
30
|
+
mapExecutionContext?: MapHookExecutionContext;
|
|
31
|
+
predicate?: (methodName: string) => boolean;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/** What one collection runs in: the scope, plus any per-call overrides of the collector's options. */
|
|
35
|
+
export type HookCollectionContext = ExecutionContext & HookCollectorOptions;
|
|
36
|
+
|
|
37
|
+
/** Options of `new HookCollector(...)`. */
|
|
38
|
+
export type HookCollectorProps = HookCollectorOptions & {
|
|
39
|
+
/** The hook key this collector reads: `onConstruct`, `onScopeDisposed`, `onResolved`, or a custom one. */
|
|
40
|
+
key: string | symbol;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Reads the hooks declared under one key off a target and hands them back as
|
|
45
|
+
* {@link HookAction}s, in declaration order. It runs nothing: what order the
|
|
46
|
+
* actions are performed in, what is awaited and where a failure goes are
|
|
47
|
+
* answered by the caller (ADR 0016).
|
|
48
|
+
*
|
|
49
|
+
* Wire it to whichever event should collect — the injector's `onConstructed`,
|
|
50
|
+
* the scope's `scopeDisposed`, a provider's `onResolved` — the library ships no
|
|
51
|
+
* module which does that for you (ADR 0018).
|
|
52
|
+
*
|
|
53
|
+
* ```typescript
|
|
54
|
+
* const collector = new HookCollector({ key: 'onStart' });
|
|
55
|
+
*
|
|
56
|
+
* container.getInjector().onConstructed((instance, scope) => {
|
|
57
|
+
* runInOrder(collector.getActions(instance, { scope }).map(toTask))?.catch(report);
|
|
58
|
+
* });
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
export class HookCollector {
|
|
62
|
+
private readonly key: string | symbol;
|
|
63
|
+
private readonly options: Required<HookCollectorOptions>;
|
|
64
|
+
// Hook metadata is fixed once a class is defined - decorators have all run by the time
|
|
65
|
+
// there is an instance to read them off - so the merge runs once per class and key.
|
|
66
|
+
private readonly hooksOf = memoize(getHooks);
|
|
67
|
+
|
|
68
|
+
constructor({
|
|
69
|
+
key,
|
|
70
|
+
createExecutionContext = createHookExecutionContext,
|
|
71
|
+
mapExecutionContext = (context) => context,
|
|
72
|
+
predicate = () => true,
|
|
73
|
+
}: HookCollectorProps) {
|
|
74
|
+
this.key = key;
|
|
75
|
+
this.options = { createExecutionContext, mapExecutionContext, predicate };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
hasHooks(target: Instance): boolean {
|
|
79
|
+
return hasHooks(target, this.key);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** The actions `target` declares under this collector's key — empty when it declares none. */
|
|
83
|
+
getActions(
|
|
84
|
+
target: Instance,
|
|
85
|
+
{
|
|
86
|
+
scope,
|
|
87
|
+
createExecutionContext = this.options.createExecutionContext,
|
|
88
|
+
mapExecutionContext = this.options.mapExecutionContext,
|
|
89
|
+
predicate = this.options.predicate,
|
|
90
|
+
}: HookCollectionContext,
|
|
91
|
+
): HookAction[] {
|
|
92
|
+
const actions: HookAction[] = [];
|
|
93
|
+
for (const [methodName, fn] of this.hooksOf(resolveConstructor(target), this.key)) {
|
|
94
|
+
if (predicate(methodName)) {
|
|
95
|
+
actions.push({
|
|
96
|
+
hook: toHookFn(fn),
|
|
97
|
+
context: mapExecutionContext(createExecutionContext(target, scope, methodName)),
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return actions;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** The {@link Task} an action runs as: `runInOrder(actions.map(toTask))`, `runAtOnce(actions.map(toTask))`. */
|
|
106
|
+
export const toTask =
|
|
107
|
+
({ hook, context }: HookAction): Task =>
|
|
108
|
+
() =>
|
|
109
|
+
hook(context);
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { IContainer } from '../container/IContainer';
|
|
2
|
+
|
|
3
|
+
import { type Instance } from '../utils/basic';
|
|
4
|
+
import { resolveArgs } from '../injector/MetadataInjector';
|
|
5
|
+
import { type InjectFn } from './hook';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* What a hook receives: the `instance`, the `scope` and the decorated member's
|
|
9
|
+
* name. `invokeMethod({ args: resolveArgs() })` calls the member with its
|
|
10
|
+
* `@inject` parameters; `setProperty(fn)` assigns it (see `injectProp`).
|
|
11
|
+
*/
|
|
12
|
+
export interface IHookContext {
|
|
13
|
+
instance: Instance;
|
|
14
|
+
scope: IContainer;
|
|
15
|
+
methodName?: string;
|
|
16
|
+
|
|
17
|
+
resolveArgs(...args: unknown[]): unknown[];
|
|
18
|
+
|
|
19
|
+
invokeMethod(options?: { args?: unknown[] }): unknown;
|
|
20
|
+
|
|
21
|
+
setProperty(fn: InjectFn): void;
|
|
22
|
+
|
|
23
|
+
getProperty(): unknown;
|
|
24
|
+
|
|
25
|
+
setInitialArgs(...args: unknown[]): this;
|
|
26
|
+
|
|
27
|
+
getInitialArgs(): unknown[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** The default {@link IHookContext}. */
|
|
31
|
+
export class HookContext implements IHookContext {
|
|
32
|
+
private initialArgs: unknown[] = [];
|
|
33
|
+
|
|
34
|
+
constructor(
|
|
35
|
+
readonly instance: Instance,
|
|
36
|
+
readonly scope: IContainer,
|
|
37
|
+
readonly methodName?: string,
|
|
38
|
+
) {}
|
|
39
|
+
|
|
40
|
+
resolveArgs(...args: unknown[]): unknown[] {
|
|
41
|
+
return resolveArgs(
|
|
42
|
+
this.instance,
|
|
43
|
+
this.methodName,
|
|
44
|
+
)({
|
|
45
|
+
scope: this.scope,
|
|
46
|
+
args: [...this.initialArgs, ...args],
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
invokeMethod({ args = this.resolveArgs() }: { args?: unknown[] } = {}): unknown {
|
|
51
|
+
// @ts-ignore
|
|
52
|
+
return this.instance[this.methodName](...args);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
setProperty(fn: InjectFn): void {
|
|
56
|
+
// @ts-ignore
|
|
57
|
+
this.instance[this.methodName] = fn({ scope: this.scope });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
getProperty(): unknown {
|
|
61
|
+
// @ts-ignore
|
|
62
|
+
return this.instance[this.methodName];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
setInitialArgs(...args: unknown[]): this {
|
|
66
|
+
this.initialArgs = args;
|
|
67
|
+
return this;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
getInitialArgs(): unknown[] {
|
|
71
|
+
return this.initialArgs;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Builds the context a hook receives. */
|
|
76
|
+
export type CreateHookExecutionContext = (Target: Instance, scope: IContainer, methodName?: string) => IHookContext;
|
|
77
|
+
/** The default {@link CreateHookExecutionContext}: a {@link HookContext}. */
|
|
78
|
+
export const createHookExecutionContext: CreateHookExecutionContext = (Target, scope, methodName = 'constructor') =>
|
|
79
|
+
new HookContext(Target, scope, methodName);
|
|
80
|
+
|
|
81
|
+
/** A {@link CreateHookExecutionContext} whose contexts start with `args` as initial args. */
|
|
82
|
+
export const createHookContextFactory =
|
|
83
|
+
({ args = [] }: { args?: unknown[] } = {}): CreateHookExecutionContext =>
|
|
84
|
+
(Target, scope, methodName) =>
|
|
85
|
+
createHookExecutionContext(Target, scope, methodName).setInitialArgs(...args);
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { IContainer } from '../container/IContainer';
|
|
2
|
+
import { runAtOnce, runInOrder } from '../utils/task';
|
|
3
|
+
import { type HookFn, type HookType, toHookFn } from './hook';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A provider hook narrowed to the dependencies that can carry hook metadata.
|
|
7
|
+
*/
|
|
8
|
+
export type ResolvedObjectHook = (dependency: object, scope: IContainer) => void;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Combines hooks into one which runs them in declaration order, staying
|
|
12
|
+
* synchronous until one returns a promise and awaiting the rest from that point.
|
|
13
|
+
*
|
|
14
|
+
* A member carries a single hook, so this is how several are declared together:
|
|
15
|
+
* `@hook('onConstruct', sequential(validate, persist))`.
|
|
16
|
+
*/
|
|
17
|
+
export const sequential = (...hooks: HookType[]): HookFn => {
|
|
18
|
+
const fns = hooks.map(toHookFn);
|
|
19
|
+
return (context) => runInOrder(fns.map((fn) => () => fn(context)));
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Combines hooks into one which starts them all at once. Settles when every
|
|
24
|
+
* hook has; a fully synchronous run returns nothing, like {@link sequential}.
|
|
25
|
+
*
|
|
26
|
+
* Use it for hooks of one member which do not depend on each other:
|
|
27
|
+
* `@hook('onScopeDisposed', parallel(flush, closeSocket))`.
|
|
28
|
+
*/
|
|
29
|
+
export const parallel = (...hooks: HookType[]): HookFn => {
|
|
30
|
+
const fns = hooks.map(toHookFn);
|
|
31
|
+
return (context) => runAtOnce(fns.map((fn) => () => fn(context)));
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Wraps a hook so it runs a single time per instance, however often the event
|
|
36
|
+
* it is declared under fires. Composes with the combinators above —
|
|
37
|
+
* `oncePerInstance(sequential(connect, warmUp))` runs the whole sequence once.
|
|
38
|
+
*/
|
|
39
|
+
export const oncePerInstance = (execute: HookType): HookFn => {
|
|
40
|
+
const invokedInstances = new WeakSet<object>();
|
|
41
|
+
const fn = toHookFn(execute);
|
|
42
|
+
|
|
43
|
+
return (context) => {
|
|
44
|
+
if (invokedInstances.has(context.instance)) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
invokedInstances.add(context.instance);
|
|
49
|
+
return fn(context);
|
|
50
|
+
};
|
|
51
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { type IHookContext } from './HookContext';
|
|
2
|
+
import { type constructor, type Instance, Is } from '../utils/basic';
|
|
3
|
+
import { resolveConstructor } from '../metadata/target';
|
|
4
|
+
import { getConstructorChain } from '../utils/getConstructorChain';
|
|
5
|
+
import { ProviderOptions } from '../provider/IProvider';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* What `@inject(...)` and `injectProp(...)` take: a function of the resolution
|
|
9
|
+
* context returning the value to inject. Usually built with `by(...)` or `arg(...)`.
|
|
10
|
+
*/
|
|
11
|
+
export type InjectFn<T = unknown> = (options: ProviderOptions) => T;
|
|
12
|
+
|
|
13
|
+
/** A hook as a function of its context. Must return `void` or `Promise<void>`. */
|
|
14
|
+
export type HookFn<T extends IHookContext = IHookContext> = (context: T) => void | Promise<void>;
|
|
15
|
+
|
|
16
|
+
/** A hook as a class resolved from the scope; its `execute` receives the context. */
|
|
17
|
+
export interface HookClass<T extends IHookContext = IHookContext> {
|
|
18
|
+
execute(context: Omit<T, 'scope'>): void | Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Anything `@hook(...)` accepts: a {@link HookFn} or a {@link HookClass} constructor. */
|
|
22
|
+
export type HookType<T extends IHookContext = IHookContext> = HookFn<T> | constructor<HookClass<T>>;
|
|
23
|
+
|
|
24
|
+
/** The hooks of one class under one key, by member name: one hook per member (compose with `sequential` / `parallel`). */
|
|
25
|
+
export type HooksOfClass = Map<string, HookType>;
|
|
26
|
+
|
|
27
|
+
const isHookClassConstructor = <C extends IHookContext>(
|
|
28
|
+
execute: HookFn<C> | constructor<HookClass<C>>,
|
|
29
|
+
): execute is constructor<HookClass<C>> => {
|
|
30
|
+
return Is.constructor(execute) && execute.prototype.execute;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/** Normalizes a {@link HookType} to a {@link HookFn}; a hook class is resolved from the context's scope. */
|
|
34
|
+
export const toHookFn = <C extends IHookContext>(execute: HookFn<C> | constructor<HookClass<C>>): HookFn<C> =>
|
|
35
|
+
isHookClassConstructor(execute) ? (context) => context.scope.resolve(execute).execute(context) : execute;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Reads the hooks declared under `key`, merging hooks declared on parent classes.
|
|
39
|
+
* Hooks are collected from base to derived, so a derived class's hook for the
|
|
40
|
+
* same member replaces the parent's.
|
|
41
|
+
*
|
|
42
|
+
* `target` is an instance or its class, a proxy of either included - it is
|
|
43
|
+
* normalized by `resolveConstructor`, so callers never unwrap it themselves.
|
|
44
|
+
*
|
|
45
|
+
* The merge runs on every call and returns a fresh map. Hook metadata is fixed
|
|
46
|
+
* once a class is defined, so a caller which reads it often can wrap this in
|
|
47
|
+
* `memoize` (`HookCollector` already does).
|
|
48
|
+
*/
|
|
49
|
+
export function getHooks(target: Instance | constructor<unknown>, key: string | symbol): HooksOfClass {
|
|
50
|
+
const merged: HooksOfClass = new Map();
|
|
51
|
+
for (const ctor of getConstructorChain(resolveConstructor(target)).reverse()) {
|
|
52
|
+
const ownHooks: HooksOfClass | undefined = Reflect.getOwnMetadata(key, ctor);
|
|
53
|
+
if (ownHooks) {
|
|
54
|
+
for (const [methodName, fn] of ownHooks) {
|
|
55
|
+
merged.set(methodName, fn);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return merged;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Whether `target` (an instance or class, proxies included) declares any hook under `key`. */
|
|
63
|
+
export function hasHooks(target: Instance | constructor<unknown>, key: string | symbol): boolean {
|
|
64
|
+
return getConstructorChain(resolveConstructor(target)).some((ctor) => Reflect.hasOwnMetadata(key, ctor));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Method/property decorator that declares a hook under `key`. The library only
|
|
69
|
+
* stores it: collect with `new HookCollector({ key })` and run the actions
|
|
70
|
+
* yourself. A member carries exactly one hook per key - decorating it twice
|
|
71
|
+
* replaces the earlier hook; compose several with `sequential(...)` / `parallel(...)`.
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* const onInit = (fn: HookType) => hook('onInit', fn);
|
|
75
|
+
*
|
|
76
|
+
* class Service {
|
|
77
|
+
* @onInit((ctx) => {
|
|
78
|
+
* ctx.invokeMethod({ args: ctx.resolveArgs() });
|
|
79
|
+
* })
|
|
80
|
+
* init() {}
|
|
81
|
+
* }
|
|
82
|
+
*/
|
|
83
|
+
export const hook = (key: string | symbol, fn: HookType) => (target: object, propertyKey: string | symbol) => {
|
|
84
|
+
const hooks: HooksOfClass = Reflect.hasOwnMetadata(key, target.constructor)
|
|
85
|
+
? Reflect.getOwnMetadata(key, target.constructor)
|
|
86
|
+
: new Map();
|
|
87
|
+
hooks.set(propertyKey as string, fn);
|
|
88
|
+
Reflect.defineMetadata(key, hooks, target.constructor);
|
|
89
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { HookFn, InjectFn } from './hook';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Injects a dependency into the decorated property.
|
|
5
|
+
*
|
|
6
|
+
* `fn` is an `InjectFn`, exactly as for `@inject`: it receives the resolution
|
|
7
|
+
* context and returns the value to assign, so `injectProp(({ scope }) =>
|
|
8
|
+
* scope.resolve('key'))` assigns the dependency and `injectProp(pipe(fn, sanitize()))`
|
|
9
|
+
* a mapped one.
|
|
10
|
+
*/
|
|
11
|
+
export const injectProp =
|
|
12
|
+
<T>(fn: InjectFn<T>): HookFn =>
|
|
13
|
+
(context) =>
|
|
14
|
+
context.setProperty(fn);
|