@ardatan/relay-compiler
Version:
A compiler tool for building GraphQL-driven applications.
472 lines (385 loc) • 13.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 CodeMarker = require('../util/CodeMarker');
var argumentContainsVariables = require('../util/argumentContainsVariables');
var generateAbstractTypeRefinementKey = require('../util/generateAbstractTypeRefinementKey');
var _require = require('../core/CompilerError'),
createCompilerError = _require.createCompilerError,
createUserError = _require.createUserError;
var _require2 = require('relay-runtime'),
getStorageKey = _require2.getStorageKey,
stableCopy = _require2.stableCopy;
/**
* @public
*
* Converts an IR node into a plain JS object representation that can be
* used at runtime.
*/
function generate(schema, node) {
if (node == null) {
return node;
}
var metadata = null;
if (node.metadata != null) {
var _node$metadata4 = node.metadata,
mask = _node$metadata4.mask,
plural = _node$metadata4.plural,
connection = _node$metadata4.connection,
refetch = _node$metadata4.refetch;
if (Array.isArray(connection)) {
var _metadata;
metadata = (_metadata = metadata) !== null && _metadata !== void 0 ? _metadata : {};
metadata.connection = connection;
}
if (typeof mask === 'boolean') {
var _metadata2;
metadata = (_metadata2 = metadata) !== null && _metadata2 !== void 0 ? _metadata2 : {};
metadata.mask = mask;
}
if (plural === true) {
var _metadata3;
metadata = (_metadata3 = metadata) !== null && _metadata3 !== void 0 ? _metadata3 : {};
metadata.plural = true;
}
if (refetch != null && typeof refetch === 'object') {
var _metadata4;
metadata = (_metadata4 = metadata) !== null && _metadata4 !== void 0 ? _metadata4 : {};
metadata.refetch = {
connection: refetch.connection,
fragmentPathInResult: refetch.fragmentPathInResult,
operation: CodeMarker.moduleDependency( // $FlowFixMe[unclear-addition]
refetch.operation + '.graphql')
};
if (typeof refetch.identifierField === 'string') {
metadata.refetch = (0, _objectSpread2["default"])((0, _objectSpread2["default"])({}, metadata.refetch), {}, {
identifierField: refetch.identifierField
});
}
}
}
var rawType = schema.getRawType(node.type);
return {
argumentDefinitions: generateArgumentDefinitions(schema, node.argumentDefinitions),
kind: 'Fragment',
// $FlowFixMe[incompatible-return]
metadata: metadata,
name: node.name,
selections: generateSelections(schema, node.selections),
type: schema.getTypeString(rawType),
abstractKey: schema.isAbstractType(rawType) ? generateAbstractTypeRefinementKey(schema, rawType) : null
};
}
function generateSelections(schema, selections) {
return selections.map(function (selection) {
var _selection$metadata;
switch (selection.kind) {
case 'ClientExtension':
return generateClientExtension(schema, selection);
case 'FragmentSpread':
return generateFragmentSpread(schema, selection);
case 'Condition':
return generateCondition(schema, selection);
case 'ScalarField':
// NOTE: The type discriminator is used only for the
// normalization ast.
var isTypeDiscriminator = ((_selection$metadata = selection.metadata) === null || _selection$metadata === void 0 ? void 0 : _selection$metadata.abstractKey) != null;
if (isTypeDiscriminator) {
return null;
}
return generateScalarField(schema, selection);
case 'ModuleImport':
return generateModuleImport(schema, selection);
case 'InlineDataFragmentSpread':
return generateInlineDataFragmentSpread(schema, selection);
case 'InlineFragment':
return generateInlineFragment(schema, selection);
case 'LinkedField':
return generateLinkedField(schema, selection);
case 'Defer':
return generateDefer(schema, selection);
case 'Stream':
return generateStream(schema, selection);
default:
selection;
throw new Error();
}
}).filter(Boolean);
}
function generateArgumentDefinitions(schema, nodes) {
return nodes.map(function (node) {
switch (node.kind) {
case 'LocalArgumentDefinition':
return {
defaultValue: stableCopy(node.defaultValue),
kind: 'LocalArgument',
name: node.name
};
case 'RootArgumentDefinition':
return {
kind: 'RootArgument',
name: node.name
};
default:
throw new Error();
}
}).sort(function (nodeA, nodeB) {
if (nodeA.name > nodeB.name) {
return 1;
}
if (nodeA.name < nodeB.name) {
return -1;
}
return 0;
});
}
function generateClientExtension(schema, node) {
return {
kind: 'ClientExtension',
selections: generateSelections(schema, node.selections)
};
}
function generateDefer(schema, node) {
return {
kind: 'Defer',
selections: generateSelections(schema, node.selections)
};
}
function generateStream(schema, node) {
return {
kind: 'Stream',
selections: generateSelections(schema, node.selections)
};
}
function generateCondition(schema, node) {
if (node.condition.kind !== 'Variable') {
throw createCompilerError("ReaderCodeGenerator: 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 generateFragmentSpread(schema, node) {
return {
args: generateArgs(node.args),
kind: 'FragmentSpread',
name: node.name
};
}
function generateInlineFragment(schema, node) {
var rawType = schema.getRawType(node.typeCondition);
return {
kind: 'InlineFragment',
selections: generateSelections(schema, node.selections),
type: schema.getTypeString(rawType),
abstractKey: schema.isAbstractType(rawType) ? generateAbstractTypeRefinementKey(schema, rawType) : null
};
}
function generateInlineDataFragmentSpread(schema, node) {
return {
kind: 'InlineDataFragmentSpread',
name: node.name,
selections: generateSelections(schema, node.selections)
};
}
function generateLinkedField(schema, node) {
var _node$metadata;
// 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.
// TODO(T37646905) enable this invariant after splitting the
// RelayCodeGenerator-test and running the FieldHandleTransform on
// Reader ASTs.
//
// invariant(
// node.handles == null,
// 'ReaderCodeGenerator: unexpected handles',
// );
var rawType = schema.getRawType(node.type);
var field = {
alias: node.alias === node.name ? null : node.alias,
args: generateArgs(node.args),
concreteType: !schema.isAbstractType(rawType) ? schema.getTypeString(rawType) : 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) {
field = (0, _objectSpread2["default"])((0, _objectSpread2["default"])({}, field), {}, {
storageKey: storageKey
});
}
var requiredMetadata = (_node$metadata = node.metadata) === null || _node$metadata === void 0 ? void 0 : _node$metadata.required;
if (requiredMetadata != null) {
return createRequiredField(field, requiredMetadata);
}
return field;
}
function createRequiredField(field, requiredMetadata) {
return {
kind: 'RequiredField',
field: field,
action: requiredMetadata.action,
path: requiredMetadata.path
};
}
function generateModuleImport(schema, 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('ReaderCodeGenerator: @match fragments should be named ' + "'FragmentName_propName', got '".concat(fragmentName, "'."), [node.loc]);
}
var fragmentPropName = regExpMatch[2];
if (typeof fragmentPropName !== 'string') {
throw createCompilerError('ReaderCodeGenerator: @module fragments should be named ' + "'FragmentName_propName', got '".concat(fragmentName, "'."), [node.loc]);
}
return {
documentName: node.key,
fragmentName: fragmentName,
fragmentPropName: fragmentPropName,
kind: 'ModuleImport'
};
}
function generateScalarField(schema, node) {
var _node$metadata2, _node$metadata3;
// 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.
// TODO(T37646905) enable this invariant after splitting the
// RelayCodeGenerator-test and running the FieldHandleTransform on
// Reader ASTs.
//
// invariant(
// node.handles == null,
// 'ReaderCodeGenerator: unexpected handles',
// );
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) {
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'
});
}
var requiredMetadata = (_node$metadata3 = node.metadata) === null || _node$metadata3 === void 0 ? void 0 : _node$metadata3.required;
if (requiredMetadata != null) {
if (field.kind === 'FlightField') {
throw new createUserError('@required cannot be used on a ReactFlightComponent.', [node.loc]);
}
return createRequiredField(field, requiredMetadata);
}
return field;
}
function generateArgument(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 _generateArgument;
var fieldValue = objectValues.get(fieldName);
if (fieldValue == null) {
throw createCompilerError('Expected to have object field value');
}
return (_generateArgument = generateArgument(fieldName, fieldValue)) !== null && _generateArgument !== void 0 ? _generateArgument : {
kind: 'Literal',
name: fieldName,
value: null
};
}),
kind: 'ObjectValue',
name: name
};
}
case 'ListValue':
{
return {
items: value.items.map(function (item, index) {
return generateArgument("".concat(name, ".").concat(index), item);
}),
kind: 'ListValue',
name: name
};
}
default:
throw createUserError('ReaderCodeGenerator: 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 = generateArgument(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
};