diff --git a/src/__tests__/DIContainer.test.ts b/src/__tests__/DIContainer.test.ts index 23878f8..556c6a2 100644 --- a/src/__tests__/DIContainer.test.ts +++ b/src/__tests__/DIContainer.test.ts @@ -1,4 +1,4 @@ import { test_IDIContainer } from './IDIContainer.spec'; -import { TSinjex } from '../TSinjex'; +import { TSinjex } from '../classes/TSinjex'; test_IDIContainer(TSinjex); diff --git a/src/TSinjex.ts b/src/classes/TSinjex.ts similarity index 53% rename from src/TSinjex.ts rename to src/classes/TSinjex.ts index 84f8615..e172a0d 100644 --- a/src/TSinjex.ts +++ b/src/classes/TSinjex.ts @@ -1,104 +1,127 @@ -import { ImplementsStatic } from './helper/ImplementsStatic'; -import { DependencyResolutionError } from './interfaces/Exceptions'; -import { IDependency } from './interfaces/IDependency'; -import { ITSinjex, ITSinjex_ } from './interfaces/ITSinjex'; - -/** - * **TSInjex**: Dependency Injection Container - */ -@ImplementsStatic() -export class TSinjex implements ITSinjex { - private static _instance: TSinjex; - private readonly _dependencies = new Map(); - - /** - * Private constructor to prevent direct instantiation. - */ - private constructor() {} - - //#region IDIContainer_ - - /** - * Retrieves the singleton instance of DependencyRegistry. - * @returns The singleton instance. - */ - public static getInstance(): ITSinjex { - if (this._instance == null) { - this._instance = new TSinjex(); - } - - return this._instance; - } - - /** - * @inheritdoc - * @see {@link ITSinjex.register} - */ - public static register( - identifier: string, - dependency: T, - deprecated = false, - ): void { - (TSinjex.getInstance() as TSinjex)._dependencies.set(identifier, { - dependency: dependency, - deprecated: deprecated, - }); - } - - /** - * @inheritdoc - * @see {@link ITSinjex.resolve} - */ - public static resolve( - identifier: string, - necessary = true, - ): T | undefined { - return (TSinjex.getInstance() as TSinjex).resolve( - identifier, - necessary, - ); - } - - //#endregion - - //#region IDIContainer - - /** - * @inheritdoc - */ - public register( - identifier: string, - dependency: T, - deprecated = false, - ): void { - this._dependencies.set(identifier, { - dependency: dependency, - deprecated: deprecated, - }); - } - - /** - * @inheritdoc - */ - public resolve(identifier: string, necessary = true): T | undefined { - const dependency = this._dependencies.get(identifier); - - if (necessary && !dependency) { - throw new DependencyResolutionError(identifier); - } else if (!dependency) { - return undefined; - } - - if (dependency.deprecated) { - // eslint-disable-next-line no-console - console.warn(`Dependency ${identifier} is deprecated`); - - // Remove the deprecation warning; it should only be logged once. - dependency.deprecated = false; - } - - return dependency.dependency as T; - } - - //#endregion -} +import { Identifier } from 'src/types/Identifier'; +import type { Inject } from '../decorators/Inject'; +import type { Register } from '../decorators/Register'; +import type { RegisterInstance } from '../decorators/RegisterInstance'; +import type { register } from '../functions/register'; +import type { resolve } from '../functions/resolve'; +import { ImplementsStatic } from '../helper/ImplementsStatic'; +import { DependencyResolutionError } from '../interfaces/Exceptions'; +import { IDependency } from '../interfaces/IDependency'; +import { ITSinjex, ITSinjex_ } from '../interfaces/ITSinjex'; + +/** + * # TSinjex + * The main class for the Dependency Injection Container **TSinjex**. + * ### Decorators + * @see {@link Register} for registering a class in the DI container. + * @see {@link RegisterInstance} for registering an instance in the DI container. + * @see {@link Inject} for injecting a dependency into a property. + * --- + * ### Functions + * @see {@link register} for registering a dependency (class or instance) as a function. + * @see {@link resolve} for resolving a dependency as a function. + */ +@ImplementsStatic() +export class TSinjex implements ITSinjex { + /** + * The singleton instance of the TSinjex class. + */ + private static _instance: TSinjex; + + /** + * The dependencies map. + */ + private readonly _dependencies = new Map(); + + /** + * Private constructor to prevent direct instantiation. + */ + private constructor() {} + + //#region ITSinjex_ (Static) + + /** + * Get the **singleton** TSInjex instance. + * @returns The singleton instance. + */ + public static getInstance(): ITSinjex { + if (this._instance == null) { + this._instance = new TSinjex(); + } + + return this._instance; + } + + /** + * Static implementation of {@link ITSinjex.register}. + * @see {@link ITSinjex.register} + * @inheritdoc + */ + public static register( + identifier: Identifier, + dependency: T, + deprecated = false, + ): void { + (TSinjex.getInstance() as TSinjex)._dependencies.set(identifier, { + dependency: dependency, + deprecated: deprecated, + }); + } + + /** + * Static implementation of {@link ITSinjex.resolve}. + * @see {@link ITSinjex.resolve} + * @inheritdoc + */ + public static resolve( + identifier: Identifier, + necessary = true, + ): T | undefined { + return (TSinjex.getInstance() as TSinjex).resolve( + identifier, + necessary, + ); + } + + //#endregion + + //#region ITSinjex (Instance) + + /** + * @inheritdoc + */ + public register( + identifier: Identifier, + dependency: T, + deprecated = false, + ): void { + this._dependencies.set(identifier, { + dependency: dependency, + deprecated: deprecated, + }); + } + + /** + * @inheritdoc + */ + public resolve(identifier: Identifier, necessary = true): T | undefined { + const dependency = this._dependencies.get(identifier); + + if (necessary && !dependency) { + throw new DependencyResolutionError(identifier); + } else if (!dependency) { + return undefined; + } + + if (dependency.deprecated) { + console.warn(`Dependency ${identifier} is deprecated`); + + // Remove the deprecation warning; it should only be logged once. + dependency.deprecated = false; + } + + return dependency.dependency as T; + } + + //#endregion +} diff --git a/src/decorators/Inject.ts b/src/decorators/Inject.ts index 7022e1c..9e5239f 100644 --- a/src/decorators/Inject.ts +++ b/src/decorators/Inject.ts @@ -1,18 +1,19 @@ -import { TSinjex } from '../TSinjex'; +import { Identifier } from 'src/types/Identifier'; +import { TSinjex } from '../classes/TSinjex'; import { InitDelegate } from '../types/InitDelegate'; /** - * A decorator to inject a dependency from a DI (Dependency Injection) container. - * The dependency is lazily evaluated when the property is accessed for the first time. - * This can help avoid issues like circular dependencies and not-found dependencies. - * @template ClassType The type of the property to be injected. - * @param identifier The identifier used to resolve the dependency from the DI container. + * 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 An optional initializer function to transform the dependency before injection. - * @param necessary Indicates if the dependency is necessary. - * - If `true`, an error will be thrown if the dependency cannot be resolved. - * - If `false`, `undefined` will be returned if the dependency cannot be resolved. - * @returns A decorator function to be applied on the class property. - * @see {@link TSinjex} + * @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 A {@link DependencyResolutionError} if the dependency is not found and necessary. * @example * ```ts * class MyClass { @@ -29,7 +30,7 @@ import { InitDelegate } from '../types/InitDelegate'; * ``` */ export function Inject( - identifier: string, + identifier: Identifier, init?: InitDelegate, necessary = true, ) { diff --git a/src/decorators/Register.ts b/src/decorators/Register.ts index 797bf1c..aae2d41 100644 --- a/src/decorators/Register.ts +++ b/src/decorators/Register.ts @@ -1,12 +1,14 @@ -import { TSinjex } from '../TSinjex'; +import { Identifier } from 'src/types/Identifier'; +import { TSinjex } from '../classes/TSinjex'; /** - * A decorator to register a class in the DI (Dependency Injection) container. + * 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. - * @param deprecated If true, the dependency is deprecated => a warning - * is logged when the dependency is resolved. - * @returns A function that is applied as a decorator to the class. + * @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') @@ -17,7 +19,7 @@ import { TSinjex } from '../TSinjex'; */ export function Register< TargetType extends new (...args: unknown[]) => InstanceType, ->(identifier: string, deprecated?: boolean) { +>(identifier: Identifier, deprecated?: boolean) { return function (constructor: TargetType, ...args: unknown[]): void { // Get the instance of the DI container const diContainer = TSinjex.getInstance(); diff --git a/src/decorators/RegisterInstance.ts b/src/decorators/RegisterInstance.ts index ffc72f6..5e34629 100644 --- a/src/decorators/RegisterInstance.ts +++ b/src/decorators/RegisterInstance.ts @@ -1,17 +1,19 @@ -import { TSinjex } from '../TSinjex'; +import { Identifier } from 'src/types/Identifier'; +import { TSinjex } from '../classes/TSinjex'; import { InitDelegate } from '../types/InitDelegate'; /** * A decorator to register an instance of a class in the DI (Dependency Injection) container. - * The instance is created only when it is first needed (Lazy Initialization). * @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. - * @returns A function that is applied as a decorator to the class. + * @see {@link InitDelegate} for more information on initializer functions. + * @returns The decorator function to be applied on the class. * @example * ```ts - * \@RegisterInstance('MyClassInstanceIdentifier', arg1, arg2) + * \@RegisterInstance('MyClassInstanceIdentifier', (constructor) => new constructor()) * class MyClass { * // ... * } @@ -20,7 +22,7 @@ import { InitDelegate } from '../types/InitDelegate'; export function RegisterInstance< TargetType extends new (..._args: unknown[]) => InstanceType, >( - identifier: string, + identifier: Identifier, init?: InitDelegate< TargetType & { new (..._args: unknown[]): InstanceType }, InstanceType diff --git a/src/functions/register.ts b/src/functions/register.ts index 00f435c..4e95615 100644 --- a/src/functions/register.ts +++ b/src/functions/register.ts @@ -1,33 +1,37 @@ -import { TSinjex } from '../TSinjex'; +import { Identifier } from 'src/types/Identifier'; +import { TSinjex } from '../classes/TSinjex'; /** * Register a dependency. - * @param identifier The identifier of the dependency. + * @param identifier The identifier used to register the class in the DI container. + * @see {@link Identifier} for more information on identifiers.. * @param dependency The dependency to register. */ -export function register(identifier: string, dependency: T): void; +export function register(identifier: Identifier, dependency: T): void; /** * Register a dependency. - * @param identifier The identifier of the dependency. + * @param identifier The identifier used to register the class in the DI container. + * @see {@link Identifier} for more information on identifiers. * @param dependency The dependency to register. * @param deprecated A warning is logged when the dependency is resolved. */ export function register( - identifier: string, + identifier: Identifier, dependency: T, deprecated?: true, ): void; /** * Register a dependency. - * @param identifier The identifier of the dependency. + * @param identifier The identifier used to register the class in the DI container. + * @see {@link Identifier} for more information on identifiers. * @param dependency The dependency to register. * @param deprecated If true, the dependency is deprecated => a warning * is logged when the dependency is resolved. */ export function register( - identifier: string, + identifier: Identifier, dependency: T, deprecated?: boolean, ): void { diff --git a/src/functions/resolve.ts b/src/functions/resolve.ts index 23d5cf7..015ffa1 100644 --- a/src/functions/resolve.ts +++ b/src/functions/resolve.ts @@ -1,32 +1,39 @@ +import { Identifier } from 'src/types/Identifier'; +import { TSinjex } from '../classes/TSinjex'; import { DependencyResolutionError } from '../interfaces/Exceptions'; -import { TSinjex } from '../TSinjex'; /** * Resolve a dependency. - * @param identifier The identifier of the dependency. + * @param identifier The identifier used to register the class in the DI container. + * @see {@link Identifier} for more information on identifiers. * @returns The resolved dependency. * @throws A {@link DependencyResolutionError} if the dependency is not found. */ -export function resolve(identifier: string): T; +export function resolve(identifier: Identifier): T; /** * Resolve a dependency - * @param identifier The identifier of the dependency. + * @param identifier The identifier used to register the class in the DI container. + * @see {@link Identifier} for more information on identifiers. * @param necessary The dependency is **not** necessary. * @returns The resolved dependency or undefined if the dependency is not found. */ -export function resolve(identifier: string, necessary: false): T | undefined; +export function resolve( + identifier: Identifier, + necessary: false, +): T | undefined; /** * Resolve a dependency. - * @param identifier The identifier of the dependency. + * @param identifier The identifier used to register the class in the DI container. + * @see {@link Identifier} for more information on identifiers. * @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 A {@link DependencyResolutionError} if the dependency is not found and necessary. */ export function resolve( - identifier: string, + identifier: Identifier, necessary?: boolean, ): T | undefined { return TSinjex.getInstance().resolve(identifier, necessary); diff --git a/src/index.ts b/src/index.ts index 65e9fca..bfe2716 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,5 @@ // Main -export * from './TSinjex'; +export * from './classes/TSinjex'; // Decorators export * from './decorators/Inject'; diff --git a/src/interfaces/Exceptions.ts b/src/interfaces/Exceptions.ts index 9faa9e3..ba6c46e 100644 --- a/src/interfaces/Exceptions.ts +++ b/src/interfaces/Exceptions.ts @@ -10,7 +10,7 @@ export class TSinjexError extends Error { */ constructor(message: string) { super(message); - this.name = 'TSInjex'; + this.name = 'TSinjex'; } } @@ -24,7 +24,7 @@ export class DependencyResolutionError extends TSinjexError { * @param identifier **The identifier of the dependency** */ constructor(identifier: string) { - super(`Dependency ${identifier} not found.`); - this.name = 'TSInjexResolutionError'; + super(`Dependency ${identifier} could not be resolved.`); + this.name = 'TSinjexResolutionError'; } } diff --git a/src/interfaces/ITSinjex.ts b/src/interfaces/ITSinjex.ts index 427f47b..b05c65d 100644 --- a/src/interfaces/ITSinjex.ts +++ b/src/interfaces/ITSinjex.ts @@ -1,15 +1,19 @@ +import { Identifier } from 'src/types/Identifier'; +import { DependencyResolutionError } from './Exceptions'; + /** * Static TSInjex Interface */ export interface ITSinjex_ extends ITSinjexRegister, ITSinjexResolve { /** * Get the **singleton** TSInjex instance. + * @returns The singleton instance. */ getInstance(): ITSinjex; } /** - * Register method for static and instance Dependency Injection Container. + * `Register` method for static and instance Dependency Injection Container. */ export interface ITSinjexRegister { /** @@ -19,25 +23,33 @@ export interface ITSinjexRegister { * @param deprecated If true, the dependency is deprecated => a warning * is logged when the dependency is resolved. */ - register(identifier: string, dependency: T, deprecated?: boolean): void; + register( + identifier: Identifier, + dependency: T, + deprecated?: boolean, + ): void; /** * Register a deprecated dependency. * @param identifier The identifier of the dependency. * @param dependency The dependency to register. * @param deprecated A warning is logged when the dependency is resolved. */ - register(identifier: string, dependency: T, deprecated?: true): void; + register(identifier: Identifier, dependency: T, deprecated?: true): void; /** * Register a dependency. * @param identifier The identifier of the dependency. * @param dependency The dependency to register. * @param deprecated No warning is logged when the dependency is resolved. */ - register(identifier: string, dependency: T, deprecated?: false): void; + register( + identifier: Identifier, + dependency: T, + deprecated?: false, + ): void; } /** - * Resolve method for static and instance Dependency Injection Container. + * `Resolve` method for static and instance Dependency Injection Container. */ export interface ITSinjexResolve { /** @@ -45,26 +57,27 @@ export interface ITSinjexResolve { * @param identifier The identifier of the dependency * @param necessary If true, throws an error if the dependency is not found * @returns The resolved dependency or undefined if the dependency is not found + * @throws A {@link DependencyResolutionError} if the dependency is not found and necessary. */ - resolve(identifier: string, necessary?: boolean): T | undefined; + resolve(identifier: Identifier, necessary?: boolean): T | undefined; /** * Resolve a necessary dependency. * @param identifier The identifier of the dependency. * @param necessary If true, throws an error if the dependency is not found. * @returns The resolved dependency. - * @throws Error if the dependency is not found. + * @throws A {@link DependencyResolutionError} if the dependency is not found. */ - resolve(identifier: string, necessary?: true): T; + resolve(identifier: Identifier, necessary?: true): T; /** * Resolve a non necessary dependency * @param identifier The identifier of the dependency * @param necessary Not necessary, does not throw an error if the dependency is not found. * @returns The resolved dependency or undefined if the dependency is not found */ - resolve(identifier: string, necessary?: false): T | undefined; + resolve(identifier: Identifier, necessary?: false): T | undefined; } /** - * TSInjex Interface + * Instance TSinjex Interface */ export interface ITSinjex extends ITSinjexRegister, ITSinjexResolve {} diff --git a/src/types/GenericContructor.ts b/src/types/GenericContructor.ts index 0b8ffe0..477b0df 100644 --- a/src/types/GenericContructor.ts +++ b/src/types/GenericContructor.ts @@ -1,5 +1,6 @@ /** * Generic constructor type. + * This type is used to define a constructor of a class. */ export type GenericConstructor< T extends abstract new (...args: unknown[]) => InstanceType, @@ -7,6 +8,6 @@ export type GenericConstructor< /** * Force generic constructor type. - * This type is used to force a class to be a constructor. + * This type is used to force a class to has a constructor. */ export type ForceConstructor = new (...args: unknown[]) => T; diff --git a/src/types/Identifier.ts b/src/types/Identifier.ts new file mode 100644 index 0000000..781f704 --- /dev/null +++ b/src/types/Identifier.ts @@ -0,0 +1,11 @@ +/** + * The dependency identifier. + * You can use any string as identifier. + * To create order, it is also possible to + * provide these with a separator: `GroupA.ClassZ`. + * The convection for naming is as follows: + * The name should generally correspond to the interface that is relevant. + * I.e. a class `ClassA` that implements the interface `IClassA` and is + * registered as a dependent class is registered under the interface name `IClassA`. + */ +export type Identifier = string;