UNPKG

9.74 kBJavaScriptView Raw
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 * strict-local
8 * @format
9 */
10'use strict';
11
12var SUPPORTED_ARGUMENT_NAME = 'supported';
13var JS_FIELD_TYPE = 'JSDependency';
14var JS_FIELD_ARG = 'module';
15var JS_FIELD_NAME = 'js';
16var SCHEMA_EXTENSION = "\n directive @match on FIELD\n\n directive @module(\n name: String!\n ) on FRAGMENT_SPREAD\n";
17/**
18 * This transform rewrites LinkedField nodes with @match and rewrites them
19 * into MatchField nodes with a `supported` argument and MatchBranch selections.
20 */
21
22function relayMatchTransform(context) {
23 return require("./GraphQLIRTransformer").transform(context, {
24 // $FlowFixMe this transform intentionally changes the AST node type
25 LinkedField: visitLinkedField,
26 InlineFragment: visitInlineFragment
27 }, function (node) {
28 return node.type;
29 });
30}
31
32function visitInlineFragment(node, state) {
33 return this.traverse(node, node.typeCondition);
34}
35
36function visitLinkedField(node, parentType) {
37 var _transformedNode$alia;
38
39 var transformedNode = this.traverse(node, node.type);
40 var matchDirective = transformedNode.directives.find(function (directive) {
41 return directive.name === 'match';
42 });
43
44 if (matchDirective == null) {
45 return transformedNode;
46 }
47
48 var rawType = require("./GraphQLSchemaUtils").getRawType(parentType);
49
50 if (!(rawType instanceof require("graphql").GraphQLInterfaceType || rawType instanceof require("graphql").GraphQLObjectType)) {
51 throw require("./RelayCompilerError").createUserError('@match may only be used on fields whose parent type is an interface ' + "or object, field '".concat(node.name, "' has invalid type '").concat(String(parentType), "'"), [node.loc]);
52 }
53
54 var context = this.getContext();
55 var schema = context.serverSchema;
56 var jsModuleType = schema.getType(JS_FIELD_TYPE);
57
58 if (jsModuleType == null || !(jsModuleType instanceof require("graphql").GraphQLScalarType)) {
59 throw new Error("RelayMatchTransform: Expected schema to define a scalar '".concat(JS_FIELD_TYPE, "' type."));
60 }
61
62 var currentField = rawType.getFields()[transformedNode.name];
63 var supportedArg = currentField.args.find(function (_ref2) {
64 var name = _ref2.name;
65 return SUPPORTED_ARGUMENT_NAME;
66 });
67 var supportedArgType = supportedArg != null ? require("graphql").getNullableType(supportedArg.type) : null;
68 var supportedArgOfType = supportedArgType != null && supportedArgType instanceof require("graphql").GraphQLList ? supportedArgType.ofType : null;
69
70 if (supportedArg == null || supportedArgType == null || supportedArgOfType == null || require("graphql").getNullableType(supportedArgOfType) !== require("graphql").GraphQLString) {
71 throw new Error('RelayMatchTransform: @match used on an incompatible ' + "field '".concat(transformedNode.name, "'. @match may only ") + "be used with fields that can accept '".concat(SUPPORTED_ARGUMENT_NAME, "' ") + "argument with type '[String!]!'.");
72 }
73
74 var unionType = transformedNode.type;
75
76 if (!(unionType instanceof require("graphql").GraphQLUnionType)) {
77 throw new Error('RelayMatchTransform: You are trying to apply @match ' + "directive to a field '".concat(transformedNode.name, "' that has unsupported ") + "output type. '".concat(transformedNode.name, "' output type should be union ") + 'type of object types.');
78 }
79
80 var seenTypes = new Map();
81 var typeToSelectionMap = {};
82 var selections = [];
83 transformedNode.selections.forEach(function (matchSelection) {
84 var _ref, _moduleDirective$args;
85
86 if (matchSelection.kind !== 'FragmentSpread') {
87 throw new Error('RelayMatchTransform: all selections in a @match field should be ' + "fragment spreads, got '".concat(matchSelection.kind, "'."));
88 }
89
90 var fragment = context.getFragment(matchSelection.name);
91
92 if (!(fragment.type instanceof require("graphql").GraphQLObjectType)) {
93 throw new Error('RelayMatchTransform: all fragment spreads in a @match field should ' + 'be for fragments on an object type. Union or interface type ' + "'".concat(fragment.type.name, "' for '...").concat(fragment.name, "' is not supported."));
94 }
95
96 var matchedType = fragment.type;
97
98 if (seenTypes.has(matchedType)) {
99 throw new Error('RelayMatchTransform: Each "match" type has to appear at-most once. ' + "Type '".concat(matchedType.name, "' was matched in both ") + "'...".concat(matchSelection.name, "' and '...").concat(seenTypes.get(matchedType) || '(unknown)', "'."));
100 }
101
102 seenTypes.set(matchedType, matchSelection.name);
103 var belongsToUnion = unionType.getTypes().includes(matchedType);
104
105 if (!belongsToUnion) {
106 throw new Error("RelayMatchTransform: Unsupported type '".concat(matchedType.toString(), "' in ") + 'the list of matches in the @match. Type ' + "\"".concat(matchedType.toString(), "\" does not belong to the union ") + "\"".concat(unionType.toString(), "\"."));
107 }
108
109 var jsField = matchedType.getFields()[JS_FIELD_NAME];
110 var jsFieldArg = jsField ? jsField.args.find(function (arg) {
111 return arg.name === JS_FIELD_ARG;
112 }) : null;
113
114 if (jsField == null || jsFieldArg == null || require("graphql").getNullableType(jsFieldArg.type) !== require("graphql").GraphQLString || jsField.type.name !== jsModuleType.name // object identity fails in tests
115 ) {
116 throw new Error("RelayMatchTransform: expcted type '".concat(matchedType.name, "' to have a '").concat(JS_FIELD_NAME, "(").concat(JS_FIELD_ARG, ": String!): ").concat(JS_FIELD_TYPE, "' field ."));
117 }
118
119 var moduleDirective = matchSelection.directives.find(function (directive) {
120 return directive.name === 'module';
121 });
122
123 if (moduleDirective == null || matchSelection.directives.length !== 1) {
124 throw new Error('RelayMatchTransform: Fragment spreads in a @match field must have a ' + "'@module' directive and no other directives, got invalid directives " + "on fragment spread '...".concat(matchSelection.name, "'"));
125 }
126
127 var moduleDirectiveArgs = require("./getLiteralArgumentValues")(moduleDirective.args);
128
129 typeToSelectionMap[String(matchedType)] = {
130 component: moduleDirectiveArgs.name,
131 fragment: matchSelection.name
132 };
133 var normalizationName = require("./getNormalizationOperationName")(matchSelection.name) + '.graphql';
134 var moduleField = {
135 alias: '__match_component',
136 args: [{
137 kind: 'Argument',
138 name: JS_FIELD_ARG,
139 type: jsFieldArg.type,
140 value: {
141 kind: 'Literal',
142 loc: (_ref = (_moduleDirective$args = moduleDirective.args[0]) === null || _moduleDirective$args === void 0 ? void 0 : _moduleDirective$args.loc) !== null && _ref !== void 0 ? _ref : moduleDirective.loc,
143 metadata: {},
144 value: moduleDirectiveArgs.name
145 },
146 loc: moduleDirective.loc,
147 metadata: {}
148 }],
149 directives: [],
150 handles: null,
151 kind: 'ScalarField',
152 loc: moduleDirective.loc,
153 metadata: {
154 storageKey: '__match_component'
155 },
156 name: JS_FIELD_NAME,
157 type: jsModuleType
158 };
159 var fragmentField = {
160 alias: '__match_fragment',
161 args: [{
162 kind: 'Argument',
163 name: JS_FIELD_ARG,
164 type: jsFieldArg.type,
165 value: {
166 kind: 'Literal',
167 loc: matchSelection.loc,
168 metadata: {},
169 value: normalizationName
170 },
171 loc: matchSelection.loc,
172 metadata: {}
173 }],
174 directives: [],
175 handles: null,
176 kind: 'ScalarField',
177 loc: matchSelection.loc,
178 metadata: {
179 storageKey: '__match_fragment'
180 },
181 name: JS_FIELD_NAME,
182 type: jsModuleType
183 };
184 selections.push({
185 kind: 'MatchBranch',
186 loc: matchSelection.loc,
187 module: moduleDirectiveArgs.name,
188 name: matchSelection.name,
189 selections: [{
190 args: [],
191 directives: [],
192 kind: 'FragmentSpread',
193 loc: matchSelection.loc,
194 metadata: {},
195 name: matchSelection.name
196 }, {
197 directives: [],
198 kind: 'InlineFragment',
199 loc: matchSelection.loc,
200 metadata: {},
201 selections: [moduleField, fragmentField],
202 typeCondition: matchedType
203 }],
204 type: matchedType
205 });
206 });
207 var stableArgs = [];
208 Object.keys(typeToSelectionMap).sort().forEach(function (typeName) {
209 var _typeToSelectionMap$t = typeToSelectionMap[typeName],
210 component = _typeToSelectionMap$t.component,
211 fragment = _typeToSelectionMap$t.fragment;
212 stableArgs.push("".concat(fragment, ":").concat(component));
213 });
214 var storageKey = ((_transformedNode$alia = transformedNode.alias) !== null && _transformedNode$alia !== void 0 ? _transformedNode$alia : transformedNode.name) + "(".concat(stableArgs.join(','), ")");
215 var matchField = {
216 kind: 'MatchField',
217 alias: transformedNode.alias,
218 args: [{
219 kind: 'Argument',
220 name: SUPPORTED_ARGUMENT_NAME,
221 type: supportedArg.type,
222 value: {
223 kind: 'Literal',
224 loc: node.loc,
225 metadata: {},
226 value: Array.from(seenTypes.keys()).map(function (type) {
227 return type.name;
228 })
229 },
230 loc: node.loc,
231 metadata: {}
232 }],
233 directives: [],
234 handles: null,
235 loc: node.loc,
236 metadata: {
237 storageKey: storageKey
238 },
239 name: transformedNode.name,
240 type: unionType,
241 selections: selections
242 }; // $FlowFixMe intentionally changing the result type in this transform
243
244 return matchField;
245}
246
247module.exports = {
248 SCHEMA_EXTENSION: SCHEMA_EXTENSION,
249 transform: relayMatchTransform
250};
\No newline at end of file