Skip to content

Commit

Permalink
Pass operation availability to the network layer for loadQuery
Browse files Browse the repository at this point in the history
Reviewed By: lynnshaoyu

Differential Revision: D70144365

fbshipit-source-id: d97370b3087987baff2a59183dca613cc223ba59
  • Loading branch information
tyao1 authored and facebook-github-bot committed Feb 27, 2025
1 parent 687e996 commit ab3b117
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 12 deletions.
56 changes: 55 additions & 1 deletion packages/react-relay/relay-hooks/__tests__/loadQuery-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import type {
} from './__generated__/loadQueryTestQuery.graphql';
import type {
CacheConfig,
INetwork,
LogRequestInfoFunction,
Query,
RequestParameters,
Expand Down Expand Up @@ -96,6 +97,7 @@ describe('loadQuery', () => {
let mockAvailability: {fetchTime?: number, status: string};
let disposeOnloadCallback;
let executeOnloadCallback;
let checkOperation;

beforeEach(() => {
fetch = jest.fn(
Expand All @@ -122,7 +124,17 @@ describe('loadQuery', () => {
return observable;
},
);
environment = createMockEnvironment({network: Network.create(fetch)});
function wrapNetworkExecute(network: INetwork): INetwork {
return {
execute: (_1, _2, _3, _4, _5, _6, _7, _checkOperation) => {
checkOperation = _checkOperation;
return network.execute(_1, _2, _3, _4, _5, _6, _7, _checkOperation);
},
};
}
environment = createMockEnvironment({
network: wrapNetworkExecute(Network.create(fetch)),
});

jest.clearAllTimers();
jest.useFakeTimers();
Expand Down Expand Up @@ -397,6 +409,48 @@ describe('loadQuery', () => {
expect(disposeEnvironmentRetain).toHaveBeenCalledTimes(1);
});
});

describe("with fetchPolicy === 'store-and-network'", () => {
it('should call fetch if the query can be fulfilled by the store', () => {
const {source} = loadQuery(
environment,
preloadableConcreteRequest,
variables,
{
fetchPolicy: 'store-and-network',
},
);
expect(fetch).toHaveBeenCalled();
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
expect(environment.executeWithSource).toHaveBeenCalled();
expect(source).toBeDefined();
// Query should still be retained even if we don't fetch
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
expect(environment.retain).toHaveBeenCalled();
});

it('returns the correct operation availability (available)', () => {
loadQuery(environment, preloadableConcreteRequest, variables, {
fetchPolicy: 'store-and-network',
});
expect(fetch).toHaveBeenCalled();
expect(checkOperation != null && checkOperation().status).toEqual(
'available',
);
});

it('returns the correct operation availability (missing)', () => {
mockAvailability = {status: 'missing'};

loadQuery(environment, preloadableConcreteRequest, variables, {
fetchPolicy: 'store-and-network',
});
expect(fetch).toHaveBeenCalled();
expect(checkOperation != null && checkOperation().status).toEqual(
'missing',
);
});
});
});

describe('when the query AST is unavailable synchronously', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@

'use strict';

import type {GraphQLResponse} from 'relay-runtime/network/RelayNetworkTypes';
import type {
GraphQLResponse,
INetwork,
} from 'relay-runtime/network/RelayNetworkTypes';

const preloadQuery_DEPRECATED = require('../preloadQuery_DEPRECATED');
const {
Expand Down Expand Up @@ -76,19 +79,37 @@ describe.each(['RelayModernEnvironment', 'MultiActorEnvironment'])(
let sink;
let variables;
let operation;
let checkOperation;

beforeEach(() => {
// $FlowFixMe[missing-local-annot] error found when enabling Flow LTI mode
fetch = jest.fn((_query, _variables, _cacheConfig) => {
fetch = jest.fn((_query, _variables, _cacheConfig, _4, _5) => {
// $FlowFixMe[missing-local-annot] error found when enabling Flow LTI mode
return Observable.create(_sink => {
sink = _sink;
});
});

function wrapNetworkExecute(network: INetwork): INetwork {
return {
execute: (_1, _2, _3, _4, _5, _6, _7, _checkOperation) => {
checkOperation = _checkOperation;
return network.execute(
_1,
_2,
_3,
_4,
_5,
_6,
_7,
_checkOperation,
);
},
};
}
const multiActorEnvironment = new MultiActorEnvironment({
// $FlowFixMe[invalid-tuple-arity] Error found while enabling LTI on this file
createNetworkForActor: _actorID => Network.create(fetch),
createNetworkForActor: _actorID =>
wrapNetworkExecute(Network.create(fetch)),
createStoreForActor: _actorID =>
new Store(new RecordSource(), {
gcReleaseBufferSize: 1,
Expand All @@ -99,7 +120,7 @@ describe.each(['RelayModernEnvironment', 'MultiActorEnvironment'])(
? multiActorEnvironment.forActor(getActorIdentifier('actor:1234'))
: new Environment({
// $FlowFixMe[invalid-tuple-arity] Error found while enabling LTI on this file
network: Network.create(fetch),
network: wrapNetworkExecute(Network.create(fetch)),
store: new Store(new RecordSource(), {
gcReleaseBufferSize: 1,
}),
Expand Down Expand Up @@ -535,6 +556,10 @@ describe.each(['RelayModernEnvironment', 'MultiActorEnvironment'])(
expect(fetch.mock.calls[0][0]).toBe(query.params);
expect(fetch.mock.calls[0][1]).toEqual(variables);
expect(fetch.mock.calls[0][2]).toEqual({force: true});
expect(checkOperation && checkOperation()).toEqual({
status: 'available',
fetchTime,
});

const [events, observer] = createObserver();
if (preloaded.source) {
Expand Down
21 changes: 18 additions & 3 deletions packages/react-relay/relay-hooks/loadQuery.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import type {
RequestIdentifier,
RequestParameters,
} from 'relay-runtime';
import type {OperationAvailability} from 'relay-runtime/store/RelayStoreTypes';

const invariant = require('invariant');
const {
Expand Down Expand Up @@ -130,6 +131,7 @@ function loadQuery<
let didMakeNetworkRequest = false;
const makeNetworkRequest = (
params: RequestParameters,
checkOperation?: () => OperationAvailability,
): Observable<GraphQLResponse> => {
// N.B. this function is called synchronously or not at all
// didMakeNetworkRequest is safe to rely on in the returned value
Expand Down Expand Up @@ -160,7 +162,16 @@ function loadQuery<
'raw-network-request-' + getRequestIdentifier(params, variables);
const observable = fetchQueryDeduped(environment, identifier, () => {
const network = environment.getNetwork();
return network.execute(params, variables, networkCacheConfig);
return network.execute(
params,
variables,
networkCacheConfig,
undefined,
undefined,
undefined,
undefined,
checkOperation,
);
});

const {unsubscribe} = observable.subscribe({
Expand Down Expand Up @@ -245,15 +256,19 @@ function loadQuery<
// N.B. If the fetch policy allows fulfillment from the store but the
// environment already has the data for that operation cached in the store,
// then we do nothing.
const operationAvailability = environment.check(operation);
const shouldFetch =
fetchPolicy !== 'store-or-network' ||
environment.check(operation).status !== 'available';
operationAvailability.status !== 'available';

if (shouldFetch) {
executeDeduped(operation, () => {
// N.B. Since we have the operation synchronously available here,
// we can immediately fetch and execute the operation.
const networkObservable = makeNetworkRequest(concreteRequest.params);
const networkObservable = makeNetworkRequest(
concreteRequest.params,
() => operationAvailability,
);
const executeObservable = executeWithNetworkSource(
operation,
networkObservable,
Expand Down
20 changes: 17 additions & 3 deletions packages/react-relay/relay-hooks/preloadQuery_DEPRECATED.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,12 +167,17 @@ function preloadQueryDeduped<TQuery: OperationType>(
}`;
const prevQueryEntry = pendingQueries.get(cacheKey);

const availability =
fetchPolicy === STORE_OR_NETWORK_DEFAULT && query != null && query != null
function checkOperation() {
return query != null
? environment.check(
createOperationDescriptor(query, variables, networkCacheConfig),
)
: {status: 'missing'};
}
const availability =
fetchPolicy === STORE_OR_NETWORK_DEFAULT
? checkOperation()
: {status: 'missing'};

let nextQueryEntry: ?PendingQueryEntry;
if (availability.status === 'available' && query != null) {
Expand Down Expand Up @@ -203,7 +208,16 @@ function preloadQueryDeduped<TQuery: OperationType>(
}
} else if (prevQueryEntry == null || prevQueryEntry.kind !== 'network') {
// Should fetch but we're not already fetching: fetch!
const source = network.execute(params, variables, networkCacheConfig, null);
const source = network.execute(
params,
variables,
networkCacheConfig,
null,
undefined,
undefined,
undefined,
checkOperation,
);
const subject = new ReplaySubject<GraphQLResponse>();
nextQueryEntry = {
cacheKey,
Expand Down

0 comments on commit ab3b117

Please sign in to comment.