From 5fb67ed1cbf0e863cb47f834eec38ff1a601b5e5 Mon Sep 17 00:00:00 2001 From: Denis Nedelyaev Date: Mon, 7 Mar 2016 06:40:53 -0800 Subject: [PATCH] Contextualize RelayNetworkLayer Summary:== Original Description == This PR is another step toward making all Relay state contextual (#558). Contextual `RelayNetworkLayer` is important for server-side rendering. It will allow to inject a contextual network layer initialized with client request specific params (e.g. authentication cookies) on the server. Currently it is impossible because the injected network layer is global, and shared between client requests. == Summary of Changes == - Moves `fetchRelayQuery` to a method on `RelayNetworkLayer`, which is now a class with an instance held by each `RelayStoreData`, thereby making the network layer contextual to the context instance. - Changes `Relay.injectNetworkLayer` to a facade for `RelayStore.injectNetworkLayer()`. - Changes the FB/OSS `Relay` module to inject a network layer directly on `RelayStore`. - Re-create `fetchRelayQuery` in relay-fb for convenience Closes https://github.com/facebook/relay/pull/704 Reviewed By: wincent Differential Revision: D3016293 Pulled By: josephsavona fb-gh-sync-id: 2e5f3163bc048a089776477b3d2dac2546fd9f48 shipit-source-id: 2e5f3163bc048a089776477b3d2dac2546fd9f48 --- src/RelayPublic.js | 3 +- src/__forks__/Relay.js | 4 +- src/legacy/store/GraphQLQueryRunner.js | 6 +- .../__tests__/GraphQLQueryRunner-test.js | 13 +-- src/mutation/RelayMutationQueue.js | 3 +- .../__tests__/RelayMutationQueue-test.js | 52 +++++----- src/network/RelayNetworkLayer.js | 97 ++++++++++++++----- src/network/__mocks__/RelayNetworkLayer.js | 44 ++++++++- src/network/__mocks__/fetchRelayQuery.js | 42 -------- .../__tests__/RelayNetworkLayer-test.js | 28 +++--- src/network/fetchRelayQuery.js | 72 -------------- src/store/RelayContext.js | 5 + src/store/RelayPendingQueryTracker.js | 3 +- src/store/RelayStoreData.js | 7 ++ .../RelayPendingQueryTracker-test.js | 4 +- src/tools/RelayInternals.js | 3 +- src/tools/RelayTypes.js | 5 + 17 files changed, 191 insertions(+), 200 deletions(-) delete mode 100644 src/network/__mocks__/fetchRelayQuery.js delete mode 100644 src/network/fetchRelayQuery.js diff --git a/src/RelayPublic.js b/src/RelayPublic.js index 4a1d03864e74a..ca1fee8dc68b0 100644 --- a/src/RelayPublic.js +++ b/src/RelayPublic.js @@ -15,7 +15,6 @@ const RelayContainer = require('RelayContainer'); const RelayMutation = require('RelayMutation'); -const RelayNetworkLayer = require('RelayNetworkLayer'); const RelayPropTypes = require('RelayPropTypes'); const RelayQL = require('RelayQL'); const RelayRootContainer = require('RelayRootContainer'); @@ -47,7 +46,7 @@ var RelayPublic = { createContainer: RelayContainer.create, createQuery: createRelayQuery, getQueries: getRelayQueries, - injectNetworkLayer: RelayNetworkLayer.injectNetworkLayer, + injectNetworkLayer: RelayStore.injectNetworkLayer.bind(RelayStore), injectTaskScheduler: RelayTaskScheduler.injectScheduler, isContainer: isRelayContainer, }; diff --git a/src/__forks__/Relay.js b/src/__forks__/Relay.js index 6991d49046ea4..298292120b118 100644 --- a/src/__forks__/Relay.js +++ b/src/__forks__/Relay.js @@ -15,10 +15,10 @@ var RelayDefaultNetworkLayer = require('RelayDefaultNetworkLayer'); var RelayPublic = require('RelayPublic'); +const RelayStore = require('RelayStore'); // By default, assume that GraphQL is served at `/graphql` on the same domain. -// $FlowFixMe(>=0.16.0) -RelayPublic.injectNetworkLayer(new RelayDefaultNetworkLayer('/graphql')); +RelayStore.injectNetworkLayer(new RelayDefaultNetworkLayer('/graphql')); module.exports = { ...RelayPublic, diff --git a/src/legacy/store/GraphQLQueryRunner.js b/src/legacy/store/GraphQLQueryRunner.js index 90f46018056ff..31714d737b90a 100644 --- a/src/legacy/store/GraphQLQueryRunner.js +++ b/src/legacy/store/GraphQLQueryRunner.js @@ -17,7 +17,6 @@ const RelayFetchMode = require('RelayFetchMode'); import type {FetchMode} from 'RelayFetchMode'; import type {RelayQuerySet} from 'RelayInternalTypes'; import type {PendingFetch} from 'RelayPendingQueryTracker'; -const RelayNetworkLayer = require('RelayNetworkLayer'); const RelayProfiler = require('RelayProfiler'); import type RelayQuery from 'RelayQuery'; const RelayReadyState = require('RelayReadyState'); @@ -92,9 +91,10 @@ function hasItems(map: Object): boolean { } function splitAndFlattenQueries( + storeData: RelayStoreData, queries: Array ): Array { - if (!RelayNetworkLayer.supports('defer')) { + if (!storeData.getNetworkLayer().supports('defer')) { if (__DEV__) { queries.forEach(query => { warning( @@ -202,7 +202,7 @@ function runQueries( }); } - splitAndFlattenQueries(queries).forEach(query => { + splitAndFlattenQueries(storeData, queries).forEach(query => { var pendingFetch = storeData.getPendingQueryTracker().add( {query, fetchMode, forceIndex, storeData} ); diff --git a/src/legacy/store/__tests__/GraphQLQueryRunner-test.js b/src/legacy/store/__tests__/GraphQLQueryRunner-test.js index e8f3de1f64867..6768930f7543d 100644 --- a/src/legacy/store/__tests__/GraphQLQueryRunner-test.js +++ b/src/legacy/store/__tests__/GraphQLQueryRunner-test.js @@ -20,7 +20,6 @@ jest const Relay = require('Relay'); const RelayFetchMode = require('RelayFetchMode'); -const RelayNetworkLayer = require('RelayNetworkLayer'); const RelayStoreData = require('RelayStoreData'); const RelayTestUtils = require('RelayTestUtils'); @@ -30,6 +29,7 @@ const splitDeferredRelayQueries = require('splitDeferredRelayQueries'); const warning = require('warning'); describe('GraphQLQueryRunner', () => { + var networkLayer; var queryRunner; var pendingQueryTracker; @@ -62,14 +62,15 @@ describe('GraphQLQueryRunner', () => { beforeEach(() => { jest.resetModuleRegistry(); - RelayNetworkLayer.injectNetworkLayer({ - supports: () => true, - }); - var storeData = new RelayStoreData(); + networkLayer = storeData.getNetworkLayer(); queryRunner = storeData.getQueryRunner(); pendingQueryTracker = storeData.getPendingQueryTracker(); + networkLayer.injectNetworkLayer({ + supports: () => true, + }); + mockCallback = jest.genMockFunction(); mockQuerySet = { foo: getNode(Relay.QL`query{viewer{actor{id,name}}}`), @@ -108,7 +109,7 @@ describe('GraphQLQueryRunner', () => { it('warns and uses fallback when defer is unsupported', () => { diffRelayQuery.mockImplementation(query => [query]); checkRelayQueryData.mockImplementation(() => false); - RelayNetworkLayer.injectNetworkLayer({ + networkLayer.injectNetworkLayer({ supports: () => false, }); diff --git a/src/mutation/RelayMutationQueue.js b/src/mutation/RelayMutationQueue.js index 68e64107f384e..42eb2d4f65723 100644 --- a/src/mutation/RelayMutationQueue.js +++ b/src/mutation/RelayMutationQueue.js @@ -21,7 +21,6 @@ const RelayMutationQuery = require('RelayMutationQuery'); const RelayMutationRequest = require('RelayMutationRequest'); const RelayMutationTransaction = require('RelayMutationTransaction'); const RelayMutationTransactionStatus = require('RelayMutationTransactionStatus'); -const RelayNetworkLayer = require('RelayNetworkLayer'); import type RelayStoreData from 'RelayStoreData'; import type {FileMap} from 'RelayMutation'; import type RelayMutation from 'RelayMutation'; @@ -238,7 +237,7 @@ class RelayMutationQueue { transaction.getQuery(this._storeData), transaction.getFiles(), ); - RelayNetworkLayer.sendMutation(request); + this._storeData.getNetworkLayer().sendMutation(request); request.getPromise().done( result => this._handleCommitSuccess(transaction, result.response), diff --git a/src/mutation/__tests__/RelayMutationQueue-test.js b/src/mutation/__tests__/RelayMutationQueue-test.js index 582d0d389b0cb..d890616d1d6f0 100644 --- a/src/mutation/__tests__/RelayMutationQueue-test.js +++ b/src/mutation/__tests__/RelayMutationQueue-test.js @@ -30,19 +30,17 @@ const flattenRelayQuery = require('flattenRelayQuery'); const fromGraphQL = require('fromGraphQL'); describe('RelayMutationQueue', () => { - var RelayNetworkLayer; var storeData; var mutationQueue; + var networkLayer; beforeEach(() => { jest.resetModuleRegistry(); - RelayNetworkLayer = jest.genMockFromModule('RelayNetworkLayer'); - jest.setMock('RelayNetworkLayer', RelayNetworkLayer); - RelayStoreData.prototype.handleUpdatePayload = jest.genMockFunction(); storeData = RelayStore.getStoreData(); mutationQueue = storeData.getMutationQueue(); + networkLayer = storeData.getNetworkLayer(); jasmine.addMatchers(RelayTestUtils.matchers); }); @@ -178,9 +176,9 @@ describe('RelayMutationQueue', () => { {onSuccess: successCallback1} ); transaction1.commit(); - expect(RelayNetworkLayer.sendMutation.mock.calls.length).toBe(1); + expect(networkLayer.sendMutation.mock.calls.length).toBe(1); - var request = RelayNetworkLayer.sendMutation.mock.calls[0][0]; + var request = networkLayer.sendMutation.mock.calls[0][0]; request.resolve({response: {'res': 'ponse'}}); jest.runAllTimers(); expect(successCallback1.mock.calls).toEqual([[{'res': 'ponse'}]]); @@ -200,8 +198,8 @@ describe('RelayMutationQueue', () => { var mockError = new Error('error'); transaction1.commit(); - expect(RelayNetworkLayer.sendMutation.mock.calls.length).toBe(1); - var request = RelayNetworkLayer.sendMutation.mock.calls[0][0]; + expect(networkLayer.sendMutation.mock.calls.length).toBe(1); + var request = networkLayer.sendMutation.mock.calls[0][0]; request.reject(mockError); jest.runAllTimers(); expect(failureCallback1).toBeCalled(); @@ -225,9 +223,9 @@ describe('RelayMutationQueue', () => { expect(transaction2.getStatus()).toBe( RelayMutationTransactionStatus.COMMIT_QUEUED ); - expect(RelayNetworkLayer.sendMutation.mock.calls.length).toBe(1); + expect(networkLayer.sendMutation.mock.calls.length).toBe(1); - var request = RelayNetworkLayer.sendMutation.mock.calls[0][0]; + var request = networkLayer.sendMutation.mock.calls[0][0]; request.resolve({response: {}}); jest.runAllTimers(); @@ -235,7 +233,7 @@ describe('RelayMutationQueue', () => { expect(transaction2.getStatus()).toBe( RelayMutationTransactionStatus.COMMITTING ); - expect(RelayNetworkLayer.sendMutation.mock.calls.length).toBe(2); + expect(networkLayer.sendMutation.mock.calls.length).toBe(2); }); it('does not queue commits for non-colliding transactions', () => { @@ -245,7 +243,7 @@ describe('RelayMutationQueue', () => { expect(transaction1.getStatus()).toBe( RelayMutationTransactionStatus.COMMITTING ); - expect(RelayNetworkLayer.sendMutation.mock.calls.length).toBe(1); + expect(networkLayer.sendMutation.mock.calls.length).toBe(1); var transaction2 = mutationQueue.createTransaction(mockMutation2); transaction2.commit(); @@ -253,7 +251,7 @@ describe('RelayMutationQueue', () => { expect(transaction2.getStatus()).toBe( RelayMutationTransactionStatus.COMMITTING ); - expect(RelayNetworkLayer.sendMutation.mock.calls.length).toBe(2); + expect(networkLayer.sendMutation.mock.calls.length).toBe(2); }); it('does not queue commits for `null` collision key transactions', () => { @@ -263,7 +261,7 @@ describe('RelayMutationQueue', () => { expect(transaction1.getStatus()).toBe( RelayMutationTransactionStatus.COMMITTING ); - expect(RelayNetworkLayer.sendMutation.mock.calls.length).toBe(1); + expect(networkLayer.sendMutation.mock.calls.length).toBe(1); var transaction2 = mutationQueue.createTransaction(mockMutation3); transaction2.commit(); @@ -271,7 +269,7 @@ describe('RelayMutationQueue', () => { expect(transaction2.getStatus()).toBe( RelayMutationTransactionStatus.COMMITTING ); - expect(RelayNetworkLayer.sendMutation.mock.calls.length).toBe(2); + expect(networkLayer.sendMutation.mock.calls.length).toBe(2); }); it('empties collision queue after a failure', () => { @@ -292,7 +290,7 @@ describe('RelayMutationQueue', () => { expect(transaction1.getStatus()).toBe( RelayMutationTransactionStatus.COMMITTING ); - expect(RelayNetworkLayer.sendMutation.mock.calls.length).toBe(1); + expect(networkLayer.sendMutation.mock.calls.length).toBe(1); var failureCallback2 = jest.genMockFunction().mockImplementation( (transaction, preventAutoRollback) => { @@ -313,9 +311,9 @@ describe('RelayMutationQueue', () => { expect(transaction2.getStatus()).toBe( RelayMutationTransactionStatus.COMMIT_QUEUED ); - expect(RelayNetworkLayer.sendMutation.mock.calls.length).toBe(1); + expect(networkLayer.sendMutation.mock.calls.length).toBe(1); - var request = RelayNetworkLayer.sendMutation.mock.calls[0][0]; + var request = networkLayer.sendMutation.mock.calls[0][0]; request.reject(new Error('error')); jest.runAllTimers(); @@ -334,7 +332,7 @@ describe('RelayMutationQueue', () => { expect(transaction3.getStatus()).toBe( RelayMutationTransactionStatus.COMMITTING ); - expect(RelayNetworkLayer.sendMutation.mock.calls.length).toBe(2); + expect(networkLayer.sendMutation.mock.calls.length).toBe(2); }); it('rolls back colliding transactions on failure unless prevented', () => { @@ -412,9 +410,9 @@ describe('RelayMutationQueue', () => { expect(transaction5.getStatus()).toBe( RelayMutationTransactionStatus.COMMIT_QUEUED ); - expect(RelayNetworkLayer.sendMutation.mock.calls.length).toBe(2); + expect(networkLayer.sendMutation.mock.calls.length).toBe(2); - var request = RelayNetworkLayer.sendMutation.mock.calls[0][0]; + var request = networkLayer.sendMutation.mock.calls[0][0]; request.reject(new Error('error')); jest.runAllTimers(); @@ -476,8 +474,8 @@ describe('RelayMutationQueue', () => { ); transaction1.commit(); - expect(RelayNetworkLayer.sendMutation.mock.calls.length).toBe(1); - var request = RelayNetworkLayer.sendMutation.mock.calls[0][0]; + expect(networkLayer.sendMutation.mock.calls.length).toBe(1); + var request = networkLayer.sendMutation.mock.calls[0][0]; request.reject(new Error('error')); jest.runAllTimers(); @@ -493,24 +491,24 @@ describe('RelayMutationQueue', () => { ); transaction2.commit(); - expect(RelayNetworkLayer.sendMutation.mock.calls.length).toBe(2); + expect(networkLayer.sendMutation.mock.calls.length).toBe(2); transaction1.recommit(); expect(transaction1.getStatus()).toBe( RelayMutationTransactionStatus.COMMIT_QUEUED ); - request = RelayNetworkLayer.sendMutation.mock.calls[1][0]; + request = networkLayer.sendMutation.mock.calls[1][0]; request.resolve({response: {}}); jest.runAllTimers(); expect(successCallback2).toBeCalled(); - expect(RelayNetworkLayer.sendMutation.mock.calls.length).toBe(3); + expect(networkLayer.sendMutation.mock.calls.length).toBe(3); expect(transaction1.getStatus()).toBe( RelayMutationTransactionStatus.COMMITTING ); - request = RelayNetworkLayer.sendMutation.mock.calls[2][0]; + request = networkLayer.sendMutation.mock.calls[2][0]; request.resolve({response: {}}); jest.runAllTimers(); diff --git a/src/network/RelayNetworkLayer.js b/src/network/RelayNetworkLayer.js index 79849623b3c0e..3b5578cdac4ce 100644 --- a/src/network/RelayNetworkLayer.js +++ b/src/network/RelayNetworkLayer.js @@ -15,59 +15,104 @@ import type RelayMutationRequest from 'RelayMutationRequest'; const RelayProfiler = require('RelayProfiler'); -import type RelayQueryRequest from 'RelayQueryRequest'; +import type RelayQuery from 'RelayQuery'; +const RelayQueryRequest = require('RelayQueryRequest'); +import type {NetworkLayer} from 'RelayTypes'; const invariant = require('invariant'); - -type NetworkLayer = { - sendMutation: (mutationRequest: RelayMutationRequest) => ?Promise; - sendQueries: (queryRequests: Array) => ?Promise; - supports: (...options: Array) => boolean; -}; - -var injectedNetworkLayer; +const resolveImmediate = require('resolveImmediate'); /** * @internal * * `RelayNetworkLayer` provides a method to inject custom network behavior. */ -var RelayNetworkLayer = { +class RelayNetworkLayer { + _injectedNetworkLayer: ?NetworkLayer; + _queue: ?Array; + + constructor() { + this._injectedNetworkLayer = null; + this._queue = null; + } + injectNetworkLayer(networkLayer: ?NetworkLayer): void { - injectedNetworkLayer = networkLayer; - }, + this._injectedNetworkLayer = networkLayer; + } sendMutation(mutationRequest: RelayMutationRequest): void { - var networkLayer = getCurrentNetworkLayer(); + var networkLayer = this._getCurrentNetworkLayer(); var promise = networkLayer.sendMutation(mutationRequest); if (promise) { Promise.resolve(promise).done(); } - }, + } sendQueries(queryRequests: Array): void { - var networkLayer = getCurrentNetworkLayer(); + var networkLayer = this._getCurrentNetworkLayer(); var promise = networkLayer.sendQueries(queryRequests); if (promise) { Promise.resolve(promise).done(); } - }, + } supports(...options: Array): boolean { - var networkLayer = getCurrentNetworkLayer(); + var networkLayer = this._getCurrentNetworkLayer(); return networkLayer.supports(...options); - }, -}; + } + + _getCurrentNetworkLayer(): NetworkLayer { + invariant( + this._injectedNetworkLayer, + 'RelayNetworkLayer: Use `injectNetworkLayer` to configure a network ' + + 'layer.' + ); + return this._injectedNetworkLayer; + } -function getCurrentNetworkLayer(): $FlowIssue { - invariant( - injectedNetworkLayer, - 'RelayNetworkLayer: Use `injectNetworkLayer` to configure a network layer.' - ); - return injectedNetworkLayer; + /** + * Schedules the supplied `query` to be sent to the server. + * + * This is a low-level transport API; application code should use higher-level + * interfaces exposed by RelayContainer for retrieving data transparently via + * queries defined on components. + */ + fetchRelayQuery(query: RelayQuery.Root): Promise { + const currentQueue = this._queue || []; + if (!this._queue) { + this._queue = currentQueue; + resolveImmediate(() => { + this._queue = null; + profileQueue(currentQueue); + this.sendQueries(currentQueue); + }); + } + const request = new RelayQueryRequest(query); + currentQueue.push(request); + return request.getPromise(); + } +} + +/** + * Profiles time from request to receiving the first server response. + */ +function profileQueue(currentQueue: Array): void { + // TODO #8783781: remove aggregate `fetchRelayQuery` profiler + let firstResultProfiler = RelayProfiler.profile('fetchRelayQuery'); + currentQueue.forEach(query => { + const profiler = RelayProfiler.profile('fetchRelayQuery.query'); + const onSettle = () => { + profiler.stop(); + if (firstResultProfiler) { + firstResultProfiler.stop(); + firstResultProfiler = null; + } + }; + query.getPromise().done(onSettle, onSettle); + }); } -RelayProfiler.instrumentMethods(RelayNetworkLayer, { +RelayProfiler.instrumentMethods(RelayNetworkLayer.prototype, { sendMutation: 'RelayNetworkLayer.sendMutation', sendQueries: 'RelayNetworkLayer.sendQueries', }); diff --git a/src/network/__mocks__/RelayNetworkLayer.js b/src/network/__mocks__/RelayNetworkLayer.js index b3b12c48efb24..c2ad101ad50eb 100644 --- a/src/network/__mocks__/RelayNetworkLayer.js +++ b/src/network/__mocks__/RelayNetworkLayer.js @@ -9,4 +9,46 @@ 'use strict'; -module.exports = require.requireActual('RelayNetworkLayer'); +const RelayNetworkLayer = require.requireActual('RelayNetworkLayer'); + +function RelayNetworkLayerMock() { + const networkLayer = new RelayNetworkLayer(); + + networkLayer.fetchRelayQuery = jest.genMockFunction().mockImplementation( + () => new Promise(genMockRequest) + ); + const requests = networkLayer.fetchRelayQuery.mock.requests = []; + + networkLayer.sendMutation = jest.genMockFunction(); + + return networkLayer; + + /** + * Mock object to simulate the behavior of a request. Example usage: + * + * // Successful fetch. + * networkLayer.fetchRelayQuery(queryA); + * networkLayer.fetchRelayQuery.mock.requests[0].resolve(response); + * + * // Fetch with partial error. + * networkLayer.fetchRelayQuery(queryB); + * networkLayer.fetchRelayQuery.mock.requests[0].resolve(response, error); + * + * // Failed fetch. + * networkLayer.fetchRelayQuery(queryC); + * networkLayer.fetchRelayQuery.mock.requests[0].reject(error); + * + */ + function genMockRequest(resolve, reject) { + requests.push({ + resolve(response, error) { + resolve({error: error || null, response}); + }, + reject, + }); + } +} + +RelayNetworkLayerMock.prototype = RelayNetworkLayer.prototype; + +module.exports = RelayNetworkLayerMock; diff --git a/src/network/__mocks__/fetchRelayQuery.js b/src/network/__mocks__/fetchRelayQuery.js deleted file mode 100644 index 6e2b1a3fa52fa..0000000000000 --- a/src/network/__mocks__/fetchRelayQuery.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright (c) 2013-present, 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'; - -var fetchRelayQuery = jest.genMockFromModule('fetchRelayQuery'); - -/** - * Mock object to simulate the behavior of a request. Example usage: - * - * // Successful fetch. - * fetchRelayQuery(queryA); - * fetchRelayQuery.mock.requests[0].resolve(response); - * - * // Fetch with partial error. - * fetchRelayQuery(queryB); - * fetchRelayQuery.mock.requests[0].resolve(response, error); - * - * // Failed fetch. - * fetchRelayQuery(queryC); - * fetchRelayQuery.mock.requests[0].reject(error); - * - */ -function genMockRequest(resolve, reject) { - fetchRelayQuery.mock.requests.push({ - resolve(response, error) { - resolve({error: error || null, response}); - }, - reject, - }); -} - -fetchRelayQuery.mock.requests = []; -fetchRelayQuery.mockImplementation(() => new Promise(genMockRequest)); - -module.exports = fetchRelayQuery; diff --git a/src/network/__tests__/RelayNetworkLayer-test.js b/src/network/__tests__/RelayNetworkLayer-test.js index 713f0286bc381..15e15db23fb41 100644 --- a/src/network/__tests__/RelayNetworkLayer-test.js +++ b/src/network/__tests__/RelayNetworkLayer-test.js @@ -13,6 +13,8 @@ require('configureForRelayOSS'); +jest.dontMock('RelayNetworkLayer'); + const Deferred = require('Deferred'); const RelayNetworkLayer = require('RelayNetworkLayer'); const RelayTestUtils = require('RelayTestUtils'); @@ -21,6 +23,7 @@ describe('RelayNetworkLayer', () => { var RelayQuery; var injectedNetworkLayer; + var networkLayer; beforeEach(() => { jest.resetModuleRegistry(); @@ -33,16 +36,17 @@ describe('RelayNetworkLayer', () => { sendQueries: jest.genMockFunction(), supports: jest.genMockFunction().mockReturnValue(true), }; - RelayNetworkLayer.injectNetworkLayer(injectedNetworkLayer); + networkLayer = new RelayNetworkLayer(); + networkLayer.injectNetworkLayer(injectedNetworkLayer); jasmine.addMatchers(RelayTestUtils.matchers); }); describe('supports', () => { it('throws when no network layer is injected', () => { - RelayNetworkLayer.injectNetworkLayer(null); + networkLayer.injectNetworkLayer(null); expect(() => { - RelayNetworkLayer.sendQueries([]); + networkLayer.sendQueries([]); }).toFailInvariant( 'RelayNetworkLayer: Use `injectNetworkLayer` to configure a network ' + 'layer.' @@ -51,16 +55,16 @@ describe('RelayNetworkLayer', () => { it('delegates to the injected network layer', () => { expect(injectedNetworkLayer.supports).not.toBeCalled(); - RelayNetworkLayer.supports('foo', 'bar'); + networkLayer.supports('foo', 'bar'); expect(injectedNetworkLayer.supports).toBeCalledWith('foo', 'bar'); }); }); describe('sendQueries', () => { it('throws when no network layer is injected', () => { - RelayNetworkLayer.injectNetworkLayer(null); + networkLayer.injectNetworkLayer(null); expect(() => { - RelayNetworkLayer.sendQueries([]); + networkLayer.sendQueries([]); }).toFailInvariant( 'RelayNetworkLayer: Use `injectNetworkLayer` to configure a network ' + 'layer.' @@ -70,7 +74,7 @@ describe('RelayNetworkLayer', () => { it('delegates queries to the injected network layer', () => { var queries = []; expect(injectedNetworkLayer.sendQueries).not.toBeCalled(); - RelayNetworkLayer.sendQueries(queries); + networkLayer.sendQueries(queries); expect(injectedNetworkLayer.sendQueries).toBeCalledWith(queries); }); }); @@ -92,9 +96,9 @@ describe('RelayNetworkLayer', () => { }); it('throws when no network layer is injected', () => { - RelayNetworkLayer.injectNetworkLayer(null); + networkLayer.injectNetworkLayer(null); expect(() => { - RelayNetworkLayer.sendMutation({mutation, variables, deferred}); + networkLayer.sendMutation({mutation, variables, deferred}); }).toFailInvariant( 'RelayNetworkLayer: Use `injectNetworkLayer` to configure a network ' + 'layer.' @@ -103,7 +107,7 @@ describe('RelayNetworkLayer', () => { it('delegates mutation to the injected network layer', () => { expect(injectedNetworkLayer.sendQueries).not.toBeCalled(); - RelayNetworkLayer.sendMutation({mutation, variables, deferred}); + networkLayer.sendMutation({mutation, variables, deferred}); expect(injectedNetworkLayer.sendMutation).toBeCalled(); var pendingMutation = injectedNetworkLayer.sendMutation.mock.calls[0][0]; @@ -112,7 +116,7 @@ describe('RelayNetworkLayer', () => { }); it('resolves the deferred if the mutation succeeds', () => { - RelayNetworkLayer.sendMutation({mutation, variables, deferred}); + networkLayer.sendMutation({mutation, variables, deferred}); expect(resolvedCallback).not.toBeCalled(); expect(rejectedCallback).not.toBeCalled(); @@ -126,7 +130,7 @@ describe('RelayNetworkLayer', () => { }); it('rejects the deferred if the mutation fails', () => { - RelayNetworkLayer.sendMutation({mutation, variables, deferred}); + networkLayer.sendMutation({mutation, variables, deferred}); expect(resolvedCallback).not.toBeCalled(); expect(rejectedCallback).not.toBeCalled(); diff --git a/src/network/fetchRelayQuery.js b/src/network/fetchRelayQuery.js deleted file mode 100644 index 547d8af33fb15..0000000000000 --- a/src/network/fetchRelayQuery.js +++ /dev/null @@ -1,72 +0,0 @@ -/** - * Copyright (c) 2013-present, 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 fetchRelayQuery - * @typechecks - * @flow - */ - -'use strict'; - -const RelayNetworkLayer = require('RelayNetworkLayer'); -const RelayProfiler = require('RelayProfiler'); -const RelayQueryRequest = require('RelayQueryRequest'); -import type RelayQuery from 'RelayQuery'; - -const resolveImmediate = require('resolveImmediate'); - -let queue: ?Array = null; - -/** - * @internal - * - * Schedules the supplied `query` to be sent to the server. - * - * This is a low-level transport API; application code should use higher-level - * interfaces exposed by RelayContainer for retrieving data transparently via - * queries defined on components. - */ -function fetchRelayQuery(query: RelayQuery.Root): Promise { - if (!queue) { - queue = []; - const currentQueue = queue; - resolveImmediate(() => { - queue = null; - profileQueue(currentQueue); - processQueue(currentQueue); - }); - } - const request = new RelayQueryRequest(query); - queue.push(request); - return request.getPromise(); -} - -function processQueue(currentQueue: Array): void { - RelayNetworkLayer.sendQueries(currentQueue); -} - -/** - * Profiles time from request to receiving the first server response. - */ -function profileQueue(currentQueue: Array): void { - // TODO #8783781: remove aggregate `fetchRelayQuery` profiler - let firstResultProfiler = RelayProfiler.profile('fetchRelayQuery'); - currentQueue.forEach(query => { - const profiler = RelayProfiler.profile('fetchRelayQuery.query'); - const onSettle = () => { - profiler.stop(); - if (firstResultProfiler) { - firstResultProfiler.stop(); - firstResultProfiler = null; - } - }; - query.getPromise().done(onSettle, onSettle); - }); -} - -module.exports = fetchRelayQuery; diff --git a/src/store/RelayContext.js b/src/store/RelayContext.js index 48fd2ee672fbb..98d12b338105b 100644 --- a/src/store/RelayContext.js +++ b/src/store/RelayContext.js @@ -19,6 +19,7 @@ import type RelayMutationTransaction from 'RelayMutationTransaction'; import type RelayQuery from 'RelayQuery'; const RelayQueryResultObservable = require('RelayQueryResultObservable'); const RelayStoreData = require('RelayStoreData'); +import type {NetworkLayer} from 'RelayTypes'; const forEachRootCallArg = require('forEachRootCallArg'); const readRelayQueryData = require('readRelayQueryData'); @@ -115,6 +116,10 @@ class RelayContext { return this._storeData; } + injectNetworkLayer(networkLayer: ?NetworkLayer) { + this._storeData.getNetworkLayer().injectNetworkLayer(networkLayer); + } + /** * Primes the store by sending requests for any missing data that would be * required to satisfy the supplied set of queries. diff --git a/src/store/RelayPendingQueryTracker.js b/src/store/RelayPendingQueryTracker.js index 140b48f1dbb12..f84221415ecf2 100644 --- a/src/store/RelayPendingQueryTracker.js +++ b/src/store/RelayPendingQueryTracker.js @@ -24,7 +24,6 @@ import type {QueryResult} from 'RelayTypes'; const containsRelayQueryRootCall = require('containsRelayQueryRootCall'); const everyObject = require('everyObject'); -const fetchRelayQuery = require('fetchRelayQuery'); const invariant = require('invariant'); const subtractRelayQuery = require('subtractRelayQuery'); @@ -144,7 +143,7 @@ class PendingFetch { } else { subtractedQuery = this._subtractPending(query); this._fetchSubtractedQueryPromise = subtractedQuery ? - fetchRelayQuery(subtractedQuery) : + storeData.getNetworkLayer().fetchRelayQuery(subtractedQuery) : Promise.resolve(); } diff --git a/src/store/RelayStoreData.js b/src/store/RelayStoreData.js index b5fe9066e4fa9..8400345ad8a16 100644 --- a/src/store/RelayStoreData.js +++ b/src/store/RelayStoreData.js @@ -32,6 +32,7 @@ import type { RootCallMap, UpdateOptions, } from 'RelayInternalTypes'; +const RelayNetworkLayer = require('RelayNetworkLayer'); const RelayNodeInterface = require('RelayNodeInterface'); const RelayPendingQueryTracker = require('RelayPendingQueryTracker'); const RelayProfiler = require('RelayProfiler'); @@ -79,6 +80,7 @@ class RelayStoreData { _changeEmitter: GraphQLStoreChangeEmitter; _garbageCollector: ?RelayGarbageCollector; _mutationQueue: RelayMutationQueue; + _networkLayer: RelayNetworkLayer; _nodeRangeMap: NodeRangeMap; _pendingQueryTracker: RelayPendingQueryTracker; _records: RecordMap; @@ -118,6 +120,7 @@ class RelayStoreData { this._cachedStore = cachedStore; this._changeEmitter = new GraphQLStoreChangeEmitter(rangeData); this._mutationQueue = new RelayMutationQueue(this); + this._networkLayer = new RelayNetworkLayer(); this._nodeRangeMap = nodeRangeMap; this._pendingQueryTracker = new RelayPendingQueryTracker(this); this._queryRunner = new GraphQLQueryRunner(this); @@ -450,6 +453,10 @@ class RelayStoreData { return this._mutationQueue; } + getNetworkLayer(): RelayNetworkLayer { + return this._networkLayer; + } + /** * Get the record store with only the cached and base data (no queued data). */ diff --git a/src/store/__tests__/RelayPendingQueryTracker-test.js b/src/store/__tests__/RelayPendingQueryTracker-test.js index 129321dffbd13..0f6e6f37c7363 100644 --- a/src/store/__tests__/RelayPendingQueryTracker-test.js +++ b/src/store/__tests__/RelayPendingQueryTracker-test.js @@ -22,7 +22,6 @@ const RelayFetchMode = require('RelayFetchMode'); const RelayStoreData = require('RelayStoreData'); const RelayTestUtils = require('RelayTestUtils'); -const fetchRelayQuery = require('fetchRelayQuery'); const subtractRelayQuery = require('subtractRelayQuery'); const writeRelayQueryPayload = require('writeRelayQueryPayload'); @@ -31,12 +30,15 @@ describe('RelayPendingQueryTracker', () => { var addPending; + var fetchRelayQuery; + var {getNode} = RelayTestUtils; beforeEach(() => { jest.resetModuleRegistry(); var storeData = new RelayStoreData(); + fetchRelayQuery = storeData.getNetworkLayer().fetchRelayQuery; pendingQueryTracker = storeData.getPendingQueryTracker(); subtractRelayQuery.mockImplementation(query => query); diff --git a/src/tools/RelayInternals.js b/src/tools/RelayInternals.js index 633005c0c80e3..0d83e6051402d 100644 --- a/src/tools/RelayInternals.js +++ b/src/tools/RelayInternals.js @@ -12,7 +12,6 @@ 'use strict'; -const RelayNetworkLayer = require('RelayNetworkLayer'); const RelayStore = require('RelayStore'); const flattenRelayQuery = require('flattenRelayQuery'); @@ -25,7 +24,7 @@ const printRelayQuery = require('printRelayQuery'); * @internal */ var RelayInternals = { - NetworkLayer: RelayNetworkLayer, + NetworkLayer: RelayStore.getStoreData().getNetworkLayer(), DefaultStoreData: RelayStore.getStoreData(), flattenRelayQuery: flattenRelayQuery, printRelayQuery: printRelayQuery, diff --git a/src/tools/RelayTypes.js b/src/tools/RelayTypes.js index 14ec488b94888..831c4a8d2df53 100644 --- a/src/tools/RelayTypes.js +++ b/src/tools/RelayTypes.js @@ -188,6 +188,11 @@ export type CacheWriter = { }; // Network requests +export type NetworkLayer = { + sendMutation: (mutationRequest: RelayMutationRequest) => ?Promise; + sendQueries: (queryRequests: Array) => ?Promise; + supports: (...options: Array) => boolean; +}; export type RequestOptions = { data?: ?{[key: string]: mixed}; errorHandler?: ?(error: XHRErrorData) => void;