From 06ae6737fd29784923621c129515273ba9b1d15b Mon Sep 17 00:00:00 2001 From: Max P Date: Sat, 24 Aug 2024 02:32:58 +0200 Subject: [PATCH] feat!: Update `Inject` Decorator for stable decorator api of typescript Update the function and refactor the typing to reflect the property type --- src/decorators/Inject.ts | 127 ++++++++++++++++++++++----------------- 1 file changed, 72 insertions(+), 55 deletions(-) diff --git a/src/decorators/Inject.ts b/src/decorators/Inject.ts index 1dd5652..8ef6566 100644 --- a/src/decorators/Inject.ts +++ b/src/decorators/Inject.ts @@ -1,5 +1,6 @@ import { DependencyResolutionError, + IdentifierRequiredError, InitializationError, InjectorError, 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. - * @template T The type of the dependency to be injected. - * @template U The type of the property to be injected. - * @param identifier The identifier used to resolve the class in the DI container. - * @see {@link Identifier} for more information on identifiers. - * @param init Optional an initializer function to transform the dependency before injection + * @template TargetType The type of the class to inject the dependency into. + * @template DependencyType The type of the dependency to be injected. + * @template PropertyType The type of the property to be injected. + * @param identifier The {@link Identifier|identifier} used to resolve the dependencie in the DI container or the property name if not provided. + * @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. - * @see {@link InitDelegate} for more information on initializer functions. * @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 * 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 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 InjectorError} if an error occurs during the injection process. * @throws A {@link NoInstantiationMethodError} if the dependency does not have a constructor. @@ -40,70 +41,86 @@ import { InitDelegate } from '../types/InitDelegate'; * } * ``` */ -export function Inject( - identifier: Identifier, - init?: InitDelegate | true, +export function Inject( + identifier?: Identifier, + init?: InitDelegate | true, necessary = true, ) { - return function (target: unknown, propertyKey: string | symbol): void { + return function ( + constructor: undefined, + context: ClassFieldDecoratorContext & { + name: PropertyType; + }, + ): void { + const _identifier = identifier ?? context.name; + + if (_identifier == null && necessary === true) + throw new IdentifierRequiredError(); + /** * Function to evaluate the dependency lazily * to avoid circular dependencies, not found dependencies, etc. * @returns The resolved dependency or undefined if the dependency is not found. */ - const resolve = (): T | undefined => { - return TSinjex.getInstance().resolve(identifier, necessary); + const resolve = (): DependencyType | undefined => { + return TSinjex.getInstance().resolve( + _identifier, + necessary, + ); }; - Object.defineProperty(target, propertyKey, { - get() { - let instance: T | U | undefined; + context.addInitializer(function (this: TargetType) { + Object.defineProperty(this, context.name, { + get() { + let instance: DependencyType | PropertyType | undefined; - const dependency: T | undefined = tryAndCatch( - () => resolve(), - necessary, - identifier, - DependencyResolutionError, - ); + const dependency: DependencyType | undefined = tryAndCatch( + () => resolve(), + necessary, + _identifier, + DependencyResolutionError, + ); - if (dependency != null) { - const initFunction: (() => U) | undefined = - typeof init === 'function' && dependency != null - ? (): U => init(dependency) - : init === true && hasConstructor(dependency) - ? (): U => new dependency() as U - : undefined; + if (dependency != null) { + const initFunction: (() => PropertyType) | undefined = + typeof init === 'function' && dependency != null + ? (): PropertyType => init(dependency) + : init === true && hasConstructor(dependency) + ? (): PropertyType => + new dependency() as PropertyType + : undefined; - if (init == null) instance = dependency; - else if (initFunction != null) - instance = tryAndCatch( - initFunction, - necessary, - identifier, - InitializationError, - ); - else if (necessary) - throw new NoInstantiationMethodError(identifier); - } else if (necessary) - throw new DependencyResolutionError(identifier); + if (init == null) instance = dependency; + else if (initFunction != null) + instance = tryAndCatch( + initFunction, + necessary, + _identifier, + InitializationError, + ); + else if (necessary) + throw new NoInstantiationMethodError(_identifier); + } else if (necessary) + 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 - * for performance reasons. + * Make the property configurable to allow replacing it */ - Object.defineProperty(this, propertyKey, { - value: instance, - writable: false, - enumerable: false, - configurable: false, - }); - - return instance; - }, - /** - * Make the property configurable to allow replacing it - */ - configurable: true, + configurable: true, + }); }); }; }