332 lines
10 KiB
JavaScript
332 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.
|
|
*
|
|
*
|
|
* @format
|
|
*/
|
|
'use strict';
|
|
|
|
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
|
|
var _objectSpread2 = _interopRequireDefault(require("@babel/runtime/helpers/objectSpread"));
|
|
|
|
var CodeMarker = require("./CodeMarker");
|
|
|
|
var IRVisitor = require("./GraphQLIRVisitor");
|
|
|
|
var SchemaUtils = require("./GraphQLSchemaUtils");
|
|
|
|
var _require = require("./RelayCompilerError"),
|
|
createCompilerError = _require.createCompilerError,
|
|
createUserError = _require.createUserError;
|
|
|
|
var _require2 = require("graphql"),
|
|
GraphQLList = _require2.GraphQLList;
|
|
|
|
var _require3 = require("relay-runtime"),
|
|
getStorageKey = _require3.getStorageKey,
|
|
stableCopy = _require3.stableCopy;
|
|
|
|
var getRawType = SchemaUtils.getRawType,
|
|
isAbstractType = SchemaUtils.isAbstractType,
|
|
getNullableType = SchemaUtils.getNullableType;
|
|
/**
|
|
* @public
|
|
*
|
|
* Converts a GraphQLIR node into a plain JS object representation that can be
|
|
* used at runtime.
|
|
*/
|
|
|
|
function generate(node) {
|
|
return IRVisitor.visit(node, ReaderCodeGenVisitor);
|
|
}
|
|
|
|
var ReaderCodeGenVisitor = {
|
|
leave: {
|
|
Request: function Request(node) {
|
|
throw createCompilerError('ReaderCodeGenerator: unexpeted Request node.');
|
|
},
|
|
Fragment: function Fragment(node) {
|
|
var metadata = null;
|
|
|
|
if (node.metadata != null) {
|
|
var _node$metadata = node.metadata,
|
|
mask = _node$metadata.mask,
|
|
plural = _node$metadata.plural,
|
|
connection = _node$metadata.connection,
|
|
refetch = _node$metadata.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 (typeof plural === 'boolean') {
|
|
var _metadata3;
|
|
|
|
metadata = (_metadata3 = metadata) !== null && _metadata3 !== void 0 ? _metadata3 : {};
|
|
metadata.plural = plural;
|
|
}
|
|
|
|
if (typeof refetch === 'object') {
|
|
var _metadata4;
|
|
|
|
metadata = (_metadata4 = metadata) !== null && _metadata4 !== void 0 ? _metadata4 : {};
|
|
metadata.refetch = {
|
|
connection: refetch.connection,
|
|
operation: CodeMarker.moduleDependency(refetch.operation + '.graphql'),
|
|
fragmentPathInResult: refetch.fragmentPathInResult
|
|
};
|
|
}
|
|
}
|
|
|
|
return {
|
|
kind: 'Fragment',
|
|
name: node.name,
|
|
type: node.type.toString(),
|
|
metadata: metadata,
|
|
argumentDefinitions: node.argumentDefinitions,
|
|
selections: node.selections
|
|
};
|
|
},
|
|
LocalArgumentDefinition: function LocalArgumentDefinition(node) {
|
|
return {
|
|
kind: 'LocalArgument',
|
|
name: node.name,
|
|
type: node.type.toString(),
|
|
defaultValue: node.defaultValue
|
|
};
|
|
},
|
|
RootArgumentDefinition: function RootArgumentDefinition(node) {
|
|
return {
|
|
kind: 'RootArgument',
|
|
name: node.name,
|
|
type: node.type ? node.type.toString() : null
|
|
};
|
|
},
|
|
Condition: function Condition(node, key, parent, ancestors) {
|
|
if (node.condition.kind !== 'Variable') {
|
|
throw createCompilerError("ReaderCodeGenerator: Expected 'Condition' with static value to be " + 'pruned or inlined', [node.condition.loc]);
|
|
}
|
|
|
|
return {
|
|
kind: 'Condition',
|
|
passingValue: node.passingValue,
|
|
condition: node.condition.variableName,
|
|
selections: node.selections
|
|
};
|
|
},
|
|
FragmentSpread: function FragmentSpread(node) {
|
|
return {
|
|
kind: 'FragmentSpread',
|
|
name: node.name,
|
|
args: valuesOrNull(sortByName(node.args))
|
|
};
|
|
},
|
|
InlineFragment: function InlineFragment(node) {
|
|
return {
|
|
kind: 'InlineFragment',
|
|
type: node.typeCondition.toString(),
|
|
selections: node.selections
|
|
};
|
|
},
|
|
LinkedField: function LinkedField(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.
|
|
// TODO(T37646905) enable this invariant after splitting the
|
|
// RelayCodeGenerator-test and running the RelayFieldHandleTransform on
|
|
// Reader ASTs.
|
|
//
|
|
// invariant(
|
|
// node.handles == null,
|
|
// 'ReaderCodeGenerator: unexpected handles',
|
|
// );
|
|
var type = getRawType(node.type);
|
|
var field = {
|
|
kind: 'LinkedField',
|
|
alias: node.alias,
|
|
name: node.name,
|
|
storageKey: null,
|
|
args: valuesOrNull(sortByName(node.args)),
|
|
concreteType: !isAbstractType(type) ? type.toString() : null,
|
|
plural: isPlural(node.type),
|
|
selections: node.selections
|
|
}; // Precompute storageKey if possible
|
|
|
|
var storageKey = getStaticStorageKey(field, node.metadata);
|
|
|
|
if (storageKey) {
|
|
field = (0, _objectSpread2["default"])({}, field, {
|
|
storageKey: storageKey
|
|
});
|
|
}
|
|
|
|
return field;
|
|
},
|
|
MatchField: function MatchField(node, key, parent, ancestors) {
|
|
var matchesByType = {};
|
|
node.selections.forEach(function (selection) {
|
|
var _regExpMatch$;
|
|
|
|
if (selection.kind === 'ScalarField' && selection.name === '__typename') {
|
|
// The RelayGenerateTypename transform will add a __typename selection
|
|
// to the selections of the match field.
|
|
return;
|
|
}
|
|
|
|
if (selection.kind !== 'MatchBranch') {
|
|
throw createCompilerError("ReaderCodeGenerator: Expected selection for MatchField '".concat(node.name, "' to be a 'MatchBranch', got '").concat(selection.kind, "'."), [selection.loc]);
|
|
}
|
|
|
|
if (matchesByType.hasOwnProperty(selection.type)) {
|
|
throw createCompilerError('ReaderCodeGenerator: Each @match type can appear at-most once. ' + "Type '".concat(String(selection.type), "' was duplicated."), selection.type, [selection.loc]);
|
|
}
|
|
|
|
var fragmentName = selection.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, "'."), [selection.loc]);
|
|
}
|
|
|
|
var fragmentPropName = (_regExpMatch$ = regExpMatch[2]) !== null && _regExpMatch$ !== void 0 ? _regExpMatch$ : 'matchData';
|
|
matchesByType[selection.type] = {
|
|
fragmentPropName: fragmentPropName,
|
|
fragmentName: fragmentName
|
|
};
|
|
});
|
|
var field = {
|
|
kind: 'MatchField',
|
|
alias: node.alias,
|
|
name: node.name,
|
|
storageKey: null,
|
|
args: valuesOrNull(sortByName(node.args)),
|
|
matchesByType: matchesByType
|
|
}; // Precompute storageKey if possible
|
|
|
|
var storageKey = getStaticStorageKey(field, node.metadata);
|
|
|
|
if (storageKey) {
|
|
field = (0, _objectSpread2["default"])({}, field, {
|
|
storageKey: storageKey
|
|
});
|
|
}
|
|
|
|
return field;
|
|
},
|
|
ScalarField: function ScalarField(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.
|
|
// TODO(T37646905) enable this invariant after splitting the
|
|
// RelayCodeGenerator-test and running the RelayFieldHandleTransform on
|
|
// Reader ASTs.
|
|
//
|
|
// invariant(
|
|
// node.handles == null,
|
|
// 'ReaderCodeGenerator: unexpected handles',
|
|
var field = {
|
|
kind: 'ScalarField',
|
|
alias: node.alias,
|
|
name: node.name,
|
|
args: valuesOrNull(sortByName(node.args)),
|
|
storageKey: null
|
|
}; // Precompute storageKey if possible
|
|
|
|
var storageKey = getStaticStorageKey(field, node.metadata);
|
|
|
|
if (storageKey) {
|
|
field = (0, _objectSpread2["default"])({}, field, {
|
|
storageKey: storageKey
|
|
});
|
|
}
|
|
|
|
return field;
|
|
},
|
|
SplitOperation: function SplitOperation(node, key, parent) {
|
|
return {
|
|
kind: 'SplitOperation',
|
|
name: node.name,
|
|
metadata: null,
|
|
selections: node.selections
|
|
};
|
|
},
|
|
Variable: function Variable(node, key, parent) {
|
|
return {
|
|
kind: 'Variable',
|
|
name: parent.name,
|
|
variableName: node.variableName,
|
|
type: parent.type ? parent.type.toString() : null
|
|
};
|
|
},
|
|
Literal: function Literal(node, key, parent) {
|
|
return {
|
|
kind: 'Literal',
|
|
name: parent.name,
|
|
value: stableCopy(node.value),
|
|
type: parent.type ? parent.type.toString() : null
|
|
};
|
|
},
|
|
Argument: function Argument(node, key, parent, ancestors) {
|
|
if (!['Variable', 'Literal'].includes(node.value.kind)) {
|
|
var valueString = JSON.stringify(node.value, null, 2);
|
|
throw createUserError('ReaderCodeGenerator: Complex argument values (Lists or ' + 'InputObjects with nested variables) are not supported.', [node.value.loc]);
|
|
}
|
|
|
|
return node.value.value !== null ? node.value : null;
|
|
}
|
|
}
|
|
};
|
|
|
|
function isPlural(type) {
|
|
return getNullableType(type) instanceof GraphQLList;
|
|
}
|
|
|
|
function valuesOrNull(array) {
|
|
return !array || array.length === 0 ? null : array;
|
|
}
|
|
|
|
function sortByName(array) {
|
|
return array instanceof Array ? array.slice().sort(function (a, b) {
|
|
return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
|
|
}) : array;
|
|
}
|
|
/**
|
|
* 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(function (arg) {
|
|
return arg.kind !== 'Literal';
|
|
})) {
|
|
return null;
|
|
}
|
|
|
|
return getStorageKey(field, {});
|
|
}
|
|
|
|
module.exports = {
|
|
generate: generate
|
|
}; |