falcor
Version:
A JavaScript library for efficient data fetching.
200 lines (182 loc) • 8.76 kB
JavaScript
var $ref = require("./../types/ref");
var $error = require("./../types/error");
var getSize = require("./../support/getSize");
var getTimestamp = require("./../support/getTimestamp");
var isObject = require("./../support/isObject");
var isExpired = require("./../support/isExpired");
var isFunction = require("./../support/isFunction");
var wrapNode = require("./../support/wrapNode");
var insertNode = require("./../support/insertNode");
var expireNode = require("./../support/expireNode");
var replaceNode = require("./../support/replaceNode");
var updateNodeAncestors = require("./../support/updateNodeAncestors");
var reconstructPath = require("./../support/reconstructPath");
module.exports = function mergeJSONGraphNode(
parent, node, message, key, requestedPath, optimizedPath,
version, expired, lru, comparator, errorSelector, replacedPaths) {
var sizeOffset;
var cType, mType,
cIsObject, mIsObject,
cTimestamp, mTimestamp;
var nodeValue = node && node.value !== undefined ? node.value : node;
// If the cache and message are the same, we can probably return early:
// - If they're both nullsy,
// - If null then the node needs to be wrapped in an atom and inserted.
// This happens from whole branch merging when a leaf is just a null value
// instead of being wrapped in an atom.
// - If undefined then return null (previous behavior).
// - If they're both branches, return the branch.
// - If they're both edges, continue below.
if (nodeValue === message) {
// There should not be undefined values. Those should always be
// wrapped in an $atom
if (message === null) {
node = wrapNode(message, undefined, message);
parent = updateNodeAncestors(parent, -node.$size, lru, version);
node = insertNode(node, parent, key, undefined, optimizedPath);
return node;
}
// The messange and cache are both undefined, therefore return null.
else if (message === undefined) {
return message;
}
else {
cIsObject = isObject(node);
if (cIsObject) {
// Is the cache node a branch? If so, return the cache branch.
cType = node.$type;
if (cType == null) {
// Has the branch been introduced to the cache yet? If not,
// give it a parent, key, and absolute path.
if (node.$_parent == null) {
insertNode(node, parent, key, version, optimizedPath);
}
return node;
}
}
}
} else {
cIsObject = isObject(node);
if (cIsObject) {
cType = node.$type;
}
}
// If the cache isn't a reference, we might be able to return early.
if (cType !== $ref) {
mIsObject = isObject(message);
if (mIsObject) {
mType = message.$type;
}
if (cIsObject && !cType) {
// If the cache is a branch and the message is empty or
// also a branch, continue with the cache branch.
if (message == null || (mIsObject && !mType)) {
return node;
}
}
}
// If the cache is a reference, we might not need to replace it.
else {
// If the cache is a reference, but the message is empty, leave the cache alone...
if (message == null) {
// ...unless the cache is an expired reference. In that case, expire
// the cache node and return undefined.
if (isExpired(node)) {
expireNode(node, expired, lru);
return void 0;
}
return node;
}
mIsObject = isObject(message);
if (mIsObject) {
mType = message.$type;
// If the cache and the message are both references,
// check if we need to replace the cache reference.
if (mType === $ref) {
if (node === message) {
// If the cache and message are the same reference,
// we performed a whole-branch merge of one of the
// grandparents. If we've previously graphed this
// reference, break early. Otherwise, continue to
// leaf insertion below.
if (node.$_parent != null) {
return node;
}
} else {
cTimestamp = node.$timestamp;
mTimestamp = message.$timestamp;
// - If either the cache or message reference is expired,
// replace the cache reference with the message.
// - If neither of the references are expired, compare their
// timestamps. If either of them don't have a timestamp,
// or the message's timestamp is newer, replace the cache
// reference with the message reference.
// - If the message reference is older than the cache
// reference, short-circuit.
if (!isExpired(node) && !isExpired(message) && mTimestamp < cTimestamp) {
return void 0;
}
}
}
}
}
// If the cache is a leaf but the message is a branch, merge the branch over the leaf.
if (cType && mIsObject && !mType) {
return insertNode(replaceNode(node, message, parent, key, lru, replacedPaths), parent, key, undefined, optimizedPath);
}
// If the message is a sentinel or primitive, insert it into the cache.
else if (mType || !mIsObject) {
// If the cache and the message are the same value, we branch-merged one
// of the message's ancestors. If this is the first time we've seen this
// leaf, give the message a $size and $type, attach its graph pointers,
// and update the cache sizes and versions.
if (mType === $error && isFunction(errorSelector)) {
message = errorSelector(reconstructPath(requestedPath, key), message);
mType = message.$type || mType;
}
if (mType && node === message) {
if (node.$_parent == null) {
node = wrapNode(node, mType, node.value);
parent = updateNodeAncestors(parent, -node.$size, lru, version);
node = insertNode(node, parent, key, version, optimizedPath);
}
}
// If the cache and message are different, the cache value is expired,
// or the message is a primitive, replace the cache with the message value.
// If the message is a sentinel, clone and maintain its type.
// If the message is a primitive value, wrap it in an atom.
else {
var isDistinct = true;
// If the cache is a branch, but the message is a leaf, replace the
// cache branch with the message leaf.
if ((cType && !isExpired(node)) || !cIsObject) {
// Compare the current cache value with the new value. If either of
// them don't have a timestamp, or the message's timestamp is newer,
// replace the cache value with the message value. If a comparator
// is specified, the comparator takes precedence over timestamps.
//
// Comparing either Number or undefined to undefined always results in false.
isDistinct = (getTimestamp(message) < getTimestamp(node)) === false;
// If at least one of the cache/message are sentinels, compare them.
if (isDistinct && (cType || mType) && isFunction(comparator)) {
isDistinct = !comparator(nodeValue, message, optimizedPath.slice(0, optimizedPath.index));
}
}
if (isDistinct) {
message = wrapNode(message, mType, mType ? message.value : message);
sizeOffset = getSize(node) - getSize(message);
node = replaceNode(node, message, parent, key, lru, replacedPaths);
parent = updateNodeAncestors(parent, sizeOffset, lru, version);
node = insertNode(node, parent, key, version, optimizedPath);
}
}
// Promote the message edge in the LRU.
if (isExpired(node)) {
expireNode(node, expired, lru);
}
}
else if (node == null) {
node = insertNode({}, parent, key, undefined, optimizedPath);
}
return node;
};