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.
Files changed (142) hide show
  1. package/handlers/RelayDefaultHandlerProvider.js.flow +47 -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 +199 -0
  5. package/index.js +1 -1
  6. package/index.js.flow +335 -0
  7. package/lib/handlers/RelayDefaultHandlerProvider.js +20 -0
  8. package/lib/handlers/connection/ConnectionHandler.js +1 -3
  9. package/lib/handlers/connection/MutationHandlers.js +212 -0
  10. package/lib/index.js +14 -2
  11. package/lib/mutations/RelayDeclarativeMutationConfig.js +22 -45
  12. package/lib/mutations/RelayRecordProxy.js +1 -3
  13. package/lib/mutations/RelayRecordSourceMutator.js +1 -3
  14. package/lib/mutations/RelayRecordSourceProxy.js +1 -3
  15. package/lib/mutations/RelayRecordSourceSelectorProxy.js +1 -3
  16. package/lib/mutations/commitMutation.js +2 -3
  17. package/lib/mutations/validateMutation.js +40 -9
  18. package/lib/network/RelayObservable.js +9 -9
  19. package/lib/network/RelayQueryResponseCache.js +8 -6
  20. package/lib/query/GraphQLTag.js +2 -1
  21. package/lib/query/PreloadableQueryRegistry.js +70 -0
  22. package/lib/query/fetchQuery.js +2 -3
  23. package/lib/query/fetchQueryInternal.js +5 -14
  24. package/lib/store/DataChecker.js +200 -71
  25. package/lib/store/RelayConcreteVariables.js +6 -2
  26. package/lib/store/RelayModernEnvironment.js +124 -65
  27. package/lib/store/RelayModernFragmentSpecResolver.js +19 -14
  28. package/lib/store/RelayModernOperationDescriptor.js +6 -5
  29. package/lib/store/RelayModernQueryExecutor.js +122 -73
  30. package/lib/store/RelayModernRecord.js +14 -9
  31. package/lib/store/RelayModernSelector.js +6 -2
  32. package/lib/store/RelayModernStore.js +281 -131
  33. package/lib/store/RelayOperationTracker.js +35 -78
  34. package/lib/store/RelayOptimisticRecordSource.js +7 -5
  35. package/lib/store/RelayPublishQueue.js +2 -4
  36. package/lib/store/RelayReader.js +304 -52
  37. package/lib/store/RelayRecordSource.js +1 -3
  38. package/lib/store/RelayRecordSourceMapImpl.js +13 -18
  39. package/lib/store/RelayReferenceMarker.js +125 -14
  40. package/lib/store/RelayResponseNormalizer.js +261 -66
  41. package/lib/store/RelayStoreReactFlightUtils.js +47 -0
  42. package/lib/store/RelayStoreUtils.js +1 -0
  43. package/lib/store/StoreInspector.js +8 -8
  44. package/lib/store/TypeID.js +28 -0
  45. package/lib/store/cloneRelayScalarHandleSourceField.js +44 -0
  46. package/lib/store/defaultRequiredFieldLogger.js +18 -0
  47. package/lib/store/normalizeRelayPayload.js +6 -2
  48. package/lib/store/readInlineData.js +1 -1
  49. package/lib/subscription/requestSubscription.js +4 -3
  50. package/lib/util/NormalizationNode.js +1 -5
  51. package/lib/util/RelayConcreteNode.js +11 -6
  52. package/lib/util/RelayError.js +39 -9
  53. package/lib/util/RelayFeatureFlags.js +6 -3
  54. package/lib/util/RelayReplaySubject.js +3 -3
  55. package/lib/util/createPayloadFor3DField.js +7 -2
  56. package/lib/util/getFragmentIdentifier.js +12 -3
  57. package/lib/util/getOperation.js +33 -0
  58. package/lib/util/getRequestIdentifier.js +2 -2
  59. package/lib/util/isEmptyObject.js +25 -0
  60. package/lib/util/recycleNodesInto.js +6 -7
  61. package/lib/util/reportMissingRequiredFields.js +48 -0
  62. package/mutations/RelayDeclarativeMutationConfig.js.flow +380 -0
  63. package/mutations/RelayRecordProxy.js.flow +165 -0
  64. package/mutations/RelayRecordSourceMutator.js.flow +238 -0
  65. package/mutations/RelayRecordSourceProxy.js.flow +164 -0
  66. package/mutations/RelayRecordSourceSelectorProxy.js.flow +119 -0
  67. package/mutations/applyOptimisticMutation.js.flow +76 -0
  68. package/mutations/commitLocalUpdate.js.flow +24 -0
  69. package/mutations/commitMutation.js.flow +181 -0
  70. package/mutations/validateMutation.js.flow +242 -0
  71. package/network/ConvertToExecuteFunction.js.flow +49 -0
  72. package/network/RelayNetwork.js.flow +84 -0
  73. package/network/RelayNetworkTypes.js.flow +145 -0
  74. package/network/RelayObservable.js.flow +634 -0
  75. package/network/RelayQueryResponseCache.js.flow +111 -0
  76. package/package.json +2 -2
  77. package/query/GraphQLTag.js.flow +168 -0
  78. package/query/PreloadableQueryRegistry.js.flow +65 -0
  79. package/query/fetchQuery.js.flow +47 -0
  80. package/query/fetchQueryInternal.js.flow +343 -0
  81. package/relay-runtime.js +2 -2
  82. package/relay-runtime.min.js +2 -2
  83. package/store/ClientID.js.flow +43 -0
  84. package/store/DataChecker.js.flow +568 -0
  85. package/store/RelayConcreteVariables.js.flow +96 -0
  86. package/store/RelayModernEnvironment.js.flow +571 -0
  87. package/store/RelayModernFragmentSpecResolver.js.flow +438 -0
  88. package/store/RelayModernOperationDescriptor.js.flow +92 -0
  89. package/store/RelayModernQueryExecutor.js.flow +1345 -0
  90. package/store/RelayModernRecord.js.flow +403 -0
  91. package/store/RelayModernSelector.js.flow +455 -0
  92. package/store/RelayModernStore.js.flow +858 -0
  93. package/store/RelayOperationTracker.js.flow +164 -0
  94. package/store/RelayOptimisticRecordSource.js.flow +119 -0
  95. package/store/RelayPublishQueue.js.flow +401 -0
  96. package/store/RelayReader.js.flow +638 -0
  97. package/store/RelayRecordSource.js.flow +29 -0
  98. package/store/RelayRecordSourceMapImpl.js.flow +87 -0
  99. package/store/RelayRecordState.js.flow +37 -0
  100. package/store/RelayReferenceMarker.js.flow +324 -0
  101. package/store/RelayResponseNormalizer.js.flow +791 -0
  102. package/store/RelayStoreReactFlightUtils.js.flow +64 -0
  103. package/store/RelayStoreTypes.js.flow +958 -0
  104. package/store/RelayStoreUtils.js.flow +219 -0
  105. package/store/StoreInspector.js.flow +171 -0
  106. package/store/TypeID.js.flow +28 -0
  107. package/store/ViewerPattern.js.flow +26 -0
  108. package/store/cloneRelayHandleSourceField.js.flow +66 -0
  109. package/store/cloneRelayScalarHandleSourceField.js.flow +62 -0
  110. package/store/createFragmentSpecResolver.js.flow +55 -0
  111. package/store/createRelayContext.js.flow +44 -0
  112. package/store/defaultGetDataID.js.flow +27 -0
  113. package/store/defaultRequiredFieldLogger.js.flow +23 -0
  114. package/store/hasOverlappingIDs.js.flow +34 -0
  115. package/store/isRelayModernEnvironment.js.flow +27 -0
  116. package/store/normalizeRelayPayload.js.flow +51 -0
  117. package/store/readInlineData.js.flow +75 -0
  118. package/subscription/requestSubscription.js.flow +103 -0
  119. package/util/JSResourceTypes.flow.js.flow +20 -0
  120. package/util/NormalizationNode.js.flow +213 -0
  121. package/util/ReaderNode.js.flow +227 -0
  122. package/util/RelayConcreteNode.js.flow +99 -0
  123. package/util/RelayDefaultHandleKey.js.flow +17 -0
  124. package/util/RelayError.js.flow +62 -0
  125. package/util/RelayFeatureFlags.js.flow +37 -0
  126. package/util/RelayProfiler.js.flow +284 -0
  127. package/util/RelayReplaySubject.js.flow +135 -0
  128. package/util/RelayRuntimeTypes.js.flow +72 -0
  129. package/util/createPayloadFor3DField.js.flow +43 -0
  130. package/util/deepFreeze.js.flow +36 -0
  131. package/util/generateID.js.flow +21 -0
  132. package/util/getFragmentIdentifier.js.flow +76 -0
  133. package/util/getOperation.js.flow +40 -0
  134. package/util/getRelayHandleKey.js.flow +41 -0
  135. package/util/getRequestIdentifier.js.flow +42 -0
  136. package/util/isEmptyObject.js.flow +25 -0
  137. package/util/isPromise.js.flow +21 -0
  138. package/util/isScalarAndEqual.js.flow +26 -0
  139. package/util/recycleNodesInto.js.flow +87 -0
  140. package/util/reportMissingRequiredFields.js.flow +51 -0
  141. package/util/resolveImmediate.js.flow +30 -0
  142. 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};