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 / RelayPendingQueryTracker.js

'use strict';

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

var _Object$keys = require('babel-runtime/core-js/object/keys')['default'];

Object.defineProperty(exports, '__esModule', {
  value: true
});

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

/**
 * 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 RelayPendingQueryTracker
 * @typechecks
 * 
 */

'use strict';

var Deferred = require('fbjs/lib/Deferred');
var DliteFetchModeConstants = require('./DliteFetchModeConstants');
var PromiseMap = require('fbjs/lib/PromiseMap');

var RelayTaskScheduler = require('./RelayTaskScheduler');

var containsRelayQueryRootCall = require('./containsRelayQueryRootCall');
var everyObject = require('fbjs/lib/everyObject');
var fetchRelayQuery = require('./fetchRelayQuery');
var invariant = require('fbjs/lib/invariant');
var subtractRelayQuery = require('./subtractRelayQuery');

/**
 * @internal
 *
 * Tracks pending (in-flight) queries.
 *
 * In order to send minimal queries and avoid re-retrieving data,
 * `RelayPendingQueryTracker` maintains a registry of pending queries, and
 * "subtracts" those from any new queries that callers enqueue.
 */

var RelayPendingQueryTracker = (function () {
  function RelayPendingQueryTracker(storeData) {
    _classCallCheck(this, RelayPendingQueryTracker);

    this._pendingFetchMap = {};
    this._preloadQueryMap = new PromiseMap();
    this._storeData = storeData;
  }

  /**
   * @private
   */

  /**
   * Used by `GraphQLQueryRunner` to enqueue new queries.
   */

  RelayPendingQueryTracker.prototype.add = function add(params) {
    return new PendingFetch(params, {
      pendingFetchMap: this._pendingFetchMap,
      preloadQueryMap: this._preloadQueryMap,
      storeData: this._storeData
    });
  };

  RelayPendingQueryTracker.prototype.hasPendingQueries = function hasPendingQueries() {
    return hasItems(this._pendingFetchMap);
  };

  /**
   * Clears all pending query tracking. Does not cancel the queries themselves.
   */

  RelayPendingQueryTracker.prototype.resetPending = function resetPending() {
    this._pendingFetchMap = {};
  };

  RelayPendingQueryTracker.prototype.resolvePreloadQuery = function resolvePreloadQuery(queryID, result) {
    this._preloadQueryMap.resolveKey(queryID, result);
  };

  RelayPendingQueryTracker.prototype.rejectPreloadQuery = function rejectPreloadQuery(queryID, error) {
    this._preloadQueryMap.rejectKey(queryID, error);
  };

  return RelayPendingQueryTracker;
})();

var PendingFetch = (function () {
  function PendingFetch(_ref, _ref2) {
    var fetchMode = _ref.fetchMode;
    var forceIndex = _ref.forceIndex;
    var query = _ref.query;
    var pendingFetchMap = _ref2.pendingFetchMap;
    var preloadQueryMap = _ref2.preloadQueryMap;
    var storeData = _ref2.storeData;
    return (function () {
      _classCallCheck(this, PendingFetch);

      var queryID = query.getID();
      this._dependents = [];
      this._forceIndex = forceIndex;
      this._pendingDependencyMap = {};
      this._pendingFetchMap = pendingFetchMap;
      this._preloadQueryMap = preloadQueryMap;
      this._query = query;
      this._resolvedDeferred = new Deferred();
      this._resolvedSubtractedQuery = false;
      this._storeData = storeData;

      var subtractedQuery;
      if (fetchMode === DliteFetchModeConstants.FETCH_MODE_PRELOAD) {
        subtractedQuery = query;
        this._fetchSubtractedQueryPromise = this._preloadQueryMap.get(queryID);
      } else {
        subtractedQuery = this._subtractPending(query);
        this._fetchSubtractedQueryPromise = subtractedQuery ? fetchRelayQuery(subtractedQuery) : Promise.resolve();
      }

      this._fetchedSubtractedQuery = !subtractedQuery;
      this._errors = [];

      if (subtractedQuery) {
        this._pendingFetchMap[queryID] = {
          fetch: this,
          query: subtractedQuery
        };
        this._fetchSubtractedQueryPromise.done(this._handleSubtractedQuerySuccess.bind(this, subtractedQuery), this._handleSubtractedQueryFailure.bind(this, subtractedQuery));
      } else {
        this._markSubtractedQueryAsResolved();
      }
    }).apply(this, arguments);
  }

  /**
   * A pending query is resolvable if it is already resolved or will be resolved
   * imminently (i.e. its subtracted query and the subtracted queries of all its
   * pending dependencies have been fetched).
   */

  PendingFetch.prototype.isResolvable = function isResolvable() {
    if (this._fetchedSubtractedQuery) {
      return everyObject(this._pendingDependencyMap, function (pendingDependency) {
        return pendingDependency._fetchedSubtractedQuery;
      });
      // Pending dependencies further down the graph either don't affect the
      // result or are already in `_pendingDependencyMap`.
    }
    return false;
  };

  PendingFetch.prototype.getQuery = function getQuery() {
    return this._query;
  };

  PendingFetch.prototype.getResolvedPromise = function getResolvedPromise() {
    return this._resolvedDeferred.getPromise();
  };

  /**
   * Subtracts all pending queries from the supplied `query` and returns the
   * resulting difference. The difference can be null if the entire query is
   * pending.
   *
   * If any pending queries were subtracted, they will be added as dependencies
   * and the query will only resolve once the subtracted query and all
   * dependencies have resolved.
   *
   * This, combined with our use of diff queries (see `diffRelayQuery`) means
   * that we only go to the server for things that are not in (or not on their
   * way to) the cache (`RelayRecordStore`).
   */

  PendingFetch.prototype._subtractPending = function _subtractPending(query) {
    var _this = this;

    everyObject(this._pendingFetchMap, function (pending) {
      // Stop if the entire query is subtracted.
      if (!query) {
        return false;
      }
      if (containsRelayQueryRootCall(pending.query, query)) {
        var subtractedQuery = subtractRelayQuery(query, pending.query);
        if (subtractedQuery !== query) {
          query = subtractedQuery;
          _this._addPendingDependency(pending.fetch);
        }
      }
      return true;
    });
    return query;
  };

  PendingFetch.prototype._addPendingDependency = function _addPendingDependency(pendingFetch) {
    var queryID = pendingFetch.getQuery().getID();
    this._pendingDependencyMap[queryID] = pendingFetch;
    pendingFetch._addDependent(this);
  };

  PendingFetch.prototype._addDependent = function _addDependent(pendingFetch) {
    this._dependents.push(pendingFetch);
  };

  PendingFetch.prototype._handleSubtractedQuerySuccess = function _handleSubtractedQuerySuccess(subtractedQuery, result) {
    var _this2 = this;

    this._fetchedSubtractedQuery = true;

    RelayTaskScheduler.enqueue(function () {
      var response = result.response;
      !(response && typeof response === 'object') ? process.env.NODE_ENV !== 'production' ? invariant(false, 'RelayPendingQueryTracker: Expected response to be an object, got ' + '`%s`.', response ? typeof response : response) : invariant(false) : undefined;
      _this2._storeData.handleQueryPayload(subtractedQuery, response, _this2._forceIndex);
    }).done(this._markSubtractedQueryAsResolved.bind(this), this._markAsRejected.bind(this));
  };

  PendingFetch.prototype._handleSubtractedQueryFailure = function _handleSubtractedQueryFailure(subtractedQuery, error) {
    this._markAsRejected(error);
  };

  PendingFetch.prototype._markSubtractedQueryAsResolved = function _markSubtractedQueryAsResolved() {
    var queryID = this.getQuery().getID();
    delete this._pendingFetchMap[queryID];

    this._resolvedSubtractedQuery = true;
    this._updateResolvedDeferred();

    this._dependents.forEach(function (dependent) {
      return dependent._markDependencyAsResolved(queryID);
    });
  };

  PendingFetch.prototype._markAsRejected = function _markAsRejected(error) {
    var queryID = this.getQuery().getID();
    delete this._pendingFetchMap[queryID];

    console.warn(error.message);

    this._errors.push(error);
    this._updateResolvedDeferred();

    this._dependents.forEach(function (dependent) {
      return dependent._markDependencyAsRejected(queryID, error);
    });
  };

  PendingFetch.prototype._markDependencyAsResolved = function _markDependencyAsResolved(dependencyQueryID) {
    delete this._pendingDependencyMap[dependencyQueryID];

    this._updateResolvedDeferred();
  };

  PendingFetch.prototype._markDependencyAsRejected = function _markDependencyAsRejected(dependencyQueryID, error) {
    delete this._pendingDependencyMap[dependencyQueryID];

    this._errors.push(error);
    this._updateResolvedDeferred();

    // Dependencies further down the graph are either not affected or informed
    // by `dependencyQueryID`.
  };

  PendingFetch.prototype._updateResolvedDeferred = function _updateResolvedDeferred() {
    if (this._isSettled() && !this._resolvedDeferred.isSettled()) {
      if (this._errors.length) {
        this._resolvedDeferred.reject(this._errors[0]);
      } else {
        this._resolvedDeferred.resolve(undefined);
      }
    }
  };

  PendingFetch.prototype._isSettled = function _isSettled() {
    return this._errors.length > 0 || this._resolvedSubtractedQuery && !hasItems(this._pendingDependencyMap);
  };

  return PendingFetch;
})();

function hasItems(map) {
  return !!_Object$keys(map).length;
}

exports.PendingFetch = PendingFetch;

module.exports = RelayPendingQueryTracker;

// Asynchronous mapping from preload query IDs to results.

/**
 * Error(s) in fetching/handleUpdate-ing its or one of its pending
 * dependency's subtracted query. There may be more than one error. However,
 * `_resolvedDeferred` is rejected with the earliest encountered error.
 */
// babel-eslint issue