relay-runtime 9.0.0 → 10.1.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 (142) hide show
  1. package/handlers/RelayDefaultHandlerProvider.js.flow +47 -0
  2. package/handlers/connection/ConnectionHandler.js.flow +549 -0
  3. package/handlers/connection/ConnectionInterface.js.flow +92 -0
  4. package/handlers/connection/MutationHandlers.js.flow +199 -0
  5. package/index.js +1 -1
  6. package/index.js.flow +335 -0
  7. package/lib/handlers/RelayDefaultHandlerProvider.js +20 -0
  8. package/lib/handlers/connection/ConnectionHandler.js +1 -3
  9. package/lib/handlers/connection/MutationHandlers.js +212 -0
  10. package/lib/index.js +14 -2
  11. package/lib/mutations/RelayDeclarativeMutationConfig.js +22 -45
  12. package/lib/mutations/RelayRecordProxy.js +1 -3
  13. package/lib/mutations/RelayRecordSourceMutator.js +1 -3
  14. package/lib/mutations/RelayRecordSourceProxy.js +1 -3
  15. package/lib/mutations/RelayRecordSourceSelectorProxy.js +1 -3
  16. package/lib/mutations/commitMutation.js +2 -3
  17. package/lib/mutations/validateMutation.js +40 -9
  18. package/lib/network/RelayObservable.js +9 -9
  19. package/lib/network/RelayQueryResponseCache.js +8 -6
  20. package/lib/query/GraphQLTag.js +2 -1
  21. package/lib/query/PreloadableQueryRegistry.js +70 -0
  22. package/lib/query/fetchQuery.js +2 -3
  23. package/lib/query/fetchQueryInternal.js +5 -14
  24. package/lib/store/DataChecker.js +200 -71
  25. package/lib/store/RelayConcreteVariables.js +6 -2
  26. package/lib/store/RelayModernEnvironment.js +124 -65
  27. package/lib/store/RelayModernFragmentSpecResolver.js +19 -14
  28. package/lib/store/RelayModernOperationDescriptor.js +6 -5
  29. package/lib/store/RelayModernQueryExecutor.js +122 -73
  30. package/lib/store/RelayModernRecord.js +14 -9
  31. package/lib/store/RelayModernSelector.js +6 -2
  32. package/lib/store/RelayModernStore.js +281 -131
  33. package/lib/store/RelayOperationTracker.js +35 -78
  34. package/lib/store/RelayOptimisticRecordSource.js +7 -5
  35. package/lib/store/RelayPublishQueue.js +2 -4
  36. package/lib/store/RelayReader.js +304 -52
  37. package/lib/store/RelayRecordSource.js +1 -3
  38. package/lib/store/RelayRecordSourceMapImpl.js +13 -18
  39. package/lib/store/RelayReferenceMarker.js +125 -14
  40. package/lib/store/RelayResponseNormalizer.js +261 -66
  41. package/lib/store/RelayStoreReactFlightUtils.js +47 -0
  42. package/lib/store/RelayStoreUtils.js +1 -0
  43. package/lib/store/StoreInspector.js +8 -8
  44. package/lib/store/TypeID.js +28 -0
  45. package/lib/store/cloneRelayScalarHandleSourceField.js +44 -0
  46. package/lib/store/defaultRequiredFieldLogger.js +18 -0
  47. package/lib/store/normalizeRelayPayload.js +6 -2
  48. package/lib/store/readInlineData.js +1 -1
  49. package/lib/subscription/requestSubscription.js +4 -3
  50. package/lib/util/NormalizationNode.js +1 -5
  51. package/lib/util/RelayConcreteNode.js +11 -6
  52. package/lib/util/RelayError.js +39 -9
  53. package/lib/util/RelayFeatureFlags.js +6 -3
  54. package/lib/util/RelayReplaySubject.js +3 -3
  55. package/lib/util/createPayloadFor3DField.js +7 -2
  56. package/lib/util/getFragmentIdentifier.js +12 -3
  57. package/lib/util/getOperation.js +33 -0
  58. package/lib/util/getRequestIdentifier.js +2 -2
  59. package/lib/util/isEmptyObject.js +25 -0
  60. package/lib/util/recycleNodesInto.js +6 -7
  61. package/lib/util/reportMissingRequiredFields.js +48 -0
  62. package/mutations/RelayDeclarativeMutationConfig.js.flow +380 -0
  63. package/mutations/RelayRecordProxy.js.flow +165 -0
  64. package/mutations/RelayRecordSourceMutator.js.flow +238 -0
  65. package/mutations/RelayRecordSourceProxy.js.flow +164 -0
  66. package/mutations/RelayRecordSourceSelectorProxy.js.flow +119 -0
  67. package/mutations/applyOptimisticMutation.js.flow +76 -0
  68. package/mutations/commitLocalUpdate.js.flow +24 -0
  69. package/mutations/commitMutation.js.flow +181 -0
  70. package/mutations/validateMutation.js.flow +242 -0
  71. package/network/ConvertToExecuteFunction.js.flow +49 -0
  72. package/network/RelayNetwork.js.flow +84 -0
  73. package/network/RelayNetworkTypes.js.flow +145 -0
  74. package/network/RelayObservable.js.flow +634 -0
  75. package/network/RelayQueryResponseCache.js.flow +111 -0
  76. package/package.json +2 -2
  77. package/query/GraphQLTag.js.flow +168 -0
  78. package/query/PreloadableQueryRegistry.js.flow +65 -0
  79. package/query/fetchQuery.js.flow +47 -0
  80. package/query/fetchQueryInternal.js.flow +343 -0
  81. package/relay-runtime.js +2 -2
  82. package/relay-runtime.min.js +2 -2
  83. package/store/ClientID.js.flow +43 -0
  84. package/store/DataChecker.js.flow +568 -0
  85. package/store/RelayConcreteVariables.js.flow +96 -0
  86. package/store/RelayModernEnvironment.js.flow +571 -0
  87. package/store/RelayModernFragmentSpecResolver.js.flow +438 -0
  88. package/store/RelayModernOperationDescriptor.js.flow +92 -0
  89. package/store/RelayModernQueryExecutor.js.flow +1345 -0
  90. package/store/RelayModernRecord.js.flow +403 -0
  91. package/store/RelayModernSelector.js.flow +455 -0
  92. package/store/RelayModernStore.js.flow +858 -0
  93. package/store/RelayOperationTracker.js.flow +164 -0
  94. package/store/RelayOptimisticRecordSource.js.flow +119 -0
  95. package/store/RelayPublishQueue.js.flow +401 -0
  96. package/store/RelayReader.js.flow +638 -0
  97. package/store/RelayRecordSource.js.flow +29 -0
  98. package/store/RelayRecordSourceMapImpl.js.flow +87 -0
  99. package/store/RelayRecordState.js.flow +37 -0
  100. package/store/RelayReferenceMarker.js.flow +324 -0
  101. package/store/RelayResponseNormalizer.js.flow +791 -0
  102. package/store/RelayStoreReactFlightUtils.js.flow +64 -0
  103. package/store/RelayStoreTypes.js.flow +958 -0
  104. package/store/RelayStoreUtils.js.flow +219 -0
  105. package/store/StoreInspector.js.flow +171 -0
  106. package/store/TypeID.js.flow +28 -0
  107. package/store/ViewerPattern.js.flow +26 -0
  108. package/store/cloneRelayHandleSourceField.js.flow +66 -0
  109. package/store/cloneRelayScalarHandleSourceField.js.flow +62 -0
  110. package/store/createFragmentSpecResolver.js.flow +55 -0
  111. package/store/createRelayContext.js.flow +44 -0
  112. package/store/defaultGetDataID.js.flow +27 -0
  113. package/store/defaultRequiredFieldLogger.js.flow +23 -0
  114. package/store/hasOverlappingIDs.js.flow +34 -0
  115. package/store/isRelayModernEnvironment.js.flow +27 -0
  116. package/store/normalizeRelayPayload.js.flow +51 -0
  117. package/store/readInlineData.js.flow +75 -0
  118. package/subscription/requestSubscription.js.flow +103 -0
  119. package/util/JSResourceTypes.flow.js.flow +20 -0
  120. package/util/NormalizationNode.js.flow +213 -0
  121. package/util/ReaderNode.js.flow +227 -0
  122. package/util/RelayConcreteNode.js.flow +99 -0
  123. package/util/RelayDefaultHandleKey.js.flow +17 -0
  124. package/util/RelayError.js.flow +62 -0
  125. package/util/RelayFeatureFlags.js.flow +37 -0
  126. package/util/RelayProfiler.js.flow +284 -0
  127. package/util/RelayReplaySubject.js.flow +135 -0
  128. package/util/RelayRuntimeTypes.js.flow +72 -0
  129. package/util/createPayloadFor3DField.js.flow +43 -0
  130. package/util/deepFreeze.js.flow +36 -0
  131. package/util/generateID.js.flow +21 -0
  132. package/util/getFragmentIdentifier.js.flow +76 -0
  133. package/util/getOperation.js.flow +40 -0
  134. package/util/getRelayHandleKey.js.flow +41 -0
  135. package/util/getRequestIdentifier.js.flow +42 -0
  136. package/util/isEmptyObject.js.flow +25 -0
  137. package/util/isPromise.js.flow +21 -0
  138. package/util/isScalarAndEqual.js.flow +26 -0
  139. package/util/recycleNodesInto.js.flow +87 -0
  140. package/util/reportMissingRequiredFields.js.flow +51 -0
  141. package/util/resolveImmediate.js.flow +30 -0
  142. package/util/stableCopy.js.flow +35 -0
@@ -0,0 +1,568 @@
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
+ * @emails oncall+relay
10
+ */
11
+
12
+ // flowlint ambiguous-object-type:error
13
+
14
+ 'use strict';
15
+
16
+ const RelayConcreteNode = require('../util/RelayConcreteNode');
17
+ const RelayFeatureFlags = require('../util/RelayFeatureFlags');
18
+ const RelayModernRecord = require('./RelayModernRecord');
19
+ const RelayRecordSourceMutator = require('../mutations/RelayRecordSourceMutator');
20
+ const RelayRecordSourceProxy = require('../mutations/RelayRecordSourceProxy');
21
+ const RelayStoreReactFlightUtils = require('./RelayStoreReactFlightUtils');
22
+ const RelayStoreUtils = require('./RelayStoreUtils');
23
+
24
+ const cloneRelayHandleSourceField = require('./cloneRelayHandleSourceField');
25
+ const cloneRelayScalarHandleSourceField = require('./cloneRelayScalarHandleSourceField');
26
+ const getOperation = require('../util/getOperation');
27
+ const invariant = require('invariant');
28
+
29
+ const {isClientID} = require('./ClientID');
30
+ const {EXISTENT, UNKNOWN} = require('./RelayRecordState');
31
+ const {generateTypeID} = require('./TypeID');
32
+
33
+ import type {
34
+ NormalizationField,
35
+ NormalizationFlightField,
36
+ NormalizationLinkedField,
37
+ NormalizationModuleImport,
38
+ NormalizationNode,
39
+ NormalizationScalarField,
40
+ NormalizationSelection,
41
+ } from '../util/NormalizationNode';
42
+ import type {DataID, Variables} from '../util/RelayRuntimeTypes';
43
+ import type {GetDataID} from './RelayResponseNormalizer';
44
+ import type {
45
+ MissingFieldHandler,
46
+ MutableRecordSource,
47
+ NormalizationSelector,
48
+ OperationLoader,
49
+ ReactFlightReachableQuery,
50
+ Record,
51
+ RecordSource,
52
+ } from './RelayStoreTypes';
53
+
54
+ export type Availability = {|
55
+ +status: 'available' | 'missing',
56
+ +mostRecentlyInvalidatedAt: ?number,
57
+ |};
58
+
59
+ const {
60
+ CONDITION,
61
+ CLIENT_EXTENSION,
62
+ DEFER,
63
+ FLIGHT_FIELD,
64
+ FRAGMENT_SPREAD,
65
+ INLINE_FRAGMENT,
66
+ LINKED_FIELD,
67
+ LINKED_HANDLE,
68
+ MODULE_IMPORT,
69
+ SCALAR_FIELD,
70
+ SCALAR_HANDLE,
71
+ STREAM,
72
+ TYPE_DISCRIMINATOR,
73
+ } = RelayConcreteNode;
74
+ const {
75
+ ROOT_ID,
76
+ getModuleOperationKey,
77
+ getStorageKey,
78
+ getArgumentValues,
79
+ } = RelayStoreUtils;
80
+
81
+ /**
82
+ * Synchronously check whether the records required to fulfill the given
83
+ * `selector` are present in `source`.
84
+ *
85
+ * If a field is missing, it uses the provided handlers to attempt to substitute
86
+ * data. The `target` will store all records that are modified because of a
87
+ * successful substitution.
88
+ *
89
+ * If all records are present, returns `true`, otherwise `false`.
90
+ */
91
+ function check(
92
+ source: RecordSource,
93
+ target: MutableRecordSource,
94
+ selector: NormalizationSelector,
95
+ handlers: $ReadOnlyArray<MissingFieldHandler>,
96
+ operationLoader: ?OperationLoader,
97
+ getDataID: GetDataID,
98
+ ): Availability {
99
+ const {dataID, node, variables} = selector;
100
+ const checker = new DataChecker(
101
+ source,
102
+ target,
103
+ variables,
104
+ handlers,
105
+ operationLoader,
106
+ getDataID,
107
+ );
108
+ return checker.check(node, dataID);
109
+ }
110
+
111
+ /**
112
+ * @private
113
+ */
114
+ class DataChecker {
115
+ _handlers: $ReadOnlyArray<MissingFieldHandler>;
116
+ _mostRecentlyInvalidatedAt: number | null;
117
+ _mutator: RelayRecordSourceMutator;
118
+ _operationLoader: OperationLoader | null;
119
+ _operationLastWrittenAt: ?number;
120
+ _recordSourceProxy: RelayRecordSourceProxy;
121
+ _recordWasMissing: boolean;
122
+ _source: RecordSource;
123
+ _variables: Variables;
124
+
125
+ constructor(
126
+ source: RecordSource,
127
+ target: MutableRecordSource,
128
+ variables: Variables,
129
+ handlers: $ReadOnlyArray<MissingFieldHandler>,
130
+ operationLoader: ?OperationLoader,
131
+ getDataID: GetDataID,
132
+ ) {
133
+ const mutator = new RelayRecordSourceMutator(source, target);
134
+ this._mostRecentlyInvalidatedAt = null;
135
+ this._handlers = handlers;
136
+ this._mutator = mutator;
137
+ this._operationLoader = operationLoader ?? null;
138
+ this._recordSourceProxy = new RelayRecordSourceProxy(mutator, getDataID);
139
+ this._recordWasMissing = false;
140
+ this._source = source;
141
+ this._variables = variables;
142
+ }
143
+
144
+ check(node: NormalizationNode, dataID: DataID): Availability {
145
+ this._traverse(node, dataID);
146
+
147
+ return this._recordWasMissing === true
148
+ ? {
149
+ status: 'missing',
150
+ mostRecentlyInvalidatedAt: this._mostRecentlyInvalidatedAt,
151
+ }
152
+ : {
153
+ status: 'available',
154
+ mostRecentlyInvalidatedAt: this._mostRecentlyInvalidatedAt,
155
+ };
156
+ }
157
+
158
+ _getVariableValue(name: string): mixed {
159
+ invariant(
160
+ this._variables.hasOwnProperty(name),
161
+ 'RelayAsyncLoader(): Undefined variable `%s`.',
162
+ name,
163
+ );
164
+ return this._variables[name];
165
+ }
166
+
167
+ _handleMissing(): void {
168
+ this._recordWasMissing = true;
169
+ }
170
+
171
+ _getDataForHandlers(
172
+ field: NormalizationField,
173
+ dataID: DataID,
174
+ ): {
175
+ args: Variables,
176
+ record: ?Record,
177
+ ...
178
+ } {
179
+ return {
180
+ args: field.args ? getArgumentValues(field.args, this._variables) : {},
181
+ // Getting a snapshot of the record state is potentially expensive since
182
+ // we will need to merge the sink and source records. Since we do not create
183
+ // any new records in this process, it is probably reasonable to provide
184
+ // handlers with a copy of the source record.
185
+ // The only thing that the provided record will not contain is fields
186
+ // added by previous handlers.
187
+ record: this._source.get(dataID),
188
+ };
189
+ }
190
+
191
+ _handleMissingScalarField(
192
+ field: NormalizationScalarField,
193
+ dataID: DataID,
194
+ ): mixed {
195
+ if (field.name === 'id' && field.alias == null && isClientID(dataID)) {
196
+ return undefined;
197
+ }
198
+ const {args, record} = this._getDataForHandlers(field, dataID);
199
+ for (const handler of this._handlers) {
200
+ if (handler.kind === 'scalar') {
201
+ const newValue = handler.handle(
202
+ field,
203
+ record,
204
+ args,
205
+ this._recordSourceProxy,
206
+ );
207
+ if (newValue !== undefined) {
208
+ return newValue;
209
+ }
210
+ }
211
+ }
212
+ this._handleMissing();
213
+ }
214
+
215
+ _handleMissingLinkField(
216
+ field: NormalizationLinkedField,
217
+ dataID: DataID,
218
+ ): ?DataID {
219
+ const {args, record} = this._getDataForHandlers(field, dataID);
220
+ for (const handler of this._handlers) {
221
+ if (handler.kind === 'linked') {
222
+ const newValue = handler.handle(
223
+ field,
224
+ record,
225
+ args,
226
+ this._recordSourceProxy,
227
+ );
228
+ if (
229
+ newValue !== undefined &&
230
+ (newValue === null || this._mutator.getStatus(newValue) === EXISTENT)
231
+ ) {
232
+ return newValue;
233
+ }
234
+ }
235
+ }
236
+ this._handleMissing();
237
+ }
238
+
239
+ _handleMissingPluralLinkField(
240
+ field: NormalizationLinkedField,
241
+ dataID: DataID,
242
+ ): ?Array<?DataID> {
243
+ const {args, record} = this._getDataForHandlers(field, dataID);
244
+ for (const handler of this._handlers) {
245
+ if (handler.kind === 'pluralLinked') {
246
+ const newValue = handler.handle(
247
+ field,
248
+ record,
249
+ args,
250
+ this._recordSourceProxy,
251
+ );
252
+ if (newValue != null) {
253
+ const allItemsKnown = newValue.every(
254
+ linkedID =>
255
+ linkedID != null &&
256
+ this._mutator.getStatus(linkedID) === EXISTENT,
257
+ );
258
+ if (allItemsKnown) {
259
+ return newValue;
260
+ }
261
+ } else if (newValue === null) {
262
+ return null;
263
+ }
264
+ }
265
+ }
266
+ this._handleMissing();
267
+ }
268
+
269
+ _traverse(node: NormalizationNode, dataID: DataID): void {
270
+ const status = this._mutator.getStatus(dataID);
271
+ if (status === UNKNOWN) {
272
+ this._handleMissing();
273
+ }
274
+
275
+ if (status === EXISTENT) {
276
+ const record = this._source.get(dataID);
277
+ const invalidatedAt = RelayModernRecord.getInvalidationEpoch(record);
278
+ if (invalidatedAt != null) {
279
+ this._mostRecentlyInvalidatedAt =
280
+ this._mostRecentlyInvalidatedAt != null
281
+ ? Math.max(this._mostRecentlyInvalidatedAt, invalidatedAt)
282
+ : invalidatedAt;
283
+ }
284
+
285
+ this._traverseSelections(node.selections, dataID);
286
+ }
287
+ }
288
+
289
+ _traverseSelections(
290
+ selections: $ReadOnlyArray<NormalizationSelection>,
291
+ dataID: DataID,
292
+ ): void {
293
+ selections.forEach(selection => {
294
+ switch (selection.kind) {
295
+ case SCALAR_FIELD:
296
+ this._checkScalar(selection, dataID);
297
+ break;
298
+ case LINKED_FIELD:
299
+ if (selection.plural) {
300
+ this._checkPluralLink(selection, dataID);
301
+ } else {
302
+ this._checkLink(selection, dataID);
303
+ }
304
+ break;
305
+ case CONDITION:
306
+ const conditionValue = this._getVariableValue(selection.condition);
307
+ if (conditionValue === selection.passingValue) {
308
+ this._traverseSelections(selection.selections, dataID);
309
+ }
310
+ break;
311
+ case INLINE_FRAGMENT: {
312
+ const {abstractKey} = selection;
313
+ if (abstractKey == null) {
314
+ // concrete type refinement: only check data if the type exactly matches
315
+ const typeName = this._mutator.getType(dataID);
316
+ if (typeName === selection.type) {
317
+ this._traverseSelections(selection.selections, dataID);
318
+ }
319
+ } else if (RelayFeatureFlags.ENABLE_PRECISE_TYPE_REFINEMENT) {
320
+ // Abstract refinement: check data depending on whether the type
321
+ // conforms to the interface/union or not:
322
+ // - Type known to _not_ implement the interface: don't check the selections.
323
+ // - Type is known _to_ implement the interface: check selections.
324
+ // - Unknown whether the type implements the interface: don't check the selections
325
+ // and treat the data as missing; we do this because the Relay Compiler
326
+ // guarantees that the type discriminator will always be fetched.
327
+ const recordType = this._mutator.getType(dataID);
328
+ invariant(
329
+ recordType != null,
330
+ 'DataChecker: Expected record `%s` to have a known type',
331
+ dataID,
332
+ );
333
+ const typeID = generateTypeID(recordType);
334
+ const implementsInterface = this._mutator.getValue(
335
+ typeID,
336
+ abstractKey,
337
+ );
338
+ if (implementsInterface === true) {
339
+ this._traverseSelections(selection.selections, dataID);
340
+ } else if (implementsInterface == null) {
341
+ // unsure if the type implements the interface: data is
342
+ // missing so don't bother reading the fragment
343
+ this._handleMissing();
344
+ } // else false: known to not implement the interface
345
+ } else {
346
+ // legacy behavior for abstract refinements: always check even
347
+ // if the type doesn't conform
348
+ this._traverseSelections(selection.selections, dataID);
349
+ }
350
+ break;
351
+ }
352
+ case LINKED_HANDLE: {
353
+ // Handles have no selections themselves; traverse the original field
354
+ // where the handle was set-up instead.
355
+ const handleField = cloneRelayHandleSourceField(
356
+ selection,
357
+ selections,
358
+ this._variables,
359
+ );
360
+ if (handleField.plural) {
361
+ this._checkPluralLink(handleField, dataID);
362
+ } else {
363
+ this._checkLink(handleField, dataID);
364
+ }
365
+ break;
366
+ }
367
+ case SCALAR_HANDLE: {
368
+ const handleField = cloneRelayScalarHandleSourceField(
369
+ selection,
370
+ selections,
371
+ this._variables,
372
+ );
373
+
374
+ this._checkScalar(handleField, dataID);
375
+ break;
376
+ }
377
+ case MODULE_IMPORT:
378
+ this._checkModuleImport(selection, dataID);
379
+ break;
380
+ case DEFER:
381
+ case STREAM:
382
+ this._traverseSelections(selection.selections, dataID);
383
+ break;
384
+ // $FlowFixMe[incompatible-type]
385
+ case FRAGMENT_SPREAD:
386
+ invariant(
387
+ false,
388
+ 'RelayAsyncLoader(): Unexpected ast kind `%s`.',
389
+ selection.kind,
390
+ );
391
+ // $FlowExpectedError[unreachable-code] - we need the break; for OSS linter
392
+ break;
393
+ case CLIENT_EXTENSION:
394
+ const recordWasMissing = this._recordWasMissing;
395
+ this._traverseSelections(selection.selections, dataID);
396
+ this._recordWasMissing = recordWasMissing;
397
+ break;
398
+ case TYPE_DISCRIMINATOR:
399
+ if (RelayFeatureFlags.ENABLE_PRECISE_TYPE_REFINEMENT) {
400
+ const {abstractKey} = selection;
401
+ const recordType = this._mutator.getType(dataID);
402
+ invariant(
403
+ recordType != null,
404
+ 'DataChecker: Expected record `%s` to have a known type',
405
+ dataID,
406
+ );
407
+ const typeID = generateTypeID(recordType);
408
+ const implementsInterface = this._mutator.getValue(
409
+ typeID,
410
+ abstractKey,
411
+ );
412
+ if (implementsInterface == null) {
413
+ // unsure if the type implements the interface: data is
414
+ // missing
415
+ this._handleMissing();
416
+ } // else: if it does or doesn't implement, we don't need to check or skip anything else
417
+ }
418
+ break;
419
+ case FLIGHT_FIELD:
420
+ if (RelayFeatureFlags.ENABLE_REACT_FLIGHT_COMPONENT_FIELD) {
421
+ this._checkFlightField(selection, dataID);
422
+ } else {
423
+ throw new Error('Flight fields are not yet supported.');
424
+ }
425
+ break;
426
+ default:
427
+ (selection: empty);
428
+ invariant(
429
+ false,
430
+ 'RelayAsyncLoader(): Unexpected ast kind `%s`.',
431
+ selection.kind,
432
+ );
433
+ }
434
+ });
435
+ }
436
+
437
+ _checkModuleImport(
438
+ moduleImport: NormalizationModuleImport,
439
+ dataID: DataID,
440
+ ): void {
441
+ const operationLoader = this._operationLoader;
442
+ invariant(
443
+ operationLoader !== null,
444
+ 'DataChecker: Expected an operationLoader to be configured when using `@module`.',
445
+ );
446
+ const operationKey = getModuleOperationKey(moduleImport.documentName);
447
+ const operationReference = this._mutator.getValue(dataID, operationKey);
448
+ if (operationReference == null) {
449
+ if (operationReference === undefined) {
450
+ this._handleMissing();
451
+ }
452
+ return;
453
+ }
454
+ const normalizationRootNode = operationLoader.get(operationReference);
455
+ if (normalizationRootNode != null) {
456
+ const operation = getOperation(normalizationRootNode);
457
+ this._traverse(operation, dataID);
458
+ } else {
459
+ // If the fragment is not available, we assume that the data cannot have been
460
+ // processed yet and must therefore be missing.
461
+ this._handleMissing();
462
+ }
463
+ }
464
+
465
+ _checkScalar(field: NormalizationScalarField, dataID: DataID): void {
466
+ const storageKey = getStorageKey(field, this._variables);
467
+ let fieldValue = this._mutator.getValue(dataID, storageKey);
468
+ if (fieldValue === undefined) {
469
+ fieldValue = this._handleMissingScalarField(field, dataID);
470
+ if (fieldValue !== undefined) {
471
+ this._mutator.setValue(dataID, storageKey, fieldValue);
472
+ }
473
+ }
474
+ }
475
+
476
+ _checkLink(field: NormalizationLinkedField, dataID: DataID): void {
477
+ const storageKey = getStorageKey(field, this._variables);
478
+ let linkedID = this._mutator.getLinkedRecordID(dataID, storageKey);
479
+
480
+ if (linkedID === undefined) {
481
+ linkedID = this._handleMissingLinkField(field, dataID);
482
+ if (linkedID != null) {
483
+ this._mutator.setLinkedRecordID(dataID, storageKey, linkedID);
484
+ } else if (linkedID === null) {
485
+ this._mutator.setValue(dataID, storageKey, null);
486
+ }
487
+ }
488
+ if (linkedID != null) {
489
+ this._traverse(field, linkedID);
490
+ }
491
+ }
492
+
493
+ _checkPluralLink(field: NormalizationLinkedField, dataID: DataID): void {
494
+ const storageKey = getStorageKey(field, this._variables);
495
+ let linkedIDs = this._mutator.getLinkedRecordIDs(dataID, storageKey);
496
+
497
+ if (linkedIDs === undefined) {
498
+ linkedIDs = this._handleMissingPluralLinkField(field, dataID);
499
+ if (linkedIDs != null) {
500
+ this._mutator.setLinkedRecordIDs(dataID, storageKey, linkedIDs);
501
+ } else if (linkedIDs === null) {
502
+ this._mutator.setValue(dataID, storageKey, null);
503
+ }
504
+ }
505
+ if (linkedIDs) {
506
+ linkedIDs.forEach(linkedID => {
507
+ if (linkedID != null) {
508
+ this._traverse(field, linkedID);
509
+ }
510
+ });
511
+ }
512
+ }
513
+
514
+ _checkFlightField(field: NormalizationFlightField, dataID: DataID): void {
515
+ const storageKey = getStorageKey(field, this._variables);
516
+ const linkedID = this._mutator.getLinkedRecordID(dataID, storageKey);
517
+
518
+ if (linkedID == null) {
519
+ if (linkedID === undefined) {
520
+ this._handleMissing();
521
+ return;
522
+ }
523
+ return;
524
+ }
525
+
526
+ const tree = this._mutator.getValue(
527
+ linkedID,
528
+ RelayStoreReactFlightUtils.REACT_FLIGHT_TREE_STORAGE_KEY,
529
+ );
530
+ const reachableQueries = this._mutator.getValue(
531
+ linkedID,
532
+ RelayStoreReactFlightUtils.REACT_FLIGHT_QUERIES_STORAGE_KEY,
533
+ );
534
+
535
+ if (tree == null || !Array.isArray(reachableQueries)) {
536
+ this._handleMissing();
537
+ return;
538
+ }
539
+
540
+ const operationLoader = this._operationLoader;
541
+ invariant(
542
+ operationLoader !== null,
543
+ 'DataChecker: Expected an operationLoader to be configured when using ' +
544
+ 'React Flight.',
545
+ );
546
+ // In Flight, the variables that are in scope for reachable queries aren't
547
+ // the same as what's in scope for the outer query.
548
+ const prevVariables = this._variables;
549
+ // $FlowFixMe[incompatible-cast]
550
+ for (const query of (reachableQueries: Array<ReactFlightReachableQuery>)) {
551
+ this._variables = query.variables;
552
+ const normalizationRootNode = operationLoader.get(query.module);
553
+ if (normalizationRootNode != null) {
554
+ const operation = getOperation(normalizationRootNode);
555
+ this._traverseSelections(operation.selections, ROOT_ID);
556
+ } else {
557
+ // If the fragment is not available, we assume that the data cannot have
558
+ // been processed yet and must therefore be missing.
559
+ this._handleMissing();
560
+ }
561
+ }
562
+ this._variables = prevVariables;
563
+ }
564
+ }
565
+
566
+ module.exports = {
567
+ check,
568
+ };
@@ -0,0 +1,96 @@
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 invariant = require('invariant');
16
+
17
+ import type {NormalizationOperation} from '../util/NormalizationNode';
18
+ import type {ReaderFragment} from '../util/ReaderNode';
19
+ import type {Variables} from '../util/RelayRuntimeTypes';
20
+
21
+ /**
22
+ * Determines the variables that are in scope for a fragment given the variables
23
+ * in scope at the root query as well as any arguments applied at the fragment
24
+ * spread via `@arguments`.
25
+ *
26
+ * Note that this is analagous to determining function arguments given a function call.
27
+ */
28
+ function getFragmentVariables(
29
+ fragment: ReaderFragment,
30
+ rootVariables: Variables,
31
+ argumentVariables: Variables,
32
+ ): Variables {
33
+ let variables;
34
+ fragment.argumentDefinitions.forEach(definition => {
35
+ if (argumentVariables.hasOwnProperty(definition.name)) {
36
+ return;
37
+ }
38
+ variables = variables || {...argumentVariables};
39
+ switch (definition.kind) {
40
+ case 'LocalArgument':
41
+ variables[definition.name] = definition.defaultValue;
42
+ break;
43
+ case 'RootArgument':
44
+ if (!rootVariables.hasOwnProperty(definition.name)) {
45
+ /*
46
+ * Global variables passed as values of @arguments are not required to
47
+ * be declared unless they are used by the callee fragment or a
48
+ * descendant. In this case, the root variable may not be defined when
49
+ * resolving the callee's variables. The value is explicitly set to
50
+ * undefined to conform to the check in
51
+ * RelayStoreUtils.getStableVariableValue() that variable keys are all
52
+ * present.
53
+ */
54
+ variables[definition.name] = undefined;
55
+ break;
56
+ }
57
+ variables[definition.name] = rootVariables[definition.name];
58
+ break;
59
+ default:
60
+ (definition: empty);
61
+ invariant(
62
+ false,
63
+ 'RelayConcreteVariables: Unexpected node kind `%s` in fragment `%s`.',
64
+ definition.kind,
65
+ fragment.name,
66
+ );
67
+ }
68
+ });
69
+ return variables || argumentVariables;
70
+ }
71
+
72
+ /**
73
+ * Determines the variables that are in scope for a given operation given values
74
+ * for some/all of its arguments. Extraneous input variables are filtered from
75
+ * the output, and missing variables are set to default values (if given in the
76
+ * operation's definition).
77
+ */
78
+ function getOperationVariables(
79
+ operation: NormalizationOperation,
80
+ variables: Variables,
81
+ ): Variables {
82
+ const operationVariables = {};
83
+ operation.argumentDefinitions.forEach(def => {
84
+ let value = def.defaultValue;
85
+ if (variables[def.name] != null) {
86
+ value = variables[def.name];
87
+ }
88
+ operationVariables[def.name] = value;
89
+ });
90
+ return operationVariables;
91
+ }
92
+
93
+ module.exports = {
94
+ getFragmentVariables,
95
+ getOperationVariables,
96
+ };