From 0d545bb0b833d1631f55e4bc2fb8919ec898c005 Mon Sep 17 00:00:00 2001 From: Gerald Monaco Date: Sun, 15 Nov 2015 22:05:30 -0800 Subject: [PATCH] Make RelayStore contextual --- src/container/RelayContainer.js | 177 +++---------- src/container/RelayPropTypes.js | 4 + src/container/RelayRenderer.js | 39 +-- src/container/RelayRootContainer.js | 6 + .../RelayContainer_hasFragmentData-test.js | 6 +- ...RelayContainer_hasOptimisticUpdate-test.js | 17 +- src/legacy/store/GraphQLStoreChangeEmitter.js | 2 +- src/legacy/store/GraphQLStoreQueryResolver.js | 5 +- .../GraphQLDeferredQueryTracker-test.js | 4 +- src/mutation/RelayMutation.js | 12 +- src/mutation/__tests__/RelayMutation-test.js | 5 +- .../__tests__/RelayMutationQueue-test.js | 2 +- src/store/RelayContext.js | 249 ++++++++++++++++++ src/store/RelayDeferredFragmentTracker.js | 130 +++++++++ src/store/RelayStore.js | 180 ++----------- src/store/RelayStoreData.js | 13 - src/store/__mocks__/RelayContext.js | 12 + .../__mocks__/RelayDeferredFragmentTracker.js | 12 + src/store/__mocks__/RelayStore.js | 6 +- ...elayStore-test.js => RelayContext-test.js} | 34 +-- .../RelayStoreData_cacheManager-test.js | 2 +- src/tools/RelayGarbageCollection.js | 18 +- src/tools/RelayInternals.js | 2 - src/tools/__mocks__/RelayTestUtils.js | 7 +- 24 files changed, 565 insertions(+), 379 deletions(-) create mode 100644 src/store/RelayContext.js create mode 100644 src/store/RelayDeferredFragmentTracker.js create mode 100644 src/store/__mocks__/RelayContext.js create mode 100644 src/store/__mocks__/RelayDeferredFragmentTracker.js rename src/store/__tests__/{RelayStore-test.js => RelayContext-test.js} (81%) diff --git a/src/container/RelayContainer.js b/src/container/RelayContainer.js index 97a9db87b8f8f..234f93bd30ec5 100644 --- a/src/container/RelayContainer.js +++ b/src/container/RelayContainer.js @@ -17,21 +17,20 @@ import type {ConcreteFragment} from 'ConcreteQuery'; var ErrorUtils = require('ErrorUtils'); var GraphQLFragmentPointer = require('GraphQLFragmentPointer'); var GraphQLStoreDataHandler = require('GraphQLStoreDataHandler'); -var GraphQLStoreQueryResolver = require('GraphQLStoreQueryResolver'); +import type GraphQLStoreQueryResolver from 'GraphQLStoreQueryResolver'; var React = require('React'); var ReactDOM = require('ReactDOM'); +var RelayDeferredFragmentTracker = require('RelayDeferredFragmentTracker'); var RelayContainerComparators = require('RelayContainerComparators'); var RelayContainerProxy = require('RelayContainerProxy'); var RelayDeprecated = require('RelayDeprecated'); var RelayFragmentReference = require('RelayFragmentReference'); -import type {DataID, RelayQuerySet} from 'RelayInternalTypes'; +import type {RelayQuerySet} from 'RelayInternalTypes'; var RelayMetaRoute = require('RelayMetaRoute'); var RelayMutationTransaction = require('RelayMutationTransaction'); var RelayPropTypes = require('RelayPropTypes'); var RelayProfiler = require('RelayProfiler'); var RelayQuery = require('RelayQuery'); -var RelayStore = require('RelayStore'); -var RelayStoreData = require('RelayStoreData'); import type { Abortable, ComponentReadyStateChangeCallback, @@ -74,15 +73,10 @@ export type RootQueries = { var containerContextTypes = { route: RelayPropTypes.QueryConfig.isRequired, + relay: RelayPropTypes.RelayContext.isRequired, }; var nextContainerID = 0; -var storeData = RelayStoreData.getDefaultInstance(); - -storeData.getChangeEmitter().injectBatchingStrategy( - ReactDOM.unstable_batchedUpdates -); - /** * @public * @@ -111,8 +105,7 @@ function createContainerComponent( class RelayContainer extends React.Component { mounted: boolean; - _deferredErrors: ?Object; - _deferredSubscriptions: ?Object; + _deferredFragmentTracker: RelayDeferredFragmentTracker; _didShowFakeDataWarning: boolean; _fragmentPointers: {[key: string]: GraphQLFragmentPointer}; _hasNewDeferredData: boolean; @@ -148,8 +141,27 @@ function createContainerComponent( self.hasOptimisticUpdate = this.hasOptimisticUpdate.bind(this); self.setVariables = this.setVariables.bind(this); - this._deferredErrors = null; - this._deferredSubscriptions = null; + this._deferredFragmentTracker = + context.relay.createDeferredFragmentTracker({ + onSuccess: () => { + // Flag to force `shouldComponentUpdate` to return true. + this._hasNewDeferredData = true; + var deferredSuccessProfiler = RelayProfiler.profile( + 'RelayContainer.handleDeferredSuccess' + ); + var queryData = this._getQueryData(this.props); + this.setState({queryData}, deferredSuccessProfiler.stop); + }, + onFailure: () => { + // Flag to force `shouldComponentUpdate` to return true. + this._hasNewDeferredData = true; + var deferredFailureProfiler = RelayProfiler.profile( + 'RelayContainer.handleDeferredFailure' + ); + // Dummy `setState` to trigger re-render. + this.setState(this.state, deferredFailureProfiler.stop); + }, + }); this._didShowFakeDataWarning = false; this._fragmentPointers = {}; this._hasNewDeferredData = false; @@ -219,8 +231,8 @@ function createContainerComponent( queryData.forEach((data, ii) => { var dataID = GraphQLStoreDataHandler.getID(data); if (dataID) { - querySet[fragmentName + ii] = - storeData.buildFragmentQueryForDataID(fragment, dataID); + querySet[fragmentName + ii] = this.context.relay + .createFragmentQueryForDataID(fragment, dataID); dataIDs.push(dataID); } }); @@ -232,7 +244,7 @@ function createContainerComponent( if (dataID) { fragmentPointer = new GraphQLFragmentPointer(dataID, fragment); querySet[fragmentName] = - storeData.buildFragmentQueryForDataID(fragment, dataID); + this.context.relay.createFragmentQueryForDataID(fragment, dataID); } } @@ -314,8 +326,8 @@ function createContainerComponent( var current = { variables: nextVariables, request: forceFetch ? - RelayStore.forceFetch(querySet, onReadyStateChange) : - RelayStore.primeCache(querySet, onReadyStateChange), + this.context.relay.forceFetch(querySet, onReadyStateChange) : + this.context.relay.primeCache(querySet, onReadyStateChange), }; this.pending = current; } @@ -332,7 +344,7 @@ function createContainerComponent( 'RelayContainer.hasOptimisticUpdate(): Expected a record in `%s`.', componentName ); - return storeData.hasOptimisticUpdate(dataID); + return this.context.relay.hasOptimisticUpdate(dataID); } /** @@ -345,12 +357,7 @@ function createContainerComponent( 'RelayContainer.getPendingTransactions(): Expected a record in `%s`.', componentName ); - const mutationIDs = storeData.getClientMutationIDs(dataID); - if (!mutationIDs) { - return null; - } - const mutationQueue = storeData.getMutationQueue(); - return mutationIDs.map(id => mutationQueue.getTransaction(id)); + return this.context.relay.getPendingMutationTransactions(dataID); } /** @@ -360,10 +367,6 @@ function createContainerComponent( fragmentReference: RelayFragmentReference, record: Object ): ?Error { - var deferredErrors = this._deferredErrors; - if (!deferredErrors) { - return null; - } var dataID = GraphQLStoreDataHandler.getID(record); if (dataID == null) { // TODO: Throw instead, like we do in `hasFragmentData`, #7857010. @@ -387,8 +390,7 @@ function createContainerComponent( 'conditions.' ); var fragmentID = fragment.getFragmentID(); - var subscriptionKey = getSubscriptionKey(dataID, fragmentID); - return deferredErrors[subscriptionKey]; + return this._deferredFragmentTracker.getFragmentError(dataID, fragmentID); } /** @@ -400,13 +402,6 @@ function createContainerComponent( fragmentReference: RelayFragmentReference, record: Object ): boolean { - if ( - !storeData.getPendingQueryTracker().hasPendingQueries() && - !this._deferredErrors - ) { - // nothing can be missing => must have data - return true; - } // convert builder -> fragment in order to get the fragment's name var dataID = GraphQLStoreDataHandler.getID(record); invariant( @@ -429,40 +424,7 @@ function createContainerComponent( 'conditions.' ); var fragmentID = fragment.getFragmentID(); - var hasData = !storeData.getDeferredQueryTracker().isQueryPending( - dataID, - fragmentID - ); - - var subscriptionKey = getSubscriptionKey(dataID, fragmentID); - if (!hasData) { - // Query is pending: subscribe for updates to any missing deferred data. - var deferredSubscriptions = this._deferredSubscriptions || {}; - if (!this._deferredSubscriptions) { - this._deferredSubscriptions = deferredSubscriptions; - } - if (!deferredSubscriptions.hasOwnProperty(subscriptionKey)) { - deferredSubscriptions[subscriptionKey] = - storeData.getDeferredQueryTracker().addListenerForFragment( - dataID, - fragmentID, - { - onSuccess: this._handleDeferredSuccess.bind(this), - onFailure: this._handleDeferredFailure.bind(this), - } - ); - } - } else { - // query completed: check for errors - if ( - this._deferredErrors && - this._deferredErrors.hasOwnProperty(subscriptionKey) - ) { - hasData = false; - } - } - - return hasData; + return this._deferredFragmentTracker.hasFragmentData(dataID, fragmentID); } componentWillMount(): void { @@ -514,16 +476,7 @@ function createContainerComponent( ); } - // Remove any subscriptions for pending deferred queries. - var deferredSubscriptions = this._deferredSubscriptions; - if (deferredSubscriptions) { - forEachObject(deferredSubscriptions, subscription => { - subscription && subscription.remove(); - }); - } - - this._deferredErrors = null; - this._deferredSubscriptions = null; + this._deferredFragmentTracker.reset(); this._fragmentPointers = {}; this._queryResolvers = {}; @@ -547,8 +500,7 @@ function createContainerComponent( queryResolvers[fragmentName] = null; } } else if (!queryResolver) { - queryResolver = new GraphQLStoreQueryResolver( - storeData, + queryResolver = this.context.relay.createQueryResolver( fragmentPointer, this._handleFragmentDataUpdate.bind(this) ); @@ -702,54 +654,6 @@ function createContainerComponent( return queryData; } - /** - * Update query props when deferred data becomes available. - */ - _handleDeferredSuccess( - dataID: string, - fragmentID: string - ): void { - var subscriptionKey = getSubscriptionKey(dataID, fragmentID); - var deferredSubscriptions = this._deferredSubscriptions; - if (deferredSubscriptions && - deferredSubscriptions.hasOwnProperty(subscriptionKey)) { - // Flag to force `shouldComponentUpdate` to return true. - this._hasNewDeferredData = true; - deferredSubscriptions[subscriptionKey].remove(); - delete deferredSubscriptions[subscriptionKey]; - - var deferredSuccessProfiler = RelayProfiler.profile( - 'RelayContainer.handleDeferredSuccess' - ); - var queryData = this._getQueryData(this.props); - this.setState({queryData}, deferredSuccessProfiler.stop); - } - } - - /** - * Update query props when deferred queries fail. - */ - _handleDeferredFailure( - dataID: string, - fragmentID: string, - error: Error - ): void { - var subscriptionKey = getSubscriptionKey(dataID, fragmentID); - var deferredErrors = this._deferredErrors; - if (!deferredErrors) { - this._deferredErrors = deferredErrors = {}; - } - // Flag to force `shouldComponentUpdate` to return true. - this._hasNewDeferredData = true; - deferredErrors[subscriptionKey] = error; - - var deferredFailureProfiler = RelayProfiler.profile( - 'RelayContainer.handleDeferredFailure' - ); - // Dummy `setState` to trigger re-render. - this.setState(this.state, deferredFailureProfiler.stop); - } - shouldComponentUpdate( nextProps: Object, nextState: any, @@ -899,13 +803,6 @@ function resetPropOverridesForVariables( return variables; } -/** - * Constructs a unique key for a deferred subscription. - */ -function getSubscriptionKey(dataID: DataID, fragmentID: string): string { - return dataID + '.' + fragmentID; -} - function initializeProfiler(RelayContainer: RelayContainer): void { RelayProfiler.instrumentMethods(RelayContainer.prototype, { componentWillMount: diff --git a/src/container/RelayPropTypes.js b/src/container/RelayPropTypes.js index af7f7983cb906..93e1ea0584d73 100644 --- a/src/container/RelayPropTypes.js +++ b/src/container/RelayPropTypes.js @@ -15,6 +15,8 @@ const {PropTypes} = require('React'); +const RelayContext = require('RelayContext'); + const isRelayContainer = require('isRelayContainer'); const sprintf = require('sprintf'); @@ -43,6 +45,8 @@ const RelayPropTypes = { queries: PropTypes.object.isRequired, uri: PropTypes.object, }), + + RelayContext: PropTypes.instanceOf(RelayContext), }; module.exports = RelayPropTypes; diff --git a/src/container/RelayRenderer.js b/src/container/RelayRenderer.js index 73c154f0c0f0f..633fb4876b27d 100644 --- a/src/container/RelayRenderer.js +++ b/src/container/RelayRenderer.js @@ -13,18 +13,18 @@ 'use strict'; -const GraphQLFragmentPointer = require('GraphQLFragmentPointer'); const React = require('React'); +var ReactDOM = require('ReactDOM'); +import type RelayContext from 'RelayContext'; import type {RelayQueryConfigSpec} from 'RelayContainer'; const RelayPropTypes = require('RelayPropTypes'); -const RelayStore = require('RelayStore'); -const RelayStoreData = require('RelayStoreData'); import type { Abortable, ComponentReadyState, ReadyState, RelayContainer } from 'RelayTypes'; +const RelayStore = require('RelayStore'); const StaticContainer = require('StaticContainer.react'); const getRelayQueries = require('getRelayQueries'); @@ -37,6 +37,7 @@ type RelayRendererProps = { onReadyStateChange?: ?(readyState: ReadyState) => void; queryConfig: RelayQueryConfigSpec; render?: ?(renderArgs: RelayRendererRenderArgs) => ?ReactElement; + relayContext: RelayContext; }; type RelayRendererRenderArgs = { done: boolean; @@ -123,10 +124,17 @@ class RelayRenderer extends React.Component { super(props, context); this.mounted = true; this.state = this._runQueries(this.props); + + this.props.relayContext.injectBatchingStrategy( + ReactDOM.unstable_batchedUpdates + ); } getChildContext(): Object { - return {route: this.props.queryConfig}; + return { + route: this.props.queryConfig, + relay: this.props.relayContext, + }; } /** @@ -152,7 +160,9 @@ class RelayRenderer extends React.Component { if (readyState.ready && !props) { props = { ...queryConfig.params, - ...mapObject(querySet, createFragmentPointerForRoot), + ...mapObject(querySet, (query) => + this.props.relayContext.createFragmentPointerForRoot(query) + ), }; } this.setState({ @@ -171,8 +181,8 @@ class RelayRenderer extends React.Component { }; const request = forceFetch ? - RelayStore.forceFetch(querySet, onReadyStateChange) : - RelayStore.primeCache(querySet, onReadyStateChange); + this.props.relayContext.forceFetch(querySet, onReadyStateChange) : + this.props.relayContext.primeCache(querySet, onReadyStateChange); return { activeComponent: this.state ? this.state.activeComponent : null, @@ -283,25 +293,22 @@ class RelayRenderer extends React.Component { } } -function createFragmentPointerForRoot(query) { - return query ? - GraphQLFragmentPointer.createForRoot( - RelayStoreData.getDefaultInstance().getQueuedStore(), - query - ) : - null; -} - RelayRenderer.propTypes = { Component: RelayPropTypes.Container, forceFetch: PropTypes.bool, onReadyStateChange: PropTypes.func, queryConfig: RelayPropTypes.QueryConfig.isRequired, render: PropTypes.func, + relayContext: RelayPropTypes.RelayContext.isRequired, +}; + +RelayRenderer.defaultProps = { + relayContext: RelayStore, }; RelayRenderer.childContextTypes = { route: RelayPropTypes.QueryConfig.isRequired, + relay: RelayPropTypes.RelayContext.isRequired, }; module.exports = RelayRenderer; diff --git a/src/container/RelayRootContainer.js b/src/container/RelayRootContainer.js index afcd78df9e1f8..ec44890fb3b6c 100644 --- a/src/container/RelayRootContainer.js +++ b/src/container/RelayRootContainer.js @@ -15,6 +15,7 @@ const React = require('React'); import type {RelayQueryConfigSpec} from 'RelayContainer'; +import type RelayContext from 'RelayContext'; const RelayPropTypes = require('RelayPropTypes'); import type { ComponentFetchState, @@ -22,6 +23,7 @@ import type { RelayContainer, } from 'RelayTypes'; const RelayRenderer = require('RelayRenderer'); +const RelayStore = require('RelayStore'); type RootContainerProps = { Component: RelayContainer; @@ -34,6 +36,7 @@ type RootContainerProps = { ) => ReactElement; renderLoading?: ?() => ReactElement; route: RelayQueryConfigSpec; + relayContext: RelayContext; }; const {PropTypes} = React; @@ -102,6 +105,7 @@ function RelayRootContainer({ renderFetched, renderLoading, route, + relayContext, }: RootContainerProps): ReactElement { return ( { if (error) { if (renderFailure) { @@ -139,6 +144,7 @@ RelayRootContainer.propTypes = { renderFetched: PropTypes.func, renderLoading: PropTypes.func, route: RelayPropTypes.QueryConfig.isRequired, + relay: RelayPropTypes.RelayContext, }; RelayRootContainer.childContextTypes = { diff --git a/src/container/__tests__/RelayContainer_hasFragmentData-test.js b/src/container/__tests__/RelayContainer_hasFragmentData-test.js index ca5469cd6ffda..e119a10ce95bc 100644 --- a/src/container/__tests__/RelayContainer_hasFragmentData-test.js +++ b/src/container/__tests__/RelayContainer_hasFragmentData-test.js @@ -17,6 +17,7 @@ RelayTestUtils.unmockRelay(); var GraphQLStoreQueryResolver = require('GraphQLStoreQueryResolver'); var React = require('React'); var Relay = require('Relay'); +var RelayContext = require('RelayContext'); var RelayStoreData = require('RelayStoreData'); describe('RelayContainer.hasFragmentData', function() { @@ -29,7 +30,8 @@ describe('RelayContainer.hasFragmentData', function() { beforeEach(function() { jest.resetModuleRegistry(); - var storeData = RelayStoreData.getDefaultInstance(); + var storeData = new RelayStoreData(); + var relayContext = new RelayContext(storeData); deferredQueryTracker = storeData.getDeferredQueryTracker(); pendingQueryTracker = storeData.getPendingQueryTracker(); @@ -53,7 +55,7 @@ describe('RelayContainer.hasFragmentData', function() { mockRender = () => { return RelayTestRenderer.render(genMockPointer => { return ; - }); + }, null, relayContext); }; mockPointer = {__dataID__: '42'}; }); diff --git a/src/container/__tests__/RelayContainer_hasOptimisticUpdate-test.js b/src/container/__tests__/RelayContainer_hasOptimisticUpdate-test.js index 516962fd8eb66..7b93a1900dd21 100644 --- a/src/container/__tests__/RelayContainer_hasOptimisticUpdate-test.js +++ b/src/container/__tests__/RelayContainer_hasOptimisticUpdate-test.js @@ -19,15 +19,22 @@ jest.dontMock('RelayContainer'); var GraphQLStoreQueryResolver = require('GraphQLStoreQueryResolver'); var React = require('React'); var Relay = require('Relay'); +var RelayContext = require('RelayContext'); var RelayStoreData = require('RelayStoreData'); describe('RelayContainer.hasOptimisticUpdate', () => { + var relayContext; + var storeData; + var MockContainer; var RelayTestRenderer; beforeEach(() => { jest.resetModuleRegistry(); + storeData = new RelayStoreData(); + relayContext = new RelayContext(storeData); + class MockComponent extends React.Component { render() { return
; @@ -48,7 +55,7 @@ describe('RelayContainer.hasOptimisticUpdate', () => { it('throws for invalid records', () => { var instance = RelayTestRenderer.render(genMockPointer => { return ; - }); + }, null, relayContext); expect(() => { instance.hasOptimisticUpdate({}); @@ -59,23 +66,21 @@ describe('RelayContainer.hasOptimisticUpdate', () => { }); it('is only true for queued records', () => { - var storeData = RelayStoreData.getDefaultInstance(); var recordStore = storeData.getRecordStoreForOptimisticMutation('mutation'); recordStore.putRecord('123', 'Type'); var instance = RelayTestRenderer.render(genMockPointer => { return ; - }); + }, null, relayContext); expect(instance.hasOptimisticUpdate({__dataID__: '123'})).toBe(true); }); it('is false for non-queued records', () => { - RelayStoreData.getDefaultInstance().getRecordStore() - .putRecord('123', 'Type'); + storeData.getRecordStore().putRecord('123', 'Type'); var instance = RelayTestRenderer.render(genMockPointer => { return ; - }); + }, null, relayContext); expect(instance.hasOptimisticUpdate({__dataID__: '123'})).toBe(false); }); }); diff --git a/src/legacy/store/GraphQLStoreChangeEmitter.js b/src/legacy/store/GraphQLStoreChangeEmitter.js index cfadc06bc2997..8ffb1a7409182 100644 --- a/src/legacy/store/GraphQLStoreChangeEmitter.js +++ b/src/legacy/store/GraphQLStoreChangeEmitter.js @@ -18,7 +18,7 @@ import type GraphQLStoreRangeUtils from 'GraphQLStoreRangeUtils'; var resolveImmediate = require('resolveImmediate'); -type BatchStrategy = (callback: Function) => void; +export type BatchStrategy = (callback: Function) => void; type SubscriptionCallback = () => void; export type ChangeSubscription = { diff --git a/src/legacy/store/GraphQLStoreQueryResolver.js b/src/legacy/store/GraphQLStoreQueryResolver.js index fe1c4e47a7a34..128517a5aec8b 100644 --- a/src/legacy/store/GraphQLStoreQueryResolver.js +++ b/src/legacy/store/GraphQLStoreQueryResolver.js @@ -27,6 +27,7 @@ var readRelayQueryData = require('readRelayQueryData'); var recycleNodesInto = require('recycleNodesInto'); type DataIDSet = {[dataID: DataID]: any}; +export type GraphQLStoreQueryResolverCallback = () => void; /** * @internal @@ -37,7 +38,7 @@ type DataIDSet = {[dataID: DataID]: any}; * invocation to `resolve` has changed. */ class GraphQLStoreQueryResolver { - _callback: Function; + _callback: GraphQLStoreQueryResolverCallback; _fragmentPointer: GraphQLFragmentPointer; _resolver: ?( GraphQLStorePluralQueryResolver | @@ -48,7 +49,7 @@ class GraphQLStoreQueryResolver { constructor( storeData: RelayStoreData, fragmentPointer: GraphQLFragmentPointer, - callback: Function + callback: GraphQLStoreQueryResolverCallback ) { this.reset(); this._callback = callback; diff --git a/src/legacy/store/__tests__/GraphQLDeferredQueryTracker-test.js b/src/legacy/store/__tests__/GraphQLDeferredQueryTracker-test.js index 441181cb860c0..f703244640a5d 100644 --- a/src/legacy/store/__tests__/GraphQLDeferredQueryTracker-test.js +++ b/src/legacy/store/__tests__/GraphQLDeferredQueryTracker-test.js @@ -35,7 +35,9 @@ describe('GraphQLDeferredQueryTracker', () => { beforeEach(() => { jest.resetModuleRegistry(); - recordStore = RelayStoreData.getDefaultInstance().getRecordStore(); + var storeData = new RelayStoreData(); + + recordStore = storeData.getRecordStore(); deferredQueryTracker = new GraphQLDeferredQueryTracker(recordStore); }); diff --git a/src/mutation/RelayMutation.js b/src/mutation/RelayMutation.js index 42f687826cffd..677050d9ee832 100644 --- a/src/mutation/RelayMutation.js +++ b/src/mutation/RelayMutation.js @@ -17,7 +17,7 @@ import type {ConcreteFragment} from 'ConcreteQuery'; import type {RelayConcreteNode} from 'RelayQL'; var RelayFragmentReference = require('RelayFragmentReference'); import type RelayMetaRoute from 'RelayMetaRoute'; -var RelayStore = require('RelayStore'); +import type RelayContext from 'RelayContext'; import type { RelayMutationConfig, Variables, @@ -50,11 +50,12 @@ class RelayMutation { ) => Variables; props: Tp; + _props: Tp; _didShowFakeDataWarning: boolean; constructor(props: Tp) { + this._props = props; this._didShowFakeDataWarning = false; - this._resolveProps(props); } /** @@ -229,10 +230,11 @@ class RelayMutation { return null; } - _resolveProps(props: Tp): void { + _resolveProps(relayContext: RelayContext): void { const fragments = this.constructor.fragments; const initialVariables = this.constructor.initialVariables || {}; + const props = this._props; const resolvedProps = {...props}; forEachObject(fragments, (fragmentBuilder, fragmentName) => { var propValue = props[fragmentName]; @@ -277,7 +279,7 @@ class RelayMutation { return acc.concat(eachFragmentPointer.getDataIDs()); }, []); - resolvedProps[fragmentName] = RelayStore.readAll(fragment, dataIDs); + resolvedProps[fragmentName] = relayContext.readAll(fragment, dataIDs); } else { invariant( !Array.isArray(propValue), @@ -289,7 +291,7 @@ class RelayMutation { var fragmentPointer = propValue[concreteFragmentID]; if (fragmentPointer) { var dataID = fragmentPointer.getDataID(); - resolvedProps[fragmentName] = RelayStore.read(fragment, dataID); + resolvedProps[fragmentName] = relayContext.read(fragment, dataID); } else { if (__DEV__) { if (!this._didShowFakeDataWarning) { diff --git a/src/mutation/__tests__/RelayMutation-test.js b/src/mutation/__tests__/RelayMutation-test.js index 633924541ea15..b9b6dfd8e9dd1 100644 --- a/src/mutation/__tests__/RelayMutation-test.js +++ b/src/mutation/__tests__/RelayMutation-test.js @@ -61,12 +61,11 @@ describe('RelayMutation', function() { }); it('resolves props', () => { - /* eslint-disable no-new */ - new MockMutation({ + var mutation = new MockMutation({ bar: mockBarPointer, foo: mockFooPointer, }); - /* eslint-enable no-new */ + mutation._resolveProps(Relay.Store); expect(Relay.Store.read.mock.calls.length).toBe(2); var mockBarRequiredFragment = fromGraphQL.Fragment(buildRQL.Fragment( diff --git a/src/mutation/__tests__/RelayMutationQueue-test.js b/src/mutation/__tests__/RelayMutationQueue-test.js index 643421fc315ac..e0e1161e09591 100644 --- a/src/mutation/__tests__/RelayMutationQueue-test.js +++ b/src/mutation/__tests__/RelayMutationQueue-test.js @@ -39,7 +39,7 @@ describe('RelayMutationQueue', () => { fromGraphQL.Fragment = jest.genMockFunction().mockImplementation(f => f); RelayStoreData.prototype.handleUpdatePayload = jest.genMockFunction(); - storeData = RelayStoreData.getDefaultInstance(); + storeData = new RelayStoreData(); mutationQueue = storeData.getMutationQueue(); }); diff --git a/src/store/RelayContext.js b/src/store/RelayContext.js new file mode 100644 index 0000000000000..5307fa5691d1f --- /dev/null +++ b/src/store/RelayContext.js @@ -0,0 +1,249 @@ +/** + * 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 RelayContext + * @typechecks + * @flow + */ + +'use strict'; + +import type {BatchStrategy} from 'GraphQLStoreChangeEmitter'; +var GraphQLFragmentPointer = require('GraphQLFragmentPointer'); +var GraphQLStoreQueryResolver = require('GraphQLStoreQueryResolver'); +import type { + GraphQLStoreQueryResolverCallback +} from 'GraphQLStoreQueryResolver'; +var RelayDeferredFragmentTracker = require('RelayDeferredFragmentTracker'); +import type { + RelayDeferredFragmentTrackerOptions +} from 'RelayDeferredFragmentTracker'; +import type RelayMutation from 'RelayMutation'; +import type RelayMutationTransaction from 'RelayMutationTransaction'; +var RelayQuery = require('RelayQuery'); +var RelayQueryResultObservable = require('RelayQueryResultObservable'); +var RelayStoreData = require('RelayStoreData'); + +var forEachRootCallArg = require('forEachRootCallArg'); +var readRelayQueryData = require('readRelayQueryData'); + +import type { + Abortable, + Observable, + RelayMutationTransactionCommitCallbacks, + ReadyStateChangeCallback, + StoreReaderData, + StoreReaderOptions +} from 'RelayTypes'; + +import type { + DataID, + RelayQuerySet +} from 'RelayInternalTypes'; + +/** + * @public + * + * RelayContext is a caching layer that records GraphQL response data and + * enables resolving and subscribing to queries. + * + * === onReadyStateChange === + * + * Whenever Relay sends a request for data via GraphQL, an "onReadyStateChange" + * callback can be supplied. This callback is called one or more times with a + * `readyState` object with the following properties: + * + * aborted: Whether the request was aborted. + * done: Whether all response data has been fetched. + * error: An error in the event of a failure, or null if none. + * ready: Whether the queries are at least partially resolvable. + * stale: When resolvable during `forceFetch`, whether data is stale. + * + * If the callback is invoked with `aborted`, `done`, or a non-null `error`, the + * callback will never be called again. Example usage: + * + * function onReadyStateChange(readyState) { + * if (readyState.aborted) { + * // Request was aborted. + * } else if (readyState.error) { + * // Failure occurred. + * } else if (readyState.ready) { + * // Queries are at least partially resolvable. + * if (readyState.done) { + * // Queries are completely resolvable. + * } + * } + * } + * + */ +class RelayContext { + _storeData: RelayStoreData; + + constructor(storeData: ?RelayStoreData) { + this._storeData = storeData || new RelayStoreData(); + } + + /** + * Primes the store by sending requests for any missing data that would be + * required to satisfy the supplied set of queries. + */ + primeCache( + querySet: RelayQuerySet, + callback: ReadyStateChangeCallback + ): Abortable { + return this._storeData.getQueryRunner().run(querySet, callback); + } + + /** + * Forces the supplied set of queries to be fetched and written to the store. + * Any data that previously satisfied the queries will be overwritten. + */ + forceFetch( + querySet: RelayQuerySet, + callback: ReadyStateChangeCallback + ): Abortable { + return this._storeData.getQueryRunner().forceFetch(querySet, callback); + } + + /** + * Reads query data anchored at the supplied data ID. + */ + read( + node: RelayQuery.Node, + dataID: DataID, + options?: StoreReaderOptions + ): ?StoreReaderData { + return readRelayQueryData(this._storeData, node, dataID, options).data; + } + + /** + * Reads query data anchored at the supplied data IDs. + */ + readAll( + node: RelayQuery.Node, + dataIDs: Array, + options?: StoreReaderOptions + ): Array { + return dataIDs.map( + dataID => readRelayQueryData(this._storeData, node, dataID, options).data + ); + } + + /** + * Reads query data, where each element in the result array corresponds to a + * root call argument. If the root call has no arguments, the result array + * will contain exactly one element. + */ + readQuery( + root: RelayQuery.Root, + options?: StoreReaderOptions + ): Array { + const storageKey = root.getStorageKey(); + var results = []; + forEachRootCallArg(root, identifyingArgValue => { + var data; + var dataID = this._storeData.getQueuedStore() + .getDataID(storageKey, identifyingArgValue); + if (dataID != null) { + data = this.read(root, dataID, options); + } + results.push(data); + }); + return results; + } + + /** + * Reads and subscribes to query data anchored at the supplied data ID. The + * returned observable emits updates as the data changes over time. + */ + observe( + fragment: RelayQuery.Fragment, + dataID: DataID + ): Observable { + var fragmentPointer = new GraphQLFragmentPointer( + fragment.isPlural()? [dataID] : dataID, + fragment + ); + return new RelayQueryResultObservable(this._storeData, fragmentPointer); + } + + update( + mutation: RelayMutation, + callbacks?: RelayMutationTransactionCommitCallbacks + ): void { + mutation._resolveProps(this); + var transaction = this._storeData.getMutationQueue().createTransaction( + mutation, + callbacks + ); + transaction.commit(); + } + + createQueryResolver( + fragmentPointer: GraphQLFragmentPointer, + callback: GraphQLStoreQueryResolverCallback + ): GraphQLStoreQueryResolver { + return new GraphQLStoreQueryResolver( + this._storeData, + fragmentPointer, + callback + ); + } + + createFragmentQueryForDataID( + fragment: RelayQuery.Fragment, + dataID: DataID + ): RelayQuery.Root { + return this._storeData.buildFragmentQueryForDataID(fragment, dataID); + } + + createFragmentPointerForRoot( + root: RelayQuery.Root + ): any { + return root ? GraphQLFragmentPointer.createForRoot( + this._storeData.getQueuedStore(), + root + ) : + null; + } + + createDeferredFragmentTracker( + options: RelayDeferredFragmentTrackerOptions + ): RelayDeferredFragmentTracker { + return new RelayDeferredFragmentTracker( + this._storeData.getDeferredQueryTracker(), + this._storeData.getPendingQueryTracker(), + options + ); + } + + getPendingMutationTransactions( + dataID: DataID + ): ?Array { + const mutationIDs = this._storeData.getClientMutationIDs(dataID); + if (!mutationIDs) { + return null; + } + const mutationQueue = this._storeData.getMutationQueue(); + return mutationIDs.map(id => mutationQueue.getTransaction(id)); + } + + hasOptimisticUpdate( + dataID: DataID + ): boolean { + return this._storeData.hasOptimisticUpdate(dataID); + } + + injectBatchingStrategy( + batchStrategy: BatchStrategy + ): void { + this._storeData.getChangeEmitter().injectBatchingStrategy(batchStrategy); + } +} + +module.exports = RelayContext; diff --git a/src/store/RelayDeferredFragmentTracker.js b/src/store/RelayDeferredFragmentTracker.js new file mode 100644 index 0000000000000..62c54f3887a44 --- /dev/null +++ b/src/store/RelayDeferredFragmentTracker.js @@ -0,0 +1,130 @@ +/** + * 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 RelayDeferredFragmentTracker + * @flow + * @typechecks + */ + +'use strict'; + +import type {DataID} from 'RelayInternalTypes'; +import type GraphQLDeferredQueryTracker from 'GraphQLDeferredQueryTracker'; +import type RelayPendingQueryTracker from 'RelayPendingQueryTracker'; + +var forEachObject = require('forEachObject'); + +export type RelayDeferredFragmentTrackerOptions = { + onSuccess: () => void; + onFailure: () => void, +}; + +class RelayDeferredFragmentTracker { + _deferredErrors: {[key: string]: Error}; + _deferredSubscriptions: {[key: string]: any}; + _deferredQueryTracker: GraphQLDeferredQueryTracker; + _options: RelayDeferredFragmentTrackerOptions; + _pendingQueryTracker: RelayPendingQueryTracker; + + constructor( + deferredQueryTracker: GraphQLDeferredQueryTracker, + pendingQueryTracker: RelayPendingQueryTracker, + options: RelayDeferredFragmentTrackerOptions = { + onSuccess: () => {}, + onFailure: () => {}, + } + ) { + this._deferredErrors = {}; + this._deferredSubscriptions = {}; + this._deferredQueryTracker = deferredQueryTracker; + this._options = options; + this._pendingQueryTracker = pendingQueryTracker; + } + + getFragmentError(dataID: DataID, fragmentID: string): ?Error { + var subscriptionKey = getSubscriptionKey(dataID, fragmentID); + return this._deferredErrors[subscriptionKey]; + } + + hasFragmentData(dataID: DataID, fragmentID: string): boolean { + var hasKeys = false; + for (var key in this._deferredSubscriptions) { + if (this._deferredSubscriptions.hasOwnProperty(key)) { + hasKeys = true; + break; + } + } + if (!this._pendingQueryTracker.hasPendingQueries() && !hasKeys) { + return true; + } + var hasData = !this._deferredQueryTracker.isQueryPending( + dataID, + fragmentID + ); + var subscriptionKey = getSubscriptionKey(dataID, fragmentID); + if (!hasData) { + // Query is pending: subscribe for updates to any missing deferred data. + if (!this._deferredSubscriptions.hasOwnProperty(subscriptionKey)) { + this._deferredSubscriptions[subscriptionKey] = + this._deferredQueryTracker.addListenerForFragment( + dataID, + fragmentID, + { + onSuccess: this._handleDeferredSuccess.bind(this), + onFailure: this._handleDeferredFailure.bind(this), + } + ); + } + } else { + // query completed: check for errors + if (this._deferredErrors.hasOwnProperty(subscriptionKey)) { + hasData = false; + } + } + return hasData; + } + + reset(): void { + forEachObject(this._deferredSubscriptions, subscription => { + subscription && subscription.remove(); + }); + this._deferredErrors = {}; + this._deferredSubscriptions = {}; + } + + _handleDeferredSuccess( + dataID: string, + fragmentID: string + ): void { + var subscriptionKey = getSubscriptionKey(dataID, fragmentID); + if (this._deferredSubscriptions.hasOwnProperty(subscriptionKey)) { + this._deferredSubscriptions[subscriptionKey].remove(); + delete this._deferredSubscriptions[subscriptionKey]; + this._options.onSuccess(); + } + } + + _handleDeferredFailure( + dataID: string, + fragmentID: string, + error: Error + ): void { + var subscriptionKey = getSubscriptionKey(dataID, fragmentID); + this._deferredErrors[subscriptionKey] = error; + this._options.onFailure(); + } +} + +/** +* Constructs a unique key for a deferred subscription. +*/ +function getSubscriptionKey(dataID: DataID, fragmentID: string): string { + return dataID + '.' + fragmentID; +} + +module.exports = RelayDeferredFragmentTracker; diff --git a/src/store/RelayStore.js b/src/store/RelayStore.js index 95a88d5bd726b..e7d56af646a84 100644 --- a/src/store/RelayStore.js +++ b/src/store/RelayStore.js @@ -8,169 +8,33 @@ * * @providesModule RelayStore * @typechecks - * @flow */ 'use strict'; -var GraphQLFragmentPointer = require('GraphQLFragmentPointer'); -import type RelayMutation from 'RelayMutation'; -var RelayMutationTransaction = require('RelayMutationTransaction'); -var RelayQuery = require('RelayQuery'); -var RelayQueryResultObservable = require('RelayQueryResultObservable'); +var RelayContext = require('RelayContext'); var RelayStoreData = require('RelayStoreData'); -var forEachRootCallArg = require('forEachRootCallArg'); -var readRelayQueryData = require('readRelayQueryData'); - -import type { - Abortable, - Observable, - RelayMutationTransactionCommitCallbacks, - ReadyStateChangeCallback, - StoreReaderData, - StoreReaderOptions -} from 'RelayTypes'; - -import type { - DataID, - RelayQuerySet -} from 'RelayInternalTypes'; - -var storeData = RelayStoreData.getDefaultInstance(); -var queryRunner = storeData.getQueryRunner(); -var queuedStore = storeData.getQueuedStore(); - -/** - * @public - * - * RelayStore is a caching layer that records GraphQL response data and enables - * resolving and subscribing to queries. - * - * === onReadyStateChange === - * - * Whenever Relay sends a request for data via GraphQL, an "onReadyStateChange" - * callback can be supplied. This callback is called one or more times with a - * `readyState` object with the following properties: - * - * aborted: Whether the request was aborted. - * done: Whether all response data has been fetched. - * error: An error in the event of a failure, or null if none. - * ready: Whether the queries are at least partially resolvable. - * stale: When resolvable during `forceFetch`, whether data is stale. - * - * If the callback is invoked with `aborted`, `done`, or a non-null `error`, the - * callback will never be called again. Example usage: - * - * function onReadyStateChange(readyState) { - * if (readyState.aborted) { - * // Request was aborted. - * } else if (readyState.error) { - * // Failure occurred. - * } else if (readyState.ready) { - * // Queries are at least partially resolvable. - * if (readyState.done) { - * // Queries are completely resolvable. - * } - * } - * } - * - */ -var RelayStore = { - - /** - * Primes the store by sending requests for any missing data that would be - * required to satisfy the supplied set of queries. - */ - primeCache( - querySet: RelayQuerySet, - callback: ReadyStateChangeCallback - ): Abortable { - return queryRunner.run(querySet, callback); - }, - - /** - * Forces the supplied set of queries to be fetched and written to the store. - * Any data that previously satisfied the queries will be overwritten. - */ - forceFetch( - querySet: RelayQuerySet, - callback: ReadyStateChangeCallback - ): Abortable { - return queryRunner.forceFetch(querySet, callback); - }, - - /** - * Reads query data anchored at the supplied data ID. - */ - read( - node: RelayQuery.Node, - dataID: DataID, - options?: StoreReaderOptions - ): ?StoreReaderData { - return readRelayQueryData(storeData, node, dataID, options).data; - }, - - /** - * Reads query data anchored at the supplied data IDs. - */ - readAll( - node: RelayQuery.Node, - dataIDs: Array, - options?: StoreReaderOptions - ): Array { - return dataIDs.map( - dataID => readRelayQueryData(storeData, node, dataID, options).data - ); - }, - - /** - * Reads query data, where each element in the result array corresponds to a - * root call argument. If the root call has no arguments, the result array - * will contain exactly one element. - */ - readQuery( - root: RelayQuery.Root, - options?: StoreReaderOptions - ): Array { - const storageKey = root.getStorageKey(); - var results = []; - forEachRootCallArg(root, identifyingArgValue => { - var data; - var dataID = queuedStore.getDataID(storageKey, identifyingArgValue); - if (dataID != null) { - data = RelayStore.read(root, dataID, options); - } - results.push(data); - }); - return results; - }, - - /** - * Reads and subscribes to query data anchored at the supplied data ID. The - * returned observable emits updates as the data changes over time. - */ - observe( - fragment: RelayQuery.Fragment, - dataID: DataID - ): Observable { - var fragmentPointer = new GraphQLFragmentPointer( - fragment.isPlural()? [dataID] : dataID, - fragment - ); - return new RelayQueryResultObservable(storeData, fragmentPointer); - }, - - update( - mutation: RelayMutation, - callbacks?: RelayMutationTransactionCommitCallbacks - ): void { - var transaction = storeData.getMutationQueue().createTransaction( - mutation, - callbacks +var warning = require('warning'); + +var _relayStore = new RelayContext(new RelayStoreData()); + +for (var propName in _relayStore) { + var oldFn = _relayStore[propName]; + if (!_relayStore.hasOwnProperty(propName) || !typeof oldFn !== 'function') { + continue; + } + _relayStore[propName] = function(...args) { + warning( + true, + 'RelayStore: Using RelayStore as a singleton (e.g. Relay.Store.%s) is ' + + 'deprecated. Set `relayContext` on your RelayRenderer and call ' + + '`this.props.relay.%s` instead.', + propName, + propName ); - transaction.commit(); - }, -}; + return oldFn.call(_relayStore, args); + }; +} -module.exports = RelayStore; +module.exports = _relayStore; diff --git a/src/store/RelayStoreData.js b/src/store/RelayStoreData.js index 80db313ef6e06..bed2ac42f634d 100644 --- a/src/store/RelayStoreData.js +++ b/src/store/RelayStoreData.js @@ -53,9 +53,6 @@ var writeRelayUpdatePayload = require('writeRelayUpdatePayload'); var {CLIENT_MUTATION_ID} = RelayConnectionInterface; -// The source of truth for application data. -var _instance; - /** * @internal * @@ -82,16 +79,6 @@ class RelayStoreData { _rangeData: GraphQLStoreRangeUtils; _rootCalls: RootCallMap; - /** - * Get the data set backing actual Relay operations. Used in GraphQLStore. - */ - static getDefaultInstance(): RelayStoreData { - if (!_instance) { - _instance = new RelayStoreData(); - } - return _instance; - } - constructor() { var cachedRecords: Records = ({}: $FixMe); var cachedRootCallMap: RootCallMap = {}; diff --git a/src/store/__mocks__/RelayContext.js b/src/store/__mocks__/RelayContext.js new file mode 100644 index 0000000000000..cf9f2aec90fd4 --- /dev/null +++ b/src/store/__mocks__/RelayContext.js @@ -0,0 +1,12 @@ +/** + * 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. + */ + +'use strict'; + +module.exports = require.requireActual('RelayContext'); diff --git a/src/store/__mocks__/RelayDeferredFragmentTracker.js b/src/store/__mocks__/RelayDeferredFragmentTracker.js new file mode 100644 index 0000000000000..454fd42fabc9f --- /dev/null +++ b/src/store/__mocks__/RelayDeferredFragmentTracker.js @@ -0,0 +1,12 @@ +/** + * 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. + */ + +'use strict'; + +module.exports = require.requireActual('RelayDeferredFragmentTracker'); diff --git a/src/store/__mocks__/RelayStore.js b/src/store/__mocks__/RelayStore.js index f79e6f0016062..53b7e80fb2aed 100644 --- a/src/store/__mocks__/RelayStore.js +++ b/src/store/__mocks__/RelayStore.js @@ -9,8 +9,8 @@ 'use strict'; -var RelayStore = jest.genMockFromModule('RelayStore'); var RelayRecordStore = require('RelayRecordStore'); +var RelayStore = require.requireActual('RelayStore'); var resolveImmediate = require('resolveImmediate'); @@ -51,6 +51,7 @@ function genMockRequest(args) { }; } +RelayStore.primeCache = jest.genMockFunction(); RelayStore.primeCache.mock.abort = []; RelayStore.primeCache.mock.requests = []; RelayStore.primeCache.mockImplementation((...args) => { @@ -65,6 +66,7 @@ RelayStore.primeCache.mockImplementation((...args) => { return returnValue; }); +RelayStore.forceFetch = jest.genMockFunction(); RelayStore.forceFetch.mock.abort = []; RelayStore.forceFetch.mock.requests = []; RelayStore.forceFetch.mockImplementation((...args) => { @@ -79,6 +81,8 @@ RelayStore.forceFetch.mockImplementation((...args) => { return returnValue; }); +RelayStore.read = jest.genMockFunction(); + RelayStore.mock = { setMockRecords: records => { RelayStore.mock.recordStore = new RelayRecordStore({records}); diff --git a/src/store/__tests__/RelayStore-test.js b/src/store/__tests__/RelayContext-test.js similarity index 81% rename from src/store/__tests__/RelayStore-test.js rename to src/store/__tests__/RelayContext-test.js index 234243d53439f..966fea6ef3a79 100644 --- a/src/store/__tests__/RelayStore-test.js +++ b/src/store/__tests__/RelayContext-test.js @@ -11,19 +11,20 @@ 'use strict'; -jest.dontMock('RelayStore'); +jest.dontMock('RelayContext'); var RelayTestUtils = require('RelayTestUtils'); RelayTestUtils.unmockRelay(); var GraphQLStoreQueryResolver = require('GraphQLStoreQueryResolver'); var Relay = require('Relay'); +var RelayContext = require('RelayContext'); var RelayQueryResultObservable = require('RelayQueryResultObservable'); var RelayStoreData = require('RelayStoreData'); var readRelayQueryData = require('readRelayQueryData'); -describe('RelayStore', () => { - var RelayStore; +describe('RelayContext', () => { + var relayContext; var filter; var dataIDs; @@ -37,19 +38,20 @@ describe('RelayStore', () => { beforeEach(() => { jest.resetModuleRegistry(); - RelayStore = require('RelayStore'); + var storeData = new RelayStoreData(); + relayContext = new RelayContext(storeData); filter = () => true; dataIDs = ['feedback_id', 'likers_id']; queries = {}; callback = jest.genMockFunction(); - queryRunner = RelayStoreData.getDefaultInstance().getQueryRunner(); - recordStore = RelayStoreData.getDefaultInstance().getRecordStore(); + queryRunner = storeData.getQueryRunner(); + recordStore = storeData.getRecordStore(); }); describe('primeCache', () => { it('invokes `GraphQLQueryRunner#run`', () => { - RelayStore.primeCache(queries, callback); + relayContext.primeCache(queries, callback); expect(queryRunner.run).toBeCalled(); expect(queryRunner.run.mock.calls[0][0]).toBe(queries); @@ -59,7 +61,7 @@ describe('RelayStore', () => { describe('forceFetch', () => { it('invokes `GraphQLQueryRunner#forceFetch`', () => { - RelayStore.forceFetch(queries, callback); + relayContext.forceFetch(queries, callback); expect(queryRunner.forceFetch).toBeCalled(); expect(queryRunner.forceFetch.mock.calls[0][0]).toBe(queries); @@ -69,7 +71,7 @@ describe('RelayStore', () => { describe('read', () => { it('invokes `readRelayQueryData`', () => { - RelayStore.read(queries, dataIDs[0]); + relayContext.read(queries, dataIDs[0]); expect(readRelayQueryData).toBeCalled(); expect(readRelayQueryData.mock.calls[0][1]).toEqual(queries); expect(readRelayQueryData.mock.calls[0][2]).toBe(dataIDs[0]); @@ -77,7 +79,7 @@ describe('RelayStore', () => { }); it('invokes `readRelayQueryData` with a filter', () => { - RelayStore.read(queries, dataIDs[0], filter); + relayContext.read(queries, dataIDs[0], filter); expect(readRelayQueryData).toBeCalled(); expect(readRelayQueryData.mock.calls[0][3]).toBe(filter); }); @@ -85,7 +87,7 @@ describe('RelayStore', () => { describe('readAll', () => { it('invokes `readRelayQueryData`', () => { - RelayStore.readAll(queries, dataIDs); + relayContext.readAll(queries, dataIDs); expect(readRelayQueryData.mock.calls.length).toBe(dataIDs.length); expect(readRelayQueryData.mock.calls.map(call => call[2])).toEqual( dataIDs @@ -93,7 +95,7 @@ describe('RelayStore', () => { }); it('invokes `readRelayQueryData` with a filter', () => { - RelayStore.readAll(queries, dataIDs, filter); + relayContext.readAll(queries, dataIDs, filter); expect(readRelayQueryData.mock.calls.length).toBe(dataIDs.length); readRelayQueryData.mock.calls.forEach((call) => { expect(call[3]).toBe(filter); @@ -104,20 +106,20 @@ describe('RelayStore', () => { describe('readQuery', () => { it('accepts a query with no arguments', () => { recordStore.putDataID('viewer', null, 'client:1'); - RelayStore.readQuery(getNode(Relay.QL`query{viewer{actor{id}}}`)); + relayContext.readQuery(getNode(Relay.QL`query{viewer{actor{id}}}`)); expect(readRelayQueryData.mock.calls.length).toBe(1); expect(readRelayQueryData.mock.calls[0][2]).toBe('client:1'); }); it('accepts a query with arguments', () => { - RelayStore.readQuery(getNode(Relay.QL`query{nodes(ids:["123","456"]){id}}`)); + relayContext.readQuery(getNode(Relay.QL`query{nodes(ids:["123","456"]){id}}`)); expect(readRelayQueryData.mock.calls.length).toBe(2); expect(readRelayQueryData.mock.calls[0][2]).toBe('123'); expect(readRelayQueryData.mock.calls[1][2]).toBe('456'); }); it('accepts a query with unrecognized arguments', () => { - var result = RelayStore.readQuery(getNode(Relay.QL`query{username(name:"foo"){id}}`)); + var result = relayContext.readQuery(getNode(Relay.QL`query{username(name:"foo"){id}}`)); expect(readRelayQueryData.mock.calls.length).toBe(0); expect(result).toEqual([undefined]); }); @@ -139,7 +141,7 @@ describe('RelayStore', () => { }; }); - var observer = RelayStore.observe(fragment, '123'); + var observer = relayContext.observe(fragment, '123'); var onNext = jest.genMockFunction(); expect(observer instanceof RelayQueryResultObservable).toBe(true); observer.subscribe({onNext}); diff --git a/src/store/__tests__/RelayStoreData_cacheManager-test.js b/src/store/__tests__/RelayStoreData_cacheManager-test.js index 644cf17048e37..dbed211d240ac 100644 --- a/src/store/__tests__/RelayStoreData_cacheManager-test.js +++ b/src/store/__tests__/RelayStoreData_cacheManager-test.js @@ -56,7 +56,7 @@ describe('RelayStoreData', function() { } = RelayConnectionInterface); cacheManager = RelayMockCacheManager.genCacheManager(); - storeData = RelayStoreData.getDefaultInstance(); + storeData = new RelayStoreData(); storeData.injectCacheManager(cacheManager); jest.addMatchers({ diff --git a/src/tools/RelayGarbageCollection.js b/src/tools/RelayGarbageCollection.js index 81536c3ff31b0..401c2f1f63963 100644 --- a/src/tools/RelayGarbageCollection.js +++ b/src/tools/RelayGarbageCollection.js @@ -33,9 +33,9 @@ var RelayGarbageCollection = { * collection. */ initialize(): void { - RelayStoreData - .getDefaultInstance() - .initializeGarbageCollector(); + // RelayStoreData + // .getDefaultInstance() + // .initializeGarbageCollector(); }, /** @@ -57,12 +57,12 @@ var RelayGarbageCollection = { * be removed). */ scheduleCollection(stepLength?: number): void { - var garbageCollector = - RelayStoreData.getDefaultInstance().getGarbageCollector(); - - if (garbageCollector) { - garbageCollector.scheduleCollection(stepLength); - } + // var garbageCollector = + // RelayStoreData.getDefaultInstance().getGarbageCollector(); + // + // if (garbageCollector) { + // garbageCollector.scheduleCollection(stepLength); + // } } }; diff --git a/src/tools/RelayInternals.js b/src/tools/RelayInternals.js index 3868e7ace7b8a..58e15d884467d 100644 --- a/src/tools/RelayInternals.js +++ b/src/tools/RelayInternals.js @@ -13,7 +13,6 @@ 'use strict'; var RelayNetworkLayer = require('RelayNetworkLayer'); -var RelayStoreData = require('RelayStoreData'); var flattenRelayQuery = require('flattenRelayQuery'); var printRelayQuery = require('printRelayQuery'); @@ -26,7 +25,6 @@ var printRelayQuery = require('printRelayQuery'); */ var RelayInternals = { NetworkLayer: RelayNetworkLayer, - DefaultStoreData: RelayStoreData.getDefaultInstance(), flattenRelayQuery: flattenRelayQuery, printRelayQuery: printRelayQuery, }; diff --git a/src/tools/__mocks__/RelayTestUtils.js b/src/tools/__mocks__/RelayTestUtils.js index ebbaadb3af7af..1c449d36af769 100644 --- a/src/tools/__mocks__/RelayTestUtils.js +++ b/src/tools/__mocks__/RelayTestUtils.js @@ -33,6 +33,7 @@ var RelayTestUtils = { var ReactDOM = require('ReactDOM'); var RelayPropTypes = require('RelayPropTypes'); var RelayRoute = require('RelayRoute'); + var RelayStore = require('RelayStore'); class ContextSetter extends React.Component { getChildContext() { @@ -44,6 +45,7 @@ var RelayTestUtils = { } ContextSetter.childContextTypes = { route: RelayPropTypes.QueryConfig.isRequired, + relay: RelayPropTypes.RelayContext.isRequired, }; class MockPointer { @@ -55,8 +57,9 @@ var RelayTestUtils = { container = container || document.createElement('div'); return { - render(render, route) { + render(render, route, relay) { route = route || RelayRoute.genMockInstance(); + relay = relay || RelayStore; var result; function ref(component) { @@ -64,7 +67,7 @@ var RelayTestUtils = { } ReactDOM.render( { var element = render(dataID => new MockPointer(dataID)); var pointers = {};