Overview
The JavaScript client library is a light-weight fluent API used to communcate with DeployR from both the browser and Node.js environments. It is crafted for flexibility, readability, and a low learning curve.
Full API Documentation
The full DeployR JavaScript client library API can be found here.
Download and Install
Download the DeployR JavaScript client library and begin using in your application to communicate with DeployR.
Unzip the compressed folder. You will see the following structure:
Install the DeployR JavaScript client library using the instructions for your particular environment:
<!-- Latest compiled raw JavaScript variation --> <script src="./browser/deployr.js"></script> Or <!-- Latest compiled minified JavaScript variation --> <script src="./browser/deployr.min.js"></script>
Download and install Node.js, which includes npm. npm, which stands for node packaged modules, is a way to manage development dependencies through Node.js.
From the command line:
Navigate to the root
/deployr/
directory.Run
npm install --production
. npm will look at thepackage.json
file and automatically install the necessary local runtime dependencies listed there.
Since the DeployR JavaScript client library is not yet registered as a public npm package, after running
npm install --production
, you will need to manually copy the entire /deployr/ directory into the node_modules directory of the Node.js project that will be using it.require
the directory:var deployr = require('deployr');
deployr/ ├── examples/ ├── gulp/ ├── lib/ ├── browser/ │ ├── deployr.js │ └── deployr.min.js ├── apis.json ├── deployr.js ├── gulpfile.js ├── package.json ├── LICENSE.md └── README.md
The base deployr.js
file contains the source code for the DeployR JavaScript
client library. The browser/
folder includes the precompiled browser only
version of the DeployR JavaScript client library. The examples/
folder
includes both browser and Node.js samples. Beyond that, any other included file
provides support for packages, license information, and development.
Examples
The DeployR JavaScript client library
ships with a set of small examples under the /deployr/examples
directory
that run in both the Browser and Node.js environments. The intention of the
examples are to demonstrate the syntax and core areas of the API. They are
not intended to be a tutorial on how to write web applications.
We encourage you to start here and customize these examples and adapt them to suit your needs as you explore the API.
Global Object 'deployr'
The deployr.js
library provides the deployr
object to your
application, which lets you make API requests to the DeployR server. All
interactions with the server will go through deployr
.
To see the full set of public supported features on the deployr
object,
view the API documentation.
Initialization
The .configure() method on the deployr
object is an
optional static method for configuring all DeployR requests. Settings made here
are applied globally across every request. For example, initializing the
location of the DeployR server for all request using the host
property:
deployr.configure( { host: 'http://dhost:8050' });
// All subsequent requests will go to the DeployR server 'http://dhost:8050'
Or turn logging on:
deployr.configure( { logging: true });
// All requests will now emit logging statements
Or enable cross-origin resource sharing (CORS) using the cors
property:
deployr.configure( { cors: true, host: 'http://dhost:8050' });
// All subsequent requests will go to the DeployR server 'http://dhost:8050'
What is CORS and why is it important? Read the next section for the answer.
CORS
Note! Cross-origin resource sharing (CORS) pertains to browser environments only.
By default, the JavaScript client library uses the XMLHttpRequest
object as the
transport for HTTP transactions to DeployR. As a result, the library is bound
by a security restriction, called Same Origin Policy,
which is imposed by all modern web browsers on network connections. This
constraint prevents a script or application from making a connection to any
web server other than the originating web page (cross-domain). Often, this
restriction is not an issue; however, there are obvious scenarios in which you
need your application using the JavaScript client library to be served-up on a
server other than the machine hosting DeployR.
How To
Make sure CORS is enabled on DeployR in the
deployr.groovy
file. If it isn't, enable it now.In your JavaScript, indicate that the requests will be using CORS. This is done through the
deployr.configuration()
method using thecors
property:
// Where http://dhost:8050 is the Cross-origin DeployR server
deployr.configure( { cors: true, host: 'http://dhost:8050' });
Browser Support
CORS is supported on the following browser versions:
Browser | Version(s) | |
---|---|---|
Internet Explorer+ | 10.0 | |
Chrome+ | Latest stable | |
Firefox | Latest stable | |
Safari | Safari 6.1 (OS X 10.8) |
Safari 7.x (OS X 10.9) |
3rd party cookies are disabled by default. For CORS to work with DeployR in these browsers, 3rd party cookies must be allowed by the browser as follows:
- For IE 10: Internet Options > Privacy > Advanced > Third Party Cookies > Accept
- For Safari: Preferences > Privacy > Block Cookies > Never
Request Basics
The .io() method on the deployr
object is the static method
for making a DeployR request. This method accepts a single DeployR API service
argument:
A request to DeployR can be initiated by providing the appropriate API to the
deployr.io()
method, then calling .end()
to send the request. For
example, a simple request to retrieve details about the currently authenticated
user:
deployr.io('/r/user/about')
.end(function(res) {
// handle successful response
});
Requests can also be broken up into multiple lines rather than chaining. The following example is equivalent to the previous:
var userAbout = deployr.io('/r/user/about');
userAbout.end(function(res) {
// handle successful response
});
Deciding which approach to use is mostly a matter of preference however method chaining can offer a convenience in some use-cases where a more fluid and expressive API call structure in needed. Please see the section on Chaining Requests to learn more.
.end()
method is called.
Passing Parameters
For each DeployR API service call, there is a documented list of required and optional parameters that can be sent:
Data is passed using the .data() method chained-off the returned
.io()
request object. A typical DeployR request might look a little like
this:
deployr.io('/r/user/login')
.data({ username: 'testuser' , password: 'secret' })
.end(function(res) {
// handle successful response
});
Or using multiple .data()
calls:
deployr.io('/r/user/login')
.data({ username: 'testuser' })
.data({ password: 'secret' })
.end(function(res) {
// handle successful response
});
Or broken up into multiple lines:
var userLogin = deployr.io('/r/user/login');
userLogin.data({ username: 'testuser' });
// do not send empty passwords!
if (password) {
userLogin.data({ password: password });
userLogin.end(function(res) {
// handle successful response
});
}
R Inputs
DeployR-specific encodings are used to encode R object data for use on the API. Encoded object data can be sent to the server as parameter values on the following calls:
To pass values from JavaScript as input to an R script you use one of the defined R data type methods.
This example uses both the .numeric()
and .logical()
methods to pass a
JavaScript Number 10 and a Boolean true to an R script:
deployr.io('/r/repository/script/execute')
.data({ filename: 'map.R', author: 'testuser' })
.numeric('randomNum', 10)
.logical('reduce', true)
.end(function(res) {
// handle successful response
});
The R script map.R has reviced the input decoded into their proper types:
#######################
# map.R
#######################
# prints 10
print(randomNum)
# prints TRUE
print(reduce)
At times it might not be convenient to use the R data type methods
inlined like this so we provide both the .rinput()
and .rinputs()
methods.
These allow you to construct R inputs ahead of time using the deployr.RInput
factory and pass them in.
For example, using the .rinputs()
method to specify multiple R inputs at
once:
var RIn = deployr.RInput;
var rinputs = [ RIn.numeric('randomNum', 10), RIn.logical('reduce', true) ];
deployr.io('/r/repository/script/execute')
.data({ filename: 'map.R', author: 'testuser' })
.rinputs(rinputs)
.end(function(res) {
// handle successful response
});
Or a single R input at a time using the .rinput()
method:
var RIn = deployr.RInput;
var randomNum = RIn.numeric('randomNum', 10);
var reduce = RIn.logical('reduce', true);
deployr.io('/r/repository/script/execute')
.data({ filename: 'map.R', author: 'testuser' })
.rinput(randomNum)
.rinput(reduce)
.end(function(res) {
// handle successful response
});
Note all three examples are equivalent to one another.
R Outputs
You can retrieve a list of object names that will be returned as DeployR-encoded R objects on the response markup after a script execution completes. To specify a list of object names from a service you use the .routput() method. For example:
deployr.io('/r/repository/script/execute')
.data({ filename: 'ReadCSV.R', author: 'testuser' })
.routput('output_vars')
.end(function(res) {
// handle successful response
});
Or multiple DeployR-encoded R objects spread out over multiple .routput()
method calls:
deployr.io('/r/repository/script/execute')
.data({ filename: 'TimeSeries.R', author: 'testuser' })
.routput('output_min')
.routput('output_max')
.routput('output_mean')
.end(function(res) {
// handle successful response
});
Or using the .routputs() method to specify multiple DeployR-encoded R objects at once in a single call using an Array:
deployr.io('/r/repository/script/execute')
.data({ filename: 'TimeSeries.R', author: 'testuser' })
.routputs([ 'output_min', 'output_max', 'output_mean' ])
.end(function(res) {
// handle successful response
});
File Upload
The following DeployR APIS support file uploads:
- /r/project/workspace/upload
- /r/project/directory/upload
- /r/repository/directory/upload
- /r/repository/file/upload
A high-level JavaScript client API is provided in the form of the .attach() method for performing individule file uploads. The argument types in the signature are diffrent depending on if you are running in the Browser or Node.js:
Browser: .attach(File|Blob, filename)
Node.js: .attach('/path/to/the/file', filename)
Note! The filename is an optional convenience argument.
Browser
Assuming we have an HTML <input>
element with a type value set to file
in the page:
<input id="csv-file" type="file">
And using the HTML5 File API, we can extract the
File Object from the <input>
element and feed it to the .attach()
method:
// HTML5 File Object
var file = document.getElementById('csv-file').files[0];
deployr.io('/r/repository/file/upload')
.data({ filename: 'satscores.csv', descr: 'SAT scores by state.' })
.attach(file)
.end();
Or with the HTML5 Blob object. The end result of the following example is equivalent to the previous:
// HTML5 Blob object (satscore.csv)
var satscores = 'State, PctSAT, VerbalSAT, MathSAT, AveSAT' +
'Alabama, 8, 491, 538, 1029' +
'Alaska, 47, 445, 489, 934';
var blob = new Blob([satscores], { type: "text/plain;charset=UTF-8" });
deployr.io('/r/repository/file/upload')
.data({ filename: 'satscores.csv', descr: 'SAT scores by state.' })
.attach(blob)
.end();
For convenience you can drop the filename property from .data()
call
and include it in the .attach()
method:
// HTML5 File Object
var file = document.getElementById('csv-file').files[0];
deployr.io('/r/repository/file/upload')
.attach(file, 'satscores.csv')
.end();
Node.js
A single file can be attached and uploaded by providing a path to the file:
#!/usr/bin/env node
var filepath = '/path/to/satscore.csv';
deployr.io('/r/repository/file/upload')
.data({ filename: 'satscores.csv', descr: 'SAT scores by state.' })
.attach(filepath)
.end();
Similar to the browser, you can drop the filename property from .data()
call and include it in the .attach()
method call:
#!/usr/bin/env node
var filepath = '/path/to/satscore.csv';
deployr.io('/r/repository/file/upload')
.attach(filepath, 'satscores.csv')
.end();
Shutdown
It is the responsibility of the application developer to make an explicit call to release any residual resources associated with the application instance whenever a client application terminates. For example:
var ruser = deployr.io('/r/user/login')
.data( { username: 'testuser', password: 'secret' } )
.end();
// ...
// close all allocated projects by id and logout
var openProjects = projectIds.length - 1,
closedProjects = 0; // counter
projectIds.forEach(function(project) {
ruser.io('/r/project/close')
.data({ project: project })
.end(function() {
// logout when [all] projects are closed
if (openProjects === closedProjects) {
ruser.io('/r/user/logout').end();
}
closedProjects++;
});
});
To aid in this process the .release() convenience method is supplied. It takes an (optional) argument of a project or an array of projects.
Close a single project and logout:
ruser.release(project);
Or close an array of projects and logout:
ruser.release([project1, project2, ...]);
Or just logout:
ruser.release();
Finally
The .ensure(λ) method acts as a finally statement allowing you to execute "cleanup" type tasks in a request chain. It arranges for cleanup to be called, with no arguments, when the DeployR request chain is either completely fulfilled or rejected.
For example:
var ruser = deployr.io('/r/user/login')
.data({ username: 'testuser' , password: 'secret' })
.end();
// do something...
// ...
// ...
ruser.io('/r/user/about')
.end()
.ensure(function() {
// `ensure()` will always be called last regardless a failure or success
console.log('cleanup...');
});
The .ensure()
method is a good place to cleanup resources such as open
projects or to log users out. It works nicly with the .release()
method:
var ruser = // assuming authentication `/r/user/login`
// assuming a project has already been created in aother task and the id stored
var project = 'PROJECT-5ac47aff-8f8c-4ac2-8a8f-6e31468e6a19';
ruser.io('/r/project/script/execute')
.data({ filename: 'script.R', author: 'testusesr', project: project })
.end()
.ensure(function() {
// closes project and logs user out
ruser.release(project);
});
Aborting Request
A request to DeployR can be stopped if it has already been sent by using the .abort() method:
var rscript = deployr.io('/r/repository/script/execute');
rscript.data({ filename: 'LongRunningScript.R', author: 'testuser' });
rscript.end();
// if request is still being executed, stop it.
rscript.abort();
Request Timeout
A timeout can be applied to a request by invoking the .timeout(ms) method. The timeout duration is defined in milliseconds, for example:
deployr.io('/r/user/login')
.data({ username: 'testuser' , password: 'secret' }).
.timeout(10000) // timeout after 10 seconds
.end();
Logging
Logging can be turned on for a individual request by calling the
.log() method or for all requests globally through the
deployr.configure( { logging: true } )
method found
here. To remove logging simpily remove the .log()
call
or set { logging: false }
, respectively.
Logging for an individual request:
deployr.io('/r/user/about')
.log()
.end(function(res) {
// handle successful response
});
Or global logging applied to all requests:
// All requests will emit logging statements
deployr.configure( { logging: true });
deployr.io('/r/user/about')
.end(function(res) {
// handle successful response
});
deployr.io('/r/user/logout')
.end(function(res) {
// handle successful response
});
If you intend to log all requests globally, .log()
should be removed as it
is redundant.
Error Handling
You can capture all DeployR errors for a request using the .error(λ) method:
deployr.io('/r/user/login')
.data({ username: 'testuser', password: 'secret' })
.error(function(err) {
// handle failure
})
.end(function(res) {
// handle successful response
});
Or you can add an event listener to capture all DeployR errors for a request
using the .on()
method. The following example is equivalent to the previous:
deployr.io('/r/user/login')
.data({ username: 'testuser', password: 'secret' })
.on('error', function(err) {
// handle failure
})
.end(function(res) {
// handle successful response
});
In addition, capturing specific DeployR errors for a
request can be achieved using the .on()
method and passing in the name of
the error. For example, catching a DeployR 409
concurrency error when a
project is busy on another call:
deployr.io('/r/project/execute/script')
.data({ filename: 'Regression.R', author: 'testuser', project: pid })
.on('deployr-io:409', (function(err) {
// Conflict: project is currently busy on call, concurrent call rejected
})
.end(function(res) {
// handle successful response
});
Chaining Requests
The DeployR JavaScript client library is a fluent API which allows for writing very concise expressions. Asynchronous requests can be processed sequentially without a nasty callback pyramid. Sequential request chaining can be accomplished by tying together aditional .io() calls:
deployr.io('/r/user/login')
.data({ username: 'testuser' , password: 'secret' })
.end(function(res) {
})
.io('/r/user/about')
.end(function(res, chain) {
})
.io('/r/user/logout')
.end(function(res, chain) {
});
The order of execution is guaranteed while remaining asynchronous. If an error occures the request chain is broken and any further calls are voided.
An additional argument is provided to the .end(function(res, chain) {})
callback function when chaining requests. The second argument represents the
successful response chain array from the previous calls. This is often useful
when the next request depends on the response of a previous call:
// ----- Login -----
deployr.io('/r/user/login')
.data({ username: 'testuser' , password: 'secret' })
.end()
// ----- Create a new Project -----
.io('/r/project/create')
.end(function(res, chain) {
// Returing an object literial from end() will get merged onto the next
// call's parameter list
return { project: res.get('project') };
})
// ----- Execute a script on the previous Project -----
.io('/r/project/execute/script')
.data({ filename : 'DeployR - Hello World', author: 'testuser' })
.numeric('input_randomNum', 10)
.end(function(res, chain) {
// Returing an object literial from end() will get merged onto the next
// request. Here we attach the project ID from the `/r/project/create`
// invocation two calls previous
return { project: chain[1].get('project') };
})
// ----- Close the Project -----
.io('/r/project/close')
.end()
// ----- Logout -----
.io('/r/user/logout')
.end();
It is suttle, however you will notice returing an object literial from an
.end()'s
callback will get merged onto the next request and passed over.
Promises
The DeployR JavaScript library supports the implementation of the promises/A+ specifications:
deployr.io('/r/user/login')
.data({ username: 'testuser', 'secret' })
.end()
.then(function(res) {
return res; // pass result to the next `.then()` if any...
}, function(err) {
console.log(err);
});
Or chained together:
deployr.io('/r/user/login')
.data({ username: 'testuser', 'secret' })
.end()
.then(task1)
.then(task2)
.then(task3);
Sequencing Requests
Somtimes it is necessary to run an series of chained DeployR requests in sequence and without overlap. Managing this asynchronously while maintaining execution order can get messy. For that reason the DeployR JavaScript library provies the deployr.pipeline() method:
function firstTask() {
return deployr.io('/r/user/login')
.delay()
.data({ username: 'testuser', password: 'secret' })
.end()
.io('/r/project/create')
.end();
}
function secondTask() {
return deployr.io('/r/user/about')
.delay()
.end()
.io('/r/user/logout')
.end();
}
// Runs the `firstTask` task batch first and when complete will run the
// `secondTask` batch without overlap
deployr.pipeline([firstTask(), secondTask()]);
The return value for a call to .pipeline()
will be a promise:
deployr.pipeline(tasks)
.then(function(chain) {
// `chain` contains the result for each request in each task that ran
// through the pipeline
chain.results.forEach(function(task, index) {
console.log('---------------------------------');
console.log('Task ' + index + ' results');
console.log('---------------------------------');
task.forEach(function(result) { console.log(result); });
});
}, function(err) {
// All the task batches were rejected
});
Piping Data
Write programs that do one thing and do it well. Write programs to work together. Write programs to handle text streams, because that is a universal interface.
-- Doug McIlroy, inventor of Unix pipes and one of the founders of the Unix tradition
The Node.j client allows you to pipe data to and from the DeployR request. For example, piping the content of a file as the request:
deployr.io('/r/repository/file/upload')
.attach('/path/to/the/file', 'satscores.csv')
.stream() // creates a readable stream
.end();
Or piping the DeployR response to a writable stream:
var fs = require('fs');
deployr.io('/r/repository/script/execute')
.data({ filename : 'Map.R', author: 'testuser' })
.pipe(fs.createWriteStream(__dirname + '/deployr.log'));
Or chain multiple .pipe()
calls together using the Node.js Stream interface.
For example, using the popular JSONStream
library to apply a data transform on the DeployR response:
// prase all file urls out of the response and stream them to stdout
var JSONStream = require('JSONStream');
deployr.io('/r/repository/script/execute')
.data({ filename : 'DeployR - Hello World', author: 'testuser' })
.numeric('input_randomNum', 10)
.pipe(JSONStream.parse('deployr.response.execution.artifacts.*.url'))
.pipe(JSONStream.stringify(false))
.pipe(process.stdout);
Verbose APIS
The DeployR JavaScript client library supplies some sugar methods .auth() and .script() for a couple API invocations that are a little verbose and redundant:
/r/user/login
From:
deployr.io('/r/user/login') .data({ username: 'testuser', password: 'secret' }) .end();
To:
deployr.auth('testuser', 'secret');
/r/repository/script/execute
From:
deployr.io('/r/repository/script/execute') .data({ filename: 'script.R', author: 'testuser', directory: 'root' }) .end();
To:
deployr.script('/testuser/root/script.R').end();
Or:
deployr.script('script.R', 'testuser', 'root').end();
Or:
deployr.script({ filename: 'script.R', author: 'testuser', directory: 'root' }).end();
/r/project/execute/script
From:
var project = 'PROJECT-5ac47aff-8f8c-4ac2-8a8f-6e31468e6a19'; deployr.io('/r/repository/script/execute') .data({ filename: 'script.R', author: 'testuser', directory: 'root', project: project }) .end();
To:
deployr.script('/testuser/root/script.R', project).end();
Or:
deployr.script('script.R', 'testuser', 'root', project).end();
Or:
deployr.script({ filename: 'script.R', author: 'testuser', directory: 'root', project: project }).end()
This is completely optional and comes down to preference. It aims at (hopefully) making the more frequently used APIS easier to understand and write.