Giter Club home page Giter Club logo

neode's Introduction

Neode

Neode is a Neo4j OGM for Node JS designed to take care of the CRUD boilerplate involved with setting up a neo4j project with Node. Just install, set up your models and go.

Getting Started

Installation

npm install --save neode

Usage

// index.js
import Neode from 'neode';

const instance = new Neode('bolt://localhost:7687', 'username', 'password');

Enterprise Mode

To initiate Neode in enterprise mode and enable enterprise features, provide a true variable as the fourth parameter.

// index.js
import Neode from 'neode';

const instance = new Neode('bolt://localhost:7687', 'username', 'password', true);

Usage with .env variables

npm i --save dotenv
// .env
NEO4J_PROTOCOL=neo4j
NEO4J_HOST=localhost
NEO4J_USERNAME=neo4j
NEO4J_PASSWORD=neo4j
NEO4J_PORT=7687
NEO4J_DATABASE=neo4j
NEO4J_ENCRYPTION=ENCRYPTION_OFF
// index.js
import Neode from 'neode';

const instance = Neode.fromEnv();

Additional Driver Config

Additional driver configuration can be passed as the fifth parameter in the constructor, or defined in .env:

NEO4J_ENCRYPTED=ENCRYPTION_ON                   # ENCRYPTION_ON or ENCRYPTION_OFF
NEO4J_TRUST=TRUST_SIGNED_CERTIFICATES           # TRUST_ALL_CERTIFICATES, TRUST_ON_FIRST_USE, TRUST_SIGNED_CERTIFICATES, TRUST_CUSTOM_CA_SIGNED_CERTIFICATES, TRUST_SYSTEM_CA_SIGNED_CERTIFICATES
NEO4J_TRUSTED_CERTIFICATES=/path/to/cert.pem
NEO4J_KNOWN_HOSTS=127.0.0.1
NEO4J_MAX_CONNECTION_POOLSIZE=100
NEO4J_MAX_TRANSACTION_RETRY_TIME=5000
NEO4J_LOAD_BALANCING_STRATEGY=least_connected   # least_connected or round_robin
NEO4J_MAX_CONNECTION_LIFETIME=36000
NEO4J_CONNECTION_TIMEOUT=36000
NEO4J_DISABLE_LOSSLESS_INTEGERS=false

Loading with Models

You can use the with() method to load multiple models at once.

const neode = require('neode')
    .fromEnv()
    .with({
        Movie: require('./models/Movie'),
        Person: require('./models/Person')
    });

Load from Directory

You can load a directory of models by calling the withDirectory() method.

// models/Person.js
module.exports = {
  id: {
    type: 'uuid',
    primary: true
  },
  name: 'string'
}
// index.js
instance.withDirectory(__dirname+'/models');

Defining a Node Definition

Neode revolves around the notion of node definitions, or Models. To interact with the graph, you will need to define a node, identified by a name and with a schema of properties.

instance.model(name, schema);

Schema Object

instance.model('Person', {
    person_id: {
        primary: true,
        type: 'uuid',
        required: true, // Creates an Exists Constraint in Enterprise mode
    },
    payroll: {
        type: 'number',
        unique: 'true', // Creates a Unique Constraint
    },
    name: {
        type: 'name',
        index: true, // Creates an Index
    },
    age: 'number' // Simple schema definition of property : type
});
Property Types

The following property types are supported:

  • string
  • number
  • int
  • integer
  • float
  • uuid
  • node
  • nodes
  • relationship
  • relationships
  • Temporal
    • date
    • time
    • datetime
    • localtime
    • localdatetime
    • duration
  • Spatial
    • point
    • distance
Validation

Validation is provided by the Joi library. Certain data types (float, integer, boolean) will also be type cast during the data cleansing process. For more information on the full range of validation options, read the Joi API documentation.

All Types
option type description example
allow Array Whitelist of values that are allowed allow: ['A', 'B', 'C']
valid Array A strict whitelist of valid options. All others will be rejected. valid: ['A', 'B', 'C']
invalid Array A list of forbidden values invalid: ['A', 'B', 'C']
required Boolean Should this field be required? required: true
optional Boolean Allow the value to be undefined optional: true
forbidden Boolean Marks a key as forbidden which will not allow any value except undefined. Used to explicitly forbid keys. forbidden: true
strict Boolean prevent type casting for the current key strict: true
strip Boolean Marks a key to be removed from a resulting object or array after validation. strip: true
default Mixed/Function Default value for the property default: () => new Date()
empty Boolean Considers anything that matches the schema to be empty empty: true
error Error/String/Function Overrides the default error error: errors => new CustomValidationError('Oh No!', errors)
Boolean
option type description example
truthy String
falsy String
insensitive Boolean
Date, Time, DateTime, LocalDateTime, LocalTime
option type description example
before String Date, date string or "now" to compare to the current date
after String Date, date string or "now" to compare to the current date
Numbers (number, int, integer, float)
option type description example
min Number
max Number
integer Boolean Requires the number to be an integer
precision Number Specifies the maximum number of decimal places precision: 2
multiple Number Multiple of a number multiple: 2
positive Boolean
negative Boolean
port Boolean Requires the number to be a TCP port, so between 0 and 65535.
Strings
option type description example
insensitive Boolean
min Number Min length
max Number Max length
truncate Boolean Will truncate value to the max length
creditCard Boolean Requires the number to be a credit card number (Using Luhn Algorithm).
length Number Exact string length
regex Object Regular expression rule { pattern: /([A-Z]+)/, invert: true, name: 'myRule'}
replace Object Replace in value { pattern: /(^[A-Z]+)/, replace: '-' }
alphanum Boolean Requires the string value to only contain a-z, A-Z, and 0-9.
token Boolean Requires the string value to only contain a-z, A-Z, 0-9, and underscore _.
email Boolean/Object
ip Boolean/Object
uri Boolean/Object
guid Boolean
hex Boolean/Object
base64 Boolean/Object
hostname Boolean
normalize Boolean/String
lowercase Boolean
uppercase Boolean
trim Boolean
isoDate Boolean

Defining Relationships

Relationships can be created in the schema or defined retrospectively.

instance.model(label).relationship(type, relationship, direction, target, schema, eager, cascade, node_alias);
instance.model('Person').relationship('knows', 'relationship', 'KNOWS', 'out', 'Person', {
    since: {
        type: 'number',
        required: true,
    },
    defaulted: {
        type: 'string',
        default: 'default'
    }
});

Eager Loading

You can eager load relationships in a findAll() call by setting the eager property inside the relationship schema to true.

{
    acts_in: {
        type: "relationship",
        target: "Movie",
        relationship: "ACTS_IN",
        direction: "out",
        properties: {
            name: "string"
        },
        eager: true // <-- eager load this relationship
    }
}

Eager loaded relationships can be retrieved by using the get() method. A Collection instance will be returned.

const person = person.find({name: "Tom Hanks"})
const movies = person.get('acts_in');
const first = movies.first();

Extending a Schema definition

You can inherit the schema of a class and extend by calling the extend method.

instance.extend(original, new, schema)
instance.extend('Person', 'Actor', {
    acts_in: {
        type: "relationship",
        target: "Movie",
        relationship: "ACTS_IN",
        direction: "out",
        properties: {
            name: "string"
        }
    }
})

Reading

Running a Cypher Query

instance.cypher(query, params)
instance.cypher('MATCH (p:Person {name: $name}) RETURN p', {name: "Adam"})
    .then(res => {
        console.log(res.records.length);
    })

Running a Batch

Batch queries run within their own transaction. Transactions can be sent as either a string or an object containing query and param properties.

instance.batch(queries)
instance.batch([
    {query: 'CREATE (p:Person {name: $name}) RETURN p', params: {name: "Adam"}},
    {query: 'CREATE (p:Person {name: $name}) RETURN p', params: {name: "Joe"}},
    {query: 'MATCH (first:Person {name: $first_name}), (second:Person {name: $second_name}) CREATE (first)-[:KNOWS]->(second)', params: {name: "Joe"}}
])
    .then(res => {
        console.log(res.records.length);
    })

Get all Nodes

instance.all(label, properties)
instance.model(label).all(properties)
instance.all('Person', {name: 'Adam'}, {name: 'ASC', id: 'DESC'}, 1, 0)
    .then(collection => {
        console.log(collection.length); // 1
        console.log(collection.get(0).get('name')); // 'Adam'
    })

Get Node by Internal Node ID

instance.findById(label, id)
instance.model(label).findById(id)
instance.findById('Person', 1)
    .then(person => {
        console.log(person.id()); // 1
    });

Get Node by Primary Key

Neode will work out the model's primary key and query based on the supplied value.

instance.find(label, id)
instance.model(label).find(id)
instance.find('Person', '1234')
    .then(res => {...});

First by Properties

Using a key and value

instance.first(label, key, value)
instance.first(label).first(key, value)
instance.first('Person', 'name', 'Adam')
    .then(adam => {...})

Using multiple properties

instance.first(label, properties)
instance.first(label).first(properties)
instance.first('Person', {name: 'Adam', age: 29})
    .then(adam => {...})

Writing

Creating a Node

instance.create(label, properties);
instance.model(label).create(properties);
instance.create('Person', {
    name: 'Adam'
})
.then(adam => {
    console.log(adam.get('name')); // 'Adam'
});

Merging a Node

Nodes are merged based on the indexes and constraints.

instance.merge(label, properties);
instance.model(label).merge(properties);
instance.merge('Person', {
    person_id: 1234,
    name: 'Adam',
});

Merge On Specific Properties

If you know the properties that you would like to merge on, you can use the mergeOn method.

instance.mergeOn(label, match, set);
instance.model(label).mergeOn(match, set);
instance.mergeOn('Person', {person_id: 1234}, {name: 'Adam'});

Updating a Node

You can update a Node instance directly by calling the update() method.

instance.create('Person', {name: 'Adam'})
    .then(adam => adam.update({age: 29}));

Creating a Relationships

You can relate two nodes together by calling the relateTo() method.

model.relateTo(other, type, properties)
Promise.all([
    instance.create('Person', {name: 'Adam'}),
    instance.create('Person', {name: 'Joe'})
])
.then(([adam, joe]) => {
    adam.relateTo(joe, 'knows', {since: 2010})
        .then(res => {
            console.log(res.startNode().get('name'), ' has known ', res.endNode().get('name'), 'since', res.get('since'));  // Adam has known Joe since 2010
        });
});

Note: when creating a relationship defined as in (DIRECTION_IN), from from() and to() properties will be inversed regardless of which model the relationship is created by.

Detaching two nodes

You can detach two nodes by calling the detachFrom() method.

model.detachFrom(other)
Promise.all([
    instance.create('Person', {name: 'Adam'}),
    instance.create('Person', {name: 'Joe'})
])
.then(([adam, joe]) => {
    adam.detachFrom(joe) // Adam does not know Joe
});

### Deleting a node
You can delete a Node instance directly by calling the `delete()` method.

```javascript
instance.create('Person', {name: 'Adam'})
  .then(adam => adam.delete());

Cascade Deletion

While deleting a Node with the delete() method, you can delete any dependant nodes or relationships. For example, when deleting a Movie you may also want to remove any reviews but keep the actors.

You cna do this by setting the cascade property of a relationship to "delete" or "detach". "delete" will remove the node and relationship by performing a DETACH DELETE, while "detach" will simply remove the relationship, leaving the node in the graph.

// Movie.js
module.exports = {
  // ...
  ratings: {
    type: 'relationship',
    'relationship': 'RATED',
    direction: 'IN',
    target: 'User',
    'cascade': 'delete'
  },
  actors: {
    type: 'relationship',
    'relationship': 'ACTS_IN',
    direction: 'IN',
    target: 'Actor',
    'cascade': 'detach'
  }
};

Note: Attempting to delete a Node without first removing any relationships will result in an error.

Deleting a set of nodes

TODO

instance.delete(label, where)
instance.delete('Person', {living: false});

Deleting all nodes of a given type

instance.deleteAll('Person');
  .then(() => console.log('Everyone has been deleted'));

Query Builder

Neode comes bundled with a query builder. You can create a Query Builder instance by calling the query() method on the Neode instance.

const builder = instance.query();

Once you have a Builder instance, you can start to defining the query using the fluent API.

builder.match('p', 'Person')
    .where('p.name', 'Adam')
    .return('p');

For query examples, check out the Query Builder Test suite.

Building Cypher

You can get the generated cypher query by calling the build() method. This method will return an object containing the cypher query string and an object of params.

const {query, params} = builder.build();

instance.query(query, params)
    .then(res => {
        console.log(res.records.length);
    });

Executing a Query

You can execute a query by calling the execute() method on the query builder.

builder.match('this', 'Node')
    .whereId('this', 1)
    .return('this')
    .execute()
    .then(res => {
        console.log(res.records.length);
    });

Schema

Neode will install the schema created by the constraints defined in your Node definitions.

Installing the Schema

instance.schema.install()
    .then(() => console.log('Schema installed!'))

Note: exists constraints will only be created when running in enterprise mode. Attempting to create an exists constraint on Community edition will cause a Neo.DatabaseError.Schema.ConstraintCreationFailed to be thrown.

Dropping the schema

Dropping the schema will remove all indexes and constraints created by Neode. All other indexes and constraints will be left intact.

instance.schema.drop()
    .then(() => console.log('Schema dropped!'))

neode's People

Contributors

0xflotus avatar a-type avatar abdullahali avatar adam-cowley avatar alex-laycalvert avatar am17torres avatar bboure avatar codingbyjerez avatar gueno-dev avatar guham avatar jclappiway avatar johnsonjo4531 avatar kerryboyko avatar mhoangvslev avatar msst avatar purplemana avatar steve-nzr avatar sukantgujar avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

neode's Issues

Temporal & Spatial Support

Neo4j 3.4 introduced two new data types, Temporal and Spatial. Currently the OGM only supports strings, numbers and booleans. At the very least these new types should be supported.

startsAt: {
  type: 'Date', // or Time, DateTime, LocalDateTime, LocalTime
  location: 'Point',
}

New Types:

  • Date
  • Time
  • DateTime
  • LocalDateTime
  • LocalTime
  • Duration
  • Point (Spatial)

Changes Required:

  • Add support for new types
  • Transformations for Strings & Native Dates into Dates and Times
  • Custom validation schemas

Websocket connection failed

Hello,

Is there a limit on the maximum number of transaction?

In a test I have 3000 objects to handle with 3 operations (create - merge - relate), so I guess is 9000 transactions overall.

The console returns this error
browser-channel.js:219 WebSocket connection to 'ws://localhost:7687/' failed: net::ERR_FAILED

Different things I tried;

  1. close driver, open new session
  2. close driver, create new db instance with model

Is there anything we can do?

Eager does not return all the elements of a relationship only the first

Hello, im using the schema to relate a user with multiples roles

has_role: {
type: "relationship",
target: "Role",
relationship: "HAS_ROLE",
direction: "out",
eager: true, // <-- eager load this relationship
cascade: true
}

but when i used .all() method i only receive the first.
Is there a possibility that the .all () will return an array with all the roles associated with the user ?

[Bug] Provided Object to Create fails validation

For the following code:

neode.model(LABEL).create(data);

where data is an object (i.e. { foo: 'bar' }), create throws a validation error from GenerateDefaultValues saying that properties is not an object. This is due to the following check:

if (!(properties instanceof Object)) { ... }

I believe this should be the following instead:

if (!typeof properties === 'object' || Array.isArray(properties)) { ... }

(You can also use assert to provide a cleaner read on your validation)

i need add new property

eg: in model user i define

name:'String',
dob:'String'

I want to create new user have

{
name:'Hung',
dob:'31-03-2019',
gender:'male'
}

Who has the solution?
please help me

Eager Loading & Serialising Relationships

Opening up a private discussion with @noumaans.

I'm planning to write an enhancement to improve the serialisation of relationships. Say we have a Movie node, and we want to also eager load the Director of the movie.

Movie: {
  name: 'string',
  directed_by: {
     type: 'relationship',
     target: 'Person',
     properties: {
        budget: 'number', // budget spent on direction
     }
  }
}

We'll get something similar to the following:

{
    "_id": 7288,
    "languages": [
        "English",
        " French",
        " Swedish"
    ],
    "year": 2009,
    "imdbId": "1022603",
    "runtime": 95,
    "imdbRating": 7.8,
    "movieId": "69757",
    "countries": [
        "USA"
    ],
    "imdbVotes": 363490,
    "title": "(500) Days of Summer",
    "tmdbId": "19913",
    "plot": "An offbeat romantic comedy about a woman who doesn't believe true love exists, and the young man who falls for her.",
    "poster": "http://ia.media-imdb.com/images/M/MV5BMTk5MjM4OTU1OV5BMl5BanBnXkFtZTcwODkzNDIzMw@@._V1_SX300.jpg",
    "released": "2009-08-07",
    "director": [{
        "_id": 31462,
        "name": "Marc Webb"
    }]
}

This approach has a couple of problems:

  1. An array of nodes is returned even if only one exists
  2. There is no visibility of the properties stored against the relationship - what was the director's salary?

Proposed Changes

  1. Introduce either a cardinality option to the configuration to return the first result as an object
  directed_by: {
     type: 'relationship',
     cardinality: 1, // or limit, 
  }

Or a new type:

  directed_by: {
     type: 'node',  // or 'nodes' to return an array of nodes
     target: 'Director',
  }
  1. Where you want to see a relationship with it's properties, keep the relationship type but use this to return the relationship and node.

Define:

  directed_by: {
     type: 'relationship',
  }

To return:

{
    "_id": 7288,
    // ...
    "directed_by": {
        "_id": 31462,
        "salary": 50000, // I don't know much much a director earns?!
        "node": { // kind of inconvenient name because, see notes...
          "_id": 31462,
          "name": "Marc Webb"
        }
    ]
}

In the example above, the properties for the relationship are flattened and then a node key for the node at the other end.

Neo4j's API's define relationships as having a startNode, endNode and other. Because the definition has an in/out direction, the only applicable name would be other - that's not ideal. If Person A knows Person B, using startNode or endNode could lead to inconsistent returns.

Potentially, this could be defaulted to other, with an option to configure the key name if required.

  directed_by: {
     type: 'relationship',
     alias: 'person'
  }
{
    "_id": 7288,
    // ...
    "directed_by": {
        "_id": 31462,
        "salary": 12000, 
        "person": { // as per alias
          "_id": 31462,
          "name": "Marc Webb"
        }
    ]
}

This could also default to the target name:

{
    "_id": 7288,
    // ...
    "directed_by": {
        "_id": 31462,
        "salary": 12000, 
        "Person": { // We've defined the target to be Person
          "_id": 31462,
          "name": "Marc Webb"
        }
    ]
}

All comments welcome...

Unique Constraints

Are unique constraints supported? I think I'm doing the right stuff but it keeps allowing me to create nodes with the same email address. Using version 0.2.10. Here's my model:
module.exports = {
person_id: {
primary: true,
type: 'uuid',
// Creates an Exists Constraint in Enterprise mode
required: true,
},
name: {
type: 'string',
required: true

},
email: {
    type: 'string',
    required: true,
    email: true,
    index: true,
    unique:true

}

}

Here's my code:
var Neode = require('neode');
const neo4jdb = new Neode('bolt://localhost:7687', 'neo4j', 'graphdb');
neo4jdb.withDirectory(__dirname+'/../models');
const createPerson = (person) => {
var person = neo4jdb.model("Person").create(person).then(person =>
{
neo4jdb.close();
return person;

});
return person;

}

module.exports.createPerson = createPerson

is this production ready?

Hello, I want to use this OGM for a project, how far is it from being production ready?
There are known unsolved bug currently?

Custom errors not working as expected.

For a schema like this

{
    name: {
      type: "string",
      required: "true"
    },
    password: {
      type: "string",
      required: true,
      regex: /^[a-zA-Z0-9]{8,100}$/,
      error: () =>
        "Password must be between 8 to 100 characters and only alphabets and numbers are allowed"
    }
}

The expected error message should be the one defined in the error field. However, the actual error being returned is:

{ 
  Error: ERROR_VALIDATION
    at new ValidationError (/mnt/c/Code/alchemist/node_modules/neode/build/ValidationError.js:21:124)
    at /mnt/c/Code/alchemist/node_modules/neode/build/Services/Validator.js:242:23
    at internals.Object._validateWithOptions (/mnt/c/Code/alchemist/node_modules/joi/lib/types/any/index.js:678:20)
    at module.exports.internals.Any.root.validate (/mnt/c/Code/alchemist/node_modules/joi/lib/index.js:138:23)
    at /mnt/c/Code/alchemist/node_modules/neode/build/Services/Validator.js:229:19
    at new Promise (<anonymous>)
    at Validator (/mnt/c/Code/alchemist/node_modules/neode/build/Services/Validator.js:228:10)
    at /mnt/c/Code/alchemist/node_modules/neode/build/Services/Create.js:28:40
    at process._tickCallback (internal/process/next_tick.js:68:7)
    at Function.Module.runMain (internal/modules/cjs/loader.js:745:11)
  details: { password: [ 'string.regex.base' ] },
  input: undefined 
}

Looking into the code, in BuildValidationSchema, the validation is built by iterating over the schema properties, chaining the Joi validators. This causes error to be added in the middle of the chain. The validators chained after it will influence the returned message. A fix would be to pluck the error key from the schema and add the Joi.error validator at the end.

Would be glad to submit a PR.

bug: extend function assign array instead of cloning

Hey me again, found another one :)

On line 142 of ModelMap.js it says:

var labels = original.labels();

But this makes a reference to the array on the original model instead of cloning it. Therefor if when you inherit (or multiple inherit) schema's then all the models will hold the same labels.

To fix it simply rewrite to:

var labels = original.labels().slice(0);

So, combined with the other bug on line 143 the whole function could look like this:

    // starting from line 136 in ModelMap.js
    key: 'extend',
            value: function extend(name, as, using) {
            // Get Original Model
            var original = this.models.get(name);

            // Add new Labels
            var labels = original.labels().slice(0);
            labels.push(as);
            labels.sort();

            // Merge Schema
            var schema = Object.assign({}, original.schema(), using);

            // Create and set
            var model = new _Model2.default(this._neode, as, schema);

            model.setLabels.apply(model, _toConsumableArray(labels));

            this.models.set(as, model);

            return model;
        }

how to paginate a query?

Writing a query manually, I use the SKIP and LIMIT parameters to do pagination.
how to do this with ogm?

CRITICAL: Support for env param `disableLosslessIntegers` is broken.

After spending an entire day finally figured out that this was the reason why the specs failed mysteriously on my setup but were working in your repo's CI.

The code written to parse this value from .env file sets the value to true even if it is given as false. E.g. in the .env.example file provided with the code -

NEO4J_DISABLE_LOSSLESS_INTEGERS=false

This will still be converted to disableLosslessIntegers:true in the parsing logic present at https://github.com/adam-cowley/neode/blob/master/src/index.js#L74,
because false or true values are strings in process.env so !!value will always be true.

A workaround is to remove the line from .env file.

Although this looks innocent, it breaks Neode for two reasons:

  1. The code is always assuming query results to be in neo4j high-precision number format. With disableLosslessIntegers true, all the places where get is being invoked fails because neo4j returns plain numbers which don't have this method.
  2. Relationship code stops working because the way node ids are being populated in the statement https://github.com/adam-cowley/neode/blob/master/src/Services/RelateTo.js#L34 cause the ids to be sent to neo4j driver as plain integers. The driver then converts them to float. So a value of id 10 becomes 10.0 and the query is no longer able to match the nodes and relation creation fails silently. An exception is thrown by https://github.com/adam-cowley/neode/blob/master/src/Services/RelateTo.js#L45 which is expecting records[0] to be present.

I would suggest disabling this mode completely till the relevant code is fixed. Two changes need to be done:

  1. Remove the NEO4J_DISABLE_LOSSLESS_INTEGERS=false line from env.example so people (like yours truly) don't inadvertently copy that into their setup.
  2. Remove disableLosslessIntegers from config till the code is fixed.

Misleading Documentation around 'default' for model field

default | Mixed/Function | Default value for the property | default: () => new Date()

If used in code, this actually fails Joi validation since date needs to be a string. Recommend updating docs to be:

default | Mixed/Function | Default value for the property | default: () => new Date().toISOString()

NPM for 0.2.x ?

When will you update the NPM to the current release branch?

Validating and hydrating Models with custom queries

Hi,

I have been testing this package for my latest project.
I realized that most of the time, if not always, I find more convenient and performant to use the query builder instead of using the Model helpers.
The reason is that I think it is better to just do everything in just one cypher query rather than several ones.

Example:
I have a Person node in my db; I want to add a relation to that Person with another person.
Using the Models helper I would have to do something like:

  1. find() Person 1
  2. find() Person 2
  3. Call person1.relateTo(person2)

That is 3 different queries when I could just

MATCH (p1:Person {id: 1}), (p2:Person {id: 2})
CREATE (p1)-[:KNOWS]->(p2)
RETURN p1, p2

(I did not find a way to do something like this through the models)

The query builder allows me that but I am missing a few things:

  • Validation: I find very convenient that the Model validates but also "marshals" the data types between neo4j types and JS types.

  • Hydratation: Models come hydrated when the query builder just returns plain Records directly from the driver.
    Hydration gives me some convenient helpers like toJson()

I think there is probably some margin here to solve those issues.
We could probably rely on the Labels to know how to hydrate and validate inputs/results in the query builder (whenever possible).

What do you think?

Incorrect validation on update node

Update functions throw a validation error if the values which are being updated doesn't include the properties marked as required in the model. For eg. property a,b are marked as required, then while updating property c, update function throws validation error for a and b as they are marked as required in the model, here I don't wish to update a and b.

Howto get from cypher result to object from model

How can I convert the result of a cypher query to the corresponding model objects:

instance._neode.cypher('MATCH (s:Session)-[:foo]->(f:Foo) WHERE s.id=$sid AND f.id=$fid',
{
fid: ...,
sid: ...
}).then(function (result) {
const session = result.records[0];
}

For session I have a model Session which I would like to use

Compilation errors when used with TypeScript

When I use neode with AngularJS:
import { Neode } from 'neode';
gives compilation error
ERROR in src/app/database/database.service.ts(2,10): error TS2724: Module '"../../../node_modules/neode/types"' has no exported member 'Neode'. Did you mean 'Node'?

Fix:
in index.js: change export = Neode to export { Neode }

or

in your script: import * as Neode from 'neode'

Could be duplicate of #14

A more flexible target definition for relations

Within Neo4j it is possible to use a single request with different labelled nodes like
MATCH (u)-[works_on]->(w) WHERE u.name="John" RETURN w
With a user u named John, w could be a company, department, project.

In Neode it should then be something like:

`module.exports = {
name: {
type: 'string',
indexed: true,
required: true
},

work: {
    type: 'relationship',
    relationship: 'work',
    direction: 'out',
    target: ['Company', 'Department', 'Workgroup', 'Project'],
}

};`
or even target: [] for any kind of target

bug in instance.extend(original, new, schema) function

Hey great work, I found a little bug tho:

In ModelMap.js on line 143 it says:

labels.push(name);

But this should be changed to:

labels.push(as);

Because otherwise the original label is added to the new model twice. In my code I do this for instance:

neode.extend('Artwork', 'Sculpture');

which results in the following model:

_model._labels: ['Artwork', 'Artwork']

But with my proposed change it will result in

_model._labels: ['Artwork', 'Sculpture']

Invalid input '.': expected whitespace, comment, a property key name, '}', an

I've tried to run node/example/index.js and got the follow exception

Any ideas?

Neo4jError: Invalid input '.': expected whitespace, comment, a property key name, '}', an identifier or UnsignedDecimalInteger (line 7, column 9 (offset: 128))
" .*"
^

at captureStacktrace (/Users/lacerdaph/IdeaProjects/bb/kernel-label/node_modules/neode/node_modules/neo4j-driver/lib/v1/result.js:200:15)
at new Result (/Users/lacerdaph/IdeaProjects/bb/kernel-label/node_modules/neode/node_modules/neo4j-driver/lib/v1/result.js:73:19)
at Session._run (/Users/lacerdaph/IdeaProjects/bb/kernel-label/node_modules/neode/node_modules/neo4j-driver/lib/v1/session.js:116:14)
at Session.run (/Users/lacerdaph/IdeaProjects/bb/kernel-label/node_modules/neode/node_modules/neo4j-driver/lib/v1/session.js:95:19)
at Neode.cypher (/Users/lacerdaph/IdeaProjects/bb/kernel-label/node_modules/neode/build/index.js:328:28)
at Neode.writeCypher (/Users/lacerdaph/IdeaProjects/bb/kernel-label/node_modules/neode/build/index.js:306:25)
at Builder.execute (/Users/lacerdaph/IdeaProjects/bb/kernel-label/node_modules/neode/build/Query/Builder.js:706:40)
at /Users/lacerdaph/IdeaProjects/bb/kernel-label/node_modules/neode/build/Services/Create.js:39:39
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:188:7)

(node:3244) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:3244) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

Why does `Validator` method create its own error?

There are several problems with this -

  1. The code assumes certain properties, namely details and path are always available. However this is not the case if a schema declares a custom error this way -

     error: new Error("git happens")
    

    Because Joi replaces its own error object with the user supplied one.

  2. The validation error details are lost, the code is not copying some important properties like message, limiting the information.

As neode falls back to joi for validation, why not simply reject with the same error object which the validate method returns!?

".create("label", {property1 : "a" ...})" creates empty nodes on the db

Hello I'm new to everything related to neo4j and I've decided to try out your module.
Right now I'm having an issue, I'm simulating a registration to a website.

I receive the form as a json object

{
	"name": "Elena",
	"surname": "Fiera",
	"email": "[email protected]",
	"password": "miciomiciomiaomiao",
	"type": "HMM"
}

then after being converted to object, it is passed to the "createNode" method as "properties".

    /**
     * createNode -
     * it runs the creation of a new node
     * @param {Object} properties 
     * @param {Object} cb - callaback function
     */
    createNode: function (label, properties, cb) {
        var message = "succes"
        instance
            .model(label)
            .create(properties)
            .then(
                result => {
                    cb(message);
                },

                error => {
                    message = this.errorHandler(error);
                    cb(message);
                })
    }

(There you have the "User" model)

const User = {
    labels: ["User"],
    "name" : "name",
    "surname": "string",
    "email": "string",
    "password" : "string",
    "type" : "string",
};

Everything seems fine to me but on neo4j this is the result:

schermata 2019-02-25 alle 12 24 01

As you can see none of the properties gets set in the new node, like if I've just created an empty "User" node.
I tried to pass different object to "create()" and I also tried to use just "create()" without "model()" but nothing, the result doesn't change... am I missing something? can you give me some suggestion?

Incorrect type signature

The signature for Model.relationship method is incorrect. It's missing the type parameter.

relationship(name: string, relationship: string, direction?: Neode.Direction, target?: string | Model<T>, schema?: Neode.SchemaObject, eager?: boolean, cascade?: boolean | string): Relationship;

In addition to this, Relationship Types should be defined.

Submitting a PR for this.

If value on property is 0, Neode assumes it is null.

I'm trying to set a property on a relationship which measures distance. Funnily enough, some of the distances are 0 - i.e., a user willing to travel to his own home has to travel 0 miles to get there.

All works well except when a zero value is entered here, in which case it is stored correctly in the database, but it's NOT returned correctly from the neode OGM.

Here's an example:

const set = async (someNumber, nodeA, nodeB) => {
    return await nodeA.relateTo(nodeB, "someRelationship", {
        someProperty: someNumber
      });
}

This works fine unless someNumber === 0;

let ten = await set(10, nodeA, nodeB);
console.log(ten.get('someProperty')) // -> 10
let zero = await set(0, nodeA, nodeB);
console.log(zero.get('someProperty')) // -> null

Documentation for creating/working with Schemas/Models having multiple labels.

Hi,

After searching and comparing a lot of ORM/OGM wrappers around Neo4j, I came across this and it looks fantastic, just the right bunch of APIs and very close to the Neo4J concepts.

I am working on a project where for authentication, I need to support users from various authentication modes, e.g. Email/Password, Ldap, Oauth etc. For this, I have decided to use labels to identify the type, e.g. :User:Email, :User:Ldap, :User:OAuth etc. Looking at the code I can see that it is possible to set labels as an array on the Model. Maybe I missed this in the documentation.

Could you please point me to some examples related to defining a Schema which associates with multiple users? I see that schemas extend, and my user graph should be ideally extendible, e.g.

  1. Any User must have id, email and name.
  2. A User:Email will also have password.
  3. A User:Ldap will have domain and username.
  4. A Oauth user might have oauth specific properties like refreshtoken.

What would be the best way to model this using Neode?

Thanks!

Why "There are no properties set for this Node"?

I have

    instance
        .with({
            WorkBase: require('./models/WorkBase'),
        });

    instance.extend('WorkBase', 'OrgUnit', require('./models/OrgUnit'));

with WorkBase.ts:

module.exports = {

    id: {
        type: 'uuid',
        primary: true
    },
    name: {
        type: 'string',
        indexed: true,
        required: true
    }}

and OrgUnit.ts:

module.exports = {
    // EXTENDS: WorkBase

    owns: { // orgunit belongs to this orgunit
        type: 'relationship',
        relationship: 'owns',
        direction: 'out',
        target: 'OrgUnit',
        properties: {
            since: 'number',
        },
    },
    participate: { // orgunit participates in this orgunit
        type: 'relationship',
        relationship: 'participate',
        direction: 'out',
        target: 'OrgUnit',
        properties: {
            since: 'number',
        },
    }}

Why do I get the error on:
Neode.model('OrgUnit').create({id:1, name:'xyz'}).then()

Dependencies/Cascade Deletes

Raised on the Neo4j User's slack channel.

Issue:

Neo4j currently lacks a DBMS style cascade delete. On deleting a model, all dependencies should also be removed from the graph. For example, when a Movie is deleted, all subsequent actors should also be deleted.

MATCH (m:Movie)
OPTIONAL MATCH (m)<-[:ACTS_IN]-(a:Actor)
DETACH DELETE m, a

Proposed Change:

Add a dependancy: true property to the relationship. When this property is present and set to true, the OGM should cascade delete each dependency.

// movie.js
module.exports = {
  // ...
  actors: {
    type: "relationship",
    target: "Actor",
    relationship: "ACTS_IN",
    direction: "in",
    dependency: true
  }
}

Any comments or suggestions welcome.

Typescript: match expect model

I try example query builder:

const builder = instance.query();

    builder
      .match('p', 'Person')
      .return('p')
      .then(persons => console.log(persons));

But I got a ts error:
'Person' can be assigned to 'Model<{}>'

Doubts about defining attributes

Very good NPM package
I have a little problem.
Is there a GET SET method similar to mongoose for defining attributes?
Serialization and deserialization are required when dealing with JSON

[Bug] Neo4jError: Property values can only be of primitive types or arrays thereof

Ran into the error 'Neo4jError: Property values can only be of primitive types or arrays thereof' while attempting to create a node with a point.
The property shape is:

{
  createdAt: <default value of new Date().toISOString()>,
  location: {
    latitude: <float>,
    longitude: <float>,
  },
  userId: 1,
}

The code being ran is:

const result = await neo4j.model(CHECKIN).create({ ...args.data });

The query being executed is:

"CREATE
(this:Checkin { createdAt: $this_createdAt, id: $this_id, location: $this_location, userId: $this_userId })

RETURN

     this { 
        .*
        ,__EAGER_ID__: id(this)
        ,__EAGER_LABELS__: labels(this)
    }"

The param object is:
params

I'm still trying to debug, but I'm hoping you've encountered this issue before?

Neode.withDirectory gets crazy with Typescript

Typescript writes its .map files in the same directory as the compiled .js files. Please add a filter like:

var path = require('path');

{
key: 'withDirectory',
value: function withDirectory(directory) {
var _this2 = this;

        var files = _fs2.default.readdirSync(directory);

        files.filter(function(file) {
            return path.extname(file).toLowerCase() === '.js';
        });
        
        files.forEach(function (file) {
            var model = file.replace('.js', '');
            var path = directory + '/' + file;
            var schema = require(path);

            return _this2.model(model, schema);
        });

        return this;
    }

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.