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

'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 GraphQLMutatorConstants = require('./GraphQLMutatorConstants');
var GraphQLRange = require('./GraphQLRange');
var GraphQLStoreDataHandler = require('./GraphQLStoreDataHandler');
var RelayConnectionInterface = require('./RelayConnectionInterface');

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

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

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

var CURSOR = RelayConnectionInterface.CURSOR;
var NODE = RelayConnectionInterface.NODE;

var EMPTY = '';
var FILTER_CALLS = '__filterCalls__';
var FORCE_INDEX = '__forceIndex__';
var RANGE = '__range__';
var RESOLVED_FRAGMENT_MAP = '__resolvedFragmentMap__';
var RESOLVED_FRAGMENT_MAP_GENERATION = '__resolvedFragmentMapGeneration__';
var PATH = '__path__';
var APPEND = GraphQLMutatorConstants.APPEND;
var PREPEND = GraphQLMutatorConstants.PREPEND;
var REMOVE = GraphQLMutatorConstants.REMOVE;

/**
 * @internal
 *
 * `RelayRecordStore` is the central repository for all data fetched by the
 * client. Data is stored as a map of IDs to Records. Records are maps of
 * field names to values.
 *
 * TODO: #6584253 Mediate access to node/cached/queued data via RelayRecordStore
 */

var RelayRecordStore = (function () {
  function RelayRecordStore(records, rootCallMaps, nodeConnectionMap, cacheWriter, clientMutationID) {
    _classCallCheck(this, RelayRecordStore);

    this._cacheWriter = cacheWriter;
    this._cachedRecords = records.cachedRecords;
    this._cachedRootCallMap = rootCallMaps && rootCallMaps.cachedRootCallMap || {};
    this._clientMutationID = clientMutationID;
    this._queuedRecords = records.queuedRecords;
    this._nodeConnectionMap = nodeConnectionMap || {};
    this._records = records.records;
    this._rootCallMap = rootCallMaps && rootCallMaps.rootCallMap || {};
    this._storage = [];
    if (this._queuedRecords) {
      this._storage.push(this._queuedRecords);
    }
    if (this._records) {
      this._storage.push(this._records);
    }
    if (this._cachedRecords) {
      this._storage.push(this._cachedRecords);
    }
  }

  /**
   * Filter calls to only those that specify conditions on the returned results
   * (ex: `orderby(TOP_STORIES)`), removing generic calls (ex: `first`, `find`).
   */

  /**
   * Get the data ID associated with a storage key (and optionally an
   * identifying argument value) for a root query.
   */

  RelayRecordStore.prototype.getDataID = function getDataID(storageKey, identifyingArgValue) {
    if (RelayNodeInterface.isNodeRootCall(storageKey)) {
      !(identifyingArgValue != null) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'RelayRecordStore.getDataID(): Argument to `%s()` ' + 'cannot be null or undefined.', storageKey) : invariant(false) : undefined;
      return identifyingArgValue;
    }
    if (identifyingArgValue == null) {
      identifyingArgValue = EMPTY;
    }
    if (this._rootCallMap.hasOwnProperty(storageKey) && this._rootCallMap[storageKey].hasOwnProperty(identifyingArgValue)) {
      return this._rootCallMap[storageKey][identifyingArgValue];
    } else if (this._cachedRootCallMap.hasOwnProperty(storageKey)) {
      return this._cachedRootCallMap[storageKey][identifyingArgValue];
    }
  };

  /**
   * Associate a data ID with a storage key (and optionally an identifying
   * argument value) for a root query.
   */

  RelayRecordStore.prototype.putDataID = function putDataID(storageKey, identifyingArgValue, dataID) {
    if (RelayNodeInterface.isNodeRootCall(storageKey)) {
      !(identifyingArgValue != null) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'RelayRecordStore.putDataID(): Argument to `%s()` ' + 'cannot be null or undefined.', storageKey) : invariant(false) : undefined;
      return;
    }
    if (identifyingArgValue == null) {
      identifyingArgValue = EMPTY;
    }
    this._rootCallMap[storageKey] = this._rootCallMap[storageKey] || {};
    this._rootCallMap[storageKey][identifyingArgValue] = dataID;
    if (this._cacheWriter) {
      this._cacheWriter.writeRootCall(storageKey, identifyingArgValue, dataID);
    }
  };

  /**
   * Returns the status of the record stored at `dataID`.
   */

  RelayRecordStore.prototype.getRecordState = function getRecordState(dataID) {
    var record = this._getRecord(dataID);
    if (record === null) {
      return 'NONEXISTENT';
    } else if (record === undefined) {
      return 'UNKNOWN';
    }
    return 'EXISTENT';
  };

  /**
   * Create an empty record at `dataID` if a record does not already exist.
   */

  RelayRecordStore.prototype.putRecord = function putRecord(dataID, typeName, path) {
    var target = this._queuedRecords || this._records;
    var prevRecord = target[dataID];
    if (prevRecord) {
      if (target === this._queuedRecords) {
        this._setClientMutationID(prevRecord);
      }
      return;
    }
    var nextRecord = {
      __dataID__: dataID,
      __typename: typeName
    };
    if (target === this._queuedRecords) {
      this._setClientMutationID(nextRecord);
    }
    if (GraphQLStoreDataHandler.isClientID(dataID)) {
      !path ? process.env.NODE_ENV !== 'production' ? invariant(false, 'RelayRecordStore.putRecord(): Expected a path for non-refetchable ' + 'record `%s`.', dataID) : invariant(false) : undefined;
      nextRecord[PATH] = path;
    }
    target[dataID] = nextRecord;
    var cacheWriter = this._cacheWriter;
    if (!this._queuedRecords && cacheWriter) {
      cacheWriter.writeField(dataID, '__dataID__', dataID, typeName);
    }
  };

  /**
   * Returns the path to a non-refetchable record.
   */

  RelayRecordStore.prototype.getPathToRecord = function getPathToRecord(dataID) {
    var path = this._getField(dataID, PATH);
    return path;
  };

  /**
   * Returns whether a given record is affected by an optimistic update.
   */

  RelayRecordStore.prototype.hasOptimisticUpdate = function hasOptimisticUpdate(dataID) {
    !this._queuedRecords ? process.env.NODE_ENV !== 'production' ? invariant(false, 'RelayRecordStore.hasOptimisticUpdate(): Optimistic updates require ' + 'queued records.') : invariant(false) : undefined;
    return this._queuedRecords.hasOwnProperty(dataID);
  };

  /**
   * Returns a list of client mutation IDs for queued mutations whose optimistic
   * updates are affecting the record corresponding the given dataID. Returns
   * null if the record isn't affected by any optimistic updates.
   */

  RelayRecordStore.prototype.getClientMutationIDs = function getClientMutationIDs(dataID) {
    !this._queuedRecords ? process.env.NODE_ENV !== 'production' ? invariant(false, 'RelayRecordStore.getClientMutationIDs(): Optimistic updates require ' + 'queued records.') : invariant(false) : undefined;
    var record = this._queuedRecords[dataID];
    return record ? record.__mutationIDs__ : null;
  };

  /**
   * Returns whether an error occurred during a mutation affecting the
   * given (queued) record.
   */

  RelayRecordStore.prototype.hasMutationError = function hasMutationError(dataID) {
    if (this._queuedRecords) {
      var record = this._queuedRecords[dataID];
      return !!(record && RelayRecordStatusMap.isErrorStatus(record.__status__));
    }
    return false;
  };

  /**
   * Sets the mutation status of a queued record to the given value.
   */

  RelayRecordStore.prototype.setMutationErrorStatus = function setMutationErrorStatus(dataID, hasError) {
    !this._queuedRecords ? process.env.NODE_ENV !== 'production' ? invariant(false, 'RelayRecordStore.setMutationErrorStatus(): Can only set the ' + 'mutation status of queued records.') : invariant(false) : undefined;
    var record = this._queuedRecords[dataID];
    !record ? process.env.NODE_ENV !== 'production' ? invariant(false, 'RelayRecordStore.setMutationErrorStatus(): Expected record `%s` to ' + 'exist before settings its mutation error status.', dataID) : invariant(false) : undefined;
    record.__status__ = RelayRecordStatusMap.setErrorStatus(record.__status__, hasError);
  };

  /**
   * Check whether a given record has received data for a deferred fragment.
   */

  RelayRecordStore.prototype.hasDeferredFragmentData = function hasDeferredFragmentData(dataID, fragmentID) {
    var resolvedFragmentMap = this._getField(dataID, RESOLVED_FRAGMENT_MAP);
    !(typeof resolvedFragmentMap === 'object' || resolvedFragmentMap == null) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'RelayRecordStore.hasDeferredFragmentData(): Expected the map of ' + 'resolved deferred fragments associated with record `%s` to be null or ' + 'an object. Found a(n) `%s`.', dataID, typeof resolvedFragmentMap) : invariant(false) : undefined;
    return !!(resolvedFragmentMap && resolvedFragmentMap[fragmentID]);
  };

  /**
   * Mark a given record as having received data for a deferred fragment.
   */

  RelayRecordStore.prototype.setHasDeferredFragmentData = function setHasDeferredFragmentData(dataID, fragmentID) {
    var record = this._getRecord(dataID);
    !record ? process.env.NODE_ENV !== 'production' ? invariant(false, 'RelayRecordStore.setHasDeferredFragmentData(): Expected record `%s` ' + 'to exist before marking it as having received data for the deferred ' + 'fragment with id `%s`.', dataID, fragmentID) : invariant(false) : undefined;
    var resolvedFragmentMap = record[RESOLVED_FRAGMENT_MAP];
    if (typeof resolvedFragmentMap !== 'object' || !resolvedFragmentMap) {
      resolvedFragmentMap = {};
    }
    resolvedFragmentMap[fragmentID] = true;
    record[RESOLVED_FRAGMENT_MAP] = resolvedFragmentMap;
    if (typeof record[RESOLVED_FRAGMENT_MAP_GENERATION] === 'number') {
      record[RESOLVED_FRAGMENT_MAP_GENERATION]++;
    } else {
      record[RESOLVED_FRAGMENT_MAP_GENERATION] = 0;
    }
  };

  /**
   * Delete the record at `dataID`, setting its value to `null`.
   */

  RelayRecordStore.prototype.deleteRecord = function deleteRecord(dataID) {
    var target = this._queuedRecords || this._records;
    target[dataID] = null;

    // Remove any links for this record
    if (!this._queuedRecords) {
      delete this._nodeConnectionMap[dataID];
      if (this._cacheWriter) {
        this._cacheWriter.writeNode(dataID, null);
      }
    }
  };

  RelayRecordStore.prototype.getType = function getType(dataID) {
    // `__typename` property is typed as `string`
    return this._getField(dataID, '__typename');
  };

  /**
   * Returns the value of the field for the given dataID.
   */

  RelayRecordStore.prototype.getField = function getField(dataID, storageKey) {
    return this._getField(dataID, storageKey);
  };

  /**
   * Sets the value of a scalar field.
   */

  RelayRecordStore.prototype.putField = function putField(dataID, storageKey, value) {
    var record = this._getRecordForWrite(dataID);
    !record ? process.env.NODE_ENV !== 'production' ? invariant(false, 'RelayRecordStore.putField(): Expected record `%s` to exist before ' + 'writing field `%s`.', dataID, storageKey) : invariant(false) : undefined;
    record[storageKey] = value;
    if (!this._queuedRecords && this._cacheWriter) {
      var typeName = record.__typename;
      this._cacheWriter.writeField(dataID, storageKey, value, typeName);
    }
  };

  /**
   * Clears the value of a field by setting it to null/undefined.
   */

  RelayRecordStore.prototype.deleteField = function deleteField(dataID, storageKey) {
    var record = this._getRecordForWrite(dataID);
    !record ? process.env.NODE_ENV !== 'production' ? invariant(false, 'RelayRecordStore.deleteField(): Expected record `%s` to exist before ' + 'deleting field `%s`.', dataID, storageKey) : invariant(false) : undefined;
    record[storageKey] = null;
    if (!this._queuedRecords && this._cacheWriter) {
      this._cacheWriter.writeField(dataID, storageKey, null);
    }
  };

  /**
   * Returns the Data ID of a linked record (eg the ID of the `address` record
   * in `actor{address}`).
   */

  RelayRecordStore.prototype.getLinkedRecordID = function getLinkedRecordID(dataID, storageKey) {
    var field = this._getField(dataID, storageKey);
    if (field == null) {
      return field;
    }
    !(typeof field === 'object' && field !== null && !Array.isArray(field)) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'RelayRecordStore.getLinkedRecordID(): Expected field `%s` for record ' + '`%s` to have a linked record.', storageKey, dataID) : invariant(false) : undefined;
    return field.__dataID__;
  };

  /**
   * Creates/updates a link between two records via the given field.
   */

  RelayRecordStore.prototype.putLinkedRecordID = function putLinkedRecordID(parentID, storageKey, recordID) {
    var parent = this._getRecordForWrite(parentID);
    !parent ? process.env.NODE_ENV !== 'production' ? invariant(false, 'RelayRecordStore.putLinkedRecordID(): Expected record `%s` to exist ' + 'before linking to record `%s`.', parentID, recordID) : invariant(false) : undefined;
    var record = this._getRecord(recordID);
    !record ? process.env.NODE_ENV !== 'production' ? invariant(false, 'RelayRecordStore.putLinkedRecordID(): Expected record `%s` to exist ' + 'before linking from record `%s`.', recordID, parentID) : invariant(false) : undefined;
    var fieldValue = {
      __dataID__: recordID
    };
    parent[storageKey] = fieldValue;
    if (!this._queuedRecords && this._cacheWriter) {
      this._cacheWriter.writeField(parentID, storageKey, fieldValue);
    }
  };

  /**
   * Returns an array of Data ID for a plural linked field (eg the actor IDs of
   * the `likers` in `story{likers}`).
   */

  RelayRecordStore.prototype.getLinkedRecordIDs = function getLinkedRecordIDs(dataID, storageKey) {
    var field = this._getField(dataID, storageKey);
    if (field == null) {
      return field;
    }
    !Array.isArray(field) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'RelayRecordStore.getLinkedRecordIDs(): Expected field `%s` for ' + 'record `%s` to have an array of linked records.', storageKey, dataID) : invariant(false) : undefined;
    return field.map(function (item, ii) {
      !(typeof item === 'object' && item.__dataID__) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'RelayRecordStore.getLinkedRecordIDs(): Expected element at index %s ' + 'in field `%s` for record `%s` to be a linked record.', ii, storageKey, dataID) : invariant(false) : undefined;
      return item.__dataID__;
    });
  };

  /**
   * Creates/updates a one-to-many link between records via the given field.
   */

  RelayRecordStore.prototype.putLinkedRecordIDs = function putLinkedRecordIDs(parentID, storageKey, recordIDs) {
    var _this = this;

    var parent = this._getRecordForWrite(parentID);
    !parent ? process.env.NODE_ENV !== 'production' ? invariant(false, 'RelayRecordStore.putLinkedRecordIDs(): Expected record `%s` to exist ' + 'before linking records.', parentID) : invariant(false) : undefined;
    var records = recordIDs.map(function (recordID) {
      var record = _this._getRecord(recordID);
      !record ? process.env.NODE_ENV !== 'production' ? invariant(false, 'RelayRecordStore.putLinkedRecordIDs(): Expected record `%s` to ' + 'exist before linking from `%s`.', recordID, parentID) : invariant(false) : undefined;
      return {
        __dataID__: recordID
      };
    });
    parent[storageKey] = records;
    if (!this._queuedRecords && this._cacheWriter) {
      this._cacheWriter.writeField(parentID, storageKey, records);
    }
  };

  /**
   * Gets the connectionIDs for all the connections that contain the given
   * record as a `node`, or null if the record does not appear as a `node` in
   * any connection.
   */

  RelayRecordStore.prototype.getConnectionIDsForRecord = function getConnectionIDsForRecord(dataID) {
    var connectionIDs = this._nodeConnectionMap[dataID];
    if (connectionIDs) {
      return _Object$keys(connectionIDs);
    }
    return null;
  };

  /**
   * Gets the connectionIDs for all variations of calls for the given base
   * schema name (Ex: `posts.orderby(recent)` and `posts.orderby(likes)`).
   */

  RelayRecordStore.prototype.getConnectionIDsForField = function getConnectionIDsForField(dataID, schemaName) {
    // ignore queued records because not all range fields may be present there
    var record = this._records[dataID];
    if (record == null) {
      return record;
    }
    var connectionIDs;
    forEachObject(record, function (datum, key) {
      if (datum && getFieldNameFromKey(key) === schemaName) {
        var dataID = datum.__dataID__;
        if (dataID) {
          connectionIDs = connectionIDs || [];
          connectionIDs.push(dataID);
        }
      }
    });
    return connectionIDs;
  };

  /**
   * Get the force index associated with the range at `connectionID`.
   */

  RelayRecordStore.prototype.getRangeForceIndex = function getRangeForceIndex(connectionID) {
    var forceIndex = this._getField(connectionID, FORCE_INDEX);
    if (forceIndex === null) {
      return -1;
    }
    // __forceIndex__ can only be a number
    return forceIndex || 0;
  };

  /**
   * Get the condition calls that were used to fetch the given connection.
   * Ex: for a field `photos.orderby(recent)`, this would be
   * [{name: 'orderby', value: 'recent'}]
   */

  RelayRecordStore.prototype.getRangeFilterCalls = function getRangeFilterCalls(connectionID) {
    return this._getField(connectionID, FILTER_CALLS);
  };

  /**
   * Returns range information for the given connection field:
   * - `filteredEdges`: any edges already fetched for the given `calls`.
   * - `diffCalls`: an array of calls describing the difference
   *   between the given `calls` and already fetched data. Includes conditional
   *   calls (`orderby`) and range/offset calls (`first`, `after`).
   * - `filterCalls`: the subset of `calls` that are condition calls
   *   (`orderby`).
   */

  RelayRecordStore.prototype.getRangeMetadata = function getRangeMetadata(connectionID, calls) {
    var _this2 = this;

    if (connectionID == null) {
      return connectionID;
    }
    var range = this._getField(connectionID, RANGE);
    if (range == null) {
      if (range === null) {
        process.env.NODE_ENV !== 'production' ? warning(false, 'RelayRecordStore.getRangeMetadata(): Expected range to exist if ' + '`edges` has been fetched.') : undefined;
      }
      return undefined;
    }
    var filterCalls = getFilterCalls(calls);
    // Edges can only be fetched if a range call (first/last/find) is given.
    // Otherwise return diffCalls/filterCalls with empty edges.
    if (calls.length === filterCalls.length) {
      return {
        diffCalls: calls,
        filterCalls: filterCalls,
        pageInfo: undefined,
        requestedEdgeIDs: [],
        filteredEdges: []
      };
    }
    var queuedRecord = this._queuedRecords ? this._queuedRecords[connectionID] : null;

    var _range$retrieveRangeInfoForQuery = range.retrieveRangeInfoForQuery(calls, queuedRecord);

    var diffCalls = _range$retrieveRangeInfoForQuery.diffCalls;
    var pageInfo = _range$retrieveRangeInfoForQuery.pageInfo;
    var requestedEdgeIDs = _range$retrieveRangeInfoForQuery.requestedEdgeIDs;

    if (diffCalls && diffCalls.length) {
      diffCalls = filterCalls.concat(diffCalls);
    } else {
      diffCalls = [];
    }
    var filteredEdges;
    if (requestedEdgeIDs) {
      filteredEdges = requestedEdgeIDs.map(function (edgeID) {
        return {
          edgeID: edgeID,
          nodeID: _this2.getLinkedRecordID(edgeID, NODE)
        };
      }).filter(function (edge) {
        return _this2._getRecord(edge.nodeID);
      });
    } else {
      filteredEdges = [];
    }
    return {
      diffCalls: diffCalls,
      filterCalls: filterCalls,
      pageInfo: pageInfo,
      requestedEdgeIDs: requestedEdgeIDs,
      filteredEdges: filteredEdges
    };
  };

  /**
   * Creates a range at `dataID` with an optional `forceIndex`.
   */

  RelayRecordStore.prototype.putRange = function putRange(connectionID, calls, forceIndex) {
    !!this._queuedRecords ? process.env.NODE_ENV !== 'production' ? invariant(false, 'RelayRecordStore.putRange(): Cannot create a queued range.') : invariant(false) : undefined;
    var record = this._getRecord(connectionID);
    !record ? process.env.NODE_ENV !== 'production' ? invariant(false, 'RelayRecordStore.putRange(): Expected record `%s` to exist before ' + 'adding a range.', connectionID) : invariant(false) : undefined;
    var range = new GraphQLRange();
    var filterCalls = getFilterCalls(calls);
    forceIndex = forceIndex || 0;
    record.__filterCalls__ = filterCalls;
    record.__forceIndex__ = forceIndex;
    record.__range__ = range;

    var cacheWriter = this._cacheWriter;
    if (!this._queuedRecords && cacheWriter) {
      cacheWriter.writeField(connectionID, FILTER_CALLS, filterCalls);
      cacheWriter.writeField(connectionID, FORCE_INDEX, forceIndex);
      cacheWriter.writeField(connectionID, RANGE, range);
    }
  };

  /**
   * Returns whether there is a range at `connectionID`.
   */

  RelayRecordStore.prototype.hasRange = function hasRange(connectionID) {
    return !!this._getField(connectionID, RANGE);
  };

  /**
   * Adds newly fetched edges to a range.
   */

  RelayRecordStore.prototype.putRangeEdges = function putRangeEdges(connectionID, calls, pageInfo, edges) {
    var _this3 = this;

    var range = this._getField(connectionID, RANGE);
    !range ? process.env.NODE_ENV !== 'production' ? invariant(false, 'RelayRecordStore.putRangeEdges(): Expected record `%s` to exist and ' + 'have a range.', connectionID) : invariant(false) : undefined;
    var edgesData = [];
    edges.forEach(function (edgeID) {
      var edgeData = _this3._getRangeEdgeData(edgeID);
      edgesData.push(edgeData);
      _this3._addConnectionForNode(connectionID, edgeData.node.__dataID__);
    });
    range.addItems(calls, edgesData, pageInfo);
    if (!this._queuedRecords && this._cacheWriter) {
      this._cacheWriter.writeField(connectionID, RANGE, range);
    }
  };

  /**
   * Prepend, append, or delete edges to/from a range.
   */

  RelayRecordStore.prototype.applyRangeUpdate = function applyRangeUpdate(connectionID, edgeID, operation) {
    if (this._queuedRecords) {
      this._applyOptimisticRangeUpdate(connectionID, edgeID, operation);
    } else {
      this._applyServerRangeUpdate(connectionID, edgeID, operation);
    }
  };

  /**
   * Completely removes the record identified by `dataID` from the store
   */

  RelayRecordStore.prototype.removeRecord = function removeRecord(dataID) {
    delete this._records[dataID];
    if (this._queuedRecords) {
      delete this._queuedRecords[dataID];
    }
    if (this._cachedRecords) {
      delete this._cachedRecords[dataID];
    }
    delete this._nodeConnectionMap[dataID];
  };

  /**
   * Get edge data in a format compatibile with `GraphQLRange`.
   * TODO: change `GraphQLRange` to accept `(edgeID, cursor, nodeID)` tuple
   */

  RelayRecordStore.prototype._getRangeEdgeData = function _getRangeEdgeData(edgeID) {
    var nodeID = this.getLinkedRecordID(edgeID, NODE);
    !nodeID ? process.env.NODE_ENV !== 'production' ? invariant(false, 'RelayRecordStore: Expected edge `%s` to have a `node` record.', edgeID) : invariant(false) : undefined;
    return {
      __dataID__: edgeID,
      cursor: this.getField(edgeID, CURSOR),
      node: {
        __dataID__: nodeID
      }
    };
  };

  RelayRecordStore.prototype._applyOptimisticRangeUpdate = function _applyOptimisticRangeUpdate(connectionID, edgeID, operation) {
    !this._queuedRecords ? process.env.NODE_ENV !== 'production' ? invariant(false, 'RelayRecordStore: Expected queued records to exist for optimistic ' + '`%s` update to record `%s`.', operation, connectionID) : invariant(false) : undefined;
    var record = this._queuedRecords[connectionID];
    if (!record) {
      // $FlowIssue: this fails with:
      // "property `append/prepend/remove` not found in object literal"
      record = { __dataID__: connectionID };
      this._queuedRecords[connectionID] = record;
    }
    this._setClientMutationID(record);
    var queue = record[operation];
    if (!queue) {
      queue = [];
      record[operation] = queue;
    }
    if (operation === PREPEND) {
      queue.unshift(edgeID);
    } else {
      queue.push(edgeID);
    }
  };

  RelayRecordStore.prototype._applyServerRangeUpdate = function _applyServerRangeUpdate(connectionID, edgeID, operation) {
    !this._records ? process.env.NODE_ENV !== 'production' ? invariant(false, 'RelayRecordStore: Expected base records to exist for `%s` update to ' + 'record `%s`.', operation, connectionID) : invariant(false) : undefined;
    var range = this._getField(connectionID, RANGE);
    !range ? process.env.NODE_ENV !== 'production' ? invariant(false, 'RelayRecordStore: Cannot apply `%s` update to non-existent record `%s`.', operation, connectionID) : invariant(false) : undefined;
    if (operation === REMOVE) {
      range.removeEdgeWithID(edgeID);
      var nodeID = this.getLinkedRecordID(edgeID, 'node');
      if (nodeID) {
        this._removeConnectionForNode(connectionID, nodeID);
      }
    } else {
      var edgeData = this._getRangeEdgeData(edgeID);
      this._addConnectionForNode(connectionID, edgeData.node.__dataID__);
      if (operation === APPEND) {
        range.appendEdge(this._getRangeEdgeData(edgeID));
      } else {
        // prepend
        range.prependEdge(this._getRangeEdgeData(edgeID));
      }
    }
    if (this._cacheWriter) {
      this._cacheWriter.writeField(connectionID, RANGE, range);
    }
  };

  /**
   * Record that the node is contained in the connection.
   */

  RelayRecordStore.prototype._addConnectionForNode = function _addConnectionForNode(connectionID, nodeID) {
    var connectionMap = this._nodeConnectionMap[nodeID];
    if (!connectionMap) {
      connectionMap = {};
      this._nodeConnectionMap[nodeID] = connectionMap;
    }
    connectionMap[connectionID] = true;
  };

  /**
   * Record that the given node is no longer part of the connection.
   */

  RelayRecordStore.prototype._removeConnectionForNode = function _removeConnectionForNode(connectionID, nodeID) {
    var connectionMap = this._nodeConnectionMap[nodeID];
    if (connectionMap) {
      delete connectionMap[connectionID];
      if (_Object$keys(connectionMap).length === 0) {
        delete this._nodeConnectionMap[nodeID];
      }
    }
  };

  /**
   * Gets the first version of the record from the available caches.
   */

  RelayRecordStore.prototype._getRecord = function _getRecord(dataID) {
    if (this._queuedRecords && this._queuedRecords.hasOwnProperty(dataID)) {
      return this._queuedRecords[dataID];
    } else if (this._records.hasOwnProperty(dataID)) {
      return this._records[dataID];
    } else if (this._cachedRecords) {
      return this._cachedRecords[dataID];
    }
  };

  /**
   * If the record is in the store, gets or creates a version of the record
   * in the store being used for writes.
   */

  RelayRecordStore.prototype._getRecordForWrite = function _getRecordForWrite(dataID) {
    // Cannot write to non-existent records, so ensure the record exists first.
    // Returning null/undefined allows for local invariant checks at call sites
    // with specific error messaging.
    var record = this._getRecord(dataID);
    if (!record) {
      return record;
    }
    // Create an empty version of the record in the writable store if it does
    // not already exist there.
    var source = this._queuedRecords || this._records;
    if (!source[dataID]) {
      record = source[dataID] = {
        __dataID__: dataID
      };
    }
    if (source === this._queuedRecords) {
      this._setClientMutationID(record);
    }
    return record;
  };

  /**
   * Get the value of the field from the first version of the record for which
   * the field is defined, returning `null` if the record has been deleted or
   * `undefined` if the record has not been fetched.
   */

  RelayRecordStore.prototype._getField = function _getField(dataID, storageKey) {
    var storage = this._storage;
    for (var ii = 0; ii < storage.length; ii++) {
      var record = storage[ii][dataID];
      if (record === null) {
        return null;
      } else if (record && record.hasOwnProperty(storageKey)) {
        return record[storageKey];
      }
    }
    return undefined;
  };

  /**
   * Injects the client mutation id associated with the record store instance
   * into the given record.
   */

  RelayRecordStore.prototype._setClientMutationID = function _setClientMutationID(record) {
    var clientMutationID = this._clientMutationID;
    !clientMutationID ? process.env.NODE_ENV !== 'production' ? invariant(false, 'RelayRecordStore: _clientMutationID cannot be null/undefined.') : invariant(false) : undefined;
    var mutationIDs = record.__mutationIDs__ || [];
    if (mutationIDs.indexOf(clientMutationID) === -1) {
      mutationIDs.push(clientMutationID);
      record.__mutationIDs__ = mutationIDs;
    }
    record.__status__ = RelayRecordStatusMap.setOptimisticStatus(0, true);
  };

  return RelayRecordStore;
})();

function getFilterCalls(calls) {
  return calls.filter(function (call) {
    return !RelayConnectionInterface.isConnectionCall(call);
  });
}

/**
 * Returns the field name based on the object key used to store the data in
 * nodeData. It returns the field name without any calls. For example, the
 * field name for 'profile_picture{size:"50"}' will be 'profile_picture'
 */
function getFieldNameFromKey(key) {
  // This is based on the GraphQL spec for what constitutes a valid field name.
  return key.split(/(?![_A-Za-z][_0-9A-Za-z]*)/, 1)[0];
}

module.exports = RelayRecordStore;