diff --git a/README.md b/README.md index fe8b36361..05d5dd4dd 100644 --- a/README.md +++ b/README.md @@ -30,20 +30,14 @@ Stable channel: npm install neo4j-driver ``` -Pre-release channel: - -```shell -npm install neo4j-driver@next -``` - -Please note that `@next` only points to pre-releases that are not suitable for production use. -To get the latest stable release omit `@next` part altogether or use `@latest` instead. - ```javascript +// If you are using CommonJS var neo4j = require('neo4j-driver') +// Alternatively, if you are using ES6 +import neo4j from 'neo4j-driver' ``` -Driver instance should be closed when Node.js application exits: +Driver instance should be closed when the application exits: ```javascript driver.close() // returns a Promise @@ -225,7 +219,7 @@ readTxResultPromise .catch(error => { console.log(error) }) - .then(() => session.close()) + .finally(() => session.close()) ``` #### Reading with Reactive Session @@ -269,7 +263,7 @@ writeTxResultPromise .catch(error => { console.log(error) }) - .then(() => session.close()) + .finally(() => session.close()) ``` #### Writing with Reactive Session @@ -289,74 +283,62 @@ rxSession }) ``` -### Consuming Records - -#### Consuming Records with Streaming API +### ExecuteQuery Function ```javascript -// Run a Cypher statement, reading the result in a streaming manner as records arrive: -session - .run('MERGE (alice:Person {name : $nameParam}) RETURN alice.name AS name', { - nameParam: 'Alice' - }) - .subscribe({ - onKeys: keys => { - console.log(keys) - }, - onNext: record => { - console.log(record.get('name')) - }, - onCompleted: () => { - session.close() // returns a Promise - }, - onError: error => { - console.log(error) +// Since 5.8.0, the driver has offered a way to run a single query transaction with minimal boilerplate. +// The driver.executeQuery() function features the same automatic retries as transaction functions. +// +var executeQueryResultPromise = driver + .executeQuery( + "MATCH (alice:Person {name: $nameParam}) RETURN alice.DOB AS DateOfBirth", + { + nameParam: 'Alice' + }, + { + routing: 'READ', + database: 'neo4j' } - }) -``` - -Subscriber API allows following combinations of `onKeys`, `onNext`, `onCompleted` and `onError` callback invocations: - -- zero or one `onKeys`, -- zero or more `onNext` followed by `onCompleted` when operation was successful. `onError` will not be invoked in this case -- zero or more `onNext` followed by `onError` when operation failed. Callback `onError` might be invoked after couple `onNext` invocations because records are streamed lazily by the database. `onCompleted` will not be invoked in this case. - -#### Consuming Records with Promise API + ) -```javascript -// the Promise way, where the complete result is collected before we act on it: -session - .run('MERGE (james:Person {name : $nameParam}) RETURN james.name AS name', { - nameParam: 'James' - }) +// returned Promise can be later consumed like this: +executeQueryResultPromise .then(result => { - result.records.forEach(record => { - console.log(record.get('name')) - }) + console.log(result.records) }) .catch(error => { console.log(error) }) - .then(() => session.close()) ``` -#### Consuming Records with Reactive API +### Auto-Commit/Implicit Transaction ```javascript -rxSession - .run('MERGE (james:Person {name: $nameParam}) RETURN james.name AS name', { - nameParam: 'Bob' - }) - .records() - .pipe( - map(record => record.get('name')), - concatWith(rxSession.close()) +// This is the most basic and limited form with which to run a Cypher query. +// The driver will not automatically retry implicit transactions. +// This function should only be used when the other driver query interfaces do not fit the purpose. +// Implicit transactions are the only ones that can be used for CALL { …​ } IN TRANSACTIONS queries. + +var implicitTxResultPromise = session + .run( + "CALL { …​ } IN TRANSACTIONS", + { + param1: 'param' + }, + { + database: 'neo4j' + } ) - .subscribe({ - next: data => console.log(data), - complete: () => console.log('completed'), - error: err => console.log(err) + +// returned Promise can be later consumed like this: +implicitTxResultPromise + .then(result => { + console.log(result.records) }) + .catch(error => { + console.log(error) + }) + .finally(() => session.close()) ``` ### Explicit Transactions @@ -403,7 +385,7 @@ rxSession .beginTransaction() .pipe( mergeMap(txc => - concatWith( + concat( txc .run( 'MERGE (bob:Person {name: $nameParam}) RETURN bob.name AS name', @@ -436,6 +418,76 @@ rxSession }) ``` +### Consuming Records + +#### Consuming Records with Streaming API + +```javascript +// Run a Cypher statement, reading the result in a streaming manner as records arrive: +session + .executeWrite(tx => tx.run('MERGE (alice:Person {name : $nameParam}) RETURN alice.name AS name', { + nameParam: 'Alice' + }).subscribe({ + onKeys: keys => { + console.log(keys) + }, + onNext: record => { + console.log(record.get('name')) + }, + onCompleted: () => { + session.close() // returns a Promise + }, + onError: error => { + console.log(error) + } + }) +) +``` + +Subscriber API allows following combinations of `onKeys`, `onNext`, `onCompleted` and `onError` callback invocations: + +- zero or one `onKeys`, +- zero or more `onNext` followed by `onCompleted` when operation was successful. `onError` will not be invoked in this case +- zero or more `onNext` followed by `onError` when operation failed. Callback `onError` might be invoked after couple `onNext` invocations because records are streamed lazily by the database. `onCompleted` will not be invoked in this case. + +#### Consuming Records with Promise API + +```javascript +// the Promise way, where the complete result is collected before we act on it: +driver + .executeQuery('MERGE (james:Person {name : $nameParam}) RETURN james.name AS name', { + nameParam: 'James' + }) + .then(result => { + result.records.forEach(record => { + console.log(record.get('name')) + }) + }) + .catch(error => { + console.log(error) + }) + .then(() => driver.close()) +``` + +#### Consuming Records with Reactive API + +```javascript +rxSession + .run('MERGE (james:Person {name: $nameParam}) RETURN james.name AS name', { + nameParam: 'Bob' + }) + .records() + .pipe( + map(record => record.get('name')), + concatWith(rxSession.close()) + ) + .subscribe({ + next: data => console.log(data), + complete: () => console.log('completed'), + error: err => console.log(err) + }) +``` + ### Numbers and the Integer type The Neo4j type system uses 64-bit signed integer values. The range of values is between `-(2``64``- 1)` and `(2``63``- 1)`. @@ -449,20 +501,20 @@ _**Any javascript number value passed as a parameter will be recognized as `Floa #### Writing integers -Numbers written directly e.g. `session.run("CREATE (n:Node {age: $age})", {age: 22})` will be of type `Float` in Neo4j. +Numbers written directly e.g. `driver.executeQuery("CREATE (n:Node {age: $age})", {age: 22})` will be of type `Float` in Neo4j. To write the `age` as an integer the `neo4j.int` method should be used: ```javascript var neo4j = require('neo4j-driver') -session.run('CREATE (n {age: $myIntParam})', { myIntParam: neo4j.int(22) }) +driver.executeQuery('CREATE (n {age: $myIntParam})', { myIntParam: neo4j.int(22) }) ``` To write an integer value that are not within the range of `Number.MIN_SAFE_INTEGER` `-(2``53``- 1)` and `Number.MAX_SAFE_INTEGER` `(2``53``- 1)`, use a string argument to `neo4j.int`: ```javascript -session.run('CREATE (n {age: $myIntParam})', { +driver.executeQuery('CREATE (n {age: $myIntParam})', { myIntParam: neo4j.int('9223372036854775807') }) ``` diff --git a/packages/bolt-connection/README.md b/packages/bolt-connection/README.md index 84bd1b164..965a55c10 100644 --- a/packages/bolt-connection/README.md +++ b/packages/bolt-connection/README.md @@ -10,14 +10,13 @@ The build of this package is handled by the root package of this repository. First it is needed to install the mono-repo dependencies by running `npm ci` in the root of the repository. Then: -* Build all could be performed with +* Building the whole repository can be performed with ``` npm run build ``` -* Build only the Core could be performed with -Builind only Core: +* Building only the bolt-connection can be performed with ``` npm run build -- --scope=neo4j-driver-bolt-connection @@ -27,7 +26,7 @@ This produces a Node.js module version under `lib/`. ## Testing -The tests could be executed by running `npm test` in this package folder. For development, you can have the build tool rerun the tests each time you change the source code: +The tests can be executed by running `npm test` in this package folder. For development, you can have the build tool rerun the tests each time you change the source code: ``` npm run test::watch diff --git a/packages/core/README.md b/packages/core/README.md index 94d8a71a7..abd96b70f 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -10,14 +10,15 @@ The build of this package is handled by the root package of this repository. First it is needed to install the mono-repo dependencies by running `npm ci` in the root of the repository. Then: -* Build all could be performed with +* Building the whole repository can be performed with ``` npm run build ``` -* Build only the Core could be performed with -Builind only Core: +* Building only the Core can be performed with + + ``` npm run build -- --scope=neo4j-driver-core @@ -27,7 +28,7 @@ This produces a Node.js module version under `lib/`. ## Testing -The tests could be executed by running `npm test` in this package folder. For development, you can have the build tool rerun the tests each time you change the source code: +The tests can be executed by running `npm test` in this package folder. For development, you can have the build tool rerun the tests each time you change the source code: ``` npm run test::watch diff --git a/packages/neo4j-driver-deno/README.md b/packages/neo4j-driver-deno/README.md index dc836e169..943ea8dd4 100644 --- a/packages/neo4j-driver-deno/README.md +++ b/packages/neo4j-driver-deno/README.md @@ -49,17 +49,263 @@ For using system certificates, the `DENO_TLS_CA_STORE` should be set to `"system Client certificates are not support in this version of the driver since there is no support for this feature in the DenoJS API. See, https://deno.land/api@v1.29.0?s=Deno.ConnectTlsOptions. -### Basic Example +## Usage examples -```typescript -const URI = "bolt://localhost:7687"; -const driver = neo4j.driver(URI, neo4j.auth.basic("neo4j", "driverdemo")); +### Constructing a Driver + +```javascript +// Create a driver instance, for the user `neo4j` with password `password`. +// It should be enough to have a single driver per database per application. +var driver = neo4j.driver( + 'neo4j://localhost', + neo4j.auth.basic('neo4j', 'password') +) + +// Close the driver when application exits. +// This closes all used network connections. +await driver.close() +``` + +### Acquiring a Session + +#### Regular Session + +```javascript +// Create a session to run Cypher statements in. +// Note: Always make sure to close sessions when you are done using them! +var session = driver.session() +``` + +##### with a Default Access Mode of `READ` + +```javascript +var session = driver.session({ defaultAccessMode: neo4j.session.READ }) +``` + +##### with Bookmarks + +```javascript +var session = driver.session({ + bookmarks: [bookmark1FromPreviousSession, bookmark2FromPreviousSession] +}) +``` + +##### against a Database + +```javascript +var session = driver.session({ + database: 'foo', + defaultAccessMode: neo4j.session.WRITE +}) +``` + +### Transaction functions + +```javascript +// Transaction functions provide a convenient API with minimal boilerplate and +// retries on network fluctuations and transient errors. Maximum retry time is +// configured on the driver level and is 30 seconds by default: +// Applies both to standard and reactive sessions. +neo4j.driver('neo4j://localhost', neo4j.auth.basic('neo4j', 'password'), { + maxTransactionRetryTime: 30000 +}) +``` -const { records } = await driver.executeQuery("MATCH (n) RETURN n LIMIT 25"); -console.log(records); +#### Reading with transaction functions -await driver.close(); +```javascript +// It is possible to execute read transactions that will benefit from automatic +// retries on both single instance ('bolt' URI scheme) and Causal Cluster +// ('neo4j' URI scheme) and will get automatic load balancing in cluster deployments +var readTxResultPromise = session.executeRead(txc => { + // used transaction will be committed automatically, no need for explicit commit/rollback + + var result = txc.run('MATCH (person:Person) RETURN person.name AS name') + // at this point it is possible to either return the result or process it and return the + // result of processing it is also possible to run more statements in the same transaction + return result +}) +// returned Promise can be later consumed like this: +readTxResultPromise + .then(result => { + console.log(result.records) + }) + .catch(error => { + console.log(error) + }) + .finally(() => session.close()) +``` + +#### Writing with transaction functions + +```javascript +// It is possible to execute write transactions that will benefit from automatic retries +// on both single instance ('bolt' URI scheme) and Causal Cluster ('neo4j' URI scheme) +var writeTxResultPromise = session.executeWrite(async txc => { + // used transaction will be committed automatically, no need for explicit commit/rollback + + var result = await txc.run( + "MERGE (alice:Person {name : 'Alice'}) RETURN alice.name AS name" + ) + // at this point it is possible to either return the result or process it and return the + // result of processing it is also possible to run more statements in the same transaction + return result.records.map(record => record.get('name')) +}) + +// returned Promise can be later consumed like this: +writeTxResultPromise + .then(namesArray => { + console.log(namesArray) + }) + .catch(error => { + console.log(error) + }) + .finally(() => session.close()) +``` + +### ExecuteQuery Function + +```javascript +// Since 5.8.0, the driver has offered a way to run a single query transaction with minimal boilerplate. +// The driver.executeQuery() function features the same automatic retries as transaction functions. +// +var executeQueryResultPromise = driver + .executeQuery( + "MATCH (alice:Person {name: $nameParam}) RETURN alice.DOB AS DateOfBirth", + { + nameParam: 'Alice' + }, + { + routing: 'READ', + database: 'neo4j' + } + ) + +// returned Promise can be later consumed like this: +executeQueryResultPromise + .then(result => { + console.log(result.records) + }) + .catch(error => { + console.log(error) + }) +``` + +### Auto-Commit/Implicit Transaction + +```javascript +// This is the most basic and limited form with which to run a Cypher query. +// The driver will not automatically retry implicit transactions. +// This function should only be used when the other driver query interfaces do not fit the purpose. +// Implicit transactions are the only ones that can be used for CALL { …​ } IN TRANSACTIONS queries. + +var implicitTxResultPromise = session + .run( + "CALL { …​ } IN TRANSACTIONS", + { + param1: 'param' + }, + { + database: 'neo4j' + } + ) + +// returned Promise can be later consumed like this: +implicitTxResultPromise + .then(result => { + console.log(result.records) + }) + .catch(error => { + console.log(error) + }) + .finally(() => session.close()) +``` + +### Explicit Transactions + +```javascript +// run statement in a transaction +const txc = session.beginTransaction() +try { + const result1 = await txc.run( + 'MERGE (bob:Person {name: $nameParam}) RETURN bob.name AS name', + { + nameParam: 'Bob' + } + ) + result1.records.forEach(r => console.log(r.get('name'))) + console.log('First query completed') + + const result2 = await txc.run( + 'MERGE (adam:Person {name: $nameParam}) RETURN adam.name AS name', + { + nameParam: 'Adam' + } + ) + result2.records.forEach(r => console.log(r.get('name'))) + console.log('Second query completed') + + await txc.commit() + console.log('committed') +} catch (error) { + console.log(error) + await txc.rollback() + console.log('rolled back') +} finally { + await session.close() +} +``` + +### Consuming Records + +#### Consuming Records with Streaming API + +```javascript +// Run a Cypher statement, reading the result in a streaming manner as records arrive: +session + .executeWrite(tx => tx.run('MERGE (alice:Person {name : $nameParam}) RETURN alice.name AS name', { + nameParam: 'Alice' + }).subscribe({ + onKeys: keys => { + console.log(keys) + }, + onNext: record => { + console.log(record.get('name')) + }, + onCompleted: () => { + session.close() // returns a Promise + }, + onError: error => { + console.log(error) + } + }) +) +``` + +Subscriber API allows following combinations of `onKeys`, `onNext`, `onCompleted` and `onError` callback invocations: + +- zero or one `onKeys`, +- zero or more `onNext` followed by `onCompleted` when operation was successful. `onError` will not be invoked in this case +- zero or more `onNext` followed by `onError` when operation failed. Callback `onError` might be invoked after couple `onNext` invocations because records are streamed lazily by the database. `onCompleted` will not be invoked in this case. + +#### Consuming Records with Promise API + +```javascript +// the Promise way, where the complete result is collected before we act on it: +driver + .executeQuery('MERGE (james:Person {name : $nameParam}) RETURN james.name AS name', { + nameParam: 'James' + }) + .then(result => { + result.records.forEach(record => { + console.log(record.get('name')) + }) + }) + .catch(error => { + console.log(error) + }) + .then(() => driver.close()) ``` ### Numbers and the Integer type @@ -75,18 +321,18 @@ _**Any javascript number value passed as a parameter will be recognized as `Floa #### Writing integers -Numbers written directly e.g. `session.run("CREATE (n:Node {age: $age})", {age: 22})` will be of type `Float` in Neo4j. +Numbers written directly e.g. `driver.executeQuery("CREATE (n:Node {age: $age})", {age: 22})` will be of type `Float` in Neo4j. To write the `age` as an integer the `neo4j.int` method should be used: ```javascript -session.run('CREATE (n {age: $myIntParam})', { myIntParam: neo4j.int(22) }) +driver.executeQuery('CREATE (n {age: $myIntParam})', { myIntParam: neo4j.int(22) }) ``` To write an integer value that are not within the range of `Number.MIN_SAFE_INTEGER` `-(2``53``- 1)` and `Number.MAX_SAFE_INTEGER` `(2``53``- 1)`, use a string argument to `neo4j.int`: ```javascript -session.run('CREATE (n {age: $myIntParam})', { +driver.executeQuery('CREATE (n {age: $myIntParam})', { myIntParam: neo4j.int('9223372036854775807') }) ``` @@ -130,3 +376,26 @@ var driver = neo4j.driver( ) ``` +#### Writing and reading Vectors + +Neo4j supports storing vector embeddings in a dedicated vector type. Sending large lists with the driver will result in significant overhead as each value will be transmitted with type information, so the 6.0.0 release of the driver introduced the Neo4j Vector type. + +The Vector type supports signed integers of 8, 16, 32 and 64 bits, and floats of 32 and 64 bits. The Vector type is a wrapper for JavaScript TypedArrays of those types. + +To create a neo4j Vector in your code, do the following: + +```javascript +var typedArray = Float32Array.from([1, 2, 3]) //this is how to convert a regular array of numbers into a TypedArray, useful if you handle vectors as regular arrays in your code + +var neo4jVector = neo4j.vector(typedArray) //this creates a neo4j Vector of type Float32, containing the values [1, 2, 3] + +driver.executeQuery('CREATE (n {embeddings: $myVectorParam})', { myVectorParam: neo4jVector }) +``` + +To access the data in a retrieved Vector you can do the following: + +```javascript +var retrievedTypedArray = neo4jVector.asTypedArray() //This will return a TypedArray of the same type as the Vector + +var retrievedArray = Array.from(retrievedTypedArray) //This will convert the TypedArray to a regular array of Numbers. (Not safe for Int64 arrays) +``` diff --git a/packages/neo4j-driver-deno/lib/README.md b/packages/neo4j-driver-deno/lib/README.md index dc836e169..943ea8dd4 100644 --- a/packages/neo4j-driver-deno/lib/README.md +++ b/packages/neo4j-driver-deno/lib/README.md @@ -49,17 +49,263 @@ For using system certificates, the `DENO_TLS_CA_STORE` should be set to `"system Client certificates are not support in this version of the driver since there is no support for this feature in the DenoJS API. See, https://deno.land/api@v1.29.0?s=Deno.ConnectTlsOptions. -### Basic Example +## Usage examples -```typescript -const URI = "bolt://localhost:7687"; -const driver = neo4j.driver(URI, neo4j.auth.basic("neo4j", "driverdemo")); +### Constructing a Driver + +```javascript +// Create a driver instance, for the user `neo4j` with password `password`. +// It should be enough to have a single driver per database per application. +var driver = neo4j.driver( + 'neo4j://localhost', + neo4j.auth.basic('neo4j', 'password') +) + +// Close the driver when application exits. +// This closes all used network connections. +await driver.close() +``` + +### Acquiring a Session + +#### Regular Session + +```javascript +// Create a session to run Cypher statements in. +// Note: Always make sure to close sessions when you are done using them! +var session = driver.session() +``` + +##### with a Default Access Mode of `READ` + +```javascript +var session = driver.session({ defaultAccessMode: neo4j.session.READ }) +``` + +##### with Bookmarks + +```javascript +var session = driver.session({ + bookmarks: [bookmark1FromPreviousSession, bookmark2FromPreviousSession] +}) +``` + +##### against a Database + +```javascript +var session = driver.session({ + database: 'foo', + defaultAccessMode: neo4j.session.WRITE +}) +``` + +### Transaction functions + +```javascript +// Transaction functions provide a convenient API with minimal boilerplate and +// retries on network fluctuations and transient errors. Maximum retry time is +// configured on the driver level and is 30 seconds by default: +// Applies both to standard and reactive sessions. +neo4j.driver('neo4j://localhost', neo4j.auth.basic('neo4j', 'password'), { + maxTransactionRetryTime: 30000 +}) +``` -const { records } = await driver.executeQuery("MATCH (n) RETURN n LIMIT 25"); -console.log(records); +#### Reading with transaction functions -await driver.close(); +```javascript +// It is possible to execute read transactions that will benefit from automatic +// retries on both single instance ('bolt' URI scheme) and Causal Cluster +// ('neo4j' URI scheme) and will get automatic load balancing in cluster deployments +var readTxResultPromise = session.executeRead(txc => { + // used transaction will be committed automatically, no need for explicit commit/rollback + + var result = txc.run('MATCH (person:Person) RETURN person.name AS name') + // at this point it is possible to either return the result or process it and return the + // result of processing it is also possible to run more statements in the same transaction + return result +}) +// returned Promise can be later consumed like this: +readTxResultPromise + .then(result => { + console.log(result.records) + }) + .catch(error => { + console.log(error) + }) + .finally(() => session.close()) +``` + +#### Writing with transaction functions + +```javascript +// It is possible to execute write transactions that will benefit from automatic retries +// on both single instance ('bolt' URI scheme) and Causal Cluster ('neo4j' URI scheme) +var writeTxResultPromise = session.executeWrite(async txc => { + // used transaction will be committed automatically, no need for explicit commit/rollback + + var result = await txc.run( + "MERGE (alice:Person {name : 'Alice'}) RETURN alice.name AS name" + ) + // at this point it is possible to either return the result or process it and return the + // result of processing it is also possible to run more statements in the same transaction + return result.records.map(record => record.get('name')) +}) + +// returned Promise can be later consumed like this: +writeTxResultPromise + .then(namesArray => { + console.log(namesArray) + }) + .catch(error => { + console.log(error) + }) + .finally(() => session.close()) +``` + +### ExecuteQuery Function + +```javascript +// Since 5.8.0, the driver has offered a way to run a single query transaction with minimal boilerplate. +// The driver.executeQuery() function features the same automatic retries as transaction functions. +// +var executeQueryResultPromise = driver + .executeQuery( + "MATCH (alice:Person {name: $nameParam}) RETURN alice.DOB AS DateOfBirth", + { + nameParam: 'Alice' + }, + { + routing: 'READ', + database: 'neo4j' + } + ) + +// returned Promise can be later consumed like this: +executeQueryResultPromise + .then(result => { + console.log(result.records) + }) + .catch(error => { + console.log(error) + }) +``` + +### Auto-Commit/Implicit Transaction + +```javascript +// This is the most basic and limited form with which to run a Cypher query. +// The driver will not automatically retry implicit transactions. +// This function should only be used when the other driver query interfaces do not fit the purpose. +// Implicit transactions are the only ones that can be used for CALL { …​ } IN TRANSACTIONS queries. + +var implicitTxResultPromise = session + .run( + "CALL { …​ } IN TRANSACTIONS", + { + param1: 'param' + }, + { + database: 'neo4j' + } + ) + +// returned Promise can be later consumed like this: +implicitTxResultPromise + .then(result => { + console.log(result.records) + }) + .catch(error => { + console.log(error) + }) + .finally(() => session.close()) +``` + +### Explicit Transactions + +```javascript +// run statement in a transaction +const txc = session.beginTransaction() +try { + const result1 = await txc.run( + 'MERGE (bob:Person {name: $nameParam}) RETURN bob.name AS name', + { + nameParam: 'Bob' + } + ) + result1.records.forEach(r => console.log(r.get('name'))) + console.log('First query completed') + + const result2 = await txc.run( + 'MERGE (adam:Person {name: $nameParam}) RETURN adam.name AS name', + { + nameParam: 'Adam' + } + ) + result2.records.forEach(r => console.log(r.get('name'))) + console.log('Second query completed') + + await txc.commit() + console.log('committed') +} catch (error) { + console.log(error) + await txc.rollback() + console.log('rolled back') +} finally { + await session.close() +} +``` + +### Consuming Records + +#### Consuming Records with Streaming API + +```javascript +// Run a Cypher statement, reading the result in a streaming manner as records arrive: +session + .executeWrite(tx => tx.run('MERGE (alice:Person {name : $nameParam}) RETURN alice.name AS name', { + nameParam: 'Alice' + }).subscribe({ + onKeys: keys => { + console.log(keys) + }, + onNext: record => { + console.log(record.get('name')) + }, + onCompleted: () => { + session.close() // returns a Promise + }, + onError: error => { + console.log(error) + } + }) +) +``` + +Subscriber API allows following combinations of `onKeys`, `onNext`, `onCompleted` and `onError` callback invocations: + +- zero or one `onKeys`, +- zero or more `onNext` followed by `onCompleted` when operation was successful. `onError` will not be invoked in this case +- zero or more `onNext` followed by `onError` when operation failed. Callback `onError` might be invoked after couple `onNext` invocations because records are streamed lazily by the database. `onCompleted` will not be invoked in this case. + +#### Consuming Records with Promise API + +```javascript +// the Promise way, where the complete result is collected before we act on it: +driver + .executeQuery('MERGE (james:Person {name : $nameParam}) RETURN james.name AS name', { + nameParam: 'James' + }) + .then(result => { + result.records.forEach(record => { + console.log(record.get('name')) + }) + }) + .catch(error => { + console.log(error) + }) + .then(() => driver.close()) ``` ### Numbers and the Integer type @@ -75,18 +321,18 @@ _**Any javascript number value passed as a parameter will be recognized as `Floa #### Writing integers -Numbers written directly e.g. `session.run("CREATE (n:Node {age: $age})", {age: 22})` will be of type `Float` in Neo4j. +Numbers written directly e.g. `driver.executeQuery("CREATE (n:Node {age: $age})", {age: 22})` will be of type `Float` in Neo4j. To write the `age` as an integer the `neo4j.int` method should be used: ```javascript -session.run('CREATE (n {age: $myIntParam})', { myIntParam: neo4j.int(22) }) +driver.executeQuery('CREATE (n {age: $myIntParam})', { myIntParam: neo4j.int(22) }) ``` To write an integer value that are not within the range of `Number.MIN_SAFE_INTEGER` `-(2``53``- 1)` and `Number.MAX_SAFE_INTEGER` `(2``53``- 1)`, use a string argument to `neo4j.int`: ```javascript -session.run('CREATE (n {age: $myIntParam})', { +driver.executeQuery('CREATE (n {age: $myIntParam})', { myIntParam: neo4j.int('9223372036854775807') }) ``` @@ -130,3 +376,26 @@ var driver = neo4j.driver( ) ``` +#### Writing and reading Vectors + +Neo4j supports storing vector embeddings in a dedicated vector type. Sending large lists with the driver will result in significant overhead as each value will be transmitted with type information, so the 6.0.0 release of the driver introduced the Neo4j Vector type. + +The Vector type supports signed integers of 8, 16, 32 and 64 bits, and floats of 32 and 64 bits. The Vector type is a wrapper for JavaScript TypedArrays of those types. + +To create a neo4j Vector in your code, do the following: + +```javascript +var typedArray = Float32Array.from([1, 2, 3]) //this is how to convert a regular array of numbers into a TypedArray, useful if you handle vectors as regular arrays in your code + +var neo4jVector = neo4j.vector(typedArray) //this creates a neo4j Vector of type Float32, containing the values [1, 2, 3] + +driver.executeQuery('CREATE (n {embeddings: $myVectorParam})', { myVectorParam: neo4jVector }) +``` + +To access the data in a retrieved Vector you can do the following: + +```javascript +var retrievedTypedArray = neo4jVector.asTypedArray() //This will return a TypedArray of the same type as the Vector + +var retrievedArray = Array.from(retrievedTypedArray) //This will convert the TypedArray to a regular array of Numbers. (Not safe for Int64 arrays) +``` diff --git a/packages/neo4j-driver-lite/README.md b/packages/neo4j-driver-lite/README.md index b540b0c5b..48a632419 100644 --- a/packages/neo4j-driver-lite/README.md +++ b/packages/neo4j-driver-lite/README.md @@ -119,7 +119,6 @@ is not the same as the lifetime of the web page: ```javascript driver.close() // returns a Promise ``` - ## Usage examples ### Constructing a Driver @@ -170,57 +169,6 @@ var session = driver.session({ }) ``` -### Executing Queries - -#### Consuming Records with Streaming API - -```javascript -// Run a Cypher statement, reading the result in a streaming manner as records arrive: -session - .run('MERGE (alice:Person {name : $nameParam}) RETURN alice.name AS name', { - nameParam: 'Alice' - }) - .subscribe({ - onKeys: keys => { - console.log(keys) - }, - onNext: record => { - console.log(record.get('name')) - }, - onCompleted: () => { - session.close() // returns a Promise - }, - onError: error => { - console.log(error) - } - }) -``` - -Subscriber API allows following combinations of `onKeys`, `onNext`, `onCompleted` and `onError` callback invocations: - -- zero or one `onKeys`, -- zero or more `onNext` followed by `onCompleted` when operation was successful. `onError` will not be invoked in this case -- zero or more `onNext` followed by `onError` when operation failed. Callback `onError` might be invoked after couple `onNext` invocations because records are streamed lazily by the database. `onCompleted` will not be invoked in this case. - -#### Consuming Records with Promise API - -```javascript -// the Promise way, where the complete result is collected before we act on it: -session - .run('MERGE (james:Person {name : $nameParam}) RETURN james.name AS name', { - nameParam: 'James' - }) - .then(result => { - result.records.forEach(record => { - console.log(record.get('name')) - }) - }) - .catch(error => { - console.log(error) - }) - .then(() => session.close()) -``` - ### Transaction functions ```javascript @@ -233,13 +181,13 @@ neo4j.driver('neo4j://localhost', neo4j.auth.basic('neo4j', 'password'), { }) ``` -#### Reading with Async Session +#### Reading with transaction functions ```javascript // It is possible to execute read transactions that will benefit from automatic // retries on both single instance ('bolt' URI scheme) and Causal Cluster // ('neo4j' URI scheme) and will get automatic load balancing in cluster deployments -var readTxResultPromise = session.readTransaction(txc => { +var readTxResultPromise = session.executeRead(txc => { // used transaction will be committed automatically, no need for explicit commit/rollback var result = txc.run('MATCH (person:Person) RETURN person.name AS name') @@ -256,10 +204,10 @@ readTxResultPromise .catch(error => { console.log(error) }) - .then(() => session.close()) + .finally(() => session.close()) ``` -#### Writing with Async Session +#### Writing with transaction functions ```javascript // It is possible to execute write transactions that will benefit from automatic retries @@ -283,12 +231,68 @@ writeTxResultPromise .catch(error => { console.log(error) }) - .then(() => session.close()) + .finally(() => session.close()) ``` -### Explicit Transactions +### ExecuteQuery Function -#### With Async Session +```javascript +// Since 5.8.0, the driver has offered a way to run a single query transaction with minimal boilerplate. +// The driver.executeQuery() function features the same automatic retries as transaction functions. +// +var executeQueryResultPromise = driver + .executeQuery( + "MATCH (alice:Person {name: $nameParam}) RETURN alice.DOB AS DateOfBirth", + { + nameParam: 'Alice' + }, + { + routing: 'READ', + database: 'neo4j' + } + ) + +// returned Promise can be later consumed like this: +executeQueryResultPromise + .then(result => { + console.log(result.records) + }) + .catch(error => { + console.log(error) + }) +``` + +### Auto-Commit/Implicit Transaction + +```javascript +// This is the most basic and limited form with which to run a Cypher query. +// The driver will not automatically retry implicit transactions. +// This function should only be used when the other driver query interfaces do not fit the purpose. +// Implicit transactions are the only ones that can be used for CALL { …​ } IN TRANSACTIONS queries. + +var implicitTxResultPromise = session + .run( + "CALL { …​ } IN TRANSACTIONS", + { + param1: 'param' + }, + { + database: 'neo4j' + } + ) + +// returned Promise can be later consumed like this: +implicitTxResultPromise + .then(result => { + console.log(result.records) + }) + .catch(error => { + console.log(error) + }) + .finally(() => session.close()) +``` + +### Explicit Transactions ```javascript // run statement in a transaction @@ -323,6 +327,57 @@ try { } ``` +### Consuming Records + +#### Consuming Records with Streaming API + +```javascript +// Run a Cypher statement, reading the result in a streaming manner as records arrive: +session + .executeWrite(tx => tx.run('MERGE (alice:Person {name : $nameParam}) RETURN alice.name AS name', { + nameParam: 'Alice' + }).subscribe({ + onKeys: keys => { + console.log(keys) + }, + onNext: record => { + console.log(record.get('name')) + }, + onCompleted: () => { + session.close() // returns a Promise + }, + onError: error => { + console.log(error) + } + }) +) +``` + +Subscriber API allows following combinations of `onKeys`, `onNext`, `onCompleted` and `onError` callback invocations: + +- zero or one `onKeys`, +- zero or more `onNext` followed by `onCompleted` when operation was successful. `onError` will not be invoked in this case +- zero or more `onNext` followed by `onError` when operation failed. Callback `onError` might be invoked after couple `onNext` invocations because records are streamed lazily by the database. `onCompleted` will not be invoked in this case. + +#### Consuming Records with Promise API + +```javascript +// the Promise way, where the complete result is collected before we act on it: +driver + .executeQuery('MERGE (james:Person {name : $nameParam}) RETURN james.name AS name', { + nameParam: 'James' + }) + .then(result => { + result.records.forEach(record => { + console.log(record.get('name')) + }) + }) + .catch(error => { + console.log(error) + }) + .then(() => driver.close()) +``` + ### Numbers and the Integer type The Neo4j type system uses 64-bit signed integer values. The range of values is between `-(2``64``- 1)` and `(2``63``- 1)`. @@ -336,20 +391,18 @@ _**Any javascript number value passed as a parameter will be recognized as `Floa #### Writing integers -Numbers written directly e.g. `session.run("CREATE (n:Node {age: $age})", {age: 22})` will be of type `Float` in Neo4j. +Numbers written directly e.g. `driver.executeQuery("CREATE (n:Node {age: $age})", {age: 22})` will be of type `Float` in Neo4j. To write the `age` as an integer the `neo4j.int` method should be used: ```javascript -var neo4j = require('neo4j-driver-lite') - -session.run('CREATE (n {age: $myIntParam})', { myIntParam: neo4j.int(22) }) +driver.executeQuery('CREATE (n {age: $myIntParam})', { myIntParam: neo4j.int(22) }) ``` To write an integer value that are not within the range of `Number.MIN_SAFE_INTEGER` `-(2``53``- 1)` and `Number.MAX_SAFE_INTEGER` `(2``53``- 1)`, use a string argument to `neo4j.int`: ```javascript -session.run('CREATE (n {age: $myIntParam})', { +driver.executeQuery('CREATE (n {age: $myIntParam})', { myIntParam: neo4j.int('9223372036854775807') }) ``` @@ -402,8 +455,6 @@ The Vector type supports signed integers of 8, 16, 32 and 64 bits, and floats of To create a neo4j Vector in your code, do the following: ```javascript -var neo4j = require('neo4j-driver') - var typedArray = Float32Array.from([1, 2, 3]) //this is how to convert a regular array of numbers into a TypedArray, useful if you handle vectors as regular arrays in your code var neo4jVector = neo4j.vector(typedArray) //this creates a neo4j Vector of type Float32, containing the values [1, 2, 3] @@ -417,4 +468,4 @@ To access the data in a retrieved Vector you can do the following: var retrievedTypedArray = neo4jVector.asTypedArray() //This will return a TypedArray of the same type as the Vector var retrievedArray = Array.from(retrievedTypedArray) //This will convert the TypedArray to a regular array of Numbers. (Not safe for Int64 arrays) -``` \ No newline at end of file +``` diff --git a/packages/neo4j-driver/README.md b/packages/neo4j-driver/README.md index 80ac8ff95..05d5dd4dd 100644 --- a/packages/neo4j-driver/README.md +++ b/packages/neo4j-driver/README.md @@ -22,26 +22,22 @@ Resources to get you started: ### In Node.js application +The node version of the driver requires node 18 or later. + Stable channel: ```shell npm install neo4j-driver ``` -Pre-release channel: - -```shell -npm install neo4j-driver@next -``` - -Please note that `@next` only points to pre-releases that are not suitable for production use. -To get the latest stable release omit `@next` part altogether or use `@latest` instead. - ```javascript +// If you are using CommonJS var neo4j = require('neo4j-driver') +// Alternatively, if you are using ES6 +import neo4j from 'neo4j-driver' ``` -Driver instance should be closed when Node.js application exits: +Driver instance should be closed when the application exits: ```javascript driver.close() // returns a Promise @@ -188,76 +184,6 @@ var rxSession = driver.rxSession({ }) ``` -### Executing Queries - -#### Consuming Records with Streaming API - -```javascript -// Run a Cypher statement, reading the result in a streaming manner as records arrive: -session - .run('MERGE (alice:Person {name : $nameParam}) RETURN alice.name AS name', { - nameParam: 'Alice' - }) - .subscribe({ - onKeys: keys => { - console.log(keys) - }, - onNext: record => { - console.log(record.get('name')) - }, - onCompleted: () => { - session.close() // returns a Promise - }, - onError: error => { - console.log(error) - } - }) -``` - -Subscriber API allows following combinations of `onKeys`, `onNext`, `onCompleted` and `onError` callback invocations: - -- zero or one `onKeys`, -- zero or more `onNext` followed by `onCompleted` when operation was successful. `onError` will not be invoked in this case -- zero or more `onNext` followed by `onError` when operation failed. Callback `onError` might be invoked after couple `onNext` invocations because records are streamed lazily by the database. `onCompleted` will not be invoked in this case. - -#### Consuming Records with Promise API - -```javascript -// the Promise way, where the complete result is collected before we act on it: -session - .run('MERGE (james:Person {name : $nameParam}) RETURN james.name AS name', { - nameParam: 'James' - }) - .then(result => { - result.records.forEach(record => { - console.log(record.get('name')) - }) - }) - .catch(error => { - console.log(error) - }) - .then(() => session.close()) -``` - -#### Consuming Records with Reactive API - -```javascript -rxSession - .run('MERGE (james:Person {name: $nameParam}) RETURN james.name AS name', { - nameParam: 'Bob' - }) - .records() - .pipe( - map(record => record.get('name')), - concatWith(rxSession.close()) - ) - .subscribe({ - next: data => console.log(data), - complete: () => console.log('completed'), - error: err => console.log(err) - }) -``` - ### Transaction functions ```javascript @@ -293,7 +219,7 @@ readTxResultPromise .catch(error => { console.log(error) }) - .then(() => session.close()) + .finally(() => session.close()) ``` #### Reading with Reactive Session @@ -337,7 +263,7 @@ writeTxResultPromise .catch(error => { console.log(error) }) - .then(() => session.close()) + .finally(() => session.close()) ``` #### Writing with Reactive Session @@ -357,6 +283,64 @@ rxSession }) ``` +### ExecuteQuery Function + +```javascript +// Since 5.8.0, the driver has offered a way to run a single query transaction with minimal boilerplate. +// The driver.executeQuery() function features the same automatic retries as transaction functions. +// +var executeQueryResultPromise = driver + .executeQuery( + "MATCH (alice:Person {name: $nameParam}) RETURN alice.DOB AS DateOfBirth", + { + nameParam: 'Alice' + }, + { + routing: 'READ', + database: 'neo4j' + } + ) + +// returned Promise can be later consumed like this: +executeQueryResultPromise + .then(result => { + console.log(result.records) + }) + .catch(error => { + console.log(error) + }) +``` + +### Auto-Commit/Implicit Transaction + +```javascript +// This is the most basic and limited form with which to run a Cypher query. +// The driver will not automatically retry implicit transactions. +// This function should only be used when the other driver query interfaces do not fit the purpose. +// Implicit transactions are the only ones that can be used for CALL { …​ } IN TRANSACTIONS queries. + +var implicitTxResultPromise = session + .run( + "CALL { …​ } IN TRANSACTIONS", + { + param1: 'param' + }, + { + database: 'neo4j' + } + ) + +// returned Promise can be later consumed like this: +implicitTxResultPromise + .then(result => { + console.log(result.records) + }) + .catch(error => { + console.log(error) + }) + .finally(() => session.close()) +``` + ### Explicit Transactions #### With Async Session @@ -401,7 +385,7 @@ rxSession .beginTransaction() .pipe( mergeMap(txc => - concatWith( + concat( txc .run( 'MERGE (bob:Person {name: $nameParam}) RETURN bob.name AS name', @@ -434,6 +418,76 @@ rxSession }) ``` +### Consuming Records + +#### Consuming Records with Streaming API + +```javascript +// Run a Cypher statement, reading the result in a streaming manner as records arrive: +session + .executeWrite(tx => tx.run('MERGE (alice:Person {name : $nameParam}) RETURN alice.name AS name', { + nameParam: 'Alice' + }).subscribe({ + onKeys: keys => { + console.log(keys) + }, + onNext: record => { + console.log(record.get('name')) + }, + onCompleted: () => { + session.close() // returns a Promise + }, + onError: error => { + console.log(error) + } + }) +) +``` + +Subscriber API allows following combinations of `onKeys`, `onNext`, `onCompleted` and `onError` callback invocations: + +- zero or one `onKeys`, +- zero or more `onNext` followed by `onCompleted` when operation was successful. `onError` will not be invoked in this case +- zero or more `onNext` followed by `onError` when operation failed. Callback `onError` might be invoked after couple `onNext` invocations because records are streamed lazily by the database. `onCompleted` will not be invoked in this case. + +#### Consuming Records with Promise API + +```javascript +// the Promise way, where the complete result is collected before we act on it: +driver + .executeQuery('MERGE (james:Person {name : $nameParam}) RETURN james.name AS name', { + nameParam: 'James' + }) + .then(result => { + result.records.forEach(record => { + console.log(record.get('name')) + }) + }) + .catch(error => { + console.log(error) + }) + .then(() => driver.close()) +``` + +#### Consuming Records with Reactive API + +```javascript +rxSession + .run('MERGE (james:Person {name: $nameParam}) RETURN james.name AS name', { + nameParam: 'Bob' + }) + .records() + .pipe( + map(record => record.get('name')), + concatWith(rxSession.close()) + ) + .subscribe({ + next: data => console.log(data), + complete: () => console.log('completed'), + error: err => console.log(err) + }) +``` + ### Numbers and the Integer type The Neo4j type system uses 64-bit signed integer values. The range of values is between `-(2``64``- 1)` and `(2``63``- 1)`. @@ -447,20 +501,20 @@ _**Any javascript number value passed as a parameter will be recognized as `Floa #### Writing integers -Numbers written directly e.g. `session.run("CREATE (n:Node {age: $age})", {age: 22})` will be of type `Float` in Neo4j. +Numbers written directly e.g. `driver.executeQuery("CREATE (n:Node {age: $age})", {age: 22})` will be of type `Float` in Neo4j. To write the `age` as an integer the `neo4j.int` method should be used: ```javascript var neo4j = require('neo4j-driver') -session.run('CREATE (n {age: $myIntParam})', { myIntParam: neo4j.int(22) }) +driver.executeQuery('CREATE (n {age: $myIntParam})', { myIntParam: neo4j.int(22) }) ``` To write an integer value that are not within the range of `Number.MIN_SAFE_INTEGER` `-(2``53``- 1)` and `Number.MAX_SAFE_INTEGER` `(2``53``- 1)`, use a string argument to `neo4j.int`: ```javascript -session.run('CREATE (n {age: $myIntParam})', { +driver.executeQuery('CREATE (n {age: $myIntParam})', { myIntParam: neo4j.int('9223372036854775807') }) ``` @@ -503,3 +557,29 @@ var driver = neo4j.driver( { disableLosslessIntegers: true } ) ``` + +#### Writing and reading Vectors + +Neo4j supports storing vector embeddings in a dedicated vector type. Sending large lists with the driver will result in significant overhead as each value will be transmitted with type information, so the 6.0.0 release of the driver introduced the Neo4j Vector type. + +The Vector type supports signed integers of 8, 16, 32 and 64 bits, and floats of 32 and 64 bits. The Vector type is a wrapper for JavaScript TypedArrays of those types. + +To create a neo4j Vector in your code, do the following: + +```javascript +var neo4j = require('neo4j-driver') + +var typedArray = Float32Array.from([1, 2, 3]) //this is how to convert a regular array of numbers into a TypedArray, useful if you handle vectors as regular arrays in your code + +var neo4jVector = neo4j.vector(typedArray) //this creates a neo4j Vector of type Float32, containing the values [1, 2, 3] + +driver.executeQuery('CREATE (n {embeddings: $myVectorParam})', { myVectorParam: neo4jVector }) +``` + +To access the data in a retrieved Vector you can do the following: + +```javascript +var retrievedTypedArray = neo4jVector.asTypedArray() //This will return a TypedArray of the same type as the Vector + +var retrievedArray = Array.from(retrievedTypedArray) //This will convert the TypedArray to a regular array of Numbers. (Not safe for Int64 arrays) +```