All files / lib/di di.ts

38.77% Statements 19/49
0% Branches 0/24
50% Functions 7/14
25% Lines 10/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 1278x 8x 8x   8x                 8x           11x           8x           8x 8x                                                                                                                                                                                           8x
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();