Socket
Socket
Sign inDemoInstall

gangway

Package Overview
Dependencies
Maintainers
2
Versions
34
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

gangway - npm Package Compare versions

Comparing version 1.4.0 to 2.0.0-alpha

58

CHANGELOG.md
# Changelog
## 2.0.0
- The promise returned from endpoints now exposes a `request`
reference point to the original superagent request
- Promises are not included by default. If not polyfilled,
use the `Promise` option to provide the Promise implementation to
use for Gangway.
- `route` now operates one step shallower. See README for usage.
- routes now respect relative URLs. Absolute urls will operate from the
base path.
### Upgrading
If you are upgrading from 1.0, there are a couple of necessary
changes:
#### Promises
If a global Promise object is not found, Gangway will throw an
error. To eliminate this error, provide a Promise library to Gangway:
```javascript
var Promise = require('promise')
Gangway({
Promise: Promise
})
```
#### Routes
The two-level approach to declaring routes has been removed. The
following patterns should be switched out:
```javascript
// OLD
API.route({
users: {
get: {
path: '/users'
}
}
})
// NEW (correct)
API.namespace('users').route{
get: {
path:'/users'
}
}}
```
#### Relative routes
Routes without a `/` at the beginning will resolve to their
namespace. Check to ensure that your paths are resolving correctly for
your endpoints if you use absolute paths.
## 1.4.0

@@ -4,0 +62,0 @@

90

docs/guides/hello-gangway.md

@@ -22,11 +22,10 @@ # Hello Gangway

Speaking of which, endpoints may be created using the `route` method:
So what is an endpoint? An endpoint describes a request to an API for
information. In Gangway, they are created with `route`:
```javascript
API.route({
users: {
read: {
method : 'GET',
path : 'users/{id?}'
}
readUsers: {
method : 'GET',
path : 'users/{id?}'
}

@@ -36,24 +35,34 @@ })

Executing this code will produce namespaces on `API` for the `users`
resource. This is the second layer of configuration options: the route
layer. Options given to routes will override the general options
specified to the API.
In the code above, `route` will assign a `readUser` method to `API`
that performs a GET request to `/users/{id}`. The `?` in the path
indicates that the id parameter is optional.
You can now perform a `GET` request to `http://example.com/users`
with:
### Namespaces
In the previous example, we created an endpoint directly on
`API`. However RESTful APIs are organized into discrete resources. In
order to help you stay organized, Gangway provides a `namespace`
method that will make all subsequent method calls operate within a
given path:
```javascript
API.users.read().then(function(data) {
console.log(data)
var users = API.namespace('users')
users.route({
read: {
method : 'GET',
path : '{id?}
}
})
```
In the code above, a request is sent out to fetch information. Gangway
returns a [Promise](https://www.promisejs.org/) to represent the
request. When this promise resolves, it will pass the body of the
request to `.then`.
This is _nearly_ the same as using `API.route`, however there are a
couple of differences. First, we don't need to include `users` in the
path, because the route is already working from the `users`
namespace. Second, the endpoint is added at `API.users` instead of
simply being available at `API`.
## Individual requests
The example in the previous section had a path property of
The examples in the previous sections had a path property of
`users/{id?}`. Gangway uses the

@@ -82,5 +91,6 @@ [route pattern matching from HapiJS](http://hapijs.com/tutorials/routing)

For RESTful API endpoints, manually producing a route for every action
is tedious. In light of this, Gangway provides an additional
`resource` method for quickly building routes for RESTful resources:
`route` and `namespace` reduce a lot of boilerplate, however even this
can get tedious when mapping actions for every resource of a RESTful
API. To account for this, Gangway provides a `resource` method. This
method helps you to quickly building endpoints for RESTful resources:

@@ -94,25 +104,21 @@ ```javascript

```javascript
API.route({
API.namespace('users').route({
create: {
method: 'POST',
path: '{id}'
},
users: {
create: {
method: 'POST',
path: 'users/{id}'
},
read: {
method: 'GET',
path: '{id?}'
},
read: {
method: 'GET',
path: 'users/{id?}'
},
update: {
method: 'PATCH',
path: '{id}'
},
update: {
method: 'PATCH',
path: 'users/{id}'
},
destroy: {
method: 'DELETE',
path: 'users/{id}'
}
destroy: {
method: 'DELETE',
path: '{id}'
}

@@ -119,0 +125,0 @@ })

@@ -5,4 +5,2 @@ # Working with Promises in Gangway

2. [Promises in Gangway](#promises-in-gangway)
3. [Using done() instead of then()](#using-done-instead-of-then)
4. [Using nodeify for error-first callbacks](#using-nodeify-for-error-first-callbacks)

@@ -18,6 +16,3 @@ ## Overview

For all endpoints created by Gangway, promises are returned to
represent the
request. [The usage of promises is well-documented](https://www.promisejs.org/),
however in Gangway we have made some decisions that build on top of
promises to support a better developer experience.
represent the request.

@@ -29,7 +24,5 @@ The examples for this guide will assume the following setup code:

API.route({
users: {
read: {
path: 'users/{id*}
}
API.namespace('users').route({
read: {
path: '{id?}'
}

@@ -41,6 +34,2 @@ })

Gangway uses the [then/promise](https://github.com/then/promise)
implementation of Promises. This means that it follows the standard,
A+ Promise specification:
```javascript

@@ -50,4 +39,10 @@ API.users.read().then(handleSuccess, handleRejection).catch(handleError)

## Using done() instead of then()
Gangway does not include a Promise implementation, it is up to you to
provide one. We recommended using the [then/promise](https://github.com/then/promise)
implementation of Promises.
Let's go into some reasons why.
### Using done() instead of then() to prevent uncaught errors
Promises are chainable. `then` can be called multiple times, layering

@@ -58,5 +53,5 @@ on behaviors after a promise is resolved:

API.users.read()
.then(doSomething)
.then(doSomethingElse)
.catch(handleError)
.then(doSomething)
.then(doSomethingElse)
.catch(handleError)
```

@@ -105,3 +100,3 @@

## Using nodeify for error-first callbacks
### Using nodeify for error-first callbacks

@@ -108,0 +103,0 @@ [`then/promise`](https://github.com/then/promise) supports an

{
"name": "gangway",
"version": "1.4.0",
"version": "2.0.0-alpha",
"description": "A client-side API abstraction layer",

@@ -22,10 +22,9 @@ "engines": {

"dependencies": {
"promise": "~7.1",
"superagent": "~1.7"
},
"devDependencies": {
"faux-jax": "4.2.2",
"istanbul": "0.4.2",
"faux-jax": "5.0.1",
"istanbul": "0.4.3",
"mocha": "2.4.5"
}
}

@@ -11,3 +11,3 @@ # Gangway

## Usage
## Getting started

@@ -17,2 +17,15 @@ Gangway is a factory function that progressively layers configuration

All request methods return Promises. This means you'll need to include
your own polyfill for Promise (depending on your environment). We
recommend [`then/promise`](https://github.com/then/promise) as it
includes additional methods like `.done()` and `.nodeify()` that
improve interoperability and debugging:
```
require('promise/polyfill')
// Continue with the rest of your code
```
Alternatively, provide `Promise` as a configuration option (see below):
### Create an instance of Gangway

@@ -27,3 +40,4 @@

'x-api-key': 'your-token-for-every-request'
}
},
Promise: require('promise') // Optional, if Promise is not polyfilled
})

@@ -36,4 +50,3 @@ ```

API.route({
users: {
read: {
getUser: {
method : 'GET',

@@ -46,3 +59,3 @@ path : '/users/{id?}' // ? indicates that the parameter is optional

`API.users.read()` will now perform a GET request to
`API.getUser()` will now perform a GET request to
`/users/{id}`. The `?` in the path option specifies that it is

@@ -52,2 +65,20 @@ optional. This is useful when using the same route for index and show

### Add namespaces
Most APIs break down endpoints into discrete resources. Gangway
provides a `namespace` method for this purpose. All routes will be
prefixed with a provided URL segment:
```javascript
API.namespace('users').route({
read: {
method : 'GET',
path : '{id?}' // ? indicates that the parameter is optional
}
}
})
```
`API.users.read({ params: { id: 2 }})` will perform a GET request to `/users/2`.
### Add routes in bulk with `.resource`

@@ -80,2 +111,14 @@

### Accessing the original request object
It is some times useful to access the unwrapped superagent request
object. The value returned from endpoints contains a `request`
property that grants access to this instance:
```javascript
let fetch = API.users.read({ params: { id: '10' } })
fetch.request.abort()
```
## Documentation

@@ -98,2 +141,3 @@

params : Populate bindings in paths and are sent as request bodies. Defaults to body.
Promise : The Promise implementation. Defaults to global.Promise.
path : The path fragment of the endpoint, appended to baseURL

@@ -100,0 +144,0 @@ type : Content type, defaults to JSON

var Mock = require('./mock')
var Promise = require('promise')
var Request = require('superagent')
var prepare = require('./prepare')
var url = require('./url')
var assign = require('./assign')

@@ -11,7 +9,11 @@ module.exports = function AJAX (options) {

if (!options.Promise) {
throw TypeError('Gangway uses Promises. The current environment does not support them. Please include a Promise polyfill.')
}
if ('mock' in options) {
return Mock(options)
return options.Promise.resolve(Mock(options))
}
var location = url(options.baseURL, url.resolve(options.basePath, options.path), assign({}, options.body, options.params))
var location = url(options.baseURL, url.resolve(options.basePath, options.path), options.params)
var message = Request(options.method, location)

@@ -26,3 +28,3 @@

return new Promise(function(resolve, reject) {
var promise = new options.Promise(function(resolve, reject) {
message.end(function(err, response) {

@@ -32,2 +34,6 @@ return err ? reject(options.onError(err)) : resolve(options.onResponse(response))

})
promise.request = message
return promise
}

@@ -23,3 +23,3 @@ var ajax = require('./ajax')

namespace: function (key) {
namespace: function (key, options) {
var segments = this.segments.concat(key)

@@ -29,3 +29,3 @@

basePath: segmentize(segments)
})
}, options)

@@ -32,0 +32,0 @@ var child = Object.create(this, {

@@ -1,7 +0,3 @@

var Promise = require('promise')
module.exports = function (options) {
var reply = typeof options.mock === 'function' ? options.mock(options) : options.mock
return Promise.resolve(reply)
return typeof options.mock === 'function' ? options.mock(options) : options.mock
}

@@ -22,3 +22,4 @@ /**

onResponse : function(response) { return response.body },
onError : function(error) { return error }
onError : function(error) { return error },
Promise : global.Promise
}

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

@@ -1,27 +0,24 @@

var assign = require('./assign')
var endpoint = require('./endpoint')
var route = require('./route')
module.exports = function resource (API, name, options, nest) {
var child = API.namespace(name)
var child = API.namespace(name, options).route({
endpoint(child, {
create: assign({}, options, {
create: {
method: 'POST'
}),
},
read: assign({}, options, {
read: {
method: 'GET',
path: '{id?}'
}),
},
update: assign({}, options, {
update: {
method: 'PATCH',
path: '{id}'
}),
},
destroy: assign({}, options, {
destroy: {
method: 'DELETE',
path: '{id}'
})
}

@@ -28,0 +25,0 @@ })

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

var remap = require('./remap')
var endpoint = require('./endpoint')
var remap = require('./remap')
var prepare = require('./prepare')
var url = require('./url')
module.exports = function route (API, routes) {
return remap(routes || {}, endpoint.bind(null, API), API)
module.exports = function route (namespace, methods) {
// For each endpoint (create, read, update, delete...)
return remap(methods, function (options) {
var config = prepare(namespace.config, options)
var request = function (overrides) {
return namespace.ajax(prepare(config, overrides))
}
request.toString = function () {
return url.resolve(namespace.toString(), config.path)
}
// Make the config for this route available under 'config'
request.config = config
return request
}, namespace)
}

@@ -5,6 +5,21 @@ var parameterizeRoute = require('./parameterizeRoute')

function isAbsolute (path) {
return (path || '').toString()[0] === '/'
}
function urlRoot (url) {
return url = url.replace(/\/\w+$/, '')
}
function resolve (base, path) {
base = (base || '').toString().replace(trimRight, '')
path = (path || '').toString().replace(trimLeft, '')
base = (base || '').toString()
path = (path || '').toString()
if (isAbsolute(path)) {
base = urlRoot(base)
}
base = base.replace(trimRight, '')
path = path.replace(trimLeft, '')
return (base + '/' + path).replace(trimRight, '')

@@ -18,3 +33,2 @@ }

module.exports = url
module.exports.resolve = resolve

@@ -11,5 +11,7 @@ var ajax = require('../src/ajax')

fauxJax.install()
fauxJax.on('request', function(request) {
fauxJax.on('request', function (request) {
if (request.requestURL.match(/response\.json/)) {
request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ "name" : "Gangway" }))
setTimeout(function() {
request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ "name" : "Gangway" }))
}, 10)
} else {

@@ -29,6 +31,6 @@ request.respond(404, { 'Content-Type': 'text/plain' }, "Not found")

path : '/base/test/response.json'
}).done(function(body) {
}).then(function(body) {
assert.equal(body.name, 'Gangway')
done()
})
}).catch(done)
})

@@ -38,4 +40,6 @@

ajax({
beforeSend(ajax) {
assert.equal(ajax.header.accept, 'application/json')
beforeSend: function (ajax) {
// NOTE: The browser sets this header with a capital "a", however it
// is lower case within Node
assert.equal(ajax.header.Accept || ajax.header.accept, 'application/json')
done()

@@ -50,6 +54,6 @@ }

mock : 'fiz'
}).done(function(body) {
}).then(function(body) {
assert.equal(body, 'fiz')
done()
})
}).catch(done)
})

@@ -66,6 +70,6 @@

ajax(options).done(function(data) {
ajax(options).then(function(data) {
assert.equal(data, 'yes')
done()
})
}).catch(done)
})

@@ -77,6 +81,6 @@

mock : 'fiz'
}).done(function(body) {
}).then(function(body) {
assert.equal(body, 'fiz')
done()
})
}).catch(done)
})

@@ -88,6 +92,6 @@

path : '/asdf'
}).done(null, function(error) {
}).then(null, function(error) {
assert.equal(error.status, 404)
done()
})
}).catch(done)
})

@@ -99,6 +103,6 @@

path : '/base/test/response.json'
}).done(function(data) {
}).then(function(data) {
assert.equal(data.name, 'Gangway')
done()
})
}).catch(done)
})

@@ -111,3 +115,3 @@

params : { path: 'response'}
}).nodeify(done)
}).then(function () { done() }, done)
})

@@ -120,3 +124,3 @@

params : { path: 'response' }
}).nodeify(done)
}).then(function () { done() }, done)
})

@@ -128,3 +132,3 @@

path : '/base/test/response.json'
}).nodeify(done)
}).then(function () { done() }, done)
})

@@ -139,3 +143,3 @@

}
}).nodeify(done)
}).then(function () { done() }, done)
})

@@ -151,3 +155,3 @@

}
}).nodeify(done)
}).then(function () { done() }, done)
})

@@ -161,4 +165,41 @@

}
}).nodeify(done)
}).then(function () { done() }, done)
})
it ('accepts a custom Promise implimentation option', function(done) {
ajax({
Promise: {
resolve: function (value) {
assert.equal(value, 'test')
done()
}
},
mock: 'test'
})
})
it ('throws an error when promise is not defined', function () {
assert.throws(function () {
return ajax({ Promise: undefined })
}, /Please include a Promise polyfill/)
})
it ('can cancel requests', function (done) {
var message = ajax({
baseURL : baseURL,
path : '/base/test/response.json'
})
function errorOut (error, callback) {
done(new Error('Request should not have completed'))
}
message.then(errorOut, errorOut).catch(errorOut)
message.request.abort()
setTimeout(function() {
done()
}, 500)
})
})

@@ -17,12 +17,10 @@ var API = require('../src/api')

it ('maps over a list of endpoints', function() {
it ('maps over a list of endpoints when instantiated', function() {
var endpoints = API({ baseURL: baseURL }, {
foo: {
bar: {
get: '/bip'
}
get: '/bip'
}
})
assert('bar' in endpoints.foo)
assert('foo' in endpoints)
})

@@ -34,10 +32,8 @@

endpoints.route({
foo: {
bar: {
get: '/bip'
}
bar: {
get: '/bip'
}
})
assert('bar' in endpoints.foo)
assert('bar' in endpoints)
})

@@ -55,7 +51,5 @@

foo: {
bar: {
get: '/bip',
headers: {
'two': 2
}
get: '/bip',
headers: {
'two': 2
}

@@ -65,4 +59,4 @@ }

assert.equal(endpoints.foo.bar.config.headers.one, 1)
assert.equal(endpoints.foo.bar.config.headers.two, 2)
assert.equal(endpoints.foo.config.headers.one, 1)
assert.equal(endpoints.foo.config.headers.two, 2)
})

@@ -80,7 +74,5 @@

foo: {
bar: {
get: '/bip',
query: {
'two': 2
}
get: '/bip',
query: {
'two': 2
}

@@ -90,4 +82,4 @@ }

assert.equal(endpoints.foo.bar.config.query.one, 1)
assert.equal(endpoints.foo.bar.config.query.two, 2)
assert.equal(endpoints.foo.config.query.one, 1)
assert.equal(endpoints.foo.config.query.two, 2)
})

@@ -105,7 +97,4 @@

foo: {
bar: {
get: '/bip',
body: {
'two': 2
}
body: {
'two': 2
}

@@ -115,4 +104,4 @@ }

assert.equal(endpoints.foo.bar.config.body.one, 1)
assert.equal(endpoints.foo.bar.config.body.two, 2)
assert.equal(endpoints.foo.config.body.one, 1)
assert.equal(endpoints.foo.config.body.two, 2)
})

@@ -130,7 +119,5 @@

foo: {
bar: {
get: '/bip',
params: {
'two': 2
}
get: '/bip',
params: {
'two': 2
}

@@ -140,4 +127,4 @@ }

assert.equal(endpoints.foo.bar.config.params.one, 1)
assert.equal(endpoints.foo.bar.config.params.two, 2)
assert.equal(endpoints.foo.config.params.one, 1)
assert.equal(endpoints.foo.config.params.two, 2)
})

@@ -151,7 +138,5 @@

api.route({
users: {
read: {
path: 'users'
}
api.namespace('users').route({
read: {
path: ''
}

@@ -158,0 +143,0 @@ })

@@ -17,11 +17,9 @@ var API = require('../src/api')

api.route({
users: {
read: {}
}
var read = api.namespace('users').route({
read: {}
})
var users = api.namespace('users')
var users = api.resource('users')
assert('read' in api.users)
assert.equal(api.users.read, users.read, read)
})

@@ -45,2 +43,30 @@

context('when a namespace is created with options', function() {
var api = new API()
var users = api.namespace('users', { query: { test: true }})
it ('sends those default options to child namespaces', function() {
var posts = users.namespace('posts')
assert.equal(posts.config.query.test, true)
})
it ('sends those default options to routes', function() {
var posts = users.namespace('posts').route({
create: {
method: 'POST'
}
})
assert.equal(posts.create.config.query.test, true)
})
it ('the basePath option is overrideable', function() {
var posts = users.namespace('posts', { basePath: 'test' }).route({
create: {
method: 'POST'
}
})
assert.equal(posts.create.toString(), '/test')
})
})
context('when a resource is created', function() {

@@ -47,0 +73,0 @@ var api = new API()

@@ -19,9 +19,9 @@ var API = require('../src/api')

route(api, { users: { read: read } })
route(api, { read: read })
it ('extends each route with configuration settings', function() {
assert.equal(api.users.read.config.path, read.path)
assert.equal(api.users.read.config.description, api.config.description)
assert.equal(api.read.config.path, read.path)
assert.equal(api.read.config.description, api.config.description)
})
})
})

@@ -10,6 +10,2 @@ var url = require('../src/url')

it ('trims path url left-handslashes', function() {
assert.equal(url('http://foobar.com', '/path'), 'http://foobar.com/path')
})
it ('handles both base and path urls with slashes', function() {

@@ -33,2 +29,23 @@ assert.equal(url('http://foobar.com', 'path'), 'http://foobar.com/path')

})
context('when resolving URL segments', function () {
var samples = [
{ base: '//foobar.com', path: '/path', expected: '//foobar.com/path' },
{ base: 'http://foobar.com/fiz', path: '/path', expected: 'http://foobar.com/path' },
{ base: 'http://foobar.biz.co/fiz', path: '/path', expected: 'http://foobar.biz.co/path' },
{ base: 'http://foobar.biz.co/users', path: 'posts', expected: 'http://foobar.biz.co/users/posts' },
{ base: '/fiz', path: '/path', expected: '/path' },
{ base: '/foo', path: 'bar', expected: '/foo/bar' }
]
samples.forEach(function (sample) {
context('for: ' + sample.base + ' + ' + sample.path, function () {
it ('correctly resolves the url', function() {
assert.equal(url(sample.base, sample.path), sample.expected)
})
})
})
})
})

Sorry, the diff of this file is not supported yet

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