From ac139cad439e6e2ee4d6232ec49defee4900fa20 Mon Sep 17 00:00:00 2001 From: "Max P." Date: Tue, 11 Mar 2025 17:43:32 +0100 Subject: [PATCH] refactor: consolidate registration decorators - Introduced `Register` decorator to handle class and instance registration in the DI container. - Deprecated `RegisterInstance` in favor of `Register`, which now internally handles instance registration. - Added support for marking dependencies as deprecated with a warning logged upon first resolution. - Updated documentation with examples and notes on deprecation. --- src/decorators/Register.ts | 232 +++++++++++++++++++++++++++++ src/decorators/RegisterInstance.ts | 52 ++----- 2 files changed, 241 insertions(+), 43 deletions(-) diff --git a/src/decorators/Register.ts b/src/decorators/Register.ts index fa65076..b733e89 100644 --- a/src/decorators/Register.ts +++ b/src/decorators/Register.ts @@ -1,3 +1,4 @@ +import { InitDelegate } from 'src/types/InitDelegate'; import { TSinjex } from '../classes/TSinjex'; import { Identifier } from '../types/Identifier'; @@ -16,9 +17,132 @@ import { Identifier } from '../types/Identifier'; * // ... * } * ``` + * @example + * ```ts + * \@Register('MyClassIdentifier', true) + * class MyClass { + * // ... + * } + * ``` */ export function Register< TargetType extends new (...args: unknown[]) => InstanceType, +>( + identifier: Identifier, + deprecated?: boolean, +): (constructor: TargetType, ...args: unknown[]) => void; + +/** + * A decorator to register an instance of a class in the DI (Dependency Injection) container. + * @template TargetType The type of the class whose instance is to be registered. + * @param identifier The identifier used to register the instance in the DI container. + * @see {@link Identifier} for more information on identifiers. + * @param shouldRegister Set to 'instance' to register the instance in the DI container + * with an empty constructor. + * @param deprecated If true, the dependency is deprecated and a warning + * is logged only once upon the first resolution of the dependency. + * @returns The decorator function to be applied on the class. + * @example + * ```ts + * \@RegisterInstance('MyClassInstanceIdentifier', 'instance') + * class MyClass { + * // ... + * } + * ``` + * @example + * ```ts + * \@RegisterInstance('MyClassInstanceIdentifier', 'instance', true) + * class MyClass { + * // ... + * } + * ``` + */ +export function Register< + TargetType extends new (..._args: unknown[]) => InstanceType, +>( + identifier: Identifier, + shouldRegister: 'instance', + deprecated?: boolean, +): (constructor: TargetType, ...args: unknown[]) => void; + +/** + * A decorator to register an instance of a class in the DI (Dependency Injection) container. + * @template TargetType The type of the class whose instance is to be registered. + * @param identifier The identifier used to register the instance in the DI container. + * @see {@link Identifier} for more information on identifiers. + * @param init An optional initializer function which get the constructor of the class + * as input and returns an instance of the class. + * @param deprecated If true, the dependency is deprecated and a warning + * is logged only once upon the first resolution of the dependency. + * @see {@link InitDelegate} for more information on initializer functions. + * @returns The decorator function to be applied on the class. + * @example + * ```ts + * \@RegisterInstance('MyClassInstanceIdentifier', (constructor) => new constructor()) + * class MyClass { + * // ... + * } + * ``` + * @example + * ```ts + * \@RegisterInstance('MyClassInstanceIdentifier', (constructor) => new constructor(), true) + * class MyClass { + * // ... + * } + * ``` + */ +export function Register< + TargetType extends new (..._args: unknown[]) => InstanceType, +>( + identifier: Identifier, + init?: InitDelegate< + TargetType & { new (..._args: unknown[]): InstanceType }, + InstanceType + >, + deprecated?: boolean, +): (constructor: TargetType, ...args: unknown[]) => void; + +// eslint-disable-next-line jsdoc/require-jsdoc +export function Register< + TargetType extends new (...args: unknown[]) => InstanceType, +>( + identifier: Identifier, + arg1?: + | undefined + | boolean + | InitDelegate> + | 'instance', + arg2?: boolean, +): (constructor: TargetType, ...args: unknown[]) => void { + const deprecated = typeof arg1 === 'boolean' ? arg1 : arg2; + const init = typeof arg1 === 'function' ? arg1 : undefined; + const shouldRegisterInstance = arg1 === 'instance'; + + if (init == undefined && shouldRegisterInstance !== true) { + return _register(identifier, deprecated); + } else { + return _registerInstance(identifier, init, deprecated); + } +} + +/** + * A decorator to register a class in the **TSinjex** DI (Dependency Injection) container. + * @template TargetType The type of the class to be registered. + * @param identifier The identifier used to register the class in the DI container. + * @see {@link Identifier} for more information on identifiers. + * @param deprecated If true, the dependency is deprecated and a warning + * is logged only once upon the first resolution of the dependency. + * @returns The decorator function to be applied on the class. + * @example + * ```ts + * \@Register('MyClassIdentifier') + * class MyClass { + * // ... + * } + * ``` + */ +function _register< + TargetType extends new (...args: unknown[]) => InstanceType, >(identifier: Identifier, deprecated?: boolean) { return function (constructor: TargetType, ...args: unknown[]): void { // Get the instance of the DI container @@ -28,3 +152,111 @@ export function Register< diContainer.register(identifier, constructor, deprecated); }; } + +/** + * A decorator to register an instance of a class in the DI (Dependency Injection) container. + * @template TargetType The type of the class whose instance is to be registered. + * @param identifier The identifier used to register the instance in the DI container. + * @see {@link Identifier} for more information on identifiers. + * @param init An optional initializer function which get the constructor of the class + * as input and returns an instance of the class. + * @param deprecated If true, the dependency is deprecated and a warning + * is logged only once upon the first resolution of the dependency. + * @see {@link InitDelegate} for more information on initializer functions. + * @returns The decorator function to be applied on the class. + * @example + * ```ts + * \@RegisterInstance('MyClassInstanceIdentifier', (constructor) => new constructor()) + * class MyClass { + * // ... + * } + * ``` + */ +function _registerInstance< + TargetType extends new (..._args: unknown[]) => InstanceType, +>( + identifier: Identifier, + init?: InitDelegate< + TargetType & { new (..._args: unknown[]): InstanceType }, + InstanceType + >, + deprecated?: boolean, +) { + return function (constructor: TargetType, ...args: unknown[]): void { + // Get the instance of the DI container + const diContainer = TSinjex.getInstance(); + let instance: InstanceType; + + // Create a proxy to instantiate the class when needed (Lazy Initialization) + let lazyProxy: unknown = new Proxy( + {}, + { + get(target, prop, receiver) { + ({ instance, lazyProxy } = initializeInstance( + instance, + init, + constructor, + args, + lazyProxy, + )); + + // Return the requested property of the instance + return instance[prop as keyof InstanceType]; + }, + set(target, prop, value, receiver) { + ({ instance, lazyProxy } = initializeInstance( + instance, + init, + constructor, + args, + lazyProxy, + )); + + // Set the requested property of the instance + return (instance[prop as keyof InstanceType] = + value); + }, + }, + ); + + // Register the lazy proxy in the DI container + diContainer.register(identifier, lazyProxy, deprecated); + }; +} + +/** + * Initializes the instance of the class. + * @template TargetType The type of the class whose instance is to be initialized. + * @param instance The instance of the class to be initialized. + * @param init The optional initializer function to initialize the instance. + * @param constructor The constructor of the class. + * @param args The arguments to be passed to the constructor of the class. + * @param lazyProxy The lazy proxy to instantiate the class when needed. + * @returns The initialized instance and the lazy proxy. + */ +function initializeInstance< + TargetType extends new (..._args: unknown[]) => InstanceType, +>( + instance: InstanceType, + init: + | InitDelegate< + TargetType & + (new (..._args: unknown[]) => InstanceType), + InstanceType + > + | undefined, + constructor: TargetType, + args: unknown[], + lazyProxy: unknown, +): { instance: InstanceType; lazyProxy: unknown } { + if (instance == null) { + if (init) { + instance = init(constructor); + } else { + instance = new constructor(...args); + } + } + lazyProxy = instance; + + return { instance, lazyProxy }; +} diff --git a/src/decorators/RegisterInstance.ts b/src/decorators/RegisterInstance.ts index d638f6e..2fa4545 100644 --- a/src/decorators/RegisterInstance.ts +++ b/src/decorators/RegisterInstance.ts @@ -1,4 +1,4 @@ -import { TSinjex } from '../classes/TSinjex'; +import { Register } from './Register'; import { Identifier } from '../types/Identifier'; import { InitDelegate } from '../types/InitDelegate'; @@ -9,6 +9,8 @@ import { InitDelegate } from '../types/InitDelegate'; * @see {@link Identifier} for more information on identifiers. * @param init An optional initializer function which get the constructor of the class * as input and returns an instance of the class. + * @param deprecated If true, the dependency is deprecated and a warning + * is logged only once upon the first resolution of the dependency. * @see {@link InitDelegate} for more information on initializer functions. * @returns The decorator function to be applied on the class. * @example @@ -18,6 +20,7 @@ import { InitDelegate } from '../types/InitDelegate'; * // ... * } * ``` + * @deprecated Use {@link Register} instead. This decorator already uses the {@link Register} decorator internally. */ export function RegisterInstance< TargetType extends new (..._args: unknown[]) => InstanceType, @@ -27,47 +30,10 @@ export function RegisterInstance< TargetType & { new (..._args: unknown[]): InstanceType }, InstanceType >, -) { - return function (constructor: TargetType, ...args: unknown[]): void { - // Get the instance of the DI container - const diContainer = TSinjex.getInstance(); - let instance: InstanceType; + deprecated?: boolean, +): (constructor: TargetType, ...args: unknown[]) => void { + const initDelegate = typeof init === 'function' ? init : undefined; - // Create a proxy to instantiate the class when needed (Lazy Initialization) - let lazyProxy: unknown = new Proxy( - {}, - { - get(target, prop, receiver) { - if (instance == null) { - if (init) { - instance = init(constructor); - } else { - instance = new constructor(...args); - } - } - lazyProxy = instance; - - // Return the requested property of the instance - return instance[prop as keyof InstanceType]; - }, - set(target, prop, value, receiver) { - if (instance == null) { - if (init) { - instance = init(constructor); - } else { - instance = new constructor(...args); - } - } - lazyProxy = instance; - - // Set the requested property of the instance - return (instance[prop as keyof InstanceType] = - value); - }, - }, - ); - - // Register the lazy proxy in the DI container - diContainer.register(identifier, lazyProxy); - }; + if (initDelegate) return Register(identifier, initDelegate, deprecated); + else return Register(identifier, 'instance', deprecated); }