index.js

import Symbol from 'es6-symbol';
import isString from 'is-string';
import Namespace from './namespace';
import Storage from './storage';
import Resolver from './resolver';
import parseSettings from './utils/settings';
import path from './utils/path';
import { requires, assert } from './utils/assertions';

const INVALID_MODULE_PATH = 'Invalid module path';
const INVALID_INPUT_TYPE = 'Invalid input type';

const FIELDS = {
    resolver: Symbol('resolver'),
    storage: Symbol('storage')
};

function getNamespace(input) {
    if (!input) {
        return null;
    }

    let result = null;

    if (isString(input)) {
        result = input;
    } else if (input instanceof Namespace) {
        result = input.getName();
    } else {
        throw new Error(INVALID_INPUT_TYPE);
    }

    return result;
}

/**
 * Represents a container for registered modules.
 */
class Container extends Namespace {
    /**
     * Converts object/array to a function chain that's help to use namespaces.
     * @param {(Object|Array<any>)} namespaces - An object or an array of nested namespace names.
     * @param {string} [separator] - Namespace separator.
     * @returns {function} Chain of functions that converts a module name into a full module path.
     */
    static map(namespaces, separator = '/') {
        return path.map(namespaces, separator);
    }

    /**
     * Creates a new instance of Container.
     * @param {string} [params="/"] - Namespace separator.
     * @param {object} [params=null] - Container settings.
     * @param {string} [params.separator="/"] - Namespace separator.
     * @param {boolean} [params.panic=false] - Indicates whether it needs to throw an error when module not found.
     */
    constructor(params) {
        const settings = parseSettings(params);
        const storage = new Storage(settings.separator, settings.panic);

        super(storage, '', settings.separator);

        // Ouch! We have to keep ref twice :(
        this[FIELDS.storage] = storage;
        this[FIELDS.resolver] = new Resolver(this[FIELDS.storage]);
    }

    /**
     * Determines whether a module exists by a given path.
     * @param {string} fullPath - Module full path.
     * @return {boolean} Value that determines whether a module exists by a given path.
     */
    contains(fullPath) {
        requires('full path', fullPath);
        assert(isString(fullPath), INVALID_MODULE_PATH);

        return this[FIELDS.storage].contains(fullPath);
    }

    /**
     * Returns size of whole container or a namespace.
     * If namespace was given, count of items inside this namespace will be returned.
     * @param {(Namespace|string)} [namespace=undefine] Namespace or namespace name
     * @returns {number} Size of a container/namespace.
     */
    size(namespace) {
        return this[FIELDS.storage].size(getNamespace(namespace));
    }

    /**
     * Clears a container or a given namespace.
     * If namespace name is passed - removes all modules in the namespace.
     * @param {(Namespace|string)} [namespace=undefine] Namespace or namespace name
     * @returns {Container} Returns current instance of Container.
     */
    clear(namespace) {
        this[FIELDS.storage].clear(getNamespace(namespace));

        return this;
    }

    /**
     * Resolves a module by a given full path.
     * @param {string} fullPath - Module's full path.
     * @returns {any} Module value.
     */
    resolve(fullPath) {
        requires('full path', fullPath);
        assert(isString(fullPath), INVALID_MODULE_PATH);

        return this[FIELDS.resolver].resolve(fullPath);
    }

    /**
     * Resolves all modules from a given namespace.
     * @param {(Namespace|string)} namespace - Namespace or namespace name
     * @param {boolean} [nested=false] - Value that detects whether it needs to resolve modules from nested namespaces.
     * If 'true', all resolved values will be put into an array.
     * @returns {Map<string, (any|Array<any>)>} Map of module values, where key is a module name.
     */
    resolveAll(namespace, nested = false) {
        requires('namespace', namespace);

        return this[FIELDS.resolver].resolveAll(getNamespace(namespace), nested);
    }
}

module.exports = Container;