Source: lib/index.js

/**
 * @module
 * @author Özüm Eldoğan
 */
/*jslint node: true, nomen: true, regexp: true */
/*global __dirname */
"use strict";

process.env.SUPPRESS_NO_CONFIG_WARNING = true;

var fs              = require('fs-extra');
var path            = require('path');
var pgStructure     = require('pg-structure');
var cons            = require('consolidate');
var async           = require('async');
var beautify        = require('js-beautify').js_beautify;
var inflection      = require('inflection');
var config          = require('config');
var lodash          = require('lodash');
var winston         = require('winston');
var logger          = new (winston.Logger)({
    transports: [
        new (winston.transports.Console)({ level: 'info' }),
        new (winston.transports.File)({ level: 'error', filename: 'sequelize-pg-generator.log', timestamp: true })
    ]
});
require('./_polyfill-quote.js');       // add String's prototype quote function.

/**
 * @private
 * @typedef {Object} template
 * @property {string} engine - Template engine to use.
 * @property {string} extension - File extension to use in template files.
 * @property {string} folder - Template folder to look for templates.
 */


/**
 * Gets requested config by prefixing the key with 'sequelize-pg-generator.'
 * As a result getConfig('database.host') returns result of config.get('sequelize-pg-generator.database.host')
 * @private
 * @param configKey
 * @returns {Object|any}
 */
function getConfig(configKey) {
    configKey = 'sequelize-pg-generator.' + configKey;
    return config.get(configKey);
}


/**
 * Gets requested config value. Returns more specific config for table if there is one, returns general otherwise.
 * Specific configs are config keys having suffix "Override".
 * @private
 * @param {string} table - Name of the table. This will be used to find specific config for the table.
 * @param {string} configName - Configuration name to look for.
 * @returns {*}
 * @example
 * var config = {
 *     "generate": {
 *         "columnDescription": false
 *     },
 *     "generateOverride: {
 *         "account": {
 *             "columnDescription": true
 *         }
 *     }
 * };
 *
 * console.log(getSpecificConfig('contact.columnDescription'));  // false
 * console.log(getSpecificConfig('account.columnDescription'));  // true
 */
function getSpecificConfig(table, configName) {
    var specific  = configName.replace(/^(.+?)\./, '$1Override.' + table + '.'); // generateOverride.product_category.columnDefault
    return config.has(specific) ? getConfig(specific) : getConfig(configName);
}


/**
 * Renders the template and executes callback function.
 * @private
 * @param {string} filePath - Path of the template file relative to template folder.
 * @param {object} locals - Local variables to pass to template
 * @param {function} fn - Callback function(err, output)
 */
function template(filePath, locals, fn) {
    filePath = path.join(getConfig('template.folder'), filePath  + '.' + getConfig('template.extension')); // i.e. index -> index.ejs
    return cons[getConfig('template.engine')].call(null, filePath, locals, fn);                            // i.e. cons.swig('views/page.html', { user: 'tobi' }, function(err, html){}
}


/**
 * Parses by reverse engineering using pgStructure module and calls callback.
 * @private
 * @param {function} callback - Callback function(err, structure)
 */
function parseDB(callback) {
    pgStructure(getConfig('database.host'), getConfig('database.database'), getConfig('database.user'),
        getConfig('database.password'), { port: getConfig('database.port'), schema: getConfig('database.schema') },
        callback);
}


/**
 * Given plain object, this function does the following:
 * - Strips null or undefined values from object,
 * - Add quotes around string values
 * @private
 * @param {object} obj - Simple object to filter.
 * @returns {object}
 */
function filterAttributes(obj) {
    var attribute;
    for (attribute in obj) {
        if (obj.hasOwnProperty(attribute)) {
            if (obj[attribute] === undefined || obj[attribute] === null) {
                delete obj[attribute];
            } else if (lodash.isString(obj[attribute])) {
                obj[attribute] = obj[attribute].quote();    // DO NOT quote type string. It is a JS variable.
            }
        }
    }
    return obj;
}


/**
 * Clears SQL type escapes (Two quote '' to one quote ') and strips beginning and trailing quotes around string.
 * @private
 * @param {string} string - Default
 * @returns {string|boolean|undefined}
 * @example
 * var clear = clearDefaultValue("'No ''value'' given'");
 * console.log(clear);    // No value 'given'
 */
function clearDefaultValue(string) {
    // Does not support SQL functions. IMHO it is better to handle sql function default values in RDMS.
    var defaultValue = undefined, lowercaseString, booleanValue;
    if (string.charAt(0) === "'" || string.charAt(0) === '"') {
        string = string.substring(1, string.length - 1);
        defaultValue = string.replace(/''/g, "'");
    } else {
        lowercaseString = string.toLowerCase()
        booleanValue = (lowercaseString === 'true');
        if (booleanValue || lowercaseString === 'false') {
            defaultValue = booleanValue;
        }
    }
    return defaultValue;
}


/**
 * Calculates belongsTo relation name based on table, column and config.
 * @private
 * @param {object} fkc - pg-structure foreign key constraint object.
 * @returns {string} - Name for the belongsTo relationship.
 */
function getBelongsToName(fkc) {
    var as          = fkc.foreignKey(0).name(),                                             // company_id
        tableName   = fkc.table().name(),
        camelCase   = getSpecificConfig(tableName, 'generate.relationAccessorCamelCase'),
        prefix      = getSpecificConfig(tableName, 'generate.prefixForBelongsTo');          // related

    if (as.match(/_id$/i)) {
        as = as.replace(/_id$/i, '');                                                       // company_id -> company
    } else {
        as = prefix + '_' + as;                                                             // company -> related_company
    }
    if (camelCase) {
        as = inflection.camelize(inflection.underscore(as), true);
    }
    return inflection.singularize(as);
}


/**
 * Calculates belongsToMany relation name based on table, column and config.
 * @private
 * @param {object} hasManyThrough - pg-structure foreign key constraint object.
 * @returns {string} - Name for the belongsToMany relationship.
 */
function getBelongsToManyName(hasManyThrough) {
    var tfkc            = hasManyThrough.throughForeignKeyConstraint(),
        as              = tfkc.foreignKey(0).name(),                                            // company_id
        throughTableName    = hasManyThrough.through().name(),
        relationName    = hasManyThrough.throughForeignKeyConstraintToSelf().name(),
        tableName       = hasManyThrough.table().name(),
        camelCase       = getSpecificConfig(tableName, 'generate.relationAccessorCamelCase'),
        prefix          = getSpecificConfig(tableName, 'generate.prefixForBelongsTo');          // related

    if (getSpecificConfig(tableName, 'generate.addRelationNameToManyToMany')) {
        if (getSpecificConfig(tableName, 'generate.stripFirstTableNameFromManyToMany')) {
            relationName = relationName.replace(new RegExp('^' + tableName + '[_-]?', 'i'), '');     // cart_cart_line_items -> cart_line_item
        }
        as = inflection.singularize(relationName) + '_' + as;
    } else if (getSpecificConfig(tableName, 'generate.addTableNameToManyToMany')) {
        as = inflection.singularize(throughTableName) + '_' + as;
    }

    if (as.match(/_id$/i)) {
        as = as.replace(/_id$/i, '');                                                       // company_id -> company
    } else {
        as = prefix + '_' + as;                                                             // company -> related_company
    }
    if (camelCase) {
        as = inflection.camelize(inflection.underscore(as), true);
    }
    return inflection.pluralize(as);
}


/**
 * Calculates hasMany relation name based on table, column and config.
 * @private
 * @param {object} hasMany - pg-structure foreign key constraint object.
 * @returns {string} - Name for the belongsTo relationship.
 */
function getHasManyName(hasMany) {
    var as, tableName;

    if (hasMany.through() !== undefined) {
        as = getBelongsToManyName(hasMany); //inflection.pluralize(getBelongsToName(hasMany.throughForeignKeyConstraint()));
    } else {
        as          = inflection.pluralize(hasMany.name());                  // cart_cart_line_items
        tableName   = hasMany.table().name();
        if (getSpecificConfig(tableName, 'generate.stripFirstTableFromHasMany')) {
            as = as.replace(new RegExp('^' + tableName + '[_-]?', 'i'), '');     // cart_cart_line_items -> cart_line_items
        }
        if (getSpecificConfig(tableName, 'generate.relationAccessorCamelCase')) {
            as = inflection.camelize(inflection.underscore(as), true);           // cart_line_items -> cartLineItems
        }
    }

    return as;
}


/**
 * Calculates model name for given table.
 * @private
 * @param {object} table - pg-structure table object.
 * @returns {string} - Model name for table
 */
function getModelNameFor(table) {
    var schemaName = getSpecificConfig(table.name(), 'generate.modelCamelCase') ? inflection.camelize(inflection.underscore(table.schema().name()), true) : table.schema().name(),
        tableName  = getSpecificConfig(table.name(), 'generate.modelCamelCase') ? inflection.camelize(inflection.underscore(table.name()), true) : table.name();

    return getSpecificConfig(table.name(), 'generate.useSchemaName') ? schemaName + '.' + tableName : tableName;
}


/**
 * Returns table details as plain object to use in templates.
 * @private
 * @param table
 * @returns {Object}
 */
function getTableOptions(table) {
    logger.debug('table details are calculated for: %s', table.name());
    var specificName    = 'tableOptionsOverride.' + table.name(),
        specificOptions = config.has(specificName) ? getConfig(specificName) : {},
        generalOptions  = getConfig('tableOptions'),
        otherOptions    = {
            modelName       : getModelNameFor(table),
            tableName       : table.name(),
            schema          : table.schema().name(),
            comment         : getSpecificConfig(table.name(), 'generate.tableDescription') ? table.description() : undefined,
            columns         : [],
            hasManies       : [],
            belongsTos      : [],
            belongsToManies : []
        },
        tableOptions            = filterAttributes(lodash.defaults(specificOptions, generalOptions, otherOptions));
    tableOptions.baseFileName   = table.name();
    return tableOptions;

}


/**
 * Returns column details as plain object to use in templates.
 * @private
 * @param {object} column - pg-structure column object
 * @returns {Object} - Simple object to use in template
 */
function getColumnDetails(column) {
    logger.debug('column details are calculated for: %s', column.name());
    var result = filterAttributes({
        source              : 'generator',
        accessorName        : getSpecificConfig(column.table().name(), 'generate.columnAccessorCamelCase') ? inflection.camelize(inflection.underscore(column.name()), true) : column.name(),
        name                : column.name(),
        primaryKey          : column.isPrimaryKey(),
        autoIncrement       : column.isAutoIncrement() && getSpecificConfig(column.table().name(), 'generate.columnAutoIncrement') ? true : undefined,
        allowNull           : column.allowNull(),
        defaultValue        : getSpecificConfig(column.table().name(), 'generate.columnDefault') && column.default() !== null ? clearDefaultValue(column.default()) : undefined,
        unique              : column.unique(),
        comment             : getSpecificConfig(column.table().name(), 'generate.columnDescription') ? column.description() : undefined,
        references          : column.foreignKeyConstraint() ? column.foreignKeyConstraint().referencesTable().name() : undefined,
        referencesKey       : column.foreignKeyConstraint() ? column.foreignKeyConstraint().foreignKey(0).name() : undefined,
        onUpdate            : column.onUpdate(),
        onDelete            : column.onDelete()
    });
    result.type = column.sequelizeType(getSpecificConfig(column.table().name(), 'generate.dataTypeVariable')); // To prevent type having quotes.
    return result;
}


/**
 * Returns hasMany details as plain object to use in templates.
 * @private
 * @param {object} hasMany - pg-structure hasMany object
 * @returns {Object} - Simple object to use in template
 */
function getHasManyDetails(hasMany) {
    logger.debug('hasMany%s details are calculated for: %s', hasMany.through() ? 'through' : '', hasMany.name());
    return filterAttributes({
        type                : 'hasMany',
        source              : 'generator',
        name                : hasMany.name(),
        model               : getModelNameFor(hasMany.referencesTable()),
        as                  : getHasManyName(hasMany),
        targetSchema        : hasMany.referencesTable().schema().name(),
        targetTable         : hasMany.referencesTable().name(),
        foreignKey          : hasMany.foreignKey(0).name(), // Sequelize support single key only
        onDelete            : hasMany.onDelete(),
        onUpdate            : hasMany.onUpdate(),
        through             : hasMany.through() ? hasMany.through().name() : undefined
    });
}


/**
 * Returns belongsTo details as plain object to use in templates.
 * @private
 * @param {object} fkc - pg-structure belongsTo object
 * @returns {Object} - Simple object to use in template
 */
function getBelongsToDetails(fkc) {
    logger.debug('belongsTo details are calculated for: %s', fkc.name());
    return filterAttributes({
        type                : 'belongsTo',
        source              : 'generator',
        name                : fkc.name(),
        model               : getModelNameFor(fkc.referencesTable()),
        as                  : getBelongsToName(fkc),
        targetSchema        : fkc.referencesTable().schema().name(),
        targetTable         : fkc.referencesTable().name(),
        foreignKey          : fkc.foreignKey(0).name(), // Sequelize support single key only
        onDelete            : fkc.onDelete(),
        onUpdate            : fkc.onUpdate()
    });
}

/**
 * Returns belongsToMany details as plain object to use in templates.
 * @private
 * @param {object} hasManyThrough - pg-structure hasManyThrough object
 * @returns {Object} - Simple object to use in template
 */
function getBelongsToManyDetails(hasManyThrough) {
    logger.debug('belongsToMany details are calculated for: %s', hasManyThrough.name());
    return filterAttributes({
        type                : 'belongsToMany',
        source              : 'generator',
        name                : hasManyThrough.name(),
        model               : getModelNameFor(hasManyThrough.referencesTable()),
        as                  : getBelongsToManyName(hasManyThrough),
        targetSchema        : hasManyThrough.referencesTable().schema().name(),
        targetTable         : hasManyThrough.referencesTable().name(),
        foreignKey          : hasManyThrough.foreignKey(0).name(),      // Sequelize support single key only
        otherKey            : hasManyThrough.throughForeignKeyConstraint().foreignKey(0).name(),        // Sequelize support single key only
        onDelete            : hasManyThrough.onDelete(),
        onUpdate            : hasManyThrough.onUpdate(),
        through             : hasManyThrough.through().name()
    });
}


/**
 * Calculates and returns file name based on schema and table.
 * @private
 * @param {object} table - pg-structure table object
 * @returns {string} - file name for the model
 */
function getFileName(table) {
    var fileName = table.name() + '.js';
    if (getSpecificConfig(table.name(), 'generate.useSchemaName')) {        // Prefix with schema name if config requested it.
        fileName =  table.schema().name() + '_' + fileName;
    }
    return fileName;
}


/**
 * Returns if table is in the list of tables to be skipped. Looks for schema.table and table name.
 * @private
 * @param {object} table - pg-structure object
 * @param {string} detail - Type of object to include it in explanation
 * @returns {boolean}
 */
function shouldSkip(table, detail) {
    var skipTable = getConfig('generate.skipTable');            // Do not auto generate files for those tables.
    if (skipTable.indexOf(table.name()) !== -1 || skipTable.indexOf(table.schema().name() + '.' + table.name()) !== -1) {
        if (getConfig('output.log')) {
            if (detail === 'table') {
                logger.info('(Skipped ' + detail + ') File \'' + getFileName(table) + '\' is skipped for model \'' + getModelNameFor(table) + '\'');
            } else if (detail === 'relation') {
                logger.info('(Skipped ' + detail + ') Relation is skipped for model \'' + getModelNameFor(table) + '\'');
            }

        }
        return true;
    }
    return false;
}

/**
 * Validates model relations naming. If any relation has same alias with any column and/or relation, this function
 * returns description of the error as string.
 * @private
 * @param {Object} templateTable -
 * @param {Object} relation -
 */
function validateNaming(templateTable) {
    var ri, ci, pi, relationToValidate, column, catched, relation, duplicates = [];

    for (ri = 0; ri < templateTable.relations.length; ri = ri + 1) {
        relationToValidate = templateTable.relations[ri];

        // Check against columns
        for (ci = 0, catched = false; ci < templateTable.columns.length && !catched; ci = ci + 1) {
            column = templateTable.columns[ci];
            if (column.name === relationToValidate.as) {
                logger.warn('ERROR: %s is already used. %s relation is trying to have alias of previously defined column with same name in table %s.%s.', relationToValidate.as, relationToValidate.type, templateTable.schema, templateTable.tableName);
                duplicates.push(relationToValidate.as);
                catched = true;
            }
        }

        // Check against previous relations. Later realitons will check against this already.
        for (pi = 0, catched = false; pi < ri && !catched; pi = pi + 1) {
            relation = templateTable.relations[pi];
            if (relation.as === relationToValidate.as) {
                logger.warn('ERROR: %s is already used. %s relation is trying to have alias of previously defined %s relation in table %s.%s.', relationToValidate.as, relationToValidate.type, relation.type, templateTable.schema, templateTable.tableName);
                duplicates.push(relationToValidate.as);
                catched = true;
            }
        }
    }

    if (duplicates.length > 0) {
        return 'Table ' + templateTable.schema + '.' + templateTable.tableName + ' has duplicate relation aliases: ' + duplicates.join(',') + '.';
    }

}


/**
 * Generates all model files.
 * @private
 * @param {object} db - pg-structure db object
 * @param {function} next - Callback to execute.
 */
function generateModelFiles(db, next) {
    var q, templateTable, allNamingErrors = [];

    q = async.queue(function (task, workerCallback) {
        var output = getConfig('output.beautify') ? beautify(task.content, { indent_size: getConfig('output.indent'), preserve_newlines: getConfig('output.preserveNewLines') }) : task.content;
        fs.writeFile(path.join(getConfig('output.folder'), 'definition-files', getFileName(task.table)), output, workerCallback);
        if (getConfig('output.log')) { logger.info('(Created) File \'' + getFileName(task.table) + '\' is created for model \'' + getModelNameFor(task.table) + '\''); }
    }, 4);

    db.schemas(function (schema) {
        schema.tables(function (table) {
            var namingError = '';
            logger.debug('Now parsing table: %s', table.name());
            if (getSpecificConfig(table.name(), 'generate.hasManyThrough') && getSpecificConfig(table.name(), 'generate.belongsToMany')) {
                next(new Error('Configuration error: Both "generate.hasManyThrough" and "generate.belongsToMany" configuration is set to be true for table "' + table.schema().name() + '.' + table.name() + '". They cannot be true at the same time.'));
            }

            if (shouldSkip(table, 'table')) { return; }
            templateTable           = getTableOptions(table);
            table.columns(function (column) {
                templateTable.columns.push(getColumnDetails(column));
            });
            table.hasManies(function (hasMany) {
                if (shouldSkip(hasMany.referencesTable(), 'relation')) { return; }
                templateTable.hasManies.push(getHasManyDetails(hasMany));
            });
            table.hasManyThroughs(function (hasManyThrough) {
                if (getSpecificConfig(table.name(), 'generate.addRelationNameToManyToMany') && getSpecificConfig(table.name(), 'generate.addTableNameToManyToMany')) {
                    next(new Error('Configuration error: Both "generate.addRelationNameToManyToMany" and "generate.addTableNameToManyToMany" configuration is set to be true for table "' + table.schema().name() + '.' + table.name() + '". They cannot be true at the same time.'));
                }

                if (shouldSkip(hasManyThrough.referencesTable(), 'relation') || shouldSkip(hasManyThrough.through(), 'relation')) { return; }
                if (getSpecificConfig(table.name(), 'generate.hasManyThrough')) {
                    templateTable.hasManies.push(getHasManyDetails(hasManyThrough)); // has many throughs are deprecated after Sequelize 2.0 RC3
                }
                if (getSpecificConfig(table.name(), 'generate.belongsToMany')) {
                    templateTable.belongsToManies.push(getBelongsToManyDetails(hasManyThrough));
                }
            });
            table.foreignKeyConstraints(function (fkc) {
                if (shouldSkip(fkc.referencesTable(), 'relation')) { return; }
                templateTable.belongsTos.push(getBelongsToDetails(fkc));
            });
            templateTable.relations = templateTable.hasManies.concat(templateTable.belongsTos, templateTable.belongsToManies);

            namingError = validateNaming(templateTable);

            if (namingError) {
                allNamingErrors.push(namingError);
            }

            template('index', {
                table: templateTable,
                mainScript: path.join(getConfig('output.folder'), 'index.js'),
                warning: getConfig('output.warning'),
                dataTypeVariable: getSpecificConfig(table.name(), 'generate.dataTypeVariable')
            }, function (err, result) {
                if (err) { next(err); }
                q.push({content: result, table: table}, function (err) { if (err) { next(err); } });
            });
        });
    });

    q.drain = function () {
        if (allNamingErrors.length > 0) {
            next(new Error('There are naming errors. Either set "generate.addRelationNameToManyToMany" true in configuration or rename your relations.\n' + allNamingErrors.join('\n')));
        } else {
            next(null);
        }
    };
}


/**
 * Creates 'definition-files' and 'definition-files-custom' directories if they do not exist.
 * Before creating definition-files it deletes definition-files directory.
 * @private
 * @param {function} next - Callback to execute
 */
function createOutputFolder(next) {
    var defPath         = path.join(getConfig('output.folder'), 'definition-files'),
        defPathCustom   = path.join(getConfig('output.folder'), 'definition-files-custom');

    fs.remove(defPath, function (err) {
        if (err) { next(err); }
        fs.createFile(path.join(defPath, '_Dont_add_or_edit_any_files'), function (err) {
            if (err) { next(err); }
            fs.mkdirs(defPathCustom, function (err) {
                if (err) { next(err); }
                next(null);
            });
        });
    });
}


/**
 * Generates index file by copying index.js from template directory to model directory. These locations come from
 * config.
 * @private
 * @param next
 */
function generateUtilityFiles(next) {
    fs.copy(path.join(getConfig('template.folder'), 'index.js'), path.join(getConfig('output.folder'), 'index.js'), function (err) {
        if (err) { next(err); return; }
        if (getConfig('output.log')) { logger.info('(Created) Index file: ' + path.resolve(path.join(getConfig('output.folder'), 'index.js'))); }
        fs.copy(path.join(getConfig('template.folder'), '..', 'utils.js'), path.join(getConfig('output.folder'), '..', 'utils.js'), function (err) {
            if (err) { next(err); return; }
            next(null);
        });
    });
}


/**
 * Combines default configuration, custom config file and command line options by overriding lower priority ones.
 * @private
 * @param {object} options - Options converted to config structure.
 */
function setupConfig(options) {
    if (options.resetConfig) {
        // Reset your environment variables for resetting node-config module. node-config is a singleton.
        // For testing it's necessary to reset and this is a workaround from node-config support.
        global.NODE_CONFIG = null;
        delete require.cache[require.resolve('config')];
        config = require('config');
        delete options.resetConfig;
    }

    var customConfigFile, customConfig,
        defaultConfig       = require('../config/default.js');

    options = options || {};

    if (options.config) {
        customConfigFile    = options.config.charAt(0) === '/' || options.config.charAt(0) === '\\' ? options.config : path.join(process.cwd(), options.config); // Absolute or relative
        customConfig        = require(customConfigFile);
    }

    delete options.config;

    // Combine configs and override lower priority configs.
    config.util.extendDeep(config, defaultConfig, customConfig || {}, { "sequelize-pg-generator": options });
}


/**
 * Converts options to config params by inserting root level values into correct position in config object.
 * For example: { host: '127.0.0.1' } -> { database: { host: '127.0.0.1'} }
 * @private
 * @param {Object} options - Options from constructor
 * @returns {{database: {}, output: {}}}
 */
function convertOptionsToConfig(options) {
    var result = { database: {}, output: {} };
    // Copy all database options to options global variable to override config.
    ['host', 'port', 'database', 'user', 'password', 'schema'].forEach(function (option) {
        if (options[option] !== undefined) {
            result.database[option] = options[option];
            delete options[option];
        }
    });

    if (options.nolog !== undefined) {
        result.output.log = !options.nolog; // Reverse of the nolog
        delete options.nolog;
    }

    // Override output folder if given.
    if (options.output !== undefined) {
        result.output.folder = options.output;
        delete options.output;
    }

    // Override template folder
    if (options.templateName !== undefined) {
        if (options.template === undefined) {
            options.template = {};
        }
        options.template.folder = path.join(__dirname, '..', 'template', options.templateName);
        delete options.templateName;
    }

    // Copy remaining non deleted options to root of result.
    lodash.defaults(result, options);
    return result;
}


/**
 * Generates model files for Sequelize ORM.
 * @param {function} callback - Function to execute after completion of auto generation. callback(err)
 * @param {object} options - Options to override configuration parameters from config file
 * @param {string} options.host - IP address or host name of the database server
 * @param {number} options.port - Port of database server to connect
 * @param {string} options.database - Database name
 * @param {string} options.user - Username to connect to database
 * @param {string} options.password - Password to connect to database
 * @param {Array} options.schema - List of comma separated names of the database schemas to traverse. Example public,extra_schema.
 * @param {string} options.output - Output folder
 * @param {string} options.config - Path of the configuration file
 * @param {boolean} options.nolog - Don't output log of generated files.
 * @param {boolean} options.resetConfig - Reset configuration via side-step solution to prevent singleton behaviour. (Not recomended for production)
 */
module.exports = function (callback, options) {
    var delayedError;
    setupConfig(convertOptionsToConfig(options));

    if (!getConfig('database.database') || !getConfig('database.host')) {
        callback(new Error('Host and database are required'));
        return;
    }
    if (getConfig('generate.hasManyThrough') && getConfig('generate.belongsToMany')) {
        callback(new Error('Configuration error: Both "generate.hasManyThrough" and "generate.belongsToMany" configuration is set to be true. They cannot be true at the same time.'));
        return;
    }
    if (getConfig('generate.addRelationNameToManyToMany') && getConfig('generate.addTableNameToManyToMany')) {
        callback(new Error('Configuration error: Both "generate.addRelationNameToManyToMany" and "generate.addTableNameToManyToMany" configuration is set to be true. They cannot be true at the same time.'));
    }

    async.waterfall([
        createOutputFolder,
        generateUtilityFiles,
        parseDB,
        generateModelFiles
    ], function (err) {
        if (err) {
            callback(err);
        } else {
            callback(null);
        }
    });
};
sequelize-pg-generator Copyright © 2014 Özüm Eldoğan.
Documentation generated by JSDoc 3.3.1 on Thu Sep 10th 2015 using the DocStrap template.