relay-runtime
Version:
A core runtime for building GraphQL-driven applications.
179 lines (170 loc) • 5.36 kB
Flow
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall relay
*/
;
import type {
IEnvironment,
OperationDescriptor,
Snapshot,
} from '../store/RelayStoreTypes';
import type {
CacheConfig,
FetchQueryFetchPolicy,
OperationType,
Query,
Variables,
} from '../util/RelayRuntimeTypes';
const RelayObservable = require('../network/RelayObservable');
const {
createOperationDescriptor,
} = require('../store/RelayModernOperationDescriptor');
const {
handlePotentialSnapshotErrors,
} = require('../util/handlePotentialSnapshotErrors');
const fetchQueryInternal = require('./fetchQueryInternal');
const {getRequest} = require('./GraphQLTag');
const invariant = require('invariant');
/**
* Fetches the given query and variables on the provided environment,
* and de-dupes identical in-flight requests.
*
* Observing a request:
* ====================
* fetchQuery returns an Observable which you can call .subscribe()
* on. Subscribe optionally takes an Observer, which you can provide to
* observe network events:
*
* ```
* fetchQuery(environment, query, variables).subscribe({
* // Called when network requests starts
* start: (subsctiption) => {},
*
* // Called after a payload is received and written to the local store
* next: (payload) => {},
*
* // Called when network requests errors
* error: (error) => {},
*
* // Called when network requests fully completes
* complete: () => {},
*
* // Called when network request is unsubscribed
* unsubscribe: (subscription) => {},
* });
* ```
*
* Request Promise:
* ================
* The obervable can be converted to a Promise with .toPromise(), which will
* resolve to a snapshot of the query data when the first response is received
* from the server.
*
* ```
* fetchQuery(environment, query, variables).toPromise().then((data) => {
* // ...
* });
* ```
*
* In-flight request de-duping:
* ============================
* By default, calling fetchQuery multiple times with the same
* environment, query and variables will not initiate a new request if a request
* for those same parameters is already in flight.
*
* A request is marked in-flight from the moment it starts until the moment it
* fully completes, regardless of error or successful completion.
*
* NOTE: If the request completes _synchronously_, calling fetchQuery
* a second time with the same arguments in the same tick will _NOT_ de-dupe
* the request given that it will no longer be in-flight.
*
*
* Data Retention:
* ===============
* This function will NOT retain query data, meaning that it is not guaranteed
* that the fetched data will remain in the Relay store after the request has
* completed.
* If you need to retain the query data outside of the network request,
* you need to use `environment.retain()`.
*
*
* Cancelling requests:
* ====================
* If the disposable returned by subscribe is called while the
* request is in-flight, the request will be cancelled.
*
* ```
* const disposable = fetchQuery(...).subscribe(...);
*
* // This will cancel the request if it is in-flight.
* disposable.dispose();
* ```
* NOTE: When using .toPromise(), the request cannot be cancelled.
*/
function fetchQuery<TVariables: Variables, TData, TRawResponse>(
environment: IEnvironment,
query: Query<TVariables, TData, TRawResponse>,
variables: TVariables,
options?: $ReadOnly<{
fetchPolicy?: FetchQueryFetchPolicy,
networkCacheConfig?: CacheConfig,
}>,
): RelayObservable<TData> {
const queryNode = getRequest(query);
invariant(
queryNode.params.operationKind === 'query',
'fetchQuery: Expected query operation',
);
const networkCacheConfig = {
force: true,
...options?.networkCacheConfig,
};
const operation = createOperationDescriptor(
queryNode,
variables,
networkCacheConfig,
);
const fetchPolicy = options?.fetchPolicy ?? 'network-only';
function readData(snapshot: Snapshot): TData {
handlePotentialSnapshotErrors(environment, snapshot.fieldErrors);
/* $FlowFixMe[incompatible-return] we assume readData returns the right
* data just having written it from network or checked availability. */
return snapshot.data;
}
switch (fetchPolicy) {
case 'network-only': {
return getNetworkObservable<$FlowFixMe>(environment, operation).map(
readData,
);
}
case 'store-or-network': {
if (environment.check(operation).status === 'available') {
return RelayObservable.from<Snapshot>(
environment.lookup(operation.fragment),
).map(readData);
}
return getNetworkObservable<$FlowFixMe>(environment, operation).map(
readData,
);
}
default:
(fetchPolicy: empty);
throw new Error('fetchQuery: Invalid fetchPolicy ' + fetchPolicy);
}
}
function getNetworkObservable<TQuery: OperationType>(
environment: IEnvironment,
operation: OperationDescriptor,
): RelayObservable<TQuery['response']> {
return fetchQueryInternal
.fetchQuery(environment, operation)
.map(() => environment.lookup(operation.fragment));
}
module.exports = fetchQuery;