diff --git a/build/Collection.js b/build/Collection.js index a53b02f..9afc873 100644 --- a/build/Collection.js +++ b/build/Collection.js @@ -63,6 +63,20 @@ var Collection = /*#__PURE__*/function () { value: function first() { return this._values[0]; } + /** + * Add value in collection + * + * @param {mixed} value + * @return {Collection} + */ + + }, { + key: "add", + value: function add(value) { + this._values.push(value); + + return this; + } /** * Map a function to all values * diff --git a/build/Factory.js b/build/Factory.js index 6bb0220..3db2a08 100644 --- a/build/Factory.js +++ b/build/Factory.js @@ -37,17 +37,190 @@ var Factory = /*#__PURE__*/function () { _classCallCheck(this, Factory); this._neode = neode; + this._objectsById = []; + this._objectsAliases = []; + this._objectsResult = []; } /** - * Hydrate the first record in a result set + * Hydrate all nodes and relations from a result set, return first result * * @param {Object} res Neo4j Result - * @param {String} alias Alias of Node to pluck + * @param {String} alias Alias of Node to pluck first * @return {Node} */ _createClass(Factory, [{ + key: "hydrateResult", + value: function hydrateResult(res, alias) { + var results = this.hydrateResults(res, alias); + + if (results.length > 0) { + return results[0]; + } + + return null; + } + /** + * Hydrate all nodes and relations from a result set, based on schema + * + * @param {Object} res Neo4j Result + * @param {String} alias Alias of Node to pluck first + * @return {Node} + */ + + }, { + key: "hydrateResults", + value: function hydrateResults(res, alias) { + var _this = this; + + this._objectsById = []; + this._objectsAliases = []; + this._objectsResult = []; + res.records.forEach(function (record) { + _this._visitedAliases = []; + + _this.hydrateRecord(record, alias); + + _this.hydrateRecordEagers(record, alias); + }); + return this._objectsResult; + } + /** + * Hydrate nodes and relations from a record result, based on schema + * + * @param {Object} record Neo4j Result Line + * @param {String} alias Alias of Node to pluck first + * @return {Node} + */ + + }, { + key: "hydrateRecord", + value: function hydrateRecord(record, alias) { + var _this2 = this; + + record.keys.forEach(function (key) { + var node = record.get(key); + + if (node !== undefined && node.constructor.name == "Node") { + if (_this2._objectsById[node.identity.toNumber()] !== undefined) { + return; + } + + var entity = _this2.hydrateNode(node); + + _this2._objectsById[node.identity.toNumber()] = entity; + _this2._objectsAliases[node.identity.toNumber()] = key; + + if (key == alias) { + _this2._objectsResult.push(entity); + } + } + }); + } + /** + * Hydrate nodes and relations from a record result, based on schema + * + * @param {Object} record Neo4j Result Line + * @param {String} alias Alias of reference Node + * @return {Node} + */ + + }, { + key: "hydrateRecordEagers", + value: function hydrateRecordEagers(record, alias) { + var _this3 = this; + + record.keys.forEach(function (key) { + var relation = record.get(key); + + if (relation === undefined || relation.constructor.name !== "Relationship") { + return; + } + + if (_this3._objectsById[relation.identity.toNumber()] !== undefined) { + return; + } + + var referenceNode = null; + var otherNode = null; + + if (record.get(alias).identity.toNumber() == relation.start.toNumber()) { + referenceNode = _this3._objectsById[relation.start.toNumber()]; + otherNode = _this3._objectsById[relation.end.toNumber()]; + } else if (record.get(alias).identity.toNumber() == relation.end.toNumber()) { + referenceNode = _this3._objectsById[relation.end.toNumber()]; + otherNode = _this3._objectsById[relation.start.toNumber()]; + } else { + return; + } + + var definition = _this3.getDefinition(null, referenceNode.labels()); + + definition.eager().forEach(function (eager) { + if (relation.type != eager.relationship()) { + return; + } + + if (otherNode.labels().indexOf(eager.target()) == -1) { + return; + } + + var refEager = null; + var name = eager.name(); + + switch (eager.type()) { + case 'node': + referenceNode.setEager(name, otherNode); + break; + + case 'nodes': + refEager = referenceNode.getEager(name); + + if (refEager === undefined) { + refEager = new _Collection["default"](_this3._neode); + referenceNode.setEager(name, refEager); + } + + refEager.add(otherNode); + break; + + case 'relationship': + referenceNode.setEager(name, _this3.hydrateRelationship(eager, relation, referenceNode)); + break; + + case 'relationships': + refEager = referenceNode.getEager(name); + + if (refEager === undefined) { + refEager = new _Collection["default"](_this3._neode); + referenceNode.setEager(name, refEager); + } + + refEager.add(_this3.hydrateRelationship(eager, relation, referenceNode)); + break; + } + }); + _this3._objectsById[relation.identity.toNumber()] = relation; + + _this3._visitedAliases.push(alias); + + var otherAlias = _this3._objectsAliases[otherNode.id()]; + + if (_this3._visitedAliases.indexOf(otherAlias) == -1) { + _this3.hydrateRecordEagers(record, otherAlias); + } + }); + } + /** + * Hydrate the first record in a result set + * + * @param {Object} res Neo4j Result + * @param {String} alias Alias of Node to pluck + * @return {Node} + */ + + }, { key: "hydrateFirst", value: function hydrateFirst(res, alias, definition) { if (!res || !res.records.length) { @@ -68,14 +241,14 @@ var Factory = /*#__PURE__*/function () { }, { key: "hydrate", value: function hydrate(res, alias, definition) { - var _this = this; + var _this4 = this; if (!res) { return false; } var nodes = res.records.map(function (row) { - return _this.hydrateNode(row.get(alias), definition); + return _this4.hydrateNode(row.get(alias), definition); }); return new _Collection["default"](this._neode, nodes); } @@ -88,8 +261,20 @@ var Factory = /*#__PURE__*/function () { }, { key: "getDefinition", - value: function getDefinition(labels) { - return this._neode.models.getByLabels(labels); + value: function getDefinition(definition, labels) { + // Get Definition from + if (!definition) { + definition = this._neode.models.getByLabels(labels); + } else if (typeof definition === 'string') { + definition = this._neode.models.get(definition); + } // Helpful error message if nothing could be found + + + if (!definition) { + throw new Error("No model definition found for labels ".concat(JSON.stringify(labels))); + } + + return definition; } /** * Take a result object and convert it into a Model @@ -102,7 +287,7 @@ var Factory = /*#__PURE__*/function () { }, { key: "hydrateNode", value: function hydrateNode(record, definition) { - var _this2 = this; + var _this5 = this; // Is there no better way to check this?! if (_neo4jDriver["default"].isInt(record.identity) && Array.isArray(record.labels)) { @@ -113,19 +298,9 @@ var Factory = /*#__PURE__*/function () { var identity = record[_EagerUtils.EAGER_ID]; - var labels = record[_EagerUtils.EAGER_LABELS]; // Get Definition from - - if (!definition) { - definition = this.getDefinition(labels); - } else if (typeof definition === 'string') { - definition = this._neode.models.get(definition); - } // Helpful error message if nothing could be found - - - if (!definition) { - throw new Error("No model definition found for labels ".concat(JSON.stringify(labels))); - } // Get Properties + var labels = record[_EagerUtils.EAGER_LABELS]; // Get Definition + definition = this.getDefinition(definition, labels); // Get Properties var properties = new Map(); definition.properties().forEach(function (value, key) { @@ -145,22 +320,22 @@ var Factory = /*#__PURE__*/function () { switch (eager.type()) { case 'node': - node.setEager(name, _this2.hydrateNode(record[name])); + node.setEager(name, _this5.hydrateNode(record[name])); break; case 'nodes': - node.setEager(name, new _Collection["default"](_this2._neode, record[name].map(function (value) { - return _this2.hydrateNode(value); + node.setEager(name, new _Collection["default"](_this5._neode, record[name].map(function (value) { + return _this5.hydrateNode(value); }))); break; case 'relationship': - node.setEager(name, _this2.hydrateRelationship(eager, record[name], node)); + node.setEager(name, _this5.hydrateRelationship(eager, record[name], node)); break; case 'relationships': - node.setEager(name, new _Collection["default"](_this2._neode, record[name].map(function (value) { - return _this2.hydrateRelationship(eager, value, node); + node.setEager(name, new _Collection["default"](_this5._neode, record[name].map(function (value) { + return _this5.hydrateRelationship(eager, value, node); }))); break; } @@ -181,9 +356,7 @@ var Factory = /*#__PURE__*/function () { value: function hydrateRelationship(definition, record, this_node) { // Get Internals var identity = record[_EagerUtils.EAGER_ID]; - var type = record[_EagerUtils.EAGER_TYPE]; // Get Definition from - // const definition = this.getDefinition(labels); - // Get Properties + var type = record[_EagerUtils.EAGER_TYPE]; // Get Properties var properties = new Map(); definition.properties().forEach(function (value, key) { diff --git a/build/Node.js b/build/Node.js index 3147025..fd1afe1 100644 --- a/build/Node.js +++ b/build/Node.js @@ -115,6 +115,18 @@ var Node = /*#__PURE__*/function (_Entity) { return this; } + /** + * Set an eager value on the fly + * + * @param {String} key + * @return {Mixed} + */ + + }, { + key: "getEager", + value: function getEager(key) { + return this._eager.get(key); + } /** * Delete this node from the Graph * diff --git a/build/index.js b/build/index.js index 6e9b902..cb292b2 100644 --- a/build/index.js +++ b/build/index.js @@ -115,8 +115,8 @@ var Neode = /*#__PURE__*/function () { } /** * Set the default database for all future connections - * - * @param {String} database + * + * @param {String} database */ }, { @@ -518,6 +518,32 @@ var Neode = /*#__PURE__*/function () { value: function first(label, key, value) { return this.models.get(label).first(key, value); } + /** + * Hydrate a set of nodes and return a Collection + * + * @param {Object} res Neo4j result set + * @param {String} alias Alias of node to pluck + * @return {Collection} + */ + + }, { + key: "hydrateResult", + value: function hydrateResult(res, alias) { + return this.factory.hydrateResult(res, alias); + } + /** + * Hydrate a set of nodes and return a Collection + * + * @param {Object} res Neo4j result set + * @param {String} alias Alias of node to pluck + * @return {Collection} + */ + + }, { + key: "hydrateResults", + value: function hydrateResults(res, alias) { + return this.factory.hydrateResults(res, alias); + } /** * Hydrate a set of nodes and return a Collection * diff --git a/src/Collection.js b/src/Collection.js index 0fe66aa..0294a71 100644 --- a/src/Collection.js +++ b/src/Collection.js @@ -47,6 +47,18 @@ export default class Collection { return this._values[0]; } + /** + * Add value in collection + * + * @param {mixed} value + * @return {Collection} + */ + add(value) { + this._values.push(value); + + return this; + } + /** * Map a function to all values * diff --git a/src/Factory.js b/src/Factory.js index bf0e537..2e8bf64 100644 --- a/src/Factory.js +++ b/src/Factory.js @@ -15,6 +15,159 @@ export default class Factory { */ constructor(neode) { this._neode = neode; + this._objectsById = []; + this._objectsAliases = []; + this._objectsResult = []; + } + + /** + * Hydrate all nodes and relations from a result set, return first result + * + * @param {Object} res Neo4j Result + * @param {String} alias Alias of Node to pluck first + * @return {Node} + */ + hydrateResult(res, alias) { + const results = this.hydrateResults(res, alias); + + if (results.length > 0) { + return results[0]; + } + + return null; + } + + /** + * Hydrate all nodes and relations from a result set, based on schema + * + * @param {Object} res Neo4j Result + * @param {String} alias Alias of Node to pluck first + * @return {Node} + */ + hydrateResults(res, alias) { + this._objectsById = []; + this._objectsAliases = []; + this._objectsResult = []; + + res.records.forEach((record) => { + this._visitedAliases = []; + this.hydrateRecord(record, alias); + this.hydrateRecordEagers(record, alias); + }); + + return this._objectsResult; + } + + /** + * Hydrate nodes and relations from a record result, based on schema + * + * @param {Object} record Neo4j Result Line + * @param {String} alias Alias of Node to pluck first + * @return {Node} + */ + hydrateRecord(record, alias) { + record.keys.forEach((key) => { + const node = record.get(key); + + if (node !== undefined && (node.constructor.name == "Node")) { + if (this._objectsById[node.identity.toNumber()] !== undefined) { + return; + } + + const entity = this.hydrateNode(node); + this._objectsById[node.identity.toNumber()] = entity; + this._objectsAliases[node.identity.toNumber()] = key; + + if (key == alias) { + this._objectsResult.push(entity); + } + } + }); + } + + /** + * Hydrate nodes and relations from a record result, based on schema + * + * @param {Object} record Neo4j Result Line + * @param {String} alias Alias of reference Node + * @return {Node} + */ + hydrateRecordEagers(record, alias) { + record.keys.forEach((key) => { + const relation = record.get(key); + + if (relation === undefined || (relation.constructor.name !== "Relationship")) { + return; + } + + if (this._objectsById[relation.identity.toNumber()] !== undefined) { + return; + } + + let referenceNode = null; + let otherNode = null; + if (record.get(alias).identity.toNumber() == relation.start.toNumber()) { + referenceNode = this._objectsById[relation.start.toNumber()]; + otherNode = this._objectsById[relation.end.toNumber()]; + } else if (record.get(alias).identity.toNumber() == relation.end.toNumber()) { + referenceNode = this._objectsById[relation.end.toNumber()]; + otherNode = this._objectsById[relation.start.toNumber()]; + } else { + return; + } + + const definition = this.getDefinition(null, referenceNode.labels()) + definition.eager().forEach(eager => { + if (relation.type != eager.relationship()) { + return; + } + + if (otherNode.labels().indexOf(eager.target()) == -1) { + return; + } + + let refEager = null; + const name = eager.name(); + + switch (eager.type()) { + case 'node': + referenceNode.setEager(name, otherNode); + break; + + case 'nodes': + refEager = referenceNode.getEager(name); + if (refEager === undefined) { + refEager = new Collection(this._neode); + referenceNode.setEager(name, refEager); + } + + refEager.add(otherNode); + break; + + case 'relationship': + referenceNode.setEager(name, this.hydrateRelationship(eager, relation, referenceNode)); + break; + + case 'relationships': + refEager = referenceNode.getEager(name); + if (refEager === undefined) { + refEager = new Collection(this._neode); + referenceNode.setEager(name, refEager); + } + + refEager.add(this.hydrateRelationship(eager, relation, referenceNode)); + break; + } + }); + + this._objectsById[relation.identity.toNumber()] = relation; + this._visitedAliases.push(alias); + + const otherAlias = this._objectsAliases[otherNode.id()]; + if (this._visitedAliases.indexOf(otherAlias) == -1) { + this.hydrateRecordEagers(record, otherAlias); + } + }); } /** @@ -57,8 +210,21 @@ export default class Factory { * @param {Array} labels * @return {Model} */ - getDefinition(labels) { - return this._neode.models.getByLabels(labels); + getDefinition(definition, labels) { + // Get Definition from + if ( !definition ) { + definition = this._neode.models.getByLabels(labels); + } + else if ( typeof definition === 'string' ) { + definition = this._neode.models.get(definition); + } + + // Helpful error message if nothing could be found + if ( !definition ) { + throw new Error(`No model definition found for labels ${ JSON.stringify(labels) }`); + } + + return definition; } /** @@ -81,18 +247,8 @@ export default class Factory { const identity = record[ EAGER_ID ]; const labels = record[ EAGER_LABELS ]; - // Get Definition from - if ( !definition ) { - definition = this.getDefinition(labels); - } - else if ( typeof definition === 'string' ) { - definition = this._neode.models.get(definition); - } - - // Helpful error message if nothing could be found - if ( !definition ) { - throw new Error(`No model definition found for labels ${ JSON.stringify(labels) }`); - } + // Get Definition + definition = this.getDefinition(definition, labels); // Get Properties const properties = new Map; @@ -149,9 +305,6 @@ export default class Factory { const identity = record[ EAGER_ID ]; const type = record[ EAGER_TYPE ]; - // Get Definition from - // const definition = this.getDefinition(labels); - // Get Properties const properties = new Map; @@ -174,4 +327,4 @@ export default class Factory { return new Relationship(this._neode, definition, identity, type, properties, start_node, end_node); } -} \ No newline at end of file +} diff --git a/src/Node.js b/src/Node.js index 22b5e37..17b4582 100644 --- a/src/Node.js +++ b/src/Node.js @@ -66,6 +66,16 @@ export default class Node extends Entity { return this; } + /** + * Set an eager value on the fly + * + * @param {String} key + * @return {Mixed} + */ + getEager(key) { + return this._eager.get(key); + } + /** * Delete this node from the Graph * @@ -201,4 +211,4 @@ export default class Node extends Entity { }); } -} \ No newline at end of file +} diff --git a/src/index.js b/src/index.js index 9ded7ff..63fae4a 100644 --- a/src/index.js +++ b/src/index.js @@ -125,8 +125,8 @@ export default class Neode { /** * Set the default database for all future connections - * - * @param {String} database + * + * @param {String} database */ setDatabase(database) { this.database = database; @@ -483,6 +483,28 @@ export default class Neode { return this.models.get(label).first(key, value); } + /** + * Hydrate a set of nodes and return a Collection + * + * @param {Object} res Neo4j result set + * @param {String} alias Alias of node to pluck + * @return {Collection} + */ + hydrateResult(res, alias) { + return this.factory.hydrateResult(res, alias); + } + + /** + * Hydrate a set of nodes and return a Collection + * + * @param {Object} res Neo4j result set + * @param {String} alias Alias of node to pluck + * @return {Collection} + */ + hydrateResults(res, alias) { + return this.factory.hydrateResults(res, alias); + } + /** * Hydrate a set of nodes and return a Collection *