relay-runtime 0.0.0-main-55b3f2c0 → 0.0.0-main-c454f22f

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.
@@ -16,6 +16,7 @@ import type {NormalizationSelectableNode} from '../util/NormalizationNode';
16
16
  import type {ReaderFragment} from '../util/ReaderNode';
17
17
  import type {DataID, Variables} from '../util/RelayRuntimeTypes';
18
18
  import type {
19
+ ClientEdgeTraversalPath,
19
20
  NormalizationSelector,
20
21
  PluralReaderSelector,
21
22
  ReaderSelector,
@@ -25,6 +26,7 @@ import type {
25
26
 
26
27
  const {getFragmentVariables} = require('./RelayConcreteVariables');
27
28
  const {
29
+ CLIENT_EDGE_TRAVERSAL_PATH,
28
30
  FRAGMENT_OWNER_KEY,
29
31
  FRAGMENTS_KEY,
30
32
  ID_KEY,
@@ -79,6 +81,7 @@ function getSingularSelector(
79
81
  const mixedOwner = item[FRAGMENT_OWNER_KEY];
80
82
  const isWithinUnmatchedTypeRefinement =
81
83
  item[IS_WITHIN_UNMATCHED_TYPE_REFINEMENT] === true;
84
+ const mixedClientEdgeTraversalPath = item[CLIENT_EDGE_TRAVERSAL_PATH];
82
85
  if (
83
86
  typeof dataID === 'string' &&
84
87
  typeof fragments === 'object' &&
@@ -86,22 +89,28 @@ function getSingularSelector(
86
89
  typeof fragments[fragment.name] === 'object' &&
87
90
  fragments[fragment.name] !== null &&
88
91
  typeof mixedOwner === 'object' &&
89
- mixedOwner !== null
92
+ mixedOwner !== null &&
93
+ (mixedClientEdgeTraversalPath == null ||
94
+ Array.isArray(mixedClientEdgeTraversalPath))
90
95
  ) {
91
96
  const owner: RequestDescriptor = (mixedOwner: $FlowFixMe);
92
- const argumentVariables = fragments[fragment.name];
97
+ const clientEdgeTraversalPath: ?ClientEdgeTraversalPath =
98
+ (mixedClientEdgeTraversalPath: $FlowFixMe);
93
99
 
100
+ const argumentVariables = fragments[fragment.name];
94
101
  const fragmentVariables = getFragmentVariables(
95
102
  fragment,
96
103
  owner.variables,
97
104
  argumentVariables,
98
105
  );
106
+
99
107
  return createReaderSelector(
100
108
  fragment,
101
109
  dataID,
102
110
  fragmentVariables,
103
111
  owner,
104
112
  isWithinUnmatchedTypeRefinement,
113
+ clientEdgeTraversalPath,
105
114
  );
106
115
  }
107
116
 
@@ -418,11 +427,13 @@ function createReaderSelector(
418
427
  variables: Variables,
419
428
  request: RequestDescriptor,
420
429
  isWithinUnmatchedTypeRefinement: boolean = false,
430
+ clientEdgeTraversalPath: ?ClientEdgeTraversalPath,
421
431
  ): SingularReaderSelector {
422
432
  return {
423
433
  kind: 'SingularReaderSelector',
424
434
  dataID,
425
435
  isWithinUnmatchedTypeRefinement,
436
+ clientEdgeTraversalPath: clientEdgeTraversalPath ?? null,
426
437
  node: fragment,
427
438
  variables,
428
439
  owner: request,
@@ -14,6 +14,7 @@
14
14
 
15
15
  import type {
16
16
  ReaderActorChange,
17
+ ReaderClientEdge,
17
18
  ReaderFlightField,
18
19
  ReaderFragment,
19
20
  ReaderFragmentSpread,
@@ -28,7 +29,9 @@ import type {
28
29
  } from '../util/ReaderNode';
29
30
  import type {DataID, Variables} from '../util/RelayRuntimeTypes';
30
31
  import type {
32
+ ClientEdgeTraversalInfo,
31
33
  DataIDSet,
34
+ MissingClientEdgeRequestInfo,
32
35
  MissingRequiredFields,
33
36
  Record,
34
37
  RecordSource,
@@ -41,6 +44,7 @@ import type {ResolverCache} from './ResolverCache';
41
44
 
42
45
  const {
43
46
  ACTOR_CHANGE,
47
+ CLIENT_EDGE,
44
48
  CLIENT_EXTENSION,
45
49
  CONDITION,
46
50
  DEFER,
@@ -60,6 +64,7 @@ const ClientID = require('./ClientID');
60
64
  const RelayModernRecord = require('./RelayModernRecord');
61
65
  const {getReactFlightClientResponse} = require('./RelayStoreReactFlightUtils');
62
66
  const {
67
+ CLIENT_EDGE_TRAVERSAL_PATH,
63
68
  FRAGMENT_OWNER_KEY,
64
69
  FRAGMENT_PROP_NAME_KEY,
65
70
  FRAGMENTS_KEY,
@@ -93,7 +98,9 @@ function read(
93
98
  * @private
94
99
  */
95
100
  class RelayReader {
101
+ _clientEdgeTraversalPath: Array<ClientEdgeTraversalInfo | null>;
96
102
  _isMissingData: boolean;
103
+ _missingClientEdges: Array<MissingClientEdgeRequestInfo>;
97
104
  _isWithinUnmatchedTypeRefinement: boolean;
98
105
  _missingRequiredFields: ?MissingRequiredFields;
99
106
  _owner: RequestDescriptor;
@@ -108,6 +115,12 @@ class RelayReader {
108
115
  selector: SingularReaderSelector,
109
116
  resolverCache: ResolverCache,
110
117
  ) {
118
+ this._clientEdgeTraversalPath =
119
+ RelayFeatureFlags.ENABLE_CLIENT_EDGES &&
120
+ selector.clientEdgeTraversalPath?.length
121
+ ? [...selector.clientEdgeTraversalPath]
122
+ : [];
123
+ this._missingClientEdges = [];
111
124
  this._isMissingData = false;
112
125
  this._isWithinUnmatchedTypeRefinement = false;
113
126
  this._missingRequiredFields = null;
@@ -182,12 +195,36 @@ class RelayReader {
182
195
  return {
183
196
  data,
184
197
  isMissingData: this._isMissingData && isDataExpectedToBePresent,
198
+ missingClientEdges:
199
+ RelayFeatureFlags.ENABLE_CLIENT_EDGES && this._missingClientEdges.length
200
+ ? this._missingClientEdges
201
+ : null,
185
202
  seenRecords: this._seenRecords,
186
203
  selector: this._selector,
187
204
  missingRequiredFields: this._missingRequiredFields,
188
205
  };
189
206
  }
190
207
 
208
+ _markDataAsMissing(): void {
209
+ this._isMissingData = true;
210
+ if (
211
+ RelayFeatureFlags.ENABLE_CLIENT_EDGES &&
212
+ this._clientEdgeTraversalPath.length
213
+ ) {
214
+ const top =
215
+ this._clientEdgeTraversalPath[this._clientEdgeTraversalPath.length - 1];
216
+ // Top can be null if we've traversed past a client edge into an ordinary
217
+ // client extension field; we never want to fetch in response to missing
218
+ // data off of a client extension field.
219
+ if (top !== null) {
220
+ this._missingClientEdges.push({
221
+ request: top.readerClientEdge.operation,
222
+ clientEdgeDestinationID: top.clientEdgeDestinationID,
223
+ });
224
+ }
225
+ }
226
+ }
227
+
191
228
  _traverse(
192
229
  node: ReaderNode,
193
230
  dataID: DataID,
@@ -197,7 +234,7 @@ class RelayReader {
197
234
  this._seenRecords.add(dataID);
198
235
  if (record == null) {
199
236
  if (record === undefined) {
200
- this._isMissingData = true;
237
+ this._markDataAsMissing();
201
238
  }
202
239
  return record;
203
240
  }
@@ -344,7 +381,7 @@ class RelayReader {
344
381
  this._isMissingData = parentIsMissingData;
345
382
  } else if (implementsInterface == null) {
346
383
  // Don't know if the type implements the interface or not
347
- this._isMissingData = true;
384
+ this._markDataAsMissing();
348
385
  }
349
386
  }
350
387
  break;
@@ -372,12 +409,24 @@ class RelayReader {
372
409
  case DEFER:
373
410
  case CLIENT_EXTENSION: {
374
411
  const isMissingData = this._isMissingData;
412
+ const alreadyMissingClientEdges = this._missingClientEdges.length;
413
+ if (RelayFeatureFlags.ENABLE_CLIENT_EDGES) {
414
+ this._clientEdgeTraversalPath.push(null);
415
+ }
375
416
  const hasExpectedData = this._traverseSelections(
376
417
  selection.selections,
377
418
  record,
378
419
  data,
379
420
  );
380
- this._isMissingData = isMissingData;
421
+ // The only case where we want to suspend due to missing data off of
422
+ // a client extension is if we reached a client edge that we might be
423
+ // able to fetch:
424
+ this._isMissingData =
425
+ isMissingData ||
426
+ this._missingClientEdges.length > alreadyMissingClientEdges;
427
+ if (RelayFeatureFlags.ENABLE_CLIENT_EDGES) {
428
+ this._clientEdgeTraversalPath.pop();
429
+ }
381
430
  if (!hasExpectedData) {
382
431
  return false;
383
432
  }
@@ -404,6 +453,13 @@ class RelayReader {
404
453
  case ACTOR_CHANGE:
405
454
  this._readActorChange(selection, record, data);
406
455
  break;
456
+ case CLIENT_EDGE:
457
+ if (RelayFeatureFlags.ENABLE_CLIENT_EDGES) {
458
+ this._readClientEdge(selection, record, data);
459
+ } else {
460
+ throw new Error('Client edges are not yet supported.');
461
+ }
462
+ break;
407
463
  default:
408
464
  (selection: empty);
409
465
  invariant(
@@ -450,7 +506,7 @@ class RelayReader {
450
506
  field: ReaderRelayResolver,
451
507
  record: Record,
452
508
  data: SelectorData,
453
- ): ?mixed {
509
+ ): void {
454
510
  const {resolverModule, fragment} = field;
455
511
  const storageKey = getStorageKey(field, this._variables);
456
512
  const resolverID = ClientID.generateClientID(
@@ -523,8 +579,66 @@ class RelayReader {
523
579
  if (seenRecord != null) {
524
580
  this._seenRecords.add(seenRecord);
525
581
  }
526
- data[storageKey] = result;
527
- return result;
582
+
583
+ const applicationName = field.alias ?? field.name;
584
+ data[applicationName] = result;
585
+ }
586
+
587
+ _readClientEdge(
588
+ field: ReaderClientEdge,
589
+ record: Record,
590
+ data: SelectorData,
591
+ ): void {
592
+ const backingField = field.backingField;
593
+
594
+ // Because ReaderClientExtension doesn't have `alias` or `name` and so I don't know
595
+ // how to get its applicationName or storageKey yet:
596
+ invariant(
597
+ backingField.kind !== 'ClientExtension',
598
+ 'Client extension client edges are not yet implemented.',
599
+ );
600
+
601
+ const applicationName = backingField.alias ?? backingField.name;
602
+
603
+ const backingFieldData = {};
604
+ this._traverseSelections([backingField], record, backingFieldData);
605
+ const destinationDataID = backingFieldData[applicationName];
606
+
607
+ if (destinationDataID == null) {
608
+ data[applicationName] = destinationDataID;
609
+ return;
610
+ }
611
+
612
+ invariant(
613
+ typeof destinationDataID === 'string',
614
+ 'Plural client edges not are yet implemented',
615
+ ); // FIXME support plural
616
+
617
+ // Not wrapping the push/pop in a try/finally because if we throw, the
618
+ // Reader object is not usable after that anyway.
619
+ this._clientEdgeTraversalPath.push({
620
+ readerClientEdge: field,
621
+ clientEdgeDestinationID: destinationDataID,
622
+ });
623
+
624
+ const prevData = data[applicationName];
625
+ invariant(
626
+ prevData == null || typeof prevData === 'object',
627
+ 'RelayReader(): Expected data for field `%s` on record `%s` ' +
628
+ 'to be an object, got `%s`.',
629
+ applicationName,
630
+ RelayModernRecord.getDataID(record),
631
+ prevData,
632
+ );
633
+ const value = this._traverse(
634
+ field.linkedField,
635
+ destinationDataID,
636
+ // $FlowFixMe[incompatible-variance]
637
+ prevData,
638
+ );
639
+ data[applicationName] = value;
640
+
641
+ this._clientEdgeTraversalPath.pop();
528
642
  }
529
643
 
530
644
  _readFlightField(
@@ -539,7 +653,7 @@ class RelayReader {
539
653
  if (reactFlightClientResponseRecordID == null) {
540
654
  data[applicationName] = reactFlightClientResponseRecordID;
541
655
  if (reactFlightClientResponseRecordID === undefined) {
542
- this._isMissingData = true;
656
+ this._markDataAsMissing();
543
657
  }
544
658
  return reactFlightClientResponseRecordID;
545
659
  }
@@ -550,7 +664,7 @@ class RelayReader {
550
664
  if (reactFlightClientResponseRecord == null) {
551
665
  data[applicationName] = reactFlightClientResponseRecord;
552
666
  if (reactFlightClientResponseRecord === undefined) {
553
- this._isMissingData = true;
667
+ this._markDataAsMissing();
554
668
  }
555
669
  return reactFlightClientResponseRecord;
556
670
  }
@@ -570,7 +684,7 @@ class RelayReader {
570
684
  const storageKey = getStorageKey(field, this._variables);
571
685
  const value = RelayModernRecord.getValue(record, storageKey);
572
686
  if (value === undefined) {
573
- this._isMissingData = true;
687
+ this._markDataAsMissing();
574
688
  }
575
689
  data[applicationName] = value;
576
690
  return value;
@@ -587,7 +701,7 @@ class RelayReader {
587
701
  if (linkedID == null) {
588
702
  data[applicationName] = linkedID;
589
703
  if (linkedID === undefined) {
590
- this._isMissingData = true;
704
+ this._markDataAsMissing();
591
705
  }
592
706
  return linkedID;
593
707
  }
@@ -622,7 +736,7 @@ class RelayReader {
622
736
  if (externalRef == null) {
623
737
  data[applicationName] = externalRef;
624
738
  if (externalRef === undefined) {
625
- this._isMissingData = true;
739
+ this._markDataAsMissing();
626
740
  }
627
741
  return data[applicationName];
628
742
  }
@@ -655,7 +769,7 @@ class RelayReader {
655
769
  if (linkedIDs == null) {
656
770
  data[applicationName] = linkedIDs;
657
771
  if (linkedIDs === undefined) {
658
- this._isMissingData = true;
772
+ this._markDataAsMissing();
659
773
  }
660
774
  return linkedIDs;
661
775
  }
@@ -673,7 +787,7 @@ class RelayReader {
673
787
  linkedIDs.forEach((linkedID, nextIndex) => {
674
788
  if (linkedID == null) {
675
789
  if (linkedID === undefined) {
676
- this._isMissingData = true;
790
+ this._markDataAsMissing();
677
791
  }
678
792
  // $FlowFixMe[cannot-write]
679
793
  linkedArray[nextIndex] = linkedID;
@@ -711,7 +825,7 @@ class RelayReader {
711
825
  const component = RelayModernRecord.getValue(record, componentKey);
712
826
  if (component == null) {
713
827
  if (component === undefined) {
714
- this._isMissingData = true;
828
+ this._markDataAsMissing();
715
829
  }
716
830
  return;
717
831
  }
@@ -758,6 +872,17 @@ class RelayReader {
758
872
  data[FRAGMENT_OWNER_KEY] = this._owner;
759
873
  data[IS_WITHIN_UNMATCHED_TYPE_REFINEMENT] =
760
874
  this._isWithinUnmatchedTypeRefinement;
875
+
876
+ if (RelayFeatureFlags.ENABLE_CLIENT_EDGES) {
877
+ if (
878
+ this._clientEdgeTraversalPath.length > 0 &&
879
+ this._clientEdgeTraversalPath[
880
+ this._clientEdgeTraversalPath.length - 1
881
+ ] !== null
882
+ ) {
883
+ data[CLIENT_EDGE_TRAVERSAL_PATH] = [...this._clientEdgeTraversalPath];
884
+ }
885
+ }
761
886
  }
762
887
 
763
888
  _createInlineDataOrResolverFragmentPointer(
@@ -101,6 +101,7 @@ class RelayStoreSubscriptions implements StoreSubscriptions {
101
101
  subscription.snapshot = {
102
102
  data: subscription.snapshot.data,
103
103
  isMissingData: backup.isMissingData,
104
+ missingClientEdges: backup.missingClientEdges,
104
105
  seenRecords: backup.seenRecords,
105
106
  selector: backup.selector,
106
107
  missingRequiredFields: backup.missingRequiredFields,
@@ -162,6 +163,7 @@ class RelayStoreSubscriptions implements StoreSubscriptions {
162
163
  nextSnapshot = ({
163
164
  data: nextData,
164
165
  isMissingData: nextSnapshot.isMissingData,
166
+ missingClientEdges: nextSnapshot.missingClientEdges,
165
167
  seenRecords: nextSnapshot.seenRecords,
166
168
  selector: nextSnapshot.selector,
167
169
  missingRequiredFields: nextSnapshot.missingRequiredFields,
@@ -35,7 +35,7 @@ import type {
35
35
  NormalizationScalarField,
36
36
  NormalizationSelectableNode,
37
37
  } from '../util/NormalizationNode';
38
- import type {ReaderFragment} from '../util/ReaderNode';
38
+ import type {ReaderClientEdge, ReaderFragment} from '../util/ReaderNode';
39
39
  import type {
40
40
  ConcreteRequest,
41
41
  RequestParameters,
@@ -82,6 +82,7 @@ export type SingularReaderSelector = {|
82
82
  +kind: 'SingularReaderSelector',
83
83
  +dataID: DataID,
84
84
  +isWithinUnmatchedTypeRefinement: boolean,
85
+ +clientEdgeTraversalPath: ClientEdgeTraversalPath | null,
85
86
  +node: ReaderFragment,
86
87
  +owner: RequestDescriptor,
87
88
  +variables: Variables,
@@ -120,12 +121,26 @@ export type MissingRequiredFields =
120
121
  | {|action: 'THROW', field: MissingRequiredField|}
121
122
  | {|action: 'LOG', fields: Array<MissingRequiredField>|};
122
123
 
124
+ export type ClientEdgeTraversalInfo = {|
125
+ +readerClientEdge: ReaderClientEdge,
126
+ +clientEdgeDestinationID: DataID,
127
+ |};
128
+
129
+ export type ClientEdgeTraversalPath =
130
+ $ReadOnlyArray<ClientEdgeTraversalInfo | null>;
131
+
132
+ export type MissingClientEdgeRequestInfo = {|
133
+ +request: ConcreteRequest,
134
+ +clientEdgeDestinationID: DataID,
135
+ |};
136
+
123
137
  /**
124
138
  * A representation of a selector and its results at a particular point in time.
125
139
  */
126
140
  export type Snapshot = {|
127
141
  +data: ?SelectorData,
128
142
  +isMissingData: boolean,
143
+ +missingClientEdges: null | $ReadOnlyArray<MissingClientEdgeRequestInfo>,
129
144
  +seenRecords: DataIDSet,
130
145
  +selector: SingularReaderSelector,
131
146
  +missingRequiredFields: ?MissingRequiredFields,
@@ -202,6 +202,7 @@ function getModuleOperationKey(documentName: string): string {
202
202
  */
203
203
  const RelayStoreUtils = {
204
204
  ACTOR_IDENTIFIER_KEY: '__actorIdentifier',
205
+ CLIENT_EDGE_TRAVERSAL_PATH: '__clientEdgeTraversalPath',
205
206
  FRAGMENTS_KEY: '__fragments',
206
207
  FRAGMENT_OWNER_KEY: '__fragmentOwner',
207
208
  FRAGMENT_PROP_NAME_KEY: '__fragmentPropName',
@@ -232,8 +232,16 @@ export type ReaderRelayResolver = {|
232
232
  }) => mixed,
233
233
  |};
234
234
 
235
+ export type ReaderClientEdge = {|
236
+ +kind: 'ClientEdge',
237
+ +linkedField: ReaderLinkedField,
238
+ +operation: ConcreteRequest,
239
+ +backingField: ReaderRelayResolver | ReaderClientExtension,
240
+ |};
241
+
235
242
  export type ReaderSelection =
236
243
  | ReaderCondition
244
+ | ReaderClientEdge
237
245
  | ReaderClientExtension
238
246
  | ReaderDefer
239
247
  | ReaderField
@@ -70,6 +70,7 @@ const RelayConcreteNode = {
70
70
  ACTOR_CHANGE: 'ActorChange',
71
71
  CONDITION: 'Condition',
72
72
  CLIENT_COMPONENT: 'ClientComponent',
73
+ CLIENT_EDGE: 'ClientEdge',
73
74
  CLIENT_EXTENSION: 'ClientExtension',
74
75
  DEFER: 'Defer',
75
76
  CONNECTION: 'Connection',
@@ -14,8 +14,9 @@
14
14
 
15
15
  import type {Disposable} from '../util/RelayRuntimeTypes';
16
16
 
17
- type FeatureFlags = {|
17
+ export type FeatureFlags = {|
18
18
  DELAY_CLEANUP_OF_PENDING_PRELOAD_QUERIES: boolean,
19
+ ENABLE_CLIENT_EDGES: boolean,
19
20
  ENABLE_VARIABLE_CONNECTION_KEY: boolean,
20
21
  ENABLE_PARTIAL_RENDERING_DEFAULT: boolean,
21
22
  ENABLE_REACT_FLIGHT_COMPONENT_FIELD: boolean,
@@ -30,12 +31,14 @@ type FeatureFlags = {|
30
31
  ENABLE_CONTAINERS_SUBSCRIBE_ON_COMMIT: boolean,
31
32
  ENABLE_QUERY_RENDERER_OFFSCREEN_SUPPORT: boolean,
32
33
  MAX_DATA_ID_LENGTH: ?number,
34
+ REFACTOR_SUSPENSE_RESOURCE: boolean,
33
35
  STRING_INTERN_LEVEL: number,
34
36
  USE_REACT_CACHE: boolean,
35
37
  |};
36
38
 
37
39
  const RelayFeatureFlags: FeatureFlags = {
38
40
  DELAY_CLEANUP_OF_PENDING_PRELOAD_QUERIES: false,
41
+ ENABLE_CLIENT_EDGES: false,
39
42
  ENABLE_VARIABLE_CONNECTION_KEY: false,
40
43
  ENABLE_PARTIAL_RENDERING_DEFAULT: true,
41
44
  ENABLE_REACT_FLIGHT_COMPONENT_FIELD: false,
@@ -50,6 +53,7 @@ const RelayFeatureFlags: FeatureFlags = {
50
53
  ENABLE_CONTAINERS_SUBSCRIBE_ON_COMMIT: false,
51
54
  ENABLE_QUERY_RENDERER_OFFSCREEN_SUPPORT: false,
52
55
  MAX_DATA_ID_LENGTH: null,
56
+ REFACTOR_SUSPENSE_RESOURCE: true,
53
57
  STRING_INTERN_LEVEL: 0,
54
58
  USE_REACT_CACHE: false,
55
59
  };