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.
Files changed (91) hide show
  1. package/bin/relay-compiler +1538 -975
  2. package/codegen/NormalizationCodeGenerator.js.flow +12 -4
  3. package/codegen/ReaderCodeGenerator.js.flow +38 -3
  4. package/codegen/RelayFileWriter.js.flow +2 -0
  5. package/codegen/writeRelayGeneratedFile.js.flow +1 -1
  6. package/core/ASTCache.js.flow +1 -0
  7. package/core/CompilerContext.js.flow +1 -0
  8. package/core/CompilerError.js.flow +6 -1
  9. package/core/IR.js.flow +0 -1
  10. package/core/IRPrinter.js.flow +3 -8
  11. package/core/RelayIRTransforms.js.flow +7 -0
  12. package/core/Schema.js.flow +55 -1
  13. package/index.js +1 -1
  14. package/language/javascript/FindGraphQLTags.js.flow +2 -97
  15. package/language/javascript/RelayFlowBabelFactories.js.flow +11 -15
  16. package/language/javascript/RelayFlowGenerator.js.flow +76 -19
  17. package/language/javascript/RelayFlowTypeTransformers.js.flow +4 -4
  18. package/lib/bin/RelayCompilerMain.js +8 -14
  19. package/lib/codegen/CodegenRunner.js +5 -9
  20. package/lib/codegen/NormalizationCodeGenerator.js +21 -13
  21. package/lib/codegen/ReaderCodeGenerator.js +44 -13
  22. package/lib/codegen/RelayFileWriter.js +3 -7
  23. package/lib/codegen/compileRelayArtifacts.js +4 -8
  24. package/lib/codegen/sortObjectByKey.js +3 -5
  25. package/lib/codegen/writeRelayGeneratedFile.js +3 -7
  26. package/lib/core/ASTCache.js +1 -0
  27. package/lib/core/CompilerContext.js +1 -0
  28. package/lib/core/CompilerError.js +8 -8
  29. package/lib/core/IRPrinter.js +3 -4
  30. package/lib/core/IRTransformer.js +3 -7
  31. package/lib/core/RelayGraphQLEnumsGenerator.js +3 -7
  32. package/lib/core/RelayIRTransforms.js +10 -4
  33. package/lib/core/RelayParser.js +7 -15
  34. package/lib/core/Schema.js +50 -13
  35. package/lib/core/getFieldDefinition.js +3 -5
  36. package/lib/core/inferRootArgumentDefinitions.js +6 -14
  37. package/lib/language/javascript/FindGraphQLTags.js +3 -69
  38. package/lib/language/javascript/RelayFlowBabelFactories.js +5 -5
  39. package/lib/language/javascript/RelayFlowGenerator.js +85 -34
  40. package/lib/runner/Artifacts.js +13 -17
  41. package/lib/runner/BufferedFilesystem.js +6 -10
  42. package/lib/runner/GraphQLASTNodeGroup.js +10 -14
  43. package/lib/runner/GraphQLNodeMap.js +3 -7
  44. package/lib/runner/Sources.js +27 -17
  45. package/lib/runner/StrictMap.js +5 -7
  46. package/lib/runner/getChangedNodeNames.js +6 -8
  47. package/lib/transforms/ApplyFragmentArgumentTransform.js +16 -25
  48. package/lib/transforms/ClientExtensionsTransform.js +8 -9
  49. package/lib/transforms/ConnectionTransform.js +9 -14
  50. package/lib/transforms/DeclarativeConnectionMutationTransform.js +115 -64
  51. package/lib/transforms/DeferStreamTransform.js +3 -7
  52. package/lib/transforms/DisallowTypenameOnRoot.js +3 -5
  53. package/lib/transforms/FieldHandleTransform.js +3 -7
  54. package/lib/transforms/FilterCompilerDirectivesTransform.js +29 -0
  55. package/lib/transforms/FlattenTransform.js +14 -17
  56. package/lib/transforms/GenerateIDFieldTransform.js +3 -7
  57. package/lib/transforms/GenerateTypeNameTransform.js +4 -8
  58. package/lib/transforms/InlineDataFragmentTransform.js +3 -7
  59. package/lib/transforms/MaskTransform.js +4 -12
  60. package/lib/transforms/MatchTransform.js +8 -8
  61. package/lib/transforms/ReactFlightComponentTransform.js +158 -0
  62. package/lib/transforms/RefetchableFragmentTransform.js +5 -9
  63. package/lib/transforms/RelayDirectiveTransform.js +3 -7
  64. package/lib/transforms/RequiredFieldTransform.js +369 -0
  65. package/lib/transforms/SkipHandleFieldTransform.js +2 -6
  66. package/lib/transforms/SkipRedundantNodesTransform.js +4 -6
  67. package/lib/transforms/SkipUnreachableNodeTransform.js +2 -6
  68. package/lib/transforms/SkipUnusedVariablesTransform.js +4 -12
  69. package/lib/transforms/TestOperationTransform.js +3 -7
  70. package/lib/transforms/ValidateGlobalVariablesTransform.js +4 -6
  71. package/lib/transforms/ValidateRequiredArgumentsTransform.js +3 -5
  72. package/lib/transforms/ValidateServerOnlyDirectivesTransform.js +4 -6
  73. package/lib/transforms/ValidateUnusedVariablesTransform.js +4 -6
  74. package/lib/transforms/query-generators/FetchableQueryGenerator.js +2 -6
  75. package/lib/transforms/query-generators/NodeQueryGenerator.js +2 -6
  76. package/lib/transforms/query-generators/index.js +3 -5
  77. package/lib/transforms/query-generators/utils.js +3 -5
  78. package/package.json +3 -3
  79. package/relay-compiler.js +4 -4
  80. package/relay-compiler.min.js +4 -4
  81. package/runner/Sources.js.flow +14 -0
  82. package/transforms/ClientExtensionsTransform.js.flow +3 -0
  83. package/transforms/ConnectionTransform.js.flow +0 -1
  84. package/transforms/DeclarativeConnectionMutationTransform.js.flow +140 -48
  85. package/transforms/FieldHandleTransform.js.flow +0 -1
  86. package/transforms/FilterCompilerDirectivesTransform.js.flow +33 -0
  87. package/transforms/FlattenTransform.js.flow +3 -2
  88. package/transforms/MatchTransform.js.flow +6 -0
  89. package/transforms/ReactFlightComponentTransform.js.flow +195 -0
  90. package/transforms/RequiredFieldTransform.js.flow +415 -0
  91. package/transforms/SkipRedundantNodesTransform.js.flow +3 -0
@@ -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 */
@@ -308,7 +308,6 @@ function buildConnectionArguments(
308
308
  };
309
309
  }
310
310
 
311
- // T45504512: new connection model
312
311
  const dynamicKeyArg = connectionDirective.args.find(
313
312
  arg => arg.name === 'dynamicKey_UNSTABLE',
314
313
  );
@@ -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 LINKED_FIELD_DIRECTIVES = [APPEND_EDGE, PREPEND_EDGE];
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: [String!]!
39
+ connections: [ID!]!
29
40
  ) on FIELD
30
41
  directive @${PREPEND_EDGE}(
31
- connections: [String!]!
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 deleteDirective = field.directives.find(
80
+ const deleteNodeDirective = field.directives.find(
62
81
  directive => directive.name === DELETE_RECORD,
63
82
  );
64
- if (deleteDirective == null) {
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
- if (!schema.isId(field.type)) {
98
+
99
+ if (!schema.isId(schema.getRawType(field.type))) {
69
100
  throw createUserError(
70
- `Invalid use of @${DELETE_RECORD} on field '${
101
+ `Invalid use of @${targetDirective.name} on field '${
71
102
  field.name
72
- }'. Expected field type ID, got ${schema.getTypeString(field.type)}.`,
73
- [deleteDirective.loc],
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: DELETE_RECORD,
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 !== deleteDirective,
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 => LINKED_FIELD_DIRECTIVES.indexOf(directive.name) > -1,
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
- if (edgeDirective == null) {
145
+
146
+ if (edgeDirective == null && nodeDirective == null) {
106
147
  return transformedField;
107
148
  }
108
- const connectionsArg = edgeDirective.args.find(
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 @${edgeDirective.name}.`,
114
- [edgeDirective.loc],
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
- const fields = schema.getFields(transformedField.type);
119
- let cursorFieldID;
120
- let nodeFieldID;
121
- for (const fieldID of fields) {
122
- const fieldName = schema.getFieldName(fieldID);
123
- if (fieldName === ConnectionInterface.get().CURSOR) {
124
- cursorFieldID = fieldID;
125
- } else if (fieldName === ConnectionInterface.get().NODE) {
126
- nodeFieldID = fieldID;
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
+ };