Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

fetchr

Package Overview
Dependencies
Maintainers
5
Versions
95
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

fetchr - npm Package Compare versions

Comparing version 0.5.44 to 0.5.45

7

CONTRIBUTING.md

@@ -1,7 +0,6 @@

Contributing Code to `fetchr`
-------------------------------
# Contributing Code to `fetchr`
Please be sure to sign our [CLA][] before you submit pull requests or otherwise contribute to `fetchr`. This protects developers, who rely on [BSD license][].
[BSD license]: https://github.com/yahoo/fetchr/blob/master/LICENSE.md
[CLA]: https://yahoocla.herokuapp.com/
[bsd license]: https://github.com/yahoo/fetchr/blob/master/LICENSE.md
[cla]: https://yahoocla.herokuapp.com/

@@ -12,3 +12,3 @@ /**

*/
require("setimmediate");
require('setimmediate');
var REST = require('./util/http.client');

@@ -48,3 +48,3 @@ var deepmerge = require('deepmerge');

*/
function pickContext (context, picker, method) {
function pickContext(context, picker, method) {
if (!picker || !picker[method]) {

@@ -60,13 +60,15 @@ return context;

} else if (Array.isArray(p)) {
p.forEach(function(key) {
p.forEach(function (key) {
result[key] = context[key];
});
} else if (typeof p === 'function') {
forEach(context, function(value, key) {
forEach(context, function (value, key) {
if (p(value, key, context)) {
result[key] = context[key];
}
})
});
} else {
throw new TypeError('picker must be an string, an array, or a function.');
throw new TypeError(
'picker must be an string, an array, or a function.'
);
}

@@ -89,3 +91,3 @@

*/
function Request (operation, resource, options) {
function Request(operation, resource, options) {
if (!resource) {

@@ -105,3 +107,3 @@ throw new Error('Resource is required for a fetcher request');

statsCollector: options.statsCollector,
_serviceMeta: options._serviceMeta || []
_serviceMeta: options._serviceMeta || [],
};

@@ -172,3 +174,3 @@ this._params = {};

err: err,
time: Date.now() - self._startTime
time: Date.now() - self._startTime,
};

@@ -191,9 +193,18 @@ statsCollector(stats);

if (callback) {
return executeRequest(self, function requestSucceeded(result) {
self._captureMetaAndStats(null, result);
setImmediate(callback, null, result && result.data, result && result.meta);
}, function requestFailed(err) {
self._captureMetaAndStats(err);
setImmediate(callback, err);
});
return executeRequest(
self,
function requestSucceeded(result) {
self._captureMetaAndStats(null, result);
setImmediate(
callback,
null,
result && result.data,
result && result.meta
);
},
function requestFailed(err) {
self._captureMetaAndStats(err);
setImmediate(callback, err);
}
);
} else {

@@ -203,9 +214,12 @@ var promise = new Promise(function requestExecutor(resolve, reject) {

});
promise = promise.then(function requestSucceeded(result) {
self._captureMetaAndStats(null, result);
return result;
}, function requestFailed(err) {
self._captureMetaAndStats(err);
throw err;
});
promise = promise.then(
function requestSucceeded(result) {
self._captureMetaAndStats(null, result);
return result;
},
function requestFailed(err) {
self._captureMetaAndStats(err);
throw err;
}
);
return promise;

@@ -222,3 +236,3 @@ }

*/
function executeRequest (request, resolve, reject) {
function executeRequest(request, resolve, reject) {
var clientConfig = request._clientConfig;

@@ -232,3 +246,5 @@ var use_post;

if (!uri) {
uri = clientConfig.cors ? request.options.corsPath : request.options.xhrPath;
uri = clientConfig.cors
? request.options.corsPath
: request.options.xhrPath;
}

@@ -240,4 +256,17 @@

if (!use_post) {
var getUriFn = isFunction(clientConfig.constructGetUri) ? clientConfig.constructGetUri : defaultConstructGetUri;
var get_uri = getUriFn.call(request, uri, request.resource, request._params, clientConfig, pickContext(request.options.context, request.options.contextPicker, 'GET'));
var getUriFn = isFunction(clientConfig.constructGetUri)
? clientConfig.constructGetUri
: defaultConstructGetUri;
var get_uri = getUriFn.call(
request,
uri,
request.resource,
request._params,
clientConfig,
pickContext(
request.options.context,
request.options.contextPicker,
'GET'
)
);
/* istanbul ignore next */

@@ -247,3 +276,10 @@ if (!get_uri) {

// TODO: Add test for this fallback
get_uri = defaultConstructGetUri.call(request, uri, request.resource, request._params, clientConfig, request.options.context);
get_uri = defaultConstructGetUri.call(
request,
uri,
request.resource,
request._params,
clientConfig,
request.options.context
);
}

@@ -261,3 +297,3 @@ // TODO: Remove `returnMeta` feature flag after next release

// receive the new format
get_uri += (get_uri.indexOf('?') !== -1) ? '&' : '?';
get_uri += get_uri.indexOf('?') !== -1 ? '&' : '?';
get_uri += 'returnMeta=true';

@@ -273,8 +309,13 @@ if (get_uri.length <= MAX_URI_LEN) {

if (!use_post) {
return REST.get(uri, customHeaders, deepmerge({xhrTimeout: request.options.xhrTimeout}, clientConfig), function getDone(err, response) {
if (err) {
return reject(err);
return REST.get(
uri,
customHeaders,
deepmerge({ xhrTimeout: request.options.xhrTimeout }, clientConfig),
function getDone(err, response) {
if (err) {
return reject(err);
}
resolve(parseResponse(response));
}
resolve(parseResponse(response));
});
);
}

@@ -287,3 +328,3 @@

operation: request.operation,
params: request._params
params: request._params,
};

@@ -295,18 +336,30 @@ if (request._body) {

requests: requests,
context: request.options.context
context: request.options.context,
}; // TODO: remove. leave here for now for backward compatibility
uri = request._constructGroupUri(uri);
allow_retry_post = (request.operation === OP_READ);
return REST.post(uri, customHeaders, data, deepmerge({unsafeAllowRetry: allow_retry_post, xhrTimeout: request.options.xhrTimeout}, clientConfig), function postDone(err, response) {
if (err) {
return reject(err);
allow_retry_post = request.operation === OP_READ;
return REST.post(
uri,
customHeaders,
data,
deepmerge(
{
unsafeAllowRetry: allow_retry_post,
xhrTimeout: request.options.xhrTimeout,
},
clientConfig
),
function postDone(err, response) {
if (err) {
return reject(err);
}
var result = parseResponse(response);
if (result) {
result = result[DEFAULT_GUID] || {};
} else {
result = {};
}
resolve(result);
}
var result = parseResponse(response);
if (result) {
result = result[DEFAULT_GUID] || {};
} else {
result = {};
}
resolve(result);
});
);
}

@@ -323,5 +376,8 @@

var final_uri = uri;
forEach(pickContext(this.options.context, this.options.contextPicker, 'POST'), function eachContext(v, k) {
query.push(k + '=' + encodeURIComponent(v));
});
forEach(
pickContext(this.options.context, this.options.contextPicker, 'POST'),
function eachContext(v, k) {
query.push(k + '=' + encodeURIComponent(v));
}
);
if (query.length > 0) {

@@ -354,3 +410,3 @@ final_uri += '?' + query.sort().join('&');

function Fetcher (options) {
function Fetcher(options) {
this._serviceMeta = [];

@@ -365,3 +421,3 @@ this.options = {

statsCollector: options.statsCollector,
_serviceMeta: this._serviceMeta
_serviceMeta: this._serviceMeta,
};

@@ -423,6 +479,3 @@ }

}
return request
.params(params)
.clientConfig(clientConfig)
.end(callback);
return request.params(params).clientConfig(clientConfig).end(callback);
},

@@ -468,3 +521,3 @@

*/
'delete': function (resource, params, clientConfig, callback) {
delete: function (resource, params, clientConfig, callback) {
var request = new Request('delete', resource, this.options);

@@ -479,6 +532,3 @@ if (1 === arguments.length) {

}
return request
.params(params)
.clientConfig(clientConfig)
.end(callback);
return request.params(params).clientConfig(clientConfig).end(callback);
},

@@ -503,5 +553,5 @@

return this._serviceMeta;
}
},
};
module.exports = Fetcher;

@@ -5,3 +5,3 @@ /**

*/
require("setimmediate");
require('setimmediate');
var OP_READ = 'read';

@@ -16,3 +16,3 @@ var OP_CREATE = 'create';

var Promise = global.Promise || require('es6-promise').Promise;
var RESOURCE_SANTIZER_REGEXP = /[^\w\.]+/g;
var RESOURCE_SANTIZER_REGEXP = /[^\w.]+/g;

@@ -36,3 +36,3 @@ function parseValue(value) {

function parseParamValues (params) {
function parseParamValues(params) {
return Object.keys(params).reduce(function (parsed, curr) {

@@ -45,3 +45,5 @@ parsed[curr] = parseValue(params[curr]);

function sanitizeResourceName(resource) {
return resource ? resource.replace(RESOURCE_SANTIZER_REGEXP, '*') : resource;
return resource
? resource.replace(RESOURCE_SANTIZER_REGEXP, '*')
: resource;
}

@@ -58,3 +60,3 @@

var output = {
message: 'request failed'
message: 'request failed',
};

@@ -70,7 +72,6 @@

statusCode: statusCode,
output: output
output: output,
};
}
/**

@@ -92,3 +93,3 @@ * A Request instance represents a single fetcher request.

*/
function Request (operation, resource, options) {
function Request(operation, resource, options) {
if (!resource) {

@@ -119,5 +120,10 @@ throw new Error('Resource is required for a fetcher request');

Request.prototype.params = function (params) {
this._params = (typeof this._paramsProcessor === 'function')
? this._paramsProcessor(this.req, {operation: this.operation, resource: this.resource}, params)
: params;
this._params =
typeof this._paramsProcessor === 'function'
? this._paramsProcessor(
this.req,
{ operation: this.operation, resource: this.resource },
params
)
: params;
return this;

@@ -169,5 +175,7 @@ };

params: self._params,
statusCode: err ? err.statusCode : (result && result.meta && result.meta.statusCode || 200),
statusCode: err
? err.statusCode
: (result && result.meta && result.meta.statusCode) || 200,
err: err,
time: Date.now() - self._startTime
time: Date.now() - self._startTime,
};

@@ -192,16 +200,22 @@ statsCollector(stats);

promise = promise.then(function requestSucceeded(result) {
self._captureMetaAndStats(null, result);
return result;
}, function requestFailed(errData) {
self._captureMetaAndStats(errData);
throw errData.err;
});
promise = promise.then(
function requestSucceeded(result) {
self._captureMetaAndStats(null, result);
return result;
},
function requestFailed(errData) {
self._captureMetaAndStats(errData);
throw errData.err;
}
);
if (callback) {
promise.then(function requestSucceeded(result) {
setImmediate(callback, null, result.data, result.meta);
}, function requestFailed(err) {
setImmediate(callback, err);
});
promise.then(
function requestSucceeded(result) {
setImmediate(callback, null, result.data, result.meta);
},
function requestFailed(err) {
setImmediate(callback, err);
}
);
} else {

@@ -219,18 +233,24 @@ return promise;

*/
function executeRequest (request, resolve, reject) {
var args = [request.req, request.resource, request._params, request._clientConfig, function executeRequestCallback(err, data, meta) {
if (err) {
reject({
err: err,
meta: meta
});
} else {
resolve({
data: data,
meta: meta
});
}
}];
function executeRequest(request, resolve, reject) {
var args = [
request.req,
request.resource,
request._params,
request._clientConfig,
function executeRequestCallback(err, data, meta) {
if (err) {
reject({
err: err,
meta: meta,
});
} else {
resolve({
data: data,
meta: meta,
});
}
},
];
var op = request.operation;
if ((op === OP_CREATE) || (op === OP_UPDATE)) {
if (op === OP_CREATE || op === OP_UPDATE) {
args.splice(3, 0, request._body);

@@ -242,7 +262,12 @@ }

if (!service[op]) {
throw new Error('operation: ' + op + ' is undefined on service: ' + request.resource);
throw new Error(
'operation: ' +
op +
' is undefined on service: ' +
request.resource
);
}
service[op].apply(service, args);
} catch (err) {
reject({err: err});
reject({ err: err });
}

@@ -266,3 +291,3 @@ }

*/
function Fetcher (options) {
function Fetcher(options) {
this.options = options || {};

@@ -313,3 +338,5 @@ this.req = this.options.req || {};

} else {
throw new Error('"resource" property is missing in service definition.');
throw new Error(
'"resource" property is missing in service definition.'
);
}

@@ -347,3 +374,5 @@

if (!service) {
throw new Error('Service "' + sanitizeResourceName(name) + '" could not be found');
throw new Error(
'Service "' + sanitizeResourceName(name) + '" could not be found'
);
}

@@ -384,8 +413,15 @@ return service;

options = options || {};
var responseFormatter = options.responseFormatter || function noOp(req, res, data) {
return data;
};
var responseFormatter =
options.responseFormatter ||
function noOp(req, res, data) {
return data;
};
if (Fetcher._deprecatedServicesDefinitions.length && 'production' !== process.env.NODE_ENV) {
var deprecatedServices = Fetcher._deprecatedServicesDefinitions.sort().join(', ');
if (
Fetcher._deprecatedServicesDefinitions.length &&
'production' !== process.env.NODE_ENV
) {
var deprecatedServices = Fetcher._deprecatedServicesDefinitions
.sort()
.join(', ');

@@ -396,3 +432,4 @@ console.warn(

'following services definitions:\n' +
deprecatedServices + '.'
deprecatedServices +
'.'
);

@@ -413,3 +450,5 @@ }

if (!resource) {
error = fumble.http.badRequest('No resource specified', { debug: 'Bad resource' });
error = fumble.http.badRequest('No resource specified', {
debug: 'Bad resource',
});
error.source = 'fetchr';

@@ -423,3 +462,3 @@ return next(error);

error = fumble.http.badRequest(errorMsg, {
debug: 'Bad resource ' + resourceName
debug: 'Bad resource ' + resourceName,
});

@@ -434,3 +473,3 @@ error.source = 'fetchr';

statsCollector: options.statsCollector,
paramsProcessor: options.paramsProcessor
paramsProcessor: options.paramsProcessor,
});

@@ -447,8 +486,12 @@ request

if (req.query && req.query.returnMeta) {
res.status(errResponse.statusCode).json(responseFormatter(req, res, {
output: errResponse.output,
meta: meta
}));
res.status(errResponse.statusCode).json(
responseFormatter(req, res, {
output: errResponse.output,
meta: meta,
})
);
} else {
res.status(errResponse.statusCode).json(responseFormatter(req, res, errResponse.output));
res.status(errResponse.statusCode).json(
responseFormatter(req, res, errResponse.output)
);
}

@@ -458,6 +501,8 @@ return;

if (req.query.returnMeta) {
res.status(meta.statusCode || 200).json(responseFormatter(req, res, {
data: data,
meta: meta
}));
res.status(meta.statusCode || 200).json(
responseFormatter(req, res, {
data: data,
meta: meta,
})
);
} else {

@@ -473,3 +518,3 @@ // TODO: Remove `returnMeta` feature flag after next release

error = fumble.http.badRequest('No resource specified', {
debug: 'No resources'
debug: 'No resources',
});

@@ -487,3 +532,3 @@ error.source = 'fetchr';

error = fumble.http.badRequest(errorMsg, {
debug: 'Bad resource ' + resourceName
debug: 'Bad resource ' + resourceName,
});

@@ -494,6 +539,17 @@ error.source = 'fetchr';

var operation = singleRequest.operation;
if(operation !== OP_CREATE && operation !== OP_UPDATE && operation !== OP_DELETE && operation !== OP_READ) {
if (
operation !== OP_CREATE &&
operation !== OP_UPDATE &&
operation !== OP_DELETE &&
operation !== OP_READ
) {
error = fumble.http.badRequest(
'Unsupported "' + resourceName + '.' + operation + '" operation',
{ debug: 'Only "create", "read", "update" or "delete" operations are allowed' }
'Unsupported "' +
resourceName +
'.' +
operation +
'" operation',
{
debug: 'Only "create", "read", "update" or "delete" operations are allowed',
}
);

@@ -508,3 +564,3 @@ error.source = 'fetchr';

statsCollector: options.statsCollector,
paramsProcessor: options.paramsProcessor
paramsProcessor: options.paramsProcessor,
});

@@ -514,3 +570,3 @@ request

.body(singleRequest.body || {})
.end(function(err, data) {
.end(function (err, data) {
var meta = serviceMeta[0] || {};

@@ -522,3 +578,5 @@ if (meta.headers) {

var errResponse = getErrorResponse(err);
res.status(errResponse.statusCode).json(responseFormatter(req, res, errResponse.output));
res.status(errResponse.statusCode).json(
responseFormatter(req, res, errResponse.output)
);
return;

@@ -529,3 +587,3 @@ }

data: data,
meta: meta
meta: meta,
});

@@ -539,3 +597,2 @@ res.status(meta.statusCode || 200).json(responseObj);

// ------------------------------------------------------------------

@@ -560,3 +617,3 @@ // CRUD Data Access Wrapper Methods

serviceMeta: this._serviceMeta,
statsCollector: this.options.statsCollector
statsCollector: this.options.statsCollector,
});

@@ -576,6 +633,3 @@ if (1 === arguments.length) {

}
return request
.params(params)
.clientConfig(config)
.end(callback);
return request.params(params).clientConfig(config).end(callback);
};

@@ -598,3 +652,3 @@ /**

serviceMeta: this._serviceMeta,
statsCollector: this.options.statsCollector
statsCollector: this.options.statsCollector,
});

@@ -614,7 +668,3 @@ if (1 === arguments.length) {

}
return request
.params(params)
.body(body)
.clientConfig(config)
.end(callback);
return request.params(params).body(body).clientConfig(config).end(callback);
};

@@ -637,3 +687,3 @@ /**

serviceMeta: this._serviceMeta,
statsCollector: this.options.statsCollector
statsCollector: this.options.statsCollector,
});

@@ -653,7 +703,3 @@ if (1 === arguments.length) {

}
return request
.params(params)
.body(body)
.clientConfig(config)
.end(callback);
return request.params(params).body(body).clientConfig(config).end(callback);
};

@@ -675,3 +721,3 @@ /**

serviceMeta: this._serviceMeta,
statsCollector: this.options.statsCollector
statsCollector: this.options.statsCollector,
});

@@ -692,6 +738,3 @@ if (1 === arguments.length) {

}
return request
.params(params)
.clientConfig(config)
.end(callback);
return request.params(params).clientConfig(config).end(callback);
};

@@ -698,0 +741,0 @@

@@ -31,3 +31,9 @@ /**

*/
module.exports = function defaultConstructGetUri(baseUri, resource, params, config, context) {
module.exports = function defaultConstructGetUri(
baseUri,
resource,
params,
config,
context
) {
var query = [];

@@ -45,5 +51,7 @@ var matrix = [];

try {
matrix.push(k + '=' + encodeURIComponent(jsonifyComplexType(v)));
matrix.push(
k + '=' + encodeURIComponent(jsonifyComplexType(v))
);
} catch (err) {
console.debug('jsonifyComplexType failed: ' + err)
console.debug('jsonifyComplexType failed: ' + err);
}

@@ -50,0 +58,0 @@ }

@@ -5,3 +5,3 @@ /**

*/
/*jslint nomen:true,plusplus:true*/
/**

@@ -11,2 +11,5 @@ * @module rest-http

var xhr = require('xhr');
var forEach = require('./forEach');
/*

@@ -21,4 +24,6 @@ * Default configurations:

interval: 200,
max_retries: 0
}
maxRetries: 0,
statusCodes: [0, 408, 999],
},
unsafeAllowRetry: false,
},

@@ -31,12 +36,11 @@ CONTENT_TYPE = 'Content-Type',

METHOD_DELETE = 'DELETE',
NULL = null,
xhr = require('xhr');
NULL = null;
var forEach = require('./forEach');
var INITIAL_ATTEMPT = 0;
//trim polyfill, maybe pull from npm later
if (!String.prototype.trim) {
String.prototype.trim = function () {
return this.replace(/^\s+|\s+$/g, '');
};
String.prototype.trim = function () {
return this.replace(/^\s+|\s+$/g, '');
};
}

@@ -49,3 +53,3 @@

}
var needContentType = (method === METHOD_PUT || method === METHOD_POST);
var needContentType = method === METHOD_PUT || method === METHOD_POST;
forEach(headers, function (v, field) {

@@ -78,13 +82,16 @@ if (field.toLowerCase() === 'content-type') {

function shouldRetry(method, config, statusCode) {
var isIdempotent = (method === METHOD_GET || method === METHOD_PUT || method === METHOD_DELETE);
if (!isIdempotent && !config.unsafeAllowRetry) {
function shouldRetry(method, config, statusCode, attempt) {
if (attempt >= config.retry.maxRetries) {
return false;
}
if ((statusCode !== 0 && statusCode !== 408 && statusCode !== 999) || config.tmp.retry_counter >= config.retry.max_retries) {
var isIdempotent =
method === METHOD_GET ||
method === METHOD_PUT ||
method === METHOD_DELETE;
if (!isIdempotent && !config.unsafeAllowRetry) {
return false;
}
config.tmp.retry_counter++;
config.retry.interval = config.retry.interval * 2;
return true;
return config.retry.statusCodes.indexOf(statusCode) !== -1;
}

@@ -94,14 +101,12 @@

var cfg = {
unsafeAllowRetry: config.unsafeAllowRetry || false,
retry: {
interval: DEFAULT_CONFIG.retry.interval,
max_retries: DEFAULT_CONFIG.retry.max_retries
}
}, // Performant-but-verbose way of cloning the default config as base
timeout,
interval,
maxRetries;
unsafeAllowRetry: config.unsafeAllowRetry || false,
retry: {
interval: DEFAULT_CONFIG.retry.interval,
maxRetries: DEFAULT_CONFIG.retry.maxRetries,
statusCodes: DEFAULT_CONFIG.retry.statusCodes,
},
};
if (config) {
timeout = config.timeout || config.xhrTimeout;
var timeout = config.timeout || config.xhrTimeout;
timeout = parseInt(timeout, 10);

@@ -113,10 +118,24 @@ if (!isNaN(timeout) && timeout > 0) {

if (config.retry) {
interval = parseInt(config.retry && config.retry.interval, 10);
var interval = parseInt(config.retry.interval, 10);
if (!isNaN(interval) && interval > 0) {
cfg.retry.interval = interval;
}
maxRetries = parseInt(config.retry && config.retry.max_retries, 10);
if (config.retry.max_retries !== undefined) {
console.warn(
'"max_retries" is deprecated and will be removed in a future release, use "maxRetries" instead.'
);
}
var maxRetries = parseInt(
config.retry.max_retries || config.retry.maxRetries,
10
);
if (!isNaN(maxRetries) && maxRetries >= 0) {
cfg.retry.max_retries = maxRetries;
cfg.retry.maxRetries = maxRetries;
}
if (config.retry.statusCodes) {
cfg.retry.statusCodes = config.retry.statusCodes;
}
}

@@ -127,7 +146,2 @@

}
// tmp stores transient state data, such as retry count
if (config.tmp) {
cfg.tmp = config.tmp;
}
}

@@ -138,32 +152,42 @@

function doXhr(method, url, headers, data, config, callback) {
var options, timeout;
function doXhr(method, url, headers, data, config, attempt, callback) {
headers = normalizeHeaders(headers, method, config.cors);
config = mergeConfig(config);
// use config.tmp to store temporary values
config.tmp = config.tmp || {retry_counter: 0};
timeout = config.timeout;
options = {
method : method,
timeout : timeout,
var options = {
method: method,
timeout: config.timeout,
headers: headers,
useXDR: config.useXDR,
withCredentials: config.withCredentials,
on : {
success : function (err, response) {
on: {
success: function (err, response) {
callback(NULL, response);
},
failure : function (err, response) {
if (!shouldRetry(method, config, response.statusCode)) {
failure: function (err, response) {
if (
!shouldRetry(method, config, response.statusCode, attempt)
) {
callback(err);
} else {
setTimeout(
function retryXHR() { doXhr(method, url, headers, data, config, callback); },
config.retry.interval
);
// Use exponential backoff and full jitter strategy published in https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/
var delay =
Math.random() *
config.retry.interval *
Math.pow(2, attempt);
setTimeout(function retryXHR() {
doXhr(
method,
url,
headers,
data,
config,
attempt + 1,
callback
);
}, delay);
}
}
}
},
},
};

@@ -177,52 +201,57 @@ if (data !== undefined && data !== NULL) {

function io(url, options) {
return xhr({
url: url,
method: options.method || METHOD_GET,
timeout: options.timeout,
headers: options.headers,
body: options.data,
useXDR: options.cors,
withCredentials: options.withCredentials
}, function (err, resp, body) {
var status = resp.statusCode;
var errMessage, errBody;
return xhr(
{
url: url,
method: options.method || METHOD_GET,
timeout: options.timeout,
headers: options.headers,
body: options.data,
useXDR: options.cors,
withCredentials: options.withCredentials,
},
function (err, resp, body) {
var status = resp.statusCode;
var errMessage, errBody;
if (!err && (status === 0 || (status >= 400 && status < 600))) {
if (typeof body === 'string') {
try {
errBody = JSON.parse(body);
if (errBody.message) {
errMessage = errBody.message;
} else {
if (!err && (status === 0 || (status >= 400 && status < 600))) {
if (typeof body === 'string') {
try {
errBody = JSON.parse(body);
if (errBody.message) {
errMessage = errBody.message;
} else {
errMessage = body;
}
} catch (e) {
errMessage = body;
}
} catch(e) {
errMessage = body;
} else {
errMessage = status
? 'Error ' + status
: 'Internal Fetchr XMLHttpRequest Error';
}
} else {
errMessage = status ? 'Error ' + status : 'Internal Fetchr XMLHttpRequest Error';
}
err = new Error(errMessage);
err.statusCode = status;
err.body = errBody || body;
if (err.body) {
err.output = err.body.output;
err.meta = err.body.meta;
err = new Error(errMessage);
err.statusCode = status;
err.body = errBody || body;
if (err.body) {
err.output = err.body.output;
err.meta = err.body.meta;
}
}
}
resp.responseText = body;
resp.responseText = body;
if (err) {
// getting detail info from xhr module
err.rawRequest = resp.rawRequest;
err.url = resp.url;
err.timeout = options.timeout;
if (err) {
// getting detail info from xhr module
err.rawRequest = resp.rawRequest;
err.url = resp.url;
err.timeout = options.timeout;
options.on.failure.call(null, err, resp);
} else {
options.on.success.call(null, null, resp);
options.on.failure.call(null, err, resp);
} else {
options.on.success.call(null, null, resp);
}
}
});
);
}

@@ -241,10 +270,19 @@

* @param {Number} [config.timeout=3000] Timeout (in ms) for each request
* @param {Object} config.retry Retry config object.
* @param {Number} [config.retry.interval=200] The start interval unit (in ms).
* @param {Number} [config.retry.max_retries=2] Number of max retries.
* @param {Object} config.retry Retry config object.
* @param {Number} [config.retry.interval=200] The start interval unit (in ms).
* @param {Number} [config.retry.maxRetries=0] Number of max retries.
* @param {Number} [config.retry.statusCodes=[0, 408, 999]] Response status codes to be retried.
* @param {Boolean} [config.cors] Whether to enable CORS & use XDR on IE8/9.
* @param {Function} callback The callback function, with two params (error, response)
*/
get : function (url, headers, config, callback) {
return doXhr(METHOD_GET, url, headers, NULL, config, callback);
get: function (url, headers, config, callback) {
return doXhr(
METHOD_GET,
url,
headers,
NULL,
config,
INITIAL_ATTEMPT,
callback
);
},

@@ -259,9 +297,18 @@

* @param {Number} [config.timeout=3000] Timeout (in ms) for each request
* @param {Number} [config.retry.interval=200] The start interval unit (in ms).
* @param {Number} [config.retry.max_retries=2] Number of max retries.
* @param {Number} [config.retry.interval=200] The start interval unit (in ms).
* @param {Number} [config.retry.maxRetries=0] Number of max retries.
* @param {Number} [config.retry.statusCodes=[0, 408, 999]] Response status codes to be retried.
* @param {Boolean} [config.cors] Whether to enable CORS & use XDR on IE8/9.
* @param {Function} callback The callback function, with two params (error, response)
*/
put : function (url, headers, data, config, callback) {
return doXhr(METHOD_PUT, url, headers, data, config, callback);
put: function (url, headers, data, config, callback) {
return doXhr(
METHOD_PUT,
url,
headers,
data,
config,
INITIAL_ATTEMPT,
callback
);
},

@@ -277,9 +324,18 @@

* @param {Boolean} [config.unsafeAllowRetry=false] Whether to allow retrying this post.
* @param {Number} [config.retry.interval=200] The start interval unit (in ms).
* @param {Number} [config.retry.max_retries=2] Number of max retries.
* @param {Number} [config.retry.interval=200] The start interval unit (in ms).
* @param {Number} [config.retry.maxRetries=0] Number of max retries.
* @param {Number} [config.retry.statusCodes=[0, 408, 999]] Response status codes to be retried.
* @param {Boolean} [config.cors] Whether to enable CORS & use XDR on IE8/9.
* @param {Function} callback The callback function, with two params (error, response)
*/
post : function (url, headers, data, config, callback) {
return doXhr(METHOD_POST, url, headers, data, config, callback);
post: function (url, headers, data, config, callback) {
return doXhr(
METHOD_POST,
url,
headers,
data,
config,
INITIAL_ATTEMPT,
callback
);
},

@@ -293,10 +349,19 @@

* @param {Number} [config.timeout=3000] Timeout (in ms) for each request
* @param {Number} [config.retry.interval=200] The start interval unit (in ms).
* @param {Number} [config.retry.max_retries=2] Number of max retries.
* @param {Number} [config.retry.interval=200] The start interval unit (in ms).
* @param {Number} [config.retry.maxRetries=0] Number of max retries.
* @param {Number} [config.retry.statusCodes=[0, 408, 999]] Response status codes to be retried.
* @param {Boolean} [config.cors] Whether to enable CORS & use XDR on IE8/9.
* @param {Function} callback The callback function, with two params (error, response)
*/
'delete' : function (url, headers, config, callback) {
return doXhr(METHOD_DELETE, url, headers, NULL, config, callback);
}
delete: function (url, headers, config, callback) {
return doXhr(
METHOD_DELETE,
url,
headers,
NULL,
config,
INITIAL_ATTEMPT,
callback
);
},
};

@@ -1,6 +0,4 @@

Software License Agreement (BSD License)
========================================
# Software License Agreement (BSD License)
Copyright (c) 2014, Yahoo! Inc. All rights reserved.
----------------------------------------------------
## Copyright (c) 2014, Yahoo! Inc. All rights reserved.

@@ -11,8 +9,8 @@ Redistribution and use of this software in source and binary forms, with or

* Redistributions of source code must retain the above copyright notice, this
- Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
- Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of Yahoo! Inc. nor the names of YUI's contributors may be
- Neither the name of Yahoo! Inc. nor the names of YUI's contributors may be
used to endorse or promote products derived from this software without

@@ -19,0 +17,0 @@ specific prior written permission of Yahoo! Inc.

{
"name": "fetchr",
"version": "0.5.44",
"version": "0.5.45",
"description": "Fetchr augments Flux applications by allowing Flux stores to be used on server and client to fetch data",

@@ -9,3 +9,5 @@ "main": "index.js",

"cover": "istanbul cover --dir artifacts -- ./node_modules/mocha/bin/_mocha tests/unit/ --recursive --reporter spec --timeout 20000 --exit",
"lint": "eslint .",
"format": "prettier --write .",
"format:check": "prettier --check .",
"lint": "eslint . && npm run format:check",
"test": "NODE_ENV=test mocha tests/unit/ --recursive --reporter spec --timeout 20000 --exit"

@@ -43,2 +45,3 @@ },

"pre-commit": "^1.0.0",
"prettier": "^2.3.2",
"qs": "^6.7.0",

@@ -55,3 +58,7 @@ "request": "^2.81.0",

"dispatchr"
]
],
"prettier": {
"singleQuote": true,
"tabWidth": 4
}
}
# Fetchr
[![npm version](https://badge.fury.io/js/fetchr.svg)](http://badge.fury.io/js/fetchr)
[![Build Status](https://travis-ci.org/yahoo/fetchr.svg?branch=master)](https://travis-ci.org/yahoo/fetchr)
![Build Status](https://github.com/yahoo/fetchr/actions/workflows/node.js.yml/badge.svg)
[![Coverage Status](https://coveralls.io/repos/yahoo/fetchr/badge.png?branch=master)](https://coveralls.io/r/yahoo/fetchr?branch=master)

@@ -15,3 +15,3 @@

```
```bash
npm install fetchr --save

@@ -31,6 +31,6 @@ ```

```js
var express = require('express');
var Fetcher = require('fetchr');
var bodyParser = require('body-parser');
var app = express();
import express from 'express';
import Fetcher from 'fetchr';
import bodyParser from 'body-parser';
const app = express();

@@ -50,5 +50,5 @@ // you need to use body-parser middleware before fetcher middleware

```js
var Fetcher = require('fetchr');
var fetcher = new Fetcher({
xhrPath: '/myCustomAPIEndpoint'
import Fetcher from 'fetchr';
const fetcher = new Fetcher({
xhrPath: '/myCustomAPIEndpoint',
});

@@ -60,6 +60,6 @@ ```

You will need to register any data services that you wish to use in
your application. The interface for your service will be an object
your application. The interface for your service will be an object
that must define a `resource` property and at least one
[CRUD](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete)
operation. The `resource` property will be used when you call one of the
operation. The `resource` property will be used when you call one of the
CRUD operations.

@@ -69,4 +69,4 @@

// app.js
var Fetcher = require('fetchr');
var myDataService = require('./dataService');
import Fetcher from 'fetchr';
import myDataService from './dataService';
Fetcher.registerService(myDataService);

@@ -77,8 +77,8 @@ ```

// dataService.js
module.exports = {
export default {
// resource is required
resource: 'data_service',
// at least one of the CRUD methods is required
read: function(req, resource, params, config, callback) {
//...
read: function (req, resource, params, config, callback) {
//...
},

@@ -89,3 +89,3 @@ // other methods

// delete: function(req, resource, params, config, callback) {}
}
};
```

@@ -101,9 +101,8 @@

```js
// app.js - server
var express = require('express');
var Fetcher = require('fetchr');
var app = express();
var myDataService = require('./dataService');
import express from 'express';
import Fetcher from 'fetchr';
import myDataService from './dataService';
const app = express();

@@ -118,3 +117,3 @@ // register the service

// instantiated fetcher with access to req object
var fetcher = new Fetcher({
const fetcher = new Fetcher({
xhrPath: '/myCustomAPIEndpoint', // xhrPath will be ignored on the serverside fetcher instantiation

@@ -136,4 +135,4 @@ req: req

// app.js - client
var Fetcher = require('fetchr');
var fetcher = new Fetcher({
import Fetcher from 'fetchr';
const fetcher = new Fetcher({
xhrPath: '/myCustomAPIEndpoint' // xhrPath is REQUIRED on the clientside fetcher instantiation

@@ -157,4 +156,2 @@ });

## Usage Examples

@@ -177,16 +174,16 @@

// dataService.js
module.exports = {
export default {
resource: 'data_service',
read: function(req, resource, params, config, callback) {
read: function (req, resource, params, config, callback) {
// business logic
var data = 'response';
var meta = {
const data = 'response';
const meta = {
headers: {
'cache-control': 'public, max-age=3600'
'cache-control': 'public, max-age=3600',
},
statusCode: 200 // You can even provide a custom statusCode for the xhr response
statusCode: 200, // You can even provide a custom statusCode for the xhr response
};
callback(null, data, meta);
}
}
},
};
```

@@ -210,3 +207,2 @@

## Updating Configuration

@@ -221,5 +217,5 @@

// Start
var fetcher = new Fetcher({
const fetcher = new Fetcher({
xhrPath: '/myCustomAPIEndpoint',
xhrTimeout: 2000
xhrTimeout: 2000,
});

@@ -229,7 +225,6 @@

fetcher.updateOptions({
xhrTimeout: 4000
xhrTimeout: 4000,
});
```
## Error Handling

@@ -240,10 +235,10 @@

```js
module.exports = {
export default {
resource: 'FooService',
read: function create(req, resource, params, configs, callback) {
var err = new Error('it failed');
const err = new Error('it failed');
err.statusCode = 404;
err.output = { message: "Not found", more: "meta data" };
err.output = { message: 'Not found', more: 'meta data' };
return callback(err);
}
},
};

@@ -267,7 +262,7 @@ ```

The xhr object is returned by the `.end()` method as long as you're *not* chaining promises.
The xhr object is returned by the `.end()` method as long as you're _not_ chaining promises.
This is useful if you want to abort a request before it is completed.
```js
var req = fetcher
const req = fetcher
.read('someData')

@@ -283,4 +278,5 @@ .params({id: ###})

However, you can't acces the xhr object if using promise chaining like so:
```js
var req = fetcher
const req = fetcher
.read('someData')

@@ -300,6 +296,6 @@ .params({id: ###})

```js
var Fetcher = require('fetchr');
var fetcher = new Fetcher({
import Fetcher from 'fetchr';
const fetcher = new Fetcher({
xhrPath: '/myCustomAPIEndpoint',
xhrTimeout: 4000
xhrTimeout: 4000,
});

@@ -333,8 +329,11 @@ ```

*/
app.use('/myCustomAPIEndpoint', Fetcher.middleware({
paramsProcessor: function (req, serviceInfo, params) {
console.log(serviceInfo.resource, serviceInfo.operation);
return Object.assign({foo: 'fillDefaultValueForFoo'}, params);
}
}));
app.use(
'/myCustomAPIEndpoint',
Fetcher.middleware({
paramsProcessor: function (req, serviceInfo, params) {
console.log(serviceInfo.resource, serviceInfo.operation);
return Object.assign({ foo: 'fillDefaultValueForFoo' }, params);
},
})
);
```

@@ -355,8 +354,11 @@

*/
app.use('/myCustomAPIEndpoint', Fetcher.middleware({
responseFormatter: function (req, res, data) {
data.debug = 'some debug information';
return data;
}
}));
app.use(
'/myCustomAPIEndpoint',
Fetcher.middleware({
responseFormatter: function (req, res, data) {
data.debug = 'some debug information';
return data;
},
})
);
```

@@ -373,6 +375,6 @@

```js
var Fetcher = require('fetchr');
var fetcher = new Fetcher({
import Fetcher from 'fetchr';
const fetcher = new Fetcher({
corsPath: 'http://www.foo.com',
xhrPath: '/fooProxy'
xhrPath: '/fooProxy',
});

@@ -389,3 +391,3 @@ fetcher

```js
var qs = require('qs');
import qs from 'qs';
function customConstructGetUri(uri, resource, params, config) {

@@ -399,6 +401,6 @@ // this refers to the Fetcher object itself that this function is invoked with.

var Fetcher = require('fetchr');
var fetcher = new Fetcher({
import Fetcher from 'fetchr';
const fetcher = new Fetcher({
corsPath: 'http://www.foo.com',
xhrPath: '/fooProxy'
xhrPath: '/fooProxy',
});

@@ -410,3 +412,3 @@ fetcher

cors: true,
constructGetUri: customConstructGetUri
constructGetUri: customConstructGetUri,
})

@@ -416,3 +418,2 @@ .end(callbackFn);

## CSRF Protection

@@ -429,7 +430,8 @@

```js
var fetcher = new Fetcher({
const fetcher = new Fetcher({
xhrPath: '/myCustomAPIEndpoint', //xhrPath is REQUIRED on the clientside fetcher instantiation
context: { // These context values are persisted with XHR calls as query params
_csrf: 'Ax89D94j'
}
context: {
// These context values are persisted with XHR calls as query params
_csrf: 'Ax89D94j',
},
});

@@ -448,16 +450,67 @@ ```

//app.js - client
var config = {
const config = {
timeout: 6000, // Timeout (in ms) for each request
unsafeAllowRetry: false // for POST requests, whether to allow retrying this post
unsafeAllowRetry: false, // for POST requests, whether to allow retrying this post
};
fetcher.read('service').params({ id: 1 }).clientConfig(config).end(callbackFn);
```
For requests from the server, the config object is simply passed into the service being called.
## Retry
You can set Fetchr to retry failed requests automatically by setting a `retry` settings in the client configuration:
```js
fetcher
.read('service')
.params({ id: 1 })
.clientConfig(config)
.end(callbackFn);
.clientConfig({
retry: {
maxRetries: 2,
},
})
.end();
```
For requests from the server, the config object is simply passed into the service being called.
With this configuration, Fetchr will retry all requests that fail with 408 status code or with an XHR 0 status code two more times before returning an error. The interval between each request respects
the following formula, based on the exponential backoff and full jitter strategy published in [this AWS architecture blog post](https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/):
```js
Math.random() * Math.pow(2, attempt) * interval;
```
`attempt` is the number of the current retry attempt starting
from 0. By default `interval` corresponds to 200ms.
You can customize the retry behavior by adding more properties in the
`retry` object:
```js
fetcher
.read('resource')
.clientConfig({
retry: {
maxRetries: 5,
interval: 1000,
statusCodes: [408, 502],
},
})
.end();
```
With the above configuration, Fetchr will retry all failed (408 or 502 status code) requests for a maximum of 5 times. The interval between each request will still use the formula from above, but the interval of 1000ms will be used instead.
**Note:** Fetchr doesn't retry POST requests for safety reasons. You can enable retries for POST requests by setting the `unsafeAllowRetry` property to `true`:
```js
fetcher
.create('resource')
.clientConfig({
retry: { maxRetries: 2 },
unsafeAllowRetry: true,
})
.end();
```
## Context Variables

@@ -470,6 +523,7 @@

```js
var fetcher = new Fetcher({
context: { // These context values are persisted with XHR calls as query params
const fetcher = new Fetcher({
context: {
// These context values are persisted with XHR calls as query params
_csrf: 'Ax89D94j',
device: 'desktop'
device: 'desktop',
},

@@ -483,15 +537,16 @@ contextPicker: {

return true;
}
},
// for other method e.g., POST, if you don't define the picker, it will pick the entire context object
}
},
});
var fetcher = new Fetcher({
context: { // These context values are persisted with XHR calls as query params
const fetcher = new Fetcher({
context: {
// These context values are persisted with XHR calls as query params
_csrf: 'Ax89D94j',
device: 'desktop'
device: 'desktop',
},
contextPicker: {
GET: ['device'] // predicate can be an array of strings
}
GET: ['device'], // predicate can be an array of strings
},
});

@@ -507,13 +562,9 @@ ```

```js
var config = {
const config = {
headers: {
'X-VERSION': '1.0.0'
}
'X-VERSION': '1.0.0',
},
};
fetcher
.read('service')
.params({ id: 1 })
.clientConfig(config)
.end(callbackFn);
fetcher.read('service').params({ id: 1 }).clientConfig(config).end(callbackFn);
```

@@ -524,21 +575,20 @@

```js
var Fetcher = require('fetchr');
var fetcher = new Fetcher({
import Fetcher from 'fetchr';
const fetcher = new Fetcher({
headers: {
'X-VERSION': '1.0.0'
}
'X-VERSION': '1.0.0',
},
});
```
## Stats Monitoring & Analysis
To collect fetcher service's success/failure/latency stats, you can configure `statsCollector` for `Fetchr`. The `statsCollector` function will be invoked with one argumment: `stats`. The `stats` object will contain the following fields:
To collect fetcher service's success/failure/latency stats, you can configure `statsCollector` for `Fetchr`. The `statsCollector` function will be invoked with one argumment: `stats`. The `stats` object will contain the following fields:
* **resource:** The name of the resource for the request
* **operation:** The name of the operation, `create|read|update|delete`
* **params:** The params object for the resource
* **statusCode:** The status code of the response
* **err:** The error object of failed request; null if request was successful
* **time:** The time spent for this request, in milliseconds
- **resource:** The name of the resource for the request
- **operation:** The name of the operation, `create|read|update|delete`
- **params:** The params object for the resource
- **statusCode:** The status code of the response
- **err:** The error object of failed request; null if request was successful
- **time:** The time spent for this request, in milliseconds

@@ -548,4 +598,4 @@ ### Fetcher Instance

```js
var Fetcher = require('fetchr');
var fetcher = new Fetcher({
import Fetcher from 'fetchr';
const fetcher = new Fetcher({
xhrPath: '/myCustomAPIEndpoint',

@@ -555,7 +605,14 @@ statsCollector: function (stats) {

// like aggregating stats or filtering out stats you don't want to monitor
console.log('Request for resource', stats.resource,
'with', stats.operation,
'returned statusCode:', stats.statusCode,
' within', stats.time, 'ms');
}
console.log(
'Request for resource',
stats.resource,
'with',
stats.operation,
'returned statusCode:',
stats.statusCode,
' within',
stats.time,
'ms'
);
},
});

@@ -567,12 +624,22 @@ ```

```js
app.use('/myCustomAPIEndpoint', Fetcher.middleware({
statsCollector: function (stats) {
// just console logging as a naive example. there is a lot more you can do here,
// like aggregating stats or filtering out stats you don't want to monitor
console.log('Request for resource', stats.resource,
'with', stats.operation,
'returned statusCode:', stats.statusCode,
' within', stats.time, 'ms');
}
}));
app.use(
'/myCustomAPIEndpoint',
Fetcher.middleware({
statsCollector: function (stats) {
// just console logging as a naive example. there is a lot more you can do here,
// like aggregating stats or filtering out stats you don't want to monitor
console.log(
'Request for resource',
stats.resource,
'with',
stats.operation,
'returned statusCode:',
stats.statusCode,
' within',
stats.time,
'ms'
);
},
})
);
```

@@ -582,3 +649,3 @@

- [Fetchr](https://github.com/yahoo/fetchr/blob/master/docs/fetchr.md)
- [Fetchr](https://github.com/yahoo/fetchr/blob/master/docs/fetchr.md)

@@ -590,2 +657,2 @@ ## License

[LICENSE file]: https://github.com/yahoo/fetchr/blob/master/LICENSE.md
[license file]: https://github.com/yahoo/fetchr/blob/master/LICENSE.md
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