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