manyfest 1.0.13 → 1.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.config/configstore/update-notifier-npm.json +1 -1
- package/README.md +2 -0
- package/package.json +1 -1
- package/source/Manyfest-CleanWrapCharacters.js +28 -0
- package/source/Manyfest-ObjectAddress-CheckAddressExists.js +217 -0
- package/source/Manyfest-ObjectAddress-DeleteValue.js +452 -0
- package/source/{Manyfest-ObjectAddressResolver.js → Manyfest-ObjectAddress-GetValue.js} +27 -404
- package/source/Manyfest-ObjectAddress-SetValue.js +217 -0
- package/source/Manyfest-ObjectAddressGeneration.js +8 -8
- package/source/Manyfest-SchemaManipulation.js +3 -3
- package/source/Manyfest.js +42 -13
- package/test/Manyfest_Object_Delete_tests.js +96 -0
package/README.md
CHANGED
|
@@ -581,4 +581,6 @@ let _Schema = new libManyfest(schemaArchiveOrg);
|
|
|
581
581
|
|
|
582
582
|
console.log(`The URL for "${_Schema.getValueByHash(dataArchiveOrg,'Title')}" is: ${_Schema.getValueByHash(dataArchiveOrg,'Server')}${_Schema.getValueByHash(dataArchiveOrg,'Path')}`);
|
|
583
583
|
```
|
|
584
|
+
# Architectural TODO:
|
|
584
585
|
|
|
586
|
+
Change the complex address resolution functions to leverage a single resolver that returns both `container` and `entry`.
|
package/package.json
CHANGED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// When a boxed property is passed in, it should have quotes of some
|
|
2
|
+
// kind around it.
|
|
3
|
+
//
|
|
4
|
+
// For instance:
|
|
5
|
+
// MyValues['Name']
|
|
6
|
+
// MyValues["Age"]
|
|
7
|
+
// MyValues[`Cost`]
|
|
8
|
+
//
|
|
9
|
+
// This function removes the wrapping quotes.
|
|
10
|
+
//
|
|
11
|
+
// Please note it *DOES NOT PARSE* template literals, so backticks just
|
|
12
|
+
// end up doing the same thing as other quote types.
|
|
13
|
+
//
|
|
14
|
+
// TODO: Should template literals be processed? If so what state do they have access to? That should happen here if so.
|
|
15
|
+
// TODO: Make a simple class include library with these
|
|
16
|
+
let cleanWrapCharacters = (pCharacter, pString) =>
|
|
17
|
+
{
|
|
18
|
+
if (pString.startsWith(pCharacter) && pString.endsWith(pCharacter))
|
|
19
|
+
{
|
|
20
|
+
return pString.substring(1, pString.length - 1);
|
|
21
|
+
}
|
|
22
|
+
else
|
|
23
|
+
{
|
|
24
|
+
return pString;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
module.exports = cleanWrapCharacters;
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license MIT
|
|
3
|
+
* @author <steven@velozo.com>
|
|
4
|
+
*/
|
|
5
|
+
let libSimpleLog = require('./Manyfest-LogToConsole.js');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Object Address Resolver
|
|
9
|
+
*
|
|
10
|
+
* IMPORTANT NOTE: This code is intentionally more verbose than necessary, to
|
|
11
|
+
* be extremely clear what is going on in the recursion for
|
|
12
|
+
* each of the three address resolution functions.
|
|
13
|
+
*
|
|
14
|
+
* Although there is some opportunity to repeat ourselves a
|
|
15
|
+
* bit less in this codebase (e.g. with detection of arrays
|
|
16
|
+
* versus objects versus direct properties), it can make
|
|
17
|
+
* debugging.. challenging. The minified version of the code
|
|
18
|
+
* optimizes out almost anything repeated in here. So please
|
|
19
|
+
* be kind and rewind... meaning please keep the codebase less
|
|
20
|
+
* terse and more verbose so humans can comprehend it.
|
|
21
|
+
*
|
|
22
|
+
*
|
|
23
|
+
* @class ManyfestObjectAddressResolverCheckAddressExists
|
|
24
|
+
*/
|
|
25
|
+
class ManyfestObjectAddressResolverCheckAddressExists
|
|
26
|
+
{
|
|
27
|
+
constructor(pInfoLog, pErrorLog)
|
|
28
|
+
{
|
|
29
|
+
// Wire in logging
|
|
30
|
+
this.logInfo = (typeof(pInfoLog) == 'function') ? pInfoLog : libSimpleLog;
|
|
31
|
+
this.logError = (typeof(pErrorLog) == 'function') ? pErrorLog : libSimpleLog;
|
|
32
|
+
|
|
33
|
+
this.elucidatorSolver = false;
|
|
34
|
+
this.elucidatorSolverState = {};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Check if an address exists.
|
|
38
|
+
//
|
|
39
|
+
// This is necessary because the getValueAtAddress function is ambiguous on
|
|
40
|
+
// whether the element/property is actually there or not (it returns
|
|
41
|
+
// undefined whether the property exists or not). This function checks for
|
|
42
|
+
// existance and returns true or false dependent.
|
|
43
|
+
checkAddressExists (pObject, pAddress)
|
|
44
|
+
{
|
|
45
|
+
// TODO: Should these throw an error?
|
|
46
|
+
// Make sure pObject is an object
|
|
47
|
+
if (typeof(pObject) != 'object') return false;
|
|
48
|
+
// Make sure pAddress is a string
|
|
49
|
+
if (typeof(pAddress) != 'string') return false;
|
|
50
|
+
|
|
51
|
+
// TODO: Make this work for things like SomeRootObject.Metadata["Some.People.Use.Bad.Object.Property.Names"]
|
|
52
|
+
let tmpSeparatorIndex = pAddress.indexOf('.');
|
|
53
|
+
|
|
54
|
+
// This is the terminal address string (no more dots so the RECUSION ENDS IN HERE somehow)
|
|
55
|
+
if (tmpSeparatorIndex == -1)
|
|
56
|
+
{
|
|
57
|
+
// Check if the address refers to a boxed property
|
|
58
|
+
let tmpBracketStartIndex = pAddress.indexOf('[');
|
|
59
|
+
let tmpBracketStopIndex = pAddress.indexOf(']');
|
|
60
|
+
// Boxed elements look like this:
|
|
61
|
+
// MyValues[10]
|
|
62
|
+
// MyValues['Name']
|
|
63
|
+
// MyValues["Age"]
|
|
64
|
+
// MyValues[`Cost`]
|
|
65
|
+
//
|
|
66
|
+
// When we are passed SomeObject["Name"] this code below recurses as if it were SomeObject.Name
|
|
67
|
+
// The requirements to detect a boxed element are:
|
|
68
|
+
// 1) The start bracket is after character 0
|
|
69
|
+
if ((tmpBracketStartIndex > 0)
|
|
70
|
+
// 2) The end bracket has something between them
|
|
71
|
+
&& (tmpBracketStopIndex > tmpBracketStartIndex)
|
|
72
|
+
// 3) There is data
|
|
73
|
+
&& (tmpBracketStopIndex - tmpBracketStartIndex > 1))
|
|
74
|
+
{
|
|
75
|
+
// The "Name" of the Object contained too the left of the bracket
|
|
76
|
+
let tmpBoxedPropertyName = pAddress.substring(0, tmpBracketStartIndex).trim();
|
|
77
|
+
|
|
78
|
+
// If the subproperty doesn't test as a proper Object, none of the rest of this is possible.
|
|
79
|
+
// This is a rare case where Arrays testing as Objects is useful
|
|
80
|
+
if (typeof(pObject[tmpBoxedPropertyName]) !== 'object')
|
|
81
|
+
{
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// The "Reference" to the property within it, either an array element or object property
|
|
86
|
+
let tmpBoxedPropertyReference = pAddress.substring(tmpBracketStartIndex+1, tmpBracketStopIndex).trim();
|
|
87
|
+
// Attempt to parse the reference as a number, which will be used as an array element
|
|
88
|
+
let tmpBoxedPropertyNumber = parseInt(tmpBoxedPropertyReference, 10);
|
|
89
|
+
|
|
90
|
+
// Guard: If the referrant is a number and the boxed property is not an array, or vice versa, return undefined.
|
|
91
|
+
// This seems confusing to me at first read, so explaination:
|
|
92
|
+
// Is the Boxed Object an Array? TRUE
|
|
93
|
+
// And is the Reference inside the boxed Object not a number? TRUE
|
|
94
|
+
// --> So when these are in agreement, it's an impossible access state
|
|
95
|
+
if (Array.isArray(pObject[tmpBoxedPropertyName]) == isNaN(tmpBoxedPropertyNumber))
|
|
96
|
+
{
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// 4) If the middle part is *only* a number (no single, double or backtick quotes) it is an array element,
|
|
101
|
+
// otherwise we will try to treat it as a dynamic object property.
|
|
102
|
+
if (isNaN(tmpBoxedPropertyNumber))
|
|
103
|
+
{
|
|
104
|
+
// This isn't a number ... let's treat it as a dynamic object property.
|
|
105
|
+
// We would expect the property to be wrapped in some kind of quotes so strip them
|
|
106
|
+
tmpBoxedPropertyReference = this.cleanWrapCharacters('"', tmpBoxedPropertyReference);
|
|
107
|
+
tmpBoxedPropertyReference = this.cleanWrapCharacters('`', tmpBoxedPropertyReference);
|
|
108
|
+
tmpBoxedPropertyReference = this.cleanWrapCharacters("'", tmpBoxedPropertyReference);
|
|
109
|
+
|
|
110
|
+
// Check if the property exists.
|
|
111
|
+
return pObject[tmpBoxedPropertyName].hasOwnProperty(tmpBoxedPropertyReference);
|
|
112
|
+
}
|
|
113
|
+
else
|
|
114
|
+
{
|
|
115
|
+
// Use the new in operator to see if the element is in the array
|
|
116
|
+
return (tmpBoxedPropertyNumber in pObject[tmpBoxedPropertyName]);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
else
|
|
120
|
+
{
|
|
121
|
+
// Check if the property exists
|
|
122
|
+
return pObject.hasOwnProperty(pAddress);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
else
|
|
126
|
+
{
|
|
127
|
+
let tmpSubObjectName = pAddress.substring(0, tmpSeparatorIndex);
|
|
128
|
+
let tmpNewAddress = pAddress.substring(tmpSeparatorIndex+1);
|
|
129
|
+
|
|
130
|
+
// Test if the tmpNewAddress is an array or object
|
|
131
|
+
// Check if it's a boxed property
|
|
132
|
+
let tmpBracketStartIndex = tmpSubObjectName.indexOf('[');
|
|
133
|
+
let tmpBracketStopIndex = tmpSubObjectName.indexOf(']');
|
|
134
|
+
// Boxed elements look like this:
|
|
135
|
+
// MyValues[42]
|
|
136
|
+
// MyValues['Color']
|
|
137
|
+
// MyValues["Weight"]
|
|
138
|
+
// MyValues[`Diameter`]
|
|
139
|
+
//
|
|
140
|
+
// When we are passed SomeObject["Name"] this code below recurses as if it were SomeObject.Name
|
|
141
|
+
// The requirements to detect a boxed element are:
|
|
142
|
+
// 1) The start bracket is after character 0
|
|
143
|
+
if ((tmpBracketStartIndex > 0)
|
|
144
|
+
// 2) The end bracket has something between them
|
|
145
|
+
&& (tmpBracketStopIndex > tmpBracketStartIndex)
|
|
146
|
+
// 3) There is data
|
|
147
|
+
&& (tmpBracketStopIndex - tmpBracketStartIndex > 1))
|
|
148
|
+
{
|
|
149
|
+
let tmpBoxedPropertyName = tmpSubObjectName.substring(0, tmpBracketStartIndex).trim();
|
|
150
|
+
|
|
151
|
+
let tmpBoxedPropertyReference = tmpSubObjectName.substring(tmpBracketStartIndex+1, tmpBracketStopIndex).trim();
|
|
152
|
+
|
|
153
|
+
let tmpBoxedPropertyNumber = parseInt(tmpBoxedPropertyReference, 10);
|
|
154
|
+
|
|
155
|
+
// Guard: If the referrant is a number and the boxed property is not an array, or vice versa, return undefined.
|
|
156
|
+
// This seems confusing to me at first read, so explaination:
|
|
157
|
+
// Is the Boxed Object an Array? TRUE
|
|
158
|
+
// And is the Reference inside the boxed Object not a number? TRUE
|
|
159
|
+
// --> So when these are in agreement, it's an impossible access state
|
|
160
|
+
// This could be a failure in the recursion chain because they passed something like this in:
|
|
161
|
+
// StudentData.Sections.Algebra.Students[1].Tardy
|
|
162
|
+
// BUT
|
|
163
|
+
// StudentData.Sections.Algebra.Students is an object, so the [1].Tardy is not possible to access
|
|
164
|
+
// This could be a failure in the recursion chain because they passed something like this in:
|
|
165
|
+
// StudentData.Sections.Algebra.Students["JaneDoe"].Grade
|
|
166
|
+
// BUT
|
|
167
|
+
// StudentData.Sections.Algebra.Students is an array, so the ["JaneDoe"].Grade is not possible to access
|
|
168
|
+
// TODO: Should this be an error or something? Should we keep a log of failures like this?
|
|
169
|
+
if (Array.isArray(pObject[tmpBoxedPropertyName]) == isNaN(tmpBoxedPropertyNumber))
|
|
170
|
+
{
|
|
171
|
+
// Because this is an impossible address, the property doesn't exist
|
|
172
|
+
// TODO: Should we throw an error in this condition?
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
//This is a bracketed value
|
|
177
|
+
// 4) If the middle part is *only* a number (no single, double or backtick quotes) it is an array element,
|
|
178
|
+
// otherwise we will try to reat it as a dynamic object property.
|
|
179
|
+
if (isNaN(tmpBoxedPropertyNumber))
|
|
180
|
+
{
|
|
181
|
+
// This isn't a number ... let's treat it as a dynanmic object property.
|
|
182
|
+
tmpBoxedPropertyReference = this.cleanWrapCharacters('"', tmpBoxedPropertyReference);
|
|
183
|
+
tmpBoxedPropertyReference = this.cleanWrapCharacters('`', tmpBoxedPropertyReference);
|
|
184
|
+
tmpBoxedPropertyReference = this.cleanWrapCharacters("'", tmpBoxedPropertyReference);
|
|
185
|
+
|
|
186
|
+
// Recurse directly into the subobject
|
|
187
|
+
return this.checkAddressExists(pObject[tmpBoxedPropertyName][tmpBoxedPropertyReference], tmpNewAddress);
|
|
188
|
+
}
|
|
189
|
+
else
|
|
190
|
+
{
|
|
191
|
+
// We parsed a valid number out of the boxed property name, so recurse into the array
|
|
192
|
+
return this.checkAddressExists(pObject[tmpBoxedPropertyName][tmpBoxedPropertyNumber], tmpNewAddress);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// If there is an object property already named for the sub object, but it isn't an object
|
|
197
|
+
// then the system can't set the value in there. Error and abort!
|
|
198
|
+
if (pObject.hasOwnProperty(tmpSubObjectName) && typeof(pObject[tmpSubObjectName]) !== 'object')
|
|
199
|
+
{
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
else if (pObject.hasOwnProperty(tmpSubObjectName))
|
|
203
|
+
{
|
|
204
|
+
// If there is already a subobject pass that to the recursive thingy
|
|
205
|
+
return this.checkAddressExists(pObject[tmpSubObjectName], tmpNewAddress);
|
|
206
|
+
}
|
|
207
|
+
else
|
|
208
|
+
{
|
|
209
|
+
// Create a subobject and then pass that
|
|
210
|
+
pObject[tmpSubObjectName] = {};
|
|
211
|
+
return this.checkAddressExists(pObject[tmpSubObjectName], tmpNewAddress);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
module.exports = ManyfestObjectAddressResolverCheckAddressExists;
|