Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Handle all things asynchronous internally with RxJS #50

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
65 changes: 20 additions & 45 deletions README.md
Expand Up @@ -95,10 +95,9 @@ $ `rejectUnauthorized`: when using ssl, whether to reject self-signed certificat
### Executing Gremlin queries

The client currently supports three modes:
* callback mode (with internal buffer)
* promise mode
* streaming moderesults
* streaming protocol messages (low level API, for advanced usages)
* callback
* promise
* Observable (RxJS)

#### Callback mode: client.execute(script, bindings, message, callback)

Expand Down Expand Up @@ -157,58 +156,34 @@ const fetchByName = async (name) => {
fetchByName('Alice');
```

#### Stream mode
#### Observable mode

##### client.stream(script, bindings, message)
##### client.observable(script, bindings, message)

Return a Node.js ReadableStream set in Object mode. The stream emits a distinct `data` event per query result returned by Gremlin Server.
Return an RxJS `Observable` of results.

Internally, a 1-level flatten is performed on all raw protocol messages returned. If you do not wish this behavior and prefer handling raw protocol messages with batched results, prefer using `client.messageStream()`.

The order in which results are returned is guaranteed, allowing you to effectively use `order` steps and the like in your Gremlin traversal.

The stream emits an `end` event when the client receives the last `statusCode: 299` message returned by Gremlin Server.
Internally, a 1-level flatten is performed on all protocol messages returned.

```javascript
const query = client.stream('g.V()');

// If playing with classic TinkerPop graph, will emit 6 data events
query.on('data', (result) => {
// Handle first vertex
console.log(result);
});
const query$ = client.observable('g.V()');

query.on('end', () => {
console.log('All results fetched');
});
query$.subscribe(
(vertex) => console.log(vertex), // will log 6 times,
(err) => console.error(err),
() => console.log('Done!')
);
```

This allows you to effectively `.pipe()` the stream to any other Node.js WritableStream/TransformStream.

##### client.messageStream(script, bindings, message)

A lower level method that returns a `ReadableStream` which emits the raw protocol messages returned by Gremlin Server as distinct `data` events.

If you wish a higher-level stream of `results` rather than protocol messages, please use `client.stream()`.
##### client.messageObservable(script, bindings, message)

Although a public method, this is recommended for advanced usages only.

```javascript
const client = Gremlin.createClient();

const stream = client.messageStream('g.V()');

// Will emit 3 events with a resultIterationBatchSize set to 2 and classic graph defined in gremlin-server.yaml
stream.on('data', (message) => {
console.log(message.result); // Array of 2 vertices
});
```
A lower level method that returns an `Observable` of raw protocol messages return by Gremlin Server.
Recommended for advanced usages/troubleshooting.

### Adding bound parameters to your scripts

For better performance and security concerns (script injection), you must send bound parameters (`bindings`) with your scripts.

`client.execute()`, `client.stream()` and `client.messageStream()` share the same function signature: `(script, bindings, querySettings)`.
`client.execute()`, `client.observable()` and `client.messageObservable()` share the same function signature: `(script, bindings, querySettings)`.

Notes/Gotchas:
- Any bindings set to `undefined` will be automatically escaped with `null` values (first-level only) in order to generate a valid JSON string sent to Gremlin Server.
Expand Down Expand Up @@ -251,7 +226,7 @@ client.execute('g.v(1)', null, { args: { language: 'nashorn' }}, (err, results)
// Handle result
});
```
Basically, all you have to do is provide an Object as third parameter to any `client.stream()`, `client.execute()` or `client.streamMessage()` methods.
Basically, all you have to do is provide an Object as third parameter to any `client.observable()`, `client.execute()` or `client.messageObservable()` methods.

Because we're not sending any bound parameters (`bindings`) in this example, notice how the second argument **must** be set to `null` so the low level message object is not mistaken with bound arguments.

Expand All @@ -263,9 +238,9 @@ client.execute('g.v(vid)', { vid: 1 }, { args: { language: 'nashorn' }}, (err, r
});
```

Or in stream mode:
Or in Observable mode:
```javascript
client.stream('g.v(vid)', { vid: 1 }, { args: { language: 'nashorn' }})
client.observable('g.v(vid)', { vid: 1 }, { args: { language: 'nashorn' }})
.pipe(/* ... */);
```

Expand Down
34 changes: 34 additions & 0 deletions examples/server-disconnect.js
@@ -0,0 +1,34 @@
var http = require('http');



var gremlin = require('../src');

var client = gremlin.createClient();


var script = 'g.V().range(1, 2)';

// Callback style
client.execute(script, function(err, res) {
console.log(err, res);
});


client.on('error', (err) => {
console.log('oops error', err.message);
});




var server = http.createServer(function(req, res) {
// var done = finalhandler(req, res);
// serve(req, res, done);
res.send('yo');
});

// Listen
var port = 3000;
server.listen(port);
console.log('Gremlin Client example server listening on port', port);
19 changes: 16 additions & 3 deletions package.json
Expand Up @@ -12,10 +12,20 @@
"coverage": "babel-node ./node_modules/istanbul/lib/cli.js cover _mocha",
"coverage:travis": "babel-node ./node_modules/istanbul/lib/cli.js cover _mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage",
"examples:browser": "babel-node examples/server",
"examples:disconnect": "nodemon --exec babel-node -- examples/server-disconnect.js",
"examples:node": "babel-node examples/node-example",
"examples:rxjs": "babel-node examples/rxjs",
"precommit": "lint-staged",
"prettify": "prettier --single-quote --trailing-comma all --write \"src/**/*.js\"",
"test": "NODE_TLS_REJECT_UNAUTHORIZED=0 mocha $(find src -path '*test.js') --compilers js:babel-register --recursive --reporter spec",
"test:watch": "npm run test -- --watch"
},
"lint-staged": {
"*.js": [
"npm run prettify",
"git add"
]
},
"repository": {
"type": "git",
"url": "https://github.com/jbmusso/gremlin-javascript"
Expand All @@ -35,15 +45,14 @@
"homepage": "https://github.com/jbmusso/gremlin-javascript",
"dependencies": {
"gremlin-template-string": "^2.0.0",
"highland": "^2.5.1",
"lodash": "^3.10.1",
"node-uuid": "^1.4.3",
"readable-stream": "^2.0.2",
"rxjs": "^5.4.0",
"ws": "^2.3.1",
"zer": "^0.1.0"
},
"devDependencies": {
"babel-cli": "^6.4.5",
"babel-cli": "^6.24.1",
"babel-core": "^6.4.0",
"babel-loader": "^6.2.1",
"babel-plugin-transform-async-to-module-method": "^6.5.2",
Expand All @@ -62,6 +71,7 @@
"gulp-rename": "^1.2.0",
"gulp-size": "^1.0.0",
"gulp-uglify": "^0.3.1",
"husky": "^0.13.4",
"istanbul": "^0.4.2",
"istanbul-coveralls": "^1.0.3",
"karma": "^0.12.31",
Expand All @@ -71,8 +81,11 @@
"karma-firefox-launcher": "^0.1.3",
"karma-mocha": "^0.1.10",
"karma-safari-launcher": "^0.1.1",
"lint-staged": "^3.6.0",
"mocha": "^1.21.4",
"mocha-lcov-reporter": "^1.2.0",
"nodemon": "^1.11.0",
"prettier": "^1.4.2",
"rimraf": "^2.6.1",
"serve-static": "^1.5.3",
"vinyl-source-stream": "^1.0.0",
Expand Down