relay-runtime 11.0.0-rc.0 → 12.0.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 (128) hide show
  1. package/handlers/connection/ConnectionHandler.js.flow +7 -0
  2. package/handlers/connection/MutationHandlers.js.flow +28 -0
  3. package/index.js +1 -1
  4. package/index.js.flow +20 -3
  5. package/lib/handlers/RelayDefaultHandlerProvider.js +1 -1
  6. package/lib/handlers/connection/ConnectionHandler.js +12 -6
  7. package/lib/handlers/connection/MutationHandlers.js +67 -8
  8. package/lib/index.js +15 -0
  9. package/lib/multi-actor-environment/ActorIdentifier.js +33 -0
  10. package/lib/multi-actor-environment/ActorSpecificEnvironment.js +148 -0
  11. package/lib/multi-actor-environment/ActorUtils.js +27 -0
  12. package/lib/multi-actor-environment/MultiActorEnvironment.js +406 -0
  13. package/lib/multi-actor-environment/MultiActorEnvironmentTypes.js +11 -0
  14. package/lib/multi-actor-environment/index.js +21 -0
  15. package/lib/mutations/RelayRecordProxy.js +1 -1
  16. package/lib/mutations/RelayRecordSourceMutator.js +1 -1
  17. package/lib/mutations/RelayRecordSourceProxy.js +1 -1
  18. package/lib/mutations/RelayRecordSourceSelectorProxy.js +7 -2
  19. package/lib/mutations/applyOptimisticMutation.js +1 -1
  20. package/lib/mutations/commitMutation.js +5 -2
  21. package/lib/mutations/validateMutation.js +39 -17
  22. package/lib/network/RelayNetwork.js +1 -1
  23. package/lib/network/RelayObservable.js +3 -1
  24. package/lib/network/RelayQueryResponseCache.js +20 -3
  25. package/lib/network/wrapNetworkWithLogObserver.js +78 -0
  26. package/lib/query/GraphQLTag.js +1 -1
  27. package/lib/query/fetchQuery.js +1 -1
  28. package/lib/query/fetchQueryInternal.js +1 -1
  29. package/lib/store/DataChecker.js +132 -50
  30. package/lib/store/{RelayModernQueryExecutor.js → OperationExecutor.js} +524 -187
  31. package/lib/store/RelayConcreteVariables.js +29 -4
  32. package/lib/store/RelayModernEnvironment.js +137 -220
  33. package/lib/store/RelayModernFragmentSpecResolver.js +49 -23
  34. package/lib/store/RelayModernRecord.js +36 -2
  35. package/lib/store/RelayModernSelector.js +1 -1
  36. package/lib/store/RelayModernStore.js +53 -22
  37. package/lib/store/RelayOperationTracker.js +34 -24
  38. package/lib/store/RelayPublishQueue.js +30 -8
  39. package/lib/store/RelayReader.js +177 -29
  40. package/lib/store/RelayRecordSource.js +87 -3
  41. package/lib/store/RelayReferenceMarker.js +53 -28
  42. package/lib/store/RelayResponseNormalizer.js +247 -108
  43. package/lib/store/RelayStoreReactFlightUtils.js +7 -11
  44. package/lib/store/RelayStoreSubscriptions.js +8 -5
  45. package/lib/store/RelayStoreUtils.js +10 -4
  46. package/lib/store/ResolverCache.js +213 -0
  47. package/lib/store/ResolverFragments.js +57 -0
  48. package/lib/store/cloneRelayHandleSourceField.js +1 -1
  49. package/lib/store/cloneRelayScalarHandleSourceField.js +1 -1
  50. package/lib/store/createRelayContext.js +2 -2
  51. package/lib/store/defaultGetDataID.js +3 -1
  52. package/lib/store/readInlineData.js +1 -1
  53. package/lib/subscription/requestSubscription.js +32 -6
  54. package/lib/util/RelayConcreteNode.js +3 -0
  55. package/lib/util/RelayFeatureFlags.js +5 -4
  56. package/lib/util/RelayProfiler.js +17 -187
  57. package/lib/util/RelayReplaySubject.js +22 -7
  58. package/lib/util/deepFreeze.js +1 -0
  59. package/lib/util/getPaginationMetadata.js +41 -0
  60. package/lib/util/getPaginationVariables.js +67 -0
  61. package/lib/util/getPendingOperationsForFragment.js +55 -0
  62. package/lib/util/getRefetchMetadata.js +36 -0
  63. package/lib/util/getRelayHandleKey.js +1 -1
  64. package/lib/util/getRequestIdentifier.js +1 -1
  65. package/lib/util/getValueAtPath.js +51 -0
  66. package/lib/util/isEmptyObject.js +1 -1
  67. package/lib/util/registerEnvironmentWithDevTools.js +26 -0
  68. package/lib/util/withDuration.js +31 -0
  69. package/multi-actor-environment/ActorIdentifier.js.flow +43 -0
  70. package/multi-actor-environment/ActorSpecificEnvironment.js.flow +217 -0
  71. package/multi-actor-environment/ActorUtils.js.flow +33 -0
  72. package/multi-actor-environment/MultiActorEnvironment.js.flow +485 -0
  73. package/multi-actor-environment/MultiActorEnvironmentTypes.js.flow +245 -0
  74. package/multi-actor-environment/index.js.flow +27 -0
  75. package/mutations/RelayRecordSourceSelectorProxy.js.flow +7 -2
  76. package/mutations/commitMutation.js.flow +3 -1
  77. package/mutations/validateMutation.js.flow +42 -16
  78. package/network/RelayNetworkTypes.js.flow +17 -8
  79. package/network/RelayObservable.js.flow +2 -0
  80. package/network/RelayQueryResponseCache.js.flow +31 -17
  81. package/network/wrapNetworkWithLogObserver.js.flow +99 -0
  82. package/package.json +3 -2
  83. package/relay-runtime.js +2 -2
  84. package/relay-runtime.min.js +2 -2
  85. package/store/ClientID.js.flow +5 -1
  86. package/store/DataChecker.js.flow +148 -44
  87. package/store/{RelayModernQueryExecutor.js.flow → OperationExecutor.js.flow} +578 -237
  88. package/store/RelayConcreteVariables.js.flow +31 -1
  89. package/store/RelayModernEnvironment.js.flow +132 -220
  90. package/store/RelayModernFragmentSpecResolver.js.flow +40 -14
  91. package/store/RelayModernOperationDescriptor.js.flow +9 -3
  92. package/store/RelayModernRecord.js.flow +49 -0
  93. package/store/RelayModernStore.js.flow +57 -17
  94. package/store/RelayOperationTracker.js.flow +56 -34
  95. package/store/RelayPublishQueue.js.flow +37 -11
  96. package/store/RelayReader.js.flow +186 -27
  97. package/store/RelayRecordSource.js.flow +72 -6
  98. package/store/RelayReferenceMarker.js.flow +51 -21
  99. package/store/RelayResponseNormalizer.js.flow +251 -67
  100. package/store/RelayStoreReactFlightUtils.js.flow +6 -9
  101. package/store/RelayStoreSubscriptions.js.flow +10 -3
  102. package/store/RelayStoreTypes.js.flow +144 -21
  103. package/store/RelayStoreUtils.js.flow +19 -4
  104. package/store/ResolverCache.js.flow +247 -0
  105. package/store/ResolverFragments.js.flow +128 -0
  106. package/store/createRelayContext.js.flow +1 -1
  107. package/store/defaultGetDataID.js.flow +3 -1
  108. package/subscription/requestSubscription.js.flow +43 -8
  109. package/util/NormalizationNode.js.flow +16 -3
  110. package/util/ReaderNode.js.flow +29 -2
  111. package/util/RelayConcreteNode.js.flow +3 -0
  112. package/util/RelayFeatureFlags.js.flow +10 -6
  113. package/util/RelayProfiler.js.flow +22 -194
  114. package/util/RelayReplaySubject.js.flow +7 -6
  115. package/util/RelayRuntimeTypes.js.flow +4 -2
  116. package/util/deepFreeze.js.flow +2 -1
  117. package/util/getPaginationMetadata.js.flow +74 -0
  118. package/util/getPaginationVariables.js.flow +112 -0
  119. package/util/getPendingOperationsForFragment.js.flow +62 -0
  120. package/util/getRefetchMetadata.js.flow +80 -0
  121. package/util/getValueAtPath.js.flow +46 -0
  122. package/util/isEmptyObject.js.flow +2 -1
  123. package/util/registerEnvironmentWithDevTools.js.flow +33 -0
  124. package/util/withDuration.js.flow +32 -0
  125. package/lib/store/RelayRecordSourceMapImpl.js +0 -107
  126. package/lib/store/RelayStoreSubscriptionsUsingMapByID.js +0 -318
  127. package/store/RelayRecordSourceMapImpl.js.flow +0 -91
  128. package/store/RelayStoreSubscriptionsUsingMapByID.js.flow +0 -283
@@ -14,14 +14,19 @@
14
14
 
15
15
  const RelayFeatureFlags = require('../util/RelayFeatureFlags');
16
16
  const RelayModernRecord = require('./RelayModernRecord');
17
- const RelayProfiler = require('../util/RelayProfiler');
18
17
 
19
18
  const areEqual = require('areEqual');
20
19
  const invariant = require('invariant');
21
20
  const warning = require('warning');
22
21
 
23
22
  const {
23
+ ACTOR_IDENTIFIER_FIELD_NAME,
24
+ getActorIdentifierFromPayload,
25
+ } = require('../multi-actor-environment/ActorUtils');
26
+ const {
27
+ ACTOR_CHANGE,
24
28
  CONDITION,
29
+ CLIENT_COMPONENT,
25
30
  CLIENT_EXTENSION,
26
31
  DEFER,
27
32
  FLIGHT_FIELD,
@@ -36,10 +41,11 @@ const {
36
41
  TYPE_DISCRIMINATOR,
37
42
  } = require('../util/RelayConcreteNode');
38
43
  const {generateClientID, isClientID} = require('./ClientID');
44
+ const {getLocalVariables} = require('./RelayConcreteVariables');
39
45
  const {createNormalizationSelector} = require('./RelayModernSelector');
40
46
  const {
41
47
  refineToReactFlightPayloadData,
42
- REACT_FLIGHT_QUERIES_STORAGE_KEY,
48
+ REACT_FLIGHT_EXECUTABLE_DEFINITIONS_STORAGE_KEY,
43
49
  REACT_FLIGHT_TREE_STORAGE_KEY,
44
50
  REACT_FLIGHT_TYPE_NAME,
45
51
  } = require('./RelayStoreReactFlightUtils');
@@ -55,8 +61,10 @@ const {
55
61
  } = require('./RelayStoreUtils');
56
62
  const {generateTypeID, TYPE_SCHEMA_TYPE} = require('./TypeID');
57
63
 
64
+ import type {ActorIdentifier} from '../multi-actor-environment/ActorIdentifier';
58
65
  import type {PayloadData} from '../network/RelayNetworkTypes';
59
66
  import type {
67
+ NormalizationActorChange,
60
68
  NormalizationDefer,
61
69
  NormalizationFlightField,
62
70
  NormalizationLinkedField,
@@ -67,20 +75,20 @@ import type {
67
75
  } from '../util/NormalizationNode';
68
76
  import type {DataID, Variables} from '../util/RelayRuntimeTypes';
69
77
  import type {
78
+ FollowupPayload,
70
79
  HandleFieldPayload,
71
80
  IncrementalDataPlaceholder,
72
- ModuleImportPayload,
73
81
  MutableRecordSource,
74
82
  NormalizationSelector,
75
- ReactFlightReachableQuery,
76
83
  ReactFlightPayloadDeserializer,
84
+ ReactFlightReachableExecutableDefinitions,
77
85
  ReactFlightServerErrorHandler,
78
86
  Record,
79
87
  RelayResponsePayload,
80
88
  } from './RelayStoreTypes';
81
89
 
82
90
  export type GetDataID = (
83
- fieldValue: {[string]: mixed, ...},
91
+ fieldValue: interface {[string]: mixed},
84
92
  typeName: string,
85
93
  ) => mixed;
86
94
 
@@ -90,6 +98,8 @@ export type NormalizationOptions = {|
90
98
  +path?: $ReadOnlyArray<string>,
91
99
  +reactFlightPayloadDeserializer?: ?ReactFlightPayloadDeserializer,
92
100
  +reactFlightServerErrorHandler?: ?ReactFlightServerErrorHandler,
101
+ +shouldProcessClientComponents?: ?boolean,
102
+ +actorIdentifier?: ?ActorIdentifier,
93
103
  |};
94
104
 
95
105
  /**
@@ -117,37 +127,41 @@ function normalize(
117
127
  * Helper for handling payloads.
118
128
  */
119
129
  class RelayResponseNormalizer {
130
+ _actorIdentifier: ?ActorIdentifier;
120
131
  _getDataId: GetDataID;
121
132
  _handleFieldPayloads: Array<HandleFieldPayload>;
122
133
  _treatMissingFieldsAsNull: boolean;
123
134
  _incrementalPlaceholders: Array<IncrementalDataPlaceholder>;
124
135
  _isClientExtension: boolean;
125
136
  _isUnmatchedAbstractType: boolean;
126
- _moduleImportPayloads: Array<ModuleImportPayload>;
137
+ _followupPayloads: Array<FollowupPayload>;
127
138
  _path: Array<string>;
128
139
  _recordSource: MutableRecordSource;
129
140
  _variables: Variables;
130
141
  _reactFlightPayloadDeserializer: ?ReactFlightPayloadDeserializer;
131
142
  _reactFlightServerErrorHandler: ?ReactFlightServerErrorHandler;
143
+ _shouldProcessClientComponents: ?boolean;
132
144
 
133
145
  constructor(
134
146
  recordSource: MutableRecordSource,
135
147
  variables: Variables,
136
148
  options: NormalizationOptions,
137
149
  ) {
150
+ this._actorIdentifier = options.actorIdentifier;
138
151
  this._getDataId = options.getDataID;
139
152
  this._handleFieldPayloads = [];
140
153
  this._treatMissingFieldsAsNull = options.treatMissingFieldsAsNull;
141
154
  this._incrementalPlaceholders = [];
142
155
  this._isClientExtension = false;
143
156
  this._isUnmatchedAbstractType = false;
144
- this._moduleImportPayloads = [];
157
+ this._followupPayloads = [];
145
158
  this._path = options.path ? [...options.path] : [];
146
159
  this._recordSource = recordSource;
147
160
  this._variables = variables;
148
161
  this._reactFlightPayloadDeserializer =
149
162
  options.reactFlightPayloadDeserializer;
150
163
  this._reactFlightServerErrorHandler = options.reactFlightServerErrorHandler;
164
+ this._shouldProcessClientComponents = options.shouldProcessClientComponents;
151
165
  }
152
166
 
153
167
  normalizeResponse(
@@ -166,7 +180,7 @@ class RelayResponseNormalizer {
166
180
  errors: null,
167
181
  fieldPayloads: this._handleFieldPayloads,
168
182
  incrementalPlaceholders: this._incrementalPlaceholders,
169
- moduleImportPayloads: this._moduleImportPayloads,
183
+ followupPayloads: this._followupPayloads,
170
184
  source: this._recordSource,
171
185
  isFinal: false,
172
186
  };
@@ -178,6 +192,7 @@ class RelayResponseNormalizer {
178
192
  'RelayResponseNormalizer(): Undefined variable `%s`.',
179
193
  name,
180
194
  );
195
+ // $FlowFixMe[cannot-write]
181
196
  return this._variables[name];
182
197
  }
183
198
 
@@ -204,13 +219,22 @@ class RelayResponseNormalizer {
204
219
  this._normalizeField(node, selection, record, data);
205
220
  break;
206
221
  case CONDITION:
207
- const conditionValue = this._getVariableValue(selection.condition);
222
+ const conditionValue = Boolean(
223
+ this._getVariableValue(selection.condition),
224
+ );
208
225
  if (conditionValue === selection.passingValue) {
209
226
  this._traverseSelections(selection, record, data);
210
227
  }
211
228
  break;
212
229
  case FRAGMENT_SPREAD: {
230
+ const prevVariables = this._variables;
231
+ this._variables = getLocalVariables(
232
+ this._variables,
233
+ selection.fragment.argumentDefinitions,
234
+ selection.args,
235
+ );
213
236
  this._traverseSelections(selection.fragment, record, data);
237
+ this._variables = prevVariables;
214
238
  break;
215
239
  }
216
240
  case INLINE_FRAGMENT: {
@@ -220,7 +244,7 @@ class RelayResponseNormalizer {
220
244
  if (typeName === selection.type) {
221
245
  this._traverseSelections(selection, record, data);
222
246
  }
223
- } else if (RelayFeatureFlags.ENABLE_PRECISE_TYPE_REFINEMENT) {
247
+ } else {
224
248
  const implementsInterface = data.hasOwnProperty(abstractKey);
225
249
  const typeName = RelayModernRecord.getType(record);
226
250
  const typeID = generateTypeID(typeName);
@@ -237,36 +261,24 @@ class RelayResponseNormalizer {
237
261
  if (implementsInterface) {
238
262
  this._traverseSelections(selection, record, data);
239
263
  }
240
- } else {
241
- // legacy behavior for abstract refinements: always normalize even
242
- // if the type doesn't conform, but track if the type matches or not
243
- // for determining whether response fields are expected to be present
244
- const implementsInterface = data.hasOwnProperty(abstractKey);
245
- const parentIsUnmatchedAbstractType = this._isUnmatchedAbstractType;
246
- this._isUnmatchedAbstractType =
247
- this._isUnmatchedAbstractType || !implementsInterface;
248
- this._traverseSelections(selection, record, data);
249
- this._isUnmatchedAbstractType = parentIsUnmatchedAbstractType;
250
264
  }
251
265
  break;
252
266
  }
253
267
  case TYPE_DISCRIMINATOR: {
254
- if (RelayFeatureFlags.ENABLE_PRECISE_TYPE_REFINEMENT) {
255
- const {abstractKey} = selection;
256
- const implementsInterface = data.hasOwnProperty(abstractKey);
257
- const typeName = RelayModernRecord.getType(record);
258
- const typeID = generateTypeID(typeName);
259
- let typeRecord = this._recordSource.get(typeID);
260
- if (typeRecord == null) {
261
- typeRecord = RelayModernRecord.create(typeID, TYPE_SCHEMA_TYPE);
262
- this._recordSource.set(typeID, typeRecord);
263
- }
264
- RelayModernRecord.setValue(
265
- typeRecord,
266
- abstractKey,
267
- implementsInterface,
268
- );
268
+ const {abstractKey} = selection;
269
+ const implementsInterface = data.hasOwnProperty(abstractKey);
270
+ const typeName = RelayModernRecord.getType(record);
271
+ const typeID = generateTypeID(typeName);
272
+ let typeRecord = this._recordSource.get(typeID);
273
+ if (typeRecord == null) {
274
+ typeRecord = RelayModernRecord.create(typeID, TYPE_SCHEMA_TYPE);
275
+ this._recordSource.set(typeID, typeRecord);
269
276
  }
277
+ RelayModernRecord.setValue(
278
+ typeRecord,
279
+ abstractKey,
280
+ implementsInterface,
281
+ );
270
282
  break;
271
283
  }
272
284
  case LINKED_HANDLE:
@@ -277,13 +289,17 @@ class RelayResponseNormalizer {
277
289
  const fieldKey = getStorageKey(selection, this._variables);
278
290
  const handleKey = getHandleStorageKey(selection, this._variables);
279
291
  this._handleFieldPayloads.push({
292
+ /* $FlowFixMe[class-object-subtyping] added when improving typing
293
+ * for this parameters */
280
294
  args,
281
295
  dataID: RelayModernRecord.getDataID(record),
282
296
  fieldKey,
283
297
  handle: selection.handle,
284
298
  handleKey,
285
299
  handleArgs: selection.handleArgs
286
- ? getArgumentValues(selection.handleArgs, this._variables)
300
+ ? /* $FlowFixMe[class-object-subtyping] added when improving typing
301
+ * for this parameters */
302
+ getArgumentValues(selection.handleArgs, this._variables)
287
303
  : {},
288
304
  });
289
305
  break;
@@ -302,6 +318,12 @@ class RelayResponseNormalizer {
302
318
  this._traverseSelections(selection, record, data);
303
319
  this._isClientExtension = isClientExtension;
304
320
  break;
321
+ case CLIENT_COMPONENT:
322
+ if (this._shouldProcessClientComponents === false) {
323
+ break;
324
+ }
325
+ this._traverseSelections(selection.fragment, record, data);
326
+ break;
305
327
  case FLIGHT_FIELD:
306
328
  if (RelayFeatureFlags.ENABLE_REACT_FLIGHT_COMPONENT_FIELD) {
307
329
  this._normalizeFlightField(node, selection, record, data);
@@ -309,6 +331,9 @@ class RelayResponseNormalizer {
309
331
  throw new Error('Flight fields are not yet supported.');
310
332
  }
311
333
  break;
334
+ case ACTOR_CHANGE:
335
+ this._normalizeActorChange(node, selection, record, data);
336
+ break;
312
337
  default:
313
338
  (selection: empty);
314
339
  invariant(
@@ -352,6 +377,7 @@ class RelayResponseNormalizer {
352
377
  this._variables,
353
378
  ),
354
379
  typeName: RelayModernRecord.getType(record),
380
+ actorIdentifier: this._actorIdentifier,
355
381
  });
356
382
  }
357
383
  }
@@ -384,6 +410,7 @@ class RelayResponseNormalizer {
384
410
  parentID: RelayModernRecord.getDataID(record),
385
411
  node: stream,
386
412
  variables: this._variables,
413
+ actorIdentifier: this._actorIdentifier,
387
414
  });
388
415
  }
389
416
  }
@@ -414,13 +441,16 @@ class RelayResponseNormalizer {
414
441
  operationReference ?? null,
415
442
  );
416
443
  if (operationReference != null) {
417
- this._moduleImportPayloads.push({
444
+ this._followupPayloads.push({
445
+ kind: 'ModuleImportPayload',
446
+ args: moduleImport.args,
418
447
  data,
419
448
  dataID: RelayModernRecord.getDataID(record),
420
449
  operationReference,
421
450
  path: [...this._path],
422
451
  typeName,
423
452
  variables: this._variables,
453
+ actorIdentifier: this._actorIdentifier,
424
454
  });
425
455
  }
426
456
  }
@@ -513,6 +543,99 @@ class RelayResponseNormalizer {
513
543
  }
514
544
  }
515
545
 
546
+ _normalizeActorChange(
547
+ parent: NormalizationNode,
548
+ selection: NormalizationActorChange,
549
+ record: Record,
550
+ data: PayloadData,
551
+ ) {
552
+ const field = selection.linkedField;
553
+ invariant(
554
+ typeof data === 'object' && data,
555
+ '_normalizeActorChange(): Expected data for field `%s` to be an object.',
556
+ field.name,
557
+ );
558
+ const responseKey = field.alias || field.name;
559
+ const storageKey = getStorageKey(field, this._variables);
560
+ const fieldValue = data[responseKey];
561
+
562
+ if (fieldValue == null) {
563
+ if (fieldValue === undefined) {
564
+ const isOptionalField =
565
+ this._isClientExtension || this._isUnmatchedAbstractType;
566
+
567
+ if (isOptionalField) {
568
+ return;
569
+ } else if (!this._treatMissingFieldsAsNull) {
570
+ if (__DEV__) {
571
+ warning(
572
+ false,
573
+ 'RelayResponseNormalizer: Payload did not contain a value ' +
574
+ 'for field `%s: %s`. Check that you are parsing with the same ' +
575
+ 'query that was used to fetch the payload.',
576
+ responseKey,
577
+ storageKey,
578
+ );
579
+ }
580
+ return;
581
+ }
582
+ }
583
+ RelayModernRecord.setValue(record, storageKey, null);
584
+ return;
585
+ }
586
+
587
+ const actorIdentifier = getActorIdentifierFromPayload(fieldValue);
588
+ if (actorIdentifier == null) {
589
+ if (__DEV__) {
590
+ warning(
591
+ false,
592
+ 'RelayResponseNormalizer: Payload did not contain a value ' +
593
+ 'for field `%s`. Check that you are parsing with the same ' +
594
+ 'query that was used to fetch the payload. Payload is `%s`.',
595
+ ACTOR_IDENTIFIER_FIELD_NAME,
596
+ JSON.stringify(fieldValue, null, 2),
597
+ );
598
+ }
599
+ RelayModernRecord.setValue(record, storageKey, null);
600
+ return;
601
+ }
602
+
603
+ // $FlowFixMe[incompatible-call]
604
+ const typeName = field.concreteType ?? this._getRecordType(fieldValue);
605
+ const nextID =
606
+ this._getDataId(
607
+ // $FlowFixMe[incompatible-call]
608
+ fieldValue,
609
+ typeName,
610
+ ) ||
611
+ RelayModernRecord.getLinkedRecordID(record, storageKey) ||
612
+ generateClientID(RelayModernRecord.getDataID(record), storageKey);
613
+
614
+ invariant(
615
+ typeof nextID === 'string',
616
+ 'RelayResponseNormalizer: Expected id on field `%s` to be a string.',
617
+ storageKey,
618
+ );
619
+
620
+ RelayModernRecord.setActorLinkedRecordID(
621
+ record,
622
+ storageKey,
623
+ actorIdentifier,
624
+ nextID,
625
+ );
626
+
627
+ this._followupPayloads.push({
628
+ kind: 'ActorPayload',
629
+ data: (fieldValue: $FlowFixMe),
630
+ dataID: nextID,
631
+ path: [...this._path, responseKey],
632
+ typeName,
633
+ variables: this._variables,
634
+ node: field,
635
+ actorIdentifier,
636
+ });
637
+ }
638
+
516
639
  _normalizeFlightField(
517
640
  parent: NormalizationNode,
518
641
  selection: NormalizationFlightField,
@@ -524,11 +647,37 @@ class RelayResponseNormalizer {
524
647
  const fieldValue = data[responseKey];
525
648
 
526
649
  if (fieldValue == null) {
650
+ if (fieldValue === undefined) {
651
+ // Flight field may be missing in the response if:
652
+ // - It is inside an abstract type refinement where the concrete type does
653
+ // not conform to the interface/union.
654
+ // However an otherwise-required field may also be missing if the server
655
+ // is configured to skip fields with `null` values, in which case the
656
+ // client is assumed to be correctly configured with
657
+ // treatMissingFieldsAsNull=true.
658
+ if (this._isUnmatchedAbstractType) {
659
+ // Field not expected to exist regardless of whether the server is pruning null
660
+ // fields or not.
661
+ return;
662
+ } else {
663
+ // Not optional and the server is not pruning null fields: field is expected
664
+ // to be present
665
+ invariant(
666
+ this._treatMissingFieldsAsNull,
667
+ 'RelayResponseNormalizer: Payload did not contain a value for ' +
668
+ 'field `%s: %s`. Check that you are parsing with the same ' +
669
+ 'query that was used to fetch the payload.',
670
+ responseKey,
671
+ storageKey,
672
+ );
673
+ }
674
+ }
527
675
  RelayModernRecord.setValue(record, storageKey, null);
528
676
  return;
529
677
  }
530
678
 
531
679
  const reactFlightPayload = refineToReactFlightPayloadData(fieldValue);
680
+ const reactFlightPayloadDeserializer = this._reactFlightPayloadDeserializer;
532
681
 
533
682
  invariant(
534
683
  reactFlightPayload != null,
@@ -538,10 +687,10 @@ class RelayResponseNormalizer {
538
687
  fieldValue,
539
688
  );
540
689
  invariant(
541
- typeof this._reactFlightPayloadDeserializer === 'function',
690
+ typeof reactFlightPayloadDeserializer === 'function',
542
691
  'RelayResponseNormalizer: Expected reactFlightPayloadDeserializer to ' +
543
692
  'be a function, got `%s`.',
544
- this._reactFlightPayloadDeserializer,
693
+ reactFlightPayloadDeserializer,
545
694
  );
546
695
 
547
696
  if (reactFlightPayload.errors.length > 0) {
@@ -562,65 +711,103 @@ class RelayResponseNormalizer {
562
711
  }
563
712
  }
564
713
 
565
- // This typically indicates that a fatal server error prevented rows from
566
- // being written. When this occurs, we should not continue normalization of
567
- // the Flight field because the row response is malformed.
568
- //
569
- // Receiving empty rows is OK because it can indicate the start of a stream.
714
+ const reactFlightID = generateClientID(
715
+ RelayModernRecord.getDataID(record),
716
+ getStorageKey(selection, this._variables),
717
+ );
718
+ let reactFlightClientResponseRecord = this._recordSource.get(reactFlightID);
719
+ if (reactFlightClientResponseRecord == null) {
720
+ reactFlightClientResponseRecord = RelayModernRecord.create(
721
+ reactFlightID,
722
+ REACT_FLIGHT_TYPE_NAME,
723
+ );
724
+ this._recordSource.set(reactFlightID, reactFlightClientResponseRecord);
725
+ }
726
+
570
727
  if (reactFlightPayload.tree == null) {
728
+ // This typically indicates that a fatal server error prevented rows from
729
+ // being written. When this occurs, we should not continue normalization of
730
+ // the Flight field because the row response is malformed.
731
+ //
732
+ // Receiving empty rows is OK because it can indicate the start of a stream.
571
733
  warning(
572
734
  false,
573
735
  'RelayResponseNormalizer: Expected `tree` not to be null. This ' +
574
736
  'typically indicates that a fatal server error prevented any Server ' +
575
737
  'Component rows from being written.',
576
738
  );
739
+ // We create the flight record with a null value for the tree
740
+ // and empty reachable definitions
741
+ RelayModernRecord.setValue(
742
+ reactFlightClientResponseRecord,
743
+ REACT_FLIGHT_TREE_STORAGE_KEY,
744
+ null,
745
+ );
746
+ RelayModernRecord.setValue(
747
+ reactFlightClientResponseRecord,
748
+ REACT_FLIGHT_EXECUTABLE_DEFINITIONS_STORAGE_KEY,
749
+ [],
750
+ );
751
+ RelayModernRecord.setLinkedRecordID(record, storageKey, reactFlightID);
577
752
  return;
578
753
  }
579
754
 
580
755
  // We store the deserialized reactFlightClientResponse in a separate
581
756
  // record and link it to the parent record. This is so we can GC the Flight
582
757
  // tree later even if the parent record is still reachable.
583
- const reactFlightClientResponse = this._reactFlightPayloadDeserializer(
758
+ const reactFlightClientResponse = reactFlightPayloadDeserializer(
584
759
  reactFlightPayload.tree,
585
760
  );
586
- const reactFlightID = generateClientID(
587
- RelayModernRecord.getDataID(record),
588
- getStorageKey(selection, this._variables),
589
- );
590
- let reactFlightClientResponseRecord = this._recordSource.get(reactFlightID);
591
- if (reactFlightClientResponseRecord == null) {
592
- reactFlightClientResponseRecord = RelayModernRecord.create(
593
- reactFlightID,
594
- REACT_FLIGHT_TYPE_NAME,
595
- );
596
- this._recordSource.set(reactFlightID, reactFlightClientResponseRecord);
597
- }
761
+
598
762
  RelayModernRecord.setValue(
599
763
  reactFlightClientResponseRecord,
600
764
  REACT_FLIGHT_TREE_STORAGE_KEY,
601
765
  reactFlightClientResponse,
602
766
  );
603
- const reachableQueries: Array<ReactFlightReachableQuery> = [];
767
+
768
+ const reachableExecutableDefinitions: Array<ReactFlightReachableExecutableDefinitions> = [];
604
769
  for (const query of reactFlightPayload.queries) {
605
770
  if (query.response.data != null) {
606
- this._moduleImportPayloads.push({
771
+ this._followupPayloads.push({
772
+ kind: 'ModuleImportPayload',
773
+ args: null,
607
774
  data: query.response.data,
608
775
  dataID: ROOT_ID,
609
776
  operationReference: query.module,
610
777
  path: [],
611
778
  typeName: ROOT_TYPE,
612
779
  variables: query.variables,
780
+ actorIdentifier: this._actorIdentifier,
613
781
  });
614
782
  }
615
- reachableQueries.push({
783
+ reachableExecutableDefinitions.push({
616
784
  module: query.module,
617
785
  variables: query.variables,
618
786
  });
619
787
  }
788
+ for (const fragment of reactFlightPayload.fragments) {
789
+ if (fragment.response.data != null) {
790
+ this._followupPayloads.push({
791
+ kind: 'ModuleImportPayload',
792
+ args: null,
793
+ data: fragment.response.data,
794
+ dataID: fragment.__id,
795
+ operationReference: fragment.module,
796
+ path: [],
797
+ typeName: fragment.__typename,
798
+ variables: fragment.variables,
799
+ actorIdentifier: this._actorIdentifier,
800
+ });
801
+ }
802
+ reachableExecutableDefinitions.push({
803
+ module: fragment.module,
804
+ variables: fragment.variables,
805
+ });
806
+ }
620
807
  RelayModernRecord.setValue(
621
808
  reactFlightClientResponseRecord,
622
- REACT_FLIGHT_QUERIES_STORAGE_KEY,
623
- reachableQueries,
809
+ REACT_FLIGHT_EXECUTABLE_DEFINITIONS_STORAGE_KEY,
810
+ reachableExecutableDefinitions,
624
811
  );
625
812
  RelayModernRecord.setLinkedRecordID(record, storageKey, reactFlightID);
626
813
  }
@@ -826,9 +1013,6 @@ class RelayResponseNormalizer {
826
1013
  }
827
1014
  }
828
1015
 
829
- const instrumentedNormalize: typeof normalize = RelayProfiler.instrument(
830
- 'RelayResponseNormalizer.normalize',
1016
+ module.exports = {
831
1017
  normalize,
832
- );
833
-
834
- module.exports = {normalize: instrumentedNormalize};
1018
+ };
@@ -19,7 +19,9 @@ const {getType} = require('./RelayModernRecord');
19
19
  import type {ReactFlightPayloadData} from '../network/RelayNetworkTypes';
20
20
  import type {ReactFlightClientResponse, Record} from './RelayStoreTypes';
21
21
 
22
- const REACT_FLIGHT_QUERIES_STORAGE_KEY = 'queries';
22
+ // Reachable (client) executable definitions encountered while server component
23
+ // rendering
24
+ const REACT_FLIGHT_EXECUTABLE_DEFINITIONS_STORAGE_KEY = 'executableDefinitions';
23
25
  const REACT_FLIGHT_TREE_STORAGE_KEY = 'tree';
24
26
  const REACT_FLIGHT_TYPE_NAME = 'ReactFlightComponent';
25
27
 
@@ -32,6 +34,7 @@ function refineToReactFlightPayloadData(
32
34
  typeof payload.status !== 'string' ||
33
35
  (!Array.isArray(payload.tree) && payload.tree !== null) ||
34
36
  !Array.isArray(payload.queries) ||
37
+ !Array.isArray(payload.fragments) ||
35
38
  !Array.isArray(payload.errors)
36
39
  ) {
37
40
  return null;
@@ -48,17 +51,11 @@ function getReactFlightClientResponse(
48
51
  'got %s.',
49
52
  record,
50
53
  );
51
- const response: ?ReactFlightClientResponse = (record[
52
- REACT_FLIGHT_TREE_STORAGE_KEY
53
- ]: $FlowFixMe);
54
- if (response != null) {
55
- return response;
56
- }
57
- return null;
54
+ return (record[REACT_FLIGHT_TREE_STORAGE_KEY]: $FlowFixMe);
58
55
  }
59
56
 
60
57
  module.exports = {
61
- REACT_FLIGHT_QUERIES_STORAGE_KEY,
58
+ REACT_FLIGHT_EXECUTABLE_DEFINITIONS_STORAGE_KEY,
62
59
  REACT_FLIGHT_TREE_STORAGE_KEY,
63
60
  REACT_FLIGHT_TYPE_NAME,
64
61
  getReactFlightClientResponse,
@@ -29,6 +29,7 @@ import type {
29
29
  Snapshot,
30
30
  StoreSubscriptions,
31
31
  } from './RelayStoreTypes';
32
+ import type {ResolverCache} from './ResolverCache';
32
33
 
33
34
  type Subscription = {|
34
35
  callback: (snapshot: Snapshot) => void,
@@ -40,10 +41,12 @@ type Subscription = {|
40
41
  class RelayStoreSubscriptions implements StoreSubscriptions {
41
42
  _subscriptions: Set<Subscription>;
42
43
  __log: ?LogFunction;
44
+ _resolverCache: ResolverCache;
43
45
 
44
- constructor(log?: ?LogFunction) {
46
+ constructor(log?: ?LogFunction, resolverCache: ResolverCache) {
45
47
  this._subscriptions = new Set();
46
48
  this.__log = log;
49
+ this._resolverCache = resolverCache;
47
50
  }
48
51
 
49
52
  subscribe(
@@ -77,7 +80,11 @@ class RelayStoreSubscriptions implements StoreSubscriptions {
77
80
  return;
78
81
  }
79
82
  const snapshot = subscription.snapshot;
80
- const backup = RelayReader.read(source, snapshot.selector);
83
+ const backup = RelayReader.read(
84
+ source,
85
+ snapshot.selector,
86
+ this._resolverCache,
87
+ );
81
88
  const nextData = recycleNodesInto(snapshot.data, backup.data);
82
89
  (backup: $FlowFixMe).data = nextData; // backup owns the snapshot and can safely mutate
83
90
  subscription.backup = backup;
@@ -150,7 +157,7 @@ class RelayStoreSubscriptions implements StoreSubscriptions {
150
157
  }
151
158
  let nextSnapshot: Snapshot =
152
159
  hasOverlappingUpdates || !backup
153
- ? RelayReader.read(source, snapshot.selector)
160
+ ? RelayReader.read(source, snapshot.selector, this._resolverCache)
154
161
  : backup;
155
162
  const nextData = recycleNodesInto(snapshot.data, nextSnapshot.data);
156
163
  nextSnapshot = ({