diff --git a/packages/relay-runtime/store/OperationExecutor.js b/packages/relay-runtime/store/OperationExecutor.js index 4340570563e4..d0c507520c0e 100644 --- a/packages/relay-runtime/store/OperationExecutor.js +++ b/packages/relay-runtime/store/OperationExecutor.js @@ -42,12 +42,15 @@ import type { TaskScheduler, } from '../store/RelayStoreTypes'; import type { + NormalizationDefer, NormalizationLinkedField, NormalizationOperation, NormalizationRootNode, NormalizationSelectableNode, + NormalizationSelection, NormalizationSplitOperation, } from '../util/NormalizationNode'; +import type {NormalizationSelector} from './RelayStoreTypes'; import type {DataID, Disposable, Variables} from '../util/RelayRuntimeTypes'; import type {GetDataID} from './RelayResponseNormalizer'; import type {NormalizeResponseFunction} from './RelayStoreTypes'; @@ -67,6 +70,7 @@ const { createReaderSelector, } = require('./RelayModernSelector'); const RelayRecordSource = require('./RelayRecordSource'); +const resolveDeferSubPathChunk = require('./resolveDeferSubPathChunk'); const {ROOT_TYPE, TYPENAME_KEY, getStorageKey} = require('./RelayStoreUtils'); const invariant = require('invariant'); const warning = require('warning'); @@ -1266,6 +1270,13 @@ class Executor { : null; resultForLabel.set(pathKey, {kind: 'placeholder', placeholder}); + // If the parent has no data of its own (only inner @defers), the + // server never emits a parent chunk — register inner placeholders + // here so their chunks find a home. + if (placeholder.kind === 'defer') { + this._registerInnerDeferPlaceholders(placeholder); + } + // Store references to the parent node to allow detecting concurrent // modifications to the parent before items arrive and to replay // handle field payloads to account for new information on source records. @@ -1361,6 +1372,14 @@ class Executor { if (label.indexOf('$defer$') !== -1) { const pathKey = path.map(String).join('.'); let resultForPath = resultForLabel.get(pathKey); + let deferSubPath: ?ReadonlyArray = null; + if (resultForPath == null || resultForPath.kind !== 'placeholder') { + const prefix = findDeferPlaceholderByPrefix(resultForLabel, path); + if (prefix != null) { + resultForPath = prefix.placeholder; + deferSubPath = prefix.subPath; + } + } if (resultForPath == null) { resultForPath = {kind: 'response', responses: [incrementalResponse]}; resultForLabel.set(pathKey, resultForPath); @@ -1378,8 +1397,15 @@ class Executor { label, placeholder.kind, ); + this._registerInnerDeferPlaceholders(placeholder); relayPayloads.push( - this._processDeferResponse(label, path, placeholder, response), + this._processDeferResponse( + label, + path, + placeholder, + response, + deferSubPath, + ), ); } else { // @stream payload path values end in the field name and item index, @@ -1413,20 +1439,89 @@ class Executor { return relayPayloads; } + /** + * Register placeholders for any top-level Defer selections inside a parent + * defer's fragment. Called from two sites so both failure modes are covered: + * - `_processIncrementalPlaceholder`, when the parent placeholder is + * created from the initial payload — needed if the parent has no data + * of its own (only inner @defers), because in that case the server + * never emits a chunk for the parent label. + * - `_processIncrementalResponses`, when a chunk arrives that matches + * the parent placeholder — needed when the parent streams as sub-path + * chunks only (per-item, single sub-record), so the fragment root is + * never normalized and inner @defer AST nodes are never encountered. + * Without either registration, inner chunks arrive with an unregistered + * label and get silently queued forever. Idempotent, recurses so multiple + * levels of nesting are handled, and drains any responses that queued + * before the placeholder existed. + */ + _registerInnerDeferPlaceholders(parentPlaceholder: DeferPlaceholder): void { + const parentPathKey = parentPlaceholder.path.map(String).join('.'); + forEachInnerDefer(parentPlaceholder.selector, defer => { + let resultForLabel = this._incrementalResults.get(defer.label); + if (resultForLabel == null) { + resultForLabel = new Map(); + this._incrementalResults.set(defer.label, resultForLabel); + } + const existing = resultForLabel.get(parentPathKey); + if (existing != null && existing.kind === 'placeholder') { + return; + } + const innerPlaceholder = buildInnerDeferPlaceholder( + parentPlaceholder, + defer, + ); + resultForLabel.set(parentPathKey, { + kind: 'placeholder', + placeholder: innerPlaceholder, + }); + this._registerInnerDeferPlaceholders(innerPlaceholder); + if (existing != null && existing.kind === 'response') { + const payloadFollowups = this._processIncrementalResponses( + existing.responses, + ); + this._processPayloadFollowups(payloadFollowups); + } + }); + } + _processDeferResponse( label: string, path: ReadonlyArray, placeholder: DeferPlaceholder, response: GraphQLResponseWithData, + subPath?: ?ReadonlyArray, ): RelayResponsePayload { const {dataID: parentID} = placeholder.selector; const prevActorIdentifier = this._actorIdentifier; this._actorIdentifier = placeholder.actorIdentifier ?? this._actorIdentifier; + const activeSubPath = + subPath != null && subPath.length > 0 ? subPath : null; + const recovery = + activeSubPath != null + ? resolveDeferSubPathChunk( + placeholder, + response, + activeSubPath, + this._getStore(this._actorIdentifier).getSource(), + ) + : null; + if (activeSubPath != null && recovery == null) { + this._actorIdentifier = prevActorIdentifier; + return { + errors: response.errors ?? null, + fieldPayloads: [], + followupPayloads: null, + incrementalPlaceholders: null, + isFinal: false, + source: RelayRecordSource.create(), + }; + } const relayPayload = this._normalizeResponse( - response, - placeholder.selector, - placeholder.typeName, + recovery?.response ?? response, + recovery?.selector ?? placeholder.selector, + recovery?.typeName ?? placeholder.typeName, { actorIdentifier: this._actorIdentifier, deferDeduplicatedFields: this._deferDeduplicatedFields, @@ -1437,6 +1532,7 @@ class Executor { treatMissingFieldsAsNull: this._treatMissingFieldsAsNull, }, this._useExecTimeResolvers, + recovery?.existingRootRecord, ); this._getPublishQueueAndSaveActor().commitPayload( this._operation, @@ -1823,6 +1919,78 @@ function validateOptimisticResponsePayload( } } +function findDeferPlaceholderByPrefix( + resultForLabel: Map, + path: ReadonlyArray, +): ?{ + placeholder: IncrementalResults, + subPath: ReadonlyArray, +} { + for (let prefixLen = path.length - 1; prefixLen > 0; prefixLen--) { + const prefixKey = path.slice(0, prefixLen).map(String).join('.'); + const result = resultForLabel.get(prefixKey); + if (result != null && result.kind === 'placeholder') { + return {placeholder: result, subPath: path.slice(prefixLen)}; + } + } + return null; +} + +/** + * Walk the top-level selections of a placeholder's fragment for Defer nodes, + * descending through wrapper selections that don't change the record scope + * (InlineFragment, ClientExtension, Condition). Conditional defers are + * skipped when their `if` variable resolves to false at the placeholder's + * variables — matching `_normalizeDefer`'s behavior. + */ +function forEachInnerDefer( + selector: NormalizationSelector, + visit: (defer: NormalizationDefer) => void, +): void { + const walk = (selections: ReadonlyArray) => { + for (const sel of selections) { + switch (sel.kind) { + case 'Defer': + if ( + sel.if === null || + Boolean(selector.variables[sel.if]) + ) { + visit(sel); + } + break; + case 'InlineFragment': + case 'ClientExtension': + case 'Condition': + walk(sel.selections); + break; + } + } + }; + const selections = selector.node.selections; + if (selections != null) { + walk(selections); + } +} + +function buildInnerDeferPlaceholder( + parent: DeferPlaceholder, + defer: NormalizationDefer, +): DeferPlaceholder { + return { + actorIdentifier: parent.actorIdentifier, + data: {}, + kind: 'defer', + label: defer.label, + path: parent.path, + selector: { + dataID: parent.selector.dataID, + node: defer, + variables: parent.selector.variables, + }, + typeName: parent.typeName, + }; +} + module.exports = { execute, }; diff --git a/packages/relay-runtime/store/RelayStoreTypes.js b/packages/relay-runtime/store/RelayStoreTypes.js index f455df3b8689..52e9b985ef13 100644 --- a/packages/relay-runtime/store/RelayStoreTypes.js +++ b/packages/relay-runtime/store/RelayStoreTypes.js @@ -1249,6 +1249,7 @@ export type NormalizeResponseFunction = ( typeName: string, options: NormalizationOptions, useExecTimeResolvers: boolean, + existingRootRecord?: ?Record, ) => RelayResponsePayload; /** diff --git a/packages/relay-runtime/store/__tests__/RelayModernEnvironment-ExecuteWithDeferAndSubPath-test.js b/packages/relay-runtime/store/__tests__/RelayModernEnvironment-ExecuteWithDeferAndSubPath-test.js new file mode 100644 index 000000000000..05f5f5106efd --- /dev/null +++ b/packages/relay-runtime/store/__tests__/RelayModernEnvironment-ExecuteWithDeferAndSubPath-test.js @@ -0,0 +1,464 @@ +/** + * 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 + */ + +'use strict'; + +import type {GraphQLResponse} from 'relay-runtime/network/RelayNetworkTypes'; +import type {Sink} from 'relay-runtime/network/RelayObservable'; +import type {Snapshot} from 'relay-runtime/store/RelayStoreTypes'; +import type {RequestParameters} from 'relay-runtime/util/RelayConcreteNode'; +import type { + CacheConfig, + Variables, +} from 'relay-runtime/util/RelayRuntimeTypes'; + +const { + MultiActorEnvironment, + getActorIdentifier, +} = require('relay-runtime/multi-actor-environment'); +const RelayNetwork = require('relay-runtime/network/RelayNetwork'); +const RelayObservable = require('relay-runtime/network/RelayObservable'); +const {graphql} = require('relay-runtime/query/GraphQLTag'); +const RelayModernEnvironment = require('relay-runtime/store/RelayModernEnvironment'); +const { + createOperationDescriptor, +} = require('relay-runtime/store/RelayModernOperationDescriptor'); +const { + createReaderSelector, +} = require('relay-runtime/store/RelayModernSelector'); +const RelayModernStore = require('relay-runtime/store/RelayModernStore'); +const RelayRecordSource = require('relay-runtime/store/RelayRecordSource'); +const {disallowWarnings} = require('relay-test-utils-internal'); + +disallowWarnings(); + +describe.each(['RelayModernEnvironment', 'MultiActorEnvironment'])( + 'execute() a query with @defer + subPath', + environmentType => { + let callbacks; + let complete; + let dataSource; + let environment; + let error; + let next; + + describe(environmentType, () => { + beforeEach(() => { + complete = jest.fn<[], unknown>(); + error = jest.fn<[Error], unknown>(); + next = jest.fn<[GraphQLResponse], unknown>(); + callbacks = {complete, error, next}; + const fetch = ( + _query: RequestParameters, + _variables: Variables, + _cacheConfig: CacheConfig, + ): RelayObservable => { + return RelayObservable.create( + (sink: Sink) => { + dataSource = sink; + }, + ); + }; + const store = new RelayModernStore(RelayRecordSource.create()); + const multiActorEnvironment = new MultiActorEnvironment({ + createNetworkForActor: _actorID => RelayNetwork.create(fetch), + createStoreForActor: _actorID => store, + }); + environment = + environmentType === 'MultiActorEnvironment' + ? multiActorEnvironment.forActor(getActorIdentifier('actor:1234')) + : new RelayModernEnvironment({ + network: RelayNetwork.create(fetch), + store, + }); + }); + + it('processes a deferred payload whose subPath addresses a nested sub-record', () => { + const query = graphql` + query RelayModernEnvironmentExecuteWithDeferAndSubPathTestAddressQuery( + $id: ID! + ) { + node(id: $id) { + ... on User { + id + ...RelayModernEnvironmentExecuteWithDeferAndSubPathTestAddressFragment + @dangerously_unaliased_fixme + @defer(label: "AddressFragment") + } + } + } + `; + const fragment = graphql` + fragment RelayModernEnvironmentExecuteWithDeferAndSubPathTestAddressFragment on User { + address { + city + country + } + } + `; + const operation = createOperationDescriptor(query, {id: '1'}); + const selector = createReaderSelector( + fragment, + '1', + {}, + operation.request, + ); + const callback = jest.fn<[Snapshot], void>(); + environment.subscribe(environment.lookup(selector), callback); + + environment.execute({operation}).subscribe(callbacks); + dataSource.next({ + data: { + node: { + id: '1', + __typename: 'User', + address: {country: 'US'}, + }, + }, + }); + jest.runAllTimers(); + next.mockClear(); + callback.mockClear(); + + dataSource.next({ + data: {city: 'San Francisco', country: 'US'}, + label: + 'RelayModernEnvironmentExecuteWithDeferAndSubPathTestAddressQuery$defer$AddressFragment', + path: ['node', 'address'], + }); + + expect(complete).toBeCalledTimes(0); + expect(error).toBeCalledTimes(0); + expect(callback).toBeCalledTimes(1); + const snapshot = callback.mock.calls[0][0]; + expect(snapshot.isMissingData).toBe(false); + expect(snapshot.data).toEqual({ + address: {city: 'San Francisco', country: 'US'}, + }); + }); + + it('processes deferred sub-record payloads addressed via numeric+string subPath', () => { + const query = graphql` + query RelayModernEnvironmentExecuteWithDeferAndSubPathTestSubRecordQuery( + $id: ID! + ) { + node(id: $id) { + ... on User { + id + allPhones { + phoneNumber { + displayNumber + } + } + ...RelayModernEnvironmentExecuteWithDeferAndSubPathTestSubRecordFragment + @dangerously_unaliased_fixme + @defer(label: "SubRecordFragment") + } + } + } + `; + const fragment = graphql` + fragment RelayModernEnvironmentExecuteWithDeferAndSubPathTestSubRecordFragment on User { + allPhones { + phoneNumber { + countryCode + } + } + } + `; + const operation = createOperationDescriptor(query, {id: '1'}); + const selector = createReaderSelector( + fragment, + '1', + {}, + operation.request, + ); + + environment.execute({operation}).subscribe(callbacks); + dataSource.next({ + data: { + node: { + id: '1', + __typename: 'User', + allPhones: [ + {phoneNumber: {displayNumber: '+1-555-0100'}}, + {phoneNumber: {displayNumber: '+1-555-0101'}}, + ], + }, + }, + }); + jest.runAllTimers(); + next.mockClear(); + + dataSource.next({ + data: {countryCode: 'US'}, + label: + 'RelayModernEnvironmentExecuteWithDeferAndSubPathTestSubRecordQuery$defer$SubRecordFragment', + path: ['node', 'allPhones', 0, 'phoneNumber'], + }); + dataSource.next({ + data: {countryCode: 'CA'}, + label: + 'RelayModernEnvironmentExecuteWithDeferAndSubPathTestSubRecordQuery$defer$SubRecordFragment', + path: ['node', 'allPhones', 1, 'phoneNumber'], + }); + + expect(complete).toBeCalledTimes(0); + expect(error).toBeCalledTimes(0); + const snapshot = environment.lookup(selector); + expect(snapshot.isMissingData).toBe(false); + expect(snapshot.data).toEqual({ + allPhones: [ + {phoneNumber: {countryCode: 'US'}}, + {phoneNumber: {countryCode: 'CA'}}, + ], + }); + }); + + it('processes deferred per-item payloads addressed via numeric subPath', () => { + const query = graphql` + query RelayModernEnvironmentExecuteWithDeferAndSubPathTestPhonesQuery( + $id: ID! + ) { + node(id: $id) { + ... on User { + id + allPhones { + phoneNumber { + displayNumber + } + } + ...RelayModernEnvironmentExecuteWithDeferAndSubPathTestPhonesFragment + @dangerously_unaliased_fixme + @defer(label: "PhonesFragment") + } + } + } + `; + const fragment = graphql` + fragment RelayModernEnvironmentExecuteWithDeferAndSubPathTestPhonesFragment on User { + allPhones { + isVerified + phoneNumber { + displayNumber + } + } + } + `; + const operation = createOperationDescriptor(query, {id: '1'}); + const selector = createReaderSelector( + fragment, + '1', + {}, + operation.request, + ); + + environment.execute({operation}).subscribe(callbacks); + dataSource.next({ + data: { + node: { + id: '1', + __typename: 'User', + allPhones: [ + {phoneNumber: {displayNumber: '+1-555-0100'}}, + {phoneNumber: {displayNumber: '+1-555-0101'}}, + ], + }, + }, + }); + jest.runAllTimers(); + next.mockClear(); + + dataSource.next({ + data: { + isVerified: true, + phoneNumber: {displayNumber: '+1-555-0100'}, + }, + label: + 'RelayModernEnvironmentExecuteWithDeferAndSubPathTestPhonesQuery$defer$PhonesFragment', + path: ['node', 'allPhones', 0], + }); + dataSource.next({ + data: { + isVerified: false, + phoneNumber: {displayNumber: '+1-555-0101'}, + }, + label: + 'RelayModernEnvironmentExecuteWithDeferAndSubPathTestPhonesQuery$defer$PhonesFragment', + path: ['node', 'allPhones', 1], + }); + + expect(complete).toBeCalledTimes(0); + expect(error).toBeCalledTimes(0); + const snapshot = environment.lookup(selector); + expect(snapshot.isMissingData).toBe(false); + expect(snapshot.data).toEqual({ + allPhones: [ + {isVerified: true, phoneNumber: {displayNumber: '+1-555-0100'}}, + {isVerified: false, phoneNumber: {displayNumber: '+1-555-0101'}}, + ], + }); + }); + + it('processes a nested @defer chunk when the outer defer streams as sub-path chunks only', () => { + const query = graphql` + query RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedQuery( + $id: ID! + ) { + node(id: $id) { + ... on User { + id + allPhones { + phoneNumber { + displayNumber + } + } + ...RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedOuterFragment + @dangerously_unaliased_fixme + @defer(label: "NestedOuterFragment") + } + } + } + `; + graphql` + fragment RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedOuterFragment on User { + allPhones { + isVerified + } + ...RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedInnerFragment + @defer(label: "NestedInnerFragment") + } + `; + const innerFragment = graphql` + fragment RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedInnerFragment on User { + name + } + `; + const operation = createOperationDescriptor(query, {id: '1'}); + const innerSelector = createReaderSelector( + innerFragment, + '1', + {}, + operation.request, + ); + + environment.execute({operation}).subscribe(callbacks); + dataSource.next({ + data: { + node: { + id: '1', + __typename: 'User', + allPhones: [ + {phoneNumber: {displayNumber: '+1-555-0100'}}, + ], + }, + }, + }); + jest.runAllTimers(); + next.mockClear(); + + // Outer @defer's data streams ONLY as sub-path chunks (per-item). + // The outer fragment root at ['node'] never gets a chunk of its own, + // so a direct normalisation of the outer fragment never happens. + dataSource.next({ + data: {isVerified: true}, + label: + 'RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedQuery$defer$NestedOuterFragment', + path: ['node', 'allPhones', 0], + }); + + // Inner @defer chunk arrives at the outer's parent path, addressed + // by the inner label. Without the eager inner-placeholder registration, + // no placeholder exists for this label and the chunk is silently dropped. + dataSource.next({ + data: {name: 'Alice'}, + label: + 'RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedOuterFragment$defer$NestedInnerFragment', + path: ['node'], + }); + + expect(complete).toBeCalledTimes(0); + expect(error).toBeCalledTimes(0); + const snapshot = environment.lookup(innerSelector); + expect(snapshot.isMissingData).toBe(false); + expect(snapshot.data).toEqual({name: 'Alice'}); + }); + + it('processes a nested @defer chunk when the outer defer emits no chunk of its own', () => { + const query = graphql` + query RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedQuery( + $id: ID! + ) { + node(id: $id) { + ... on User { + id + allPhones { + phoneNumber { + displayNumber + } + } + ...RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedOuterFragment + @dangerously_unaliased_fixme + @defer(label: "NestedOuterFragment") + } + } + } + `; + const innerFragment = graphql` + fragment RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedInnerFragment on User { + name + } + `; + const operation = createOperationDescriptor(query, {id: '1'}); + const innerSelector = createReaderSelector( + innerFragment, + '1', + {}, + operation.request, + ); + + environment.execute({operation}).subscribe(callbacks); + dataSource.next({ + data: { + node: { + id: '1', + __typename: 'User', + allPhones: [ + {phoneNumber: {displayNumber: '+1-555-0100'}}, + ], + }, + }, + }); + jest.runAllTimers(); + next.mockClear(); + + // No outer chunk arrives — mirrors the server-side optimisation of + // skipping the parent label when its non-defer selections resolve + // to nothing. Inner @defer chunk arrives directly at the parent + // path. Without the eager placeholder registration triggered when + // the outer placeholder itself is created, no inner placeholder + // exists and the chunk is silently dropped. + dataSource.next({ + data: {name: 'Alice'}, + label: + 'RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedOuterFragment$defer$NestedInnerFragment', + path: ['node'], + }); + + expect(complete).toBeCalledTimes(0); + expect(error).toBeCalledTimes(0); + const snapshot = environment.lookup(innerSelector); + expect(snapshot.isMissingData).toBe(false); + expect(snapshot.data).toEqual({name: 'Alice'}); + }); + }); + }, +); diff --git a/packages/relay-runtime/store/__tests__/RelayModernEnvironment-ExecuteWithDeferInFragmentAndConnection-test.js b/packages/relay-runtime/store/__tests__/RelayModernEnvironment-ExecuteWithDeferInFragmentAndConnection-test.js new file mode 100644 index 000000000000..8116546613be --- /dev/null +++ b/packages/relay-runtime/store/__tests__/RelayModernEnvironment-ExecuteWithDeferInFragmentAndConnection-test.js @@ -0,0 +1,190 @@ +/** + * 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 + */ + +'use strict'; + +import type {GraphQLResponse} from 'relay-runtime/network/RelayNetworkTypes'; +import type {Sink} from 'relay-runtime/network/RelayObservable'; +import type {RequestParameters} from 'relay-runtime/util/RelayConcreteNode'; +import type { + CacheConfig, + Variables, +} from 'relay-runtime/util/RelayRuntimeTypes'; + +const { + MultiActorEnvironment, + getActorIdentifier, +} = require('relay-runtime/multi-actor-environment'); +const RelayNetwork = require('relay-runtime/network/RelayNetwork'); +const RelayObservable = require('relay-runtime/network/RelayObservable'); +const {graphql} = require('relay-runtime/query/GraphQLTag'); +const RelayModernEnvironment = require('relay-runtime/store/RelayModernEnvironment'); +const { + createOperationDescriptor, +} = require('relay-runtime/store/RelayModernOperationDescriptor'); +const { + createReaderSelector, +} = require('relay-runtime/store/RelayModernSelector'); +const RelayModernStore = require('relay-runtime/store/RelayModernStore'); +const RelayRecordSource = require('relay-runtime/store/RelayRecordSource'); +const {disallowWarnings} = require('relay-test-utils-internal'); + +disallowWarnings(); + +describe.each(['RelayModernEnvironment', 'MultiActorEnvironment'])( + 'execute() a query with @defer on a fragment-nested spread that uses @connection', + environmentType => { + let callbacks; + let complete; + let dataSource; + let environment; + let error; + let next; + + describe(environmentType, () => { + beforeEach(() => { + complete = jest.fn<[], unknown>(); + error = jest.fn<[Error], unknown>(); + next = jest.fn<[GraphQLResponse], unknown>(); + callbacks = {complete, error, next}; + const fetch = ( + _query: RequestParameters, + _variables: Variables, + _cacheConfig: CacheConfig, + ): RelayObservable => { + return RelayObservable.create( + (sink: Sink) => { + dataSource = sink; + }, + ); + }; + const store = new RelayModernStore(RelayRecordSource.create()); + const multiActorEnvironment = new MultiActorEnvironment({ + createNetworkForActor: _actorID => RelayNetwork.create(fetch), + createStoreForActor: _actorID => store, + }); + environment = + environmentType === 'MultiActorEnvironment' + ? multiActorEnvironment.forActor(getActorIdentifier('actor:1234')) + : new RelayModernEnvironment({ + network: RelayNetwork.create(fetch), + store, + }); + }); + + it('populates the connection edges from the deferred payload', () => { + const query = graphql` + query RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTestQuery( + $id: ID! + ) { + node(id: $id) { + ... on User { + ...RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTestWrapper + @dangerously_unaliased_fixme + } + } + } + `; + const wrapper = graphql` + fragment RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTestWrapper on User { + id + ...RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTestConnection + @dangerously_unaliased_fixme + @defer(label: "ConnectionFragment") + } + `; + const connectionFragment = graphql` + fragment RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTestConnection on User { + friends(first: 2) + @connection( + key: "RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTest_friends" + ) { + edges { + node { + id + name + } + } + } + } + `; + const operation = createOperationDescriptor(query, {id: '1'}); + const selector = createReaderSelector( + connectionFragment, + '1', + {}, + operation.request, + ); + + environment.execute({operation}).subscribe(callbacks); + dataSource.next({ + data: { + node: { + id: '1', + __typename: 'User', + }, + }, + }); + jest.runAllTimers(); + next.mockClear(); + + dataSource.next({ + data: { + friends: { + edges: [ + { + cursor: 'cursor-1', + node: {id: 'u2', name: 'Alice', __typename: 'User'}, + }, + { + cursor: 'cursor-2', + node: {id: 'u3', name: 'Bob', __typename: 'User'}, + }, + ], + pageInfo: { + endCursor: 'cursor-2', + hasNextPage: false, + startCursor: 'cursor-1', + hasPreviousPage: false, + }, + }, + }, + label: + 'RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTestWrapper$defer$ConnectionFragment', + path: ['node'], + }); + + expect(complete).toBeCalledTimes(0); + expect(error).toBeCalledTimes(0); + const snapshot = environment.lookup(selector); + expect(snapshot.isMissingData).toBe(false); + expect(snapshot.data).toEqual({ + friends: { + edges: [ + { + cursor: 'cursor-1', + node: {__typename: 'User', id: 'u2', name: 'Alice'}, + }, + { + cursor: 'cursor-2', + node: {__typename: 'User', id: 'u3', name: 'Bob'}, + }, + ], + pageInfo: { + endCursor: 'cursor-2', + hasNextPage: false, + }, + }, + }); + }); + }); + }, +); diff --git a/packages/relay-runtime/store/__tests__/__generated__/RelayModernEnvironmentExecuteWithDeferAndSubPathTestAddressFragment.graphql.js b/packages/relay-runtime/store/__tests__/__generated__/RelayModernEnvironmentExecuteWithDeferAndSubPathTestAddressFragment.graphql.js new file mode 100644 index 000000000000..86c4cb03212b --- /dev/null +++ b/packages/relay-runtime/store/__tests__/__generated__/RelayModernEnvironmentExecuteWithDeferAndSubPathTestAddressFragment.graphql.js @@ -0,0 +1,70 @@ +/** + * @generated SignedSource<> + * @flow + * @lightSyntaxTransform + */ + +/* eslint-disable */ + +'use strict'; + +/*:: +import type { Fragment, ReaderFragment } from 'relay-runtime'; +import type { FragmentType } from "relay-runtime"; +declare export opaque type RelayModernEnvironmentExecuteWithDeferAndSubPathTestAddressFragment$fragmentType: FragmentType; +export type RelayModernEnvironmentExecuteWithDeferAndSubPathTestAddressFragment$data = { + readonly address: ?{ + readonly city: ?string, + readonly country: ?string, + }, + readonly $fragmentType: RelayModernEnvironmentExecuteWithDeferAndSubPathTestAddressFragment$fragmentType, +}; +export type RelayModernEnvironmentExecuteWithDeferAndSubPathTestAddressFragment$key = { + readonly $data?: RelayModernEnvironmentExecuteWithDeferAndSubPathTestAddressFragment$data, + readonly $fragmentSpreads: RelayModernEnvironmentExecuteWithDeferAndSubPathTestAddressFragment$fragmentType, + ... +}; +*/ + +var node/*: ReaderFragment*/ = { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "RelayModernEnvironmentExecuteWithDeferAndSubPathTestAddressFragment", + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "StreetAddress", + "kind": "LinkedField", + "name": "address", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "city", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "country", + "storageKey": null + } + ], + "storageKey": null + } + ], + "type": "User", + "abstractKey": null +}; + +(node/*:: as any*/).hash = "274e0765e9812990054a75e1b55ba7a2"; + +module.exports = ((node/*:: as any*/)/*:: as Fragment< + RelayModernEnvironmentExecuteWithDeferAndSubPathTestAddressFragment$fragmentType, + RelayModernEnvironmentExecuteWithDeferAndSubPathTestAddressFragment$data, +>*/); diff --git a/packages/relay-runtime/store/__tests__/__generated__/RelayModernEnvironmentExecuteWithDeferAndSubPathTestAddressQuery.graphql.js b/packages/relay-runtime/store/__tests__/__generated__/RelayModernEnvironmentExecuteWithDeferAndSubPathTestAddressQuery.graphql.js new file mode 100644 index 000000000000..527cd7b41a63 --- /dev/null +++ b/packages/relay-runtime/store/__tests__/__generated__/RelayModernEnvironmentExecuteWithDeferAndSubPathTestAddressQuery.graphql.js @@ -0,0 +1,173 @@ +/** + * @generated SignedSource<<399fd526d4d897ef162b23f7aa9567b4>> + * @flow + * @lightSyntaxTransform + */ + +/* eslint-disable */ + +'use strict'; + +/*:: +import type { ConcreteRequest, Query } from 'relay-runtime'; +import type { RelayModernEnvironmentExecuteWithDeferAndSubPathTestAddressFragment$fragmentType } from "./RelayModernEnvironmentExecuteWithDeferAndSubPathTestAddressFragment.graphql"; +export type RelayModernEnvironmentExecuteWithDeferAndSubPathTestAddressQuery$variables = { + id: string, +}; +export type RelayModernEnvironmentExecuteWithDeferAndSubPathTestAddressQuery$data = { + readonly node: ?{ + readonly id?: string, + readonly $fragmentSpreads: RelayModernEnvironmentExecuteWithDeferAndSubPathTestAddressFragment$fragmentType, + }, +}; +export type RelayModernEnvironmentExecuteWithDeferAndSubPathTestAddressQuery = { + response: RelayModernEnvironmentExecuteWithDeferAndSubPathTestAddressQuery$data, + variables: RelayModernEnvironmentExecuteWithDeferAndSubPathTestAddressQuery$variables, +}; +*/ + +var node/*: ConcreteRequest*/ = (function(){ +var v0 = [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "id" + } +], +v1 = [ + { + "kind": "Variable", + "name": "id", + "variableName": "id" + } +], +v2 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}; +return { + "fragment": { + "argumentDefinitions": (v0/*:: as any*/), + "kind": "Fragment", + "metadata": null, + "name": "RelayModernEnvironmentExecuteWithDeferAndSubPathTestAddressQuery", + "selections": [ + { + "alias": null, + "args": (v1/*:: as any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + { + "kind": "InlineFragment", + "selections": [ + (v2/*:: as any*/), + { + "kind": "Defer", + "selections": [ + { + "args": null, + "kind": "FragmentSpread", + "name": "RelayModernEnvironmentExecuteWithDeferAndSubPathTestAddressFragment" + } + ] + } + ], + "type": "User", + "abstractKey": null + } + ], + "storageKey": null + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*:: as any*/), + "kind": "Operation", + "name": "RelayModernEnvironmentExecuteWithDeferAndSubPathTestAddressQuery", + "selections": [ + { + "alias": null, + "args": (v1/*:: as any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__typename", + "storageKey": null + }, + (v2/*:: as any*/), + { + "kind": "InlineFragment", + "selections": [ + { + "if": null, + "kind": "Defer", + "label": "RelayModernEnvironmentExecuteWithDeferAndSubPathTestAddressQuery$defer$AddressFragment", + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "StreetAddress", + "kind": "LinkedField", + "name": "address", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "city", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "country", + "storageKey": null + } + ], + "storageKey": null + } + ] + } + ], + "type": "User", + "abstractKey": null + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "c84e3031b9d6b7dd2abe4e2c5e48a42c", + "id": null, + "metadata": {}, + "name": "RelayModernEnvironmentExecuteWithDeferAndSubPathTestAddressQuery", + "operationKind": "query", + "text": "query RelayModernEnvironmentExecuteWithDeferAndSubPathTestAddressQuery(\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ... on User {\n id\n ...RelayModernEnvironmentExecuteWithDeferAndSubPathTestAddressFragment @defer(label: \"RelayModernEnvironmentExecuteWithDeferAndSubPathTestAddressQuery$defer$AddressFragment\")\n }\n id\n }\n}\n\nfragment RelayModernEnvironmentExecuteWithDeferAndSubPathTestAddressFragment on User {\n address {\n city\n country\n }\n}\n" + } +}; +})(); + +(node/*:: as any*/).hash = "c30cda65cc7c383f034293ef3abf08eb"; + +module.exports = ((node/*:: as any*/)/*:: as Query< + RelayModernEnvironmentExecuteWithDeferAndSubPathTestAddressQuery$variables, + RelayModernEnvironmentExecuteWithDeferAndSubPathTestAddressQuery$data, +>*/); diff --git a/packages/relay-runtime/store/__tests__/__generated__/RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedInnerFragment.graphql.js b/packages/relay-runtime/store/__tests__/__generated__/RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedInnerFragment.graphql.js new file mode 100644 index 000000000000..dfadd3f7ac8c --- /dev/null +++ b/packages/relay-runtime/store/__tests__/__generated__/RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedInnerFragment.graphql.js @@ -0,0 +1,58 @@ +/** + * 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. + * + * @oncall relay + * + * @generated SignedSource<> + * @flow + * @lightSyntaxTransform + */ + +/* eslint-disable */ + +'use strict'; + +/*:: +import type { Fragment, ReaderFragment } from 'relay-runtime'; +import type { FragmentType } from "relay-runtime"; +declare export opaque type RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedInnerFragment$fragmentType: FragmentType; +export type RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedInnerFragment$data = { + readonly name: ?string, + readonly $fragmentType: RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedInnerFragment$fragmentType, +}; +export type RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedInnerFragment$key = { + readonly $data?: RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedInnerFragment$data, + readonly $fragmentSpreads: RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedInnerFragment$fragmentType, + ... +}; +*/ + +var node/*: ReaderFragment*/ = { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedInnerFragment", + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null + } + ], + "type": "User", + "abstractKey": null +}; + +if (__DEV__) { + (node/*:: as any*/).hash = "59c0141e275b2d5eec1b385755517121"; +} + +module.exports = ((node/*:: as any*/)/*:: as Fragment< + RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedInnerFragment$fragmentType, + RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedInnerFragment$data, +>*/); diff --git a/packages/relay-runtime/store/__tests__/__generated__/RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedOuterFragment.graphql.js b/packages/relay-runtime/store/__tests__/__generated__/RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedOuterFragment.graphql.js new file mode 100644 index 000000000000..888ff89d6fb2 --- /dev/null +++ b/packages/relay-runtime/store/__tests__/__generated__/RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedOuterFragment.graphql.js @@ -0,0 +1,83 @@ +/** + * 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. + * + * @oncall relay + * + * @generated SignedSource<> + * @flow + * @lightSyntaxTransform + */ + +/* eslint-disable */ + +'use strict'; + +/*:: +import type { Fragment, ReaderFragment } from 'relay-runtime'; +import type { RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedInnerFragment$fragmentType } from "./RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedInnerFragment.graphql"; +import type { FragmentType } from "relay-runtime"; +declare export opaque type RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedOuterFragment$fragmentType: FragmentType; +export type RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedOuterFragment$data = { + readonly allPhones: ?ReadonlyArray, + readonly $fragmentSpreads: RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedInnerFragment$fragmentType, + readonly $fragmentType: RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedOuterFragment$fragmentType, +}; +export type RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedOuterFragment$key = { + readonly $data?: RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedOuterFragment$data, + readonly $fragmentSpreads: RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedOuterFragment$fragmentType, + ... +}; +*/ + +var node/*: ReaderFragment*/ = { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedOuterFragment", + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Phone", + "kind": "LinkedField", + "name": "allPhones", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "isVerified", + "storageKey": null + } + ], + "storageKey": null + }, + { + "kind": "Defer", + "selections": [ + { + "args": null, + "kind": "FragmentSpread", + "name": "RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedInnerFragment" + } + ] + } + ], + "type": "User", + "abstractKey": null +}; + +if (__DEV__) { + (node/*:: as any*/).hash = "5f2372e3381446e313db1309beaf91a7"; +} + +module.exports = ((node/*:: as any*/)/*:: as Fragment< + RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedOuterFragment$fragmentType, + RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedOuterFragment$data, +>*/); diff --git a/packages/relay-runtime/store/__tests__/__generated__/RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedQuery.graphql.js b/packages/relay-runtime/store/__tests__/__generated__/RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedQuery.graphql.js new file mode 100644 index 000000000000..5fd1a9f62840 --- /dev/null +++ b/packages/relay-runtime/store/__tests__/__generated__/RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedQuery.graphql.js @@ -0,0 +1,230 @@ +/** + * 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. + * + * @oncall relay + * + * @generated SignedSource<<6f6535ffbc9b7458ef23684766e33ea6>> + * @flow + * @lightSyntaxTransform + */ + +/* eslint-disable */ + +'use strict'; + +/*:: +import type { ConcreteRequest, Query } from 'relay-runtime'; +import type { RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedOuterFragment$fragmentType } from "./RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedOuterFragment.graphql"; +export type RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedQuery$variables = { + id: string, +}; +export type RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedQuery$data = { + readonly node: ?({ + readonly __typename: "User", + readonly allPhones: ?ReadonlyArray, + readonly id: string, + readonly $fragmentSpreads: RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedOuterFragment$fragmentType, + } | { + // This will never be '%other', but we need some + // value in case none of the concrete values match. + readonly __typename: "%other", + }), +}; +export type RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedQuery = { + response: RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedQuery$data, + variables: RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedQuery$variables, +}; +*/ + +var node/*: ConcreteRequest*/ = (function(){ +var v0 = [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "id" + } +], +v1 = [ + { + "kind": "Variable", + "name": "id", + "variableName": "id" + } +], +v2 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}, +v3 = { + "alias": null, + "args": null, + "concreteType": "Phone", + "kind": "LinkedField", + "name": "allPhones", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "PhoneNumber", + "kind": "LinkedField", + "name": "phoneNumber", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "displayNumber", + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null +}; +return { + "fragment": { + "argumentDefinitions": (v0/*:: as any*/), + "kind": "Fragment", + "metadata": null, + "name": "RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedQuery", + "selections": [ + { + "alias": null, + "args": (v1/*:: as any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + { + "kind": "InlineFragment", + "selections": [ + (v2/*:: as any*/), + (v3/*:: as any*/), + { + "kind": "Defer", + "selections": [ + { + "args": null, + "kind": "FragmentSpread", + "name": "RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedOuterFragment" + } + ] + } + ], + "type": "User", + "abstractKey": null + } + ], + "storageKey": null + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*:: as any*/), + "kind": "Operation", + "name": "RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedQuery", + "selections": [ + { + "alias": null, + "args": (v1/*:: as any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__typename", + "storageKey": null + }, + (v2/*:: as any*/), + { + "kind": "InlineFragment", + "selections": [ + (v3/*:: as any*/), + { + "if": null, + "kind": "Defer", + "label": "RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedQuery$defer$NestedOuterFragment", + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Phone", + "kind": "LinkedField", + "name": "allPhones", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "isVerified", + "storageKey": null + } + ], + "storageKey": null + }, + { + "if": null, + "kind": "Defer", + "label": "RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedOuterFragment$defer$NestedInnerFragment", + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null + } + ] + } + ] + } + ], + "type": "User", + "abstractKey": null + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "450865a9ea11afe26c8f79351f5552cb", + "id": null, + "metadata": {}, + "name": "RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedQuery", + "operationKind": "query", + "text": "query RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedQuery(\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ... on User {\n id\n allPhones {\n phoneNumber {\n displayNumber\n }\n }\n ...RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedOuterFragment @defer(label: \"RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedQuery$defer$NestedOuterFragment\")\n }\n id\n }\n}\n\nfragment RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedInnerFragment on User {\n name\n}\n\nfragment RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedOuterFragment on User {\n allPhones {\n isVerified\n }\n ...RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedInnerFragment @defer(label: \"RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedOuterFragment$defer$NestedInnerFragment\")\n}\n" + } +}; +})(); + +if (__DEV__) { + (node/*:: as any*/).hash = "dfc01954ee757284b4310b2d0208711c"; +} + +module.exports = ((node/*:: as any*/)/*:: as Query< + RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedQuery$variables, + RelayModernEnvironmentExecuteWithDeferAndSubPathTestNestedQuery$data, +>*/); diff --git a/packages/relay-runtime/store/__tests__/__generated__/RelayModernEnvironmentExecuteWithDeferAndSubPathTestPhonesFragment.graphql.js b/packages/relay-runtime/store/__tests__/__generated__/RelayModernEnvironmentExecuteWithDeferAndSubPathTestPhonesFragment.graphql.js new file mode 100644 index 000000000000..331f73b66b75 --- /dev/null +++ b/packages/relay-runtime/store/__tests__/__generated__/RelayModernEnvironmentExecuteWithDeferAndSubPathTestPhonesFragment.graphql.js @@ -0,0 +1,83 @@ +/** + * @generated SignedSource<<89dc8dea6c66d9af6c6badac4c20cdb8>> + * @flow + * @lightSyntaxTransform + */ + +/* eslint-disable */ + +'use strict'; + +/*:: +import type { Fragment, ReaderFragment } from 'relay-runtime'; +import type { FragmentType } from "relay-runtime"; +declare export opaque type RelayModernEnvironmentExecuteWithDeferAndSubPathTestPhonesFragment$fragmentType: FragmentType; +export type RelayModernEnvironmentExecuteWithDeferAndSubPathTestPhonesFragment$data = { + readonly allPhones: ?ReadonlyArray, + readonly $fragmentType: RelayModernEnvironmentExecuteWithDeferAndSubPathTestPhonesFragment$fragmentType, +}; +export type RelayModernEnvironmentExecuteWithDeferAndSubPathTestPhonesFragment$key = { + readonly $data?: RelayModernEnvironmentExecuteWithDeferAndSubPathTestPhonesFragment$data, + readonly $fragmentSpreads: RelayModernEnvironmentExecuteWithDeferAndSubPathTestPhonesFragment$fragmentType, + ... +}; +*/ + +var node/*: ReaderFragment*/ = { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "RelayModernEnvironmentExecuteWithDeferAndSubPathTestPhonesFragment", + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Phone", + "kind": "LinkedField", + "name": "allPhones", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "isVerified", + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "PhoneNumber", + "kind": "LinkedField", + "name": "phoneNumber", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "displayNumber", + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + } + ], + "type": "User", + "abstractKey": null +}; + +(node/*:: as any*/).hash = "9f85512473f742e9293d095e9b587bc6"; + +module.exports = ((node/*:: as any*/)/*:: as Fragment< + RelayModernEnvironmentExecuteWithDeferAndSubPathTestPhonesFragment$fragmentType, + RelayModernEnvironmentExecuteWithDeferAndSubPathTestPhonesFragment$data, +>*/); diff --git a/packages/relay-runtime/store/__tests__/__generated__/RelayModernEnvironmentExecuteWithDeferAndSubPathTestPhonesQuery.graphql.js b/packages/relay-runtime/store/__tests__/__generated__/RelayModernEnvironmentExecuteWithDeferAndSubPathTestPhonesQuery.graphql.js new file mode 100644 index 000000000000..967b42d99cd7 --- /dev/null +++ b/packages/relay-runtime/store/__tests__/__generated__/RelayModernEnvironmentExecuteWithDeferAndSubPathTestPhonesQuery.graphql.js @@ -0,0 +1,204 @@ +/** + * @generated SignedSource<> + * @flow + * @lightSyntaxTransform + */ + +/* eslint-disable */ + +'use strict'; + +/*:: +import type { ConcreteRequest, Query } from 'relay-runtime'; +import type { RelayModernEnvironmentExecuteWithDeferAndSubPathTestPhonesFragment$fragmentType } from "./RelayModernEnvironmentExecuteWithDeferAndSubPathTestPhonesFragment.graphql"; +export type RelayModernEnvironmentExecuteWithDeferAndSubPathTestPhonesQuery$variables = { + id: string, +}; +export type RelayModernEnvironmentExecuteWithDeferAndSubPathTestPhonesQuery$data = { + readonly node: ?{ + readonly allPhones?: ?ReadonlyArray, + readonly id?: string, + readonly $fragmentSpreads: RelayModernEnvironmentExecuteWithDeferAndSubPathTestPhonesFragment$fragmentType, + }, +}; +export type RelayModernEnvironmentExecuteWithDeferAndSubPathTestPhonesQuery = { + response: RelayModernEnvironmentExecuteWithDeferAndSubPathTestPhonesQuery$data, + variables: RelayModernEnvironmentExecuteWithDeferAndSubPathTestPhonesQuery$variables, +}; +*/ + +var node/*: ConcreteRequest*/ = (function(){ +var v0 = [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "id" + } +], +v1 = [ + { + "kind": "Variable", + "name": "id", + "variableName": "id" + } +], +v2 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}, +v3 = { + "alias": null, + "args": null, + "concreteType": "PhoneNumber", + "kind": "LinkedField", + "name": "phoneNumber", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "displayNumber", + "storageKey": null + } + ], + "storageKey": null +}, +v4 = { + "alias": null, + "args": null, + "concreteType": "Phone", + "kind": "LinkedField", + "name": "allPhones", + "plural": true, + "selections": [ + (v3/*:: as any*/) + ], + "storageKey": null +}; +return { + "fragment": { + "argumentDefinitions": (v0/*:: as any*/), + "kind": "Fragment", + "metadata": null, + "name": "RelayModernEnvironmentExecuteWithDeferAndSubPathTestPhonesQuery", + "selections": [ + { + "alias": null, + "args": (v1/*:: as any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + { + "kind": "InlineFragment", + "selections": [ + (v2/*:: as any*/), + (v4/*:: as any*/), + { + "kind": "Defer", + "selections": [ + { + "args": null, + "kind": "FragmentSpread", + "name": "RelayModernEnvironmentExecuteWithDeferAndSubPathTestPhonesFragment" + } + ] + } + ], + "type": "User", + "abstractKey": null + } + ], + "storageKey": null + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*:: as any*/), + "kind": "Operation", + "name": "RelayModernEnvironmentExecuteWithDeferAndSubPathTestPhonesQuery", + "selections": [ + { + "alias": null, + "args": (v1/*:: as any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__typename", + "storageKey": null + }, + (v2/*:: as any*/), + { + "kind": "InlineFragment", + "selections": [ + (v4/*:: as any*/), + { + "if": null, + "kind": "Defer", + "label": "RelayModernEnvironmentExecuteWithDeferAndSubPathTestPhonesQuery$defer$PhonesFragment", + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Phone", + "kind": "LinkedField", + "name": "allPhones", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "isVerified", + "storageKey": null + }, + (v3/*:: as any*/) + ], + "storageKey": null + } + ] + } + ], + "type": "User", + "abstractKey": null + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "b514fc2d60c74585676058a0d76d1dd3", + "id": null, + "metadata": {}, + "name": "RelayModernEnvironmentExecuteWithDeferAndSubPathTestPhonesQuery", + "operationKind": "query", + "text": "query RelayModernEnvironmentExecuteWithDeferAndSubPathTestPhonesQuery(\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ... on User {\n id\n allPhones {\n phoneNumber {\n displayNumber\n }\n }\n ...RelayModernEnvironmentExecuteWithDeferAndSubPathTestPhonesFragment @defer(label: \"RelayModernEnvironmentExecuteWithDeferAndSubPathTestPhonesQuery$defer$PhonesFragment\")\n }\n id\n }\n}\n\nfragment RelayModernEnvironmentExecuteWithDeferAndSubPathTestPhonesFragment on User {\n allPhones {\n isVerified\n phoneNumber {\n displayNumber\n }\n }\n}\n" + } +}; +})(); + +(node/*:: as any*/).hash = "2a2aa7c8efdecd21271297008fa575a8"; + +module.exports = ((node/*:: as any*/)/*:: as Query< + RelayModernEnvironmentExecuteWithDeferAndSubPathTestPhonesQuery$variables, + RelayModernEnvironmentExecuteWithDeferAndSubPathTestPhonesQuery$data, +>*/); diff --git a/packages/relay-runtime/store/__tests__/__generated__/RelayModernEnvironmentExecuteWithDeferAndSubPathTestSubRecordFragment.graphql.js b/packages/relay-runtime/store/__tests__/__generated__/RelayModernEnvironmentExecuteWithDeferAndSubPathTestSubRecordFragment.graphql.js new file mode 100644 index 000000000000..79537048e408 --- /dev/null +++ b/packages/relay-runtime/store/__tests__/__generated__/RelayModernEnvironmentExecuteWithDeferAndSubPathTestSubRecordFragment.graphql.js @@ -0,0 +1,75 @@ +/** + * @generated SignedSource<<0000000000000000000000000000000000>> + * @flow + * @lightSyntaxTransform + */ + +/* eslint-disable */ + +'use strict'; + +/*:: +import type { Fragment, ReaderFragment } from 'relay-runtime'; +import type { FragmentType } from "relay-runtime"; +declare export opaque type RelayModernEnvironmentExecuteWithDeferAndSubPathTestSubRecordFragment$fragmentType: FragmentType; +export type RelayModernEnvironmentExecuteWithDeferAndSubPathTestSubRecordFragment$data = { + readonly allPhones: ?ReadonlyArray, + readonly $fragmentType: RelayModernEnvironmentExecuteWithDeferAndSubPathTestSubRecordFragment$fragmentType, +}; +export type RelayModernEnvironmentExecuteWithDeferAndSubPathTestSubRecordFragment$key = { + readonly $data?: RelayModernEnvironmentExecuteWithDeferAndSubPathTestSubRecordFragment$data, + readonly $fragmentSpreads: RelayModernEnvironmentExecuteWithDeferAndSubPathTestSubRecordFragment$fragmentType, + ... +}; +*/ + +var node/*: ReaderFragment*/ = { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "RelayModernEnvironmentExecuteWithDeferAndSubPathTestSubRecordFragment", + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Phone", + "kind": "LinkedField", + "name": "allPhones", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "PhoneNumber", + "kind": "LinkedField", + "name": "phoneNumber", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "countryCode", + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + } + ], + "type": "User", + "abstractKey": null +}; + +(node/*:: as any*/).hash = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1"; + +module.exports = ((node/*:: as any*/)/*:: as Fragment< + RelayModernEnvironmentExecuteWithDeferAndSubPathTestSubRecordFragment$fragmentType, + RelayModernEnvironmentExecuteWithDeferAndSubPathTestSubRecordFragment$data, +>*/); diff --git a/packages/relay-runtime/store/__tests__/__generated__/RelayModernEnvironmentExecuteWithDeferAndSubPathTestSubRecordQuery.graphql.js b/packages/relay-runtime/store/__tests__/__generated__/RelayModernEnvironmentExecuteWithDeferAndSubPathTestSubRecordQuery.graphql.js new file mode 100644 index 000000000000..2309bf0481b7 --- /dev/null +++ b/packages/relay-runtime/store/__tests__/__generated__/RelayModernEnvironmentExecuteWithDeferAndSubPathTestSubRecordQuery.graphql.js @@ -0,0 +1,213 @@ +/** + * @generated SignedSource<<0000000000000000000000000000000000>> + * @flow + * @lightSyntaxTransform + */ + +/* eslint-disable */ + +'use strict'; + +/*:: +import type { ConcreteRequest, Query } from 'relay-runtime'; +import type { RelayModernEnvironmentExecuteWithDeferAndSubPathTestSubRecordFragment$fragmentType } from "./RelayModernEnvironmentExecuteWithDeferAndSubPathTestSubRecordFragment.graphql"; +export type RelayModernEnvironmentExecuteWithDeferAndSubPathTestSubRecordQuery$variables = { + id: string, +}; +export type RelayModernEnvironmentExecuteWithDeferAndSubPathTestSubRecordQuery$data = { + readonly node: ?{ + readonly allPhones?: ?ReadonlyArray, + readonly id?: string, + readonly $fragmentSpreads: RelayModernEnvironmentExecuteWithDeferAndSubPathTestSubRecordFragment$fragmentType, + }, +}; +export type RelayModernEnvironmentExecuteWithDeferAndSubPathTestSubRecordQuery = { + response: RelayModernEnvironmentExecuteWithDeferAndSubPathTestSubRecordQuery$data, + variables: RelayModernEnvironmentExecuteWithDeferAndSubPathTestSubRecordQuery$variables, +}; +*/ + +var node/*: ConcreteRequest*/ = (function(){ +var v0 = [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "id" + } +], +v1 = [ + { + "kind": "Variable", + "name": "id", + "variableName": "id" + } +], +v2 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}, +v3 = { + "alias": null, + "args": null, + "concreteType": "Phone", + "kind": "LinkedField", + "name": "allPhones", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "PhoneNumber", + "kind": "LinkedField", + "name": "phoneNumber", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "displayNumber", + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null +}; +return { + "fragment": { + "argumentDefinitions": (v0/*:: as any*/), + "kind": "Fragment", + "metadata": null, + "name": "RelayModernEnvironmentExecuteWithDeferAndSubPathTestSubRecordQuery", + "selections": [ + { + "alias": null, + "args": (v1/*:: as any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + { + "kind": "InlineFragment", + "selections": [ + (v2/*:: as any*/), + (v3/*:: as any*/), + { + "kind": "Defer", + "selections": [ + { + "args": null, + "kind": "FragmentSpread", + "name": "RelayModernEnvironmentExecuteWithDeferAndSubPathTestSubRecordFragment" + } + ] + } + ], + "type": "User", + "abstractKey": null + } + ], + "storageKey": null + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*:: as any*/), + "kind": "Operation", + "name": "RelayModernEnvironmentExecuteWithDeferAndSubPathTestSubRecordQuery", + "selections": [ + { + "alias": null, + "args": (v1/*:: as any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__typename", + "storageKey": null + }, + (v2/*:: as any*/), + { + "kind": "InlineFragment", + "selections": [ + (v3/*:: as any*/), + { + "if": null, + "kind": "Defer", + "label": "RelayModernEnvironmentExecuteWithDeferAndSubPathTestSubRecordQuery$defer$SubRecordFragment", + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Phone", + "kind": "LinkedField", + "name": "allPhones", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "PhoneNumber", + "kind": "LinkedField", + "name": "phoneNumber", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "countryCode", + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + } + ] + } + ], + "type": "User", + "abstractKey": null + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa2", + "id": null, + "metadata": {}, + "name": "RelayModernEnvironmentExecuteWithDeferAndSubPathTestSubRecordQuery", + "operationKind": "query", + "text": "query RelayModernEnvironmentExecuteWithDeferAndSubPathTestSubRecordQuery(\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ... on User {\n id\n allPhones {\n phoneNumber {\n displayNumber\n }\n }\n ...RelayModernEnvironmentExecuteWithDeferAndSubPathTestSubRecordFragment @defer(label: \"RelayModernEnvironmentExecuteWithDeferAndSubPathTestSubRecordQuery$defer$SubRecordFragment\")\n }\n id\n }\n}\n\nfragment RelayModernEnvironmentExecuteWithDeferAndSubPathTestSubRecordFragment on User {\n allPhones {\n phoneNumber {\n countryCode\n }\n }\n}\n" + } +}; +})(); + +(node/*:: as any*/).hash = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa3"; + +module.exports = ((node/*:: as any*/)/*:: as Query< + RelayModernEnvironmentExecuteWithDeferAndSubPathTestSubRecordQuery$variables, + RelayModernEnvironmentExecuteWithDeferAndSubPathTestSubRecordQuery$data, +>*/); diff --git a/packages/relay-runtime/store/__tests__/__generated__/RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTestConnection.graphql.js b/packages/relay-runtime/store/__tests__/__generated__/RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTestConnection.graphql.js new file mode 100644 index 000000000000..f79999783b1f --- /dev/null +++ b/packages/relay-runtime/store/__tests__/__generated__/RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTestConnection.graphql.js @@ -0,0 +1,155 @@ +/** + * 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. + * + * @oncall relay + * + * @generated SignedSource<<3b7025c3dc336899e47460ee187764a9>> + * @flow + * @lightSyntaxTransform + */ + +/* eslint-disable */ + +'use strict'; + +/*:: +import type { Fragment, ReaderFragment } from 'relay-runtime'; +import type { FragmentType } from "relay-runtime"; +declare export opaque type RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTestConnection$fragmentType: FragmentType; +export type RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTestConnection$data = { + readonly friends: ?{ + readonly edges: ?ReadonlyArray, + }, + readonly $fragmentType: RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTestConnection$fragmentType, +}; +export type RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTestConnection$key = { + readonly $data?: RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTestConnection$data, + readonly $fragmentSpreads: RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTestConnection$fragmentType, + ... +}; +*/ + +var node/*: ReaderFragment*/ = { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": { + "connection": [ + { + "count": null, + "cursor": null, + "direction": "forward", + "path": [ + "friends" + ] + } + ] + }, + "name": "RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTestConnection", + "selections": [ + { + "alias": "friends", + "args": null, + "concreteType": "FriendsConnection", + "kind": "LinkedField", + "name": "__RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTest_friends_connection", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "FriendsEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "User", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__typename", + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "cursor", + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "PageInfo", + "kind": "LinkedField", + "name": "pageInfo", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "endCursor", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "hasNextPage", + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + } + ], + "type": "User", + "abstractKey": null +}; + +if (__DEV__) { + (node/*:: as any*/).hash = "5e1107113c0893c5714dc8e2846e6552"; +} + +module.exports = ((node/*:: as any*/)/*:: as Fragment< + RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTestConnection$fragmentType, + RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTestConnection$data, +>*/); diff --git a/packages/relay-runtime/store/__tests__/__generated__/RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTestQuery.graphql.js b/packages/relay-runtime/store/__tests__/__generated__/RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTestQuery.graphql.js new file mode 100644 index 000000000000..511a8e50800f --- /dev/null +++ b/packages/relay-runtime/store/__tests__/__generated__/RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTestQuery.graphql.js @@ -0,0 +1,246 @@ +/** + * 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. + * + * @oncall relay + * + * @generated SignedSource<> + * @flow + * @lightSyntaxTransform + */ + +/* eslint-disable */ + +'use strict'; + +/*:: +import type { ConcreteRequest, Query } from 'relay-runtime'; +import type { RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTestWrapper$fragmentType } from "./RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTestWrapper.graphql"; +export type RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTestQuery$variables = { + id: string, +}; +export type RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTestQuery$data = { + readonly node: ?({ + readonly __typename: "User", + readonly $fragmentSpreads: RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTestWrapper$fragmentType, + } | { + // This will never be '%other', but we need some + // value in case none of the concrete values match. + readonly __typename: "%other", + }), +}; +export type RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTestQuery = { + response: RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTestQuery$data, + variables: RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTestQuery$variables, +}; +*/ + +var node/*: ConcreteRequest*/ = (function(){ +var v0 = [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "id" + } +], +v1 = [ + { + "kind": "Variable", + "name": "id", + "variableName": "id" + } +], +v2 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__typename", + "storageKey": null +}, +v3 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}, +v4 = [ + { + "kind": "Literal", + "name": "first", + "value": 2 + } +]; +return { + "fragment": { + "argumentDefinitions": (v0/*:: as any*/), + "kind": "Fragment", + "metadata": null, + "name": "RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTestQuery", + "selections": [ + { + "alias": null, + "args": (v1/*:: as any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + { + "kind": "InlineFragment", + "selections": [ + { + "args": null, + "kind": "FragmentSpread", + "name": "RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTestWrapper" + } + ], + "type": "User", + "abstractKey": null + } + ], + "storageKey": null + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*:: as any*/), + "kind": "Operation", + "name": "RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTestQuery", + "selections": [ + { + "alias": null, + "args": (v1/*:: as any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v2/*:: as any*/), + (v3/*:: as any*/), + { + "kind": "InlineFragment", + "selections": [ + { + "if": null, + "kind": "Defer", + "label": "RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTestWrapper$defer$ConnectionFragment", + "selections": [ + { + "alias": null, + "args": (v4/*:: as any*/), + "concreteType": "FriendsConnection", + "kind": "LinkedField", + "name": "friends", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "FriendsEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "User", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v3/*:: as any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null + }, + (v2/*:: as any*/) + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "cursor", + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "PageInfo", + "kind": "LinkedField", + "name": "pageInfo", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "endCursor", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "hasNextPage", + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": "friends(first:2)" + }, + { + "alias": null, + "args": (v4/*:: as any*/), + "filters": null, + "handle": "connection", + "key": "RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTest_friends", + "kind": "LinkedHandle", + "name": "friends" + } + ] + } + ], + "type": "User", + "abstractKey": null + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "ad2ef0b08d86553295e138dccf1d9a7a", + "id": null, + "metadata": {}, + "name": "RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTestQuery", + "operationKind": "query", + "text": "query RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTestQuery(\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ... on User {\n ...RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTestWrapper\n }\n id\n }\n}\n\nfragment RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTestConnection on User {\n friends(first: 2) {\n edges {\n node {\n id\n name\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n\nfragment RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTestWrapper on User {\n id\n ...RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTestConnection @defer(label: \"RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTestWrapper$defer$ConnectionFragment\")\n}\n" + } +}; +})(); + +if (__DEV__) { + (node/*:: as any*/).hash = "628a7940fe391ca22acbf0aab3a8258a"; +} + +module.exports = ((node/*:: as any*/)/*:: as Query< + RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTestQuery$variables, + RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTestQuery$data, +>*/); diff --git a/packages/relay-runtime/store/__tests__/__generated__/RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTestWrapper.graphql.js b/packages/relay-runtime/store/__tests__/__generated__/RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTestWrapper.graphql.js new file mode 100644 index 000000000000..e8ad145d08a2 --- /dev/null +++ b/packages/relay-runtime/store/__tests__/__generated__/RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTestWrapper.graphql.js @@ -0,0 +1,70 @@ +/** + * 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. + * + * @oncall relay + * + * @generated SignedSource<> + * @flow + * @lightSyntaxTransform + */ + +/* eslint-disable */ + +'use strict'; + +/*:: +import type { Fragment, ReaderFragment } from 'relay-runtime'; +import type { RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTestConnection$fragmentType } from "./RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTestConnection.graphql"; +import type { FragmentType } from "relay-runtime"; +declare export opaque type RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTestWrapper$fragmentType: FragmentType; +export type RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTestWrapper$data = { + readonly id: string, + readonly $fragmentSpreads: RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTestConnection$fragmentType, + readonly $fragmentType: RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTestWrapper$fragmentType, +}; +export type RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTestWrapper$key = { + readonly $data?: RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTestWrapper$data, + readonly $fragmentSpreads: RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTestWrapper$fragmentType, + ... +}; +*/ + +var node/*: ReaderFragment*/ = { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTestWrapper", + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null + }, + { + "kind": "Defer", + "selections": [ + { + "args": null, + "kind": "FragmentSpread", + "name": "RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTestConnection" + } + ] + } + ], + "type": "User", + "abstractKey": null +}; + +if (__DEV__) { + (node/*:: as any*/).hash = "5ace9d3bbeea4c876fb8ba62f8ea1933"; +} + +module.exports = ((node/*:: as any*/)/*:: as Fragment< + RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTestWrapper$fragmentType, + RelayModernEnvironmentExecuteWithDeferInFragmentAndConnectionTestWrapper$data, +>*/); diff --git a/packages/relay-runtime/store/normalizeResponse.js b/packages/relay-runtime/store/normalizeResponse.js index 0b6b736803f7..d6f350226e77 100644 --- a/packages/relay-runtime/store/normalizeResponse.js +++ b/packages/relay-runtime/store/normalizeResponse.js @@ -15,6 +15,7 @@ import type {GraphQLResponseWithData} from '../network/RelayNetworkTypes'; import type {NormalizationOptions} from './RelayResponseNormalizer'; import type { NormalizationSelector, + Record, RelayResponsePayload, } from './RelayStoreTypes'; @@ -28,10 +29,14 @@ function normalizeResponse( typeName: string, options: NormalizationOptions, useExecTimeResolvers: boolean, + existingRootRecord?: ?Record, ): RelayResponsePayload { const {data, errors} = response; const source = RelayRecordSource.create(); - const record = RelayModernRecord.create(selector.dataID, typeName); + const record = + existingRootRecord != null + ? RelayModernRecord.clone(existingRootRecord) + : RelayModernRecord.create(selector.dataID, typeName); source.set(selector.dataID, record); const relayPayload = RelayResponseNormalizer.normalize( source, diff --git a/packages/relay-runtime/store/resolveDeferSubPathChunk.js b/packages/relay-runtime/store/resolveDeferSubPathChunk.js new file mode 100644 index 000000000000..6ef81907ed05 --- /dev/null +++ b/packages/relay-runtime/store/resolveDeferSubPathChunk.js @@ -0,0 +1,198 @@ +/** + * 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 + */ + +'use strict'; + +import type {GraphQLResponseWithData} from '../network/RelayNetworkTypes'; +import type { + NormalizationLinkedField, + NormalizationSelectableNode, + NormalizationSelection, +} from '../util/NormalizationNode'; +import type {DataID, Variables} from '../util/RelayRuntimeTypes'; +import type { + DeferPlaceholder, + NormalizationSelector, + Record, + RecordSource, +} from './RelayStoreTypes'; + +import RelayModernRecord from './RelayModernRecord'; +import {createNormalizationSelector} from './RelayModernSelector'; +import {getStorageKey} from './RelayStoreUtils'; + +export type RecoveredDeferPayload = { + readonly selector: NormalizationSelector, + readonly typeName: string, + readonly response: GraphQLResponseWithData, + readonly existingRootRecord: ?Record, +}; + +/** + * Recover the effective selector + response for a deferred chunk that + * landed at a `subPath` beyond its placeholder's registration path. + * Returns null when the addressed location can't be resolved in the store. + */ +function resolveDeferSubPathChunk( + placeholder: DeferPlaceholder, + response: GraphQLResponseWithData, + subPath: ReadonlyArray, + storeSource: RecordSource, +): ?RecoveredDeferPayload { + return subPath.some(key => typeof key === 'number') + ? recoverAtChildRecord(placeholder, response, subPath, storeSource) + : recoverAtParentRecord(placeholder, response, subPath, storeSource); +} + +function recoverAtParentRecord( + placeholder: DeferPlaceholder, + response: GraphQLResponseWithData, + subPath: ReadonlyArray, + storeSource: RecordSource, +): RecoveredDeferPayload { + return { + selector: placeholder.selector, + typeName: placeholder.typeName, + response: {...response, data: wrapUnderSubPath(subPath, response.data)}, + existingRootRecord: storeSource.get(placeholder.selector.dataID), + }; +} + +function recoverAtChildRecord( + placeholder: DeferPlaceholder, + response: GraphQLResponseWithData, + subPath: ReadonlyArray, + storeSource: RecordSource, +): ?RecoveredDeferPayload { + const walk = walkSubPathToLinkedField( + placeholder.selector.node, + subPath, + storeSource, + placeholder.selector.dataID, + placeholder.selector.variables, + ); + if (walk == null) return null; + const {dataID, field} = walk; + const {concreteType} = field; + if (concreteType == null) return null; + return { + selector: createNormalizationSelector( + field, + dataID, + placeholder.selector.variables, + ), + typeName: concreteType, + response, + existingRootRecord: storeSource.get(dataID), + }; +} + +/** + * Fold string subPath keys back into `data`: + * `wrapUnderSubPath(['address'], {city}) → {address: {city}}`. + */ +function wrapUnderSubPath( + subPath: ReadonlyArray, + data: unknown, +): {[string]: unknown} { + let wrapped: {[string]: unknown} = { + [String(subPath[subPath.length - 1])]: data, + }; + for (let i = subPath.length - 2; i >= 0; i--) { + wrapped = {[String(subPath[i])]: wrapped}; + } + return wrapped; +} + +type WalkState = { + readonly field: ?NormalizationLinkedField, + readonly selections: ReadonlyArray, + readonly dataID: DataID, + readonly pendingIDs: ?ReadonlyArray, +}; + +/** + * Walk the fragment's normalization AST alongside the store, following each + * `subPath` key (string = LinkedField name, number = index into the previous + * plural link), to find the target LinkedField and its item dataID. + */ +function walkSubPathToLinkedField( + node: NormalizationSelectableNode, + subPath: ReadonlyArray, + storeSource: RecordSource, + rootDataID: DataID, + variables: Variables, +): ?{dataID: DataID, field: NormalizationLinkedField} { + let state: WalkState = { + field: null, + selections: node.selections, + dataID: rootDataID, + pendingIDs: null, + }; + for (const key of subPath) { + const next = + typeof key === 'string' + ? stepIntoField(state, key, storeSource, variables) + : typeof key === 'number' + ? stepIntoIndex(state, key) + : null; + if (next == null) return null; + state = next; + } + return state.field != null + ? {dataID: state.dataID, field: state.field} + : null; +} + +function stepIntoField( + state: WalkState, + name: string, + storeSource: RecordSource, + variables: Variables, +): ?WalkState { + const field = findLinkedField(state.selections, name); + if (field == null) return null; + const record = storeSource.get(state.dataID); + if (record == null) return null; + const storageKey = getStorageKey(field, variables); + if (field.plural === true) { + const pendingIDs = RelayModernRecord.getLinkedRecordIDs(record, storageKey); + if (pendingIDs == null) return null; + return {field, selections: field.selections, dataID: state.dataID, pendingIDs}; + } + const linked = RelayModernRecord.getLinkedRecordID(record, storageKey); + if (linked == null) return null; + return {field, selections: field.selections, dataID: linked, pendingIDs: null}; +} + +function stepIntoIndex(state: WalkState, index: number): ?WalkState { + if (state.pendingIDs == null) return null; + const itemID = state.pendingIDs[index]; + if (itemID == null) return null; + return {...state, dataID: itemID, pendingIDs: null}; +} + +function findLinkedField( + selections: ReadonlyArray, + responseKey: string, +): ?NormalizationLinkedField { + for (const selection of selections) { + if ( + selection.kind === 'LinkedField' && + (selection.alias ?? selection.name) === responseKey + ) { + return selection; + } + } + return null; +} + +module.exports = resolveDeferSubPathChunk;