Skip to content

Commit

Permalink
Merge 0c344c0 into 915c53a
Browse files Browse the repository at this point in the history
  • Loading branch information
robak86 committed Jan 2, 2018
2 parents 915c53a + 0c344c0 commit c2c2269
Show file tree
Hide file tree
Showing 98 changed files with 2,761 additions and 1,448 deletions.
10 changes: 7 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
sudo: required
language: node_js
node_js:
- "6"
- "8"
- "9"

services:
- docker

before_script:
- npm run dist
- npm run build:dist
- docker-compose up -d
- npm run neo4j:wait
- npm run neo4j:wait

after_script:
- npm run build:coverage:publish
13 changes: 12 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,15 @@
- Add ```NodeRelationsRepository``` accessible by ```getNodeRelationsRepository```

## 0.0.4
- limit Connection instance to using only one neo4j session at the time (Solves Error: read ECONNRESET error )
- limit Connection instance to using only one neo4j session at the time (Solves Error: read ECONNRESET error )

## 0.0.9
- remove ```Persisted``` type. Repositories accept now entities with optional ```id``` property
and throw error if ```id``` is missing
- rename ```pickOne``` method of GraphResponse to more intuitive ```pluck```
- allow user to pass logger instance in configuration
- add saveMany for NodeRepository
- @timestamp maps neo4j integer value to javascript Date object
- fix bug related to closing driver sessions
- timestamps are stored as float
- all Integer values read from database are converted to number
140 changes: 63 additions & 77 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
# Neography

[![Build Status](https://travis-ci.org/robak86/neography.svg?branch=master)](https://travis-ci.org/robak86/neography)
[![Coverage Status](https://coveralls.io/repos/github/robak86/neography/badge.svg?branch=master&service=github)](https://coveralls.io/github/robak86/neography?branch=simplify_types)

Thin opinionated mapping layer and queries builder for official neo4j driver. (https://github.com/neo4j/neo4j-javascript-driver)
Simple object mapper and queries builder for official neo4j driver. (https://github.com/neo4j/neo4j-javascript-driver)

## Warning
This library is **highly experimental** and in many places the code is just a draft. Use it at your own risk.
This library is at early stage of development. The API most likely will change over time.
**All suggestion, opinions and ideas are gladly welcome.**


Expand All @@ -31,44 +32,48 @@ neography.registerExtension(TimestampsExtension.getDefault());

## Defining Model Classes

Neography provides mapping layer over persisted neo4j data and classes defined by UserNode. In order to create mappable class
Neography provides mapping layer over persisted neo4j data. In order to create mappable class
you have to decorate it with ```@node('NodeLabel')``` or ```@relation('RELATION_TYPE')``` decorators. Additionally each
entity class have to inherit consequently from ```AbstractNode``` or ```AbstractRelation``` class. Abstract classes provide optional ```id```
string property. By default neography generates uuid string for identifying entities.
Abstract classes were introduced for making most of query builder's and repositories' methods type safe.
entity class have to inherit consequently from ```AbstractNode``` or ```AbstractRelation``` class. ```AbstractNode``` class
provides auto generated unique ```id``` property.


```typescript
import {AbstractNode, AbstractRelation, Integer, int, createFactoryMethod} from 'neography/model';
import {node, relation} from 'neography/annotations';
import {AbstractNode, AbstractRelation} from 'neography/model';
import {node, relation, timestamp} from 'neography/annotations';

//Nodes definitions

@node('User') //node label
class UserNode extends AbstractNode {
build = createFactoryMethod(UserNode);

class UserNode extends AbstractNode<UserNode> {
@attribute() firstName:string;
@attribute() lastName:string;
}

@node('Address')
class AddressNode {
build = createFactoryMethod(AddressNode);

class AddressNode extends AbstractNode<AddressNode> {
@attribute() street:string;
@attribute() city:string;
}

@relation('KNOWS') //relation type
class KnowsRelation extends AbstractRelation {
build = createFactoryMethod(KnowsRelation);
@attribute() since:Integer;
//Relations definitions

@relation('KNOWS') //relation name
class KnowsRelation extends AbstractRelation<KnowsRelation> {
@timestamp() since:Date;
}

@relation('HAS_HOME_ADDRESS')
class HasHomeAddressRelation extends AbstractRelation{
build = createFactoryMethod(HasHomeAddressRelation);
}
class HasHomeAddressRelation extends AbstractRelation<HasHomeAddressRelation>{}

```

Passing generic types for ```AbstractRelation``` and ```AbstractNode``` is optional (starting from typescript ^2.3).
In this case generic types were introduced in order to enable type safety for constructors.

```typescript
new User({firstName: 'John', lastName: 'Doe'}); //OK
new User({firstName: 'John', someUnknownAttribute: 'Doe'}) //generates compile time error

```

Expand All @@ -80,28 +85,28 @@ Neography provides repositories for basic operations.
import {Neography} from 'neography';

const neography = new Neography({host: 'localhost', username: 'neo4j', password: 'password'});
const connection = neography.checkoutConnection()
const connection = neography.checkoutConnection();

//Create repository for given node class
const usersRepository = connection.getNodeRepository(UserNode);
const addressesRepository = connection.getNodeRepository(AddressNode);
//Create repository for given nodes types
const usersRepository = connection.nodeType(UserNode);
const addressesRepository = connection.nodeType(AddressNode);

//Create repository for given node classes and it's relation
const knowsRelationsRepository = connection.getRelationRepository(UserNode, KnowsRelation, UserNode);
const hasHomeAddressRelationsRepository = connection.getRelationRepository(UserNode, HasHomeAddressRelation, AddressNode);
//Create repository for given relations types
const knowsRelationsRepository = connection.relationType(KnowsRelation);
const hasHomeAddressRelationsRepository = connection.relationType(HasHomeAddressRelation);
```

### Managing Nodes

```typescript
let user1: Persisted<UserNode> = await usersRepository.save(User.build({firstName: 'Jane', lastName: 'Doe'}));
// User { id: 'BJ-_f8-Al', createdAt: 1492374120811, updatedAt: 1492374120811, firstName: 'Jane', lastName: 'Doe'}
let user1: UserNode = await usersRepository.save(new User({firstName: 'Jane', lastName: 'Doe'}));
// User { id: 'BJ-_f8-Al', createdAt: Sat Dec 30 2017 20:51:51 GMT+0100 (CET), updatedAt: ..., firstName: 'Jane', lastName: 'Doe'}

user1.firstName = 'John';
user1 = await usersRepository.update(user1);
// User { id: 'BJ-_f8-Al', createdAt: 1492374120811, updatedAt: 1492375654349, firstName: 'John', lastName: 'Doe'}
// User { id: 'BJ-_f8-Al', createdAt: Sat Dec 30 2017 20:51:51 GMT+0100 (CET), updatedAt: ..., firstName: 'John', lastName: 'Doe'}

let user2:Persisted<User>[] = await usersRepository.where({id: user1.id})
let user2:User[] = await usersRepository.where({id: user1.id})
// user2[0] and user1 points to the same persisted entity

await usersRepository.remove(user1.id);
Expand All @@ -110,35 +115,23 @@ await usersRepository.remove(user1.id);
### Managing Relations

```typescript
let user1:Persisted<User> = await usersRepository.save(User.build({firstName: 'Jane', lastName: 'Doe'}));
let user2:Persisted<User> = await usersRepository.save(User.build({firstName: 'John', lastName: 'Smith'}));
let user1:User = await usersRepository.save(new User({firstName: 'Jane', lastName: 'Doe'}));
let user2:User = await usersRepository.save(new User({firstName: 'John', lastName: 'Smith'}));

let relation:Persisted<KnowsRelation> = await knowsRelationsRepository.save(user1, user2, KnowsRelation.build({since: int(new Date().getTime())}))
// Relation {id: 'SfXi-89', since: Integer(1492711624208)}
let relation:KnowsRelation = await knowsRelationsRepository.nodes(user1, user2).connectWith(new KnowsRelation({since: new Date()}));

relation.since = int(0);
relation = await knowsRelationsRepository.update(relation);
// Relation {id: 'SfXi-89', since: Integer(0)}
await knowsRelationsRepository.remove(relation.id)
```
//alternatively
let relation:KnowsRelation = await knowsRelationsRepository.node(user1).connectTo(user2, new KnowsRelation({since: new Date()}));
// Relation {id: 'SfXi-89', since: Sat Dec 30 2017 20:51:51 GMT+0100 (CET)}

**TODO**
```typescript
knowsRelationsRepository.where();
knowsRelationsRepository.first();
//and getting all connected nodes for given node
relation.since = new Date();
relation = await knowsRelationsRepository.nodes(user1, user2).update(relation);
// Relation {id: 'SfXi-89', since: Sat Dec 30 2017 20:51:52 GMT+0100 (CET)}
await knowsRelationsRepository.nodes(user1, user2)
```




## Connection

TODO

## Query Builder

Query builder provides simple dsl for building cypher queries.
Query builder provides simple DSL for building cypher queries.
It tries to reflect cypher syntax without introducing any additional abstractions.


Expand All @@ -153,7 +146,7 @@ neography.query();
### Inserting Nodes
```typescript
//create instance of UserNode and assign properties values
let userNode = UserNode.build({firstName: 'Jane', lastName: 'Doe'});
let userNode = new UserNode({firstName: 'Jane', lastName: 'Doe'});

//create query
let insertQuery = neography.query()
Expand All @@ -164,31 +157,28 @@ let insertQuery = neography.query()
Query can be executed by calling ```runQuery``` method on ```Connection``` instance. And returns properly mapped response

```typescript
import {Persisted} from 'neography/model';
let response:{user: Persisted<UserNode>}[] = await connection.runQuery(insertQuery).toArray();
let response:{user: UserNode}[] = await connection.runQuery(insertQuery).toArray();
```
It equals to following cypher query
```cypher
CREATE(user:UserNode { firstName: "Jane", lastName: "Doe" })
RETURN user
```

Response object provides convenient helper methods for queries like the previous one.
Response object provides convenient helper methods for manipulating data.

```typescript
let response:Persisted<UserNode> = await connection.runQuery(insertQuery).pickOne('user').first();
let response:UserNode = await connection.runQuery(insertQuery).pluck('user').first();
```

```Persisted``` class declares that ```id``` property isn't optional and it is string. It's useful with strict type checking

### Matching Nodes

```typescript
let matchQuery = neography.query()
.match(m => m.node(UserNode).params({firstName: 'Jane'}).as('user'))
.returns('user');

let users:Persisted<UserNode>[] = await connection.runQuery(matchQuery).pickOne('user').toArray();
let users:UserNode[] = await connection.runQuery(matchQuery).pluck('user').toArray();
```

### Matching Nodes Using Where
Expand All @@ -199,7 +189,7 @@ let matchWhere = neography.query()
.where(w => w.literal(`user.createdAt < {userCreateDate}`).params({userCreateDate: int(new Date('2016-12-31').getTime())}))
.returns('user');

let users:Persisted<UserNode>[] = await connection.runQuery(matchWhere).pickOne('user').toArray();
let users:UserNode[] = await connection.runQuery(matchWhere).pluck('user').toArray();
```

### Matching Nodes Connected by Relation
Expand All @@ -213,7 +203,7 @@ let matchWhere = neography.query()
])
.returns('user', 'relation', 'friend');

type Response = {user: Persisted<UserNode>, relation: Persisted<KnowsRelation>, friend: Persisted<UserNode>};
type Response = {user: UserNode, relation: KnowsRelation, friend: UserNode};
let friends:Response = await connection.runQuery(matchWhere).toArray();
```

Expand All @@ -229,14 +219,14 @@ let matchWhere = neography.query()
])
.returns('user', 'relation', 'friend');

type Response = {user: Persisted<UserNode>, relation: Persisted<KnowsRelation>, friend: Persisted<UserNode>};
type Response = {user: UserNode, relation: KnowsRelation, friend: UserNode};
let usersOptionallyHavingFriends:Response = await connection.runQuery(matchWhere).toArray();
```

### Creating Relations For Existing Nodes

```typescript
let knowsRelation = KnowsRelation.build({since: int(new Date().getTime())})
let knowsRelation = new KnowsRelation({since: int(new Date().getTime())})

let createRelation = neography.query()
.match(m => [
Expand All @@ -250,15 +240,15 @@ let createRelation = neography.query()
)
.returns('user','relation', 'friend');

type Response = {user: Persisted<UserNode>, relation: Persisted<KnowsRelation>, friend: Persisted<UserNode>};
type Response = {user: UserNode, relation: KnowsRelation, friend: UserNode};
let userWithFriend:Response = await connection.runQuery(createRelation).toArray();
```

### Creating Relations with New Node

```typescript
let friend = User.build({firstName: 'Aaron', lastName: 'Smith'});
let knowsRelation = KnowsRelation.build({since: int(new Date().getTime())})
let friend = new User({firstName: 'Aaron', lastName: 'Smith'});
let knowsRelation = new KnowsRelation({since: int(new Date().getTime())})

let createRelation = neography.query()
.match(m => m.node(UserNode).params({id: 'someExistingId'}).as('user'))
Expand All @@ -269,10 +259,6 @@ let createRelation = neography.query()
)
.returns('user','relation', 'friend');

type Response = {user: Persisted<UserNode>, relation: Persisted<KnowsRelation>, friend: Persisted<UserNode>};
type Response = {user: UserNode, relation: KnowsRelation, friend: UserNode};
let userWithFriend:Response = await connection.runQuery(createRelation).toArray();
```

### Update Entities Using SET Clause
TODO

```
Loading

0 comments on commit c2c2269

Please sign in to comment.