UNPKG

@graphistry/falcor

Version:
153 lines (136 loc) 6.32 kB
var isArray = Array.isArray; var clone = require('../clone'); var FalcorJSON = require('./json/FalcorJSON'); var NullInPathError = require('../../errors/NullInPathError'); var InvalidKeySetError = require('../../errors/InvalidKeySetError'); var materializedAtom = require('@graphistry/falcor-path-utils/lib/support/materializedAtom'); module.exports = onMaterialize; /* eslint-disable camelcase */ /* eslint-disable no-cond-assign */ /* eslint-disable no-constant-condition */ function onMaterialize(jsonArg, path, depth, length, branchSelector, boxValues, modelRoot, reportMissing) { var json = jsonArg, type, refTarget; // ============ Check for base cases ================ // If there's nowhere to go, we've reached a terminal node, or hit // the end of the path, stop now. Either build missing paths or report the value. if (depth === length) { return boxValues ? clone(materializedAtom) : undefined; } var f_meta; var nextKey, keyset, keyIsRange, nextDepth = depth + 1, rangeEnd, keysOrRanges, keysetIndex = -1, keysetLength = 0; keyset = path[depth]; if (!json || 'object' !== typeof json) { json = {}; json.__proto__ = FalcorJSON.prototype; json[f_meta_data] = f_meta = {}; f_meta[f_meta_status] = reportMissing ? 'pending' : 'resolved'; f_meta[f_meta_version] = modelRoot.version; f_meta[f_meta_abs_path] = path.slice(0, depth); if (branchSelector) { json = branchSelector(json); } } else if (!(f_meta = json[f_meta_data])) { json[f_meta_data] = f_meta = {}; f_meta[f_meta_status] = reportMissing ? 'pending' : 'resolved'; f_meta[f_meta_version] = modelRoot.version; f_meta[f_meta_abs_path] = path.slice(0, depth); } else { f_meta[f_meta_status] = reportMissing ? 'pending' : 'resolved'; f_meta[f_meta_version] = modelRoot.version; f_meta[f_meta_abs_path] = path.slice(0, depth); } // Iterate over every key in the keyset. This loop is perhaps a bit clever, // but we do it this way because this is a performance-sensitive code path. // This loop simulates a recursive function if we encounter a Keyset that // contains Keys or Ranges. This is accomplished by a nifty dance between an // outer loop and an inner loop. // // The outer loop is responsible for identifying if the value at this depth // is a Key, Range, or Keyset. If it encounters a Keyset, the `keysetIndex`, // `keysetLength`, and `keysOrRanges` variables are assigned and the outer // loop restarts. If it encounters a Key or Range, `nextKey`, `keyIsRange`, // and `rangeEnd` are assigned values which signal whether the inner loop // should iterate a Range or exit after the first run. // // The inner loop steps `nextKey` one level down in the cache. If a Range // was encountered in the outer loop, the inner loop will iterate until the // Range has been exhausted. If a Key was encountered, the inner loop exits // after the first execution. // // After the inner loop exits, the outer loop iterates the `keysetIndex` // until the Keyset is exhausted. `keysetIndex` and `keysetLength` are // initialized to -1 and 0 respectively, so if a Keyset wasn't encountered // at this depth in the path, then the outer loop exits after one execution. iteratingKeyset: do { // If the keyset is a primitive value, we've found our `nextKey`. if ('object' !== typeof keyset) { nextKey = keyset; rangeEnd = undefined; keyIsRange = false; } // If we encounter a Keyset, either iterate through the Keys and Ranges, // or throw an error if we're already iterating a Keyset. Keysets cannot // contain other Keysets. else if (isArray(keyset)) { // If we've already encountered an Array keyset, throw an error. if (keysOrRanges !== undefined) { throw new InvalidKeySetError(path, keysOrRanges); } keysetIndex = 0; keysOrRanges = keyset; keysetLength = keyset.length; // If an Array of keys or ranges is empty, terminate the graph walk // and return the json constructed so far. An example of an empty // Keyset is: ['lolomo', [], 'summary']. This should short circuit // without building missing paths. if (0 === keysetLength) { break iteratingKeyset; } // Assign `keyset` to the first value in the Keyset. Re-entering the // outer loop mimics a singly-recursive function call. keyset = keysOrRanges[keysetIndex]; continue iteratingKeyset; } // If the Keyset isn't a primitive or Array, then it must be a Range. else { rangeEnd = keyset.to; nextKey = keyset.from || 0; if ('number' !== typeof rangeEnd) { rangeEnd = nextKey + (keyset.length || 0) - 1; } if ((rangeEnd - nextKey) < 0) { break iteratingKeyset; } keyIsRange = true; } // Now that we have the next key, step down one level in the cache. do { // insert the materialized branch json[nextKey] = onMaterialize( json[nextKey], path, nextDepth, length, branchSelector, boxValues, modelRoot ); } // Re-enter the inner loop and continue iterating the Range, or exit // here if we encountered a Key. while (keyIsRange && ++nextKey <= rangeEnd); // If we've exhausted the Keyset (or never encountered one at all), // exit the outer loop. if (++keysetIndex === keysetLength) { break iteratingKeyset; } // Otherwise get the next Key or Range from the Keyset and re-enter the // outer loop from the top. keyset = keysOrRanges[keysetIndex]; } while (true); // `json` will be a branch if any cache hits, or undefined if all cache misses return json; }