diff --git a/packages/relay-runtime/network/RelayNetworkTypes.js b/packages/relay-runtime/network/RelayNetworkTypes.js index 20e700beb1174..a4c02454b6f98 100644 --- a/packages/relay-runtime/network/RelayNetworkTypes.js +++ b/packages/relay-runtime/network/RelayNetworkTypes.js @@ -15,6 +15,7 @@ import type {RequestParameters} from '../util/RelayConcreteNode'; import type {CacheConfig, Variables} from '../util/RelayRuntimeTypes'; import type RelayObservable, {ObservableFromValue} from './RelayObservable'; +import {boolean} from 'yargs'; /** * An interface for fetching the data for one or more (possibly interdependent) @@ -51,6 +52,7 @@ export type GraphQLResponseWithData = {| +extensions?: PayloadExtensions, +label?: string, +path?: Array, + +hasNext?: boolean, |}; export type GraphQLResponseWithoutData = {| @@ -59,6 +61,7 @@ export type GraphQLResponseWithoutData = {| +extensions?: PayloadExtensions, +label?: string, +path?: Array, + +hasNext?: boolean, |}; export type GraphQLResponseWithExtensionsOnly = {| @@ -72,6 +75,7 @@ export type GraphQLResponseWithExtensionsOnly = {| // does not necessarily indicate that there was an error. +data: null, +extensions: PayloadExtensions, + +hasNext?: boolean, |}; export type GraphQLSingularResponse = diff --git a/packages/relay-runtime/query/__tests__/fetchQueryInternal-test.js b/packages/relay-runtime/query/__tests__/fetchQueryInternal-test.js index 48332bb19fe01..c90e6d03c9631 100644 --- a/packages/relay-runtime/query/__tests__/fetchQueryInternal-test.js +++ b/packages/relay-runtime/query/__tests__/fetchQueryInternal-test.js @@ -964,6 +964,30 @@ describe('getObservableForActiveRequest', () => { expect(events).toEqual(['next']); }); + it('calls next asynchronously with subsequent non-final payloads (OSS)', () => { + fetchQuery(environment, query).subscribe({}); + const observable = getObservableForActiveRequest( + environment, + query.request, + ); + expect(observable).not.toEqual(null); + if (!observable) { + return; + } + + response = { + ...response, + extensions: {}, + hasNext: true, + }; + + observable.subscribe(observer); + expect(events).toEqual([]); + + environment.mock.nextValue(gqlQuery, response); + expect(events).toEqual(['next']); + }); + it('calls complete asynchronously with subsequent final payload', () => { fetchQuery(environment, query).subscribe({}); const observable = getObservableForActiveRequest( @@ -982,6 +1006,30 @@ describe('getObservableForActiveRequest', () => { expect(events).toEqual(['complete']); }); + it('calls complete asynchronously with subsequent final payload (OSS)', () => { + fetchQuery(environment, query).subscribe({}); + const observable = getObservableForActiveRequest( + environment, + query.request, + ); + expect(observable).not.toEqual(null); + if (!observable) { + return; + } + + response = { + ...response, + extensions: {}, + hasNext: false, + }; + + observable.subscribe(observer); + expect(events).toEqual([]); + + environment.mock.nextValue(gqlQuery, response); + expect(events).toEqual(['complete']); + }); + describe('when loading @module', () => { let operationLoader; let resolveModule; diff --git a/packages/relay-runtime/store/RelayModernQueryExecutor.js b/packages/relay-runtime/store/RelayModernQueryExecutor.js index e9e5a1b525dbe..efa77cb14631d 100644 --- a/packages/relay-runtime/store/RelayModernQueryExecutor.js +++ b/packages/relay-runtime/store/RelayModernQueryExecutor.js @@ -400,7 +400,7 @@ class Executor { if (responsesWithData.length === 0) { // no results with data, nothing to process // this can occur with extensions-only payloads - const isFinal = responses.some(x => x.extensions?.is_final === true); + const isFinal = responses.some(x => responseIsFinal(x)); if (isFinal) { this._state = 'loading_final'; this._updateActiveState(); @@ -1019,7 +1019,7 @@ class Executor { incrementalPlaceholders: null, moduleImportPayloads: null, source: RelayRecordSource.create(), - isFinal: response.extensions?.is_final === true, + isFinal: responseIsFinal(response), }; this._publishQueue.commitPayload( this._operation, @@ -1296,7 +1296,7 @@ function normalizeResponse( return { ...relayPayload, errors, - isFinal: response.extensions?.is_final === true, + isFinal: responseIsFinal(response), }; } @@ -1318,4 +1318,17 @@ function validateOptimisticResponsePayload( } } +/** + * Check for both FB specific (extensions?.is_final) + * and spec-complaint (hasNext) properties. + */ +function responseIsFinal(response: GraphQLSingularResponse): boolean { + if (response.extensions?.is_final === true) { + return true; + } else if (response.hasNext === false) { + return true; + } + return false; +} + module.exports = {execute};