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.
- package/index.js +1 -1
- package/index.js.flow +16 -1
- package/lib/index.js +15 -0
- package/lib/multi-actor-environment/ActorIdentifier.js +11 -1
- package/lib/multi-actor-environment/ActorSpecificEnvironment.js +59 -19
- package/lib/multi-actor-environment/ActorUtils.js +27 -0
- package/lib/multi-actor-environment/MultiActorEnvironment.js +305 -55
- package/lib/multi-actor-environment/index.js +5 -1
- package/lib/mutations/RelayRecordSourceSelectorProxy.js +6 -1
- package/lib/mutations/commitMutation.js +4 -1
- package/lib/mutations/validateMutation.js +6 -1
- package/lib/network/RelayObservable.js +3 -1
- package/lib/network/RelayQueryResponseCache.js +19 -3
- package/lib/network/wrapNetworkWithLogObserver.js +78 -0
- package/lib/store/DataChecker.js +110 -40
- package/lib/store/OperationExecutor.js +478 -204
- package/lib/store/RelayConcreteVariables.js +21 -0
- package/lib/store/RelayModernEnvironment.js +41 -85
- package/lib/store/RelayModernFragmentSpecResolver.js +48 -22
- package/lib/store/RelayModernRecord.js +35 -1
- package/lib/store/RelayModernStore.js +48 -14
- package/lib/store/RelayOperationTracker.js +33 -23
- package/lib/store/RelayPublishQueue.js +23 -5
- package/lib/store/RelayReader.js +138 -44
- package/lib/store/RelayRecordSource.js +87 -3
- package/lib/store/RelayReferenceMarker.js +28 -15
- package/lib/store/RelayResponseNormalizer.js +164 -91
- package/lib/store/RelayStoreReactFlightUtils.js +1 -7
- package/lib/store/RelayStoreSubscriptions.js +8 -5
- package/lib/store/RelayStoreUtils.js +7 -2
- package/lib/store/ResolverCache.js +213 -0
- package/lib/store/ResolverFragments.js +1 -1
- package/lib/store/createRelayContext.js +1 -1
- package/lib/subscription/requestSubscription.js +27 -29
- package/lib/util/RelayConcreteNode.js +1 -0
- package/lib/util/RelayFeatureFlags.js +3 -5
- package/lib/util/RelayReplaySubject.js +21 -6
- package/lib/util/getPaginationMetadata.js +41 -0
- package/lib/util/getPaginationVariables.js +67 -0
- package/lib/util/getPendingOperationsForFragment.js +55 -0
- package/lib/util/getRefetchMetadata.js +36 -0
- package/lib/util/getValueAtPath.js +51 -0
- package/lib/util/isEmptyObject.js +1 -1
- package/lib/util/registerEnvironmentWithDevTools.js +26 -0
- package/lib/util/withDuration.js +31 -0
- package/multi-actor-environment/ActorIdentifier.js.flow +17 -1
- package/multi-actor-environment/ActorSpecificEnvironment.js.flow +72 -44
- package/multi-actor-environment/ActorUtils.js.flow +33 -0
- package/multi-actor-environment/MultiActorEnvironment.js.flow +332 -80
- package/multi-actor-environment/MultiActorEnvironmentTypes.js.flow +61 -12
- package/multi-actor-environment/index.js.flow +3 -0
- package/mutations/RelayRecordSourceSelectorProxy.js.flow +7 -2
- package/mutations/commitMutation.js.flow +2 -0
- package/mutations/validateMutation.js.flow +8 -0
- package/network/RelayObservable.js.flow +2 -0
- package/network/RelayQueryResponseCache.js.flow +31 -18
- package/network/wrapNetworkWithLogObserver.js.flow +99 -0
- package/package.json +1 -1
- package/relay-runtime.js +2 -2
- package/relay-runtime.min.js +2 -2
- package/store/ClientID.js.flow +5 -1
- package/store/DataChecker.js.flow +126 -35
- package/store/OperationExecutor.js.flow +528 -265
- package/store/RelayConcreteVariables.js.flow +26 -1
- package/store/RelayModernEnvironment.js.flow +41 -94
- package/store/RelayModernFragmentSpecResolver.js.flow +40 -14
- package/store/RelayModernOperationDescriptor.js.flow +9 -3
- package/store/RelayModernRecord.js.flow +49 -0
- package/store/RelayModernStore.js.flow +50 -12
- package/store/RelayOperationTracker.js.flow +56 -34
- package/store/RelayPublishQueue.js.flow +31 -8
- package/store/RelayReader.js.flow +148 -42
- package/store/RelayRecordSource.js.flow +72 -6
- package/store/RelayReferenceMarker.js.flow +29 -12
- package/store/RelayResponseNormalizer.js.flow +164 -48
- package/store/RelayStoreReactFlightUtils.js.flow +1 -7
- package/store/RelayStoreSubscriptions.js.flow +10 -3
- package/store/RelayStoreTypes.js.flow +128 -12
- package/store/RelayStoreUtils.js.flow +17 -3
- package/store/ResolverCache.js.flow +247 -0
- package/store/ResolverFragments.js.flow +6 -3
- package/store/createRelayContext.js.flow +1 -1
- package/subscription/requestSubscription.js.flow +41 -29
- package/util/NormalizationNode.js.flow +10 -3
- package/util/ReaderNode.js.flow +15 -1
- package/util/RelayConcreteNode.js.flow +1 -0
- package/util/RelayFeatureFlags.js.flow +8 -10
- package/util/RelayReplaySubject.js.flow +7 -6
- package/util/getPaginationMetadata.js.flow +74 -0
- package/util/getPaginationVariables.js.flow +112 -0
- package/util/getPendingOperationsForFragment.js.flow +62 -0
- package/util/getRefetchMetadata.js.flow +80 -0
- package/util/getValueAtPath.js.flow +46 -0
- package/util/isEmptyObject.js.flow +1 -0
- package/util/registerEnvironmentWithDevTools.js.flow +33 -0
- package/util/withDuration.js.flow +32 -0
- package/lib/store/RelayRecordSourceMapImpl.js +0 -107
- package/lib/store/RelayStoreSubscriptionsUsingMapByID.js +0 -318
- package/store/RelayRecordSourceMapImpl.js.flow +0 -91
- 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
|
|
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:
|
|
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
|
-
+
|
|
503
|
+
+networkRequestId: number,
|
|
472
504
|
+info: mixed,
|
|
473
505
|
|}
|
|
474
506
|
| {|
|
|
475
507
|
+name: 'network.start',
|
|
476
|
-
+
|
|
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
|
-
+
|
|
515
|
+
+networkRequestId: number,
|
|
484
516
|
+response: GraphQLResponse,
|
|
485
517
|
|}
|
|
486
518
|
| {|
|
|
487
519
|
+name: 'network.error',
|
|
488
|
-
+
|
|
520
|
+
+networkRequestId: number,
|
|
489
521
|
+error: Error,
|
|
490
522
|
|}
|
|
491
523
|
| {|
|
|
492
524
|
+name: 'network.complete',
|
|
493
|
-
+
|
|
525
|
+
+networkRequestId: number,
|
|
494
526
|
|}
|
|
495
527
|
| {|
|
|
496
528
|
+name: 'network.unsubscribe',
|
|
497
|
-
+
|
|
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
|
|
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
|
-
+
|
|
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 {
|
|
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:
|
|
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
|
|
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:
|
|
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:
|
|
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;
|