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.
- package/handlers/RelayDefaultHandlerProvider.js.flow +47 -0
- package/handlers/connection/ConnectionHandler.js.flow +549 -0
- package/handlers/connection/ConnectionInterface.js.flow +92 -0
- package/handlers/connection/MutationHandlers.js.flow +199 -0
- package/index.js +1 -1
- package/index.js.flow +335 -0
- package/lib/handlers/RelayDefaultHandlerProvider.js +20 -0
- package/lib/handlers/connection/ConnectionHandler.js +1 -3
- package/lib/handlers/connection/MutationHandlers.js +212 -0
- package/lib/index.js +14 -2
- package/lib/mutations/RelayDeclarativeMutationConfig.js +22 -45
- package/lib/mutations/RelayRecordProxy.js +1 -3
- package/lib/mutations/RelayRecordSourceMutator.js +1 -3
- package/lib/mutations/RelayRecordSourceProxy.js +1 -3
- package/lib/mutations/RelayRecordSourceSelectorProxy.js +1 -3
- package/lib/mutations/commitMutation.js +2 -3
- package/lib/mutations/validateMutation.js +40 -9
- package/lib/network/RelayObservable.js +9 -9
- package/lib/network/RelayQueryResponseCache.js +8 -6
- package/lib/query/GraphQLTag.js +2 -1
- package/lib/query/PreloadableQueryRegistry.js +70 -0
- package/lib/query/fetchQuery.js +2 -3
- package/lib/query/fetchQueryInternal.js +5 -14
- package/lib/store/DataChecker.js +200 -71
- package/lib/store/RelayConcreteVariables.js +6 -2
- package/lib/store/RelayModernEnvironment.js +124 -65
- package/lib/store/RelayModernFragmentSpecResolver.js +19 -14
- package/lib/store/RelayModernOperationDescriptor.js +6 -5
- package/lib/store/RelayModernQueryExecutor.js +122 -73
- package/lib/store/RelayModernRecord.js +14 -9
- package/lib/store/RelayModernSelector.js +6 -2
- package/lib/store/RelayModernStore.js +281 -131
- package/lib/store/RelayOperationTracker.js +35 -78
- package/lib/store/RelayOptimisticRecordSource.js +7 -5
- package/lib/store/RelayPublishQueue.js +2 -4
- package/lib/store/RelayReader.js +304 -52
- package/lib/store/RelayRecordSource.js +1 -3
- package/lib/store/RelayRecordSourceMapImpl.js +13 -18
- package/lib/store/RelayReferenceMarker.js +125 -14
- package/lib/store/RelayResponseNormalizer.js +261 -66
- package/lib/store/RelayStoreReactFlightUtils.js +47 -0
- 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/defaultRequiredFieldLogger.js +18 -0
- package/lib/store/normalizeRelayPayload.js +6 -2
- package/lib/store/readInlineData.js +1 -1
- package/lib/subscription/requestSubscription.js +4 -3
- package/lib/util/NormalizationNode.js +1 -5
- package/lib/util/RelayConcreteNode.js +11 -6
- package/lib/util/RelayError.js +39 -9
- package/lib/util/RelayFeatureFlags.js +6 -3
- package/lib/util/RelayReplaySubject.js +3 -3
- package/lib/util/createPayloadFor3DField.js +7 -2
- package/lib/util/getFragmentIdentifier.js +12 -3
- package/lib/util/getOperation.js +33 -0
- package/lib/util/getRequestIdentifier.js +2 -2
- package/lib/util/isEmptyObject.js +25 -0
- package/lib/util/recycleNodesInto.js +6 -7
- package/lib/util/reportMissingRequiredFields.js +48 -0
- 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 +181 -0
- package/mutations/validateMutation.js.flow +242 -0
- package/network/ConvertToExecuteFunction.js.flow +49 -0
- package/network/RelayNetwork.js.flow +84 -0
- package/network/RelayNetworkTypes.js.flow +145 -0
- package/network/RelayObservable.js.flow +634 -0
- package/network/RelayQueryResponseCache.js.flow +111 -0
- package/package.json +2 -2
- package/query/GraphQLTag.js.flow +168 -0
- package/query/PreloadableQueryRegistry.js.flow +65 -0
- package/query/fetchQuery.js.flow +47 -0
- package/query/fetchQueryInternal.js.flow +343 -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 +568 -0
- package/store/RelayConcreteVariables.js.flow +96 -0
- package/store/RelayModernEnvironment.js.flow +571 -0
- package/store/RelayModernFragmentSpecResolver.js.flow +438 -0
- package/store/RelayModernOperationDescriptor.js.flow +92 -0
- package/store/RelayModernQueryExecutor.js.flow +1345 -0
- package/store/RelayModernRecord.js.flow +403 -0
- package/store/RelayModernSelector.js.flow +455 -0
- package/store/RelayModernStore.js.flow +858 -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 +638 -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 +324 -0
- package/store/RelayResponseNormalizer.js.flow +791 -0
- package/store/RelayStoreReactFlightUtils.js.flow +64 -0
- package/store/RelayStoreTypes.js.flow +958 -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/defaultRequiredFieldLogger.js.flow +23 -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 +103 -0
- package/util/JSResourceTypes.flow.js.flow +20 -0
- package/util/NormalizationNode.js.flow +213 -0
- package/util/ReaderNode.js.flow +227 -0
- package/util/RelayConcreteNode.js.flow +99 -0
- package/util/RelayDefaultHandleKey.js.flow +17 -0
- package/util/RelayError.js.flow +62 -0
- package/util/RelayFeatureFlags.js.flow +37 -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 +76 -0
- package/util/getOperation.js.flow +40 -0
- package/util/getRelayHandleKey.js.flow +41 -0
- package/util/getRequestIdentifier.js.flow +42 -0
- package/util/isEmptyObject.js.flow +25 -0
- package/util/isPromise.js.flow +21 -0
- package/util/isScalarAndEqual.js.flow +26 -0
- package/util/recycleNodesInto.js.flow +87 -0
- package/util/reportMissingRequiredFields.js.flow +51 -0
- package/util/resolveImmediate.js.flow +30 -0
- package/util/stableCopy.js.flow +35 -0
|
@@ -0,0 +1,181 @@
|
|
|
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
|
|
8
|
+
* @format
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// flowlint ambiguous-object-type:error
|
|
12
|
+
|
|
13
|
+
'use strict';
|
|
14
|
+
|
|
15
|
+
const RelayDeclarativeMutationConfig = require('./RelayDeclarativeMutationConfig');
|
|
16
|
+
|
|
17
|
+
const invariant = require('invariant');
|
|
18
|
+
const isRelayModernEnvironment = require('../store/isRelayModernEnvironment');
|
|
19
|
+
const validateMutation = require('./validateMutation');
|
|
20
|
+
const warning = require('warning');
|
|
21
|
+
|
|
22
|
+
const {getRequest} = require('../query/GraphQLTag');
|
|
23
|
+
const {generateUniqueClientID} = require('../store/ClientID');
|
|
24
|
+
const {
|
|
25
|
+
createOperationDescriptor,
|
|
26
|
+
} = require('../store/RelayModernOperationDescriptor');
|
|
27
|
+
|
|
28
|
+
import type {PayloadError, UploadableMap} from '../network/RelayNetworkTypes';
|
|
29
|
+
import type {GraphQLTaggedNode} from '../query/GraphQLTag';
|
|
30
|
+
import type {
|
|
31
|
+
IEnvironment,
|
|
32
|
+
SelectorStoreUpdater,
|
|
33
|
+
} from '../store/RelayStoreTypes';
|
|
34
|
+
import type {
|
|
35
|
+
CacheConfig,
|
|
36
|
+
Disposable,
|
|
37
|
+
Variables,
|
|
38
|
+
} from '../util/RelayRuntimeTypes';
|
|
39
|
+
import type {DeclarativeMutationConfig} from './RelayDeclarativeMutationConfig';
|
|
40
|
+
|
|
41
|
+
export type DEPRECATED_MutationConfig<T> = {|
|
|
42
|
+
configs?: Array<DeclarativeMutationConfig>,
|
|
43
|
+
cacheConfig?: CacheConfig,
|
|
44
|
+
mutation: GraphQLTaggedNode,
|
|
45
|
+
variables: Variables,
|
|
46
|
+
uploadables?: UploadableMap,
|
|
47
|
+
onCompleted?: ?(response: T, errors: ?Array<PayloadError>) => void,
|
|
48
|
+
onError?: ?(error: Error) => void,
|
|
49
|
+
onUnsubscribe?: ?() => void,
|
|
50
|
+
optimisticUpdater?: ?SelectorStoreUpdater,
|
|
51
|
+
optimisticResponse?: Object,
|
|
52
|
+
updater?: ?SelectorStoreUpdater,
|
|
53
|
+
|};
|
|
54
|
+
|
|
55
|
+
export type MutationParameters = {|
|
|
56
|
+
+response: {...},
|
|
57
|
+
+variables: {...},
|
|
58
|
+
+rawResponse?: {...},
|
|
59
|
+
|};
|
|
60
|
+
|
|
61
|
+
export type MutationConfig<T: MutationParameters> = {|
|
|
62
|
+
configs?: Array<DeclarativeMutationConfig>,
|
|
63
|
+
cacheConfig?: CacheConfig,
|
|
64
|
+
mutation: GraphQLTaggedNode,
|
|
65
|
+
onError?: ?(error: Error) => void,
|
|
66
|
+
onCompleted?: ?(
|
|
67
|
+
response: $ElementType<T, 'response'>,
|
|
68
|
+
errors: ?Array<PayloadError>,
|
|
69
|
+
) => void,
|
|
70
|
+
onUnsubscribe?: ?() => void,
|
|
71
|
+
optimisticResponse?: $ElementType<
|
|
72
|
+
{
|
|
73
|
+
+rawResponse?: {...},
|
|
74
|
+
...T,
|
|
75
|
+
...
|
|
76
|
+
},
|
|
77
|
+
'rawResponse',
|
|
78
|
+
>,
|
|
79
|
+
optimisticUpdater?: ?SelectorStoreUpdater,
|
|
80
|
+
updater?: ?SelectorStoreUpdater,
|
|
81
|
+
uploadables?: UploadableMap,
|
|
82
|
+
variables: $ElementType<T, 'variables'>,
|
|
83
|
+
|};
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Higher-level helper function to execute a mutation against a specific
|
|
87
|
+
* environment.
|
|
88
|
+
*/
|
|
89
|
+
function commitMutation<T: MutationParameters>(
|
|
90
|
+
environment: IEnvironment,
|
|
91
|
+
config: MutationConfig<T>,
|
|
92
|
+
): Disposable {
|
|
93
|
+
invariant(
|
|
94
|
+
isRelayModernEnvironment(environment),
|
|
95
|
+
'commitMutation: expected `environment` to be an instance of ' +
|
|
96
|
+
'`RelayModernEnvironment`.',
|
|
97
|
+
);
|
|
98
|
+
const mutation = getRequest(config.mutation);
|
|
99
|
+
if (mutation.params.operationKind !== 'mutation') {
|
|
100
|
+
throw new Error('commitMutation: Expected mutation operation');
|
|
101
|
+
}
|
|
102
|
+
if (mutation.kind !== 'Request') {
|
|
103
|
+
throw new Error('commitMutation: Expected mutation to be of type request');
|
|
104
|
+
}
|
|
105
|
+
let {optimisticResponse, optimisticUpdater, updater} = config;
|
|
106
|
+
const {
|
|
107
|
+
configs,
|
|
108
|
+
cacheConfig,
|
|
109
|
+
onError,
|
|
110
|
+
onUnsubscribe,
|
|
111
|
+
variables,
|
|
112
|
+
uploadables,
|
|
113
|
+
} = config;
|
|
114
|
+
const operation = createOperationDescriptor(
|
|
115
|
+
mutation,
|
|
116
|
+
variables,
|
|
117
|
+
cacheConfig,
|
|
118
|
+
generateUniqueClientID(),
|
|
119
|
+
);
|
|
120
|
+
// TODO: remove this check after we fix flow.
|
|
121
|
+
if (typeof optimisticResponse === 'function') {
|
|
122
|
+
optimisticResponse = optimisticResponse();
|
|
123
|
+
warning(
|
|
124
|
+
false,
|
|
125
|
+
'commitMutation: Expected `optimisticResponse` to be an object, ' +
|
|
126
|
+
'received a function.',
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
if (__DEV__) {
|
|
130
|
+
if (optimisticResponse instanceof Object) {
|
|
131
|
+
validateMutation(optimisticResponse, mutation, variables);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (configs) {
|
|
135
|
+
({optimisticUpdater, updater} = RelayDeclarativeMutationConfig.convert(
|
|
136
|
+
configs,
|
|
137
|
+
mutation,
|
|
138
|
+
optimisticUpdater,
|
|
139
|
+
updater,
|
|
140
|
+
));
|
|
141
|
+
}
|
|
142
|
+
const errors = [];
|
|
143
|
+
const subscription = environment
|
|
144
|
+
.executeMutation({
|
|
145
|
+
operation,
|
|
146
|
+
optimisticResponse,
|
|
147
|
+
optimisticUpdater,
|
|
148
|
+
updater,
|
|
149
|
+
uploadables,
|
|
150
|
+
})
|
|
151
|
+
.subscribe({
|
|
152
|
+
next: payload => {
|
|
153
|
+
if (Array.isArray(payload)) {
|
|
154
|
+
payload.forEach(item => {
|
|
155
|
+
if (item.errors) {
|
|
156
|
+
errors.push(...item.errors);
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
} else {
|
|
160
|
+
if (payload.errors) {
|
|
161
|
+
errors.push(...payload.errors);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
complete: () => {
|
|
166
|
+
const {onCompleted} = config;
|
|
167
|
+
if (onCompleted) {
|
|
168
|
+
const snapshot = environment.lookup(operation.fragment);
|
|
169
|
+
onCompleted(
|
|
170
|
+
(snapshot.data: $FlowFixMe),
|
|
171
|
+
errors.length !== 0 ? errors : null,
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
error: onError,
|
|
176
|
+
unsubscribe: onUnsubscribe,
|
|
177
|
+
});
|
|
178
|
+
return {dispose: subscription.unsubscribe};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
module.exports = commitMutation;
|
|
@@ -0,0 +1,242 @@
|
|
|
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
|
|
8
|
+
* @format
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// flowlint ambiguous-object-type:error
|
|
12
|
+
|
|
13
|
+
'use strict';
|
|
14
|
+
|
|
15
|
+
import type {
|
|
16
|
+
NormalizationSelection,
|
|
17
|
+
NormalizationField,
|
|
18
|
+
} from '../util/NormalizationNode';
|
|
19
|
+
import type {ConcreteRequest} from '../util/RelayConcreteNode';
|
|
20
|
+
import type {Variables} from '../util/RelayRuntimeTypes';
|
|
21
|
+
|
|
22
|
+
type ValidationContext = {|
|
|
23
|
+
visitedPaths: Set<string>,
|
|
24
|
+
path: string,
|
|
25
|
+
variables: Variables,
|
|
26
|
+
missingDiff: Object,
|
|
27
|
+
extraDiff: Object,
|
|
28
|
+
moduleImportPaths: Set<string>,
|
|
29
|
+
|};
|
|
30
|
+
|
|
31
|
+
const warning = require('warning');
|
|
32
|
+
|
|
33
|
+
const hasOwnProperty = Object.prototype.hasOwnProperty;
|
|
34
|
+
|
|
35
|
+
let validateMutation = () => {};
|
|
36
|
+
if (__DEV__) {
|
|
37
|
+
const addFieldToDiff = (path: string, diff: Object, isScalar) => {
|
|
38
|
+
let deepLoc = diff;
|
|
39
|
+
path.split('.').forEach((key, index, arr) => {
|
|
40
|
+
if (deepLoc[key] == null) {
|
|
41
|
+
deepLoc[key] = {};
|
|
42
|
+
}
|
|
43
|
+
if (isScalar && index === arr.length - 1) {
|
|
44
|
+
deepLoc[key] = '<scalar>';
|
|
45
|
+
}
|
|
46
|
+
deepLoc = deepLoc[key];
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
validateMutation = (
|
|
50
|
+
optimisticResponse: Object,
|
|
51
|
+
mutation: ConcreteRequest,
|
|
52
|
+
variables: ?Object,
|
|
53
|
+
) => {
|
|
54
|
+
const operationName = mutation.operation.name;
|
|
55
|
+
const context: ValidationContext = {
|
|
56
|
+
path: 'ROOT',
|
|
57
|
+
visitedPaths: new Set(),
|
|
58
|
+
variables: variables || {},
|
|
59
|
+
missingDiff: {},
|
|
60
|
+
extraDiff: {},
|
|
61
|
+
moduleImportPaths: new Set(),
|
|
62
|
+
};
|
|
63
|
+
validateSelections(
|
|
64
|
+
optimisticResponse,
|
|
65
|
+
mutation.operation.selections,
|
|
66
|
+
context,
|
|
67
|
+
);
|
|
68
|
+
validateOptimisticResponse(optimisticResponse, context);
|
|
69
|
+
warning(
|
|
70
|
+
context.missingDiff.ROOT == null,
|
|
71
|
+
'Expected `optimisticResponse` to match structure of server response for mutation `%s`, please define fields for all of\n%s',
|
|
72
|
+
operationName,
|
|
73
|
+
JSON.stringify(context.missingDiff.ROOT, null, 2),
|
|
74
|
+
);
|
|
75
|
+
warning(
|
|
76
|
+
context.extraDiff.ROOT == null,
|
|
77
|
+
'Expected `optimisticResponse` to match structure of server response for mutation `%s`, please remove all fields of\n%s',
|
|
78
|
+
operationName,
|
|
79
|
+
JSON.stringify(context.extraDiff.ROOT, null, 2),
|
|
80
|
+
);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const validateSelections = (
|
|
84
|
+
optimisticResponse: Object,
|
|
85
|
+
selections: $ReadOnlyArray<NormalizationSelection>,
|
|
86
|
+
context: ValidationContext,
|
|
87
|
+
) => {
|
|
88
|
+
selections.forEach(selection =>
|
|
89
|
+
validateSelection(optimisticResponse, selection, context),
|
|
90
|
+
);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const validateSelection = (
|
|
94
|
+
optimisticResponse: Object,
|
|
95
|
+
selection: NormalizationSelection,
|
|
96
|
+
context: ValidationContext,
|
|
97
|
+
) => {
|
|
98
|
+
switch (selection.kind) {
|
|
99
|
+
case 'Condition':
|
|
100
|
+
validateSelections(optimisticResponse, selection.selections, context);
|
|
101
|
+
return;
|
|
102
|
+
case 'ScalarField':
|
|
103
|
+
case 'LinkedField':
|
|
104
|
+
case 'FlightField':
|
|
105
|
+
return validateField(optimisticResponse, selection, context);
|
|
106
|
+
case 'InlineFragment':
|
|
107
|
+
const type = selection.type;
|
|
108
|
+
const isConcreteType = selection.abstractKey == null;
|
|
109
|
+
selection.selections.forEach(subselection => {
|
|
110
|
+
if (isConcreteType && optimisticResponse.__typename !== type) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
validateSelection(optimisticResponse, subselection, context);
|
|
114
|
+
});
|
|
115
|
+
return;
|
|
116
|
+
case 'ClientExtension':
|
|
117
|
+
selection.selections.forEach(subselection => {
|
|
118
|
+
validateSelection(optimisticResponse, subselection, context);
|
|
119
|
+
});
|
|
120
|
+
return;
|
|
121
|
+
case 'ModuleImport':
|
|
122
|
+
return validateModuleImport(context);
|
|
123
|
+
case 'LinkedHandle':
|
|
124
|
+
case 'ScalarHandle':
|
|
125
|
+
case 'Defer':
|
|
126
|
+
case 'Stream':
|
|
127
|
+
case 'TypeDiscriminator': {
|
|
128
|
+
// TODO(T35864292) - Add missing validations for these types
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
default:
|
|
132
|
+
(selection: empty);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const validateModuleImport = (context: ValidationContext) => {
|
|
138
|
+
context.moduleImportPaths.add(context.path);
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const validateField = (
|
|
142
|
+
optimisticResponse: Object,
|
|
143
|
+
field: NormalizationField,
|
|
144
|
+
context: ValidationContext,
|
|
145
|
+
) => {
|
|
146
|
+
const fieldName = field.alias || field.name;
|
|
147
|
+
const path = `${context.path}.${fieldName}`;
|
|
148
|
+
context.visitedPaths.add(path);
|
|
149
|
+
switch (field.kind) {
|
|
150
|
+
case 'ScalarField':
|
|
151
|
+
if (hasOwnProperty.call(optimisticResponse, fieldName) === false) {
|
|
152
|
+
addFieldToDiff(path, context.missingDiff, true);
|
|
153
|
+
}
|
|
154
|
+
return;
|
|
155
|
+
case 'LinkedField':
|
|
156
|
+
const selections = field.selections;
|
|
157
|
+
if (
|
|
158
|
+
optimisticResponse[fieldName] === null ||
|
|
159
|
+
(hasOwnProperty.call(optimisticResponse, fieldName) &&
|
|
160
|
+
optimisticResponse[fieldName] === undefined)
|
|
161
|
+
) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (field.plural) {
|
|
165
|
+
if (Array.isArray(optimisticResponse[fieldName])) {
|
|
166
|
+
optimisticResponse[fieldName].forEach(r => {
|
|
167
|
+
if (r !== null) {
|
|
168
|
+
validateSelections(r, selections, {
|
|
169
|
+
...context,
|
|
170
|
+
path,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
return;
|
|
175
|
+
} else {
|
|
176
|
+
addFieldToDiff(path, context.missingDiff);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
} else {
|
|
180
|
+
if (optimisticResponse[fieldName] instanceof Object) {
|
|
181
|
+
validateSelections(optimisticResponse[fieldName], selections, {
|
|
182
|
+
...context,
|
|
183
|
+
path,
|
|
184
|
+
});
|
|
185
|
+
return;
|
|
186
|
+
} else {
|
|
187
|
+
addFieldToDiff(path, context.missingDiff);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
case 'FlightField':
|
|
192
|
+
if (
|
|
193
|
+
optimisticResponse[fieldName] === null ||
|
|
194
|
+
(hasOwnProperty.call(optimisticResponse, fieldName) &&
|
|
195
|
+
optimisticResponse[fieldName] === undefined)
|
|
196
|
+
) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
throw new Error(
|
|
200
|
+
'validateMutation: Flight fields are not compatible with ' +
|
|
201
|
+
'optimistic updates, as React does not have the component code ' +
|
|
202
|
+
'necessary to process new data on the client. Instead, you ' +
|
|
203
|
+
'should update your code to require a full refetch of the Flight ' +
|
|
204
|
+
'field so your UI can be updated.',
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
const validateOptimisticResponse = (
|
|
210
|
+
optimisticResponse: Object,
|
|
211
|
+
context: ValidationContext,
|
|
212
|
+
) => {
|
|
213
|
+
if (Array.isArray(optimisticResponse)) {
|
|
214
|
+
optimisticResponse.forEach(r => {
|
|
215
|
+
if (r instanceof Object) {
|
|
216
|
+
validateOptimisticResponse(r, context);
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
Object.keys(optimisticResponse).forEach((key: string) => {
|
|
222
|
+
const value = optimisticResponse[key];
|
|
223
|
+
const path = `${context.path}.${key}`;
|
|
224
|
+
// if it's a module import path we don't have an ast so we cannot validate it
|
|
225
|
+
if (context.moduleImportPaths.has(path)) {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
if (!context.visitedPaths.has(path)) {
|
|
229
|
+
addFieldToDiff(path, context.extraDiff);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
if (value instanceof Object) {
|
|
233
|
+
validateOptimisticResponse(value, {
|
|
234
|
+
...context,
|
|
235
|
+
path,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
module.exports = (validateMutation: (Object, ConcreteRequest, ?Object) => void);
|
|
@@ -0,0 +1,49 @@
|
|
|
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 RelayObservable = require('./RelayObservable');
|
|
16
|
+
|
|
17
|
+
import type {ExecuteFunction, FetchFunction} from './RelayNetworkTypes';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Converts a FetchFunction into an ExecuteFunction for use by RelayNetwork.
|
|
21
|
+
*/
|
|
22
|
+
function convertFetch(fn: FetchFunction): ExecuteFunction {
|
|
23
|
+
return function fetch(
|
|
24
|
+
request,
|
|
25
|
+
variables,
|
|
26
|
+
cacheConfig,
|
|
27
|
+
uploadables,
|
|
28
|
+
logRequestInfo,
|
|
29
|
+
) {
|
|
30
|
+
const result = fn(
|
|
31
|
+
request,
|
|
32
|
+
variables,
|
|
33
|
+
cacheConfig,
|
|
34
|
+
uploadables,
|
|
35
|
+
logRequestInfo,
|
|
36
|
+
);
|
|
37
|
+
// Note: We allow FetchFunction to directly return Error to indicate
|
|
38
|
+
// a failure to fetch. To avoid handling this special case throughout the
|
|
39
|
+
// Relay codebase, it is explicitly handled here.
|
|
40
|
+
if (result instanceof Error) {
|
|
41
|
+
return RelayObservable.create(sink => sink.error(result));
|
|
42
|
+
}
|
|
43
|
+
return RelayObservable.from(result);
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
module.exports = {
|
|
48
|
+
convertFetch,
|
|
49
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
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
|
+
const {convertFetch} = require('./ConvertToExecuteFunction');
|
|
18
|
+
|
|
19
|
+
import type {RequestParameters} from '../util/RelayConcreteNode';
|
|
20
|
+
import type {CacheConfig, Variables} from '../util/RelayRuntimeTypes';
|
|
21
|
+
import type {
|
|
22
|
+
FetchFunction,
|
|
23
|
+
GraphQLResponse,
|
|
24
|
+
LogRequestInfoFunction,
|
|
25
|
+
INetwork,
|
|
26
|
+
SubscribeFunction,
|
|
27
|
+
UploadableMap,
|
|
28
|
+
} from './RelayNetworkTypes';
|
|
29
|
+
import type RelayObservable from './RelayObservable';
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Creates an implementation of the `Network` interface defined in
|
|
33
|
+
* `RelayNetworkTypes` given `fetch` and `subscribe` functions.
|
|
34
|
+
*/
|
|
35
|
+
function create(
|
|
36
|
+
fetchFn: FetchFunction,
|
|
37
|
+
subscribe?: SubscribeFunction,
|
|
38
|
+
): INetwork {
|
|
39
|
+
// Convert to functions that returns RelayObservable.
|
|
40
|
+
const observeFetch = convertFetch(fetchFn);
|
|
41
|
+
|
|
42
|
+
function execute(
|
|
43
|
+
request: RequestParameters,
|
|
44
|
+
variables: Variables,
|
|
45
|
+
cacheConfig: CacheConfig,
|
|
46
|
+
uploadables?: ?UploadableMap,
|
|
47
|
+
logRequestInfo: ?LogRequestInfoFunction,
|
|
48
|
+
): RelayObservable<GraphQLResponse> {
|
|
49
|
+
if (request.operationKind === 'subscription') {
|
|
50
|
+
invariant(
|
|
51
|
+
subscribe,
|
|
52
|
+
'RelayNetwork: This network layer does not support Subscriptions. ' +
|
|
53
|
+
'To use Subscriptions, provide a custom network layer.',
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
invariant(
|
|
57
|
+
!uploadables,
|
|
58
|
+
'RelayNetwork: Cannot provide uploadables while subscribing.',
|
|
59
|
+
);
|
|
60
|
+
return subscribe(request, variables, cacheConfig);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const pollInterval = cacheConfig.poll;
|
|
64
|
+
if (pollInterval != null) {
|
|
65
|
+
invariant(
|
|
66
|
+
!uploadables,
|
|
67
|
+
'RelayNetwork: Cannot provide uploadables while polling.',
|
|
68
|
+
);
|
|
69
|
+
return observeFetch(request, variables, {force: true}).poll(pollInterval);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return observeFetch(
|
|
73
|
+
request,
|
|
74
|
+
variables,
|
|
75
|
+
cacheConfig,
|
|
76
|
+
uploadables,
|
|
77
|
+
logRequestInfo,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return {execute};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
module.exports = {create};
|
|
@@ -0,0 +1,145 @@
|
|
|
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
|
+
import type {RequestParameters} from '../util/RelayConcreteNode';
|
|
16
|
+
import type {CacheConfig, Variables} from '../util/RelayRuntimeTypes';
|
|
17
|
+
import type RelayObservable, {ObservableFromValue} from './RelayObservable';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* An interface for fetching the data for one or more (possibly interdependent)
|
|
21
|
+
* queries.
|
|
22
|
+
*/
|
|
23
|
+
export type INetwork = {|
|
|
24
|
+
execute: ExecuteFunction,
|
|
25
|
+
|};
|
|
26
|
+
export type LogRequestInfoFunction = mixed => void;
|
|
27
|
+
|
|
28
|
+
export type PayloadData = {[key: string]: mixed, ...};
|
|
29
|
+
|
|
30
|
+
export type PayloadError = {
|
|
31
|
+
message: string,
|
|
32
|
+
locations?: Array<{
|
|
33
|
+
line: number,
|
|
34
|
+
column: number,
|
|
35
|
+
...
|
|
36
|
+
}>,
|
|
37
|
+
// Not officially part of the spec, but used at Facebook
|
|
38
|
+
severity?: 'CRITICAL' | 'ERROR' | 'WARNING',
|
|
39
|
+
...
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type PayloadExtensions = {[key: string]: mixed, ...};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* The shape of a GraphQL response as dictated by the
|
|
46
|
+
* [spec](https://graphql.github.io/graphql-spec/June2018/#sec-Response-Format)
|
|
47
|
+
*/
|
|
48
|
+
export type GraphQLResponseWithData = {|
|
|
49
|
+
+data: PayloadData,
|
|
50
|
+
+errors?: Array<PayloadError>,
|
|
51
|
+
+extensions?: PayloadExtensions,
|
|
52
|
+
+label?: string,
|
|
53
|
+
+path?: Array<string | number>,
|
|
54
|
+
|};
|
|
55
|
+
|
|
56
|
+
export type GraphQLResponseWithoutData = {|
|
|
57
|
+
+data?: ?PayloadData,
|
|
58
|
+
+errors: Array<PayloadError>,
|
|
59
|
+
+extensions?: PayloadExtensions,
|
|
60
|
+
+label?: string,
|
|
61
|
+
+path?: Array<string | number>,
|
|
62
|
+
|};
|
|
63
|
+
|
|
64
|
+
export type GraphQLResponseWithExtensionsOnly = {|
|
|
65
|
+
// Per https://spec.graphql.org/June2018/#sec-Errors
|
|
66
|
+
// > If the data entry in the response is not present, the errors entry
|
|
67
|
+
// > in the response must not be empty. It must contain at least one error
|
|
68
|
+
// This means a payload has to have either a data key or an errors key:
|
|
69
|
+
// but the spec leaves room for the combination of data: null plus extensions
|
|
70
|
+
// since `data: null` is a *required* output if there was an error during
|
|
71
|
+
// execution, but the inverse is not described in the sepc: `data: null`
|
|
72
|
+
// does not necessarily indicate that there was an error.
|
|
73
|
+
+data: null,
|
|
74
|
+
+extensions: PayloadExtensions,
|
|
75
|
+
|};
|
|
76
|
+
|
|
77
|
+
export type GraphQLSingularResponse =
|
|
78
|
+
| GraphQLResponseWithData
|
|
79
|
+
| GraphQLResponseWithExtensionsOnly
|
|
80
|
+
| GraphQLResponseWithoutData;
|
|
81
|
+
|
|
82
|
+
export type GraphQLResponse =
|
|
83
|
+
| GraphQLSingularResponse
|
|
84
|
+
| $ReadOnlyArray<GraphQLSingularResponse>;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* A function that returns an Observable representing the response of executing
|
|
88
|
+
* a GraphQL operation.
|
|
89
|
+
*/
|
|
90
|
+
export type ExecuteFunction = (
|
|
91
|
+
request: RequestParameters,
|
|
92
|
+
variables: Variables,
|
|
93
|
+
cacheConfig: CacheConfig,
|
|
94
|
+
uploadables?: ?UploadableMap,
|
|
95
|
+
logRequestInfo?: ?LogRequestInfoFunction,
|
|
96
|
+
) => RelayObservable<GraphQLResponse>;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* A function that executes a GraphQL operation with request/response semantics.
|
|
100
|
+
*
|
|
101
|
+
* May return an Observable or Promise of a plain GraphQL server response, or
|
|
102
|
+
* a composed ExecutePayload object supporting additional metadata.
|
|
103
|
+
*/
|
|
104
|
+
export type FetchFunction = (
|
|
105
|
+
request: RequestParameters,
|
|
106
|
+
variables: Variables,
|
|
107
|
+
cacheConfig: CacheConfig,
|
|
108
|
+
uploadables: ?UploadableMap,
|
|
109
|
+
logRequestInfo?: ?LogRequestInfoFunction,
|
|
110
|
+
) => ObservableFromValue<GraphQLResponse>;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* A function that executes a GraphQL subscription operation, returning zero or
|
|
114
|
+
* more raw server responses over time.
|
|
115
|
+
*/
|
|
116
|
+
export type SubscribeFunction = (
|
|
117
|
+
request: RequestParameters,
|
|
118
|
+
variables: Variables,
|
|
119
|
+
cacheConfig: CacheConfig,
|
|
120
|
+
) => RelayObservable<GraphQLResponse>;
|
|
121
|
+
|
|
122
|
+
export type Uploadable = File | Blob;
|
|
123
|
+
export type UploadableMap = {[key: string]: Uploadable, ...};
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* React Flight tree created on the server.
|
|
127
|
+
*/
|
|
128
|
+
export type ReactFlightServerTree = mixed;
|
|
129
|
+
export type ReactFlightPayloadQuery = {|
|
|
130
|
+
+id: mixed,
|
|
131
|
+
+module: mixed,
|
|
132
|
+
+response: GraphQLSingularResponse,
|
|
133
|
+
+variables: Variables,
|
|
134
|
+
|};
|
|
135
|
+
/**
|
|
136
|
+
* Data that is returned by a Flight compliant GraphQL server.
|
|
137
|
+
*
|
|
138
|
+
* - tree: an array of values that will be iterated and fed into
|
|
139
|
+
* ReactFlightDOMRelayClient.
|
|
140
|
+
* - queries: an array of queries that the server preloaded for the client.
|
|
141
|
+
*/
|
|
142
|
+
export type ReactFlightPayloadData = {|
|
|
143
|
+
+tree: Array<ReactFlightServerTree>,
|
|
144
|
+
+queries: Array<ReactFlightPayloadQuery>,
|
|
145
|
+
|};
|