relay-runtime 18.1.0 → 19.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 (92) hide show
  1. package/experimental.js +1 -1
  2. package/experimental.js.flow +22 -9
  3. package/handlers/connection/ConnectionHandler.js.flow +6 -1
  4. package/index.js +1 -1
  5. package/index.js.flow +4 -0
  6. package/lib/experimental.js +5 -2
  7. package/lib/handlers/connection/ConnectionHandler.js +1 -1
  8. package/lib/index.js +3 -0
  9. package/lib/multi-actor-environment/ActorSpecificEnvironment.js +1 -1
  10. package/lib/mutations/RelayRecordProxy.js +14 -3
  11. package/lib/mutations/RelayRecordSourceMutator.js +17 -0
  12. package/lib/mutations/RelayRecordSourceProxy.js +2 -1
  13. package/lib/mutations/createUpdatableProxy.js +1 -1
  14. package/lib/mutations/validateMutation.js +2 -2
  15. package/lib/network/RelayObservable.js +1 -3
  16. package/lib/network/wrapNetworkWithLogObserver.js +2 -2
  17. package/lib/query/fetchQuery.js +1 -1
  18. package/lib/store/DataChecker.js +4 -5
  19. package/lib/store/OperationExecutor.js +11 -0
  20. package/lib/store/RelayModernEnvironment.js +13 -4
  21. package/lib/store/RelayModernFragmentSpecResolver.js +4 -4
  22. package/lib/store/RelayModernStore.js +43 -21
  23. package/lib/store/RelayPublishQueue.js +11 -15
  24. package/lib/store/RelayReader.js +244 -183
  25. package/lib/store/RelayReferenceMarker.js +3 -4
  26. package/lib/store/RelayResponseNormalizer.js +48 -26
  27. package/lib/store/RelayStoreSubscriptions.js +2 -2
  28. package/lib/store/RelayStoreUtils.js +8 -0
  29. package/lib/store/ResolverCache.js +1 -165
  30. package/lib/store/ResolverFragments.js +2 -2
  31. package/lib/store/createRelayLoggingContext.js +17 -0
  32. package/lib/store/generateTypenamePrefixedDataID.js +9 -0
  33. package/lib/store/live-resolvers/LiveResolverCache.js +5 -10
  34. package/lib/store/live-resolvers/resolverDataInjector.js +4 -4
  35. package/lib/store/observeFragmentExperimental.js +60 -13
  36. package/lib/store/observeQueryExperimental.js +21 -0
  37. package/lib/util/RelayFeatureFlags.js +7 -2
  38. package/lib/util/handlePotentialSnapshotErrors.js +12 -9
  39. package/multi-actor-environment/ActorSpecificEnvironment.js.flow +1 -0
  40. package/mutations/RelayRecordProxy.js.flow +30 -3
  41. package/mutations/RelayRecordSourceMutator.js.flow +27 -0
  42. package/mutations/RelayRecordSourceProxy.js.flow +4 -0
  43. package/mutations/createUpdatableProxy.js.flow +1 -1
  44. package/mutations/validateMutation.js.flow +3 -3
  45. package/network/RelayNetworkTypes.js.flow +3 -0
  46. package/network/RelayObservable.js.flow +1 -5
  47. package/network/wrapNetworkWithLogObserver.js.flow +19 -1
  48. package/package.json +1 -1
  49. package/query/fetchQuery.js.flow +1 -1
  50. package/store/DataChecker.js.flow +5 -2
  51. package/store/OperationExecutor.js.flow +12 -1
  52. package/store/RelayExperimentalGraphResponseTransform.js.flow +4 -4
  53. package/store/RelayModernEnvironment.js.flow +22 -6
  54. package/store/RelayModernFragmentSpecResolver.js.flow +6 -6
  55. package/store/RelayModernRecord.js.flow +1 -1
  56. package/store/RelayModernSelector.js.flow +2 -0
  57. package/store/RelayModernStore.js.flow +74 -27
  58. package/store/RelayOptimisticRecordSource.js.flow +2 -0
  59. package/store/RelayPublishQueue.js.flow +32 -21
  60. package/store/RelayReader.js.flow +400 -145
  61. package/store/RelayRecordState.js.flow +1 -1
  62. package/store/RelayReferenceMarker.js.flow +3 -4
  63. package/store/RelayResponseNormalizer.js.flow +94 -62
  64. package/store/RelayStoreSubscriptions.js.flow +2 -2
  65. package/store/RelayStoreTypes.js.flow +45 -15
  66. package/store/RelayStoreUtils.js.flow +30 -1
  67. package/store/ResolverCache.js.flow +2 -271
  68. package/store/ResolverFragments.js.flow +5 -3
  69. package/store/StoreInspector.js.flow +5 -0
  70. package/store/createRelayContext.js.flow +3 -2
  71. package/store/createRelayLoggingContext.js.flow +46 -0
  72. package/store/generateTypenamePrefixedDataID.js.flow +25 -0
  73. package/store/live-resolvers/LiveResolverCache.js.flow +5 -10
  74. package/store/live-resolvers/resolverDataInjector.js.flow +10 -6
  75. package/store/observeFragmentExperimental.js.flow +82 -28
  76. package/store/observeQueryExperimental.js.flow +61 -0
  77. package/store/waitForFragmentExperimental.js.flow +4 -3
  78. package/util/NormalizationNode.js.flow +10 -1
  79. package/util/ReaderNode.js.flow +9 -3
  80. package/util/RelayConcreteNode.js.flow +3 -1
  81. package/util/RelayError.js.flow +1 -0
  82. package/util/RelayFeatureFlags.js.flow +31 -7
  83. package/util/RelayRuntimeTypes.js.flow +17 -3
  84. package/util/getPaginationVariables.js.flow +2 -0
  85. package/util/handlePotentialSnapshotErrors.js.flow +24 -12
  86. package/util/registerEnvironmentWithDevTools.js.flow +4 -2
  87. package/util/withProvidedVariables.js.flow +1 -0
  88. package/util/withStartAndDuration.js.flow +3 -0
  89. package/relay-runtime-experimental.js +0 -4
  90. package/relay-runtime-experimental.min.js +0 -9
  91. package/relay-runtime.js +0 -4
  92. package/relay-runtime.min.js +0 -9
@@ -9,7 +9,7 @@
9
9
  * @oncall relay
10
10
  */
11
11
 
12
- import type {RequestDescriptor} from './RelayStoreTypes';
12
+ import type {PluralReaderSelector, RequestDescriptor} from './RelayStoreTypes';
13
13
  import type {
14
14
  Fragment,
15
15
  FragmentType,
@@ -20,8 +20,8 @@ import type {
20
20
  } from 'relay-runtime';
21
21
 
22
22
  const Observable = require('../network/RelayObservable');
23
+ const {getObservableForActiveRequest} = require('../query/fetchQueryInternal');
23
24
  const {getFragment} = require('../query/GraphQLTag');
24
- const getPendingOperationsForFragment = require('../util/getPendingOperationsForFragment');
25
25
  const {
26
26
  handlePotentialSnapshotErrors,
27
27
  } = require('../util/handlePotentialSnapshotErrors');
@@ -47,8 +47,7 @@ export type HasSpread<TFragmentType> = {
47
47
 
48
48
  /**
49
49
  * EXPERIMENTAL: This API is experimental and does not yet support all Relay
50
- * features. Notably, it does not correectly handle plural fragments or some
51
- * features of Relay Resolvers.
50
+ * features. Notably, it does not correctly handle some features of Relay Resolvers.
52
51
  *
53
52
  * Given a fragment and a fragment reference, returns a promise that resolves
54
53
  * once the fragment data is available, or rejects if the fragment has an error.
@@ -63,7 +62,9 @@ export type HasSpread<TFragmentType> = {
63
62
  async function waitForFragmentData<TFragmentType: FragmentType, TData>(
64
63
  environment: IEnvironment,
65
64
  fragment: Fragment<TFragmentType, TData>,
66
- fragmentRef: HasSpread<TFragmentType>,
65
+ fragmentRef:
66
+ | HasSpread<TFragmentType>
67
+ | $ReadOnlyArray<HasSpread<TFragmentType>>,
67
68
  ): Promise<TData> {
68
69
  let subscription: ?Subscription;
69
70
 
@@ -94,13 +95,14 @@ async function waitForFragmentData<TFragmentType: FragmentType, TData>(
94
95
  declare function observeFragment<TFragmentType: FragmentType, TData>(
95
96
  environment: IEnvironment,
96
97
  fragment: Fragment<TFragmentType, TData>,
97
- fragmentRef: HasSpread<TFragmentType>,
98
+ fragmentRef:
99
+ | HasSpread<TFragmentType>
100
+ | $ReadOnlyArray<HasSpread<TFragmentType>>,
98
101
  ): Observable<FragmentState<TData>>;
99
102
 
100
103
  /**
101
104
  * EXPERIMENTAL: This API is experimental and does not yet support all Relay
102
- * features. Notably, it does not correectly handle plural fragments or some
103
- * features of Relay Resolvers.
105
+ * features. Notably, it does not correctly handle some features of Relay Resolvers.
104
106
  *
105
107
  * Given a fragment and a fragment reference, returns an observable that emits
106
108
  * the state of the fragment over time. The observable will emit the following
@@ -114,7 +116,7 @@ function observeFragment<TFragmentType: FragmentType, TData>(
114
116
  environment: IEnvironment,
115
117
  fragment: Fragment<TFragmentType, TData>,
116
118
  fragmentRef: mixed,
117
- ): Observable<FragmentState<TData>> {
119
+ ): mixed {
118
120
  const fragmentNode = getFragment(fragment);
119
121
  const fragmentSelector = getSelector(fragmentNode, fragmentRef);
120
122
  invariant(
@@ -124,24 +126,19 @@ function observeFragment<TFragmentType: FragmentType, TData>(
124
126
  invariant(fragmentSelector != null, 'Expected a selector, got null.');
125
127
  switch (fragmentSelector.kind) {
126
128
  case 'SingularReaderSelector':
127
- return observeSelector(environment, fragment, fragmentSelector);
129
+ return observeSingularSelector(environment, fragment, fragmentSelector);
128
130
  case 'PluralReaderSelector': {
129
- // TODO: We could use something like this RXJS's combineLatest to create
130
- // an observable for each selector and merge them.
131
- // https://github.com/ReactiveX/rxjs/blob/master/packages/rxjs/src/internal/observable/combineLatest.ts
132
- //
133
- // Note that this problem is a bit tricky because Relay currently only
134
- // lets you subscribe at a singular fragment granularity. This makes it
135
- // hard to batch updates such that when a store update causes multiple
136
- // fragments to change, we can only publish a single update to the
137
- // fragment owner.
138
- invariant(false, 'Plural fragments are not supported');
131
+ return observePluralSelector(
132
+ environment,
133
+ (fragment: $FlowFixMe),
134
+ fragmentSelector,
135
+ );
139
136
  }
140
137
  }
141
138
  invariant(false, 'Unsupported fragment selector kind');
142
139
  }
143
140
 
144
- function observeSelector<TFragmentType: FragmentType, TData>(
141
+ function observeSingularSelector<TFragmentType: FragmentType, TData>(
145
142
  environment: IEnvironment,
146
143
  fragmentNode: Fragment<TFragmentType, TData>,
147
144
  fragmentSelector: SingularReaderSelector,
@@ -173,6 +170,49 @@ function observeSelector<TFragmentType: FragmentType, TData>(
173
170
  });
174
171
  }
175
172
 
173
+ function observePluralSelector<
174
+ TFragmentType: FragmentType,
175
+ TData: Array<mixed>,
176
+ >(
177
+ environment: IEnvironment,
178
+ fragmentNode: Fragment<TFragmentType, TData>,
179
+ fragmentSelector: PluralReaderSelector,
180
+ ): Observable<FragmentState<TData>> {
181
+ const snapshots = fragmentSelector.selectors.map(selector =>
182
+ environment.lookup(selector),
183
+ );
184
+
185
+ return Observable.create(sink => {
186
+ // This array is mutable since each subscription updates the array in place.
187
+ const states = snapshots.map((snapshot, index) =>
188
+ snapshotToFragmentState(
189
+ environment,
190
+ fragmentNode,
191
+ fragmentSelector.selectors[index].owner,
192
+ snapshot,
193
+ ),
194
+ );
195
+
196
+ sink.next((mergeFragmentStates(states): $FlowFixMe));
197
+
198
+ const subscriptions = snapshots.map((snapshot, index) =>
199
+ environment.subscribe(snapshot, latestSnapshot => {
200
+ states[index] = snapshotToFragmentState(
201
+ environment,
202
+ fragmentNode,
203
+ fragmentSelector.selectors[index].owner,
204
+ latestSnapshot,
205
+ );
206
+ // This doesn't batch updates, so it will notify the subscriber multiple times
207
+ // if a store update impacting multiple items in the list is published.
208
+ sink.next((mergeFragmentStates(states): $FlowFixMe));
209
+ }),
210
+ );
211
+
212
+ return () => subscriptions.forEach(subscription => subscription.dispose());
213
+ });
214
+ }
215
+
176
216
  function snapshotToFragmentState<TFragmentType: FragmentType, TData>(
177
217
  environment: IEnvironment,
178
218
  fragmentNode: Fragment<TFragmentType, TData>,
@@ -201,18 +241,18 @@ function snapshotToFragmentState<TFragmentType: FragmentType, TData>(
201
241
  }
202
242
 
203
243
  if (snapshot.isMissingData) {
204
- const pendingOperations = getPendingOperationsForFragment(
205
- environment,
206
- fragmentNode,
207
- owner,
208
- );
209
- if (pendingOperations != null) {
244
+ if (
245
+ getObservableForActiveRequest(environment, owner) != null ||
246
+ environment
247
+ .getOperationTracker()
248
+ .getPendingOperationsAffectingOwner(owner) != null
249
+ ) {
210
250
  return {state: 'loading'};
211
251
  }
212
252
  }
213
253
 
214
254
  try {
215
- handlePotentialSnapshotErrors(environment, snapshot.errorResponseFields);
255
+ handlePotentialSnapshotErrors(environment, snapshot.fieldErrors);
216
256
  } catch (error) {
217
257
  return {error, state: 'error'};
218
258
  }
@@ -229,6 +269,20 @@ function snapshotToFragmentState<TFragmentType: FragmentType, TData>(
229
269
  return {state: 'ok', value: (snapshot.data: $FlowFixMe)};
230
270
  }
231
271
 
272
+ function mergeFragmentStates<T>(
273
+ states: $ReadOnlyArray<FragmentState<T>>,
274
+ ): FragmentState<Array<T>> {
275
+ const value = [];
276
+ for (const state of states) {
277
+ if (state.state === 'ok') {
278
+ value.push(state.value);
279
+ } else {
280
+ return state;
281
+ }
282
+ }
283
+ return {state: 'ok', value};
284
+ }
285
+
232
286
  module.exports = {
233
287
  observeFragment,
234
288
  waitForFragmentData,
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and 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
+ * @oncall relay
10
+ */
11
+
12
+ 'use strict';
13
+
14
+ import type RelayObservable from '../network/RelayObservable';
15
+ import type {FragmentState} from './observeFragmentExperimental';
16
+ import type {OperationDescriptor} from './RelayStoreTypes';
17
+ import type {Fragment, IEnvironment, Query, Variables} from 'relay-runtime';
18
+
19
+ const {observeFragment} = require('./observeFragmentExperimental');
20
+ const {createOperationDescriptor} = require('./RelayModernOperationDescriptor');
21
+
22
+ /**
23
+ * This function returns an observable that can be used to subscribe to the data
24
+ * contained in a query. It does not return the full response shape, but rather
25
+ * the contents of the query body minus any fragment spreads. If you wish to
26
+ * read the contents of a fragment spread into this query you may pass the
27
+ * object into which the fragment was spread to `observeFragment`.
28
+ *
29
+ * NOTE: `observeQuery` assumes that you have already fetched and retained the
30
+ * query via some other means, such as `fetchQuery`.
31
+ *
32
+ * This feature is still experimental and does not properly handle some resolver
33
+ * features such as client-to-server edges.
34
+ */
35
+ function observeQuery<TVariables: Variables, TData>(
36
+ environment: IEnvironment,
37
+ gqlQuery: Query<TVariables, TData>,
38
+ variables: TVariables,
39
+ ): RelayObservable<FragmentState<TData>> {
40
+ const operation: OperationDescriptor = createOperationDescriptor(
41
+ gqlQuery,
42
+ variables,
43
+ );
44
+
45
+ const rootFragmentRef: $FlowFixMe = {
46
+ __id: operation.fragment.dataID,
47
+ __fragments: {
48
+ [operation.fragment.node.name]: operation.request.variables,
49
+ },
50
+ __fragmentOwner: operation.request,
51
+ };
52
+
53
+ const fragmentNode: Fragment<$FlowFixMe, TData> = (operation.request.node
54
+ .fragment: $FlowFixMe);
55
+
56
+ return observeFragment(environment, fragmentNode, rootFragmentRef);
57
+ }
58
+
59
+ module.exports = {
60
+ observeQuery,
61
+ };
@@ -21,8 +21,7 @@ const {observeFragment} = require('./observeFragmentExperimental');
21
21
 
22
22
  /**
23
23
  * EXPERIMENTAL: This API is experimental and does not yet support all Relay
24
- * features. Notably, it does not correectly handle plural fragments or some
25
- * features of Relay Resolvers.
24
+ * features. Notably, it does not correctly handle some features of Relay Resolvers.
26
25
  *
27
26
  * Given a fragment and a fragment reference, returns a promise that resolves
28
27
  * once the fragment data is available, or rejects if the fragment has an error.
@@ -37,7 +36,9 @@ const {observeFragment} = require('./observeFragmentExperimental');
37
36
  async function waitForFragmentData<TFragmentType: FragmentType, TData>(
38
37
  environment: IEnvironment,
39
38
  fragment: Fragment<TFragmentType, TData>,
40
- fragmentRef: HasSpread<TFragmentType>,
39
+ fragmentRef:
40
+ | HasSpread<TFragmentType>
41
+ | $ReadOnlyArray<HasSpread<TFragmentType>>,
41
42
  ): Promise<TData> {
42
43
  let subscription: ?Subscription;
43
44
 
@@ -12,7 +12,7 @@
12
12
  'use strict';
13
13
 
14
14
  import type {ResolverFunction, ResolverModule} from './ReaderNode';
15
- import type {ConcreteRequest} from './RelayConcreteNode';
15
+ import type {ConcreteRequest, ProvidedVariableType} from './RelayConcreteNode';
16
16
  import type {JSResourceReference} from 'JSResourceReference';
17
17
 
18
18
  /**
@@ -27,6 +27,8 @@ export type NormalizationOperation = {
27
27
  +clientAbstractTypes?: {
28
28
  +[string]: $ReadOnlyArray<string>,
29
29
  },
30
+ +use_exec_time_resolvers?: boolean,
31
+ +exec_time_resolvers_enabled_provider?: ProvidedVariableType,
30
32
  };
31
33
 
32
34
  export type NormalizationHandle =
@@ -200,10 +202,17 @@ export type NormalizationLiveResolverField = {
200
202
  ...ResolverData,
201
203
  };
202
204
 
205
+ export type NormalizationModelResolvers = {
206
+ [string]: {
207
+ +resolverModule: ResolverModule,
208
+ },
209
+ };
210
+
203
211
  export type NormalizationClientEdgeToClientObject = {
204
212
  +kind: 'ClientEdgeToClientObject',
205
213
  +linkedField: NormalizationLinkedField,
206
214
  +backingField: NormalizationResolverField | NormalizationLiveResolverField,
215
+ +modelResolvers?: NormalizationModelResolvers | null,
207
216
  };
208
217
 
209
218
  export type NormalizationClientComponent = {
@@ -37,7 +37,10 @@ export type ReaderFragment = {
37
37
  +abstractKey?: ?string,
38
38
  +metadata?: ?{
39
39
  +connection?: $ReadOnlyArray<ConnectionMetadata>,
40
+ // Indicates if the fragment has been annotated with `@throwOnFieldError`
40
41
  +throwOnFieldError?: boolean,
42
+ // Indicates if the fragment has been annotated with `@catch`
43
+ +catchTo?: CatchFieldTo,
41
44
  +hasClientEdges?: boolean,
42
45
  +mask?: boolean,
43
46
  +plural?: boolean,
@@ -80,6 +83,7 @@ export type ReaderRefetchMetadata = {
80
83
  +operation: string | ConcreteRequest,
81
84
  +fragmentPathInResult: Array<string>,
82
85
  +identifierInfo?: ?RefetchableIdentifierInfo,
86
+ +edgesFragment?: ReaderFragment,
83
87
  };
84
88
 
85
89
  // Stricter form of ConnectionMetadata
@@ -231,16 +235,18 @@ export type ReaderRequiredField = {
231
235
  +kind: 'RequiredField',
232
236
  +field: ReaderField | ReaderClientEdge,
233
237
  +action: RequiredFieldAction,
234
- +path: string,
238
+ // TODO: This field is not used any more, we should be able to remove it.
239
+ +path?: mixed,
235
240
  };
236
241
 
237
242
  export type CatchFieldTo = 'RESULT' | 'NULL';
238
243
 
239
244
  export type ReaderCatchField = {
240
245
  +kind: 'CatchField',
241
- +field: ReaderField | ReaderClientEdge,
246
+ +field: ReaderField | ReaderClientEdge | ReaderAliasedInlineFragmentSpread,
242
247
  +to: CatchFieldTo,
243
- +path: string,
248
+ // TODO: This field is not used any more, we should be able to remove it.
249
+ +path?: mixed,
244
250
  };
245
251
 
246
252
  export type ResolverFunction = (...args: Array<any>) => mixed; // flowlint-line unclear-type:off
@@ -39,6 +39,8 @@ export type NormalizationRootNode =
39
39
  | ConcreteRequest
40
40
  | NormalizationSplitOperation;
41
41
 
42
+ export type ProvidedVariableType = {get(): mixed};
43
+
42
44
  export type ProvidedVariablesType = {+[key: string]: {get(): mixed}};
43
45
 
44
46
  /**
@@ -130,6 +132,6 @@ const RelayConcreteNode = {
130
132
  TYPE_DISCRIMINATOR: 'TypeDiscriminator',
131
133
  UPDATABLE_QUERY: 'UpdatableQuery',
132
134
  VARIABLE: 'Variable',
133
- };
135
+ } as const;
134
136
 
135
137
  module.exports = RelayConcreteNode;
@@ -25,6 +25,7 @@ function createError(
25
25
  String(messageParams[index++]),
26
26
  );
27
27
  const err = new Error(message);
28
+ // $FlowFixMe[unsafe-object-assign]
28
29
  const error = Object.assign((err: any), {
29
30
  name,
30
31
  messageFormat,
@@ -18,7 +18,6 @@ export type FeatureFlags = {
18
18
  ENABLE_RELAY_RESOLVERS: boolean,
19
19
  ENABLE_GETFRAGMENTIDENTIFIER_OPTIMIZATION: boolean,
20
20
  ENABLE_FRIENDLY_QUERY_NAME_GQL_URL: boolean,
21
- ENABLE_LOAD_QUERY_REQUEST_DEDUPING: boolean,
22
21
  ENABLE_DO_NOT_WRAP_LIVE_QUERY: boolean,
23
22
  ENABLE_NOTIFY_SUBSCRIPTION: boolean,
24
23
  BATCH_ASYNC_MODULE_UPDATES_FN: ?(() => void) => Disposable,
@@ -27,6 +26,15 @@ export type FeatureFlags = {
27
26
  STRING_INTERN_LEVEL: number,
28
27
  LOG_MISSING_RECORDS_IN_PROD: boolean,
29
28
  ENABLE_RELAY_OPERATION_TRACKER_SUSPENSE: boolean,
29
+ ENABLE_UI_CONTEXT_ON_RELAY_LOGGER: boolean,
30
+
31
+ // Some GraphQL servers are noncompliant with the GraphQL specification and
32
+ // return an empty list instead of null when there is a field error on a list.
33
+ //
34
+ // If this describes your server, enable this flag so that Relay will treat
35
+ // empty lists as null when deciding whether or not to check for field errors
36
+ // in the response.
37
+ ENABLE_NONCOMPLIANT_ERROR_HANDLING_ON_LISTS: boolean,
30
38
 
31
39
  // Configure RelayStoreSubscriptions to mark a subscription as affected by an
32
40
  // update if there are any overlapping IDs other than ROOT_ID or VIWER_ID,
@@ -48,10 +56,21 @@ export type FeatureFlags = {
48
56
  // Temporary flag to experiment to enable compatibility with React's unstable <Activity> API
49
57
  ENABLE_ACTIVITY_COMPATIBILITY: boolean,
50
58
 
51
- // Gating a fix to prevent infinite loops when invalidating Relay Resolvers.
52
- // We believe the fix to be correct but don't yet have a test to validate it
53
- // fixes the error, so we're gating it for now.
54
- AVOID_CYCLES_IN_RESOLVER_NOTIFICATION: boolean,
59
+ // Adds a prefix to the storage key of read time resolvers. This is used to
60
+ // disambiguate the same resolver being used at both read time and exec time.
61
+ ENABLE_READ_TIME_RESOLVER_STORAGE_KEY_PREFIX: boolean,
62
+
63
+ // Enable the fix for usePaginationFragment stucking in loading state
64
+ ENABLE_USE_PAGINATION_IS_LOADING_FIX: boolean,
65
+
66
+ // Enable logging an ID collision in the Relay store
67
+ ENABLE_STORE_ID_COLLISION_LOGGING: boolean,
68
+
69
+ // Throw on nested store updates
70
+ DISALLOW_NESTED_UPDATES: boolean,
71
+
72
+ // Enable prefixing of DataID in the store with __typename
73
+ ENABLE_TYPENAME_PREFIXED_DATA_ID: boolean,
55
74
  };
56
75
 
57
76
  const RelayFeatureFlags: FeatureFlags = {
@@ -59,7 +78,6 @@ const RelayFeatureFlags: FeatureFlags = {
59
78
  ENABLE_RELAY_RESOLVERS: false,
60
79
  ENABLE_GETFRAGMENTIDENTIFIER_OPTIMIZATION: false,
61
80
  ENABLE_FRIENDLY_QUERY_NAME_GQL_URL: false,
62
- ENABLE_LOAD_QUERY_REQUEST_DEDUPING: true,
63
81
  ENABLE_DO_NOT_WRAP_LIVE_QUERY: false,
64
82
  ENABLE_NOTIFY_SUBSCRIPTION: false,
65
83
  BATCH_ASYNC_MODULE_UPDATES_FN: null,
@@ -67,6 +85,8 @@ const RelayFeatureFlags: FeatureFlags = {
67
85
  MAX_DATA_ID_LENGTH: null,
68
86
  STRING_INTERN_LEVEL: 0,
69
87
  LOG_MISSING_RECORDS_IN_PROD: false,
88
+ ENABLE_STORE_ID_COLLISION_LOGGING: false,
89
+ ENABLE_NONCOMPLIANT_ERROR_HANDLING_ON_LISTS: false,
70
90
  ENABLE_LOOSE_SUBSCRIPTION_ATTRIBUTION: false,
71
91
  ENABLE_OPERATION_TRACKER_OPTIMISTIC_UPDATES: false,
72
92
  ENABLE_RELAY_OPERATION_TRACKER_SUSPENSE: false,
@@ -74,7 +94,11 @@ const RelayFeatureFlags: FeatureFlags = {
74
94
  MARK_RESOLVER_VALUES_AS_CLEAN_AFTER_FRAGMENT_REREAD: false,
75
95
  ENABLE_CYLE_DETECTION_IN_VARIABLES: false,
76
96
  ENABLE_ACTIVITY_COMPATIBILITY: false,
77
- AVOID_CYCLES_IN_RESOLVER_NOTIFICATION: false,
97
+ ENABLE_READ_TIME_RESOLVER_STORAGE_KEY_PREFIX: true,
98
+ ENABLE_USE_PAGINATION_IS_LOADING_FIX: false,
99
+ DISALLOW_NESTED_UPDATES: false,
100
+ ENABLE_TYPENAME_PREFIXED_DATA_ID: false,
101
+ ENABLE_UI_CONTEXT_ON_RELAY_LOGGER: false,
78
102
  };
79
103
 
80
104
  module.exports = RelayFeatureFlags;
@@ -53,9 +53,10 @@ export type VariablesOf<T: OperationType> = T['variables'];
53
53
  * state of any configured response cache.
54
54
  * - `poll`: causes a query to live update by polling at the specified interval
55
55
  * in milliseconds. (This value will be passed to setTimeout.)
56
- * - `liveConfigId`: causes a query to live update by calling GraphQLLiveQuery,
57
- * it represents a configuration of gateway when doing live query
58
- * - `onSubscribe`: Not in use.
56
+ * - `liveConfigId`: Makes a query live by sending through RTI stack.
57
+ * - `onSubscribe`: Callback to be called when a live query stream is started.
58
+ * - `onPause`: Callback to be called when a live query stream is paused, e.g. due to a network disconnection.
59
+ * - `onResume`: Callback to be called when a live query stream is resumed, e.g. from a network disconnection.
59
60
  * - `metadata`: user-supplied metadata.
60
61
  * - `transactionId`: a user-supplied value, intended for use as a unique id for
61
62
  * a given instance of executing an operation.
@@ -65,6 +66,8 @@ export type CacheConfig = {
65
66
  poll?: ?number,
66
67
  liveConfigId?: ?string,
67
68
  onSubscribe?: () => void,
69
+ onResume?: (pauseTimeMs: number) => void,
70
+ onPause?: (mqttConnectionIsOk: boolean, internetIsOk: boolean) => void,
68
71
  metadata?: {[key: string]: mixed, ...},
69
72
  transactionId?: ?string,
70
73
  };
@@ -165,3 +168,14 @@ declare export opaque type RefetchableFragment<
165
168
  +TData,
166
169
  TVariables: Variables,
167
170
  >: Fragment<TFragmentType, TData>;
171
+
172
+ /**
173
+ * Return type of graphql tag literals for `@refetchable` fragments
174
+ * with prefetchable pagination connections.
175
+ */
176
+ declare export opaque type PrefetchableRefetchableFragment<
177
+ TFragmentType,
178
+ +TData,
179
+ +TEdgeData,
180
+ TVariables: Variables,
181
+ >: Fragment<TFragmentType, TData>;
@@ -56,6 +56,7 @@ function getPaginationVariables(
56
56
  ...baseVariables,
57
57
  ...extraVariables,
58
58
  [backwardMetadata.cursor]: cursor,
59
+ // $FlowFixMe[incompatible-type]
59
60
  [backwardMetadata.count]: count,
60
61
  };
61
62
  if (forwardMetadata && forwardMetadata.cursor) {
@@ -92,6 +93,7 @@ function getPaginationVariables(
92
93
  ...baseVariables,
93
94
  ...extraVariables,
94
95
  [forwardMetadata.cursor]: cursor,
96
+ // $FlowFixMe[incompatible-type]
95
97
  [forwardMetadata.count]: count,
96
98
  };
97
99
  if (backwardMetadata && backwardMetadata.cursor) {
@@ -12,8 +12,8 @@
12
12
  'use strict';
13
13
 
14
14
  import type {
15
- ErrorResponseField,
16
- ErrorResponseFields,
15
+ FieldError,
16
+ FieldErrors,
17
17
  IEnvironment,
18
18
  } from '../store/RelayStoreTypes';
19
19
 
@@ -21,25 +21,36 @@ const invariant = require('invariant');
21
21
 
22
22
  function handleFieldErrors(
23
23
  environment: IEnvironment,
24
- errorResponseFields: ErrorResponseFields,
24
+ fieldErrors: FieldErrors,
25
+ loggingContext: mixed | void,
25
26
  ) {
26
- for (const fieldError of errorResponseFields) {
27
+ for (const fieldError of fieldErrors) {
27
28
  // First we log all events. Note that the logger may opt to throw its own
28
29
  // error here if it wants to throw an error that is better integrated into
29
30
  // site's error handling infrastructure.
30
- environment.relayFieldLogger(fieldError);
31
+
32
+ // Awkward. We don't want to attach the ui context in RelayReader where we
33
+ // create the event, but it means we need to add it here instead of just
34
+ // passing the event through.
35
+
36
+ environment.relayFieldLogger({
37
+ // the uiContext on fieldError undefined *always*,
38
+ ...fieldError,
39
+ // and this is where we assign loggingContext to uiContext to populate it
40
+ uiContext: loggingContext,
41
+ });
31
42
  }
32
43
 
33
- for (const fieldError of errorResponseFields) {
44
+ for (const fieldError of fieldErrors) {
34
45
  if (eventShouldThrow(fieldError)) {
35
46
  switch (fieldError.kind) {
36
47
  case 'relay_resolver.error':
37
48
  throw new Error(
38
- `Relay: Resolver error at path '${fieldError.fieldPath}' in '${fieldError.owner}'.`,
49
+ `Relay: Resolver error at path '${fieldError.fieldPath}' in '${fieldError.owner}'. Message: ${fieldError.error.message}`,
39
50
  );
40
51
  case 'relay_field_payload.error':
41
52
  throw new Error(
42
- `Relay: Unexpected response payload - this object includes an errors property in which you can access the underlying errors`,
53
+ `Relay: Unexpected response payload - check server logs for details.`,
43
54
  );
44
55
  case 'missing_expected_data.throw':
45
56
  throw new Error(
@@ -63,7 +74,7 @@ function handleFieldErrors(
63
74
  }
64
75
  }
65
76
 
66
- function eventShouldThrow(event: ErrorResponseField): boolean {
77
+ function eventShouldThrow(event: FieldError): boolean {
67
78
  switch (event.kind) {
68
79
  case 'relay_resolver.error':
69
80
  case 'relay_field_payload.error':
@@ -82,14 +93,15 @@ function eventShouldThrow(event: ErrorResponseField): boolean {
82
93
 
83
94
  function handlePotentialSnapshotErrors(
84
95
  environment: IEnvironment,
85
- errorResponseFields: ?ErrorResponseFields,
96
+ fieldErrors: ?FieldErrors,
97
+ loggingContext: mixed | void,
86
98
  ) {
87
99
  /**
88
100
  * Inside handleFieldErrors, we check for throwOnFieldError - but this fn logs the error anyway by default
89
101
  * which is why this still should run in any case there's errors.
90
102
  */
91
- if (errorResponseFields != null) {
92
- handleFieldErrors(environment, errorResponseFields);
103
+ if (fieldErrors != null) {
104
+ handleFieldErrors(environment, fieldErrors, loggingContext);
93
105
  }
94
106
  }
95
107
 
@@ -19,8 +19,10 @@ function registerEnvironmentWithDevTools(environment: IEnvironment): void {
19
19
  const _global =
20
20
  typeof global !== 'undefined'
21
21
  ? global
22
- : typeof window !== 'undefined'
23
- ? window
22
+ : // $FlowFixMe[cannot-resolve-name]
23
+ typeof window !== 'undefined'
24
+ ? // $FlowFixMe[cannot-resolve-name]
25
+ window
24
26
  : undefined;
25
27
 
26
28
  // $FlowFixMe[incompatible-use] D61394600
@@ -30,6 +30,7 @@ function withProvidedVariables(
30
30
  ): Variables {
31
31
  if (providedVariables != null) {
32
32
  const operationVariables: {[string]: mixed} = {};
33
+ // $FlowFixMe[unsafe-object-assign]
33
34
  Object.assign(operationVariables, userSuppliedVariables);
34
35
  Object.keys(providedVariables).forEach((varName: string) => {
35
36
  const providerFunction = providedVariables[varName].get;
@@ -12,11 +12,14 @@
12
12
  'use strict';
13
13
 
14
14
  const isPerformanceNowAvailable =
15
+ // $FlowFixMe[cannot-resolve-name]
15
16
  typeof window !== 'undefined' &&
17
+ // $FlowFixMe[cannot-resolve-name]
16
18
  typeof window?.performance?.now === 'function';
17
19
 
18
20
  function currentTimestamp(): number {
19
21
  if (isPerformanceNowAvailable) {
22
+ // $FlowFixMe[cannot-resolve-name]
20
23
  return window.performance.now();
21
24
  }
22
25
  return Date.now();