relay-runtime 12.0.0 → 13.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 (216) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +67 -0
  3. package/handlers/RelayDefaultHandlerProvider.js.flow +3 -3
  4. package/handlers/connection/ConnectionHandler.js.flow +9 -18
  5. package/handlers/connection/ConnectionInterface.js.flow +1 -1
  6. package/handlers/connection/MutationHandlers.js.flow +8 -12
  7. package/index.js +2 -2
  8. package/index.js.flow +43 -34
  9. package/lib/handlers/RelayDefaultHandlerProvider.js +1 -1
  10. package/lib/handlers/connection/ConnectionHandler.js +13 -19
  11. package/lib/handlers/connection/ConnectionInterface.js +1 -1
  12. package/lib/handlers/connection/MutationHandlers.js +4 -7
  13. package/lib/index.js +49 -46
  14. package/lib/multi-actor-environment/ActorIdentifier.js +1 -1
  15. package/lib/multi-actor-environment/ActorSpecificEnvironment.js +9 -5
  16. package/lib/multi-actor-environment/ActorUtils.js +1 -1
  17. package/lib/multi-actor-environment/MultiActorEnvironment.js +36 -23
  18. package/lib/multi-actor-environment/MultiActorEnvironmentTypes.js +1 -1
  19. package/lib/multi-actor-environment/index.js +3 -3
  20. package/lib/mutations/RelayDeclarativeMutationConfig.js +5 -2
  21. package/lib/mutations/RelayRecordProxy.js +4 -3
  22. package/lib/mutations/RelayRecordSourceMutator.js +4 -3
  23. package/lib/mutations/RelayRecordSourceProxy.js +13 -5
  24. package/lib/mutations/RelayRecordSourceSelectorProxy.js +13 -5
  25. package/lib/mutations/applyOptimisticMutation.js +7 -7
  26. package/lib/mutations/commitLocalUpdate.js +1 -1
  27. package/lib/mutations/commitMutation.js +16 -15
  28. package/lib/mutations/readUpdatableQuery_EXPERIMENTAL.js +242 -0
  29. package/lib/mutations/validateMutation.js +7 -7
  30. package/lib/network/ConvertToExecuteFunction.js +3 -2
  31. package/lib/network/RelayNetwork.js +4 -3
  32. package/lib/network/RelayNetworkTypes.js +1 -1
  33. package/lib/network/RelayObservable.js +2 -4
  34. package/lib/network/RelayQueryResponseCache.js +3 -3
  35. package/lib/network/wrapNetworkWithLogObserver.js +3 -2
  36. package/lib/query/GraphQLTag.js +3 -2
  37. package/lib/query/PreloadableQueryRegistry.js +1 -1
  38. package/lib/query/fetchQuery.js +7 -6
  39. package/lib/query/fetchQueryInternal.js +1 -1
  40. package/lib/query/fetchQuery_DEPRECATED.js +3 -2
  41. package/lib/store/ClientID.js +8 -2
  42. package/lib/store/DataChecker.js +17 -18
  43. package/lib/store/OperationExecutor.js +14 -14
  44. package/lib/store/RelayConcreteVariables.js +7 -10
  45. package/lib/store/RelayExperimentalGraphResponseHandler.js +153 -0
  46. package/lib/store/RelayExperimentalGraphResponseTransform.js +391 -0
  47. package/lib/store/RelayModernEnvironment.js +67 -43
  48. package/lib/store/RelayModernFragmentSpecResolver.js +9 -9
  49. package/lib/store/RelayModernOperationDescriptor.js +3 -2
  50. package/lib/store/RelayModernRecord.js +13 -12
  51. package/lib/store/RelayModernSelector.js +15 -9
  52. package/lib/store/RelayModernStore.js +15 -16
  53. package/lib/store/RelayOperationTracker.js +1 -1
  54. package/lib/store/RelayOptimisticRecordSource.js +1 -1
  55. package/lib/store/RelayPublishQueue.js +12 -6
  56. package/lib/store/RelayReader.js +131 -40
  57. package/lib/store/RelayRecordSource.js +1 -1
  58. package/lib/store/RelayRecordState.js +1 -1
  59. package/lib/store/RelayReferenceMarker.js +11 -12
  60. package/lib/store/RelayResponseNormalizer.js +26 -23
  61. package/lib/store/RelayStoreReactFlightUtils.js +4 -4
  62. package/lib/store/RelayStoreSubscriptions.js +7 -5
  63. package/lib/store/RelayStoreTypes.js +1 -1
  64. package/lib/store/RelayStoreUtils.js +6 -6
  65. package/lib/store/ResolverCache.js +7 -7
  66. package/lib/store/ResolverFragments.js +12 -8
  67. package/lib/store/StoreInspector.js +1 -1
  68. package/lib/store/TypeID.js +1 -1
  69. package/lib/store/ViewerPattern.js +1 -1
  70. package/lib/store/cloneRelayHandleSourceField.js +6 -5
  71. package/lib/store/cloneRelayScalarHandleSourceField.js +6 -5
  72. package/lib/store/createFragmentSpecResolver.js +1 -1
  73. package/lib/store/createRelayContext.js +4 -2
  74. package/lib/store/defaultGetDataID.js +1 -1
  75. package/lib/store/defaultRequiredFieldLogger.js +1 -1
  76. package/lib/store/hasOverlappingIDs.js +1 -1
  77. package/lib/store/isRelayModernEnvironment.js +1 -1
  78. package/lib/store/normalizeRelayPayload.js +1 -1
  79. package/lib/store/readInlineData.js +7 -3
  80. package/lib/subscription/requestSubscription.js +4 -6
  81. package/lib/util/JSResourceTypes.flow.js +1 -1
  82. package/lib/util/NormalizationNode.js +1 -1
  83. package/lib/util/ReaderNode.js +1 -1
  84. package/lib/util/RelayConcreteNode.js +2 -1
  85. package/lib/util/RelayDefaultHandleKey.js +1 -1
  86. package/lib/util/RelayError.js +1 -1
  87. package/lib/util/RelayFeatureFlags.js +8 -3
  88. package/lib/util/RelayProfiler.js +1 -1
  89. package/lib/util/RelayReplaySubject.js +1 -1
  90. package/lib/util/RelayRuntimeTypes.js +1 -7
  91. package/lib/util/StringInterner.js +71 -0
  92. package/lib/util/createPayloadFor3DField.js +1 -1
  93. package/lib/util/deepFreeze.js +1 -1
  94. package/lib/util/generateID.js +1 -1
  95. package/lib/util/getAllRootVariables.js +29 -0
  96. package/lib/util/getFragmentIdentifier.js +16 -8
  97. package/lib/util/getOperation.js +3 -2
  98. package/lib/util/getPaginationMetadata.js +1 -1
  99. package/lib/util/getPaginationVariables.js +3 -4
  100. package/lib/util/getPendingOperationsForFragment.js +1 -1
  101. package/lib/util/getRefetchMetadata.js +1 -1
  102. package/lib/util/getRelayHandleKey.js +3 -3
  103. package/lib/util/getRequestIdentifier.js +3 -3
  104. package/lib/util/getValueAtPath.js +1 -1
  105. package/lib/util/isEmptyObject.js +1 -1
  106. package/lib/util/isPromise.js +1 -1
  107. package/lib/util/isScalarAndEqual.js +1 -1
  108. package/lib/util/recycleNodesInto.js +1 -1
  109. package/lib/util/registerEnvironmentWithDevTools.js +1 -1
  110. package/lib/util/reportMissingRequiredFields.js +1 -1
  111. package/lib/util/resolveImmediate.js +1 -1
  112. package/lib/util/stableCopy.js +1 -1
  113. package/lib/util/withDuration.js +1 -1
  114. package/multi-actor-environment/ActorIdentifier.js.flow +1 -1
  115. package/multi-actor-environment/ActorSpecificEnvironment.js.flow +28 -20
  116. package/multi-actor-environment/ActorUtils.js.flow +3 -3
  117. package/multi-actor-environment/MultiActorEnvironment.js.flow +46 -25
  118. package/multi-actor-environment/MultiActorEnvironmentTypes.js.flow +28 -12
  119. package/multi-actor-environment/index.js.flow +2 -3
  120. package/mutations/RelayDeclarativeMutationConfig.js.flow +33 -28
  121. package/mutations/RelayRecordProxy.js.flow +5 -6
  122. package/mutations/RelayRecordSourceMutator.js.flow +5 -7
  123. package/mutations/RelayRecordSourceProxy.js.flow +20 -11
  124. package/mutations/RelayRecordSourceSelectorProxy.js.flow +16 -6
  125. package/mutations/applyOptimisticMutation.js.flow +14 -15
  126. package/mutations/commitLocalUpdate.js.flow +2 -2
  127. package/mutations/commitMutation.js.flow +36 -49
  128. package/mutations/readUpdatableQuery_EXPERIMENTAL.js.flow +318 -0
  129. package/mutations/validateMutation.js.flow +20 -18
  130. package/network/ConvertToExecuteFunction.js.flow +3 -3
  131. package/network/RelayNetwork.js.flow +5 -6
  132. package/network/RelayNetworkTypes.js.flow +1 -1
  133. package/network/RelayObservable.js.flow +2 -4
  134. package/network/RelayQueryResponseCache.js.flow +4 -4
  135. package/network/wrapNetworkWithLogObserver.js.flow +9 -9
  136. package/package.json +2 -2
  137. package/query/GraphQLTag.js.flow +11 -11
  138. package/query/PreloadableQueryRegistry.js.flow +5 -3
  139. package/query/fetchQuery.js.flow +19 -19
  140. package/query/fetchQueryInternal.js.flow +7 -10
  141. package/query/fetchQuery_DEPRECATED.js.flow +7 -7
  142. package/relay-runtime.js +2 -2
  143. package/relay-runtime.min.js +3 -3
  144. package/store/ClientID.js.flow +10 -3
  145. package/store/DataChecker.js.flow +21 -30
  146. package/store/OperationExecutor.js.flow +55 -63
  147. package/store/RelayConcreteVariables.js.flow +5 -11
  148. package/store/RelayExperimentalGraphResponseHandler.js.flow +121 -0
  149. package/store/RelayExperimentalGraphResponseTransform.js.flow +470 -0
  150. package/store/RelayModernEnvironment.js.flow +57 -28
  151. package/store/RelayModernFragmentSpecResolver.js.flow +18 -20
  152. package/store/RelayModernOperationDescriptor.js.flow +11 -12
  153. package/store/RelayModernRecord.js.flow +20 -13
  154. package/store/RelayModernSelector.js.flow +25 -15
  155. package/store/RelayModernStore.js.flow +22 -26
  156. package/store/RelayOperationTracker.js.flow +12 -18
  157. package/store/RelayOptimisticRecordSource.js.flow +3 -3
  158. package/store/RelayPublishQueue.js.flow +43 -24
  159. package/store/RelayReader.js.flow +181 -68
  160. package/store/RelayRecordSource.js.flow +3 -3
  161. package/store/RelayRecordState.js.flow +1 -1
  162. package/store/RelayReferenceMarker.js.flow +13 -16
  163. package/store/RelayResponseNormalizer.js.flow +44 -42
  164. package/store/RelayStoreReactFlightUtils.js.flow +4 -5
  165. package/store/RelayStoreSubscriptions.js.flow +10 -9
  166. package/store/RelayStoreTypes.js.flow +73 -30
  167. package/store/RelayStoreUtils.js.flow +9 -10
  168. package/store/ResolverCache.js.flow +17 -15
  169. package/store/ResolverFragments.js.flow +18 -25
  170. package/store/StoreInspector.js.flow +3 -3
  171. package/store/TypeID.js.flow +2 -2
  172. package/store/ViewerPattern.js.flow +3 -3
  173. package/store/cloneRelayHandleSourceField.js.flow +6 -7
  174. package/store/cloneRelayScalarHandleSourceField.js.flow +6 -7
  175. package/store/createFragmentSpecResolver.js.flow +4 -5
  176. package/store/createRelayContext.js.flow +3 -3
  177. package/store/defaultGetDataID.js.flow +1 -1
  178. package/store/defaultRequiredFieldLogger.js.flow +1 -1
  179. package/store/hasOverlappingIDs.js.flow +1 -1
  180. package/store/isRelayModernEnvironment.js.flow +1 -1
  181. package/store/normalizeRelayPayload.js.flow +7 -8
  182. package/store/readInlineData.js.flow +8 -9
  183. package/subscription/requestSubscription.js.flow +16 -25
  184. package/util/JSResourceTypes.flow.js.flow +1 -1
  185. package/util/NormalizationNode.js.flow +1 -1
  186. package/util/ReaderNode.js.flow +10 -1
  187. package/util/RelayConcreteNode.js.flow +4 -1
  188. package/util/RelayDefaultHandleKey.js.flow +1 -1
  189. package/util/RelayError.js.flow +1 -1
  190. package/util/RelayFeatureFlags.js.flow +15 -5
  191. package/util/RelayProfiler.js.flow +1 -1
  192. package/util/RelayReplaySubject.js.flow +3 -4
  193. package/util/RelayRuntimeTypes.js.flow +70 -3
  194. package/util/StringInterner.js.flow +69 -0
  195. package/util/createPayloadFor3DField.js.flow +4 -4
  196. package/util/deepFreeze.js.flow +1 -1
  197. package/util/generateID.js.flow +1 -1
  198. package/util/getAllRootVariables.js.flow +36 -0
  199. package/util/getFragmentIdentifier.js.flow +28 -16
  200. package/util/getOperation.js.flow +3 -3
  201. package/util/getPaginationMetadata.js.flow +6 -11
  202. package/util/getPaginationVariables.js.flow +6 -10
  203. package/util/getPendingOperationsForFragment.js.flow +3 -3
  204. package/util/getRefetchMetadata.js.flow +8 -12
  205. package/util/getRelayHandleKey.js.flow +2 -3
  206. package/util/getRequestIdentifier.js.flow +4 -4
  207. package/util/getValueAtPath.js.flow +1 -1
  208. package/util/isEmptyObject.js.flow +1 -1
  209. package/util/isPromise.js.flow +1 -1
  210. package/util/isScalarAndEqual.js.flow +1 -1
  211. package/util/recycleNodesInto.js.flow +1 -1
  212. package/util/registerEnvironmentWithDevTools.js.flow +1 -1
  213. package/util/reportMissingRequiredFields.js.flow +1 -1
  214. package/util/resolveImmediate.js.flow +2 -2
  215. package/util/stableCopy.js.flow +1 -1
  216. package/util/withDuration.js.flow +1 -1
@@ -0,0 +1,470 @@
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
+ * @emails oncall+relay
8
+ * @flow
9
+ * @format
10
+ */
11
+
12
+ import type {ActorIdentifier} from '../multi-actor-environment/ActorIdentifier';
13
+ import type {PayloadData} from '../network/RelayNetworkTypes';
14
+ import type {
15
+ NormalizationField,
16
+ NormalizationLinkedField,
17
+ NormalizationNode,
18
+ } from '../util/NormalizationNode';
19
+ import type {DataID, Variables} from '../util/RelayRuntimeTypes';
20
+ import type {NormalizationOptions} from './RelayResponseNormalizer';
21
+ import type {GetDataID} from './RelayResponseNormalizer';
22
+ import type {
23
+ IncrementalDataPlaceholder,
24
+ NormalizationSelector,
25
+ } from './RelayStoreTypes';
26
+
27
+ const {
28
+ CLIENT_EXTENSION,
29
+ CONDITION,
30
+ DEFER,
31
+ FRAGMENT_SPREAD,
32
+ INLINE_FRAGMENT,
33
+ LINKED_FIELD,
34
+ SCALAR_FIELD,
35
+ } = require('../util/RelayConcreteNode');
36
+ const {getLocalVariables} = require('./RelayConcreteVariables');
37
+ const {createNormalizationSelector} = require('./RelayModernSelector');
38
+ const {ROOT_TYPE, TYPENAME_KEY, getStorageKey} = require('./RelayStoreUtils');
39
+ const invariant = require('invariant');
40
+ const {generateClientID} = require('relay-runtime');
41
+
42
+ /**
43
+ * This module is an experiment to explore a proposal normalized response format for GraphQL.
44
+ * See the Quip document: Canonical Normalized Response Format (“GraphMode”) Proposal
45
+ */
46
+
47
+ /**
48
+ * # TODO
49
+ *
50
+ * - [ ] Compute storage keys using method outlined in the proposal
51
+ * - [ ] Plural fields
52
+ * - [ ] Write a utility to populate the store using a GraphMode response.
53
+ */
54
+
55
+ export type ScalarField = string | number | null;
56
+ export type LinkedField =
57
+ | {
58
+ __id: number,
59
+ }
60
+ | {
61
+ __ids: Array<number | null>,
62
+ };
63
+
64
+ export type ChunkField = ScalarField | Array<ScalarField> | LinkedField;
65
+
66
+ export type ChunkFields = {
67
+ [string]: ChunkField,
68
+ };
69
+
70
+ export type RecordChunk = {
71
+ $kind: 'Record',
72
+ $streamID: number,
73
+ __id: string,
74
+ __typename: string,
75
+ [string]: ChunkField,
76
+ };
77
+
78
+ export type ExtendChunk = {
79
+ $kind: 'Extend',
80
+ $streamID: number,
81
+ [string]: ChunkField,
82
+ };
83
+
84
+ export type CompleteChunk = {
85
+ $kind: 'Complete',
86
+ };
87
+
88
+ export type DataChunk = RecordChunk | ExtendChunk;
89
+
90
+ export type GraphModeChunk = DataChunk | CompleteChunk;
91
+
92
+ export type GraphModeResponse = Iterable<GraphModeChunk>;
93
+
94
+ /**
95
+ * Converts a JSON response (and Normalization AST) into a stream of GraphMode chunks
96
+ *
97
+ * The stream is modeled as a Generator in order to highlight the streaming
98
+ * nature of the response. Once a chunk is generated, it can be immediately flushed
99
+ * to the client.
100
+ *
101
+ * The response is traversed depth-first, meaning children are emitted before
102
+ * the parent. This allows parent objects to reference children using their
103
+ * `$streamID`.
104
+ *
105
+ * After each object is traversed, a chunk is emitted. The first time an object
106
+ * -- identified by its strong ID -- is encountered we emit a `Record`, and its
107
+ * `$streamID` is recorded. If that same object is encountered again later in
108
+ * the response, an `Extend` chunk is emitted, which includes any previously
109
+ * unsent fields. If no unsent fields are present in the second appearance of
110
+ * the new object, no chunk is emitted.
111
+ *
112
+ * ## State
113
+ *
114
+ * As we traverse we must maintain some state:
115
+ *
116
+ * - The next streamID
117
+ * - A mapping of cache keys to streamIDs
118
+ * - The set of fields which we've sent for each streamID. This allows us to
119
+ * avoid sending fields twice.
120
+ */
121
+ export function normalizeResponse(
122
+ response: PayloadData,
123
+ selector: NormalizationSelector,
124
+ options: NormalizationOptions,
125
+ ): GraphModeResponse {
126
+ const {node, variables, dataID} = selector;
127
+ const normalizer = new GraphModeNormalizer(variables, options);
128
+ return normalizer.normalizeResponse(node, dataID, response);
129
+ }
130
+
131
+ class GraphModeNormalizer {
132
+ _cacheKeyToStreamID: Map<string, number>;
133
+ _sentFields: Map<string, Set<string>>;
134
+ _getDataId: GetDataID;
135
+ _nextStreamID: number;
136
+ _getDataID: GetDataID;
137
+ _variables: Variables;
138
+ _path: Array<string>;
139
+ _incrementalPlaceholders: Array<IncrementalDataPlaceholder>;
140
+ _actorIdentifier: ?ActorIdentifier;
141
+ constructor(variables: Variables, options: NormalizationOptions) {
142
+ this._actorIdentifier = options.actorIdentifier;
143
+ this._path = options.path ? [...options.path] : [];
144
+ this._getDataID = options.getDataID;
145
+ this._cacheKeyToStreamID = new Map();
146
+ this._sentFields = new Map();
147
+ this._nextStreamID = 0;
148
+ this._variables = variables;
149
+ }
150
+
151
+ _getStreamID() {
152
+ return this._nextStreamID++;
153
+ }
154
+
155
+ _getSentFields(cacheKey: string): Set<string> {
156
+ const maybeSent = this._sentFields.get(cacheKey);
157
+ if (maybeSent != null) {
158
+ return maybeSent;
159
+ }
160
+ const sent = new Set();
161
+ this._sentFields.set(cacheKey, sent);
162
+ return sent;
163
+ }
164
+
165
+ _getObjectType(data: PayloadData): string {
166
+ const typeName = (data: any)[TYPENAME_KEY];
167
+ invariant(
168
+ typeName != null,
169
+ 'Expected a typename for record `%s`.',
170
+ JSON.stringify(data, null, 2),
171
+ );
172
+ return typeName;
173
+ }
174
+
175
+ // TODO: The GraphMode proposal outlines different approachs to derive keys. We
176
+ // can expriment with different approaches here.
177
+ _getStorageKey(selection: NormalizationField) {
178
+ return getStorageKey(selection, this._variables);
179
+ }
180
+
181
+ _getVariableValue(name: string): mixed {
182
+ invariant(
183
+ this._variables.hasOwnProperty(name),
184
+ 'Unexpected undefined variable `%s`.',
185
+ name,
186
+ );
187
+ return this._variables[name];
188
+ }
189
+
190
+ *normalizeResponse(
191
+ node: NormalizationNode,
192
+ dataID: DataID,
193
+ data: PayloadData,
194
+ ): Generator<GraphModeChunk, void, void> {
195
+ const rootFields: ChunkFields = {};
196
+ yield* this._traverseSelections(node, data, rootFields, dataID, new Set());
197
+
198
+ const $streamID = this._getStreamID();
199
+ yield {
200
+ ...rootFields,
201
+ $kind: 'Record',
202
+ $streamID,
203
+ __id: dataID,
204
+ __typename: ROOT_TYPE,
205
+ };
206
+ yield {
207
+ $kind: 'Complete',
208
+ };
209
+ }
210
+
211
+ *_flushFields(
212
+ cacheKey: string,
213
+ typename: string,
214
+ fields: ChunkFields,
215
+ ): Generator<GraphModeChunk, number, void> {
216
+ const maybeStreamID = this._cacheKeyToStreamID.get(cacheKey);
217
+ const $streamID = maybeStreamID ?? this._getStreamID();
218
+ if (maybeStreamID == null) {
219
+ this._cacheKeyToStreamID.set(cacheKey, $streamID);
220
+ // TODO: We could mutate `fields` rather than constructing a new
221
+ // chunk object, but it's hard to convince Flow that we've
222
+ // constructed a valid Chunk, and perf is not important for this
223
+ // experimental transform
224
+ yield {
225
+ ...fields,
226
+ $kind: 'Record',
227
+ __typename: typename,
228
+ __id: cacheKey,
229
+ $streamID,
230
+ };
231
+ } else if (Object.keys(fields).length > 0) {
232
+ yield {...fields, $kind: 'Extend', $streamID};
233
+ }
234
+ return $streamID;
235
+ }
236
+
237
+ *_traverseSelections(
238
+ node: NormalizationNode,
239
+ data: PayloadData,
240
+ parentFields: ChunkFields,
241
+ parentID: string,
242
+ sentFields: Set<string>,
243
+ ): Generator<GraphModeChunk, void, void> {
244
+ const selections = node.selections;
245
+
246
+ for (const selection of selections) {
247
+ switch (selection.kind) {
248
+ case LINKED_FIELD: {
249
+ const responseKey = selection.alias ?? selection.name;
250
+ const fieldData = ((data[responseKey]: any): PayloadData);
251
+
252
+ const storageKey = this._getStorageKey(selection);
253
+
254
+ this._path.push(responseKey);
255
+
256
+ const fieldValue = yield* this._traverseLinkedField(
257
+ selection.plural,
258
+ fieldData,
259
+ storageKey,
260
+ selection,
261
+ parentID,
262
+ );
263
+
264
+ this._path.pop();
265
+
266
+ // TODO: We could also opt to confirm that this matches the previously
267
+ // seen value.
268
+ if (sentFields.has(storageKey)) {
269
+ break;
270
+ }
271
+
272
+ parentFields[storageKey] = fieldValue;
273
+ sentFields.add(storageKey);
274
+ break;
275
+ }
276
+ case SCALAR_FIELD: {
277
+ const responseKey = selection.alias ?? selection.name;
278
+
279
+ const storageKey = this._getStorageKey(selection);
280
+
281
+ // TODO: We could also opt to confirm that this matches the previously
282
+ // seen value.
283
+ if (sentFields.has(storageKey)) {
284
+ break;
285
+ }
286
+ const fieldData = ((data[responseKey]: any): ChunkField);
287
+
288
+ parentFields[storageKey] = fieldData;
289
+ sentFields.add(storageKey);
290
+ break;
291
+ }
292
+ case INLINE_FRAGMENT: {
293
+ const objType = this._getObjectType(data);
294
+ const {abstractKey} = selection;
295
+ if (abstractKey == null) {
296
+ if (objType !== selection.type) {
297
+ break;
298
+ }
299
+ } else if (!data.hasOwnProperty(abstractKey)) {
300
+ break;
301
+ }
302
+ yield* this._traverseSelections(
303
+ selection,
304
+ data,
305
+ parentFields,
306
+ parentID,
307
+ sentFields,
308
+ );
309
+ break;
310
+ }
311
+ case FRAGMENT_SPREAD: {
312
+ const prevVariables = this._variables;
313
+ this._variables = getLocalVariables(
314
+ this._variables,
315
+ selection.fragment.argumentDefinitions,
316
+ selection.args,
317
+ );
318
+ yield* this._traverseSelections(
319
+ selection.fragment,
320
+ data,
321
+ parentFields,
322
+ parentID,
323
+ sentFields,
324
+ );
325
+ this._variables = prevVariables;
326
+ break;
327
+ }
328
+ case CONDITION:
329
+ const conditionValue = Boolean(
330
+ this._getVariableValue(selection.condition),
331
+ );
332
+ if (conditionValue === selection.passingValue) {
333
+ yield* this._traverseSelections(
334
+ selection,
335
+ data,
336
+ parentFields,
337
+ parentID,
338
+ sentFields,
339
+ );
340
+ }
341
+ break;
342
+ case DEFER:
343
+ const isDeferred =
344
+ selection.if === null || this._getVariableValue(selection.if);
345
+ if (isDeferred === false) {
346
+ // If defer is disabled there will be no additional response chunk:
347
+ // normalize the data already present.
348
+ yield* this._traverseSelections(
349
+ selection,
350
+ data,
351
+ parentFields,
352
+ parentID,
353
+ sentFields,
354
+ );
355
+ } else {
356
+ // Otherwise data *for this selection* should not be present: enqueue
357
+ // metadata to process the subsequent response chunk.
358
+ this._incrementalPlaceholders.push({
359
+ kind: 'defer',
360
+ data,
361
+ label: selection.label,
362
+ path: [...this._path],
363
+ selector: createNormalizationSelector(
364
+ selection,
365
+ parentID,
366
+ this._variables,
367
+ ),
368
+ typeName: this._getObjectType(data),
369
+ actorIdentifier: this._actorIdentifier,
370
+ });
371
+ }
372
+ break;
373
+ case CLIENT_EXTENSION:
374
+ // Since we are only expecting to handle server responses, we can skip
375
+ // over client extensions.
376
+ break;
377
+ default:
378
+ throw new Error(`Unexpected selection type: ${selection.kind}`);
379
+ }
380
+ }
381
+ }
382
+
383
+ *_traverseLinkedField(
384
+ plural: boolean,
385
+ fieldData: PayloadData,
386
+ storageKey: string,
387
+ selection: NormalizationLinkedField,
388
+ parentID: string,
389
+ index?: number,
390
+ ): Generator<GraphModeChunk, ChunkField, void> {
391
+ if (fieldData == null) {
392
+ return null;
393
+ }
394
+
395
+ if (plural) {
396
+ invariant(
397
+ Array.isArray(fieldData),
398
+ `Expected fieldData to be an array. Got ${JSON.stringify(fieldData)}`,
399
+ );
400
+
401
+ const fieldValue = [];
402
+ for (const [i, itemData] of fieldData.entries()) {
403
+ this._path.push(String(i));
404
+ const itemValue = yield* this._traverseLinkedField(
405
+ false,
406
+ itemData,
407
+ storageKey,
408
+ selection,
409
+ parentID,
410
+ i,
411
+ );
412
+ this._path.pop();
413
+ fieldValue.push(itemValue);
414
+ }
415
+
416
+ const ids = fieldValue.map(value => {
417
+ if (value == null) {
418
+ return null;
419
+ }
420
+ invariant(
421
+ typeof value.__id === 'number',
422
+ 'Expected objects in a plural linked field to have an __id.',
423
+ );
424
+ return value.__id;
425
+ });
426
+
427
+ return {__ids: ids};
428
+ }
429
+
430
+ invariant(
431
+ typeof fieldData === 'object',
432
+ 'Expected data for field `%s` to be an object.',
433
+ storageKey,
434
+ );
435
+
436
+ const objType = selection.concreteType ?? this._getObjectType(fieldData);
437
+
438
+ const nextID =
439
+ this._getDataID(fieldData, objType) ||
440
+ // Note: In RelayResponseNormalizer we try to access a cached
441
+ // version of the key before generating a new one. I'm not clear if
442
+ // that's a performance optimization (which would not be important
443
+ // here) or important for stable ids.
444
+
445
+ // TODO: The proposal does not yet specify how we handle objects
446
+ // without strong ids.
447
+ generateClientID(parentID, storageKey, index);
448
+
449
+ invariant(
450
+ typeof nextID === 'string',
451
+ 'Expected id on field `%s` to be a string.',
452
+ storageKey,
453
+ );
454
+
455
+ const fields: ChunkFields = {};
456
+
457
+ // Yield any decendent record chunks, and mutatively populate direct fields.
458
+ yield* this._traverseSelections(
459
+ selection,
460
+ fieldData,
461
+ fields,
462
+ nextID,
463
+ this._getSentFields(nextID),
464
+ );
465
+
466
+ const $streamID = yield* this._flushFields(nextID, objType, fields);
467
+
468
+ return {__id: $streamID};
469
+ }
470
+ }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Copyright (c) Facebook, Inc. and its affiliates.
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
3
  *
4
4
  * This source code is licensed under the MIT license found in the
5
5
  * LICENSE file in the root directory of this source tree.
@@ -13,25 +13,6 @@
13
13
 
14
14
  'use strict';
15
15
 
16
- const OperationExecutor = require('./OperationExecutor');
17
- const RelayDefaultHandlerProvider = require('../handlers/RelayDefaultHandlerProvider');
18
- const RelayFeatureFlags = require('../util/RelayFeatureFlags');
19
- const RelayObservable = require('../network/RelayObservable');
20
- const RelayOperationTracker = require('../store/RelayOperationTracker');
21
- const RelayPublishQueue = require('./RelayPublishQueue');
22
- const RelayRecordSource = require('./RelayRecordSource');
23
-
24
- const defaultGetDataID = require('./defaultGetDataID');
25
- const defaultRequiredFieldLogger = require('./defaultRequiredFieldLogger');
26
- const invariant = require('invariant');
27
- const registerEnvironmentWithDevTools = require('../util/registerEnvironmentWithDevTools');
28
- const wrapNetworkWithLogObserver = require('../network/wrapNetworkWithLogObserver');
29
-
30
- const {
31
- INTERNAL_ACTOR_IDENTIFIER_DO_NOT_USE,
32
- assertInternalActorIndentifier,
33
- } = require('../multi-actor-environment/ActorIdentifier');
34
-
35
16
  import type {HandlerProvider} from '../handlers/RelayDefaultHandlerProvider';
36
17
  import type {ActorIdentifier} from '../multi-actor-environment/ActorIdentifier';
37
18
  import type {
@@ -47,6 +28,7 @@ import type {
47
28
  IEnvironment,
48
29
  LogFunction,
49
30
  MissingFieldHandler,
31
+ MutationParameters,
50
32
  OperationAvailability,
51
33
  OperationDescriptor,
52
34
  OperationLoader,
@@ -64,6 +46,23 @@ import type {
64
46
  StoreUpdater,
65
47
  } from './RelayStoreTypes';
66
48
 
49
+ const RelayDefaultHandlerProvider = require('../handlers/RelayDefaultHandlerProvider');
50
+ const {
51
+ INTERNAL_ACTOR_IDENTIFIER_DO_NOT_USE,
52
+ assertInternalActorIndentifier,
53
+ } = require('../multi-actor-environment/ActorIdentifier');
54
+ const RelayObservable = require('../network/RelayObservable');
55
+ const wrapNetworkWithLogObserver = require('../network/wrapNetworkWithLogObserver');
56
+ const RelayOperationTracker = require('../store/RelayOperationTracker');
57
+ const registerEnvironmentWithDevTools = require('../util/registerEnvironmentWithDevTools');
58
+ const RelayFeatureFlags = require('../util/RelayFeatureFlags');
59
+ const defaultGetDataID = require('./defaultGetDataID');
60
+ const defaultRequiredFieldLogger = require('./defaultRequiredFieldLogger');
61
+ const OperationExecutor = require('./OperationExecutor');
62
+ const RelayPublishQueue = require('./RelayPublishQueue');
63
+ const RelayRecordSource = require('./RelayRecordSource');
64
+ const invariant = require('invariant');
65
+
67
66
  export type EnvironmentConfig = {|
68
67
  +configName?: string,
69
68
  +handlerProvider?: ?HandlerProvider,
@@ -228,7 +227,9 @@ class RelayModernEnvironment implements IEnvironment {
228
227
  });
229
228
  }
230
229
 
231
- applyMutation(optimisticConfig: OptimisticResponseConfig): Disposable {
230
+ applyMutation<TMutation: MutationParameters>(
231
+ optimisticConfig: OptimisticResponseConfig<TMutation>,
232
+ ): Disposable {
232
233
  const subscription = this._execute({
233
234
  createSource: () => RelayObservable.create(_sink => {}),
234
235
  isClientPayload: false,
@@ -328,7 +329,7 @@ class RelayModernEnvironment implements IEnvironment {
328
329
 
329
330
  /**
330
331
  * Returns an Observable of GraphQLResponse resulting from executing the
331
- * provided Query or Subscription operation, each result of which is then
332
+ * provided Query operation, each result of which is then
332
333
  * normalized and committed to the publish queue.
333
334
  *
334
335
  * Note: Observables are lazy, so calling this method will do nothing until
@@ -336,10 +337,38 @@ class RelayModernEnvironment implements IEnvironment {
336
337
  */
337
338
  execute({
338
339
  operation,
340
+ }: {|
341
+ operation: OperationDescriptor,
342
+ |}): RelayObservable<GraphQLResponse> {
343
+ return this._execute({
344
+ createSource: () =>
345
+ this._network.execute(
346
+ operation.request.node.params,
347
+ operation.request.variables,
348
+ operation.request.cacheConfig || {},
349
+ null,
350
+ ),
351
+ isClientPayload: false,
352
+ operation,
353
+ optimisticConfig: null,
354
+ updater: null,
355
+ });
356
+ }
357
+
358
+ /**
359
+ * Returns an Observable of GraphQLResponse resulting from executing the
360
+ * provided Subscription operation, each result of which is then
361
+ * normalized and committed to the publish queue.
362
+ *
363
+ * Note: Observables are lazy, so calling this method will do nothing until
364
+ * the result is subscribed to: environment.execute({...}).subscribe({...}).
365
+ */
366
+ executeSubscription<TMutation: MutationParameters>({
367
+ operation,
339
368
  updater,
340
369
  }: {|
341
370
  operation: OperationDescriptor,
342
- updater?: ?SelectorStoreUpdater,
371
+ updater?: ?SelectorStoreUpdater<TMutation['response']>,
343
372
  |}): RelayObservable<GraphQLResponse> {
344
373
  return this._execute({
345
374
  createSource: () =>
@@ -366,13 +395,13 @@ class RelayModernEnvironment implements IEnvironment {
366
395
  * the result is subscribed to:
367
396
  * environment.executeMutation({...}).subscribe({...}).
368
397
  */
369
- executeMutation({
398
+ executeMutation<TMutation: MutationParameters>({
370
399
  operation,
371
400
  optimisticResponse,
372
401
  optimisticUpdater,
373
402
  updater,
374
403
  uploadables,
375
- }: ExecuteMutationConfig): RelayObservable<GraphQLResponse> {
404
+ }: ExecuteMutationConfig<TMutation>): RelayObservable<GraphQLResponse> {
376
405
  let optimisticConfig;
377
406
  if (optimisticResponse || optimisticUpdater) {
378
407
  optimisticConfig = {
@@ -428,7 +457,7 @@ class RelayModernEnvironment implements IEnvironment {
428
457
  return `RelayModernEnvironment(${this.configName ?? ''})`;
429
458
  }
430
459
 
431
- _execute({
460
+ _execute<TMutation: MutationParameters>({
432
461
  createSource,
433
462
  isClientPayload,
434
463
  operation,
@@ -438,8 +467,8 @@ class RelayModernEnvironment implements IEnvironment {
438
467
  createSource: () => RelayObservable<GraphQLResponse>,
439
468
  isClientPayload: boolean,
440
469
  operation: OperationDescriptor,
441
- optimisticConfig: ?OptimisticResponseConfig,
442
- updater: ?SelectorStoreUpdater,
470
+ optimisticConfig: ?OptimisticResponseConfig<TMutation>,
471
+ updater: ?SelectorStoreUpdater<TMutation['response']>,
443
472
  |}): RelayObservable<GraphQLResponse> {
444
473
  const publishQueue = this._publishQueue;
445
474
  const store = this._store;
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Copyright (c) Facebook, Inc. and its affiliates.
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
3
  *
4
4
  * This source code is licensed under the MIT license found in the
5
5
  * LICENSE file in the root directory of this source tree.
@@ -12,30 +12,13 @@
12
12
 
13
13
  'use strict';
14
14
 
15
- const RelayFeatureFlags = require('../util/RelayFeatureFlags');
16
-
17
- const areEqual = require('areEqual');
18
- const getPendingOperationsForFragment = require('../util/getPendingOperationsForFragment');
19
- const invariant = require('invariant');
20
- const isScalarAndEqual = require('../util/isScalarAndEqual');
21
- const recycleNodesInto = require('../util/recycleNodesInto');
22
- const reportMissingRequiredFields = require('../util/reportMissingRequiredFields');
23
- const warning = require('warning');
24
-
25
- const {createRequestDescriptor} = require('./RelayModernOperationDescriptor');
26
- const {
27
- areEqualSelectors,
28
- createReaderSelector,
29
- getSelectorsFromObject,
30
- } = require('./RelayModernSelector');
31
-
32
15
  import type {ConcreteRequest} from '../util/RelayConcreteNode';
33
16
  import type {Disposable, Variables} from '../util/RelayRuntimeTypes';
34
17
  import type {
35
- IEnvironment,
36
18
  FragmentMap,
37
19
  FragmentSpecResolver,
38
20
  FragmentSpecResults,
21
+ IEnvironment,
39
22
  MissingRequiredFields,
40
23
  PluralReaderSelector,
41
24
  RelayContext,
@@ -44,10 +27,25 @@ import type {
44
27
  Snapshot,
45
28
  } from './RelayStoreTypes';
46
29
 
30
+ const getPendingOperationsForFragment = require('../util/getPendingOperationsForFragment');
31
+ const isScalarAndEqual = require('../util/isScalarAndEqual');
32
+ const recycleNodesInto = require('../util/recycleNodesInto');
33
+ const RelayFeatureFlags = require('../util/RelayFeatureFlags');
34
+ const reportMissingRequiredFields = require('../util/reportMissingRequiredFields');
35
+ const {createRequestDescriptor} = require('./RelayModernOperationDescriptor');
36
+ const {
37
+ areEqualSelectors,
38
+ createReaderSelector,
39
+ getSelectorsFromObject,
40
+ } = require('./RelayModernSelector');
41
+ const areEqual = require('areEqual');
42
+ const invariant = require('invariant');
43
+ const warning = require('warning');
44
+
47
45
  type Props = {[key: string]: mixed, ...};
48
46
  type Resolvers = {
49
47
  [key: string]: ?(SelectorListResolver | SelectorResolver),
50
- ...,
48
+ ...
51
49
  };
52
50
 
53
51
  /**