Comparing version 0.5.17 to 0.5.18
@@ -74,4 +74,9 @@ # Fetchr API | ||
### getServiceMeta() | ||
Returns metadata for all service calls in an array format. | ||
The 0 index will be the first service call. | ||
### updateOptions(options) | ||
Update the options of the fetchr instance. |
@@ -27,2 +27,3 @@ /** | ||
var defaultConstructGetUri = require('./util/defaultConstructGetUri'); | ||
var Promise = global.Promise || require('es6-promise').Promise; | ||
@@ -59,2 +60,5 @@ function parseResponse(response) { | ||
* @param {Object} options configuration options for Request | ||
* @param {Array} [options._serviceMeta] Array to hold per-request/session metadata from all service calls. | ||
* Data will be pushed on to this array while the Fetchr instance maintains the reference for this session. | ||
* | ||
* @constructor | ||
@@ -74,3 +78,4 @@ */ | ||
context: options.context || {}, | ||
contextPicker: options.contextPicker || {} | ||
contextPicker: options.contextPicker || {}, | ||
_serviceMeta: options._serviceMeta || [] | ||
}; | ||
@@ -127,4 +132,35 @@ this._params = {}; | ||
Request.prototype.end = function (callback) { | ||
var clientConfig = this._clientConfig; | ||
var callback = callback || lodash.noop; | ||
var self = this; | ||
var promise = new Promise(function (resolve, reject) { | ||
debug('Executing request %s.%s with params %o and body %o', self.resource, self.operation, self._params, self._body); | ||
setImmediate(executeRequest, self, resolve, reject); | ||
}); | ||
promise.then(function (result) { | ||
if (result.meta) { | ||
self.options._serviceMeta.push(result.meta) | ||
}; | ||
return result; | ||
}); | ||
if (callback) { | ||
promise.then(function (result) { | ||
setImmediate(callback, null, result.data, result.meta); | ||
}, function (err) { | ||
setImmediate(callback, err); | ||
}); | ||
} else { | ||
return promise; | ||
} | ||
}; | ||
/** | ||
* Execute and resolve/reject this fetcher request | ||
* @method executeRequest | ||
* @param {Object} request Request instance object | ||
* @param {Function} resolve function to call when request fulfilled | ||
* @param {Function} reject function to call when request rejected | ||
*/ | ||
function executeRequest (request, resolve, reject) { | ||
var clientConfig = request._clientConfig; | ||
var use_post; | ||
@@ -138,6 +174,6 @@ var allow_retry_post; | ||
if (!uri) { | ||
uri = clientConfig.cors ? this.options.corsPath : this.options.xhrPath; | ||
uri = clientConfig.cors ? request.options.corsPath : request.options.xhrPath; | ||
} | ||
use_post = this.operation !== OP_READ || clientConfig.post_for_read; | ||
use_post = request.operation !== OP_READ || clientConfig.post_for_read; | ||
// We use GET request by default for READ operation, but you can override that behavior | ||
@@ -147,3 +183,3 @@ // by specifying {post_for_read: true} in your request's clientConfig | ||
var getUriFn = lodash.isFunction(clientConfig.constructGetUri) ? clientConfig.constructGetUri : defaultConstructGetUri; | ||
var get_uri = getUriFn.call(this, uri, this.resource, this._params, clientConfig, pickContext(this.options.context, this.options.contextPicker, 'GET')); | ||
var get_uri = getUriFn.call(request, uri, request.resource, request._params, clientConfig, pickContext(request.options.context, request.options.contextPicker, 'GET')); | ||
/* istanbul ignore next */ | ||
@@ -153,4 +189,17 @@ if (!get_uri) { | ||
// TODO: Add test for this fallback | ||
get_uri = defaultConstructGetUri.call(this, uri, this.resource, this._params, clientConfig, this.options.context); | ||
get_uri = defaultConstructGetUri.call(request, uri, request.resource, request._params, clientConfig, request.options.context); | ||
} | ||
// TODO: Remove `returnMeta` feature flag after next release | ||
// This feature flag will enable the new return format for GET api requests | ||
// Whereas before any data from services was returned as is. We now return | ||
// an object with a data key containing the service response, and a meta key | ||
// containing the service's metadata response (i.e headers and statusCode). | ||
// We need this feature flag to be truly backwards compatible because it is | ||
// concievable that some active browser sessions could have the old version of | ||
// client fetcher while the server upgrades to the new version. This could be | ||
// easily fixed by refreshing the browser, but the feature flag will ensure | ||
// old fetcher clients will receive the old format and the new client will | ||
// receive the new format | ||
get_uri += (get_uri.indexOf('?') !== -1) ? '&' : '?'; | ||
get_uri += 'returnMeta=true'; | ||
if (get_uri.length <= MAX_URI_LEN) { | ||
@@ -164,8 +213,8 @@ uri = get_uri; | ||
if (!use_post) { | ||
return REST.get(uri, {}, lodash.merge({xhrTimeout: this.options.xhrTimeout}, clientConfig), function getDone(err, response) { | ||
return REST.get(uri, {}, lodash.merge({xhrTimeout: request.options.xhrTimeout}, clientConfig), function getDone(err, response) { | ||
if (err) { | ||
debug('Syncing ' + this.resource + ' failed: statusCode=' + err.statusCode, 'info'); | ||
return callback(err); | ||
debug('Syncing ' + request.resource + ' failed: statusCode=' + err.statusCode, 'info'); | ||
return reject(err); | ||
} | ||
callback(null, parseResponse(response)); | ||
resolve(parseResponse(response)); | ||
}); | ||
@@ -177,19 +226,19 @@ } | ||
requests[DEFAULT_GUID] = { | ||
resource: this.resource, | ||
operation: this.operation, | ||
params: this._params | ||
resource: request.resource, | ||
operation: request.operation, | ||
params: request._params | ||
}; | ||
if (this._body) { | ||
requests[DEFAULT_GUID].body = this._body; | ||
if (request._body) { | ||
requests[DEFAULT_GUID].body = request._body; | ||
} | ||
data = { | ||
requests: requests, | ||
context: this.options.context | ||
context: request.options.context | ||
}; // TODO: remove. leave here for now for backward compatibility | ||
uri = this._constructGroupUri(uri); | ||
allow_retry_post = (this.operation === OP_READ); | ||
REST.post(uri, {}, data, lodash.merge({unsafeAllowRetry: allow_retry_post, xhrTimeout: this.options.xhrTimeout}, clientConfig), function postDone(err, response) { | ||
uri = request._constructGroupUri(uri); | ||
allow_retry_post = (request.operation === OP_READ); | ||
REST.post(uri, {}, data, lodash.merge({unsafeAllowRetry: allow_retry_post, xhrTimeout: request.options.xhrTimeout}, clientConfig), function postDone(err, response) { | ||
if (err) { | ||
debug('Syncing ' + this.resource + ' failed: statusCode=' + err.statusCode, 'info'); | ||
return callback(err); | ||
debug('Syncing ' + request.resource + ' failed: statusCode=' + err.statusCode, 'info'); | ||
return reject(err); | ||
} | ||
@@ -202,3 +251,3 @@ var result = parseResponse(response); | ||
} | ||
callback(null, result.data); | ||
resolve(result); | ||
}); | ||
@@ -242,3 +291,11 @@ }; | ||
function Fetcher (options) { | ||
this.options = options || {}; | ||
this._serviceMeta = []; | ||
this.options = { | ||
xhrPath: options.xhrPath, | ||
xhrTimeout: options.xhrTimeout, | ||
corsPath: options.corsPath, | ||
context: options.context, | ||
contextPicker: options.contextPicker, | ||
_serviceMeta: this._serviceMeta | ||
}; | ||
} | ||
@@ -365,2 +422,13 @@ | ||
this.options = lodash.merge(this.options, options); | ||
}, | ||
/** | ||
* get the serviceMeta array. | ||
* The array contains all xhr meta returned in this session | ||
* with the 0 index being the first call. | ||
* @method getServiceMeta | ||
* @return {Array} array of metadata returned by each service call | ||
*/ | ||
getServiceMeta: function () { | ||
return this._serviceMeta; | ||
} | ||
@@ -367,0 +435,0 @@ }; |
@@ -14,2 +14,3 @@ /** | ||
var objectAssign = require('object-assign'); | ||
var Promise = global.Promise || require('es6-promise').Promise; | ||
@@ -75,2 +76,3 @@ function parseValue(value) { | ||
* @param {Object} [options.req] The request object from express/connect. It can contain per-request/context data. | ||
* @param {Array} [options.serviceMeta] Array to hold per-request/session metadata from all service calls. | ||
* @constructor | ||
@@ -87,2 +89,3 @@ */ | ||
this.req = options.req || {}; | ||
this.serviceMeta = options.serviceMeta || []; | ||
this._params = {}; | ||
@@ -127,2 +130,3 @@ this._body = null; | ||
}; | ||
/** | ||
@@ -135,11 +139,51 @@ * Execute this fetcher request and call callback. | ||
Request.prototype.end = function (callback) { | ||
var args = [this.req, this.resource, this._params, this._clientConfig, callback]; | ||
var op = this.operation; | ||
var self = this; | ||
var promise = new Promise(function (resolve, reject) { | ||
executeRequest(self, resolve, reject); | ||
setImmediate(executeRequest, self, resolve, reject); | ||
}); | ||
promise.then(function (result) { | ||
if (result.meta) { | ||
self.serviceMeta.push(result.meta) | ||
}; | ||
return result; | ||
}); | ||
if (callback) { | ||
promise.then(function (result) { | ||
setImmediate(callback, null, result.data, result.meta); | ||
}, function (err) { | ||
setImmediate(callback, err); | ||
}); | ||
} else { | ||
return promise; | ||
} | ||
}; | ||
/** | ||
* Execute and resolve/reject this fetcher request | ||
* @method executeRequest | ||
* @param {Object} request Request instance object | ||
* @param {Function} resolve function to call when request fulfilled | ||
* @param {Function} reject function to call when request rejected | ||
*/ | ||
function executeRequest (request, resolve, reject) { | ||
var args = [request.req, request.resource, request._params, request._clientConfig, function executeRequestCallback(err, data, meta) { | ||
if (err) { | ||
reject(err); | ||
} else { | ||
resolve({ | ||
data: data, | ||
meta: meta | ||
}); | ||
} | ||
}]; | ||
var op = request.operation; | ||
if ((op === OP_CREATE) || (op === OP_UPDATE)) { | ||
args.splice(3, 0, this._body); | ||
args.splice(3, 0, request._body); | ||
} | ||
var service = Fetcher.getService(this.resource); | ||
var service = Fetcher.getService(request.resource); | ||
service[op].apply(service, args); | ||
}; | ||
} | ||
@@ -159,2 +203,4 @@ /** | ||
this.req = this.options.req || {}; | ||
this.serviceMeta = []; | ||
} | ||
@@ -264,7 +310,11 @@ | ||
} | ||
request = new Request(OP_READ, resource, {req: req}); | ||
var serviceMeta = []; | ||
request = new Request(OP_READ, resource, { | ||
req: req, | ||
serviceMeta: serviceMeta | ||
}); | ||
request | ||
.params(parseParamValues(qs.parse(path.join('&')))) | ||
.end(function (err, data, meta) { | ||
meta = meta || {}; | ||
.end(function (err, data) { | ||
var meta = serviceMeta[0] || {}; | ||
if (meta.headers) { | ||
@@ -278,3 +328,11 @@ res.set(meta.headers); | ||
} | ||
res.status(meta.statusCode || 200).json(data); | ||
if (req.query.returnMeta) { | ||
res.status(meta.statusCode || 200).json({ | ||
data: data, | ||
meta: meta | ||
}); | ||
} else { | ||
// TODO: Remove `returnMeta` feature flag after next release | ||
res.status(meta.statusCode || 200).json(data); | ||
} | ||
}); | ||
@@ -302,9 +360,12 @@ } else { | ||
} | ||
request = new Request(singleRequest.operation, singleRequest.resource, {req: req}); | ||
var serviceMeta = []; | ||
request = new Request(singleRequest.operation, singleRequest.resource, { | ||
req: req, | ||
serviceMeta: serviceMeta | ||
}); | ||
request | ||
.params(singleRequest.params) | ||
.body(singleRequest.body || {}) | ||
.end(function(err, data, meta) { | ||
meta = meta || {}; | ||
.end(function(err, data) { | ||
var meta = serviceMeta[0] || {}; | ||
if (meta.headers) { | ||
@@ -319,3 +380,6 @@ res.set(meta.headers); | ||
var responseObj = {}; | ||
responseObj[DEFAULT_GUID] = {data: data}; | ||
responseObj[DEFAULT_GUID] = { | ||
data: data, | ||
meta: meta | ||
}; | ||
res.status(meta.statusCode || 200).json(responseObj); | ||
@@ -345,3 +409,6 @@ }); | ||
Fetcher.prototype.read = function (resource, params, config, callback) { | ||
var request = new Request('read', resource, {req: this.req}); | ||
var request = new Request('read', resource, { | ||
req: this.req, | ||
serviceMeta: this.serviceMeta | ||
}); | ||
if (1 === arguments.length) { | ||
@@ -378,3 +445,6 @@ return request; | ||
Fetcher.prototype.create = function (resource, params, body, config, callback) { | ||
var request = new Request('create', resource, {req: this.req}); | ||
var request = new Request('create', resource, { | ||
req: this.req, | ||
serviceMeta: this.serviceMeta | ||
}); | ||
if (1 === arguments.length) { | ||
@@ -412,3 +482,6 @@ return request; | ||
Fetcher.prototype.update = function (resource, params, body, config, callback) { | ||
var request = new Request('update', resource, {req: this.req}); | ||
var request = new Request('update', resource, { | ||
req: this.req, | ||
serviceMeta: this.serviceMeta | ||
}); | ||
if (1 === arguments.length) { | ||
@@ -445,3 +518,6 @@ return request; | ||
Fetcher.prototype['delete'] = function (resource, params, config, callback) { | ||
var request = new Request('delete', resource, {req: this.req}); | ||
var request = new Request('delete', resource, { | ||
req: this.req, | ||
serviceMeta: this.serviceMeta | ||
}); | ||
if (1 === arguments.length) { | ||
@@ -480,2 +556,9 @@ return request; | ||
/** | ||
* Get all the aggregated metadata sent data services in this request | ||
*/ | ||
Fetcher.prototype.getServiceMeta = function () { | ||
return this.serviceMeta; | ||
} | ||
module.exports = Fetcher; | ||
@@ -482,0 +565,0 @@ |
{ | ||
"name": "fetchr", | ||
"version": "0.5.17", | ||
"version": "0.5.18", | ||
"description": "Fetchr augments Flux applications by allowing Flux stores to be used on server and client to fetch data", | ||
@@ -25,2 +25,3 @@ "main": "index.js", | ||
"debug": "^2.0.0", | ||
"es6-promise": "^3.0.2", | ||
"fumble": "^0.1.0", | ||
@@ -41,4 +42,4 @@ "lodash": "^3.3.0", | ||
"pre-commit": "^1.0.0", | ||
"qs": "^3.0.0", | ||
"request": "^2.55.0", | ||
"qs": "^4.0.0", | ||
"request": "^2.61.0", | ||
"supertest": "^1.0.1" | ||
@@ -45,0 +46,0 @@ }, |
@@ -152,2 +152,48 @@ # Fetchr | ||
## Service Metadata | ||
Service calls on the client transparently become xhr requests. | ||
It is a good idea to set cache headers on common xhr calls. | ||
You can do so by providing a third parameter in your service's callback. | ||
If you want to look at what headers were set by the service you just called, | ||
simply inspect the third parameter in the callback. | ||
Note: If you're using promises, the metadata will be available on the `meta` | ||
property of the resolved value. | ||
```js | ||
// dataService.js | ||
module.exports = { | ||
name: 'data_service', | ||
read: function(req, resource, params, config, callback) { | ||
// business logic | ||
var data = 'response'; | ||
var meta = { | ||
headers: { | ||
'cache-control': 'public, max-age=3600' | ||
}, | ||
statusCode: 200 // You can even provide a custom statusCode for the xhr response | ||
}; | ||
callback(null, data, meta); | ||
} | ||
} | ||
``` | ||
```js | ||
fetcher | ||
.read('data_service') | ||
.params({id: ###}) | ||
.end(function (err, data, meta) { | ||
// data will be 'response' | ||
// meta will have the header and statusCode from above | ||
}); | ||
``` | ||
There is a convenience method called `fetcher.getServiceMeta` on the fetchr instance. | ||
This method will return the metadata for all the calls that have happened so far | ||
in an array format. | ||
In the server, this will include all service calls for the current request. | ||
In the client, this will include all service calls for the current session. | ||
## Updating Configuration | ||
@@ -312,2 +358,3 @@ | ||
```js | ||
var fetcher = new Fetcher({ | ||
@@ -339,2 +386,3 @@ context: { // These context values are persisted with XHR calls as query params | ||
}); | ||
``` | ||
@@ -341,0 +389,0 @@ ## API |
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
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
65541
1244
396
6
+ Addedes6-promise@^3.0.2
+ Addedes6-promise@3.3.1(transitive)