@shoutem/json-api-denormalizer
Version:
Denormalizes json api data
296 lines (247 loc) • 11 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.createUniqueTargetKey = createUniqueTargetKey;
exports["default"] = exports.MAX_DEPTH_LIMIT = void 0;
var _isArray2 = _interopRequireDefault(require("lodash/isArray"));
var _isPlainObject2 = _interopRequireDefault(require("lodash/isPlainObject"));
var _reduce2 = _interopRequireDefault(require("lodash/reduce"));
var _isFunction2 = _interopRequireDefault(require("lodash/isFunction"));
var _isNumber2 = _interopRequireDefault(require("lodash/isNumber"));
var _errors = require("./errors");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
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 MAX_DEPTH_LIMIT = 50;
exports.MAX_DEPTH_LIMIT = MAX_DEPTH_LIMIT;
function createUniqueTargetKey(targetDescriptor) {
return "".concat(targetDescriptor.id, ".").concat(targetDescriptor.type);
}
/**
* JSON type denormalizer.
* Abstract (base) Denormalizers class
* Child class needs to implement denormalizeItem method.
* Provides (depends) (json) circularDependencyPreventor.
*
* Debug mode provides additional logs.
*/
var AbstractDenormalizer = /*#__PURE__*/function () {
function AbstractDenormalizer() {
_classCallCheck(this, AbstractDenormalizer);
if (this.denormalizeItem === AbstractDenormalizer.prototype.denormalizeItem) {
throw Error('Child class must implement denormalizeItem');
}
this.reduceRelationships = this.reduceRelationships.bind(this);
this.nestingDepthLimit = MAX_DEPTH_LIMIT;
this.denormalizingDescriptorKeys = new Set();
this.debugMode = false;
}
_createClass(AbstractDenormalizer, [{
key: "turnOnDebugMode",
value: function turnOnDebugMode() {
this.debugMode = true;
}
}, {
key: "turnOffDebugMode",
value: function turnOffDebugMode() {
this.debugMode = false;
}
/**
* Logs debug message if debug mode is turned on.
* @param message
*/
}, {
key: "debugLog",
value: function debugLog(message) {
if (this.debugMode) {
console.log(message);
}
}
/**
* Set allowed nesting depth limit
* @param nestingDepthLimit int
*/
}, {
key: "setNestingDepthLimit",
value: function setNestingDepthLimit(nestingDepthLimit) {
if (!(0, _isNumber2["default"])(nestingDepthLimit)) {
throw Error('Invalid nestingDepthLimit, must be int or undefined!');
}
this.nestingDepthLimit = nestingDepthLimit;
}
/**
* Watch JSON typed descriptor
* @param targetDescriptor { id, type }
*/
}, {
key: "watchDescriptor",
value: function watchDescriptor(targetDescriptor) {
var _this = this;
this.denormalizingDescriptorKeys.add(createUniqueTargetKey(targetDescriptor));
return {
/**
* Used to prevent circular relationships or too deep nesting
* @param denormalizerMethod
* @returns {AbstractDenormalizer}
*/
safeDenormalize: function safeDenormalize(denormalizerMethod) {
if (!(0, _isFunction2["default"])(denormalizerMethod)) {
throw Error('Invalid denormalizerMethod, must be function.');
}
denormalizerMethod();
return _this;
}
};
}
/**
* Unwatch resolved dependency
* @param targetDescriptor { id, type }
*/
}, {
key: "unwatchDescriptor",
value: function unwatchDescriptor(targetDescriptor) {
this.denormalizingDescriptorKeys["delete"](createUniqueTargetKey(targetDescriptor));
return this;
}
/**
* Check if already resolving targetDescriptorKey
* @param targetDescriptor { id, type }
*/
}, {
key: "checkIfCircularOrTooDeepRelationship",
value: function checkIfCircularOrTooDeepRelationship(targetDescriptor) {
var maxDepth = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : -1;
var targetDescriptorKey = createUniqueTargetKey(targetDescriptor);
if (this.denormalizingDescriptorKeys.has(targetDescriptorKey)) {
throw new _errors.CircularDenormalizationError("Circular denormalization while resolving ".concat(targetDescriptorKey, " relationship."));
}
var depth = this.denormalizingDescriptorKeys.size + 1;
var hasMaxDepth = maxDepth > -1;
var reachedDefaultDepth = !hasMaxDepth && depth > this.nestingDepthLimit;
var reachedMaxDepth = hasMaxDepth && depth > maxDepth;
if (reachedMaxDepth) {
throw new _errors.TooDeepDenormalizationError("Too deep dependency nesting for ".concat(targetDescriptorKey));
}
if (reachedDefaultDepth) {
throw new _errors.TooDeepDenormalizationError("Too deep dependency nesting for ".concat(targetDescriptorKey));
}
return this;
}
/**
* Resolve relationships data depending on data type (object or array)
* @param type
* @returns {*}
*/
}, {
key: "reduceRelationships",
value: function reduceRelationships(normalizedRelationships, maxDepth) {
var _this2 = this;
return (0, _reduce2["default"])(normalizedRelationships, function (relationships, relationship, type) {
return _objectSpread({}, relationships, _defineProperty({}, type, _this2.denormalizeRelationship(relationship, maxDepth)));
}, {});
}
}, {
key: "denormalizeArrayRelationshipsData",
value: function denormalizeArrayRelationshipsData(relationshipsData, maxDepth) {
var _this3 = this;
return relationshipsData.reduce(function (denormalizedRelationships, item) {
if (!item) {
_this3.debugLog("Invalid item descriptor, received ".concat(_typeof(item), " for relationship item."));
} else {
denormalizedRelationships.push(_this3.denormalizeItem(item, maxDepth));
}
return denormalizedRelationships;
}, []);
}
}, {
key: "denormalizeRelationship",
value: function denormalizeRelationship(relationship, maxDepth) {
var relationshipData = relationship.data;
if ((0, _isPlainObject2["default"])(relationshipData)) {
// single
return this.denormalizeItem(relationshipData, maxDepth);
} else if ((0, _isArray2["default"])(relationshipData)) {
// collection
return this.denormalizeArrayRelationshipsData(relationshipData, maxDepth);
} else if (relationshipData === null) {
// for empty to-one relationships
return null;
}
throw Error('Invalid relationships object, must contain "data" property');
}
/**
* Returns normalized item relationship names
* @param normalizedItem
* @returns {*}
*/
}, {
key: "getItemRelationships",
value: function getItemRelationships(normalizedItem) {
return normalizedItem.relationships;
}
/**
* Returns normalized item attribute names
* @param normalizedItem
* @returns {*}
*/
}, {
key: "getItemAttributes",
value: function getItemAttributes(normalizedItem) {
return normalizedItem.attributes;
}
/**
* Denormalizes normalized item relationships
* @param normalizedItem
* @returns {*}
*/
}, {
key: "denormalizeItemRelationships",
value: function denormalizeItemRelationships(normalizedItem, maxDepth) {
var normalizedRelationships = this.getItemRelationships(normalizedItem);
return this.reduceRelationships(normalizedRelationships, maxDepth);
}
/**
* Denormalizes normalized item attributes
* @param normalizedItem
* @returns {*}
*/
}, {
key: "denormalizeItemAttributes",
value: function denormalizeItemAttributes(normalizedItem) {
return this.getItemAttributes(normalizedItem);
}
/**
* Merges data from normalized item and extracted items, relationships data
* @param normalizedItem, itemData, relationshipsItems
* @returns {*}
*/
}, {
key: "mergeDenormalizedItemData",
value: function mergeDenormalizedItemData(normalizedItem, itemData, relationshipsData) {
return Object.assign({
id: normalizedItem.id,
type: normalizedItem.type
}, itemData, relationshipsData);
}
/**
* Denormalize given JSON type normalized item
* @param normalizedItem
* @returns {*}
*/
}, {
key: "denormalizeItem",
value: function denormalizeItem(normalizedItem, maxDepth) {
var itemData = this.denormalizeItemAttributes(normalizedItem);
var relationshipsData = this.denormalizeItemRelationships(normalizedItem, maxDepth);
return this.mergeDenormalizedItemData(normalizedItem, itemData, relationshipsData);
}
}]);
return AbstractDenormalizer;
}();
exports["default"] = AbstractDenormalizer;