a very small library for creating browser-side modules that map to api-side resources
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
auto
src
test
.gitignore
.travis.yml
Gruntfile.coffee
README.md
bower.json
karma.conf.coffee
package.json

README.md

flyby

codecov Build Status Bower version

Bower Repo | Source Repo

A lightweight library for mapping client-side interactions to server-side apis. The project was heavily influenced by angular's ngResource, and was originally built to bring the functionality out of the framework as a micro-library that can be used as a part of any project.

  1. usage
  2. installing
  3. building

Usage

Flyby is a factory - it builds an interface that expose actions which provide semantically intuitive communication between client side data and server side apis.

Defining a resource

Flyby(<url template>, <url mappings>, <custom actions>)

param type details
url template string The string used by the resource factory to generate the final url based on data sent into the api call.
url mappings object An object with key-value pairs used by the factory to map action input values to url parameters.
custom actions object An object that defines custom actions following the action interface defined below.

url template

The url template of a resource lets the resource build the uri that gets sent to the server.

url mappings

In order for the factory to know what information to use from the request data in the url template, definitions must provide an object to facilitate that operation. Each property of the object represents the name of a parameter to be written into the url template. For example, a resource defined with a url template of /api/user/:id/:fn would expect to see url mappings object with both an id and fn property. This could look like:

var User = Flyby("/api/user/:id/:fn", {
  id: "@id",
  fn: "@fn"
}, null);

If the parameter value is prefixed with @ then the value for that parameter will be extracted from the corresponding property on the data object (provided when calling an action method). This means that executing the get method on the User resource defined above:

User.get({id: 1}, callback);

would create a GET request to: /api/user/1. Notice how the fn parameter was removed from the generated string, and the id parameter was replaced with the number 1.

Actions & Default actions

Actions are the functions/methods included on the returned service created by the Flyby factory - e.g create, update, destroy, etc... Common API interactions are already covered by the Flyby factory out of the box. They are:

name method body example
get GET no User.get({id: 1}, callback);
update PATCH yes User.update({id: 1, name: "fred"}, callback);
create POST yes User.create({name: "fred"}, callback);
destroy DELETE yes User.destroy({id: 1}, callback);

Each of these actions are objects based on this model:

name type default notes
url string the original url template If this property is left blank (or not a string) the action will "inherit" the url from the resource. See customizing action urls.
method string GET This is the http verb that the resource will use for the action's request.
has_body boolean false Lets the factory know if it should use the data sent into it as query string information or request body data.
params object none Allows the action to override the resource's default url mappings.
transform.response function default response transform This function gets called once the xhr has finished successfully, allowing users to manipulate their data before it is used by the callee.
transform.response function default response transform Allows users to override the function applied to the data before it is sent into the xhr.

The callback

Unlike the ngResource service, Flyby only uses "error-first callbacks" to handle the asnychronous control-flow. This was done to avoid needing a promise shim/library - keeping the library smaller and free of third-party dependencies, not because error-first callbacks are a better option than promises.

These error-first callback functions are used by the Flyby factory to communicate the successfulness of the action's operation. A typical usage might look something like this:

function callback(err, result, xhr) {
  if(err)
    return window.alert(xhr.status);

  // yay, we have a result
}

User.get({id: 1}, callback);

The function will be sent the following arguments:

argument index notes
0 An error object, if the status was unsuccessful.
1 The result of transforming the response text
2 The actual XMLHttpRequest used by the action to send the http request.

Request & Response transforms

Though flyby does define default functions to handle massaging of data sent and received from the server, user's can override this behavior by providing a transform object with response and request functions.

defaults

transform behavior
response if the response headers contain Content-Type: application/json, this transform will try running JSON.parse on the text and returning the result.
request if the request data is not a native File, FormData, or Blob data types, this transform will try running JSON.stringify on the data.

example

define([
], function() {

  var User = Flyby("/api/users/:id", {id: "@id"}, {
    emote: {
      metod: "POST",
      has_body: true,
      transform: {
        request: function(data) {
          return {"emote": data};
        },
        response: function(response) {
          return JSON.parse(response).result;
        }
      }
    }
  });

  return User;

});
require([
  "User"
], function(User) {

  /* if response succeeds (200 status code) and it responds with:
   *
   * {result: "freddy smiles."}
   *
   * this callback would log: "freddy smiles."
   */
  function finished(err, result) {
    console.log(result);
  }

  /* would send a POST request to /api/users
   * with the request body:
   *
   * {emote: "smile"}
   */
  User.emote("smile", finished); 

});

How "successfulness" is determined

Flyby bases's an action's resulting successfulness on the status code of the XMLHttpRequest used during the call.

Anything greater than 200 and less than 300

If the successfulness condition is satisfied, the first argument of the callback will be falsey.

Customizing action urls

Sometimes it's helpful to define custom actions on a resource when the api supports "subresource" routes:

var User = Flyby("/api/users/:id", {id: "@id"}, {
  tracks: {
    method: "GET",
    url: "/api/users/:id/tracks"
  }
});

Complete example

Lets say we're using Flyby in a requirejs project that uses the grunt requirejs optimizer task to compile the project.

src/js/resources/user.coffee

define [
  "API_HOME"
  "Flyby"
], (API_HOME, Flyby) ->

  User = Flyby "#{API_HOME}/users/:id", null, null

src/js/resources/user_account_mapping.coffee

define [
  "API_HOME"
  "Flyby"
], (API_HOME, Flyby) ->

  UserAccount = Flyby "#{API_HOME}/user_account/:id", null, null

src/js/resources/account.coffee

define [
  "API_HOME"
  "Flyby"
], (API_HOME, Flyby) ->

  Account = Flyby "#{API_HOME}/accounts/:id", null, null

src/js/managers/account.coffee

define [
  "resources/user"
  "resources/user_account_mapping"
  "resources/account"
], (User, UserAccountMapping, Account) ->

  class AccountManager
    
    constructor: (@user, @mappings=[], @accounts=[]) ->

    addBankAccount: (account_id, callback) ->
      mappings = @mappings
      created_mapping = null

      loadedAccount = (err, account) =>
        return callback err if err
        @accounts.push account
        callback false, created_mapping

      addedAccount = (err, new_mapping) ->
        return callback err if err
        mappings.push new_mapping
        created_mapping = new_mapping
        Account.get {id: account_id}, loadedAccount

      UserAccountMapping.create {
        user: @user.id
        account: account_id
      }, addedAccount
      

    updatePassword: (new_password, callback) ->
      finished = (err) =>
        callback err if err
        @emit "updated:password"
        callback false

      User.update {password: new_password, id: @user.id}, finished

Installing

The compiled source for this library is published to a separate respository during the build automation, and automatically published to bower. This allows developers to install the compiled code through the bower cli and maintain it's version in their bower.json:

$ bower install flyby --save

This will download a copy of the compiled library and save it into the bower_components directory of your current project. From here, it is recommended that you use a tool like grunt to concatenate your vendor libraries together, including flyby. Using this approach will expose the Flyby api globally via the window object.

requirejs

The project is also compiled with a requirejs guard, making it an eligible requirejs module:

requirejs.config({
  paths: {
    "Flyby": "path/to/your/installation"
  }
});

require([
  "Flyby"
], function(Flyby) {

  return Flyby("/api/something/:id", {"id": "@id"}, {});

});

Building

Flyby is built using coffeescript and grunt.

$ git clone git@github.com:dadleyy/flyby.git
$ cd flyby
$ npm i
$ grunt

The default grunt task will clean, compile and run the tests for the code:

image

release

During release builds, grunt release can be executed to generate the minified code using uglify.