manyfest
Version:
JSON Object Manifest for Data Description and Parsing
864 lines (811 loc) • 146 kB
JavaScript
"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