libflitter/config/ConfigUnit.js

/**
 * @module libflitter/config/ConfigUnit
 */

const CanonicalUnit = require('../canon/CanonicalUnit')
const path = require('path')

// Private helper functions
// Some of these are duplicated in UtilityUnit,
// however ConfigUnit starts before UtilityUnit
// so they must be included here.
const priv = {
    /**
     * Grabs an environment variable by name and tries to infer its type.
     * @param {string} name
     * @param default_value
     * @return {string|null|boolean|number}
     */
    env(name, default_value){
        const val = process.env[name]
        if ( !val ) return typeof default_value !== "undefined" ? default_value : null
        else return this.infer(val)
    },

    /**
     * Attempt to infer the variable type of a string's data.
     * @param {string} val
     * @return {boolean|null|*|number|undefined}
     */
    infer(val){
        if ( !val ) return null
        else if ( val.toLowerCase() === 'true' ) return true
        else if ( val.toLowerCase() === 'false' ) return false
        else if ( !isNaN(val) ) return +val
        else if ( this.is_json(val) ) return JSON.parse(val)
        else if ( val.toLowerCase() === 'null' ) return null
        else if ( val.toLowerCase() === 'undefined' ) return undefined
        else return val
    },

    /**
     * Checks if a string is valid JSON.
     * @param string
     * @return {boolean} - true if the string is valid JSON
     */
    is_json(string){
        try {
            JSON.parse(string)
            return true
        }
        catch (e) {
            return false
        }
    }
}

/**
 * Unit to load and manage config files.
 * @extends module:libflitter/canon/CanonicalUnit~CanonicalUnit
 */
class ConfigUnit extends CanonicalUnit {
    /**
     * Name of the service provided by this unit: 'configs'
     * @returns {string} - 'configs'
     */
    static get name() {
        return 'configs'
    }

    /**
     * Instantiate the unit.
     * @param {string} [base_directory = './config'] - the base directory
     */
    constructor(base_directory = './config') {
        super(base_directory)

        /**
         * The canonical name of the item.
         * @type {string} = 'config'
         */
        this.canonical_item = 'config'

        /**
         * The file extension of the canonical item files.
         * @type {string} - '.config.js'
         */
        this.suffix = '.config.js'
    }

    /**
     * Initialize the unit. Load the dotenv configuration.
     * @param app
     * @returns {Promise<void>}
     */
    async go(app) {
        require('dotenv').config()
        global.env = priv.env.bind(priv)
        await super.go(app)
        delete global.env
    }

    /**
     * A helper function that calls {@link module:libflitter/config/ConfigUnit~ConfigUnit#get}, but
     * guarantees an object is returned, even if no config is found for the given accessor.
     * @param {string} accessor - period-delineated access string
     * @param {Object} [merge = {}] - default values to override
     * @return {Object}
     */
    guarantee(accessor, merge = {}) {
        const config = this.get(accessor)
        const grant = config ? config : {}
        return {...merge, ...grant}
    }

    /**
     * Get the templates provided by this unit.
     * Currently, "config" provided by {@link module:libflitter/templates/config}
     * @returns {{config: {template: (config|(function(string): string)|*), extension: string, directory: string}}}
     */
    templates() {
        return {
            config: {
                template: require('../templates/config'),
                directory: this.directory,
                extension: '.config.js'
            }
        }
    }

    /**
     * Get the fully-qualified path to the migrations provided by this unit.
     * @returns {string}
     */
    migrations(){
        return path.resolve(__dirname, 'migrations')
    }
}

module.exports = exports = ConfigUnit