diff --git a/src/functions/inject.ts b/src/functions/inject.ts new file mode 100644 index 0000000..192769e --- /dev/null +++ b/src/functions/inject.ts @@ -0,0 +1,113 @@ +import { TSinjex } from '../classes/TSinjex.js'; +import { + DependencyResolutionError, + InitializationError, + InjectorError, + NoInstantiationMethodError, +} from '../interfaces/Exceptions.js'; +import { Identifier } from '../types/Identifier.js'; +import { InitDelegate } from '../types/InitDelegate.js'; + +/** + * A function to inject a dependency from a DI (Dependency Injection) container into a variable. + * @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 + * or true to instantiate the dependency if it has a constructor. + * @see {@link InitDelegate} for more information on initializer functions. + * @param isNecessary 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 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. + * @throws An {@link InitializationError} if an error occurs during the initialization process. + * @example + * ```ts + * let myDependency = inject('MyDependencyIdentifier'); + * ``` + * @example + * ```ts + * let logger = inject('ILogger_', (x: ILogger_) => x.getLogger('Tags'), false); + * ``` + */ +export function inject( + identifier: Identifier, + init?: InitDelegate | true, + isNecessary = true, +): T | U | undefined { + let instance: T | U | undefined; + + const dependency: T | undefined = tryAndCatch( + () => TSinjex.getInstance().resolve(identifier, isNecessary), + isNecessary, + 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 (init == null) instance = dependency; + else if (initFunction != null) + instance = tryAndCatch( + initFunction, + isNecessary, + identifier, + InitializationError, + ); + else if (isNecessary) throw new NoInstantiationMethodError(identifier); + } else if (isNecessary) throw new DependencyResolutionError(identifier); + + return instance as T | U; +} + +/** + * Tries to execute a function and catches any errors that occur. + * If the function is necessary and an error occurs, it throws the error + * with the specified error class and identifier. + * @param fn The function to execute. + * @param necessary If true, throws an error if an error occurs. + * @param identifier The identifier of the dependency. + * @param errorClass The error class to throw if an error occurs. + * @returns The result of the function or undefined if an error occurs and the function is not necessary. + */ +function tryAndCatch( + fn: () => ReturnType, + necessary: boolean, + identifier?: Identifier, + errorClass?: ErrorType, +): ReturnType | undefined { + try { + return fn(); + } catch (error) { + if (necessary) + throw new (errorClass != null ? errorClass : error)( + identifier ?? 'not specified', + error, + ); + else return undefined; + } +} + +/** + * Checks if an object has a constructor. + * @param obj The object to check. + * @returns True if the object has a constructor, false otherwise. + */ +function hasConstructor(obj: T): obj is T & { new (): unknown } { + const _obj = obj as unknown as { prototype?: { constructor?: unknown } }; + + return ( + _obj?.prototype != null && + typeof _obj.prototype.constructor === 'function' + ); +}