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.
Files changed (217) hide show
  1. package/bin/RelayCompilerBin.js.flow +169 -0
  2. package/bin/RelayCompilerMain.js.flow +515 -0
  3. package/bin/__fixtures__/plugin-module.js.flow +17 -0
  4. package/bin/relay-compiler +3862 -2505
  5. package/codegen/CodegenDirectory.js.flow +375 -0
  6. package/codegen/CodegenRunner.js.flow +432 -0
  7. package/codegen/CodegenTypes.js.flow +28 -0
  8. package/codegen/CodegenWatcher.js.flow +254 -0
  9. package/codegen/NormalizationCodeGenerator.js.flow +571 -0
  10. package/codegen/ReaderCodeGenerator.js.flow +512 -0
  11. package/codegen/RelayCodeGenerator.js.flow +85 -0
  12. package/codegen/RelayFileWriter.js.flow +367 -0
  13. package/codegen/SourceControl.js.flow +58 -0
  14. package/codegen/compileRelayArtifacts.js.flow +182 -0
  15. package/codegen/createPrintRequireModuleDependency.js.flow +21 -0
  16. package/codegen/sortObjectByKey.js.flow +25 -0
  17. package/codegen/writeRelayGeneratedFile.js.flow +223 -0
  18. package/core/ASTCache.js.flow +74 -0
  19. package/core/ASTConvert.js.flow +233 -0
  20. package/core/CompilerContext.js.flow +191 -0
  21. package/core/CompilerError.js.flow +250 -0
  22. package/core/DotGraphQLParser.js.flow +39 -0
  23. package/core/GraphQLCompilerProfiler.js.flow +341 -0
  24. package/core/GraphQLDerivedFromMetadata.js.flow +36 -0
  25. package/core/GraphQLWatchmanClient.js.flow +111 -0
  26. package/core/IR.js.flow +326 -0
  27. package/core/IRPrinter.js.flow +477 -0
  28. package/core/IRTransformer.js.flow +377 -0
  29. package/core/IRValidator.js.flow +260 -0
  30. package/core/IRVisitor.js.flow +150 -0
  31. package/core/JSModuleParser.js.flow +24 -0
  32. package/core/RelayCompilerScope.js.flow +199 -0
  33. package/core/RelayFindGraphQLTags.js.flow +119 -0
  34. package/core/RelayGraphQLEnumsGenerator.js.flow +55 -0
  35. package/core/RelayIRTransforms.js.flow +138 -0
  36. package/core/RelayParser.js.flow +1731 -0
  37. package/core/RelaySourceModuleParser.js.flow +135 -0
  38. package/core/Schema.js.flow +2037 -0
  39. package/core/SchemaUtils.js.flow +120 -0
  40. package/core/filterContextForNode.js.flow +50 -0
  41. package/core/getFieldDefinition.js.flow +156 -0
  42. package/core/getIdentifierForArgumentValue.js.flow +49 -0
  43. package/core/getIdentifierForSelection.js.flow +69 -0
  44. package/core/getLiteralArgumentValues.js.flow +32 -0
  45. package/core/getNormalizationOperationName.js.flow +19 -0
  46. package/core/inferRootArgumentDefinitions.js.flow +323 -0
  47. package/index.js +1 -1
  48. package/index.js.flow +200 -0
  49. package/language/RelayLanguagePluginInterface.js.flow +283 -0
  50. package/language/javascript/FindGraphQLTags.js.flow +137 -0
  51. package/language/javascript/RelayFlowBabelFactories.js.flow +176 -0
  52. package/language/javascript/RelayFlowGenerator.js.flow +1099 -0
  53. package/language/javascript/RelayFlowTypeTransformers.js.flow +184 -0
  54. package/language/javascript/RelayLanguagePluginJavaScript.js.flow +34 -0
  55. package/language/javascript/formatGeneratedModule.js.flow +65 -0
  56. package/lib/bin/RelayCompilerBin.js +10 -0
  57. package/lib/bin/RelayCompilerMain.js +127 -130
  58. package/lib/codegen/CodegenDirectory.js +2 -6
  59. package/lib/codegen/CodegenRunner.js +37 -76
  60. package/lib/codegen/CodegenWatcher.js +13 -21
  61. package/lib/codegen/NormalizationCodeGenerator.js +131 -50
  62. package/lib/codegen/ReaderCodeGenerator.js +116 -49
  63. package/lib/codegen/RelayCodeGenerator.js +17 -6
  64. package/lib/codegen/RelayFileWriter.js +15 -37
  65. package/lib/codegen/compileRelayArtifacts.js +16 -30
  66. package/lib/codegen/sortObjectByKey.js +43 -0
  67. package/lib/codegen/writeRelayGeneratedFile.js +86 -96
  68. package/lib/core/ASTCache.js +3 -4
  69. package/lib/core/CompilerContext.js +3 -4
  70. package/lib/core/CompilerError.js +27 -54
  71. package/lib/core/GraphQLCompilerProfiler.js +8 -12
  72. package/lib/core/GraphQLDerivedFromMetadata.js +1 -10
  73. package/lib/core/GraphQLWatchmanClient.js +4 -12
  74. package/lib/core/IRPrinter.js +16 -21
  75. package/lib/core/IRTransformer.js +8 -6
  76. package/lib/core/IRValidator.js +1 -3
  77. package/lib/core/RelayCompilerScope.js +4 -4
  78. package/lib/core/RelayGraphQLEnumsGenerator.js +12 -15
  79. package/lib/core/RelayIRTransforms.js +23 -13
  80. package/lib/core/RelayParser.js +53 -89
  81. package/lib/core/RelaySourceModuleParser.js +1 -3
  82. package/lib/core/Schema.js +106 -77
  83. package/lib/core/SchemaUtils.js +15 -1
  84. package/lib/core/getFieldDefinition.js +12 -15
  85. package/lib/core/getIdentifierForSelection.js +1 -1
  86. package/lib/core/inferRootArgumentDefinitions.js +27 -36
  87. package/lib/index.js +1 -3
  88. package/lib/language/javascript/FindGraphQLTags.js +7 -72
  89. package/lib/language/javascript/RelayFlowBabelFactories.js +5 -5
  90. package/lib/language/javascript/RelayFlowGenerator.js +131 -108
  91. package/lib/language/javascript/RelayFlowTypeTransformers.js +1 -3
  92. package/lib/reporters/ConsoleReporter.js +1 -3
  93. package/lib/reporters/MultiReporter.js +1 -3
  94. package/lib/runner/Artifacts.js +69 -170
  95. package/lib/runner/BufferedFilesystem.js +32 -66
  96. package/lib/runner/GraphQLASTNodeGroup.js +54 -120
  97. package/lib/runner/GraphQLNodeMap.js +14 -19
  98. package/lib/runner/Sources.js +70 -85
  99. package/lib/runner/StrictMap.js +21 -37
  100. package/lib/runner/getChangedNodeNames.js +30 -62
  101. package/lib/transforms/ApplyFragmentArgumentTransform.js +71 -31
  102. package/lib/transforms/ClientExtensionsTransform.js +15 -15
  103. package/lib/transforms/ConnectionTransform.js +26 -38
  104. package/lib/transforms/DeclarativeConnectionMutationTransform.js +225 -0
  105. package/lib/transforms/DeferStreamTransform.js +27 -17
  106. package/lib/transforms/DisallowTypenameOnRoot.js +55 -0
  107. package/lib/transforms/FieldHandleTransform.js +7 -3
  108. package/lib/transforms/FilterCompilerDirectivesTransform.js +29 -0
  109. package/lib/transforms/FlattenTransform.js +23 -19
  110. package/lib/transforms/GenerateIDFieldTransform.js +56 -35
  111. package/lib/transforms/GenerateTypeNameTransform.js +84 -10
  112. package/lib/transforms/InlineDataFragmentTransform.js +9 -4
  113. package/lib/transforms/MaskTransform.js +17 -17
  114. package/lib/transforms/MatchTransform.js +114 -32
  115. package/lib/transforms/ReactFlightComponentTransform.js +162 -0
  116. package/lib/transforms/RefetchableFragmentTransform.js +21 -17
  117. package/lib/transforms/RelayDirectiveTransform.js +8 -3
  118. package/lib/transforms/RequiredFieldTransform.js +380 -0
  119. package/lib/transforms/SkipClientExtensionsTransform.js +8 -0
  120. package/lib/transforms/SkipHandleFieldTransform.js +6 -2
  121. package/lib/transforms/SkipRedundantNodesTransform.js +9 -2
  122. package/lib/transforms/SkipSplitOperationTransform.js +32 -0
  123. package/lib/transforms/SkipUnreachableNodeTransform.js +9 -2
  124. package/lib/transforms/SkipUnusedVariablesTransform.js +18 -17
  125. package/lib/transforms/SplitModuleImportTransform.js +2 -2
  126. package/lib/transforms/TestOperationTransform.js +26 -20
  127. package/lib/transforms/ValidateGlobalVariablesTransform.js +18 -30
  128. package/lib/transforms/ValidateRequiredArgumentsTransform.js +12 -15
  129. package/lib/transforms/ValidateServerOnlyDirectivesTransform.js +16 -30
  130. package/lib/transforms/ValidateUnusedVariablesTransform.js +18 -30
  131. package/lib/transforms/query-generators/FetchableQueryGenerator.js +161 -0
  132. package/lib/transforms/query-generators/NodeQueryGenerator.js +22 -3
  133. package/lib/transforms/query-generators/QueryQueryGenerator.js +2 -1
  134. package/lib/transforms/query-generators/ViewerQueryGenerator.js +1 -0
  135. package/lib/transforms/query-generators/index.js +23 -6
  136. package/lib/transforms/query-generators/utils.js +17 -16
  137. package/lib/util/RelayCompilerCache.js +2 -4
  138. package/lib/util/argumentContainsVariables.js +37 -0
  139. package/lib/util/dedupeJSONStringify.js +15 -12
  140. package/lib/util/generateAbstractTypeRefinementKey.js +24 -0
  141. package/lib/util/getModuleName.js +1 -1
  142. package/lib/util/joinArgumentDefinitions.js +3 -1
  143. package/package.json +7 -7
  144. package/relay-compiler.js +4 -4
  145. package/relay-compiler.min.js +4 -4
  146. package/reporters/ConsoleReporter.js.flow +81 -0
  147. package/reporters/MultiReporter.js.flow +43 -0
  148. package/reporters/Reporter.js.flow +19 -0
  149. package/runner/Artifacts.js.flow +219 -0
  150. package/runner/BufferedFilesystem.js.flow +194 -0
  151. package/runner/GraphQLASTNodeGroup.js.flow +176 -0
  152. package/runner/GraphQLASTUtils.js.flow +26 -0
  153. package/runner/GraphQLNodeMap.js.flow +55 -0
  154. package/runner/Sources.js.flow +228 -0
  155. package/runner/StrictMap.js.flow +96 -0
  156. package/runner/compileArtifacts.js.flow +76 -0
  157. package/runner/extractAST.js.flow +100 -0
  158. package/runner/getChangedNodeNames.js.flow +48 -0
  159. package/runner/getSchemaInstance.js.flow +36 -0
  160. package/runner/types.js.flow +37 -0
  161. package/transforms/ApplyFragmentArgumentTransform.js.flow +526 -0
  162. package/transforms/ClientExtensionsTransform.js.flow +224 -0
  163. package/transforms/ConnectionTransform.js.flow +855 -0
  164. package/transforms/DeclarativeConnectionMutationTransform.js.flow +246 -0
  165. package/transforms/DeferStreamTransform.js.flow +265 -0
  166. package/transforms/DisallowIdAsAlias.js.flow +47 -0
  167. package/transforms/DisallowTypenameOnRoot.js.flow +45 -0
  168. package/transforms/FieldHandleTransform.js.flow +79 -0
  169. package/transforms/FilterCompilerDirectivesTransform.js.flow +33 -0
  170. package/transforms/FilterDirectivesTransform.js.flow +45 -0
  171. package/transforms/FlattenTransform.js.flow +454 -0
  172. package/transforms/GenerateIDFieldTransform.js.flow +152 -0
  173. package/transforms/GenerateTypeNameTransform.js.flow +161 -0
  174. package/transforms/InlineDataFragmentTransform.js.flow +125 -0
  175. package/transforms/InlineFragmentsTransform.js.flow +71 -0
  176. package/transforms/MaskTransform.js.flow +126 -0
  177. package/transforms/MatchTransform.js.flow +589 -0
  178. package/transforms/ReactFlightComponentTransform.js.flow +195 -0
  179. package/transforms/RefetchableFragmentTransform.js.flow +272 -0
  180. package/transforms/RelayDirectiveTransform.js.flow +97 -0
  181. package/transforms/RequiredFieldTransform.js.flow +415 -0
  182. package/transforms/SkipClientExtensionsTransform.js.flow +54 -0
  183. package/transforms/SkipHandleFieldTransform.js.flow +44 -0
  184. package/transforms/SkipRedundantNodesTransform.js.flow +257 -0
  185. package/transforms/SkipSplitOperationTransform.js.flow +37 -0
  186. package/transforms/SkipUnreachableNodeTransform.js.flow +149 -0
  187. package/transforms/SkipUnusedVariablesTransform.js.flow +59 -0
  188. package/transforms/SplitModuleImportTransform.js.flow +98 -0
  189. package/transforms/TestOperationTransform.js.flow +142 -0
  190. package/transforms/TransformUtils.js.flow +26 -0
  191. package/transforms/ValidateGlobalVariablesTransform.js.flow +81 -0
  192. package/transforms/ValidateRequiredArgumentsTransform.js.flow +127 -0
  193. package/transforms/ValidateServerOnlyDirectivesTransform.js.flow +112 -0
  194. package/transforms/ValidateUnusedVariablesTransform.js.flow +89 -0
  195. package/transforms/query-generators/FetchableQueryGenerator.js.flow +189 -0
  196. package/transforms/query-generators/NodeQueryGenerator.js.flow +219 -0
  197. package/transforms/query-generators/QueryQueryGenerator.js.flow +57 -0
  198. package/transforms/query-generators/ViewerQueryGenerator.js.flow +97 -0
  199. package/transforms/query-generators/index.js.flow +90 -0
  200. package/transforms/query-generators/utils.js.flow +76 -0
  201. package/util/CodeMarker.js.flow +79 -0
  202. package/util/DefaultHandleKey.js.flow +17 -0
  203. package/util/RelayCompilerCache.js.flow +88 -0
  204. package/util/Rollout.js.flow +39 -0
  205. package/util/TimeReporter.js.flow +79 -0
  206. package/util/areEqualOSS.js.flow +123 -0
  207. package/util/argumentContainsVariables.js.flow +38 -0
  208. package/util/dedupeJSONStringify.js.flow +152 -0
  209. package/util/generateAbstractTypeRefinementKey.js.flow +29 -0
  210. package/util/getDefinitionNodeHash.js.flow +25 -0
  211. package/util/getModuleName.js.flow +39 -0
  212. package/util/joinArgumentDefinitions.js.flow +105 -0
  213. package/util/md5.js.flow +22 -0
  214. package/util/murmurHash.js.flow +94 -0
  215. package/util/nullthrowsOSS.js.flow +25 -0
  216. package/util/orList.js.flow +37 -0
  217. package/util/partitionArray.js.flow +37 -0
@@ -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
+ };
@@ -0,0 +1,272 @@
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 IRVisitor = require('../core/IRVisitor');
16
+
17
+ const getLiteralArgumentValues = require('../core/getLiteralArgumentValues');
18
+ const inferRootArgumentDefinitions = require('../core/inferRootArgumentDefinitions');
19
+
20
+ const {
21
+ createUserError,
22
+ eachWithCombinedError,
23
+ } = require('../core/CompilerError');
24
+ const {buildRefetchOperation} = require('./query-generators');
25
+
26
+ import type CompilerContext from '../core/CompilerContext';
27
+ import type {Argument, Field, Fragment} from '../core/IR';
28
+ import type {Schema} from '../core/Schema';
29
+ import type {ReaderPaginationMetadata} from 'relay-runtime';
30
+
31
+ const SCHEMA_EXTENSION = `
32
+ directive @refetchable(
33
+ queryName: String!
34
+ ) on FRAGMENT_DEFINITION
35
+ `;
36
+
37
+ /**
38
+ * This transform synthesizes "refetch" queries for fragments that
39
+ * are trivially refetchable. This is comprised of three main stages:
40
+ *
41
+ * 1. Validating that fragments marked with @refetchable qualify for
42
+ * refetch query generation; mainly this means that the fragment
43
+ * type is able to be refetched in some canonical way.
44
+ * 2. Determining the variable definitions to use for each generated
45
+ * query. GraphQL does not have a notion of fragment-local variables
46
+ * at all, and although Relay adds this concept developers are still
47
+ * allowed to reference global variables. This necessitates a
48
+ * visiting all reachable fragments for each @refetchable fragment,
49
+ * and finding the union of all global variables expceted to be defined.
50
+ * 3. Building the refetch queries, a straightforward copying transform from
51
+ * Fragment to Root IR nodes.
52
+ */
53
+ function refetchableFragmentTransform(
54
+ context: CompilerContext,
55
+ ): CompilerContext {
56
+ const schema = context.getSchema();
57
+
58
+ const refetchOperations = buildRefetchMap(context);
59
+ let nextContext = context;
60
+ eachWithCombinedError(refetchOperations, ([refetchName, fragment]) => {
61
+ const {
62
+ identifierField,
63
+ path,
64
+ node,
65
+ transformedFragment,
66
+ } = buildRefetchOperation(schema, fragment, refetchName);
67
+ const connectionMetadata = extractConnectionMetadata(
68
+ context.getSchema(),
69
+ transformedFragment,
70
+ );
71
+ nextContext = nextContext.replace({
72
+ ...transformedFragment,
73
+ metadata: {
74
+ ...(transformedFragment.metadata || {}),
75
+ refetch: {
76
+ connection: connectionMetadata ?? null,
77
+ operation: refetchName,
78
+ fragmentPathInResult: path,
79
+ identifierField,
80
+ },
81
+ },
82
+ });
83
+ nextContext = nextContext.add({
84
+ ...node,
85
+ metadata: {
86
+ ...(node.metadata || {}),
87
+ derivedFrom: transformedFragment.name,
88
+ isRefetchableQuery: true,
89
+ },
90
+ });
91
+ });
92
+ return nextContext;
93
+ }
94
+
95
+ /**
96
+ * Walk the documents of a compiler context and create a mapping of
97
+ * refetch operation names to the source fragment from which the refetch
98
+ * operation should be derived.
99
+ */
100
+ function buildRefetchMap(context: CompilerContext): Map<string, Fragment> {
101
+ const refetchOperations = new Map();
102
+ eachWithCombinedError(context.documents(), node => {
103
+ if (node.kind !== 'Fragment') {
104
+ return;
105
+ }
106
+ const refetchName = getRefetchQueryName(node);
107
+ if (refetchName === null) {
108
+ return;
109
+ }
110
+ const previousOperation = refetchOperations.get(refetchName);
111
+ if (previousOperation != null) {
112
+ throw createUserError(
113
+ `Duplicate definition for @refetchable operation '${refetchName}' from fragments '${node.name}' and '${previousOperation.name}'`,
114
+ [node.loc, previousOperation.loc],
115
+ );
116
+ }
117
+ refetchOperations.set(refetchName, node);
118
+ });
119
+ const transformed = inferRootArgumentDefinitions(context);
120
+ return new Map(
121
+ Array.from(refetchOperations.entries(), ([name, fragment]) => {
122
+ return [name, transformed.getFragment(fragment.name)];
123
+ }),
124
+ );
125
+ }
126
+
127
+ /**
128
+ * Validate that any @connection usage is valid for refetching:
129
+ * - Variables are used for both the "count" and "cursor" arguments
130
+ * (after/first or before/last)
131
+ * - Exactly one connection
132
+ * - Has a stable path to the connection data
133
+ *
134
+ * Returns connection metadata to add to the transformed fragment or undefined
135
+ * if there is no connection.
136
+ */
137
+ function extractConnectionMetadata(
138
+ schema: Schema,
139
+ fragment: Fragment,
140
+ ): ReaderPaginationMetadata | void {
141
+ const fields = [];
142
+ let connectionField = null;
143
+ let path = null;
144
+ IRVisitor.visit(fragment, {
145
+ LinkedField: {
146
+ enter(field) {
147
+ fields.push(field);
148
+ if (
149
+ field.connection === true ||
150
+ (field.handles &&
151
+ field.handles.some(handle => handle.name === 'connection'))
152
+ ) {
153
+ // Disallow multiple @connections
154
+ if (connectionField != null) {
155
+ throw createUserError(
156
+ `Invalid use of @refetchable with @connection in fragment '${fragment.name}', at most once @connection can appear in a refetchable fragment.`,
157
+ [field.loc],
158
+ );
159
+ }
160
+ // Disallow connections within plurals
161
+ const pluralOnPath = fields.find(pathField =>
162
+ schema.isList(schema.getNullableType(pathField.type)),
163
+ );
164
+ if (pluralOnPath) {
165
+ throw createUserError(
166
+ `Invalid use of @refetchable with @connection in fragment '${fragment.name}', refetchable connections cannot appear inside plural fields.`,
167
+ [field.loc, pluralOnPath.loc],
168
+ );
169
+ }
170
+ connectionField = field;
171
+ path = fields.map(pathField => pathField.alias);
172
+ }
173
+ },
174
+ leave() {
175
+ fields.pop();
176
+ },
177
+ },
178
+ });
179
+ if (connectionField == null || path == null) {
180
+ return;
181
+ }
182
+ // Validate arguments: if either of before/last appear they must both appear
183
+ // and use variables (not scalar values)
184
+ let backward = null;
185
+ const before = findArgument(connectionField, 'before');
186
+ const last = findArgument(connectionField, 'last');
187
+ if (before || last) {
188
+ if (
189
+ !before ||
190
+ !last ||
191
+ before.value.kind !== 'Variable' ||
192
+ last.value.kind !== 'Variable'
193
+ ) {
194
+ throw createUserError(
195
+ `Invalid use of @refetchable with @connection in fragment '${fragment.name}', refetchable connections must use variables for the before and last arguments.`,
196
+ [
197
+ connectionField.loc,
198
+ before && before.value.kind !== 'Variable' ? before.value.loc : null,
199
+ last && last.value.kind !== 'Variable' ? last.value.loc : null,
200
+ ].filter(Boolean),
201
+ );
202
+ }
203
+ backward = {
204
+ count: last.value.variableName,
205
+ cursor: before.value.variableName,
206
+ };
207
+ }
208
+ // Validate arguments: if either of after/first appear they must both appear
209
+ // and use variables (not scalar values)
210
+ let forward = null;
211
+ const after = findArgument(connectionField, 'after');
212
+ const first = findArgument(connectionField, 'first');
213
+ if (after || first) {
214
+ if (
215
+ !after ||
216
+ !first ||
217
+ after.value.kind !== 'Variable' ||
218
+ first.value.kind !== 'Variable'
219
+ ) {
220
+ throw createUserError(
221
+ `Invalid use of @refetchable with @connection in fragment '${fragment.name}', refetchable connections must use variables for the after and first arguments.`,
222
+ [
223
+ connectionField.loc,
224
+ after && after.value.kind !== 'Variable' ? after.value.loc : null,
225
+ first && first.value.kind !== 'Variable' ? first.value.loc : null,
226
+ ].filter(Boolean),
227
+ );
228
+ }
229
+ forward = {
230
+ count: first.value.variableName,
231
+ cursor: after.value.variableName,
232
+ };
233
+ }
234
+ return {forward, backward, path};
235
+ }
236
+
237
+ function getRefetchQueryName(fragment: Fragment): string | null {
238
+ const refetchableDirective = fragment.directives.find(
239
+ directive => directive.name === 'refetchable',
240
+ );
241
+ if (refetchableDirective == null) {
242
+ return null;
243
+ }
244
+ const refetchArguments = getLiteralArgumentValues(refetchableDirective.args);
245
+ const queryName = refetchArguments.queryName;
246
+ if (queryName == null) {
247
+ throw createUserError(
248
+ "Expected the 'queryName' argument of @refetchable to be provided",
249
+ [refetchableDirective.loc],
250
+ );
251
+ } else if (typeof queryName !== 'string') {
252
+ const queryNameArg = refetchableDirective.args.find(
253
+ arg => arg.name === 'queryName',
254
+ );
255
+ throw createUserError(
256
+ `Expected the 'queryName' argument of @refetchable to be a string, got '${String(
257
+ queryName,
258
+ )}'.`,
259
+ [queryNameArg?.loc ?? refetchableDirective.loc],
260
+ );
261
+ }
262
+ return queryName;
263
+ }
264
+
265
+ function findArgument(field: Field, argumentName: string): Argument | null {
266
+ return field.args.find(arg => arg.name === argumentName) ?? null;
267
+ }
268
+
269
+ module.exports = {
270
+ SCHEMA_EXTENSION,
271
+ transform: refetchableFragmentTransform,
272
+ };
@@ -0,0 +1,97 @@
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
+
17
+ const getLiteralArgumentValues = require('../core/getLiteralArgumentValues');
18
+ const invariant = require('invariant');
19
+
20
+ import type CompilerContext from '../core/CompilerContext';
21
+ import type {Fragment, FragmentSpread} from '../core/IR';
22
+
23
+ const RELAY = 'relay';
24
+ const SCHEMA_EXTENSION = `
25
+ directive @relay(
26
+ # Marks a fragment as being backed by a GraphQLList.
27
+ plural: Boolean,
28
+
29
+ # Marks a fragment spread which should be unmasked if provided false
30
+ mask: Boolean = true,
31
+ ) on FRAGMENT_DEFINITION | FRAGMENT_SPREAD
32
+ `;
33
+
34
+ /**
35
+ * A transform that extracts `@relay(plural: Boolean)` directives and converts
36
+ * them to metadata that can be accessed at runtime.
37
+ */
38
+ function relayDirectiveTransform(context: CompilerContext): CompilerContext {
39
+ return IRTransformer.transform(context, {
40
+ Fragment: visitRelayMetadata(fragmentMetadata),
41
+ FragmentSpread: visitRelayMetadata(fragmentSpreadMetadata),
42
+ });
43
+ }
44
+
45
+ type MixedObj = {[key: string]: mixed, ...};
46
+ function visitRelayMetadata<T: Fragment | FragmentSpread>(
47
+ metadataFn: MixedObj => MixedObj,
48
+ ): T => T {
49
+ return function(node) {
50
+ const relayDirective = node.directives.find(({name}) => name === RELAY);
51
+ if (!relayDirective) {
52
+ return this.traverse(node);
53
+ }
54
+ const argValues = getLiteralArgumentValues(relayDirective.args);
55
+ const metadata = metadataFn(argValues);
56
+ return this.traverse({
57
+ ...node,
58
+ directives: node.directives.filter(
59
+ directive => directive !== relayDirective,
60
+ ),
61
+ // $FlowFixMe[cannot-spread-indexer]
62
+ metadata: {
63
+ ...(node.metadata || {}),
64
+ ...metadata,
65
+ },
66
+ });
67
+ };
68
+ }
69
+
70
+ function fragmentMetadata({mask, plural}): MixedObj {
71
+ invariant(
72
+ plural === undefined || typeof plural === 'boolean',
73
+ 'RelayDirectiveTransform: Expected the "plural" argument to @relay ' +
74
+ 'to be a boolean literal if specified.',
75
+ );
76
+ invariant(
77
+ mask === undefined || typeof mask === 'boolean',
78
+ 'RelayDirectiveTransform: Expected the "mask" argument to @relay ' +
79
+ 'to be a boolean literal if specified.',
80
+ );
81
+ return {mask, plural};
82
+ }
83
+
84
+ function fragmentSpreadMetadata({mask}): MixedObj {
85
+ invariant(
86
+ mask === undefined || typeof mask === 'boolean',
87
+ 'RelayDirectiveTransform: Expected the "mask" argument to @relay ' +
88
+ 'to be a boolean literal if specified.',
89
+ );
90
+ return {mask};
91
+ }
92
+
93
+ module.exports = {
94
+ RELAY,
95
+ SCHEMA_EXTENSION,
96
+ transform: relayDirectiveTransform,
97
+ };