@burriko/devour-client
Version:
A lightweight, framework agnostic, flexible JSON API client
218 lines (178 loc) • 6.85 kB
JavaScript
var _createClass = 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); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var _forOwn = require('lodash/forOwn');
var _isArray = require('lodash/isArray');
var _isUndefined = require('lodash/isUndefined');
var _isPlainObject = require('lodash/isPlainObject');
var _includes = require('lodash/includes');
var _find = require('lodash/find');
var _get = require('lodash/get');
var _map = require('lodash/map');
var _filter = require('lodash/filter');
var _matches = require('lodash/matches');
var _flatten = require('lodash/flatten');
var Logger = require('../../logger');
var cache = new (function () {
function _class() {
_classCallCheck(this, _class);
this._cache = [];
}
_createClass(_class, [{
key: 'set',
value: function set(type, id, deserializedData) {
this._cache.push({
type: type,
id: id,
deserialized: deserializedData
});
}
}, {
key: 'get',
value: function get(type, id) {
var match = _find(this._cache, function (r) {
return r.type === type && r.id === id;
});
return match && match.deserialized;
}
}, {
key: 'clear',
value: function clear() {
this._cache = [];
}
}]);
return _class;
}())();
function collection(items, included) {
var _this = this;
var useCache = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
return items.map(function (item) {
return resource.call(_this, item, included, useCache);
});
}
function resource(item, included) {
var _this2 = this;
var useCache = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
if (useCache) {
var cachedItem = cache.get(item.type, item.id);
if (cachedItem) return cachedItem;
}
var model = this.modelFor(this.pluralize.singular(item.type));
if (model.options.deserializer) return model.options.deserializer.call(this, item);
var deserializedModel = { id: item.id, type: item.type };
_forOwn(item.attributes, function (value, attr) {
var attrConfig = model.attributes[attr];
if (_isUndefined(attrConfig) && attr !== 'id') {
attr = attr.replace(/-([a-z])/g, function (g) {
return g[1].toUpperCase();
});
attrConfig = model.attributes[attr];
}
if (_isUndefined(attrConfig) && attr !== 'id') {
Logger.warn('Resource response for type "' + item.type + '" contains attribute "' + attr + '", but it is not present on model config and therefore not deserialized.');
} else {
deserializedModel[attr] = value;
}
});
// Important: cache before parsing relationships to avoid infinite loop
cache.set(item.type, item.id, deserializedModel);
_forOwn(item.relationships, function (value, rel) {
var relConfig = model.attributes[rel];
if (_isUndefined(relConfig)) {
rel = rel.replace(/-([a-z])/g, function (g) {
return g[1].toUpperCase();
});
relConfig = model.attributes[rel];
}
if (_isUndefined(relConfig)) {
Logger.warn('Resource response for type "' + item.type + '" contains relationship "' + rel + '", but it is not present on model config and therefore not deserialized.');
} else if (!isRelationship(relConfig)) {
Logger.warn('Resource response for type "' + item.type + '" contains relationship "' + rel + '", but it is present on model config as a plain attribute.');
} else {
deserializedModel[rel] = attachRelationsFor.call(_this2, model, relConfig, item, included, rel);
}
});
var params = ['meta', 'links'];
params.forEach(function (param) {
if (item[param]) {
deserializedModel[param] = item[param];
}
});
cache.clear();
return deserializedModel;
}
function attachRelationsFor(model, attribute, item, included, key) {
var relation = null;
if (attribute.jsonApi === 'hasOne') {
relation = attachHasOneFor.call(this, model, attribute, item, included, key);
}
if (attribute.jsonApi === 'hasMany') {
relation = attachHasManyFor.call(this, model, attribute, item, included, key);
}
return relation;
}
function attachHasOneFor(model, attribute, item, included, key) {
if (!item.relationships) {
return null;
}
var relatedItems = relatedItemsFor(model, attribute, item, included, key);
if (relatedItems && relatedItems[0]) {
return resource.call(this, relatedItems[0], included, true);
}
var relatedId = _get(item.relationships, [key, 'data', 'id'], false);
if (relatedId) {
return relatedId;
}
return null;
}
function attachHasManyFor(model, attribute, item, included, key) {
if (!item.relationships) {
return null;
}
var relatedItems = relatedItemsFor(model, attribute, item, included, key);
if (relatedItems && relatedItems.length > 0) {
return collection.call(this, relatedItems, included, true);
}
var relatedIds = _get(item.relationships, [key, 'data'], false);
if (relatedIds) {
return _map(relatedIds, function (relatedItem) {
return relatedItem.id;
});
}
return [];
}
function isRelationship(attribute) {
return _isPlainObject(attribute) && _includes(['hasOne', 'hasMany'], attribute.jsonApi);
}
/*
* == relatedItemsFor
* Returns unserialized related items.
*/
function relatedItemsFor(model, attribute, item, included, key) {
var relationMap = _get(item.relationships, [key, 'data'], false);
if (!relationMap) {
return [];
}
if (_isArray(relationMap)) {
return _flatten(_map(relationMap, function (relationMapItem) {
return _filter(included, function (includedItem) {
return isRelatedItemFor(attribute, includedItem, relationMapItem);
});
}));
} else {
return _filter(included, function (includedItem) {
return isRelatedItemFor(attribute, includedItem, relationMap);
});
}
}
function isRelatedItemFor(attribute, relatedItem, relationMapItem) {
var passesFilter = true;
if (attribute.filter) {
passesFilter = _matches(relatedItem.attributes, attribute.filter);
}
return relatedItem.id === relationMapItem.id && relatedItem.type === relationMapItem.type && passesFilter;
}
module.exports = {
resource: resource,
collection: collection
};
;