All files / di di.ts

51.02% Statements 25/49
20.83% Branches 5/24
64.28% Functions 9/14
40% Lines 16/40

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 1276x 6x 6x   6x                 6x           6x           6x           6x 9x                 8x 5x                     11x 11x 1x   10x                                                                                                                                         6x
import 'reflect-metadata';
import * as fs from 'fs';
import * as path from 'path';
 
const DECORATOR_KEY = 'custom:decorator';
 
export type Token<T> = new (...args: any[]) => T;
 
/**
 * @decorator Controller
 * @description A class decorator that marks a class as a controller.
 * @returns {ClassDecorator}
 */
export const Controller = (): ClassDecorator => (target: object) => Reflect.defineMetadata(DECORATOR_KEY, 'controller', target);
/**
 * @decorator Service
 * @description A class decorator that marks a class as a service.
 * @returns {ClassDecorator}
 */
export const Service = (): ClassDecorator => (target: object) => Reflect.defineMetadata(DECORATOR_KEY, 'service', target);
/**
 * @decorator Repository
 * @description A class decorator that marks a class as a repository.
 * @returns {ClassDecorator}
 */
export const Repository = (): ClassDecorator => (target: object) => Reflect.defineMetadata(DECORATOR_KEY, 'repository', target);
 
/**
 * @class DependencyInjectionContainer
 * @description A container for managing dependencies.
 */
export class DependencyInjectionContainer {
    private dependencies: Map<Token<any>, any> = new Map();
 
    /**
     * @method register
     * @description Registers a dependency with the container.
     * @param {Token<T>} token - The token to register the dependency with.
     * @param {T} instance - The instance of the dependency.
     */
    public register<T>(token: Token<T>, instance: T): void {
        if (!this.dependencies.has(token)) {
            this.dependencies.set(token, instance);
        }
    }
 
    /**
     * @method resolve
     * @description Resolves a dependency from the container.
     * @param {Token<T>} token - The token of the dependency to resolve.
     * @returns {T} The resolved dependency.
     */
    public resolve<T>(token: Token<T>): T {
        const instance = this.dependencies.get(token);
        if (!instance) {
            throw new Error(`Dependency not found for token: ${token.name || token}`);
        }
        return instance;
    }
 
    /**
     * @method load
     * @description Loads all dependencies from a directory.
     * @param {string} srcPath - The path to the directory to load dependencies from.
     */
    public load(srcPath: string): void {
        const absolutePath = path.resolve(srcPath);
        this.loadFromDirectory(absolutePath);
    }
 
    /**
     * @method loadFromDirectory
     * @description Recursively loads all dependencies from a directory.
     * @param {string} directory - The directory to load dependencies from.
     */
    private loadFromDirectory(directory: string): void {
        const files = fs.readdirSync(directory);
 
        for (const file of files) {
            const fullPath = path.join(directory, file);
            const stat = fs.statSync(fullPath);
 
            if (stat.isDirectory() && path.basename(fullPath) !== 'lib' && path.basename(fullPath) !== 'test') {
                this.loadFromDirectory(fullPath);
            } else if (stat.isFile() && (fullPath.endsWith('.ts') || fullPath.endsWith('.js'))) {
                this.loadDependenciesFromFile(fullPath);
            }
        }
    }
 
    /**
     * @method loadDependenciesFromFile
     * @description Loads all dependencies from a file.
     * @param {string} filePath - The path to the file to load dependencies from.
     */
    private loadDependenciesFromFile(filePath: string): void {
        const module = require(filePath);
        for (const key in module) {
            const exported = module[key];
            if (typeof exported === 'function' && Reflect.getMetadata(DECORATOR_KEY, exported)) {
                this.resolveAndRegister(exported);
            }
        }
    }
 
    /**
     * @method resolveAndRegister
     * @description Resolves and registers a dependency and its dependencies.
     * @param {T} target - The dependency to resolve and register.
     */
    private resolveAndRegister<T extends { new(...args: any[]): {} }>(target: T): void {
        if (this.dependencies.has(target)) {
            return;
        }
 
        const paramTypes = Reflect.getMetadata('design:paramtypes', target) || [];
        const resolvedDependencies = paramTypes.map((param: Token<any>) => {
            this.resolveAndRegister(param);
            return this.resolve(param);
        });
 
        const instance = new target(...resolvedDependencies);
        this.register(target, instance);
        console.log(`Loaded: ${target.name}`);
    }
}
export const container = new DependencyInjectionContainer();