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
@@ -12,6 +12,10 @@
12
12
 
13
13
  'use strict';
14
14
 
15
+ import type {
16
+ ActorIdentifier,
17
+ IActorEnvironment,
18
+ } from '../multi-actor-environment';
15
19
  import type {
16
20
  GraphQLResponse,
17
21
  INetwork,
@@ -27,6 +31,7 @@ import type {
27
31
  NormalizationRootNode,
28
32
  NormalizationScalarField,
29
33
  NormalizationSelectableNode,
34
+ NormalizationArgument,
30
35
  } from '../util/NormalizationNode';
31
36
  import type {ReaderFragment} from '../util/ReaderNode';
32
37
  import type {
@@ -144,6 +149,9 @@ export type Props = {[key: string]: mixed, ...};
144
149
  */
145
150
  export type RelayContext = {|
146
151
  environment: IEnvironment,
152
+ getEnvironmentForActor?: ?(
153
+ actorIdentifier: ActorIdentifier,
154
+ ) => IActorEnvironment,
147
155
  |};
148
156
 
149
157
  /**
@@ -190,7 +198,7 @@ export interface FragmentSpecResolver {
190
198
  * Subscribe to resolver updates.
191
199
  * Overrides existing callback (if one has been specified).
192
200
  */
193
- setCallback(callback: () => void): void;
201
+ setCallback(props: Props, callback: () => void): void;
194
202
  }
195
203
 
196
204
  /**
@@ -216,8 +224,10 @@ export interface MutableRecordSource extends RecordSource {
216
224
  }
217
225
 
218
226
  export type CheckOptions = {|
219
- target: MutableRecordSource,
220
227
  handlers: $ReadOnlyArray<MissingFieldHandler>,
228
+ defaultActorIdentifier: ActorIdentifier,
229
+ getTargetForActor: (actorIdentifier: ActorIdentifier) => MutableRecordSource,
230
+ getSourceForActor: (actorIdentifier: ActorIdentifier) => RecordSource,
221
231
  |};
222
232
 
223
233
  export type OperationAvailability =
@@ -332,6 +342,11 @@ export interface Store {
332
342
  invalidationState: InvalidationState,
333
343
  callback: () => void,
334
344
  ): Disposable;
345
+
346
+ /**
347
+ * Get the current write epoch
348
+ */
349
+ getEpoch(): number;
335
350
  }
336
351
 
337
352
  export interface StoreSubscriptions {
@@ -359,7 +374,7 @@ export interface StoreSubscriptions {
359
374
  /**
360
375
  * Notifies each subscription if the snapshot for the subscription selector has changed.
361
376
  * Mutates the updatedOwners array with any owners (RequestDescriptors) associated
362
- * with the subscriptions that were notifed; i.e. the owners affected by the changes.
377
+ * with the subscriptions that were notified; i.e. the owners affected by the changes.
363
378
  */
364
379
  updateSubscriptions(
365
380
  source: RecordSource,
@@ -445,6 +460,23 @@ export interface RecordSourceSelectorProxy extends RecordSourceProxy {
445
460
  }
446
461
 
447
462
  export type LogEvent =
463
+ | {|
464
+ +name: 'suspense.fragment',
465
+ +data: mixed,
466
+ +fragment: ReaderFragment,
467
+ +isRelayHooks: boolean,
468
+ +isMissingData: boolean,
469
+ +isPromiseCached: boolean,
470
+ +pendingOperations: $ReadOnlyArray<RequestDescriptor>,
471
+ |}
472
+ | {|
473
+ +name: 'suspense.query',
474
+ +fetchPolicy: string,
475
+ +isPromiseCached: boolean,
476
+ +operation: OperationDescriptor,
477
+ +queryAvailability: ?OperationAvailability,
478
+ +renderPolicy: RenderPolicy,
479
+ |}
448
480
  | {|
449
481
  +name: 'queryresource.fetch',
450
482
  // ID of this query resource request and will be the same
@@ -456,7 +488,7 @@ export type LogEvent =
456
488
  // FetchPolicy from Relay Hooks
457
489
  +fetchPolicy: string,
458
490
  // RenderPolicy from Relay Hooks
459
- +renderPolicy: string,
491
+ +renderPolicy: RenderPolicy,
460
492
  +queryAvailability: OperationAvailability,
461
493
  +shouldFetch: boolean,
462
494
  |}
@@ -468,33 +500,67 @@ export type LogEvent =
468
500
  |}
469
501
  | {|
470
502
  +name: 'network.info',
471
- +transactionID: number,
503
+ +networkRequestId: number,
472
504
  +info: mixed,
473
505
  |}
474
506
  | {|
475
507
  +name: 'network.start',
476
- +transactionID: number,
508
+ +networkRequestId: number,
477
509
  +params: RequestParameters,
478
510
  +variables: Variables,
479
511
  +cacheConfig: CacheConfig,
480
512
  |}
481
513
  | {|
482
514
  +name: 'network.next',
483
- +transactionID: number,
515
+ +networkRequestId: number,
484
516
  +response: GraphQLResponse,
485
517
  |}
486
518
  | {|
487
519
  +name: 'network.error',
488
- +transactionID: number,
520
+ +networkRequestId: number,
489
521
  +error: Error,
490
522
  |}
491
523
  | {|
492
524
  +name: 'network.complete',
493
- +transactionID: number,
525
+ +networkRequestId: number,
494
526
  |}
495
527
  | {|
496
528
  +name: 'network.unsubscribe',
497
- +transactionID: number,
529
+ +networkRequestId: number,
530
+ |}
531
+ | {|
532
+ +name: 'execute.start',
533
+ +executeId: number,
534
+ +params: RequestParameters,
535
+ +variables: Variables,
536
+ +cacheConfig: CacheConfig,
537
+ |}
538
+ | {|
539
+ +name: 'execute.next',
540
+ +executeId: number,
541
+ +response: GraphQLResponse,
542
+ +duration: number,
543
+ |}
544
+ | {|
545
+ +name: 'execute.async.module',
546
+ +executeId: number,
547
+ +operationName: string,
548
+ +duration: number,
549
+ |}
550
+ | {|
551
+ +name: 'execute.flight.payload_deserialize',
552
+ +executeId: number,
553
+ +operationName: string,
554
+ +duration: number,
555
+ |}
556
+ | {|
557
+ +name: 'execute.error',
558
+ +executeId: number,
559
+ +error: Error,
560
+ |}
561
+ | {|
562
+ +name: 'execute.complete',
563
+ +executeId: number,
498
564
  |}
499
565
  | {|
500
566
  +name: 'store.publish',
@@ -584,6 +650,19 @@ export interface IEnvironment {
584
650
  */
585
651
  applyUpdate(optimisticUpdate: OptimisticUpdateFunction): Disposable;
586
652
 
653
+ /**
654
+ * Revert updates for the `update` function.
655
+ */
656
+ revertUpdate(update: OptimisticUpdateFunction): void;
657
+
658
+ /**
659
+ * Revert updates for the `update` function, and apply the `replacement` update.
660
+ */
661
+ replaceUpdate(
662
+ update: OptimisticUpdateFunction,
663
+ replacement: OptimisticUpdateFunction,
664
+ ): void;
665
+
587
666
  /**
588
667
  * Apply an optimistic mutation response and/or updater. The mutation can be
589
668
  * reverted by calling `dispose()` on the returned value.
@@ -665,7 +744,7 @@ export interface IEnvironment {
665
744
  /**
666
745
  * Returns an Observable of GraphQLResponse resulting from executing the
667
746
  * provided Query or Subscription operation responses, the result of which is
668
- * then normalized and comitted to the publish queue.
747
+ * then normalized and committed to the publish queue.
669
748
  *
670
749
  * Note: Observables are lazy, so calling this method will do nothing until
671
750
  * the result is subscribed to:
@@ -750,6 +829,7 @@ export type HandleFieldPayload = {|
750
829
  * with a `@module` fragment spread, or a Flight field's:
751
830
  *
752
831
  * ## @module Fragment Spread
832
+ * - args: Local arguments from the parent
753
833
  * - data: The GraphQL response value for the @match field.
754
834
  * - dataID: The ID of the store object linked to by the @match field.
755
835
  * - operationReference: A reference to a generated module containing the
@@ -777,14 +857,48 @@ export type HandleFieldPayload = {|
777
857
  * root data.
778
858
  */
779
859
  export type ModuleImportPayload = {|
860
+ +kind: 'ModuleImportPayload',
861
+ +args: ?$ReadOnlyArray<NormalizationArgument>,
780
862
  +data: PayloadData,
781
863
  +dataID: DataID,
782
864
  +operationReference: mixed,
783
865
  +path: $ReadOnlyArray<string>,
784
866
  +typeName: string,
785
867
  +variables: Variables,
868
+ +actorIdentifier: ?ActorIdentifier,
786
869
  |};
787
870
 
871
+ /**
872
+ * A payload that represents data necessary to process the results of an object
873
+ * with experimental actor change directive.
874
+ *
875
+ * - data: The GraphQL response value for the actor change field.
876
+ * - dataID: The ID of the store object linked to by the actor change field.
877
+ * - node: NormalizationLinkedField, where the actor change directive is used
878
+ * - path: to a field in the response
879
+ * - variables: Query variables.
880
+ * - typeName: the type that matched.
881
+ *
882
+ * The dataID, variables, and fragmentName can be used to create a Selector
883
+ * which can in turn be used to normalize and publish the data. The dataID and
884
+ * typeName can also be used to construct a root record for normalization.
885
+ */
886
+ export type ActorPayload = {|
887
+ +kind: 'ActorPayload',
888
+ +data: PayloadData,
889
+ +dataID: DataID,
890
+ +node: NormalizationLinkedField,
891
+ +path: $ReadOnlyArray<string>,
892
+ +typeName: string,
893
+ +variables: Variables,
894
+ +actorIdentifier: ActorIdentifier,
895
+ |};
896
+
897
+ /**
898
+ * Union type of possible payload followups we may handle during normalization.
899
+ */
900
+ export type FollowupPayload = ModuleImportPayload | ActorPayload;
901
+
788
902
  /**
789
903
  * Data emitted after processing a Defer or Stream node during normalization
790
904
  * that describes how to process the corresponding response chunk when it
@@ -797,6 +911,7 @@ export type DeferPlaceholder = {|
797
911
  +path: $ReadOnlyArray<string>,
798
912
  +selector: NormalizationSelector,
799
913
  +typeName: string,
914
+ +actorIdentifier: ?ActorIdentifier,
800
915
  |};
801
916
  export type StreamPlaceholder = {|
802
917
  +kind: 'stream',
@@ -805,6 +920,7 @@ export type StreamPlaceholder = {|
805
920
  +parentID: DataID,
806
921
  +node: NormalizationSelectableNode,
807
922
  +variables: Variables,
923
+ +actorIdentifier: ?ActorIdentifier,
808
924
  |};
809
925
  export type IncrementalDataPlaceholder = DeferPlaceholder | StreamPlaceholder;
810
926
 
@@ -926,7 +1042,7 @@ export type RelayResponsePayload = {|
926
1042
  +errors: ?Array<PayloadError>,
927
1043
  +fieldPayloads: ?Array<HandleFieldPayload>,
928
1044
  +incrementalPlaceholders: ?Array<IncrementalDataPlaceholder>,
929
- +moduleImportPayloads: ?Array<ModuleImportPayload>,
1045
+ +followupPayloads: ?Array<FollowupPayload>,
930
1046
  +source: MutableRecordSource,
931
1047
  +isFinal: boolean,
932
1048
  |};
@@ -23,7 +23,11 @@ import type {
23
23
  NormalizationArgument,
24
24
  NormalizationField,
25
25
  } from '../util/NormalizationNode';
26
- import type {ReaderArgument, ReaderField} from '../util/ReaderNode';
26
+ import type {
27
+ ReaderArgument,
28
+ ReaderField,
29
+ ReaderActorChange,
30
+ } from '../util/ReaderNode';
27
31
  import type {Variables} from '../util/RelayRuntimeTypes';
28
32
 
29
33
  export type Arguments = interface {+[string]: mixed};
@@ -121,14 +125,19 @@ function getHandleStorageKey(
121
125
  * used here for consistency.
122
126
  */
123
127
  function getStorageKey(
124
- field: NormalizationField | NormalizationHandle | ReaderField,
128
+ field:
129
+ | NormalizationField
130
+ | NormalizationHandle
131
+ | ReaderField
132
+ | ReaderActorChange,
125
133
  variables: Variables,
126
134
  ): string {
127
135
  if (field.storageKey) {
128
136
  // TODO T23663664: Handle nodes do not yet define a static storageKey.
129
137
  return (field: $FlowFixMe).storageKey;
130
138
  }
131
- const {args, name} = field;
139
+ const args = typeof field.args === 'undefined' ? undefined : field.args;
140
+ const name = field.name;
132
141
  return args && args.length !== 0
133
142
  ? formatStorageKey(name, getArgumentValues(args, variables))
134
143
  : name;
@@ -194,6 +203,7 @@ function getModuleOperationKey(documentName: string): string {
194
203
  * Constants shared by all implementations of RecordSource/MutableRecordSource/etc.
195
204
  */
196
205
  const RelayStoreUtils = {
206
+ ACTOR_IDENTIFIER_KEY: '__actorIdentifier',
197
207
  FRAGMENTS_KEY: '__fragments',
198
208
  FRAGMENT_OWNER_KEY: '__fragmentOwner',
199
209
  FRAGMENT_PROP_NAME_KEY: '__fragmentPropName',
@@ -206,6 +216,10 @@ const RelayStoreUtils = {
206
216
  TYPENAME_KEY: '__typename',
207
217
  INVALIDATED_AT_KEY: '__invalidated_at',
208
218
  IS_WITHIN_UNMATCHED_TYPE_REFINEMENT: '__isWithinUnmatchedTypeRefinement',
219
+ RELAY_RESOLVER_VALUE_KEY: '__resolverValue',
220
+ RELAY_RESOLVER_INVALIDATION_KEY: '__resolverValueMayBeInvalid',
221
+ RELAY_RESOLVER_INPUTS_KEY: '__resolverInputValues',
222
+ RELAY_RESOLVER_READER_SELECTOR_KEY: '__resolverReaderSelector',
209
223
 
210
224
  formatStorageKey,
211
225
  getArgumentValue,
@@ -0,0 +1,247 @@
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 strict-local
8
+ * @format
9
+ */
10
+
11
+ // flowlint ambiguous-object-type:error
12
+
13
+ 'use strict';
14
+
15
+ const RelayModernRecord = require('./RelayModernRecord');
16
+
17
+ const recycleNodesInto = require('../util/recycleNodesInto');
18
+ const warning = require('warning');
19
+
20
+ const {generateClientID} = require('./ClientID');
21
+ const {
22
+ RELAY_RESOLVER_VALUE_KEY,
23
+ RELAY_RESOLVER_INVALIDATION_KEY,
24
+ RELAY_RESOLVER_INPUTS_KEY,
25
+ RELAY_RESOLVER_READER_SELECTOR_KEY,
26
+ getStorageKey,
27
+ } = require('./RelayStoreUtils');
28
+
29
+ import type {ReaderRelayResolver} from '../util/ReaderNode';
30
+ import type {DataID, Variables} from '../util/RelayRuntimeTypes';
31
+ import type {
32
+ MutableRecordSource,
33
+ Record,
34
+ SingularReaderSelector,
35
+ } from './RelayStoreTypes';
36
+
37
+ type ResolverID = string;
38
+
39
+ type EvaluationResult<T> = {|
40
+ resolverResult: T,
41
+ fragmentValue: {...},
42
+ resolverID: ResolverID,
43
+ seenRecordIDs: Set<DataID>,
44
+ readerSelector: SingularReaderSelector,
45
+ |};
46
+
47
+ export interface ResolverCache {
48
+ readFromCacheOrEvaluate<T>(
49
+ record: Record,
50
+ field: ReaderRelayResolver,
51
+ variables: Variables,
52
+ evaluate: () => EvaluationResult<T>,
53
+ getDataForResolverFragment: (SingularReaderSelector) => mixed,
54
+ ): [T /* Answer */, ?DataID /* Seen record */];
55
+ invalidateDataIDs(
56
+ updatedDataIDs: Set<DataID>, // Mutated in place
57
+ ): void;
58
+ }
59
+
60
+ // $FlowFixMe[unclear-type] - will always be empty
61
+ const emptySet: $ReadOnlySet<any> = new Set();
62
+
63
+ class NoopResolverCache implements ResolverCache {
64
+ readFromCacheOrEvaluate<T>(
65
+ record: Record,
66
+ field: ReaderRelayResolver,
67
+ variables: Variables,
68
+ evaluate: () => EvaluationResult<T>,
69
+ getDataForResolverFragment: SingularReaderSelector => mixed,
70
+ ): [T /* Answer */, ?DataID /* Seen record */] {
71
+ return [evaluate().resolverResult, undefined];
72
+ }
73
+ invalidateDataIDs(updatedDataIDs: Set<DataID>): void {}
74
+ }
75
+
76
+ function addDependencyEdge(edges, from, to): void {
77
+ let set = edges.get(from);
78
+ if (!set) {
79
+ set = new Set();
80
+ edges.set(from, set);
81
+ }
82
+ set.add(to);
83
+ }
84
+
85
+ class RecordResolverCache implements ResolverCache {
86
+ _resolverIDToRecordIDs: Map<ResolverID, Set<DataID>>;
87
+ _recordIDToResolverIDs: Map<DataID, Set<ResolverID>>;
88
+
89
+ _getRecordSource: () => MutableRecordSource;
90
+
91
+ constructor(getRecordSource: () => MutableRecordSource) {
92
+ this._resolverIDToRecordIDs = new Map();
93
+ this._recordIDToResolverIDs = new Map();
94
+ this._getRecordSource = getRecordSource;
95
+ }
96
+
97
+ readFromCacheOrEvaluate<T>(
98
+ record: Record,
99
+ field: ReaderRelayResolver,
100
+ variables: Variables,
101
+ evaluate: () => EvaluationResult<T>,
102
+ getDataForResolverFragment: SingularReaderSelector => mixed,
103
+ ): [T /* Answer */, ?DataID /* Seen record */] {
104
+ const recordSource = this._getRecordSource();
105
+ const recordID = RelayModernRecord.getDataID(record);
106
+
107
+ const storageKey = getStorageKey(field, variables);
108
+ let linkedID = RelayModernRecord.getLinkedRecordID(record, storageKey);
109
+ let linkedRecord = linkedID == null ? null : recordSource.get(linkedID);
110
+ if (
111
+ linkedRecord == null ||
112
+ this._isInvalid(linkedRecord, getDataForResolverFragment)
113
+ ) {
114
+ // Cache miss; evaluate the selector and store the result in a new record:
115
+ linkedID = linkedID ?? generateClientID(recordID, storageKey);
116
+ linkedRecord = RelayModernRecord.create(linkedID, '__RELAY_RESOLVER__');
117
+
118
+ const evaluationResult = evaluate();
119
+ RelayModernRecord.setValue(
120
+ linkedRecord,
121
+ RELAY_RESOLVER_VALUE_KEY,
122
+ evaluationResult.resolverResult,
123
+ );
124
+ RelayModernRecord.setValue(
125
+ linkedRecord,
126
+ RELAY_RESOLVER_INPUTS_KEY,
127
+ evaluationResult.fragmentValue,
128
+ );
129
+ RelayModernRecord.setValue(
130
+ linkedRecord,
131
+ RELAY_RESOLVER_READER_SELECTOR_KEY,
132
+ evaluationResult.readerSelector,
133
+ );
134
+ recordSource.set(linkedID, linkedRecord);
135
+
136
+ // Link the resolver value record to the resolver field of the record being read:
137
+ const nextRecord = RelayModernRecord.clone(record);
138
+ RelayModernRecord.setLinkedRecordID(nextRecord, storageKey, linkedID);
139
+ recordSource.set(RelayModernRecord.getDataID(nextRecord), nextRecord);
140
+
141
+ // Put records observed by the resolver into the dependency graph:
142
+ const resolverID = evaluationResult.resolverID;
143
+ addDependencyEdge(this._resolverIDToRecordIDs, resolverID, linkedID);
144
+ addDependencyEdge(this._recordIDToResolverIDs, recordID, resolverID);
145
+ for (const seenRecordID of evaluationResult.seenRecordIDs) {
146
+ addDependencyEdge(
147
+ this._recordIDToResolverIDs,
148
+ seenRecordID,
149
+ resolverID,
150
+ );
151
+ }
152
+ }
153
+
154
+ // $FlowFixMe[incompatible-type] - will always be empty
155
+ const answer: T = linkedRecord[RELAY_RESOLVER_VALUE_KEY];
156
+ return [answer, linkedID];
157
+ }
158
+
159
+ invalidateDataIDs(
160
+ updatedDataIDs: Set<DataID>, // Mutated in place
161
+ ): void {
162
+ const recordSource = this._getRecordSource();
163
+ const visited: Set<string> = new Set();
164
+ const recordsToVisit = Array.from(updatedDataIDs);
165
+ while (recordsToVisit.length) {
166
+ const recordID = recordsToVisit.pop();
167
+ updatedDataIDs.add(recordID);
168
+ for (const fragment of this._recordIDToResolverIDs.get(recordID) ??
169
+ emptySet) {
170
+ if (!visited.has(fragment)) {
171
+ for (const anotherRecordID of this._resolverIDToRecordIDs.get(
172
+ fragment,
173
+ ) ?? emptySet) {
174
+ this._markInvalidatedResolverRecord(
175
+ anotherRecordID,
176
+ recordSource,
177
+ updatedDataIDs,
178
+ );
179
+ if (!visited.has(anotherRecordID)) {
180
+ recordsToVisit.push(anotherRecordID);
181
+ }
182
+ }
183
+ }
184
+ }
185
+ }
186
+ }
187
+
188
+ _markInvalidatedResolverRecord(
189
+ dataID: DataID,
190
+ recordSource: MutableRecordSource, // Written to
191
+ updatedDataIDs: Set<DataID>, // Mutated in place
192
+ ) {
193
+ const record = recordSource.get(dataID);
194
+ if (!record) {
195
+ warning(
196
+ false,
197
+ 'Expected a resolver record with ID %s, but it was missing.',
198
+ dataID,
199
+ );
200
+ return;
201
+ }
202
+ const nextRecord = RelayModernRecord.clone(record);
203
+ RelayModernRecord.setValue(
204
+ nextRecord,
205
+ RELAY_RESOLVER_INVALIDATION_KEY,
206
+ true,
207
+ );
208
+ recordSource.set(dataID, nextRecord);
209
+ }
210
+
211
+ _isInvalid(
212
+ record: Record,
213
+ getDataForResolverFragment: SingularReaderSelector => mixed,
214
+ ): boolean {
215
+ if (!RelayModernRecord.getValue(record, RELAY_RESOLVER_INVALIDATION_KEY)) {
216
+ return false;
217
+ }
218
+ const originalInputs = RelayModernRecord.getValue(
219
+ record,
220
+ RELAY_RESOLVER_INPUTS_KEY,
221
+ );
222
+ // $FlowFixMe[incompatible-type] - storing values in records is not typed
223
+ const readerSelector: ?SingularReaderSelector = RelayModernRecord.getValue(
224
+ record,
225
+ RELAY_RESOLVER_READER_SELECTOR_KEY,
226
+ );
227
+ if (originalInputs == null || readerSelector == null) {
228
+ warning(
229
+ false,
230
+ 'Expected previous inputs and reader selector on resolver record with ID %s, but they were missing.',
231
+ RelayModernRecord.getDataID(record),
232
+ );
233
+ return true;
234
+ }
235
+ const latestValues = getDataForResolverFragment(readerSelector);
236
+ const recycled = recycleNodesInto(originalInputs, latestValues);
237
+ if (recycled !== originalInputs) {
238
+ return true;
239
+ }
240
+ return false;
241
+ }
242
+ }
243
+
244
+ module.exports = {
245
+ NoopResolverCache,
246
+ RecordResolverCache,
247
+ };
@@ -28,7 +28,10 @@ import type {
28
28
  // about what resolver is being executed, which is supplied by putting the
29
29
  // info on this stack before we call the resolver function.
30
30
  type ResolverContext = {|
31
- getDataForResolverFragment: SingularReaderSelector => mixed,
31
+ getDataForResolverFragment: (
32
+ SingularReaderSelector,
33
+ FragmentReference,
34
+ ) => mixed,
32
35
  |};
33
36
  const contextStack: Array<ResolverContext> = [];
34
37
 
@@ -101,7 +104,7 @@ declare function readFragment<
101
104
 
102
105
  function readFragment(
103
106
  fragmentInput: GraphQLTaggedNode,
104
- fragmentRef: mixed,
107
+ fragmentRef: FragmentReference,
105
108
  ): mixed {
106
109
  if (!contextStack.length) {
107
110
  throw new Error(
@@ -119,7 +122,7 @@ function readFragment(
119
122
  fragmentSelector.kind === 'SingularReaderSelector',
120
123
  `Expected a singular reader selector for the fragment of the resolver ${fragmentNode.name}, but it was plural.`,
121
124
  );
122
- return context.getDataForResolverFragment(fragmentSelector);
125
+ return context.getDataForResolverFragment(fragmentSelector, fragmentRef);
123
126
  }
124
127
 
125
128
  module.exports = {readFragment, withResolverContext};
@@ -38,7 +38,7 @@ function createRelayContext(react: React): React$Context<RelayContext | null> {
38
38
  }
39
39
  invariant(
40
40
  react === firstReact,
41
- '[createRelayContext]: You passing a different instance of React',
41
+ '[createRelayContext]: You are passing a different instance of React',
42
42
  react.version,
43
43
  );
44
44
  return relayContext;