feat!: Update Inject Decorator for stable decorator api of typescript

Update the function and refactor the typing to reflect the property type
This commit is contained in:
2024-08-24 02:32:58 +02:00
parent 81873f3689
commit 06ae6737fd

View File

@@ -1,5 +1,6 @@
import { import {
DependencyResolutionError, DependencyResolutionError,
IdentifierRequiredError,
InitializationError, InitializationError,
InjectorError, InjectorError,
NoInstantiationMethodError, NoInstantiationMethodError,
@@ -10,17 +11,17 @@ import { InitDelegate } from '../types/InitDelegate';
/** /**
* A decorator to inject a dependency from a DI (Dependency Injection) container into a class property. * A decorator to inject a dependency from a DI (Dependency Injection) container into a class property.
* @template T The type of the dependency to be injected. * @template TargetType The type of the class to inject the dependency into.
* @template U The type of the property to be injected. * @template DependencyType The type of the dependency to be injected.
* @param identifier The identifier used to resolve the class in the DI container. * @template PropertyType The type of the property to be injected.
* @see {@link Identifier} for more information on identifiers. * @param identifier The {@link Identifier|identifier} used to resolve the dependencie in the DI container or the property name if not provided.
* @param init Optional an initializer function to transform the dependency before injection * @param init An optional initializer {@link InitDelegate|function} to transform the dependency before injection
* or true to instantiate the dependency if it has a constructor. * or true to instantiate the dependency if it has a constructor.
* @see {@link InitDelegate} for more information on initializer functions.
* @param necessary If true, throws an error if the dependency is not found. * @param necessary If true, throws an error if the dependency is not found.
* @returns The resolved dependency or undefined if the dependency is not necessary * @returns The resolved dependency or undefined if the dependency is not necessary
* and not found, or throws an error if the dependency is necessary and not found. * and not found, or throws an error if the dependency is necessary and not found.
* @throws **Only throws errors if the dependency is necessary.** * @throws **Only throws errors if the dependency is necessary.**
* @throws An {@link IdentifierRequiredError} if the identifier is not provided and the class name is not available.
* @throws A {@link DependencyResolutionError} if the dependency is not found. * @throws A {@link DependencyResolutionError} if the dependency is not found.
* @throws A {@link InjectorError} if an error occurs during the injection process. * @throws A {@link InjectorError} if an error occurs during the injection process.
* @throws A {@link NoInstantiationMethodError} if the dependency does not have a constructor. * @throws A {@link NoInstantiationMethodError} if the dependency does not have a constructor.
@@ -40,70 +41,86 @@ import { InitDelegate } from '../types/InitDelegate';
* } * }
* ``` * ```
*/ */
export function Inject<T, U>( export function Inject<TargetType, DependencyType, PropertyType>(
identifier: Identifier, identifier?: Identifier,
init?: InitDelegate<T, U> | true, init?: InitDelegate<DependencyType, PropertyType> | true,
necessary = true, necessary = true,
) { ) {
return function (target: unknown, propertyKey: string | symbol): void { return function (
constructor: undefined,
context: ClassFieldDecoratorContext<TargetType> & {
name: PropertyType;
},
): void {
const _identifier = identifier ?? context.name;
if (_identifier == null && necessary === true)
throw new IdentifierRequiredError();
/** /**
* Function to evaluate the dependency lazily * Function to evaluate the dependency lazily
* to avoid circular dependencies, not found dependencies, etc. * to avoid circular dependencies, not found dependencies, etc.
* @returns The resolved dependency or undefined if the dependency is not found. * @returns The resolved dependency or undefined if the dependency is not found.
*/ */
const resolve = (): T | undefined => { const resolve = (): DependencyType | undefined => {
return TSinjex.getInstance().resolve<T>(identifier, necessary); return TSinjex.getInstance().resolve<DependencyType>(
_identifier,
necessary,
);
}; };
Object.defineProperty(target, propertyKey, { context.addInitializer(function (this: TargetType) {
get() { Object.defineProperty(this, context.name, {
let instance: T | U | undefined; get() {
let instance: DependencyType | PropertyType | undefined;
const dependency: T | undefined = tryAndCatch( const dependency: DependencyType | undefined = tryAndCatch(
() => resolve(), () => resolve(),
necessary, necessary,
identifier, _identifier,
DependencyResolutionError, DependencyResolutionError,
); );
if (dependency != null) { if (dependency != null) {
const initFunction: (() => U) | undefined = const initFunction: (() => PropertyType) | undefined =
typeof init === 'function' && dependency != null typeof init === 'function' && dependency != null
? (): U => init(dependency) ? (): PropertyType => init(dependency)
: init === true && hasConstructor(dependency) : init === true && hasConstructor(dependency)
? (): U => new dependency() as U ? (): PropertyType =>
: undefined; new dependency() as PropertyType
: undefined;
if (init == null) instance = dependency; if (init == null) instance = dependency;
else if (initFunction != null) else if (initFunction != null)
instance = tryAndCatch( instance = tryAndCatch(
initFunction, initFunction,
necessary, necessary,
identifier, _identifier,
InitializationError, InitializationError,
); );
else if (necessary) else if (necessary)
throw new NoInstantiationMethodError(identifier); throw new NoInstantiationMethodError(_identifier);
} else if (necessary) } else if (necessary)
throw new DependencyResolutionError(identifier); throw new DependencyResolutionError(_identifier);
/**
* Replace itself with the resolved dependency
* for performance reasons.
*/
Object.defineProperty(this, context.name, {
value: instance,
writable: false,
enumerable: false,
configurable: false,
});
return instance;
},
/** /**
* Replace itself with the resolved dependency * Make the property configurable to allow replacing it
* for performance reasons.
*/ */
Object.defineProperty(this, propertyKey, { configurable: true,
value: instance, });
writable: false,
enumerable: false,
configurable: false,
});
return instance;
},
/**
* Make the property configurable to allow replacing it
*/
configurable: true,
}); });
}; };
} }