manyfest 1.0.3 → 1.0.5
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 +7 -1
- package/package.json +1 -1
- package/source/Manyfest-HashTranslation.js +118 -0
- package/source/Manyfest-ObjectAddressResolver.js +11 -11
- package/source/Manyfest-SchemaManipulation.js +112 -0
- package/source/Manyfest.js +46 -36
- package/test/Manyfest_Object_CheckExistence_tests.js +1 -1
- package/test/Manyfest_Object_HashTranslation_tests.js +182 -0
- package/test/Manyfest_Object_Populate_tests.js +1 -1
- package/test/Manyfest_Object_Read_tests.js +1 -1
- package/test/Manyfest_Object_SchemaManipulation_tests.js +93 -0
- package/test/Manyfest_Object_Validate_tests.js +1 -1
- package/test/Manyfest_Object_Write_tests.js +1 -1
- package/test/Manyfest_tests.js +1 -1
package/README.md
CHANGED
|
@@ -75,13 +75,15 @@ Scope | The scope of this representation; generally the clustered or parent reco
|
|
|
75
75
|
Schema | The stateful representation of an object's structural definition.
|
|
76
76
|
Element | A defined element of data in an object.
|
|
77
77
|
Address | The address where that data lies in the object.
|
|
78
|
+
Hash | A unique within this scope string-based key for this element. Used for easy access of data.
|
|
78
79
|
Descriptor | A description of an element including data such as Name, NameShort, Hash, Description, and other important properties.
|
|
79
80
|
Name | The name of the element. Meant to be the most succinct human readable name possible.
|
|
80
81
|
NameShort | A shorter name for the element. Meant to be useful enough to identify the property in log lines, tabular views, graphs and anywhere where we don't always want to see the full name.
|
|
81
82
|
Description | A description for the element. Very useful when consuming other APIs with their own terse naming standards (or no naming standards)!
|
|
82
|
-
Hash | A unique within this scope string-based key for this element. Used for easy access of data.
|
|
83
83
|
Required | Set to true if this element is required.
|
|
84
84
|
|
|
85
|
+
Okay so these are a lot of crazy words. The important two are *Address* and *Hash*. Every element in a schema must have an address. Having a hash just multiplies the usefulness of these addresses.
|
|
86
|
+
|
|
85
87
|
## A More Advanced Schema Example
|
|
86
88
|
|
|
87
89
|
Addresses are meant to be kinda magic. They describe locations in nested JSON just as well as simple objects. Further, they can allow us to manipulate and read JSON values at specific addresses.
|
|
@@ -205,6 +207,10 @@ console.log(animalManyfest.getValueByHash(favAnimal,'ComfET'));
|
|
|
205
207
|
|
|
206
208
|
For any elements that haven't defined a Hash, the Address is used. This allows code to gracefully fall back.
|
|
207
209
|
|
|
210
|
+
## Hash Translation Tables
|
|
211
|
+
|
|
212
|
+
Sometimes we want to reuse the structure of a schema, but look up values by hash using translations.
|
|
213
|
+
|
|
208
214
|
## Programmatically Defining a Schema
|
|
209
215
|
|
|
210
216
|
Sometimes we don't have schemas, and want to define object element structure on the fly. This can be done programmatically. As a refresher, here is how we loaded our simplest schema manifest above:
|
package/package.json
CHANGED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license MIT
|
|
3
|
+
* @author <steven@velozo.com>
|
|
4
|
+
*/
|
|
5
|
+
let libSimpleLog = require('./Manyfest-LogToConsole.js');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Hash Translation
|
|
9
|
+
*
|
|
10
|
+
* This is a very simple translation table for hashes, which allows the same schema to resolve
|
|
11
|
+
* differently based on a loaded translation table.
|
|
12
|
+
*
|
|
13
|
+
* This is to prevent the requirement for mutating schemas over and over again when we want to
|
|
14
|
+
* reuse the structure but look up data elements by different addresses.
|
|
15
|
+
*
|
|
16
|
+
* One side-effect of this is that a translation table can "override" the built-in hashes, since
|
|
17
|
+
* this is always used to resolve hashes before any of the functionCallByHash(pHash, ...) perform
|
|
18
|
+
* their lookups by hash.
|
|
19
|
+
*
|
|
20
|
+
* @class ManyfestHashTranslation
|
|
21
|
+
*/
|
|
22
|
+
class ManyfestHashTranslation
|
|
23
|
+
{
|
|
24
|
+
constructor(pInfoLog, pErrorLog)
|
|
25
|
+
{
|
|
26
|
+
// Wire in logging
|
|
27
|
+
this.logInfo = (typeof(pInfoLog) === 'function') ? pInfoLog : libSimpleLog;
|
|
28
|
+
this.logError = (typeof(pErrorLog) === 'function') ? pErrorLog : libSimpleLog;
|
|
29
|
+
|
|
30
|
+
this.translationTable = {};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
translationCount()
|
|
34
|
+
{
|
|
35
|
+
return Object.keys(this.translationTable).length;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
addTranslation(pTranslation)
|
|
39
|
+
{
|
|
40
|
+
// This adds a translation in the form of:
|
|
41
|
+
// { "SourceHash": "DestinationHash", "SecondSourceHash":"SecondDestinationHash" }
|
|
42
|
+
if (typeof(pTranslation) != 'object')
|
|
43
|
+
{
|
|
44
|
+
this.logError(`Hash translation addTranslation expected a translation be type object but was passed in ${typeof(pTranslation)}`);
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let tmpTranslationSources = Object.keys(pTranslation)
|
|
49
|
+
|
|
50
|
+
tmpTranslationSources.forEach(
|
|
51
|
+
(pTranslationSource) =>
|
|
52
|
+
{
|
|
53
|
+
if (typeof(pTranslation[pTranslationSource]) != 'string')
|
|
54
|
+
{
|
|
55
|
+
this.logError(`Hash translation addTranslation expected a translation destination hash for [${pTranslationSource}] to be a string but the referrant was a ${typeof(pTranslation[pTranslationSource])}`);
|
|
56
|
+
}
|
|
57
|
+
else
|
|
58
|
+
{
|
|
59
|
+
this.translationTable[pTranslationSource] = pTranslation[pTranslationSource];
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
removeTranslationHash(pTranslationHash)
|
|
65
|
+
{
|
|
66
|
+
if (this.translationTable.hasOwnProperty(pTranslationHash))
|
|
67
|
+
{
|
|
68
|
+
delete this.translationTable[pTranslationHash];
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// This removes translations.
|
|
73
|
+
// If passed a string, just removes the single one.
|
|
74
|
+
// If passed an object, it does all the source keys.
|
|
75
|
+
removeTranslation(pTranslation)
|
|
76
|
+
{
|
|
77
|
+
if (typeof(pTranslation) == 'string')
|
|
78
|
+
{
|
|
79
|
+
this.removeTranslationHash(pTranslation);
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
else if (typeof(pTranslation) == 'object')
|
|
83
|
+
{
|
|
84
|
+
let tmpTranslationSources = Object.keys(pTranslation)
|
|
85
|
+
|
|
86
|
+
tmpTranslationSources.forEach(
|
|
87
|
+
(pTranslationSource) =>
|
|
88
|
+
{
|
|
89
|
+
this.removeTranslation(pTranslationSource);
|
|
90
|
+
});
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
else
|
|
94
|
+
{
|
|
95
|
+
this.logError(`Hash translation removeTranslation expected either a string or an object but the passed-in translation was type ${typeof(pTranslation)}`);
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
clearTranslations()
|
|
101
|
+
{
|
|
102
|
+
this.translationTable = {};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
translate(pTranslation)
|
|
106
|
+
{
|
|
107
|
+
if (this.translationTable.hasOwnProperty(pTranslation))
|
|
108
|
+
{
|
|
109
|
+
return this.translationTable[pTranslation];
|
|
110
|
+
}
|
|
111
|
+
else
|
|
112
|
+
{
|
|
113
|
+
return pTranslation;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
module.exports = ManyfestHashTranslation;
|
|
@@ -27,8 +27,8 @@ class ManyfestObjectAddressResolver
|
|
|
27
27
|
constructor(pInfoLog, pErrorLog)
|
|
28
28
|
{
|
|
29
29
|
// Wire in logging
|
|
30
|
-
this.logInfo = (typeof(pInfoLog)
|
|
31
|
-
this.logError = (typeof(pErrorLog)
|
|
30
|
+
this.logInfo = (typeof(pInfoLog) == 'function') ? pInfoLog : libSimpleLog;
|
|
31
|
+
this.logError = (typeof(pErrorLog) == 'function') ? pErrorLog : libSimpleLog;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
// When a boxed property is passed in, it should have quotes of some
|
|
@@ -67,15 +67,15 @@ class ManyfestObjectAddressResolver
|
|
|
67
67
|
{
|
|
68
68
|
// TODO: Should these throw an error?
|
|
69
69
|
// Make sure pObject is an object
|
|
70
|
-
if (
|
|
70
|
+
if (typeof(pObject) != 'object') return false;
|
|
71
71
|
// Make sure pAddress is a string
|
|
72
|
-
if (
|
|
72
|
+
if (typeof(pAddress) != 'string') return false;
|
|
73
73
|
|
|
74
74
|
// TODO: Make this work for things like SomeRootObject.Metadata["Some.People.Use.Bad.Object.Property.Names"]
|
|
75
75
|
let tmpSeparatorIndex = pAddress.indexOf('.');
|
|
76
76
|
|
|
77
77
|
// This is the terminal address string (no more dots so the RECUSION ENDS IN HERE somehow)
|
|
78
|
-
if (tmpSeparatorIndex
|
|
78
|
+
if (tmpSeparatorIndex == -1)
|
|
79
79
|
{
|
|
80
80
|
// Check if the address refers to a boxed property
|
|
81
81
|
let tmpBracketStartIndex = pAddress.indexOf('[');
|
|
@@ -240,15 +240,15 @@ class ManyfestObjectAddressResolver
|
|
|
240
240
|
getValueAtAddress (pObject, pAddress)
|
|
241
241
|
{
|
|
242
242
|
// Make sure pObject is an object
|
|
243
|
-
if (
|
|
243
|
+
if (typeof(pObject) != 'object') return undefined;
|
|
244
244
|
// Make sure pAddress is a string
|
|
245
|
-
if (
|
|
245
|
+
if (typeof(pAddress) != 'string') return undefined;
|
|
246
246
|
|
|
247
247
|
// TODO: Make this work for things like SomeRootObject.Metadata["Some.People.Use.Bad.Object.Property.Names"]
|
|
248
248
|
let tmpSeparatorIndex = pAddress.indexOf('.');
|
|
249
249
|
|
|
250
250
|
// This is the terminal address string (no more dots so the RECUSION ENDS IN HERE somehow)
|
|
251
|
-
if (tmpSeparatorIndex
|
|
251
|
+
if (tmpSeparatorIndex == -1)
|
|
252
252
|
{
|
|
253
253
|
// Check if the address refers to a boxed property
|
|
254
254
|
let tmpBracketStartIndex = pAddress.indexOf('[');
|
|
@@ -410,13 +410,13 @@ class ManyfestObjectAddressResolver
|
|
|
410
410
|
setValueAtAddress (pObject, pAddress, pValue)
|
|
411
411
|
{
|
|
412
412
|
// Make sure pObject is an object
|
|
413
|
-
if (
|
|
413
|
+
if (typeof(pObject) != 'object') return false;
|
|
414
414
|
// Make sure pAddress is a string
|
|
415
|
-
if (
|
|
415
|
+
if (typeof(pAddress) != 'string') return false;
|
|
416
416
|
|
|
417
417
|
let tmpSeparatorIndex = pAddress.indexOf('.');
|
|
418
418
|
|
|
419
|
-
if (tmpSeparatorIndex
|
|
419
|
+
if (tmpSeparatorIndex == -1)
|
|
420
420
|
{
|
|
421
421
|
// Check if it's a boxed property
|
|
422
422
|
let tmpBracketStartIndex = pAddress.indexOf('[');
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license MIT
|
|
3
|
+
* @author <steven@velozo.com>
|
|
4
|
+
*/
|
|
5
|
+
let libSimpleLog = require('./Manyfest-LogToConsole.js');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Schema Manipulation Functions
|
|
9
|
+
*
|
|
10
|
+
* @class ManyfestSchemaManipulation
|
|
11
|
+
*/
|
|
12
|
+
class ManyfestSchemaManipulation
|
|
13
|
+
{
|
|
14
|
+
constructor(pInfoLog, pErrorLog)
|
|
15
|
+
{
|
|
16
|
+
// Wire in logging
|
|
17
|
+
this.logInfo = (typeof(pInfoLog) === 'function') ? pInfoLog : libSimpleLog;
|
|
18
|
+
this.logError = (typeof(pErrorLog) === 'function') ? pErrorLog : libSimpleLog;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// This translates the default address mappings to something different.
|
|
22
|
+
//
|
|
23
|
+
// For instance you can pass in manyfest schema descriptor object:
|
|
24
|
+
// {
|
|
25
|
+
// "Address.Of.a": { "Hash": "a", "Type": "Number" },
|
|
26
|
+
// "Address.Of.b": { "Hash": "b", "Type": "Number" }
|
|
27
|
+
// }
|
|
28
|
+
//
|
|
29
|
+
//
|
|
30
|
+
// And then an address mapping (basically a Hash->Address map)
|
|
31
|
+
// {
|
|
32
|
+
// "a": "New.Address.Of.a",
|
|
33
|
+
// "b": "New.Address.Of.b"
|
|
34
|
+
// }
|
|
35
|
+
//
|
|
36
|
+
// NOTE: This mutates the schema object permanently, altering the base hash.
|
|
37
|
+
// If there is a collision with an existing address, it can lead to overwrites.
|
|
38
|
+
// TODO: Discuss what should happen on collisions.
|
|
39
|
+
resolveAddressMappings(pManyfestSchemaDescriptors, pAddressMapping)
|
|
40
|
+
{
|
|
41
|
+
if (typeof(pManyfestSchemaDescriptors) != 'object')
|
|
42
|
+
{
|
|
43
|
+
this.logError(`Attempted to resolve address mapping but the descriptor was not an object.`);
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (typeof(pAddressMapping) != 'object')
|
|
48
|
+
{
|
|
49
|
+
// No mappings were passed in
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Get the arrays of both the schema definition and the hash mapping
|
|
54
|
+
let tmpManyfestAddresses = Object.keys(pManyfestSchemaDescriptors);
|
|
55
|
+
let tmpHashMapping = {};
|
|
56
|
+
tmpManyfestAddresses.forEach(
|
|
57
|
+
(pAddress) =>
|
|
58
|
+
{
|
|
59
|
+
if (pManyfestSchemaDescriptors[pAddress].hasOwnProperty('Hash'))
|
|
60
|
+
{
|
|
61
|
+
tmpHashMapping[pManyfestSchemaDescriptors[pAddress].Hash] = pAddress;
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
let tmpAddressMappingSet = Object.keys(pAddressMapping);
|
|
66
|
+
|
|
67
|
+
tmpAddressMappingSet.forEach(
|
|
68
|
+
(pInputAddress) =>
|
|
69
|
+
{
|
|
70
|
+
let tmpNewDescriptorAddress = pAddressMapping[pInputAddress];
|
|
71
|
+
let tmpOldDescriptorAddress = false;
|
|
72
|
+
let tmpDescriptor = false;
|
|
73
|
+
|
|
74
|
+
// See if there is a matching descriptor either by Address directly or Hash
|
|
75
|
+
if (pManyfestSchemaDescriptors.hasOwnProperty(pInputAddress))
|
|
76
|
+
{
|
|
77
|
+
tmpOldDescriptorAddress = pInputAddress;
|
|
78
|
+
}
|
|
79
|
+
else if (tmpHashMapping.hasOwnProperty(pInputAddress))
|
|
80
|
+
{
|
|
81
|
+
tmpOldDescriptorAddress = tmpHashMapping[pInputAddress];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// If there was a matching descriptor in the manifest, store it in the temporary descriptor
|
|
85
|
+
if (tmpOldDescriptorAddress)
|
|
86
|
+
{
|
|
87
|
+
tmpDescriptor = pManyfestSchemaDescriptors[tmpOldDescriptorAddress];
|
|
88
|
+
delete pManyfestSchemaDescriptors[tmpOldDescriptorAddress];
|
|
89
|
+
}
|
|
90
|
+
else
|
|
91
|
+
{
|
|
92
|
+
// Create a new descriptor! Map it to the input address.
|
|
93
|
+
tmpDescriptor = { Hash:pInputAddress };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Now re-add the descriptor to the manyfest schema
|
|
97
|
+
pManyfestSchemaDescriptors[tmpNewDescriptorAddress] = tmpDescriptor;
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
safeResolveAddressMappings(pManyfestSchemaDescriptors, pAddressMapping)
|
|
104
|
+
{
|
|
105
|
+
// This returns the descriptors as a new object, safely remapping without mutating the original schema Descriptors
|
|
106
|
+
let tmpManyfestSchemaDescriptors = JSON.parse(JSON.stringify(pManyfestSchemaDescriptors));
|
|
107
|
+
this.resolveAddressMappings(tmpManyfestSchemaDescriptors, pAddressMapping);
|
|
108
|
+
return tmpManyfestSchemaDescriptors;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
module.exports = ManyfestSchemaManipulation;
|
package/source/Manyfest.js
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
*/
|
|
5
5
|
let libSimpleLog = require('./Manyfest-LogToConsole.js');
|
|
6
6
|
let libObjectAddressResolver = require('./Manyfest-ObjectAddressResolver.js');
|
|
7
|
+
let libHashTranslation = require('./Manyfest-HashTranslation.js');
|
|
8
|
+
let libSchemaManipulation = require('./Manyfest-SchemaManipulation.js');
|
|
7
9
|
|
|
8
10
|
/**
|
|
9
11
|
* Manyfest object address-based descriptions and manipulations.
|
|
@@ -50,6 +52,10 @@ class Manyfest
|
|
|
50
52
|
{
|
|
51
53
|
this.loadManifest(pManifest);
|
|
52
54
|
}
|
|
55
|
+
|
|
56
|
+
this.schemaManipulations = new libSchemaManipulation(this.logInfo, this.logError);
|
|
57
|
+
|
|
58
|
+
this.hashTranslations = new libHashTranslation(this.logInfo, this.logError);
|
|
53
59
|
}
|
|
54
60
|
|
|
55
61
|
/*************************************************************************
|
|
@@ -153,14 +159,18 @@ class Manyfest
|
|
|
153
159
|
this.elementDescriptors[pAddress] = pDescriptor;
|
|
154
160
|
|
|
155
161
|
// Always add the address as a hash
|
|
156
|
-
// TODO: Check if this is a good idea or not.
|
|
157
|
-
// Collisions are bound to happen with both representations of the address/hash in here.
|
|
158
162
|
this.elementHashes[pAddress] = pAddress;
|
|
159
163
|
|
|
160
164
|
if (pDescriptor.hasOwnProperty('Hash'))
|
|
161
165
|
{
|
|
166
|
+
// TODO: Check if this is a good idea or not..
|
|
167
|
+
// Collisions are bound to happen with both representations of the address/hash in here and developers being able to create their own hashes.
|
|
162
168
|
this.elementHashes[pDescriptor.Hash] = pAddress;
|
|
163
169
|
}
|
|
170
|
+
else
|
|
171
|
+
{
|
|
172
|
+
pDescriptor.Hash = pAddress;
|
|
173
|
+
}
|
|
164
174
|
|
|
165
175
|
return true;
|
|
166
176
|
}
|
|
@@ -173,15 +183,7 @@ class Manyfest
|
|
|
173
183
|
|
|
174
184
|
getDescriptorByHash(pHash)
|
|
175
185
|
{
|
|
176
|
-
|
|
177
|
-
{
|
|
178
|
-
return this.getDescriptor(this.elementHashes[pHash]);
|
|
179
|
-
}
|
|
180
|
-
else
|
|
181
|
-
{
|
|
182
|
-
this.logError(`(${this.scope}) Error in getDescriptorByHash; the Hash ${pHash} doesn't exist in the schema.`);
|
|
183
|
-
return undefined;
|
|
184
|
-
}
|
|
186
|
+
return this.getDescriptor(this.resolveHashAddress(pHash));
|
|
185
187
|
}
|
|
186
188
|
|
|
187
189
|
getDescriptor(pAddress)
|
|
@@ -195,15 +197,7 @@ class Manyfest
|
|
|
195
197
|
// Check if an element exists by its hash
|
|
196
198
|
checkAddressExistsByHash (pObject, pHash)
|
|
197
199
|
{
|
|
198
|
-
|
|
199
|
-
{
|
|
200
|
-
return this.checkAddressExists(pObject, this.elementHashes[pHash]);
|
|
201
|
-
}
|
|
202
|
-
else
|
|
203
|
-
{
|
|
204
|
-
this.logError(`(${this.scope}) Error in checkAddressExistsByHash; the Hash ${pHash} doesn't exist in the schema.`);
|
|
205
|
-
return undefined;
|
|
206
|
-
}
|
|
200
|
+
return this.checkAddressExists(pObject,this.resolveHashAddress(pHash));
|
|
207
201
|
}
|
|
208
202
|
|
|
209
203
|
// Check if an element exists at an address
|
|
@@ -212,19 +206,43 @@ class Manyfest
|
|
|
212
206
|
return this.objectAddressResolver.checkAddressExists(pObject, pAddress);
|
|
213
207
|
}
|
|
214
208
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
getValueByHash (pObject, pHash)
|
|
209
|
+
// Turn a hash into an address, factoring in the translation table.
|
|
210
|
+
resolveHashAddress(pHash)
|
|
218
211
|
{
|
|
219
|
-
|
|
212
|
+
let tmpAddress = undefined;
|
|
213
|
+
|
|
214
|
+
let tmpInElementHashTable = this.elementHashes.hasOwnProperty(pHash);
|
|
215
|
+
let tmpInTranslationTable = this.hashTranslations.translationTable.hasOwnProperty(pHash);
|
|
216
|
+
|
|
217
|
+
// The most straightforward: the hash exists, no translations.
|
|
218
|
+
if (tmpInElementHashTable && !tmpInTranslationTable)
|
|
220
219
|
{
|
|
221
|
-
|
|
220
|
+
tmpAddress = this.elementHashes[pHash];
|
|
222
221
|
}
|
|
222
|
+
// There is a translation from one hash to another, and, the elementHashes contains the pointer end
|
|
223
|
+
else if (tmpInTranslationTable && this.elementHashes.hasOwnProperty(this.hashTranslations.translate(pHash)))
|
|
224
|
+
{
|
|
225
|
+
tmpAddress = this.elementHashes[this.hashTranslations.translate(pHash)];
|
|
226
|
+
}
|
|
227
|
+
// Use the level of indirection only in the Translation Table
|
|
228
|
+
else if (tmpInTranslationTable)
|
|
229
|
+
{
|
|
230
|
+
tmpAddress = this.hashTranslations.translate(pHash);
|
|
231
|
+
}
|
|
232
|
+
// Just treat the hash as an address.
|
|
233
|
+
// TODO: Discuss this ... it is magic but controversial
|
|
223
234
|
else
|
|
224
235
|
{
|
|
225
|
-
|
|
226
|
-
return undefined;
|
|
236
|
+
tmpAddress = pHash;
|
|
227
237
|
}
|
|
238
|
+
|
|
239
|
+
return tmpAddress;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Get the value of an element by its hash
|
|
243
|
+
getValueByHash (pObject, pHash)
|
|
244
|
+
{
|
|
245
|
+
return this.getValueAtAddress(pObject, this.resolveHashAddress(pHash));
|
|
228
246
|
}
|
|
229
247
|
|
|
230
248
|
// Get the value of an element at an address
|
|
@@ -236,15 +254,7 @@ class Manyfest
|
|
|
236
254
|
// Set the value of an element by its hash
|
|
237
255
|
setValueByHash(pObject, pHash, pValue)
|
|
238
256
|
{
|
|
239
|
-
|
|
240
|
-
{
|
|
241
|
-
return this.setValueAtAddress(pObject, this.elementHashes[pHash], pValue);
|
|
242
|
-
}
|
|
243
|
-
else
|
|
244
|
-
{
|
|
245
|
-
this.logError(`(${this.scope}) Error in setValueByHash; the Hash ${pHash} doesn't exist in the schema. Value ${pValue} will not be written!`);
|
|
246
|
-
return undefined;
|
|
247
|
-
}
|
|
257
|
+
return this.setValueAtAddress(pObject, this.resolveHashAddress(pHash), pValue);
|
|
248
258
|
}
|
|
249
259
|
|
|
250
260
|
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for Manyfest
|
|
3
|
+
*
|
|
4
|
+
* @license MIT
|
|
5
|
+
*
|
|
6
|
+
* @author Steven Velozo <steven@velozo.com>
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
var Chai = require("chai");
|
|
10
|
+
var Expect = Chai.expect;
|
|
11
|
+
|
|
12
|
+
let libManyfest = require('../source/Manyfest.js');
|
|
13
|
+
|
|
14
|
+
let _SampleDataArchiveOrgFrankenberry = require('./Data-Archive-org-Frankenberry.json');
|
|
15
|
+
|
|
16
|
+
suite
|
|
17
|
+
(
|
|
18
|
+
'Manyfest Hash Translations',
|
|
19
|
+
function()
|
|
20
|
+
{
|
|
21
|
+
setup (()=> {} );
|
|
22
|
+
|
|
23
|
+
suite
|
|
24
|
+
(
|
|
25
|
+
'Translation Operations',
|
|
26
|
+
()=>
|
|
27
|
+
{
|
|
28
|
+
test
|
|
29
|
+
(
|
|
30
|
+
'A simple hash translation.',
|
|
31
|
+
(fTestComplete)=>
|
|
32
|
+
{
|
|
33
|
+
let _Manyfest = new libManyfest({ Scope:'Archive.org', Descriptors: {'metadata.creator': {Name:'Creator', Hash:'Creator'}}});
|
|
34
|
+
// Property not schema, accessed by hash:
|
|
35
|
+
let tmpCreator = _Manyfest.getValueByHash(_SampleDataArchiveOrgFrankenberry, 'Creator');
|
|
36
|
+
Expect(tmpCreator)
|
|
37
|
+
.to.equal('General Mills');
|
|
38
|
+
// Create a translation between "Creator" and "Director"
|
|
39
|
+
_Manyfest.hashTranslations.addTranslation({"Director":"Creator"});
|
|
40
|
+
// Creator should still work
|
|
41
|
+
tmpCreator = _Manyfest.getValueByHash(_SampleDataArchiveOrgFrankenberry, 'Creator');
|
|
42
|
+
Expect(tmpCreator)
|
|
43
|
+
.to.equal('General Mills');
|
|
44
|
+
// Director should also work
|
|
45
|
+
tmpCreator = _Manyfest.getValueByHash(_SampleDataArchiveOrgFrankenberry, 'Director');
|
|
46
|
+
Expect(tmpCreator)
|
|
47
|
+
.to.equal('General Mills');
|
|
48
|
+
|
|
49
|
+
fTestComplete();
|
|
50
|
+
}
|
|
51
|
+
);
|
|
52
|
+
test
|
|
53
|
+
(
|
|
54
|
+
'Multiple translations.',
|
|
55
|
+
(fTestComplete)=>
|
|
56
|
+
{
|
|
57
|
+
let _Manyfest = new libManyfest({ Scope:'Archive.org', Descriptors: {'metadata.creator': {Name:'Creator', Hash:'Creator'}}});
|
|
58
|
+
// Property not schema, accessed by hash:
|
|
59
|
+
let tmpCreator = _Manyfest.getValueByHash(_SampleDataArchiveOrgFrankenberry, 'Creator');
|
|
60
|
+
Expect(tmpCreator).to.equal('General Mills');
|
|
61
|
+
// Create a translation between "Creator" and "Director" as well as "Author"
|
|
62
|
+
_Manyfest.hashTranslations.addTranslation({"Director":"Creator", "Author":"Creator"});
|
|
63
|
+
// Creator should still work
|
|
64
|
+
tmpCreator = _Manyfest.getValueByHash(_SampleDataArchiveOrgFrankenberry, 'Creator');
|
|
65
|
+
Expect(tmpCreator).to.equal('General Mills');
|
|
66
|
+
// Director should also work
|
|
67
|
+
Expect(_Manyfest.getValueByHash(_SampleDataArchiveOrgFrankenberry, 'Director')).to.equal('General Mills');
|
|
68
|
+
// And Author!
|
|
69
|
+
Expect(_Manyfest.getValueByHash(_SampleDataArchiveOrgFrankenberry, 'Author')).to.equal('General Mills');
|
|
70
|
+
|
|
71
|
+
fTestComplete();
|
|
72
|
+
}
|
|
73
|
+
);
|
|
74
|
+
test
|
|
75
|
+
(
|
|
76
|
+
'Remove a translation.',
|
|
77
|
+
(fTestComplete)=>
|
|
78
|
+
{
|
|
79
|
+
let _Manyfest = new libManyfest({ Scope:'Archive.org', Descriptors: {'metadata.creator': {Name:'Creator', Hash:'Creator'}}});
|
|
80
|
+
// Property not schema, accessed by hash:
|
|
81
|
+
let tmpCreator = _Manyfest.getValueByHash(_SampleDataArchiveOrgFrankenberry, 'Creator');
|
|
82
|
+
Expect(tmpCreator).to.equal('General Mills');
|
|
83
|
+
// Create a translation between "Creator" and "Director" as well as "Author"
|
|
84
|
+
_Manyfest.hashTranslations.addTranslation({"Director":"Creator", "Author":"Creator"});
|
|
85
|
+
Expect(tmpCreator).to.equal('General Mills');
|
|
86
|
+
// Director should also work
|
|
87
|
+
Expect(_Manyfest.getValueByHash(_SampleDataArchiveOrgFrankenberry, 'Director')).to.equal('General Mills');
|
|
88
|
+
// And Author!
|
|
89
|
+
Expect(_Manyfest.getValueByHash(_SampleDataArchiveOrgFrankenberry, 'Author')).to.equal('General Mills');
|
|
90
|
+
// Now remove Director
|
|
91
|
+
_Manyfest.hashTranslations.removeTranslation('Director');
|
|
92
|
+
Expect(_Manyfest.getValueByHash(_SampleDataArchiveOrgFrankenberry, 'Author')).to.equal('General Mills');
|
|
93
|
+
Expect(_Manyfest.getValueByHash(_SampleDataArchiveOrgFrankenberry, 'Director')).to.equal(undefined);
|
|
94
|
+
|
|
95
|
+
fTestComplete();
|
|
96
|
+
}
|
|
97
|
+
);
|
|
98
|
+
test
|
|
99
|
+
(
|
|
100
|
+
'Remove multiple translations.',
|
|
101
|
+
(fTestComplete)=>
|
|
102
|
+
{
|
|
103
|
+
let _Manyfest = new libManyfest({ Scope:'Archive.org', Descriptors: {'metadata.creator': {Name:'Creator', Hash:'Creator'}}});
|
|
104
|
+
// Property not schema, accessed by hash:
|
|
105
|
+
let tmpCreator = _Manyfest.getValueByHash(_SampleDataArchiveOrgFrankenberry, 'Creator');
|
|
106
|
+
Expect(tmpCreator).to.equal('General Mills');
|
|
107
|
+
// Create a translation between "Creator" and "Director" as well as "Author"
|
|
108
|
+
_Manyfest.hashTranslations.addTranslation({"Director":"Creator", "Author":"Creator", "Songwriter":"Creator"});
|
|
109
|
+
Expect(tmpCreator).to.equal('General Mills');
|
|
110
|
+
// Director should also work
|
|
111
|
+
Expect(_Manyfest.getValueByHash(_SampleDataArchiveOrgFrankenberry, 'Director')).to.equal('General Mills');
|
|
112
|
+
// And Author!
|
|
113
|
+
Expect(_Manyfest.getValueByHash(_SampleDataArchiveOrgFrankenberry, 'Author')).to.equal('General Mills');
|
|
114
|
+
// Now remove Director
|
|
115
|
+
_Manyfest.hashTranslations.removeTranslation({'Director':true,'Author':'TheseValuesDontMatter'});
|
|
116
|
+
Expect(_Manyfest.getValueByHash(_SampleDataArchiveOrgFrankenberry, 'Author')).to.equal(undefined);
|
|
117
|
+
Expect(_Manyfest.getValueByHash(_SampleDataArchiveOrgFrankenberry, 'Director')).to.equal(undefined);
|
|
118
|
+
Expect(_Manyfest.getValueByHash(_SampleDataArchiveOrgFrankenberry, 'Songwriter')).to.equal('General Mills');
|
|
119
|
+
|
|
120
|
+
fTestComplete();
|
|
121
|
+
}
|
|
122
|
+
);
|
|
123
|
+
test
|
|
124
|
+
(
|
|
125
|
+
'Remove all translations.',
|
|
126
|
+
(fTestComplete)=>
|
|
127
|
+
{
|
|
128
|
+
let _Manyfest = new libManyfest({ Scope:'Archive.org', Descriptors: {'metadata.creator': {Name:'Creator', Hash:'Creator'}}});
|
|
129
|
+
// Property not schema, accessed by hash:
|
|
130
|
+
let tmpCreator = _Manyfest.getValueByHash(_SampleDataArchiveOrgFrankenberry, 'Creator');
|
|
131
|
+
Expect(tmpCreator).to.equal('General Mills');
|
|
132
|
+
// Create a translation between "Creator" and "Director" as well as "Author"
|
|
133
|
+
_Manyfest.hashTranslations.addTranslation({"Director":"Creator", "Author":"Creator", "Songwriter":"Creator"});
|
|
134
|
+
Expect(tmpCreator).to.equal('General Mills');
|
|
135
|
+
// Director should also work
|
|
136
|
+
Expect(_Manyfest.getValueByHash(_SampleDataArchiveOrgFrankenberry, 'Director')).to.equal('General Mills');
|
|
137
|
+
// And Author!
|
|
138
|
+
Expect(_Manyfest.getValueByHash(_SampleDataArchiveOrgFrankenberry, 'Author')).to.equal('General Mills');
|
|
139
|
+
// Now remove Director
|
|
140
|
+
_Manyfest.hashTranslations.clearTranslations();
|
|
141
|
+
Expect(_Manyfest.getValueByHash(_SampleDataArchiveOrgFrankenberry, 'Author')).to.equal(undefined);
|
|
142
|
+
Expect(_Manyfest.getValueByHash(_SampleDataArchiveOrgFrankenberry, 'Director')).to.equal(undefined);
|
|
143
|
+
Expect(_Manyfest.getValueByHash(_SampleDataArchiveOrgFrankenberry, 'Songwriter')).to.equal(undefined);
|
|
144
|
+
Expect(_Manyfest.getValueByHash(_SampleDataArchiveOrgFrankenberry, 'Creator')).to.equal('General Mills');
|
|
145
|
+
|
|
146
|
+
fTestComplete();
|
|
147
|
+
}
|
|
148
|
+
);
|
|
149
|
+
test
|
|
150
|
+
(
|
|
151
|
+
'Translate to a value not in the hashes, falling back to address.',
|
|
152
|
+
(fTestComplete)=>
|
|
153
|
+
{
|
|
154
|
+
let _Manyfest = new libManyfest({ Scope:'Archive.org', Descriptors: {'metadata.creator': {Name:'Creator', Hash:'Creator'}}});
|
|
155
|
+
// Create a translation between "Creator" and "metadata.identifier", which isn't in the manifest in any way
|
|
156
|
+
_Manyfest.hashTranslations.addTranslation({"Creator":"metadata.identifier"});
|
|
157
|
+
// This address is not in the descriptor address list or the hash list
|
|
158
|
+
Expect(_Manyfest.getValueAtAddress(_SampleDataArchiveOrgFrankenberry, 'metadata.identifier')).to.equal('FrankenberryCountChoculaTevevisionCommercial1971');
|
|
159
|
+
// But now we've pointed the Creator hash to it!
|
|
160
|
+
Expect(_Manyfest.getValueByHash(_SampleDataArchiveOrgFrankenberry, 'Creator')).to.equal('FrankenberryCountChoculaTevevisionCommercial1971');
|
|
161
|
+
|
|
162
|
+
fTestComplete();
|
|
163
|
+
}
|
|
164
|
+
);
|
|
165
|
+
test
|
|
166
|
+
(
|
|
167
|
+
'Add a bogus translation.',
|
|
168
|
+
(fTestComplete)=>
|
|
169
|
+
{
|
|
170
|
+
let _Manyfest = new libManyfest({ Scope:'Archive.org', Descriptors: {'metadata.creator': {Name:'Creator', Hash:'Creator'}}});
|
|
171
|
+
|
|
172
|
+
Expect(_Manyfest.hashTranslations.addTranslation('THIS SHOULD BE AN OBJECT')).to.equal(false);
|
|
173
|
+
|
|
174
|
+
Expect(_Manyfest.hashTranslations.translationCount()).to.equal(0);
|
|
175
|
+
|
|
176
|
+
fTestComplete();
|
|
177
|
+
}
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
);
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for Manyfest
|
|
3
|
+
*
|
|
4
|
+
* @license MIT
|
|
5
|
+
*
|
|
6
|
+
* @author Steven Velozo <steven@velozo.com>
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
var Chai = require("chai");
|
|
10
|
+
var Expect = Chai.expect;
|
|
11
|
+
|
|
12
|
+
let libManyfest = require('../source/Manyfest.js');
|
|
13
|
+
|
|
14
|
+
let _SampleDataArchiveOrgFrankenberry = require('./Data-Archive-org-Frankenberry.json');
|
|
15
|
+
|
|
16
|
+
suite
|
|
17
|
+
(
|
|
18
|
+
'Manyfest Schema Manipulation',
|
|
19
|
+
function()
|
|
20
|
+
{
|
|
21
|
+
setup (()=> {} );
|
|
22
|
+
|
|
23
|
+
suite
|
|
24
|
+
(
|
|
25
|
+
'Address Mapping Resolution',
|
|
26
|
+
()=>
|
|
27
|
+
{
|
|
28
|
+
test
|
|
29
|
+
(
|
|
30
|
+
'We should be able to remap properties in place.',
|
|
31
|
+
(fTestComplete)=>
|
|
32
|
+
{
|
|
33
|
+
let tmpSchemaDescriptors = (
|
|
34
|
+
{
|
|
35
|
+
"a": { "Hash": "a", "Type": "Number" },
|
|
36
|
+
"b": { "Hash": "b", "Type": "Number" }
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
let tmpTranslationTable = (
|
|
40
|
+
{
|
|
41
|
+
"a": "CarrotCost",
|
|
42
|
+
"b": "AppleCost"
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
Expect(tmpSchemaDescriptors.a.Hash).to.equal('a');
|
|
46
|
+
|
|
47
|
+
let _Manyfest = new libManyfest();
|
|
48
|
+
// Now remap the schema (in-place)
|
|
49
|
+
_Manyfest.schemaManipulations.resolveAddressMappings(tmpSchemaDescriptors, tmpTranslationTable);
|
|
50
|
+
|
|
51
|
+
// The schema should be fundamentally altered to point these addresses to the old hashes
|
|
52
|
+
Expect(tmpSchemaDescriptors.CarrotCost.Hash).to.equal('a');
|
|
53
|
+
Expect(tmpSchemaDescriptors.AppleCost.Hash).to.equal('b');
|
|
54
|
+
|
|
55
|
+
fTestComplete();
|
|
56
|
+
}
|
|
57
|
+
);
|
|
58
|
+
test
|
|
59
|
+
(
|
|
60
|
+
'We should be able to remap properties safely.',
|
|
61
|
+
(fTestComplete)=>
|
|
62
|
+
{
|
|
63
|
+
let tmpSchemaDescriptors = (
|
|
64
|
+
{
|
|
65
|
+
"a": { "Hash": "a", "Type": "Number" },
|
|
66
|
+
"b": { "Hash": "b", "Type": "Number" }
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
let tmpTranslationTable = (
|
|
70
|
+
{
|
|
71
|
+
"a": "CarrotCost",
|
|
72
|
+
"b": "AppleCost"
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
Expect(tmpSchemaDescriptors.a.Hash).to.equal('a');
|
|
76
|
+
|
|
77
|
+
let _Manyfest = new libManyfest();
|
|
78
|
+
// Now remap the schema (in-place)
|
|
79
|
+
let tmpNewSchemaDescriptors = _Manyfest.schemaManipulations.safeResolveAddressMappings(tmpSchemaDescriptors, tmpTranslationTable);
|
|
80
|
+
|
|
81
|
+
// The schema should be safe
|
|
82
|
+
Expect(tmpSchemaDescriptors.a.Hash).to.equal('a');
|
|
83
|
+
// And a new schema should have been created with the alterations
|
|
84
|
+
Expect(tmpNewSchemaDescriptors.CarrotCost.Hash).to.equal('a');
|
|
85
|
+
Expect(tmpNewSchemaDescriptors.AppleCost.Hash).to.equal('b');
|
|
86
|
+
|
|
87
|
+
fTestComplete();
|
|
88
|
+
}
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
);
|
package/test/Manyfest_tests.js
CHANGED