Refactor DI container for better structure and clarity

- Moved `TSinjex` class to `classes` directory for better organization.
- Updated imports across the codebase to reflect the new location of `TSinjex`.
- Introduced `Identifier` type to standardize dependency identifiers.
- Enhanced JSDoc comments for improved clarity and consistency.
- Adjusted error messages for `DependencyResolutionError` to provide clearer information.
- Updated and expanded decorator and function types to use `Identifier` type.
This commit is contained in:
2024-08-16 16:13:17 +02:00
committed by Max P.
parent 5277f93df1
commit 41be08e02e
12 changed files with 221 additions and 157 deletions

View File

@@ -1,4 +1,4 @@
import { test_IDIContainer } from './IDIContainer.spec'; import { test_IDIContainer } from './IDIContainer.spec';
import { TSinjex } from '../TSinjex'; import { TSinjex } from '../classes/TSinjex';
test_IDIContainer(TSinjex); test_IDIContainer(TSinjex);

View File

@@ -1,104 +1,127 @@
import { ImplementsStatic } from './helper/ImplementsStatic'; import { Identifier } from 'src/types/Identifier';
import { DependencyResolutionError } from './interfaces/Exceptions'; import type { Inject } from '../decorators/Inject';
import { IDependency } from './interfaces/IDependency'; import type { Register } from '../decorators/Register';
import { ITSinjex, ITSinjex_ } from './interfaces/ITSinjex'; import type { RegisterInstance } from '../decorators/RegisterInstance';
import type { register } from '../functions/register';
/** import type { resolve } from '../functions/resolve';
* **TSInjex**: Dependency Injection Container import { ImplementsStatic } from '../helper/ImplementsStatic';
*/ import { DependencyResolutionError } from '../interfaces/Exceptions';
@ImplementsStatic<ITSinjex_>() import { IDependency } from '../interfaces/IDependency';
export class TSinjex implements ITSinjex { import { ITSinjex, ITSinjex_ } from '../interfaces/ITSinjex';
private static _instance: TSinjex;
private readonly _dependencies = new Map<string, IDependency>(); /**
* # TSinjex
/** * The main class for the Dependency Injection Container **TSinjex**.
* Private constructor to prevent direct instantiation. * ### Decorators
*/ * @see {@link Register} for registering a class in the DI container.
private constructor() {} * @see {@link RegisterInstance} for registering an instance in the DI container.
* @see {@link Inject} for injecting a dependency into a property.
//#region IDIContainer_ * ---
* ### Functions
/** * @see {@link register} for registering a dependency (class or instance) as a function.
* Retrieves the singleton instance of DependencyRegistry. * @see {@link resolve} for resolving a dependency as a function.
* @returns The singleton instance. */
*/ @ImplementsStatic<ITSinjex_>()
public static getInstance(): ITSinjex { export class TSinjex implements ITSinjex {
if (this._instance == null) { /**
this._instance = new TSinjex(); * The singleton instance of the TSinjex class.
} */
private static _instance: TSinjex;
return this._instance;
} /**
* The dependencies map.
/** */
* @inheritdoc private readonly _dependencies = new Map<Identifier, IDependency>();
* @see {@link ITSinjex.register}
*/ /**
public static register<T>( * Private constructor to prevent direct instantiation.
identifier: string, */
dependency: T, private constructor() {}
deprecated = false,
): void { //#region ITSinjex_ (Static)
(TSinjex.getInstance() as TSinjex)._dependencies.set(identifier, {
dependency: dependency, /**
deprecated: deprecated, * Get the **singleton** TSInjex instance.
}); * @returns The singleton instance.
} */
public static getInstance(): ITSinjex {
/** if (this._instance == null) {
* @inheritdoc this._instance = new TSinjex();
* @see {@link ITSinjex.resolve} }
*/
public static resolve<T>( return this._instance;
identifier: string, }
necessary = true,
): T | undefined { /**
return (TSinjex.getInstance() as TSinjex).resolve<T>( * Static implementation of {@link ITSinjex.register}.
identifier, * @see {@link ITSinjex.register}
necessary, * @inheritdoc
); */
} public static register<T>(
identifier: Identifier,
//#endregion dependency: T,
deprecated = false,
//#region IDIContainer ): void {
(TSinjex.getInstance() as TSinjex)._dependencies.set(identifier, {
/** dependency: dependency,
* @inheritdoc deprecated: deprecated,
*/ });
public register<T>( }
identifier: string,
dependency: T, /**
deprecated = false, * Static implementation of {@link ITSinjex.resolve}.
): void { * @see {@link ITSinjex.resolve}
this._dependencies.set(identifier, { * @inheritdoc
dependency: dependency, */
deprecated: deprecated, public static resolve<T>(
}); identifier: Identifier,
} necessary = true,
): T | undefined {
/** return (TSinjex.getInstance() as TSinjex).resolve<T>(
* @inheritdoc identifier,
*/ necessary,
public resolve<T>(identifier: string, necessary = true): T | undefined { );
const dependency = this._dependencies.get(identifier); }
if (necessary && !dependency) { //#endregion
throw new DependencyResolutionError(identifier);
} else if (!dependency) { //#region ITSinjex (Instance)
return undefined;
} /**
* @inheritdoc
if (dependency.deprecated) { */
// eslint-disable-next-line no-console public register<T>(
console.warn(`Dependency ${identifier} is deprecated`); identifier: Identifier,
dependency: T,
// Remove the deprecation warning; it should only be logged once. deprecated = false,
dependency.deprecated = false; ): void {
} this._dependencies.set(identifier, {
dependency: dependency,
return dependency.dependency as T; deprecated: deprecated,
} });
}
//#endregion
} /**
* @inheritdoc
*/
public resolve<T>(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
}

View File

@@ -1,18 +1,19 @@
import { TSinjex } from '../TSinjex'; import { Identifier } from 'src/types/Identifier';
import { TSinjex } from '../classes/TSinjex';
import { InitDelegate } from '../types/InitDelegate'; import { InitDelegate } from '../types/InitDelegate';
/** /**
* A decorator to inject a dependency from a DI (Dependency Injection) container. * A decorator to inject a dependency from a DI (Dependency Injection) container into a class property.
* The dependency is lazily evaluated when the property is accessed for the first time. * @template T The type of the dependency to be injected.
* This can help avoid issues like circular dependencies and not-found dependencies. * @template U The type of the property to be injected.
* @template ClassType The type of the property to be injected. * @param identifier The identifier used to resolve the class in the DI container.
* @param identifier The identifier used to resolve the dependency from the DI container. * @see {@link Identifier} for more information on identifiers.
* @param init An optional initializer function to transform the dependency before injection. * @param init An optional initializer function to transform the dependency before injection.
* @param necessary Indicates if the dependency is necessary. * @see {@link InitDelegate} for more information on initializer functions.
* - If `true`, an error will be thrown if the dependency cannot be resolved. * @param necessary If true, throws an error if the dependency is not found.
* - If `false`, `undefined` will be returned if the dependency cannot be resolved. * @returns The resolved dependency or undefined if the dependency is not necessary
* @returns A decorator function to be applied on the class property. * and not found, or throws an error if the dependency is necessary and not found.
* @see {@link TSinjex} * @throws A {@link DependencyResolutionError} if the dependency is not found and necessary.
* @example * @example
* ```ts * ```ts
* class MyClass { * class MyClass {
@@ -29,7 +30,7 @@ import { InitDelegate } from '../types/InitDelegate';
* ``` * ```
*/ */
export function Inject<T, U>( export function Inject<T, U>(
identifier: string, identifier: Identifier,
init?: InitDelegate<T, U>, init?: InitDelegate<T, U>,
necessary = true, necessary = true,
) { ) {

View File

@@ -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. * @template TargetType The type of the class to be registered.
* @param identifier The identifier used to register the class in the DI container. * @param identifier The identifier used to register the class in the DI container.
* @param deprecated If true, the dependency is deprecated => a warning * @see {@link Identifier} for more information on identifiers.
* is logged when the dependency is resolved. * @param deprecated If true, the dependency is deprecated and a warning
* @returns A function that is applied as a decorator to the class. * is logged only once upon the first resolution of the dependency.
* @returns The decorator function to be applied on the class.
* @example * @example
* ```ts * ```ts
* \@Register('MyClassIdentifier') * \@Register('MyClassIdentifier')
@@ -17,7 +19,7 @@ import { TSinjex } from '../TSinjex';
*/ */
export function Register< export function Register<
TargetType extends new (...args: unknown[]) => InstanceType<TargetType>, TargetType extends new (...args: unknown[]) => InstanceType<TargetType>,
>(identifier: string, deprecated?: boolean) { >(identifier: Identifier, deprecated?: boolean) {
return function (constructor: TargetType, ...args: unknown[]): void { return function (constructor: TargetType, ...args: unknown[]): void {
// Get the instance of the DI container // Get the instance of the DI container
const diContainer = TSinjex.getInstance(); const diContainer = TSinjex.getInstance();

View File

@@ -1,17 +1,19 @@
import { TSinjex } from '../TSinjex'; import { Identifier } from 'src/types/Identifier';
import { TSinjex } from '../classes/TSinjex';
import { InitDelegate } from '../types/InitDelegate'; import { InitDelegate } from '../types/InitDelegate';
/** /**
* A decorator to register an instance of a class in the DI (Dependency Injection) container. * 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. * @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. * @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 * @param init An optional initializer function which get the constructor of the class
* as input and returns an instance 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 * @example
* ```ts * ```ts
* \@RegisterInstance('MyClassInstanceIdentifier', arg1, arg2) * \@RegisterInstance('MyClassInstanceIdentifier', (constructor) => new constructor())
* class MyClass { * class MyClass {
* // ... * // ...
* } * }
@@ -20,7 +22,7 @@ import { InitDelegate } from '../types/InitDelegate';
export function RegisterInstance< export function RegisterInstance<
TargetType extends new (..._args: unknown[]) => InstanceType<TargetType>, TargetType extends new (..._args: unknown[]) => InstanceType<TargetType>,
>( >(
identifier: string, identifier: Identifier,
init?: InitDelegate< init?: InitDelegate<
TargetType & { new (..._args: unknown[]): InstanceType<TargetType> }, TargetType & { new (..._args: unknown[]): InstanceType<TargetType> },
InstanceType<TargetType> InstanceType<TargetType>

View File

@@ -1,33 +1,37 @@
import { TSinjex } from '../TSinjex'; import { Identifier } from 'src/types/Identifier';
import { TSinjex } from '../classes/TSinjex';
/** /**
* Register a dependency. * 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 dependency The dependency to register.
*/ */
export function register<T>(identifier: string, dependency: T): void; export function register<T>(identifier: Identifier, dependency: T): void;
/** /**
* Register a dependency. * 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 dependency The dependency to register.
* @param deprecated A warning is logged when the dependency is resolved. * @param deprecated A warning is logged when the dependency is resolved.
*/ */
export function register<T>( export function register<T>(
identifier: string, identifier: Identifier,
dependency: T, dependency: T,
deprecated?: true, deprecated?: true,
): void; ): void;
/** /**
* Register a dependency. * 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 dependency The dependency to register.
* @param deprecated If true, the dependency is deprecated => a warning * @param deprecated If true, the dependency is deprecated => a warning
* is logged when the dependency is resolved. * is logged when the dependency is resolved.
*/ */
export function register<T>( export function register<T>(
identifier: string, identifier: Identifier,
dependency: T, dependency: T,
deprecated?: boolean, deprecated?: boolean,
): void { ): void {

View File

@@ -1,32 +1,39 @@
import { Identifier } from 'src/types/Identifier';
import { TSinjex } from '../classes/TSinjex';
import { DependencyResolutionError } from '../interfaces/Exceptions'; import { DependencyResolutionError } from '../interfaces/Exceptions';
import { TSinjex } from '../TSinjex';
/** /**
* Resolve a dependency. * 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. * @returns The resolved dependency.
* @throws A {@link DependencyResolutionError} if the dependency is not found. * @throws A {@link DependencyResolutionError} if the dependency is not found.
*/ */
export function resolve<T>(identifier: string): T; export function resolve<T>(identifier: Identifier): T;
/** /**
* Resolve a dependency * 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. * @param necessary The dependency is **not** necessary.
* @returns The resolved dependency or undefined if the dependency is not found. * @returns The resolved dependency or undefined if the dependency is not found.
*/ */
export function resolve<T>(identifier: string, necessary: false): T | undefined; export function resolve<T>(
identifier: Identifier,
necessary: false,
): T | undefined;
/** /**
* Resolve a dependency. * 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. * @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 A {@link DependencyResolutionError} if the dependency is not found and necessary. * @throws A {@link DependencyResolutionError} if the dependency is not found and necessary.
*/ */
export function resolve<T>( export function resolve<T>(
identifier: string, identifier: Identifier,
necessary?: boolean, necessary?: boolean,
): T | undefined { ): T | undefined {
return TSinjex.getInstance().resolve<T>(identifier, necessary); return TSinjex.getInstance().resolve<T>(identifier, necessary);

View File

@@ -1,5 +1,5 @@
// Main // Main
export * from './TSinjex'; export * from './classes/TSinjex';
// Decorators // Decorators
export * from './decorators/Inject'; export * from './decorators/Inject';

View File

@@ -10,7 +10,7 @@ export class TSinjexError extends Error {
*/ */
constructor(message: string) { constructor(message: string) {
super(message); super(message);
this.name = 'TSInjex'; this.name = 'TSinjex';
} }
} }
@@ -24,7 +24,7 @@ export class DependencyResolutionError extends TSinjexError {
* @param identifier **The identifier of the dependency** * @param identifier **The identifier of the dependency**
*/ */
constructor(identifier: string) { constructor(identifier: string) {
super(`Dependency ${identifier} not found.`); super(`Dependency ${identifier} could not be resolved.`);
this.name = 'TSInjexResolutionError'; this.name = 'TSinjexResolutionError';
} }
} }

View File

@@ -1,15 +1,19 @@
import { Identifier } from 'src/types/Identifier';
import { DependencyResolutionError } from './Exceptions';
/** /**
* Static TSInjex Interface * Static TSInjex Interface
*/ */
export interface ITSinjex_ extends ITSinjexRegister, ITSinjexResolve { export interface ITSinjex_ extends ITSinjexRegister, ITSinjexResolve {
/** /**
* Get the **singleton** TSInjex instance. * Get the **singleton** TSInjex instance.
* @returns The singleton instance.
*/ */
getInstance(): ITSinjex; getInstance(): ITSinjex;
} }
/** /**
* Register method for static and instance Dependency Injection Container. * `Register` method for static and instance Dependency Injection Container.
*/ */
export interface ITSinjexRegister { export interface ITSinjexRegister {
/** /**
@@ -19,25 +23,33 @@ export interface ITSinjexRegister {
* @param deprecated If true, the dependency is deprecated => a warning * @param deprecated If true, the dependency is deprecated => a warning
* is logged when the dependency is resolved. * is logged when the dependency is resolved.
*/ */
register<T>(identifier: string, dependency: T, deprecated?: boolean): void; register<T>(
identifier: Identifier,
dependency: T,
deprecated?: boolean,
): void;
/** /**
* Register a deprecated dependency. * Register a deprecated dependency.
* @param identifier The identifier of the dependency. * @param identifier The identifier of the dependency.
* @param dependency The dependency to register. * @param dependency The dependency to register.
* @param deprecated A warning is logged when the dependency is resolved. * @param deprecated A warning is logged when the dependency is resolved.
*/ */
register<T>(identifier: string, dependency: T, deprecated?: true): void; register<T>(identifier: Identifier, dependency: T, deprecated?: true): void;
/** /**
* Register a dependency. * Register a dependency.
* @param identifier The identifier of the dependency. * @param identifier The identifier of the dependency.
* @param dependency The dependency to register. * @param dependency The dependency to register.
* @param deprecated No warning is logged when the dependency is resolved. * @param deprecated No warning is logged when the dependency is resolved.
*/ */
register<T>(identifier: string, dependency: T, deprecated?: false): void; register<T>(
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 { export interface ITSinjexResolve {
/** /**
@@ -45,26 +57,27 @@ export interface ITSinjexResolve {
* @param identifier The identifier of the dependency * @param identifier The identifier of the dependency
* @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 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<T>(identifier: string, necessary?: boolean): T | undefined; resolve<T>(identifier: Identifier, necessary?: boolean): T | undefined;
/** /**
* Resolve a necessary dependency. * Resolve a necessary dependency.
* @param identifier The identifier of the dependency. * @param identifier The identifier of the dependency.
* @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. * @returns The resolved dependency.
* @throws Error if the dependency is not found. * @throws A {@link DependencyResolutionError} if the dependency is not found.
*/ */
resolve<T>(identifier: string, necessary?: true): T; resolve<T>(identifier: Identifier, necessary?: true): T;
/** /**
* Resolve a non necessary dependency * Resolve a non necessary dependency
* @param identifier The identifier of the dependency * @param identifier The identifier of the dependency
* @param necessary Not necessary, does not throw an error if the dependency is not found. * @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 * @returns The resolved dependency or undefined if the dependency is not found
*/ */
resolve<T>(identifier: string, necessary?: false): T | undefined; resolve<T>(identifier: Identifier, necessary?: false): T | undefined;
} }
/** /**
* TSInjex Interface * Instance TSinjex Interface
*/ */
export interface ITSinjex extends ITSinjexRegister, ITSinjexResolve {} export interface ITSinjex extends ITSinjexRegister, ITSinjexResolve {}

View File

@@ -1,5 +1,6 @@
/** /**
* Generic constructor type. * Generic constructor type.
* This type is used to define a constructor of a class.
*/ */
export type GenericConstructor< export type GenericConstructor<
T extends abstract new (...args: unknown[]) => InstanceType<T>, T extends abstract new (...args: unknown[]) => InstanceType<T>,
@@ -7,6 +8,6 @@ export type GenericConstructor<
/** /**
* Force generic constructor type. * 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<T> = new (...args: unknown[]) => T; export type ForceConstructor<T> = new (...args: unknown[]) => T;

11
src/types/Identifier.ts Normal file
View File

@@ -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;