relay-runtime 9.0.0 → 10.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/handlers/RelayDefaultHandlerProvider.js.flow +47 -0
- package/handlers/connection/ConnectionHandler.js.flow +549 -0
- package/handlers/connection/ConnectionInterface.js.flow +92 -0
- package/handlers/connection/MutationHandlers.js.flow +199 -0
- package/index.js +1 -1
- package/index.js.flow +335 -0
- package/lib/handlers/RelayDefaultHandlerProvider.js +20 -0
- package/lib/handlers/connection/ConnectionHandler.js +1 -3
- package/lib/handlers/connection/MutationHandlers.js +212 -0
- package/lib/index.js +14 -2
- package/lib/mutations/RelayDeclarativeMutationConfig.js +22 -45
- package/lib/mutations/RelayRecordProxy.js +1 -3
- package/lib/mutations/RelayRecordSourceMutator.js +1 -3
- package/lib/mutations/RelayRecordSourceProxy.js +1 -3
- package/lib/mutations/RelayRecordSourceSelectorProxy.js +1 -3
- package/lib/mutations/commitMutation.js +2 -3
- package/lib/mutations/validateMutation.js +40 -9
- package/lib/network/RelayObservable.js +9 -9
- package/lib/network/RelayQueryResponseCache.js +8 -6
- package/lib/query/GraphQLTag.js +2 -1
- package/lib/query/PreloadableQueryRegistry.js +70 -0
- package/lib/query/fetchQuery.js +2 -3
- package/lib/query/fetchQueryInternal.js +5 -14
- package/lib/store/DataChecker.js +200 -71
- package/lib/store/RelayConcreteVariables.js +6 -2
- package/lib/store/RelayModernEnvironment.js +124 -65
- package/lib/store/RelayModernFragmentSpecResolver.js +19 -14
- package/lib/store/RelayModernOperationDescriptor.js +6 -5
- package/lib/store/RelayModernQueryExecutor.js +122 -73
- package/lib/store/RelayModernRecord.js +14 -9
- package/lib/store/RelayModernSelector.js +6 -2
- package/lib/store/RelayModernStore.js +281 -131
- package/lib/store/RelayOperationTracker.js +35 -78
- package/lib/store/RelayOptimisticRecordSource.js +7 -5
- package/lib/store/RelayPublishQueue.js +2 -4
- package/lib/store/RelayReader.js +304 -52
- package/lib/store/RelayRecordSource.js +1 -3
- package/lib/store/RelayRecordSourceMapImpl.js +13 -18
- package/lib/store/RelayReferenceMarker.js +125 -14
- package/lib/store/RelayResponseNormalizer.js +261 -66
- package/lib/store/RelayStoreReactFlightUtils.js +47 -0
- package/lib/store/RelayStoreUtils.js +1 -0
- package/lib/store/StoreInspector.js +8 -8
- package/lib/store/TypeID.js +28 -0
- package/lib/store/cloneRelayScalarHandleSourceField.js +44 -0
- package/lib/store/defaultRequiredFieldLogger.js +18 -0
- package/lib/store/normalizeRelayPayload.js +6 -2
- package/lib/store/readInlineData.js +1 -1
- package/lib/subscription/requestSubscription.js +4 -3
- package/lib/util/NormalizationNode.js +1 -5
- package/lib/util/RelayConcreteNode.js +11 -6
- package/lib/util/RelayError.js +39 -9
- package/lib/util/RelayFeatureFlags.js +6 -3
- package/lib/util/RelayReplaySubject.js +3 -3
- package/lib/util/createPayloadFor3DField.js +7 -2
- package/lib/util/getFragmentIdentifier.js +12 -3
- package/lib/util/getOperation.js +33 -0
- package/lib/util/getRequestIdentifier.js +2 -2
- package/lib/util/isEmptyObject.js +25 -0
- package/lib/util/recycleNodesInto.js +6 -7
- package/lib/util/reportMissingRequiredFields.js +48 -0
- package/mutations/RelayDeclarativeMutationConfig.js.flow +380 -0
- package/mutations/RelayRecordProxy.js.flow +165 -0
- package/mutations/RelayRecordSourceMutator.js.flow +238 -0
- package/mutations/RelayRecordSourceProxy.js.flow +164 -0
- package/mutations/RelayRecordSourceSelectorProxy.js.flow +119 -0
- package/mutations/applyOptimisticMutation.js.flow +76 -0
- package/mutations/commitLocalUpdate.js.flow +24 -0
- package/mutations/commitMutation.js.flow +181 -0
- package/mutations/validateMutation.js.flow +242 -0
- package/network/ConvertToExecuteFunction.js.flow +49 -0
- package/network/RelayNetwork.js.flow +84 -0
- package/network/RelayNetworkTypes.js.flow +145 -0
- package/network/RelayObservable.js.flow +634 -0
- package/network/RelayQueryResponseCache.js.flow +111 -0
- package/package.json +2 -2
- package/query/GraphQLTag.js.flow +168 -0
- package/query/PreloadableQueryRegistry.js.flow +65 -0
- package/query/fetchQuery.js.flow +47 -0
- package/query/fetchQueryInternal.js.flow +343 -0
- package/relay-runtime.js +2 -2
- package/relay-runtime.min.js +2 -2
- package/store/ClientID.js.flow +43 -0
- package/store/DataChecker.js.flow +568 -0
- package/store/RelayConcreteVariables.js.flow +96 -0
- package/store/RelayModernEnvironment.js.flow +571 -0
- package/store/RelayModernFragmentSpecResolver.js.flow +438 -0
- package/store/RelayModernOperationDescriptor.js.flow +92 -0
- package/store/RelayModernQueryExecutor.js.flow +1345 -0
- package/store/RelayModernRecord.js.flow +403 -0
- package/store/RelayModernSelector.js.flow +455 -0
- package/store/RelayModernStore.js.flow +858 -0
- package/store/RelayOperationTracker.js.flow +164 -0
- package/store/RelayOptimisticRecordSource.js.flow +119 -0
- package/store/RelayPublishQueue.js.flow +401 -0
- package/store/RelayReader.js.flow +638 -0
- package/store/RelayRecordSource.js.flow +29 -0
- package/store/RelayRecordSourceMapImpl.js.flow +87 -0
- package/store/RelayRecordState.js.flow +37 -0
- package/store/RelayReferenceMarker.js.flow +324 -0
- package/store/RelayResponseNormalizer.js.flow +791 -0
- package/store/RelayStoreReactFlightUtils.js.flow +64 -0
- package/store/RelayStoreTypes.js.flow +958 -0
- package/store/RelayStoreUtils.js.flow +219 -0
- package/store/StoreInspector.js.flow +171 -0
- package/store/TypeID.js.flow +28 -0
- package/store/ViewerPattern.js.flow +26 -0
- package/store/cloneRelayHandleSourceField.js.flow +66 -0
- package/store/cloneRelayScalarHandleSourceField.js.flow +62 -0
- package/store/createFragmentSpecResolver.js.flow +55 -0
- package/store/createRelayContext.js.flow +44 -0
- package/store/defaultGetDataID.js.flow +27 -0
- package/store/defaultRequiredFieldLogger.js.flow +23 -0
- package/store/hasOverlappingIDs.js.flow +34 -0
- package/store/isRelayModernEnvironment.js.flow +27 -0
- package/store/normalizeRelayPayload.js.flow +51 -0
- package/store/readInlineData.js.flow +75 -0
- package/subscription/requestSubscription.js.flow +103 -0
- package/util/JSResourceTypes.flow.js.flow +20 -0
- package/util/NormalizationNode.js.flow +213 -0
- package/util/ReaderNode.js.flow +227 -0
- package/util/RelayConcreteNode.js.flow +99 -0
- package/util/RelayDefaultHandleKey.js.flow +17 -0
- package/util/RelayError.js.flow +62 -0
- package/util/RelayFeatureFlags.js.flow +37 -0
- package/util/RelayProfiler.js.flow +284 -0
- package/util/RelayReplaySubject.js.flow +135 -0
- package/util/RelayRuntimeTypes.js.flow +72 -0
- package/util/createPayloadFor3DField.js.flow +43 -0
- package/util/deepFreeze.js.flow +36 -0
- package/util/generateID.js.flow +21 -0
- package/util/getFragmentIdentifier.js.flow +76 -0
- package/util/getOperation.js.flow +40 -0
- package/util/getRelayHandleKey.js.flow +41 -0
- package/util/getRequestIdentifier.js.flow +42 -0
- package/util/isEmptyObject.js.flow +25 -0
- package/util/isPromise.js.flow +21 -0
- package/util/isScalarAndEqual.js.flow +26 -0
- package/util/recycleNodesInto.js.flow +87 -0
- package/util/reportMissingRequiredFields.js.flow +51 -0
- package/util/resolveImmediate.js.flow +30 -0
- package/util/stableCopy.js.flow +35 -0
|
@@ -0,0 +1,791 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
* @flow
|
|
8
|
+
* @format
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// flowlint ambiguous-object-type:error
|
|
12
|
+
|
|
13
|
+
'use strict';
|
|
14
|
+
|
|
15
|
+
const RelayFeatureFlags = require('../util/RelayFeatureFlags');
|
|
16
|
+
const RelayModernRecord = require('./RelayModernRecord');
|
|
17
|
+
const RelayProfiler = require('../util/RelayProfiler');
|
|
18
|
+
|
|
19
|
+
const areEqual = require('areEqual');
|
|
20
|
+
const invariant = require('invariant');
|
|
21
|
+
const warning = require('warning');
|
|
22
|
+
|
|
23
|
+
const {
|
|
24
|
+
CONDITION,
|
|
25
|
+
CLIENT_EXTENSION,
|
|
26
|
+
DEFER,
|
|
27
|
+
FLIGHT_FIELD,
|
|
28
|
+
INLINE_FRAGMENT,
|
|
29
|
+
LINKED_FIELD,
|
|
30
|
+
LINKED_HANDLE,
|
|
31
|
+
MODULE_IMPORT,
|
|
32
|
+
SCALAR_FIELD,
|
|
33
|
+
SCALAR_HANDLE,
|
|
34
|
+
STREAM,
|
|
35
|
+
TYPE_DISCRIMINATOR,
|
|
36
|
+
} = require('../util/RelayConcreteNode');
|
|
37
|
+
const {generateClientID, isClientID} = require('./ClientID');
|
|
38
|
+
const {createNormalizationSelector} = require('./RelayModernSelector');
|
|
39
|
+
const {
|
|
40
|
+
refineToReactFlightPayloadData,
|
|
41
|
+
REACT_FLIGHT_QUERIES_STORAGE_KEY,
|
|
42
|
+
REACT_FLIGHT_TREE_STORAGE_KEY,
|
|
43
|
+
REACT_FLIGHT_TYPE_NAME,
|
|
44
|
+
} = require('./RelayStoreReactFlightUtils');
|
|
45
|
+
const {
|
|
46
|
+
getArgumentValues,
|
|
47
|
+
getHandleStorageKey,
|
|
48
|
+
getModuleComponentKey,
|
|
49
|
+
getModuleOperationKey,
|
|
50
|
+
getStorageKey,
|
|
51
|
+
TYPENAME_KEY,
|
|
52
|
+
ROOT_ID,
|
|
53
|
+
ROOT_TYPE,
|
|
54
|
+
} = require('./RelayStoreUtils');
|
|
55
|
+
const {generateTypeID, TYPE_SCHEMA_TYPE} = require('./TypeID');
|
|
56
|
+
|
|
57
|
+
import type {PayloadData} from '../network/RelayNetworkTypes';
|
|
58
|
+
import type {
|
|
59
|
+
NormalizationDefer,
|
|
60
|
+
NormalizationFlightField,
|
|
61
|
+
NormalizationLinkedField,
|
|
62
|
+
NormalizationModuleImport,
|
|
63
|
+
NormalizationNode,
|
|
64
|
+
NormalizationScalarField,
|
|
65
|
+
NormalizationStream,
|
|
66
|
+
} from '../util/NormalizationNode';
|
|
67
|
+
import type {DataID, Variables} from '../util/RelayRuntimeTypes';
|
|
68
|
+
import type {
|
|
69
|
+
HandleFieldPayload,
|
|
70
|
+
IncrementalDataPlaceholder,
|
|
71
|
+
ModuleImportPayload,
|
|
72
|
+
MutableRecordSource,
|
|
73
|
+
NormalizationSelector,
|
|
74
|
+
ReactFlightReachableQuery,
|
|
75
|
+
ReactFlightPayloadDeserializer,
|
|
76
|
+
Record,
|
|
77
|
+
RelayResponsePayload,
|
|
78
|
+
} from './RelayStoreTypes';
|
|
79
|
+
|
|
80
|
+
export type GetDataID = (
|
|
81
|
+
fieldValue: {[string]: mixed, ...},
|
|
82
|
+
typeName: string,
|
|
83
|
+
) => mixed;
|
|
84
|
+
|
|
85
|
+
export type NormalizationOptions = {|
|
|
86
|
+
+getDataID: GetDataID,
|
|
87
|
+
+treatMissingFieldsAsNull: boolean,
|
|
88
|
+
+path?: $ReadOnlyArray<string>,
|
|
89
|
+
+reactFlightPayloadDeserializer?: ?ReactFlightPayloadDeserializer,
|
|
90
|
+
|};
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Normalizes the results of a query and standard GraphQL response, writing the
|
|
94
|
+
* normalized records/fields into the given MutableRecordSource.
|
|
95
|
+
*/
|
|
96
|
+
function normalize(
|
|
97
|
+
recordSource: MutableRecordSource,
|
|
98
|
+
selector: NormalizationSelector,
|
|
99
|
+
response: PayloadData,
|
|
100
|
+
options: NormalizationOptions,
|
|
101
|
+
): RelayResponsePayload {
|
|
102
|
+
const {dataID, node, variables} = selector;
|
|
103
|
+
const normalizer = new RelayResponseNormalizer(
|
|
104
|
+
recordSource,
|
|
105
|
+
variables,
|
|
106
|
+
options,
|
|
107
|
+
);
|
|
108
|
+
return normalizer.normalizeResponse(node, dataID, response);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* @private
|
|
113
|
+
*
|
|
114
|
+
* Helper for handling payloads.
|
|
115
|
+
*/
|
|
116
|
+
class RelayResponseNormalizer {
|
|
117
|
+
_getDataId: GetDataID;
|
|
118
|
+
_handleFieldPayloads: Array<HandleFieldPayload>;
|
|
119
|
+
_treatMissingFieldsAsNull: boolean;
|
|
120
|
+
_incrementalPlaceholders: Array<IncrementalDataPlaceholder>;
|
|
121
|
+
_isClientExtension: boolean;
|
|
122
|
+
_isUnmatchedAbstractType: boolean;
|
|
123
|
+
_moduleImportPayloads: Array<ModuleImportPayload>;
|
|
124
|
+
_path: Array<string>;
|
|
125
|
+
_recordSource: MutableRecordSource;
|
|
126
|
+
_variables: Variables;
|
|
127
|
+
_reactFlightPayloadDeserializer: ?ReactFlightPayloadDeserializer;
|
|
128
|
+
|
|
129
|
+
constructor(
|
|
130
|
+
recordSource: MutableRecordSource,
|
|
131
|
+
variables: Variables,
|
|
132
|
+
options: NormalizationOptions,
|
|
133
|
+
) {
|
|
134
|
+
this._getDataId = options.getDataID;
|
|
135
|
+
this._handleFieldPayloads = [];
|
|
136
|
+
this._treatMissingFieldsAsNull = options.treatMissingFieldsAsNull;
|
|
137
|
+
this._incrementalPlaceholders = [];
|
|
138
|
+
this._isClientExtension = false;
|
|
139
|
+
this._isUnmatchedAbstractType = false;
|
|
140
|
+
this._moduleImportPayloads = [];
|
|
141
|
+
this._path = options.path ? [...options.path] : [];
|
|
142
|
+
this._recordSource = recordSource;
|
|
143
|
+
this._variables = variables;
|
|
144
|
+
this._reactFlightPayloadDeserializer =
|
|
145
|
+
options.reactFlightPayloadDeserializer;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
normalizeResponse(
|
|
149
|
+
node: NormalizationNode,
|
|
150
|
+
dataID: DataID,
|
|
151
|
+
data: PayloadData,
|
|
152
|
+
): RelayResponsePayload {
|
|
153
|
+
const record = this._recordSource.get(dataID);
|
|
154
|
+
invariant(
|
|
155
|
+
record,
|
|
156
|
+
'RelayResponseNormalizer(): Expected root record `%s` to exist.',
|
|
157
|
+
dataID,
|
|
158
|
+
);
|
|
159
|
+
this._traverseSelections(node, record, data);
|
|
160
|
+
return {
|
|
161
|
+
errors: null,
|
|
162
|
+
fieldPayloads: this._handleFieldPayloads,
|
|
163
|
+
incrementalPlaceholders: this._incrementalPlaceholders,
|
|
164
|
+
moduleImportPayloads: this._moduleImportPayloads,
|
|
165
|
+
source: this._recordSource,
|
|
166
|
+
isFinal: false,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
_getVariableValue(name: string): mixed {
|
|
171
|
+
invariant(
|
|
172
|
+
this._variables.hasOwnProperty(name),
|
|
173
|
+
'RelayResponseNormalizer(): Undefined variable `%s`.',
|
|
174
|
+
name,
|
|
175
|
+
);
|
|
176
|
+
return this._variables[name];
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
_getRecordType(data: PayloadData): string {
|
|
180
|
+
const typeName = (data: any)[TYPENAME_KEY];
|
|
181
|
+
invariant(
|
|
182
|
+
typeName != null,
|
|
183
|
+
'RelayResponseNormalizer(): Expected a typename for record `%s`.',
|
|
184
|
+
JSON.stringify(data, null, 2),
|
|
185
|
+
);
|
|
186
|
+
return typeName;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
_traverseSelections(
|
|
190
|
+
node: NormalizationNode,
|
|
191
|
+
record: Record,
|
|
192
|
+
data: PayloadData,
|
|
193
|
+
): void {
|
|
194
|
+
for (let i = 0; i < node.selections.length; i++) {
|
|
195
|
+
const selection = node.selections[i];
|
|
196
|
+
switch (selection.kind) {
|
|
197
|
+
case SCALAR_FIELD:
|
|
198
|
+
case LINKED_FIELD:
|
|
199
|
+
this._normalizeField(node, selection, record, data);
|
|
200
|
+
break;
|
|
201
|
+
case CONDITION:
|
|
202
|
+
const conditionValue = this._getVariableValue(selection.condition);
|
|
203
|
+
if (conditionValue === selection.passingValue) {
|
|
204
|
+
this._traverseSelections(selection, record, data);
|
|
205
|
+
}
|
|
206
|
+
break;
|
|
207
|
+
case INLINE_FRAGMENT: {
|
|
208
|
+
const {abstractKey} = selection;
|
|
209
|
+
if (abstractKey == null) {
|
|
210
|
+
const typeName = RelayModernRecord.getType(record);
|
|
211
|
+
if (typeName === selection.type) {
|
|
212
|
+
this._traverseSelections(selection, record, data);
|
|
213
|
+
}
|
|
214
|
+
} else if (RelayFeatureFlags.ENABLE_PRECISE_TYPE_REFINEMENT) {
|
|
215
|
+
const implementsInterface = data.hasOwnProperty(abstractKey);
|
|
216
|
+
const typeName = RelayModernRecord.getType(record);
|
|
217
|
+
const typeID = generateTypeID(typeName);
|
|
218
|
+
let typeRecord = this._recordSource.get(typeID);
|
|
219
|
+
if (typeRecord == null) {
|
|
220
|
+
typeRecord = RelayModernRecord.create(typeID, TYPE_SCHEMA_TYPE);
|
|
221
|
+
this._recordSource.set(typeID, typeRecord);
|
|
222
|
+
}
|
|
223
|
+
RelayModernRecord.setValue(
|
|
224
|
+
typeRecord,
|
|
225
|
+
abstractKey,
|
|
226
|
+
implementsInterface,
|
|
227
|
+
);
|
|
228
|
+
if (implementsInterface) {
|
|
229
|
+
this._traverseSelections(selection, record, data);
|
|
230
|
+
}
|
|
231
|
+
} else {
|
|
232
|
+
// legacy behavior for abstract refinements: always normalize even
|
|
233
|
+
// if the type doesn't conform, but track if the type matches or not
|
|
234
|
+
// for determining whether response fields are expected to be present
|
|
235
|
+
const implementsInterface = data.hasOwnProperty(abstractKey);
|
|
236
|
+
const parentIsUnmatchedAbstractType = this._isUnmatchedAbstractType;
|
|
237
|
+
this._isUnmatchedAbstractType =
|
|
238
|
+
this._isUnmatchedAbstractType || !implementsInterface;
|
|
239
|
+
this._traverseSelections(selection, record, data);
|
|
240
|
+
this._isUnmatchedAbstractType = parentIsUnmatchedAbstractType;
|
|
241
|
+
}
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
case TYPE_DISCRIMINATOR: {
|
|
245
|
+
if (RelayFeatureFlags.ENABLE_PRECISE_TYPE_REFINEMENT) {
|
|
246
|
+
const {abstractKey} = selection;
|
|
247
|
+
const implementsInterface = data.hasOwnProperty(abstractKey);
|
|
248
|
+
const typeName = RelayModernRecord.getType(record);
|
|
249
|
+
const typeID = generateTypeID(typeName);
|
|
250
|
+
let typeRecord = this._recordSource.get(typeID);
|
|
251
|
+
if (typeRecord == null) {
|
|
252
|
+
typeRecord = RelayModernRecord.create(typeID, TYPE_SCHEMA_TYPE);
|
|
253
|
+
this._recordSource.set(typeID, typeRecord);
|
|
254
|
+
}
|
|
255
|
+
RelayModernRecord.setValue(
|
|
256
|
+
typeRecord,
|
|
257
|
+
abstractKey,
|
|
258
|
+
implementsInterface,
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
case LINKED_HANDLE:
|
|
264
|
+
case SCALAR_HANDLE:
|
|
265
|
+
const args = selection.args
|
|
266
|
+
? getArgumentValues(selection.args, this._variables)
|
|
267
|
+
: {};
|
|
268
|
+
const fieldKey = getStorageKey(selection, this._variables);
|
|
269
|
+
const handleKey = getHandleStorageKey(selection, this._variables);
|
|
270
|
+
this._handleFieldPayloads.push({
|
|
271
|
+
args,
|
|
272
|
+
dataID: RelayModernRecord.getDataID(record),
|
|
273
|
+
fieldKey,
|
|
274
|
+
handle: selection.handle,
|
|
275
|
+
handleKey,
|
|
276
|
+
handleArgs: selection.handleArgs
|
|
277
|
+
? getArgumentValues(selection.handleArgs, this._variables)
|
|
278
|
+
: {},
|
|
279
|
+
});
|
|
280
|
+
break;
|
|
281
|
+
case MODULE_IMPORT:
|
|
282
|
+
this._normalizeModuleImport(node, selection, record, data);
|
|
283
|
+
break;
|
|
284
|
+
case DEFER:
|
|
285
|
+
this._normalizeDefer(selection, record, data);
|
|
286
|
+
break;
|
|
287
|
+
case STREAM:
|
|
288
|
+
this._normalizeStream(selection, record, data);
|
|
289
|
+
break;
|
|
290
|
+
case CLIENT_EXTENSION:
|
|
291
|
+
const isClientExtension = this._isClientExtension;
|
|
292
|
+
this._isClientExtension = true;
|
|
293
|
+
this._traverseSelections(selection, record, data);
|
|
294
|
+
this._isClientExtension = isClientExtension;
|
|
295
|
+
break;
|
|
296
|
+
case FLIGHT_FIELD:
|
|
297
|
+
if (RelayFeatureFlags.ENABLE_REACT_FLIGHT_COMPONENT_FIELD) {
|
|
298
|
+
this._normalizeFlightField(node, selection, record, data);
|
|
299
|
+
} else {
|
|
300
|
+
throw new Error('Flight fields are not yet supported.');
|
|
301
|
+
}
|
|
302
|
+
break;
|
|
303
|
+
default:
|
|
304
|
+
(selection: empty);
|
|
305
|
+
invariant(
|
|
306
|
+
false,
|
|
307
|
+
'RelayResponseNormalizer(): Unexpected ast kind `%s`.',
|
|
308
|
+
selection.kind,
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
_normalizeDefer(
|
|
315
|
+
defer: NormalizationDefer,
|
|
316
|
+
record: Record,
|
|
317
|
+
data: PayloadData,
|
|
318
|
+
) {
|
|
319
|
+
const isDeferred = defer.if === null || this._getVariableValue(defer.if);
|
|
320
|
+
if (__DEV__) {
|
|
321
|
+
warning(
|
|
322
|
+
typeof isDeferred === 'boolean',
|
|
323
|
+
'RelayResponseNormalizer: Expected value for @defer `if` argument to ' +
|
|
324
|
+
'be a boolean, got `%s`.',
|
|
325
|
+
isDeferred,
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
if (isDeferred === false) {
|
|
329
|
+
// If defer is disabled there will be no additional response chunk:
|
|
330
|
+
// normalize the data already present.
|
|
331
|
+
this._traverseSelections(defer, record, data);
|
|
332
|
+
} else {
|
|
333
|
+
// Otherwise data *for this selection* should not be present: enqueue
|
|
334
|
+
// metadata to process the subsequent response chunk.
|
|
335
|
+
this._incrementalPlaceholders.push({
|
|
336
|
+
kind: 'defer',
|
|
337
|
+
data,
|
|
338
|
+
label: defer.label,
|
|
339
|
+
path: [...this._path],
|
|
340
|
+
selector: createNormalizationSelector(
|
|
341
|
+
defer,
|
|
342
|
+
RelayModernRecord.getDataID(record),
|
|
343
|
+
this._variables,
|
|
344
|
+
),
|
|
345
|
+
typeName: RelayModernRecord.getType(record),
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
_normalizeStream(
|
|
351
|
+
stream: NormalizationStream,
|
|
352
|
+
record: Record,
|
|
353
|
+
data: PayloadData,
|
|
354
|
+
) {
|
|
355
|
+
// Always normalize regardless of whether streaming is enabled or not,
|
|
356
|
+
// this populates the initial array value (including any items when
|
|
357
|
+
// initial_count > 0).
|
|
358
|
+
this._traverseSelections(stream, record, data);
|
|
359
|
+
const isStreamed = stream.if === null || this._getVariableValue(stream.if);
|
|
360
|
+
if (__DEV__) {
|
|
361
|
+
warning(
|
|
362
|
+
typeof isStreamed === 'boolean',
|
|
363
|
+
'RelayResponseNormalizer: Expected value for @stream `if` argument ' +
|
|
364
|
+
'to be a boolean, got `%s`.',
|
|
365
|
+
isStreamed,
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
if (isStreamed === true) {
|
|
369
|
+
// If streaming is enabled, *also* emit metadata to process any
|
|
370
|
+
// response chunks that may be delivered.
|
|
371
|
+
this._incrementalPlaceholders.push({
|
|
372
|
+
kind: 'stream',
|
|
373
|
+
label: stream.label,
|
|
374
|
+
path: [...this._path],
|
|
375
|
+
parentID: RelayModernRecord.getDataID(record),
|
|
376
|
+
node: stream,
|
|
377
|
+
variables: this._variables,
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
_normalizeModuleImport(
|
|
383
|
+
parent: NormalizationNode,
|
|
384
|
+
moduleImport: NormalizationModuleImport,
|
|
385
|
+
record: Record,
|
|
386
|
+
data: PayloadData,
|
|
387
|
+
) {
|
|
388
|
+
invariant(
|
|
389
|
+
typeof data === 'object' && data,
|
|
390
|
+
'RelayResponseNormalizer: Expected data for @module to be an object.',
|
|
391
|
+
);
|
|
392
|
+
const typeName: string = RelayModernRecord.getType(record);
|
|
393
|
+
const componentKey = getModuleComponentKey(moduleImport.documentName);
|
|
394
|
+
const componentReference = data[componentKey];
|
|
395
|
+
RelayModernRecord.setValue(
|
|
396
|
+
record,
|
|
397
|
+
componentKey,
|
|
398
|
+
componentReference ?? null,
|
|
399
|
+
);
|
|
400
|
+
const operationKey = getModuleOperationKey(moduleImport.documentName);
|
|
401
|
+
const operationReference = data[operationKey];
|
|
402
|
+
RelayModernRecord.setValue(
|
|
403
|
+
record,
|
|
404
|
+
operationKey,
|
|
405
|
+
operationReference ?? null,
|
|
406
|
+
);
|
|
407
|
+
if (operationReference != null) {
|
|
408
|
+
this._moduleImportPayloads.push({
|
|
409
|
+
data,
|
|
410
|
+
dataID: RelayModernRecord.getDataID(record),
|
|
411
|
+
operationReference,
|
|
412
|
+
path: [...this._path],
|
|
413
|
+
typeName,
|
|
414
|
+
variables: this._variables,
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
_normalizeField(
|
|
420
|
+
parent: NormalizationNode,
|
|
421
|
+
selection: NormalizationLinkedField | NormalizationScalarField,
|
|
422
|
+
record: Record,
|
|
423
|
+
data: PayloadData,
|
|
424
|
+
) {
|
|
425
|
+
invariant(
|
|
426
|
+
typeof data === 'object' && data,
|
|
427
|
+
'writeField(): Expected data for field `%s` to be an object.',
|
|
428
|
+
selection.name,
|
|
429
|
+
);
|
|
430
|
+
const responseKey = selection.alias || selection.name;
|
|
431
|
+
const storageKey = getStorageKey(selection, this._variables);
|
|
432
|
+
const fieldValue = data[responseKey];
|
|
433
|
+
if (fieldValue == null) {
|
|
434
|
+
if (fieldValue === undefined) {
|
|
435
|
+
// Fields may be missing in the response in two main cases:
|
|
436
|
+
// - Inside a client extension: the server will not generally return
|
|
437
|
+
// values for these fields, but a local update may provide them.
|
|
438
|
+
// - Inside an abstract type refinement where the concrete type does
|
|
439
|
+
// not conform to the interface/union.
|
|
440
|
+
// However an otherwise-required field may also be missing if the server
|
|
441
|
+
// is configured to skip fields with `null` values, in which case the
|
|
442
|
+
// client is assumed to be correctly configured with
|
|
443
|
+
// treatMissingFieldsAsNull=true.
|
|
444
|
+
const isOptionalField =
|
|
445
|
+
this._isClientExtension || this._isUnmatchedAbstractType;
|
|
446
|
+
|
|
447
|
+
if (isOptionalField) {
|
|
448
|
+
// Field not expected to exist regardless of whether the server is pruning null
|
|
449
|
+
// fields or not.
|
|
450
|
+
return;
|
|
451
|
+
} else if (!this._treatMissingFieldsAsNull) {
|
|
452
|
+
// Not optional and the server is not pruning null fields: field is expected
|
|
453
|
+
// to be present
|
|
454
|
+
if (__DEV__) {
|
|
455
|
+
warning(
|
|
456
|
+
false,
|
|
457
|
+
'RelayResponseNormalizer: Payload did not contain a value ' +
|
|
458
|
+
'for field `%s: %s`. Check that you are parsing with the same ' +
|
|
459
|
+
'query that was used to fetch the payload.',
|
|
460
|
+
responseKey,
|
|
461
|
+
storageKey,
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
if (__DEV__) {
|
|
468
|
+
if (selection.kind === SCALAR_FIELD) {
|
|
469
|
+
this._validateConflictingFieldsWithIdenticalId(
|
|
470
|
+
record,
|
|
471
|
+
storageKey,
|
|
472
|
+
fieldValue,
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
RelayModernRecord.setValue(record, storageKey, null);
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
if (selection.kind === SCALAR_FIELD) {
|
|
481
|
+
if (__DEV__) {
|
|
482
|
+
this._validateConflictingFieldsWithIdenticalId(
|
|
483
|
+
record,
|
|
484
|
+
storageKey,
|
|
485
|
+
fieldValue,
|
|
486
|
+
);
|
|
487
|
+
}
|
|
488
|
+
RelayModernRecord.setValue(record, storageKey, fieldValue);
|
|
489
|
+
} else if (selection.kind === LINKED_FIELD) {
|
|
490
|
+
this._path.push(responseKey);
|
|
491
|
+
if (selection.plural) {
|
|
492
|
+
this._normalizePluralLink(selection, record, storageKey, fieldValue);
|
|
493
|
+
} else {
|
|
494
|
+
this._normalizeLink(selection, record, storageKey, fieldValue);
|
|
495
|
+
}
|
|
496
|
+
this._path.pop();
|
|
497
|
+
} else {
|
|
498
|
+
(selection: empty);
|
|
499
|
+
invariant(
|
|
500
|
+
false,
|
|
501
|
+
'RelayResponseNormalizer(): Unexpected ast kind `%s` during normalization.',
|
|
502
|
+
selection.kind,
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
_normalizeFlightField(
|
|
508
|
+
parent: NormalizationNode,
|
|
509
|
+
selection: NormalizationFlightField,
|
|
510
|
+
record: Record,
|
|
511
|
+
data: PayloadData,
|
|
512
|
+
) {
|
|
513
|
+
const responseKey = selection.alias || selection.name;
|
|
514
|
+
const storageKey = getStorageKey(selection, this._variables);
|
|
515
|
+
const fieldValue = data[responseKey];
|
|
516
|
+
|
|
517
|
+
if (fieldValue == null) {
|
|
518
|
+
RelayModernRecord.setValue(record, storageKey, null);
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
const reactFlightPayload = refineToReactFlightPayloadData(fieldValue);
|
|
523
|
+
|
|
524
|
+
invariant(
|
|
525
|
+
reactFlightPayload != null,
|
|
526
|
+
'RelayResponseNormalizer(): Expected React Flight payload data ' +
|
|
527
|
+
'to be an object with `tree` and `queries` properties, got `%s`.',
|
|
528
|
+
fieldValue,
|
|
529
|
+
);
|
|
530
|
+
invariant(
|
|
531
|
+
typeof this._reactFlightPayloadDeserializer === 'function',
|
|
532
|
+
'RelayResponseNormalizer: Expected reactFlightPayloadDeserializer to ' +
|
|
533
|
+
'be a function, got `%s`.',
|
|
534
|
+
this._reactFlightPayloadDeserializer,
|
|
535
|
+
);
|
|
536
|
+
|
|
537
|
+
// We store the deserialized reactFlightClientResponse in a separate
|
|
538
|
+
// record and link it to the parent record. This is so we can GC the Flight
|
|
539
|
+
// tree later even if the parent record is still reachable.
|
|
540
|
+
const reactFlightClientResponse = this._reactFlightPayloadDeserializer(
|
|
541
|
+
reactFlightPayload.tree,
|
|
542
|
+
);
|
|
543
|
+
const reactFlightID = generateClientID(
|
|
544
|
+
RelayModernRecord.getDataID(record),
|
|
545
|
+
getStorageKey(selection, this._variables),
|
|
546
|
+
);
|
|
547
|
+
let reactFlightClientResponseRecord = this._recordSource.get(reactFlightID);
|
|
548
|
+
if (reactFlightClientResponseRecord == null) {
|
|
549
|
+
reactFlightClientResponseRecord = RelayModernRecord.create(
|
|
550
|
+
reactFlightID,
|
|
551
|
+
REACT_FLIGHT_TYPE_NAME,
|
|
552
|
+
);
|
|
553
|
+
this._recordSource.set(reactFlightID, reactFlightClientResponseRecord);
|
|
554
|
+
}
|
|
555
|
+
RelayModernRecord.setValue(
|
|
556
|
+
reactFlightClientResponseRecord,
|
|
557
|
+
REACT_FLIGHT_TREE_STORAGE_KEY,
|
|
558
|
+
reactFlightClientResponse,
|
|
559
|
+
);
|
|
560
|
+
const reachableQueries: Array<ReactFlightReachableQuery> = [];
|
|
561
|
+
for (const query of reactFlightPayload.queries) {
|
|
562
|
+
if (query.response.data != null) {
|
|
563
|
+
this._moduleImportPayloads.push({
|
|
564
|
+
data: query.response.data,
|
|
565
|
+
dataID: ROOT_ID,
|
|
566
|
+
operationReference: query.module,
|
|
567
|
+
path: [],
|
|
568
|
+
typeName: ROOT_TYPE,
|
|
569
|
+
variables: query.variables,
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
reachableQueries.push({
|
|
573
|
+
module: query.module,
|
|
574
|
+
variables: query.variables,
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
RelayModernRecord.setValue(
|
|
578
|
+
reactFlightClientResponseRecord,
|
|
579
|
+
REACT_FLIGHT_QUERIES_STORAGE_KEY,
|
|
580
|
+
reachableQueries,
|
|
581
|
+
);
|
|
582
|
+
RelayModernRecord.setLinkedRecordID(record, storageKey, reactFlightID);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
_normalizeLink(
|
|
586
|
+
field: NormalizationLinkedField,
|
|
587
|
+
record: Record,
|
|
588
|
+
storageKey: string,
|
|
589
|
+
fieldValue: mixed,
|
|
590
|
+
): void {
|
|
591
|
+
invariant(
|
|
592
|
+
typeof fieldValue === 'object' && fieldValue,
|
|
593
|
+
'RelayResponseNormalizer: Expected data for field `%s` to be an object.',
|
|
594
|
+
storageKey,
|
|
595
|
+
);
|
|
596
|
+
const nextID =
|
|
597
|
+
this._getDataId(
|
|
598
|
+
// $FlowFixMe[incompatible-variance]
|
|
599
|
+
fieldValue,
|
|
600
|
+
// $FlowFixMe[incompatible-variance]
|
|
601
|
+
field.concreteType ?? this._getRecordType(fieldValue),
|
|
602
|
+
) ||
|
|
603
|
+
// Reuse previously generated client IDs
|
|
604
|
+
RelayModernRecord.getLinkedRecordID(record, storageKey) ||
|
|
605
|
+
generateClientID(RelayModernRecord.getDataID(record), storageKey);
|
|
606
|
+
invariant(
|
|
607
|
+
typeof nextID === 'string',
|
|
608
|
+
'RelayResponseNormalizer: Expected id on field `%s` to be a string.',
|
|
609
|
+
storageKey,
|
|
610
|
+
);
|
|
611
|
+
if (__DEV__) {
|
|
612
|
+
this._validateConflictingLinkedFieldsWithIdenticalId(
|
|
613
|
+
record,
|
|
614
|
+
RelayModernRecord.getLinkedRecordID(record, storageKey),
|
|
615
|
+
nextID,
|
|
616
|
+
storageKey,
|
|
617
|
+
);
|
|
618
|
+
}
|
|
619
|
+
RelayModernRecord.setLinkedRecordID(record, storageKey, nextID);
|
|
620
|
+
let nextRecord = this._recordSource.get(nextID);
|
|
621
|
+
if (!nextRecord) {
|
|
622
|
+
// $FlowFixMe[incompatible-variance]
|
|
623
|
+
const typeName = field.concreteType || this._getRecordType(fieldValue);
|
|
624
|
+
nextRecord = RelayModernRecord.create(nextID, typeName);
|
|
625
|
+
this._recordSource.set(nextID, nextRecord);
|
|
626
|
+
} else if (__DEV__) {
|
|
627
|
+
this._validateRecordType(nextRecord, field, fieldValue);
|
|
628
|
+
}
|
|
629
|
+
// $FlowFixMe[incompatible-variance]
|
|
630
|
+
this._traverseSelections(field, nextRecord, fieldValue);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
_normalizePluralLink(
|
|
634
|
+
field: NormalizationLinkedField,
|
|
635
|
+
record: Record,
|
|
636
|
+
storageKey: string,
|
|
637
|
+
fieldValue: mixed,
|
|
638
|
+
): void {
|
|
639
|
+
invariant(
|
|
640
|
+
Array.isArray(fieldValue),
|
|
641
|
+
'RelayResponseNormalizer: Expected data for field `%s` to be an array ' +
|
|
642
|
+
'of objects.',
|
|
643
|
+
storageKey,
|
|
644
|
+
);
|
|
645
|
+
const prevIDs = RelayModernRecord.getLinkedRecordIDs(record, storageKey);
|
|
646
|
+
const nextIDs = [];
|
|
647
|
+
fieldValue.forEach((item, nextIndex) => {
|
|
648
|
+
// validate response data
|
|
649
|
+
if (item == null) {
|
|
650
|
+
nextIDs.push(item);
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
this._path.push(String(nextIndex));
|
|
654
|
+
invariant(
|
|
655
|
+
typeof item === 'object',
|
|
656
|
+
'RelayResponseNormalizer: Expected elements for field `%s` to be ' +
|
|
657
|
+
'objects.',
|
|
658
|
+
storageKey,
|
|
659
|
+
);
|
|
660
|
+
const nextID =
|
|
661
|
+
this._getDataId(
|
|
662
|
+
// $FlowFixMe[incompatible-variance]
|
|
663
|
+
item,
|
|
664
|
+
// $FlowFixMe[incompatible-variance]
|
|
665
|
+
field.concreteType ?? this._getRecordType(item),
|
|
666
|
+
) ||
|
|
667
|
+
(prevIDs && prevIDs[nextIndex]) || // Reuse previously generated client IDs:
|
|
668
|
+
generateClientID(
|
|
669
|
+
RelayModernRecord.getDataID(record),
|
|
670
|
+
storageKey,
|
|
671
|
+
nextIndex,
|
|
672
|
+
);
|
|
673
|
+
invariant(
|
|
674
|
+
typeof nextID === 'string',
|
|
675
|
+
'RelayResponseNormalizer: Expected id of elements of field `%s` to ' +
|
|
676
|
+
'be strings.',
|
|
677
|
+
storageKey,
|
|
678
|
+
);
|
|
679
|
+
|
|
680
|
+
nextIDs.push(nextID);
|
|
681
|
+
let nextRecord = this._recordSource.get(nextID);
|
|
682
|
+
if (!nextRecord) {
|
|
683
|
+
// $FlowFixMe[incompatible-variance]
|
|
684
|
+
const typeName = field.concreteType || this._getRecordType(item);
|
|
685
|
+
nextRecord = RelayModernRecord.create(nextID, typeName);
|
|
686
|
+
this._recordSource.set(nextID, nextRecord);
|
|
687
|
+
} else if (__DEV__) {
|
|
688
|
+
this._validateRecordType(nextRecord, field, item);
|
|
689
|
+
}
|
|
690
|
+
// NOTE: the check to strip __DEV__ code only works for simple
|
|
691
|
+
// `if (__DEV__)`
|
|
692
|
+
if (__DEV__) {
|
|
693
|
+
if (prevIDs) {
|
|
694
|
+
this._validateConflictingLinkedFieldsWithIdenticalId(
|
|
695
|
+
record,
|
|
696
|
+
prevIDs[nextIndex],
|
|
697
|
+
nextID,
|
|
698
|
+
storageKey,
|
|
699
|
+
);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
// $FlowFixMe[incompatible-variance]
|
|
703
|
+
this._traverseSelections(field, nextRecord, item);
|
|
704
|
+
this._path.pop();
|
|
705
|
+
});
|
|
706
|
+
RelayModernRecord.setLinkedRecordIDs(record, storageKey, nextIDs);
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
/**
|
|
710
|
+
* Warns if the type of the record does not match the type of the field/payload.
|
|
711
|
+
*/
|
|
712
|
+
_validateRecordType(
|
|
713
|
+
record: Record,
|
|
714
|
+
field: NormalizationLinkedField,
|
|
715
|
+
payload: Object,
|
|
716
|
+
): void {
|
|
717
|
+
const typeName = field.concreteType ?? this._getRecordType(payload);
|
|
718
|
+
const dataID = RelayModernRecord.getDataID(record);
|
|
719
|
+
warning(
|
|
720
|
+
(isClientID(dataID) && dataID !== ROOT_ID) ||
|
|
721
|
+
RelayModernRecord.getType(record) === typeName,
|
|
722
|
+
'RelayResponseNormalizer: Invalid record `%s`. Expected %s to be ' +
|
|
723
|
+
'consistent, but the record was assigned conflicting types `%s` ' +
|
|
724
|
+
'and `%s`. The GraphQL server likely violated the globally unique ' +
|
|
725
|
+
'id requirement by returning the same id for different objects.',
|
|
726
|
+
dataID,
|
|
727
|
+
TYPENAME_KEY,
|
|
728
|
+
RelayModernRecord.getType(record),
|
|
729
|
+
typeName,
|
|
730
|
+
);
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
/**
|
|
734
|
+
* Warns if a single response contains conflicting fields with the same id
|
|
735
|
+
*/
|
|
736
|
+
_validateConflictingFieldsWithIdenticalId(
|
|
737
|
+
record: Record,
|
|
738
|
+
storageKey: string,
|
|
739
|
+
fieldValue: mixed,
|
|
740
|
+
): void {
|
|
741
|
+
// NOTE: Only call this function in DEV
|
|
742
|
+
if (__DEV__) {
|
|
743
|
+
const dataID = RelayModernRecord.getDataID(record);
|
|
744
|
+
var previousValue = RelayModernRecord.getValue(record, storageKey);
|
|
745
|
+
warning(
|
|
746
|
+
storageKey === TYPENAME_KEY ||
|
|
747
|
+
previousValue === undefined ||
|
|
748
|
+
areEqual(previousValue, fieldValue),
|
|
749
|
+
'RelayResponseNormalizer: Invalid record. The record contains two ' +
|
|
750
|
+
'instances of the same id: `%s` with conflicting field, %s and its values: %s and %s. ' +
|
|
751
|
+
'If two fields are different but share ' +
|
|
752
|
+
'the same id, one field will overwrite the other.',
|
|
753
|
+
dataID,
|
|
754
|
+
storageKey,
|
|
755
|
+
previousValue,
|
|
756
|
+
fieldValue,
|
|
757
|
+
);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
/**
|
|
762
|
+
* Warns if a single response contains conflicting fields with the same id
|
|
763
|
+
*/
|
|
764
|
+
_validateConflictingLinkedFieldsWithIdenticalId(
|
|
765
|
+
record: Record,
|
|
766
|
+
prevID: ?DataID,
|
|
767
|
+
nextID: DataID,
|
|
768
|
+
storageKey: string,
|
|
769
|
+
): void {
|
|
770
|
+
// NOTE: Only call this function in DEV
|
|
771
|
+
if (__DEV__) {
|
|
772
|
+
warning(
|
|
773
|
+
prevID === undefined || prevID === nextID,
|
|
774
|
+
'RelayResponseNormalizer: Invalid record. The record contains ' +
|
|
775
|
+
'references to the conflicting field, %s and its id values: %s and %s. ' +
|
|
776
|
+
'We need to make sure that the record the field points ' +
|
|
777
|
+
'to remains consistent or one field will overwrite the other.',
|
|
778
|
+
storageKey,
|
|
779
|
+
prevID,
|
|
780
|
+
nextID,
|
|
781
|
+
);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
const instrumentedNormalize: typeof normalize = RelayProfiler.instrument(
|
|
787
|
+
'RelayResponseNormalizer.normalize',
|
|
788
|
+
normalize,
|
|
789
|
+
);
|
|
790
|
+
|
|
791
|
+
module.exports = {normalize: instrumentedNormalize};
|