falcor-router
Version:
A router DataSource constructor for falcor that allows you to model all your cloud data sources as a single JSON resource.
161 lines (140 loc) • 6.46 kB
JavaScript
var Rx = require('../RouterRx.js');
var Observable = Rx.Observable;
var runByPrecedence = require('./precedence/runByPrecedence');
var pathUtils = require('falcor-path-utils');
var collapse = pathUtils.collapse;
var optimizePathSets = require('./../cache/optimizePathSets');
var mCGRI = require('./mergeCacheAndGatherRefsAndInvalidations');
var isArray = Array.isArray;
/**
* The recurse and match function will async recurse as long as
* there are still more paths to be executed. The match function
* will return a set of objects that have how much of the path that
* is matched. If there still is more, denoted by suffixes,
* paths to be matched then the recurser will keep running.
*/
module.exports = function recurseMatchAndExecute(
match, actionRunner, paths,
method, routerInstance, jsongCache) {
return _recurseMatchAndExecute(
match, actionRunner, paths,
method, routerInstance, jsongCache);
};
/**
* performs the actual recursing
*/
function _recurseMatchAndExecute(
match, actionRunner, paths,
method, routerInstance, jsongCache) {
var unhandledPaths = [];
var invalidated = [];
var reportedPaths = [];
var currentMethod = method;
return Observable.
// Each pathSet (some form of collapsed path) need to be sent
// independently. for each collapsed pathSet will, if producing
// refs, be the highest likelihood of collapsibility.
from(paths).
expand(function(nextPaths) {
if (!nextPaths.length) {
return Observable.empty();
}
// We have to return an Observable of error instead of just
// throwing.
var matchedResults;
try {
matchedResults = match(currentMethod, nextPaths);
} catch (e) {
return Observable.throw(e);
}
// When there is explicitly not a match then we need to handle
// the unhandled paths.
if (!matchedResults.length) {
unhandledPaths.push(nextPaths);
return Observable.empty();
}
return runByPrecedence(nextPaths, matchedResults, actionRunner).
// Generate from the combined results the next requestable paths
// and insert errors / values into the cache.
flatMap(function(results) {
var value = results.value;
var suffix = results.match.suffix;
// TODO: MaterializedPaths, use result.path to build up a
// "foundPaths" array. This could be used to materialize
// if that is the case. I don't think this is a
// requirement, but it could be.
if (!isArray(value)) {
value = [value];
}
var invsRefsAndValues =
mCGRI(jsongCache, value, routerInstance);
var invalidations = invsRefsAndValues.invalidations;
var unhandled = invsRefsAndValues.unhandledPaths;
var messages = invsRefsAndValues.messages;
var pathsToExpand = [];
if (suffix.length > 0) {
pathsToExpand = invsRefsAndValues.references;
}
// Merge the invalidations and unhandledPaths.
invalidations.forEach(function(invalidation) {
invalidated[invalidated.length] = invalidation.path;
});
unhandled.forEach(function(unhandledPath) {
unhandledPaths[unhandledPaths.length] = unhandledPath;
});
// Merges the remaining suffix with remaining nextPaths
pathsToExpand = pathsToExpand.map(function(next) {
return next.value.concat(suffix);
});
// Alters the behavior of the expand
messages.forEach(function(message) {
// mutates the method type for the matcher
if (message.method) {
currentMethod = message.method;
}
// Mutates the nextPaths and adds any additionalPaths
else if (message.additionalPath) {
var path = message.additionalPath;
pathsToExpand[pathsToExpand.length] = path;
reportedPaths[reportedPaths.length] = path;
}
// Any invalidations that come down from a call
else if (message.invalidations) {
message.
invalidations.
forEach(function(invalidation) {
invalidated.push(invalidation);
});
}
// We need to add the unhandledPaths to the jsonGraph
// response.
else if (message.unhandledPaths) {
unhandledPaths = unhandledPaths.
concat(message.unhandledPaths);
}
});
// Explodes and collapse the tree to remove
// redundants and get optimized next set of
// paths to evaluate.
pathsToExpand = optimizePathSets(
jsongCache, pathsToExpand, routerInstance.maxRefFollow);
if (pathsToExpand.length) {
pathsToExpand = collapse(pathsToExpand);
}
return Observable.
from(pathsToExpand);
}).
defaultIfEmpty([]);
}, Number.POSITIVE_INFINITY, Rx.Scheduler.queue).
reduce(function(acc, x) {
return acc;
}, null).
map(function() {
return {
unhandledPaths: unhandledPaths,
invalidated: invalidated,
jsonGraph: jsongCache,
reportedPaths: reportedPaths
};
});
}