resource-client
Advanced tools
Comparing version 2.1.0 to 2.3.0
372
lib/index.js
// Generated by CoffeeScript 1.9.3 | ||
var Promise, _, defaultActions, request, resourceClient, urlBuilder, | ||
var Promise, _, defaultActions, request, requestValidator, resourceClient, urlBuilder, | ||
slice = [].slice; | ||
@@ -11,2 +11,4 @@ | ||
requestValidator = require('./request_validator'); | ||
urlBuilder = require('./url_builder'); | ||
@@ -16,11 +18,17 @@ | ||
module.exports = resourceClient = function(resourceOptions) { | ||
/* | ||
Create resource with default configuration | ||
@param {Object} [resourceConfig] - configuration shared for all actions on this resource | ||
*/ | ||
module.exports = resourceClient = function(resourceConfig) { | ||
var Resource, actionConfig, actionName, handleResponse, resourceRequest; | ||
if (resourceOptions.params == null) { | ||
resourceOptions.params = {}; | ||
if (resourceConfig.params == null) { | ||
resourceConfig.params = {}; | ||
} | ||
if (resourceOptions.json == null) { | ||
resourceOptions.json = true; | ||
if (resourceConfig.json == null) { | ||
resourceConfig.json = true; | ||
} | ||
resourceRequest = request.defaults(resourceOptions); | ||
resourceRequest = request.defaults(resourceConfig); | ||
Resource = (function() { | ||
@@ -38,17 +46,32 @@ function Resource(newObject) { | ||
})(); | ||
Resource.action = function(actionName, actionOptions) { | ||
/* | ||
Register an action for the resource | ||
@param {String} actionName - name of action being registered | ||
@param {Object} [actionConfig] - configuration for this particular action | ||
*/ | ||
Resource.action = function(actionName, actionConfig) { | ||
var actionRequest, actionUrl, ref; | ||
actionOptions.params = {}; | ||
actionUrl = actionOptions.url || resourceOptions.url; | ||
actionRequest = resourceRequest.defaults(actionOptions); | ||
if (actionOptions.method === 'GET' && !actionOptions.isArray) { | ||
if (typeof actionName !== 'string') { | ||
throw new TypeError('actionName must be a string'); | ||
} | ||
if ((ref = actionConfig.method) !== 'GET' && ref !== 'POST' && ref !== 'PUT' && ref !== 'DELETE') { | ||
throw new TypeError('actionConfig.method must be GET POST PUT or DELETE'); | ||
} | ||
if (actionConfig.params == null) { | ||
actionConfig.params = {}; | ||
} | ||
actionUrl = actionConfig.url || resourceConfig.url; | ||
actionRequest = Promise.promisify(resourceRequest.defaults(actionConfig)); | ||
if (actionConfig.method === 'GET') { | ||
/* | ||
* get single w/ params (class method). | ||
* | ||
* call w/ `({params}, {requestOptions}, callback)` | ||
* OR `({params}, callback)` | ||
Send a GET request with specified configuration | ||
@param {Object} [params] - url params and query params for this action | ||
@param {Object} [requestOptions] - custom options set for just this request (such as headers) | ||
@param {Function} [callback] - if you don't want to use promises. Can be first second, or third parameter. | ||
@returns {Promise} - resolves to returned resource(s) | ||
*/ | ||
return Resource[actionName] = function() { | ||
var done, opts, promise; | ||
var done, opts, requestOptions, requestParams; | ||
opts = 1 <= arguments.length ? slice.call(arguments, 0) : []; | ||
@@ -58,35 +81,45 @@ if (typeof opts[opts.length - 1] === 'function') { | ||
} | ||
promise = new Promise((function(_this) { | ||
return function(resolve, reject) { | ||
var requestOptions, requestParams; | ||
requestParams = opts.shift() || {}; | ||
requestOptions = opts.pop() || {}; | ||
requestOptions.url = (function() { | ||
var mergedParams; | ||
mergedParams = _.assign({}, resourceOptions.params, actionOptions.params, requestParams); | ||
return urlBuilder.build(actionUrl, mergedParams); | ||
})(); | ||
return actionRequest.get(requestOptions, function(err, response) { | ||
return handleResponse({ | ||
err: err, | ||
response: response, | ||
resolve: resolve, | ||
reject: reject | ||
}); | ||
requestParams = opts.shift() || {}; | ||
requestOptions = opts.pop() || {}; | ||
requestOptions.url = (function() { | ||
var mergedParams; | ||
mergedParams = _.assign({}, resourceConfig.params, actionConfig.params, requestParams); | ||
return urlBuilder.build(actionUrl, mergedParams); | ||
})(); | ||
return Promise["try"]((function(_this) { | ||
return function() { | ||
requestValidator.validateUrlParams({ | ||
requestParams: requestParams, | ||
actionConfig: actionConfig, | ||
resourceConfig: resourceConfig, | ||
actionName: actionName | ||
}); | ||
requestValidator.validateQueryParams({ | ||
requestParams: requestParams, | ||
actionConfig: actionConfig, | ||
resourceConfig: resourceConfig, | ||
actionName: actionName | ||
}); | ||
return actionRequest(requestOptions); | ||
}; | ||
})(this)); | ||
return promise.nodeify(done); | ||
})(this)).spread(function(response) { | ||
return handleResponse({ | ||
response: response, | ||
actionConfig: actionConfig, | ||
actionName: actionName | ||
}); | ||
}).nodeify(done); | ||
}; | ||
} else if (actionOptions.method === 'GET' && actionOptions.isArray) { | ||
} else { | ||
/* | ||
* get multi w/o params (class method). | ||
* | ||
* call w/ `({params}, {requestOptions}, [callback])` | ||
* OR `({params}, [callback])` | ||
* OR `([callback])` | ||
Send a PUT, POST, or DELETE request with specified configuration | ||
@param {Object} [params] - url params and query params for this action | ||
@param {Object} [body] - request body | ||
@param {Object} [requestOptions] - custom options set for just this request (such as headers) | ||
@param {Function} [callback] - if you don't want to use promises. Can be first second, third, or fourth parameter. | ||
@returns {Promise} - resolves to returned resource(s) | ||
*/ | ||
return Resource[actionName] = function() { | ||
var done, opts, promise; | ||
Resource[actionName] = function() { | ||
var done, opts, requestBody, requestOptions, requestParams; | ||
opts = 1 <= arguments.length ? slice.call(arguments, 0) : []; | ||
@@ -96,118 +129,139 @@ if (typeof opts[opts.length - 1] === 'function') { | ||
} | ||
promise = new Promise((function(_this) { | ||
return function(resolve, reject) { | ||
var requestOptions, requestParams; | ||
requestParams = opts.shift() || {}; | ||
requestOptions = opts.pop() || {}; | ||
requestOptions.url = (function() { | ||
var mergedParams; | ||
mergedParams = _.assign({}, resourceOptions.params, actionOptions.params, requestParams); | ||
return urlBuilder.build(actionUrl, mergedParams); | ||
})(); | ||
return actionRequest.get(requestOptions, function(err, response) { | ||
return handleResponse({ | ||
err: err, | ||
response: response, | ||
resolve: resolve, | ||
reject: reject, | ||
actionOptions: actionOptions | ||
}); | ||
requestParams = opts.shift() || {}; | ||
requestBody = opts.shift() || {}; | ||
requestOptions = opts.pop() || {}; | ||
requestOptions.body = requestBody; | ||
requestOptions.url = (function() { | ||
var mergedParams; | ||
mergedParams = _.assign({}, resourceConfig.params, actionConfig.params, requestParams); | ||
return urlBuilder.build(actionUrl, mergedParams, requestOptions.body); | ||
})(); | ||
return Promise["try"]((function(_this) { | ||
return function() { | ||
requestValidator.validateUrlParams({ | ||
requestParams: requestParams, | ||
actionConfig: actionConfig, | ||
resourceConfig: resourceConfig, | ||
actionName: actionName, | ||
requestBody: requestBody | ||
}); | ||
requestValidator.validateQueryParams({ | ||
requestParams: requestParams, | ||
actionConfig: actionConfig, | ||
resourceConfig: resourceConfig, | ||
actionName: actionName, | ||
requestBody: requestBody | ||
}); | ||
requestValidator.validateRequestBody({ | ||
actionConfig: actionConfig, | ||
resourceConfig: resourceConfig, | ||
actionName: actionName, | ||
requestBody: requestBody | ||
}); | ||
return actionRequest(requestOptions); | ||
}; | ||
})(this)); | ||
return promise.nodeify(done); | ||
})(this)).spread((function(_this) { | ||
return function(response) { | ||
return handleResponse({ | ||
response: response, | ||
actionConfig: actionConfig, | ||
actionName: actionName | ||
}); | ||
}; | ||
})(this)).nodeify(done); | ||
}; | ||
} else if ((ref = actionOptions.method) === 'PUT' || ref === 'POST' || ref === 'DELETE') { | ||
return (function(methodFn) { | ||
if (methodFn === 'delete') { | ||
methodFn = 'del'; | ||
/* | ||
Send a PUT, POST, or DELETE request with specified configuration. Sends the instantiated | ||
object as the body of the request | ||
@param {Object} [params] - url params and query params for this action | ||
@param {Object} [requestOptions] - custom options set for just this request (such as headers) | ||
@param {Function} [callback] - if you don't want to use promises. Can be first second, or third parameter. | ||
@returns {Promise} - resolves to returned resource | ||
*/ | ||
return Resource.prototype[actionName] = function() { | ||
var done, i, opts, requestOptions, requestParams; | ||
opts = 2 <= arguments.length ? slice.call(arguments, 0, i = arguments.length - 1) : (i = 0, []), done = arguments[i++]; | ||
if (typeof opts[opts.length - 1] === 'function') { | ||
done = opts.pop(); | ||
} | ||
/* | ||
* modify single (class method). | ||
* | ||
* call w/ `({params}, body, {requestOptions}, [callback])` | ||
* OR `({params}, body, [callback])` | ||
* OR `({params}, [callback])` | ||
*/ | ||
Resource[actionName] = function() { | ||
var done, opts, promise; | ||
opts = 1 <= arguments.length ? slice.call(arguments, 0) : []; | ||
if (typeof opts[opts.length - 1] === 'function') { | ||
done = opts.pop(); | ||
} | ||
promise = new Promise((function(_this) { | ||
return function(resolve, reject) { | ||
var requestBody, requestOptions, requestParams; | ||
requestParams = opts.shift() || {}; | ||
requestBody = opts.shift() || {}; | ||
requestOptions = opts.pop() || {}; | ||
requestOptions.body = requestBody; | ||
requestOptions.url = (function() { | ||
var mergedParams; | ||
mergedParams = _.assign({}, resourceOptions.params, actionOptions.params, requestParams); | ||
return urlBuilder.build(actionUrl, mergedParams, requestOptions.body); | ||
})(); | ||
return actionRequest[methodFn](requestOptions, function(err, response) { | ||
return handleResponse({ | ||
err: err, | ||
response: response, | ||
resolve: resolve, | ||
reject: reject | ||
}); | ||
}); | ||
}; | ||
})(this)); | ||
return promise.nodeify(done); | ||
}; | ||
/* | ||
* modify single (instance method). | ||
* | ||
* call w/ `({params}, {requestOptions}, [callback])` | ||
* OR `({params}, [callback])` | ||
* OR `([callback])` | ||
*/ | ||
return Resource.prototype[actionName] = function() { | ||
var done, i, opts, promise; | ||
opts = 2 <= arguments.length ? slice.call(arguments, 0, i = arguments.length - 1) : (i = 0, []), done = arguments[i++]; | ||
if (typeof opts[opts.length - 1] === 'function') { | ||
done = opts.pop(); | ||
} | ||
promise = new Promise((function(_this) { | ||
return function(resolve, reject) { | ||
var requestOptions, requestParams; | ||
requestParams = opts.shift() || {}; | ||
requestOptions = opts.pop() || {}; | ||
requestOptions.body = _this; | ||
requestOptions.url = (function() { | ||
var mergedParams; | ||
mergedParams = _.assign({}, resourceOptions.params, actionOptions.params, requestParams); | ||
return urlBuilder.build(actionUrl, mergedParams, requestOptions.body); | ||
})(); | ||
return actionRequest[methodFn](requestOptions, function(err, response) { | ||
return handleResponse({ | ||
err: err, | ||
response: response, | ||
originalObject: this, | ||
resolve: resolve, | ||
reject: reject | ||
}); | ||
}); | ||
}; | ||
})(this)); | ||
return promise.nodeify(done); | ||
}; | ||
})(actionOptions.method.toLowerCase()); | ||
requestParams = opts.shift() || {}; | ||
requestOptions = opts.pop() || {}; | ||
requestOptions.body = this; | ||
requestOptions.url = (function() { | ||
var mergedParams; | ||
mergedParams = _.assign({}, resourceConfig.params, actionConfig.params, requestParams); | ||
return urlBuilder.build(actionUrl, mergedParams, requestOptions.body); | ||
})(); | ||
return Promise["try"]((function(_this) { | ||
return function() { | ||
requestValidator.validateUrlParams({ | ||
requestParams: requestParams, | ||
actionConfig: actionConfig, | ||
resourceConfig: resourceConfig, | ||
actionName: actionName, | ||
requestBody: _this | ||
}); | ||
requestValidator.validateQueryParams({ | ||
requestParams: requestParams, | ||
actionConfig: actionConfig, | ||
resourceConfig: resourceConfig, | ||
actionName: actionName, | ||
requestBody: _this | ||
}); | ||
requestValidator.validateRequestBody({ | ||
actionConfig: actionConfig, | ||
resourceConfig: resourceConfig, | ||
actionName: actionName, | ||
requestBody: _this | ||
}); | ||
return actionRequest(requestOptions); | ||
}; | ||
})(this)).spread((function(_this) { | ||
return function(response) { | ||
return handleResponse({ | ||
response: response, | ||
actionConfig: actionConfig, | ||
resourceConfig: resourceConfig, | ||
actionName: actionName, | ||
originalObject: _this | ||
}); | ||
}; | ||
})(this)).nodeify(done); | ||
}; | ||
} | ||
}; | ||
/* | ||
@param {Object} response - request response object | ||
@param {Object} actionConfig | ||
@param {String} actionName | ||
@param {Object} [originalObject] - original resource instance that was saved | ||
- once successful, original instance will be updated in place with the response object | ||
*/ | ||
handleResponse = function(arg) { | ||
var actionOptions, err, errorMessage, originalObject, ref, reject, resolve, resource, resources, response; | ||
err = arg.err, response = arg.response, originalObject = arg.originalObject, resolve = arg.resolve, reject = arg.reject, actionOptions = arg.actionOptions; | ||
if (actionOptions == null) { | ||
actionOptions = {}; | ||
var actionConfig, actionName, errorMessage, originalObject, ref, resource, resources, response; | ||
response = arg.response, actionConfig = arg.actionConfig, actionName = arg.actionName, originalObject = arg.originalObject; | ||
if (typeof response !== 'object') { | ||
throw new TypeError('response must be an object'); | ||
} | ||
if (err) { | ||
return reject(err); | ||
} else if ((200 <= (ref = response.statusCode) && ref < 300)) { | ||
if (typeof actionConfig !== 'object') { | ||
throw new TypeError('actionConfig must be an object'); | ||
} | ||
if (typeof actionName !== 'string') { | ||
throw new TypeError('actionName must be a string'); | ||
} | ||
if ((originalObject != null) && typeof originalObject !== 'object') { | ||
throw new TypeError('originalObject must be an object'); | ||
} | ||
if (actionConfig == null) { | ||
actionConfig = {}; | ||
} | ||
if ((200 <= (ref = response.statusCode) && ref < 300)) { | ||
requestValidator.validateResponseBody({ | ||
actionConfig: actionConfig, | ||
resourceConfig: resourceConfig, | ||
actionName: actionName, | ||
responseBody: response.body | ||
}); | ||
if (Array.isArray(response.body)) { | ||
@@ -217,17 +271,21 @@ resources = response.body.map(function(resource) { | ||
}); | ||
if (actionOptions.returnFirst) { | ||
if (actionConfig.returnFirst) { | ||
resources = resources[0]; | ||
} | ||
return resolve(resources); | ||
return resources; | ||
} else { | ||
resource = originalObject != null ? _.assign(originalObject, response.body) : new Resource(response.body); | ||
return resolve(resource); | ||
return resource; | ||
} | ||
} else if (response.statusCode === 404) { | ||
return resolve(void 0); | ||
return void 0; | ||
} else { | ||
errorMessage = JSON.stringify(response.body); | ||
return reject(new Error(errorMessage)); | ||
throw new Error(errorMessage); | ||
} | ||
}; | ||
/* | ||
Add default methods. | ||
*/ | ||
for (actionName in defaultActions) { | ||
@@ -234,0 +292,0 @@ actionConfig = defaultActions[actionName]; |
@@ -9,6 +9,5 @@ // Generated by CoffeeScript 1.9.3 | ||
Build url from url template | ||
@param urlTemplate - url that can contain variables such as /products/:_id | ||
@param params - params to populate the url. Will fill url params first, and then | ||
all remaining params will become query params | ||
@param params - params to populate the url. | ||
- Will fill url params first, and then all remaining params will become query params | ||
@param [body] - body object to use for populating params (when params are defined using @, eg. {_id: '@_id'}) | ||
@@ -15,0 +14,0 @@ */ |
{ | ||
"name": "resource-client", | ||
"version": "2.1.0", | ||
"version": "2.3.0", | ||
"description": "Easily create api clients for your server side resources.", | ||
@@ -26,2 +26,3 @@ "author": "Good Eggs <open-source@goodeggs.com>", | ||
"bluebird": "^2.9.30", | ||
"goodeggs-json-schema-validator": "^2.0.2", | ||
"lodash": "^3.0.0", | ||
@@ -32,6 +33,7 @@ "request": "2.54.0", | ||
"devDependencies": { | ||
"chai": "^2.1.0", | ||
"chai-as-promised": "^5.1.0", | ||
"coffee-script": ">=1.7.x", | ||
"fibrous": "^0.3.3", | ||
"mocha": "~1.x.x", | ||
"chai": "^2.1.0", | ||
"fibrous": "^0.3.3", | ||
"nock": "^1.6.1" | ||
@@ -38,0 +40,0 @@ }, |
# Resource Client | ||
Easily create node API clients for your APIs. Inspired by [Angular Resource](https://docs.angularjs.org/api/ngResource/service/$resource). | ||
Easily create node API clients for your APIs. Inspired by [Angular Resource](https://docs.angularjs.org/api/ngResource/service/$resource) and [Angular Validated Resource](https://github.com/goodeggs/angular-validated-resource). | ||
@@ -41,2 +41,26 @@ [![NPM version](http://img.shields.io/npm/v/resource-client.svg?style=flat-square)](https://www.npmjs.org/package/resource-client) | ||
You can also configure the resource to use JSON schema validation for every request: | ||
```javascript | ||
var resourceClient = require('resource-client'); | ||
var Product = resourceClient({ | ||
url: 'http://www.mysite.com/api/products/:_id', | ||
headers: { | ||
'X-Secret-Token': 'ABCD1234' | ||
} | ||
// recommended that you do not allow unkown properties when testing. | ||
banUnknownProperties: true | ||
}); | ||
Product.action('update', { | ||
method: 'PUT' | ||
// will reject or pass error to callback if validation fails for any of the below | ||
urlParamsSchema: require('./product_schemas/update/url_params.json') | ||
queryParamsSchema: require('./product_schemas/update/query_params.json') | ||
requestBodySchema: require('./product_schemas/update/request_body.json') | ||
responseBodySchema: require('./product_schemas/update/response_body.json') | ||
}) | ||
``` | ||
## Creating a Resource | ||
@@ -43,0 +67,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
52721
14
647
196
5
6
+ Addedcoffee-script@1.12.7(transitive)
+ Addedgoodeggs-json-schema-validator@2.0.2(transitive)
+ Addedtv4@1.3.0(transitive)