@absolunet/ioc2.1.0

View on GitHub

config/repositories/ConfigRepository.js

//--------------------------------------------------------
//-- Node IoC - Config - Config Repository
//--------------------------------------------------------

import __  from '@absolunet/private-registry';
import dot from 'dot-object';


/**
 * Configuration repository that loads, stores and exposes application configuration.
 *
 * @memberof config.repositories
 * @hideconstructor
 */
class ConfigRepository {

	/**
	 * Class dependencies: <code>['app', 'config.grammar', 'file']</code>.
	 *
	 * @type {Array<string>}
	 */
	static get dependencies() {
		return ['app', 'config.grammar', 'file'];
	}

	/**
	 * @inheritdoc
	 * @private
	 */
	init() {
		this.setConfig({});
		this.loadConfigFromFolder(this.app.configPath(), true);
	}

	/**
	 * Get configuration value.
	 *
	 * @param {string|null} [key] - The configuration key.
	 * @param {*|null} [defaultValue] - The default value to use if the value is not in configuration.
	 * @returns {*} The configuration value.
	 */
	get(key = null, defaultValue = null) {
		const config = Object.assign({}, __(this).get('config'));
		const value  = typeof key === 'string' ? dot.pick(key, config) : config;

		return typeof value === 'undefined' ? defaultValue : value;
	}

	/**
	 * Set configuration value.
	 *
	 * @param {string} key - The configuration key.
	 * @param {*} value - The configuration value.
	 * @returns {config.repositories.ConfigRepository} The current config repository instance.
	 */
	set(key, value) {
		if (typeof key === 'string') {
			dot.override = true;
			const formattedValue = this.formatValues(value);
			dot.str(key, formattedValue, __(this).get('config'));
			dot.override = false;
		} else {
			this.setConfig(key);
		}

		return this;
	}

	/**
	 * Merge configuration value to existing value.
	 *
	 * @param {string} key - The configuration key.
	 * @param {*} value - The configuration value.
	 * @param {boolean} [overwrite] - Flag that indicates if the old value should be overwritten or merged.
	 * @returns {config.repositories.ConfigRepository} The current config repository instance.
	 */
	merge(key, value, overwrite = false) {
		let mergedValue = value;

		if (typeof value === 'object' && value && !Array.isArray(value)) {
			const original = this.get(key, {});
			mergedValue    = overwrite ? Object.assign(original, value) : Object.assign({}, value, original);
		}

		return this.set(key, mergedValue);
	}

	/**
	 * Set global configuration.
	 *
	 * @param {*} config - The full configuration object.
	 * @returns {config.repositories.ConfigRepository} The current config repository instance.
	 */
	setConfig(config) {
		__(this).set('config', this.formatValues(config));

		return this;
	}

	/**
	 * Set global configuration based on folder files.
	 *
	 * @param {string} folder - The folder to search configuration file into.
	 * @param {boolean} [overwrite] - Flag that indicates if the current configuration should be overridden or merged.
	 * @returns {config.repositories.ConfigRepository} The current config repository instance.
	 */
	loadConfigFromFolder(folder, overwrite = false) {
		Object.entries(this.file.loadRecursivelyInFolder(folder)).forEach(([key, value]) => {
			this.merge(key.replace(/[/\\]/gu, '.'), value, overwrite);
		});

		return this;
	}

	/**
	 * Load configuration from file and store as a root index.
	 *
	 * @param {string} key - The configuration key.
	 * @param {string} filePath - The file to load which contains the configuration object for the specified key.
	 * @param {boolean} [overwrite] - Flag that indicates if the current configuration should be overridden or merged.
	 * @returns {config.repositories.ConfigRepository} The current config repository instance.
	 */
	loadConfig(key, filePath, overwrite = false) {
		return this.merge(key, this.file.load(filePath), overwrite);
	}

	/**
	 * Set global configuration from file.
	 *
	 * @param {string|Array<string>}file - The file, or set of files, to load to set config from. If a set of file is given, the first existing one will be used, and all the others will be ignored.
	 * @returns {config.repositories.ConfigRepository} The current config repository instance.
	 */
	setConfigFromFile(file) {
		const config = this.file.loadFirst(Array.isArray(file) ? file : [file]);

		if (config) {
			this.setConfig(config);
		}

		return this;
	}

	/**
	 * Format values with grammar.
	 *
	 * @param {*} config - The configuration value, either a single value or a whole object.
	 * @returns {*} The formatted value or values.
	 */
	formatValues(config) {
		if (typeof config === 'object' && config) {
			return Object.keys(config).reduce((object, key) => {
				object[key] = this.formatValues(object[key]);

				return object;
			}, config);
		}

		return this.configGrammar.format(config);
	}

}


export default ConfigRepository;