Grypher to JSON parser for Node.js
This module is installed via npm:
$ npm install grypher
var grypher = require('grypher');
var rules = grypher.parse('user <--[POSTED]-- :User');
Grypher uses relations to maps properties to nodes determined by classes and their indices. That's all.
For example we have a tweet with embedded user object:
var tweet = {
id: 481101713646960641
user: {
default_profile_image : false,
id : 119102990,
profile_background_image_url_https : "https://pbs.twimg.com/profile_background_images/444244318/16840333315.png",
verified : false,
/* ... */
}
}
In this case we can say that tweet posted by user:
--[POSTED_BY]--> user:User
Or that user posted a tweet:
<--[POSTED]-- user:User
Or user and tweet has a bidirectional relationship:
<--[POSTED|POSTED_BY]--> user:User
Where user
is a property name that contains embedded object and :User
is a name of a class.
You can specify what attribute will be stored in relation passing them to relation in parentheses:
--[PARTICIPATE_IN(score, wins)]--> game:Game
You can specify types for relation attributes:
--[PARTICIPATE_IN(score: Score, wins: Integer)]--> game:Game
Or even destructuring an array property:
entities.urls[] --[REFERS_TO(indices[first, last])]--> :Url
Attention. Currently only plain values are supported.
Sometimes your document just points to something via key. In this case you can specify how relation should be resolved.
in_reply_to_status_id_str => id_str <--[ REPLIED_WITH|REPLIED_TO ]--> in_reply_to:Tweet
In the last case =>
means that in_reply_to_status_id_str
should be treated as an id_str
key.
For compound keys you can use following syntax:
(longitude, latitude) => (x, y) --[ LOCATED ]--> location:Point
Sometimes your document's structure doesn't fit plane key-value nature of node. In this case you can specify properties by paths:
--[CITIZEN_OF]--> place.country:Country
If document's field doesn't match what you want to have in the target node you can rename it:
--[CITIZEN_OF]--> place.country => country:Country
You also can assign types to attributes (user-defined classes are also supported):
def :Person {
+ firstName :String
+ dateOfBirth :Date
}
Attention: attribute aliasing in types constraints are not supported yet.
Embedded objects are stored inside the node. Mechanism depends on graph database engine.
You can store unstructured embedded object as a link to node:
--> metadata
As for normal relations you can specify classes and arrays for links:
--> tweet:Tweet
--> comments:Comment[]
You can specify unique constraint:
UNIQUE(id)
Grypher also handles compound constraints:
UNIQUE(firstName, lastName)
You can refer to already defined class or type by colon notation:
UNIQUE(id:Int)
--> tweet:Tweet
In case when you have a collection of instances you can use an array syntax:
--> comments:Comment[]
Unstructured arrays are also supported:
--> things[]
To define class use def
keyword:
def Tweet
You can specify primary key in parentheses:
def HashTag(text)
Compound indexes are also supported:
def Person(firstname, lastname)
And you can use paths to specify properties:
def Citizen(credentials.passport.number)
Sometimes your class's primary key is stored in array. For example we have a location document with unique combinations of longitude and latitude stored in array. For this cases you can write:
def Location(coordinates[longitude, latitude])
You can define rules for complex classes in curly braces:
def User(passport.id) {
--[CHILD_OF]--> father:Person
--[REFERS_TO(indices[first, last])]--> entities.url.urls[]:Url
}
You can define enumeration mappers for arrays of fixed length:
def Coordinates [ longitude, latitude ]
After array items specified we can treat it as a regular document:
def VendorCoordinates [ longitude, latitude, vendor ] {
UNIQUE(longitude, latitude, vendor.id)
--[PRODUCED_BY]--> vendor:Vendor
}
Simple classes can be defined inside relation rules by appending class name with parentheses:
--[POPULATED_WITH]--> comment:Comment(id)
You can specify indices as in normal declaration:
--[REFERS_TO]--> urls:Url(url)[]
That will create a class Url
with url
as primary key and use it as a map for related nodes.
Grypher supports only one line comments (both #
and //
):
# JIRA task
def Task {
UNIQUE(id) // Primary key
--[BLOCKED_BY]-->blocker:Task
}
The following Grypher script describes tweet structure from Twitter Stream API:
# The whole tweet document returned by Twitter Stream API
def Tweet {
UNIQUE(id_str:String)
UNIQUE(id:Int)
+ location: String
<--[POSTED]-- user:User
--> metadata
<--[ REPLIED_WITH|REPLIED_TO ]--> in_reply_to_status_id_str => in_reply_to:Tweet
--[ REFERS_TO(indices[first, last]) ]--> entities.urls[]:Url
--[ HAS_TAG(indices[first, last]) ]--> entities.hashtags[]:HashTag(text)
--[ AT ]--> coordinates:Location
--[ ON ]--> geo:Location
--[ PLACED ]--> place:Place
}
# Just a point
def Coordinates [ longitude, latitude ] {
UNIQUE(longitude: Double, latitude: Double)
}
def Location(coordinates:Coordinates) // Simply a wrapper for coordinates
def Place {
--[ BOUNDED_BY ]--> bounding_box.coordinates => bounding_box:Coordinates[]
}
def User(id_str) {
--[REFERS_TO(indices[first, last])]--> entities.url.urls[]:Url
}
Client versions of node-grypher
can be built from sources or can be found in ./client/
directory of NPM module.
You can start from API docs.
I will accept only changes covered by unit tests.
def :Person
UNIQUE(passport.id)
--[CHILD_OF]--> father:Person
def :Person {
+ name.first => firstName :String
}