Skip to main content

JavaScript Client Library Introduction

Version 8.0.5

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

  1. Download the DeployR JavaScript client library and begin using in your application to communicate with DeployR.

  2. Unzip the compressed folder. You will see the following structure:

  3. 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.

  4. 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>

    1. 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.

    2. From the command line:

      1. Navigate to the root /deployr/ directory.

      2. Run npm install --production. npm will look at the package.json file and automatically install the necessary local runtime dependencies listed there.

    3. 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.

    4. require the directory:

      var deployr = require('deployr');

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

  1. Make sure CORS is enabled on DeployR in the deployr.groovy file. If it isn't, enable it now.

  2. In your JavaScript, indicate that the requests will be using CORS. This is done through the deployr.configuration() method using the cors 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)
+Internet Explorer 10 and Safari users:
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.

Important! A request to DeployR will not be sent until the .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.


Learn More: R Inputs


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:

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
   });

Learn More: Error handling


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.