@ardatan/relay-compiler
Version:
A compiler tool for building GraphQL-driven applications.
480 lines (395 loc) • 15.7 kB
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.
*
*
* @format
*/
// flowlint ambiguous-object-type:error
;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _objectSpread2 = _interopRequireDefault(require("@babel/runtime/helpers/objectSpread2"));
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
var argumentContainsVariables = require('../util/argumentContainsVariables');
var generateAbstractTypeRefinementKey = require('../util/generateAbstractTypeRefinementKey');
var partitionArray = require('../util/partitionArray');
var sortObjectByKey = require('./sortObjectByKey');
var _require = require('../core/CompilerError'),
createCompilerError = _require.createCompilerError,
createUserError = _require.createUserError;
var _require2 = require('relay-runtime'),
getStorageKey = _require2.getStorageKey,
stableCopy = _require2.stableCopy;
function generate(schema, node) {
switch (node.kind) {
case 'Root':
return generateRoot(schema, node);
case 'SplitOperation':
return generateSplitOperation(schema, node);
default:
throw createCompilerError("NormalizationCodeGenerator: Unsupported AST kind '".concat(node.kind, "'."), [node.loc]);
}
}
function generateRoot(schema, node) {
return {
argumentDefinitions: generateArgumentDefinitions(schema, node.argumentDefinitions),
kind: 'Operation',
name: node.name,
selections: generateSelections(schema, node.selections)
};
}
function generateSplitOperation(schema, node) {
return {
kind: 'SplitOperation',
metadata: sortObjectByKey(node.metadata),
name: node.name,
selections: generateSelections(schema, node.selections)
};
}
function generateSelections(schema, selections) {
var normalizationSelections = [];
selections.forEach(function (selection) {
var _selection$metadata;
switch (selection.kind) {
case 'Condition':
normalizationSelections.push(generateCondition(schema, selection));
break;
case 'ClientExtension':
normalizationSelections.push(generateClientExtension(schema, selection));
break;
case 'ScalarField':
// NOTE: Inline fragments in normalization ast have the abstractKey
// but we skip the corresponding ScalarField for the type discriminator
// selection, since it's guaranteed to be a duplicate of a parent __typename
// selection.
var abstractKey = (_selection$metadata = selection.metadata) === null || _selection$metadata === void 0 ? void 0 : _selection$metadata.abstractKey;
if (typeof abstractKey === 'string') {
normalizationSelections.push(generateTypeDiscriminator(abstractKey));
} else {
normalizationSelections.push.apply(normalizationSelections, (0, _toConsumableArray2["default"])(generateScalarField(selection)));
}
break;
case 'ModuleImport':
normalizationSelections.push(generateModuleImport(selection));
break;
case 'InlineFragment':
normalizationSelections.push(generateInlineFragment(schema, selection));
break;
case 'LinkedField':
normalizationSelections.push.apply(normalizationSelections, (0, _toConsumableArray2["default"])(generateLinkedField(schema, selection)));
break;
case 'Defer':
normalizationSelections.push(generateDefer(schema, selection));
break;
case 'Stream':
normalizationSelections.push(generateStream(schema, selection));
break;
case 'InlineDataFragmentSpread':
case 'FragmentSpread':
throw new createCompilerError("NormalizationCodeGenerator: Unexpected IR node ".concat(selection.kind, "."), [selection.loc]);
default:
selection;
throw new Error();
}
});
return normalizationSelections;
}
function generateArgumentDefinitions(schema, nodes) {
return nodes.map(function (node) {
return {
defaultValue: stableCopy(node.defaultValue),
kind: 'LocalArgument',
name: node.name
};
});
}
function generateClientExtension(schema, node) {
return {
kind: 'ClientExtension',
selections: generateSelections(schema, node.selections)
};
}
function generateCondition(schema, node) {
if (node.condition.kind !== 'Variable') {
throw createCompilerError("NormalizationCodeGenerator: Expected 'Condition' with static " + 'value to be pruned or inlined', [node.condition.loc]);
}
return {
condition: node.condition.variableName,
kind: 'Condition',
passingValue: node.passingValue,
selections: generateSelections(schema, node.selections)
};
}
function generateDefer(schema, node) {
if (!(node["if"] == null || node["if"].kind === 'Variable' || node["if"].kind === 'Literal' && node["if"].value === true)) {
var _node$if$loc, _node$if;
throw createCompilerError('NormalizationCodeGenerator: Expected @defer `if` condition to be ' + 'a variable, unspecified, or the literal `true`.', [(_node$if$loc = (_node$if = node["if"]) === null || _node$if === void 0 ? void 0 : _node$if.loc) !== null && _node$if$loc !== void 0 ? _node$if$loc : node.loc]);
}
return {
"if": node["if"] != null && node["if"].kind === 'Variable' ? node["if"].variableName : null,
kind: 'Defer',
label: node.label,
selections: generateSelections(schema, node.selections)
};
}
function generateInlineFragment(schema, node) {
var rawType = schema.getRawType(node.typeCondition);
var isAbstractType = schema.isAbstractType(rawType);
var abstractKey = isAbstractType ? generateAbstractTypeRefinementKey(schema, rawType) : null;
var selections = generateSelections(schema, node.selections);
if (isAbstractType) {
// Maintain a few invariants:
// - InlineFragment (and `selections` arrays generally) cannot be empty
// - Don't emit a TypeDiscriminator under an InlineFragment unless it has
// a different abstractKey
// This means we have to handle two cases:
// - The inline fragment only contains a TypeDiscriminator with the same
// abstractKey: replace the Fragment w the Discriminator
// - The inline fragment contains other selections: return all the selections
// minus any Discriminators w the same key
var _partitionArray = partitionArray(selections, function (selection) {
return selection.kind === 'TypeDiscriminator' && selection.abstractKey === abstractKey;
}),
discriminators = _partitionArray[0],
otherSelections = _partitionArray[1];
var discriminator = discriminators[0];
if (discriminator != null && otherSelections.length === 0) {
return discriminator;
} else {
selections = otherSelections;
}
}
return {
kind: 'InlineFragment',
selections: selections,
type: schema.getTypeString(rawType),
abstractKey: abstractKey
};
}
function generateLinkedField(schema, node) {
// Note: it is important that the arguments of this field be sorted to
// ensure stable generation of storage keys for equivalent arguments
// which may have originally appeared in different orders across an app.
var handles = node.handles && node.handles.map(function (handle) {
var handleNode = {
alias: node.alias === node.name ? null : node.alias,
args: generateArgs(node.args),
filters: handle.filters,
handle: handle.name,
key: handle.key,
kind: 'LinkedHandle',
name: node.name
}; // NOTE: this intentionally adds a dynamic key in order to avoid
// triggering updates to existing queries that do not use dynamic
// keys.
if (handle.dynamicKey != null) {
var dynamicKeyArgName = '__dynamicKey';
handleNode = (0, _objectSpread2["default"])((0, _objectSpread2["default"])({}, handleNode), {}, {
dynamicKey: {
kind: 'Variable',
name: dynamicKeyArgName,
variableName: handle.dynamicKey.variableName
}
});
}
if (handle.handleArgs != null) {
var handleArgs = generateArgs(handle.handleArgs);
if (handleArgs != null) {
handleNode = (0, _objectSpread2["default"])((0, _objectSpread2["default"])({}, handleNode), {}, {
handleArgs: handleArgs
});
}
}
return handleNode;
}) || [];
var type = schema.getRawType(node.type);
var field = {
alias: node.alias === node.name ? null : node.alias,
args: generateArgs(node.args),
concreteType: !schema.isAbstractType(type) ? schema.getTypeString(type) : null,
kind: 'LinkedField',
name: node.name,
plural: isPlural(schema, node.type),
selections: generateSelections(schema, node.selections),
storageKey: null
}; // Precompute storageKey if possible
var storageKey = getStaticStorageKey(field, node.metadata);
if (storageKey != null) {
field = (0, _objectSpread2["default"])((0, _objectSpread2["default"])({}, field), {}, {
storageKey: storageKey
});
}
return [field].concat(handles);
}
function generateModuleImport(node) {
var fragmentName = node.name;
var regExpMatch = fragmentName.match(/^([a-zA-Z][a-zA-Z0-9]*)(?:_([a-zA-Z][_a-zA-Z0-9]*))?$/);
if (!regExpMatch) {
throw createCompilerError('NormalizationCodeGenerator: @module fragments should be named ' + "'FragmentName_propName', got '".concat(fragmentName, "'."), [node.loc]);
}
var fragmentPropName = regExpMatch[2];
if (typeof fragmentPropName !== 'string') {
throw createCompilerError('NormalizationCodeGenerator: @module fragments should be named ' + "'FragmentName_propName', got '".concat(fragmentName, "'."), [node.loc]);
}
return {
args: null,
documentName: node.key,
fragmentName: fragmentName,
fragmentPropName: fragmentPropName,
kind: 'ModuleImport'
};
}
function generateTypeDiscriminator(abstractKey) {
return {
kind: 'TypeDiscriminator',
abstractKey: abstractKey
};
}
function generateScalarField(node) {
var _node$metadata, _node$metadata2;
// flowlint-next-line sketchy-null-mixed:off
if ((_node$metadata = node.metadata) === null || _node$metadata === void 0 ? void 0 : _node$metadata.skipNormalizationNode) {
return [];
} // Note: it is important that the arguments of this field be sorted to
// ensure stable generation of storage keys for equivalent arguments
// which may have originally appeared in different orders across an app.
var handles = node.handles && node.handles.map(function (handle) {
if (handle.dynamicKey != null) {
throw createUserError('Dynamic key values are not supported on scalar fields.', [handle.dynamicKey.loc]);
}
var nodeHandle = {
alias: node.alias === node.name ? null : node.alias,
args: generateArgs(node.args),
filters: handle.filters,
handle: handle.name,
key: handle.key,
kind: 'ScalarHandle',
name: node.name
};
if (handle.handleArgs != null) {
// $FlowFixMe handleArgs exists in Handle
nodeHandle.handleArgs = generateArgs(handle.handleArgs);
}
return nodeHandle;
}) || [];
var field = {
alias: node.alias === node.name ? null : node.alias,
args: generateArgs(node.args),
kind: 'ScalarField',
name: node.name,
storageKey: null
}; // Precompute storageKey if possible
var storageKey = getStaticStorageKey(field, node.metadata);
if (storageKey != null) {
field = (0, _objectSpread2["default"])((0, _objectSpread2["default"])({}, field), {}, {
storageKey: storageKey
});
}
if (((_node$metadata2 = node.metadata) === null || _node$metadata2 === void 0 ? void 0 : _node$metadata2.flight) === true) {
field = (0, _objectSpread2["default"])((0, _objectSpread2["default"])({}, field), {}, {
kind: 'FlightField'
});
}
return [field].concat(handles);
}
function generateStream(schema, node) {
if (!(node["if"] == null || node["if"].kind === 'Variable' || node["if"].kind === 'Literal' && node["if"].value === true)) {
var _node$if$loc2, _node$if2;
throw createCompilerError('NormalizationCodeGenerator: Expected @stream `if` condition to be ' + 'a variable, unspecified, or the literal `true`.', [(_node$if$loc2 = (_node$if2 = node["if"]) === null || _node$if2 === void 0 ? void 0 : _node$if2.loc) !== null && _node$if$loc2 !== void 0 ? _node$if$loc2 : node.loc]);
}
return {
"if": node["if"] != null && node["if"].kind === 'Variable' ? node["if"].variableName : null,
kind: 'Stream',
label: node.label,
selections: generateSelections(schema, node.selections)
};
}
function generateArgumentValue(name, value) {
switch (value.kind) {
case 'Variable':
return {
kind: 'Variable',
name: name,
variableName: value.variableName
};
case 'Literal':
return value.value === null ? null : {
kind: 'Literal',
name: name,
value: stableCopy(value.value)
};
case 'ObjectValue':
{
var objectKeys = value.fields.map(function (field) {
return field.name;
}).sort();
var objectValues = new Map(value.fields.map(function (field) {
return [field.name, field.value];
}));
return {
fields: objectKeys.map(function (fieldName) {
var _generateArgumentValu;
var fieldValue = objectValues.get(fieldName);
if (fieldValue == null) {
throw createCompilerError('Expected to have object field value');
}
return (_generateArgumentValu = generateArgumentValue(fieldName, fieldValue)) !== null && _generateArgumentValu !== void 0 ? _generateArgumentValu : {
kind: 'Literal',
name: fieldName,
value: null
};
}),
kind: 'ObjectValue',
name: name
};
}
case 'ListValue':
{
return {
items: value.items.map(function (item, index) {
return generateArgumentValue("".concat(name, ".").concat(index), item);
}),
kind: 'ListValue',
name: name
};
}
default:
throw createUserError('NormalizationCodeGenerator: Complex argument values (Lists or ' + 'InputObjects with nested variables) are not supported.', [value.loc]);
}
}
function generateArgs(args) {
var concreteArguments = [];
args.forEach(function (arg) {
var concreteArgument = generateArgumentValue(arg.name, arg.value);
if (concreteArgument !== null) {
concreteArguments.push(concreteArgument);
}
});
return concreteArguments.length === 0 ? null : concreteArguments.sort(nameComparator);
}
function nameComparator(a, b) {
return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
}
/**
* Pre-computes storage key if possible and advantageous. Storage keys are
* generated for fields with supplied arguments that are all statically known
* (ie. literals, no variables) at build time.
*/
function getStaticStorageKey(field, metadata) {
var metadataStorageKey = metadata === null || metadata === void 0 ? void 0 : metadata.storageKey;
if (typeof metadataStorageKey === 'string') {
return metadataStorageKey;
}
if (!field.args || field.args.length === 0 || field.args.some(argumentContainsVariables)) {
return null;
}
return getStorageKey(field, {});
}
function isPlural(schema, type) {
return schema.isList(schema.getNullableType(type));
}
module.exports = {
generate: generate
};