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 { TSinjex } from '../TSinjex';
import { TSinjex } from '../classes/TSinjex';
test_IDIContainer(TSinjex);

View File

@@ -1,25 +1,47 @@
import { ImplementsStatic } from './helper/ImplementsStatic';
import { DependencyResolutionError } from './interfaces/Exceptions';
import { IDependency } from './interfaces/IDependency';
import { ITSinjex, ITSinjex_ } from './interfaces/ITSinjex';
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**: Dependency Injection Container
* # 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<ITSinjex_>()
export class TSinjex implements ITSinjex {
/**
* The singleton instance of the TSinjex class.
*/
private static _instance: TSinjex;
private readonly _dependencies = new Map<string, IDependency>();
/**
* The dependencies map.
*/
private readonly _dependencies = new Map<Identifier, IDependency>();
/**
* Private constructor to prevent direct instantiation.
*/
private constructor() {}
//#region IDIContainer_
//#region ITSinjex_ (Static)
/**
* Retrieves the singleton instance of DependencyRegistry.
* Get the **singleton** TSInjex instance.
* @returns The singleton instance.
*/
public static getInstance(): ITSinjex {
@@ -31,11 +53,12 @@ export class TSinjex implements ITSinjex {
}
/**
* @inheritdoc
* Static implementation of {@link ITSinjex.register}.
* @see {@link ITSinjex.register}
* @inheritdoc
*/
public static register<T>(
identifier: string,
identifier: Identifier,
dependency: T,
deprecated = false,
): void {
@@ -46,11 +69,12 @@ export class TSinjex implements ITSinjex {
}
/**
* @inheritdoc
* Static implementation of {@link ITSinjex.resolve}.
* @see {@link ITSinjex.resolve}
* @inheritdoc
*/
public static resolve<T>(
identifier: string,
identifier: Identifier,
necessary = true,
): T | undefined {
return (TSinjex.getInstance() as TSinjex).resolve<T>(
@@ -61,13 +85,13 @@ export class TSinjex implements ITSinjex {
//#endregion
//#region IDIContainer
//#region ITSinjex (Instance)
/**
* @inheritdoc
*/
public register<T>(
identifier: string,
identifier: Identifier,
dependency: T,
deprecated = false,
): void {
@@ -80,7 +104,7 @@ export class TSinjex implements ITSinjex {
/**
* @inheritdoc
*/
public resolve<T>(identifier: string, necessary = true): T | undefined {
public resolve<T>(identifier: Identifier, necessary = true): T | undefined {
const dependency = this._dependencies.get(identifier);
if (necessary && !dependency) {
@@ -90,7 +114,6 @@ export class TSinjex implements ITSinjex {
}
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.

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';
/**
* 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<T, U>(
identifier: string,
identifier: Identifier,
init?: InitDelegate<T, U>,
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.
* @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<TargetType>,
>(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();

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';
/**
* 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<TargetType>,
>(
identifier: string,
identifier: Identifier,
init?: InitDelegate<
TargetType & { new (..._args: unknown[]): 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.
* @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<T>(identifier: string, dependency: T): void;
export function register<T>(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<T>(
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<T>(
identifier: string,
identifier: Identifier,
dependency: T,
deprecated?: boolean,
): void {

View File

@@ -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<T>(identifier: string): T;
export function resolve<T>(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<T>(identifier: string, necessary: false): T | undefined;
export function resolve<T>(
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<T>(
identifier: string,
identifier: Identifier,
necessary?: boolean,
): T | undefined {
return TSinjex.getInstance().resolve<T>(identifier, necessary);

View File

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

View File

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

View File

@@ -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<T>(identifier: string, dependency: T, deprecated?: boolean): void;
register<T>(
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<T>(identifier: string, dependency: T, deprecated?: true): void;
register<T>(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<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 {
/**
@@ -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<T>(identifier: string, necessary?: boolean): T | undefined;
resolve<T>(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<T>(identifier: string, necessary?: true): T;
resolve<T>(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<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 {}

View File

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