react-relay
Version:
A framework for building data-driven React applications.
286 lines (234 loc) • 11 kB
JavaScript
/**
* 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 RelayStoreData
*
* @typechecks
*/
'use strict';
var _classCallCheck = require('babel-runtime/helpers/class-call-check')['default'];
var _Object$keys = require('babel-runtime/core-js/object/keys')['default'];
var GraphQLStoreChangeEmitter = require('./GraphQLStoreChangeEmitter');
var GraphQLStoreDataHandler = require('./GraphQLStoreDataHandler');
var RelayChangeTracker = require('./RelayChangeTracker');
var RelayConnectionInterface = require('./RelayConnectionInterface');
var RelayNodeInterface = require('./RelayNodeInterface');
var RelayProfiler = require('./RelayProfiler');
var RelayQuery = require('./RelayQuery');
var RelayQueryTracker = require('./RelayQueryTracker');
var RelayQueryWriter = require('./RelayQueryWriter');
var RelayRecordStore = require('./RelayRecordStore');
var RelayStoreGarbageCollector = require('./RelayStoreGarbageCollector');
var forEachObject = require('fbjs/lib/forEachObject');
var invariant = require('fbjs/lib/invariant');
var generateForceIndex = require('./generateForceIndex');
var refragmentRelayQuery = require('./refragmentRelayQuery');
var resolveImmediate = require('fbjs/lib/resolveImmediate');
var warning = require('fbjs/lib/warning');
var writeRelayQueryPayload = require('./writeRelayQueryPayload');
var writeRelayUpdatePayload = require('./writeRelayUpdatePayload');
var CLIENT_MUTATION_ID = RelayConnectionInterface.CLIENT_MUTATION_ID;
// The source of truth for application data.
var _instance;
/**
* @internal
*
* Wraps the data caches and associated metadata tracking objects used by
* GraphQLStore/RelayStore.
*/
var RelayStoreData = (function () {
/**
* Get the data set backing actual Relay operations. Used in GraphQLStore.
*/
RelayStoreData.getDefaultInstance = function getDefaultInstance() {
if (!_instance) {
_instance = new RelayStoreData();
}
return _instance;
};
function RelayStoreData() {
_classCallCheck(this, RelayStoreData);
var cachedRecords = {};
var cachedRootCallMap = {};
var queuedRecords = {};
var records = {};
var rootCallMap = {};
var nodeRangeMap = {};
this._cacheManager = null;
this._cachePopulated = true;
this._cachedRecords = cachedRecords;
this._cachedRootCalls = cachedRootCallMap;
this._nodeRangeMap = nodeRangeMap;
this._records = records;
this._queuedRecords = queuedRecords;
this._queuedStore = new RelayRecordStore({ cachedRecords: cachedRecords, queuedRecords: queuedRecords, records: records }, { cachedRootCallMap: cachedRootCallMap, rootCallMap: rootCallMap }, nodeRangeMap);
this._recordStore = new RelayRecordStore({ records: records }, { rootCallMap: rootCallMap }, nodeRangeMap);
this._queryTracker = new RelayQueryTracker();
this._rootCalls = rootCallMap;
}
/**
* Creates a garbage collector for this instance. After initialization all
* newly added DataIDs will be registered in the created garbage collector.
* This will show a warning if data has already been added to the instance.
*/
RelayStoreData.prototype.initializeGarbageCollector = function initializeGarbageCollector() {
!!this._garbageCollector ? process.env.NODE_ENV !== 'production' ? invariant(false, 'RelayStoreData: Garbage collector is already initialized.') : invariant(false) : undefined;
var shouldInitialize = this._isStoreDataEmpty();
process.env.NODE_ENV !== 'production' ? warning(shouldInitialize, 'RelayStoreData: Garbage collection can only be initialized when no ' + 'data is present.') : undefined;
if (shouldInitialize) {
this._garbageCollector = new RelayStoreGarbageCollector(this);
}
};
/**
* Sets/clears the cache manager that is used to cache changes written to
* the store.
*/
RelayStoreData.prototype.injectCacheManager = function injectCacheManager(cacheManager) {
var cachedRecords = this._cachedRecords;
var cachedRootCallMap = this._cachedRootCalls;
var rootCallMap = this._rootCalls;
var queuedRecords = this._queuedRecords;
var records = this._records;
this._cacheManager = cacheManager;
this._cachePopulated = false;
this._queuedStore = new RelayRecordStore({ cachedRecords: cachedRecords, queuedRecords: queuedRecords, records: records }, { cachedRootCallMap: cachedRootCallMap, rootCallMap: rootCallMap }, this._nodeRangeMap, cacheManager);
this._recordStore = new RelayRecordStore({ records: records }, { rootCallMap: rootCallMap }, this._nodeRangeMap, cacheManager);
};
/**
* Runs the callback after all data has been read out from diskc cache into
* cachedRecords
*/
RelayStoreData.prototype.runWithDiskCache = function runWithDiskCache(callback) {
var _this = this;
if (this._cachePopulated || !this._cacheManager) {
resolveImmediate(callback);
} else {
this._cacheManager.readAllData(this._cachedRecords, this._cachedRootCalls, function () {
_this._cachePopulated = true;
callback();
});
}
};
/**
* Write the results of a query into the base record store.
*/
RelayStoreData.prototype.handleQueryPayload = function handleQueryPayload(query, response, forceIndex) {
var changeTracker = new RelayChangeTracker();
var writer = new RelayQueryWriter(this._recordStore, this._queryTracker, changeTracker, {
forceIndex: forceIndex,
updateTrackedQueries: true
});
writeRelayQueryPayload(writer, query, response);
this._handleChangedAndNewDataIDs(changeTracker.getChangeSet());
};
/**
* Write the results of an update into the base record store.
*/
RelayStoreData.prototype.handleUpdatePayload = function handleUpdatePayload(operation, payload, _ref) {
var configs = _ref.configs;
var isOptimisticUpdate = _ref.isOptimisticUpdate;
var changeTracker = new RelayChangeTracker();
var store = isOptimisticUpdate ? this.getRecordStoreForOptimisticMutation(payload[CLIENT_MUTATION_ID]) : this._recordStore;
var writer = new RelayQueryWriter(store, this._queryTracker, changeTracker, {
forceIndex: generateForceIndex(),
updateTrackedQueries: false
});
writeRelayUpdatePayload(writer, operation, payload, { configs: configs, isOptimisticUpdate: isOptimisticUpdate });
this._handleChangedAndNewDataIDs(changeTracker.getChangeSet());
};
/**
* Given a query fragment and a data ID, returns a root query that applies
* the fragment to the object specified by the data ID.
*/
RelayStoreData.prototype.buildFragmentQueryForDataID = function buildFragmentQueryForDataID(fragment, dataID) {
if (GraphQLStoreDataHandler.isClientID(dataID)) {
var path = this._queuedStore.getPathToRecord(dataID);
!path ? process.env.NODE_ENV !== 'production' ? invariant(false, 'RelayStoreData.buildFragmentQueryForDataID(): Cannot refetch ' + 'record `%s` without a path.', dataID) : invariant(false) : undefined;
var query = refragmentRelayQuery(path.getQuery(fragment));
!query ? process.env.NODE_ENV !== 'production' ? invariant(false, 'RelayStoreData.buildFragmentQueryForDataID(): Expected a query for ' + 'record `%s`.', dataID) : invariant(false) : undefined;
return query;
}
// Fragment fields cannot be spread directly into the root because they
// may not exist on the `Node` type.
return RelayQuery.Node.buildRoot(RelayNodeInterface.NODE, dataID, [fragment], { rootArg: RelayNodeInterface.ID }, fragment.getDebugName() || 'UnknownQuery');
};
RelayStoreData.prototype.getNodeData = function getNodeData() {
return this._records;
};
RelayStoreData.prototype.getQueuedData = function getQueuedData() {
return this._queuedRecords;
};
RelayStoreData.prototype.clearQueuedData = function clearQueuedData() {
var _this2 = this;
forEachObject(this._queuedRecords, function (_, key) {
delete _this2._queuedRecords[key];
GraphQLStoreChangeEmitter.broadcastChangeForID(key);
});
};
RelayStoreData.prototype.getCachedData = function getCachedData() {
return this._cachedRecords;
};
RelayStoreData.prototype.getGarbageCollector = function getGarbageCollector() {
return this._garbageCollector;
};
/**
* Get the record store with full data (cached, base, queued).
*/
RelayStoreData.prototype.getQueuedStore = function getQueuedStore() {
return this._queuedStore;
};
/**
* Get the record store with only the base data (no queued/cached data).
*/
RelayStoreData.prototype.getRecordStore = function getRecordStore() {
return this._recordStore;
};
RelayStoreData.prototype.getQueryTracker = function getQueryTracker() {
return this._queryTracker;
};
/**
* @deprecated
*
* Used temporarily by GraphQLStore, but all updates to this object are now
* handled through a `RelayRecordStore` instance.
*/
RelayStoreData.prototype.getRootCallData = function getRootCallData() {
return this._rootCalls;
};
RelayStoreData.prototype._isStoreDataEmpty = function _isStoreDataEmpty() {
return _Object$keys(this._records).length === 0 && _Object$keys(this._queuedRecords).length === 0 && _Object$keys(this._cachedRecords).length === 0;
};
/**
* Given a ChangeSet, broadcasts changes for updated DataIDs
* and registers new DataIDs with the garbage collector.
*/
RelayStoreData.prototype._handleChangedAndNewDataIDs = function _handleChangedAndNewDataIDs(changeSet) {
var updatedDataIDs = _Object$keys(changeSet.updated);
updatedDataIDs.forEach(GraphQLStoreChangeEmitter.broadcastChangeForID);
if (this._garbageCollector) {
var createdDataIDs = _Object$keys(changeSet.created);
var garbageCollector = this._garbageCollector;
createdDataIDs.forEach(function (dataID) {
return garbageCollector.register(dataID);
});
}
};
RelayStoreData.prototype.getRecordStoreForOptimisticMutation = function getRecordStoreForOptimisticMutation(clientMutationID) {
var cachedRecords = this._cachedRecords;
var cachedRootCallMap = this._cachedRootCalls;
var rootCallMap = this._rootCalls;
var queuedRecords = this._queuedRecords;
var records = this._records;
return new RelayRecordStore({ cachedRecords: cachedRecords, queuedRecords: queuedRecords, records: records }, { cachedRootCallMap: cachedRootCallMap, rootCallMap: rootCallMap }, this._nodeRangeMap, this._cacheManager, clientMutationID);
};
return RelayStoreData;
})();
RelayProfiler.instrumentMethods(RelayStoreData.prototype, {
handleQueryPayload: 'RelayStoreData.prototype.handleQueryPayload'
});
module.exports = RelayStoreData;