@graphistry/falcor
Version:
A JavaScript library for efficient data fetching.
153 lines (136 loc) • 6.32 kB
JavaScript
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;
}