Giter VIP home page Giter VIP logo

hyperagent's Introduction

hyperagent.js Build Status Coverage Status devDependency Status

hyperagent.js is a JavaScript library for consuming HAL hypermedia APIs in the browser.

Installation

Download with bower or alternatively install manually.

bower install hyperagent

Compatibility

hyperagent aims to be compatible with draft 5 of the HAL specification. As the spec is still being developed, hyperagent.js is unlikely to have a stable API until HAL itself stabilizes.

Dependencies

hyperagent.js has one hard and two soft dependencies:

  • URI.js (+ URITemplate.js)
  • A jQuery-compatible AJAX implementation (e.g jQuery, zepto, reqwest), default: jQuery
  • A Promise/A+ implementation (e.g q, RSVP.js), default: q

To use other than the default implementations, see configure below.

Demo

You can see the library in action in the live sample application and check out the source in sample/.

Plugins

hyperagent.js provides some facilities for plugins to hook into and work with data from the response object. There is currently one plugin:

Example

The following JSON response represents the entry point of https://api.example.com and shall serve as an example for using hyperclient.

{
  "_links": {
    "self": {
      "href": "/"
    },
    "curies": [
      {
        "name": "ht",
        "href": "http://haltalk.herokuapp.com/rels/{rel}",
        "templated": true
      }
    ],
    "ht:users": {
      "href": "/users"
    },
    "ht:signup": {
      "href": "/signup"
    },
    "ht:me": {
      "href": "/users/{name}",
      "templated": true
    },
    "ht:latest-posts": {
      "href": "/posts/latest"
    }
  },
  "_embedded": {
    "ht:post": [{
      "_links": {
        "self": {
          "href": "/posts/4ff8b9b52e95950002000004"
        },
        "ht:author": {
          "href": "/users/mamund",
          "title": "Mike Amundsen"
        }
      },
      "content": "having fun w/ the HAL Talk explorer",
      "created_at": "2012-07-07T22:35:33+00:00"
    }, {
      "_links": {
        "self": {
          "href": "/posts/4ff9331ee85ace0002000001"
        },
        "ht:author": {
          "href": "/users/mike",
          "title": "Mike Kelly"
        },
        "ht:in-reply-to": {
          "href": "/posts/4ff8b9b52e95950002000004"
        }
      },
      "content": "Awesome! Good too see someone figured out how to post something!! ;)",
      "created_at": "2012-07-08T07:13:34+00:00"
    }]
  },
  "welcome": "Welcome to a haltalk server.",
  "hint_1": "You need an account to post stuff..",
  "hint_2": "Create one by POSTing via the ht:signup link..",
  "hint_3": "Click the orange buttons on the right to make POST requests..",
  "hint_4": "Click the green button to follow a link with a GET request..",
  "hint_5": "Click the book icon to read docs for the link relation."
}

Instantiating

Using defaults:

var Resource = require('hyperagent').Resource;
var api = new Resource('https://api.example.com/');

api.fetch().then(function (root) {
  console.log('API root resolved:', root);
  assert(root.url() === 'https://api.example.com/');
}, function (err) {
  console.warn('Error fetching API root', err);
});

With custom connection parameters:

var Resource = require('hyperagent').Resource;
var api = new Resource({
  url: 'https://api.example.com/',
  headers: { 'Accept': 'application/vnd.example.com.hal+json' },
  username: 'foo',
  password: 'bar',
  ajax: {
    foo: 'bar'
  }
});

The options username, password, headers as well as any additional options in ajax will be passed on to the AJAX implementation. For example, the above request would call the underlying AJAX function with these parameters:

config.ajax({
  url: 'https://api.example.com/',
  headers: { 'Accept': 'application/vnd.example.com.hal+json' },
  username: 'foo',
  password: 'bar',
  foo: 'bar'
});

WARNING: Note that defining a success or error function in the ajax options hash will make Hyperagent unable to properly handle AJAX calls (Hyperagent ties into these two ajax options to resolve promises). A configuration setting these options will make it so Hyperagent resources will never properly resolve/throw errors:

var Resource = require('hyperagent').Resource;
var api = new Resource({
  url: 'https://api.example.com/',
  ajax: {
    success: function(){}, // DON'T DO THIS
    error: function(){}    // OR THIS
  }
});

Attributes

Attributes are exposed as the props object on the Resource instance:

var welcome = root.props.welcome;
var hint1 = root.props.hint_1;

assert(welcome === 'Welcome to a haltalk server.');
assert(hint1 === 'You need an account to post stuff..');

Embedded resources

Embedded ressources are exposed via the embedded attribute of the Resource object and can be accessed either via the expanded URI or their currie. Resources are Resource instances of their own.

assert(root.embedded['ht:post'][0].props.content ===
       'having fun w/ the HAL Talk explorer');

root.embedded['ht:post'][1].links['ht:in-reply-to'].fetch().then(function (post) {
  console.log('User replying to comment #2:', post.links['ht:author'].props.title);
})

Sub-resources like embedded or links are also enumerable, so you can use them like this:

var contents = root.embedded['ht:post'].map(function (post) {
  return post.props.content;
});
assert(contents[0], 'having fun w/ the HAL Talk explorer');
assert(contents[1], 'Awesome! Good too see someone figured out how to post something!! ;)');

Links

Links are exposed through the links attribute and are either Resource instances or a list of instances.

Using standalone links:

assert(root.links.self.url() === root.url());

// Access via currie ht:users
root.links['ht:users'].fetch().then(function (users) {
  // Access via expanded URI
  return users.links['http://haltalk.herokuapp.com/rels/user'][0].fetch();
}).then(function (user) {
  console.log('First user name: ', user.props.title);
});

To use RFC6570 templated links, you can provide additional options to the link function:

root.link('ht:me', { name: 'mike' }).fetch().then(function (user) {
  assert(user.props.username === 'mike');
});

Using the url() accessor, you can get the absolute URL of the resource you are accessing:

var url = root.links['ht:signup'].url();
assert(url === 'http://haltalk.herokuapp.com/signup');

By default, fetch() only requests the resource once from the server and directly returns a promise on the cached result on successive calls. If you want to force a refresh from the server, you can set the force flag in an options object:

root.links['ht:users'].fetch().then(...);

// Enforce a refresh.
root.links['ht:users'].fetch({ force: true }).then(...);

If you want to pass in custom options to the AJAX call, you can specify them via the ajax option:

root.links['ht:users'].fetch({ ajax: { headers: { 'X-Awesome': '1337' } } }).then(...);

Curies

Curies are supported in that you can access links, properties and embedded resources either with their short form or the expanded link, which means the following two statements are equivalent:

var link1 = root.links['ht:signup'];
var link2 = root.links['http://haltalk.herokuapp.com/rels/signup'];

assert.deepEqual(link1, link2);

API

configure

Hyperagent depends on an AJAX and a Promise/A+ implementation, which are replaceable as long as they implement the common interface. The default implementations are:

  • ajax -- window.$.ajax
  • defer -- window.Q.defer
  • _ -- Hyperagent.miniscore (based on underscore.js)

You can use the configure function to override those defaults:

Hyperagent.configure('ajax', reqwest);
Hyperagent.configure('defer', RSVP.Promise);
Hyperagent.configure('_', lodash);

Resource#url()

Returns the URL of where the resource was or is about to be fetched from. This value is always an absolute, normalized URL in contrast to the value of links.self.href.

Resource#fetch([options])

Loads the document from the URL provided and enabled the access via props, links, and embedded. Returns a chainable promise.

(new Resource('http://example.com/')).fetch().then(function (api) {
  console.log('href: ', api.links.self.props.href);
});

The optional options object can have these keys:

  • force: defaults to false, overrides the internal cache
  • ajax: overrides Resource-level AJAX options

Resource#loaded

A boolean indicating whether the resource has been completely loaded or is potentially incomplete. Resources retrieved via fetched() and embedded resources are considered as fully loaded.

Resource#links

An object, containing links with their rel as key. Links are resources, lazily created on access or arrays of links.

Resource#link(rel[, params])

Creates a new link resource identified by the given rel and expands the link template if params are provided. For non-templated links, those too calls are equivalent:

assert.deepEqual(api.links.self, api.link('self'));

Calling with parameters:

// Given a `me` URI template of `http://example.com/users/{username}`
var link = api.link('me', { username: 'sindresorhus' });
assert(link.url() === 'http://example.com/users/sindresorhus');

Resource#embedded

An object containing all embedded resource, created lazily on access.

Resources#props

An object containing all properties on the current resource. This includes all properties of the resource, except _links and _embedded.

Resource#related(rel[, params])

Navigates the link identified by the given rel regardless of whether it is in the _embedded or _links section. If params are given they are used to expand the URI template. This allows consumers of this API to be indifferent to which section of the HAL document contains the link.

// Given a set embedded or normal `post` links
var posts = api.related('post');
assert(posts[0].url() === 'http://example.com/posts/4ff8b9b52e95950002000004');

Calling with parameters:

// Given a `me` URI template of `http://example.com/users/{username}`
var me = api.related('me', { username: 'sindresorhus' });
assert(me.url() === 'http://example.com/users/sindresorhus');

Resource.resolveUrl(oldUrl, newUrl)

Combines an old with a new URL:

var res = Hyperagent.Resource.resolveUrl('http://example.com/foo', '/bar');
assert.equal(res, 'http://example.com/bar');

Contributing

Please follow the existing code style and the commit message style from conventional changelog.

FAQ

Promises?

For now, hyperagent only supports a promise-based callback mechanism, because I believe that working with Hypermedia APIs inherently leads to deeply nested code using the standard callback-based approach. Promises, however, solve this beautifully by providing chaining mechanisms to flatten those calls.

It is not impossible though, that hyperagent will eventually get an alternative callback-based API.

License

Licensed under MIT

hyperagent's People

Contributors

adstage-david avatar jwoodcock avatar morrislaptop avatar nico-adstage avatar passy avatar pezra avatar robincoma avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

hyperagent's Issues

Suggestion to avoid distinction between retrieving a rel via _embedded or _links

Consider this example where a GET request to https://example.org/blog-post at one point in time responded with (situation A):

{
  "_links": {
    "self": { "href": "https://example.org/blog-post" },
    "author": { "href": "https://example.org/people/alan-watts" }
  },
  "_embedded": {
    "author": {
      "_links": {
        "self": { "href": "https://example.org/people/alan-watts" }
      },
      "name": "Alan Watts",
      "born": "January 6, 1915",
      "died": "November 16, 1973"
    }
  }
}

And some while later (or earlier) with (situation B):

{
  "_links": {
    "self": { "href": "https://example.org/blog-post" },
    "author": { "href": "https://example.org/people/alan-watts" }
  }
}

If you consider the Hypertext cache pattern, you could see the embedding as an optional optimization, and your client code shouldn't have to check whether a "rel" is embedded or not for every possible resource.

So for example Resource("https://example.org/blog-post").author.name is the same for both situation A and B (but in situation B it would result in an extra request being done).

See also https://github.com/gamache/hyperresource as a (Ruby) example of one way this distinction can be avoided in client code.

Support for bearer tokens and/or JWT?

I don't see any explicit support for bearer tokens or JWT in hyperagent. It looks like you could specify headers in the same way you could bundle username and password.

Is the current best practice to simply generate an Authorization header to insert the necessary information? Is there interest in a PR that adds special functionality to explicitly add these somehow?

What I have in mind is the ability to persist a bearer token or JWT so that it is automatically added to each request. Any issues? Would a PR for this be accepted?

Build

How do I build hyperagent?

I ran npm install && bower install && grunt build

I get a parse error for uglify (unexpected token: keyword (function)). If I comment out the uglify option the transpile does not seem to work.

Nested _embedded objects slow property access

When loading HAL object with nested _embedded objects, first _embedded being an array of object containing _embedded objects and then trying to iterate thru the first embedded and access the nested embedded property, access to the property is very slow. And by slow I mean seconds.

I'm loading a HAL object which contains embedded array of 500 objects. Each one of those objects contains one embedded object with properties. Looping through this list while accessing nested embedded property takes between 190 and 250 sec in Chrome on Mac (everything latest)

Example:

_embedded: {
      items: [ 
          {
               _embedded: {
                     otherItem: {
                            prop1: 'foo'
                     },
                     oneMore: {
                            prop2: 'bar'
                     }
               },
               itemProp: 'fooBar'
          } 
      ]
}

Iterating thru items and trying to access:

items.filter(function(item) { 
     return item.embedded.otherItem.props.prop1 === 'foo';
});

Simplify resource interface if possible

This is much more like an RFC than an issue. First, let me say that this library is already very good. I think that you'll find my feedback useful.

When dealing with resources with other library or ad-hoc ajax call, we usually do something like:

var theResource = $.getFromServerSomehow({id: 3});
console.log("loaded resource with id: " + theResource.id);

We generally assume that what is returned from the server is a Resource as the server defines it. With Hateoas and Hyperagent on the other side, the result is a "surrogate" object, which let you "rebuild" the intended resource.

For example if I have a collection with count, pageNumber and collection, I need to access the variable this way:

// var theResource = lazy loaded from server
console.log("count", theResource.props.count);
console.log("pageNumber", theResource.props.pageNumber);
console.log("collection", theResource.embedded.collection[0].props.id);

This may be simplified IMO to:

// var theResource = lazy loaded from server
console.log("count", theResource.count);
console.log("pageNumber", theResource.pageNumber);
console.log("collection", theResource.collection[0].id);

I know that this may be complex to implement, but IMO will increase productivity and (guessing mode: on) popularity.

Here' my suggestion:

  1. invert the logic of prop. Instead of having prop for the end-user, the Resource classes should have a _prop which track the internal status which must be considered private by the end-user.
  2. embedded resources can be used both as properties (with a simple accessor as in point 1) or by the relation evaluation. The former method is more convenient (see the example above), but will break if the server decide to not inline the resource. The latter method is more stable in term of server changes, but require the end-user to use promises.
  3. Link will be link anyway. :)

I'm looking forward for your feedback! And, if you ever decide to go this way, I promise I'll help you with the implementation or testing ;)

hyperagent on npm?

Hey โ€” I hope I am not abusing the issues for this questions.

I was wondering why hyperagent is available through npm? I see you guys have a package.json etc. in place. So I guess all that is missing is someone to increment the version in it and then do npm publish to push the release to npm.

Is this by choice or something no one got to? :)

The backstory is that we are not using bower, etc. and currently we install hyperagent by using a git-URL in our own package.json which comes with its own set of issues.

Thanks!

Compatibility with Angular's $http/$q services

I'm wondering if anyone has attempted to get angular's native $http service to work as the request lib Hyperagent uses?

It seems straightforward to decorate $http accordingly but I haven't tried it myself yet because I'm in a project where we've accepted jQuery.ajax long ago.

The important issue here is making Angular aware of requests HA is making so as to work seamlessly with Angular's internals (interceptors and so forth)

Internal cache

When I do 2 consecutive call to a same link with differrents parameters.
The response is the same.
I try to force the cache but it doen't work.

I work with angular and my custom API:

link: function(linkName, linkParams, fetchParams, currentRoot){
currentRoot = currentRoot || root;
var deferred = $q.defer();
var resource = currentRoot.link(linkName, linkParams || {});
if(!resource){
throw 'No resource with name : ' + linkName;
}
if (fetchParams) {
fetchParams = {ajax: fetchParams};
}
angular.copy(resource).fetch(fetchParams || {}).then(function (data) {
deferred.resolve(data);
}).fail(deferred.reject);
return deferred.promise;
},

This work around works correctly :
angular.copy(resource) but it is not efficient.

My resource var is not stateless.

What's the best way to handle link metadata?

Problem: Links can have metadata like title, profile, href. These are stored like normal properties and were reset when fetch() was called on the link as it created a new props attribute. My temporary fix is to extend the props instead of replacing them.

This still feels like a hackish solution, since properties of the resource can overwrite the metadata attributes of the link. Also, resources may have different properties based on whether they are resolved as links or as stand-alone objects. One idea I had would be to store metadata in a different attribute (attrs? meta?).

Make it fast

On my phone the integration tests take around 80ms.

passing in ajax options for fetch

First of all, thanks for the library! It's been great so far.

In the docs for Resource.prototype.fetch() it says:

  • In addition, all options from options.ajax are mixed in.

How are options actually set for linked resources? I have a resource that I load via fetch() and one of the _links is a resource that responds to POST, which is specified in the docs for that rel. LazyResources are created automatically for the _links when I fetch the first resource though and there doesn't seem to be any way to pass in options to that object's constructor.

I guess what I'd like to be able to do is pass in ajax options when I call fetch on the resource like this:

myResource.links['dm:create_entry'].fetch({ ajax: { type: 'POST', data: { ... } } })

Is there something I'm missing? Is fetch meant to be used only for safe operations? Sorry if this is obvious.

Handling POST requests

This isn't really an issue but more of a question (that might highlight an issue...but I'm not sure).

I've created a HAL based API. So hyperagent seems well suited for that. I don't 100% understand when I'm supposed to fetch and when I'm not, but that is a separate, manageable issue.

The main issue I'm having is with handling POST requests. There are cases where I have to do a POST to create resources on the server. I'm aware of the "dialects" to HAL for such things, but I'm not using those (at least in this case) because I'm uploading files and I don't think HAL makes any sense in that context (although correct me if I'm wrong).

Being a good REST api, the POST returns a 201 status and a Location header. But in a hyperagent context, that is just another URL, not a proper Resource object. What I do now is basically:

var loc = resp.headers("Location");
var new_url = Hyperagent.Resource.resolveUrl(base_url, loc);
return new Hyperagent.Resource(new_url).fetch();

So basically, I read the Location header of the POST response (which is just that path portion), combine that with the base_url for the API to get a fully qualified URL and then create a new Resource object from that. This feels odd to me because I feel that when I stay in the realm of GET, I only have to create one "api" Resource and I can navigate entirely from there (which is how it should be, right).

But here, I have to start messing around with URLs again, which is exactly what I was hoping to avoid with a very hypermedia centric API. The strange thing is...why should a POST complicate things. The Location header is a standard way to return the URL of a new resource. Why not take advantage of that somehow? I didn't see any reference to the Location header anywhere in the hyperagent source so it doesn't seem like this is taken into account.

What I envision is something like this:

var res = api.link('files').post(data, { headers: ... })

The idea here would be a method, post, that (behind the scenes) makes a POST request to the files resource, parses the Location header (like I've done above?) and then returns the resource. In this way, the client code looks clean and avoids all this URL handling?

Is this the "proper" way to go about this or am I missing something. I'm happy to do the work and submit a PR. I just want to make sure a) I'm not doing it wrong, b) there isn't already a way to do this cleanly and c) if it were to be done that this is the way to do it.

Comments?

Usage questions

  1. I see how to navigate resources using resource.fetch(). Is there any way to create/update/delete resources on the server? In other words, is there any way to generate POST, PUT and DELETE methods?
  2. In a MVC based client application, do you see the "Resource" object as representing the Model itself or something that populates the Model? Generally a Model is capable of sending events to views, performing validations etc. The hyperagent Resource does not seem to have these capabilities. Specifically, I am using Backbone.js as my MVC framework and trying to figure out how the hyperagent Resources would interact with Backbone models.
  3. Is there any plan to extend the usage of hyperagent on the server side? For example, can I pull application entites from the database and expose them as resources? This would mean populating resources from application entities and adding links etc. to them.

Allowing a global error handler/documenting pitfall of {ajax: {error}} as an option

I've been trying to find the reason I can't properly handle an AJAX error using hyperagent and traced it back to a global configuration setting somebody placed in our project to catch 401s and redirect.

Seems that the configuration option below breaks the promise that Hyperagent sets up, so I'll need to reconfigure that, but it might be nice to allow defining a global error handler that can resolve alongside or bubble up the reject?

If nothing else, might be good to just document this in the README as a potential pitfall - don't set ajax.error in the hyperagent config.

var Resource = require('hyperagent').Resource;
var api = new Resource({
  url: 'https://api.example.com/',
  ajax: {
    error: function(xhr, status, error){ 
      if(status == "error" && xhr.status == 401){ window.location = '/login' }
    }
  }
});

PUT, DELETE

Hello,

What is the way to do PUT and DELETE operation with a success callback?

After a fetch with {ajax: 'PUT'}, the HTTP status code is 200 but the fail callback of the promise is called.

The error is : "Unexpected end of input"

Thanks for your answer

Reduce dependencies

jQuery, Q, underscore.js and URI.js is ridiculous, even if they are configurable. I should pull out the parts of jQuery and _ that I actually need. Q and URI.js should work ...

fetch() caches previous responses, returns bad results

When an array is returned from a fetch() call, any consecutive fetch() calls use the same (cached) array as a response, overwrite it with any incoming indices, and returns the resulted array to the caller.
e.g.

some_link_resource.fetch() ->
network response: [ object1, object2, object3, object4 ]
fetch results: [ object1, object2, object3, object4 ]

some_other_link_resource.fetch() ->
network response: [ object5, object6 ]
fetch results: [ object5, object6, object3, object4 ]

some_third_link_resource.fetch() ->
network response: [ ]
fetch results: [ object5, object6, object3, object4 ]

forEach error in IE8 and IE7

Hi,

So when I started testing this in IE8 and 7, the console gave me the following error:

Object doesn't support property or method 'forEach'.

Is it possible to support the older JS engines? Thanks.

Update Q and URIjs

Q should be straight-forward since we only use the A+ featues (I think), but URIjs should be upgraded carefully.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.