di/src/DependencyInjector.js

/**
 * @module flitter-di/src/DependencyInjector
 */

/**
 * @type {typeof module:flitter-di/src/Container~Container}
 */
const Container = require('./Container')

/** Manages services and injects classes from its service container. */
class DependencyInjector {
    constructor(container = new Container()) {
        /**
         * The service container used by this dependency injector.
         * @type {module:flitter-di/src/Container~Container}
         */
        this.container = container
        this.container.di = this
    }

    /**
     * Instantiate the passed in class. If it is injectable, it will be injected.
     * @param {typeof module:flitter-di/src/Injectable~Injectable} Class
     * @param {...*} args - additional arguments to be passed to the constructor of the class
     * @returns {*} - the injected static reference to the Class
     */
    make(Class, ...args) {
        if ( this.__is_injectable(Class) ) {
            this.inject(Class)
        }

        return new Class(...args)
    }

    /**
     * Inject the passed in Class with the IoC items it requires.
     * @param {typeof module:flitter-di/src/Injectable~Injectable} Class
     * @returns {*}
     */
    inject(Class) {
        if ( !this.__is_injectable(Class) ) {
            throw new TypeError(`Cannot inject non-injectable class: ${Class.name}`)
        }

        Class.__inject(this.container)
        return Class
    }

    /**
     * Fetch a service by name. If no name is provided, return the service
     * proxy container. This container has getters for all the services by
     * name.
     * @deprecated - prefer DependencyInjector.get. This will be removed in the future.
     * @param {string} name - the name of the service
     * @returns {module:flitter-di/src/Service~Service|undefined|Proxy} - the service instance or service container proxy
     */
    service(name) {
        return this.container.get(name)
    }

    /**
     * Fetch an IoC item by name.
     * @param {string} name
     * @returns {*}
     */
    get(name) {
        return this.container.get(name)
    }

    /**
     * Verify that the injector's container has a service or set of services.
     * @param {string|Array<string>} name - service name or array of service names
     * @returns {boolean} - true if the container has the service(s)
     */
    has(name) {
        if ( Array.isArray(name) ) {
            return name.every(x => this.container.has(x))
        }

        return this.container.has(name)
    }

    /**
     * If called, this method will extend the global nodejs require() method to
     * check for injectable classes. If a require value is injectable, it will be
     * automatically injected with the services from this DI.
     */
    inject_globally() {
        const Module = require('module')
        const original_require = Module.prototype.require
        const di = this

        di._original_require = original_require

        Module.prototype.require = function() {
            const value = original_require.apply(this, arguments)
            return di.__is_injectable(value) ? di.inject(value) : value
        }
    }

    /**
     * Verify that a class is injectable. This means that it has a static __inject
     * method and that method takes at least one argument. In almost all cases, this
     * should be satisfied by using the Injectable base class.
     * @param {*} Class - the class to check
     * @returns {boolean} - true if the class is injectable
     * @private
     */
    __is_injectable(Class) {
        return (
            typeof Class === 'function'
            && typeof Class.__inject === 'function'
            && Class.__inject.length > 0
        )
    }
}

module.exports = exports = DependencyInjector