ostrio:neo4jreactivity

v0.8.5Published 9 years ago

This package has not had recent updates. Please investigate it's current state before committing to using it in your project.

Join the chat at https://gitter.im/VeliovGroup/ostrio-neo4jdriver

Neo4j Reactivity Driver

Neo4j DB reactive layer for Meteor

Neo4jreactivity creates reactive and isomorphic layer between Neo4j and your Meteor based application. All write requests is synchronized between all clients. Please see this package on atmospherejs.com.

Example Application

The basic example is build on top of --example leaderboard - the Meteor's Neo4j-based Leaderboard App

Install the driver

meteor add ostrio:neo4jreactivity

Several Notes

TTL

If you have many different queries to Neo4j database on production environment, you will probably want to avoid Neo4jCache collection overwhelming. Make build-in JavaScript-based TTL utility is useless, so we are suggest to take a look on TTL indexes and expire data tutorial. Neo4jCache records has created {Date} field, so in our case it will be something like:

1/* run this at mongodb shell */
2db.Neo4jCache.createIndex({ 
3  created: 1 
4},{ 
5  expireAfterSeconds: 3600 * 24 /* 3600 * 24 = 1 day */
6}); 
The way to work with queries

In documentation below you will find two different approaches how to send queries and retrieve data to/from Neo4j database. It is methods/calls and collection/publish/subscription.

It is okay to combine them both. Most advanced way is to use methods/calls, - using this approach allows to you send and retrieve data directly to/from Neo4j database, our driver will only hold reactive updates on all clients.

But at the same moment collection/publish/subscription approach has latency compensation and let to work with data and requests as with minimongo instance, but limited to simple insert/update/remove operations on data sets, so you can't set relations, indexes, predicates and other Cypher query options (Labels and Properties is well supported. For Labels use __labels property as {__labels: ":First:Second:Third"}).

API

Isomorphic

  • Meteor.neo4j.allowClientQuery
  • allowClientQuery {Boolean} - Allow/Deny Cypher queries execution on the client side
  • Meteor.neo4j.connectionURL = 'http://user:pass@localhost:7474';
  • Set connection URL, uncluding login and password to Neo4j DataBase
  • Example
  • Meteor.neo4j.rules.write - Array of strings with Cypher write operators
  • Meteor.neo4j.rules.read - Array of strings with Cypher read operators
  • Meteor.neo4j.set.allow([rules]) - Set allowed Cypher operators for client side
  • rules {[String]} - Array of Cyphper query operators Strings
  • Meteor.neo4j.set.deny([rules]) - Set denied Cypher operators for client side
  • rules {[String]} - Array of Cyphper query operators Strings
  • For example to deny all write queries, use: Meteor.neo4j.set.deny(Meteor.neo4j.rules.write)
  • Example
  • Meteor.neo4j.query(query, opts, callback) - Returns reactive {Object} with get() method.
  • query {String} - Name of publish function. Please use same name in collection/publish/subscription
  • opts {Object} - A map of parameters for the Cypher query.
  • callback {Function} - Callback which runs after each subscription
    • error {Object|null} - Error of Neo4j Cypher query execution or null
    • data {Object|null} - Data or null from Neo4j Cypher query execution
  • Example
  • Meteor.neo4j.collection(name)
  • name {String} - Name of collection.
1users = Meteor.neo4j.collection 'Users'
  • This method returns collection with next methods:
    • publish(name, func, [onSubscribe]) [Server] - Publish dataset to client.
      • name {String} - Publish/Subscription name
      • func {Function} - Function which returns Cypher query
      • onSubscibe {Function} - Callback function called right after data is published
      • Example
    1users.publish 'currentUser', () ->
    2  return 'MATCH (user:User {_id: {_id}}) RETURN user;'
    • subscribe(name, [opts], link) [Client] - Subscribe on dataset.
      • name {String} - Publish/Subscription name
      • opts {Object|null} - A map of parameters for the Cypher query
      • link {String} - Sub object name, to link as MobgoDB row(s). See example below:
      • Example
    1users.subscribe 'currentUser', _id: Meteor.userId(), 'user'
    • find([selector], [options]) - Example. Use to search thru returned data from Neo4j
      • fetch() - Use to fetch Cursor data
    • findOne([selector], [options])
    • insert(doc, [callback]) - Example
    • update(selector, modifier, [options], [callback]) - Example
    • upsert(selector, modifier, [options], [callback])
    • remove(selector, [callback]) - Example
    • Note: All selectors and doc support __labels property, - use it to set Cypher label on insert or searching data, see this example
    • Collection() example

Server

  • Meteor.neo4j.methods(object) - Create server Cypher queries
  • object {Object} - Object of method functions, which returns Cypher query string
  • Example
  • Meteor.neo4j.publish(collectionName, name, func, [onSubscribe])
  • collectionName {String} - Collection name of method function
  • name {String} - Name of publish function. Please use same name in publish/subscription
  • func {Function} - Function wich returns Cypher query string
  • onSubscribe {Function} - Callback which runs after each subscription
  • Example

Client

  • Meteor.neo4j.call(name, [[opts], [link].. ], callback) - Call server Neo4j method

Call for method registered via Meteor.neo4j.methods.

  • name {String} - Name of method function
  • opts {Object} - A map of parameters for the Cypher query.
  • callback {Function} - Returns error and data arguments.
  • Returns {Object} - With cursor and reactive get() method
  • Example
  • Meteor.neo4j.subscribe(collectionName, name, [opts], [link])
  • collectionName {String} - Collection name of method function
  • name {String} - Name of subscribe function. Please use same name in publish/subscription
  • opts {Object} - A map of parameters for the Cypher query.
  • link {String} - Sub object name, to link as MobgoDB row(s)
  • Example
  • Note: Wrap Meteor.neo4j.subscribe() into Tracker.autorun()

Predefined Cypher Operators:

  • Allow:
  • RETURN
  • MATCH
  • SKIP
  • LIMIT
  • OPTIONAL
  • ORDER BY
  • WITH
  • AS
  • WHERE
  • CONSTRAINT
  • UNWIND
  • DISTINCT
  • CASE
  • WHEN
  • THEN
  • ELSE
  • END
  • CREATE
  • UNIQUE
  • MERGE
  • SET
  • DELETE
  • REMOVE
  • FOREACH
  • ON
  • INDEX
  • USING
  • DROP
  • Deny: None

  • Write:

  • CREATE
  • SET
  • DELETE
  • REMOVE
  • INDEX
  • DROP
  • MERGE

About reactive data and queries

Note: This is very important to use same node's link names for same node types in all Cypher queries, cause the way Neo4jReactivity subscribes on data. For example if we would like to retrieve Users from Neo4j and update them later, so data will be updated reactively:

MATCH (usr {type: 'User'}) RETURN usr

# To update use only `usr` alias for node: 
MATCH (usr {type: 'User', perms: 'guest'}) SET usr.something = 2

Of course Neo4jReactivity knows about Neo4j labels and use them for subscription too. With labels you may use different node's name aliases, but it's not recommended:

# To retrieve
MATCH (a:User) RETURN a

# To update: 
MATCH (b:User {perms: 'guest'}) SET b.something = 2

It will work, but much better if you will use:

# To retrieve
MATCH (user:User) RETURN user

# To update: 
MATCH (user:User {perms: 'guest'}) SET user.something = 2

Usage examples:

As collection and publish/subscribe

Create collection [Isomorphic]
1friends = Meteor.neo4j.collection 'friends'
Publish data [Server]
1friends.publish 'allFriends', () ->
2  return "MATCH (user {_id: {userId}})-[:FriendOf]->(friends) RETURN friends"
Subscribe on this data [Client]
1friends.subscribe 'allFriends', {userId: Meteor.userId()}, 'friends'
Template helper [Client]
1Template.friendsNamesList.helpers
2  friends: ()->
3    friends.find({})
In Template:
1<template name="friendsNamesList">
2    <ul>
3        {{#each friends}}
4           <li>{{name}}</li>
5        {{/each}}
6    </ul>
7</template>

As methods/call

In Server Methods
1#CoffeeScript
2Meteor.neo4j.methods 
3    getUsersFriends: () ->
4        return  "MATCH (user {_id: {userId}})-[:FriendOf]->(friends) RETURN friends"
In Helper
1#CoffeeScript
2Template.friendsNamesList.helpers
3    userFriends: () ->
4        Meteor.neo4j.call 'getUsersFriends', {userId: Meteor.userId()}, (error, data) ->
5            throw new Meteor.error '500', 'Something goes wrong here', error.toString() if error
6            else
7              Session.set 'currenUserFriends', data
8        return Session.get 'currentUserFriens'
In Template:
1<template name="friendsNamesList">
2    <ul>
3        {{#each userFriends.friends}}
4           <li>{{name}}</li>
5        {{/each}}
6    </ul>
7</template>
About security

By default query execution is allowed only on server, but for development purpose (or any other), you may enable it on client:

1#Write this line in /lib/ directory to execute this code on both client and server side
2Meteor.neo4j.allowClientQuery = true
3#Do not forget about minimum security, deny all write queries
4Meteor.neo4j.set.deny Meteor.neo4j.rules.write

To allow or deny actions use neo4j.set.allow(['array of strings']) and neo4j.set.deny(['array of strings'])

1#CoffeeScript
2Meteor.neo4j.set.allow ['create', 'Remove']
3Meteor.neo4j.set.deny ['SKIP', 'LIMIT']
4
5#OR to allow or deny all
6Meteor.neo4j.set.allow '*'
7Meteor.neo4j.set.deny '*'
8
9#To deny all write operators
10Meteor.neo4j.set.deny Meteor.neo4j.rules.write
11
12#default rules
13Meteor.neo4j.rules = 
14    allow: ['RETURN', 'MATCH', 'SKIP', 'LIMIT', 'OPTIONAL', 'ORDER BY', 'WITH', 'AS', 'WHERE', 'CONSTRAINT', 'UNWIND', 'DISTINCT', 'CASE', 'WHEN', 'THEN', 'ELSE', 'END', 'CREATE', 'UNIQUE', 'MERGE', 'SET', 'DELETE', 'REMOVE', 'FOREACH', 'ON', 'INDEX', 'USING', 'DROP']
15    deny: []
Execute query on client side:
1#Write this line in /lib/ directory to execute this code on both client and server side
2Meteor.neo4j.allowClientQuery = true
3
4#Client code
5getAllUsers = ->
6    return Meteor.neo4j.query('MATCH (a:User) RETURN a').get();

For more info see: neo4jdriver and node-neo4j

Code licensed under Apache v. 2.0: node-neo4j License

Testing & Dev usage

Local usage
  • Download (or clone) to local dir
  • Stop meteor if running
  • Run mrt link-package [*full path to folder with package*] in a project dir
  • Then run meteor add ostrio:neo4jreactivity
  • Run meteor in a project dir
  • From now any changes in ostrio:neo4jreactivity package folder will cause rebuilding of project app

Understanding the package

After installing ostrio:neo4jreactivity package - you will have next variables:

  • Meteor.Neo4j; - [Server] GraphDatabase object from node-neo4j npm package. Use to connect to other Neo4j servers.
  • Meteor.N4JDB; - [Server] GraphDatabase instance connected to Neo4j server. Use to run Cypher queries directly in Neo4j DB, without any reactivity
  • Meteor.neo4j; - [Isomorphic] Neo4jReactivity Driver object
Meteor.Neo4j;
1/* 
2 * Server only
3 * @class
4 * @name Neo4j
5 * @param url {string} - URL to Neo4j database
6 * Note: It’s better to store URL in environment 
7 * variable, 'NEO4J_URL' or 'GRAPHENEDB_URL' - 
8 * so it will be automatically picked up by our driver
9 * 
10 * @description Run it to create connection to database
11 */
12Meteor.N4JDB = new Meteor.Neo4j(/* URL TO SERVER */);

Newly created object has next functions, you will use:

1/* @name query */
2Meteor.N4JDB.query('MATCH (n:User) RETURN n', null /* A map of parameters for the Cypher query */, function(err, data){
3    Session.set('allUsers', data);
4});
5
6/* @name listen */
7Meteor.N4JDB.listen(function(query, opts){
8    console.log('Incoming request to neo4j database detected!');
9});
Meteor.neo4j;
1/* Both (Client and Server)
2 * @object
3 * @name neo4j
4 * @description Application wide object neo4j
5 */
6Meteor.neo4j;
7Meteor.neo4j.allowClientQuery = true; /* Allow/deny client query executions */
8Meteor.neo4j.connectionURL = null; /* Set custom connection URL to Neo4j DB, Note: It’s better to store URL in environment variable, 'NEO4J_URL' or 'GRAPHENEDB_URL' - so it will be automatically picked up by the driver */

neo4j object has multiple functions, you will use:

1/* @namespace Meteor.neo4j.set
2 * @name allow
3 * @param rules {array} - Array of Cypher operators to be allowed in app
4 */
5Meteor.neo4j.set.allow(rules /* array of strings */);
6
7/* @namespace Meteor.neo4j.set
8 * @name deny
9 * @param rules {array} - Array of Cypher operators to be forbidden in app
10 */
11Meteor.neo4j.set.deny(rules /* array of strings */);
12
13
14/*
15 * @function
16 * @namespace neo4j
17 * @name query
18 * @param query {string}      - Cypher query
19 * @param opts {object}       - A map of parameters for the Cypher query
20 * @param callback {function} - Callback function(error, data){...}. Where is data is [REACTIVE DATA SOURCE]
21 *                              So to get data for query like:
22 *                              'MATCH (a:User) RETURN a', you will need to: 
23 *                              data.a
24 * @param settings {object}   - {returnCursor: boolean} if set to true, returns Mongo\Cursor 
25 * @description Isomorphic Cypher query call
26 * @returns Mongo\Cursor or ReactiveVar [REACTIVE DATA SOURCE] 
27 *
28 * @note Please keep in mind what on client it returns ReactiveVar, but on server it returns just data, see difference in usage at example below
29 *
30 */
31allUsers = Meteor.neo4j.query('MATCH (users:User) RETURN users');
32var users = allUsers.get().users;
33
34/* or via callback, on callback there is no need to run `get()` method */
35var users;
36Meteor.neo4j.query('MATCH (users:User) RETURN users', null, function(error, data){
37    users = data.users;
38});
39
40
41/*
42 * Server only
43 * @name methods
44 * @param methods {object} - Object of methods, like: { methodName: function(){ return 'MATCH (a:User {name: {userName}}) RETURN a' } }
45 * @description Create server methods to send query to neo4j database
46 */
47Meteor.neo4j.methods({
48   'GetAllUsers': function(){
49      return 'MATCH (users:User) RETURN users';
50   }
51});
52
53
54/*
55 * Client only
56 * @name call
57 * @description Call for server method registered via neo4j.methods() method, 
58 *              returns error, data via callback.
59 */
60Meteor.neo4j.call('GetAllUsers', null, function(error, data){
61   Session.set('AllUsers', data.users);
62});
Meteor.N4JDB;
1/* 
2 * Server only
3 * @description Current GraphDatabase connection object, basically created from 'new Neo4j()''
4 */
5Meteor.N4JDB;
6
7
8/* You may run queries with no returns on server with it: */
9Meteor.N4JDB.query('CREATE (a:User {_id: ”123”})');
10
11
12/* To set listener: */
13Meteor.N4JDB.listen(function(query, opts){
14  console.log('Incoming query: ' + query, opts);
15});