relay-runtime 11.0.2 → 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 (100) hide show
  1. package/index.js +1 -1
  2. package/index.js.flow +16 -1
  3. package/lib/index.js +15 -0
  4. package/lib/multi-actor-environment/ActorIdentifier.js +11 -1
  5. package/lib/multi-actor-environment/ActorSpecificEnvironment.js +59 -19
  6. package/lib/multi-actor-environment/ActorUtils.js +27 -0
  7. package/lib/multi-actor-environment/MultiActorEnvironment.js +305 -55
  8. package/lib/multi-actor-environment/index.js +5 -1
  9. package/lib/mutations/RelayRecordSourceSelectorProxy.js +6 -1
  10. package/lib/mutations/commitMutation.js +4 -1
  11. package/lib/mutations/validateMutation.js +6 -1
  12. package/lib/network/RelayObservable.js +3 -1
  13. package/lib/network/RelayQueryResponseCache.js +19 -3
  14. package/lib/network/wrapNetworkWithLogObserver.js +78 -0
  15. package/lib/store/DataChecker.js +110 -40
  16. package/lib/store/OperationExecutor.js +478 -204
  17. package/lib/store/RelayConcreteVariables.js +21 -0
  18. package/lib/store/RelayModernEnvironment.js +41 -85
  19. package/lib/store/RelayModernFragmentSpecResolver.js +48 -22
  20. package/lib/store/RelayModernRecord.js +35 -1
  21. package/lib/store/RelayModernStore.js +48 -14
  22. package/lib/store/RelayOperationTracker.js +33 -23
  23. package/lib/store/RelayPublishQueue.js +23 -5
  24. package/lib/store/RelayReader.js +138 -44
  25. package/lib/store/RelayRecordSource.js +87 -3
  26. package/lib/store/RelayReferenceMarker.js +28 -15
  27. package/lib/store/RelayResponseNormalizer.js +164 -91
  28. package/lib/store/RelayStoreReactFlightUtils.js +1 -7
  29. package/lib/store/RelayStoreSubscriptions.js +8 -5
  30. package/lib/store/RelayStoreUtils.js +7 -2
  31. package/lib/store/ResolverCache.js +213 -0
  32. package/lib/store/ResolverFragments.js +1 -1
  33. package/lib/store/createRelayContext.js +1 -1
  34. package/lib/subscription/requestSubscription.js +27 -29
  35. package/lib/util/RelayConcreteNode.js +1 -0
  36. package/lib/util/RelayFeatureFlags.js +3 -5
  37. package/lib/util/RelayReplaySubject.js +21 -6
  38. package/lib/util/getPaginationMetadata.js +41 -0
  39. package/lib/util/getPaginationVariables.js +67 -0
  40. package/lib/util/getPendingOperationsForFragment.js +55 -0
  41. package/lib/util/getRefetchMetadata.js +36 -0
  42. package/lib/util/getValueAtPath.js +51 -0
  43. package/lib/util/isEmptyObject.js +1 -1
  44. package/lib/util/registerEnvironmentWithDevTools.js +26 -0
  45. package/lib/util/withDuration.js +31 -0
  46. package/multi-actor-environment/ActorIdentifier.js.flow +17 -1
  47. package/multi-actor-environment/ActorSpecificEnvironment.js.flow +72 -44
  48. package/multi-actor-environment/ActorUtils.js.flow +33 -0
  49. package/multi-actor-environment/MultiActorEnvironment.js.flow +332 -80
  50. package/multi-actor-environment/MultiActorEnvironmentTypes.js.flow +61 -12
  51. package/multi-actor-environment/index.js.flow +3 -0
  52. package/mutations/RelayRecordSourceSelectorProxy.js.flow +7 -2
  53. package/mutations/commitMutation.js.flow +2 -0
  54. package/mutations/validateMutation.js.flow +8 -0
  55. package/network/RelayObservable.js.flow +2 -0
  56. package/network/RelayQueryResponseCache.js.flow +31 -18
  57. package/network/wrapNetworkWithLogObserver.js.flow +99 -0
  58. package/package.json +1 -1
  59. package/relay-runtime.js +2 -2
  60. package/relay-runtime.min.js +2 -2
  61. package/store/ClientID.js.flow +5 -1
  62. package/store/DataChecker.js.flow +126 -35
  63. package/store/OperationExecutor.js.flow +528 -265
  64. package/store/RelayConcreteVariables.js.flow +26 -1
  65. package/store/RelayModernEnvironment.js.flow +41 -94
  66. package/store/RelayModernFragmentSpecResolver.js.flow +40 -14
  67. package/store/RelayModernOperationDescriptor.js.flow +9 -3
  68. package/store/RelayModernRecord.js.flow +49 -0
  69. package/store/RelayModernStore.js.flow +50 -12
  70. package/store/RelayOperationTracker.js.flow +56 -34
  71. package/store/RelayPublishQueue.js.flow +31 -8
  72. package/store/RelayReader.js.flow +148 -42
  73. package/store/RelayRecordSource.js.flow +72 -6
  74. package/store/RelayReferenceMarker.js.flow +29 -12
  75. package/store/RelayResponseNormalizer.js.flow +164 -48
  76. package/store/RelayStoreReactFlightUtils.js.flow +1 -7
  77. package/store/RelayStoreSubscriptions.js.flow +10 -3
  78. package/store/RelayStoreTypes.js.flow +128 -12
  79. package/store/RelayStoreUtils.js.flow +17 -3
  80. package/store/ResolverCache.js.flow +247 -0
  81. package/store/ResolverFragments.js.flow +6 -3
  82. package/store/createRelayContext.js.flow +1 -1
  83. package/subscription/requestSubscription.js.flow +41 -29
  84. package/util/NormalizationNode.js.flow +10 -3
  85. package/util/ReaderNode.js.flow +15 -1
  86. package/util/RelayConcreteNode.js.flow +1 -0
  87. package/util/RelayFeatureFlags.js.flow +8 -10
  88. package/util/RelayReplaySubject.js.flow +7 -6
  89. package/util/getPaginationMetadata.js.flow +74 -0
  90. package/util/getPaginationVariables.js.flow +112 -0
  91. package/util/getPendingOperationsForFragment.js.flow +62 -0
  92. package/util/getRefetchMetadata.js.flow +80 -0
  93. package/util/getValueAtPath.js.flow +46 -0
  94. package/util/isEmptyObject.js.flow +1 -0
  95. package/util/registerEnvironmentWithDevTools.js.flow +33 -0
  96. package/util/withDuration.js.flow +32 -0
  97. package/lib/store/RelayRecordSourceMapImpl.js +0 -107
  98. package/lib/store/RelayStoreSubscriptionsUsingMapByID.js +0 -318
  99. package/store/RelayRecordSourceMapImpl.js.flow +0 -91
  100. package/store/RelayStoreSubscriptionsUsingMapByID.js.flow +0 -283
@@ -22,6 +22,7 @@ const cloneRelayHandleSourceField = require('./cloneRelayHandleSourceField');
22
22
  const getOperation = require('../util/getOperation');
23
23
  const invariant = require('invariant');
24
24
 
25
+ const {getLocalVariables} = require('./RelayConcreteVariables');
25
26
  const {generateTypeID} = require('./TypeID');
26
27
 
27
28
  import type {
@@ -42,6 +43,7 @@ import type {
42
43
  } from './RelayStoreTypes';
43
44
 
44
45
  const {
46
+ ACTOR_CHANGE,
45
47
  CONDITION,
46
48
  CLIENT_COMPONENT,
47
49
  CLIENT_EXTENSION,
@@ -136,6 +138,10 @@ class RelayReferenceMarker {
136
138
  selections.forEach(selection => {
137
139
  /* eslint-disable no-fallthrough */
138
140
  switch (selection.kind) {
141
+ case ACTOR_CHANGE:
142
+ // TODO: T89695151 Support multi-actor record sources in RelayReferenceMarker.js
143
+ this._traverseLink(selection.linkedField, record);
144
+ break;
139
145
  case LINKED_FIELD:
140
146
  if (selection.plural) {
141
147
  this._traversePluralLink(selection, record);
@@ -144,7 +150,9 @@ class RelayReferenceMarker {
144
150
  }
145
151
  break;
146
152
  case CONDITION:
147
- const conditionValue = this._getVariableValue(selection.condition);
153
+ const conditionValue = Boolean(
154
+ this._getVariableValue(selection.condition),
155
+ );
148
156
  if (conditionValue === selection.passingValue) {
149
157
  this._traverseSelections(selection.selections, record);
150
158
  }
@@ -155,18 +163,22 @@ class RelayReferenceMarker {
155
163
  if (typeName != null && typeName === selection.type) {
156
164
  this._traverseSelections(selection.selections, record);
157
165
  }
158
- } else if (RelayFeatureFlags.ENABLE_PRECISE_TYPE_REFINEMENT) {
166
+ } else {
159
167
  const typeName = RelayModernRecord.getType(record);
160
168
  const typeID = generateTypeID(typeName);
161
169
  this._references.add(typeID);
162
170
  this._traverseSelections(selection.selections, record);
163
- } else {
164
- this._traverseSelections(selection.selections, record);
165
171
  }
166
172
  break;
167
- // $FlowFixMe[incompatible-type]
168
173
  case FRAGMENT_SPREAD:
174
+ const prevVariables = this._variables;
175
+ this._variables = getLocalVariables(
176
+ this._variables,
177
+ selection.fragment.argumentDefinitions,
178
+ selection.args,
179
+ );
169
180
  this._traverseSelections(selection.fragment.selections, record);
181
+ this._variables = prevVariables;
170
182
  break;
171
183
  case LINKED_HANDLE:
172
184
  // The selections for a "handle" field are the same as those of the
@@ -197,11 +209,9 @@ class RelayReferenceMarker {
197
209
  case SCALAR_HANDLE:
198
210
  break;
199
211
  case TYPE_DISCRIMINATOR: {
200
- if (RelayFeatureFlags.ENABLE_PRECISE_TYPE_REFINEMENT) {
201
- const typeName = RelayModernRecord.getType(record);
202
- const typeID = generateTypeID(typeName);
203
- this._references.add(typeID);
204
- }
212
+ const typeName = RelayModernRecord.getType(record);
213
+ const typeID = generateTypeID(typeName);
214
+ this._references.add(typeID);
205
215
  break;
206
216
  }
207
217
  case MODULE_IMPORT:
@@ -253,8 +263,15 @@ class RelayReferenceMarker {
253
263
  }
254
264
  const normalizationRootNode = operationLoader.get(operationReference);
255
265
  if (normalizationRootNode != null) {
256
- const selections = getOperation(normalizationRootNode).selections;
257
- this._traverseSelections(selections, record);
266
+ const operation = getOperation(normalizationRootNode);
267
+ const prevVariables = this._variables;
268
+ this._variables = getLocalVariables(
269
+ this._variables,
270
+ operation.argumentDefinitions,
271
+ moduleImport.args,
272
+ );
273
+ this._traverseSelections(operation.selections, record);
274
+ this._variables = prevVariables;
258
275
  }
259
276
  // Otherwise, if the operation is not available, we assume that the data
260
277
  // cannot have been processed yet and therefore isn't in the store to
@@ -20,6 +20,11 @@ const invariant = require('invariant');
20
20
  const warning = require('warning');
21
21
 
22
22
  const {
23
+ ACTOR_IDENTIFIER_FIELD_NAME,
24
+ getActorIdentifierFromPayload,
25
+ } = require('../multi-actor-environment/ActorUtils');
26
+ const {
27
+ ACTOR_CHANGE,
23
28
  CONDITION,
24
29
  CLIENT_COMPONENT,
25
30
  CLIENT_EXTENSION,
@@ -36,6 +41,7 @@ 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,
@@ -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,13 +75,13 @@ 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
- ReactFlightReachableExecutableDefinitions,
76
83
  ReactFlightPayloadDeserializer,
84
+ ReactFlightReachableExecutableDefinitions,
77
85
  ReactFlightServerErrorHandler,
78
86
  Record,
79
87
  RelayResponsePayload,
@@ -91,6 +99,7 @@ export type NormalizationOptions = {|
91
99
  +reactFlightPayloadDeserializer?: ?ReactFlightPayloadDeserializer,
92
100
  +reactFlightServerErrorHandler?: ?ReactFlightServerErrorHandler,
93
101
  +shouldProcessClientComponents?: ?boolean,
102
+ +actorIdentifier?: ?ActorIdentifier,
94
103
  |};
95
104
 
96
105
  /**
@@ -118,13 +127,14 @@ function normalize(
118
127
  * Helper for handling payloads.
119
128
  */
120
129
  class RelayResponseNormalizer {
130
+ _actorIdentifier: ?ActorIdentifier;
121
131
  _getDataId: GetDataID;
122
132
  _handleFieldPayloads: Array<HandleFieldPayload>;
123
133
  _treatMissingFieldsAsNull: boolean;
124
134
  _incrementalPlaceholders: Array<IncrementalDataPlaceholder>;
125
135
  _isClientExtension: boolean;
126
136
  _isUnmatchedAbstractType: boolean;
127
- _moduleImportPayloads: Array<ModuleImportPayload>;
137
+ _followupPayloads: Array<FollowupPayload>;
128
138
  _path: Array<string>;
129
139
  _recordSource: MutableRecordSource;
130
140
  _variables: Variables;
@@ -137,13 +147,14 @@ class RelayResponseNormalizer {
137
147
  variables: Variables,
138
148
  options: NormalizationOptions,
139
149
  ) {
150
+ this._actorIdentifier = options.actorIdentifier;
140
151
  this._getDataId = options.getDataID;
141
152
  this._handleFieldPayloads = [];
142
153
  this._treatMissingFieldsAsNull = options.treatMissingFieldsAsNull;
143
154
  this._incrementalPlaceholders = [];
144
155
  this._isClientExtension = false;
145
156
  this._isUnmatchedAbstractType = false;
146
- this._moduleImportPayloads = [];
157
+ this._followupPayloads = [];
147
158
  this._path = options.path ? [...options.path] : [];
148
159
  this._recordSource = recordSource;
149
160
  this._variables = variables;
@@ -169,7 +180,7 @@ class RelayResponseNormalizer {
169
180
  errors: null,
170
181
  fieldPayloads: this._handleFieldPayloads,
171
182
  incrementalPlaceholders: this._incrementalPlaceholders,
172
- moduleImportPayloads: this._moduleImportPayloads,
183
+ followupPayloads: this._followupPayloads,
173
184
  source: this._recordSource,
174
185
  isFinal: false,
175
186
  };
@@ -208,13 +219,22 @@ class RelayResponseNormalizer {
208
219
  this._normalizeField(node, selection, record, data);
209
220
  break;
210
221
  case CONDITION:
211
- const conditionValue = this._getVariableValue(selection.condition);
222
+ const conditionValue = Boolean(
223
+ this._getVariableValue(selection.condition),
224
+ );
212
225
  if (conditionValue === selection.passingValue) {
213
226
  this._traverseSelections(selection, record, data);
214
227
  }
215
228
  break;
216
229
  case FRAGMENT_SPREAD: {
230
+ const prevVariables = this._variables;
231
+ this._variables = getLocalVariables(
232
+ this._variables,
233
+ selection.fragment.argumentDefinitions,
234
+ selection.args,
235
+ );
217
236
  this._traverseSelections(selection.fragment, record, data);
237
+ this._variables = prevVariables;
218
238
  break;
219
239
  }
220
240
  case INLINE_FRAGMENT: {
@@ -224,7 +244,7 @@ class RelayResponseNormalizer {
224
244
  if (typeName === selection.type) {
225
245
  this._traverseSelections(selection, record, data);
226
246
  }
227
- } else if (RelayFeatureFlags.ENABLE_PRECISE_TYPE_REFINEMENT) {
247
+ } else {
228
248
  const implementsInterface = data.hasOwnProperty(abstractKey);
229
249
  const typeName = RelayModernRecord.getType(record);
230
250
  const typeID = generateTypeID(typeName);
@@ -241,36 +261,24 @@ class RelayResponseNormalizer {
241
261
  if (implementsInterface) {
242
262
  this._traverseSelections(selection, record, data);
243
263
  }
244
- } else {
245
- // legacy behavior for abstract refinements: always normalize even
246
- // if the type doesn't conform, but track if the type matches or not
247
- // for determining whether response fields are expected to be present
248
- const implementsInterface = data.hasOwnProperty(abstractKey);
249
- const parentIsUnmatchedAbstractType = this._isUnmatchedAbstractType;
250
- this._isUnmatchedAbstractType =
251
- this._isUnmatchedAbstractType || !implementsInterface;
252
- this._traverseSelections(selection, record, data);
253
- this._isUnmatchedAbstractType = parentIsUnmatchedAbstractType;
254
264
  }
255
265
  break;
256
266
  }
257
267
  case TYPE_DISCRIMINATOR: {
258
- if (RelayFeatureFlags.ENABLE_PRECISE_TYPE_REFINEMENT) {
259
- const {abstractKey} = selection;
260
- const implementsInterface = data.hasOwnProperty(abstractKey);
261
- const typeName = RelayModernRecord.getType(record);
262
- const typeID = generateTypeID(typeName);
263
- let typeRecord = this._recordSource.get(typeID);
264
- if (typeRecord == null) {
265
- typeRecord = RelayModernRecord.create(typeID, TYPE_SCHEMA_TYPE);
266
- this._recordSource.set(typeID, typeRecord);
267
- }
268
- RelayModernRecord.setValue(
269
- typeRecord,
270
- abstractKey,
271
- implementsInterface,
272
- );
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);
273
276
  }
277
+ RelayModernRecord.setValue(
278
+ typeRecord,
279
+ abstractKey,
280
+ implementsInterface,
281
+ );
274
282
  break;
275
283
  }
276
284
  case LINKED_HANDLE:
@@ -281,13 +289,17 @@ class RelayResponseNormalizer {
281
289
  const fieldKey = getStorageKey(selection, this._variables);
282
290
  const handleKey = getHandleStorageKey(selection, this._variables);
283
291
  this._handleFieldPayloads.push({
292
+ /* $FlowFixMe[class-object-subtyping] added when improving typing
293
+ * for this parameters */
284
294
  args,
285
295
  dataID: RelayModernRecord.getDataID(record),
286
296
  fieldKey,
287
297
  handle: selection.handle,
288
298
  handleKey,
289
299
  handleArgs: selection.handleArgs
290
- ? getArgumentValues(selection.handleArgs, this._variables)
300
+ ? /* $FlowFixMe[class-object-subtyping] added when improving typing
301
+ * for this parameters */
302
+ getArgumentValues(selection.handleArgs, this._variables)
291
303
  : {},
292
304
  });
293
305
  break;
@@ -319,6 +331,9 @@ class RelayResponseNormalizer {
319
331
  throw new Error('Flight fields are not yet supported.');
320
332
  }
321
333
  break;
334
+ case ACTOR_CHANGE:
335
+ this._normalizeActorChange(node, selection, record, data);
336
+ break;
322
337
  default:
323
338
  (selection: empty);
324
339
  invariant(
@@ -362,6 +377,7 @@ class RelayResponseNormalizer {
362
377
  this._variables,
363
378
  ),
364
379
  typeName: RelayModernRecord.getType(record),
380
+ actorIdentifier: this._actorIdentifier,
365
381
  });
366
382
  }
367
383
  }
@@ -394,6 +410,7 @@ class RelayResponseNormalizer {
394
410
  parentID: RelayModernRecord.getDataID(record),
395
411
  node: stream,
396
412
  variables: this._variables,
413
+ actorIdentifier: this._actorIdentifier,
397
414
  });
398
415
  }
399
416
  }
@@ -424,13 +441,16 @@ class RelayResponseNormalizer {
424
441
  operationReference ?? null,
425
442
  );
426
443
  if (operationReference != null) {
427
- this._moduleImportPayloads.push({
444
+ this._followupPayloads.push({
445
+ kind: 'ModuleImportPayload',
446
+ args: moduleImport.args,
428
447
  data,
429
448
  dataID: RelayModernRecord.getDataID(record),
430
449
  operationReference,
431
450
  path: [...this._path],
432
451
  typeName,
433
452
  variables: this._variables,
453
+ actorIdentifier: this._actorIdentifier,
434
454
  });
435
455
  }
436
456
  }
@@ -523,6 +543,99 @@ class RelayResponseNormalizer {
523
543
  }
524
544
  }
525
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
+
526
639
  _normalizeFlightField(
527
640
  parent: NormalizationNode,
528
641
  selection: NormalizationFlightField,
@@ -546,20 +659,17 @@ class RelayResponseNormalizer {
546
659
  // Field not expected to exist regardless of whether the server is pruning null
547
660
  // fields or not.
548
661
  return;
549
- } else if (!this._treatMissingFieldsAsNull) {
662
+ } else {
550
663
  // Not optional and the server is not pruning null fields: field is expected
551
664
  // to be present
552
- if (__DEV__) {
553
- warning(
554
- false,
555
- 'RelayResponseNormalizer: Payload did not contain a value ' +
556
- 'for field `%s: %s`. Check that you are parsing with the same ' +
557
- 'query that was used to fetch the payload.',
558
- responseKey,
559
- storageKey,
560
- );
561
- }
562
- return;
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
+ );
563
673
  }
564
674
  }
565
675
  RelayModernRecord.setValue(record, storageKey, null);
@@ -658,13 +768,16 @@ class RelayResponseNormalizer {
658
768
  const reachableExecutableDefinitions: Array<ReactFlightReachableExecutableDefinitions> = [];
659
769
  for (const query of reactFlightPayload.queries) {
660
770
  if (query.response.data != null) {
661
- this._moduleImportPayloads.push({
771
+ this._followupPayloads.push({
772
+ kind: 'ModuleImportPayload',
773
+ args: null,
662
774
  data: query.response.data,
663
775
  dataID: ROOT_ID,
664
776
  operationReference: query.module,
665
777
  path: [],
666
778
  typeName: ROOT_TYPE,
667
779
  variables: query.variables,
780
+ actorIdentifier: this._actorIdentifier,
668
781
  });
669
782
  }
670
783
  reachableExecutableDefinitions.push({
@@ -674,13 +787,16 @@ class RelayResponseNormalizer {
674
787
  }
675
788
  for (const fragment of reactFlightPayload.fragments) {
676
789
  if (fragment.response.data != null) {
677
- this._moduleImportPayloads.push({
790
+ this._followupPayloads.push({
791
+ kind: 'ModuleImportPayload',
792
+ args: null,
678
793
  data: fragment.response.data,
679
794
  dataID: fragment.__id,
680
795
  operationReference: fragment.module,
681
796
  path: [],
682
797
  typeName: fragment.__typename,
683
798
  variables: fragment.variables,
799
+ actorIdentifier: this._actorIdentifier,
684
800
  });
685
801
  }
686
802
  reachableExecutableDefinitions.push({
@@ -51,13 +51,7 @@ function getReactFlightClientResponse(
51
51
  'got %s.',
52
52
  record,
53
53
  );
54
- const response: ?ReactFlightClientResponse = (record[
55
- REACT_FLIGHT_TREE_STORAGE_KEY
56
- ]: $FlowFixMe);
57
- if (response != null) {
58
- return response;
59
- }
60
- return null;
54
+ return (record[REACT_FLIGHT_TREE_STORAGE_KEY]: $FlowFixMe);
61
55
  }
62
56
 
63
57
  module.exports = {
@@ -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 = ({