<commit_before>
'use strict';

var when  = require('when');
var Class = require('cakes').Class;

var Db    = require('./db');
var _     = require('./utils');
var errors = require('./errors');

/**
 * Model constructor.
 * @return {Functin} Constructor
 */
var Model = Class(function() {

    /**
     * Db instance
     * @type {Object}
     */
    var _dbInstances = {};

    return {

        /**
         * Model name
         * @type {String}
         */
        name: '',

        /**
         * Database name
         * @type {String}
         */
        dbName: '',

        /**
         * Table name
         * @type {String}
         */
        tableName: '',

        /**
         * Table prefix
         * @type {String}
         */
        tablePrefix: '',

        /**
         * Table fields
         * @type {Object}
         */
        fields: {},

        /**
         * Primary key
         * @type {String}
         */
        pk: '',

        /**
         * Data of model
         * @type {Object}
         */
        data: {},

        /**
         * Db config
         * @type {Object}
         */
        config: null,

        /**
         * Options
         * @type {Object}
         */
        options: {},

        /**
         * Auto increament
         * @type {Boolean}
         */
        isAutoInc: false,

        /**
         * Auto check table fields
         * @type {Boolean}
         */
        isAutoCheckFields: true,

        __construct: function(name, tablePrefix, config) {
            // initialize hook
            this._initialize();

            if (_.isString(name) && name !== '') {
                if (name.indexOf('.') !== -1) {
                    var parts = name.split('.');
                    this.dbName = parts[0];
                    this.name = parts[1];
                } else {
                    this.name = name;
                }
            }

            config = config || {};
            this.config = _.mapKeys(config, function(key) {
                return key.toLowerCase();
            });

            if (_.isEmpty(tablePrefix)) {
                this.tablePrefix = '';
            } else if (_.isString(tablePrefix)) {
                this.tablePrefix = tablePrefix;
            } else if (_.isString(this.config.prefix)) {
                this.tablePrefix = this.config.prefix;
            } else {
                this.tablePrefix = '';
            }
        },

        _initialize: function() {
            // hook
        },

        /**
         * Initialize db instance.
         * @return {Promise}
         */
        initDb: function() {
            var identifier = _.serialize(this.config);
            if (!this.instance) {
                if (!_dbInstances[identifier]) {
                    _dbInstances[identifier] = (new Db()).getInstance(this.config);
                }
            }

            // set this instance
            this.instance = _dbInstances[identifier];

            // hook
            this._afterDb();

            if (!_.isEmpty(this.name) && this.isAutoCheckFields) {
                return this.initFieldsInfo();
            }

            return when.promise;
        },

        /**
         * Initialize table fields info.
         * @return {Promise}
         */
        initFieldsInfo: function() {
            var self = this;

            return this.instance.getFields(this.getTableName()).then(function(fields) {
                var _name = [];
                var _type = {};
                _.forOwn(fields, function(attr, field) {
                    _name.push(field);

                    _type[field] = attr.type;
                    if (attr.primary) {
                        self.pk = field;

                        if (attr.autoinc) {
                            self.isAutoinc = true;
                        }
                    }
                });

                self.fields._name = _name;
                self.fields._type = _type;
                self.fields._pk = self.pk;

                return self.field;
            });
        },

        /**
         * Add a record.
         * @param  {Object}  data    Data of record
         * @param  {Object}  options Options
         * @param  {Boolean} replace Insert or replace
         * @return {Promise}
         */
        add: function(data, options, replace) {
            var self = this;

            if (!_.isObject(data) || _.isEmpty(data)) {
                if (!_.isEmpty(self.data)) {
                    data = self.data;
                    self.data = {};
                } else {
                    when.reject(new errors.DbError('[add]: Invalid data.'));
                }
            }

            return this.initDb().then(function() {
                options = self.parseOptions(options);
                data = self.facade(data);
                if (_.isEmpty(data)) {
                    when.reject(new errors.DbError('[add]: Invalid data.'));
                }

                return self.instance.insert(data, options, replace).then(function(result) {
                    data[self.getPk()] = result.lastInsertId || 0;

                    return data;
                });
            });
        },

        /**
         * All a batch of records.
         * @param {Array}    data    Data array
         * @param {Object}   options Options
         * @param {Booleans} replace Insert or replace
         */
        addAll: function(data, options, replace) {
            var self = this;

            if (!_.isArray(data) || _.isEmpty(data)) {
                when.reject(new errors.DbError('[addAll]: Invalid data.'));
            }

            return this.initDb().then(function() {
                options = self.parseOptions(options);
                data.forEach(function(item, index) {
                    data[index] = self.facade(item);
                    if (_.isEmpty(data[index])) {
                        data[index] = null;
                    }
                });

                return self.instance.insertAll(data, options, replace);
            });
        },

        /**
         * Select add.
         * @param  {MIX}     fields  Fields
         * @param  {String}  table   Table name
         * @param  {Object}  options Options
         * @return {Promise}
         */
        selectAdd: function(fields, table, options) {
            if (_.isEmpty(fields) || (!_.isString(fields) && !_.isArray(fields))) {
                fields = options.fields;
            }

            if (_.isString(fields)) {
                fields = fields.split(',');
            }

            if (_.isEmpty(table) || !_.isString(table)) {
                table = this.getTableName();
            }

            var self = this;
            options = this.parseOptions(options);

            return this.initDb().then(function() {
                return self.instance.selectInsert(fields, table, options);
            });
        },

        where: function(where) {
            if (_.isString(where) && !_.isEmpty(where)) {
                var map = {};
                map._string = where;
                where = map;
            }

            if (!_.isObject(this.options.where)) {
                this.options.where = _.assign(this.options.where, where);
            } else {
                this.options.where = where;
            }

            return this;
        },

        /**
         * Return table name.
         * @return {String} Table name
         */
        getTableName: function() {
            if (_.isEmpty(this.tableName)) {
                this.tableName = !_.isEmpty(this.tablePrefix) ? this.tablePrefix : '';
                this.tableName += _.convertVarName(this.name);
                this.tableName = this.tableName.toLowerCase();
            }

            return !_.isEmpty(this.dbName) ? this.dbName + '.' + this.tableName : this.tableName;
        },

        /**
         * Return table primary key.
         * @return {String} Primary key
         */
        getPk: function() {
            return this.pk;
        },

        /**
         * Return last query or execute sql.
         * @return {String} Sql string.
         */
        getLastSql: function() {
            return this.instance.getLastSql();
        },

        /**
         * Retrun value.
         * @param  {String} key   Key
         * @param  {MIX}    value Value
         */
        setData: function(key, value) {
            if (!_.isObject(this.data)) {
                this.data = {};
            }

            this.data[key] = value;

            return this;
        },

        /**
         * Set model data.
         * @param  {String} key   Key name
         * @return {MIX}         Value
         */
        getData: function(key) {
            return this.data[key];
        },

        parseOptions: function(options) {
            options = options || {};
            var fields = null;

            if (_.isObject(options)) {
                // merge options
                options = _.assign(options, this.options);
            }

            if (!options.table) {
                options.table = this.getTableName();
            }

            options.model = this.name;
            this.options = {};

            return options;
        },

        /**
         * Filter data.
         * @param  {Object} data Data
         * @return {Object}
         */
        facade: function(data) {
            var fields = null;

            if (!_.isObject(data)) {
                data = {};
            }

            if (!_.isEmpty(this.options.field)) {
                fields = this.options.field;
                delete this.options.field;

                if (_.isString(fields)) {
                    fields = fields.split(',');
                }
            } else if (!_.isEmpty(this.fields)) {
                fields = this.fields._name;
            }

            var self = this;
            if (!_.isNull(fields)) {
                _.forOwn(data, function(value, key) {
                    if (!_.inArray(key, fields)) {
                        delete data[key];
                    } else if (_.isScalar(value)) {
                        data[key] = self.parseType(value, key);
                    }
                });
            }

            return data;
        },

        /**
         * Convert type of data field.
         * @param  {MIX}    value Value
         * @param  {String} key   Field name
         * @return {MIX}          Value
         */
        parseType: function(value, key) {
            if (this.fields._type && _.isEmpty(this.fields._type[key])) {
                return value;
            }

            var fieldType = this.fields._type[key].toLowerCase();
            if (fieldType.indexOf('bigint') === -1 && fieldType.indexOf('int') !== -1) {
                value = parseInt(value, 10);
            } else if (fieldType.indexOf('float') !== -1 || fieldType.indexOf('double') !== -1) {
                value = parseFloat(value);
            } else if (fieldType.indexOf('bool') !== -1) {
                value = Boolean(value);
            }

            return value;
        },

        _afterDb: function() {
        },
    }
});

module.exports = Model;
<commit_msg>Add comments for `where` method.<commit_after>
'use strict';

var when  = require('when');
var Class = require('cakes').Class;

var Db    = require('./db');
var _     = require('./utils');
var errors = require('./errors');

/**
 * Model constructor.
 * @return {Functin} Constructor
 */
var Model = Class(function() {

    /**
     * Db instance
     * @type {Object}
     */
    var _dbInstances = {};

    return {

        /**
         * Model name
         * @type {String}
         */
        name: '',

        /**
         * Database name
         * @type {String}
         */
        dbName: '',

        /**
         * Table name
         * @type {String}
         */
        tableName: '',

        /**
         * Table prefix
         * @type {String}
         */
        tablePrefix: '',

        /**
         * Table fields
         * @type {Object}
         */
        fields: {},

        /**
         * Primary key
         * @type {String}
         */
        pk: '',

        /**
         * Data of model
         * @type {Object}
         */
        data: {},

        /**
         * Db config
         * @type {Object}
         */
        config: null,

        /**
         * Options
         * @type {Object}
         */
        options: {},

        /**
         * Auto increament
         * @type {Boolean}
         */
        isAutoInc: false,

        /**
         * Auto check table fields
         * @type {Boolean}
         */
        isAutoCheckFields: true,

        __construct: function(name, tablePrefix, config) {
            // initialize hook
            this._initialize();

            if (_.isString(name) && name !== '') {
                if (name.indexOf('.') !== -1) {
                    var parts = name.split('.');
                    this.dbName = parts[0];
                    this.name = parts[1];
                } else {
                    this.name = name;
                }
            }

            config = config || {};
            this.config = _.mapKeys(config, function(key) {
                return key.toLowerCase();
            });

            if (_.isEmpty(tablePrefix)) {
                this.tablePrefix = '';
            } else if (_.isString(tablePrefix)) {
                this.tablePrefix = tablePrefix;
            } else if (_.isString(this.config.prefix)) {
                this.tablePrefix = this.config.prefix;
            } else {
                this.tablePrefix = '';
            }
        },

        _initialize: function() {
            // hook
        },

        /**
         * Initialize db instance.
         * @return {Promise}
         */
        initDb: function() {
            var identifier = _.serialize(this.config);
            if (!this.instance) {
                if (!_dbInstances[identifier]) {
                    _dbInstances[identifier] = (new Db()).getInstance(this.config);
                }
            }

            // set this instance
            this.instance = _dbInstances[identifier];

            // hook
            this._afterDb();

            if (!_.isEmpty(this.name) && this.isAutoCheckFields) {
                return this.initFieldsInfo();
            }

            return when.promise;
        },

        /**
         * Initialize table fields info.
         * @return {Promise}
         */
        initFieldsInfo: function() {
            var self = this;

            return this.instance.getFields(this.getTableName()).then(function(fields) {
                var _name = [];
                var _type = {};
                _.forOwn(fields, function(attr, field) {
                    _name.push(field);

                    _type[field] = attr.type;
                    if (attr.primary) {
                        self.pk = field;

                        if (attr.autoinc) {
                            self.isAutoinc = true;
                        }
                    }
                });

                self.fields._name = _name;
                self.fields._type = _type;
                self.fields._pk = self.pk;

                return self.field;
            });
        },

        /**
         * Add a record.
         * @param  {Object}  data    Data of record
         * @param  {Object}  options Options
         * @param  {Boolean} replace Insert or replace
         * @return {Promise}
         */
        add: function(data, options, replace) {
            var self = this;

            if (!_.isObject(data) || _.isEmpty(data)) {
                if (!_.isEmpty(self.data)) {
                    data = self.data;
                    self.data = {};
                } else {
                    when.reject(new errors.DbError('[add]: Invalid data.'));
                }
            }

            return this.initDb().then(function() {
                options = self.parseOptions(options);
                data = self.facade(data);
                if (_.isEmpty(data)) {
                    when.reject(new errors.DbError('[add]: Invalid data.'));
                }

                return self.instance.insert(data, options, replace).then(function(result) {
                    data[self.getPk()] = result.lastInsertId || 0;

                    return data;
                });
            });
        },

        /**
         * All a batch of records.
         * @param {Array}    data    Data array
         * @param {Object}   options Options
         * @param {Booleans} replace Insert or replace
         */
        addAll: function(data, options, replace) {
            var self = this;

            if (!_.isArray(data) || _.isEmpty(data)) {
                when.reject(new errors.DbError('[addAll]: Invalid data.'));
            }

            return this.initDb().then(function() {
                options = self.parseOptions(options);
                data.forEach(function(item, index) {
                    data[index] = self.facade(item);
                    if (_.isEmpty(data[index])) {
                        data[index] = null;
                    }
                });

                return self.instance.insertAll(data, options, replace);
            });
        },

        /**
         * Select add.
         * @param  {MIX}     fields  Fields
         * @param  {String}  table   Table name
         * @param  {Object}  options Options
         * @return {Promise}
         */
        selectAdd: function(fields, table, options) {
            if (_.isEmpty(fields) || (!_.isString(fields) && !_.isArray(fields))) {
                fields = options.fields;
            }

            if (_.isString(fields)) {
                fields = fields.split(',');
            }

            if (_.isEmpty(table) || !_.isString(table)) {
                table = this.getTableName();
            }

            var self = this;
            options = this.parseOptions(options);

            return this.initDb().then(function() {
                return self.instance.selectInsert(fields, table, options);
            });
        },

        /**
         * Where conditions.
         * @param  {MIX}    where Query conditions
         * @return {Object}       This
         */
        where: function(where) {
            if (_.isString(where) && !_.isEmpty(where)) {
                var map = {};
                map._string = where;
                where = map;
            }

            if (!_.isObject(this.options.where)) {
                this.options.where = _.assign(this.options.where, where);
            } else {
                this.options.where = where;
            }

            return this;
        },

        /**
         * Return table name.
         * @return {String} Table name
         */
        getTableName: function() {
            if (_.isEmpty(this.tableName)) {
                this.tableName = !_.isEmpty(this.tablePrefix) ? this.tablePrefix : '';
                this.tableName += _.convertVarName(this.name);
                this.tableName = this.tableName.toLowerCase();
            }

            return !_.isEmpty(this.dbName) ? this.dbName + '.' + this.tableName : this.tableName;
        },

        /**
         * Return table primary key.
         * @return {String} Primary key
         */
        getPk: function() {
            return this.pk;
        },

        /**
         * Return last query or execute sql.
         * @return {String} Sql string.
         */
        getLastSql: function() {
            return this.instance.getLastSql();
        },

        /**
         * Retrun value.
         * @param  {String} key   Key
         * @param  {MIX}    value Value
         */
        setData: function(key, value) {
            if (!_.isObject(this.data)) {
                this.data = {};
            }

            this.data[key] = value;

            return this;
        },

        /**
         * Set model data.
         * @param  {String} key   Key name
         * @return {MIX}         Value
         */
        getData: function(key) {
            return this.data[key];
        },

        parseOptions: function(options) {
            options = options || {};
            var fields = null;

            if (_.isObject(options)) {
                // merge options
                options = _.assign(options, this.options);
            }

            if (!options.table) {
                options.table = this.getTableName();
            }

            options.model = this.name;
            this.options = {};

            return options;
        },

        /**
         * Filter data.
         * @param  {Object} data Data
         * @return {Object}
         */
        facade: function(data) {
            var fields = null;

            if (!_.isObject(data)) {
                data = {};
            }

            if (!_.isEmpty(this.options.field)) {
                fields = this.options.field;
                delete this.options.field;

                if (_.isString(fields)) {
                    fields = fields.split(',');
                }
            } else if (!_.isEmpty(this.fields)) {
                fields = this.fields._name;
            }

            var self = this;
            if (!_.isNull(fields)) {
                _.forOwn(data, function(value, key) {
                    if (!_.inArray(key, fields)) {
                        delete data[key];
                    } else if (_.isScalar(value)) {
                        data[key] = self.parseType(value, key);
                    }
                });
            }

            return data;
        },

        /**
         * Convert type of data field.
         * @param  {MIX}    value Value
         * @param  {String} key   Field name
         * @return {MIX}          Value
         */
        parseType: function(value, key) {
            if (this.fields._type && _.isEmpty(this.fields._type[key])) {
                return value;
            }

            var fieldType = this.fields._type[key].toLowerCase();
            if (fieldType.indexOf('bigint') === -1 && fieldType.indexOf('int') !== -1) {
                value = parseInt(value, 10);
            } else if (fieldType.indexOf('float') !== -1 || fieldType.indexOf('double') !== -1) {
                value = parseFloat(value);
            } else if (fieldType.indexOf('bool') !== -1) {
                value = Boolean(value);
            }

            return value;
        },

        _afterDb: function() {
        },
    }
});

module.exports = Model;
<|endoftext|>


<commit_before>var async = require('async');
var AWS = require('aws-sdk');
var DynamoQuery = require('./query');

function mapToArray(source, fn) {
    var result = [];
    for (var key in source) {
        result.push(fn(key, source[key], source));
    }
    return result;
}

function mapToObject(source, fn) {
    var result = {};
    for (var key in source) {
        var r = fn(key, source[key], source);
        for (var resultKey in r) {
            result[resultKey] = r[resultKey];
        }
    }
    return result;
}

function pairs(source) {
    var result = [];
    for (var key in source) {
        result.push([key, source[key]]);
    }
    return result;
}

var DynamoModel = function (tableName, schema, options) {

    if (!tableName) throw new Error('tableName is required');
    if (!schema) throw new Error('schema is required');

    var instance = this;
    var tableStatus = 'undefined';
    var tableStatusPendingCallbacks = [];

    options = options || {};
    this.dynamodb = new AWS.DynamoDB(options);
    this.consistentRead = false;
    this.defaultThroughput = {
        ReadCapacityUnits: 10,
        WriteCapacityUnits: 5
    };

    // a default callback making sure errors are not lost
    function defaultCallback(err) {
        if (err) throw err;
    }

    DynamoModel.prototype.getItem = function (key, options, callback) {
        if (!key) throw new Error('key is required');
        if (typeof options === 'function') {
            callback = options;
            options = {};
        }
        callback = callback || defaultCallback;

        var params = options || {};
        params.TableName = tableName;
        params.Key = schema.mapToDb(key);
        if (this.consistentRead) params.ConsistentRead = true;

        this.ensureActiveTable(function (err) {
            if (err) return callback(err);
            return instance.dynamodb.getItem(params, function (err, response) {
                if (err) return callback(err);
                return callback(null, schema.mapFromDb(response.Item), response);
            });
        });
    };

    DynamoModel.prototype.putItem = function (item, options, callback) {
        if (!item) throw new Error('item is required');
        if (typeof options === 'function') {
            callback = options;
            options = {};
        }
        callback = callback || defaultCallback;

        var params = options || {};
        params.TableName = tableName;
        params.Item = schema.mapToDb(item);

        this.ensureActiveTable(function (err) {
            if (err) return callback(err);
            instance.dynamodb.putItem(params, callback);
        });
    };

    DynamoModel.prototype.updateItem = function (key, updates, options, callback) {
        if (!key) throw new Error('key is required');
        if (!updates) throw new Error('updates is required');
        if (typeof options === 'function') {
            callback = options;
            options = {};
        }
        callback = callback || defaultCallback;

        var params = options || {};
        params.TableName = tableName;
        params.Key = schema.mapToDb(key);
        params.AttributeUpdates = parseUpdates(updates);

        this.ensureActiveTable(function (err) {
            if (err) return callback(err);
            instance.dynamodb.updateItem(params, callback);
        });
    };

    DynamoModel.prototype.deleteItem = function (key, options, callback) {
        if (!key) throw new Error('key is required');
        if (typeof options === 'function') {
            callback = options;
            options = {};
        }
        callback = callback || defaultCallback;

        var params = options || {};
        params.TableName = tableName;
        params.Key = schema.mapToDb(key);

        this.ensureActiveTable(function (err) {
            if (err) return callback(err);
            instance.dynamodb.deleteItem(params, callback);
        });
    };

    DynamoModel.prototype.query = function (key, options, callback) {
        if (!key) throw new Error('key is required');
        if (typeof options === 'function') {
            callback = options;
            options = {};
        }

        var params = options || {};
        params.TableName = tableName;
        params.KeyConditions = parseConditions(key);
        if (this.consistentRead) params.ConsistentRead = true;

        var query = new DynamoQuery('query', schema, this, params);
        if (callback) return query.exec(callback);
        return query;
    };

    DynamoModel.prototype.scan = function (filter, options, callback) {
        if (typeof filter === 'function') {
            callback = filter;
            options = {};
            filter = null;
        }
        if (typeof options === 'function') {
            callback = options;
            options = {};
        }

        var params = options || {};
        params.TableName = tableName;
        if (filter) {
            params.ScanFilter = parseConditions(filter);
        }

        var query = new DynamoQuery('scan', schema, this, params);
        if (callback) return query.exec(callback);
        return query;
    };

    DynamoModel.prototype.describeTable = function (options, callback) {
        if (typeof options === 'function') {
            callback = options;
            options = {};
        }

        var params = options || {};
        params.TableName = tableName;
        return this.dynamodb.describeTable(params, callback);
    };

    DynamoModel.prototype.createTable = function(options, callback) {
        if (typeof options === 'function') {
            callback = options;
            options = {};
        }

        var params = options || {};
        params.TableName = tableName;
        params.KeySchema = mapToArray(schema.keys, function (name, val) {
            return { AttributeName: name, KeyType: val };
        });
        params.AttributeDefinitions = mapToArray(schema.keys, function (name, val) {
            return { AttributeName: name, AttributeType: schema.attributes[name] };
        });
        params.ProvisionedThroughput = params.ProvisionedThroughput || this.defaultThroughput;

        return this.dynamodb.createTable(params, callback);
    };

    DynamoModel.prototype.updateTable = function (options, callback) {
        if (typeof options === 'function') {
            callback = options;
            options = {};
        }

        var params = options || {};
        params.TableName = tableName;
        params.ProvisionedThroughput = params.ProvisionedThroughput || this.defaultThroughput;
        return this.dynamodb.updateTable(params, callback);
    };

    DynamoModel.prototype.deleteTable = function (options, callback) {
        var params = options || {};
        params.TableName = tableName;
        return this.dynamodb.deleteTable(params, callback);
    };

    DynamoModel.prototype.ensureActiveTable = function (callback) {
        if (tableStatus === 'undefined') {
            tableStatus = 'querying';
            if (callback) tableStatusPendingCallbacks.push(callback);
            this.waitForActiveTable(function (err, response) {
                tableStatus = { error: err, response: response };
                // fire all pending callbacks
                for (var i = 0; i < tableStatusPendingCallbacks.length; i++) {
                    tableStatusPendingCallbacks[i](err);
                }
                tableStatusPendingCallbacks = [];
            });
        } else if (tableStatus === 'querying' && callback) {
            // already querying table status, add callback to queue
            tableStatusPendingCallbacks.push(callback);
        } else {
            return callback(tableStatus.error);
        }
    };

    DynamoModel.prototype.waitForActiveTable = function (pollingTimeout, callback) {

        if (typeof pollingTimeout === 'function') {
            callback = pollingTimeout;
            pollingTimeout = null;
        }
        pollingTimeout = pollingTimeout || 3000;

        async.waterfall([
            function getTableDescription(callback) {
                instance.describeTable(function (err, response) {
                    if (err) {
                        if (err.code === 'ResourceNotFoundException') {
                            // table does not exist, create it now
                            instance.createTable(function (err, response) {
                                if (err) return callback(err);
                                callback(null, response.TableDescription);
                            });
                        } else {
                            return callback(err);
                        }
                    } else {
                       callback(null, response.Table);
                    }
                });
            },
            function pollUntilActive(tableDescription, callback) {
                async.until(function () {
                    return tableDescription.TableStatus === 'ACTIVE';
                }, function (callback) {
                    setTimeout(function () {
                        instance.describeTable(function (err, response) {
                            if (err) return callback(err);
                            tableDescription = response.Table;
                            callback();
                        });
                    }, pollingTimeout);
                }, function (err) {
                    if (err) return callback(err);
                    callback(null, tableDescription);
                });
            }
        ], callback);
    };

    function parseConditions (conditions) {
        var result = {};
        pairs(conditions).forEach(function (pair) {

            var key = pair[0];
            var value = pair[1];
            var operator = 'EQ';

            if (typeof value === 'object') {
                var val = pairs(value);
                if (val.length === 1) {
                    operator = val[0][0];
                    value = val[0][1];
                }
            }

            if (operator === '$gt') {
                operator = 'GT';
            } else if (operator === '$gte') {
                operator = 'GE';
            } else if (operator === '$lt') {
                operator = 'LT';
            } else if (operator === '$lte') {
                operator = 'LE';
            } else if (operator === '$begins') {
                operator = 'BEGINS_WITH';
            } else if (operator === '$between') {
                operator = 'BETWEEN';
            } else if (/^\$/.test(operator)) {
                throw new Error('conditional operator "' + operator + '" is not supported');
            }

            var values = [];

            if (operator === 'BETWEEN') {
                if (!(Array.isArray(value) && value.length === 2)) {
                    throw new Error('BETWEEN operator must have an array of two elements as the comparison value');
                }
                values.push(schema.mappers[key].mapToDb(value[0]));
                values.push(schema.mappers[key].mapToDb(value[1]));
            } else if (Array.isArray(value)) {
                throw new Error('this operator does not support array values');
            } else {
                values.push(schema.mappers[key].mapToDb(value));
            }

            result[key] = {
                AttributeValueList: values,
                ComparisonOperator: operator
            };

        });
        return result;
    }

    function parseUpdates(updates) {
        return mapToObject(updates, function (key, value) {

            var result = {};
            var mapper;

            // look for a MongoDB-like operator and translate to its DynamoDB equivalent
            if (/^\$/.test(key)) {
                var action;
                if (key === '$set') action = 'PUT';
                if (key === '$unset') action = 'DELETE';
                if (key === '$inc') action = 'ADD';
                if (!action) {
                    throw new Error('update operator "' + key + '" is not supported');
                }
                pairs(value).forEach(function (pair) {
                    if (!schema.mappers.hasOwnProperty(pair[0])) {
                        throw new Error('unknown field: ' + pair[0]);
                    }
                    mapper = schema.mappers[pair[0]];
                    result[pair[0]] = {
                        Action: action,
                        Value: mapper.mapToDb(pair[1])
                    };
                });
                return result;
            }

            if (!schema.mappers.hasOwnProperty(key)) {
                throw new Error('unknown field: ' + key);
            }
            mapper = schema.mappers[key];

            if (typeof value === 'object') {
                var val = pairs(value);
                if (val.length === 1) {
                    result[key] = {
                        Action: val[0][0],
                        Value: mapper.mapToDb(val[0][1])
                    };
                } else {
                    throw new Error('value is expected to contain only one key');
                }
            } else {
                result[key] = {
                    Action: 'PUT',
                    Value: schema.mappers[key].mapToDb(value)
                };
            }

            return result;
        });
    }

    // make sure the table is available as soon as possible
    this.ensureActiveTable();

};

module.exports = DynamoModel;
<commit_msg>putItem can map resulting attributes<commit_after>var async = require('async');
var AWS = require('aws-sdk');
var DynamoQuery = require('./query');

function mapToArray(source, fn) {
    var result = [];
    for (var key in source) {
        result.push(fn(key, source[key], source));
    }
    return result;
}

function mapToObject(source, fn) {
    var result = {};
    for (var key in source) {
        var r = fn(key, source[key], source);
        for (var resultKey in r) {
            result[resultKey] = r[resultKey];
        }
    }
    return result;
}

function pairs(source) {
    var result = [];
    for (var key in source) {
        result.push([key, source[key]]);
    }
    return result;
}

var DynamoModel = function (tableName, schema, options) {

    if (!tableName) throw new Error('tableName is required');
    if (!schema) throw new Error('schema is required');

    var instance = this;
    var tableStatus = 'undefined';
    var tableStatusPendingCallbacks = [];

    options = options || {};
    this.dynamodb = new AWS.DynamoDB(options);
    this.consistentRead = false;
    this.defaultThroughput = {
        ReadCapacityUnits: 10,
        WriteCapacityUnits: 5
    };

    // a default callback making sure errors are not lost
    function defaultCallback(err) {
        if (err) throw err;
    }

    DynamoModel.prototype.getItem = function (key, options, callback) {
        if (!key) throw new Error('key is required');
        if (typeof options === 'function') {
            callback = options;
            options = {};
        }
        callback = callback || defaultCallback;

        var params = options || {};
        params.TableName = tableName;
        params.Key = schema.mapToDb(key);
        if (this.consistentRead) params.ConsistentRead = true;

        this.ensureActiveTable(function (err) {
            if (err) return callback(err);
            return instance.dynamodb.getItem(params, function (err, response) {
                if (err) return callback(err);
                return callback(null, schema.mapFromDb(response.Item), response);
            });
        });
    };

    DynamoModel.prototype.putItem = function (item, options, callback) {
        if (!item) throw new Error('item is required');
        if (typeof options === 'function') {
            callback = options;
            options = {};
        }
        callback = callback || defaultCallback;

        var params = options || {};
        params.TableName = tableName;
        params.Item = schema.mapToDb(item);

        this.ensureActiveTable(function (err) {
            if (err) return callback(err);
            instance.dynamodb.putItem(params, function (err, response) {
                if (err) return callback(err);
                callback(null, schema.mapFromDb(response.Attributes), response);
            });
        });
    };

    DynamoModel.prototype.updateItem = function (key, updates, options, callback) {
        if (!key) throw new Error('key is required');
        if (!updates) throw new Error('updates is required');
        if (typeof options === 'function') {
            callback = options;
            options = {};
        }
        callback = callback || defaultCallback;

        var params = options || {};
        params.TableName = tableName;
        params.Key = schema.mapToDb(key);
        params.AttributeUpdates = parseUpdates(updates);

        this.ensureActiveTable(function (err) {
            if (err) return callback(err);
            instance.dynamodb.updateItem(params, callback);
        });
    };

    DynamoModel.prototype.deleteItem = function (key, options, callback) {
        if (!key) throw new Error('key is required');
        if (typeof options === 'function') {
            callback = options;
            options = {};
        }
        callback = callback || defaultCallback;

        var params = options || {};
        params.TableName = tableName;
        params.Key = schema.mapToDb(key);

        this.ensureActiveTable(function (err) {
            if (err) return callback(err);
            instance.dynamodb.deleteItem(params, callback);
        });
    };

    DynamoModel.prototype.query = function (key, options, callback) {
        if (!key) throw new Error('key is required');
        if (typeof options === 'function') {
            callback = options;
            options = {};
        }

        var params = options || {};
        params.TableName = tableName;
        params.KeyConditions = parseConditions(key);
        if (this.consistentRead) params.ConsistentRead = true;

        var query = new DynamoQuery('query', schema, this, params);
        if (callback) return query.exec(callback);
        return query;
    };

    DynamoModel.prototype.scan = function (filter, options, callback) {
        if (typeof filter === 'function') {
            callback = filter;
            options = {};
            filter = null;
        }
        if (typeof options === 'function') {
            callback = options;
            options = {};
        }

        var params = options || {};
        params.TableName = tableName;
        if (filter) {
            params.ScanFilter = parseConditions(filter);
        }

        var query = new DynamoQuery('scan', schema, this, params);
        if (callback) return query.exec(callback);
        return query;
    };

    DynamoModel.prototype.describeTable = function (options, callback) {
        if (typeof options === 'function') {
            callback = options;
            options = {};
        }

        var params = options || {};
        params.TableName = tableName;
        return this.dynamodb.describeTable(params, callback);
    };

    DynamoModel.prototype.createTable = function(options, callback) {
        if (typeof options === 'function') {
            callback = options;
            options = {};
        }

        var params = options || {};
        params.TableName = tableName;
        params.KeySchema = mapToArray(schema.keys, function (name, val) {
            return { AttributeName: name, KeyType: val };
        });
        params.AttributeDefinitions = mapToArray(schema.keys, function (name, val) {
            return { AttributeName: name, AttributeType: schema.attributes[name] };
        });
        params.ProvisionedThroughput = params.ProvisionedThroughput || this.defaultThroughput;

        return this.dynamodb.createTable(params, callback);
    };

    DynamoModel.prototype.updateTable = function (options, callback) {
        if (typeof options === 'function') {
            callback = options;
            options = {};
        }

        var params = options || {};
        params.TableName = tableName;
        params.ProvisionedThroughput = params.ProvisionedThroughput || this.defaultThroughput;
        return this.dynamodb.updateTable(params, callback);
    };

    DynamoModel.prototype.deleteTable = function (options, callback) {
        var params = options || {};
        params.TableName = tableName;
        return this.dynamodb.deleteTable(params, callback);
    };

    DynamoModel.prototype.ensureActiveTable = function (callback) {
        if (tableStatus === 'undefined') {
            tableStatus = 'querying';
            if (callback) tableStatusPendingCallbacks.push(callback);
            this.waitForActiveTable(function (err, response) {
                tableStatus = { error: err, response: response };
                // fire all pending callbacks
                for (var i = 0; i < tableStatusPendingCallbacks.length; i++) {
                    tableStatusPendingCallbacks[i](err);
                }
                tableStatusPendingCallbacks = [];
            });
        } else if (tableStatus === 'querying' && callback) {
            // already querying table status, add callback to queue
            tableStatusPendingCallbacks.push(callback);
        } else {
            return callback(tableStatus.error);
        }
    };

    DynamoModel.prototype.waitForActiveTable = function (pollingTimeout, callback) {
        if (typeof pollingTimeout === 'function') {
            callback = pollingTimeout;
            pollingTimeout = null;
        }
        callback = callback || function () {};
        pollingTimeout = pollingTimeout || 3000;

        async.waterfall([
            function getTableDescription(callback) {
                instance.describeTable(function (err, response) {
                    if (err) {
                        if (err.code === 'ResourceNotFoundException') {
                            // table does not exist, create it now
                            instance.createTable(function (err, response) {
                                if (err) return callback(err);
                                callback(null, response.TableDescription);
                            });
                        } else {
                            return callback(err);
                        }
                    } else {
                       callback(null, response.Table);
                    }
                });
            },
            function pollUntilActive(tableDescription, callback) {
                async.until(function () {
                    return tableDescription.TableStatus === 'ACTIVE';
                }, function (callback) {
                    setTimeout(function () {
                        instance.describeTable(function (err, response) {
                            if (err) return callback(err);
                            tableDescription = response.Table;
                            callback();
                        });
                    }, pollingTimeout);
                }, function (err) {
                    if (err) return callback(err);
                    callback(null, tableDescription);
                });
            }
        ], callback);
    };

    function parseConditions (conditions) {
        var result = {};
        pairs(conditions).forEach(function (pair) {

            var key = pair[0];
            var value = pair[1];
            var operator = 'EQ';

            if (typeof value === 'object') {
                var val = pairs(value);
                if (val.length === 1) {
                    operator = val[0][0];
                    value = val[0][1];
                }
            }

            if (operator === '$gt') {
                operator = 'GT';
            } else if (operator === '$gte') {
                operator = 'GE';
            } else if (operator === '$lt') {
                operator = 'LT';
            } else if (operator === '$lte') {
                operator = 'LE';
            } else if (operator === '$begins') {
                operator = 'BEGINS_WITH';
            } else if (operator === '$between') {
                operator = 'BETWEEN';
            } else if (/^\$/.test(operator)) {
                throw new Error('conditional operator "' + operator + '" is not supported');
            }

            var values = [];

            if (operator === 'BETWEEN') {
                if (!(Array.isArray(value) && value.length === 2)) {
                    throw new Error('BETWEEN operator must have an array of two elements as the comparison value');
                }
                values.push(schema.mappers[key].mapToDb(value[0]));
                values.push(schema.mappers[key].mapToDb(value[1]));
            } else if (Array.isArray(value)) {
                throw new Error('this operator does not support array values');
            } else {
                values.push(schema.mappers[key].mapToDb(value));
            }

            result[key] = {
                AttributeValueList: values,
                ComparisonOperator: operator
            };

        });
        return result;
    }

    function parseUpdates(updates) {
        return mapToObject(updates, function (key, value) {

            var result = {};
            var mapper;

            // look for a MongoDB-like operator and translate to its DynamoDB equivalent
            if (/^\$/.test(key)) {
                var action;
                if (key === '$set') action = 'PUT';
                if (key === '$unset') action = 'DELETE';
                if (key === '$inc') action = 'ADD';
                if (!action) {
                    throw new Error('update operator "' + key + '" is not supported');
                }
                pairs(value).forEach(function (pair) {
                    if (!schema.mappers.hasOwnProperty(pair[0])) {
                        throw new Error('unknown field: ' + pair[0]);
                    }
                    mapper = schema.mappers[pair[0]];
                    result[pair[0]] = {
                        Action: action,
                        Value: mapper.mapToDb(pair[1])
                    };
                });
                return result;
            }

            if (!schema.mappers.hasOwnProperty(key)) {
                throw new Error('unknown field: ' + key);
            }
            mapper = schema.mappers[key];

            if (typeof value === 'object') {
                var val = pairs(value);
                if (val.length === 1) {
                    result[key] = {
                        Action: val[0][0],
                        Value: mapper.mapToDb(val[0][1])
                    };
                } else {
                    throw new Error('value is expected to contain only one key');
                }
            } else {
                result[key] = {
                    Action: 'PUT',
                    Value: schema.mappers[key].mapToDb(value)
                };
            }

            return result;
        });
    }

    // make sure the table is available as soon as possible
    this.ensureActiveTable();

};

module.exports = DynamoModel;
<|endoftext|>


<commit_before>'use strict';

var EventEmitter = require('events').EventEmitter;
var _ = require('lodash');
var Promise = require('bluebird');
var util = require('./util');
var Document = require('./document');
var Schema = require('./schema');
var Types = require('./types');
var ElasticstoreError = require('./error');
var Queue = require('./queue');

var parseArgs = util.parseArgs;
var reverse = util.reverse;
var shuffle = util.shuffle;
var getProp = util.getProp;
var setGetter = util.setGetter;
var extend = util.extend;
var isArray = Array.isArray;

/**
 * Model constructor.
 *
 * @class Model
 * @param {String} name Model name
 * @param {Schema|Object} [schema_] Schema
 * @constructor
 * @extends EventEmitter
 * @module warehouse
 */
function Model(name, schema_) {
  EventEmitter.call(this);

  var schema, i, len, key;

  // Define schema
  if (schema_ instanceof Schema) {
    schema = schema_;
  } else if (typeof schema_ === 'object') {
    schema = new Schema(schema_);
  } else {
    schema = new Schema();
  }

  // Set `_id` path for schema
  if (!schema.path('_id')) {
    schema.path('_id', {type: Types.CUID, required: true});
  }

  /**
   * Model name.
   *
   * @property {String} name
   * @private
   */
  this.name = name;

  /**
   * The doc_type used in ElasticSearch
   */
  this._es_doc_type = name.toLowerCase();

  /**
   * Data storage.
   *
   * @property {Object} data
   * @private
   */
  this.data = {};

  /**
   * Schema.
   *
   * @property {Schema} schema
   * @private
   */
  this.schema = schema;

  /**
   * The number of documents in model.
   *
   * @property {Number} length
   * @readOnly
   */
  this.length = 0;

  /**
   * Promise queue.
   *
   * @property {Queue} _queue
   * @private
   */
  this._queue = new Queue();

  /**
   * Document constructor for this model instance.
   *
   * @property {Function} Document
   * @param {Object} data
   * @constructor
   * @private
   */
  var _Document = this.Document = function (data) {
    Document.call(this, data);

    // Apply getters
    var err = schema._applyGetters(this);
    if (err) throw err;
  };

  util.inherits(_Document, Document);
  _Document.prototype._model = this;
  _Document.prototype._schema = schema;

  // Apply static methods
  var statics = schema.statics;
  var staticKeys = Object.keys(statics);

  for (i = 0, len = staticKeys.length; i < len; i++) {
    key = staticKeys[i];
    this[key] = statics[key];
  }

  // Apply instance methods
  var methods = schema.methods;
  var methodKeys = Object.keys(methods);

  for (i = 0, len = methodKeys.length; i < len; i++) {
    key = methodKeys[i];
    _Document.prototype[key] = methods[key];
  }
}

util.inherits(Model, EventEmitter);

/**
 * Creates a new document.
 *
 * @method new
 * @param {Object} data
 * @return {Document}
 */
Model.prototype.new = function (data) {
  return new this.Document(data);
};

/**
 * Finds a document by its identifier.
 *
 * @method get
 * @param {*} id
 * @param {Function} callback
 * @return {Document}
 */
Model.prototype.get = function (id, callback) {
  var self = this;
  return self._database.getClient().then(function (client) {
    return client.get({
      index: self._database._es_index,
      type: self._es_doc_type,
      id: id
    }).then(function (resp) {
      if (resp._source) {
        var data = resp._source;
        return new self.Document(data);
      } else {
        return null;
      }
    }, function (err) {
      if (err.message == 'Not Found') {
        return null;
      } else {
        throw err;
      }
    }).catch(function(err) {
      throw err;
    });
  }).nodeify(callback);
};

Model.prototype.findById = Model.prototype.get;

function execHooks(schema, type, event, data) {
  var hooks = schema.hooks[type][event];
  if (!hooks.length) return Promise.resolve(data);

  return Promise.each(hooks, function (hook) {
    return hook(data);
  }).thenReturn(data);
}

/**
 * Inserts a document.
 *
 * @method _saveOne
 * @param {Object} data_
 * @param {Function} [callback]
 * @return {Promise}
 */
Model.prototype._saveOne = function (data_, callback) {
  var self = this;
  var schema = this.schema;
  var result;

  return new Promise(function (resolve, reject) {
    // Apply getters
    var data = data_ instanceof self.Document ? data_ : new self.Document(data_);
    var id = data._id;

    // Check ID
    if (!id) {
      return reject(new ElasticstoreError('ID is not defined'));
    }

    resolve(data);
  }).then(function (data) {
      // Apply setters
      result = data.toObject();
      var err = schema._applySetters(result);

      if (err) return Promise.reject(err);

      // Pre-hooks
      return execHooks(schema, 'pre', 'save', data);
    }).then(function (data) {
      // Insert data to ElasticSearch
      return self._database.getClient().then(function (client) {

        return client.index({
          index: self._database._es_index,
          type: self._es_doc_type,
          id: data._id,
          body: data,
          // make sure the data is searchable instantly
          refresh: true
        }).then(function (resp) {

          /**
           * Fired when a document is inserted
           *
           * @event insert
           */
          self.emit('insert', data);
          return new self.Document(data);
        });
      });
    }).then(function (data) {
      return execHooks(schema, 'post', 'save', data);
    }).nodeify(callback);
};

/**
 * Save one or more documents.
 *
 * @method save
 * @param {Object|Array} data
 * @param {Function} [callback]
 * @return {Promise}
 */
Model.prototype.save = function (data, callback) {
  if (isArray(data)) {
    var self = this;

    return Promise.map(data, function (item) {

      return self._saveOne(item);
    }).then(function(docs){
      return self._database.getClient().then(function (client) {
        return client.indices.refresh({
          index: self._database._es_index
        }).then(function() {
          return docs;
        });
      })
    }).nodeify(callback);
  } else {
    return this._saveOne(data, callback);
  }
};

/**
 * Finds a document by its identifier and update it.
 *
 * @method updateById
 * @param {*} id
 * @param {Object} update
 * @param {Function} [callback]
 * @return {Promise}
 */
Model.prototype.updateById = function (id, update, callback) {
  // Check ID
  if (!id) {
    return new Promise(function (resovle, reject) {
      return reject(new ElasticstoreError('ID is not defined'));
    });
  }

  var self = this;

  return self.get(id)
    .then(function (doc) {
      return execHooks(self.schema, 'pre', 'save', doc)
    })
    .then(function () {
      return self._database.getClient().then(function (client) {
        return client.update({

          index: self._database._es_index,
          type: self._es_doc_type,
          id: id,
          body: {
            doc: update
          }
        })
      });
    })
    .then(function () {
      // fetch the updated document
      // TODO: another IO
      return self.get(id);
    })
    .then(function (data) {
      /**
       * Fired when a document is inserted
       *
       * @event insert
       */
      self.emit('update', data);
      return execHooks(self.schema, 'post', 'save', data);
    })
    .nodeify(callback);
};

/**
 * Drop all documents in this model
 *
 * @param callback
 * @returns {Promise}
 */
Model.prototype.drop = function (callback) {
  var self = this;

  return self._database.getClient().then(function (client) {
    return client.deleteByQuery({
      index: self._database._es_index,
      type: self._es_doc_type,
      body: {
        query: {
          match_all: {}
        }
      }
    });
  }).nodeify(callback);
};

/**
 * Finds a document by its identifier and remove it.
 *
 * @method remove
 * @param {*} id
 * @param {Function} [callback]
 * @return {Promise}
 */
Model.prototype.remove = function (id, callback) {
  var self = this;
  var schema = this.schema;

  return self.get(id)
    .then(function (data) { // Pre-hooks
      if (data == null) {
        throw new ElasticstoreError('ID `' + id + '` does not exist');
      }
      return execHooks(schema, 'pre', 'remove', data);
    }).then(function (data) { // Removes data

      // delete data from ElasticSearch
      return self._database.getClient().then(function (client) {
        return client.delete({
          index: self._database._es_index,
          type: self._es_doc_type,
          id: id,
          refresh: true
        }).then(function (resp) {
          /**
           * Fired when a document is removed
           *
           * @event remove
           */
          self.emit('remove', data);
          return data;
        });
      })
    }).then(function (data) {
      return execHooks(schema, 'post', 'remove', data);
    })
    .nodeify(callback);
};

Model.prototype.removeById = Model.prototype.remove;

/**
 * Returns the number of elements.
 *
 * @method count
 * @return {Number}
 */
Model.prototype.count = function (callback) {
  var self = this;
  return this._database.getClient().then(function (client) {
    return client.count({
      index: self._database._es_index,
      type: self._es_doc_type
    }).then(function (resp) {
      return resp.count;
    });
  }).nodeify(callback);
};

/**
 * Finds matching documents.
 *
 * @method find
 * @param {Object} filter
 * @param {Object} options
 * @param {Function} callback
 * @return {Array}
 */
Model.prototype.find = function (filter, options, callback) {
  var self = this;

  // the query body for ElasticSearch
  var query = {
    filtered: {
      query: {
        // so that documents will not be scored
        match_all: {}
      },
      filter: {
        term: filter
      }
    }
  };
  // if filter is null or empty, we return all documents
  if (!filter || _.keys(filter).length == 0) {
    delete query.filtered.filter;
  }

  var limit = (options && options.limit) ? options.limit : 10;
  var skip = (options && options.skip) ? options.skip : 0;

  return self._database.getClient().then(function (client) {
    return client.search({
      index: self._database._es_index,
      type: self._es_doc_type,
      body: {
        query: query
      },
      size: limit,
      from: skip
    }).then(function (resp) {
      return _.map(resp.hits.hits, function (hit) {
        return new self.Document(hit._source);
      });
    }).catch(function(err) {
      throw err;
    });
  }).nodeify(callback);
};

/**
 * Finds the first matching documents.
 *
 * @method findOne
 * @param {Object} query
 * @return {Document|Object}
 */
Model.prototype.findOne = function(query){

  return this.find(query).then(function (docs) {
    if (docs.length > 0) {
      return docs[0];
    } else {
      return null;
    }
  });
};

Model.prototype.findAll = function(query) {
  return this.find(query, {limit: 10000});
};

Model.prototype.deleteMapping = function (callback) {
  var self = this;
  return self._database.getClient().then(function (client) {
    return client.indices.deleteMapping({
      index: self._database._es_index,
      type: self._es_doc_type
    }).catch(function (err) {
      console.log('error deleting mappings');
      console.log(err);
    });
  }).nodeify(callback);
};

module.exports = Model;<commit_msg>fix Model.find<commit_after>'use strict';

var EventEmitter = require('events').EventEmitter;
var _ = require('lodash');
var Promise = require('bluebird');
var util = require('./util');
var Document = require('./document');
var Schema = require('./schema');
var Types = require('./types');
var ElasticstoreError = require('./error');
var Queue = require('./queue');

var parseArgs = util.parseArgs;
var reverse = util.reverse;
var shuffle = util.shuffle;
var getProp = util.getProp;
var setGetter = util.setGetter;
var extend = util.extend;
var isArray = Array.isArray;

/**
 * Model constructor.
 *
 * @class Model
 * @param {String} name Model name
 * @param {Schema|Object} [schema_] Schema
 * @constructor
 * @extends EventEmitter
 * @module warehouse
 */
function Model(name, schema_) {
  EventEmitter.call(this);

  var schema, i, len, key;

  // Define schema
  if (schema_ instanceof Schema) {
    schema = schema_;
  } else if (typeof schema_ === 'object') {
    schema = new Schema(schema_);
  } else {
    schema = new Schema();
  }

  // Set `_id` path for schema
  if (!schema.path('_id')) {
    schema.path('_id', {type: Types.CUID, required: true});
  }

  /**
   * Model name.
   *
   * @property {String} name
   * @private
   */
  this.name = name;

  /**
   * The doc_type used in ElasticSearch
   */
  this._es_doc_type = name.toLowerCase();

  /**
   * Data storage.
   *
   * @property {Object} data
   * @private
   */
  this.data = {};

  /**
   * Schema.
   *
   * @property {Schema} schema
   * @private
   */
  this.schema = schema;

  /**
   * The number of documents in model.
   *
   * @property {Number} length
   * @readOnly
   */
  this.length = 0;

  /**
   * Promise queue.
   *
   * @property {Queue} _queue
   * @private
   */
  this._queue = new Queue();

  /**
   * Document constructor for this model instance.
   *
   * @property {Function} Document
   * @param {Object} data
   * @constructor
   * @private
   */
  var _Document = this.Document = function (data) {
    Document.call(this, data);

    // Apply getters
    var err = schema._applyGetters(this);
    if (err) throw err;
  };

  util.inherits(_Document, Document);
  _Document.prototype._model = this;
  _Document.prototype._schema = schema;

  // Apply static methods
  var statics = schema.statics;
  var staticKeys = Object.keys(statics);

  for (i = 0, len = staticKeys.length; i < len; i++) {
    key = staticKeys[i];
    this[key] = statics[key];
  }

  // Apply instance methods
  var methods = schema.methods;
  var methodKeys = Object.keys(methods);

  for (i = 0, len = methodKeys.length; i < len; i++) {
    key = methodKeys[i];
    _Document.prototype[key] = methods[key];
  }
}

util.inherits(Model, EventEmitter);

/**
 * Creates a new document.
 *
 * @method new
 * @param {Object} data
 * @return {Document}
 */
Model.prototype.new = function (data) {
  return new this.Document(data);
};

/**
 * Finds a document by its identifier.
 *
 * @method get
 * @param {*} id
 * @param {Function} callback
 * @return {Document}
 */
Model.prototype.get = function (id, callback) {
  var self = this;
  return self._database.getClient().then(function (client) {
    return client.get({
      index: self._database._es_index,
      type: self._es_doc_type,
      id: id
    }).then(function (resp) {
      if (resp._source) {
        var data = resp._source;
        return new self.Document(data);
      } else {
        return null;
      }
    }, function (err) {
      if (err.message == 'Not Found') {
        return null;
      } else {
        throw err;
      }
    }).catch(function(err) {
      throw err;
    });
  }).nodeify(callback);
};

Model.prototype.findById = Model.prototype.get;

function execHooks(schema, type, event, data) {
  var hooks = schema.hooks[type][event];
  if (!hooks.length) return Promise.resolve(data);

  return Promise.each(hooks, function (hook) {
    return hook(data);
  }).thenReturn(data);
}

/**
 * Inserts a document.
 *
 * @method _saveOne
 * @param {Object} data_
 * @param {Function} [callback]
 * @return {Promise}
 */
Model.prototype._saveOne = function (data_, callback) {
  var self = this;
  var schema = this.schema;
  var result;

  return new Promise(function (resolve, reject) {
    // Apply getters
    var data = data_ instanceof self.Document ? data_ : new self.Document(data_);
    var id = data._id;

    // Check ID
    if (!id) {
      return reject(new ElasticstoreError('ID is not defined'));
    }

    resolve(data);
  }).then(function (data) {
      // Apply setters
      result = data.toObject();
      var err = schema._applySetters(result);

      if (err) return Promise.reject(err);

      // Pre-hooks
      return execHooks(schema, 'pre', 'save', data);
    }).then(function (data) {
      // Insert data to ElasticSearch
      return self._database.getClient().then(function (client) {

        return client.index({
          index: self._database._es_index,
          type: self._es_doc_type,
          id: data._id,
          body: data,
          // make sure the data is searchable instantly
          refresh: true
        }).then(function (resp) {

          /**
           * Fired when a document is inserted
           *
           * @event insert
           */
          self.emit('insert', data);
          return new self.Document(data);
        });
      });
    }).then(function (data) {
      return execHooks(schema, 'post', 'save', data);
    }).nodeify(callback);
};

/**
 * Save one or more documents.
 *
 * @method save
 * @param {Object|Array} data
 * @param {Function} [callback]
 * @return {Promise}
 */
Model.prototype.save = function (data, callback) {
  if (isArray(data)) {
    var self = this;

    return Promise.map(data, function (item) {

      return self._saveOne(item);
    }).then(function(docs){
      return self._database.getClient().then(function (client) {
        return client.indices.refresh({
          index: self._database._es_index
        }).then(function() {
          return docs;
        });
      })
    }).nodeify(callback);
  } else {
    return this._saveOne(data, callback);
  }
};

/**
 * Finds a document by its identifier and update it.
 *
 * @method updateById
 * @param {*} id
 * @param {Object} update
 * @param {Function} [callback]
 * @return {Promise}
 */
Model.prototype.updateById = function (id, update, callback) {
  // Check ID
  if (!id) {
    return new Promise(function (resovle, reject) {
      return reject(new ElasticstoreError('ID is not defined'));
    });
  }

  var self = this;

  return self.get(id)
    .then(function (doc) {
      return execHooks(self.schema, 'pre', 'save', doc)
    })
    .then(function () {
      return self._database.getClient().then(function (client) {
        return client.update({

          index: self._database._es_index,
          type: self._es_doc_type,
          id: id,
          body: {
            doc: update
          }
        })
      });
    })
    .then(function () {
      // fetch the updated document
      // TODO: another IO
      return self.get(id);
    })
    .then(function (data) {
      /**
       * Fired when a document is inserted
       *
       * @event insert
       */
      self.emit('update', data);
      return execHooks(self.schema, 'post', 'save', data);
    })
    .nodeify(callback);
};

/**
 * Drop all documents in this model
 *
 * @param callback
 * @returns {Promise}
 */
Model.prototype.drop = function (callback) {
  var self = this;

  return self._database.getClient().then(function (client) {
    return client.deleteByQuery({
      index: self._database._es_index,
      type: self._es_doc_type,
      body: {
        query: {
          match_all: {}
        }
      }
    });
  }).nodeify(callback);
};

/**
 * Finds a document by its identifier and remove it.
 *
 * @method remove
 * @param {*} id
 * @param {Function} [callback]
 * @return {Promise}
 */
Model.prototype.remove = function (id, callback) {
  var self = this;
  var schema = this.schema;

  return self.get(id)
    .then(function (data) { // Pre-hooks
      if (data == null) {
        throw new ElasticstoreError('ID `' + id + '` does not exist');
      }
      return execHooks(schema, 'pre', 'remove', data);
    }).then(function (data) { // Removes data

      // delete data from ElasticSearch
      return self._database.getClient().then(function (client) {
        return client.delete({
          index: self._database._es_index,
          type: self._es_doc_type,
          id: id,
          refresh: true
        }).then(function (resp) {
          /**
           * Fired when a document is removed
           *
           * @event remove
           */
          self.emit('remove', data);
          return data;
        });
      })
    }).then(function (data) {
      return execHooks(schema, 'post', 'remove', data);
    })
    .nodeify(callback);
};

Model.prototype.removeById = Model.prototype.remove;

/**
 * Returns the number of elements.
 *
 * @method count
 * @return {Number}
 */
Model.prototype.count = function (callback) {
  var self = this;
  return this._database.getClient().then(function (client) {
    return client.count({
      index: self._database._es_index,
      type: self._es_doc_type
    }).then(function (resp) {
      return resp.count;
    });
  }).nodeify(callback);
};

/**
 * Finds matching documents.
 *
 * @method find
 * @param {Object} filter
 * @param {Object} options
 * @param {Function} callback
 * @return {Array}
 */
Model.prototype.find = function (filter, options, callback) {
  var self = this;

  // filter is an object, like {predicate: "hastag", object: "tagID"}
  // we need to convert it to an array, passed to MUST boolean filter
  // TODO: add test cases
  var filters = [];
  _.forEach(filter, function (value, key) {
    var f = {term: {}};
    f['term'][key] = value;
    filters.push(f);
  })

  // the query body for ElasticSearch
  var query = {
    filtered: {
      query: {
        // so that documents will not be scored
        match_all: {}
      },
      filter: {
        bool: {
          must: [
            filters
          ]
        }
      }
    }
  };
  // if filter is null or empty, we return all documents
  if (!filter || _.keys(filter).length == 0) {
    delete query.filtered.filter;
  }

  var limit = (options && options.limit) ? options.limit : 10;
  var skip = (options && options.skip) ? options.skip : 0;

  return self._database.getClient().then(function (client) {
    return client.search({
      index: self._database._es_index,
      type: self._es_doc_type,
      body: {
        query: query
      },
      size: limit,
      from: skip
    }).then(function (resp) {
      return _.map(resp.hits.hits, function (hit) {
        return new self.Document(hit._source);
      });
    }).catch(function(err) {
      throw err;
    });
  }).nodeify(callback);
};

/**
 * Finds the first matching documents.
 *
 * @method findOne
 * @param {Object} query
 * @return {Document|Object}
 */
Model.prototype.findOne = function(query){

  return this.find(query).then(function (docs) {
    if (docs.length > 0) {
      return docs[0];
    } else {
      return null;
    }
  });
};

Model.prototype.findAll = function(query) {
  return this.find(query, {limit: 10000});
};

Model.prototype.deleteMapping = function (callback) {
  var self = this;
  return self._database.getClient().then(function (client) {
    return client.indices.deleteMapping({
      index: self._database._es_index,
      type: self._es_doc_type
    }).catch(function (err) {
      console.log('error deleting mappings');
      console.log(err);
    });
  }).nodeify(callback);
};

module.exports = Model;<|endoftext|>


<commit_before>/*
*
* Copyright (C) 2011, The Locker Project
* All rights reserved.
*
* Please see the LICENSE file for more information.
*
*/

/* random notes:
on startup scan all folders
    Apps Collections Connectors - generate lists of "available"
    Me/* - generate lists of "existing"

when asked, run any existing and return localhost:port
if first time
    check dependencies
    create Me/ dir
    create me.json settings
    pick a port
*/
var conf = {};
conf._exit = false;
exports.alive = false;
var npm = require('npm');
//npm.load(conf, function(er) {
  //npm.commands.install([], function(err, data) {
    require.paths.push(__dirname + "/Common/node");
    var spawn = require('child_process').spawn;
    var fs = require('fs');
    var path = require('path');
    var request = require('request');
    var async = require('async');
    var util = require('util');

    // This lconfig stuff has to come before and other locker modules are loaded!!
    var lconfig = require('lconfig');
    lconfig.load((process.argv[2] == '--config'? process.argv[3] : 'Config/config.json'));

    console.log(process.pid);
    fs.writeFileSync(__dirname + '/Logs/locker.pid', "" + process.pid);

    var logger = require("logger");
    var lconsole = require("lconsole");
    var lscheduler = require("lscheduler");
    var syncManager = require('lsyncmanager');
    var serviceManager = require("lservicemanager");
    var mongodb = require('mongodb');
    var webservice = require(__dirname + "/Ops/webservice.js");
    var lcrypto = require("lcrypto");
    var thservice = require(__dirname + "/Ops/thservice.js");
    var lmongo = require('lmongo');

    if(lconfig.lockerHost != "localhost" && lconfig.lockerHost != "127.0.0.1") {
        console.warn('if I\'m running on a public IP I needs to have password protection,' + // uniquely self (de?)referential? lolz!
                    'which if so inclined can be hacked into lockerd.js and added since' +
                    ' it\'s apparently still not implemented :)\n\n');
    }
    var shuttingDown_ = false;

    var mongoProcess;
    path.exists(lconfig.me + '/' + lconfig.mongo.dataDir, function(exists) {
        if(!exists) {
            try {
                //ensure there is a Me dir
                fs.mkdirSync(lconfig.me, 0755);
            } catch(err) {
                if(err.code !== 'EEXIST')
                    console.error('err', err);
            }
            fs.mkdirSync(lconfig.me + '/' + lconfig.mongo.dataDir, 0755);
        }
        mongoProcess = spawn('mongod', ['--dbpath', lconfig.lockerDir + '/' + lconfig.me + '/' + lconfig.mongo.dataDir,
                                        '--port', lconfig.mongo.port]);
        mongoProcess.stderr.on('data', function(data) {
            console.error('mongod err: ' + data);
        });

        var mongoOutput = "";
        var mongodExit = function(errorCode) {
            if(shuttingDown_) return;
            if(errorCode !== 0) {
                var db = new mongodb.Db('locker', new mongodb.Server(lconfig.mongo.host, lconfig.mongo.port, {}), {});
                db.open(function(error, client) {
                    if(error) {
                        console.error('mongod did not start successfully and was not already running ('+errorCode+'), here was the stdout: '+mongoOutput);
                        shutdown(1);
                    } else {
                        console.error('found a previously running mongodb running on port '+lconfig.mongo.port+' so we will use that');
                        db.close();
                        checkKeys();
                    }
                });
            }
        };
        mongoProcess.on('exit', mongodExit);

        // watch for mongo startup
        var callback = function(data) {
            mongoOutput += data;
            if(mongoOutput.match(/ waiting for connections on port/g)) {
                mongoProcess.stdout.removeListener('data', callback);
                lmongo.connect(checkKeys);
           }
        };
        mongoProcess.stdout.on('data', callback);
    });

    function checkKeys() {
        lcrypto.generateSymKey(function(hasKey) {
            if (!hasKey) {
                shutdown(1);
                return;
            }
            lcrypto.generatePKKeys(function(hasKeys) {
                if (!hasKeys) {
                    shutdown(1);
                    return;
                }
                finishStartup();
            });
        });
    }

    function finishStartup() {
        // get current git revision if git is available
        var gitHead = spawn('git', ['rev-parse', '--verify', 'HEAD']);
        gitHead.stdout.on('data', function(data) {
            fs.writeFileSync(path.join(lconfig.lockerDir, lconfig.me, 'gitrev.json'), JSON.stringify(data.toString()));
        });

        // look for available things
        lconfig.scannedDirs.forEach(function(dirToScan) {
            console.log(dirToScan);
            var installable = true;
            if (dirToScan === "Collections") installable = false;
            serviceManager.scanDirectory(dirToScan, installable);
        });

        syncManager.scanDirectory("Connectors");

        // look for existing things
        serviceManager.findInstalled();

        // start web server (so we can all start talking)
        webservice.startService(lconfig.lockerPort, runMigrations);
        var lockerPortNext = "1"+lconfig.lockerPort;
        lockerPortNext++;

    }

    function runMigrations() {
        var migrations = [];
        var metaData = {version: 1};
        try {
            migrations = fs.readdirSync(path.join(lconfig.lockerDir, "/migrations"));
            console.error(migrations);
            metaData = JSON.parse(fs.readFileSync(path.join(lconfig.lockerDir, lconfig.me, "state.json")));
            console.error(metaData);
        } catch (E) {}
        if (migrations.length > 0) migrations = migrations.sort(); // do in order, so versions are saved properly
        for (var i = 0; i < migrations.length; i++) {
            if (migrations[i].substring(0, 13) > metaData.version) {
                try {
                    console.log("running global migration : " + migrations[i]);
                    migrate = require(path.join(lconfig.lockerDir, "migrations", migrations[i]));
                    var ret = migrate(lconfig); // prolly needs to be sync and given a callback someday
                    if (ret) {
                        // load new file in case it changed, then save version back out
                        var curMe = JSON.parse(fs.readFileSync(path.join(lconfig.lockerDir, lconfig.me, "state.json"), 'utf8'));
                        metaData.version = migrations[i].substring(0, 13);
                        curMe.version = metaData.version;
                        fs.writeFileSync(path.join(lconfig.lockerDir, lconfig.me, "state.json"), JSON.stringify(curMe, null, 4));
                    } else {
                        // this isn't clean but we have to do something drastic!!!
                        console.error("failed to run global migration!");
                        process.exit(1);
                    }
                    // if they returned a string, it's a post-startup callback!
                    if (typeof ret == 'string')
                    {
                        serviceMap.migrations.push(lconfig.lockerBase+"/Me/"+metaData.id+"/"+ret);
                    }
                } catch (E) {
                    console.log("error running global migration : " + migrations[i] + " ---- " + E);
                }
            }
        }

        // if there's any migrations, load synclets and do them but don't let synclets run till done
        if(serviceManager.serviceMap().migrations.length > 0)
        {
            syncManager.synclets().executable = false;
            syncManager.findInstalled();
            async.forEachSeries(serviceManager.serviceMap().migrations,function(call,cb){
                console.log('running migration followup for '+call);
                request.get({uri:call},function(err,res,body){
                    if(err || !res || res.statusCode != 200)
                    {
                        console.error("failed to run migration, should be bailing hard! "+util.inspect(err)+":"+util.inspect(res)+" trying to hit " + call);
                    }else{
                        console.log("migration success: "+JSON.stringify(body));
                    }
                    cb();
                });
            },function(){
                serviceManager.serviceMap().migrations = [];
                postStartup();
            });
        }else{
            syncManager.findInstalled();
            postStartup();
        }
    }

    // scheduling and misc things
    function postStartup() {
        thservice.start();

        lscheduler.masterScheduler.loadAndStart();

        console.log('locker is running, use your browser and visit ' + lconfig.lockerBase);
        exports.alive = true;
    }

    function shutdown(returnCode) {
        process.stdout.write("\n");
        shuttingDown_ = true;
        serviceManager.shutdown(function() {
            mongoProcess.kill();
            console.log("Shutdown complete.");
            process.exit(returnCode);
        });
    }

    process.on("SIGINT", function() {
        shutdown(0);
    });

    process.on("SIGTERM", function() {
        shutdown(0);
    });

    process.on('uncaughtException', function(err) {
        console.error(util.inspect(err));
        if(err && err.stack) console.error(util.inspect(err.stack));
        if (lconfig.airbrakeKey) {
            var airbrake = require('airbrake').createClient(lconfig.airbrakeKey);
            airbrake.notify(err, function(err, url) {
                if(url) console.log(url);
                shutdown(0);
            });
        }else{
            shutdown(0);
        }
    });

    // Export some things so this can be used by other processes, mainly for the test runner
    exports.shutdown = shutdown;
//  });
//});
<commit_msg>exit 1 when exiting via exception<commit_after>/*
*
* Copyright (C) 2011, The Locker Project
* All rights reserved.
*
* Please see the LICENSE file for more information.
*
*/

/* random notes:
on startup scan all folders
    Apps Collections Connectors - generate lists of "available"
    Me/* - generate lists of "existing"

when asked, run any existing and return localhost:port
if first time
    check dependencies
    create Me/ dir
    create me.json settings
    pick a port
*/
var conf = {};
conf._exit = false;
exports.alive = false;
var npm = require('npm');
//npm.load(conf, function(er) {
  //npm.commands.install([], function(err, data) {
    require.paths.push(__dirname + "/Common/node");
    var spawn = require('child_process').spawn;
    var fs = require('fs');
    var path = require('path');
    var request = require('request');
    var async = require('async');
    var util = require('util');

    // This lconfig stuff has to come before and other locker modules are loaded!!
    var lconfig = require('lconfig');
    lconfig.load((process.argv[2] == '--config'? process.argv[3] : 'Config/config.json'));

    console.log(process.pid);
    fs.writeFileSync(__dirname + '/Logs/locker.pid', "" + process.pid);

    var logger = require("logger");
    var lconsole = require("lconsole");
    var lscheduler = require("lscheduler");
    var syncManager = require('lsyncmanager');
    var serviceManager = require("lservicemanager");
    var mongodb = require('mongodb');
    var webservice = require(__dirname + "/Ops/webservice.js");
    var lcrypto = require("lcrypto");
    var thservice = require(__dirname + "/Ops/thservice.js");
    var lmongo = require('lmongo');

    if(lconfig.lockerHost != "localhost" && lconfig.lockerHost != "127.0.0.1") {
        console.warn('if I\'m running on a public IP I needs to have password protection,' + // uniquely self (de?)referential? lolz!
                    'which if so inclined can be hacked into lockerd.js and added since' +
                    ' it\'s apparently still not implemented :)\n\n');
    }
    var shuttingDown_ = false;

    var mongoProcess;
    path.exists(lconfig.me + '/' + lconfig.mongo.dataDir, function(exists) {
        if(!exists) {
            try {
                //ensure there is a Me dir
                fs.mkdirSync(lconfig.me, 0755);
            } catch(err) {
                if(err.code !== 'EEXIST')
                    console.error('err', err);
            }
            fs.mkdirSync(lconfig.me + '/' + lconfig.mongo.dataDir, 0755);
        }
        mongoProcess = spawn('mongod', ['--dbpath', lconfig.lockerDir + '/' + lconfig.me + '/' + lconfig.mongo.dataDir,
                                        '--port', lconfig.mongo.port]);
        mongoProcess.stderr.on('data', function(data) {
            console.error('mongod err: ' + data);
        });

        var mongoOutput = "";
        var mongodExit = function(errorCode) {
            if(shuttingDown_) return;
            if(errorCode !== 0) {
                var db = new mongodb.Db('locker', new mongodb.Server(lconfig.mongo.host, lconfig.mongo.port, {}), {});
                db.open(function(error, client) {
                    if(error) {
                        console.error('mongod did not start successfully and was not already running ('+errorCode+'), here was the stdout: '+mongoOutput);
                        shutdown(1);
                    } else {
                        console.error('found a previously running mongodb running on port '+lconfig.mongo.port+' so we will use that');
                        db.close();
                        checkKeys();
                    }
                });
            }
        };
        mongoProcess.on('exit', mongodExit);

        // watch for mongo startup
        var callback = function(data) {
            mongoOutput += data;
            if(mongoOutput.match(/ waiting for connections on port/g)) {
                mongoProcess.stdout.removeListener('data', callback);
                lmongo.connect(checkKeys);
           }
        };
        mongoProcess.stdout.on('data', callback);
    });

    function checkKeys() {
        lcrypto.generateSymKey(function(hasKey) {
            if (!hasKey) {
                shutdown(1);
                return;
            }
            lcrypto.generatePKKeys(function(hasKeys) {
                if (!hasKeys) {
                    shutdown(1);
                    return;
                }
                finishStartup();
            });
        });
    }

    function finishStartup() {
        // get current git revision if git is available
        var gitHead = spawn('git', ['rev-parse', '--verify', 'HEAD']);
        gitHead.stdout.on('data', function(data) {
            fs.writeFileSync(path.join(lconfig.lockerDir, lconfig.me, 'gitrev.json'), JSON.stringify(data.toString()));
        });

        // look for available things
        lconfig.scannedDirs.forEach(function(dirToScan) {
            console.log(dirToScan);
            var installable = true;
            if (dirToScan === "Collections") installable = false;
            serviceManager.scanDirectory(dirToScan, installable);
        });

        syncManager.scanDirectory("Connectors");

        // look for existing things
        serviceManager.findInstalled();

        // start web server (so we can all start talking)
        webservice.startService(lconfig.lockerPort, runMigrations);
        var lockerPortNext = "1"+lconfig.lockerPort;
        lockerPortNext++;

    }

    function runMigrations() {
        var migrations = [];
        var metaData = {version: 1};
        try {
            migrations = fs.readdirSync(path.join(lconfig.lockerDir, "/migrations"));
            console.error(migrations);
            metaData = JSON.parse(fs.readFileSync(path.join(lconfig.lockerDir, lconfig.me, "state.json")));
            console.error(metaData);
        } catch (E) {}
        if (migrations.length > 0) migrations = migrations.sort(); // do in order, so versions are saved properly
        for (var i = 0; i < migrations.length; i++) {
            if (migrations[i].substring(0, 13) > metaData.version) {
                try {
                    console.log("running global migration : " + migrations[i]);
                    migrate = require(path.join(lconfig.lockerDir, "migrations", migrations[i]));
                    var ret = migrate(lconfig); // prolly needs to be sync and given a callback someday
                    if (ret) {
                        // load new file in case it changed, then save version back out
                        var curMe = JSON.parse(fs.readFileSync(path.join(lconfig.lockerDir, lconfig.me, "state.json"), 'utf8'));
                        metaData.version = migrations[i].substring(0, 13);
                        curMe.version = metaData.version;
                        fs.writeFileSync(path.join(lconfig.lockerDir, lconfig.me, "state.json"), JSON.stringify(curMe, null, 4));
                    } else {
                        // this isn't clean but we have to do something drastic!!!
                        console.error("failed to run global migration!");
                        process.exit(1);
                    }
                    // if they returned a string, it's a post-startup callback!
                    if (typeof ret == 'string')
                    {
                        serviceMap.migrations.push(lconfig.lockerBase+"/Me/"+metaData.id+"/"+ret);
                    }
                } catch (E) {
                    console.log("error running global migration : " + migrations[i] + " ---- " + E);
                }
            }
        }

        // if there's any migrations, load synclets and do them but don't let synclets run till done
        if(serviceManager.serviceMap().migrations.length > 0)
        {
            syncManager.synclets().executable = false;
            syncManager.findInstalled();
            async.forEachSeries(serviceManager.serviceMap().migrations,function(call,cb){
                console.log('running migration followup for '+call);
                request.get({uri:call},function(err,res,body){
                    if(err || !res || res.statusCode != 200)
                    {
                        console.error("failed to run migration, should be bailing hard! "+util.inspect(err)+":"+util.inspect(res)+" trying to hit " + call);
                    }else{
                        console.log("migration success: "+JSON.stringify(body));
                    }
                    cb();
                });
            },function(){
                serviceManager.serviceMap().migrations = [];
                postStartup();
            });
        }else{
            syncManager.findInstalled();
            postStartup();
        }
    }

    // scheduling and misc things
    function postStartup() {
        thservice.start();

        lscheduler.masterScheduler.loadAndStart();

        console.log('locker is running, use your browser and visit ' + lconfig.lockerBase);
        exports.alive = true;
    }

    function shutdown(returnCode) {
        process.stdout.write("\n");
        shuttingDown_ = true;
        serviceManager.shutdown(function() {
            mongoProcess.kill();
            console.log("Shutdown complete.");
            process.exit(returnCode);
        });
    }

    process.on("SIGINT", function() {
        shutdown(0);
    });

    process.on("SIGTERM", function() {
        shutdown(0);
    });

    process.on('uncaughtException', function(err) {
        console.error(util.inspect(err));
        if(err && err.stack) console.error(util.inspect(err.stack));
        if (lconfig.airbrakeKey) {
            var airbrake = require('airbrake').createClient(lconfig.airbrakeKey);
            airbrake.notify(err, function(err, url) {
                if(url) console.log(url);
                shutdown(1);
            });
        }else{
            shutdown(1);
        }
    });

    // Export some things so this can be used by other processes, mainly for the test runner
    exports.shutdown = shutdown;
//  });
//});
<|endoftext|>


<commit_before>import {
  ethereum,
  ethereumClassic,
  ropsten,
  singularDTV,
  expanse,
  ubiq,
  ellaism,
  etherGem,
  callisto,
  ethereumSocial,
  musicoin,
  goChain,
  eosClassic,
  akroma,
  etherSocialNetwork,
  pirl,
  ether1,
  atheios,
  tomoChain,
  mixBlockchain,
  iolite,
  rskMainnet,
  rskTestnet,
  solidum
} from './paths';
export default [
  ethereum,
  ethereumClassic,
  ropsten,
  singularDTV,
  expanse,
  ubiq,
  ellaism,
  etherGem,
  callisto,
  ethereumSocial,
  musicoin,
  goChain,
  eosClassic,
  akroma,
  etherSocialNetwork,
  pirl,
  ether1,
  atheios,
  tomoChain,
  mixBlockchain,
  iolite,
  rskMainnet,
  rskTestnet,
  solidum
];
<commit_msg>Update trezorPaths.js<commit_after>import {
  ethereum,
  ethereumClassic,
  ropsten,
  singularDTV,
  expanse,
  ubiq,
  ellaism,
  etherGem,
  callisto,
  ethereumSocial,
  musicoin,
  goChain,
  eosClassic,
  akroma,
  etherSocialNetwork,
  pirl,
  ether1,
  atheios,
  tomoChain,
  mixBlockchain,
  iolite,
  rskMainnet,
  rskTestnet
} from './paths';
export default [
  ethereum,
  ethereumClassic,
  ropsten,
  singularDTV,
  expanse,
  ubiq,
  ellaism,
  etherGem,
  callisto,
  ethereumSocial,
  musicoin,
  goChain,
  eosClassic,
  akroma,
  etherSocialNetwork,
  pirl,
  ether1,
  atheios,
  tomoChain,
  mixBlockchain,
  iolite,
  rskMainnet,
  rskTestnet
];
<|endoftext|>