relay-compiler 9.0.0 → 10.1.0
Sign up to get free protection for your applications and to get access to all the features.
- package/bin/RelayCompilerBin.js.flow +169 -0
- package/bin/RelayCompilerMain.js.flow +515 -0
- package/bin/__fixtures__/plugin-module.js.flow +17 -0
- package/bin/relay-compiler +3862 -2505
- package/codegen/CodegenDirectory.js.flow +375 -0
- package/codegen/CodegenRunner.js.flow +432 -0
- package/codegen/CodegenTypes.js.flow +28 -0
- package/codegen/CodegenWatcher.js.flow +254 -0
- package/codegen/NormalizationCodeGenerator.js.flow +571 -0
- package/codegen/ReaderCodeGenerator.js.flow +512 -0
- package/codegen/RelayCodeGenerator.js.flow +85 -0
- package/codegen/RelayFileWriter.js.flow +367 -0
- package/codegen/SourceControl.js.flow +58 -0
- package/codegen/compileRelayArtifacts.js.flow +182 -0
- package/codegen/createPrintRequireModuleDependency.js.flow +21 -0
- package/codegen/sortObjectByKey.js.flow +25 -0
- package/codegen/writeRelayGeneratedFile.js.flow +223 -0
- package/core/ASTCache.js.flow +74 -0
- package/core/ASTConvert.js.flow +233 -0
- package/core/CompilerContext.js.flow +191 -0
- package/core/CompilerError.js.flow +250 -0
- package/core/DotGraphQLParser.js.flow +39 -0
- package/core/GraphQLCompilerProfiler.js.flow +341 -0
- package/core/GraphQLDerivedFromMetadata.js.flow +36 -0
- package/core/GraphQLWatchmanClient.js.flow +111 -0
- package/core/IR.js.flow +326 -0
- package/core/IRPrinter.js.flow +477 -0
- package/core/IRTransformer.js.flow +377 -0
- package/core/IRValidator.js.flow +260 -0
- package/core/IRVisitor.js.flow +150 -0
- package/core/JSModuleParser.js.flow +24 -0
- package/core/RelayCompilerScope.js.flow +199 -0
- package/core/RelayFindGraphQLTags.js.flow +119 -0
- package/core/RelayGraphQLEnumsGenerator.js.flow +55 -0
- package/core/RelayIRTransforms.js.flow +138 -0
- package/core/RelayParser.js.flow +1731 -0
- package/core/RelaySourceModuleParser.js.flow +135 -0
- package/core/Schema.js.flow +2037 -0
- package/core/SchemaUtils.js.flow +120 -0
- package/core/filterContextForNode.js.flow +50 -0
- package/core/getFieldDefinition.js.flow +156 -0
- package/core/getIdentifierForArgumentValue.js.flow +49 -0
- package/core/getIdentifierForSelection.js.flow +69 -0
- package/core/getLiteralArgumentValues.js.flow +32 -0
- package/core/getNormalizationOperationName.js.flow +19 -0
- package/core/inferRootArgumentDefinitions.js.flow +323 -0
- package/index.js +1 -1
- package/index.js.flow +200 -0
- package/language/RelayLanguagePluginInterface.js.flow +283 -0
- package/language/javascript/FindGraphQLTags.js.flow +137 -0
- package/language/javascript/RelayFlowBabelFactories.js.flow +176 -0
- package/language/javascript/RelayFlowGenerator.js.flow +1099 -0
- package/language/javascript/RelayFlowTypeTransformers.js.flow +184 -0
- package/language/javascript/RelayLanguagePluginJavaScript.js.flow +34 -0
- package/language/javascript/formatGeneratedModule.js.flow +65 -0
- package/lib/bin/RelayCompilerBin.js +10 -0
- package/lib/bin/RelayCompilerMain.js +127 -130
- package/lib/codegen/CodegenDirectory.js +2 -6
- package/lib/codegen/CodegenRunner.js +37 -76
- package/lib/codegen/CodegenWatcher.js +13 -21
- package/lib/codegen/NormalizationCodeGenerator.js +131 -50
- package/lib/codegen/ReaderCodeGenerator.js +116 -49
- package/lib/codegen/RelayCodeGenerator.js +17 -6
- package/lib/codegen/RelayFileWriter.js +15 -37
- package/lib/codegen/compileRelayArtifacts.js +16 -30
- package/lib/codegen/sortObjectByKey.js +43 -0
- package/lib/codegen/writeRelayGeneratedFile.js +86 -96
- package/lib/core/ASTCache.js +3 -4
- package/lib/core/CompilerContext.js +3 -4
- package/lib/core/CompilerError.js +27 -54
- package/lib/core/GraphQLCompilerProfiler.js +8 -12
- package/lib/core/GraphQLDerivedFromMetadata.js +1 -10
- package/lib/core/GraphQLWatchmanClient.js +4 -12
- package/lib/core/IRPrinter.js +16 -21
- package/lib/core/IRTransformer.js +8 -6
- package/lib/core/IRValidator.js +1 -3
- package/lib/core/RelayCompilerScope.js +4 -4
- package/lib/core/RelayGraphQLEnumsGenerator.js +12 -15
- package/lib/core/RelayIRTransforms.js +23 -13
- package/lib/core/RelayParser.js +53 -89
- package/lib/core/RelaySourceModuleParser.js +1 -3
- package/lib/core/Schema.js +106 -77
- package/lib/core/SchemaUtils.js +15 -1
- package/lib/core/getFieldDefinition.js +12 -15
- package/lib/core/getIdentifierForSelection.js +1 -1
- package/lib/core/inferRootArgumentDefinitions.js +27 -36
- package/lib/index.js +1 -3
- package/lib/language/javascript/FindGraphQLTags.js +7 -72
- package/lib/language/javascript/RelayFlowBabelFactories.js +5 -5
- package/lib/language/javascript/RelayFlowGenerator.js +131 -108
- package/lib/language/javascript/RelayFlowTypeTransformers.js +1 -3
- package/lib/reporters/ConsoleReporter.js +1 -3
- package/lib/reporters/MultiReporter.js +1 -3
- package/lib/runner/Artifacts.js +69 -170
- package/lib/runner/BufferedFilesystem.js +32 -66
- package/lib/runner/GraphQLASTNodeGroup.js +54 -120
- package/lib/runner/GraphQLNodeMap.js +14 -19
- package/lib/runner/Sources.js +70 -85
- package/lib/runner/StrictMap.js +21 -37
- package/lib/runner/getChangedNodeNames.js +30 -62
- package/lib/transforms/ApplyFragmentArgumentTransform.js +71 -31
- package/lib/transforms/ClientExtensionsTransform.js +15 -15
- package/lib/transforms/ConnectionTransform.js +26 -38
- package/lib/transforms/DeclarativeConnectionMutationTransform.js +225 -0
- package/lib/transforms/DeferStreamTransform.js +27 -17
- package/lib/transforms/DisallowTypenameOnRoot.js +55 -0
- package/lib/transforms/FieldHandleTransform.js +7 -3
- package/lib/transforms/FilterCompilerDirectivesTransform.js +29 -0
- package/lib/transforms/FlattenTransform.js +23 -19
- package/lib/transforms/GenerateIDFieldTransform.js +56 -35
- package/lib/transforms/GenerateTypeNameTransform.js +84 -10
- package/lib/transforms/InlineDataFragmentTransform.js +9 -4
- package/lib/transforms/MaskTransform.js +17 -17
- package/lib/transforms/MatchTransform.js +114 -32
- package/lib/transforms/ReactFlightComponentTransform.js +162 -0
- package/lib/transforms/RefetchableFragmentTransform.js +21 -17
- package/lib/transforms/RelayDirectiveTransform.js +8 -3
- package/lib/transforms/RequiredFieldTransform.js +380 -0
- package/lib/transforms/SkipClientExtensionsTransform.js +8 -0
- package/lib/transforms/SkipHandleFieldTransform.js +6 -2
- package/lib/transforms/SkipRedundantNodesTransform.js +9 -2
- package/lib/transforms/SkipSplitOperationTransform.js +32 -0
- package/lib/transforms/SkipUnreachableNodeTransform.js +9 -2
- package/lib/transforms/SkipUnusedVariablesTransform.js +18 -17
- package/lib/transforms/SplitModuleImportTransform.js +2 -2
- package/lib/transforms/TestOperationTransform.js +26 -20
- package/lib/transforms/ValidateGlobalVariablesTransform.js +18 -30
- package/lib/transforms/ValidateRequiredArgumentsTransform.js +12 -15
- package/lib/transforms/ValidateServerOnlyDirectivesTransform.js +16 -30
- package/lib/transforms/ValidateUnusedVariablesTransform.js +18 -30
- package/lib/transforms/query-generators/FetchableQueryGenerator.js +161 -0
- package/lib/transforms/query-generators/NodeQueryGenerator.js +22 -3
- package/lib/transforms/query-generators/QueryQueryGenerator.js +2 -1
- package/lib/transforms/query-generators/ViewerQueryGenerator.js +1 -0
- package/lib/transforms/query-generators/index.js +23 -6
- package/lib/transforms/query-generators/utils.js +17 -16
- package/lib/util/RelayCompilerCache.js +2 -4
- package/lib/util/argumentContainsVariables.js +37 -0
- package/lib/util/dedupeJSONStringify.js +15 -12
- package/lib/util/generateAbstractTypeRefinementKey.js +24 -0
- package/lib/util/getModuleName.js +1 -1
- package/lib/util/joinArgumentDefinitions.js +3 -1
- package/package.json +7 -7
- package/relay-compiler.js +4 -4
- package/relay-compiler.min.js +4 -4
- package/reporters/ConsoleReporter.js.flow +81 -0
- package/reporters/MultiReporter.js.flow +43 -0
- package/reporters/Reporter.js.flow +19 -0
- package/runner/Artifacts.js.flow +219 -0
- package/runner/BufferedFilesystem.js.flow +194 -0
- package/runner/GraphQLASTNodeGroup.js.flow +176 -0
- package/runner/GraphQLASTUtils.js.flow +26 -0
- package/runner/GraphQLNodeMap.js.flow +55 -0
- package/runner/Sources.js.flow +228 -0
- package/runner/StrictMap.js.flow +96 -0
- package/runner/compileArtifacts.js.flow +76 -0
- package/runner/extractAST.js.flow +100 -0
- package/runner/getChangedNodeNames.js.flow +48 -0
- package/runner/getSchemaInstance.js.flow +36 -0
- package/runner/types.js.flow +37 -0
- package/transforms/ApplyFragmentArgumentTransform.js.flow +526 -0
- package/transforms/ClientExtensionsTransform.js.flow +224 -0
- package/transforms/ConnectionTransform.js.flow +855 -0
- package/transforms/DeclarativeConnectionMutationTransform.js.flow +246 -0
- package/transforms/DeferStreamTransform.js.flow +265 -0
- package/transforms/DisallowIdAsAlias.js.flow +47 -0
- package/transforms/DisallowTypenameOnRoot.js.flow +45 -0
- package/transforms/FieldHandleTransform.js.flow +79 -0
- package/transforms/FilterCompilerDirectivesTransform.js.flow +33 -0
- package/transforms/FilterDirectivesTransform.js.flow +45 -0
- package/transforms/FlattenTransform.js.flow +454 -0
- package/transforms/GenerateIDFieldTransform.js.flow +152 -0
- package/transforms/GenerateTypeNameTransform.js.flow +161 -0
- package/transforms/InlineDataFragmentTransform.js.flow +125 -0
- package/transforms/InlineFragmentsTransform.js.flow +71 -0
- package/transforms/MaskTransform.js.flow +126 -0
- package/transforms/MatchTransform.js.flow +589 -0
- package/transforms/ReactFlightComponentTransform.js.flow +195 -0
- package/transforms/RefetchableFragmentTransform.js.flow +272 -0
- package/transforms/RelayDirectiveTransform.js.flow +97 -0
- package/transforms/RequiredFieldTransform.js.flow +415 -0
- package/transforms/SkipClientExtensionsTransform.js.flow +54 -0
- package/transforms/SkipHandleFieldTransform.js.flow +44 -0
- package/transforms/SkipRedundantNodesTransform.js.flow +257 -0
- package/transforms/SkipSplitOperationTransform.js.flow +37 -0
- package/transforms/SkipUnreachableNodeTransform.js.flow +149 -0
- package/transforms/SkipUnusedVariablesTransform.js.flow +59 -0
- package/transforms/SplitModuleImportTransform.js.flow +98 -0
- package/transforms/TestOperationTransform.js.flow +142 -0
- package/transforms/TransformUtils.js.flow +26 -0
- package/transforms/ValidateGlobalVariablesTransform.js.flow +81 -0
- package/transforms/ValidateRequiredArgumentsTransform.js.flow +127 -0
- package/transforms/ValidateServerOnlyDirectivesTransform.js.flow +112 -0
- package/transforms/ValidateUnusedVariablesTransform.js.flow +89 -0
- package/transforms/query-generators/FetchableQueryGenerator.js.flow +189 -0
- package/transforms/query-generators/NodeQueryGenerator.js.flow +219 -0
- package/transforms/query-generators/QueryQueryGenerator.js.flow +57 -0
- package/transforms/query-generators/ViewerQueryGenerator.js.flow +97 -0
- package/transforms/query-generators/index.js.flow +90 -0
- package/transforms/query-generators/utils.js.flow +76 -0
- package/util/CodeMarker.js.flow +79 -0
- package/util/DefaultHandleKey.js.flow +17 -0
- package/util/RelayCompilerCache.js.flow +88 -0
- package/util/Rollout.js.flow +39 -0
- package/util/TimeReporter.js.flow +79 -0
- package/util/areEqualOSS.js.flow +123 -0
- package/util/argumentContainsVariables.js.flow +38 -0
- package/util/dedupeJSONStringify.js.flow +152 -0
- package/util/generateAbstractTypeRefinementKey.js.flow +29 -0
- package/util/getDefinitionNodeHash.js.flow +25 -0
- package/util/getModuleName.js.flow +39 -0
- package/util/joinArgumentDefinitions.js.flow +105 -0
- package/util/md5.js.flow +22 -0
- package/util/murmurHash.js.flow +94 -0
- package/util/nullthrowsOSS.js.flow +25 -0
- package/util/orList.js.flow +37 -0
- package/util/partitionArray.js.flow +37 -0
@@ -0,0 +1,855 @@
|
|
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 IRTransformer = require('../core/IRTransformer');
|
16
|
+
const RelayParser = require('../core/RelayParser');
|
17
|
+
const SchemaUtils = require('../core/SchemaUtils');
|
18
|
+
|
19
|
+
const getLiteralArgumentValues = require('../core/getLiteralArgumentValues');
|
20
|
+
|
21
|
+
const {createCompilerError, createUserError} = require('../core/CompilerError');
|
22
|
+
const {parse} = require('graphql');
|
23
|
+
const {ConnectionInterface, RelayFeatureFlags} = require('relay-runtime');
|
24
|
+
|
25
|
+
import type CompilerContext from '../core/CompilerContext';
|
26
|
+
import type {
|
27
|
+
Argument,
|
28
|
+
Directive,
|
29
|
+
Fragment,
|
30
|
+
Handle,
|
31
|
+
InlineFragment,
|
32
|
+
LinkedField,
|
33
|
+
Root,
|
34
|
+
Selection,
|
35
|
+
Variable,
|
36
|
+
Location,
|
37
|
+
Defer,
|
38
|
+
} from '../core/IR';
|
39
|
+
import type {Schema, CompositeTypeID} from '../core/Schema';
|
40
|
+
import type {ConnectionMetadata} from 'relay-runtime';
|
41
|
+
|
42
|
+
type Options = {
|
43
|
+
documentName: string,
|
44
|
+
// The current path
|
45
|
+
path: Array<?string>,
|
46
|
+
// Metadata recorded for @connection fields
|
47
|
+
connectionMetadata: Array<ConnectionMetadata>,
|
48
|
+
...
|
49
|
+
};
|
50
|
+
|
51
|
+
type ConnectionArguments = {|
|
52
|
+
handler: ?string,
|
53
|
+
key: string,
|
54
|
+
dynamicKey: Variable | null,
|
55
|
+
filters: ?$ReadOnlyArray<string>,
|
56
|
+
stream: ?{|
|
57
|
+
if: ?Argument,
|
58
|
+
initialCount: ?Argument,
|
59
|
+
useCustomizedBatch: ?Argument,
|
60
|
+
label: string,
|
61
|
+
|},
|
62
|
+
|};
|
63
|
+
|
64
|
+
const AFTER = 'after';
|
65
|
+
const BEFORE = 'before';
|
66
|
+
const FIRST = 'first';
|
67
|
+
const KEY = 'key';
|
68
|
+
const LAST = 'last';
|
69
|
+
|
70
|
+
const CONNECTION = 'connection';
|
71
|
+
const STREAM_CONNECTION = 'stream_connection';
|
72
|
+
const HANDLER = 'handler';
|
73
|
+
|
74
|
+
/**
|
75
|
+
* @public
|
76
|
+
*
|
77
|
+
* Transforms fields with the `@connection` directive:
|
78
|
+
* - Verifies that the field type is connection-like.
|
79
|
+
* - Adds a `handle` property to the field, either the user-provided `handle`
|
80
|
+
* argument or the default value "connection".
|
81
|
+
* - Inserts a sub-fragment on the field to ensure that standard connection
|
82
|
+
* fields are fetched (e.g. cursors, node ids, page info).
|
83
|
+
*/
|
84
|
+
function connectionTransform(context: CompilerContext): CompilerContext {
|
85
|
+
return IRTransformer.transform(
|
86
|
+
context,
|
87
|
+
{
|
88
|
+
Fragment: visitFragmentOrRoot,
|
89
|
+
LinkedField: visitLinkedField,
|
90
|
+
Root: visitFragmentOrRoot,
|
91
|
+
},
|
92
|
+
node => ({
|
93
|
+
documentName: node.name,
|
94
|
+
path: [],
|
95
|
+
connectionMetadata: [],
|
96
|
+
}),
|
97
|
+
);
|
98
|
+
}
|
99
|
+
|
100
|
+
const SCHEMA_EXTENSION = `
|
101
|
+
directive @connection(
|
102
|
+
key: String!
|
103
|
+
filters: [String]
|
104
|
+
handler: String
|
105
|
+
dynamicKey_UNSTABLE: String
|
106
|
+
) on FIELD
|
107
|
+
|
108
|
+
directive @stream_connection(
|
109
|
+
key: String!
|
110
|
+
filters: [String]
|
111
|
+
handler: String
|
112
|
+
initial_count: Int!
|
113
|
+
if: Boolean = true
|
114
|
+
use_customized_batch: Boolean = false
|
115
|
+
dynamicKey_UNSTABLE: String
|
116
|
+
) on FIELD
|
117
|
+
`;
|
118
|
+
|
119
|
+
/**
|
120
|
+
* @internal
|
121
|
+
*/
|
122
|
+
function visitFragmentOrRoot<N: Fragment | Root>(
|
123
|
+
node: N,
|
124
|
+
options: Options,
|
125
|
+
): ?N {
|
126
|
+
const transformedNode = this.traverse(node, options);
|
127
|
+
const connectionMetadata = options.connectionMetadata;
|
128
|
+
if (connectionMetadata.length) {
|
129
|
+
return {
|
130
|
+
...transformedNode,
|
131
|
+
metadata: {
|
132
|
+
...transformedNode.metadata,
|
133
|
+
connection: connectionMetadata,
|
134
|
+
},
|
135
|
+
};
|
136
|
+
}
|
137
|
+
return transformedNode;
|
138
|
+
}
|
139
|
+
|
140
|
+
/**
|
141
|
+
* @internal
|
142
|
+
*/
|
143
|
+
function visitLinkedField(field: LinkedField, options: Options): LinkedField {
|
144
|
+
const context: CompilerContext = this.getContext();
|
145
|
+
const schema = context.getSchema();
|
146
|
+
|
147
|
+
const nullableType = schema.getNullableType(field.type);
|
148
|
+
|
149
|
+
const isPlural = schema.isList(nullableType);
|
150
|
+
const path = options.path.concat(isPlural ? null : field.alias || field.name);
|
151
|
+
let transformedField: LinkedField = this.traverse(field, {
|
152
|
+
...options,
|
153
|
+
path,
|
154
|
+
});
|
155
|
+
const connectionDirective = field.directives.find(
|
156
|
+
directive =>
|
157
|
+
directive.name === CONNECTION || directive.name === STREAM_CONNECTION,
|
158
|
+
);
|
159
|
+
if (!connectionDirective) {
|
160
|
+
return transformedField;
|
161
|
+
}
|
162
|
+
if (!schema.isObject(nullableType) && !schema.isInterface(nullableType)) {
|
163
|
+
throw new createUserError(
|
164
|
+
`@${connectionDirective.name} used on invalid field '${field.name}'. ` +
|
165
|
+
'Expected the return type to be a non-plural interface or object, ' +
|
166
|
+
`got '${schema.getTypeString(field.type)}'.`,
|
167
|
+
[transformedField.loc],
|
168
|
+
);
|
169
|
+
}
|
170
|
+
|
171
|
+
validateConnectionSelection(transformedField);
|
172
|
+
validateConnectionType(
|
173
|
+
schema,
|
174
|
+
transformedField,
|
175
|
+
schema.assertCompositeType(nullableType),
|
176
|
+
connectionDirective,
|
177
|
+
);
|
178
|
+
|
179
|
+
const connectionArguments = buildConnectionArguments(
|
180
|
+
transformedField,
|
181
|
+
connectionDirective,
|
182
|
+
);
|
183
|
+
|
184
|
+
const connectionMetadata = buildConnectionMetadata(
|
185
|
+
transformedField,
|
186
|
+
path,
|
187
|
+
connectionArguments.stream != null,
|
188
|
+
);
|
189
|
+
options.connectionMetadata.push(connectionMetadata);
|
190
|
+
|
191
|
+
const handle: Handle = {
|
192
|
+
name: connectionArguments.handler ?? CONNECTION,
|
193
|
+
key: connectionArguments.key,
|
194
|
+
dynamicKey: connectionArguments.dynamicKey,
|
195
|
+
filters: connectionArguments.filters,
|
196
|
+
};
|
197
|
+
|
198
|
+
const {direction} = connectionMetadata;
|
199
|
+
if (direction != null) {
|
200
|
+
const selections = transformConnectionSelections(
|
201
|
+
this.getContext(),
|
202
|
+
transformedField,
|
203
|
+
schema.assertCompositeType(nullableType),
|
204
|
+
direction,
|
205
|
+
connectionArguments,
|
206
|
+
connectionDirective.loc,
|
207
|
+
options.documentName,
|
208
|
+
);
|
209
|
+
transformedField = {
|
210
|
+
...transformedField,
|
211
|
+
selections,
|
212
|
+
};
|
213
|
+
}
|
214
|
+
return {
|
215
|
+
...transformedField,
|
216
|
+
directives: transformedField.directives.filter(
|
217
|
+
directive => directive !== connectionDirective,
|
218
|
+
),
|
219
|
+
connection: true,
|
220
|
+
handles: transformedField.handles
|
221
|
+
? [...transformedField.handles, handle]
|
222
|
+
: [handle],
|
223
|
+
};
|
224
|
+
}
|
225
|
+
|
226
|
+
function buildConnectionArguments(
|
227
|
+
field: LinkedField,
|
228
|
+
connectionDirective: Directive,
|
229
|
+
): ConnectionArguments {
|
230
|
+
const {
|
231
|
+
handler,
|
232
|
+
key,
|
233
|
+
label,
|
234
|
+
filters: literalFilters,
|
235
|
+
} = getLiteralArgumentValues(connectionDirective.args);
|
236
|
+
if (handler != null && typeof handler !== 'string') {
|
237
|
+
const handleArg = connectionDirective.args.find(
|
238
|
+
arg => arg.name === 'handler',
|
239
|
+
);
|
240
|
+
throw createUserError(
|
241
|
+
`Expected the ${HANDLER} argument to @${connectionDirective.name} to ` +
|
242
|
+
`be a string literal for field ${field.name}.`,
|
243
|
+
[handleArg?.value?.loc ?? connectionDirective.loc],
|
244
|
+
);
|
245
|
+
}
|
246
|
+
if (typeof key !== 'string') {
|
247
|
+
const keyArg = connectionDirective.args.find(arg => arg.name === 'key');
|
248
|
+
throw createUserError(
|
249
|
+
`Expected the ${KEY} argument to @${connectionDirective.name} to be a ` +
|
250
|
+
`string literal for field ${field.name}.`,
|
251
|
+
[keyArg?.value?.loc ?? connectionDirective.loc],
|
252
|
+
);
|
253
|
+
}
|
254
|
+
const postfix = field.alias || field.name;
|
255
|
+
if (!key.endsWith('_' + postfix)) {
|
256
|
+
const keyArg = connectionDirective.args.find(arg => arg.name === 'key');
|
257
|
+
throw createUserError(
|
258
|
+
`Expected the ${KEY} argument to @${connectionDirective.name} to be of ` +
|
259
|
+
`form <SomeName>_${postfix}, got '${key}'. ` +
|
260
|
+
'For a detailed explanation, check out ' +
|
261
|
+
'https://relay.dev/docs/en/pagination-container#connection',
|
262
|
+
[keyArg?.value?.loc ?? connectionDirective.loc],
|
263
|
+
);
|
264
|
+
}
|
265
|
+
if (
|
266
|
+
literalFilters != null &&
|
267
|
+
(!Array.isArray(literalFilters) ||
|
268
|
+
literalFilters.some(filter => typeof filter !== 'string'))
|
269
|
+
) {
|
270
|
+
const filtersArg = connectionDirective.args.find(
|
271
|
+
arg => arg.name === 'filters',
|
272
|
+
);
|
273
|
+
throw createUserError(
|
274
|
+
`Expected the 'filters' argument to @${connectionDirective.name} to be ` +
|
275
|
+
'a string literal.',
|
276
|
+
[filtersArg?.value?.loc ?? connectionDirective.loc],
|
277
|
+
);
|
278
|
+
}
|
279
|
+
|
280
|
+
let filters = literalFilters;
|
281
|
+
if (filters == null) {
|
282
|
+
const generatedFilters = field.args
|
283
|
+
.filter(
|
284
|
+
arg =>
|
285
|
+
!ConnectionInterface.isConnectionCall({
|
286
|
+
name: arg.name,
|
287
|
+
value: null,
|
288
|
+
}),
|
289
|
+
)
|
290
|
+
.map(arg => arg.name);
|
291
|
+
filters = generatedFilters.length !== 0 ? generatedFilters : null;
|
292
|
+
}
|
293
|
+
|
294
|
+
let stream = null;
|
295
|
+
if (connectionDirective.name === STREAM_CONNECTION) {
|
296
|
+
const initialCountArg = connectionDirective.args.find(
|
297
|
+
arg => arg.name === 'initial_count',
|
298
|
+
);
|
299
|
+
const useCustomizedBatchArg = connectionDirective.args.find(
|
300
|
+
arg => arg.name === 'use_customized_batch',
|
301
|
+
);
|
302
|
+
const ifArg = connectionDirective.args.find(arg => arg.name === 'if');
|
303
|
+
stream = {
|
304
|
+
if: ifArg,
|
305
|
+
initialCount: initialCountArg,
|
306
|
+
useCustomizedBatch: useCustomizedBatchArg,
|
307
|
+
label: key,
|
308
|
+
};
|
309
|
+
}
|
310
|
+
|
311
|
+
const dynamicKeyArg = connectionDirective.args.find(
|
312
|
+
arg => arg.name === 'dynamicKey_UNSTABLE',
|
313
|
+
);
|
314
|
+
let dynamicKey: Variable | null = null;
|
315
|
+
if (dynamicKeyArg != null) {
|
316
|
+
if (
|
317
|
+
RelayFeatureFlags.ENABLE_VARIABLE_CONNECTION_KEY &&
|
318
|
+
dynamicKeyArg.value.kind === 'Variable'
|
319
|
+
) {
|
320
|
+
dynamicKey = dynamicKeyArg.value;
|
321
|
+
} else {
|
322
|
+
throw createUserError(
|
323
|
+
`Unsupported 'dynamicKey_UNSTABLE' argument to @${connectionDirective.name}. This argument is only valid when the feature flag is enabled and ` +
|
324
|
+
'the variable must be a variable',
|
325
|
+
[connectionDirective.loc],
|
326
|
+
);
|
327
|
+
}
|
328
|
+
}
|
329
|
+
|
330
|
+
return {
|
331
|
+
handler,
|
332
|
+
key,
|
333
|
+
dynamicKey,
|
334
|
+
filters: (filters: $FlowFixMe),
|
335
|
+
stream,
|
336
|
+
};
|
337
|
+
}
|
338
|
+
|
339
|
+
function buildConnectionMetadata(
|
340
|
+
field: LinkedField,
|
341
|
+
path: Array<?string>,
|
342
|
+
stream: boolean,
|
343
|
+
): ConnectionMetadata {
|
344
|
+
const pathHasPlural = path.includes(null);
|
345
|
+
const firstArg = findArg(field, FIRST);
|
346
|
+
const lastArg = findArg(field, LAST);
|
347
|
+
let direction = null;
|
348
|
+
let countArg = null;
|
349
|
+
let cursorArg = null;
|
350
|
+
if (firstArg && !lastArg) {
|
351
|
+
direction = 'forward';
|
352
|
+
countArg = firstArg;
|
353
|
+
cursorArg = findArg(field, AFTER);
|
354
|
+
} else if (lastArg && !firstArg) {
|
355
|
+
direction = 'backward';
|
356
|
+
countArg = lastArg;
|
357
|
+
cursorArg = findArg(field, BEFORE);
|
358
|
+
} else if (lastArg && firstArg) {
|
359
|
+
direction = 'bidirectional';
|
360
|
+
// TODO(T26511885) Maybe add connection metadata to this case
|
361
|
+
}
|
362
|
+
const countVariable =
|
363
|
+
countArg && countArg.value.kind === 'Variable'
|
364
|
+
? countArg.value.variableName
|
365
|
+
: null;
|
366
|
+
const cursorVariable =
|
367
|
+
cursorArg && cursorArg.value.kind === 'Variable'
|
368
|
+
? cursorArg.value.variableName
|
369
|
+
: null;
|
370
|
+
if (stream) {
|
371
|
+
return {
|
372
|
+
count: countVariable,
|
373
|
+
cursor: cursorVariable,
|
374
|
+
direction,
|
375
|
+
path: pathHasPlural ? null : (path: $FlowFixMe),
|
376
|
+
stream: true,
|
377
|
+
};
|
378
|
+
}
|
379
|
+
return {
|
380
|
+
count: countVariable,
|
381
|
+
cursor: cursorVariable,
|
382
|
+
direction,
|
383
|
+
path: pathHasPlural ? null : (path: $FlowFixMe),
|
384
|
+
};
|
385
|
+
}
|
386
|
+
|
387
|
+
/**
|
388
|
+
* @internal
|
389
|
+
*
|
390
|
+
* Transforms the selections on a connection field, generating fields necessary
|
391
|
+
* for pagination (edges.cursor, pageInfo, etc) and adding/merging them with
|
392
|
+
* existing selections.
|
393
|
+
*/
|
394
|
+
function transformConnectionSelections(
|
395
|
+
context: CompilerContext,
|
396
|
+
field: LinkedField,
|
397
|
+
nullableType: CompositeTypeID,
|
398
|
+
direction: 'forward' | 'backward' | 'bidirectional',
|
399
|
+
connectionArguments: ConnectionArguments,
|
400
|
+
directiveLocation: Location,
|
401
|
+
documentName: string,
|
402
|
+
): $ReadOnlyArray<Selection> {
|
403
|
+
const schema = context.getSchema();
|
404
|
+
const derivedFieldLocation = {kind: 'Derived', source: field.loc};
|
405
|
+
const derivedDirectiveLocation = {
|
406
|
+
kind: 'Derived',
|
407
|
+
source: directiveLocation,
|
408
|
+
};
|
409
|
+
const {
|
410
|
+
CURSOR,
|
411
|
+
EDGES,
|
412
|
+
END_CURSOR,
|
413
|
+
HAS_NEXT_PAGE,
|
414
|
+
HAS_PREV_PAGE,
|
415
|
+
NODE,
|
416
|
+
PAGE_INFO,
|
417
|
+
START_CURSOR,
|
418
|
+
} = ConnectionInterface.get();
|
419
|
+
|
420
|
+
// Find existing edges/pageInfo selections
|
421
|
+
let edgesSelection: ?LinkedField;
|
422
|
+
let pageInfoSelection: ?LinkedField;
|
423
|
+
field.selections.forEach(selection => {
|
424
|
+
if (selection.kind === 'LinkedField') {
|
425
|
+
if (selection.name === EDGES) {
|
426
|
+
if (edgesSelection != null) {
|
427
|
+
throw createCompilerError(
|
428
|
+
`ConnectionTransform: Unexpected duplicate field '${EDGES}'.`,
|
429
|
+
[edgesSelection.loc, selection.loc],
|
430
|
+
);
|
431
|
+
}
|
432
|
+
edgesSelection = selection;
|
433
|
+
return;
|
434
|
+
} else if (selection.name === PAGE_INFO) {
|
435
|
+
if (pageInfoSelection != null) {
|
436
|
+
throw createCompilerError(
|
437
|
+
`ConnectionTransform: Unexpected duplicate field '${PAGE_INFO}'.`,
|
438
|
+
[pageInfoSelection.loc, selection.loc],
|
439
|
+
);
|
440
|
+
}
|
441
|
+
pageInfoSelection = selection;
|
442
|
+
return;
|
443
|
+
}
|
444
|
+
}
|
445
|
+
});
|
446
|
+
// If streaming is enabled, construct directives to apply to the edges/
|
447
|
+
// pageInfo fields
|
448
|
+
let streamDirective;
|
449
|
+
const stream = connectionArguments.stream;
|
450
|
+
if (stream != null) {
|
451
|
+
streamDirective = {
|
452
|
+
args: [
|
453
|
+
stream.if,
|
454
|
+
stream.initialCount,
|
455
|
+
stream.useCustomizedBatch,
|
456
|
+
{
|
457
|
+
kind: 'Argument',
|
458
|
+
loc: derivedDirectiveLocation,
|
459
|
+
name: 'label',
|
460
|
+
type: SchemaUtils.getNullableStringInput(schema),
|
461
|
+
value: {
|
462
|
+
kind: 'Literal',
|
463
|
+
loc: derivedDirectiveLocation,
|
464
|
+
value: stream.label,
|
465
|
+
},
|
466
|
+
},
|
467
|
+
].filter(Boolean),
|
468
|
+
kind: 'Directive',
|
469
|
+
loc: derivedDirectiveLocation,
|
470
|
+
name: 'stream',
|
471
|
+
};
|
472
|
+
}
|
473
|
+
// For backwards compatibility with earlier versions of this transform,
|
474
|
+
// edges/pageInfo have to be generated as non-aliased fields (since product
|
475
|
+
// code may be accessing the non-aliased response keys). But for streaming
|
476
|
+
// mode we need to generate @stream/@defer directives on these fields *and*
|
477
|
+
// we prefer to avoid generating extra selections (we want one payload per
|
478
|
+
// item, not two as could happen with separate @stream directives on the
|
479
|
+
// aliased and non-aliased edges fields). So we keep things simple by
|
480
|
+
// disallowing aliases on edges/pageInfo in streaming mode.
|
481
|
+
if (edgesSelection && edgesSelection.alias !== edgesSelection.name) {
|
482
|
+
if (stream) {
|
483
|
+
throw createUserError(
|
484
|
+
`@stream_connection does not support aliasing the '${EDGES}' field.`,
|
485
|
+
[edgesSelection.loc],
|
486
|
+
);
|
487
|
+
}
|
488
|
+
edgesSelection = null;
|
489
|
+
}
|
490
|
+
if (pageInfoSelection && pageInfoSelection.alias !== pageInfoSelection.name) {
|
491
|
+
if (stream) {
|
492
|
+
throw createUserError(
|
493
|
+
`@stream_connection does not support aliasing the '${PAGE_INFO}' field.`,
|
494
|
+
[pageInfoSelection.loc],
|
495
|
+
);
|
496
|
+
}
|
497
|
+
pageInfoSelection = null;
|
498
|
+
}
|
499
|
+
|
500
|
+
// Separately create transformed versions of edges/pageInfo so that we can
|
501
|
+
// later replace the originals at the same point within the selection array
|
502
|
+
let transformedEdgesSelection: ?LinkedField = edgesSelection;
|
503
|
+
let transformedPageInfoSelection: ?(
|
504
|
+
| Defer
|
505
|
+
| InlineFragment
|
506
|
+
| LinkedField
|
507
|
+
) = pageInfoSelection;
|
508
|
+
const edgesType = schema.getFieldConfig(
|
509
|
+
schema.expectField(nullableType, EDGES),
|
510
|
+
).type;
|
511
|
+
|
512
|
+
const pageInfoType = schema.getFieldConfig(
|
513
|
+
schema.expectField(nullableType, PAGE_INFO),
|
514
|
+
).type;
|
515
|
+
|
516
|
+
if (transformedEdgesSelection == null) {
|
517
|
+
transformedEdgesSelection = {
|
518
|
+
alias: EDGES,
|
519
|
+
args: [],
|
520
|
+
connection: false,
|
521
|
+
directives: [],
|
522
|
+
handles: null,
|
523
|
+
kind: 'LinkedField',
|
524
|
+
loc: derivedFieldLocation,
|
525
|
+
metadata: null,
|
526
|
+
name: EDGES,
|
527
|
+
selections: [],
|
528
|
+
type: schema.assertLinkedFieldType(edgesType),
|
529
|
+
};
|
530
|
+
}
|
531
|
+
if (transformedPageInfoSelection == null) {
|
532
|
+
transformedPageInfoSelection = {
|
533
|
+
alias: PAGE_INFO,
|
534
|
+
args: [],
|
535
|
+
connection: false,
|
536
|
+
directives: [],
|
537
|
+
handles: null,
|
538
|
+
kind: 'LinkedField',
|
539
|
+
loc: derivedFieldLocation,
|
540
|
+
metadata: null,
|
541
|
+
name: PAGE_INFO,
|
542
|
+
selections: [],
|
543
|
+
type: schema.assertLinkedFieldType(pageInfoType),
|
544
|
+
};
|
545
|
+
}
|
546
|
+
|
547
|
+
// Generate (additional) fields on pageInfo and add to the transformed
|
548
|
+
// pageInfo field
|
549
|
+
const pageInfoRawType = schema.getRawType(pageInfoType);
|
550
|
+
let pageInfoText;
|
551
|
+
if (direction === 'forward') {
|
552
|
+
pageInfoText = `fragment PageInfo on ${schema.getTypeString(
|
553
|
+
pageInfoRawType,
|
554
|
+
)} {
|
555
|
+
${END_CURSOR}
|
556
|
+
${HAS_NEXT_PAGE}
|
557
|
+
}`;
|
558
|
+
} else if (direction === 'backward') {
|
559
|
+
pageInfoText = `fragment PageInfo on ${schema.getTypeString(
|
560
|
+
pageInfoRawType,
|
561
|
+
)} {
|
562
|
+
${HAS_PREV_PAGE}
|
563
|
+
${START_CURSOR}
|
564
|
+
}`;
|
565
|
+
} else {
|
566
|
+
pageInfoText = `fragment PageInfo on ${schema.getTypeString(
|
567
|
+
pageInfoRawType,
|
568
|
+
)} {
|
569
|
+
${END_CURSOR}
|
570
|
+
${HAS_NEXT_PAGE}
|
571
|
+
${HAS_PREV_PAGE}
|
572
|
+
${START_CURSOR}
|
573
|
+
}`;
|
574
|
+
}
|
575
|
+
const pageInfoAst = parse(pageInfoText);
|
576
|
+
const pageInfoFragment = RelayParser.transform(schema, [
|
577
|
+
pageInfoAst.definitions[0],
|
578
|
+
])[0];
|
579
|
+
if (transformedPageInfoSelection.kind !== 'LinkedField') {
|
580
|
+
throw createCompilerError(
|
581
|
+
'ConnectionTransform: Expected generated pageInfo selection to be ' +
|
582
|
+
'a LinkedField',
|
583
|
+
[field.loc],
|
584
|
+
);
|
585
|
+
}
|
586
|
+
transformedPageInfoSelection = {
|
587
|
+
...transformedPageInfoSelection,
|
588
|
+
selections: [
|
589
|
+
...transformedPageInfoSelection.selections,
|
590
|
+
{
|
591
|
+
directives: [],
|
592
|
+
kind: 'InlineFragment',
|
593
|
+
loc: derivedFieldLocation,
|
594
|
+
metadata: null,
|
595
|
+
selections: pageInfoFragment.selections,
|
596
|
+
typeCondition: pageInfoFragment.type,
|
597
|
+
},
|
598
|
+
],
|
599
|
+
};
|
600
|
+
// When streaming the pageInfo field has to be deferred
|
601
|
+
if (stream != null) {
|
602
|
+
transformedPageInfoSelection = {
|
603
|
+
if: stream.if?.value ?? null,
|
604
|
+
label: `${documentName}$defer$${stream.label}$${PAGE_INFO}`,
|
605
|
+
kind: 'Defer',
|
606
|
+
loc: derivedFieldLocation,
|
607
|
+
selections: [transformedPageInfoSelection],
|
608
|
+
};
|
609
|
+
}
|
610
|
+
|
611
|
+
// Generate additional fields on edges and append to the transformed edges
|
612
|
+
// selection
|
613
|
+
const edgeText = `
|
614
|
+
fragment Edges on ${schema.getTypeString(schema.getRawType(edgesType))} {
|
615
|
+
${CURSOR}
|
616
|
+
${NODE} {
|
617
|
+
__typename # rely on GenerateRequisiteFieldTransform to add "id"
|
618
|
+
}
|
619
|
+
}
|
620
|
+
`;
|
621
|
+
const edgeAst = parse(edgeText);
|
622
|
+
const edgeFragment = RelayParser.transform(schema, [
|
623
|
+
edgeAst.definitions[0],
|
624
|
+
])[0];
|
625
|
+
// When streaming the edges field needs @stream
|
626
|
+
transformedEdgesSelection = {
|
627
|
+
...transformedEdgesSelection,
|
628
|
+
directives:
|
629
|
+
streamDirective != null
|
630
|
+
? [...transformedEdgesSelection.directives, streamDirective]
|
631
|
+
: transformedEdgesSelection.directives,
|
632
|
+
selections: [
|
633
|
+
...transformedEdgesSelection.selections,
|
634
|
+
{
|
635
|
+
directives: [],
|
636
|
+
kind: 'InlineFragment',
|
637
|
+
loc: derivedFieldLocation,
|
638
|
+
metadata: null,
|
639
|
+
selections: edgeFragment.selections,
|
640
|
+
typeCondition: edgeFragment.type,
|
641
|
+
},
|
642
|
+
],
|
643
|
+
};
|
644
|
+
// Copy the original selections, replacing edges/pageInfo (if present)
|
645
|
+
// with the generated locations. This is to maintain the original field
|
646
|
+
// ordering.
|
647
|
+
const selections = field.selections.map(selection => {
|
648
|
+
if (
|
649
|
+
transformedEdgesSelection != null &&
|
650
|
+
edgesSelection != null &&
|
651
|
+
selection === edgesSelection
|
652
|
+
) {
|
653
|
+
return transformedEdgesSelection;
|
654
|
+
} else if (
|
655
|
+
transformedPageInfoSelection != null &&
|
656
|
+
pageInfoSelection != null &&
|
657
|
+
selection === pageInfoSelection
|
658
|
+
) {
|
659
|
+
return transformedPageInfoSelection;
|
660
|
+
} else {
|
661
|
+
return selection;
|
662
|
+
}
|
663
|
+
});
|
664
|
+
// If edges/pageInfo were missing, append the generated versions instead.
|
665
|
+
if (edgesSelection == null && transformedEdgesSelection != null) {
|
666
|
+
selections.push(transformedEdgesSelection);
|
667
|
+
}
|
668
|
+
if (pageInfoSelection == null && transformedPageInfoSelection != null) {
|
669
|
+
selections.push(transformedPageInfoSelection);
|
670
|
+
}
|
671
|
+
return selections;
|
672
|
+
}
|
673
|
+
|
674
|
+
function findArg(field: LinkedField, argName: string): ?Argument {
|
675
|
+
return field.args && field.args.find(arg => arg.name === argName);
|
676
|
+
}
|
677
|
+
|
678
|
+
/**
|
679
|
+
* @internal
|
680
|
+
*
|
681
|
+
* Validates that the selection is a valid connection:
|
682
|
+
* - Specifies a first or last argument to prevent accidental, unconstrained
|
683
|
+
* data access.
|
684
|
+
* - Has an `edges` selection, otherwise there is nothing to paginate.
|
685
|
+
*
|
686
|
+
* TODO: This implementation requires the edges field to be a direct selection
|
687
|
+
* and not contained within an inline fragment or fragment spread. It's
|
688
|
+
* technically possible to remove this restriction if this pattern becomes
|
689
|
+
* common/necessary.
|
690
|
+
*/
|
691
|
+
function validateConnectionSelection(field: LinkedField): void {
|
692
|
+
const {EDGES} = ConnectionInterface.get();
|
693
|
+
|
694
|
+
if (!findArg(field, FIRST) && !findArg(field, LAST)) {
|
695
|
+
throw createUserError(
|
696
|
+
`Expected field '${field.name}' to have a '${FIRST}' or '${LAST}' ` +
|
697
|
+
'argument.',
|
698
|
+
[field.loc],
|
699
|
+
);
|
700
|
+
}
|
701
|
+
if (
|
702
|
+
!field.selections.some(
|
703
|
+
selection => selection.kind === 'LinkedField' && selection.name === EDGES,
|
704
|
+
)
|
705
|
+
) {
|
706
|
+
throw createUserError(
|
707
|
+
`Expected field '${field.name}' to have an '${EDGES}' selection.`,
|
708
|
+
[field.loc],
|
709
|
+
);
|
710
|
+
}
|
711
|
+
}
|
712
|
+
|
713
|
+
/**
|
714
|
+
* @internal
|
715
|
+
*
|
716
|
+
* Validates that the type satisfies the Connection specification:
|
717
|
+
* - The type has an edges field, and edges have scalar `cursor` and object
|
718
|
+
* `node` fields.
|
719
|
+
* - The type has a page info field which is an object with the correct
|
720
|
+
* subfields.
|
721
|
+
*/
|
722
|
+
function validateConnectionType(
|
723
|
+
schema: Schema,
|
724
|
+
field: LinkedField,
|
725
|
+
nullableType: CompositeTypeID,
|
726
|
+
connectionDirective: Directive,
|
727
|
+
): void {
|
728
|
+
const directiveName = connectionDirective.name;
|
729
|
+
const {
|
730
|
+
CURSOR,
|
731
|
+
EDGES,
|
732
|
+
END_CURSOR,
|
733
|
+
HAS_NEXT_PAGE,
|
734
|
+
HAS_PREV_PAGE,
|
735
|
+
NODE,
|
736
|
+
PAGE_INFO,
|
737
|
+
START_CURSOR,
|
738
|
+
} = ConnectionInterface.get();
|
739
|
+
|
740
|
+
const typeName = schema.getTypeString(nullableType);
|
741
|
+
if (!schema.hasField(nullableType, EDGES)) {
|
742
|
+
throw createUserError(
|
743
|
+
`@${directiveName} used on invalid field '${field.name}'. Expected the ` +
|
744
|
+
`field type '${typeName}' to have an '${EDGES}' field`,
|
745
|
+
[field.loc],
|
746
|
+
);
|
747
|
+
}
|
748
|
+
|
749
|
+
const edges = schema.getFieldConfig(schema.expectField(nullableType, EDGES));
|
750
|
+
|
751
|
+
const edgesType = schema.getNullableType(edges.type);
|
752
|
+
if (!schema.isList(edgesType)) {
|
753
|
+
throw createUserError(
|
754
|
+
`@${directiveName} used on invalid field '${field.name}'. Expected the ` +
|
755
|
+
`field type '${typeName}' to have an '${EDGES}' field that returns ` +
|
756
|
+
'a list of objects.',
|
757
|
+
[field.loc],
|
758
|
+
);
|
759
|
+
}
|
760
|
+
let edgeType = schema.getNullableType(schema.getListItemType(edgesType));
|
761
|
+
if (!schema.isObject(edgeType) && !schema.isInterface(edgeType)) {
|
762
|
+
throw createUserError(
|
763
|
+
`@${directiveName} used on invalid field '${field.name}'. Expected the ` +
|
764
|
+
`field type '${typeName}' to have an '${EDGES}' field that returns ` +
|
765
|
+
'a list of objects.',
|
766
|
+
[field.loc],
|
767
|
+
);
|
768
|
+
}
|
769
|
+
edgeType = schema.assertCompositeType(edgeType);
|
770
|
+
|
771
|
+
if (!schema.hasField(edgeType, NODE)) {
|
772
|
+
throw createUserError(
|
773
|
+
`@${directiveName} used on invalid field '${field.name}'. Expected the ` +
|
774
|
+
`field type '${typeName}' to have an '${EDGES} { ${NODE} }' field ` +
|
775
|
+
'that returns an object, interface, or union.',
|
776
|
+
[field.loc],
|
777
|
+
);
|
778
|
+
}
|
779
|
+
const node = schema.getFieldConfig(schema.expectField(edgeType, NODE));
|
780
|
+
|
781
|
+
const nodeType = schema.getNullableType(node.type);
|
782
|
+
if (!(schema.isAbstractType(nodeType) || schema.isObject(nodeType))) {
|
783
|
+
throw createUserError(
|
784
|
+
`@${directiveName} used on invalid field '${field.name}'. Expected the ` +
|
785
|
+
`field type '${typeName}' to have an '${EDGES} { ${NODE} }' field ` +
|
786
|
+
'that returns an object, interface, or union.',
|
787
|
+
[field.loc],
|
788
|
+
);
|
789
|
+
}
|
790
|
+
|
791
|
+
if (!schema.hasField(edgeType, CURSOR)) {
|
792
|
+
throw createUserError(
|
793
|
+
`@${directiveName} used on invalid field '${field.name}'. Expected the ` +
|
794
|
+
`field type '${typeName}' to have an '${EDGES} { ${CURSOR} }' field ` +
|
795
|
+
'that returns a scalar value.',
|
796
|
+
[field.loc],
|
797
|
+
);
|
798
|
+
}
|
799
|
+
const cursor = schema.getFieldConfig(schema.expectField(edgeType, CURSOR));
|
800
|
+
|
801
|
+
if (!schema.isScalar(schema.getNullableType(cursor.type))) {
|
802
|
+
throw createUserError(
|
803
|
+
`@${directiveName} used on invalid field '${field.name}'. Expected the ` +
|
804
|
+
`field type '${typeName}' to have an '${EDGES} { ${CURSOR} }' field ` +
|
805
|
+
'that returns a scalar value.',
|
806
|
+
[field.loc],
|
807
|
+
);
|
808
|
+
}
|
809
|
+
|
810
|
+
if (!schema.hasField(nullableType, PAGE_INFO)) {
|
811
|
+
throw createUserError(
|
812
|
+
`@${directiveName} used on invalid field '${field.name}'. Expected the ` +
|
813
|
+
`field type '${typeName}' to have a '${PAGE_INFO}' field that returns ` +
|
814
|
+
'an object.',
|
815
|
+
[field.loc],
|
816
|
+
);
|
817
|
+
}
|
818
|
+
|
819
|
+
const pageInfo = schema.getFieldConfig(
|
820
|
+
schema.expectField(nullableType, PAGE_INFO),
|
821
|
+
);
|
822
|
+
|
823
|
+
const pageInfoType = schema.getNullableType(pageInfo.type);
|
824
|
+
if (!schema.isObject(pageInfoType)) {
|
825
|
+
throw createUserError(
|
826
|
+
`@${directiveName} used on invalid field '${field.name}'. Expected the ` +
|
827
|
+
`field type '${typeName}' to have a '${PAGE_INFO}' field that ` +
|
828
|
+
'returns an object.',
|
829
|
+
[field.loc],
|
830
|
+
);
|
831
|
+
}
|
832
|
+
|
833
|
+
[END_CURSOR, HAS_NEXT_PAGE, HAS_PREV_PAGE, START_CURSOR].forEach(
|
834
|
+
fieldName => {
|
835
|
+
const pageInfoField = schema.getFieldConfig(
|
836
|
+
schema.expectField(schema.assertObjectType(pageInfoType), fieldName),
|
837
|
+
);
|
838
|
+
if (!schema.isScalar(schema.getNullableType(pageInfoField.type))) {
|
839
|
+
throw createUserError(
|
840
|
+
`@${directiveName} used on invalid field '${field.name}'. Expected ` +
|
841
|
+
`the field type '${typeName}' to have a '${PAGE_INFO} { ${fieldName} }' ` +
|
842
|
+
'field returns a scalar.',
|
843
|
+
[field.loc],
|
844
|
+
);
|
845
|
+
}
|
846
|
+
},
|
847
|
+
);
|
848
|
+
}
|
849
|
+
|
850
|
+
module.exports = {
|
851
|
+
buildConnectionMetadata,
|
852
|
+
CONNECTION,
|
853
|
+
SCHEMA_EXTENSION,
|
854
|
+
transform: connectionTransform,
|
855
|
+
};
|