@tachybase/di 1.3.43
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/LICENSE +201 -0
- package/lib/container-instance.class.d.ts +111 -0
- package/lib/container-instance.class.js +343 -0
- package/lib/container-registry.class.d.ts +51 -0
- package/lib/container-registry.class.js +95 -0
- package/lib/decorators/inject-many.decorator.d.ts +8 -0
- package/lib/decorators/inject-many.decorator.js +56 -0
- package/lib/decorators/inject.decorator.d.ts +9 -0
- package/lib/decorators/inject.decorator.js +56 -0
- package/lib/decorators/service.decorator.d.ts +6 -0
- package/lib/decorators/service.decorator.js +49 -0
- package/lib/decorators.d.ts +16 -0
- package/lib/decorators.js +86 -0
- package/lib/empty.const.d.ts +6 -0
- package/lib/empty.const.js +27 -0
- package/lib/error/cannot-inject-value.error.d.ts +11 -0
- package/lib/error/cannot-inject-value.error.js +40 -0
- package/lib/error/cannot-instantiate-value.error.d.ts +11 -0
- package/lib/error/cannot-instantiate-value.error.js +49 -0
- package/lib/error/service-not-found.error.d.ts +11 -0
- package/lib/error/service-not-found.error.js +49 -0
- package/lib/index.d.ts +15 -0
- package/lib/index.js +50 -0
- package/lib/interfaces/container-options.interface.d.ts +45 -0
- package/lib/interfaces/container-options.interface.js +15 -0
- package/lib/interfaces/handler.interface.d.ts +27 -0
- package/lib/interfaces/handler.interface.js +15 -0
- package/lib/interfaces/service-metadata.interface.d.ts +53 -0
- package/lib/interfaces/service-metadata.interface.js +15 -0
- package/lib/interfaces/service-options.interface.d.ts +6 -0
- package/lib/interfaces/service-options.interface.js +15 -0
- package/lib/token.class.d.ts +11 -0
- package/lib/token.class.js +37 -0
- package/lib/types/abstract-constructable.type.d.ts +9 -0
- package/lib/types/abstract-constructable.type.js +15 -0
- package/lib/types/container-identifier.type.d.ts +4 -0
- package/lib/types/container-identifier.type.js +15 -0
- package/lib/types/container-scope.type.d.ts +1 -0
- package/lib/types/container-scope.type.js +15 -0
- package/lib/types/service-identifier.type.d.ts +8 -0
- package/lib/types/service-identifier.type.js +15 -0
- package/lib/utils/resolve-to-type-wrapper.util.d.ts +15 -0
- package/lib/utils/resolve-to-type-wrapper.util.js +39 -0
- package/package.json +11 -0
- package/src/container-instance.class.ts +487 -0
- package/src/container-registry.class.ts +92 -0
- package/src/decorators/inject-many.decorator.ts +48 -0
- package/src/decorators/inject.decorator.ts +46 -0
- package/src/decorators/service.decorator.ts +34 -0
- package/src/decorators.ts +70 -0
- package/src/empty.const.ts +6 -0
- package/src/error/cannot-inject-value.error.ts +22 -0
- package/src/error/cannot-instantiate-value.error.ts +34 -0
- package/src/error/service-not-found.error.ts +33 -0
- package/src/index.ts +21 -0
- package/src/interfaces/container-options.interface.ts +48 -0
- package/src/interfaces/handler.interface.ts +32 -0
- package/src/interfaces/service-metadata.interface.ts +62 -0
- package/src/interfaces/service-options.interface.ts +10 -0
- package/src/token.class.ts +11 -0
- package/src/types/abstract-constructable.type.ts +7 -0
- package/src/types/container-identifier.type.ts +4 -0
- package/src/types/container-scope.type.ts +1 -0
- package/src/types/service-identifier.type.ts +15 -0
- package/src/utils/resolve-to-type-wrapper.util.ts +44 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Constructable } from '@tachybase/utils';
|
|
2
|
+
|
|
3
|
+
import { ContainerInstance } from '../container-instance.class';
|
|
4
|
+
import { EMPTY_VALUE } from '../empty.const';
|
|
5
|
+
import { ServiceMetadata } from '../interfaces/service-metadata.interface';
|
|
6
|
+
import { ServiceOptions } from '../interfaces/service-options.interface';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Marks class as a service that can be injected using Container.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export function Service<T = unknown>(): Function;
|
|
13
|
+
export function Service<T = unknown>(options: ServiceOptions<T>): Function;
|
|
14
|
+
export function Service<T>(options: ServiceOptions<T> = {}) {
|
|
15
|
+
return (target: Constructable<T>, context: ClassDecoratorContext) => {
|
|
16
|
+
const serviceMetadata: ServiceMetadata<T> = {
|
|
17
|
+
id: options.id || target,
|
|
18
|
+
type: target,
|
|
19
|
+
factory: (options as any).factory || undefined,
|
|
20
|
+
multiple: options.multiple || false,
|
|
21
|
+
eager: options.eager || false,
|
|
22
|
+
scope: options.scope || 'container',
|
|
23
|
+
referencedBy: new Map().set(ContainerInstance.default.id, ContainerInstance.default),
|
|
24
|
+
value: EMPTY_VALUE,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
((context.metadata.injects as any[]) || []).forEach((inject) => {
|
|
28
|
+
inject(target);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
ContainerInstance.default.set(serviceMetadata);
|
|
32
|
+
return target;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Constructable } from '@tachybase/utils';
|
|
2
|
+
|
|
3
|
+
import { Container } from './container-instance.class';
|
|
4
|
+
import { Inject } from './decorators/inject.decorator';
|
|
5
|
+
import { Service } from './decorators/service.decorator';
|
|
6
|
+
|
|
7
|
+
export interface ActionDef {
|
|
8
|
+
type: string;
|
|
9
|
+
resourceName?: string;
|
|
10
|
+
actionName?: string;
|
|
11
|
+
method?: string;
|
|
12
|
+
options?: {
|
|
13
|
+
acl?: 'loggedIn' | 'public' | 'private';
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// init actions
|
|
18
|
+
Container.set({ id: 'actions', value: new Map<Function, ActionDef[]>() });
|
|
19
|
+
|
|
20
|
+
export function App() {
|
|
21
|
+
return Inject('app');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function Db() {
|
|
25
|
+
return Inject('db');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function InjectLog() {
|
|
29
|
+
return Inject('logger');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function Controller(name: string) {
|
|
33
|
+
return function (target: any, context: ClassDecoratorContext) {
|
|
34
|
+
const serviceOptions = { id: 'controller', multiple: true };
|
|
35
|
+
Service(serviceOptions)(target, context);
|
|
36
|
+
const actions = Container.get('actions') as Map<Function, ActionDef[]>;
|
|
37
|
+
if (!actions.has(target)) {
|
|
38
|
+
actions.set(target, []);
|
|
39
|
+
}
|
|
40
|
+
actions.get(target).push({
|
|
41
|
+
type: 'resource',
|
|
42
|
+
resourceName: name,
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function Action(
|
|
48
|
+
name: string,
|
|
49
|
+
options?: {
|
|
50
|
+
acl?: 'loggedIn' | 'public' | 'private';
|
|
51
|
+
},
|
|
52
|
+
) {
|
|
53
|
+
return function (_: any, context: ClassMethodDecoratorContext) {
|
|
54
|
+
if (!context.metadata.injects) {
|
|
55
|
+
context.metadata.injects = [];
|
|
56
|
+
}
|
|
57
|
+
(context.metadata.injects as any[]).push((target: Constructable<unknown>) => {
|
|
58
|
+
const actions = Container.get('actions') as Map<Function, ActionDef[]>;
|
|
59
|
+
if (!actions.has(target)) {
|
|
60
|
+
actions.set(target, []);
|
|
61
|
+
}
|
|
62
|
+
actions.get(target).push({
|
|
63
|
+
type: 'action',
|
|
64
|
+
method: String(context.name),
|
|
65
|
+
actionName: name,
|
|
66
|
+
options: options || { acl: 'private' },
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Constructable } from '@tachybase/utils';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Thrown when DI cannot inject value into property decorated by @Inject decorator.
|
|
5
|
+
*/
|
|
6
|
+
export class CannotInjectValueError extends Error {
|
|
7
|
+
public name = 'CannotInjectValueError';
|
|
8
|
+
|
|
9
|
+
get message(): string {
|
|
10
|
+
return (
|
|
11
|
+
`Cannot inject value into "${this.target.constructor.name}.${this.propertyName}". ` +
|
|
12
|
+
`Please make sure you setup reflect-metadata properly and you don't use interfaces without service tokens as injection value.`
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
constructor(
|
|
17
|
+
private target: Constructable<unknown>,
|
|
18
|
+
private propertyName: string,
|
|
19
|
+
) {
|
|
20
|
+
super();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Token } from '../token.class';
|
|
2
|
+
import { ServiceIdentifier } from '../types/service-identifier.type';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Thrown when DI cannot inject value into property decorated by @Inject decorator.
|
|
6
|
+
*/
|
|
7
|
+
export class CannotInstantiateValueError extends Error {
|
|
8
|
+
public name = 'CannotInstantiateValueError';
|
|
9
|
+
|
|
10
|
+
/** Normalized identifier name used in the error message. */
|
|
11
|
+
private normalizedIdentifier = '<UNKNOWN_IDENTIFIER>';
|
|
12
|
+
|
|
13
|
+
get message(): string {
|
|
14
|
+
return (
|
|
15
|
+
`Cannot instantiate the requested value for the "${this.normalizedIdentifier}" identifier. ` +
|
|
16
|
+
`The related metadata doesn't contain a factory or a type to instantiate.`
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
constructor(identifier: ServiceIdentifier) {
|
|
21
|
+
super();
|
|
22
|
+
|
|
23
|
+
// TODO: Extract this to a helper function and share between this and NotFoundError.
|
|
24
|
+
if (typeof identifier === 'string') {
|
|
25
|
+
this.normalizedIdentifier = identifier;
|
|
26
|
+
} else if (identifier instanceof Token) {
|
|
27
|
+
this.normalizedIdentifier = `Token<${identifier.name || 'UNSET_NAME'}>`;
|
|
28
|
+
} else if (identifier && (identifier.name || identifier.prototype?.name)) {
|
|
29
|
+
this.normalizedIdentifier = identifier.name
|
|
30
|
+
? `MaybeConstructable<${identifier.name}>`
|
|
31
|
+
: `MaybeConstructable<${(identifier.prototype as { name: string })?.name}>`;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Token } from '../token.class';
|
|
2
|
+
import { ServiceIdentifier } from '../types/service-identifier.type';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Thrown when requested service was not found.
|
|
6
|
+
*/
|
|
7
|
+
export class ServiceNotFoundError extends Error {
|
|
8
|
+
public name = 'ServiceNotFoundError';
|
|
9
|
+
|
|
10
|
+
/** Normalized identifier name used in the error message. */
|
|
11
|
+
private normalizedIdentifier = '<UNKNOWN_IDENTIFIER>';
|
|
12
|
+
|
|
13
|
+
get message(): string {
|
|
14
|
+
return (
|
|
15
|
+
`Service with "${this.normalizedIdentifier}" identifier was not found in the container. ` +
|
|
16
|
+
`Register it before usage via explicitly calling the "Container.set" function or using the "@Service()" decorator.`
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
constructor(identifier: ServiceIdentifier) {
|
|
21
|
+
super();
|
|
22
|
+
|
|
23
|
+
if (typeof identifier === 'string') {
|
|
24
|
+
this.normalizedIdentifier = identifier;
|
|
25
|
+
} else if (identifier instanceof Token) {
|
|
26
|
+
this.normalizedIdentifier = `Token<${identifier.name || 'UNSET_NAME'}>`;
|
|
27
|
+
} else if (identifier && (identifier.name || identifier.prototype?.name)) {
|
|
28
|
+
this.normalizedIdentifier = identifier.name
|
|
29
|
+
? `MaybeConstructable<${identifier.name}>`
|
|
30
|
+
: `MaybeConstructable<${(identifier.prototype as { name: string })?.name}>`;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Container } from './container-instance.class';
|
|
2
|
+
|
|
3
|
+
export * from './decorators/inject-many.decorator';
|
|
4
|
+
export * from './decorators/inject.decorator';
|
|
5
|
+
export * from './decorators/service.decorator';
|
|
6
|
+
|
|
7
|
+
export * from './error/cannot-inject-value.error';
|
|
8
|
+
export * from './error/cannot-instantiate-value.error';
|
|
9
|
+
export * from './error/service-not-found.error';
|
|
10
|
+
|
|
11
|
+
export type { Handler } from './interfaces/handler.interface';
|
|
12
|
+
export type { ServiceMetadata } from './interfaces/service-metadata.interface';
|
|
13
|
+
export type { ServiceOptions } from './interfaces/service-options.interface';
|
|
14
|
+
export type { ServiceIdentifier } from './types/service-identifier.type';
|
|
15
|
+
|
|
16
|
+
export { ContainerInstance, Container } from './container-instance.class';
|
|
17
|
+
export { Token } from './token.class';
|
|
18
|
+
|
|
19
|
+
export default Container;
|
|
20
|
+
|
|
21
|
+
export * from './decorators';
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export interface ContainerOptions {
|
|
2
|
+
/**
|
|
3
|
+
* Controls the behavior when a service is already registered with the same ID. The following values are allowed:
|
|
4
|
+
*
|
|
5
|
+
* - `throw` - a `ContainerCannotBeCreatedError` error is raised
|
|
6
|
+
* - `overwrite` - the previous container is disposed and a new one is created
|
|
7
|
+
* - `returnExisting` - returns the existing container or raises a `ContainerCannotBeCreatedError` error if the
|
|
8
|
+
* specified options differ from the options of the existing container
|
|
9
|
+
*
|
|
10
|
+
* The default value is `returnExisting`.
|
|
11
|
+
*/
|
|
12
|
+
onConflict: 'throw' | 'overwrite' | 'returnExisting';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Controls the behavior when a requested type doesn't exists in the current container. The following values are allowed:
|
|
16
|
+
*
|
|
17
|
+
* - `allowLookup` - the parent container will be checked for the dependency
|
|
18
|
+
* - `localOnly` - a `ServiceNotFoundError` error is raised
|
|
19
|
+
*
|
|
20
|
+
* The default value is `allowLookup`.
|
|
21
|
+
*/
|
|
22
|
+
lookupStrategy: 'allowLookup' | 'localOnly';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Enables the lookup for global (singleton) services before checking in the current container. By default every
|
|
26
|
+
* type is first checked in the default container to return singleton services. This check bypasses the lookup strategy,
|
|
27
|
+
* set in the container so if this behavior is not desired it can be disabled via this flag.
|
|
28
|
+
*
|
|
29
|
+
* The default value is `true`.
|
|
30
|
+
*/
|
|
31
|
+
allowSingletonLookup: boolean;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Controls how the child container inherits the service definitions from it's parent. The following values are allowed:
|
|
35
|
+
*
|
|
36
|
+
* - `none` - no metadata is inherited
|
|
37
|
+
* - `definitionOnly` - only metadata is inherited, a new instance will be created for each class
|
|
38
|
+
* - eager classes created as soon as the container is created
|
|
39
|
+
* - non-eager classes are created the first time they are requested
|
|
40
|
+
* - `definitionWithValues` - both metadata and service instances are inherited
|
|
41
|
+
* - when parent class is disposed the instances in this container are preserved
|
|
42
|
+
* - if a service is registered but not created yet, it will be shared when created between the two container
|
|
43
|
+
* - newly registered services won't be shared between the two container
|
|
44
|
+
*
|
|
45
|
+
* The default value is `none`.
|
|
46
|
+
*/
|
|
47
|
+
inheritanceStrategy: 'none' | 'definitionOnly' | 'definitionWithValues';
|
|
48
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Constructable } from '@tachybase/utils';
|
|
2
|
+
|
|
3
|
+
import { ContainerInstance } from '../container-instance.class';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Used to register special "handler" which will be executed on a service class during its initialization.
|
|
7
|
+
* It can be used to create custom decorators and set/replace service class properties and constructor parameters.
|
|
8
|
+
*/
|
|
9
|
+
export interface Handler<T = unknown> {
|
|
10
|
+
/**
|
|
11
|
+
* Service object used to apply handler to.
|
|
12
|
+
*/
|
|
13
|
+
object: Constructable<T>;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Class property name to set/replace value of.
|
|
17
|
+
* Used if handler is applied on a class property.
|
|
18
|
+
*/
|
|
19
|
+
propertyName?: string;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Parameter index to set/replace value of.
|
|
23
|
+
* Used if handler is applied on a constructor parameter.
|
|
24
|
+
*/
|
|
25
|
+
index?: number;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Factory function that produces value that will be set to class property or constructor parameter.
|
|
29
|
+
* Accepts container instance which requested the value.
|
|
30
|
+
*/
|
|
31
|
+
value: (container: ContainerInstance) => any;
|
|
32
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Constructable } from '@tachybase/utils';
|
|
2
|
+
|
|
3
|
+
import { ContainerInstance } from '../container-instance.class';
|
|
4
|
+
import { ContainerIdentifier } from '../types/container-identifier.type';
|
|
5
|
+
import { ContainerScope } from '../types/container-scope.type';
|
|
6
|
+
import { ServiceIdentifier } from '../types/service-identifier.type';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Service metadata is used to initialize service and store its state.
|
|
10
|
+
*/
|
|
11
|
+
export interface ServiceMetadata<Type = unknown> {
|
|
12
|
+
/** Unique identifier of the referenced service. */
|
|
13
|
+
id: ServiceIdentifier;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The injection scope for the service.
|
|
17
|
+
* - a `singleton` service always will be created in the default container regardless of who registering it
|
|
18
|
+
* - a `container` scoped service will be created once when requested from the given container
|
|
19
|
+
* - a `transient` service will be created each time it is requested
|
|
20
|
+
*/
|
|
21
|
+
scope: ContainerScope;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Class definition of the service what is used to initialize given service.
|
|
25
|
+
* This property maybe null if the value of the service is set manually.
|
|
26
|
+
* If id is not set then it serves as service id.
|
|
27
|
+
*/
|
|
28
|
+
type: Constructable<Type> | null;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Factory function used to initialize this service.
|
|
32
|
+
* Can be regular function ("createCar" for example),
|
|
33
|
+
* or other service which produces this instance ([CarFactory, "createCar"] for example).
|
|
34
|
+
*/
|
|
35
|
+
factory: [Constructable<unknown>, string] | CallableFunction | undefined;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Instance of the target class.
|
|
39
|
+
*/
|
|
40
|
+
value: unknown | symbol;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Allows to setup multiple instances the different classes under a single service id string or token.
|
|
44
|
+
*/
|
|
45
|
+
multiple: boolean;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Indicates whether a new instance should be created as soon as the class is registered.
|
|
49
|
+
* By default the registered classes are only instantiated when they are requested from the container.
|
|
50
|
+
*
|
|
51
|
+
* _Note: This option is ignored for transient services._
|
|
52
|
+
*/
|
|
53
|
+
eager: boolean;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Map of containers referencing this metadata. This is used when a container
|
|
57
|
+
* is inheriting it's parents definitions and values to track the lifecycle of
|
|
58
|
+
* the metadata. Namely, a service can be disposed only if it's only referenced
|
|
59
|
+
* by the container being disposed.
|
|
60
|
+
*/
|
|
61
|
+
referencedBy: Map<ContainerIdentifier, ContainerInstance>;
|
|
62
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ServiceMetadata } from './service-metadata.interface';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The public ServiceOptions is partial object of ServiceMetadata and either one
|
|
5
|
+
* of the following is set: `type`, `factory`, `value` but not more than one.
|
|
6
|
+
*/
|
|
7
|
+
export type ServiceOptions<T = unknown> =
|
|
8
|
+
| Omit<Partial<ServiceMetadata<T>>, 'referencedBy' | 'type' | 'factory'>
|
|
9
|
+
| Omit<Partial<ServiceMetadata<T>>, 'referencedBy' | 'value' | 'factory'>
|
|
10
|
+
| Omit<Partial<ServiceMetadata<T>>, 'referencedBy' | 'value' | 'type'>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Used to create unique typed service identifier.
|
|
3
|
+
* Useful when service has only interface, but don't have a class.
|
|
4
|
+
*/
|
|
5
|
+
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
|
|
6
|
+
export class Token<T> {
|
|
7
|
+
/**
|
|
8
|
+
* @param name Token name, optional and only used for debugging purposes.
|
|
9
|
+
*/
|
|
10
|
+
constructor(public name?: string) {}
|
|
11
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic type for abstract class definitions.
|
|
3
|
+
*
|
|
4
|
+
* Explanation: This describes a newable Function with a prototype Which is
|
|
5
|
+
* what an abstract class is - no constructor, just the prototype.
|
|
6
|
+
*/
|
|
7
|
+
export type AbstractConstructable<T> = NewableFunction & { prototype: T };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type ContainerScope = 'singleton' | 'container' | 'transient';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Constructable } from '@tachybase/utils';
|
|
2
|
+
|
|
3
|
+
import { Token } from '../token.class';
|
|
4
|
+
import { AbstractConstructable } from './abstract-constructable.type';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Unique service identifier.
|
|
8
|
+
* Can be some class type, or string id, or instance of Token.
|
|
9
|
+
*/
|
|
10
|
+
export type ServiceIdentifier<T = unknown> =
|
|
11
|
+
| Constructable<T>
|
|
12
|
+
| AbstractConstructable<T>
|
|
13
|
+
| CallableFunction
|
|
14
|
+
| Token<T>
|
|
15
|
+
| string;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Constructable } from '@tachybase/utils';
|
|
2
|
+
|
|
3
|
+
import { Token } from '../token.class';
|
|
4
|
+
import { ServiceIdentifier } from '../types/service-identifier.type';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Helper function used in inject decorators to resolve the received identifier to
|
|
8
|
+
* an eager type when possible or to a lazy type when cyclic dependencies are possibly involved.
|
|
9
|
+
*
|
|
10
|
+
* @param typeOrIdentifier a service identifier or a function returning a type acting as service identifier or nothing
|
|
11
|
+
* @param target the class definition of the target of the decorator
|
|
12
|
+
* @param propertyName the name of the property in case of a PropertyDecorator
|
|
13
|
+
* @param index the index of the parameter in the constructor in case of ParameterDecorator
|
|
14
|
+
*/
|
|
15
|
+
export function resolveToTypeWrapper(
|
|
16
|
+
typeOrIdentifier: ((type?: never) => Constructable<unknown>) | ServiceIdentifier<unknown> | undefined,
|
|
17
|
+
target: object,
|
|
18
|
+
propertyName: string | symbol,
|
|
19
|
+
index?: number,
|
|
20
|
+
): { eagerType: ServiceIdentifier | null; lazyType: (type?: never) => ServiceIdentifier } {
|
|
21
|
+
/**
|
|
22
|
+
* ? We want to error out as soon as possible when looking up services to inject, however
|
|
23
|
+
* ? we cannot determine the type at decorator execution when cyclic dependencies are involved
|
|
24
|
+
* ? because calling the received `() => MyType` function right away would cause a JS error:
|
|
25
|
+
* ? "Cannot access 'MyType' before initialization", so we need to execute the function in the handler,
|
|
26
|
+
* ? when the classes are already created. To overcome this, we use a wrapper:
|
|
27
|
+
* ? - the lazyType is executed in the handler so we never have a JS error
|
|
28
|
+
* ? - the eagerType is checked when decorator is running and an error is raised if an unknown type is encountered
|
|
29
|
+
*/
|
|
30
|
+
let typeWrapper!: { eagerType: ServiceIdentifier | null; lazyType: (type?: never) => ServiceIdentifier };
|
|
31
|
+
|
|
32
|
+
/** If requested type is explicitly set via a string ID or token, we set it explicitly. */
|
|
33
|
+
if ((typeOrIdentifier && typeof typeOrIdentifier === 'string') || typeOrIdentifier instanceof Token) {
|
|
34
|
+
typeWrapper = { eagerType: typeOrIdentifier, lazyType: () => typeOrIdentifier };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** If requested type is explicitly set via a () => MyClassType format, we set it explicitly. */
|
|
38
|
+
if (typeOrIdentifier && typeof typeOrIdentifier === 'function') {
|
|
39
|
+
/** We set eagerType to null, preventing the raising of the CannotInjectValueError in decorators. */
|
|
40
|
+
typeWrapper = { eagerType: null, lazyType: () => (typeOrIdentifier as CallableFunction)() };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return typeWrapper;
|
|
44
|
+
}
|