191 lines
6.2 KiB
JavaScript
191 lines
6.2 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.
|
|
*
|
|
* All rights reserved.
|
|
*
|
|
*
|
|
* @format
|
|
*/
|
|
'use strict';
|
|
|
|
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
|
|
var _objectSpread2 = _interopRequireDefault(require("@babel/runtime/helpers/objectSpread"));
|
|
|
|
var CompilerContext = require("./GraphQLCompilerContext");
|
|
|
|
var IRTransformer = require("./GraphQLIRTransformer");
|
|
|
|
var invariant = require("fbjs/lib/invariant");
|
|
|
|
var _require = require("graphql"),
|
|
isTypeSubTypeOf = _require.isTypeSubTypeOf,
|
|
GraphQLSchema = _require.GraphQLSchema;
|
|
|
|
/**
|
|
* A transform that inlines fragment spreads with the @relay(mask: false)
|
|
* directive.
|
|
*/
|
|
function relayMaskTransform(context) {
|
|
return IRTransformer.transform(context, {
|
|
FragmentSpread: visitFragmentSpread,
|
|
Fragment: visitFragment
|
|
}, function () {
|
|
return {
|
|
reachableArguments: []
|
|
};
|
|
});
|
|
}
|
|
|
|
function visitFragment(fragment, state) {
|
|
var result = this.traverse(fragment, state);
|
|
|
|
if (state.reachableArguments.length === 0) {
|
|
return result;
|
|
}
|
|
|
|
var schema = this.getContext().serverSchema;
|
|
var joinedArgumentDefinitions = joinFragmentArgumentDefinitions(schema, fragment, state.reachableArguments);
|
|
return (0, _objectSpread2["default"])({}, result, {
|
|
argumentDefinitions: joinedArgumentDefinitions
|
|
});
|
|
}
|
|
|
|
function visitFragmentSpread(fragmentSpread, state) {
|
|
if (!isUnmaskedSpread(fragmentSpread)) {
|
|
return fragmentSpread;
|
|
}
|
|
|
|
!(fragmentSpread.args.length === 0) ? process.env.NODE_ENV !== "production" ? invariant(false, 'RelayMaskTransform: Cannot unmask fragment spread `%s` with ' + 'arguments. Use the `ApplyFragmentArgumentTransform` before flattening', fragmentSpread.name) : invariant(false) : void 0;
|
|
var context = this.getContext();
|
|
var fragment = context.getFragment(fragmentSpread.name);
|
|
var result = {
|
|
kind: 'InlineFragment',
|
|
directives: fragmentSpread.directives,
|
|
loc: {
|
|
kind: 'Derived',
|
|
source: fragmentSpread.loc
|
|
},
|
|
metadata: fragmentSpread.metadata,
|
|
selections: fragment.selections,
|
|
typeCondition: fragment.type
|
|
};
|
|
!!fragment.argumentDefinitions.find(function (argDef) {
|
|
return argDef.kind === 'LocalArgumentDefinition';
|
|
}) ? process.env.NODE_ENV !== "production" ? invariant(false, 'RelayMaskTransform: Cannot unmask fragment spread `%s` because it has local ' + 'argument definition.', fragmentSpread.name) : invariant(false) : void 0; // Note: defer validating arguments to the containing fragment in order
|
|
// to list all invalid variables/arguments instead of only one.
|
|
|
|
var _iteratorNormalCompletion = true;
|
|
var _didIteratorError = false;
|
|
var _iteratorError = undefined;
|
|
|
|
try {
|
|
for (var _iterator = fragment.argumentDefinitions[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
|
|
var argDef = _step.value;
|
|
state.reachableArguments.push({
|
|
argDef: argDef,
|
|
source: fragmentSpread.name
|
|
});
|
|
}
|
|
} catch (err) {
|
|
_didIteratorError = true;
|
|
_iteratorError = err;
|
|
} finally {
|
|
try {
|
|
if (!_iteratorNormalCompletion && _iterator["return"] != null) {
|
|
_iterator["return"]();
|
|
}
|
|
} finally {
|
|
if (_didIteratorError) {
|
|
throw _iteratorError;
|
|
}
|
|
}
|
|
}
|
|
|
|
return this.traverse(result, state);
|
|
}
|
|
/**
|
|
* @private
|
|
*/
|
|
|
|
|
|
function isUnmaskedSpread(spread) {
|
|
return Boolean(spread.metadata && spread.metadata.mask === false);
|
|
}
|
|
/**
|
|
* @private
|
|
*
|
|
* Attempts to join the argument definitions for a root fragment
|
|
* and any unmasked fragment spreads reachable from that root fragment,
|
|
* returning a combined list of arguments or throwing if the same
|
|
* variable(s) are used in incompatible ways in different fragments.
|
|
*/
|
|
|
|
|
|
function joinFragmentArgumentDefinitions(schema, fragment, reachableArguments) {
|
|
var joinedArgumentDefinitions = new Map();
|
|
fragment.argumentDefinitions.forEach(function (prevArgDef) {
|
|
joinedArgumentDefinitions.set(prevArgDef.name, prevArgDef);
|
|
});
|
|
var errors = [];
|
|
reachableArguments.forEach(function (nextArg) {
|
|
var nextArgDef = nextArg.argDef,
|
|
source = nextArg.source;
|
|
var prevArgDef = joinedArgumentDefinitions.get(nextArgDef.name);
|
|
|
|
if (prevArgDef) {
|
|
var joinedArgDef = joinArgumentDefinition(schema, prevArgDef, nextArgDef);
|
|
|
|
if (joinedArgDef === null) {
|
|
errors.push("Variable `$".concat(nextArgDef.name, "` in `").concat(source, "`"));
|
|
} else {
|
|
joinedArgumentDefinitions.set(joinedArgDef.name, joinedArgDef);
|
|
}
|
|
} else {
|
|
joinedArgumentDefinitions.set(nextArgDef.name, nextArgDef);
|
|
}
|
|
});
|
|
|
|
if (errors.length) {
|
|
throw new Error('RelayMaskTransform: Cannot unmask one or more fragments in ' + "`".concat(fragment.name, "`, the following variables are referenced more ") + 'than once with incompatible kinds/types:\n' + errors.map(function (msg) {
|
|
return "* ".concat(msg);
|
|
}).join('\n'));
|
|
}
|
|
|
|
return Array.from(joinedArgumentDefinitions.values());
|
|
}
|
|
/**
|
|
* @private
|
|
*
|
|
* Attempts to join two argument definitions, returning a single argument
|
|
* definition that is compatible with both of the inputs:
|
|
* - If the kind, name, or defaultValue is different then the arguments
|
|
* cannot be joined, indicated by returning null.
|
|
* - If either of next/prev is a subtype of the other, return the one
|
|
* that is the subtype: a more narrow type can flow into a more general
|
|
* type but not the inverse.
|
|
* - Otherwise there is no subtyping relation between prev/next, so return
|
|
* null to indicate they cannot be joined.
|
|
*/
|
|
|
|
|
|
function joinArgumentDefinition(schema, prevArgDef, nextArgDef) {
|
|
if (prevArgDef.kind !== nextArgDef.kind || prevArgDef.name !== nextArgDef.name || // Only LocalArgumentDefinition defines defaultValue
|
|
prevArgDef.defaultValue !== nextArgDef.defaultValue) {
|
|
return null;
|
|
} else if (isTypeSubTypeOf(schema, nextArgDef.type, prevArgDef.type)) {
|
|
// prevArgDef is less strict than nextArgDef
|
|
return nextArgDef;
|
|
} else if (isTypeSubTypeOf(schema, prevArgDef.type, nextArgDef.type)) {
|
|
return prevArgDef;
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
transform: relayMaskTransform
|
|
}; |