5.0.1 • Published 3 months ago

@qualitech/qneo4j v5.0.1

Weekly downloads
62
License
Apache-2.0
Repository
github
Last release
3 months ago

qneo4j

A layer under the Neo4J driver with additional features to help queries, transaction and response parser.

Installation

Using npm:

$ npm i @qualitech/qneo4j

Usage

Create QNeo4j instance:

const QNeo4j = require('@qualitech/qneo4j')

// simplest
const db = new QNeo4j({
    url: 'bolt://localhost:7687'
})

// full options
const db = new QNeo4j({
    url: 'bolt://localhost:7687',
    username: 'neo4j',       // default: 'neo4j'
    password: 'admin',       // default: 'admin'
    
    // description: if true, returns raw value of the Neo4j.
    raw: false,              // default: false,

    // description: expects to receive a callback function. This callback is called every time an error occurs within the QNeo4j module.
    notifyError: (error, query) => console.log(error, query),
    
    // description: all configuration available to the driver Neo4j can be set here. See more https://neo4j.com/docs/driver-manual/current/client-applications/
    driverConfig: {
        // ... neo4j driver configuration
    }
})

Executing a cypher query "readTransaction"

Transaction functions are the recommended form for containing transactional units of work:

// Promise
db.readTransaction('MATCH (person:Person) RETURN person').then(result => {
    // todo something...
})

// async/await:
let result = await db.readTransaction('MATCH (person:Person) RETURN person')

The value of the execution result above would be:

[{ 
    person: { 
        name: 'Alice',
        age: 34 
    }
}, 
{
    person: { 
        name: 'Bob',
        age: 36
    }
}]

// where:
result[0].person.name === 'Alice'
result[1].person.name === 'Bob'

Executing multiple cypher queries with "readTransaction":

await db.readTransaction(async function (execute) {
    const res1 = await execute(`MATCH (p:Person {name: "Bob"}) return p'`)
    console.log(res1.name)
    const res2 = await execute(`MATCH (p:Person {name: "Ana"}) return p`)
   console.log(res2.name)
  })

Executing multiple cypher queries with "writeTransaction":

await db.writeTransaction(async function (execute) {
    const res1 = await execute(`CREATE (p:Person {name: "Bob"}) return p'`)
    console.log(res1.name)
    const res2 = await execute(`CREATE (p:Person {name: "Ana"}) return p`)
   console.log(res2.name)
  })

Executing a cypher query with its parameters:

const cypher = 'CREATE (p:Person {name: $name}) return p'
const params = { name: "Bob" }

db.writeTransaction({ cypher: cypher, params: params}).then(result => {
    // todo something...
})

Auto-commit transactions (or implicit transactions), used for 'CALL {} IN TRANSACTIONS': An auto-commit transaction, or implicit transaction, is a basic but limited form of transaction. Such a transaction consists of only one Cypher query and is not automatically retried on failure. Therefore, any error scenarios will need to be handled by the client application itself.

Auto-commit transactions serve two purposes:

  • simple use cases such as when learning Cypher or writing one-off scripts.
  • operations such as batched data load operations, where the driver cannot be aware of the committed state and therefore cannot safely request a retry. The operator will have to perform a retry or undo under these circumstances.
const cypher = 
        `MATCH (p:Person)
         CALL {
         WITH p
            set p.update = timestamp()
         }
         IN TRANSACTIONS OF 1000 ROWS`

let result = await db.execute(cypher)

Through a transaction scope:

db.transaction(async function (execute, tx){
    // todo something...
    
    let result = await execute('CREATE (p:Person {name: "Bob"}) return p')

    // todo something...

    let result2 = await execute('CREATE (p:Person {name: "Alice"}) return p')

    // todo something...
})
  • The transaction method returns a Promise;
  • If you want to return any value to Promise, simply return the value within the transaction scope, so the value is sent to the Promise;
  • At the end of the transaction scope, if all went well, the commit will be automatically performed, if there are unhandled exceptions the rollback is performed. You can also manually perfom the rollback using the given parameter tx by calling tx.rollback().

To get the raw return of Neo4j just apply some of the settings:

// all queries will return the raw result
let db = new QNeo4j({
    raw: true
    // ... other options
})

// OR - only raw result
db.execute('MATCH (person:Person) RETURN person', { raw: QNeo4j.RETURN_TYPES.RAW })

// OR - raw result and parsed together
db.execute('MATCH (person:Person) RETURN person', { raw: QNeo4j.RETURN_TYPES.PARSER_RAW })

Transaction functions

Transaction functions are the recommended form for containing transactional units of work. This form of transaction requires minimal boilerplate code and allows for a clear separation of database queries and application logic. Transaction functions are also desirable since they encapsulate retry logic and allow for the greatest degree of flexibility when swapping out a single instance of server for a cluster.

Functions can be called as either read or write operations. This choice will route the transaction to an appropriate server within a clustered environment. If you are in a single instance environment, this routing has no impact but it does give you the flexibility should you choose to later adopt a clustered environment.

Before writing a transaction function it is important to ensure that any side-effects carried out by a transaction function should be designed to be idempotent. This is because a function may be executed multiple times if initial runs fail.

Any query results obtained within a transaction function should be consumed within that function, as connection-bound resources cannot be managed correctly when out of scope. To that end, transaction functions can return values but these should be derived values rather than raw results.

When a transaction fails, the driver retry logic is invoked. For several failure cases, the transaction can be immediately retried against a different server.

These cases include connection issues, server role changes (e.g. leadership elections) and transient errors. Retry logic can be configured when creating a session.

Methods

createDriver

Creates a Neo4j driver with the informed authentication when instantiating the QNeo4j class. The createDriver method is used internally.

execute

Executes a cypher query and returns a Promise with the query result, is a basic but limited form of transaction. See the examples that were mentioned in the documentation above.

readTransaction - Transaction functions

Executes a cypher query with transaction (read) and returns a Promise with the query result, is a basic but limited form of transaction. See the examples that were mentioned in the documentation above.

writeTransaction - Transaction functions

Executes a cypher query with transaction (write) and returns a Promise with the query result, is a basic but limited form of transaction. See the examples that were mentioned in the documentation above.

transaction

Can executes various operations within a transactional scope, when this scope is completed commit is performed otherwise, if there are unhandled exceptions the rollback is performed. See the examples that were mentioned in the documentation above.

updateOptions

Update the QNeo4j instance options

let db = new QNeo4j()

db.updateOptions({
    url: 'bolt://localhost:7687',
    username: 'neo4j',       
    password: 'admin',       
    raw: false,              
    notifyError: (error, query) => console.log(error, query),
})

Other Resources

Result Class

In some cases we will have the Result class as return from an execute method, such as when we return the raw and parsed result, this data is available through the rawResult properties and value.

db.execute('MATCH (person:Person) RETURN person', { raw: QNeo4j.RETURN_TYPES.PARSER_RAW })
    .then(result => {
        console.log(result instanceOf QNeo4j.Result) // true
		console.log(result.rawResult) 	// raw result of the Neo4j
        console.log(result.value)       // parsed result
    })

RETURN_TYPES constant

The RETURN_TYPES constant is available on QNeo4j.RETURN_TYPES, it contains the options that specify how it will be the return type (raw, parsed or both).

const QNeo4j = require('@qualitech/qneo4j')
const { RETURN_TYPES } = QNeo4j

console.log(RETURN_TYPES)
// {
//     PARSER: 0,
//     PARSER_RAW: 1,
//     RAW: 2
// }

Module QNeo4jHelper

The qneo4j-helper module is accessible from QNeo4j.helper and provides useful methods that help you work with cypher query and neo4j driver.

Notes

  • From version 4.1 the driver is automatically closed when the node process exits
  • From version 5.0: Updated the neo4j native driver to version "5.17.0"

QNeo4jHelper

This module focuses on making using Neo4j for JavaScript easier by making its coding cleaner and shorter.

  • Functions for date conversions: - Neo4j date to native javascript date; - Neo4j date to momentjs; - date in string format to Neo4j date and also cypher syntax; - native javascript date to Neo4j date and also cypher syntax; - momentjs to Neo4j date and also cypher syntax;
  • Parse Neo4j response records to simple object;
  • Transforms objects in string to help create cypher queries;

Usage

Object to string:

let obj = {
    prop1: "value",
    prop2: "value2"
}
helper.objToString(obj) // returns '{prop1:"value",prop2:"value2"}'

Object to params:

let obj = {
    prop1: "value",
    prop2: "value2"
}
helper.objToParams("prefix", obj) // returns '{prefix.prop1:"value",prefix.prop2:"value2"}'

Checks if the value is a neo4j date type:

let date = "2019/02/08"
helper.isDateTypeNeo4j(date) // returns false

let date2 = neo4j.types.DateTime.fromStandardDate(new Date())
helper.isDateTypeNeo4j(date2) // returns true

Converts a Neo4j date to a native JavaScript date

let dateNeo4j = neo4j.types.DateTime.fromStandardDate(new Date())
let date = helper.toStandardDate(dateNeo4j) // returns a date object 
date instanceof Date // returns true

Converts a Neo4j date to a moment object

let dateNeo4j = neo4j.types.DateTime.fromStandardDate(new Date())
let date = helper.toMoment(dateNeo4j) // returns a moment object 
date instanceof moment // returns true

Parse any date (string, native js, moment, neo4j, number) to Neo4j date:

let dateToParse = "30/07/2019" // DD/MM/YYYY
// or
let dateToParse = new Date(2019, 6, 30) // native JavaScript date
// or
let dateToParse = moment("30/07/2019") // moment date
// or
let dateToParse = neo4j.types.DateTime.fromStandardDate(new Date(2019, 6, 30)) // Neo4j date

// THEN:
let date = helper.parseDate(dateSource) // returns a Neo4j LocalDateTime

// more options...
let dateToParse = 201907 // any format that can be informed, format: YYYYMM
let date = helper.parseDate(dateToParse, DATE_TYPE.LOCAL_DATE_TIME, "YYYYMM") // returns "Date('2019-07-01')"
// returns a Neo4j LocalDateTime

Its possible to inform the Neo4j date type to return, the options are:

  • LOCAL_TIME
  • TIME
  • DATE
  • LOCAL_DATE_TIME
  • DATE_TIME
const { DATE_TYPE } = helper
let date = helper.parseDate(dateSource, DATE_TYPE.DATE) // returns a Neo4j Date

Parse any date (string, native js, moment, neo4j, number) to cypher syntax:

let dateToParse = "30/07/2019" // DD/MM/YYYY
// or
let dateToParse = new Date(2019, 6, 30) // native JavaScript date
// or
let dateToParse = moment("30/07/2019") // moment date
// or
let dateToParse = neo4j.types.DateTime.fromStandardDate(new Date(2019, 6, 30)) // Neo4j date
// or
let dateToParse = Date.now() // timestamp

// THEN:
let date = helper.parseDateCypher(dateSource) // returns "LocalDateTime('2019-07-30T00:00:00.000Z')"

// its possible to inform the Neo4j date type to return
let date = helper.parseDateCypher(dateSource, DATE_TYPE.DATE) // returns "Date('2019-07-30')"

// more options...
let dateToParse = 201907 // any format that can be informed, format: YYYYMM
let date = helper.parseDateCypher(dateToParse, DATE_TYPE.DATE, "YYYYMM") // returns "Date('2019-07-01')"

Its possible to inform the Neo4j date type to return, the options are:

  • LOCAL_TIME
  • TIME
  • DATE
  • LOCAL_DATE_TIME
  • DATE_TIME
const { DATE_TYPE } = helper
let date = helper.parseDate(dateSource, DATE_TYPE.DATE) // returns a Neo4j Date

Parse the Neo4j response to a json structure:

// Original Neo4j Result:
Record {
    keys: [ 'myNode' ],
    length: 1,
    _fields: [ { prop2: 'value2', prop1: 'value1' } ],
    _fieldLookup: { myNode: 0 }
}

// Parsed Result:
{ 
    myNode: { 
        prop2: 'value2',
        prop1: 'value1'
    }
}
const driver = neo4j.driver(/*auth and options*/)
const session = driver.session();

let result = await session.run(`return { prop1: "value1", prop2: "value2" } as myNode`)
let parsed = helper.parseResponse(result)

console.log(parsed.myNode.prop2) // returns "value2"
5.0.1

3 months ago

4.8.2

1 year ago

4.8.1

2 years ago

4.8.0

2 years ago

4.7.0

2 years ago

4.6.0

2 years ago

4.4.1

3 years ago

4.5.0

3 years ago

4.4.0

3 years ago

4.1.4

3 years ago

4.1.3

3 years ago

4.1.2

4 years ago

4.1.1

4 years ago

4.1.0

4 years ago

4.0.0

4 years ago

0.6.1

4 years ago

0.6.0

4 years ago

0.5.0

4 years ago

0.4.0

4 years ago

0.3.1

4 years ago

0.3.0

4 years ago

0.2.7

4 years ago

0.2.6

4 years ago

0.2.5

4 years ago

0.2.4

4 years ago

0.2.3

4 years ago

0.2.2

4 years ago

0.2.1

4 years ago

0.2.0

4 years ago

0.1.1

5 years ago

0.1.0

5 years ago

0.0.3

5 years ago

0.0.2

5 years ago

0.0.1

5 years ago