From bb50fdf624cf43b326509f017107548175cb32fb Mon Sep 17 00:00:00 2001 From: Wenting Hu Date: Tue, 12 Sep 2023 15:13:59 -0700 Subject: [PATCH] Support Graph Mode GraphQL response data processing Reviewed By: voideanvalue Differential Revision: D48698421 fbshipit-source-id: 6259e5468e9d14b856f2881294ba5f07f4144811 --- .../MultiActorEnvironment.js | 2 + .../relay-runtime/store/OperationExecutor.js | 38 ++++----------- .../store/RelayModernEnvironment.js | 6 +++ .../relay-runtime/store/RelayStoreTypes.js | 9 ++++ .../relay-runtime/store/normalizeResponse.js | 48 +++++++++++++++++++ 5 files changed, 75 insertions(+), 28 deletions(-) create mode 100644 packages/relay-runtime/store/normalizeResponse.js diff --git a/packages/relay-runtime/multi-actor-environment/MultiActorEnvironment.js b/packages/relay-runtime/multi-actor-environment/MultiActorEnvironment.js index ad1d7fbdd5f16..c1ce527aea04e 100644 --- a/packages/relay-runtime/multi-actor-environment/MultiActorEnvironment.js +++ b/packages/relay-runtime/multi-actor-environment/MultiActorEnvironment.js @@ -48,6 +48,7 @@ const RelayDefaultHandlerProvider = require('../handlers/RelayDefaultHandlerProv const RelayObservable = require('../network/RelayObservable'); const defaultGetDataID = require('../store/defaultGetDataID'); const defaultRequiredFieldLogger = require('../store/defaultRequiredFieldLogger'); +const normalizeResponse = require('../store/normalizeResponse'); const OperationExecutor = require('../store/OperationExecutor'); const RelayModernStore = require('../store/RelayModernStore'); const RelayRecordSource = require('../store/RelayRecordSource'); @@ -469,6 +470,7 @@ class MultiActorEnvironment implements IMultiActorEnvironment { treatMissingFieldsAsNull: this._treatMissingFieldsAsNull, updater, log: this._logFn, + normalizeResponse: normalizeResponse, }); return () => executor.cancel(); }); diff --git a/packages/relay-runtime/store/OperationExecutor.js b/packages/relay-runtime/store/OperationExecutor.js index 0e232c176ef66..7bfde8f6be1b2 100644 --- a/packages/relay-runtime/store/OperationExecutor.js +++ b/packages/relay-runtime/store/OperationExecutor.js @@ -51,6 +51,7 @@ import type { import type {DataID, Disposable, Variables} from '../util/RelayRuntimeTypes'; import type {GetDataID} from './RelayResponseNormalizer'; import type {NormalizationOptions} from './RelayResponseNormalizer'; +import type {NormalizeResponseFunction} from './RelayStoreTypes'; const RelayObservable = require('../network/RelayObservable'); const generateID = require('../util/generateID'); @@ -77,6 +78,7 @@ export type ExecuteConfig = { +getDataID: GetDataID, +getPublishQueue: (actorIdentifier: ActorIdentifier) => PublishQueue, +getStore: (actorIdentifier: ActorIdentifier) => Store, + +normalizeResponse: NormalizeResponseFunction, +isClientPayload?: boolean, +operation: OperationDescriptor, +operationExecutions: Map, @@ -157,6 +159,7 @@ class Executor { +_isClientPayload: boolean; +_isSubscriptionOperation: boolean; +_seenActors: Set; + _normalizeResponse: NormalizeResponseFunction; constructor({ actorIdentifier, @@ -176,6 +179,7 @@ class Executor { treatMissingFieldsAsNull, updater, log, + normalizeResponse, }: ExecuteConfig): void { this._actorIdentifier = actorIdentifier; this._getDataID = getDataID; @@ -207,6 +211,7 @@ class Executor { this._retainDisposables = new Map(); this._seenActors = new Set(); this._completeFns = []; + this._normalizeResponse = normalizeResponse; const id = this._nextSubscriptionId++; source.subscribe({ @@ -575,7 +580,7 @@ class Executor { } const optimisticUpdates: Array> = []; if (response) { - const payload = normalizeResponse( + const payload = this._normalizeResponse( response, this._operation.root, ROOT_TYPE, @@ -684,7 +689,7 @@ class Executor { followupPayload.dataID, variables, ); - return normalizeResponse( + return this._normalizeResponse( {data: followupPayload.data}, selector, followupPayload.typeName, @@ -762,7 +767,7 @@ class Executor { this._incrementalResults.clear(); this._source.clear(); return responses.map(payloadPart => { - const relayPayload = normalizeResponse( + const relayPayload = this._normalizeResponse( payloadPart, this._operation.root, ROOT_TYPE, @@ -1215,7 +1220,7 @@ class Executor { const prevActorIdentifier = this._actorIdentifier; this._actorIdentifier = placeholder.actorIdentifier ?? this._actorIdentifier; - const relayPayload = normalizeResponse( + const relayPayload = this._normalizeResponse( response, placeholder.selector, placeholder.typeName, @@ -1446,7 +1451,7 @@ class Executor { record: nextParentRecord, fieldPayloads, }); - const relayPayload = normalizeResponse(response, selector, typeName, { + const relayPayload = this._normalizeResponse(response, selector, typeName, { actorIdentifier: this._actorIdentifier, getDataID: this._getDataID, path: [...normalizationPath, responseKey, String(itemIndex)], @@ -1582,29 +1587,6 @@ function partitionGraphQLResponses( return [nonIncrementalResponses, incrementalResponses]; } -function normalizeResponse( - response: GraphQLResponseWithData, - selector: NormalizationSelector, - typeName: string, - options: NormalizationOptions, -): RelayResponsePayload { - const {data, errors} = response; - const source = RelayRecordSource.create(); - const record = RelayModernRecord.create(selector.dataID, typeName); - source.set(selector.dataID, record); - const relayPayload = RelayResponseNormalizer.normalize( - source, - selector, - data, - options, - ); - return { - ...relayPayload, - errors, - isFinal: response.extensions?.is_final === true, - }; -} - function stableStringify(value: mixed): string { return JSON.stringify(stableCopy(value)) ?? ''; // null-check for flow } diff --git a/packages/relay-runtime/store/RelayModernEnvironment.js b/packages/relay-runtime/store/RelayModernEnvironment.js index afb15061c7e31..142fdc7fceeca 100644 --- a/packages/relay-runtime/store/RelayModernEnvironment.js +++ b/packages/relay-runtime/store/RelayModernEnvironment.js @@ -28,6 +28,7 @@ import type { LogFunction, MissingFieldHandler, MutationParameters, + NormalizeResponseFunction, OperationAvailability, OperationDescriptor, OperationLoader, @@ -55,6 +56,7 @@ const RelayOperationTracker = require('../store/RelayOperationTracker'); const registerEnvironmentWithDevTools = require('../util/registerEnvironmentWithDevTools'); const defaultGetDataID = require('./defaultGetDataID'); const defaultRequiredFieldLogger = require('./defaultRequiredFieldLogger'); +const normalizeResponse = require('./normalizeResponse'); const OperationExecutor = require('./OperationExecutor'); const RelayPublishQueue = require('./RelayPublishQueue'); const RelayRecordSource = require('./RelayRecordSource'); @@ -67,6 +69,7 @@ export type EnvironmentConfig = { +log?: ?LogFunction, +operationLoader?: ?OperationLoader, +network: INetwork, + +normalizeResponse?: ?NormalizeResponseFunction, +scheduler?: ?TaskScheduler, +store: Store, +missingFieldHandlers?: ?$ReadOnlyArray, @@ -97,6 +100,7 @@ class RelayModernEnvironment implements IEnvironment { +options: mixed; +_isServer: boolean; requiredFieldLogger: RequiredFieldLogger; + _normalizeResponse: NormalizeResponseFunction; constructor(config: EnvironmentConfig) { this.configName = config.configName; @@ -134,6 +138,7 @@ class RelayModernEnvironment implements IEnvironment { this._store = config.store; this.options = config.options; this._isServer = config.isServer ?? false; + this._normalizeResponse = config.normalizeResponse ?? normalizeResponse; (this: any).__setNet = newNet => (this._network = wrapNetworkWithLogObserver(this, newNet)); @@ -481,6 +486,7 @@ class RelayModernEnvironment implements IEnvironment { }, treatMissingFieldsAsNull: this._treatMissingFieldsAsNull, updater, + normalizeResponse: this._normalizeResponse, }); return () => executor.cancel(); }); diff --git a/packages/relay-runtime/store/RelayStoreTypes.js b/packages/relay-runtime/store/RelayStoreTypes.js index 78cdc6c251c82..dec08202fdba1 100644 --- a/packages/relay-runtime/store/RelayStoreTypes.js +++ b/packages/relay-runtime/store/RelayStoreTypes.js @@ -17,6 +17,7 @@ import type { } from '../multi-actor-environment'; import type { GraphQLResponse, + GraphQLResponseWithData, INetwork, PayloadData, PayloadError, @@ -56,6 +57,7 @@ import type { import type {InvalidationState} from './RelayModernStore'; import type RelayOperationTracker from './RelayOperationTracker'; import type {RecordState} from './RelayRecordState'; +import type {NormalizationOptions} from './RelayResponseNormalizer'; export opaque type FragmentType = empty; export type OperationTracker = RelayOperationTracker; @@ -1071,6 +1073,13 @@ export type StreamPlaceholder = { }; export type IncrementalDataPlaceholder = DeferPlaceholder | StreamPlaceholder; +export type NormalizeResponseFunction = ( + response: GraphQLResponseWithData, + selector: NormalizationSelector, + typeName: string, + options: NormalizationOptions, +) => RelayResponsePayload; + /** * A user-supplied object to load a generated operation (SplitOperation or * ConcreteRequest) AST by a module reference. The exact format of a module diff --git a/packages/relay-runtime/store/normalizeResponse.js b/packages/relay-runtime/store/normalizeResponse.js new file mode 100644 index 0000000000000..71c41a45b9004 --- /dev/null +++ b/packages/relay-runtime/store/normalizeResponse.js @@ -0,0 +1,48 @@ +/** + * 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 {NormalizationOptions} from './RelayResponseNormalizer'; +import type { + NormalizationSelector, + RelayResponsePayload, +} from './RelayStoreTypes'; + +import RelayModernRecord from './RelayModernRecord'; +import RelayRecordSource from './RelayRecordSource'; +import RelayResponseNormalizer from './RelayResponseNormalizer'; + +function normalizeResponse( + response: GraphQLResponseWithData, + selector: NormalizationSelector, + typeName: string, + options: NormalizationOptions, +): RelayResponsePayload { + const {data, errors} = response; + const source = RelayRecordSource.create(); + const record = RelayModernRecord.create(selector.dataID, typeName); + source.set(selector.dataID, record); + const relayPayload = RelayResponseNormalizer.normalize( + source, + selector, + data, + options, + ); + return { + ...relayPayload, + errors, + isFinal: response.extensions?.is_final === true, + }; +} + +module.exports = normalizeResponse;