UNPKG

falcor

Version:

A JavaScript library for efficient data fetching.

200 lines (182 loc) 8.76 kB
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; };