Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Bower components Debian packages RPM packages NuGet packages

pfchangs / react-relay   js

Repository URL to install this package:

Version: 0.7.1-ccinternal 

/ lib / splitDeferredRelayQueries.js

/**
 * Copyright 2013-2015, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule splitDeferredRelayQueries
 * 
 * @typechecks
 */

'use strict';

var _inherits = require('babel-runtime/helpers/inherits')['default'];

var _classCallCheck = require('babel-runtime/helpers/class-call-check')['default'];

Object.defineProperty(exports, '__esModule', {
  value: true
});
var QueryBuilder = require('./QueryBuilder');
var RelayNodeInterface = require('./RelayNodeInterface');
var RelayProfiler = require('./RelayProfiler');
var RelayQuery = require('./RelayQuery');
var RelayQueryTransform = require('./RelayQueryTransform');
var RelayRefQueryDescriptor = require('./RelayRefQueryDescriptor');

var invariant = require('fbjs/lib/invariant');

/**
 * Traverse `node` splitting off deferred query fragments into separate queries.
 *
 * @internal
 */
function splitDeferredRelayQueries(node) {
  var splitter = new GraphQLSplitDeferredQueries();
  var splitQueries = {
    __nodePath__: [],
    __parent__: null,
    __refQuery__: null,
    deferred: [],
    required: null
  };
  splitter.visit(node, splitQueries);

  return buildQueries(splitQueries);
}

/**
 * Returns the requisite siblings of `node`, but filters any non-requisite
 * children of those siblings.
 */
function getRequisiteSiblings(node, parent) {
  // Get the requisite siblings.
  var siblings = parent.getChildren().filter(function (child) {
    return child !== node && child instanceof RelayQuery.Field && child.isRequisite();
  });

  // Filter the non-requisite children from those siblings.
  return siblings.map(function (sibling) {
    var children = sibling.getChildren().filter(function (child) {
      return child instanceof RelayQuery.Field && child.isRequisite();
    });
    var clone = sibling.clone(children);
    !clone ? process.env.NODE_ENV !== 'production' ? invariant(false, 'splitDeferredRelayQueries(): Unexpected non-scalar, requisite field.') : invariant(false) : undefined;
    return clone;
  });
}

/**
 * Traverse the parent chain of `node` wrapping it at each level until it is
 * either:
 *
 * - wrapped in a RelayQuery.Root node
 * - wrapped in a non-root node that can be split off in a "ref query" (ie. a
 *   root call with a ref param that references another query)
 *
 * Additionally ensures that any requisite sibling fields are embedded in each
 * layer of the wrapper.
 */
function wrapNode(node, nodePath) {
  for (var ii = nodePath.length - 1; ii >= 0; ii--) {
    var _parent = nodePath[ii];
    if (_parent instanceof RelayQuery.Field && _parent.getInferredRootCallName()) {
      // We can make a "ref query" at this point, so stop wrapping.
      return new RelayRefQueryDescriptor(node, nodePath.slice(0, ii + 1));
    }

    var siblings = getRequisiteSiblings(node, _parent);
    var children = [node].concat(siblings);

    // Cast here because we know that `clone` will never return `null` (because
    // we always give it at least one child).
    node = _parent.clone(children);
  }
  !(node instanceof RelayQuery.Root) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'splitDeferredRelayQueries(): Cannot build query without a root node.') : invariant(false) : undefined;
  var identifyingArg = node.getIdentifyingArg();
  var identifyingArgName = identifyingArg && identifyingArg.name || null;
  var identifyingArgValue = identifyingArg && identifyingArg.value || null;
  var metadata = {
    identifyingArgName: identifyingArgName,
    isDeferred: true
  };
  return RelayQuery.Root.build(node.getName(), node.getFieldName(), identifyingArgValue, node.getChildren(), metadata, node.getType());
}

/**
 * Returns `true` if `node` is considered "empty", which means that it contains
 * no non-generated fields, and no ref query dependencies.
 */
function isEmpty(node) {
  if (node.isScalar()) {
    return node.isGenerated() && !node.isRefQueryDependency();
  } else {
    return node.getChildren().every(isEmpty);
  }
}

/**
 * Mutates and returns a nested `SplitQueries` structure, updating any deferred
 * "ref queries" to actually reference their contexts.
 */
function buildQueries(splitQueries) {
  if (splitQueries.required && isEmpty(splitQueries.required)) {
    splitQueries.required = null;
  }
  splitQueries.deferred = splitQueries.deferred.map(function (nestedSplitQueries) {
    var descriptor = nestedSplitQueries.__refQuery__;
    if (descriptor) {
      // Wrap the ref query node with a reference to the required query that is
      // its context.
      var context = splitQueries.required;
      if (!context) {
        // Traverse upwards looking for context.
        var parentSplitQueries = splitQueries;
        while (parentSplitQueries.__parent__) {
          context = parentSplitQueries.__parent__.required;
          if (context) {
            break;
          }
          parentSplitQueries = parentSplitQueries.__parent__;
        }
      }
      !context ? process.env.NODE_ENV !== 'production' ? invariant(false, 'splitDeferredRelayQueries(): Expected a context root query.') : invariant(false) : undefined;
      nestedSplitQueries.required = createRefQuery(descriptor, context);
    }

    return buildQueries(nestedSplitQueries);
  });
  return splitQueries;
}

/**
 * Wraps `descriptor` in a new top-level ref query.
 */
function createRefQuery(descriptor, context) {
  var node = descriptor.node;
  !(node instanceof RelayQuery.Field || node instanceof RelayQuery.Fragment) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'splitDeferredRelayQueries(): Ref query requires a field or fragment.') : invariant(false) : undefined;

  // Build up JSONPath.
  var jsonPath = ['$', '*'];
  var parent = undefined;
  for (var ii = 0; ii < descriptor.nodePath.length; ii++) {
    parent = descriptor.nodePath[ii];
    if (parent instanceof RelayQuery.Field) {
      jsonPath.push(parent.getSerializationKey());
      if (parent.isPlural()) {
        jsonPath.push('*');
      }
    }
  }
  !(jsonPath.length > 2) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'splitDeferredRelayQueries(): Ref query requires a complete path.') : invariant(false) : undefined;
  var field = parent; // Flow
  var primaryKey = field.getInferredPrimaryKey();
  !primaryKey ? process.env.NODE_ENV !== 'production' ? invariant(false, 'splitDeferredRelayQueries(): Ref query requires a primary key.') : invariant(false) : undefined;
  jsonPath.push(primaryKey);

  // Create the wrapper root query.
  var root = RelayQuery.Root.build(context.getName(), RelayNodeInterface.NODES, QueryBuilder.createBatchCallVariable(context.getID(), jsonPath.join('.')), [node], {
    identifyingArgName: RelayNodeInterface.ID,
    isDeferred: true
  }, RelayNodeInterface.NODE_TYPE);

  var result = root; // Flow
  return result;
}

/**
 * Traverses an input query, updating the passed in `SplitQueries` state object
 * to contain a nested structure representing the required and deferred portions
 * of the input query.
 */

var GraphQLSplitDeferredQueries = (function (_RelayQueryTransform) {
  _inherits(GraphQLSplitDeferredQueries, _RelayQueryTransform);

  function GraphQLSplitDeferredQueries() {
    _classCallCheck(this, GraphQLSplitDeferredQueries);

    _RelayQueryTransform.apply(this, arguments);
  }

  GraphQLSplitDeferredQueries.prototype.visitField = function visitField(node, splitQueries) {
    if (!node.hasDeferredDescendant()) {
      return node;
    }

    splitQueries.__nodePath__.push(node);
    var result = this.traverse(node, splitQueries);
    splitQueries.__nodePath__.pop();

    if (result && node.getInferredRootCallName()) {
      (function () {
        // The node is a ref query dependency; mark it as one.
        var key = node.getInferredPrimaryKey();
        var children = result.getChildren().map(function (child) {
          if (child instanceof RelayQuery.Field && child.getSchemaName() === key) {
            return child.cloneAsRefQueryDependency();
          } else {
            return child;
          }
        });
        result = result.clone(children);
      })();
    }

    return result;
  };

  GraphQLSplitDeferredQueries.prototype.visitFragment = function visitFragment(node, splitQueries) {
    if (!node.getChildren().length) {
      return null;
    }

    if (node.isDeferred()) {
      var nodePath = splitQueries.__nodePath__;
      var _deferred = {
        __nodePath__: nodePath,
        __parent__: splitQueries,
        __refQuery__: null,
        deferred: [],
        required: null
      };
      var result = this.traverse(node, _deferred);
      if (result) {
        var wrapped = wrapNode(result, nodePath);
        if (wrapped instanceof RelayQuery.Root) {
          _deferred.required = wrapped;
        } else if (wrapped instanceof RelayRefQueryDescriptor) {
          // for Flow
          _deferred.__refQuery__ = wrapped;
        }
      }
      if (result || _deferred.deferred.length) {
        splitQueries.deferred.push(_deferred);
      }
      return null;
    } else if (node.hasDeferredDescendant()) {
      return this.traverse(node, splitQueries);
    } else {
      return node;
    }
  };

  GraphQLSplitDeferredQueries.prototype.visitRoot = function visitRoot(node, splitQueries) {
    if (!node.hasDeferredDescendant()) {
      splitQueries.required = node;
      return node;
    } else {
      splitQueries.__nodePath__.push(node);
      var result = this.traverse(node, splitQueries);
      splitQueries.__nodePath__.pop();
      splitQueries.required = result;
      return result;
    }
  };

  return GraphQLSplitDeferredQueries;
})(RelayQueryTransform);

module.exports = RelayProfiler.instrument('splitDeferredRelayQueries', splitDeferredRelayQueries);