272 lines
10 KiB
JavaScript
272 lines
10 KiB
JavaScript
/**
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* strict-local
|
|
* @format
|
|
*/
|
|
'use strict';
|
|
|
|
var CompilerContext = require("./GraphQLCompilerContext");
|
|
|
|
var IRTransformer = require("./GraphQLIRTransformer");
|
|
|
|
var getLiteralArgumentValues = require("./getLiteralArgumentValues");
|
|
|
|
var getNormalizationOperationName = require("./getNormalizationOperationName");
|
|
|
|
var _require = require("./GraphQLSchemaUtils"),
|
|
getRawType = _require.getRawType;
|
|
|
|
var _require2 = require("./RelayCompilerError"),
|
|
createUserError = _require2.createUserError;
|
|
|
|
var _require3 = require("graphql"),
|
|
GraphQLObjectType = _require3.GraphQLObjectType,
|
|
GraphQLScalarType = _require3.GraphQLScalarType,
|
|
GraphQLInterfaceType = _require3.GraphQLInterfaceType,
|
|
GraphQLUnionType = _require3.GraphQLUnionType,
|
|
GraphQLList = _require3.GraphQLList,
|
|
GraphQLString = _require3.GraphQLString,
|
|
getNullableType = _require3.getNullableType;
|
|
|
|
var SUPPORTED_ARGUMENT_NAME = 'supported';
|
|
var JS_FIELD_TYPE = 'JSDependency';
|
|
var JS_FIELD_ARG = 'module';
|
|
var JS_FIELD_NAME = 'js';
|
|
var SCHEMA_EXTENSION = "\n directive @match on FIELD\n\n directive @module(\n name: String!\n ) on FRAGMENT_SPREAD\n";
|
|
/**
|
|
* This transform rewrites LinkedField nodes with @match and rewrites them
|
|
* into MatchField nodes with a `supported` argument and MatchBranch selections.
|
|
*/
|
|
|
|
function relayMatchTransform(context) {
|
|
return IRTransformer.transform(context, {
|
|
// $FlowFixMe this transform intentionally changes the AST node type
|
|
LinkedField: visitLinkedField,
|
|
InlineFragment: visitInlineFragment
|
|
}, function (node) {
|
|
return node.type;
|
|
});
|
|
}
|
|
|
|
function visitInlineFragment(node, state) {
|
|
return this.traverse(node, node.typeCondition);
|
|
}
|
|
|
|
function visitLinkedField(node, parentType) {
|
|
var _transformedNode$alia;
|
|
|
|
var transformedNode = this.traverse(node, node.type);
|
|
var matchDirective = transformedNode.directives.find(function (directive) {
|
|
return directive.name === 'match';
|
|
});
|
|
|
|
if (matchDirective == null) {
|
|
return transformedNode;
|
|
}
|
|
|
|
var rawType = getRawType(parentType);
|
|
|
|
if (!(rawType instanceof GraphQLInterfaceType || rawType instanceof GraphQLObjectType)) {
|
|
throw 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]);
|
|
}
|
|
|
|
var context = this.getContext();
|
|
var schema = context.serverSchema;
|
|
var jsModuleType = schema.getType(JS_FIELD_TYPE);
|
|
|
|
if (jsModuleType == null || !(jsModuleType instanceof GraphQLScalarType)) {
|
|
throw new Error("RelayMatchTransform: Expected schema to define a scalar '".concat(JS_FIELD_TYPE, "' type."));
|
|
}
|
|
|
|
var currentField = rawType.getFields()[transformedNode.name];
|
|
var supportedArg = currentField.args.find(function (_ref2) {
|
|
var name = _ref2.name;
|
|
return SUPPORTED_ARGUMENT_NAME;
|
|
});
|
|
var supportedArgType = supportedArg != null ? getNullableType(supportedArg.type) : null;
|
|
var supportedArgOfType = supportedArgType != null && supportedArgType instanceof GraphQLList ? supportedArgType.ofType : null;
|
|
|
|
if (supportedArg == null || supportedArgType == null || supportedArgOfType == null || getNullableType(supportedArgOfType) !== GraphQLString) {
|
|
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!]!'.");
|
|
}
|
|
|
|
var unionType = transformedNode.type;
|
|
|
|
if (!(unionType instanceof GraphQLUnionType)) {
|
|
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.');
|
|
}
|
|
|
|
var seenTypes = new Map();
|
|
var typeToSelectionMap = {};
|
|
var selections = [];
|
|
transformedNode.selections.forEach(function (matchSelection) {
|
|
var _ref, _moduleDirective$args;
|
|
|
|
if (matchSelection.kind !== 'FragmentSpread') {
|
|
throw new Error('RelayMatchTransform: all selections in a @match field should be ' + "fragment spreads, got '".concat(matchSelection.kind, "'."));
|
|
}
|
|
|
|
var fragment = context.getFragment(matchSelection.name);
|
|
|
|
if (!(fragment.type instanceof GraphQLObjectType)) {
|
|
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."));
|
|
}
|
|
|
|
var matchedType = fragment.type;
|
|
|
|
if (seenTypes.has(matchedType)) {
|
|
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)', "'."));
|
|
}
|
|
|
|
seenTypes.set(matchedType, matchSelection.name);
|
|
var belongsToUnion = unionType.getTypes().includes(matchedType);
|
|
|
|
if (!belongsToUnion) {
|
|
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(), "\"."));
|
|
}
|
|
|
|
var jsField = matchedType.getFields()[JS_FIELD_NAME];
|
|
var jsFieldArg = jsField ? jsField.args.find(function (arg) {
|
|
return arg.name === JS_FIELD_ARG;
|
|
}) : null;
|
|
|
|
if (jsField == null || jsFieldArg == null || getNullableType(jsFieldArg.type) !== GraphQLString || jsField.type.name !== jsModuleType.name // object identity fails in tests
|
|
) {
|
|
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 ."));
|
|
}
|
|
|
|
var moduleDirective = matchSelection.directives.find(function (directive) {
|
|
return directive.name === 'module';
|
|
});
|
|
|
|
if (moduleDirective == null || matchSelection.directives.length !== 1) {
|
|
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, "'"));
|
|
}
|
|
|
|
var moduleDirectiveArgs = getLiteralArgumentValues(moduleDirective.args);
|
|
typeToSelectionMap[String(matchedType)] = {
|
|
component: moduleDirectiveArgs.name,
|
|
fragment: matchSelection.name
|
|
};
|
|
var normalizationName = getNormalizationOperationName(matchSelection.name) + '.graphql';
|
|
var moduleField = {
|
|
alias: '__match_component',
|
|
args: [{
|
|
kind: 'Argument',
|
|
name: JS_FIELD_ARG,
|
|
type: jsFieldArg.type,
|
|
value: {
|
|
kind: 'Literal',
|
|
loc: (_ref = (_moduleDirective$args = moduleDirective.args[0]) === null || _moduleDirective$args === void 0 ? void 0 : _moduleDirective$args.loc) !== null && _ref !== void 0 ? _ref : moduleDirective.loc,
|
|
metadata: {},
|
|
value: moduleDirectiveArgs.name
|
|
},
|
|
loc: moduleDirective.loc,
|
|
metadata: {}
|
|
}],
|
|
directives: [],
|
|
handles: null,
|
|
kind: 'ScalarField',
|
|
loc: moduleDirective.loc,
|
|
metadata: {
|
|
storageKey: '__match_component'
|
|
},
|
|
name: JS_FIELD_NAME,
|
|
type: jsModuleType
|
|
};
|
|
var fragmentField = {
|
|
alias: '__match_fragment',
|
|
args: [{
|
|
kind: 'Argument',
|
|
name: JS_FIELD_ARG,
|
|
type: jsFieldArg.type,
|
|
value: {
|
|
kind: 'Literal',
|
|
loc: matchSelection.loc,
|
|
metadata: {},
|
|
value: normalizationName
|
|
},
|
|
loc: matchSelection.loc,
|
|
metadata: {}
|
|
}],
|
|
directives: [],
|
|
handles: null,
|
|
kind: 'ScalarField',
|
|
loc: matchSelection.loc,
|
|
metadata: {
|
|
storageKey: '__match_fragment'
|
|
},
|
|
name: JS_FIELD_NAME,
|
|
type: jsModuleType
|
|
};
|
|
selections.push({
|
|
kind: 'MatchBranch',
|
|
loc: matchSelection.loc,
|
|
module: moduleDirectiveArgs.name,
|
|
name: matchSelection.name,
|
|
selections: [{
|
|
args: [],
|
|
directives: [],
|
|
kind: 'FragmentSpread',
|
|
loc: matchSelection.loc,
|
|
metadata: {},
|
|
name: matchSelection.name
|
|
}, {
|
|
directives: [],
|
|
kind: 'InlineFragment',
|
|
loc: matchSelection.loc,
|
|
metadata: {},
|
|
selections: [moduleField, fragmentField],
|
|
typeCondition: matchedType
|
|
}],
|
|
type: matchedType
|
|
});
|
|
});
|
|
var stableArgs = [];
|
|
Object.keys(typeToSelectionMap).sort().forEach(function (typeName) {
|
|
var _typeToSelectionMap$t = typeToSelectionMap[typeName],
|
|
component = _typeToSelectionMap$t.component,
|
|
fragment = _typeToSelectionMap$t.fragment;
|
|
stableArgs.push("".concat(fragment, ":").concat(component));
|
|
});
|
|
var storageKey = ((_transformedNode$alia = transformedNode.alias) !== null && _transformedNode$alia !== void 0 ? _transformedNode$alia : transformedNode.name) + "(".concat(stableArgs.join(','), ")");
|
|
var matchField = {
|
|
kind: 'MatchField',
|
|
alias: transformedNode.alias,
|
|
args: [{
|
|
kind: 'Argument',
|
|
name: SUPPORTED_ARGUMENT_NAME,
|
|
type: supportedArg.type,
|
|
value: {
|
|
kind: 'Literal',
|
|
loc: node.loc,
|
|
metadata: {},
|
|
value: Array.from(seenTypes.keys()).map(function (type) {
|
|
return type.name;
|
|
})
|
|
},
|
|
loc: node.loc,
|
|
metadata: {}
|
|
}],
|
|
directives: [],
|
|
handles: null,
|
|
loc: node.loc,
|
|
metadata: {
|
|
storageKey: storageKey
|
|
},
|
|
name: transformedNode.name,
|
|
type: unionType,
|
|
selections: selections
|
|
}; // $FlowFixMe intentionally changing the result type in this transform
|
|
|
|
return matchField;
|
|
}
|
|
|
|
module.exports = {
|
|
SCHEMA_EXTENSION: SCHEMA_EXTENSION,
|
|
transform: relayMatchTransform
|
|
}; |