UNPKG

manyfest

Version:

JSON Object Manifest for Data Description and Parsing

864 lines (811 loc) 146 kB
"use strict"; function _callSuper(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e)); } function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } else if (call !== void 0) { throw new TypeError("Derived constructors may only return object or undefined"); } return _assertThisInitialized(self); } function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); } function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); Object.defineProperty(subClass, "prototype", { writable: false }); if (superClass) _setPrototypeOf(subClass, superClass); } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } 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, _toPropertyKey(descriptor.key), descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; } function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } (function (f) { if ((typeof exports === "undefined" ? "undefined" : _typeof(exports)) === "object" && typeof module !== "undefined") { module.exports = f(); } else if (typeof define === "function" && define.amd) { define([], f); } else { var g; if (typeof window !== "undefined") { g = window; } else if (typeof global !== "undefined") { g = global; } else if (typeof self !== "undefined") { g = self; } else { g = this; } g.Manyfest = f(); } })(function () { var define, module, exports; return function () { function r(e, n, t) { function o(i, f) { if (!n[i]) { if (!e[i]) { var c = "function" == typeof require && require; if (!f && c) return c(i, !0); if (u) return u(i, !0); var a = new Error("Cannot find module '" + i + "'"); throw a.code = "MODULE_NOT_FOUND", a; } var p = n[i] = { exports: {} }; e[i][0].call(p.exports, function (r) { var n = e[i][1][r]; return o(n || r); }, p, p.exports, r, e, n, t); } return n[i].exports; } for (var u = "function" == typeof require && require, i = 0; i < t.length; i++) o(t[i]); return o; } return r; }()({ 1: [function (require, module, exports) { /** * Fable Service Base * @author <steven@velozo.com> */ var FableServiceProviderBase = /*#__PURE__*/function () { // The constructor can be used in two ways: // 1) With a fable, options object and service hash (the options object and service hash are optional) // 2) With an object or nothing as the first parameter, where it will be treated as the options object function FableServiceProviderBase(pFable, pOptions, pServiceHash) { _classCallCheck(this, FableServiceProviderBase); // Check if a fable was passed in; connect it if so if (_typeof(pFable) === 'object' && pFable.isFable) { this.connectFable(pFable); } else { this.fable = false; } // initialize options and UUID based on whether the fable was passed in or not. if (this.fable) { this.UUID = pFable.getUUID(); this.options = _typeof(pOptions) === 'object' ? pOptions : {}; } else { // With no fable, check to see if there was an object passed into either of the first two // Parameters, and if so, treat it as the options object this.options = _typeof(pFable) === 'object' && !pFable.isFable ? pFable : _typeof(pOptions) === 'object' ? pOptions : {}; this.UUID = "CORE-SVC-".concat(Math.floor(Math.random() * (99999 - 10000) + 10000)); } // It's expected that the deriving class will set this this.serviceType = "Unknown-".concat(this.UUID); // The service hash is used to identify the specific instantiation of the service in the services map this.Hash = typeof pServiceHash === 'string' ? pServiceHash : !this.fable && typeof pOptions === 'string' ? pOptions : "".concat(this.UUID); } return _createClass(FableServiceProviderBase, [{ key: "connectFable", value: function connectFable(pFable) { if (_typeof(pFable) !== 'object' || !pFable.isFable) { var tmpErrorMessage = "Fable Service Provider Base: Cannot connect to Fable, invalid Fable object passed in. The pFable parameter was a [".concat(_typeof(pFable), "].}"); console.log(tmpErrorMessage); return new Error(tmpErrorMessage); } if (!this.fable) { this.fable = pFable; } if (!this.log) { this.log = this.fable.Logging; } if (!this.services) { this.services = this.fable.services; } if (!this.servicesMap) { this.servicesMap = this.fable.servicesMap; } return true; } }]); }(); _defineProperty(FableServiceProviderBase, "isFableService", true); module.exports = FableServiceProviderBase; // This is left here in case we want to go back to having different code/base class for "core" services module.exports.CoreServiceProviderBase = FableServiceProviderBase; }, {}], 2: [function (require, module, exports) { // When a boxed property is passed in, it should have quotes of some // kind around it. // // For instance: // MyValues['Name'] // MyValues["Age"] // MyValues[`Cost`] // // This function removes the wrapping quotes. // // Please note it *DOES NOT PARSE* template literals, so backticks just // end up doing the same thing as other quote types. // // TODO: Should template literals be processed? If so what state do they have access to? That should happen here if so. // TODO: Make a simple class include library with these var cleanWrapCharacters = function cleanWrapCharacters(pCharacter, pString) { if (pString.startsWith(pCharacter) && pString.endsWith(pCharacter)) { return pString.substring(1, pString.length - 1); } else { return pString; } }; module.exports = cleanWrapCharacters; }, {}], 3: [function (require, module, exports) { /** * @author <steven@velozo.com> */ var libSimpleLog = require('./Manyfest-LogToConsole.js'); /** * Hash Translation * * This is a very simple translation table for hashes, which allows the same schema to resolve * differently based on a loaded translation table. * * This is to prevent the requirement for mutating schemas over and over again when we want to * reuse the structure but look up data elements by different addresses. * * One side-effect of this is that a translation table can "override" the built-in hashes, since * this is always used to resolve hashes before any of the functionCallByHash(pHash, ...) perform * their lookups by hash. * * @class ManyfestHashTranslation */ var ManyfestHashTranslation = /*#__PURE__*/function () { function ManyfestHashTranslation(pInfoLog, pErrorLog) { _classCallCheck(this, ManyfestHashTranslation); // Wire in logging this.logInfo = typeof pInfoLog === 'function' ? pInfoLog : libSimpleLog; this.logError = typeof pErrorLog === 'function' ? pErrorLog : libSimpleLog; this.translationTable = {}; } return _createClass(ManyfestHashTranslation, [{ key: "translationCount", value: function translationCount() { return Object.keys(this.translationTable).length; } }, { key: "addTranslation", value: function addTranslation(pTranslation) { var _this = this; // This adds a translation in the form of: // { "SourceHash": "DestinationHash", "SecondSourceHash":"SecondDestinationHash" } if (_typeof(pTranslation) != 'object') { this.logError("Hash translation addTranslation expected a translation be type object but was passed in ".concat(_typeof(pTranslation))); return false; } var tmpTranslationSources = Object.keys(pTranslation); tmpTranslationSources.forEach(function (pTranslationSource) { if (typeof pTranslation[pTranslationSource] != 'string') { _this.logError("Hash translation addTranslation expected a translation destination hash for [".concat(pTranslationSource, "] to be a string but the referrant was a ").concat(_typeof(pTranslation[pTranslationSource]))); } else { _this.translationTable[pTranslationSource] = pTranslation[pTranslationSource]; } }); } }, { key: "removeTranslationHash", value: function removeTranslationHash(pTranslationHash) { if (pTranslationHash in this.translationTable) { delete this.translationTable[pTranslationHash]; } } // This removes translations. // If passed a string, just removes the single one. // If passed an object, it does all the source keys. }, { key: "removeTranslation", value: function removeTranslation(pTranslation) { var _this2 = this; if (typeof pTranslation == 'string') { this.removeTranslationHash(pTranslation); return true; } else if (_typeof(pTranslation) == 'object') { var tmpTranslationSources = Object.keys(pTranslation); tmpTranslationSources.forEach(function (pTranslationSource) { _this2.removeTranslation(pTranslationSource); }); return true; } else { this.logError("Hash translation removeTranslation expected either a string or an object but the passed-in translation was type ".concat(_typeof(pTranslation))); return false; } } }, { key: "clearTranslations", value: function clearTranslations() { this.translationTable = {}; } }, { key: "translate", value: function translate(pTranslation) { if (pTranslation in this.translationTable) { return this.translationTable[pTranslation]; } else { return pTranslation; } } }]); }(); module.exports = ManyfestHashTranslation; }, { "./Manyfest-LogToConsole.js": 4 }], 4: [function (require, module, exports) { /** * @author <steven@velozo.com> */ /** * Manyfest simple logging shim (for browser and dependency-free running) */ var logToConsole = function logToConsole(pLogLine, pLogObject) { var tmpLogLine = typeof pLogLine === 'string' ? pLogLine : ''; console.log("[Manyfest] ".concat(tmpLogLine)); if (pLogObject) console.log(JSON.stringify(pLogObject)); }; module.exports = logToConsole; }, {}], 5: [function (require, module, exports) { /** * @author <steven@velozo.com> */ var libSimpleLog = require('./Manyfest-LogToConsole.js'); // This is for resolving functions mid-address var libGetObjectValue = require('./Manyfest-ObjectAddress-GetValue.js'); // TODO: Just until this is a fable service. var _MockFable = { DataFormat: require('./Manyfest-ObjectAddress-Parser.js') }; /** * Object Address Resolver * * IMPORTANT NOTE: This code is intentionally more verbose than necessary, to * be extremely clear what is going on in the recursion for * each of the three address resolution functions. * * Although there is some opportunity to repeat ourselves a * bit less in this codebase (e.g. with detection of arrays * versus objects versus direct properties), it can make * debugging.. challenging. The minified version of the code * optimizes out almost anything repeated in here. So please * be kind and rewind... meaning please keep the codebase less * terse and more verbose so humans can comprehend it. * * * @class ManyfestObjectAddressResolverCheckAddressExists */ var ManyfestObjectAddressResolverCheckAddressExists = /*#__PURE__*/function () { function ManyfestObjectAddressResolverCheckAddressExists() { _classCallCheck(this, ManyfestObjectAddressResolverCheckAddressExists); this.getObjectValueClass = new libGetObjectValue(libSimpleLog, libSimpleLog); } // Check if an address exists. // // This is necessary because the getValueAtAddress function is ambiguous on // whether the element/property is actually there or not (it returns // undefined whether the property exists or not). This function checks for // existance and returns true or false dependent. return _createClass(ManyfestObjectAddressResolverCheckAddressExists, [{ key: "checkAddressExists", value: function checkAddressExists(pObject, pAddress, pRootObject) { // TODO: Should these throw an error? // Make sure pObject is an object if (_typeof(pObject) != 'object') return false; // Make sure pAddress is a string if (typeof pAddress != 'string') return false; // Set the root object to the passed-in object if it isn't set yet. This is expected to be the root object. // NOTE: This was added to support functions mid-stream var tmpRootObject = typeof pRootObject == 'undefined' ? pObject : pRootObject; // DONE: Make this work for things like SomeRootObject.Metadata["Some.People.Use.Bad.Object.Property.Names"] var tmpAddressPartBeginning = _MockFable.DataFormat.stringGetFirstSegment(pAddress); // This is the terminal address string (no more dots so the RECUSION ENDS IN HERE somehow) if (tmpAddressPartBeginning.length == pAddress.length) { // Check if the address refers to a boxed property var tmpBracketStartIndex = pAddress.indexOf('['); var tmpBracketStopIndex = pAddress.indexOf(']'); // Check if there is a function somewhere in the address... parenthesis start should only be in a function var tmpFunctionStartIndex = pAddress.indexOf('('); // NOTE THAT FUNCTIONS MUST RESOLVE FIRST // Functions look like this // MyFunction() // MyFunction(Some.Address) // MyFunction(Some.Address,Some.Other.Address) // MyFunction(Some.Address,Some.Other.Address,Some.Third.Address) // // This could be enhanced to allow purely numeric and string values to be passed to the function. For now, // To heck with that. This is a simple function call. // // The requirements to detect a function are: // 1) The start bracket is after character 0 if (tmpFunctionStartIndex > 0 // 2) The end bracket is after the start bracket && _MockFable.DataFormat.stringCountEnclosures(pAddress) > 0) { var tmpFunctionAddress = pAddress.substring(0, tmpFunctionStartIndex).trim(); if (tmpFunctionAddress in pObject && typeof pObject[tmpFunctionAddress] == 'function') { return true; } else { // The address suggests it is a function, but it is not. return false; } } // Boxed elements look like this: // MyValues[10] // MyValues['Name'] // MyValues["Age"] // MyValues[`Cost`] // // When we are passed SomeObject["Name"] this code below recurses as if it were SomeObject.Name // The requirements to detect a boxed element are: // 1) The start bracket is after character 0 else if (tmpBracketStartIndex > 0 // 2) The end bracket has something between them && tmpBracketStopIndex > tmpBracketStartIndex // 3) There is data && tmpBracketStopIndex - tmpBracketStartIndex > 1) { // The "Name" of the Object contained too the left of the bracket var tmpBoxedPropertyName = pAddress.substring(0, tmpBracketStartIndex).trim(); // If the subproperty doesn't test as a proper Object, none of the rest of this is possible. // This is a rare case where Arrays testing as Objects is useful if (_typeof(pObject[tmpBoxedPropertyName]) !== 'object') { return false; } // The "Reference" to the property within it, either an array element or object property var tmpBoxedPropertyReference = pAddress.substring(tmpBracketStartIndex + 1, tmpBracketStopIndex).trim(); // Attempt to parse the reference as a number, which will be used as an array element var tmpBoxedPropertyNumber = parseInt(tmpBoxedPropertyReference, 10); // Guard: If the referrant is a number and the boxed property is not an array, or vice versa, return undefined. // This seems confusing to me at first read, so explaination: // Is the Boxed Object an Array? TRUE // And is the Reference inside the boxed Object not a number? TRUE // --> So when these are in agreement, it's an impossible access state if (Array.isArray(pObject[tmpBoxedPropertyName]) == isNaN(tmpBoxedPropertyNumber)) { return false; } // 4) If the middle part is *only* a number (no single, double or backtick quotes) it is an array element, // otherwise we will try to treat it as a dynamic object property. if (isNaN(tmpBoxedPropertyNumber)) { // This isn't a number ... let's treat it as a dynamic object property. // We would expect the property to be wrapped in some kind of quotes so strip them tmpBoxedPropertyReference = this.cleanWrapCharacters('"', tmpBoxedPropertyReference); tmpBoxedPropertyReference = this.cleanWrapCharacters('`', tmpBoxedPropertyReference); tmpBoxedPropertyReference = this.cleanWrapCharacters("'", tmpBoxedPropertyReference); // Check if the property exists. return tmpBoxedPropertyReference in pObject[tmpBoxedPropertyName]; } else { // Use the new in operator to see if the element is in the array return tmpBoxedPropertyNumber in pObject[tmpBoxedPropertyName]; } } else { // Check if the property exists return pAddress in pObject; } } else { var tmpSubObjectName = tmpAddressPartBeginning; var tmpNewAddress = pAddress.substring(tmpAddressPartBeginning.length + 1); // Test if the tmpNewAddress is an array or object // Check if it's a boxed property var _tmpBracketStartIndex = tmpSubObjectName.indexOf('['); var _tmpBracketStopIndex = tmpSubObjectName.indexOf(']'); // Check if there is a function somewhere in the address... parenthesis start should only be in a function var _tmpFunctionStartIndex = tmpSubObjectName.indexOf('('); // NOTE THAT FUNCTIONS MUST RESOLVE FIRST // Functions look like this // MyFunction() // MyFunction(Some.Address) // MyFunction(Some.Address,Some.Other.Address) // MyFunction(Some.Address,Some.Other.Address,Some.Third.Address) // // This could be enhanced to allow purely numeric and string values to be passed to the function. For now, // To heck with that. This is a simple function call. // // The requirements to detect a function are: // 1) The start bracket is after character 0 if (_tmpFunctionStartIndex > 0 // 2) The end bracket is after the start bracket && _MockFable.DataFormat.stringCountEnclosures(tmpSubObjectName) > 0) { var _tmpFunctionAddress = tmpSubObjectName.substring(0, _tmpFunctionStartIndex).trim(); //tmpParentAddress = `${tmpParentAddress}${(tmpParentAddress.length > 0) ? '.' : ''}${tmpSubObjectName}`; if (!_typeof(pObject[_tmpFunctionAddress]) == 'function') { // The address suggests it is a function, but it is not. return false; } // Now see if the function has arguments. // Implementation notes: * ARGUMENTS MUST SHARE THE SAME ROOT OBJECT CONTEXT * var tmpFunctionArguments = _MockFable.DataFormat.stringGetSegments(_MockFable.DataFormat.stringGetEnclosureValueByIndex(tmpSubObjectName.substring(_tmpFunctionAddress.length), 0), ','); if (tmpFunctionArguments.length == 0 || tmpFunctionArguments[0] == '') { // No arguments... just call the function (bound to the scope of the object it is contained withing) if (_tmpFunctionAddress in pObject) { try { return this.checkAddressExists(pObject[_tmpFunctionAddress].apply(pObject), tmpNewAddress, tmpRootObject); } catch (pError) { // The function call failed, so the address doesn't exist libSimpleLog.log("Error calling function ".concat(_tmpFunctionAddress, " (address [").concat(pAddress, "]): ").concat(pError.message)); return false; } } else { // The function doesn't exist, so the address doesn't exist libSimpleLog.log("Function ".concat(_tmpFunctionAddress, " does not exist (address [").concat(pAddress, "])")); return false; } } else { var tmpArgumentValues = []; var _tmpRootObject = typeof pRootObject == 'undefined' ? pObject : pRootObject; // Now get the value for each argument for (var i = 0; i < tmpFunctionArguments.length; i++) { // Resolve the values for each subsequent entry // NOTE: This is where the resolves get really tricky. Recursion within recursion. Programming gom jabbar, yo. tmpArgumentValues.push(this.getObjectValueClass.getValueAtAddress(_tmpRootObject, tmpFunctionArguments[i])); } //return this.checkAddressExists(pObject[tmpFunctionAddress].apply(pObject, tmpArgumentValues), tmpNewAddress, tmpRootObject); if (_tmpFunctionAddress in pObject) { try { return this.checkAddressExists(pObject[_tmpFunctionAddress].apply(pObject, tmpArgumentValues), tmpNewAddress, _tmpRootObject); } catch (pError) { // The function call failed, so the address doesn't exist libSimpleLog.log("Error calling function ".concat(_tmpFunctionAddress, " (address [").concat(pAddress, "]): ").concat(pError.message)); return false; } } else { // The function doesn't exist, so the address doesn't exist libSimpleLog.log("Function ".concat(_tmpFunctionAddress, " does not exist (address [").concat(pAddress, "])")); return false; } } } // Boxed elements look like this: // MyValues[42] // MyValues['Color'] // MyValues["Weight"] // MyValues[`Diameter`] // // When we are passed SomeObject["Name"] this code below recurses as if it were SomeObject.Name // The requirements to detect a boxed element are: // 1) The start bracket is after character 0 else if (_tmpBracketStartIndex > 0 // 2) The end bracket has something between them && _tmpBracketStopIndex > _tmpBracketStartIndex // 3) There is data && _tmpBracketStopIndex - _tmpBracketStartIndex > 1) { var _tmpBoxedPropertyName = tmpSubObjectName.substring(0, _tmpBracketStartIndex).trim(); var _tmpBoxedPropertyReference = tmpSubObjectName.substring(_tmpBracketStartIndex + 1, _tmpBracketStopIndex).trim(); var _tmpBoxedPropertyNumber = parseInt(_tmpBoxedPropertyReference, 10); // Guard: If the referrant is a number and the boxed property is not an array, or vice versa, return undefined. // This seems confusing to me at first read, so explaination: // Is the Boxed Object an Array? TRUE // And is the Reference inside the boxed Object not a number? TRUE // --> So when these are in agreement, it's an impossible access state // This could be a failure in the recursion chain because they passed something like this in: // StudentData.Sections.Algebra.Students[1].Tardy // BUT // StudentData.Sections.Algebra.Students is an object, so the [1].Tardy is not possible to access // This could be a failure in the recursion chain because they passed something like this in: // StudentData.Sections.Algebra.Students["JaneDoe"].Grade // BUT // StudentData.Sections.Algebra.Students is an array, so the ["JaneDoe"].Grade is not possible to access // TODO: Should this be an error or something? Should we keep a log of failures like this? if (Array.isArray(pObject[_tmpBoxedPropertyName]) == isNaN(_tmpBoxedPropertyNumber)) { // Because this is an impossible address, the property doesn't exist // TODO: Should we throw an error in this condition? return false; } //This is a bracketed value // 4) If the middle part is *only* a number (no single, double or backtick quotes) it is an array element, // otherwise we will try to reat it as a dynamic object property. if (isNaN(_tmpBoxedPropertyNumber)) { // This isn't a number ... let's treat it as a dynanmic object property. _tmpBoxedPropertyReference = this.cleanWrapCharacters('"', _tmpBoxedPropertyReference); _tmpBoxedPropertyReference = this.cleanWrapCharacters('`', _tmpBoxedPropertyReference); _tmpBoxedPropertyReference = this.cleanWrapCharacters("'", _tmpBoxedPropertyReference); // Recurse directly into the subobject return this.checkAddressExists(pObject[_tmpBoxedPropertyName][_tmpBoxedPropertyReference], tmpNewAddress, tmpRootObject); } else { // We parsed a valid number out of the boxed property name, so recurse into the array return this.checkAddressExists(pObject[_tmpBoxedPropertyName][_tmpBoxedPropertyNumber], tmpNewAddress, tmpRootObject); } } // If there is an object property already named for the sub object, but it isn't an object // then the system can't set the value in there. Error and abort! if (tmpSubObjectName in pObject && _typeof(pObject[tmpSubObjectName]) !== 'object') { return false; } else if (tmpSubObjectName in pObject) { // If there is already a subobject pass that to the recursive thingy return this.checkAddressExists(pObject[tmpSubObjectName], tmpNewAddress, tmpRootObject); } else { // Create a subobject and then pass that pObject[tmpSubObjectName] = {}; return this.checkAddressExists(pObject[tmpSubObjectName], tmpNewAddress, tmpRootObject); } } } }]); }(); ; module.exports = ManyfestObjectAddressResolverCheckAddressExists; }, { "./Manyfest-LogToConsole.js": 4, "./Manyfest-ObjectAddress-GetValue.js": 7, "./Manyfest-ObjectAddress-Parser.js": 8 }], 6: [function (require, module, exports) { /** * @author <steven@velozo.com> */ var libSimpleLog = require('./Manyfest-LogToConsole.js'); var fCleanWrapCharacters = require('./Manyfest-CleanWrapCharacters.js'); var fParseConditionals = require("../source/Manyfest-ParseConditionals.js"); /** * Object Address Resolver - DeleteValue * * IMPORTANT NOTE: This code is intentionally more verbose than necessary, to * be extremely clear what is going on in the recursion for * each of the three address resolution functions. * * Although there is some opportunity to repeat ourselves a * bit less in this codebase (e.g. with detection of arrays * versus objects versus direct properties), it can make * debugging.. challenging. The minified version of the code * optimizes out almost anything repeated in here. So please * be kind and rewind... meaning please keep the codebase less * terse and more verbose so humans can comprehend it. * * TODO: Once we validate this pattern is good to go, break these out into * three separate modules. * * @class ManyfestObjectAddressResolverDeleteValue */ var ManyfestObjectAddressResolverDeleteValue = /*#__PURE__*/function () { function ManyfestObjectAddressResolverDeleteValue(pInfoLog, pErrorLog) { _classCallCheck(this, ManyfestObjectAddressResolverDeleteValue); // Wire in logging this.logInfo = typeof pInfoLog == 'function' ? pInfoLog : libSimpleLog; this.logError = typeof pErrorLog == 'function' ? pErrorLog : libSimpleLog; this.cleanWrapCharacters = fCleanWrapCharacters; } // TODO: Dry me return _createClass(ManyfestObjectAddressResolverDeleteValue, [{ key: "checkRecordFilters", value: function checkRecordFilters(pAddress, pRecord) { return fParseConditionals(this, pAddress, pRecord); } // Delete the value of an element at an address }, { key: "deleteValueAtAddress", value: function deleteValueAtAddress(pObject, pAddress, pParentAddress) { // Make sure pObject (the object we are meant to be recursing) is an object (which could be an array or object) if (_typeof(pObject) != 'object') return undefined; // Make sure pAddress (the address we are resolving) is a string if (typeof pAddress != 'string') return undefined; // Stash the parent address for later resolution var tmpParentAddress = ""; if (typeof pParentAddress == 'string') { tmpParentAddress = pParentAddress; } // TODO: Make this work for things like SomeRootObject.Metadata["Some.People.Use.Bad.Object.Property.Names"] var tmpSeparatorIndex = pAddress.indexOf('.'); // This is the terminal address string (no more dots so the RECUSION ENDS IN HERE somehow) if (tmpSeparatorIndex == -1) { // Check if the address refers to a boxed property var tmpBracketStartIndex = pAddress.indexOf('['); var tmpBracketStopIndex = pAddress.indexOf(']'); // Check for the Object Set Type marker. // Note this will not work with a bracket in the same address box set var tmpObjectTypeMarkerIndex = pAddress.indexOf('{}'); // Boxed elements look like this: // MyValues[10] // MyValues['Name'] // MyValues["Age"] // MyValues[`Cost`] // // When we are passed SomeObject["Name"] this code below recurses as if it were SomeObject.Name // The requirements to detect a boxed element are: // 1) The start bracket is after character 0 if (tmpBracketStartIndex > 0 // 2) The end bracket has something between them && tmpBracketStopIndex > tmpBracketStartIndex // 3) There is data && tmpBracketStopIndex - tmpBracketStartIndex > 1) { // The "Name" of the Object contained too the left of the bracket var tmpBoxedPropertyName = pAddress.substring(0, tmpBracketStartIndex).trim(); // If the subproperty doesn't test as a proper Object, none of the rest of this is possible. // This is a rare case where Arrays testing as Objects is useful if (_typeof(pObject[tmpBoxedPropertyName]) !== 'object') { return false; } // The "Reference" to the property within it, either an array element or object property var tmpBoxedPropertyReference = pAddress.substring(tmpBracketStartIndex + 1, tmpBracketStopIndex).trim(); // Attempt to parse the reference as a number, which will be used as an array element var tmpBoxedPropertyNumber = parseInt(tmpBoxedPropertyReference, 10); // Guard: If the referrant is a number and the boxed property is not an array, or vice versa, return undefined. // This seems confusing to me at first read, so explaination: // Is the Boxed Object an Array? TRUE // And is the Reference inside the boxed Object not a number? TRUE // --> So when these are in agreement, it's an impossible access state if (Array.isArray(pObject[tmpBoxedPropertyName]) == isNaN(tmpBoxedPropertyNumber)) { return false; } // 4) If the middle part is *only* a number (no single, double or backtick quotes) it is an array element, // otherwise we will try to treat it as a dynamic object property. if (isNaN(tmpBoxedPropertyNumber)) { // This isn't a number ... let's treat it as a dynamic object property. // We would expect the property to be wrapped in some kind of quotes so strip them tmpBoxedPropertyReference = this.cleanWrapCharacters('"', tmpBoxedPropertyReference); tmpBoxedPropertyReference = this.cleanWrapCharacters('`', tmpBoxedPropertyReference); tmpBoxedPropertyReference = this.cleanWrapCharacters("'", tmpBoxedPropertyReference); // Return the value in the property delete pObject[tmpBoxedPropertyName][tmpBoxedPropertyReference]; return true; } else { delete pObject[tmpBoxedPropertyName][tmpBoxedPropertyNumber]; return true; } } // The requirements to detect a boxed set element are: // 1) The start bracket is after character 0 else if (tmpBracketStartIndex > 0 // 2) The end bracket is after the start bracket && tmpBracketStopIndex > tmpBracketStartIndex // 3) There is nothing in the brackets && tmpBracketStopIndex - tmpBracketStartIndex == 1) { var _tmpBoxedPropertyName2 = pAddress.substring(0, tmpBracketStartIndex).trim(); if (!Array.isArray(pObject[_tmpBoxedPropertyName2])) { // We asked for a set from an array but it isnt' an array. return false; } var tmpInputArray = pObject[_tmpBoxedPropertyName2]; // Count from the end to the beginning so splice doesn't %&%#$ up the array for (var i = tmpInputArray.length - 1; i >= 0; i--) { // The filtering is complex but allows config-based metaprogramming directly from schema var tmpKeepRecord = this.checkRecordFilters(pAddress, tmpInputArray[i]); if (tmpKeepRecord) { // Delete elements end to beginning tmpInputArray.splice(i, 1); } } return true; } // The object has been flagged as an object set, so treat it as such else if (tmpObjectTypeMarkerIndex > 0) { var tmpObjectPropertyName = pAddress.substring(0, tmpObjectTypeMarkerIndex).trim(); if (_typeof(pObject[tmpObjectPropertyName]) != 'object') { // We asked for a set from an array but it isnt' an array. return false; } delete pObject[tmpObjectPropertyName]; return true; } else { // Now is the point in recursion to return the value in the address delete pObject[pAddress]; return true; } } else { var tmpSubObjectName = pAddress.substring(0, tmpSeparatorIndex); var tmpNewAddress = pAddress.substring(tmpSeparatorIndex + 1); // BOXED ELEMENTS // Test if the tmpNewAddress is an array or object // Check if it's a boxed property var _tmpBracketStartIndex2 = tmpSubObjectName.indexOf('['); var _tmpBracketStopIndex2 = tmpSubObjectName.indexOf(']'); // Boxed elements look like this: // MyValues[42] // MyValues['Color'] // MyValues["Weight"] // MyValues[`Diameter`] // // When we are passed SomeObject["Name"] this code below recurses as if it were SomeObject.Name // The requirements to detect a boxed element are: // 1) The start bracket is after character 0 if (_tmpBracketStartIndex2 > 0 // 2) The end bracket has something between them && _tmpBracketStopIndex2 > _tmpBracketStartIndex2 // 3) There is data && _tmpBracketStopIndex2 - _tmpBracketStartIndex2 > 1) { var _tmpBoxedPropertyName3 = tmpSubObjectName.substring(0, _tmpBracketStartIndex2).trim(); var _tmpBoxedPropertyReference2 = tmpSubObjectName.substring(_tmpBracketStartIndex2 + 1, _tmpBracketStopIndex2).trim(); var _tmpBoxedPropertyNumber2 = parseInt(_tmpBoxedPropertyReference2, 10); // Guard: If the referrant is a number and the boxed property is not an array, or vice versa, return undefined. // This seems confusing to me at first read, so explaination: // Is the Boxed Object an Array? TRUE // And is the Reference inside the boxed Object not a number? TRUE // --> So when these are in agreement, it's an impossible access state // This could be a failure in the recursion chain because they passed something like this in: // StudentData.Sections.Algebra.Students[1].Tardy // BUT // StudentData.Sections.Algebra.Students is an object, so the [1].Tardy is not possible to access // This could be a failure in the recursion chain because they passed something like this in: // StudentData.Sections.Algebra.Students["JaneDoe"].Grade // BUT // StudentData.Sections.Algebra.Students is an array, so the ["JaneDoe"].Grade is not possible to access // TODO: Should this be an error or something? Should we keep a log of failures like this? if (Array.isArray(pObject[_tmpBoxedPropertyName3]) == isNaN(_tmpBoxedPropertyNumber2)) { return false; } // Check if the boxed property is an object. if (_typeof(pObject[_tmpBoxedPropertyName3]) != 'object') { return false; } //This is a bracketed value // 4) If the middle part is *only* a number (no single, double or backtick quotes) it is an array element, // otherwise we will try to reat it as a dynamic object property. if (isNaN(_tmpBoxedPropertyNumber2)) { // This isn't a number ... let's treat it as a dynanmic object property. _tmpBoxedPropertyReference2 = this.cleanWrapCharacters('"', _tmpBoxedPropertyReference2); _tmpBoxedPropertyReference2 = this.cleanWrapCharacters('`', _tmpBoxedPropertyReference2); _tmpBoxedPropertyReference2 = this.cleanWrapCharacters("'", _tmpBoxedPropertyReference2); // Continue to manage the parent address for recursion tmpParentAddress = "".concat(tmpParentAddress).concat(tmpParentAddress.length > 0 ? '.' : '').concat(tmpSubObjectName); // Recurse directly into the subobject return this.deleteValueAtAddress(pObject[_tmpBoxedPropertyName3][_tmpBoxedPropertyReference2], tmpNewAddress, tmpParentAddress); } else { // Continue to manage the parent address for recursion tmpParentAddress = "".concat(tmpParentAddress).concat(tmpParentAddress.length > 0 ? '.' : '').concat(tmpSubObjectName); // We parsed a valid number out of the boxed property name, so recurse into the array return this.deleteValueAtAddress(pObject[_tmpBoxedPropertyName3][_tmpBoxedPropertyNumber2], tmpNewAddress, tmpParentAddress); } } // The requirements to detect a boxed set element are: // 1) The start bracket is after character 0 else if (_tmpBracketStartIndex2 > 0 // 2) The end bracket is after the start bracket && _tmpBracketStopIndex2 > _tmpBracketStartIndex2 // 3) There is nothing in the brackets && _tmpBracketStopIndex2 - _tmpBracketStartIndex2 == 1) { var _tmpBoxedPropertyName4 = pAddress.substring(0, _tmpBracketStartIndex2).trim(); if (!Array.isArray(pObject[_tmpBoxedPropertyName4])) { // We asked for a set from an array but it isnt' an array. return false; } // We need to enumerate the array and grab the addresses from there. var tmpArrayProperty = pObject[_tmpBoxedPropertyName4]; // Managing the parent address is a bit more complex here -- the box will be added for each element. tmpParentAddress = "".concat(tmpParentAddress).concat(tmpParentAddress.length > 0 ? '.' : '').concat(_tmpBoxedPropertyName4); // The container object is where we have the "Address":SOMEVALUE pairs var tmpContainerObject = {}; for (var _i = 0; _i < tmpArrayProperty.length; _i++) { var tmpPropertyParentAddress = "".concat(tmpParentAddress, "[").concat(_i, "]"); var tmpValue = this.deleteValueAtAddress(pObject[_tmpBoxedPropertyName4][_i], tmpNewAddress, tmpPropertyParentAddress); tmpContainerObject["".concat(tmpPropertyParentAddress, ".").concat(tmpNewAddress)] = tmpValue; } return tmpContainerObject; } // OBJECT SET // Note this will not work with a bracket in the same address box set var _tmpObjectTypeMarkerIndex = pAddress.indexOf('{}'); if (_tmpObjectTypeMarkerIndex > 0) { var _tmpObjectPropertyName = pAddress.substring(0, _tmpObjectTypeMarkerIndex).trim(); if (_typeof(pObject[_tmpObjectPropertyName]) != 'object') { // We asked for a set from an array but it isnt' an array. return false; } // We need to enumerate the Object and grab the addresses from there. var tmpObjectProperty = pObject[_tmpObjectPropertyName]; var tmpObjectPropertyKeys = Object.keys(tmpObjectProperty); // Managing the parent address is a bit more complex here -- the box will be added for each element. tmpParentAddress = "".concat(tmpParentAddress).concat(tmpParentAddress.length > 0 ? '.' : '').concat(_tmpObjectPropertyName); // The container object is where we have the "Address":SOMEVALUE pairs var _tmpContainerObject = {}; for (var _i2 = 0; _i2 < tmpObjectPropertyKeys.length; _i2++) { var _tmpPropertyParentAddress = "".concat(tmpParentAddress, ".").concat(tmpObjectPropertyKeys[_i2]); var _tmpValue = this.deleteValueAtAddress(pObject[_tmpObjectPropertyName][tmpObjectPropertyKeys[_i2]], tmpNewAddress, _tmpPropertyParentAddress); // The filtering is complex but allows config-based metaprogramming directly from schema var _tmpKeepRecord = this.checkRecordFilters(pAddress, _tmpValue); if (_tmpKeepRecord) { _tmpContainerObject["".concat(_tmpPropertyParentAddress, ".").concat(tmpNewAddress)] = _tmpValue; } } return _tmpContainerObject; } // If there is an object property alread