├── .gitignore ├── LICENSE ├── README.md ├── index.js ├── package.json └── test └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /node_modules/ 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 OSLabs Beta 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mogwai.js 2 | An OGM for the Gremlin traversal language 3 | 4 | ## Getting Started 5 | Install MogwaiJS as an npm package 6 | ``` 7 | npm i mogwaijs 8 | ``` 9 | 10 | ## Set up a Gremlin client & import Vertex and Edge models 11 | ``` 12 | const mogwai, { Vertex, Edge } = require('mogwaijs'); 13 | mogwai.remoteConnect(DB_NAME, COLLECTION_NAME, PRIMARY_KEY, DB_ENDPOINT); 14 | ``` 15 | or 16 | ``` 17 | mogwai.localConnect(USERNAME, PASSWORD, ENDPOINT); 18 | ``` 19 | 20 | ## Create a vertex/edge model 21 | ``` 22 | const User = new Vertex('User', { 23 | name: String, 24 | age: Number, 25 | }); 26 | ``` 27 | ``` 28 | const IsFriendsWith = new Edge('isFriendsWith', { 29 | since: Number, 30 | }); 31 | ``` 32 | 33 | ## Adding vertices and edges 34 | ``` 35 | User.createVertex({ 36 | name: 'Sam', 37 | age: 22, 38 | }); 39 | 40 | User.createVertex({ 41 | name: 'Cassandra', 42 | age: 24, 43 | }); 44 | 45 | isFriendsWith.createEdge( 46 | {name: 'Cassandra'}, 47 | {name: 'Sam'}, 48 | {since: 2018} 49 | ); 50 | ``` 51 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const Gremlin = require('gremlin'); 2 | 3 | const mogwai = { 4 | client: undefined, 5 | Vertex: VertexModel, 6 | Edge: EdgeModel, 7 | remoteConnect: remoteConnect, 8 | localConnect: localConnect, 9 | } 10 | 11 | /** Remote Connect 12 | * @param {String} db - the name of the database. 13 | * @param {String} coll - the name of the collection in the database. 14 | * @param {String} primaryKey - The API key provided by the remote server 15 | * @param {String} uri - Database's endpoint. 16 | */ 17 | 18 | function remoteConnect(db, coll, primaryKey, uri){ 19 | const authenticator = new Gremlin.driver.auth.PlainTextSaslAuthenticator(`/dbs/${db}/colls/${coll}`, primaryKey) 20 | 21 | const client = new Gremlin.driver.Client( 22 | uri, 23 | { 24 | authenticator, 25 | traversalsource : "g", 26 | rejectUnauthorized : true, 27 | mimeType : "application/vnd.gremlin-v2.0+json" 28 | } 29 | ); 30 | mogwai.client = client; 31 | return client; 32 | } 33 | 34 | /** Local Connect 35 | * @param {String} username - Database username 36 | * @param {String} password - Database password 37 | * @param {String} uri - The database's endpoint 38 | */ 39 | 40 | function localConnect(username, password, uri){ 41 | const authenticator = new Gremlin.driver.auth.PlainTextSaslAuthenticator(`'${username}', '${password}'`) 42 | 43 | const client = new Gremlin.driver.Client( 44 | uri, 45 | { 46 | authenticator, 47 | traversalsource : "g", 48 | rejectUnauthorized : true, 49 | mimeType : "application/vnd.gremlin-v2.0+json" 50 | } 51 | ); 52 | mogwai.client = client; 53 | return client; 54 | } 55 | 56 | /** Vertex Model 57 | * @param {String} label - the vertex 'type'; required; 58 | * @param {Object} schema - an object containing property names as keys; values are the constructor for the type of data contained at this key 59 | */ 60 | 61 | function VertexModel(label, schema = {}) { 62 | if (!label || typeof label !== 'string') throw new Error (`Error: Label must be a string!`) 63 | this.label = label; 64 | Object.entries(schema).forEach((keyValuePair) => { 65 | this[keyValuePair[0]] = keyValuePair[1]; 66 | }); 67 | } 68 | 69 | /** Create method 70 | * @param {Object} schema - an object containing property names as keys; 71 | * values are the constructor for the type of data contained at this key 72 | */ 73 | 74 | VertexModel.prototype.createVertex = function create(props = {}) { 75 | if (typeof props !== 'object') throw new Error (`Error: Schema must be an object!`); 76 | 77 | let qString = `g.addV('${this.label}')`; 78 | qString += this.addPropsFromObj(props); 79 | 80 | return mogwai.client.submit(qString, props); 81 | } 82 | 83 | /** 84 | * @param {Object} props - an object containing property names as keys and strings as values. 85 | * The values should correlate to the key/value pairs of a particular node. 86 | * 87 | */ 88 | VertexModel.prototype.findVertexByProps = function findVertexByProps(props) { 89 | if (typeof props !== 'object') throw new Error (`Error: Props must be an object!`); 90 | 91 | let qString = `g.V()`; 92 | qString += this.hasPropsFromObj(props); 93 | 94 | return mogwai.client.submit(qString, props); 95 | } 96 | 97 | /** 98 | * @param {Object} node - a reference to the original node, 99 | * used to find the corresponding Vertex and add the k/v pairs in PROPS to it. 100 | * @param {Object} props - an object containing property names as keys and strings as values. 101 | * The values should correlate to the key/value pairs of a particular node. 102 | * 103 | */ 104 | VertexModel.prototype.addPropsToVertex = async function addPropsToVertex(findProps, addProps) { 105 | if (typeof findProps !== 'object') throw new Error (`Error: findProps must be an object!`); 106 | if (typeof addProps !== 'object') throw new Error (`Error: addProps must be an object!`); 107 | 108 | let vertex = await this.findVertexByProps(findProps); 109 | if (vertex.length === 0) return new Promise((resolve, reject)=>{ 110 | return reject(new Error(`Error: vertex not found!`)); 111 | }); 112 | vertex = vertex['_items'][0].id; 113 | 114 | let qString = `g.V(id)`; 115 | qString += this.addPropsFromObj(addProps); 116 | 117 | return mogwai.client.submit(qString, {...addProps, id: vertex}); 118 | } 119 | 120 | /** 121 | * @param {Object} node - a reference to the original node, 122 | * used to find the corresponding Vertex and then remove it. 123 | */ 124 | 125 | VertexModel.prototype.deleteVertex = async function deleteVertex(props) { 126 | if (typeof props !== 'object') throw new Error (`Error: Props must be an object!`); 127 | 128 | let vertex = await this.findVertexByProps(props); 129 | if (vertex.length === 0) return new Promise((resolve, reject)=>{ 130 | return reject(new Error(`Error: vertex not found!`)); 131 | }); 132 | vertex = vertex['_items'][0].id; 133 | 134 | return mogwai.client.submit(`g.V(id).drop()`, {id:vertex}); 135 | } 136 | 137 | /** 138 | * @param {Object\|String} relProps - A string containing the name of the relationship or an 139 | * object containing a key 'label' containing the name of the relationship and other properties 140 | * of the relationship 141 | * @param {Object} [targetProps] - An object containing properties of the target vertices 142 | * for matching 143 | * 144 | */ 145 | 146 | /* 147 | VertexModel.prototype.match = function match(relProps = '', targetProps = {}) { 148 | let qString = `g.V().match(__.as('source')`; 149 | let qObj = {}; 150 | // validate the relationship argument 151 | if (relProps === '') throw new Error('Relationship label cannot be undefined'); 152 | if (typeof relProps === 'string') { 153 | qObj.label = relProps; 154 | qString += `.out(label).as('target')`; 155 | // full string so far would be 'g.V().match(__.as('source').out(label).as('target')' 156 | } else if (typeof relProps === 'object' && !Array.isArray(relProps)) { 157 | // if the edge has properties, then we select edges on those properties 158 | qObj = {...qObj, ...relProps} 159 | qString += `.outE(label)` 160 | 161 | if (!Object.keys(relProps).includes('label')) throw new Error('Relationship label cannot be undefined'); 162 | 163 | qString += this.hasPropsFromObj(relProps, false); 164 | qString += `.inV().as('target')`; 165 | // example: 166 | // g.V().match(__.as('source).outE(label).has('prop', prop).inV().as('target') 167 | } else throw new Error('First argument must be string or object'); 168 | 169 | // validate the target props object 170 | if (Object.keys(targetProps).length > 0) { 171 | qObj = {...qObj, ...targetProps} 172 | qString += `, __.as('target')` 173 | qString += this.hasPropsFromObj(targetProps, false); 174 | // example: 175 | // ', __.as('target').has('prop', prop)' 176 | } 177 | qString += `).select('source', 'target')`; 178 | 179 | return mogwai.client.submit(qString, qObj); 180 | } 181 | */ 182 | 183 | VertexModel.prototype.match = function match(relProps, targetProps) { 184 | let qString = `g.V()`; 185 | let qObj = {}; 186 | 187 | if (typeof targetProps !== 'object') { 188 | return Promise.reject(new Error('Target properties must be an object')); 189 | } 190 | 191 | qString += this.hasPropsFromObj(targetProps); 192 | qString += `.as("target")`; 193 | Object.assign(qObj, targetProps); 194 | 195 | if(typeof relProps === 'string') { 196 | qObj.label = relProps; 197 | qString += '.inE(label)'; 198 | } else if (typeof relProps === 'object') { 199 | if(!relProps.label) return Promise.reject(new Error('Relationship must have label')); 200 | qString += this.hasPropsFromObj(relProps); 201 | } 202 | 203 | qString += '.outV().as("source").select("source", "target")'; 204 | 205 | return mogwai.client.submit(qString, qObj); 206 | } 207 | 208 | VertexModel.prototype.addPropsFromObj = (propsObj, checkModel = true) => { 209 | let qString = ''; 210 | 211 | const typeObj = { 212 | 'string' : String, 213 | 'number' : Number, 214 | 'boolean' : Boolean, 215 | 'undefined' : undefined, 216 | }; 217 | 218 | Object.keys(propsObj).forEach((key) => { 219 | if (key !== 'label') { 220 | qString += `.property('${key}', ${key})`; 221 | } 222 | if (checkModel) { 223 | if (!this[key]){ 224 | this[key] = typeObj[typeof propsObj[key]]; 225 | } 226 | } 227 | }); 228 | 229 | return qString; 230 | } 231 | 232 | VertexModel.prototype.hasPropsFromObj = (propsObj, checkModel = true) => { 233 | let qString = ''; 234 | 235 | const typeObj = { 236 | 'string' : String, 237 | 'number' : Number, 238 | 'boolean' : Boolean, 239 | 'undefined' : undefined, 240 | }; 241 | 242 | Object.keys(propsObj).forEach((key) => { 243 | if (key !== 'label') { 244 | qString += `.has('${key}', ${key})`; 245 | } 246 | if (checkModel) { 247 | if (!this[key]){ 248 | this[key] = typeObj[typeof propsObj[key]]; 249 | } 250 | } 251 | }); 252 | return qString; 253 | } 254 | 255 | /** Edge Model 256 | * @param {String} label - "the edge type: required;" 257 | * @param {Object} props - "an object containing property names as keys; values are the constructor for the type of data contained at this key" 258 | */ 259 | 260 | function EdgeModel(label, props = {}) { 261 | if (typeof label === 'string') throw new Error('Edge label must be a string.'); 262 | this.label = label; 263 | 264 | Object.entries(props).forEach(keyValuePair => { 265 | this[keyValuePair[0]] = keyValuePair[1]; 266 | }); 267 | } 268 | 269 | EdgeModel.prototype.findVertex = function findVertex(props){ 270 | let qString = `g.V()`; 271 | qString += this.hasPropsFromObj(props); 272 | return mogwai.client.submit(qString, props); 273 | } 274 | 275 | EdgeModel.prototype.delete = async function deleteEdge(fromNode, toNode) { 276 | let qString = ''; 277 | if (!(fromNode && toNode)) { 278 | qString = `g.E('${this.label}').drop()`; 279 | } 280 | else { 281 | if (typeof fromNode !== 'object') throw new Error (`Error: fromNode must be a string!`); 282 | if (typeof toNode !== 'object') throw new Error (`Error: toNode must be a string!`); 283 | 284 | let fromV = await this.findVertex(fromNode); 285 | let toV = await this.findVertex(toNode); 286 | 287 | if (fromV.length === 0) return new Promise((resolve, reject)=> { 288 | return reject(new Error(`Error: From-vertex not found!`)); 289 | }); 290 | fromV = fromV['_items'][0].id; 291 | 292 | if (toV.length === 0) return new Promise((resolve, reject)=> { 293 | return reject(new Error(`Error: To-vertex not found!`)); 294 | }); 295 | toV = toV['_items'][0].id; 296 | 297 | qString += `g.V(from).bothE().where(otherV().is(to)).drop()`; 298 | return mogwai.client.submit(qString, {from: fromV, to: toV}); 299 | } 300 | return mogwai.client.submit(qString, {}); 301 | } 302 | 303 | /** 304 | * @param {Object} fromNode - the name of the source node in the relationship which points to the target node 305 | * @param {Object} toNode - the name of the target node in the relationship; this node is pointed to 306 | * @param {Object} props - an object containing property names as keys; values are the constructor for the type of data contained at this key 307 | * @return {Object} promise - the object returned from the client.submit method. 308 | */ 309 | EdgeModel.prototype.createEdge = async function createEdge(fromNode, toNode, props) { 310 | let fromV = await this.findVertex(fromNode); 311 | if (fromV.length === 0) return new Promise((resolve, reject)=>{ 312 | return reject(new Error(`Error: From-vertex not found!`)) 313 | }); 314 | fromV = fromV['_items'][0].id; 315 | 316 | let toV = await this.findVertex(toNode); 317 | if (toV.length === 0) return new Promise((resolve, reject)=>{ 318 | return reject(new Error(`Error: To-vertex not found!`)) 319 | }); 320 | toV = toV['_items'][0].id; 321 | 322 | let qString = `g.V(from).addE('${this.label}').to(g.V(to))`; 323 | qString += this.addPropsFromObj(props); 324 | 325 | return mogwai.client.submit(qString, {from: fromV, to: toV, ...props}); 326 | }; 327 | 328 | /** 329 | * @param {String} fromNode - the name of the source node in the relationship which points to the target node 330 | * @param {String} toNode - the name of the target node in the relationship; this node is pointed to 331 | * @param {Object} props - an object containing property names as keys; values are the constructor for the type of data contained at this key 332 | * @return {Object} promise - the object returned from the client.submit method. 333 | */ 334 | EdgeModel.prototype.addPropsToEdge = async function addPropsToEdge(fromNode, toNode, props) { 335 | if (typeof fromNode !== 'object') throw new Error (`Error: fromNode must be a string!`); 336 | if (typeof toNode !== 'object') throw new Error (`Error: toNode must be a string!`); 337 | if (typeof props !== 'object') throw new Error (`Error: props must be an object!`); 338 | 339 | let qString = ``; 340 | let fromV = await this.findVertex(fromNode); 341 | let toV = await this.findVertex(toNode); 342 | 343 | if(!(fromNode && toNode)){ 344 | qString = `g.E(id)` + this.addPropsFromObj(props); 345 | } else { 346 | if (fromV.length === 0) return new Promise((resolve, reject)=>{ 347 | return reject(new Error(`Error: From-vertex not found!`)) 348 | }) 349 | fromV = fromV['_items'][0].id; 350 | 351 | if (toV.length === 0) return new Promise((resolve, reject)=>{ 352 | return reject(new Error(`Error: To-vertex not found!`)) 353 | }) 354 | toV = toV['_items'][0].id; 355 | 356 | qString += `g.V(from).outE('${this.label}').as('a').inV().has('id', to).select('a')` + this.addPropsFromObj(props); 357 | } 358 | 359 | return mogwai.client.submit(qString, {from: fromV, to: toV, ...props}); 360 | } 361 | 362 | EdgeModel.prototype.addPropsFromObj = (propsObj, checkModel = true) => { 363 | let qString = ''; 364 | 365 | const typeObj = { 366 | 'string' : String, 367 | 'number' : Number, 368 | 'boolean' : Boolean, 369 | 'undefined' : undefined, 370 | }; 371 | 372 | Object.keys(propsObj).forEach((key) => { 373 | if (key !== 'label') { 374 | qString += `.property('${key}', ${key})`; 375 | } 376 | if (checkModel) { 377 | if (!this[key]){ 378 | this[key] = typeObj[typeof propsObj[key]]; 379 | } 380 | } 381 | }); 382 | 383 | return qString; 384 | } 385 | 386 | EdgeModel.prototype.hasPropsFromObj = (propsObj, checkModel = true) => { 387 | let qString = ''; 388 | 389 | const typeObj = { 390 | 'string' : String, 391 | 'number' : Number, 392 | 'boolean' : Boolean, 393 | 'undefined' : undefined, 394 | }; 395 | 396 | Object.keys(propsObj).forEach((key) => { 397 | if (key !== 'label') { 398 | qString += `.has('${key}', ${key})`; 399 | } 400 | if (checkModel) { 401 | if (!this[key]){ 402 | this[key] = typeObj[typeof propsObj[key]]; 403 | } 404 | } 405 | }); 406 | 407 | return qString; 408 | } 409 | 410 | module.exports = mogwai; 411 | module.exports.loadMsg = () => { 412 | console.log('Welcome to MogwaiJS alpha!'); 413 | } 414 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mogwaijs", 3 | "version": "0.0.1", 4 | "description": "A declarative OGM wrapper for Gremlin traversal language.", 5 | "main": "index.js", 6 | "dependencies": { 7 | "async": "^2.6.0", 8 | "gremlin": "^3.3.4", 9 | "mocha": "^6.2.0" 10 | }, 11 | "scripts": { 12 | "test": "mocha" 13 | }, 14 | "keywords": [ 15 | "graph", 16 | "database", 17 | "gremlin", 18 | "javascript" 19 | ], 20 | "author": "mogwaijs", 21 | "license": "MIT", 22 | "repository": { 23 | "type": "git", 24 | "url": "https://github.com/oslabs-beta/mogwaijs.git" 25 | }, 26 | "contributors": ["EricStallings", "ndominguez23", "houta483", "Tburrington"] 27 | } 28 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const vertex = require('../functions/vertexModel') 3 | const EdgeModel = require('../functions/edgeModel') 4 | const crud = require('../functions/crud') 5 | 6 | 7 | // add instance of promise check to each and every function 8 | // cannot output a string if it outputs a promise 9 | // figure out to how check what output would be at a break point 10 | // figure out what is returned when a test is completed 11 | // add test for match function 12 | 13 | // VertexModel Tests 14 | describe('VertexTests', function() { 15 | describe('VertexModel', () => { 16 | it('should generate a gremlin string without modifying the model', function() { 17 | const modelBeforeRunningFunc = vertex.VertexModel(`test`); 18 | let copy = Object.assign({}, modelBeforeRunningFunc); 19 | modelBeforeRunningFunc.create({name: 'Tanner'}) 20 | assert.equal(modelBeforeRunningFunc, copy) 21 | }) 22 | it('should output an object', () => { 23 | let testModel = vertex.VertexModel('test') 24 | assert.equal(typeof testModel, "object") 25 | }) 26 | it('should return a promise', () => { 27 | let exampleCreateModel = vertex.createModel(); 28 | assert.equal(exampleCreateModel instanceof Promise, true) 29 | }) 30 | }) 31 | describe('createVertex', () => { 32 | it('should return a string', function() { 33 | let testGremlinSting = vertex.VertexModel.create('test') 34 | assert.equal(typeof testGremlinSting, "string") 35 | }) 36 | it('should return a promise', () => { 37 | let examplecreateVertex = vertex.createVertex(); 38 | assert.equal(examplecreateVertex instanceof Promise, true) 39 | }) 40 | }) 41 | describe('findVertexByProps', () => { 42 | it('should return a promise', () => { 43 | let exampleFindVertedByProps = vertex.findVertexByProps(); 44 | assert.equal(exampleFindVertedByProps instanceof Promise, true) 45 | }) 46 | }) 47 | describe('addPropsToVertex', () => { 48 | it('should return a promise', () => { 49 | let exampleAddPropsToVertex = vertex.addPropsToVertex(); 50 | assert.equal(exampleAddPropsToVertex instanceof Promise, true) 51 | }) 52 | }) 53 | describe('deleteVertex', () => { 54 | it('should return a promise', () => { 55 | let exampleDeleteVertex = vertex.deleteVertex(); 56 | assert.equal(exampleDeleteVertex instanceof Promise, true) 57 | }) 58 | }) 59 | describe('match', () => { 60 | it('should return a promise', () => { 61 | let exampleMatch = vertex.match(); 62 | assert.equal(exampleMatch instanceof Promise, true) 63 | }) 64 | }) 65 | }) 66 | 67 | // EdgeModel Tests 68 | 69 | describe('EdgeModel', function() { 70 | describe('EdgeModel', () => { 71 | it('should output an object', () => { 72 | let testModel = EdgeModel.EdgeModel('test') 73 | assert.equal(typeof testModel, "object") 74 | }) 75 | it('should return a promise', () => { 76 | let exampleEdgeModel = vertex.EdgeModel(); 77 | assert.equal(exampleEdgeModel instanceof Promise, true) 78 | }) 79 | }) 80 | describe('createEdge', () => { 81 | it('should output a string',function () { 82 | let testCreateEdge = EdgeModel.createEdge() 83 | assert.equal(typeof testCreateEdge, 'string') 84 | }) 85 | it('should return a promise', () => { 86 | let exampleCreateEdge = EdgeModel.createEdge(); 87 | assert.equal(exampleCreateEdge instanceof Promise, true) 88 | }) 89 | }) 90 | describe('addPropsToEdge', () => { 91 | it('should output a string',function () { 92 | let testAddPropsToEdge = EdgeModel.addPropsToEdge() 93 | assert.equal(typeof testAddPropsToEdge, 'string') 94 | }) 95 | it('should return a promise', () => { 96 | let exampleaddPropsToEdge = EdgeModel.addPropsToEdge(); 97 | assert.equal(exampleaddPropsToEdge instanceof Promise, true) 98 | }) 99 | }) 100 | }) --------------------------------------------------------------------------------