Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

gremlin-client

Package Overview
Dependencies
Maintainers
1
Versions
12
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

gremlin-client - npm Package Compare versions

Comparing version 0.2.1 to 0.3.0

src/messagestream.js

5

CHANGELOG.md

@@ -0,1 +1,6 @@

## 0.3.0
- `client.stream()` now re-emits one distinct `data` event per result fetched instead of emiting an array of results
- `client.execute()` now internally uses a stream to buffer partial results before firing the provided callback
- Add `client.messageStream()` which returns a stream of raw response messages returned by Gremlin Server
## 0.2.1

@@ -2,0 +7,0 @@ - Update dependencies

4

examples/browser.js

@@ -8,7 +8,7 @@ $(function() {

var script = 'g.V';
var script = 'g.V()';
var query = client.stream(script);
query.on('data', function(d) {
$("#results").append('<li>'+ JSON.stringify(d[0]) +'</li>');
$("#results").append('<li>'+ JSON.stringify(d) +'</li>');
});

@@ -15,0 +15,0 @@

@@ -10,4 +10,4 @@ var gremlin = require('../');

g.v(id).out('created').filter(function(it) {
return it.get().value('name') !== 'gremlin';
g.v(id).out('knows').filter(function(it) {
return it.get().value('age') !== 32;
});

@@ -14,0 +14,0 @@ };

@@ -6,3 +6,3 @@ var gremlin = require('../');

var script = 'g.V[1..2]';
var script = 'g.V()[1..2]';

@@ -9,0 +9,0 @@ var s = client.stream(script);

@@ -6,3 +6,3 @@ var gremlin = require('../');

var script = 'g.V[1..2]';
var script = 'g.V()[1..2]';

@@ -21,4 +21,4 @@ // Callback style

s.on('end', function(message) {
console.log("All results fetched", message);
s.on('end', function() {
console.log("All results fetched");
});

@@ -25,0 +25,0 @@

{
"name": "gremlin-client",
"version": "0.2.1",
"version": "0.3.0",
"description": "JavaScript client for TinkerPop3 Gremlin Server",

@@ -28,3 +28,5 @@ "main": "index.js",

"guid": "0.0.12",
"highland": "^1.27.1",
"lodash.defaults": "^2.4.1",
"readable-stream": "^1.0.31",
"ws": "^0.4.32"

@@ -31,0 +33,0 @@ },

@@ -27,2 +27,5 @@ gremlin-client

```javascript
// Assuming Node.js or Browser environment with browserify:
var gremlin = require('gremlin-client');
// Will open a WebSocket to ws://localhost:8182 by default

@@ -41,3 +44,3 @@ var client = gremlin.createClient();

The `options` object currently allows you to set two options:
The `options` object currently allows you to set the following options:
* `session`: whether to use sessions or not (default: `false`)

@@ -47,28 +50,38 @@ * `language`: the script engine to use on the server, see your gremlin-server.yaml file (default: `"gremlin-groovy"`)

* `processor` (advanced usage): The name of the OpProcessor to utilize (default: `""`)
* `accept`: (default: `"application/json"`)
* `accept` (advanced usage): mime type of returned responses, depending on the serializer (default: `"application/json"`)
### Sending scripts to Gremlin Server for execution
The client supports two modes: streaming results, or traditional callback mode.
The client supports three modes:
* streaming results
* callback mode (with internal buffer)
* streaming protocol messages (for advanced usages)
#### Stream mode: client.stream(script)
#### Stream mode: client.stream(script, bindings, message)
Return a Node.js stream which emits a `data` event with the raw data returned by Gremlin Server every time it receives a message (ie. the raw message returned by Gremlin Server). The stream simultaneously also emits a higher level `result` event, with `message.result` as a payload.
Return a Node.js ReadableStream set in Object mode. The stream emits a `data` event for each result returned by Gremlin Server.
The stream emits an `end` event when the client receives the last `statusCode: 299` message.
For each low level protocol message with potentially one or more results attached (depending on the value of `resultIterationBatchSize` in your .yaml file), the stream will always reemit one `data` event per result.
The order in which results are returned should be 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.
```javascript
var query = client.stream('g.V()');
query.on('data', function(result, message) {
// If playing with classic TinkerPop graph, will emit 6 data events
query.on('data', function(result) {
// Handle first vertex
console.log(result);
});
query.on('end', function(message) {
console.log("All results fetched", message);
query.on('end', function() {
console.log("All results fetched");
});
```
#### Callback mode: client.execute(script, callback)
This allows you to effectively `.pipe()` the stream to any other Node.js ReadableStream.
#### Callback mode: client.execute(script, bindings, message, callback)
Will execute the provided callback when all results are actually returned from the server.

@@ -79,5 +92,5 @@

client.execute('g.V()', function(err, result, lastMessage, command) {
client.execute('g.V()', function(err, results) {
if (!err) {
console.log(result)
console.log(results) // Handle an array of results
}

@@ -87,9 +100,26 @@ });

The client will internally concatenate all partial results returned over different messages (possibly, depending on the total number of results and the value of `resultIterationBatchSize` set in your .yaml file).
The client will internally concatenate all partial results returned over different messages (depending on the total number of results and the value of `resultIterationBatchSize` set in your .yaml file).
When the client receives the final `statusCode: 299` message, the callback will be executed.
#### Message stream mode: 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.
Although a public method, this is recommended for advanced usages only.
```javascript
var client = gremlin.createClient();
var query = client.messageStream('g.V()');
// Will emit 3 events with a resultIterationBatchSize set to 2 and classic graph
query.on('data', function(message) {
console.log(message.result); // Array of 2 vertices
});
```
### Adding bound parameters to your scripts
For better performance and security concerns, you may wish to send bound parameters to your scripts.
For better performance and security concerns, you may wish to send bound parameters with your scripts.

@@ -99,7 +129,9 @@ ```javascript

client.execute('g.v(id)', { id: 1 }, function(err, result) {
// Handle result
client.execute('g.v(id)', { id: 1 }, function(err, results) {
console.log(results[0]) // notice how results is always an array
});
```
Also work with `client.stream()` and `client.messageStream()` for they share the same signature, without the callback as last parameter.
### Overriding low level settings on a per request basis

@@ -110,8 +142,9 @@

```javascript
client.execute('g.v(1)', null, { args: { language: 'nashorn' }}, function(err, result) {
client.execute('g.v(1)', null, { args: { language: 'nashorn' }}, function(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.
Because we're not sending any bound parameters, notice how the second argument **must** be set to `null` so the low level message object is not mistaken with bound arguments.
Because we're not sending any bound parameters 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.

@@ -121,3 +154,3 @@ If you wish to also send bound parameters while overriding the low level message, you can do the following:

```javascript
client.execute('g.v(id)', { id: 1 }, { args: { language: 'nashorn' }}, function(err, result) {
client.execute('g.v(id)', { id: 1 }, { args: { language: 'nashorn' }}, function(err, results) {
// Handle result

@@ -127,12 +160,10 @@ });

The same method signature also applies to `client.stream()`:
Or in stream mode:
```javascript
var s = client.stream(script, bindings, message);
var s = client.stream('g.v(id)', { id: 1 }, { args: { language: 'nashorn' }});
```
### Using Gremlin-JavaScript syntax with Nashorn
Providing your configured `nashorn` script engine in your `gremlin-server.yaml` file, you can send and execute Gremlin-JavaScript formatted queries:
Providing your configured `nashorn` script engine in your `gremlin-server.yaml` file, you can send and execute Gremlin-JavaScript formatted queries (see example in this repository in `/config`):

@@ -164,3 +195,3 @@ ```yaml

// Send that script function body to Gremlin Server for execution in Nashorn engine
client.execute(script, function(err, result) {
client.execute(script, function(err, results) {
// Handle result

@@ -170,9 +201,11 @@ });

The client gets a string representation of the function passed to `client.stream()` or `client.query()` by calling the `.toString()` method.
The client internally gets a string representation of the function passed to `client.stream()` or `client.execute()` by calling the `.toString()` method.
Passing bound parameters and/or low level message will also work when using nashorn script engine.
You may also simply pass a raw string as first parameter, rather than a function. The Function.toString() trick is just a convenient way to expose the full Groovy/Java API in your local JS environment. You can also use loop or try..catch that will be executed in the context of Gremlin Server.
## Running the Examples
This section assumes that you configured `resultIterationBatchSize: 1` in your Gremlin Server .yaml config file and loaded the default TinkerPop graph with `scripts: [scripts/generate-classic.groovy]`
This section assumes that loaded the default TinkerPop graph with `scripts: [scripts/generate-classic.groovy]` in your .yaml config file.

@@ -193,14 +226,10 @@

## Features
* commands issued before the WebSocket is opened are queued and sent when it's ready.
## To do list
* handle any errors
* reconnect WebSocket if connection is lost
* support `.execute()` with promise
* secure WebSocket
* tests
* better error handling
* emit more client events
* reconnect WebSocket if connection is lost?
* support `.execute()` with promise?
* add option for secure WebSocket
* more tests
* performance optimization

@@ -6,3 +6,2 @@ /*jslint -W079 */

var inherits = require('util').inherits;
var Stream = require('stream').Stream;

@@ -14,3 +13,6 @@ var WebSocket = require('ws');

};
var highland = require('highland');
var MessageStream = require('./messagestream');
function GremlinClient(port, host, options) {

@@ -56,3 +58,4 @@ this.port = port || 8182;

/**
* Process all incoming raw message events sent by Gremlin Server.
* Process all incoming raw message events sent by Gremlin Server, and dispatch
* to the appropriate command.
*

@@ -64,6 +67,7 @@ * @param {MessageEvent} event

var command = this.commands[message.requestId];
var stream = command.stream;
switch (message.code) {
case 200:
command.onData(message);
stream.push(message);
break;

@@ -73,3 +77,3 @@ case 299:

delete this.commands[message.requestId]; // TODO: optimize performance
command.onEnd(message.result, message);
stream.push(null);
break;

@@ -109,3 +113,3 @@ }

command = this.queue.shift();
this.sendMessage(command);
this.sendMessage(command.message);
}

@@ -129,3 +133,3 @@ };

command = commands[key];
command.terminate(error);
command.stream.emit('error', error);
});

@@ -138,11 +142,14 @@ };

*
* @param {String} script
* @param {String|Function} script
* @param {Object} bindings
* @param {Object} message
* @param {Object} handlers
*/
GremlinClient.prototype.buildCommand = function(script, bindings, message, handlers) {
var guid = Guid.create().value;
GremlinClient.prototype.buildCommand = function(script, bindings, message) {
if (typeof script === 'function') {
script = this.extractFunctionBody(script);
}
bindings = bindings || {};
var stream = new MessageStream({ objectMode: true });
var guid = Guid.create().value;
var args = _.defaults(message && message.args || {}, {

@@ -164,6 +171,3 @@ gremlin: script,

message: message,
onData: handlers.onData,
onEnd: handlers.onEnd,
terminate: handlers.terminate,
result: []
stream: stream
};

@@ -176,7 +180,9 @@

this.sendCommand(command); //todo improve for streams
return command;
};
GremlinClient.prototype.sendMessage = function(command) {
this.ws.send(JSON.stringify(command.message));
GremlinClient.prototype.sendMessage = function(message) {
this.ws.send(JSON.stringify(message));
};

@@ -197,14 +203,20 @@

/**
* Asynchronously send a script to Gremlin Server for execution and fire
* the provided callback when all results have been fetched.
*
* This method internally uses a stream to handle the potential concatenation
* of results.
*
* Callback signature: (Error, Array<result>)
*
* @public
* @param {String|Function} script
* @param {Object} bindings
* @param {Object} message
* @param {Function} callback
*/
GremlinClient.prototype.execute = function(script, bindings, message, callback) {
if (typeof script === 'function') {
script = this.extractFunctionBody(script);
}
callback = arguments[arguments.length - 1]; //todo: improve?
// Signature: script, callback
if (typeof bindings === 'function') {
callback = bindings;
bindings = {};
}
// Signature: script, bindings, callback
if (typeof message === 'function') {

@@ -215,43 +227,72 @@ callback = message;

var command = this.buildCommand(script, bindings, message, {
onData: function(message) {
this.result = this.result.concat(message.result);
},
onEnd: function(result, message) {
return callback(null, result, message, this);
},
terminate: function(error) {
return callback(error);
}
var stream = this.messageStream(script, bindings, message);
var results = [];
// Using a Highland.js stream here because it's not publicly exposed
stream = highland(stream)
.map(function(message) { return message.result; });
stream.on('data', function(data) {
results = results.concat(data);
});
this.sendCommand(command);
stream.on('end', function() {
callback(null, results);
});
stream.on('error', function(error) {
callback(new Error('Stream error: ' + error));
});
};
/**
* Execute the script and return a stream of distinct/single results.
* This method reemits a distinct data event for each returned result, which
* makes the stream behave as if `resultIterationBatchSize` was set to 1.
*
* If you do not wish this behavior, please use client.messageStream() instead.
*
* Even though this method uses Highland.js internally, it does not return
* a high level Highland readable stream so we do not risk having to deal
* with unexpected API breaking changes as Highland.js evolves.
*
* @return {ReadableStream} A Node.js Stream2
*/
GremlinClient.prototype.stream = function(script, bindings, message) {
if (typeof script === 'function') {
script = this.extractFunctionBody(script);
}
var messageStream = this.messageStream(script, bindings, message);
var _ = highland; // override lo-dash locally
if (typeof message === 'function') {
message = {};
}
// Create a local highland 'through' pipeline so we don't expose
// a Highland stream to the end user, but a standard Node.js Stream2
var through = _.pipeline(
_.map(function(message) {
return message.result;
}),
_.sequence()
);
var stream = new Stream();
var stream = messageStream.pipe(through);
var command = this.buildCommand(script, bindings, message, {
onData: function(data) {
stream.emit('data', data.result, data);
},
onEnd: function(result, message) {
stream.emit('end', message);
},
terminate: function(error) {
stream.emit('error', error);
}
});
return stream;
};
this.sendCommand(command);
/**
* Execute the script and return a stream of raw messages returned by Gremlin
* Server.
* This method does not reemit one distinct data event per result. It directly
* emits the raw messages returned by Gremlin Server as they are received.
*
* Although public, this is a low level method intended to be used for
* advanced usages.
*
* @public
* @param {String|Function} script
* @param {Object} bindings
* @param {Object} message
* @return {MessageStream}
*/
GremlinClient.prototype.messageStream = function(script, bindings, message) {
var command = this.buildCommand(script, bindings, message);
return stream;
return command.stream;
};

@@ -269,3 +310,3 @@

if (this.connected) {
this.sendMessage(command);
this.sendMessage(command.message);
} else {

@@ -272,0 +313,0 @@ this.queue.push(command);

@@ -60,3 +60,3 @@ var gremlin = require('../');

s.on('data', function(result) {
result.length.should.equal(1);
result.should.be.an('object');
});

@@ -63,0 +63,0 @@

@@ -8,7 +8,5 @@ /*jshint -W030 */

client.execute('g.V()', function(err, result, response, command) {
client.execute('g.V()', function(err, result) {
(err === null).should.be.true;
result.length.should.equal(6);
response.should.exist;
response.code.should.equal(299);
done();

@@ -15,0 +13,0 @@ });

@@ -7,16 +7,13 @@ var gremlin = require('../');

var client = gremlin.createClient({ language: 'nashorn' });
var count = 0;
var s = client.stream(function() { g.V(); });
s.on('data', function(result, response) {
result.should.exist;
response.should.exist;
response.code.should.equal(200);
var results = [];
count += 1;
s.on('data', function(result) {
results.push(result);
});
s.on('end', function() {
count.should.equal(6);
results.length.should.equal(6);
done();

@@ -31,5 +28,4 @@ });

s.on('data', function(result, response) {
result.length.should.equal(1);
response.code.should.equal(200);
s.on('data', function(result) {
result.should.be.an('object');
});

@@ -47,5 +43,4 @@

s.on('data', function(result, response) {
result.length.should.equal(1);
response.code.should.equal(200);
s.on('data', function(result) {
result.should.be.an('object');
});

@@ -63,5 +58,4 @@

s.on('data', function(result, response) {
result.length.should.equal(1);
response.code.should.equal(200);
s.on('data', function(result) {
result.should.be.an('object');
});

@@ -68,0 +62,0 @@

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc