Skip to content

Commit

Permalink
added comprehensive docs
Browse files Browse the repository at this point in the history
  • Loading branch information
asyncanup committed Oct 16, 2017
1 parent 3125234 commit 28edebf
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 34 deletions.
5 changes: 4 additions & 1 deletion lib/request/GetRequestV2.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,11 @@ GetRequestV2.prototype = {
/**
* Attempts to add paths to the outgoing request. If there are added
* paths then the request callback will be added to the callback list.
* Handles adding partial paths as well
*
* @returns {Array} - the remaining paths in the request.
* @returns {Array} - whether new requested paths were inserted in this
* request, the remaining paths that could not be added,
* and disposable for the inserted requested paths.
*/
add: function(requested, optimized, depthDifferences, callback) {
// uses the length tree complement calculator.
Expand Down
137 changes: 104 additions & 33 deletions lib/request/complement.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,46 @@ var arrayConcat = require("./../support/array-concat");
var iterateKeySet = require("falcor-path-utils").iterateKeySet;

/**
* creates the complement of the requested and optimized paths
* based on the provided tree.
* Figures out what paths in requested pathsets can be
* deduped based on existing optimized path tree provided.
*
* If there is no complement then this is just a glorified
* array copy.
* ## no deduping possible:
*
* if no existing requested sub tree at all for path,
* just add the entire path to complement.
*
* ## fully deduped:
*
* if required path is a complete subset of given sub tree,
* just add the entire path to intersection
*
* ## partial deduping:
*
* if some part of path, when ranges are expanded, is a subset
* of given sub tree, then add only that part to intersection,
* and all other parts of this path to complement
*
* To keep `depth` argument be a valid index for optimized path (`oPath`),
* either requested or optimized path is sent in pre-initialized with
* some items so that their remaining length matches exactly, keeping
* remaining ranges in those pathsets 1:1 in correspondence
*
* Note that positive `depthDiff` value means that requested path is
* longer than optimized path, and we need to pre-initialize current
* requested path with that many offset items, so that their remaining
* length matches. Similarly, negative `depthDiff` value means that
* optimized path is longer, and we pre-initialize optimized path with
* those many items. Note that because of the way requested and
* optimized paths are accumulated from what user requested in model.get
* (see onMissing.js), it is not possible for the pre-initialized paths
* to have any ranges in them.
*
* `intersectionData` is:
* [ requestedIntersection, optimizedComplement, requestedComplement ]
* where `requestedIntersection` is matched requested paths that can be
* deduped, `optimizedComplement` is missing optimized paths, and
* `requestedComplement` is requested counterparts of those missing
* optimized paths
*/
module.exports = function complement(requested, optimized, depthDifferences, tree) {
var optimizedComplement = [];
Expand All @@ -17,36 +52,24 @@ module.exports = function complement(requested, optimized, depthDifferences, tre
var intersectionLength = -1, complementLength = -1;

for (var i = 0, len = optimized.length; i < len; ++i) {
// If this does not intersect then add it to the output.
var oPath = optimized[i];
var rPath = requested[i];
var depthDiff = depthDifferences[i];
var subTree = tree[oPath.length];

// if no existing requested sub tree at all for path,
// just add the entire path to complement
// (no deduping possible)
// no deduping possible
if (!subTree) {
optimizedComplement[++complementLength] = oPath;
requestedComplement[complementLength] = rPath;
continue;
}
// if required path is a complete subset of given sub tree,
// just add the entire path to intersection
// (fully deduped)
// fully deduped
if (hasIntersection(subTree, oPath, 0)) {
requestedIntersection[++intersectionLength] = rPath;
continue;
}
// if some part of path, when ranges are expanded, is a subset
// of given sub tree, then add only that part to intersection,
// and all other parts of this path to complement

// intersectionData is:
// [ requestedIntersection, optimizedComplement, requestedComplement ]
// where requestedIntersection is matched requested paths for , and
// complements is paths not found, from the total set of
// individual paths (all ranges expanded)
// partial deduping
var intersectionData = findPartialIntersections(
rPath,
oPath,
Expand All @@ -70,38 +93,79 @@ module.exports = function complement(requested, optimized, depthDifferences, tre
return [requestedIntersection, optimizedComplement, requestedComplement];
};

/**
* Recursive function to calculate intersection and complement paths in 2 given
* pathsets at a given depth
* Parameters:
* - `requestedPath`: full requested path (can include ranges)
* - `optimizedPath`: corresponding optimized path (can include ranges)
* - `currentTree`: path map for in-flight request, against which to dedupe
* - `depth`: index of optimized path that we are trying to match with `currentTree`
* - `rCurrentPath`: current accumulated requested path by previous recursive
* iterations. Could also have been pre-initialized as stated
* above.
* This path cannot contain ranges, instead contains a key
* from the range, representing one of the individual paths
* in `requestedPath` pathset
* - `oCurrentPath`: corresponding accumulated optimized path, to be matched
* with `currentTree`. Could have been pre-initialized.
* Cannot contain ranges, instead contains a key from the
* range at given `depth` in `optimizedPath`
* - `depthDiff`: difference in length between `requestedPath` and `optimizedPath`
*
* Example scenario:
* - requestedPath: ['lolomo', 0, 0, 'tags', { from: 0, to: 2 }]
* - optimizedPath: ['videosById', 11, 'tags', { from: 0, to: 2 }]
* - currentTree: { videosById: 11: { tags: { 0: null, 1: null }}}
* // since requested path is longer, optimized path index starts from depth 0
* // and accumulated requested path starts pre-initialized (rCurrentPath)
* - depth: 0
* - rCurrentPath: ['lolomo']
* - oCurrentPath: []
* - depthDiff: 1
*/
function findPartialIntersections(requestedPath, optimizedPath, currentTree, depth, rCurrentPath, oCurrentPath, depthDiff) {
var intersections = [];
var rRemainingComplementPaths = [];
var oRemainingComplementPaths = [];
var rComplementPaths = [];
var oComplementPaths = [];
// iterate over optimized path, looking for deduping opportunities
for (; depth < optimizedPath.length; ++depth) {
var key = optimizedPath[depth];
var keyType = typeof key;

// range keys
// if range key is found, start inner loop to iterate over all keys in range
// and add intersections and complements from each iteration separately.
// range keys branch-out like this, providing individual deduping
// opportunities for each inner key
if (key && keyType === "object") {
var note = {};
var innerKey = iterateKeySet(key, note);

while (!note.done) {
var nextTree = currentTree[innerKey];
// dead-end for sub paths innerKey & beyond
if (nextTree === undefined) {
var oRemainingPath = oCurrentPath.concat(
// if no next sub tree exists for an inner key, it's a dead-end
// and we can add this to complement paths
var oPath = oCurrentPath.concat(
innerKey,
arraySlice(
optimizedPath,
depth + 1));
oRemainingComplementPaths[oRemainingComplementPaths.length] = oRemainingPath;
var rRemainingPath = rCurrentPath.concat(
oComplementPaths[oComplementPaths.length] = oPath;
var rPath = rCurrentPath.concat(
innerKey,
arraySlice(
requestedPath,
depth + 1 + depthDiff));
rRemainingComplementPaths[rRemainingComplementPaths.length] = rRemainingPath;
rComplementPaths[rComplementPaths.length] = rPath;
} else if (depth === optimizedPath.length - 1) {
// reaching the end of optimized path means that we found a
// corresponding node in the path map tree every time,
// so add current path to successful intersections
intersections[intersections.length] = arrayConcat(rCurrentPath, [innerKey]);
} else {
// otherwise keep trying to find further partial deduping
// opportunities in the remaining path!
var intersectionData = findPartialIntersections(
requestedPath,
optimizedPath,
Expand All @@ -114,29 +178,36 @@ function findPartialIntersections(requestedPath, optimizedPath, currentTree, dep
intersections[intersections.length] = intersectionData[0][j];
}
for (var k = 0, kLen = intersectionData[1].length; k < kLen; ++k) {
oRemainingComplementPaths[oRemainingComplementPaths.length] = intersectionData[1][k];
rRemainingComplementPaths[rRemainingComplementPaths.length] = intersectionData[2][k];
oComplementPaths[oComplementPaths.length] = intersectionData[1][k];
rComplementPaths[rComplementPaths.length] = intersectionData[2][k];
}
}
innerKey = iterateKeySet(key, note);
}
break;
}
// simple keys

// for simple keys, we don't need to branch out. looping over `depth`
// here instead of recursion, for performance
currentTree = currentTree[key];
oCurrentPath[oCurrentPath.length] = optimizedPath[depth];
rCurrentPath[rCurrentPath.length] = requestedPath[depth + depthDiff];

if (currentTree === undefined) {
oRemainingComplementPaths[oRemainingComplementPaths.length] =
// if dead-end, add this to complements
oComplementPaths[oComplementPaths.length] =
arrayConcat(oCurrentPath, arraySlice(optimizedPath, depth + 1));
rRemainingComplementPaths[rRemainingComplementPaths.length] =
rComplementPaths[rComplementPaths.length] =
arrayConcat(rCurrentPath, arraySlice(requestedPath, depth + depthDiff + 1));
break;
} else if (depth === optimizedPath.length - 1) {
// if reach end of optimized path successfully, add to intersections
intersections[intersections.length] = rCurrentPath;
}
// otherwise keep going
}

return [intersections, oRemainingComplementPaths, rRemainingComplementPaths];
// return accumulated intersection and complement pathsets
return [intersections, oComplementPaths, rComplementPaths];
}

23 changes: 23 additions & 0 deletions lib/response/get/checkCacheAndReport.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,27 @@ var getWithPathsAsPathMap = gets.getWithPathsAsPathMap;
* Checks cache for the paths and reports if in progressive mode. If
* there are missing paths then return the cache hit results.
*
* Return value (`results`) stores missing path information as 3 index-linked arrays:
* `requestedMissingPaths` holds requested paths that were not found in cache
* `optimizedMissingPaths` holds optimized versions of requested paths
* `depthDifferences` holds the difference in length of requested and optimized paths
*
* Note that requestedMissingPaths is not necessarily the list of paths requested by
* user in model.get. It does not contain those paths that were found in
* cache. It also breaks some path sets out into separate paths, those which
* resolve to different optimized lengths after walking through any references in
* cache.
* This helps maintain a 1:1 correspondence between requested and optimized missing,
* as well as their depth differences (or, length offsets).
*
* Example: Given cache: `{ lolomo: { 0: $ref('vid'), 1: $ref('a.b.c.d') }}`,
* `model.get('lolomo[0..2].name').subscribe()` will result in the following
* corresponding values:
* index requestedMissingPaths optimizedMissingPaths depthDifferences
* 0 ['lolomo', 0, 'name'] ['vid', 'name'] 1
* 1 ['lolomo', 1, 'name'] ['a', 'b', 'c', 'd', 'name'] -2
* 2 ['lolomo', 2, 'name'] ['lolomo', 2, 'name'] 0
*
* @param {Model} model - The model that the request was made with.
* @param {Array} requestedMissingPaths -
* @param {Boolean} progressive -
Expand All @@ -14,6 +35,8 @@ var getWithPathsAsPathMap = gets.getWithPathsAsPathMap;
* @param {Function} onError -
* @param {Function} onCompleted -
* @param {Object} seed - The state of the output
* @returns {Object} results -
*
* @private
*/
module.exports = function checkCacheAndReport(model, requestedPaths, observer,
Expand Down

0 comments on commit 28edebf

Please sign in to comment.