relay-runtime 8.0.0 → 10.0.1

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