UNPKG

neoogm

Version:
1,049 lines (997 loc) 32.7 kB
/** * neoogm 0.0.3 <https://github.com/bigluck/neoogm> * Neo4j OGM for Node.js * * Available under MIT license <https://github.com/bigluck/neoogm/raw/master/LICENSE> */ (function() { var NeoormError, Q, async, ensureValidModel, globals, models, neoogm, parseCypherId, parseInputId, request, _, __hasProp = {}.hasOwnProperty, __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; request = require("request"); async = require("async"); _ = require("lodash"); Q = require("q"); globals = { host: "localhost", port: 7474, secure: false, merge: true, node: { created_at: false, updated_at: false }, relationship: { created_at: false, updated_at: false } }; models = { node: {}, relationship: {}, options: { schema: {}, strict: true, created_at: false, updated_at: false } }; NeoormError = (function(_super) { __extends(_Class, _super); function _Class(data) { this.name = "Neoorm" + ((data != null ? data.exception : void 0) != null ? data != null ? data.exception : void 0 : ''); this.message = (data != null ? data.message : void 0) != null ? data.message : data; this.original = data; } return _Class; })(Error); neoogm = function() { return neoogm.cypher(arguments); }; neoogm.config = function(options) { globals = _.extend({}, globals, options); globals.url = "http" + (globals.secure ? 's' : '') + "://" + (globals.host || 'localhost') + ":" + (globals.port || 7474) + "/db/data/cypher"; return neoogm; }; neoogm.cypher = function(options, cb) { var deferred, _ref, _ref1, _ref2; deferred = Q.defer(); options = _.extend({ one: false, query: "", params: {}, models: [] }, typeof options === "object" ? options : options instanceof Array ? { query: options.join(" ") } : { query: options }); if (options.query instanceof Array) { options.query = options.query.join(" "); } if (options.one && !options.query.match(/LIMIT\s+([\d+])/)) { options.query += " LIMIT 1"; } if (((_ref = options.params) != null ? _ref._id : void 0) != null) { options.params._id = parseInputId(options.params._id); } if (((_ref1 = options.params) != null ? _ref1._start : void 0) != null) { options.params._start = parseInputId(options.params._start); } if (((_ref2 = options.params) != null ? _ref2._end : void 0) != null) { options.params._end = parseInputId(options.params._end); } request({ url: globals.url, method: "POST", headers: { "Accept": "application/json; charset=UTF-8", "Content-Type": "application/json", "X-Stream": "true" }, json: { query: options.query, params: options.params } }, function(err, res, body) { var data, i, model_remaps, name, output_columns, _i, _len, _ref3; if (res && (res != null ? res.statusCode : void 0) !== 200) { err = body; } if (err) { err = new NeoormError(err); if (typeof cb === "function") { cb(err); } return deferred.reject(err); } output_columns = []; model_remaps = []; _ref3 = options.models; for (i = _i = 0, _len = _ref3.length; _i < _len; i = ++_i) { name = _ref3[i]; output_columns.push(body.columns[i]); if ((name != null ? name[0] : void 0) === "=") { model_remaps.push({ item: body.columns[i], labels: name.slice(1) }); } else if (name === false) { output_columns.pop(); model_remaps.push({ item: body.columns[i], remove: true }); } } data = _.map(body.data, function(row, row_i) { var klass, model, rows, rule, _j, _k, _len1, _len2, _ref4; rows = _.transform(row, function(out, row, col_i) { var entity, klass, _ref4; entity = (row != null ? row.type : void 0) != null ? "relationship" : (row != null ? row.data : void 0) != null ? "node" : false; row = (function() { switch (entity) { case "relationship": return _.extend({}, row.data, { _id: parseCypherId(row.self), _start: parseCypherId(row.start), _end: parseCypherId(row.end), _type: row.type }); case "node": return _.extend({}, row.data, { _id: parseCypherId(row.self) }); default: return row; } })(); return out[body.columns[col_i]] = entity && (klass = models[entity][(_ref4 = options.models) != null ? _ref4[col_i] : void 0]) ? new klass(row) : row; }); for (_j = 0, _len1 = model_remaps.length; _j < _len1; _j++) { rule = model_remaps[_j]; if (rule.labels) { _ref4 = rows[rule.labels]; for (_k = 0, _len2 = _ref4.length; _k < _len2; _k++) { model = _ref4[_k]; if (klass = models.node[model] || (klass = models.relationship[model])) { rows[rule.item] = new klass(rows[rule.item]); break; } } } else if (rule.remove === true) { delete rows[rule.item]; } } if (output_columns.length && output_columns.length > 1) { return rows; } else { return rows[output_columns[0]]; } }); data = options.one ? data[0] : data; if (typeof cb === "function") { cb(null, data); } return deferred.resolve(data); }); return deferred.promise; }; neoogm.findNodeById = function(id, cb) { return neoogm.cypher({ one: true, query: ["START node = node({id})", "RETURN node, LABELS(node) AS node_labels"], params: { id: id }, models: ["=node_labels", false] }, cb); }; neoogm.findRelationshipById = function(id, cb) { return neoogm.cypher({ one: true, query: ["START relationship = relationship({id})", "RETURN relationship, [TYPE(relationship)] AS relationship_type"], params: { id: id }, models: ["=relationship_type", false] }, cb); }; neoogm.node = function(node_label, node_options) { var key, _i, _len, _ref; if (node_options == null) { node_options = false; } node_label = node_label.trim(); if (!models.node[node_label] && !node_options) { throw new Error("Node model \"" + node_label + "\" not defined"); } if (!node_options) { return models.node[node_label]; } if (models.node[node_label]) { throw new Error("Node model \"" + node_label + "\" already defined"); } node_options = _.extend(models.options, globals.node, node_options); _ref = ["created_at", "updated_at"]; for (_i = 0, _len = _ref.length; _i < _len; _i++) { key = _ref[_i]; if (!node_options[key]) { continue; } node_options[key] = node_options[key] === true ? key : node_options[key]; node_options.schema[node_options[key]] = Date.now; } return models.node[node_label] = (function() { function _Class(data) { var name, value; if (data == null) { data = {}; } for (name in data) { value = data[name]; this[name] = value; } } _Class.prototype.save = function(cb) { var data, deferred, self; deferred = Q.defer(); data = _.extend({}, this.toJSON()); if (node_options.updated_at) { data[node_options.updated_at] = Date.now(); } if (node_options.created_at && (this._id == null)) { data[node_options.created_at] = Date.now(); } self = this; neoogm.cypher({ one: true, query: this._id ? "START n = node({id}) WHERE n:" + node_label + " SET n = {data} RETURN n" : "CREATE (n:" + node_label + " {data}) RETURN n", params: { id: this._id, data: data }, models: [node_label] }, function(err, item) { var value; if (err) { if (typeof cb === "function") { cb(err); } return deferred.reject(err); } for (key in item) { value = item[key]; self[key] = value; } if (typeof cb === "function") { cb(null, self); } return deferred.resolve(self); }); return deferred.promise; }; _Class.prototype.remove = function(cb) { var deferred, err, self; deferred = Q.defer(); if (!this._id) { err = new NeoormError("Node could not be deleted without a valid id"); if (typeof cb === "function") { cb(err); } return deferred.reject(err); } self = this; neoogm.cypher({ query: ["START n = node({id})", "WHERE n:" + node_label, "DELETE n"], params: { id: this._id } }, function(err, item) { if (err) { if (typeof cb === "function") { cb(err); } return deferred.reject(err); } delete self._id; if (typeof cb === "function") { cb(null, self); } return deferred.resolve(self); }); return deferred.promise; }; _Class.prototype.findOutgoing = function(options, cb) { var _ref1; if (typeof options === "string") { _ref1 = [ { type: options }, cb ], options = _ref1[0], cb = _ref1[1]; } return this.findRelates(_.extend({}, options, { type: options.type, outgoing: true }), cb); }; _Class.prototype.findIncoming = function(options, cb) { var _ref1; if (typeof options === "string") { _ref1 = [ { type: options }, cb ], options = _ref1[0], cb = _ref1[1]; } return this.findRelates(_.extend({}, options, { type: options.type, incoming: true }), cb); }; _Class.prototype.findRelates = function(options, cb) { var _ref1; if (typeof options === "string") { _ref1 = [ { type: options }, cb ], options = _ref1[0], cb = _ref1[1]; } if (!options.type) { throw new NeoormError("Relationship type not defined"); } return (neoogm.relationship(options.type)).findRelates(_.extend({}, options, { model: this }), cb); }; _Class.prototype.getId = function() { return this._id; }; _Class.prototype.getLabel = function() { return node_label; }; _Class.prototype.toJSON = function() { return ensureValidModel(this, node_options); }; _Class.create = function(data, cb) { var deferred, _ref1; if (typeof data === "function") { _ref1 = [{}, data], data = _ref1[0], cb = _ref1[1]; } deferred = Q.defer(); neoogm.cypher({ query: ["CREATE (n:" + node_label + " {data})", "RETURN n"], params: { data: data }, models: [node_label] }, function(err, items) { if (err) { if (typeof cb === "function") { cb(err); } return deferred.reject(err); } if (typeof cb === "function") { cb(err, items); } return deferred.resolve(items); }); return deferred.promise; }; _Class.update = function(options, cb) { var deferred, query_update, value, _ref1; deferred = Q.defer(); options = _.extend({ query: null, params: {}, data: {} }, options); if (options.query instanceof Array) { options.query = options.query.join(" "); } else if (typeof options.query === "object") { _ref1 = options.query; for (key in _ref1) { value = _ref1[key]; options.params[key] = value; } options.query = ((function() { var _ref2, _results; _ref2 = options.query; _results = []; for (key in _ref2) { value = _ref2[key]; _results.push(" n." + key + " = {" + key + "} "); } return _results; })()).join(" AND "); } query_update = ((function() { var _ref2, _results; _ref2 = options.data; _results = []; for (key in _ref2) { value = _ref2[key]; _results.push(" n." + key + " = {" + key + "} "); } return _results; })()).join(", "); neoogm.cypher({ query: ["MATCH (n:" + node_label + ")", options.query ? "WHERE " + options.query : "", "SET " + query_update, "RETURN n"], params: options.params, models: [node_label] }, function(err, items) { if (err) { if (typeof cb === "function") { cb(err); } return deferred.reject(err); } if (typeof cb === "function") { cb(err, items); } return deferred.resolve(items); }); return deferred.promise; }; _Class["delete"] = function(options, cb) { var deferred, self; deferred = Q.defer(); self = this; async.waterfall([ function(cb) { return self.find(options, cb); }, function(items, cb) { var id, query_where; if (!items.length) { return cb(null, items); } query_where = ((function() { var _j, _len1, _ref1, _results; _ref1 = _.pluck(items, "_id"); _results = []; for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { id = _ref1[_j]; _results.push("ID(n) = " + id); } return _results; })()).join(" OR "); return neoogm.cypher("START n=node(*) WHERE n:User AND (" + query_where + ") DELETE n", function(err) { return cb(err, items); }); } ], function(err, items) { if (err) { if (typeof cb === "function") { cb(err); } return deferred.reject(err); } if (typeof cb === "function") { cb(err, items); } return deferred.resolve(items); }); return deferred.promise; }; _Class.findById = function(id, cb) { var deferred, err, _ref1; if (typeof id === "function") { _ref1 = [null, id], id = _ref1[0], cb = _ref1[1]; } deferred = Q.defer(); if (id == null) { err = new NeoormError("Node id not defined"); if (typeof cb === "function") { cb(err); } return deferred.reject(err); } neoogm.cypher({ query: ["START n = node({id})", "WHERE n:" + node_label, "RETURN n"], params: { id: id }, models: [node_label], one: true }, function(err, items) { if (err) { if (typeof cb === "function") { cb(err); } return deferred.reject(err); } if (typeof cb === "function") { cb(err, items); } return deferred.resolve(items); }); return deferred.promise; }; _Class.findByIdAndRemove = function(id, cb) { var deferred, err, self, _ref1; if (typeof id === "function") { _ref1 = [null, id], id = _ref1[0], cb = _ref1[1]; } deferred = Q.defer(); if (id == null) { err = new NeoormError("Node id not defined"); if (typeof cb === "function") { cb(err); } return deferred.reject(err); } self = this; async.waterfall([ function(cb) { return self.findById(id, cb); }, function(item, cb) { return item.remove(cb); } ], function(err, items) { if (err) { if (typeof cb === "function") { cb(err); } return deferred.reject(err); } if (typeof cb === "function") { cb(err, items); } return deferred.resolve(items); }); return deferred.promise; }; _Class.findByIdAndUpdate = function(id, data, cb) { var deferred, query_update, value, _ref1; if (typeof data === "function") { _ref1 = [{}, id], data = _ref1[0], cb = _ref1[1]; } deferred = Q.defer(); data = _.extend({}, data); if (node_options.updated_at) { data[node_options.updated_at] = Date.now(); } query_update = ((function() { var _results; _results = []; for (key in data) { value = data[key]; _results.push(" n." + key + " = {" + key + "} "); } return _results; })()).join(", "); neoogm.cypher({ query: ["START n = node({id})", "WHERE n:" + node_label, "SET " + query_update, "RETURN n"], params: _.extend(data, { id: id }), models: [node_label], one: true }, function(err, items) { if (err) { if (typeof cb === "function") { cb(err); } return deferred.reject(err); } if (typeof cb === "function") { cb(err, items); } return deferred.resolve(items); }); return deferred.promise; }; _Class.find = function(options, cb) { var deferred, value, _ref1, _ref2; if (typeof options === "function") { _ref1 = [{}, options], options = _ref1[0], cb = _ref1[1]; } deferred = Q.defer(); options = _.extend({ query: null, params: {} }, options); if (options.query instanceof Array) { options.query = options.query.join(" "); } else if (typeof options.query === "object") { _ref2 = options.query; for (key in _ref2) { value = _ref2[key]; options.params[key] = value; } options.query = ((function() { var _ref3, _results; _ref3 = options.query; _results = []; for (key in _ref3) { value = _ref3[key]; _results.push(" n." + key + " = {" + key + "} "); } return _results; })()).join(" AND "); } neoogm.cypher({ query: ["MATCH (n:" + node_label + ")", "WHERE " + options.query, "RETURN n"], params: options.params, models: [node_label] }, function(err, items) { if (err) { if (typeof cb === "function") { cb(err); } return deferred.reject(err); } if (typeof cb === "function") { cb(err, items); } return deferred.resolve(items); }); return deferred.promise; }; return _Class; })(); }; neoogm.relationship = function(relationship_type, relationship_options) { var key, _i, _len, _ref; if (relationship_options == null) { relationship_options = false; } relationship_type = relationship_type.trim(); if (!models.relationship[relationship_type] && !relationship_options) { throw new Error("Relationship model \"" + relationship_type + "\" not defined"); } if (!relationship_options) { return models.relationship[relationship_type]; } if (models.relationship[relationship_type]) { throw new Error("Relationship model \"" + relationship_type + "\" already defined"); } relationship_options = _.extend(models.options, globals.relationship, relationship_options); _ref = ["created_at", "updated_at"]; for (_i = 0, _len = _ref.length; _i < _len; _i++) { key = _ref[_i]; if (!relationship_options[key]) { continue; } relationship_options[key] = relationship_options[key] === true ? key : relationship_options[key]; relationship_options.schema[relationship_options[key]] = Date.now; } return models.relationship[relationship_type] = (function() { function _Class(data) { var name, value; if (data == null) { data = {}; } for (name in data) { value = data[name]; this[name] = value; } } _Class.prototype.save = function(cb) { var data, deferred, err, self; deferred = Q.defer(); data = _.extend({}, this.toJSON()); if (relationship_options.updated_at) { data[relationship_options.updated_at] = Date.now(); } if (relationship_options.created_at && (this._id == null)) { data[relationship_options.created_at] = Date.now(); } if (this._start == null) { err = new NeoormError("Start node not defined"); if (typeof cb === "function") { cb(err); } return deferred.reject(err); } if (this._end == null) { err = new NeoormError("End node not defined"); if (typeof cb === "function") { cb(err); } return deferred.reject(err); } self = this; async.waterfall([ function(cb) { return cb(null, self._id ? self : void 0); }, function(item, cb) { if (item) { return cb(null, item); } return neoogm.cypher({ one: true, query: ["START start = node({start}), end = node({end})", "MATCH (start) -[relationship:" + relationship_type + "]-> (end)", "RETURN relationship"], params: { start: self._start, end: self._end }, models: [relationship_type] }, function() { return typeof cb === "function" ? cb.apply(null, arguments) : void 0; }); }, function(item, cb) { if (!(item != null ? item._id : void 0)) { return cb(null, item); } if (relationship_options.created_at) { data[relationship_options.created_at] = item[relationship_options.created_at]; } return neoogm.cypher({ one: true, query: ["START relationship = relationship({id})", "WHERE TYPE(relationship) = {type}", "SET relationship = {data}", "RETURN relationship"], params: { id: item._id, type: relationship_type, data: data }, models: [relationship_type] }, function() { return typeof cb === "function" ? cb.apply(null, arguments) : void 0; }); }, function(item, cb) { if (item != null ? item._id : void 0) { return cb(null, item); } return neoogm.cypher({ one: true, query: ["START start = node({start}), end = node({end})", "CREATE (start) -[relationship:" + relationship_type + " {data}]-> (end)", "RETURN relationship"], params: { start: self._start, end: self._end, data: data }, models: [relationship_type] }, function() { return typeof cb === "function" ? cb.apply(null, arguments) : void 0; }); } ], function(err, item) { var value; if (err) { if (typeof cb === "function") { cb(err); } return deferred.reject(err); } for (key in item) { value = item[key]; self[key] = value; } if (typeof cb === "function") { cb(null, self); } return deferred.resolve(self); }); return deferred.promise; }; _Class.prototype.remove = function(cb) { var deferred, err, self; deferred = Q.defer(); if (!this._id) { err = new NeoormError("Relationship could not be deleted without a valid id"); if (typeof cb === "function") { cb(err); } return deferred.reject(err); } self = this; neoogm.cypher({ query: ["START relationship = relationship({id})", "WHERE TYPE(relationship) = {type}", "DELETE relationship"], params: { id: this._id, type: relationship_type } }, function(err, item) { if (err) { if (typeof cb === "function") { cb(err); } return deferred.reject(err); } delete self._id; delete self._start; delete self._end; if (typeof cb === "function") { cb(null, self); } return deferred.resolve(results); }); return deferred.promise; }; _Class.prototype.getId = function() { return this._id; }; _Class.prototype.getType = function() { return relationship_type; }; _Class.prototype.getStart = function(cb) { var deferred, self; deferred = Q.defer(); self = this; async.waterfall([ function(cb) { var _ref1; if (((_ref1 = self._start) != null ? _ref1._id : void 0) != null) { return cb(null, self._start); } return neoogm.findNodeById(self._start, cb); } ], function(err, item) { if (err) { if (typeof cb === "function") { cb(err); } return deferred.reject(err); } self._start = item; if (typeof cb === "function") { cb(null, item); } return deferred.resolve(item); }); return deferred.promise; }; _Class.prototype.getEnd = function(cb) { var deferred, self; deferred = Q.defer(); self = this; async.waterfall([ function(cb) { var _ref1; if (((_ref1 = self._end) != null ? _ref1._id : void 0) != null) { return cb(null, self._end); } return neoogm.findNodeById(self._end, cb); } ], function(err, item) { if (err) { if (typeof cb === "function") { cb(err); } return deferred.reject(err); } self._end = item; if (typeof cb === "function") { cb(null, item); } return deferred.resolve(item); }); return deferred.promise; }; _Class.prototype.toJSON = function() { return ensureValidModel(this, relationship_options); }; _Class.findOutgoing = function(options, cb) { var _ref1; if (typeof options === "function") { _ref1 = [{}, options], options = _ref1[0], cb = _ref1[1]; } return this.findRelates(_.extend({}, options, { outgoing: true }), cb); }; _Class.findIncoming = function(options, cb) { var _ref1; if (typeof options === "function") { _ref1 = [{}, options], options = _ref1[0], cb = _ref1[1]; } return this.findRelates(_.extend({}, options, { incoming: true }), cb); }; _Class.findRelates = function(options, cb) { var deferred, err, model_label, value, _ref1, _ref2, _ref3, _ref4; if (typeof options === "function") { _ref1 = [{}, options], options = _ref1[0], cb = _ref1[1]; } deferred = Q.defer(); options = _.extend({ model: null, outgoing: false, incoming: false, query: null, params: {} }, options); if (!(model_label = (_ref2 = options.model) != null ? typeof _ref2.getLabel === "function" ? _ref2.getLabel() : void 0 : void 0)) { err = new NeoormError("options.model have to be an Neoorm model"); if (typeof cb === "function") { cb(err); } return deferred.reject(err); } if (((_ref3 = options.model) != null ? typeof _ref3.getId === "function" ? _ref3.getId() : void 0 : void 0) == null) { err = new NeoormError("options.model is not an database reference"); if (typeof cb === "function") { cb(err); } return deferred.reject(err); } if (options.query instanceof Array) { options.query = options.query.join(" "); } else if (typeof options.query === "object") { _ref4 = options.query; for (key in _ref4) { value = _ref4[key]; options.params[key] = value; } options.query = ((function() { var _ref5, _results; _ref5 = options.query; _results = []; for (key in _ref5) { value = _ref5[key]; _results.push(" n." + key + " = {" + key + "} "); } return _results; })()).join(" AND "); } neoogm.cypher({ query: ["START target = node({id})", "MATCH ", "(target:" + model_label + ") " + (options.incoming && !options.outgoing ? '<' : '') + "-", " [relationship:" + relationship_type + "] ", "-" + (options.outgoing && !options.incoming ? '>' : '') + " (end)", options.query ? "WHERE " + options.query : "", "RETURN relationship, end, LABELS(end) AS end_labels"], params: { id: options.model.getId() }, models: [relationship_type, "=end_labels", false] }, function(err, results) { if (err) { if (typeof cb === "function") { cb(err); } return deferred.reject(err); } results = _.map(results, function(row, row_i) { row.start = options.model; return row; }); if (typeof cb === "function") { cb(err, results); } return deferred.resolve(results); }); return deferred.promise; }; return _Class; })(); }; parseCypherId = function(path) { return parseInt((path.match(/([\d]+)$/))[1]); }; parseInputId = function(data) { if ((data != null ? data._id : void 0) != null) { return parseInt(data._id); } else { return data; } }; ensureValidModel = function(model, options) { var key, keys, value; keys = _.union((function() { var _results; _results = []; for (key in options.schema) { _results.push(key); } return _results; })(), (function() { var _results; _results = []; for (key in model) { value = model[key]; if (model.hasOwnProperty(key)) { _results.push(key); } } return _results; })()); return _.transform(keys, function(out, key) { var _ref; if ((model[key] != null) && key !== "_id" && (options.strict === false || (options.strict === true && ((_ref = options.schema) != null ? _ref[key] : void 0)))) { return out[key] = model[key]; } }); }; module.exports = neoogm; }).call(this);