devour-client
Version:
A lightweight, framework agnostic, flexible JSON API client
645 lines (557 loc) • 19.9 kB
JavaScript
"use strict";
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
var axios = require('axios');
var pluralize = require('pluralize'); // Import only what we use from lodash.
var _isUndefined = require('lodash/isUndefined');
var _isNil = require('lodash/isNil');
var _isString = require('lodash/isString');
var _isPlainObject = require('lodash/isPlainObject');
var _isArray = require('lodash/isArray');
var _defaultsDeep = require('lodash/defaultsDeep');
var _forOwn = require('lodash/forOwn');
var _clone = require('lodash/clone');
var _get = require('lodash/get');
var _set = require('lodash/set');
var _hasIn = require('lodash/hasIn');
var _last = require('lodash/last');
var _map = require('lodash/map');
var _findIndex = require('lodash/findIndex');
require('es6-promise').polyfill();
var deserialize = require('./middleware/json-api/_deserialize');
var serialize = require('./middleware/json-api/_serialize');
var Logger = require('./logger');
/*
* == JsonApiMiddleware
*
* Here we construct the middleware stack that will handle building and making
* requests, as well as serializing and deserializing our payloads. Users can
* easily construct their own middleware layers that adhere to different
* standards.
*
*/
var jsonApiHttpBasicAuthMiddleware = require('./middleware/json-api/req-http-basic-auth');
var jsonApiPostMiddleware = require('./middleware/json-api/req-post');
var jsonApiPatchMiddleware = require('./middleware/json-api/req-patch');
var jsonApiDeleteMiddleware = require('./middleware/json-api/req-delete');
var jsonApiGetMiddleware = require('./middleware/json-api/req-get');
var jsonApiHeadersMiddleware = require('./middleware/json-api/req-headers');
var railsParamsSerializer = require('./middleware/json-api/rails-params-serializer');
var bearerTokenMiddleware = require('./middleware/json-api/req-bearer');
var sendRequestMiddleware = require('./middleware/request');
var deserializeResponseMiddleware = require('./middleware/json-api/res-deserialize');
var errorsMiddleware = require('./middleware/json-api/res-errors');
var JsonApi =
/*#__PURE__*/
function () {
function JsonApi() {
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
_classCallCheck(this, JsonApi);
if (!(arguments.length === 2 && _isString(arguments[0]) && _isArray(arguments[1])) && !(arguments.length === 1 && (_isPlainObject(arguments[0]) || _isString(arguments[0])))) {
throw new Error('Invalid argument, initialize Devour with an object.');
}
var processErrors = errorsMiddleware.getMiddleware(options);
var jsonApiMiddleware = [jsonApiHttpBasicAuthMiddleware, jsonApiPostMiddleware, jsonApiPatchMiddleware, jsonApiDeleteMiddleware, jsonApiGetMiddleware, jsonApiHeadersMiddleware, bearerTokenMiddleware, railsParamsSerializer, sendRequestMiddleware, processErrors, deserializeResponseMiddleware];
var defaults = {
middleware: jsonApiMiddleware,
logger: true,
resetBuilderOnCall: true,
auth: {},
bearer: null,
trailingSlash: {
collection: false,
resource: false
}
};
var deprecatedConstructors = function deprecatedConstructors(args) {
return args.length === 2 || args.length === 1 && _isString(args[0]);
};
if (deprecatedConstructors(arguments)) {
defaults.apiUrl = arguments[0];
if (arguments.length === 2) {
defaults.middleware = arguments[1];
}
}
options = _defaultsDeep(options, defaults);
var middleware = options.middleware;
this._originalMiddleware = middleware.slice(0);
this.middleware = middleware.slice(0);
this.headers = {};
this.axios = axios;
this.auth = options.auth;
this.apiUrl = options.apiUrl;
this.bearer = options.bearer;
this.models = {};
this.deserialize = deserialize;
this.serialize = serialize;
this.builderStack = [];
this.resetBuilderOnCall = !!options.resetBuilderOnCall;
if (options.pluralize === false) {
this.pluralize = function (s) {
return s;
};
this.pluralize.singular = function (s) {
return s;
};
} else if ('pluralize' in options) {
this.pluralize = options.pluralize;
} else {
this.pluralize = pluralize;
}
this.trailingSlash = options.trailingSlash === true ? _forOwn(_clone(defaults.trailingSlash), function (v, k, o) {
_set(o, k, true);
}) : options.trailingSlash;
options.logger ? Logger.enable() : Logger.disable();
if (deprecatedConstructors(arguments)) {
Logger.warn('Constructor (apiUrl, middleware) has been deprecated, initialize Devour with an object.');
}
}
_createClass(JsonApi, [{
key: "enableLogging",
value: function enableLogging() {
var enabled = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
enabled ? Logger.enable() : Logger.disable();
}
}, {
key: "one",
value: function one(model, id) {
this.builderStack.push({
model: model,
id: id,
path: this.resourcePathFor(model, id)
});
return this;
}
}, {
key: "all",
value: function all(model) {
this.builderStack.push({
model: model,
path: this.collectionPathFor(model)
});
return this;
}
}, {
key: "relationships",
value: function relationships(relationshipName) {
var lastRequest = _last(this.builderStack);
this.builderStack.push({
path: 'relationships'
});
if (!relationshipName) return this;
var modelName = _get(lastRequest, 'model');
if (!modelName) {
throw new Error('Relationships must be called with a preceeding model.');
}
var relationship = this.relationshipFor(modelName, relationshipName);
this.builderStack.push({
path: relationshipName,
model: relationship.type
});
return this;
}
}, {
key: "resetBuilder",
value: function resetBuilder() {
this.builderStack = [];
}
}, {
key: "stackForResource",
value: function stackForResource() {
return _hasIn(_last(this.builderStack), 'id');
}
}, {
key: "addSlash",
value: function addSlash() {
return this.stackForResource() ? this.trailingSlash.resource : this.trailingSlash.collection;
}
}, {
key: "buildPath",
value: function buildPath() {
return _map(this.builderStack, 'path').join('/');
}
}, {
key: "buildUrl",
value: function buildUrl() {
var path = this.buildPath();
var slash = path !== '' && this.addSlash() ? '/' : '';
return "".concat(this.apiUrl, "/").concat(path).concat(slash);
}
}, {
key: "get",
value: function get() {
var params = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var lastRequest = _last(this.builderStack);
var req = {
method: 'GET',
url: this.urlFor(),
model: _get(lastRequest, 'model'),
data: {},
params: params
};
if (this.resetBuilderOnCall) {
this.resetBuilder();
}
return this.runMiddleware(req);
}
}, {
key: "post",
value: function post(payload) {
var params = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var meta = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
var lastRequest = _last(this.builderStack);
var req = {
method: 'POST',
url: this.urlFor(),
model: _get(lastRequest, 'model'),
data: payload,
params: params,
meta: meta
};
if (this.resetBuilderOnCall) {
this.resetBuilder();
}
return this.runMiddleware(req);
}
}, {
key: "patch",
value: function patch(payload) {
var params = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var meta = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
var lastRequest = _last(this.builderStack);
var req = {
method: 'PATCH',
url: this.urlFor(),
model: _get(lastRequest, 'model'),
data: payload,
params: params,
meta: meta
};
if (this.resetBuilderOnCall) {
this.resetBuilder();
}
return this.runMiddleware(req);
}
}, {
key: "destroy",
value: function destroy() {
var req = null;
if (arguments.length >= 2) {
// destroy (modelName, id, [payload], [meta])
var _ref = Array.prototype.slice.call(arguments),
model = _ref[0],
id = _ref[1],
data = _ref[2],
meta = _ref[3];
console.assert(model, 'No model specified');
console.assert(id, 'No ID specified');
req = {
method: 'DELETE',
url: this.urlFor({
model: model,
id: id
}),
model: model,
data: data || {},
meta: meta || {}
};
} else {
// destroy ([payload])
// TODO: find a way to pass meta
var lastRequest = _last(this.builderStack);
req = {
method: 'DELETE',
url: this.urlFor(),
model: _get(lastRequest, 'model'),
data: arguments.length === 1 ? arguments[0] : {}
};
if (this.resetBuilderOnCall) {
this.resetBuilder();
}
}
return this.runMiddleware(req);
}
}, {
key: "insertMiddlewareBefore",
value: function insertMiddlewareBefore(middlewareName, newMiddleware) {
this.insertMiddleware(middlewareName, 'before', newMiddleware);
}
}, {
key: "insertMiddlewareAfter",
value: function insertMiddlewareAfter(middlewareName, newMiddleware) {
this.insertMiddleware(middlewareName, 'after', newMiddleware);
}
}, {
key: "insertMiddleware",
value: function insertMiddleware(middlewareName, direction, newMiddleware) {
if (this.middlewareExists(newMiddleware.name)) {
Logger.error('The middleware ' + newMiddleware.name + ' already exists');
return;
}
var middleware = this.middleware.filter(function (middleware) {
return middleware.name === middlewareName;
});
if (middleware.length > 0) {
var index = this.middleware.indexOf(middleware[0]);
if (direction === 'after') {
index = index + 1;
}
this.middleware.splice(index, 0, newMiddleware);
}
}
}, {
key: "replaceMiddleware",
value: function replaceMiddleware(middlewareName, newMiddleware) {
if (!this.middlewareExists(middlewareName)) {
Logger.error('The middleware ' + middlewareName + ' does not exist');
return;
}
if (this.middlewareExists(newMiddleware.name)) {
Logger.error('The middleware ' + newMiddleware.name + ' already exists');
return;
}
var index = _findIndex(this.middleware, ['name', middlewareName]);
this.middleware[index] = newMiddleware;
}
}, {
key: "removeMiddleware",
value: function removeMiddleware(middlewareName) {
if (!this.middlewareExists(middlewareName)) {
Logger.error('The middleware ' + middlewareName + ' does not exist');
return;
}
var index = _findIndex(this.middleware, ['name', middlewareName]);
this.middleware.splice(index, 1);
}
}, {
key: "middlewareExists",
value: function middlewareExists(middlewareName) {
return this.middleware.some(function (middleware) {
return middleware.name === middlewareName;
});
}
}, {
key: "define",
value: function define(modelName, attributes) {
var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
this.models[modelName] = {
attributes: attributes,
options: options
};
}
}, {
key: "resetMiddleware",
value: function resetMiddleware() {
this.middleware = this._originalMiddleware.slice(0);
}
}, {
key: "applyRequestMiddleware",
value: function applyRequestMiddleware(promise) {
var requestMiddlewares = this.middleware.filter(function (middleware) {
return middleware.req;
});
requestMiddlewares.forEach(function (middleware) {
promise = promise.then(middleware.req);
});
return promise;
}
}, {
key: "applyResponseMiddleware",
value: function applyResponseMiddleware(promise) {
var responseMiddleware = this.middleware.filter(function (middleware) {
return middleware.res;
});
responseMiddleware.forEach(function (middleware) {
promise = promise.then(middleware.res);
});
return promise;
}
}, {
key: "applyErrorMiddleware",
value: function applyErrorMiddleware(promise) {
var errorsMiddleware = this.middleware.filter(function (middleware) {
return middleware.error;
});
errorsMiddleware.forEach(function (middleware) {
promise = promise.then(middleware.error);
});
return promise;
}
}, {
key: "runMiddleware",
value: function runMiddleware(req) {
var _this = this;
var payload = {
req: req,
jsonApi: this
};
var requestPromise = Promise.resolve(payload);
requestPromise = this.applyRequestMiddleware(requestPromise);
return requestPromise.then(function (res) {
payload.res = res;
var responsePromise = Promise.resolve(payload);
return _this.applyResponseMiddleware(responsePromise);
})["catch"](function (err) {
Logger.error(err);
var errorPromise = Promise.resolve(err);
return _this.applyErrorMiddleware(errorPromise).then(function (err) {
return Promise.reject(err);
});
});
}
}, {
key: "request",
value: function request(url) {
var method = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'GET';
var params = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
var data = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
var req = {
url: url,
method: method,
params: params,
data: data
};
return this.runMiddleware(req);
}
}, {
key: "find",
value: function find(modelName, id) {
var params = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
var req = {
method: 'GET',
url: this.urlFor({
model: modelName,
id: id
}),
model: modelName,
data: {},
params: params
};
return this.runMiddleware(req);
}
}, {
key: "findAll",
value: function findAll(modelName) {
var params = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var req = {
method: 'GET',
url: this.urlFor({
model: modelName
}),
model: modelName,
params: params,
data: {}
};
return this.runMiddleware(req);
}
}, {
key: "create",
value: function create(modelName, payload) {
var params = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
var meta = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
var req = {
method: 'POST',
url: this.urlFor({
model: modelName
}),
model: modelName,
params: params,
data: payload,
meta: meta
};
return this.runMiddleware(req);
}
}, {
key: "update",
value: function update(modelName, payload) {
var params = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
var meta = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
var req = {
method: 'PATCH',
url: this.urlFor({
model: modelName,
id: payload.id
}),
model: modelName,
data: payload,
params: params,
meta: meta
};
return this.runMiddleware(req);
}
}, {
key: "modelFor",
value: function modelFor(modelName) {
if (!this.models[modelName]) {
throw new Error("API resource definition for model \"".concat(modelName, "\" not found. Available models: ").concat(Object.keys(this.models)));
}
return this.models[modelName];
}
}, {
key: "relationshipFor",
value: function relationshipFor(modelName, relationshipName) {
var model = this.modelFor(modelName);
var relationship = model.attributes[relationshipName];
if (!relationship) {
throw new Error("API resource definition on model \"".concat(modelName, "\" for relationship \"").concat(relationshipName, "\" not found. Available attributes: ").concat(Object.keys(model.attributes)));
}
return relationship;
}
}, {
key: "collectionPathFor",
value: function collectionPathFor(modelName) {
var collectionPath = _get(this.models[modelName], 'options.collectionPath') || this.pluralize(modelName);
return "".concat(collectionPath);
}
}, {
key: "resourcePathFor",
value: function resourcePathFor(modelName, id) {
if (_isNil(id)) {
throw new Error("No ID specified; id is '".concat(id, "'"));
}
var collectionPath = this.collectionPathFor(modelName);
return "".concat(collectionPath, "/").concat(encodeURIComponent(id));
}
}, {
key: "collectionUrlFor",
value: function collectionUrlFor(modelName) {
var collectionPath = this.collectionPathFor(modelName);
var trailingSlash = this.trailingSlash.collection ? '/' : '';
return "".concat(this.apiUrl, "/").concat(collectionPath).concat(trailingSlash);
}
}, {
key: "resourceUrlFor",
value: function resourceUrlFor(modelName, id) {
var resourcePath = this.resourcePathFor(modelName, id);
var trailingSlash = this.trailingSlash.resource ? '/' : '';
return "".concat(this.apiUrl, "/").concat(resourcePath).concat(trailingSlash);
}
}, {
key: "urlFor",
value: function urlFor() {
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
if (!_isUndefined(options.model) && !_isUndefined(options.id)) {
return this.resourceUrlFor(options.model, options.id);
} else if (!_isUndefined(options.model)) {
return this.collectionUrlFor(options.model);
} else {
return this.buildUrl();
}
}
}, {
key: "pathFor",
value: function pathFor() {
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
if (!_isUndefined(options.model) && !_isUndefined(options.id)) {
return this.resourcePathFor(options.model, options.id);
} else if (!_isUndefined(options.model)) {
return this.collectionPathFor(options.model);
} else {
return this.buildPath();
}
}
}]);
return JsonApi;
}();
module.exports = JsonApi;