diff --git a/packages/react-relay/__tests__/LiveResolvers-test.js b/packages/react-relay/__tests__/LiveResolvers-test.js index 9f72ccd97e452..61d2e4304bda7 100644 --- a/packages/react-relay/__tests__/LiveResolvers-test.js +++ b/packages/react-relay/__tests__/LiveResolvers-test.js @@ -975,7 +975,7 @@ describe.each([true, false])( expect(relayFieldLogger.mock.calls).toEqual([ [ { - fieldPath: '', + fieldPath: 'username', kind: 'missing_expected_data.log', owner: 'ResolverThatThrows', }, diff --git a/packages/relay-runtime/store/RelayReader.js b/packages/relay-runtime/store/RelayReader.js index fe5b16c39ac82..24a0e29ca7db9 100644 --- a/packages/relay-runtime/store/RelayReader.js +++ b/packages/relay-runtime/store/RelayReader.js @@ -217,7 +217,7 @@ class RelayReader { } } - _markDataAsMissing(): void { + _markDataAsMissing(fieldName: string): void { if (this._isWithinUnmatchedTypeRefinement) { return; } @@ -226,7 +226,6 @@ class RelayReader { } // we will add the path later - const fieldPath = ''; const owner = this._fragmentName; this._errorResponseFields.push( @@ -234,10 +233,10 @@ class RelayReader { ? { kind: 'missing_expected_data.throw', owner, - fieldPath, + fieldPath: fieldName, handled: false, } - : {kind: 'missing_expected_data.log', owner, fieldPath}, + : {kind: 'missing_expected_data.log', owner, fieldPath: fieldName}, ); this._isMissingData = true; @@ -266,7 +265,7 @@ class RelayReader { this._seenRecords.add(dataID); if (record == null) { if (record === undefined) { - this._markDataAsMissing(); + this._markDataAsMissing(''); } return record; } @@ -489,12 +488,15 @@ class RelayReader { this._createFragmentPointer(selection, record, data); break; case 'AliasedInlineFragmentSpread': { + const prevErrors = this._errorResponseFields; + this._errorResponseFields = null; let fieldValue = this._readInlineFragment( selection.fragment, record, {}, true, ); + this._prependPreviousErrors(prevErrors, selection.name); if (fieldValue === false) { fieldValue = null; } @@ -601,9 +603,12 @@ class RelayReader { data: SelectorData, ): mixed { const parentRecordID = RelayModernRecord.getDataID(record); + const prevErrors = this._errorResponseFields; + this._errorResponseFields = null; const result = this._readResolverFieldImpl(field, parentRecordID); const fieldName = field.alias ?? field.name; + this._prependPreviousErrors(prevErrors, fieldName); data[fieldName] = result; return result; } @@ -982,12 +987,15 @@ class RelayReader { RelayModernRecord.getDataID(record), prevData, ); + const prevErrors = this._errorResponseFields; + this._errorResponseFields = null; const edgeValue = this._traverse( field.linkedField, storeID, // $FlowFixMe[incompatible-variance] prevData, ); + this._prependPreviousErrors(prevErrors, fieldName); this._clientEdgeTraversalPath.pop(); data[fieldName] = edgeValue; return edgeValue; @@ -1005,7 +1013,7 @@ class RelayReader { if (value === null) { this._maybeAddErrorResponseFields(record, storageKey); } else if (value === undefined) { - this._markDataAsMissing(); + this._markDataAsMissing(fieldName); } data[fieldName] = value; return value; @@ -1024,7 +1032,7 @@ class RelayReader { if (linkedID === null) { this._maybeAddErrorResponseFields(record, storageKey); } else if (linkedID === undefined) { - this._markDataAsMissing(); + this._markDataAsMissing(fieldName); } return linkedID; } @@ -1039,12 +1047,73 @@ class RelayReader { RelayModernRecord.getDataID(record), prevData, ); + const prevErrors = this._errorResponseFields; + this._errorResponseFields = null; // $FlowFixMe[incompatible-variance] const value = this._traverse(field, linkedID, prevData); + + this._prependPreviousErrors(prevErrors, fieldName); data[fieldName] = value; return value; } + /** + * Adds a set of field errors to `this._errorResponseFields`, ensuring the + * `fieldPath` property of existing field errors are prefixed with the given + * `fieldNameOrIndex`. + * + * In order to make field errors maximally useful in logs/errors, we want to + * include the path to the field that caused the error. A naive approach would + * be to maintain a path property on RelayReader which we push/pop field names + * to as we traverse into fields/etc. However, this would be expensive to + * maintain, and in the common case where there are no field errors, the work + * would go unused. + * + * Instead, we take a lazy approach where as we exit the recurison into a + * field/etc we prepend any errors encountered while traversing that field + * with the field name. This is somewhat more expensive in the error case, but + * ~free in the common case where there are no errors. + * + * To achieve this, named field readers must do the following to correctly + * track error filePaths: + * + * 1. Stash the value of `this._errorResponseFields` in a local variable + * 2. Set `this._errorResponseFields` to `null` + * 3. Traverse into the field + * 4. Call this method with the stashed errors and the field's name + * + * Similarly, when creating field errors, we simply initialize the `fieldPath` + * as the direct field name. + * + * Today we only use this apporach for `missing_expected_data` errors, but we + * intend to broaden it to handle all field error paths. + */ + _prependPreviousErrors( + prevErrors: ?Array, + fieldNameOrIndex: string | number, + ): void { + if (this._errorResponseFields != null) { + for (let i = 0; i < this._errorResponseFields.length; i++) { + const event = this._errorResponseFields[i]; + if ( + event.owner === this._fragmentName && + (event.kind === 'missing_expected_data.throw' || + event.kind === 'missing_expected_data.log') + ) { + event.fieldPath = `${fieldNameOrIndex}.${event.fieldPath}`; + } + } + if (prevErrors != null) { + for (let i = this._errorResponseFields.length - 1; i >= 0; i--) { + prevErrors.push(this._errorResponseFields[i]); + } + this._errorResponseFields = prevErrors; + } + } else { + this._errorResponseFields = prevErrors; + } + } + _readActorChange( field: ReaderActorChange, record: Record, @@ -1060,7 +1129,7 @@ class RelayReader { if (externalRef == null) { data[fieldName] = externalRef; if (externalRef === undefined) { - this._markDataAsMissing(); + this._markDataAsMissing(fieldName); } else if (externalRef === null) { this._maybeAddErrorResponseFields(record, storageKey); } @@ -1107,7 +1176,7 @@ class RelayReader { if (linkedIDs == null) { data[fieldName] = linkedIDs; if (linkedIDs === undefined) { - this._markDataAsMissing(); + this._markDataAsMissing(fieldName); } return linkedIDs; } @@ -1121,11 +1190,13 @@ class RelayReader { RelayModernRecord.getDataID(record), prevData, ); + const prevErrors = this._errorResponseFields; + this._errorResponseFields = null; const linkedArray = prevData || []; linkedIDs.forEach((linkedID, nextIndex) => { if (linkedID == null) { if (linkedID === undefined) { - this._markDataAsMissing(); + this._markDataAsMissing(String(nextIndex)); } // $FlowFixMe[cannot-write] linkedArray[nextIndex] = linkedID; @@ -1140,10 +1211,14 @@ class RelayReader { RelayModernRecord.getDataID(record), prevItem, ); + const prevErrors = this._errorResponseFields; + this._errorResponseFields = null; // $FlowFixMe[cannot-write] // $FlowFixMe[incompatible-variance] linkedArray[nextIndex] = this._traverse(field, linkedID, prevItem); + this._prependPreviousErrors(prevErrors, nextIndex); }); + this._prependPreviousErrors(prevErrors, fieldName); data[fieldName] = linkedArray; return linkedArray; } @@ -1166,7 +1241,7 @@ class RelayReader { RelayModernRecord.getValue(record, componentKey); if (component == null) { if (component === undefined) { - this._markDataAsMissing(); + this._markDataAsMissing(''); } return; } @@ -1398,7 +1473,7 @@ class RelayReader { // fetched the `__is[AbstractType]` flag for this concrete type. In this // case we need to report that we are missing data, in case that field is // still in flight. - this._markDataAsMissing(); + this._markDataAsMissing(''); } // $FlowFixMe Casting record value return implementsInterface; diff --git a/packages/relay-runtime/store/RelayStoreTypes.js b/packages/relay-runtime/store/RelayStoreTypes.js index 800d1e8c01a39..6b1f03b443460 100644 --- a/packages/relay-runtime/store/RelayStoreTypes.js +++ b/packages/relay-runtime/store/RelayStoreTypes.js @@ -1274,7 +1274,7 @@ export type MissingFieldHandler = export type MissingExpectedDataLogEvent = { +kind: 'missing_expected_data.log', +owner: string, - +fieldPath: string, + fieldPath: string, // Purposefully mutable to allow lazy construction in RelayReader }; /** @@ -1300,7 +1300,7 @@ export type MissingExpectedDataLogEvent = { export type MissingExpectedDataThrowEvent = { +kind: 'missing_expected_data.throw', +owner: string, - +fieldPath: string, + fieldPath: string, // Purposefully mutable to allow lazy construction in RelayReader +handled: boolean, }; diff --git a/packages/relay-runtime/store/__tests__/RelayModernStore-Subscriptions-test.js b/packages/relay-runtime/store/__tests__/RelayModernStore-Subscriptions-test.js index 6dc0a6eaa0deb..67f27036848b8 100644 --- a/packages/relay-runtime/store/__tests__/RelayModernStore-Subscriptions-test.js +++ b/packages/relay-runtime/store/__tests__/RelayModernStore-Subscriptions-test.js @@ -417,12 +417,12 @@ function cloneEventWithSets(event: LogEvent) { isMissingData: true, errorResponseFields: [ { - fieldPath: '', + fieldPath: 'profilePicture', kind: 'missing_expected_data.log', owner: 'RelayModernStoreSubscriptionsTest1Fragment', }, { - fieldPath: '', + fieldPath: 'emailAddresses', kind: 'missing_expected_data.log', owner: 'RelayModernStoreSubscriptionsTest1Fragment', }, @@ -466,12 +466,12 @@ function cloneEventWithSets(event: LogEvent) { }, errorResponseFields: [ { - fieldPath: '', + fieldPath: 'profilePicture', kind: 'missing_expected_data.log', owner: 'RelayModernStoreSubscriptionsTest1Fragment', }, { - fieldPath: '', + fieldPath: 'emailAddresses', kind: 'missing_expected_data.log', owner: 'RelayModernStoreSubscriptionsTest1Fragment', }, diff --git a/packages/relay-runtime/store/__tests__/RelayModernStore-test.js b/packages/relay-runtime/store/__tests__/RelayModernStore-test.js index 056340e9e0e1b..2ef5803e28304 100644 --- a/packages/relay-runtime/store/__tests__/RelayModernStore-test.js +++ b/packages/relay-runtime/store/__tests__/RelayModernStore-test.js @@ -728,12 +728,12 @@ function cloneEventWithSets(event: LogEvent) { { owner: 'RelayModernStoreTest5Fragment', kind: 'missing_expected_data.log', - fieldPath: '', + fieldPath: 'profilePicture', }, { owner: 'RelayModernStoreTest5Fragment', kind: 'missing_expected_data.log', - fieldPath: '', + fieldPath: 'emailAddresses', }, ], missingLiveResolverFields: [], @@ -782,12 +782,12 @@ function cloneEventWithSets(event: LogEvent) { { owner: 'RelayModernStoreTest5Fragment', kind: 'missing_expected_data.log', - fieldPath: '', + fieldPath: 'profilePicture', }, { owner: 'RelayModernStoreTest5Fragment', kind: 'missing_expected_data.log', - fieldPath: '', + fieldPath: 'emailAddresses', }, ], seenRecords: new Set(['842472']), diff --git a/packages/relay-runtime/store/__tests__/RelayReader-AliasedFragments-test.js b/packages/relay-runtime/store/__tests__/RelayReader-AliasedFragments-test.js index 9d55ce9b320e0..6395068654d58 100644 --- a/packages/relay-runtime/store/__tests__/RelayReader-AliasedFragments-test.js +++ b/packages/relay-runtime/store/__tests__/RelayReader-AliasedFragments-test.js @@ -1041,7 +1041,7 @@ describe('Inline Fragments', () => { ); expect(errorResponseFields).toEqual([ { - fieldPath: '', + fieldPath: 'node.aliased_fragment.name', kind: 'missing_expected_data.log', owner: 'RelayReaderAliasedFragmentsTestRequiredBubblesOnAbstractTypeQuery', @@ -1101,7 +1101,7 @@ describe('Inline Fragments', () => { ); expect(errorResponseFields).toEqual([ { - fieldPath: '', + fieldPath: 'node.aliased_fragment.', kind: 'missing_expected_data.log', owner: 'RelayReaderAliasedFragmentsTestRequiredBubblesOnAbstractWithMissingTypeInfoQuery', diff --git a/packages/relay-runtime/store/__tests__/RelayReader-CatchFields-test.js b/packages/relay-runtime/store/__tests__/RelayReader-CatchFields-test.js index 27a122c7ed50c..c0854716cd0a6 100644 --- a/packages/relay-runtime/store/__tests__/RelayReader-CatchFields-test.js +++ b/packages/relay-runtime/store/__tests__/RelayReader-CatchFields-test.js @@ -297,7 +297,7 @@ describe('RelayReader @catch', () => { expect(errorResponseFields).toEqual([ { - fieldPath: '', + fieldPath: 'me.firstName', kind: 'missing_expected_data.log', owner: 'RelayReaderCatchFieldsTestCatchMissingToNullErrorQuery', }, diff --git a/packages/relay-runtime/store/__tests__/RelayReader-RelayErrorHandling-test.js b/packages/relay-runtime/store/__tests__/RelayReader-RelayErrorHandling-test.js index ad98487637efb..c6431d06b7827 100644 --- a/packages/relay-runtime/store/__tests__/RelayReader-RelayErrorHandling-test.js +++ b/packages/relay-runtime/store/__tests__/RelayReader-RelayErrorHandling-test.js @@ -14,6 +14,7 @@ const {graphql} = require('../../query/GraphQLTag'); const { createOperationDescriptor, } = require('../RelayModernOperationDescriptor'); +const RelayModernStore = require('../RelayModernStore'); const {read} = require('../RelayReader'); const RelayRecordSource = require('../RelayRecordSource'); @@ -117,7 +118,65 @@ describe('RelayReader error fields', () => { }, { owner: 'RelayReaderRelayErrorHandlingTest4Query', - fieldPath: '', + fieldPath: 'me.profilePicture', + kind: 'missing_expected_data.throw', + handled: false, + }, + ]); + }); + + it('adds the errors to errorResponseFields including missingData within plural fields - without @catch', () => { + const source = RelayRecordSource.create({ + 'client:root': { + __id: 'client:root', + __typename: '__Root', + nodes: {__refs: ['1']}, + }, + '1': { + __id: '1', + id: '1', + __typename: 'User', + lastName: null, + __errors: { + lastName: [ + { + message: 'There was an error!', + path: ['me', 'lastName'], + }, + ], + }, + }, + }); + + const FooQuery = graphql` + query RelayReaderRelayErrorHandlingTestMissingPluralQuery($size: [Int]) + @throwOnFieldError { + nodes { + lastName + profilePicture(size: $size) { + uri + } + } + } + `; + const operation = createOperationDescriptor(FooQuery, {size: 42}); + const {errorResponseFields} = read(source, operation.fragment); + + expect(errorResponseFields).toEqual([ + { + owner: 'RelayReaderRelayErrorHandlingTestMissingPluralQuery', + fieldPath: 'me.lastName', + kind: 'relay_field_payload.error', + error: { + message: 'There was an error!', + path: ['me', 'lastName'], + }, + shouldThrow: true, + handled: false, + }, + { + owner: 'RelayReaderRelayErrorHandlingTestMissingPluralQuery', + fieldPath: 'nodes.0.profilePicture', kind: 'missing_expected_data.throw', handled: false, }, @@ -169,7 +228,7 @@ describe('RelayReader error fields', () => { path: ['me', 'lastName'], }, { - path: [''], + path: ['me', 'profilePicture'], }, ], }, @@ -185,7 +244,7 @@ describe('RelayReader error fields', () => { shouldThrow: false, }, { - fieldPath: '', + fieldPath: 'me.profilePicture', kind: 'missing_expected_data.log', owner: 'RelayReaderRelayErrorHandlingTest3Query', }, @@ -292,6 +351,137 @@ describe('RelayReader error fields', () => { }, ]); }); + + test('@throwOnFieldError reading a client edge resolver which points to a record with missing data logs the correct path', () => { + const source = RelayRecordSource.create({ + 'client:root': { + __id: 'client:root', + __typename: '__Root', + me: {__ref: '1'}, + }, + '1': { + __id: '1', + id: '1', + __typename: 'User', + name: 'Proto Mark', // Read by the resolver fragment + }, + '1337': { + __id: '1337', + id: '1337', + __typename: 'User', + // firstName is missing + }, + }); + + const FooQuery = graphql` + query RelayReaderRelayErrorHandlingTestResolverClientEdgeWithMissingDataQuery + @throwOnFieldError { + me { + client_edge @waterfall { + firstName + } + } + } + `; + + const operation = createOperationDescriptor(FooQuery, {size: 42}); + const {errorResponseFields} = read(source, operation.fragment); + + expect(errorResponseFields).toEqual([ + { + fieldPath: 'me.client_edge.firstName', + kind: 'missing_expected_data.throw', + handled: false, + owner: + 'RelayReaderRelayErrorHandlingTestResolverClientEdgeWithMissingDataQuery', + }, + ]); + }); + + test('@throwOnFieldError reading a client edge to client object resolver which points to a record with missing data logs the correct path', () => { + const source = RelayRecordSource.create({ + 'client:root': { + __id: 'client:root', + __typename: '__Root', + me: {__ref: '1'}, + }, + '1': { + __id: '1', + id: '1', + __typename: 'User', + birthdate: {__ref: '2'}, + }, + '2': { + __id: '2', + id: '2', + __typename: 'Date', + day: 6, + month: 1, + year: 1985, + }, + }); + const store = new RelayModernStore(source); + + const FooQuery = graphql` + query RelayReaderRelayErrorHandlingTestResolverClientEdgeClientObjectWithMissingDataQuery + @throwOnFieldError { + me { + astrological_sign { + notes # Not in the store! + } + } + } + `; + + const operation = createOperationDescriptor(FooQuery, {}); + const {errorResponseFields} = store.lookup(operation.fragment); + + expect(errorResponseFields).toEqual([ + { + fieldPath: 'me.astrological_sign.notes', + kind: 'missing_expected_data.throw', + handled: false, + owner: + 'RelayReaderRelayErrorHandlingTestResolverClientEdgeClientObjectWithMissingDataQuery', + }, + ]); + }); + + test('@throwOnFieldError reading a client edge to PLURAL client object resolver which points to records with missing data logs the correct paths with index segments', () => { + const source = RelayRecordSource.create({ + 'client:root': { + __id: 'client:root', + __typename: '__Root', + me: {__ref: '1'}, + }, + '1': { + __id: '1', + id: '1', + __typename: 'User', // Read by the all_astrological_signs resolver fragment + }, + }); + const store = new RelayModernStore(source); + const FooQuery = graphql` + query RelayReaderRelayErrorHandlingTestResolverClientPluralEdgeClientObjectWithMissingDataQuery + @throwOnFieldError { + all_astrological_signs { + notes # Not in the store! + } + } + `; + const operation = createOperationDescriptor(FooQuery, {}); + const {errorResponseFields} = store.lookup(operation.fragment); + for (let i = 0; i < errorResponseFields.length; i++) { + expect(errorResponseFields[i]).toEqual({ + fieldPath: `all_astrological_signs.${i}.notes`, + kind: 'missing_expected_data.throw', + handled: false, + owner: + 'RelayReaderRelayErrorHandlingTestResolverClientPluralEdgeClientObjectWithMissingDataQuery', + }); + } + }); + it('does not report missing data within an inline fragment that does not match', () => { const source = RelayRecordSource.create({ 'client:root': { @@ -371,7 +561,7 @@ describe('RelayReader error fields', () => { expect(errorResponseFields).toEqual([ // We are missing the metadata bout the interface { - fieldPath: '', + fieldPath: 'node.', handled: false, kind: 'missing_expected_data.throw', owner: 'RelayReaderRelayErrorHandlingTestInlineFragmentMatchesQuery', @@ -379,11 +569,70 @@ describe('RelayReader error fields', () => { // We don't know if we should traverse into the inline fragment, but we do // anyway and find that the field is missing { - fieldPath: '', + fieldPath: 'node.name', handled: false, kind: 'missing_expected_data.throw', owner: 'RelayReaderRelayErrorHandlingTestInlineFragmentMatchesQuery', }, ]); }); + + it('Reports missing fields in topological order', () => { + const source = RelayRecordSource.create({ + 'client:root': { + __id: 'client:root', + __typename: '__Root', + me: {__ref: '4'}, + }, + '4': { + __id: '4', + id: '4', + __typename: 'User', + nearest_neighbor: {__ref: '4'}, + // NOTE: `name` is missing + }, + }); + + const FooQuery = graphql` + query RelayReaderRelayErrorHandlingTestErrorOrderQuery + @throwOnFieldError { + also_me: me { + name + nearest_neighbor { + name + } + } + me { + name + } + } + `; + const operation = createOperationDescriptor(FooQuery, {}); + const {errorResponseFields, isMissingData} = read( + source, + operation.fragment, + ); + + expect(isMissingData).toBe(true); + expect(errorResponseFields).toEqual([ + { + fieldPath: 'also_me.name', + handled: false, + kind: 'missing_expected_data.throw', + owner: 'RelayReaderRelayErrorHandlingTestErrorOrderQuery', + }, + { + fieldPath: 'also_me.nearest_neighbor.name', + handled: false, + kind: 'missing_expected_data.throw', + owner: 'RelayReaderRelayErrorHandlingTestErrorOrderQuery', + }, + { + fieldPath: 'me.name', + handled: false, + kind: 'missing_expected_data.throw', + owner: 'RelayReaderRelayErrorHandlingTestErrorOrderQuery', + }, + ]); + }); }); diff --git a/packages/relay-runtime/store/__tests__/__generated__/ClientEdgeQuery_RelayReaderRelayErrorHandlingTestResolverClientEdgeWithMissingDataQuery_me__client_edge.graphql.js b/packages/relay-runtime/store/__tests__/__generated__/ClientEdgeQuery_RelayReaderRelayErrorHandlingTestResolverClientEdgeWithMissingDataQuery_me__client_edge.graphql.js new file mode 100644 index 0000000000000..9bb6dbbc8fb53 --- /dev/null +++ b/packages/relay-runtime/store/__tests__/__generated__/ClientEdgeQuery_RelayReaderRelayErrorHandlingTestResolverClientEdgeWithMissingDataQuery_me__client_edge.graphql.js @@ -0,0 +1,143 @@ +/** + * 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 + * @nogrep + */ + +/* eslint-disable */ + +'use strict'; + +/*:: +import type { ConcreteRequest, Query } from 'relay-runtime'; +import type { RefetchableClientEdgeQuery_RelayReaderRelayErrorHandlingTestResolverClientEdgeWithMissingDataQuery_me__client_edge$fragmentType } from "./RefetchableClientEdgeQuery_RelayReaderRelayErrorHandlingTestResolverClientEdgeWithMissingDataQuery_me__client_edge.graphql"; +export type ClientEdgeQuery_RelayReaderRelayErrorHandlingTestResolverClientEdgeWithMissingDataQuery_me__client_edge$variables = {| + id: string, +|}; +export type ClientEdgeQuery_RelayReaderRelayErrorHandlingTestResolverClientEdgeWithMissingDataQuery_me__client_edge$data = {| + +node: ?{| + +$fragmentSpreads: RefetchableClientEdgeQuery_RelayReaderRelayErrorHandlingTestResolverClientEdgeWithMissingDataQuery_me__client_edge$fragmentType, + |}, +|}; +export type ClientEdgeQuery_RelayReaderRelayErrorHandlingTestResolverClientEdgeWithMissingDataQuery_me__client_edge = {| + response: ClientEdgeQuery_RelayReaderRelayErrorHandlingTestResolverClientEdgeWithMissingDataQuery_me__client_edge$data, + variables: ClientEdgeQuery_RelayReaderRelayErrorHandlingTestResolverClientEdgeWithMissingDataQuery_me__client_edge$variables, +|}; +*/ + +var node/*: ConcreteRequest*/ = (function(){ +var v0 = [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "id" + } +], +v1 = [ + { + "kind": "Variable", + "name": "id", + "variableName": "id" + } +]; +return { + "fragment": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Fragment", + "metadata": null, + "name": "ClientEdgeQuery_RelayReaderRelayErrorHandlingTestResolverClientEdgeWithMissingDataQuery_me__client_edge", + "selections": [ + { + "alias": null, + "args": (v1/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + { + "args": null, + "kind": "FragmentSpread", + "name": "RefetchableClientEdgeQuery_RelayReaderRelayErrorHandlingTestResolverClientEdgeWithMissingDataQuery_me__client_edge" + } + ], + "storageKey": null + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Operation", + "name": "ClientEdgeQuery_RelayReaderRelayErrorHandlingTestResolverClientEdgeWithMissingDataQuery_me__client_edge", + "selections": [ + { + "alias": null, + "args": (v1/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__typename", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null + }, + { + "kind": "InlineFragment", + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "firstName", + "storageKey": null + } + ], + "type": "User", + "abstractKey": null + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "e47549d74855f68f97e2b686ad601ea7", + "id": null, + "metadata": {}, + "name": "ClientEdgeQuery_RelayReaderRelayErrorHandlingTestResolverClientEdgeWithMissingDataQuery_me__client_edge", + "operationKind": "query", + "text": "query ClientEdgeQuery_RelayReaderRelayErrorHandlingTestResolverClientEdgeWithMissingDataQuery_me__client_edge(\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...RefetchableClientEdgeQuery_RelayReaderRelayErrorHandlingTestResolverClientEdgeWithMissingDataQuery_me__client_edge\n id\n }\n}\n\nfragment RefetchableClientEdgeQuery_RelayReaderRelayErrorHandlingTestResolverClientEdgeWithMissingDataQuery_me__client_edge on User {\n firstName\n id\n}\n" + } +}; +})(); + +if (__DEV__) { + (node/*: any*/).hash = "d3fd8c798ab7357a229e7b8e79799744"; +} + +module.exports = ((node/*: any*/)/*: Query< + ClientEdgeQuery_RelayReaderRelayErrorHandlingTestResolverClientEdgeWithMissingDataQuery_me__client_edge$variables, + ClientEdgeQuery_RelayReaderRelayErrorHandlingTestResolverClientEdgeWithMissingDataQuery_me__client_edge$data, +>*/); diff --git a/packages/relay-runtime/store/__tests__/__generated__/RefetchableClientEdgeQuery_RelayReaderRelayErrorHandlingTestResolverClientEdgeWithMissingDataQuery_me__client_edge.graphql.js b/packages/relay-runtime/store/__tests__/__generated__/RefetchableClientEdgeQuery_RelayReaderRelayErrorHandlingTestResolverClientEdgeWithMissingDataQuery_me__client_edge.graphql.js new file mode 100644 index 0000000000000..47aa56f3a4d24 --- /dev/null +++ b/packages/relay-runtime/store/__tests__/__generated__/RefetchableClientEdgeQuery_RelayReaderRelayErrorHandlingTestResolverClientEdgeWithMissingDataQuery_me__client_edge.graphql.js @@ -0,0 +1,81 @@ +/** + * 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 + * @nogrep + */ + +/* eslint-disable */ + +'use strict'; + +/*:: +import type { ReaderFragment, RefetchableFragment } from 'relay-runtime'; +import type { FragmentType } from "relay-runtime"; +declare export opaque type RefetchableClientEdgeQuery_RelayReaderRelayErrorHandlingTestResolverClientEdgeWithMissingDataQuery_me__client_edge$fragmentType: FragmentType; +type ClientEdgeQuery_RelayReaderRelayErrorHandlingTestResolverClientEdgeWithMissingDataQuery_me__client_edge$variables = any; +export type RefetchableClientEdgeQuery_RelayReaderRelayErrorHandlingTestResolverClientEdgeWithMissingDataQuery_me__client_edge$data = {| + +firstName: ?string, + +id: string, + +$fragmentType: RefetchableClientEdgeQuery_RelayReaderRelayErrorHandlingTestResolverClientEdgeWithMissingDataQuery_me__client_edge$fragmentType, +|}; +export type RefetchableClientEdgeQuery_RelayReaderRelayErrorHandlingTestResolverClientEdgeWithMissingDataQuery_me__client_edge$key = { + +$data?: RefetchableClientEdgeQuery_RelayReaderRelayErrorHandlingTestResolverClientEdgeWithMissingDataQuery_me__client_edge$data, + +$fragmentSpreads: RefetchableClientEdgeQuery_RelayReaderRelayErrorHandlingTestResolverClientEdgeWithMissingDataQuery_me__client_edge$fragmentType, + ... +}; +*/ + +var node/*: ReaderFragment*/ = { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": { + "refetch": { + "connection": null, + "fragmentPathInResult": [ + "node" + ], + "operation": require('./ClientEdgeQuery_RelayReaderRelayErrorHandlingTestResolverClientEdgeWithMissingDataQuery_me__client_edge.graphql'), + "identifierInfo": { + "identifierField": "id", + "identifierQueryVariableName": "id" + } + } + }, + "name": "RefetchableClientEdgeQuery_RelayReaderRelayErrorHandlingTestResolverClientEdgeWithMissingDataQuery_me__client_edge", + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "firstName", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null + } + ], + "type": "User", + "abstractKey": null +}; + +if (__DEV__) { + (node/*: any*/).hash = "d3fd8c798ab7357a229e7b8e79799744"; +} + +module.exports = ((node/*: any*/)/*: RefetchableFragment< + RefetchableClientEdgeQuery_RelayReaderRelayErrorHandlingTestResolverClientEdgeWithMissingDataQuery_me__client_edge$fragmentType, + RefetchableClientEdgeQuery_RelayReaderRelayErrorHandlingTestResolverClientEdgeWithMissingDataQuery_me__client_edge$data, + ClientEdgeQuery_RelayReaderRelayErrorHandlingTestResolverClientEdgeWithMissingDataQuery_me__client_edge$variables, +>*/); diff --git a/packages/relay-runtime/store/__tests__/__generated__/RelayReaderRelayErrorHandlingTestErrorOrderQuery.graphql.js b/packages/relay-runtime/store/__tests__/__generated__/RelayReaderRelayErrorHandlingTestErrorOrderQuery.graphql.js new file mode 100644 index 0000000000000..c4ac0670c86c4 --- /dev/null +++ b/packages/relay-runtime/store/__tests__/__generated__/RelayReaderRelayErrorHandlingTestErrorOrderQuery.graphql.js @@ -0,0 +1,165 @@ +/** + * 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<<2e1d56f339c3fc473fcf40d268b97df2>> + * @flow + * @lightSyntaxTransform + * @nogrep + */ + +/* eslint-disable */ + +'use strict'; + +/*:: +import type { ConcreteRequest, Query } from 'relay-runtime'; +export type RelayReaderRelayErrorHandlingTestErrorOrderQuery$variables = {||}; +export type RelayReaderRelayErrorHandlingTestErrorOrderQuery$data = {| + +also_me: ?{| + +name: ?string, + +nearest_neighbor: {| + +name: ?string, + |}, + |}, + +me: ?{| + +name: ?string, + |}, +|}; +export type RelayReaderRelayErrorHandlingTestErrorOrderQuery = {| + response: RelayReaderRelayErrorHandlingTestErrorOrderQuery$data, + variables: RelayReaderRelayErrorHandlingTestErrorOrderQuery$variables, +|}; +*/ + +var node/*: ConcreteRequest*/ = (function(){ +var v0 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null +}, +v1 = [ + (v0/*: any*/) +], +v2 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}, +v3 = [ + (v0/*: any*/), + (v2/*: any*/) +]; +return { + "fragment": { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": { + "throwOnFieldError": true + }, + "name": "RelayReaderRelayErrorHandlingTestErrorOrderQuery", + "selections": [ + { + "alias": "also_me", + "args": null, + "concreteType": "User", + "kind": "LinkedField", + "name": "me", + "plural": false, + "selections": [ + (v0/*: any*/), + { + "alias": null, + "args": null, + "concreteType": "User", + "kind": "LinkedField", + "name": "nearest_neighbor", + "plural": false, + "selections": (v1/*: any*/), + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "User", + "kind": "LinkedField", + "name": "me", + "plural": false, + "selections": (v1/*: any*/), + "storageKey": null + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [], + "kind": "Operation", + "name": "RelayReaderRelayErrorHandlingTestErrorOrderQuery", + "selections": [ + { + "alias": "also_me", + "args": null, + "concreteType": "User", + "kind": "LinkedField", + "name": "me", + "plural": false, + "selections": [ + (v0/*: any*/), + { + "alias": null, + "args": null, + "concreteType": "User", + "kind": "LinkedField", + "name": "nearest_neighbor", + "plural": false, + "selections": (v3/*: any*/), + "storageKey": null + }, + (v2/*: any*/) + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "User", + "kind": "LinkedField", + "name": "me", + "plural": false, + "selections": (v3/*: any*/), + "storageKey": null + } + ] + }, + "params": { + "cacheID": "0ca4693a410c65d12f516d013dc142c2", + "id": null, + "metadata": {}, + "name": "RelayReaderRelayErrorHandlingTestErrorOrderQuery", + "operationKind": "query", + "text": "query RelayReaderRelayErrorHandlingTestErrorOrderQuery {\n also_me: me {\n name\n nearest_neighbor {\n name\n id\n }\n id\n }\n me {\n name\n id\n }\n}\n" + } +}; +})(); + +if (__DEV__) { + (node/*: any*/).hash = "d5a7d5606148861a51f3e0ff315a68b3"; +} + +module.exports = ((node/*: any*/)/*: Query< + RelayReaderRelayErrorHandlingTestErrorOrderQuery$variables, + RelayReaderRelayErrorHandlingTestErrorOrderQuery$data, +>*/); diff --git a/packages/relay-runtime/store/__tests__/__generated__/RelayReaderRelayErrorHandlingTestMissingPluralQuery.graphql.js b/packages/relay-runtime/store/__tests__/__generated__/RelayReaderRelayErrorHandlingTestMissingPluralQuery.graphql.js new file mode 100644 index 0000000000000..fa60038c81cbd --- /dev/null +++ b/packages/relay-runtime/store/__tests__/__generated__/RelayReaderRelayErrorHandlingTestMissingPluralQuery.graphql.js @@ -0,0 +1,156 @@ +/** + * 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<<0952e46c01e925f5f9d6d46a37b10f66>> + * @flow + * @lightSyntaxTransform + * @nogrep + */ + +/* eslint-disable */ + +'use strict'; + +/*:: +import type { ConcreteRequest, Query } from 'relay-runtime'; +export type RelayReaderRelayErrorHandlingTestMissingPluralQuery$variables = {| + size?: ?$ReadOnlyArray, +|}; +export type RelayReaderRelayErrorHandlingTestMissingPluralQuery$data = {| + +nodes: ?$ReadOnlyArray, +|}; +export type RelayReaderRelayErrorHandlingTestMissingPluralQuery = {| + response: RelayReaderRelayErrorHandlingTestMissingPluralQuery$data, + variables: RelayReaderRelayErrorHandlingTestMissingPluralQuery$variables, +|}; +*/ + +var node/*: ConcreteRequest*/ = (function(){ +var v0 = [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "size" + } +], +v1 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "lastName", + "storageKey": null +}, +v2 = { + "alias": null, + "args": [ + { + "kind": "Variable", + "name": "size", + "variableName": "size" + } + ], + "concreteType": "Image", + "kind": "LinkedField", + "name": "profilePicture", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "uri", + "storageKey": null + } + ], + "storageKey": null +}; +return { + "fragment": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Fragment", + "metadata": { + "throwOnFieldError": true + }, + "name": "RelayReaderRelayErrorHandlingTestMissingPluralQuery", + "selections": [ + { + "alias": null, + "args": null, + "concreteType": null, + "kind": "LinkedField", + "name": "nodes", + "plural": true, + "selections": [ + (v1/*: any*/), + (v2/*: any*/) + ], + "storageKey": null + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Operation", + "name": "RelayReaderRelayErrorHandlingTestMissingPluralQuery", + "selections": [ + { + "alias": null, + "args": null, + "concreteType": null, + "kind": "LinkedField", + "name": "nodes", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__typename", + "storageKey": null + }, + (v1/*: any*/), + (v2/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "17bef05ba3a737b778497587e3c4bb6c", + "id": null, + "metadata": {}, + "name": "RelayReaderRelayErrorHandlingTestMissingPluralQuery", + "operationKind": "query", + "text": "query RelayReaderRelayErrorHandlingTestMissingPluralQuery(\n $size: [Int]\n) {\n nodes {\n __typename\n lastName\n profilePicture(size: $size) {\n uri\n }\n id\n }\n}\n" + } +}; +})(); + +if (__DEV__) { + (node/*: any*/).hash = "9e91ce9da23ac341e9d3815c9f9cf2f2"; +} + +module.exports = ((node/*: any*/)/*: Query< + RelayReaderRelayErrorHandlingTestMissingPluralQuery$variables, + RelayReaderRelayErrorHandlingTestMissingPluralQuery$data, +>*/); diff --git a/packages/relay-runtime/store/__tests__/__generated__/RelayReaderRelayErrorHandlingTestResolverClientEdgeClientObjectWithMissingDataQuery.graphql.js b/packages/relay-runtime/store/__tests__/__generated__/RelayReaderRelayErrorHandlingTestResolverClientEdgeClientObjectWithMissingDataQuery.graphql.js new file mode 100644 index 0000000000000..9fc8f1701e5d6 --- /dev/null +++ b/packages/relay-runtime/store/__tests__/__generated__/RelayReaderRelayErrorHandlingTestResolverClientEdgeClientObjectWithMissingDataQuery.graphql.js @@ -0,0 +1,211 @@ +/** + * 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 + * @nogrep + */ + +/* eslint-disable */ + +'use strict'; + +/*:: +import type { ConcreteRequest, Query } from 'relay-runtime'; +import type { DataID } from "relay-runtime"; +import type { UserAstrologicalSignResolver$key } from "./../resolvers/__generated__/UserAstrologicalSignResolver.graphql"; +import {astrological_sign as userAstrologicalSignResolverType} from "../resolvers/UserAstrologicalSignResolver.js"; +import type { TestResolverContextType } from "../../../mutations/__tests__/TestResolverContextType"; +// Type assertion validating that `userAstrologicalSignResolverType` resolver is correctly implemented. +// A type error here indicates that the type signature of the resolver module is incorrect. +(userAstrologicalSignResolverType: ( + rootKey: UserAstrologicalSignResolver$key, + args: void, + context: TestResolverContextType, +) => ?{| + +id: DataID, +|}); +export type RelayReaderRelayErrorHandlingTestResolverClientEdgeClientObjectWithMissingDataQuery$variables = {||}; +export type RelayReaderRelayErrorHandlingTestResolverClientEdgeClientObjectWithMissingDataQuery$data = {| + +me: ?{| + +astrological_sign: ?{| + +notes: ?string, + |}, + |}, +|}; +export type RelayReaderRelayErrorHandlingTestResolverClientEdgeClientObjectWithMissingDataQuery = {| + response: RelayReaderRelayErrorHandlingTestResolverClientEdgeClientObjectWithMissingDataQuery$data, + variables: RelayReaderRelayErrorHandlingTestResolverClientEdgeClientObjectWithMissingDataQuery$variables, +|}; +*/ + +var node/*: ConcreteRequest*/ = (function(){ +var v0 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "notes", + "storageKey": null +}, +v1 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}; +return { + "fragment": { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": { + "hasClientEdges": true, + "throwOnFieldError": true + }, + "name": "RelayReaderRelayErrorHandlingTestResolverClientEdgeClientObjectWithMissingDataQuery", + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "User", + "kind": "LinkedField", + "name": "me", + "plural": false, + "selections": [ + { + "kind": "ClientEdgeToClientObject", + "concreteType": "AstrologicalSign", + "modelResolvers": null, + "backingField": { + "alias": null, + "args": null, + "fragment": { + "args": null, + "kind": "FragmentSpread", + "name": "UserAstrologicalSignResolver" + }, + "kind": "RelayResolver", + "name": "astrological_sign", + "resolverModule": require('./../resolvers/UserAstrologicalSignResolver').astrological_sign, + "path": "me.astrological_sign" + }, + "linkedField": { + "alias": null, + "args": null, + "concreteType": "AstrologicalSign", + "kind": "LinkedField", + "name": "astrological_sign", + "plural": false, + "selections": [ + (v0/*: any*/) + ], + "storageKey": null + } + } + ], + "storageKey": null + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [], + "kind": "Operation", + "name": "RelayReaderRelayErrorHandlingTestResolverClientEdgeClientObjectWithMissingDataQuery", + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "User", + "kind": "LinkedField", + "name": "me", + "plural": false, + "selections": [ + { + "kind": "ClientEdgeToClientObject", + "backingField": { + "name": "astrological_sign", + "args": null, + "fragment": { + "kind": "InlineFragment", + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Date", + "kind": "LinkedField", + "name": "birthdate", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "month", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "day", + "storageKey": null + } + ], + "storageKey": null + } + ], + "type": "User", + "abstractKey": null + }, + "kind": "RelayResolver", + "storageKey": null, + "isOutputType": false + }, + "linkedField": { + "alias": null, + "args": null, + "concreteType": "AstrologicalSign", + "kind": "LinkedField", + "name": "astrological_sign", + "plural": false, + "selections": [ + (v0/*: any*/), + (v1/*: any*/) + ], + "storageKey": null + } + }, + (v1/*: any*/) + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "4e9dfd987ed85231c755f6c748db249d", + "id": null, + "metadata": {}, + "name": "RelayReaderRelayErrorHandlingTestResolverClientEdgeClientObjectWithMissingDataQuery", + "operationKind": "query", + "text": "query RelayReaderRelayErrorHandlingTestResolverClientEdgeClientObjectWithMissingDataQuery {\n me {\n ...UserAstrologicalSignResolver\n id\n }\n}\n\nfragment UserAstrologicalSignResolver on User {\n birthdate {\n month\n day\n }\n}\n" + } +}; +})(); + +if (__DEV__) { + (node/*: any*/).hash = "f58793dca8d722401223f2d241a24705"; +} + +module.exports = ((node/*: any*/)/*: Query< + RelayReaderRelayErrorHandlingTestResolverClientEdgeClientObjectWithMissingDataQuery$variables, + RelayReaderRelayErrorHandlingTestResolverClientEdgeClientObjectWithMissingDataQuery$data, +>*/); diff --git a/packages/relay-runtime/store/__tests__/__generated__/RelayReaderRelayErrorHandlingTestResolverClientEdgeWithMissingDataQuery.graphql.js b/packages/relay-runtime/store/__tests__/__generated__/RelayReaderRelayErrorHandlingTestResolverClientEdgeWithMissingDataQuery.graphql.js new file mode 100644 index 0000000000000..2a0ea0068246b --- /dev/null +++ b/packages/relay-runtime/store/__tests__/__generated__/RelayReaderRelayErrorHandlingTestResolverClientEdgeWithMissingDataQuery.graphql.js @@ -0,0 +1,172 @@ +/** + * 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<<78b08a8df8f4ca89e6a949e9e8ff4453>> + * @flow + * @lightSyntaxTransform + * @nogrep + */ + +/* eslint-disable */ + +'use strict'; + +/*:: +import type { ConcreteRequest, Query } from 'relay-runtime'; +import type { DataID } from "relay-runtime"; +import type { UserClientEdgeResolver$key } from "./../resolvers/__generated__/UserClientEdgeResolver.graphql"; +import {client_edge as userClientEdgeResolverType} from "../resolvers/UserClientEdgeResolver.js"; +import type { TestResolverContextType } from "../../../mutations/__tests__/TestResolverContextType"; +// Type assertion validating that `userClientEdgeResolverType` resolver is correctly implemented. +// A type error here indicates that the type signature of the resolver module is incorrect. +(userClientEdgeResolverType: ( + rootKey: UserClientEdgeResolver$key, + args: void, + context: TestResolverContextType, +) => ?{| + +id: DataID, +|}); +export type RelayReaderRelayErrorHandlingTestResolverClientEdgeWithMissingDataQuery$variables = {||}; +export type RelayReaderRelayErrorHandlingTestResolverClientEdgeWithMissingDataQuery$data = {| + +me: ?{| + +client_edge: ?{| + +firstName: ?string, + |}, + |}, +|}; +export type RelayReaderRelayErrorHandlingTestResolverClientEdgeWithMissingDataQuery = {| + response: RelayReaderRelayErrorHandlingTestResolverClientEdgeWithMissingDataQuery$data, + variables: RelayReaderRelayErrorHandlingTestResolverClientEdgeWithMissingDataQuery$variables, +|}; +*/ + +var node/*: ConcreteRequest*/ = { + "fragment": { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": { + "hasClientEdges": true, + "throwOnFieldError": true + }, + "name": "RelayReaderRelayErrorHandlingTestResolverClientEdgeWithMissingDataQuery", + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "User", + "kind": "LinkedField", + "name": "me", + "plural": false, + "selections": [ + { + "kind": "ClientEdgeToServerObject", + "operation": require('./ClientEdgeQuery_RelayReaderRelayErrorHandlingTestResolverClientEdgeWithMissingDataQuery_me__client_edge.graphql'), + "backingField": { + "alias": null, + "args": null, + "fragment": { + "args": null, + "kind": "FragmentSpread", + "name": "UserClientEdgeResolver" + }, + "kind": "RelayResolver", + "name": "client_edge", + "resolverModule": require('./../resolvers/UserClientEdgeResolver').client_edge, + "path": "me.client_edge" + }, + "linkedField": { + "alias": null, + "args": null, + "concreteType": "User", + "kind": "LinkedField", + "name": "client_edge", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "firstName", + "storageKey": null + } + ], + "storageKey": null + } + } + ], + "storageKey": null + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [], + "kind": "Operation", + "name": "RelayReaderRelayErrorHandlingTestResolverClientEdgeWithMissingDataQuery", + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "User", + "kind": "LinkedField", + "name": "me", + "plural": false, + "selections": [ + { + "name": "client_edge", + "args": null, + "fragment": { + "kind": "InlineFragment", + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null + } + ], + "type": "User", + "abstractKey": null + }, + "kind": "RelayResolver", + "storageKey": null, + "isOutputType": false + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "68091edecbac75c5fae7fbf31dc6ff71", + "id": null, + "metadata": {}, + "name": "RelayReaderRelayErrorHandlingTestResolverClientEdgeWithMissingDataQuery", + "operationKind": "query", + "text": "query RelayReaderRelayErrorHandlingTestResolverClientEdgeWithMissingDataQuery {\n me {\n ...UserClientEdgeResolver\n id\n }\n}\n\nfragment UserClientEdgeResolver on User {\n name\n}\n" + } +}; + +if (__DEV__) { + (node/*: any*/).hash = "d3fd8c798ab7357a229e7b8e79799744"; +} + +module.exports = ((node/*: any*/)/*: Query< + RelayReaderRelayErrorHandlingTestResolverClientEdgeWithMissingDataQuery$variables, + RelayReaderRelayErrorHandlingTestResolverClientEdgeWithMissingDataQuery$data, +>*/); diff --git a/packages/relay-runtime/store/__tests__/__generated__/RelayReaderRelayErrorHandlingTestResolverClientPluralEdgeClientObjectWithMissingDataQuery.graphql.js b/packages/relay-runtime/store/__tests__/__generated__/RelayReaderRelayErrorHandlingTestResolverClientPluralEdgeClientObjectWithMissingDataQuery.graphql.js new file mode 100644 index 0000000000000..cdf1f359f9466 --- /dev/null +++ b/packages/relay-runtime/store/__tests__/__generated__/RelayReaderRelayErrorHandlingTestResolverClientPluralEdgeClientObjectWithMissingDataQuery.graphql.js @@ -0,0 +1,180 @@ +/** + * 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 + * @nogrep + */ + +/* eslint-disable */ + +'use strict'; + +/*:: +import type { ConcreteRequest, Query } from 'relay-runtime'; +import type { DataID } from "relay-runtime"; +import type { QueryAllAstrologicalSignsResolver$key } from "./../resolvers/__generated__/QueryAllAstrologicalSignsResolver.graphql"; +import {all_astrological_signs as queryAllAstrologicalSignsResolverType} from "../resolvers/QueryAllAstrologicalSignsResolver.js"; +import type { TestResolverContextType } from "../../../mutations/__tests__/TestResolverContextType"; +// Type assertion validating that `queryAllAstrologicalSignsResolverType` resolver is correctly implemented. +// A type error here indicates that the type signature of the resolver module is incorrect. +(queryAllAstrologicalSignsResolverType: ( + rootKey: QueryAllAstrologicalSignsResolver$key, + args: void, + context: TestResolverContextType, +) => ?$ReadOnlyArray<{| + +id: DataID, +|}>); +export type RelayReaderRelayErrorHandlingTestResolverClientPluralEdgeClientObjectWithMissingDataQuery$variables = {||}; +export type RelayReaderRelayErrorHandlingTestResolverClientPluralEdgeClientObjectWithMissingDataQuery$data = {| + +all_astrological_signs: ?$ReadOnlyArray<{| + +notes: ?string, + |}>, +|}; +export type RelayReaderRelayErrorHandlingTestResolverClientPluralEdgeClientObjectWithMissingDataQuery = {| + response: RelayReaderRelayErrorHandlingTestResolverClientPluralEdgeClientObjectWithMissingDataQuery$data, + variables: RelayReaderRelayErrorHandlingTestResolverClientPluralEdgeClientObjectWithMissingDataQuery$variables, +|}; +*/ + +var node/*: ConcreteRequest*/ = (function(){ +var v0 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "notes", + "storageKey": null +}, +v1 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}; +return { + "fragment": { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": { + "hasClientEdges": true, + "throwOnFieldError": true + }, + "name": "RelayReaderRelayErrorHandlingTestResolverClientPluralEdgeClientObjectWithMissingDataQuery", + "selections": [ + { + "kind": "ClientEdgeToClientObject", + "concreteType": "AstrologicalSign", + "modelResolvers": null, + "backingField": { + "alias": null, + "args": null, + "fragment": { + "args": null, + "kind": "FragmentSpread", + "name": "QueryAllAstrologicalSignsResolver" + }, + "kind": "RelayResolver", + "name": "all_astrological_signs", + "resolverModule": require('./../resolvers/QueryAllAstrologicalSignsResolver').all_astrological_signs, + "path": "all_astrological_signs" + }, + "linkedField": { + "alias": null, + "args": null, + "concreteType": "AstrologicalSign", + "kind": "LinkedField", + "name": "all_astrological_signs", + "plural": true, + "selections": [ + (v0/*: any*/) + ], + "storageKey": null + } + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [], + "kind": "Operation", + "name": "RelayReaderRelayErrorHandlingTestResolverClientPluralEdgeClientObjectWithMissingDataQuery", + "selections": [ + { + "kind": "ClientEdgeToClientObject", + "backingField": { + "name": "all_astrological_signs", + "args": null, + "fragment": { + "kind": "InlineFragment", + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "User", + "kind": "LinkedField", + "name": "me", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__typename", + "storageKey": null + }, + (v1/*: any*/) + ], + "storageKey": null + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "RelayResolver", + "storageKey": null, + "isOutputType": false + }, + "linkedField": { + "alias": null, + "args": null, + "concreteType": "AstrologicalSign", + "kind": "LinkedField", + "name": "all_astrological_signs", + "plural": true, + "selections": [ + (v0/*: any*/), + (v1/*: any*/) + ], + "storageKey": null + } + } + ] + }, + "params": { + "cacheID": "815da3a9301c0c6b86eec7dcbe838001", + "id": null, + "metadata": {}, + "name": "RelayReaderRelayErrorHandlingTestResolverClientPluralEdgeClientObjectWithMissingDataQuery", + "operationKind": "query", + "text": "query RelayReaderRelayErrorHandlingTestResolverClientPluralEdgeClientObjectWithMissingDataQuery {\n ...QueryAllAstrologicalSignsResolver\n}\n\nfragment QueryAllAstrologicalSignsResolver on Query {\n me {\n __typename\n id\n }\n}\n" + } +}; +})(); + +if (__DEV__) { + (node/*: any*/).hash = "301d67aac4a492fec857e800bb8ce687"; +} + +module.exports = ((node/*: any*/)/*: Query< + RelayReaderRelayErrorHandlingTestResolverClientPluralEdgeClientObjectWithMissingDataQuery$variables, + RelayReaderRelayErrorHandlingTestResolverClientPluralEdgeClientObjectWithMissingDataQuery$data, +>*/); diff --git a/packages/relay-runtime/store/__tests__/resolvers/ResolverGC-test.js b/packages/relay-runtime/store/__tests__/resolvers/ResolverGC-test.js index 2e4589ff12f2a..d77946a896124 100644 --- a/packages/relay-runtime/store/__tests__/resolvers/ResolverGC-test.js +++ b/packages/relay-runtime/store/__tests__/resolvers/ResolverGC-test.js @@ -175,7 +175,7 @@ test('Regular resolver with fragment reads live resovler with fragment', async ( expect(snapshot.data).toEqual({counter_plus_one: null}); expect(snapshot.errorResponseFields).toEqual([ { - fieldPath: '', + fieldPath: 'me.', kind: 'missing_expected_data.log', owner: 'LiveCounterResolver', }, @@ -730,7 +730,7 @@ test('Resolver reading a plural client-edge to a client type', async () => { expect(snapshot.data).toEqual({all_astrological_signs: null}); expect(snapshot.errorResponseFields).toEqual([ { - fieldPath: '', + fieldPath: 'me.', kind: 'missing_expected_data.log', owner: 'QueryAllAstrologicalSignsResolver', }, diff --git a/packages/relay-runtime/store/__tests__/waitForFragmentData-test.js b/packages/relay-runtime/store/__tests__/waitForFragmentData-test.js index 534500cf4668c..ec64390dd1a92 100644 --- a/packages/relay-runtime/store/__tests__/waitForFragmentData-test.js +++ b/packages/relay-runtime/store/__tests__/waitForFragmentData-test.js @@ -184,6 +184,6 @@ test('data goes missing due to unrelated query response (@throwOnFieldErrro)', a result = e; } expect(result?.message).toEqual( - "Relay: Missing expected data at path '' in 'waitForFragmentDataTestMissingDataThrowOnFieldErrorFragment'.", + "Relay: Missing expected data at path 'me.name' in 'waitForFragmentDataTestMissingDataThrowOnFieldErrorFragment'.", ); }); diff --git a/website/docs/api-reference/relay-runtime/field-logger.md b/website/docs/api-reference/relay-runtime/field-logger.md index da02642828771..87f0f9d68e284 100644 --- a/website/docs/api-reference/relay-runtime/field-logger.md +++ b/website/docs/api-reference/relay-runtime/field-logger.md @@ -88,6 +88,11 @@ export type MissingExpectedDataThrowEvent = { }; ``` +The `fieldPath` property is a `.` separated string containing the path from the query/mutation/fragment root to the field which was missing. Note that there are some cases where we are missing data that is not strictly a field. In those cases the path will end with one of these segments: + +1. `path.to.` An edge points to an ID that is not present in the Relay store +2. `path.to.` A virtual field inserted by the Relay compiler to detect if a type condition holds is missing from the Relay store + ## Missing Required Field Log A field was marked as [@required(action: LOG)](../../guides/required-directive.md#action) but was null or missing in the store.