UNPKG

@shoutem/json-api-denormalizer

Version:

Denormalizes json api data

296 lines (247 loc) 11 kB
"use strict"; 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;