redux-resource-xhr
Version:
Action creators for Redux Resource using the xhr library
337 lines (269 loc) • 9.4 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('redux-resource'), require('xhr')) :
typeof define === 'function' && define.amd ? define(['exports', 'redux-resource', 'xhr'], factory) :
(factory((global.ReduxResourceXHR = {}),global.ReduxResource,global.xhr));
}(this, (function (exports,reduxResource,xhr) { 'use strict';
xhr = xhr && xhr.hasOwnProperty('default') ? xhr['default'] : xhr;
var has = Object.prototype.hasOwnProperty;
/**
* Decode a URI encoded string.
*
* @param {String} input The URI encoded string.
* @returns {String} The decoded string.
* @api private
*/
function decode(input) {
return decodeURIComponent(input.replace(/\+/g, ' '));
}
/**
* Simple query string parser.
*
* @param {String} query The query string that needs to be parsed.
* @returns {Object}
* @api public
*/
function querystring(query) {
var parser = /([^=?&]+)=?([^&]*)/g
, result = {}
, part;
//
// Little nifty parsing hack, leverage the fact that RegExp.exec increments
// the lastIndex property so we can continue executing this loop until we've
// parsed all results.
//
for (;
part = parser.exec(query);
result[decode(part[1])] = decode(part[2])
);
return result;
}
/**
* Transform a query string to an object.
*
* @param {Object} obj Object that should be transformed.
* @param {String} prefix Optional prefix.
* @returns {String}
* @api public
*/
function querystringify(obj, prefix) {
prefix = prefix || '';
var pairs = [];
//
// Optionally prefix with a '?' if needed
//
if ('string' !== typeof prefix) prefix = '?';
for (var key in obj) {
if (has.call(obj, key)) {
pairs.push(encodeURIComponent(key) +'='+ encodeURIComponent(obj[key]));
}
}
return pairs.length ? prefix + pairs.join('&') : '';
}
//
// Expose the module.
//
var stringify = querystringify;
var parse = querystring;
var querystringify_1 = {
stringify: stringify,
parse: parse
};
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
return typeof obj;
} : function (obj) {
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
};
var _extends = Object.assign || function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
/* eslint no-param-reassign: 'off' */
// This is a small wrapper around `xhr`.
// It has two improvements:
// 1. Omit the `cb`, and a Promise will be returned instead
// 2. Pass a `qs` option for query string support
function buildUrl(uri, options) {
var qsStringify = options.qsStringify || querystringify_1.stringify;
if (options.qs) {
var stringified = qsStringify(options.qs, options.qsStringifyOptions);
if (stringified[0] !== '?') {
stringified = '?' + stringified;
}
uri += stringified;
}
return uri;
}
function request(uri, options, cb) {
var params = {};
// This handles the `xhr(options, cb)` syntax
if ((typeof uri === 'undefined' ? 'undefined' : _typeof(uri)) === 'object') {
params = uri;
} else if (typeof uri === 'string' && (typeof options === 'undefined' ? 'undefined' : _typeof(options)) === 'object') {
// This handles the `xhr(uri, options, cb)` syntax
params = options;
params.uri = uri;
} else {
// This handles the `xhr(uri, cb)` syntax
params.uri = uri;
}
// This adds support for the `qs` option
var urlString = params.uri ? params.uri : params.url;
params.uri = buildUrl(urlString, params);
if (params.url) {
delete params.url;
}
var callback = void 0;
if (typeof options === 'function') {
callback = options;
} else if (typeof cb === 'function') {
callback = cb;
}
// Return the `xhr` if a callback was passed. Otherwise, a Promise is returned
if (callback) {
return xhr(params, callback);
} else {
return new Promise(function (resolve, reject) {
xhr(params, function (err, res) {
if (err) {
reject(err);
} else {
resolve(res);
}
});
});
}
}
// Also grabbed from xhr's source. This adds the convenience APIs;
// `xhr.get()`, for instance.
// Supported signatures:
//
// xhr[method](url, callback)
// xhr[method](url, options, callback)
// xhr[method](options, callback)
//
['get', 'put', 'post', 'patch', 'head', 'delete'].forEach(function (method) {
request[method === 'delete' ? 'del' : method] = function (uri, options, callback) {
var opts = void 0,
cb = void 0;
if ((typeof uri === 'undefined' ? 'undefined' : _typeof(uri)) === 'object') {
opts = uri;
cb = options;
} else if ((typeof options === 'undefined' ? 'undefined' : _typeof(options)) === 'object') {
opts = Object.assign({ uri: uri }, options);
cb = callback;
} else if (typeof uri === 'string' && (typeof options === 'undefined' ? 'undefined' : _typeof(options)) !== 'object') {
opts = { uri: uri };
cb = options;
}
opts.method = method.toUpperCase();
return request(opts, cb);
};
});
function crudRequest(crudAction, options) {
var dispatch = options.dispatch,
_options$xhrOptions = options.xhrOptions,
xhrOptions = _options$xhrOptions === undefined ? {} : _options$xhrOptions,
transformData = options.transformData,
_options$actionDefaul = options.actionDefaults,
actionDefaults = _options$actionDefaul === undefined ? {} : _options$actionDefaul,
onPending = options.onPending,
onFailed = options.onFailed,
onSucceeded = options.onSucceeded,
onAborted = options.onAborted;
var resourceName = actionDefaults.resourceName,
resourceType = actionDefaults.resourceType,
requestProperties = actionDefaults.requestProperties;
var typeToUse = resourceType || resourceName;
var crudActionOption = crudAction ? crudAction : '';
// Catch common configuration problems in dev
{
var lowercaseCrud = crudActionOption.toLowerCase();
var isValidCrudType = lowercaseCrud === 'update' || lowercaseCrud === 'delete' || lowercaseCrud === 'read' || lowercaseCrud === 'create';
var url = xhrOptions.url,
uri = xhrOptions.uri;
if (!typeToUse) {
console.warn('A resourceType was not passed to a Redux Resource Action ' + 'creator. A resourceType must be passed so that Redux Resource ' + 'knows which resource slice to update. Refer to the Request Actions ' + 'guide for more: https://redux-resource.js.org/docs/requests/request-actions.html');
}
if (!isValidCrudType) {
console.warn('An invalid "crudAction" was passed to a Redux Resource action creator. ' + 'It must be one of: "create", "read", "update", "delete"');
}
if (!url && !uri) {
console.warn('No URL was passed to a Redux Resource action creator. You must ' + 'pass either "xhrOptions.url" or "xhrOptions.uri". For more, refer to ' + 'the Redux Resource XHR documentation: ' + 'https://redux-resource.js.org/docs/extras/redux-resource-xhr.html');
}
}
var crudType = crudActionOption.toUpperCase();
var pendingAction = _extends({}, actionDefaults, {
type: reduxResource.actionTypes[crudType + '_RESOURCES_PENDING'],
requestProperties: requestProperties
});
if (onPending) {
onPending(pendingAction);
} else {
dispatch(pendingAction);
}
var req = request(xhrOptions, function (err, res, body) {
var statusCode = res ? res.statusCode : null;
if (req.aborted) {
var abortedAction = _extends({}, actionDefaults, {
type: reduxResource.actionTypes[crudType + '_RESOURCES_IDLE'],
requestProperties: requestProperties,
res: res
});
if (onAborted) {
onAborted(abortedAction, res);
} else {
dispatch(abortedAction);
}
} else if (err || statusCode >= 400 || statusCode === 0) {
var failedAction = _extends({}, actionDefaults, {
type: reduxResource.actionTypes[crudType + '_RESOURCES_FAILED'],
requestProperties: _extends({}, requestProperties, {
statusCode: statusCode
}),
res: res,
err: err
});
if (onFailed) {
onFailed(failedAction, err, res);
} else {
dispatch(failedAction);
}
} else {
var resources = void 0;
if (body) {
if (transformData) {
resources = transformData(body, options);
} else {
resources = body;
}
} else {
resources = actionDefaults.resources;
}
var succeededAction = _extends({}, actionDefaults, {
type: reduxResource.actionTypes[crudType + '_RESOURCES_SUCCEEDED'],
requestProperties: _extends({}, requestProperties, {
statusCode: statusCode
}),
resources: resources,
res: res
});
if (onSucceeded) {
onSucceeded(succeededAction, res, body);
} else {
dispatch(succeededAction);
}
}
});
return req;
}
exports.crudRequest = crudRequest;
exports.xhr = request;
Object.defineProperty(exports, '__esModule', { value: true });
})));