UNPKG

orm

Version:

NodeJS Object-relational mapping

461 lines (371 loc) 12 kB
var _ = require("lodash"); var async = require("async"); var Promise = require('bluebird'); var enforce = require("enforce"); var events = require("events"); var hat = require("hat"); var Query = require("sql-query"); var url = require("url"); var util = require("util"); var adapters = require("./Adapters"); var DriverAliases = require("./Drivers/aliases"); var ORMError = require("./Error"); var Model = require("./Model").Model; var Settings = require("./Settings"); var Singleton = require("./Singleton"); var Utilities = require("./Utilities"); var OPTS_TYPE_STRING = 'string'; var OPTS_TYPE_OBJ = 'object'; // Deprecated, use enforce exports.validators = require("./Validators"); // specific to ORM, not in enforce for now enforce.equalToProperty = exports.validators.equalToProperty; enforce.unique = exports.validators.unique; exports.enforce = enforce; exports.singleton = Singleton; exports.settings = new Settings.Container(Settings.defaults()); exports.Property = require("./Property"); exports.Settings = Settings; exports.ErrorCodes = ORMError.codes; var optsChecker = function (opts) { return [OPTS_TYPE_STRING, OPTS_TYPE_OBJ].some(function (element) { return typeof(opts) === element }) }; var fileLoader = function (filePaths, cb) { var self = this; var iterator = function (filePath, cb) { try { require(filePath)(self, cb); } catch (err) { return cb(err) } }; async.eachSeries(filePaths, iterator, cb); }; exports.connect = function (opts, cb) { if (arguments.length === 0 || !opts || !optsChecker(opts)) { cb = typeof(cb) !== 'function' ? opts : cb; return ORM_Error(new ORMError("CONNECTION_URL_EMPTY", 'PARAM_MISMATCH'), cb); } if (typeof opts === 'string') { if (opts.trim().length === 0) { return ORM_Error(new ORMError("CONNECTION_URL_EMPTY", 'PARAM_MISMATCH'), cb); } opts = url.parse(opts, true); } else if (typeof opts === 'object') { opts = _.cloneDeep(opts); } opts.query = opts.query || {}; for(var k in opts.query) { opts.query[k] = queryParamCast(opts.query[k]); opts[k] = opts.query[k]; } if (!opts.database) { // if (!opts.pathname) { // return cb(new Error("CONNECTION_URL_NO_DATABASE")); // } opts.database = (opts.pathname ? opts.pathname.substr(1) : ""); } if (!opts.protocol) { return ORM_Error(new ORMError("CONNECTION_URL_NO_PROTOCOL", 'PARAM_MISMATCH'), cb); } // if (!opts.host) { // opts.host = opts.hostname = "localhost"; // } if (opts.auth) { opts.user = opts.auth.split(":")[0]; opts.password = opts.auth.split(":")[1]; } if (!opts.hasOwnProperty("user")) { opts.user = "root"; } if (!opts.hasOwnProperty("password")) { opts.password = ""; } if (opts.hasOwnProperty("hostname")) { opts.host = opts.hostname; } var proto = opts.protocol.replace(/:$/, ''); var db; if (DriverAliases[proto]) { proto = DriverAliases[proto]; } try { var Driver = adapters.get(proto); var settings = new Settings.Container(exports.settings.get('*')); var driver = new Driver(opts, null, { debug : 'debug' in opts.query ? opts.query.debug : settings.get("connection.debug"), pool : 'pool' in opts.query ? opts.query.pool : settings.get("connection.pool"), settings : settings }); db = new ORM(proto, driver, settings); driver.connect(function (err) { if (typeof cb === "function") { if (err) { return cb(err); } else { return cb(null, db); } } db.emit("connect", err, !err ? db : null); }); } catch (ex) { if (ex.code === "MODULE_NOT_FOUND" || ex.message.indexOf('find module') > -1) { return ORM_Error(new ORMError("Connection protocol not supported - have you installed the database driver for " + proto + "?", 'NO_SUPPORT'), cb); } return ORM_Error(ex, cb); } return db; }; exports.connectAsync = Promise.promisify(exports.connect, { context: exports }); var use = function (connection, proto, opts, cb) { if (DriverAliases[proto]) { proto = DriverAliases[proto]; } if (typeof opts === "function") { cb = opts; opts = {}; } try { var Driver = adapters.get(proto); var settings = new Settings.Container(exports.settings.get('*')); var driver = new Driver(null, connection, { debug : (opts.query && opts.query.debug === 'true'), settings : settings }); return cb(null, new ORM(proto, driver, settings)); } catch (ex) { return cb(ex); } } exports.Text = Query.Text; for (var k in Query.Comparators) { exports[Query.Comparators[k]] = Query[Query.Comparators[k]]; } exports.express = function () { return require("./Express").apply(this, arguments); }; exports.use = use; exports.useAsync = Promise.promisify(use); exports.addAdapter = adapters.add; function ORM(driver_name, driver, settings) { this.validators = exports.validators; this.enforce = exports.enforce; this.settings = settings; this.driver_name = driver_name; this.driver = driver; this.driver.uid = hat(); this.tools = {}; this.models = {}; this.plugins = []; this.customTypes = {}; for (var k in Query.Comparators) { this.tools[Query.Comparators[k]] = Query[Query.Comparators[k]]; } events.EventEmitter.call(this); var onError = function (err) { if (this.settings.get("connection.reconnect")) { if (typeof this.driver.reconnect === "undefined") { return this.emit("error", new ORMError("Connection lost - driver does not support reconnection", 'CONNECTION_LOST')); } this.driver.reconnect(function () { this.driver.on("error", onError); }.bind(this)); if (this.listeners("error").length === 0) { // since user want auto reconnect, // don't emit without listeners or it will throw return; } } this.emit("error", err); }.bind(this); driver.on("error", onError); } util.inherits(ORM, events.EventEmitter); ORM.prototype.use = function (plugin_const, opts) { if (typeof plugin_const === "string") { try { plugin_const = require(Utilities.getRealPath(plugin_const)); } catch (e) { throw e; } } var plugin = new plugin_const(this, opts || {}); if (typeof plugin.define === "function") { for (var k in this.models) { plugin.define(this.models[k]); } } this.plugins.push(plugin); return this; }; ORM.prototype.define = function (name, properties, opts) { var i; properties = properties || {}; opts = opts || {}; for (i = 0; i < this.plugins.length; i++) { if (typeof this.plugins[i].beforeDefine === "function") { this.plugins[i].beforeDefine(name, properties, opts); } } this.models[name] = new Model({ db : this, settings : this.settings, driver_name : this.driver_name, driver : this.driver, table : opts.table || opts.collection || ((this.settings.get("model.namePrefix") || "") + name), properties : properties, extension : opts.extension || false, indexes : opts.indexes || [], identityCache : opts.hasOwnProperty("identityCache") ? opts.identityCache : this.settings.get("instance.identityCache"), keys : opts.id, autoSave : opts.hasOwnProperty("autoSave") ? opts.autoSave : this.settings.get("instance.autoSave"), autoFetch : opts.hasOwnProperty("autoFetch") ? opts.autoFetch : this.settings.get("instance.autoFetch"), autoFetchLimit : opts.autoFetchLimit || this.settings.get("instance.autoFetchLimit"), cascadeRemove : opts.hasOwnProperty("cascadeRemove") ? opts.cascadeRemove : this.settings.get("instance.cascadeRemove"), hooks : opts.hooks || {}, methods : opts.methods || {}, validations : opts.validations || {} }); for (i = 0; i < this.plugins.length; i++) { if (typeof this.plugins[i].define === "function") { this.plugins[i].define(this.models[name], this); } } return this.models[name]; }; ORM.prototype.defineType = function (name, opts) { this.customTypes[name] = opts; this.driver.customTypes[name] = opts; return this; }; ORM.prototype.ping = function (cb) { this.driver.ping(cb); return this; }; ORM.prototype.pingAsync = Promise.promisify(ORM.prototype.ping); ORM.prototype.close = function (cb) { this.driver.close(cb); return this; }; ORM.prototype.closeAsync = Promise.promisify(ORM.prototype.close); ORM.prototype.load = function () { var files = _.flatten(Array.prototype.slice.apply(arguments)); var cb = function () {}; if (typeof files[files.length - 1] == "function") { cb = files.pop(); } var filesWithPath = []; // Due to intricacies of `Utilities.getRealPath` the following // code has to look as it does. for(var a = 0; a < files.length; a++) { filesWithPath.push(function () { return Utilities.getRealPath(files[a], 4) }()); } fileLoader.call(this, filesWithPath, cb); }; ORM.prototype.loadAsync = function () { var files = _.flatten(Array.prototype.slice.apply(arguments)); var filesWithPath = []; // Due to intricacies of `Utilities.getRealPath` the following // code has to look as it does. for(var i = 0; i < files.length; i++) { var file = files[i]; filesWithPath.push(function () { return Utilities.getRealPath(file, 4) }()); } return Promise.promisify(fileLoader, { context: this })(filesWithPath) }; ORM.prototype.sync = function (cb) { var modelIds = Object.keys(this.models); var syncNext = function () { if (modelIds.length === 0) { return cb(); } var modelId = modelIds.shift(); this.models[modelId].sync(function (err) { if (err) { err.model = modelId; return cb(err); } return syncNext(); }); }.bind(this); if (arguments.length === 0) { cb = function () {}; } syncNext(); return this; }; ORM.prototype.syncPromise = Promise.promisify(ORM.prototype.sync); ORM.prototype.drop = function (cb) { var modelIds = Object.keys(this.models); var dropNext = function () { if (modelIds.length === 0) { return cb(); } var modelId = modelIds.shift(); this.models[modelId].drop(function (err) { if (err) { err.model = modelId; return cb(err); } return dropNext(); }); }.bind(this); if (arguments.length === 0) { cb = function () {}; } dropNext(); return this; }; ORM.prototype.dropAsync = Promise.promisify(ORM.prototype.drop); ORM.prototype.serial = function () { var chains = Array.prototype.slice.apply(arguments); return { get: function (cb) { var params = []; var getNext = function () { if (params.length === chains.length) { params.unshift(null); return cb.apply(null, params); } chains[params.length].run(function (err, instances) { if (err) { params.unshift(err); return cb.apply(null, params); } params.push(instances); return getNext(); }); }; getNext(); return this; } }; }; function ORM_Error(err, cb) { var Emitter = new events.EventEmitter(); Emitter.use = Emitter.define = Emitter.sync = Emitter.load = function () {}; if (typeof cb === "function") { cb(err); } process.nextTick(function () { Emitter.emit("connect", err); }); return Emitter; } function queryParamCast (val) { if (typeof val == 'string') { switch (val) { case '1': case 'true': return true; case '0': case 'false': return false; } } return val; }