manyfest 1.0.43 → 1.0.46

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/dist/manyfest.js DELETED
@@ -1,2894 +0,0 @@
1
- "use strict";
2
-
3
- function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
4
- function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
5
- 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); }
6
- (function (f) {
7
- if (typeof exports === "object" && typeof module !== "undefined") {
8
- module.exports = f();
9
- } else if (typeof define === "function" && define.amd) {
10
- define([], f);
11
- } else {
12
- var g;
13
- if (typeof window !== "undefined") {
14
- g = window;
15
- } else if (typeof global !== "undefined") {
16
- g = global;
17
- } else if (typeof self !== "undefined") {
18
- g = self;
19
- } else {
20
- g = this;
21
- }
22
- g.Manyfest = f();
23
- }
24
- })(function () {
25
- var define, module, exports;
26
- return function () {
27
- function r(e, n, t) {
28
- function o(i, f) {
29
- if (!n[i]) {
30
- if (!e[i]) {
31
- var c = "function" == typeof require && require;
32
- if (!f && c) return c(i, !0);
33
- if (u) return u(i, !0);
34
- var a = new Error("Cannot find module '" + i + "'");
35
- throw a.code = "MODULE_NOT_FOUND", a;
36
- }
37
- var p = n[i] = {
38
- exports: {}
39
- };
40
- e[i][0].call(p.exports, function (r) {
41
- var n = e[i][1][r];
42
- return o(n || r);
43
- }, p, p.exports, r, e, n, t);
44
- }
45
- return n[i].exports;
46
- }
47
- for (var u = "function" == typeof require && require, i = 0; i < t.length; i++) o(t[i]);
48
- return o;
49
- }
50
- return r;
51
- }()({
52
- 1: [function (require, module, exports) {
53
- module.exports = {
54
- "name": "fable-serviceproviderbase",
55
- "version": "3.0.15",
56
- "description": "Simple base classes for fable services.",
57
- "main": "source/Fable-ServiceProviderBase.js",
58
- "scripts": {
59
- "start": "node source/Fable-ServiceProviderBase.js",
60
- "test": "npx mocha -u tdd -R spec",
61
- "tests": "npx mocha -u tdd --exit -R spec --grep",
62
- "coverage": "npx nyc --reporter=lcov --reporter=text-lcov npx mocha -- -u tdd -R spec",
63
- "build": "npx quack build"
64
- },
65
- "mocha": {
66
- "diff": true,
67
- "extension": ["js"],
68
- "package": "./package.json",
69
- "reporter": "spec",
70
- "slow": "75",
71
- "timeout": "5000",
72
- "ui": "tdd",
73
- "watch-files": ["source/**/*.js", "test/**/*.js"],
74
- "watch-ignore": ["lib/vendor"]
75
- },
76
- "repository": {
77
- "type": "git",
78
- "url": "https://github.com/stevenvelozo/fable-serviceproviderbase.git"
79
- },
80
- "keywords": ["entity", "behavior"],
81
- "author": "Steven Velozo <steven@velozo.com> (http://velozo.com/)",
82
- "license": "MIT",
83
- "bugs": {
84
- "url": "https://github.com/stevenvelozo/fable-serviceproviderbase/issues"
85
- },
86
- "homepage": "https://github.com/stevenvelozo/fable-serviceproviderbase",
87
- "devDependencies": {
88
- "fable": "^3.0.143",
89
- "quackage": "^1.0.33"
90
- }
91
- };
92
- }, {}],
93
- 2: [function (require, module, exports) {
94
- /**
95
- * Fable Service Base
96
- * @author <steven@velozo.com>
97
- */
98
-
99
- const libPackage = require('../package.json');
100
- class FableServiceProviderBase {
101
- // The constructor can be used in two ways:
102
- // 1) With a fable, options object and service hash (the options object and service hash are optional)
103
- // 2) With an object or nothing as the first parameter, where it will be treated as the options object
104
- constructor(pFable, pOptions, pServiceHash) {
105
- // Check if a fable was passed in; connect it if so
106
- if (typeof pFable === 'object' && pFable.isFable) {
107
- this.connectFable(pFable);
108
- } else {
109
- this.fable = false;
110
- }
111
-
112
- // Initialize the services map if it wasn't passed in
113
- /** @type {Object} */
114
- this._PackageFableServiceProvider = libPackage;
115
-
116
- // initialize options and UUID based on whether the fable was passed in or not.
117
- if (this.fable) {
118
- this.UUID = pFable.getUUID();
119
- this.options = typeof pOptions === 'object' ? pOptions : {};
120
- } else {
121
- // With no fable, check to see if there was an object passed into either of the first two
122
- // Parameters, and if so, treat it as the options object
123
- this.options = typeof pFable === 'object' && !pFable.isFable ? pFable : typeof pOptions === 'object' ? pOptions : {};
124
- this.UUID = "CORE-SVC-".concat(Math.floor(Math.random() * (99999 - 10000) + 10000));
125
- }
126
-
127
- // It's expected that the deriving class will set this
128
- this.serviceType = "Unknown-".concat(this.UUID);
129
-
130
- // The service hash is used to identify the specific instantiation of the service in the services map
131
- this.Hash = typeof pServiceHash === 'string' ? pServiceHash : !this.fable && typeof pOptions === 'string' ? pOptions : "".concat(this.UUID);
132
- }
133
- connectFable(pFable) {
134
- if (typeof pFable !== 'object' || !pFable.isFable) {
135
- let tmpErrorMessage = "Fable Service Provider Base: Cannot connect to Fable, invalid Fable object passed in. The pFable parameter was a [".concat(typeof pFable, "].}");
136
- console.log(tmpErrorMessage);
137
- return new Error(tmpErrorMessage);
138
- }
139
- if (!this.fable) {
140
- this.fable = pFable;
141
- }
142
- if (!this.log) {
143
- this.log = this.fable.Logging;
144
- }
145
- if (!this.services) {
146
- this.services = this.fable.services;
147
- }
148
- if (!this.servicesMap) {
149
- this.servicesMap = this.fable.servicesMap;
150
- }
151
- return true;
152
- }
153
- }
154
- _defineProperty(FableServiceProviderBase, "isFableService", true);
155
- module.exports = FableServiceProviderBase;
156
-
157
- // This is left here in case we want to go back to having different code/base class for "core" services
158
- module.exports.CoreServiceProviderBase = FableServiceProviderBase;
159
- }, {
160
- "../package.json": 1
161
- }],
162
- 3: [function (require, module, exports) {
163
- // When a boxed property is passed in, it should have quotes of some
164
- // kind around it.
165
- //
166
- // For instance:
167
- // MyValues['Name']
168
- // MyValues["Age"]
169
- // MyValues[`Cost`]
170
- //
171
- // This function removes the wrapping quotes.
172
- //
173
- // Please note it *DOES NOT PARSE* template literals, so backticks just
174
- // end up doing the same thing as other quote types.
175
- //
176
- // TODO: Should template literals be processed? If so what state do they have access to? That should happen here if so.
177
- // TODO: Make a simple class include library with these
178
- /**
179
- * @param {string} pCharacter - The character to remove from the start and end of the string
180
- * @param {string} pString - The string to clean
181
- *
182
- * @return {string} The cleaned string
183
- */
184
- const cleanWrapCharacters = (pCharacter, pString) => {
185
- if (pString.startsWith(pCharacter) && pString.endsWith(pCharacter)) {
186
- return pString.substring(1, pString.length - 1);
187
- } else {
188
- return pString;
189
- }
190
- };
191
- module.exports = cleanWrapCharacters;
192
- }, {}],
193
- 4: [function (require, module, exports) {
194
- /**
195
- * @author <steven@velozo.com>
196
- */
197
- let libSimpleLog = require('./Manyfest-LogToConsole.js');
198
-
199
- /**
200
- * Hash Translation
201
- *
202
- * This is a very simple translation table for hashes, which allows the same schema to resolve
203
- * differently based on a loaded translation table.
204
- *
205
- * This is to prevent the requirement for mutating schemas over and over again when we want to
206
- * reuse the structure but look up data elements by different addresses.
207
- *
208
- * One side-effect of this is that a translation table can "override" the built-in hashes, since
209
- * this is always used to resolve hashes before any of the functionCallByHash(pHash, ...) perform
210
- * their lookups by hash.
211
- *
212
- * @class ManyfestHashTranslation
213
- */
214
- class ManyfestHashTranslation {
215
- /**
216
- * @param {function} [pInfoLog] - (optional) A logging function for info messages
217
- * @param {function} [pErrorLog] - (optional) A logging function for error messages
218
- */
219
- constructor(pInfoLog, pErrorLog) {
220
- // Wire in logging
221
- this.logInfo = typeof pInfoLog === 'function' ? pInfoLog : libSimpleLog;
222
- this.logError = typeof pErrorLog === 'function' ? pErrorLog : libSimpleLog;
223
- this.translationTable = {};
224
- }
225
-
226
- /**
227
- * @return {number} The number of translations in the table
228
- */
229
- translationCount() {
230
- return Object.keys(this.translationTable).length;
231
- }
232
-
233
- /**
234
- * @param {object} pTranslation - An object containing source:destination hash pairs to add to the translation table
235
- */
236
- addTranslation(pTranslation) {
237
- // This adds a translation in the form of:
238
- // { "SourceHash": "DestinationHash", "SecondSourceHash":"SecondDestinationHash" }
239
- if (typeof pTranslation != 'object') {
240
- this.logError("Hash translation addTranslation expected a translation be type object but was passed in ".concat(typeof pTranslation));
241
- return false;
242
- }
243
- let tmpTranslationSources = Object.keys(pTranslation);
244
- tmpTranslationSources.forEach(pTranslationSource => {
245
- if (typeof pTranslation[pTranslationSource] != 'string') {
246
- 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]));
247
- } else {
248
- this.translationTable[pTranslationSource] = pTranslation[pTranslationSource];
249
- }
250
- });
251
- }
252
-
253
- /**
254
- * @param {string} pTranslationHash - The source hash to remove from the translation table
255
- */
256
- removeTranslationHash(pTranslationHash) {
257
- delete this.translationTable[pTranslationHash];
258
- }
259
-
260
- /**
261
- * This removes translations.
262
- * If passed a string, just removes the single one.
263
- * If passed an object, it does all the source keys.
264
- *
265
- * @param {string|object} pTranslation - Either a source hash string to remove, or an object containing source:destination hash pairs to remove
266
- *
267
- * @return {boolean} True if the removal was successful, false otherwise
268
- */
269
- removeTranslation(pTranslation) {
270
- if (typeof pTranslation == 'string') {
271
- this.removeTranslationHash(pTranslation);
272
- return true;
273
- } else if (typeof pTranslation == 'object') {
274
- let tmpTranslationSources = Object.keys(pTranslation);
275
- tmpTranslationSources.forEach(pTranslationSource => {
276
- this.removeTranslation(pTranslationSource);
277
- });
278
- return true;
279
- } else {
280
- this.logError("Hash translation removeTranslation expected either a string or an object but the passed-in translation was type ".concat(typeof pTranslation));
281
- return false;
282
- }
283
- }
284
- clearTranslations() {
285
- this.translationTable = {};
286
- }
287
-
288
- /**
289
- * @param {string} pTranslation - The source hash to translate
290
- *
291
- * @return {string} The translated hash, or the original if no translation exists
292
- */
293
- translate(pTranslation) {
294
- if (pTranslation in this.translationTable) {
295
- return this.translationTable[pTranslation];
296
- } else {
297
- return pTranslation;
298
- }
299
- }
300
- }
301
- module.exports = ManyfestHashTranslation;
302
- }, {
303
- "./Manyfest-LogToConsole.js": 5
304
- }],
305
- 5: [function (require, module, exports) {
306
- /**
307
- * @author <steven@velozo.com>
308
- */
309
-
310
- /**
311
- * Manyfest simple logging shim (for browser and dependency-free running)
312
- */
313
-
314
- const logToConsole = (pLogLine, pLogObject) => {
315
- let tmpLogLine = typeof pLogLine === 'string' ? pLogLine : '';
316
- console.log("[Manyfest] ".concat(tmpLogLine));
317
- if (pLogObject) console.log(JSON.stringify(pLogObject));
318
- };
319
- module.exports = logToConsole;
320
- }, {}],
321
- 6: [function (require, module, exports) {
322
- /**
323
- * @author <steven@velozo.com>
324
- */
325
- const libSimpleLog = require('./Manyfest-LogToConsole.js');
326
- // This is for resolving functions mid-address
327
- const libGetObjectValue = require('./Manyfest-ObjectAddress-GetValue.js');
328
- const fCleanWrapCharacters = require('./Manyfest-CleanWrapCharacters.js');
329
-
330
- // TODO: Just until this is a fable service.
331
- let _MockFable = {
332
- DataFormat: require('./Manyfest-ObjectAddress-Parser.js')
333
- };
334
-
335
- /**
336
- * Object Address Resolver
337
- *
338
- * IMPORTANT NOTE: This code is intentionally more verbose than necessary, to
339
- * be extremely clear what is going on in the recursion for
340
- * each of the three address resolution functions.
341
- *
342
- * Although there is some opportunity to repeat ourselves a
343
- * bit less in this codebase (e.g. with detection of arrays
344
- * versus objects versus direct properties), it can make
345
- * debugging.. challenging. The minified version of the code
346
- * optimizes out almost anything repeated in here. So please
347
- * be kind and rewind... meaning please keep the codebase less
348
- * terse and more verbose so humans can comprehend it.
349
- *
350
- *
351
- * @class ManyfestObjectAddressResolverCheckAddressExists
352
- */
353
- class ManyfestObjectAddressResolverCheckAddressExists {
354
- /**
355
- * @param {function} [pInfoLog] - (optional) Function to use for info logging
356
- * @param {function} [pErrorLog] - (optional) Function to use for error logging
357
- */
358
- constructor(pInfoLog, pErrorLog) {
359
- // Wire in logging
360
- this.logInfo = typeof pInfoLog == 'function' ? pInfoLog : libSimpleLog;
361
- this.logError = typeof pErrorLog == 'function' ? pErrorLog : libSimpleLog;
362
- this.getObjectValueClass = new libGetObjectValue(this.logInfo, this.logError);
363
- this.cleanWrapCharacters = fCleanWrapCharacters;
364
- }
365
-
366
- /**
367
- * Check if an address exists.
368
- *
369
- * This is necessary because the getValueAtAddress function is ambiguous on
370
- * whether the element/property is actually there or not (it returns
371
- * undefined whether the property exists or not). This function checks for
372
- * existance and returns true or false dependent.
373
- *
374
- * @param {object} pObject - The object to check within
375
- * @param {string} pAddress - The address to check for
376
- * @param {object} [pRootObject] - (optional) The root object for function resolution context
377
- *
378
- * @return {boolean} - True if the address exists, false if it does not
379
- */
380
- checkAddressExists(pObject, pAddress, pRootObject) {
381
- // TODO: Should these throw an error?
382
- // Make sure pObject is an object
383
- if (typeof pObject != 'object') return false;
384
- // Make sure pAddress is a string
385
- if (typeof pAddress != 'string') return false;
386
-
387
- // Set the root object to the passed-in object if it isn't set yet. This is expected to be the root object.
388
- // NOTE: This was added to support functions mid-stream
389
- let tmpRootObject = typeof pRootObject == 'undefined' ? pObject : pRootObject;
390
-
391
- // DONE: Make this work for things like SomeRootObject.Metadata["Some.People.Use.Bad.Object.Property.Names"]
392
- let tmpAddressPartBeginning = _MockFable.DataFormat.stringGetFirstSegment(pAddress);
393
-
394
- // This is the terminal address string (no more dots so the RECUSION ENDS IN HERE somehow)
395
- if (tmpAddressPartBeginning.length == pAddress.length) {
396
- // Check if the address refers to a boxed property
397
- let tmpBracketStartIndex = pAddress.indexOf('[');
398
- let tmpBracketStopIndex = pAddress.indexOf(']');
399
-
400
- // Check if there is a function somewhere in the address... parenthesis start should only be in a function
401
- let tmpFunctionStartIndex = pAddress.indexOf('(');
402
-
403
- // NOTE THAT FUNCTIONS MUST RESOLVE FIRST
404
- // Functions look like this
405
- // MyFunction()
406
- // MyFunction(Some.Address)
407
- // MyFunction(Some.Address,Some.Other.Address)
408
- // MyFunction(Some.Address,Some.Other.Address,Some.Third.Address)
409
- //
410
- // This could be enhanced to allow purely numeric and string values to be passed to the function. For now,
411
- // To heck with that. This is a simple function call.
412
- //
413
- // The requirements to detect a function are:
414
- // 1) The start bracket is after character 0
415
- if (tmpFunctionStartIndex > 0
416
- // 2) The end bracket is after the start bracket
417
- && _MockFable.DataFormat.stringCountEnclosures(pAddress) > 0) {
418
- let tmpFunctionAddress = pAddress.substring(0, tmpFunctionStartIndex).trim();
419
- if (tmpFunctionAddress in pObject && typeof pObject[tmpFunctionAddress] == 'function') {
420
- return true;
421
- } else {
422
- // The address suggests it is a function, but it is not.
423
- return false;
424
- }
425
- }
426
- // Boxed elements look like this:
427
- // MyValues[10]
428
- // MyValues['Name']
429
- // MyValues["Age"]
430
- // MyValues[`Cost`]
431
- //
432
- // When we are passed SomeObject["Name"] this code below recurses as if it were SomeObject.Name
433
- // The requirements to detect a boxed element are:
434
- // 1) The start bracket is after character 0
435
- else if (tmpBracketStartIndex > 0
436
- // 2) The end bracket has something between them
437
- && tmpBracketStopIndex > tmpBracketStartIndex
438
- // 3) There is data
439
- && tmpBracketStopIndex - tmpBracketStartIndex > 1) {
440
- // The "Name" of the Object contained too the left of the bracket
441
- let tmpBoxedPropertyName = pAddress.substring(0, tmpBracketStartIndex).trim();
442
-
443
- // If the subproperty doesn't test as a proper Object, none of the rest of this is possible.
444
- // This is a rare case where Arrays testing as Objects is useful
445
- if (typeof pObject[tmpBoxedPropertyName] !== 'object') {
446
- return false;
447
- }
448
-
449
- // The "Reference" to the property within it, either an array element or object property
450
- let tmpBoxedPropertyReference = pAddress.substring(tmpBracketStartIndex + 1, tmpBracketStopIndex).trim();
451
- // Attempt to parse the reference as a number, which will be used as an array element
452
- let tmpBoxedPropertyNumber = parseInt(tmpBoxedPropertyReference, 10);
453
-
454
- // Guard: If the referrant is a number and the boxed property is not an array, or vice versa, return undefined.
455
- // This seems confusing to me at first read, so explaination:
456
- // Is the Boxed Object an Array? TRUE
457
- // And is the Reference inside the boxed Object not a number? TRUE
458
- // --> So when these are in agreement, it's an impossible access state
459
- if (Array.isArray(pObject[tmpBoxedPropertyName]) == isNaN(tmpBoxedPropertyNumber)) {
460
- return false;
461
- }
462
-
463
- // 4) If the middle part is *only* a number (no single, double or backtick quotes) it is an array element,
464
- // otherwise we will try to treat it as a dynamic object property.
465
- if (isNaN(tmpBoxedPropertyNumber)) {
466
- // This isn't a number ... let's treat it as a dynamic object property.
467
- // We would expect the property to be wrapped in some kind of quotes so strip them
468
- tmpBoxedPropertyReference = this.cleanWrapCharacters('"', tmpBoxedPropertyReference);
469
- tmpBoxedPropertyReference = this.cleanWrapCharacters('`', tmpBoxedPropertyReference);
470
- tmpBoxedPropertyReference = this.cleanWrapCharacters("'", tmpBoxedPropertyReference);
471
-
472
- // Check if the property exists.
473
- return tmpBoxedPropertyReference in pObject[tmpBoxedPropertyName];
474
- } else {
475
- // Use the new in operator to see if the element is in the array
476
- return tmpBoxedPropertyNumber in pObject[tmpBoxedPropertyName];
477
- }
478
- } else {
479
- // Check if the property exists
480
- return pAddress in pObject;
481
- }
482
- } else {
483
- let tmpSubObjectName = tmpAddressPartBeginning;
484
- let tmpNewAddress = pAddress.substring(tmpAddressPartBeginning.length + 1);
485
-
486
- // Test if the tmpNewAddress is an array or object
487
- // Check if it's a boxed property
488
- let tmpBracketStartIndex = tmpSubObjectName.indexOf('[');
489
- let tmpBracketStopIndex = tmpSubObjectName.indexOf(']');
490
-
491
- // Check if there is a function somewhere in the address... parenthesis start should only be in a function
492
- let tmpFunctionStartIndex = tmpSubObjectName.indexOf('(');
493
-
494
- // NOTE THAT FUNCTIONS MUST RESOLVE FIRST
495
- // Functions look like this
496
- // MyFunction()
497
- // MyFunction(Some.Address)
498
- // MyFunction(Some.Address,Some.Other.Address)
499
- // MyFunction(Some.Address,Some.Other.Address,Some.Third.Address)
500
- //
501
- // This could be enhanced to allow purely numeric and string values to be passed to the function. For now,
502
- // To heck with that. This is a simple function call.
503
- //
504
- // The requirements to detect a function are:
505
- // 1) The start bracket is after character 0
506
- if (tmpFunctionStartIndex > 0
507
- // 2) The end bracket is after the start bracket
508
- && _MockFable.DataFormat.stringCountEnclosures(tmpSubObjectName) > 0) {
509
- let tmpFunctionAddress = tmpSubObjectName.substring(0, tmpFunctionStartIndex).trim();
510
- //tmpParentAddress = `${tmpParentAddress}${(tmpParentAddress.length > 0) ? '.' : ''}${tmpSubObjectName}`;
511
-
512
- if (typeof pObject[tmpFunctionAddress] !== 'function') {
513
- // The address suggests it is a function, but it is not.
514
- return false;
515
- }
516
-
517
- // Now see if the function has arguments.
518
- // Implementation notes: * ARGUMENTS MUST SHARE THE SAME ROOT OBJECT CONTEXT *
519
- let tmpFunctionArguments = _MockFable.DataFormat.stringGetSegments(_MockFable.DataFormat.stringGetEnclosureValueByIndex(tmpSubObjectName.substring(tmpFunctionAddress.length), 0), ',');
520
- if (tmpFunctionArguments.length == 0 || tmpFunctionArguments[0] == '') {
521
- // No arguments... just call the function (bound to the scope of the object it is contained withing)
522
- if (tmpFunctionAddress in pObject) {
523
- try {
524
- return this.checkAddressExists(pObject[tmpFunctionAddress].apply(pObject), tmpNewAddress, tmpRootObject);
525
- } catch (pError) {
526
- // The function call failed, so the address doesn't exist
527
- libSimpleLog("Error calling function ".concat(tmpFunctionAddress, " (address [").concat(pAddress, "]): ").concat(pError.message));
528
- return false;
529
- }
530
- } else {
531
- // The function doesn't exist, so the address doesn't exist
532
- libSimpleLog("Function ".concat(tmpFunctionAddress, " does not exist (address [").concat(pAddress, "])"));
533
- return false;
534
- }
535
- } else {
536
- let tmpArgumentValues = [];
537
- let tmpRootObject = typeof pRootObject == 'undefined' ? pObject : pRootObject;
538
-
539
- // Now get the value for each argument
540
- for (let i = 0; i < tmpFunctionArguments.length; i++) {
541
- // Resolve the values for each subsequent entry
542
- // NOTE: This is where the resolves get really tricky. Recursion within recursion. Programming gom jabbar, yo.
543
- tmpArgumentValues.push(this.getObjectValueClass.getValueAtAddress(tmpRootObject, tmpFunctionArguments[i]));
544
- }
545
-
546
- //return this.checkAddressExists(pObject[tmpFunctionAddress].apply(pObject, tmpArgumentValues), tmpNewAddress, tmpRootObject);
547
- if (tmpFunctionAddress in pObject) {
548
- try {
549
- return this.checkAddressExists(pObject[tmpFunctionAddress].apply(pObject, tmpArgumentValues), tmpNewAddress, tmpRootObject);
550
- } catch (pError) {
551
- // The function call failed, so the address doesn't exist
552
- libSimpleLog("Error calling function ".concat(tmpFunctionAddress, " (address [").concat(pAddress, "]): ").concat(pError.message));
553
- return false;
554
- }
555
- } else {
556
- // The function doesn't exist, so the address doesn't exist
557
- libSimpleLog("Function ".concat(tmpFunctionAddress, " does not exist (address [").concat(pAddress, "])"));
558
- return false;
559
- }
560
- }
561
- }
562
- // Boxed elements look like this:
563
- // MyValues[42]
564
- // MyValues['Color']
565
- // MyValues["Weight"]
566
- // MyValues[`Diameter`]
567
- //
568
- // When we are passed SomeObject["Name"] this code below recurses as if it were SomeObject.Name
569
- // The requirements to detect a boxed element are:
570
- // 1) The start bracket is after character 0
571
- else if (tmpBracketStartIndex > 0
572
- // 2) The end bracket has something between them
573
- && tmpBracketStopIndex > tmpBracketStartIndex
574
- // 3) There is data
575
- && tmpBracketStopIndex - tmpBracketStartIndex > 1) {
576
- let tmpBoxedPropertyName = tmpSubObjectName.substring(0, tmpBracketStartIndex).trim();
577
- let tmpBoxedPropertyReference = tmpSubObjectName.substring(tmpBracketStartIndex + 1, tmpBracketStopIndex).trim();
578
- let tmpBoxedPropertyNumber = parseInt(tmpBoxedPropertyReference, 10);
579
-
580
- // Guard: If the referrant is a number and the boxed property is not an array, or vice versa, return undefined.
581
- // This seems confusing to me at first read, so explaination:
582
- // Is the Boxed Object an Array? TRUE
583
- // And is the Reference inside the boxed Object not a number? TRUE
584
- // --> So when these are in agreement, it's an impossible access state
585
- // This could be a failure in the recursion chain because they passed something like this in:
586
- // StudentData.Sections.Algebra.Students[1].Tardy
587
- // BUT
588
- // StudentData.Sections.Algebra.Students is an object, so the [1].Tardy is not possible to access
589
- // This could be a failure in the recursion chain because they passed something like this in:
590
- // StudentData.Sections.Algebra.Students["JaneDoe"].Grade
591
- // BUT
592
- // StudentData.Sections.Algebra.Students is an array, so the ["JaneDoe"].Grade is not possible to access
593
- // TODO: Should this be an error or something? Should we keep a log of failures like this?
594
- if (Array.isArray(pObject[tmpBoxedPropertyName]) == isNaN(tmpBoxedPropertyNumber)) {
595
- // Because this is an impossible address, the property doesn't exist
596
- // TODO: Should we throw an error in this condition?
597
- return false;
598
- }
599
-
600
- //This is a bracketed value
601
- // 4) If the middle part is *only* a number (no single, double or backtick quotes) it is an array element,
602
- // otherwise we will try to reat it as a dynamic object property.
603
- if (isNaN(tmpBoxedPropertyNumber)) {
604
- // This isn't a number ... let's treat it as a dynanmic object property.
605
- tmpBoxedPropertyReference = this.cleanWrapCharacters('"', tmpBoxedPropertyReference);
606
- tmpBoxedPropertyReference = this.cleanWrapCharacters('`', tmpBoxedPropertyReference);
607
- tmpBoxedPropertyReference = this.cleanWrapCharacters("'", tmpBoxedPropertyReference);
608
-
609
- // Recurse directly into the subobject
610
- return this.checkAddressExists(pObject[tmpBoxedPropertyName][tmpBoxedPropertyReference], tmpNewAddress, tmpRootObject);
611
- } else {
612
- // We parsed a valid number out of the boxed property name, so recurse into the array
613
- return this.checkAddressExists(pObject[tmpBoxedPropertyName][tmpBoxedPropertyNumber], tmpNewAddress, tmpRootObject);
614
- }
615
- }
616
-
617
- // If there is an object property already named for the sub object, but it isn't an object
618
- // then the system can't set the value in there. Error and abort!
619
- if (tmpSubObjectName in pObject && typeof pObject[tmpSubObjectName] !== 'object') {
620
- return false;
621
- } else if (tmpSubObjectName in pObject) {
622
- // If there is already a subobject pass that to the recursive thingy
623
- return this.checkAddressExists(pObject[tmpSubObjectName], tmpNewAddress, tmpRootObject);
624
- } else {
625
- // Create a subobject and then pass that
626
- pObject[tmpSubObjectName] = {};
627
- return this.checkAddressExists(pObject[tmpSubObjectName], tmpNewAddress, tmpRootObject);
628
- }
629
- }
630
- }
631
- }
632
- module.exports = ManyfestObjectAddressResolverCheckAddressExists;
633
- }, {
634
- "./Manyfest-CleanWrapCharacters.js": 3,
635
- "./Manyfest-LogToConsole.js": 5,
636
- "./Manyfest-ObjectAddress-GetValue.js": 8,
637
- "./Manyfest-ObjectAddress-Parser.js": 9
638
- }],
639
- 7: [function (require, module, exports) {
640
- /**
641
- * @author <steven@velozo.com>
642
- */
643
- let libSimpleLog = require('./Manyfest-LogToConsole.js');
644
- let fCleanWrapCharacters = require('./Manyfest-CleanWrapCharacters.js');
645
- let fParseConditionals = require("../source/Manyfest-ParseConditionals.js");
646
-
647
- /**
648
- * Object Address Resolver - DeleteValue
649
- *
650
- * IMPORTANT NOTE: This code is intentionally more verbose than necessary, to
651
- * be extremely clear what is going on in the recursion for
652
- * each of the three address resolution functions.
653
- *
654
- * Although there is some opportunity to repeat ourselves a
655
- * bit less in this codebase (e.g. with detection of arrays
656
- * versus objects versus direct properties), it can make
657
- * debugging.. challenging. The minified version of the code
658
- * optimizes out almost anything repeated in here. So please
659
- * be kind and rewind... meaning please keep the codebase less
660
- * terse and more verbose so humans can comprehend it.
661
- *
662
- * TODO: Once we validate this pattern is good to go, break these out into
663
- * three separate modules.
664
- *
665
- * @class ManyfestObjectAddressResolverDeleteValue
666
- */
667
- class ManyfestObjectAddressResolverDeleteValue {
668
- /**
669
- * @param {function} [pInfoLog] - (optional) A logging function for info messages
670
- * @param {function} [pErrorLog] - (optional) A logging function for error messages
671
- */
672
- constructor(pInfoLog, pErrorLog) {
673
- // Wire in logging
674
- this.logInfo = typeof pInfoLog == 'function' ? pInfoLog : libSimpleLog;
675
- this.logError = typeof pErrorLog == 'function' ? pErrorLog : libSimpleLog;
676
- this.cleanWrapCharacters = fCleanWrapCharacters;
677
- }
678
-
679
- // TODO: Dry me
680
- /**
681
- * @param {string} pAddress - The address being evaluated
682
- * @param {object} pRecord - The record being evaluated
683
- *
684
- * @return {boolean} True if the record passes the filters, false if it does not
685
- */
686
- checkRecordFilters(pAddress, pRecord) {
687
- return fParseConditionals(this, pAddress, pRecord);
688
- }
689
-
690
- /**
691
- * Delete the value of an element at an address
692
- *
693
- * @param {object} pObject - The object to delete the value from
694
- * @param {string} pAddress - The address to delete the value at
695
- * @param {string} [pParentAddress] - (optional) The parent address for recursion
696
- *
697
- * @return {boolean|object|undefined} - True if the value was deleted, false if it could not be deleted, undefined on error
698
- */
699
- deleteValueAtAddress(pObject, pAddress, pParentAddress) {
700
- // Make sure pObject (the object we are meant to be recursing) is an object (which could be an array or object)
701
- if (typeof pObject != 'object') return undefined;
702
- // Make sure pAddress (the address we are resolving) is a string
703
- if (typeof pAddress != 'string') return undefined;
704
- // Stash the parent address for later resolution
705
- let tmpParentAddress = "";
706
- if (typeof pParentAddress == 'string') {
707
- tmpParentAddress = pParentAddress;
708
- }
709
-
710
- // TODO: Make this work for things like SomeRootObject.Metadata["Some.People.Use.Bad.Object.Property.Names"]
711
- let tmpSeparatorIndex = pAddress.indexOf('.');
712
-
713
- // This is the terminal address string (no more dots so the RECUSION ENDS IN HERE somehow)
714
- if (tmpSeparatorIndex == -1) {
715
- // Check if the address refers to a boxed property
716
- let tmpBracketStartIndex = pAddress.indexOf('[');
717
- let tmpBracketStopIndex = pAddress.indexOf(']');
718
-
719
- // Check for the Object Set Type marker.
720
- // Note this will not work with a bracket in the same address box set
721
- let tmpObjectTypeMarkerIndex = pAddress.indexOf('{}');
722
-
723
- // Boxed elements look like this:
724
- // MyValues[10]
725
- // MyValues['Name']
726
- // MyValues["Age"]
727
- // MyValues[`Cost`]
728
- //
729
- // When we are passed SomeObject["Name"] this code below recurses as if it were SomeObject.Name
730
- // The requirements to detect a boxed element are:
731
- // 1) The start bracket is after character 0
732
- if (tmpBracketStartIndex > 0
733
- // 2) The end bracket has something between them
734
- && tmpBracketStopIndex > tmpBracketStartIndex
735
- // 3) There is data
736
- && tmpBracketStopIndex - tmpBracketStartIndex > 1) {
737
- // The "Name" of the Object contained too the left of the bracket
738
- let tmpBoxedPropertyName = pAddress.substring(0, tmpBracketStartIndex).trim();
739
-
740
- // If the subproperty doesn't test as a proper Object, none of the rest of this is possible.
741
- // This is a rare case where Arrays testing as Objects is useful
742
- if (typeof pObject[tmpBoxedPropertyName] !== 'object') {
743
- return false;
744
- }
745
-
746
- // The "Reference" to the property within it, either an array element or object property
747
- let tmpBoxedPropertyReference = pAddress.substring(tmpBracketStartIndex + 1, tmpBracketStopIndex).trim();
748
- // Attempt to parse the reference as a number, which will be used as an array element
749
- let tmpBoxedPropertyNumber = parseInt(tmpBoxedPropertyReference, 10);
750
-
751
- // Guard: If the referrant is a number and the boxed property is not an array, or vice versa, return undefined.
752
- // This seems confusing to me at first read, so explaination:
753
- // Is the Boxed Object an Array? TRUE
754
- // And is the Reference inside the boxed Object not a number? TRUE
755
- // --> So when these are in agreement, it's an impossible access state
756
- if (Array.isArray(pObject[tmpBoxedPropertyName]) == isNaN(tmpBoxedPropertyNumber)) {
757
- return false;
758
- }
759
-
760
- // 4) If the middle part is *only* a number (no single, double or backtick quotes) it is an array element,
761
- // otherwise we will try to treat it as a dynamic object property.
762
- if (isNaN(tmpBoxedPropertyNumber)) {
763
- // This isn't a number ... let's treat it as a dynamic object property.
764
- // We would expect the property to be wrapped in some kind of quotes so strip them
765
- tmpBoxedPropertyReference = this.cleanWrapCharacters('"', tmpBoxedPropertyReference);
766
- tmpBoxedPropertyReference = this.cleanWrapCharacters('`', tmpBoxedPropertyReference);
767
- tmpBoxedPropertyReference = this.cleanWrapCharacters("'", tmpBoxedPropertyReference);
768
-
769
- // Return the value in the property
770
- delete pObject[tmpBoxedPropertyName][tmpBoxedPropertyReference];
771
- return true;
772
- } else {
773
- delete pObject[tmpBoxedPropertyName][tmpBoxedPropertyNumber];
774
- return true;
775
- }
776
- }
777
- // The requirements to detect a boxed set element are:
778
- // 1) The start bracket is after character 0
779
- else if (tmpBracketStartIndex > 0
780
- // 2) The end bracket is after the start bracket
781
- && tmpBracketStopIndex > tmpBracketStartIndex
782
- // 3) There is nothing in the brackets
783
- && tmpBracketStopIndex - tmpBracketStartIndex == 1) {
784
- let tmpBoxedPropertyName = pAddress.substring(0, tmpBracketStartIndex).trim();
785
- if (!Array.isArray(pObject[tmpBoxedPropertyName])) {
786
- // We asked for a set from an array but it isnt' an array.
787
- return false;
788
- }
789
- let tmpInputArray = pObject[tmpBoxedPropertyName];
790
- // Count from the end to the beginning so splice doesn't %&%#$ up the array
791
- for (let i = tmpInputArray.length - 1; i >= 0; i--) {
792
- // The filtering is complex but allows config-based metaprogramming directly from schema
793
- let tmpKeepRecord = this.checkRecordFilters(pAddress, tmpInputArray[i]);
794
- if (tmpKeepRecord) {
795
- // Delete elements end to beginning
796
- tmpInputArray.splice(i, 1);
797
- }
798
- }
799
- return true;
800
- }
801
- // The object has been flagged as an object set, so treat it as such
802
- else if (tmpObjectTypeMarkerIndex > 0) {
803
- let tmpObjectPropertyName = pAddress.substring(0, tmpObjectTypeMarkerIndex).trim();
804
- if (typeof pObject[tmpObjectPropertyName] != 'object') {
805
- // We asked for a set from an array but it isnt' an array.
806
- return false;
807
- }
808
- delete pObject[tmpObjectPropertyName];
809
- return true;
810
- } else {
811
- // Now is the point in recursion to return the value in the address
812
- delete pObject[pAddress];
813
- return true;
814
- }
815
- } else {
816
- let tmpSubObjectName = pAddress.substring(0, tmpSeparatorIndex);
817
- let tmpNewAddress = pAddress.substring(tmpSeparatorIndex + 1);
818
-
819
- // BOXED ELEMENTS
820
- // Test if the tmpNewAddress is an array or object
821
- // Check if it's a boxed property
822
- let tmpBracketStartIndex = tmpSubObjectName.indexOf('[');
823
- let tmpBracketStopIndex = tmpSubObjectName.indexOf(']');
824
- // Boxed elements look like this:
825
- // MyValues[42]
826
- // MyValues['Color']
827
- // MyValues["Weight"]
828
- // MyValues[`Diameter`]
829
- //
830
- // When we are passed SomeObject["Name"] this code below recurses as if it were SomeObject.Name
831
- // The requirements to detect a boxed element are:
832
- // 1) The start bracket is after character 0
833
- if (tmpBracketStartIndex > 0
834
- // 2) The end bracket has something between them
835
- && tmpBracketStopIndex > tmpBracketStartIndex
836
- // 3) There is data
837
- && tmpBracketStopIndex - tmpBracketStartIndex > 1) {
838
- let tmpBoxedPropertyName = tmpSubObjectName.substring(0, tmpBracketStartIndex).trim();
839
- let tmpBoxedPropertyReference = tmpSubObjectName.substring(tmpBracketStartIndex + 1, tmpBracketStopIndex).trim();
840
- let tmpBoxedPropertyNumber = parseInt(tmpBoxedPropertyReference, 10);
841
-
842
- // Guard: If the referrant is a number and the boxed property is not an array, or vice versa, return undefined.
843
- // This seems confusing to me at first read, so explaination:
844
- // Is the Boxed Object an Array? TRUE
845
- // And is the Reference inside the boxed Object not a number? TRUE
846
- // --> So when these are in agreement, it's an impossible access state
847
- // This could be a failure in the recursion chain because they passed something like this in:
848
- // StudentData.Sections.Algebra.Students[1].Tardy
849
- // BUT
850
- // StudentData.Sections.Algebra.Students is an object, so the [1].Tardy is not possible to access
851
- // This could be a failure in the recursion chain because they passed something like this in:
852
- // StudentData.Sections.Algebra.Students["JaneDoe"].Grade
853
- // BUT
854
- // StudentData.Sections.Algebra.Students is an array, so the ["JaneDoe"].Grade is not possible to access
855
- // TODO: Should this be an error or something? Should we keep a log of failures like this?
856
- if (Array.isArray(pObject[tmpBoxedPropertyName]) == isNaN(tmpBoxedPropertyNumber)) {
857
- return false;
858
- }
859
- // Check if the boxed property is an object.
860
- if (typeof pObject[tmpBoxedPropertyName] != 'object') {
861
- return false;
862
- }
863
- //This is a bracketed value
864
- // 4) If the middle part is *only* a number (no single, double or backtick quotes) it is an array element,
865
- // otherwise we will try to reat it as a dynamic object property.
866
- if (isNaN(tmpBoxedPropertyNumber)) {
867
- // This isn't a number ... let's treat it as a dynanmic object property.
868
- tmpBoxedPropertyReference = this.cleanWrapCharacters('"', tmpBoxedPropertyReference);
869
- tmpBoxedPropertyReference = this.cleanWrapCharacters('`', tmpBoxedPropertyReference);
870
- tmpBoxedPropertyReference = this.cleanWrapCharacters("'", tmpBoxedPropertyReference);
871
-
872
- // Continue to manage the parent address for recursion
873
- tmpParentAddress = "".concat(tmpParentAddress).concat(tmpParentAddress.length > 0 ? '.' : '').concat(tmpSubObjectName);
874
- // Recurse directly into the subobject
875
- return this.deleteValueAtAddress(pObject[tmpBoxedPropertyName][tmpBoxedPropertyReference], tmpNewAddress, tmpParentAddress);
876
- } else {
877
- // Continue to manage the parent address for recursion
878
- tmpParentAddress = "".concat(tmpParentAddress).concat(tmpParentAddress.length > 0 ? '.' : '').concat(tmpSubObjectName);
879
- // We parsed a valid number out of the boxed property name, so recurse into the array
880
- return this.deleteValueAtAddress(pObject[tmpBoxedPropertyName][tmpBoxedPropertyNumber], tmpNewAddress, tmpParentAddress);
881
- }
882
- }
883
- // The requirements to detect a boxed set element are:
884
- // 1) The start bracket is after character 0
885
- else if (tmpBracketStartIndex > 0
886
- // 2) The end bracket is after the start bracket
887
- && tmpBracketStopIndex > tmpBracketStartIndex
888
- // 3) There is nothing in the brackets
889
- && tmpBracketStopIndex - tmpBracketStartIndex == 1) {
890
- let tmpBoxedPropertyName = pAddress.substring(0, tmpBracketStartIndex).trim();
891
- if (!Array.isArray(pObject[tmpBoxedPropertyName])) {
892
- // We asked for a set from an array but it isnt' an array.
893
- return false;
894
- }
895
-
896
- // We need to enumerate the array and grab the addresses from there.
897
- let tmpArrayProperty = pObject[tmpBoxedPropertyName];
898
- // Managing the parent address is a bit more complex here -- the box will be added for each element.
899
- tmpParentAddress = "".concat(tmpParentAddress).concat(tmpParentAddress.length > 0 ? '.' : '').concat(tmpBoxedPropertyName);
900
- // The container object is where we have the "Address":SOMEVALUE pairs
901
- let tmpContainerObject = {};
902
- for (let i = 0; i < tmpArrayProperty.length; i++) {
903
- let tmpPropertyParentAddress = "".concat(tmpParentAddress, "[").concat(i, "]");
904
- let tmpValue = this.deleteValueAtAddress(pObject[tmpBoxedPropertyName][i], tmpNewAddress, tmpPropertyParentAddress);
905
- tmpContainerObject["".concat(tmpPropertyParentAddress, ".").concat(tmpNewAddress)] = tmpValue;
906
- }
907
- return tmpContainerObject;
908
- }
909
-
910
- // OBJECT SET
911
- // Note this will not work with a bracket in the same address box set
912
- let tmpObjectTypeMarkerIndex = pAddress.indexOf('{}');
913
- if (tmpObjectTypeMarkerIndex > 0) {
914
- let tmpObjectPropertyName = pAddress.substring(0, tmpObjectTypeMarkerIndex).trim();
915
- if (typeof pObject[tmpObjectPropertyName] != 'object') {
916
- // We asked for a set from an array but it isnt' an array.
917
- return false;
918
- }
919
-
920
- // We need to enumerate the Object and grab the addresses from there.
921
- let tmpObjectProperty = pObject[tmpObjectPropertyName];
922
- let tmpObjectPropertyKeys = Object.keys(tmpObjectProperty);
923
- // Managing the parent address is a bit more complex here -- the box will be added for each element.
924
- tmpParentAddress = "".concat(tmpParentAddress).concat(tmpParentAddress.length > 0 ? '.' : '').concat(tmpObjectPropertyName);
925
- // The container object is where we have the "Address":SOMEVALUE pairs
926
- let tmpContainerObject = {};
927
- for (let i = 0; i < tmpObjectPropertyKeys.length; i++) {
928
- let tmpPropertyParentAddress = "".concat(tmpParentAddress, ".").concat(tmpObjectPropertyKeys[i]);
929
- let tmpValue = this.deleteValueAtAddress(pObject[tmpObjectPropertyName][tmpObjectPropertyKeys[i]], tmpNewAddress, tmpPropertyParentAddress);
930
-
931
- // The filtering is complex but allows config-based metaprogramming directly from schema
932
- let tmpKeepRecord = this.checkRecordFilters(pAddress, tmpValue);
933
- if (tmpKeepRecord) {
934
- tmpContainerObject["".concat(tmpPropertyParentAddress, ".").concat(tmpNewAddress)] = tmpValue;
935
- }
936
- }
937
- return tmpContainerObject;
938
- }
939
-
940
- // If there is an object property already named for the sub object, but it isn't an object
941
- // then the system can't set the value in there. Error and abort!
942
- if (tmpSubObjectName in pObject && typeof pObject[tmpSubObjectName] !== 'object') {
943
- return undefined;
944
- } else if (tmpSubObjectName in pObject) {
945
- // If there is already a subobject pass that to the recursive thingy
946
- // Continue to manage the parent address for recursion
947
- tmpParentAddress = "".concat(tmpParentAddress).concat(tmpParentAddress.length > 0 ? '.' : '').concat(tmpSubObjectName);
948
- return this.deleteValueAtAddress(pObject[tmpSubObjectName], tmpNewAddress, tmpParentAddress);
949
- } else {
950
- // Create a subobject and then pass that
951
- // Continue to manage the parent address for recursion
952
- tmpParentAddress = "".concat(tmpParentAddress).concat(tmpParentAddress.length > 0 ? '.' : '').concat(tmpSubObjectName);
953
- pObject[tmpSubObjectName] = {};
954
- return this.deleteValueAtAddress(pObject[tmpSubObjectName], tmpNewAddress, tmpParentAddress);
955
- }
956
- }
957
- }
958
- }
959
- ;
960
- module.exports = ManyfestObjectAddressResolverDeleteValue;
961
- }, {
962
- "../source/Manyfest-ParseConditionals.js": 12,
963
- "./Manyfest-CleanWrapCharacters.js": 3,
964
- "./Manyfest-LogToConsole.js": 5
965
- }],
966
- 8: [function (require, module, exports) {
967
- /**
968
- * @author <steven@velozo.com>
969
- */
970
- let libSimpleLog = require('./Manyfest-LogToConsole.js');
971
- let fCleanWrapCharacters = require('./Manyfest-CleanWrapCharacters.js');
972
- let fParseConditionals = require("../source/Manyfest-ParseConditionals.js");
973
- let _MockFable = {
974
- DataFormat: require('./Manyfest-ObjectAddress-Parser.js')
975
- };
976
-
977
- /**
978
- * Object Address Resolver - GetValue
979
- *
980
- * IMPORTANT NOTE: This code is intentionally more verbose than necessary, to
981
- * be extremely clear what is going on in the recursion for
982
- * each of the three address resolution functions.
983
- *
984
- * Although there is some opportunity to repeat ourselves a
985
- * bit less in this codebase (e.g. with detection of arrays
986
- * versus objects versus direct properties), it can make
987
- * debugging.. challenging. The minified version of the code
988
- * optimizes out almost anything repeated in here. So please
989
- * be kind and rewind... meaning please keep the codebase less
990
- * terse and more verbose so humans can comprehend it.
991
- *
992
- * TODO: Once we validate this pattern is good to go, break these out into
993
- * three separate modules.
994
- *
995
- * @class ManyfestObjectAddressResolverGetValue
996
- */
997
- class ManyfestObjectAddressResolverGetValue {
998
- /**
999
- * @param {function} [pInfoLog] - (optional) A logging function for info messages
1000
- * @param {function} [pErrorLog] - (optional) A logging function for error messages
1001
- */
1002
- constructor(pInfoLog, pErrorLog) {
1003
- // Wire in logging
1004
- this.logInfo = typeof pInfoLog == 'function' ? pInfoLog : libSimpleLog;
1005
- this.logError = typeof pErrorLog == 'function' ? pErrorLog : libSimpleLog;
1006
- this.cleanWrapCharacters = fCleanWrapCharacters;
1007
- }
1008
-
1009
- /**
1010
- * @param {string} pAddress - The address of the record to check
1011
- * @param {object} pRecord - The record to check against the filters
1012
- *
1013
- * @return {boolean} - True if the record passes the filters, false otherwise
1014
- */
1015
- checkRecordFilters(pAddress, pRecord) {
1016
- return fParseConditionals(this, pAddress, pRecord);
1017
- }
1018
-
1019
- /**
1020
- * Get the value of an element at an address
1021
- *
1022
- * @param {object} pObject - The object to resolve the address against
1023
- * @param {string} pAddress - The address to resolve
1024
- * @param {string} [pParentAddress] - (optional) The parent address for back-navigation
1025
- * @param {object} [pRootObject] - (optional) The root object for function argument resolution
1026
- *
1027
- * @return {any} The value at the address, or undefined if not found
1028
- */
1029
- getValueAtAddress(pObject, pAddress, pParentAddress, pRootObject) {
1030
- // Make sure pObject (the object we are meant to be recursing) is an object (which could be an array or object)
1031
- if (typeof pObject != 'object') {
1032
- return undefined;
1033
- }
1034
- if (pObject === null) {
1035
- return undefined;
1036
- }
1037
- // Make sure pAddress (the address we are resolving) is a string
1038
- if (typeof pAddress != 'string') {
1039
- return undefined;
1040
- }
1041
- // Stash the parent address for later resolution
1042
- let tmpParentAddress = "";
1043
- if (typeof pParentAddress == 'string') {
1044
- tmpParentAddress = pParentAddress;
1045
- }
1046
-
1047
- // Set the root object to the passed-in object if it isn't set yet. This is expected to be the root object.
1048
- let tmpRootObject = typeof pRootObject == 'undefined' ? pObject : pRootObject;
1049
-
1050
- // DONE: Make this work for things like SomeRootObject.Metadata["Some.People.Use.Bad.Object.Property.Names"]
1051
- let tmpAddressPartBeginning = _MockFable.DataFormat.stringGetFirstSegment(pAddress);
1052
-
1053
- // Adding simple back-navigation in objects
1054
- if (tmpAddressPartBeginning == '') {
1055
- // Given an address of "Bundle.Contract.IDContract...Project.IDProject" the ... would be interpreted as two back-navigations from IDContract.
1056
- // When the address is passed in, though, the first . is already eliminated. So we can count the dots.
1057
- let tmpParentAddressParts = _MockFable.DataFormat.stringGetSegments(tmpParentAddress);
1058
- let tmpBackNavigationCount = 0;
1059
-
1060
- // Count the number of dots
1061
- for (let i = 0; i < pAddress.length; i++) {
1062
- if (pAddress.charAt(i) != '.') {
1063
- break;
1064
- }
1065
- tmpBackNavigationCount++;
1066
- }
1067
- let tmpParentAddressLength = tmpParentAddressParts.length - tmpBackNavigationCount;
1068
- if (tmpParentAddressLength < 0) {
1069
- // We are trying to back navigate more than we can.
1070
- // TODO: Should this be undefined or should we bank out at the bottom and try to go forward?
1071
- // This seems safest for now.
1072
- return undefined;
1073
- } else {
1074
- // We are trying to back navigate to a parent object.
1075
- // Recurse with the back-propagated parent address, and, the new address without the back-navigation dots.
1076
- let tmpRecurseAddress = pAddress.slice(tmpBackNavigationCount);
1077
- if (tmpParentAddressLength > 0) {
1078
- tmpRecurseAddress = "".concat(tmpParentAddressParts.slice(0, tmpParentAddressLength).join('.'), ".").concat(tmpRecurseAddress);
1079
- }
1080
- this.logInfo("Back-navigation detected. Recursing back to address [".concat(tmpRecurseAddress, "]"));
1081
- return this.getValueAtAddress(tmpRootObject, tmpRecurseAddress);
1082
- }
1083
- }
1084
-
1085
- // This is the terminal address string (no more dots so the RECUSION ENDS IN HERE somehow)
1086
- if (tmpAddressPartBeginning.length == pAddress.length) {
1087
- // TODO: Optimize this by having these calls only happen when the previous fails.
1088
- // TODO: Alternatively look for all markers in one pass?
1089
- // Check if the address refers to a boxed property
1090
- let tmpBracketStartIndex = pAddress.indexOf('[');
1091
- let tmpBracketStopIndex = pAddress.indexOf(']');
1092
-
1093
- // Check for the Object Set Type marker.
1094
- // Note this will not work with a bracket in the same address box set
1095
- let tmpObjectTypeMarkerIndex = pAddress.indexOf('{}');
1096
-
1097
- // Check if there is a function somewhere in the address... parenthesis start should only be in a function
1098
- let tmpFunctionStartIndex = pAddress.indexOf('(');
1099
-
1100
- // NOTE THAT FUNCTIONS MUST RESOLVE FIRST
1101
- // Functions look like this
1102
- // MyFunction()
1103
- // MyFunction(Some.Address)
1104
- // MyFunction(Some.Address,Some.Other.Address)
1105
- // MyFunction(Some.Address,Some.Other.Address,Some.Third.Address)
1106
- //
1107
- // This could be enhanced to allow purely numeric and string values to be passed to the function. For now,
1108
- // To heck with that. This is a simple function call.
1109
- //
1110
- // The requirements to detect a function are:
1111
- // 1) The start bracket is after character 0
1112
- if (tmpFunctionStartIndex > 0
1113
- // 2) The end bracket is after the start bracket
1114
- && _MockFable.DataFormat.stringCountEnclosures(pAddress) > 0) {
1115
- let tmpFunctionAddress = pAddress.substring(0, tmpFunctionStartIndex).trim();
1116
- if (typeof pObject[tmpFunctionAddress] !== 'function') {
1117
- // The address suggests it is a function, but it is not.
1118
- return false;
1119
- }
1120
-
1121
- // Now see if the function has arguments.
1122
- // Implementation notes: * ARGUMENTS MUST SHARE THE SAME ROOT OBJECT CONTEXT *
1123
- let tmpFunctionArguments = _MockFable.DataFormat.stringGetSegments(_MockFable.DataFormat.stringGetEnclosureValueByIndex(pAddress.substring(tmpFunctionAddress.length), 0), ',');
1124
- if (tmpFunctionArguments.length == 0 || tmpFunctionArguments[0] == '') {
1125
- // No arguments... just call the function (bound to the scope of the object it is contained withing)
1126
- if (tmpFunctionAddress in pObject) {
1127
- try {
1128
- return pObject[tmpFunctionAddress].apply(pObject);
1129
- } catch (pError) {
1130
- // The function call failed, so the address doesn't exist
1131
- console.log("Error in getValueAtAddress calling function ".concat(tmpFunctionAddress, " (address [").concat(pAddress, "]): ").concat(pError.message));
1132
- return false;
1133
- }
1134
- } else {
1135
- // The function doesn't exist, so the address doesn't exist
1136
- console.log("Function ".concat(tmpFunctionAddress, " does not exist (address [").concat(pAddress, "])"));
1137
- return false;
1138
- }
1139
- } else {
1140
- let tmpArgumentValues = [];
1141
- let tmpRootObject = typeof pRootObject == 'undefined' ? pObject : pRootObject;
1142
-
1143
- // Now get the value for each argument
1144
- for (let i = 0; i < tmpFunctionArguments.length; i++) {
1145
- // Resolve the values for each subsequent entry
1146
- // Check if the argument value is a string literal or a reference to an address
1147
- if (tmpFunctionArguments[i].length >= 2 && (tmpFunctionArguments[i].charAt(0) == '"' || tmpFunctionArguments[i].charAt(0) == "'" || tmpFunctionArguments[i].charAt(0) == "`") && (tmpFunctionArguments[i].charAt(tmpFunctionArguments[i].length - 1) == '"' || tmpFunctionArguments[i].charAt(tmpFunctionArguments[i].length - 1) == "'" || tmpFunctionArguments[i].charAt(tmpFunctionArguments[i].length - 1) == "`")) {
1148
- // This is a string literal
1149
- tmpArgumentValues.push(tmpFunctionArguments[i].substring(1, tmpFunctionArguments[i].length - 1));
1150
- } else {
1151
- // This is a hash address
1152
- tmpArgumentValues.push(this.getValueAtAddress(tmpRootObject, tmpFunctionArguments[i]));
1153
- }
1154
- }
1155
- if (tmpFunctionAddress in pObject) {
1156
- try {
1157
- return pObject[tmpFunctionAddress].apply(pObject, tmpArgumentValues);
1158
- } catch (pError) {
1159
- // The function call failed, so the address doesn't exist
1160
- console.log("Error in getValueAtAddress calling function ".concat(tmpFunctionAddress, " (address [").concat(pAddress, "]): ").concat(pError.message));
1161
- return false;
1162
- }
1163
- } else {
1164
- // The function doesn't exist, so the address doesn't exist
1165
- console.log("Function ".concat(tmpFunctionAddress, " does not exist (address [").concat(pAddress, "])"));
1166
- return false;
1167
- }
1168
- }
1169
- }
1170
- // Boxed elements look like this:
1171
- // MyValues[10]
1172
- // MyValues['Name']
1173
- // MyValues["Age"]
1174
- // MyValues[`Cost`]
1175
- //
1176
- // When we are passed SomeObject["Name"] this code below recurses as if it were SomeObject.Name
1177
- // The requirements to detect a boxed element are:
1178
- // 1) The start bracket is after character 0
1179
- else if (tmpBracketStartIndex > 0
1180
- // 2) The end bracket has something between them
1181
- && tmpBracketStopIndex > tmpBracketStartIndex
1182
- // 3) There is data
1183
- && tmpBracketStopIndex - tmpBracketStartIndex > 1) {
1184
- // The "Name" of the Object contained too the left of the bracket
1185
- let tmpBoxedPropertyName = pAddress.substring(0, tmpBracketStartIndex).trim();
1186
-
1187
- // If the subproperty doesn't test as a proper Object, none of the rest of this is possible.
1188
- // This is a rare case where Arrays testing as Objects is useful
1189
- if (typeof pObject[tmpBoxedPropertyName] !== 'object') {
1190
- return undefined;
1191
- }
1192
-
1193
- // The "Reference" to the property within it, either an array element or object property
1194
- let tmpBoxedPropertyReference = pAddress.substring(tmpBracketStartIndex + 1, tmpBracketStopIndex).trim();
1195
- // Attempt to parse the reference as a number, which will be used as an array element
1196
- let tmpBoxedPropertyNumber = parseInt(tmpBoxedPropertyReference, 10);
1197
-
1198
- // Guard: If the referrant is a number and the boxed property is not an array, or vice versa, return undefined.
1199
- // This seems confusing to me at first read, so explaination:
1200
- // Is the Boxed Object an Array? TRUE
1201
- // And is the Reference inside the boxed Object not a number? TRUE
1202
- // --> So when these are in agreement, it's an impossible access state
1203
- if (Array.isArray(pObject[tmpBoxedPropertyName]) == isNaN(tmpBoxedPropertyNumber)) {
1204
- return undefined;
1205
- }
1206
-
1207
- // 4) If the middle part is *only* a number (no single, double or backtick quotes) it is an array element,
1208
- // otherwise we will try to treat it as a dynamic object property.
1209
- if (isNaN(tmpBoxedPropertyNumber)) {
1210
- // This isn't a number ... let's treat it as a dynamic object property.
1211
- // We would expect the property to be wrapped in some kind of quotes so strip them
1212
- tmpBoxedPropertyReference = this.cleanWrapCharacters('"', tmpBoxedPropertyReference);
1213
- tmpBoxedPropertyReference = this.cleanWrapCharacters('`', tmpBoxedPropertyReference);
1214
- tmpBoxedPropertyReference = this.cleanWrapCharacters("'", tmpBoxedPropertyReference);
1215
-
1216
- // Return the value in the property
1217
- return pObject[tmpBoxedPropertyName][tmpBoxedPropertyReference];
1218
- } else {
1219
- return pObject[tmpBoxedPropertyName][tmpBoxedPropertyNumber];
1220
- }
1221
- }
1222
- // The requirements to detect a boxed set element are:
1223
- // 1) The start bracket is after character 0
1224
- else if (tmpBracketStartIndex > 0
1225
- // 2) The end bracket is after the start bracket
1226
- && tmpBracketStopIndex > tmpBracketStartIndex
1227
- // 3) There is nothing in the brackets
1228
- && tmpBracketStopIndex - tmpBracketStartIndex == 1) {
1229
- let tmpBoxedPropertyName = pAddress.substring(0, tmpBracketStartIndex).trim();
1230
- if (!Array.isArray(pObject[tmpBoxedPropertyName])) {
1231
- // We asked for a set from an array but it isnt' an array.
1232
- return false;
1233
- }
1234
- let tmpInputArray = pObject[tmpBoxedPropertyName];
1235
- let tmpOutputArray = [];
1236
- for (let i = 0; i < tmpInputArray.length; i++) {
1237
- // The filtering is complex but allows config-based metaprogramming directly from schema
1238
- let tmpKeepRecord = this.checkRecordFilters(pAddress, tmpInputArray[i]);
1239
- if (tmpKeepRecord) {
1240
- tmpOutputArray.push(tmpInputArray[i]);
1241
- }
1242
- }
1243
- return tmpOutputArray;
1244
- }
1245
- // The object has been flagged as an object set, so treat it as such
1246
- else if (tmpObjectTypeMarkerIndex > 0) {
1247
- let tmpObjectPropertyName = pAddress.substring(0, tmpObjectTypeMarkerIndex).trim();
1248
- if (typeof pObject[tmpObjectPropertyName] != 'object') {
1249
- // We asked for a set from an array but it isnt' an array.
1250
- return false;
1251
- }
1252
- return pObject[tmpObjectPropertyName];
1253
- } else {
1254
- // Now is the point in recursion to return the value in the address
1255
- if (typeof pObject[pAddress] != null) {
1256
- return pObject[pAddress];
1257
- } else {
1258
- return null;
1259
- }
1260
- }
1261
- } else {
1262
- //let tmpSubObjectName = pAddress.substring(0, tmpSeparatorIndex);
1263
- //let tmpNewAddress = pAddress.substring(tmpSeparatorIndex+1);
1264
- let tmpSubObjectName = tmpAddressPartBeginning;
1265
- let tmpNewAddress = pAddress.substring(tmpAddressPartBeginning.length + 1);
1266
-
1267
- // BOXED ELEMENTS
1268
- // Test if the tmpNewAddress is an array or object
1269
- // Check if it's a boxed property
1270
- let tmpBracketStartIndex = tmpSubObjectName.indexOf('[');
1271
- let tmpBracketStopIndex = tmpSubObjectName.indexOf(']');
1272
-
1273
- // Check if there is a function somewhere in the address... parenthesis start should only be in a function
1274
- let tmpFunctionStartIndex = tmpSubObjectName.indexOf('(');
1275
-
1276
- // NOTE THAT FUNCTIONS MUST RESOLVE FIRST
1277
- // Functions look like this
1278
- // MyFunction()
1279
- // MyFunction(Some.Address)
1280
- // MyFunction(Some.Address,Some.Other.Address)
1281
- // MyFunction(Some.Address,Some.Other.Address,Some.Third.Address)
1282
- //
1283
- // This could be enhanced to allow purely numeric and string values to be passed to the function. For now,
1284
- // To heck with that. This is a simple function call.
1285
- //
1286
- // The requirements to detect a function are:
1287
- // 1) The start bracket is after character 0
1288
- if (tmpFunctionStartIndex > 0
1289
- // 2) The end bracket is after the start bracket
1290
- && _MockFable.DataFormat.stringCountEnclosures(tmpSubObjectName) > 0) {
1291
- let tmpFunctionAddress = tmpSubObjectName.substring(0, tmpFunctionStartIndex).trim();
1292
- tmpParentAddress = "".concat(tmpParentAddress).concat(tmpParentAddress.length > 0 ? '.' : '').concat(tmpSubObjectName);
1293
- if (typeof pObject[tmpFunctionAddress] !== 'function') {
1294
- // The address suggests it is a function, but it is not.
1295
- return false;
1296
- }
1297
-
1298
- // Now see if the function has arguments.
1299
- // Implementation notes: * ARGUMENTS MUST SHARE THE SAME ROOT OBJECT CONTEXT *
1300
- let tmpFunctionArguments = _MockFable.DataFormat.stringGetSegments(_MockFable.DataFormat.stringGetEnclosureValueByIndex(tmpSubObjectName.substring(tmpFunctionAddress.length), 0), ',');
1301
- if (tmpFunctionArguments.length == 0 || tmpFunctionArguments[0] == '') {
1302
- // No arguments... just call the function (bound to the scope of the object it is contained withing)
1303
- if (tmpFunctionAddress in pObject) {
1304
- try {
1305
- return this.getValueAtAddress(pObject[tmpFunctionAddress].apply(pObject), tmpNewAddress, tmpParentAddress, tmpRootObject);
1306
- } catch (pError) {
1307
- // The function call failed, so the address doesn't exist
1308
- console.log("Error in getValueAtAddress calling function ".concat(tmpFunctionAddress, " (address [").concat(pAddress, "]): ").concat(pError.message));
1309
- return false;
1310
- }
1311
- } else {
1312
- // The function doesn't exist, so the address doesn't exist
1313
- console.log("Function ".concat(tmpFunctionAddress, " does not exist (address [").concat(pAddress, "])"));
1314
- return false;
1315
- }
1316
- } else {
1317
- let tmpArgumentValues = [];
1318
- let tmpRootObject = typeof pRootObject == 'undefined' ? pObject : pRootObject;
1319
-
1320
- // Now get the value for each argument
1321
- for (let i = 0; i < tmpFunctionArguments.length; i++) {
1322
- // Resolve the values for each subsequent entry
1323
- // Check if the argument value is a string literal or a reference to an address
1324
- if (tmpFunctionArguments[i].length >= 2 && (tmpFunctionArguments[i].charAt(0) == '"' || tmpFunctionArguments[i].charAt(0) == "'" || tmpFunctionArguments[i].charAt(0) == "`") && (tmpFunctionArguments[i].charAt(tmpFunctionArguments[i].length - 1) == '"' || tmpFunctionArguments[i].charAt(tmpFunctionArguments[i].length - 1) == "'" || tmpFunctionArguments[i].charAt(tmpFunctionArguments[i].length - 1) == "`")) {
1325
- // This is a string literal
1326
- tmpArgumentValues.push(tmpFunctionArguments[i].substring(1, tmpFunctionArguments[i].length - 1));
1327
- } else {
1328
- // This is a hash address
1329
- tmpArgumentValues.push(this.getValueAtAddress(tmpRootObject, tmpFunctionArguments[i]));
1330
- }
1331
- }
1332
- if (tmpFunctionAddress in pObject) {
1333
- try {
1334
- return this.getValueAtAddress(pObject[tmpFunctionAddress].apply(pObject, tmpArgumentValues), tmpNewAddress, tmpParentAddress, tmpRootObject);
1335
- } catch (pError) {
1336
- // The function call failed, so the address doesn't exist
1337
- console.log("Error in getValueAtAddress calling function ".concat(tmpFunctionAddress, " (address [").concat(pAddress, "]): ").concat(pError.message));
1338
- return false;
1339
- }
1340
- } else {
1341
- // The function doesn't exist, so the address doesn't exist
1342
- console.log("Function ".concat(tmpFunctionAddress, " does not exist (address [").concat(pAddress, "])"));
1343
- return false;
1344
- }
1345
- }
1346
- }
1347
- // Boxed elements look like this:
1348
- // MyValues[42]
1349
- // MyValues['Color']
1350
- // MyValues["Weight"]
1351
- // MyValues[`Diameter`]
1352
- //
1353
- // When we are passed SomeObject["Name"] this code below recurses as if it were SomeObject.Name
1354
- // The requirements to detect a boxed element are:
1355
- // 1) The start bracket is after character 0
1356
- else if (tmpBracketStartIndex > 0
1357
- // 2) The end bracket has something between them
1358
- && tmpBracketStopIndex > tmpBracketStartIndex
1359
- // 3) There is data
1360
- && tmpBracketStopIndex - tmpBracketStartIndex > 1) {
1361
- let tmpBoxedPropertyName = tmpSubObjectName.substring(0, tmpBracketStartIndex).trim();
1362
- let tmpBoxedPropertyReference = tmpSubObjectName.substring(tmpBracketStartIndex + 1, tmpBracketStopIndex).trim();
1363
- let tmpBoxedPropertyNumber = parseInt(tmpBoxedPropertyReference, 10);
1364
-
1365
- // Guard: If the referrant is a number and the boxed property is not an array, or vice versa, return undefined.
1366
- // This seems confusing to me at first read, so explaination:
1367
- // Is the Boxed Object an Array? TRUE
1368
- // And is the Reference inside the boxed Object not a number? TRUE
1369
- // --> So when these are in agreement, it's an impossible access state
1370
- // This could be a failure in the recursion chain because they passed something like this in:
1371
- // StudentData.Sections.Algebra.Students[1].Tardy
1372
- // BUT
1373
- // StudentData.Sections.Algebra.Students is an object, so the [1].Tardy is not possible to access
1374
- // This could be a failure in the recursion chain because they passed something like this in:
1375
- // StudentData.Sections.Algebra.Students["JaneDoe"].Grade
1376
- // BUT
1377
- // StudentData.Sections.Algebra.Students is an array, so the ["JaneDoe"].Grade is not possible to access
1378
- // TODO: Should this be an error or something? Should we keep a log of failures like this?
1379
- if (Array.isArray(pObject[tmpBoxedPropertyName]) == isNaN(tmpBoxedPropertyNumber)) {
1380
- return undefined;
1381
- }
1382
- // Check if the boxed property is an object.
1383
- if (typeof pObject[tmpBoxedPropertyName] != 'object') {
1384
- return undefined;
1385
- }
1386
-
1387
- //This is a bracketed value
1388
- // 4) If the middle part is *only* a number (no single, double or backtick quotes) it is an array element,
1389
- // otherwise we will try to reat it as a dynamic object property.
1390
- if (isNaN(tmpBoxedPropertyNumber)) {
1391
- // This isn't a number ... let's treat it as a dynanmic object property.
1392
- tmpBoxedPropertyReference = this.cleanWrapCharacters('"', tmpBoxedPropertyReference);
1393
- tmpBoxedPropertyReference = this.cleanWrapCharacters('`', tmpBoxedPropertyReference);
1394
- tmpBoxedPropertyReference = this.cleanWrapCharacters("'", tmpBoxedPropertyReference);
1395
-
1396
- // Continue to manage the parent address for recursion
1397
- tmpParentAddress = "".concat(tmpParentAddress).concat(tmpParentAddress.length > 0 ? '.' : '').concat(tmpSubObjectName);
1398
- // Recurse directly into the subobject
1399
- return this.getValueAtAddress(pObject[tmpBoxedPropertyName][tmpBoxedPropertyReference], tmpNewAddress, tmpParentAddress, tmpRootObject);
1400
- } else {
1401
- // Continue to manage the parent address for recursion
1402
- tmpParentAddress = "".concat(tmpParentAddress).concat(tmpParentAddress.length > 0 ? '.' : '').concat(tmpSubObjectName);
1403
- // We parsed a valid number out of the boxed property name, so recurse into the array
1404
- return this.getValueAtAddress(pObject[tmpBoxedPropertyName][tmpBoxedPropertyNumber], tmpNewAddress, tmpParentAddress, tmpRootObject);
1405
- }
1406
- }
1407
- // The requirements to detect a boxed set element are:
1408
- // 1) The start bracket is after character 0
1409
- else if (tmpBracketStartIndex > 0
1410
- // 2) The end bracket is after the start bracket
1411
- && tmpBracketStopIndex > tmpBracketStartIndex
1412
- // 3) There is nothing in the brackets
1413
- && tmpBracketStopIndex - tmpBracketStartIndex == 1) {
1414
- let tmpBoxedPropertyName = pAddress.substring(0, tmpBracketStartIndex).trim();
1415
- if (!Array.isArray(pObject[tmpBoxedPropertyName])) {
1416
- // We asked for a set from an array but it isnt' an array.
1417
- return false;
1418
- }
1419
-
1420
- // We need to enumerate the array and grab the addresses from there.
1421
- let tmpArrayProperty = pObject[tmpBoxedPropertyName];
1422
- // Managing the parent address is a bit more complex here -- the box will be added for each element.
1423
- tmpParentAddress = "".concat(tmpParentAddress).concat(tmpParentAddress.length > 0 ? '.' : '').concat(tmpBoxedPropertyName);
1424
- // The container object is where we have the "Address":SOMEVALUE pairs
1425
- let tmpContainerObject = {};
1426
- for (let i = 0; i < tmpArrayProperty.length; i++) {
1427
- let tmpPropertyParentAddress = "".concat(tmpParentAddress, "[").concat(i, "]");
1428
- let tmpValue = this.getValueAtAddress(pObject[tmpBoxedPropertyName][i], tmpNewAddress, tmpPropertyParentAddress, tmpRootObject);
1429
- tmpContainerObject["".concat(tmpPropertyParentAddress, ".").concat(tmpNewAddress)] = tmpValue;
1430
- }
1431
- return tmpContainerObject;
1432
- }
1433
-
1434
- // OBJECT SET
1435
- // Note this will not work with a bracket in the same address box set
1436
- let tmpObjectTypeMarkerIndex = pAddress.indexOf('{}');
1437
- if (tmpObjectTypeMarkerIndex > 0) {
1438
- let tmpObjectPropertyName = pAddress.substring(0, tmpObjectTypeMarkerIndex).trim();
1439
- if (typeof pObject[tmpObjectPropertyName] != 'object') {
1440
- // We asked for a set from an array but it isnt' an array.
1441
- return false;
1442
- }
1443
-
1444
- // We need to enumerate the Object and grab the addresses from there.
1445
- let tmpObjectProperty = pObject[tmpObjectPropertyName];
1446
- let tmpObjectPropertyKeys = Object.keys(tmpObjectProperty);
1447
- // Managing the parent address is a bit more complex here -- the box will be added for each element.
1448
- tmpParentAddress = "".concat(tmpParentAddress).concat(tmpParentAddress.length > 0 ? '.' : '').concat(tmpObjectPropertyName);
1449
- // The container object is where we have the "Address":SOMEVALUE pairs
1450
- let tmpContainerObject = {};
1451
- for (let i = 0; i < tmpObjectPropertyKeys.length; i++) {
1452
- let tmpPropertyParentAddress = "".concat(tmpParentAddress, ".").concat(tmpObjectPropertyKeys[i]);
1453
- let tmpValue = this.getValueAtAddress(pObject[tmpObjectPropertyName][tmpObjectPropertyKeys[i]], tmpNewAddress, tmpPropertyParentAddress, tmpRootObject);
1454
-
1455
- // The filtering is complex but allows config-based metaprogramming directly from schema
1456
- let tmpKeepRecord = this.checkRecordFilters(pAddress, tmpValue);
1457
- if (tmpKeepRecord) {
1458
- tmpContainerObject["".concat(tmpPropertyParentAddress, ".").concat(tmpNewAddress)] = tmpValue;
1459
- }
1460
- }
1461
- return tmpContainerObject;
1462
- }
1463
-
1464
- // If there is an object property already named for the sub object, but it isn't an object
1465
- // then the system can't set the value in there. Error and abort!
1466
- if (tmpSubObjectName in pObject && typeof pObject[tmpSubObjectName] !== 'object') {
1467
- return undefined;
1468
- } else if (tmpSubObjectName in pObject) {
1469
- // If there is already a subobject pass that to the recursive thingy
1470
- // Continue to manage the parent address for recursion
1471
- tmpParentAddress = "".concat(tmpParentAddress).concat(tmpParentAddress.length > 0 ? '.' : '').concat(tmpSubObjectName);
1472
- return this.getValueAtAddress(pObject[tmpSubObjectName], tmpNewAddress, tmpParentAddress, tmpRootObject);
1473
- } else {
1474
- // Create a subobject and then pass that
1475
- // Continue to manage the parent address for recursion
1476
- tmpParentAddress = "".concat(tmpParentAddress).concat(tmpParentAddress.length > 0 ? '.' : '').concat(tmpSubObjectName);
1477
- pObject[tmpSubObjectName] = {};
1478
- return this.getValueAtAddress(pObject[tmpSubObjectName], tmpNewAddress, tmpParentAddress, tmpRootObject);
1479
- }
1480
- }
1481
- }
1482
- }
1483
- ;
1484
- module.exports = ManyfestObjectAddressResolverGetValue;
1485
- }, {
1486
- "../source/Manyfest-ParseConditionals.js": 12,
1487
- "./Manyfest-CleanWrapCharacters.js": 3,
1488
- "./Manyfest-LogToConsole.js": 5,
1489
- "./Manyfest-ObjectAddress-Parser.js": 9
1490
- }],
1491
- 9: [function (require, module, exports) {
1492
- // TODO: This is an inelegant solution to delay the rewrite of Manyfest.
1493
-
1494
- // Fable 3.0 has a service for data formatting that deals well with nested enclosures.
1495
-
1496
- // The Manyfest library predates fable 3.0 and the services structure of it, so the functions
1497
- // are more or less pure javascript and as functional as they can be made to be.
1498
-
1499
- // Until we shift Manyfest to be a fable service, these three functions were pulled out of
1500
- // fable to aid in parsing functions with nested enclosures.
1501
-
1502
- const DEFAULT_START_SYMBOL_MAP = {
1503
- '{': 0,
1504
- '[': 1,
1505
- '(': 2
1506
- };
1507
- const DEFAULT_END_SYMBOL_MAP = {
1508
- '}': 0,
1509
- ']': 1,
1510
- ')': 2
1511
- };
1512
- module.exports = {
1513
- /**
1514
- * Count the number of segments in a string, respecting enclosures
1515
- *
1516
- * @param {string} pString
1517
- * @param {string} [pSeparator]
1518
- * @param {Record<string, number>} [pEnclosureStartSymbolMap]
1519
- * @param {Record<string, number>} [pEnclosureEndSymbolMap]
1520
- *
1521
- * @return {number} - The number of segments in the string
1522
- */
1523
- stringCountSegments: (pString, pSeparator, pEnclosureStartSymbolMap, pEnclosureEndSymbolMap) => {
1524
- let tmpString = typeof pString == 'string' ? pString : '';
1525
- let tmpSeparator = typeof pSeparator == 'string' ? pSeparator : '.';
1526
- let tmpEnclosureStartSymbolMap = typeof pEnclosureStartSymbolMap == 'object' ? pEnclosureStartSymbolMap : DEFAULT_START_SYMBOL_MAP;
1527
- let tmpEnclosureEndSymbolMap = typeof pEnclosureEndSymbolMap == 'object' ? pEnclosureEndSymbolMap : DEFAULT_END_SYMBOL_MAP;
1528
- if (pString.length < 1) {
1529
- return 0;
1530
- }
1531
- let tmpSegmentCount = 1;
1532
- let tmpEnclosureStack = [];
1533
- for (let i = 0; i < tmpString.length; i++) {
1534
- // IF This is the start of a segment
1535
- if (tmpString[i] == tmpSeparator
1536
- // AND we are not in a nested portion of the string
1537
- && tmpEnclosureStack.length == 0) {
1538
- // Increment the segment count
1539
- tmpSegmentCount++;
1540
- }
1541
- // IF This is the start of an enclosure
1542
- else if (tmpString[i] in tmpEnclosureStartSymbolMap) {
1543
- // Add it to the stack!
1544
- tmpEnclosureStack.push(tmpEnclosureStartSymbolMap[tmpString[i]]);
1545
- }
1546
- // IF This is the end of an enclosure
1547
- else if (tmpString[i] in tmpEnclosureEndSymbolMap
1548
- // AND it matches the current nest level symbol
1549
- && tmpEnclosureEndSymbolMap[tmpString[i]] == tmpEnclosureStack[tmpEnclosureStack.length - 1]) {
1550
- // Pop it off the stack!
1551
- tmpEnclosureStack.pop();
1552
- }
1553
- }
1554
- return tmpSegmentCount;
1555
- },
1556
- /**
1557
- * Get the first segment in a string, respecting enclosures
1558
- *
1559
- * @param {string} pString
1560
- * @param {string} [pSeparator]
1561
- * @param {Record<string, number>} [pEnclosureStartSymbolMap]
1562
- * @param {Record<string, number>} [pEnclosureEndSymbolMap]
1563
- *
1564
- * @return {string} - the first segment in the string as a string
1565
- */
1566
- stringGetFirstSegment: (pString, pSeparator, pEnclosureStartSymbolMap, pEnclosureEndSymbolMap) => {
1567
- let tmpString = typeof pString == 'string' ? pString : '';
1568
- let tmpSeparator = typeof pSeparator == 'string' ? pSeparator : '.';
1569
- let tmpEnclosureStartSymbolMap = typeof pEnclosureStartSymbolMap == 'object' ? pEnclosureStartSymbolMap : DEFAULT_START_SYMBOL_MAP;
1570
- let tmpEnclosureEndSymbolMap = typeof pEnclosureEndSymbolMap == 'object' ? pEnclosureEndSymbolMap : DEFAULT_END_SYMBOL_MAP;
1571
- if (pString.length < 1) {
1572
- return '';
1573
- }
1574
- let tmpEnclosureStack = [];
1575
- for (let i = 0; i < tmpString.length; i++) {
1576
- // IF This is the start of a segment
1577
- if (tmpString[i] == tmpSeparator
1578
- // AND we are not in a nested portion of the string
1579
- && tmpEnclosureStack.length == 0) {
1580
- // Return the segment
1581
- return tmpString.substring(0, i);
1582
- }
1583
- // IF This is the start of an enclosure
1584
- else if (tmpString[i] in tmpEnclosureStartSymbolMap) {
1585
- // Add it to the stack!
1586
- tmpEnclosureStack.push(tmpEnclosureStartSymbolMap[tmpString[i]]);
1587
- }
1588
- // IF This is the end of an enclosure
1589
- else if (tmpString[i] in tmpEnclosureEndSymbolMap
1590
- // AND it matches the current nest level symbol
1591
- && tmpEnclosureEndSymbolMap[tmpString[i]] == tmpEnclosureStack[tmpEnclosureStack.length - 1]) {
1592
- // Pop it off the stack!
1593
- tmpEnclosureStack.pop();
1594
- }
1595
- }
1596
- return tmpString;
1597
- },
1598
- /**
1599
- * Get all segments in a string, respecting enclosures
1600
- *
1601
- * @param {string} pString
1602
- * @param {string} [pSeparator]
1603
- * @param {Record<string, number>} [pEnclosureStartSymbolMap]
1604
- * @param {Record<string, number>} [pEnclosureEndSymbolMap]
1605
- *
1606
- * @return {Array<string>} - the segments in the string as an array of strings
1607
- */
1608
- stringGetSegments: (pString, pSeparator, pEnclosureStartSymbolMap, pEnclosureEndSymbolMap) => {
1609
- let tmpString = typeof pString == 'string' ? pString : '';
1610
- let tmpSeparator = typeof pSeparator == 'string' ? pSeparator : '.';
1611
- let tmpEnclosureStartSymbolMap = typeof pEnclosureStartSymbolMap == 'object' ? pEnclosureStartSymbolMap : DEFAULT_START_SYMBOL_MAP;
1612
- let tmpEnclosureEndSymbolMap = typeof pEnclosureEndSymbolMap == 'object' ? pEnclosureEndSymbolMap : DEFAULT_END_SYMBOL_MAP;
1613
- let tmpCurrentSegmentStart = 0;
1614
- let tmpSegmentList = [];
1615
- if (pString.length < 1) {
1616
- return tmpSegmentList;
1617
- }
1618
- let tmpEnclosureStack = [];
1619
- for (let i = 0; i < tmpString.length; i++) {
1620
- // IF This is the start of a segment
1621
- if (tmpString[i] == tmpSeparator
1622
- // AND we are not in a nested portion of the string
1623
- && tmpEnclosureStack.length == 0) {
1624
- // Return the segment
1625
- tmpSegmentList.push(tmpString.substring(tmpCurrentSegmentStart, i));
1626
- tmpCurrentSegmentStart = i + 1;
1627
- }
1628
- // IF This is the start of an enclosure
1629
- else if (tmpString[i] in tmpEnclosureStartSymbolMap) {
1630
- // Add it to the stack!
1631
- tmpEnclosureStack.push(tmpEnclosureStartSymbolMap[tmpString[i]]);
1632
- }
1633
- // IF This is the end of an enclosure
1634
- else if (tmpString[i] in tmpEnclosureEndSymbolMap
1635
- // AND it matches the current nest level symbol
1636
- && tmpEnclosureEndSymbolMap[tmpString[i]] == tmpEnclosureStack[tmpEnclosureStack.length - 1]) {
1637
- // Pop it off the stack!
1638
- tmpEnclosureStack.pop();
1639
- }
1640
- }
1641
- if (tmpCurrentSegmentStart < tmpString.length) {
1642
- tmpSegmentList.push(tmpString.substring(tmpCurrentSegmentStart));
1643
- }
1644
- return tmpSegmentList;
1645
- },
1646
- /**
1647
- * Count the number of enclosures in a string based on the start and end characters.
1648
- *
1649
- * If no start or end characters are specified, it will default to parentheses. If the string is not a string, it will return 0.
1650
- *
1651
- * @param {string} pString
1652
- * @param {string} [pEnclosureStart]
1653
- * @param {string} [pEnclosureEnd]
1654
- * @returns the count of full in the string
1655
- */
1656
- stringCountEnclosures: (pString, pEnclosureStart, pEnclosureEnd) => {
1657
- let tmpString = typeof pString == 'string' ? pString : '';
1658
- let tmpEnclosureStart = typeof pEnclosureStart == 'string' ? pEnclosureStart : '(';
1659
- let tmpEnclosureEnd = typeof pEnclosureEnd == 'string' ? pEnclosureEnd : ')';
1660
- let tmpEnclosureCount = 0;
1661
- let tmpEnclosureDepth = 0;
1662
- for (let i = 0; i < tmpString.length; i++) {
1663
- // This is the start of an enclosure
1664
- if (tmpString[i] == tmpEnclosureStart) {
1665
- if (tmpEnclosureDepth == 0) {
1666
- tmpEnclosureCount++;
1667
- }
1668
- tmpEnclosureDepth++;
1669
- } else if (tmpString[i] == tmpEnclosureEnd) {
1670
- tmpEnclosureDepth--;
1671
- }
1672
- }
1673
- return tmpEnclosureCount;
1674
- },
1675
- /**
1676
- * Get the value of the enclosure at the specified index.
1677
- *
1678
- * If the index is not a number, it will default to 0. If the string is not a string, it will return an empty string. If the enclosure is not found, it will return an empty string. If the enclosure
1679
- *
1680
- * @param {string} pString
1681
- * @param {number} pEnclosureIndexToGet
1682
- * @param {string} [pEnclosureStart]
1683
- * @param {string} [pEnclosureEnd]
1684
- *
1685
- * @return {string} - The value of the enclosure at the specified index
1686
- */
1687
- stringGetEnclosureValueByIndex: (pString, pEnclosureIndexToGet, pEnclosureStart, pEnclosureEnd) => {
1688
- let tmpString = typeof pString == 'string' ? pString : '';
1689
- let tmpEnclosureIndexToGet = typeof pEnclosureIndexToGet == 'number' ? pEnclosureIndexToGet : 0;
1690
- let tmpEnclosureStart = typeof pEnclosureStart == 'string' ? pEnclosureStart : '(';
1691
- let tmpEnclosureEnd = typeof pEnclosureEnd == 'string' ? pEnclosureEnd : ')';
1692
- let tmpEnclosureCount = 0;
1693
- let tmpEnclosureDepth = 0;
1694
- let tmpMatchedEnclosureIndex = false;
1695
- let tmpEnclosedValueStartIndex = 0;
1696
- let tmpEnclosedValueEndIndex = 0;
1697
- for (let i = 0; i < tmpString.length; i++) {
1698
- // This is the start of an enclosure
1699
- if (tmpString[i] == tmpEnclosureStart) {
1700
- tmpEnclosureDepth++;
1701
-
1702
- // Only count enclosures at depth 1, but still this parses both pairs of all of them.
1703
- if (tmpEnclosureDepth == 1) {
1704
- tmpEnclosureCount++;
1705
- if (tmpEnclosureIndexToGet == tmpEnclosureCount - 1) {
1706
- // This is the start of *the* enclosure
1707
- tmpMatchedEnclosureIndex = true;
1708
- tmpEnclosedValueStartIndex = i;
1709
- }
1710
- }
1711
- }
1712
- // This is the end of an enclosure
1713
- else if (tmpString[i] == tmpEnclosureEnd) {
1714
- tmpEnclosureDepth--;
1715
-
1716
- // Again, only count enclosures at depth 1, but still this parses both pairs of all of them.
1717
- if (tmpEnclosureDepth == 0 && tmpMatchedEnclosureIndex && tmpEnclosedValueEndIndex <= tmpEnclosedValueStartIndex) {
1718
- tmpEnclosedValueEndIndex = i;
1719
- tmpMatchedEnclosureIndex = false;
1720
- }
1721
- }
1722
- }
1723
- if (tmpEnclosureCount <= tmpEnclosureIndexToGet) {
1724
- // Return an empty string if the enclosure is not found
1725
- return '';
1726
- }
1727
- if (tmpEnclosedValueEndIndex > 0 && tmpEnclosedValueEndIndex > tmpEnclosedValueStartIndex) {
1728
- return tmpString.substring(tmpEnclosedValueStartIndex + 1, tmpEnclosedValueEndIndex);
1729
- } else {
1730
- return tmpString.substring(tmpEnclosedValueStartIndex + 1);
1731
- }
1732
- }
1733
- };
1734
- }, {}],
1735
- 10: [function (require, module, exports) {
1736
- /**
1737
- * @author <steven@velozo.com>
1738
- */
1739
- let libSimpleLog = require('./Manyfest-LogToConsole.js');
1740
- let fCleanWrapCharacters = require('./Manyfest-CleanWrapCharacters.js');
1741
-
1742
- /**
1743
- * Object Address Resolver - SetValue
1744
- *
1745
- * IMPORTANT NOTE: This code is intentionally more verbose than necessary, to
1746
- * be extremely clear what is going on in the recursion for
1747
- * each of the three address resolution functions.
1748
- *
1749
- * Although there is some opportunity to repeat ourselves a
1750
- * bit less in this codebase (e.g. with detection of arrays
1751
- * versus objects versus direct properties), it can make
1752
- * debugging.. challenging. The minified version of the code
1753
- * optimizes out almost anything repeated in here. So please
1754
- * be kind and rewind... meaning please keep the codebase less
1755
- * terse and more verbose so humans can comprehend it.
1756
- *
1757
- *
1758
- * @class ManyfestObjectAddressSetValue
1759
- */
1760
- class ManyfestObjectAddressSetValue {
1761
- /**
1762
- * @param {function} [pInfoLog] - (optional) A logging function for info messages
1763
- * @param {function} [pErrorLog] - (optional) A logging function for error messages
1764
- */
1765
- constructor(pInfoLog, pErrorLog) {
1766
- // Wire in logging
1767
- this.logInfo = typeof pInfoLog == 'function' ? pInfoLog : libSimpleLog;
1768
- this.logError = typeof pErrorLog == 'function' ? pErrorLog : libSimpleLog;
1769
- this.cleanWrapCharacters = fCleanWrapCharacters;
1770
- }
1771
-
1772
- /**
1773
- * Set the value of an element at an address
1774
- *
1775
- * @param {object} pObject - The object to set the value in
1776
- * @param {string} pAddress - The address to set the value at
1777
- * @param {any} pValue - The value to set at the address
1778
- *
1779
- * @return {boolean} True if the value was set, false otherwise
1780
- */
1781
- setValueAtAddress(pObject, pAddress, pValue) {
1782
- // Make sure pObject is an object
1783
- if (typeof pObject != 'object') return false;
1784
- // Make sure pAddress is a string
1785
- if (typeof pAddress != 'string') return false;
1786
- let tmpSeparatorIndex = pAddress.indexOf('.');
1787
- if (tmpSeparatorIndex == -1) {
1788
- // Check if it's a boxed property
1789
- let tmpBracketStartIndex = pAddress.indexOf('[');
1790
- let tmpBracketStopIndex = pAddress.indexOf(']');
1791
- // Boxed elements look like this:
1792
- // MyValues[10]
1793
- // MyValues['Name']
1794
- // MyValues["Age"]
1795
- // MyValues[`Cost`]
1796
- //
1797
- // When we are passed SomeObject["Name"] this code below recurses as if it were SomeObject.Name
1798
- // The requirements to detect a boxed element are:
1799
- // 1) The start bracket is after character 0
1800
- if (tmpBracketStartIndex > 0
1801
- // 2) The end bracket has something between them
1802
- && tmpBracketStopIndex > tmpBracketStartIndex
1803
- // 3) There is data
1804
- && tmpBracketStopIndex - tmpBracketStartIndex > 1) {
1805
- // The "Name" of the Object contained too the left of the bracket
1806
- let tmpBoxedPropertyName = pAddress.substring(0, tmpBracketStartIndex).trim();
1807
-
1808
- // The "Reference" to the property within it, either an array element or object property
1809
- let tmpBoxedPropertyReference = pAddress.substring(tmpBracketStartIndex + 1, tmpBracketStopIndex).trim();
1810
- // Attempt to parse the reference as a number, which will be used as an array element
1811
- let tmpBoxedPropertyNumber = parseInt(tmpBoxedPropertyReference, 10);
1812
- let tmpIndexIsNumeric = !isNaN(tmpBoxedPropertyNumber);
1813
- if (pObject[tmpBoxedPropertyName] == null) {
1814
- if (tmpIndexIsNumeric) {
1815
- pObject[tmpBoxedPropertyName] = [];
1816
- } else {
1817
- pObject[tmpBoxedPropertyName] = {};
1818
- }
1819
- }
1820
-
1821
- // If the subproperty doesn't test as a proper Object, none of the rest of this is possible.
1822
- // This is a rare case where Arrays testing as Objects is useful
1823
- if (typeof pObject[tmpBoxedPropertyName] !== 'object') {
1824
- return false;
1825
- }
1826
-
1827
- // Guard: If the referrant is a number and the boxed property is not an array, or vice versa, return undefined.
1828
- // This seems confusing to me at first read, so explaination:
1829
- // Is the Boxed Object an Array? TRUE
1830
- // And is the Reference inside the boxed Object not a number? TRUE
1831
- // --> So when these are in agreement, it's an impossible access state
1832
- if (Array.isArray(pObject[tmpBoxedPropertyName]) == isNaN(tmpBoxedPropertyNumber)) {
1833
- return false;
1834
- }
1835
-
1836
- // 4) If the middle part is *only* a number (no single, double or backtick quotes) it is an array element,
1837
- // otherwise we will try to treat it as a dynamic object property.
1838
- if (isNaN(tmpBoxedPropertyNumber)) {
1839
- // This isn't a number ... let's treat it as a dynamic object property.
1840
- // We would expect the property to be wrapped in some kind of quotes so strip them
1841
- tmpBoxedPropertyReference = this.cleanWrapCharacters('"', tmpBoxedPropertyReference);
1842
- tmpBoxedPropertyReference = this.cleanWrapCharacters('`', tmpBoxedPropertyReference);
1843
- tmpBoxedPropertyReference = this.cleanWrapCharacters("'", tmpBoxedPropertyReference);
1844
- if (!(tmpBoxedPropertyReference in pObject[tmpBoxedPropertyName])) {
1845
- // If the subobject doesn't exist, create it
1846
- pObject[tmpBoxedPropertyName][tmpBoxedPropertyReference] = {};
1847
- }
1848
-
1849
- // Return the value in the property
1850
- //TODO: For cases where we have chained [][] properties, this needs to recurse somehow
1851
- pObject[tmpBoxedPropertyName][tmpBoxedPropertyReference] = pValue;
1852
- return true;
1853
- } else {
1854
- while (pObject[tmpBoxedPropertyName].length < tmpBoxedPropertyNumber + 1) {
1855
- // If the subobject doesn't exist, create it
1856
- pObject[tmpBoxedPropertyName].push({});
1857
- }
1858
- pObject[tmpBoxedPropertyName][tmpBoxedPropertyNumber] = pValue;
1859
- return true;
1860
- }
1861
- } else {
1862
- // Now is the time in recursion to set the value in the object
1863
- pObject[pAddress] = pValue;
1864
- return true;
1865
- }
1866
- } else {
1867
- let tmpSubObjectName = pAddress.substring(0, tmpSeparatorIndex);
1868
- let tmpNewAddress = pAddress.substring(tmpSeparatorIndex + 1);
1869
-
1870
- // Test if the tmpNewAddress is an array or object
1871
- // Check if it's a boxed property
1872
- let tmpBracketStartIndex = tmpSubObjectName.indexOf('[');
1873
- let tmpBracketStopIndex = tmpSubObjectName.indexOf(']');
1874
- // Boxed elements look like this:
1875
- // MyValues[42]
1876
- // MyValues['Color']
1877
- // MyValues["Weight"]
1878
- // MyValues[`Diameter`]
1879
- //
1880
- // When we are passed SomeObject["Name"] this code below recurses as if it were SomeObject.Name
1881
- // The requirements to detect a boxed element are:
1882
- // 1) The start bracket is after character 0
1883
- if (tmpBracketStartIndex > 0
1884
- // 2) The end bracket has something between them
1885
- && tmpBracketStopIndex > tmpBracketStartIndex
1886
- // 3) There is data
1887
- && tmpBracketStopIndex - tmpBracketStartIndex > 1) {
1888
- let tmpBoxedPropertyName = tmpSubObjectName.substring(0, tmpBracketStartIndex).trim();
1889
- let tmpBoxedPropertyReference = tmpSubObjectName.substring(tmpBracketStartIndex + 1, tmpBracketStopIndex).trim();
1890
- let tmpBoxedPropertyNumber = parseInt(tmpBoxedPropertyReference, 10);
1891
- let tmpIndexIsNumeric = !isNaN(tmpBoxedPropertyNumber);
1892
-
1893
- //if (typeof(pObject[tmpBoxedPropertyName]) !== 'object')
1894
- if (pObject[tmpBoxedPropertyName] == null) {
1895
- if (tmpIndexIsNumeric) {
1896
- pObject[tmpBoxedPropertyName] = [];
1897
- } else {
1898
- pObject[tmpBoxedPropertyName] = {};
1899
- }
1900
- }
1901
-
1902
- // Guard: If the referrant is a number and the boxed property is not an array, or vice versa, return undefined.
1903
- // This seems confusing to me at first read, so explaination:
1904
- // Is the Boxed Object an Array? TRUE
1905
- // And is the Reference inside the boxed Object not a number? TRUE
1906
- // --> So when these are in agreement, it's an impossible access state
1907
- // This could be a failure in the recursion chain because they passed something like this in:
1908
- // StudentData.Sections.Algebra.Students[1].Tardy
1909
- // BUT
1910
- // StudentData.Sections.Algebra.Students is an object, so the [1].Tardy is not possible to access
1911
- // This could be a failure in the recursion chain because they passed something like this in:
1912
- // StudentData.Sections.Algebra.Students["JaneDoe"].Grade
1913
- // BUT
1914
- // StudentData.Sections.Algebra.Students is an array, so the ["JaneDoe"].Grade is not possible to access
1915
- // TODO: Should this be an error or something? Should we keep a log of failures like this?
1916
- if (Array.isArray(pObject[tmpBoxedPropertyName]) != tmpIndexIsNumeric) {
1917
- return false;
1918
- }
1919
-
1920
- //This is a bracketed value
1921
- // 4) If the middle part is *only* a number (no single, double or backtick quotes) it is an array element,
1922
- // otherwise we will try to reat it as a dynamic object property.
1923
- if (isNaN(tmpBoxedPropertyNumber)) {
1924
- // This isn't a number ... let's treat it as a dynanmic object property.
1925
- tmpBoxedPropertyReference = this.cleanWrapCharacters('"', tmpBoxedPropertyReference);
1926
- tmpBoxedPropertyReference = this.cleanWrapCharacters('`', tmpBoxedPropertyReference);
1927
- tmpBoxedPropertyReference = this.cleanWrapCharacters("'", tmpBoxedPropertyReference);
1928
- if (!(tmpBoxedPropertyReference in pObject[tmpBoxedPropertyName])) {
1929
- // If the subobject doesn't exist, create it
1930
- pObject[tmpBoxedPropertyName][tmpBoxedPropertyReference] = {};
1931
- }
1932
-
1933
- // Recurse directly into the subobject
1934
- return this.setValueAtAddress(pObject[tmpBoxedPropertyName][tmpBoxedPropertyReference], tmpNewAddress, pValue);
1935
- } else {
1936
- while (pObject[tmpBoxedPropertyName].length < tmpBoxedPropertyNumber + 1) {
1937
- // If the subobject doesn't exist, create it
1938
- pObject[tmpBoxedPropertyName].push({});
1939
- }
1940
-
1941
- // We parsed a valid number out of the boxed property name, so recurse into the array
1942
- return this.setValueAtAddress(pObject[tmpBoxedPropertyName][tmpBoxedPropertyNumber], tmpNewAddress, pValue);
1943
- }
1944
- }
1945
-
1946
- // If there is an object property already named for the sub object, but it isn't an object
1947
- // then the system can't set the value in there. Error and abort!
1948
- if (tmpSubObjectName in pObject && typeof pObject[tmpSubObjectName] !== 'object') {
1949
- if (!('__ERROR' in pObject)) pObject['__ERROR'] = {};
1950
- // Put it in an error object so data isn't lost
1951
- pObject['__ERROR'][pAddress] = pValue;
1952
- return false;
1953
- } else if (tmpSubObjectName in pObject) {
1954
- // If there is already a subobject pass that to the recursive thingy
1955
- return this.setValueAtAddress(pObject[tmpSubObjectName], tmpNewAddress, pValue);
1956
- } else {
1957
- // Create a subobject and then pass that
1958
- pObject[tmpSubObjectName] = {};
1959
- return this.setValueAtAddress(pObject[tmpSubObjectName], tmpNewAddress, pValue);
1960
- }
1961
- }
1962
- }
1963
- }
1964
- ;
1965
- module.exports = ManyfestObjectAddressSetValue;
1966
- }, {
1967
- "./Manyfest-CleanWrapCharacters.js": 3,
1968
- "./Manyfest-LogToConsole.js": 5
1969
- }],
1970
- 11: [function (require, module, exports) {
1971
- /**
1972
- * @author <steven@velozo.com>
1973
- */
1974
- let libSimpleLog = require('./Manyfest-LogToConsole.js');
1975
-
1976
- /**
1977
- * Object Address Generation
1978
- *
1979
- * Automagically generate addresses and properties based on a passed-in object,
1980
- * to be used for easy creation of schemas. Meant to simplify the lives of
1981
- * developers wanting to create schemas without typing a bunch of stuff.
1982
- *
1983
- * IMPORTANT NOTE: This code is intentionally more verbose than necessary, to
1984
- * be extremely clear what is going on in the recursion for
1985
- * each of the three address resolution functions.
1986
- *
1987
- * Although there is some opportunity to repeat ourselves a
1988
- * bit less in this codebase (e.g. with detection of arrays
1989
- * versus objects versus direct properties), it can make
1990
- * debugging.. challenging. The minified version of the code
1991
- * optimizes out almost anything repeated in here. So please
1992
- * be kind and rewind... meaning please keep the codebase less
1993
- * terse and more verbose so humans can comprehend it.
1994
- *
1995
- *
1996
- * @class ManyfestObjectAddressGeneration
1997
- */
1998
- class ManyfestObjectAddressGeneration {
1999
- /**
2000
- * @param {function} [pInfoLog] - (optional) A logging function for info messages
2001
- * @param {function} [pErrorLog] - (optional) A logging function for error messages
2002
- */
2003
- constructor(pInfoLog, pErrorLog) {
2004
- // Wire in logging
2005
- this.logInfo = typeof pInfoLog == 'function' ? pInfoLog : libSimpleLog;
2006
- this.logError = typeof pErrorLog == 'function' ? pErrorLog : libSimpleLog;
2007
- }
2008
-
2009
- /**
2010
- * generateAddressses
2011
- *
2012
- * This flattens an object into a set of key:value pairs for *EVERY SINGLE
2013
- * POSSIBLE ADDRESS* in the object. It can get ... really insane really
2014
- * quickly. This is not meant to be used directly to generate schemas, but
2015
- * instead as a starting point for scripts or UIs.
2016
- *
2017
- * This will return a mega set of key:value pairs with all possible schema
2018
- * permutations and default values (when not an object) and everything else.
2019
- *
2020
- * @param {any} pObject - The object to generate addresses for
2021
- * @param {string} [pBaseAddress] - (optional) The base address to start from
2022
- * @param {object} [pSchema] - (optional) The schema object to append to
2023
- *
2024
- * @return {object} The generated schema object
2025
- */
2026
- generateAddressses(pObject, pBaseAddress, pSchema) {
2027
- let tmpBaseAddress = typeof pBaseAddress == 'string' ? pBaseAddress : '';
2028
- let tmpSchema = typeof pSchema == 'object' ? pSchema : {};
2029
- let tmpObjectType = typeof pObject;
2030
- let tmpSchemaObjectEntry = {
2031
- Address: tmpBaseAddress,
2032
- Hash: tmpBaseAddress,
2033
- Name: tmpBaseAddress,
2034
- // This is so scripts and UI controls can force a developer to opt-in.
2035
- InSchema: false
2036
- };
2037
- if (tmpObjectType == 'object' && pObject == null) {
2038
- tmpObjectType = 'undefined';
2039
- }
2040
- switch (tmpObjectType) {
2041
- case 'string':
2042
- tmpSchemaObjectEntry.DataType = 'String';
2043
- tmpSchemaObjectEntry.Default = pObject;
2044
- tmpSchema[tmpBaseAddress] = tmpSchemaObjectEntry;
2045
- break;
2046
- case 'number':
2047
- case 'bigint':
2048
- tmpSchemaObjectEntry.DataType = 'Number';
2049
- tmpSchemaObjectEntry.Default = pObject;
2050
- tmpSchema[tmpBaseAddress] = tmpSchemaObjectEntry;
2051
- break;
2052
- case 'undefined':
2053
- tmpSchemaObjectEntry.DataType = 'Any';
2054
- tmpSchemaObjectEntry.Default = pObject;
2055
- tmpSchema[tmpBaseAddress] = tmpSchemaObjectEntry;
2056
- break;
2057
- case 'object':
2058
- if (Array.isArray(pObject)) {
2059
- tmpSchemaObjectEntry.DataType = 'Array';
2060
- if (tmpBaseAddress != '') {
2061
- tmpSchema[tmpBaseAddress] = tmpSchemaObjectEntry;
2062
- }
2063
- for (let i = 0; i < pObject.length; i++) {
2064
- this.generateAddressses(pObject[i], "".concat(tmpBaseAddress, "[").concat(i, "]"), tmpSchema);
2065
- }
2066
- } else {
2067
- tmpSchemaObjectEntry.DataType = 'Object';
2068
- if (tmpBaseAddress != '') {
2069
- tmpSchema[tmpBaseAddress] = tmpSchemaObjectEntry;
2070
- tmpBaseAddress += '.';
2071
- }
2072
- let tmpObjectProperties = Object.keys(pObject);
2073
- for (let i = 0; i < tmpObjectProperties.length; i++) {
2074
- this.generateAddressses(pObject[tmpObjectProperties[i]], "".concat(tmpBaseAddress).concat(tmpObjectProperties[i]), tmpSchema);
2075
- }
2076
- }
2077
- break;
2078
- case 'symbol':
2079
- case 'function':
2080
- // Symbols and functions neither recurse nor get added to the schema
2081
- break;
2082
- }
2083
- return tmpSchema;
2084
- }
2085
- }
2086
- ;
2087
- module.exports = ManyfestObjectAddressGeneration;
2088
- }, {
2089
- "./Manyfest-LogToConsole.js": 5
2090
- }],
2091
- 12: [function (require, module, exports) {
2092
- // Given a string, parse out any conditional expressions and set whether or not to keep the record.
2093
- //
2094
- // For instance:
2095
- // 'files[]<<~?format,==,Thumbnail?~>>'
2096
- // 'files[]<<~?format,==,Metadata?~>>'
2097
- // 'files[]<<~?size,>,4000?~>>'
2098
- //
2099
- // The wrapping parts are the <<~? and ?~>> megabrackets.
2100
- //
2101
- // The function does not need to alter the string -- just check the conditionals within.
2102
-
2103
- // TODO: Consider making this an es6 class
2104
-
2105
- // Let's use indexOf since it is apparently the fastest.
2106
- const _ConditionalStanzaStart = '<<~?';
2107
- const _ConditionalStanzaStartLength = _ConditionalStanzaStart.length;
2108
- const _ConditionalStanzaEnd = '?~>>';
2109
- const _ConditionalStanzaEndLength = _ConditionalStanzaEnd.length;
2110
-
2111
- // Ugh dependency injection. Can't wait to make these all fable services.
2112
- //let libObjectAddressCheckAddressExists = new (require('./Manyfest-ObjectAddress-CheckAddressExists.js'))();
2113
-
2114
- // Test the condition of a value in a record
2115
- const testCondition = (pManyfest, pRecord, pSearchAddress, pSearchComparator, pValue) => {
2116
- switch (pSearchComparator) {
2117
- case 'TRUE':
2118
- return pManyfest.getValueAtAddress(pRecord, pSearchAddress) === true;
2119
- break;
2120
- case 'FALSE':
2121
- return pManyfest.getValueAtAddress(pRecord, pSearchAddress) === false;
2122
- break;
2123
- case 'LNGT':
2124
- case 'LENGTH_GREATER_THAN':
2125
- switch (typeof pManyfest.getValueAtAddress(pRecord, pSearchAddress)) {
2126
- case 'string':
2127
- return pManyfest.getValueAtAddress(pRecord, pSearchAddress).length > pValue;
2128
- break;
2129
- case 'object':
2130
- return pManyfest.getValueAtAddress(pRecord, pSearchAddress).length > pValue;
2131
- break;
2132
- default:
2133
- return false;
2134
- break;
2135
- }
2136
- break;
2137
- case 'LNLT':
2138
- case 'LENGTH_LESS_THAN':
2139
- switch (typeof pManyfest.getValueAtAddress(pRecord, pSearchAddress)) {
2140
- case 'string':
2141
- return pManyfest.getValueAtAddress(pRecord, pSearchAddress).length < pValue;
2142
- break;
2143
- case 'object':
2144
- return pManyfest.getValueAtAddress(pRecord, pSearchAddress).length < pValue;
2145
- break;
2146
- default:
2147
- return false;
2148
- break;
2149
- }
2150
- break;
2151
- // TODO: Welcome to dependency hell. This fixes itself when we move to fable services.
2152
- // case 'EX':
2153
- // case 'EXISTS':
2154
- // return libObjectAddressCheckAddressExists.checkAddressExists(pRecord, pSearchAddress);
2155
- // break;
2156
- // case 'DNEX':
2157
- // case 'DOES_NOT_EXIST':
2158
- // return !libObjectAddressCheckAddressExists.checkAddressExists(pRecord, pSearchAddress);
2159
- // break;
2160
- case '!=':
2161
- return pManyfest.getValueAtAddress(pRecord, pSearchAddress) != pValue;
2162
- break;
2163
- case '<':
2164
- return pManyfest.getValueAtAddress(pRecord, pSearchAddress) < pValue;
2165
- break;
2166
- case '>':
2167
- return pManyfest.getValueAtAddress(pRecord, pSearchAddress) > pValue;
2168
- break;
2169
- case '<=':
2170
- return pManyfest.getValueAtAddress(pRecord, pSearchAddress) <= pValue;
2171
- break;
2172
- case '>=':
2173
- return pManyfest.getValueAtAddress(pRecord, pSearchAddress) >= pValue;
2174
- break;
2175
- case '===':
2176
- return pManyfest.getValueAtAddress(pRecord, pSearchAddress) === pValue;
2177
- break;
2178
- case '==':
2179
- default:
2180
- return pManyfest.getValueAtAddress(pRecord, pSearchAddress) == pValue;
2181
- break;
2182
- }
2183
- };
2184
- const parseConditionals = (pManyfest, pAddress, pRecord) => {
2185
- let tmpKeepRecord = true;
2186
-
2187
- /*
2188
- Algorithm is simple:
2189
- 1. Enuerate start points
2190
- 2. Find stop points within each start point
2191
- 3. Check the conditional
2192
- */
2193
- let tmpStartIndex = pAddress.indexOf(_ConditionalStanzaStart);
2194
- while (tmpStartIndex != -1) {
2195
- let tmpStopIndex = pAddress.indexOf(_ConditionalStanzaEnd, tmpStartIndex + _ConditionalStanzaStartLength);
2196
- if (tmpStopIndex != -1) {
2197
- let tmpMagicComparisonPatternSet = pAddress.substring(tmpStartIndex + _ConditionalStanzaStartLength, tmpStopIndex).split(',');
2198
-
2199
- // The address to search for
2200
- let tmpSearchAddress = tmpMagicComparisonPatternSet[0];
2201
-
2202
- // The copmparison expression (EXISTS as default)
2203
- let tmpSearchComparator = 'EXISTS';
2204
- if (tmpMagicComparisonPatternSet.length > 1) {
2205
- tmpSearchComparator = tmpMagicComparisonPatternSet[1];
2206
- }
2207
-
2208
- // The value to search for
2209
- let tmpSearchValue = false;
2210
- if (tmpMagicComparisonPatternSet.length > 2) {
2211
- tmpSearchValue = tmpMagicComparisonPatternSet[2];
2212
- }
2213
-
2214
- // Process the piece
2215
- tmpKeepRecord = tmpKeepRecord && testCondition(pManyfest, pRecord, tmpSearchAddress, tmpSearchComparator, tmpSearchValue);
2216
- tmpStartIndex = pAddress.indexOf(_ConditionalStanzaStart, tmpStopIndex + _ConditionalStanzaEndLength);
2217
- } else {
2218
- tmpStartIndex = -1;
2219
- }
2220
- }
2221
- return tmpKeepRecord;
2222
- };
2223
- module.exports = parseConditionals;
2224
- }, {}],
2225
- 13: [function (require, module, exports) {
2226
- /**
2227
- * @author <steven@velozo.com>
2228
- */
2229
- let libSimpleLog = require('./Manyfest-LogToConsole.js');
2230
-
2231
- /**
2232
- * Schema Manipulation Functions
2233
- *
2234
- * @class ManyfestSchemaManipulation
2235
- */
2236
- class ManyfestSchemaManipulation {
2237
- /**
2238
- * @param {function} [pInfoLog] - (optional) A logging function for info messages
2239
- * @param {function} [pErrorLog] - (optional) A logging function for error messages
2240
- */
2241
- constructor(pInfoLog, pErrorLog) {
2242
- // Wire in logging
2243
- this.logInfo = typeof pInfoLog === 'function' ? pInfoLog : libSimpleLog;
2244
- this.logError = typeof pErrorLog === 'function' ? pErrorLog : libSimpleLog;
2245
- }
2246
-
2247
- /**
2248
- * This translates the default address mappings to something different.
2249
- *
2250
- * For instance you can pass in manyfest schema descriptor object:
2251
- * {
2252
- * "Address.Of.a": { "Hash": "a", "Type": "Number" },
2253
- * "Address.Of.b": { "Hash": "b", "Type": "Number" }
2254
- * }
2255
- *
2256
- *
2257
- * And then an address mapping (basically a Hash->Address map)
2258
- * {
2259
- * "a": "New.Address.Of.a",
2260
- * "b": "New.Address.Of.b"
2261
- * }
2262
- *
2263
- * NOTE: This mutates the schema object permanently, altering the base hash.
2264
- * If there is a collision with an existing address, it can lead to overwrites.
2265
- * TODO: Discuss what should happen on collisions.
2266
- *
2267
- * @param {object} pManyfestSchemaDescriptors - The manyfest schema descriptors to resolve address mappings for
2268
- * @param {object} pAddressMapping - The address mapping object to use for remapping
2269
- *
2270
- * @return {boolean} True if successful, false if there was an error
2271
- */
2272
- resolveAddressMappings(pManyfestSchemaDescriptors, pAddressMapping) {
2273
- if (typeof pManyfestSchemaDescriptors != 'object') {
2274
- this.logError("Attempted to resolve address mapping but the descriptor was not an object.");
2275
- return false;
2276
- }
2277
- if (typeof pAddressMapping != 'object') {
2278
- // No mappings were passed in
2279
- return true;
2280
- }
2281
-
2282
- // Get the arrays of both the schema definition and the hash mapping
2283
- let tmpManyfestAddresses = Object.keys(pManyfestSchemaDescriptors);
2284
- let tmpHashMapping = {};
2285
- tmpManyfestAddresses.forEach(pAddress => {
2286
- if ('Hash' in pManyfestSchemaDescriptors[pAddress]) {
2287
- tmpHashMapping[pManyfestSchemaDescriptors[pAddress].Hash] = pAddress;
2288
- }
2289
- });
2290
- let tmpAddressMappingSet = Object.keys(pAddressMapping);
2291
- tmpAddressMappingSet.forEach(pInputAddress => {
2292
- let tmpNewDescriptorAddress = pAddressMapping[pInputAddress];
2293
- let tmpOldDescriptorAddress = null;
2294
- let tmpDescriptor;
2295
-
2296
- // See if there is a matching descriptor either by Address directly or Hash
2297
- if (pInputAddress in pManyfestSchemaDescriptors) {
2298
- tmpOldDescriptorAddress = pInputAddress;
2299
- } else if (pInputAddress in tmpHashMapping) {
2300
- tmpOldDescriptorAddress = tmpHashMapping[pInputAddress];
2301
- }
2302
-
2303
- // If there was a matching descriptor in the manifest, store it in the temporary descriptor
2304
- if (tmpOldDescriptorAddress) {
2305
- tmpDescriptor = pManyfestSchemaDescriptors[tmpOldDescriptorAddress];
2306
- delete pManyfestSchemaDescriptors[tmpOldDescriptorAddress];
2307
- } else {
2308
- // Create a new descriptor! Map it to the input address.
2309
- tmpDescriptor = {
2310
- Hash: pInputAddress
2311
- };
2312
- }
2313
-
2314
- // Now re-add the descriptor to the manyfest schema
2315
- pManyfestSchemaDescriptors[tmpNewDescriptorAddress] = tmpDescriptor;
2316
- });
2317
- return true;
2318
- }
2319
-
2320
- /**
2321
- * @param {object} pManyfestSchemaDescriptors - The manyfest schema descriptors to resolve address mappings for
2322
- * @param {object} pAddressMapping - The address mapping object to use for remapping
2323
- *
2324
- * @return {object} A new object containing the remapped schema descriptors
2325
- */
2326
- safeResolveAddressMappings(pManyfestSchemaDescriptors, pAddressMapping) {
2327
- // This returns the descriptors as a new object, safely remapping without mutating the original schema Descriptors
2328
- let tmpManyfestSchemaDescriptors = JSON.parse(JSON.stringify(pManyfestSchemaDescriptors));
2329
- this.resolveAddressMappings(tmpManyfestSchemaDescriptors, pAddressMapping);
2330
- return tmpManyfestSchemaDescriptors;
2331
- }
2332
-
2333
- /**
2334
- * @param {object} pManyfestSchemaDescriptorsDestination - The destination manyfest schema descriptors
2335
- * @param {object} pManyfestSchemaDescriptorsSource - The source manyfest schema descriptors
2336
- *
2337
- * @return {object} A new object containing the merged schema descriptors
2338
- */
2339
- mergeAddressMappings(pManyfestSchemaDescriptorsDestination, pManyfestSchemaDescriptorsSource) {
2340
- if (typeof pManyfestSchemaDescriptorsSource != 'object' || typeof pManyfestSchemaDescriptorsDestination != 'object') {
2341
- this.logError("Attempted to merge two schema descriptors but both were not objects.");
2342
- return false;
2343
- }
2344
- let tmpSource = JSON.parse(JSON.stringify(pManyfestSchemaDescriptorsSource));
2345
- let tmpNewManyfestSchemaDescriptors = JSON.parse(JSON.stringify(pManyfestSchemaDescriptorsDestination));
2346
-
2347
- // The first passed-in set of descriptors takes precedence.
2348
- let tmpDescriptorAddresses = Object.keys(tmpSource);
2349
- tmpDescriptorAddresses.forEach(pDescriptorAddress => {
2350
- if (!(pDescriptorAddress in tmpNewManyfestSchemaDescriptors)) {
2351
- tmpNewManyfestSchemaDescriptors[pDescriptorAddress] = tmpSource[pDescriptorAddress];
2352
- }
2353
- });
2354
- return tmpNewManyfestSchemaDescriptors;
2355
- }
2356
- }
2357
- module.exports = ManyfestSchemaManipulation;
2358
- }, {
2359
- "./Manyfest-LogToConsole.js": 5
2360
- }],
2361
- 14: [function (require, module, exports) {
2362
- /**
2363
- * @author <steven@velozo.com>
2364
- */
2365
- const libFableServiceProviderBase = require('fable-serviceproviderbase');
2366
- let libSimpleLog = require('./Manyfest-LogToConsole.js');
2367
- let libHashTranslation = require('./Manyfest-HashTranslation.js');
2368
- let libObjectAddressCheckAddressExists = require('./Manyfest-ObjectAddress-CheckAddressExists.js');
2369
- let libObjectAddressGetValue = require('./Manyfest-ObjectAddress-GetValue.js');
2370
- let libObjectAddressSetValue = require('./Manyfest-ObjectAddress-SetValue.js');
2371
- let libObjectAddressDeleteValue = require('./Manyfest-ObjectAddress-DeleteValue.js');
2372
- let libObjectAddressGeneration = require('./Manyfest-ObjectAddressGeneration.js');
2373
- let libSchemaManipulation = require('./Manyfest-SchemaManipulation.js');
2374
- const _DefaultConfiguration = {
2375
- Scope: 'DEFAULT',
2376
- Descriptors: {}
2377
- };
2378
-
2379
- /**
2380
- * @typedef {{
2381
- * Hash?: string,
2382
- * Name?: string,
2383
- * DataType?: string,
2384
- * Required?: boolean,
2385
- * Address?: string,
2386
- * Description?: string,
2387
- * [key: string]: any,
2388
- * }} ManifestDescriptor
2389
- */
2390
-
2391
- /**
2392
- * Manyfest object address-based descriptions and manipulations.
2393
- *
2394
- * @class Manyfest
2395
- */
2396
- class Manyfest extends libFableServiceProviderBase {
2397
- constructor(pFable, pManifest, pServiceHash) {
2398
- if (pFable === undefined) {
2399
- super({});
2400
- } else {
2401
- super(pFable, pManifest, pServiceHash);
2402
- }
2403
-
2404
- /** @type {import('fable')} */
2405
- this.fable;
2406
- /** @type {Record<string, any>} */
2407
- this.options;
2408
- /** @type {string} */
2409
- this.Hash;
2410
- /** @type {string} */
2411
- this.UUID;
2412
- this.serviceType = 'Manifest';
2413
-
2414
- // Wire in logging
2415
- this.logInfo = libSimpleLog;
2416
- this.logError = libSimpleLog;
2417
-
2418
- // Create an object address resolver and map in the functions
2419
- this.objectAddressCheckAddressExists = new libObjectAddressCheckAddressExists(this.logInfo, this.logError);
2420
- this.objectAddressGetValue = new libObjectAddressGetValue(this.logInfo, this.logError);
2421
- this.objectAddressSetValue = new libObjectAddressSetValue(this.logInfo, this.logError);
2422
- this.objectAddressDeleteValue = new libObjectAddressDeleteValue(this.logInfo, this.logError);
2423
- if (!('defaultValues' in this.options)) {
2424
- this.options.defaultValues = {
2425
- "String": "",
2426
- "Number": 0,
2427
- "Float": 0.0,
2428
- "Integer": 0,
2429
- "PreciseNumber": "0.0",
2430
- "Boolean": false,
2431
- "Binary": 0,
2432
- "DateTime": 0,
2433
- "Array": [],
2434
- "Object": {},
2435
- "Null": null
2436
- };
2437
- }
2438
- if (!('strict' in this.options)) {
2439
- this.options.strict = false;
2440
- }
2441
-
2442
- /** @type {string} */
2443
- this.scope = undefined;
2444
- /** @type {Array<string>} */
2445
- this.elementAddresses = undefined;
2446
- /** @type {Record<string, string>} */
2447
- this.elementHashes = undefined;
2448
- /** @type {Record<string, ManifestDescriptor>} */
2449
- this.elementDescriptors = undefined;
2450
- this.reset();
2451
- if (typeof this.options === 'object') {
2452
- this.loadManifest(this.options);
2453
- }
2454
- this.schemaManipulations = new libSchemaManipulation(this.logInfo, this.logError);
2455
- this.objectAddressGeneration = new libObjectAddressGeneration(this.logInfo, this.logError);
2456
- this.hashTranslations = new libHashTranslation(this.logInfo, this.logError);
2457
- this.numberRegex = /^[+-]?(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?$/;
2458
- }
2459
-
2460
- /*************************************************************************
2461
- * Schema Manifest Loading, Reading, Manipulation and Serialization Functions
2462
- */
2463
-
2464
- // Reset critical manifest properties
2465
- reset() {
2466
- this.scope = 'DEFAULT';
2467
- this.elementAddresses = [];
2468
- this.elementHashes = {};
2469
- this.elementDescriptors = {};
2470
- }
2471
- clone() {
2472
- // Make a copy of the options in-place
2473
- let tmpNewOptions = JSON.parse(JSON.stringify(this.options));
2474
- let tmpNewManyfest = new Manyfest(this.fable, tmpNewOptions, this.Hash);
2475
- tmpNewManyfest.logInfo = this.logInfo;
2476
- tmpNewManyfest.logError = this.logError;
2477
- //FIXME: mostly written by co-pilot
2478
- const {
2479
- Scope,
2480
- Descriptors,
2481
- HashTranslations
2482
- } = this.getManifest();
2483
- tmpNewManyfest.scope = Scope;
2484
- tmpNewManyfest.elementDescriptors = Descriptors;
2485
- tmpNewManyfest.elementAddresses = Object.keys(Descriptors);
2486
- // Rebuild the element hashes
2487
- for (let i = 0; i < tmpNewManyfest.elementAddresses.length; i++) {
2488
- let tmpAddress = tmpNewManyfest.elementAddresses[i];
2489
- let tmpDescriptor = tmpNewManyfest.elementDescriptors[tmpAddress];
2490
- tmpNewManyfest.elementHashes[tmpAddress] = tmpAddress;
2491
- if ('Hash' in tmpDescriptor) {
2492
- tmpNewManyfest.elementHashes[tmpDescriptor.Hash] = tmpAddress;
2493
- }
2494
- }
2495
-
2496
- // Import the hash translations
2497
- tmpNewManyfest.hashTranslations.addTranslation(this.hashTranslations.translationTable);
2498
- return tmpNewManyfest;
2499
- }
2500
-
2501
- // Deserialize a Manifest from a string
2502
- /**
2503
- * @param {string} pManifestString - The manifest string to deserialize
2504
- *
2505
- * @return {Manyfest} The deserialized manifest
2506
- */
2507
- deserialize(pManifestString) {
2508
- // TODO: Add guards for bad manifest string
2509
- this.loadManifest(JSON.parse(pManifestString));
2510
- return this;
2511
- }
2512
-
2513
- // Load a manifest from an object
2514
- loadManifest(pManifest) {
2515
- if (typeof pManifest !== 'object') {
2516
- this.logError("(".concat(this.scope, ") Error loading manifest; expecting an object but parameter was type ").concat(typeof pManifest, "."));
2517
- }
2518
- let tmpManifest = typeof pManifest == 'object' ? pManifest : {};
2519
- let tmpDescriptorKeys = Object.keys(_DefaultConfiguration);
2520
- for (let i = 0; i < tmpDescriptorKeys.length; i++) {
2521
- if (!(tmpDescriptorKeys[i] in tmpManifest)) {
2522
- tmpManifest[tmpDescriptorKeys[i]] = JSON.parse(JSON.stringify(_DefaultConfiguration[tmpDescriptorKeys[i]]));
2523
- }
2524
- }
2525
- if ('Scope' in tmpManifest) {
2526
- if (typeof tmpManifest.Scope === 'string') {
2527
- this.scope = tmpManifest.Scope;
2528
- } else {
2529
- this.logError("(".concat(this.scope, ") Error loading scope from manifest; expecting a string but property was type ").concat(typeof tmpManifest.Scope, "."), tmpManifest);
2530
- }
2531
- } else {
2532
- this.logError("(".concat(this.scope, ") Error loading scope from manifest object. Property \"Scope\" does not exist in the root of the object."), tmpManifest);
2533
- }
2534
- if ('Descriptors' in tmpManifest) {
2535
- if (typeof tmpManifest.Descriptors === 'object') {
2536
- let tmpDescriptionAddresses = Object.keys(tmpManifest.Descriptors);
2537
- for (let i = 0; i < tmpDescriptionAddresses.length; i++) {
2538
- this.addDescriptor(tmpDescriptionAddresses[i], tmpManifest.Descriptors[tmpDescriptionAddresses[i]]);
2539
- }
2540
- } else {
2541
- this.logError("(".concat(this.scope, ") Error loading description object from manifest object. Expecting an object in 'Manifest.Descriptors' but the property was type ").concat(typeof tmpManifest.Descriptors, "."), tmpManifest);
2542
- }
2543
- } else {
2544
- this.logError("(".concat(this.scope, ") Error loading object description from manifest object. Property \"Descriptors\" does not exist in the root of the Manifest object."), tmpManifest);
2545
- }
2546
- if ('HashTranslations' in tmpManifest) {
2547
- if (typeof tmpManifest.HashTranslations === 'object') {
2548
- for (let i = 0; i < tmpManifest.HashTranslations.length; i++) {
2549
- // Each translation is
2550
- //FIXME: ?????????
2551
- }
2552
- }
2553
- }
2554
- }
2555
-
2556
- /**
2557
- * Serialize the Manifest to a string
2558
- *
2559
- * @return {string} - The serialized manifest
2560
- */
2561
- serialize() {
2562
- return JSON.stringify(this.getManifest());
2563
- }
2564
-
2565
- /**
2566
- * @return {{ Scope: string, Descriptors: Record<string, ManifestDescriptor>, HashTranslations: Record<string, string> }} - A copy of the manifest state.
2567
- */
2568
- getManifest() {
2569
- return {
2570
- Scope: this.scope,
2571
- Descriptors: JSON.parse(JSON.stringify(this.elementDescriptors)),
2572
- HashTranslations: JSON.parse(JSON.stringify(this.hashTranslations.translationTable))
2573
- };
2574
- }
2575
-
2576
- /**
2577
- * Add a descriptor to the manifest
2578
- *
2579
- * @param {string} pAddress - The address of the element to add the descriptor for.
2580
- * @param {ManifestDescriptor} pDescriptor - The descriptor object to add.
2581
- */
2582
- addDescriptor(pAddress, pDescriptor) {
2583
- if (typeof pDescriptor === 'object') {
2584
- // Add the Address into the Descriptor if it doesn't exist:
2585
- if (!('Address' in pDescriptor)) {
2586
- pDescriptor.Address = pAddress;
2587
- }
2588
- if (!(pAddress in this.elementDescriptors)) {
2589
- this.elementAddresses.push(pAddress);
2590
- }
2591
-
2592
- // Add the element descriptor to the schema
2593
- this.elementDescriptors[pAddress] = pDescriptor;
2594
-
2595
- // Always add the address as a hash
2596
- this.elementHashes[pAddress] = pAddress;
2597
- if ('Hash' in pDescriptor) {
2598
- // TODO: Check if this is a good idea or not..
2599
- // Collisions are bound to happen with both representations of the address/hash in here and developers being able to create their own hashes.
2600
- this.elementHashes[pDescriptor.Hash] = pAddress;
2601
- } else {
2602
- pDescriptor.Hash = pAddress;
2603
- }
2604
- return true;
2605
- } else {
2606
- this.logError("(".concat(this.scope, ") Error loading object descriptor for address '").concat(pAddress, "' from manifest object. Expecting an object but property was type ").concat(typeof pDescriptor, "."));
2607
- return false;
2608
- }
2609
- }
2610
-
2611
- /**
2612
- * @param {string} pHash - The hash of the address to resolve.
2613
- *
2614
- * @return {ManifestDescriptor} The descriptor for the address
2615
- */
2616
- getDescriptorByHash(pHash) {
2617
- return this.getDescriptor(this.resolveHashAddress(pHash));
2618
- }
2619
-
2620
- /**
2621
- * @param {string} pAddress - The address of the element to get the descriptor for.
2622
- *
2623
- * @return {ManifestDescriptor} The descriptor for the address
2624
- */
2625
- getDescriptor(pAddress) {
2626
- return this.elementDescriptors[pAddress];
2627
- }
2628
-
2629
- /**
2630
- * execute an action function for each descriptor
2631
- * @param {(d: ManifestDescriptor) => void} fAction - The action function to execute for each descriptor.
2632
- */
2633
- eachDescriptor(fAction) {
2634
- let tmpDescriptorAddresses = Object.keys(this.elementDescriptors);
2635
- for (let i = 0; i < tmpDescriptorAddresses.length; i++) {
2636
- fAction(this.elementDescriptors[tmpDescriptorAddresses[i]]);
2637
- }
2638
- }
2639
-
2640
- /*************************************************************************
2641
- * Beginning of Object Manipulation (read & write) Functions
2642
- */
2643
- // Check if an element exists by its hash
2644
- checkAddressExistsByHash(pObject, pHash) {
2645
- return this.checkAddressExists(pObject, this.resolveHashAddress(pHash));
2646
- }
2647
-
2648
- // Check if an element exists at an address
2649
- checkAddressExists(pObject, pAddress) {
2650
- return this.objectAddressCheckAddressExists.checkAddressExists(pObject, pAddress);
2651
- }
2652
-
2653
- // Turn a hash into an address, factoring in the translation table.
2654
- resolveHashAddress(pHash) {
2655
- let tmpAddress = undefined;
2656
- let tmpInElementHashTable = pHash in this.elementHashes;
2657
- let tmpInTranslationTable = pHash in this.hashTranslations.translationTable;
2658
-
2659
- // The most straightforward: the hash exists, no translations.
2660
- if (tmpInElementHashTable && !tmpInTranslationTable) {
2661
- tmpAddress = this.elementHashes[pHash];
2662
- }
2663
- // There is a translation from one hash to another, and, the elementHashes contains the pointer end
2664
- else if (tmpInTranslationTable && this.hashTranslations.translate(pHash) in this.elementHashes) {
2665
- tmpAddress = this.elementHashes[this.hashTranslations.translate(pHash)];
2666
- }
2667
- // Use the level of indirection only in the Translation Table
2668
- else if (tmpInTranslationTable) {
2669
- tmpAddress = this.hashTranslations.translate(pHash);
2670
- }
2671
- // Just treat the hash as an address.
2672
- // TODO: Discuss this ... it is magic but controversial
2673
- else {
2674
- tmpAddress = pHash;
2675
- }
2676
- return tmpAddress;
2677
- }
2678
-
2679
- // Get the value of an element by its hash
2680
- getValueByHash(pObject, pHash) {
2681
- let tmpValue = this.getValueAtAddress(pObject, this.resolveHashAddress(pHash));
2682
- if (typeof tmpValue == 'undefined') {
2683
- // Try to get a default if it exists
2684
- tmpValue = this.getDefaultValue(this.getDescriptorByHash(pHash));
2685
- }
2686
- return tmpValue;
2687
- }
2688
- lintAddress(pAddress) {
2689
- let tmpLintedAddress = pAddress.trim();
2690
- // Check for a single . (but not a ..) at the end of the address and remove it.
2691
- if (tmpLintedAddress.endsWith('..')) {
2692
- tmpLintedAddress = tmpLintedAddress.slice(0, -1);
2693
- } else if (tmpLintedAddress.endsWith('.')) {
2694
- tmpLintedAddress = tmpLintedAddress.slice(0, -1);
2695
- }
2696
- return tmpLintedAddress;
2697
- }
2698
-
2699
- // Get the value of an element at an address
2700
- getValueAtAddress(pObject, pAddress) {
2701
- let tmpLintedAddress = this.lintAddress(pAddress);
2702
- if (tmpLintedAddress == '') {
2703
- this.logError("(".concat(this.scope, ") Error getting value at address; address is an empty string."), pObject);
2704
- return undefined;
2705
- }
2706
- let tmpValue = this.objectAddressGetValue.getValueAtAddress(pObject, tmpLintedAddress);
2707
- if (typeof tmpValue == 'undefined') {
2708
- // Try to get a default if it exists
2709
- tmpValue = this.getDefaultValue(this.getDescriptor(tmpLintedAddress));
2710
- }
2711
- return tmpValue;
2712
- }
2713
-
2714
- // Set the value of an element by its hash
2715
- setValueByHash(pObject, pHash, pValue) {
2716
- return this.setValueAtAddress(pObject, this.resolveHashAddress(pHash), pValue);
2717
- }
2718
-
2719
- // Set the value of an element at an address
2720
- setValueAtAddress(pObject, pAddress, pValue) {
2721
- let tmpLintedAddress = this.lintAddress(pAddress);
2722
- return this.objectAddressSetValue.setValueAtAddress(pObject, tmpLintedAddress, pValue);
2723
- }
2724
-
2725
- // Delete the value of an element by its hash
2726
- deleteValueByHash(pObject, pHash, pValue) {
2727
- return this.deleteValueAtAddress(pObject, this.resolveHashAddress(pHash), pValue);
2728
- }
2729
-
2730
- // Delete the value of an element at an address
2731
- deleteValueAtAddress(pObject, pAddress, pValue) {
2732
- let tmpLintedAddress = this.lintAddress(pAddress);
2733
- return this.objectAddressDeleteValue.deleteValueAtAddress(pObject, tmpLintedAddress, pValue);
2734
- }
2735
-
2736
- // Validate the consistency of an object against the schema
2737
- validate(pObject) {
2738
- let tmpValidationData = {
2739
- Error: null,
2740
- Errors: [],
2741
- MissingElements: []
2742
- };
2743
- if (typeof pObject !== 'object') {
2744
- tmpValidationData.Error = true;
2745
- tmpValidationData.Errors.push("Expected passed in object to be type object but was passed in ".concat(typeof pObject));
2746
- }
2747
- let addValidationError = (pAddress, pErrorMessage) => {
2748
- tmpValidationData.Error = true;
2749
- tmpValidationData.Errors.push("Element at address \"".concat(pAddress, "\" ").concat(pErrorMessage, "."));
2750
- };
2751
-
2752
- // Now enumerate through the values and check for anomalies based on the schema
2753
- for (let i = 0; i < this.elementAddresses.length; i++) {
2754
- let tmpDescriptor = this.getDescriptor(this.elementAddresses[i]);
2755
- let tmpValueExists = this.checkAddressExists(pObject, tmpDescriptor.Address);
2756
- let tmpValue = this.getValueAtAddress(pObject, tmpDescriptor.Address);
2757
- if (typeof tmpValue == 'undefined' || !tmpValueExists) {
2758
- // This will technically mean that `Object.Some.Value = undefined` will end up showing as "missing"
2759
- // TODO: Do we want to do a different message based on if the property exists but is undefined?
2760
- tmpValidationData.MissingElements.push(tmpDescriptor.Address);
2761
- if (tmpDescriptor.Required || this.options.strict) {
2762
- addValidationError(tmpDescriptor.Address, 'is flagged REQUIRED but is not set in the object');
2763
- }
2764
- }
2765
-
2766
- // Now see if there is a data type specified for this element
2767
- if (tmpDescriptor.DataType) {
2768
- let tmpElementType = typeof tmpValue;
2769
- switch (tmpDescriptor.DataType.toString().trim().toLowerCase()) {
2770
- case 'string':
2771
- if (tmpElementType != 'string') {
2772
- addValidationError(tmpDescriptor.Address, "has a DataType ".concat(tmpDescriptor.DataType, " but is of the type ").concat(tmpElementType));
2773
- }
2774
- break;
2775
- case "precisenumber":
2776
- if (tmpElementType != 'string') {
2777
- addValidationError(tmpDescriptor.Address, "has a DataType ".concat(tmpDescriptor.DataType, " but is of the type ").concat(tmpElementType));
2778
- } else if (!this.numberRegex.test(tmpValue)) {
2779
- addValidationError(tmpDescriptor.Address, "has a DataType ".concat(tmpDescriptor.DataType, " but is not a valid number"));
2780
- }
2781
- break;
2782
- case 'number':
2783
- if (tmpElementType != 'number') {
2784
- addValidationError(tmpDescriptor.Address, "has a DataType ".concat(tmpDescriptor.DataType, " but is of the type ").concat(tmpElementType));
2785
- }
2786
- break;
2787
- case 'integer':
2788
- if (tmpElementType != 'number') {
2789
- addValidationError(tmpDescriptor.Address, "has a DataType ".concat(tmpDescriptor.DataType, " but is of the type ").concat(tmpElementType));
2790
- } else {
2791
- let tmpValueString = tmpValue.toString();
2792
- if (tmpValueString.indexOf('.') > -1) {
2793
- // TODO: Is this an error?
2794
- addValidationError(tmpDescriptor.Address, "has a DataType ".concat(tmpDescriptor.DataType, " but has a decimal point in the number."));
2795
- }
2796
- }
2797
- break;
2798
- case 'float':
2799
- if (tmpElementType != 'number') {
2800
- addValidationError(tmpDescriptor.Address, "has a DataType ".concat(tmpDescriptor.DataType, " but is of the type ").concat(tmpElementType));
2801
- }
2802
- break;
2803
- case 'datetime':
2804
- let tmpValueDate = new Date(tmpValue);
2805
- if (tmpValueDate.toString() == 'Invalid Date') {
2806
- addValidationError(tmpDescriptor.Address, "has a DataType ".concat(tmpDescriptor.DataType, " but is not parsable as a Date by Javascript"));
2807
- }
2808
- default:
2809
- // Check if this is a string, in the default case
2810
- // Note this is only when a DataType is specified and it is an unrecognized data type.
2811
- if (tmpElementType != 'string') {
2812
- addValidationError(tmpDescriptor.Address, "has a DataType ".concat(tmpDescriptor.DataType, " (which auto-converted to String because it was unrecognized) but is of the type ").concat(tmpElementType));
2813
- }
2814
- break;
2815
- }
2816
- }
2817
- }
2818
- return tmpValidationData;
2819
- }
2820
-
2821
- /**
2822
- * Returns a default value, or, the default value for the data type (which is overridable with configuration)
2823
- *
2824
- * @param {ManifestDescriptor} pDescriptor - The descriptor definition.
2825
- */
2826
- getDefaultValue(pDescriptor) {
2827
- if (typeof pDescriptor != 'object') {
2828
- return undefined;
2829
- }
2830
- if ('Default' in pDescriptor) {
2831
- return pDescriptor.Default;
2832
- } else {
2833
- // Default to a null if it doesn't have a type specified.
2834
- // This will ensure a placeholder is created but isn't misinterpreted.
2835
- let tmpDataType = 'DataType' in pDescriptor ? pDescriptor.DataType : 'String';
2836
- if (tmpDataType in this.options.defaultValues) {
2837
- return this.options.defaultValues[tmpDataType];
2838
- } else {
2839
- // give up and return null
2840
- return null;
2841
- }
2842
- }
2843
- }
2844
-
2845
- // Enumerate through the schema and populate default values if they don't exist.
2846
- populateDefaults(pObject, pOverwriteProperties) {
2847
- return this.populateObject(pObject, pOverwriteProperties,
2848
- // This just sets up a simple filter to see if there is a default set.
2849
- pDescriptor => {
2850
- return 'Default' in pDescriptor;
2851
- });
2852
- }
2853
-
2854
- // Forcefully populate all values even if they don't have defaults.
2855
- // Based on type, this can do unexpected things.
2856
- populateObject(pObject, pOverwriteProperties, fFilter) {
2857
- // Automatically create an object if one isn't passed in.
2858
- let tmpObject = typeof pObject === 'object' ? pObject : {};
2859
- // Default to *NOT OVERWRITING* properties
2860
- let tmpOverwriteProperties = typeof pOverwriteProperties == 'undefined' ? false : pOverwriteProperties;
2861
- // This is a filter function, which is passed the schema and allows complex filtering of population
2862
- // The default filter function just returns true, populating everything.
2863
- let tmpFilterFunction = typeof fFilter == 'function' ? fFilter : pDescriptor => {
2864
- return true;
2865
- };
2866
- this.elementAddresses.forEach(pAddress => {
2867
- let tmpDescriptor = this.getDescriptor(pAddress);
2868
- // Check the filter function to see if this is an address we want to set the value for.
2869
- if (tmpFilterFunction(tmpDescriptor)) {
2870
- // If we are overwriting properties OR the property does not exist
2871
- if (tmpOverwriteProperties || !this.checkAddressExists(tmpObject, pAddress)) {
2872
- this.setValueAtAddress(tmpObject, pAddress, this.getDefaultValue(tmpDescriptor));
2873
- }
2874
- }
2875
- });
2876
- return tmpObject;
2877
- }
2878
- }
2879
- ;
2880
- module.exports = Manyfest;
2881
- }, {
2882
- "./Manyfest-HashTranslation.js": 4,
2883
- "./Manyfest-LogToConsole.js": 5,
2884
- "./Manyfest-ObjectAddress-CheckAddressExists.js": 6,
2885
- "./Manyfest-ObjectAddress-DeleteValue.js": 7,
2886
- "./Manyfest-ObjectAddress-GetValue.js": 8,
2887
- "./Manyfest-ObjectAddress-SetValue.js": 10,
2888
- "./Manyfest-ObjectAddressGeneration.js": 11,
2889
- "./Manyfest-SchemaManipulation.js": 13,
2890
- "fable-serviceproviderbase": 2
2891
- }]
2892
- }, {}, [14])(14);
2893
- });
2894
- //# sourceMappingURL=manyfest.js.map