relay-compiler 10.0.1 → 10.1.3
Sign up to get free protection for your applications and to get access to all the features.
- package/bin/relay-compiler +1538 -975
- package/codegen/NormalizationCodeGenerator.js.flow +12 -4
- package/codegen/ReaderCodeGenerator.js.flow +38 -3
- package/codegen/RelayFileWriter.js.flow +2 -0
- package/codegen/writeRelayGeneratedFile.js.flow +1 -1
- package/core/ASTCache.js.flow +1 -0
- package/core/CompilerContext.js.flow +1 -0
- package/core/CompilerError.js.flow +6 -1
- package/core/IR.js.flow +0 -1
- package/core/IRPrinter.js.flow +3 -8
- package/core/RelayIRTransforms.js.flow +7 -0
- package/core/Schema.js.flow +55 -1
- package/index.js +1 -1
- package/language/javascript/FindGraphQLTags.js.flow +2 -97
- package/language/javascript/RelayFlowBabelFactories.js.flow +11 -15
- package/language/javascript/RelayFlowGenerator.js.flow +76 -19
- package/language/javascript/RelayFlowTypeTransformers.js.flow +4 -4
- package/lib/bin/RelayCompilerMain.js +8 -14
- package/lib/codegen/CodegenRunner.js +5 -9
- package/lib/codegen/NormalizationCodeGenerator.js +21 -13
- package/lib/codegen/ReaderCodeGenerator.js +44 -13
- package/lib/codegen/RelayFileWriter.js +3 -7
- package/lib/codegen/compileRelayArtifacts.js +4 -8
- package/lib/codegen/sortObjectByKey.js +3 -5
- package/lib/codegen/writeRelayGeneratedFile.js +3 -7
- package/lib/core/ASTCache.js +1 -0
- package/lib/core/CompilerContext.js +1 -0
- package/lib/core/CompilerError.js +8 -8
- package/lib/core/IRPrinter.js +3 -4
- package/lib/core/IRTransformer.js +3 -7
- package/lib/core/RelayGraphQLEnumsGenerator.js +3 -7
- package/lib/core/RelayIRTransforms.js +10 -4
- package/lib/core/RelayParser.js +7 -15
- package/lib/core/Schema.js +50 -13
- package/lib/core/getFieldDefinition.js +3 -5
- package/lib/core/inferRootArgumentDefinitions.js +6 -14
- package/lib/language/javascript/FindGraphQLTags.js +3 -69
- package/lib/language/javascript/RelayFlowBabelFactories.js +5 -5
- package/lib/language/javascript/RelayFlowGenerator.js +85 -34
- package/lib/runner/Artifacts.js +13 -17
- package/lib/runner/BufferedFilesystem.js +6 -10
- package/lib/runner/GraphQLASTNodeGroup.js +10 -14
- package/lib/runner/GraphQLNodeMap.js +3 -7
- package/lib/runner/Sources.js +27 -17
- package/lib/runner/StrictMap.js +5 -7
- package/lib/runner/getChangedNodeNames.js +6 -8
- package/lib/transforms/ApplyFragmentArgumentTransform.js +16 -25
- package/lib/transforms/ClientExtensionsTransform.js +8 -9
- package/lib/transforms/ConnectionTransform.js +9 -14
- package/lib/transforms/DeclarativeConnectionMutationTransform.js +115 -64
- package/lib/transforms/DeferStreamTransform.js +3 -7
- package/lib/transforms/DisallowTypenameOnRoot.js +3 -5
- package/lib/transforms/FieldHandleTransform.js +3 -7
- package/lib/transforms/FilterCompilerDirectivesTransform.js +29 -0
- package/lib/transforms/FlattenTransform.js +14 -17
- package/lib/transforms/GenerateIDFieldTransform.js +3 -7
- package/lib/transforms/GenerateTypeNameTransform.js +4 -8
- package/lib/transforms/InlineDataFragmentTransform.js +3 -7
- package/lib/transforms/MaskTransform.js +4 -12
- package/lib/transforms/MatchTransform.js +8 -8
- package/lib/transforms/ReactFlightComponentTransform.js +158 -0
- package/lib/transforms/RefetchableFragmentTransform.js +5 -9
- package/lib/transforms/RelayDirectiveTransform.js +3 -7
- package/lib/transforms/RequiredFieldTransform.js +369 -0
- package/lib/transforms/SkipHandleFieldTransform.js +2 -6
- package/lib/transforms/SkipRedundantNodesTransform.js +4 -6
- package/lib/transforms/SkipUnreachableNodeTransform.js +2 -6
- package/lib/transforms/SkipUnusedVariablesTransform.js +4 -12
- package/lib/transforms/TestOperationTransform.js +3 -7
- package/lib/transforms/ValidateGlobalVariablesTransform.js +4 -6
- package/lib/transforms/ValidateRequiredArgumentsTransform.js +3 -5
- package/lib/transforms/ValidateServerOnlyDirectivesTransform.js +4 -6
- package/lib/transforms/ValidateUnusedVariablesTransform.js +4 -6
- package/lib/transforms/query-generators/FetchableQueryGenerator.js +2 -6
- package/lib/transforms/query-generators/NodeQueryGenerator.js +2 -6
- package/lib/transforms/query-generators/index.js +3 -5
- package/lib/transforms/query-generators/utils.js +3 -5
- package/package.json +3 -3
- package/relay-compiler.js +4 -4
- package/relay-compiler.min.js +4 -4
- package/runner/Sources.js.flow +14 -0
- package/transforms/ClientExtensionsTransform.js.flow +3 -0
- package/transforms/ConnectionTransform.js.flow +0 -1
- package/transforms/DeclarativeConnectionMutationTransform.js.flow +140 -48
- package/transforms/FieldHandleTransform.js.flow +0 -1
- package/transforms/FilterCompilerDirectivesTransform.js.flow +33 -0
- package/transforms/FlattenTransform.js.flow +3 -2
- package/transforms/MatchTransform.js.flow +6 -0
- package/transforms/ReactFlightComponentTransform.js.flow +195 -0
- package/transforms/RequiredFieldTransform.js.flow +415 -0
- package/transforms/SkipRedundantNodesTransform.js.flow +3 -0
package/runner/Sources.js.flow
CHANGED
@@ -137,6 +137,20 @@ class Sources<T: ASTNode> {
|
|
137
137
|
const newTexts = new Set();
|
138
138
|
for (const {ast, text} of newDefs) {
|
139
139
|
const hashedText = md5(text);
|
140
|
+
if (newTexts.has(hashedText)) {
|
141
|
+
let name = 'unknown';
|
142
|
+
switch (ast.kind) {
|
143
|
+
case 'FragmentDefinition':
|
144
|
+
name = ast.name.value;
|
145
|
+
break;
|
146
|
+
case 'OperationDefinition':
|
147
|
+
name = ast.name?.value ?? 'unnamed operation';
|
148
|
+
break;
|
149
|
+
}
|
150
|
+
throw new Error(
|
151
|
+
`Duplicate definition of \`${name}\` in \`${file.name}\``,
|
152
|
+
);
|
153
|
+
}
|
140
154
|
newTexts.add(hashedText);
|
141
155
|
if (hasEntry && oldEntry[hashedText] != null) {
|
142
156
|
// Entity text did not change, so we
|
@@ -83,9 +83,11 @@ function traverseSelections<T: Node>(
|
|
83
83
|
compilerContext: CompilerContext,
|
84
84
|
parentType: TypeID,
|
85
85
|
): T {
|
86
|
+
// $FlowFixMe[escaped-generic]
|
86
87
|
let nodeCache = cachesByNode.get(node);
|
87
88
|
if (nodeCache == null) {
|
88
89
|
nodeCache = new Map();
|
90
|
+
// $FlowFixMe[escaped-generic]
|
89
91
|
cachesByNode.set(node, nodeCache);
|
90
92
|
}
|
91
93
|
let result = nodeCache.get(parentType);
|
@@ -182,6 +184,7 @@ function traverseSelections<T: Node>(
|
|
182
184
|
],
|
183
185
|
};
|
184
186
|
}
|
187
|
+
// $FlowFixMe[escaped-generic]
|
185
188
|
nodeCache.set(parentType, result);
|
186
189
|
/* $FlowFixMe[incompatible-return] - TODO: type IRTransformer to allow
|
187
190
|
* changing result type */
|
@@ -18,17 +18,36 @@ const {createUserError} = require('../core/CompilerError');
|
|
18
18
|
const {ConnectionInterface} = require('relay-runtime');
|
19
19
|
|
20
20
|
const DELETE_RECORD = 'deleteRecord';
|
21
|
+
const DELETE_EDGE = 'deleteEdge';
|
21
22
|
const APPEND_EDGE = 'appendEdge';
|
22
23
|
const PREPEND_EDGE = 'prependEdge';
|
23
|
-
const
|
24
|
+
const APPEND_NODE = 'appendNode';
|
25
|
+
const PREPEND_NODE = 'prependNode';
|
26
|
+
const EDGE_LINKED_FIELD_DIRECTIVES = [APPEND_EDGE, PREPEND_EDGE];
|
27
|
+
const NODE_LINKED_FIELD_DIRECTIVES = [APPEND_NODE, PREPEND_NODE];
|
28
|
+
const LINKED_FIELD_DIRECTIVES = [
|
29
|
+
...EDGE_LINKED_FIELD_DIRECTIVES,
|
30
|
+
...NODE_LINKED_FIELD_DIRECTIVES,
|
31
|
+
];
|
24
32
|
|
25
33
|
const SCHEMA_EXTENSION = `
|
26
34
|
directive @${DELETE_RECORD} on FIELD
|
35
|
+
directive @${DELETE_EDGE}(
|
36
|
+
connections: [ID!]!
|
37
|
+
) on FIELD
|
27
38
|
directive @${APPEND_EDGE}(
|
28
|
-
connections: [
|
39
|
+
connections: [ID!]!
|
29
40
|
) on FIELD
|
30
41
|
directive @${PREPEND_EDGE}(
|
31
|
-
connections: [
|
42
|
+
connections: [ID!]!
|
43
|
+
) on FIELD
|
44
|
+
directive @${APPEND_NODE}(
|
45
|
+
connections: [ID!]!
|
46
|
+
edgeTypeName: String!
|
47
|
+
) on FIELD
|
48
|
+
directive @${PREPEND_NODE}(
|
49
|
+
connections: [ID!]!
|
50
|
+
edgeTypeName: String!
|
32
51
|
) on FIELD
|
33
52
|
`;
|
34
53
|
|
@@ -58,31 +77,49 @@ function visitScalarField(field: ScalarField): ScalarField {
|
|
58
77
|
[linkedFieldDirective.loc],
|
59
78
|
);
|
60
79
|
}
|
61
|
-
const
|
80
|
+
const deleteNodeDirective = field.directives.find(
|
62
81
|
directive => directive.name === DELETE_RECORD,
|
63
82
|
);
|
64
|
-
|
83
|
+
const deleteEdgeDirective = field.directives.find(
|
84
|
+
directive => directive.name === DELETE_EDGE,
|
85
|
+
);
|
86
|
+
if (deleteNodeDirective != null && deleteEdgeDirective != null) {
|
87
|
+
throw createUserError(
|
88
|
+
`Both @deleteNode and @deleteEdge are used on field '${field.name}'. Only one directive is supported for now.`,
|
89
|
+
[deleteNodeDirective.loc, deleteEdgeDirective.loc],
|
90
|
+
);
|
91
|
+
}
|
92
|
+
const targetDirective = deleteNodeDirective ?? deleteEdgeDirective;
|
93
|
+
if (targetDirective == null) {
|
65
94
|
return field;
|
66
95
|
}
|
96
|
+
|
67
97
|
const schema = this.getContext().getSchema();
|
68
|
-
|
98
|
+
|
99
|
+
if (!schema.isId(schema.getRawType(field.type))) {
|
69
100
|
throw createUserError(
|
70
|
-
`Invalid use of @${
|
101
|
+
`Invalid use of @${targetDirective.name} on field '${
|
71
102
|
field.name
|
72
|
-
}'. Expected field
|
73
|
-
|
103
|
+
}'. Expected field to return an ID or list of ID values, got ${schema.getTypeString(
|
104
|
+
field.type,
|
105
|
+
)}.`,
|
106
|
+
[targetDirective.loc],
|
74
107
|
);
|
75
108
|
}
|
109
|
+
const connectionsArg = targetDirective.args.find(
|
110
|
+
arg => arg.name === 'connections',
|
111
|
+
);
|
76
112
|
const handle: Handle = {
|
77
|
-
name:
|
113
|
+
name: targetDirective.name,
|
78
114
|
key: '',
|
79
115
|
dynamicKey: null,
|
80
116
|
filters: null,
|
117
|
+
handleArgs: connectionsArg ? [connectionsArg] : undefined,
|
81
118
|
};
|
82
119
|
return {
|
83
120
|
...field,
|
84
121
|
directives: field.directives.filter(
|
85
|
-
directive => directive !==
|
122
|
+
directive => directive !== targetDirective,
|
86
123
|
),
|
87
124
|
handles: field.handles ? [...field.handles, handle] : [handle],
|
88
125
|
};
|
@@ -100,55 +137,110 @@ function visitLinkedField(field: LinkedField): LinkedField {
|
|
100
137
|
);
|
101
138
|
}
|
102
139
|
const edgeDirective = transformedField.directives.find(
|
103
|
-
directive =>
|
140
|
+
directive => EDGE_LINKED_FIELD_DIRECTIVES.indexOf(directive.name) > -1,
|
141
|
+
);
|
142
|
+
const nodeDirective = transformedField.directives.find(
|
143
|
+
directive => NODE_LINKED_FIELD_DIRECTIVES.indexOf(directive.name) > -1,
|
104
144
|
);
|
105
|
-
|
145
|
+
|
146
|
+
if (edgeDirective == null && nodeDirective == null) {
|
106
147
|
return transformedField;
|
107
148
|
}
|
108
|
-
|
149
|
+
if (edgeDirective != null && nodeDirective != null) {
|
150
|
+
throw createUserError(
|
151
|
+
`Invalid use of @${edgeDirective.name} and @${nodeDirective.name} on field '${transformedField.name}' - these directives cannot be used together.`,
|
152
|
+
[edgeDirective.loc],
|
153
|
+
);
|
154
|
+
}
|
155
|
+
const targetDirective = edgeDirective ?? nodeDirective;
|
156
|
+
const connectionsArg = targetDirective.args.find(
|
109
157
|
arg => arg.name === 'connections',
|
110
158
|
);
|
111
159
|
if (connectionsArg == null) {
|
112
160
|
throw createUserError(
|
113
|
-
`Expected the 'connections' argument to be defined on @${
|
114
|
-
[
|
161
|
+
`Expected the 'connections' argument to be defined on @${targetDirective.name}.`,
|
162
|
+
[targetDirective.loc],
|
115
163
|
);
|
116
164
|
}
|
117
165
|
const schema = this.getContext().getSchema();
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
const
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
166
|
+
if (edgeDirective) {
|
167
|
+
const fieldType = schema.isList(transformedField.type)
|
168
|
+
? transformedField.type.ofType
|
169
|
+
: transformedField.type;
|
170
|
+
const fields = schema.getFields(fieldType);
|
171
|
+
let cursorFieldID;
|
172
|
+
let nodeFieldID;
|
173
|
+
for (const fieldID of fields) {
|
174
|
+
const fieldName = schema.getFieldName(fieldID);
|
175
|
+
if (fieldName === ConnectionInterface.get().CURSOR) {
|
176
|
+
cursorFieldID = fieldID;
|
177
|
+
} else if (fieldName === ConnectionInterface.get().NODE) {
|
178
|
+
nodeFieldID = fieldID;
|
179
|
+
}
|
127
180
|
}
|
181
|
+
|
182
|
+
// Edge
|
183
|
+
if (cursorFieldID != null && nodeFieldID != null) {
|
184
|
+
const handle: Handle = {
|
185
|
+
name: edgeDirective.name,
|
186
|
+
key: '',
|
187
|
+
dynamicKey: null,
|
188
|
+
filters: null,
|
189
|
+
handleArgs: [connectionsArg],
|
190
|
+
};
|
191
|
+
return {
|
192
|
+
...transformedField,
|
193
|
+
directives: transformedField.directives.filter(
|
194
|
+
directive => directive !== edgeDirective,
|
195
|
+
),
|
196
|
+
handles: transformedField.handles
|
197
|
+
? [...transformedField.handles, handle]
|
198
|
+
: [handle],
|
199
|
+
};
|
200
|
+
}
|
201
|
+
throw createUserError(
|
202
|
+
`Unsupported use of @${edgeDirective.name} on field '${transformedField.name}', expected an edge field (a field with 'cursor' and 'node' selection).`,
|
203
|
+
[targetDirective.loc],
|
204
|
+
);
|
205
|
+
} else {
|
206
|
+
// Node
|
207
|
+
const edgeTypeNameArg = nodeDirective.args.find(
|
208
|
+
arg => arg.name === 'edgeTypeName',
|
209
|
+
);
|
210
|
+
if (!edgeTypeNameArg) {
|
211
|
+
throw createUserError(
|
212
|
+
`Unsupported use of @${nodeDirective.name} on field '${transformedField.name}', 'edgeTypeName' argument must be provided.`,
|
213
|
+
[targetDirective.loc],
|
214
|
+
);
|
215
|
+
}
|
216
|
+
const rawType = schema.getRawType(transformedField.type);
|
217
|
+
if (schema.canHaveSelections(rawType)) {
|
218
|
+
const handle: Handle = {
|
219
|
+
name: nodeDirective.name,
|
220
|
+
key: '',
|
221
|
+
dynamicKey: null,
|
222
|
+
filters: null,
|
223
|
+
handleArgs: [connectionsArg, edgeTypeNameArg],
|
224
|
+
};
|
225
|
+
return {
|
226
|
+
...transformedField,
|
227
|
+
directives: transformedField.directives.filter(
|
228
|
+
directive => directive !== nodeDirective,
|
229
|
+
),
|
230
|
+
handles: transformedField.handles
|
231
|
+
? [...transformedField.handles, handle]
|
232
|
+
: [handle],
|
233
|
+
};
|
234
|
+
}
|
235
|
+
throw createUserError(
|
236
|
+
`Unsupported use of @${nodeDirective.name} on field '${
|
237
|
+
transformedField.name
|
238
|
+
}'. Expected an object, union or interface, but got '${schema.getTypeString(
|
239
|
+
transformedField.type,
|
240
|
+
)}'.`,
|
241
|
+
[nodeDirective.loc],
|
242
|
+
);
|
128
243
|
}
|
129
|
-
// Edge
|
130
|
-
if (cursorFieldID != null && nodeFieldID != null) {
|
131
|
-
const handle: Handle = {
|
132
|
-
name: edgeDirective.name,
|
133
|
-
key: '',
|
134
|
-
dynamicKey: null,
|
135
|
-
filters: null,
|
136
|
-
handleArgs: [connectionsArg],
|
137
|
-
};
|
138
|
-
return {
|
139
|
-
...transformedField,
|
140
|
-
directives: transformedField.directives.filter(
|
141
|
-
directive => directive !== edgeDirective,
|
142
|
-
),
|
143
|
-
handles: transformedField.handles
|
144
|
-
? [...transformedField.handles, handle]
|
145
|
-
: [handle],
|
146
|
-
};
|
147
|
-
}
|
148
|
-
throw createUserError(
|
149
|
-
`Unsupported use of @${edgeDirective.name} on field '${transformedField.name}', expected an edge field (a field with 'cursor' and 'node' selection).`,
|
150
|
-
[edgeDirective.loc],
|
151
|
-
);
|
152
244
|
}
|
153
245
|
|
154
246
|
module.exports = {
|
@@ -55,7 +55,6 @@ function visitField<F: LinkedField | ScalarField>(field: F): F {
|
|
55
55
|
const args = filters
|
56
56
|
? nextField.args.filter(arg => filters.indexOf(arg.name) !== -1)
|
57
57
|
: [];
|
58
|
-
// T45504512: new connection model
|
59
58
|
if (handle.dynamicKey != null) {
|
60
59
|
args.push({
|
61
60
|
kind: 'Argument',
|
@@ -0,0 +1,33 @@
|
|
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
|
+
'use strict';
|
12
|
+
|
13
|
+
const IRTransformer = require('../core/IRTransformer');
|
14
|
+
|
15
|
+
import type CompilerContext from '../core/CompilerContext';
|
16
|
+
import type {Directive} from '../core/IR';
|
17
|
+
|
18
|
+
const COMPILE_TIME_DIRECTIVES = new Set(['required']);
|
19
|
+
|
20
|
+
/**
|
21
|
+
* A transform that removes any directives that are only interpreted by the Relay compiler.
|
22
|
+
*/
|
23
|
+
function filterDirectivesTransform(context: CompilerContext): CompilerContext {
|
24
|
+
return IRTransformer.transform(context, {
|
25
|
+
Directive: (directive: Directive): ?Directive => {
|
26
|
+
return COMPILE_TIME_DIRECTIVES.has(directive.name) ? null : directive;
|
27
|
+
},
|
28
|
+
});
|
29
|
+
}
|
30
|
+
|
31
|
+
module.exports = {
|
32
|
+
transform: filterDirectivesTransform,
|
33
|
+
};
|
@@ -61,9 +61,10 @@ function flattenTransformImpl(
|
|
61
61
|
Condition: visitorFn,
|
62
62
|
Defer: visitorFn,
|
63
63
|
Fragment: visitorFn,
|
64
|
-
InlineFragment: visitorFn,
|
65
64
|
InlineDataFragmentSpread: visitorFn,
|
65
|
+
InlineFragment: visitorFn,
|
66
66
|
LinkedField: visitorFn,
|
67
|
+
ModuleImport: visitorFn,
|
67
68
|
Root: visitorFn,
|
68
69
|
SplitOperation: visitorFn,
|
69
70
|
},
|
@@ -71,7 +72,7 @@ function flattenTransformImpl(
|
|
71
72
|
);
|
72
73
|
}
|
73
74
|
|
74
|
-
function memoizedFlattenSelection(cache) {
|
75
|
+
function memoizedFlattenSelection(cache: Map<Node, Map<?TypeID, any>>) {
|
75
76
|
return function flattenSelectionsFn<T: Node>(node: T, state: State): T {
|
76
77
|
const context: CompilerContext = this.getContext();
|
77
78
|
let nodeCache = cache.get(node);
|
@@ -188,6 +188,12 @@ function visitLinkedField(node: LinkedField, state: State): LinkedField {
|
|
188
188
|
({name}) => name === SUPPORTED_ARGUMENT_NAME,
|
189
189
|
);
|
190
190
|
if (supportedArgumentDefinition == null) {
|
191
|
+
if (moduleKey == null) {
|
192
|
+
throw createUserError(
|
193
|
+
'@match on a field without the `supported` argument is a no-op, please remove the `@match`.',
|
194
|
+
[node.loc],
|
195
|
+
);
|
196
|
+
}
|
191
197
|
return transformedNode;
|
192
198
|
}
|
193
199
|
|
@@ -0,0 +1,195 @@
|
|
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
|
+
'use strict';
|
13
|
+
|
14
|
+
const IRTransformer = require('../core/IRTransformer');
|
15
|
+
|
16
|
+
const {createUserError, createCompilerError} = require('../core/CompilerError');
|
17
|
+
const {RelayFeatureFlags} = require('relay-runtime');
|
18
|
+
|
19
|
+
import type CompilerContext from '../core/CompilerContext';
|
20
|
+
import type {ScalarField} from '../core/IR';
|
21
|
+
import type {TypeID, InputTypeID, ScalarFieldTypeID} from '../core/Schema';
|
22
|
+
|
23
|
+
const FLIGHT_FIELD_COMPONENT_ARGUMENT_TYPE = 'String';
|
24
|
+
const FLIGHT_FIELD_COMPONENT_ARGUMENT_NAME = 'component';
|
25
|
+
const FLIGHT_FIELD_PROPS_ARGUMENT_NAME = 'props';
|
26
|
+
const FLIGHT_FIELD_PROPS_TYPE = 'ReactFlightProps';
|
27
|
+
const FLIGHT_FIELD_RETURN_TYPE = 'ReactFlightComponent';
|
28
|
+
|
29
|
+
type State = {
|
30
|
+
parentType: TypeID,
|
31
|
+
types: {
|
32
|
+
propsType: InputTypeID,
|
33
|
+
componentType: ScalarFieldTypeID,
|
34
|
+
},
|
35
|
+
};
|
36
|
+
|
37
|
+
/**
|
38
|
+
* Experimental transform for React Flight.
|
39
|
+
*/
|
40
|
+
function reactFlightComponentTransform(
|
41
|
+
context: CompilerContext,
|
42
|
+
): CompilerContext {
|
43
|
+
const schema = context.getSchema();
|
44
|
+
let propsType = schema.getTypeFromString(FLIGHT_FIELD_PROPS_TYPE);
|
45
|
+
propsType = propsType ? schema.asInputType(propsType) : null;
|
46
|
+
let componentType = schema.getTypeFromString(FLIGHT_FIELD_RETURN_TYPE);
|
47
|
+
componentType = componentType
|
48
|
+
? schema.asScalarFieldType(componentType)
|
49
|
+
: null;
|
50
|
+
if (
|
51
|
+
!RelayFeatureFlags.ENABLE_REACT_FLIGHT_COMPONENT_FIELD ||
|
52
|
+
propsType == null ||
|
53
|
+
componentType == null
|
54
|
+
) {
|
55
|
+
return context;
|
56
|
+
}
|
57
|
+
const types = {propsType, componentType};
|
58
|
+
return IRTransformer.transform(
|
59
|
+
context,
|
60
|
+
{
|
61
|
+
ScalarField: visitScalarField,
|
62
|
+
LinkedField: visitLinkedField,
|
63
|
+
InlineFragment: visitInlineFragment,
|
64
|
+
},
|
65
|
+
node => ({
|
66
|
+
parentType: node.type,
|
67
|
+
types,
|
68
|
+
}),
|
69
|
+
);
|
70
|
+
}
|
71
|
+
|
72
|
+
function visitInlineFragment(fragment, state) {
|
73
|
+
return this.traverse(fragment, {
|
74
|
+
parentType: fragment.typeCondition ?? state.parentType,
|
75
|
+
types: state.types,
|
76
|
+
});
|
77
|
+
}
|
78
|
+
|
79
|
+
function visitLinkedField(field, state) {
|
80
|
+
return this.traverse(field, {parentType: field.type, types: state.types});
|
81
|
+
}
|
82
|
+
|
83
|
+
function visitScalarField(field: ScalarField, state: State): ScalarField {
|
84
|
+
// use the return type to quickly determine if this is a flight field
|
85
|
+
const schema = this.getContext().getSchema();
|
86
|
+
if (schema.getRawType(field.type) !== state.types.componentType) {
|
87
|
+
return field;
|
88
|
+
}
|
89
|
+
|
90
|
+
// get the name of the component that provides this field
|
91
|
+
const clientField = schema.getFieldByName(state.parentType, field.name);
|
92
|
+
if (clientField == null) {
|
93
|
+
throw createCompilerError(
|
94
|
+
`Definition not found for field '${schema.getTypeString(
|
95
|
+
state.parentType,
|
96
|
+
)}.${field.name}'`,
|
97
|
+
[field.loc],
|
98
|
+
);
|
99
|
+
}
|
100
|
+
const componentDirective = clientField.directives.find(
|
101
|
+
directive => directive.name === 'react_flight_component',
|
102
|
+
);
|
103
|
+
const componentNameArg = componentDirective?.args.find(
|
104
|
+
arg => arg.name === 'name',
|
105
|
+
);
|
106
|
+
if (
|
107
|
+
componentNameArg == null ||
|
108
|
+
componentNameArg.value.kind !== 'StringValue' ||
|
109
|
+
typeof componentNameArg.value.value !== 'string'
|
110
|
+
) {
|
111
|
+
throw createUserError(
|
112
|
+
'Invalid Flight field, expected the schema extension to specify ' +
|
113
|
+
"the component's module name with the '@react_flight_component' directive",
|
114
|
+
[field.loc],
|
115
|
+
);
|
116
|
+
}
|
117
|
+
const componentName = componentNameArg.value.value;
|
118
|
+
|
119
|
+
// validate that the parent type has a `flight(component, props)` field
|
120
|
+
const flightField = schema.getFieldByName(state.parentType, 'flight');
|
121
|
+
if (flightField == null) {
|
122
|
+
throw createUserError(
|
123
|
+
`Invalid Flight field, expected the parent type '${schema.getTypeString(
|
124
|
+
state.parentType,
|
125
|
+
)}' ` +
|
126
|
+
"to define a 'flight(component: String, props: ReactFlightProps): ReactFlightComponent' field",
|
127
|
+
[field.loc],
|
128
|
+
);
|
129
|
+
}
|
130
|
+
const componentArg = flightField.args.get(
|
131
|
+
FLIGHT_FIELD_COMPONENT_ARGUMENT_NAME,
|
132
|
+
);
|
133
|
+
const propsArg = flightField.args.get(FLIGHT_FIELD_PROPS_ARGUMENT_NAME);
|
134
|
+
if (
|
135
|
+
componentArg == null ||
|
136
|
+
propsArg == null ||
|
137
|
+
schema.getRawType(componentArg.type) !==
|
138
|
+
schema.getTypeFromString(FLIGHT_FIELD_COMPONENT_ARGUMENT_TYPE) ||
|
139
|
+
schema.getRawType(propsArg.type) !== state.types.propsType ||
|
140
|
+
schema.getRawType(flightField.type) !== state.types.componentType
|
141
|
+
) {
|
142
|
+
throw createUserError(
|
143
|
+
`Invalid Flight field, expected the parent type '${schema.getTypeString(
|
144
|
+
state.parentType,
|
145
|
+
)}' ` +
|
146
|
+
"to define a 'flight(component: String, props: ReactFlightProps): ReactFlightComponent' field",
|
147
|
+
[field.loc],
|
148
|
+
);
|
149
|
+
}
|
150
|
+
|
151
|
+
return {
|
152
|
+
...field,
|
153
|
+
name: 'flight',
|
154
|
+
args: [
|
155
|
+
{
|
156
|
+
kind: 'Argument',
|
157
|
+
loc: field.loc,
|
158
|
+
name: FLIGHT_FIELD_COMPONENT_ARGUMENT_NAME,
|
159
|
+
type: schema.getTypeFromString(FLIGHT_FIELD_COMPONENT_ARGUMENT_TYPE),
|
160
|
+
value: {
|
161
|
+
kind: 'Literal',
|
162
|
+
value: componentName,
|
163
|
+
loc: field.loc,
|
164
|
+
},
|
165
|
+
},
|
166
|
+
{
|
167
|
+
kind: 'Argument',
|
168
|
+
loc: field.loc,
|
169
|
+
name: FLIGHT_FIELD_PROPS_ARGUMENT_NAME,
|
170
|
+
type: state.types.propsType,
|
171
|
+
value: {
|
172
|
+
kind: 'ObjectValue',
|
173
|
+
fields: field.args.map(arg => {
|
174
|
+
return {
|
175
|
+
kind: 'ObjectFieldValue',
|
176
|
+
loc: arg.loc,
|
177
|
+
name: arg.name,
|
178
|
+
value: arg.value,
|
179
|
+
};
|
180
|
+
}),
|
181
|
+
loc: field.loc,
|
182
|
+
},
|
183
|
+
},
|
184
|
+
],
|
185
|
+
metadata: {
|
186
|
+
...(field.metadata || {}),
|
187
|
+
flight: true,
|
188
|
+
},
|
189
|
+
type: state.types.componentType,
|
190
|
+
};
|
191
|
+
}
|
192
|
+
|
193
|
+
module.exports = {
|
194
|
+
transform: reactFlightComponentTransform,
|
195
|
+
};
|