apollo-codegen-core
Version:
Core generator APIs for Apollo Codegen
306 lines • 13 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.compileToIR = exports.stripProp = void 0;
const graphql_1 = require("graphql");
const graphql_2 = require("../utilities/graphql");
const apollo_language_server_1 = require("apollo-language-server");
function stripProp(propName, obj) {
let cloned = JSON.parse(JSON.stringify(obj));
for (let prop in cloned) {
if (prop === propName)
delete cloned[prop];
else if (typeof cloned[prop] === "object") {
cloned[prop] = stripProp(propName, cloned[prop]);
}
}
return cloned;
}
exports.stripProp = stripProp;
function compileToIR(schema, document, options = {
exposeTypeNodes: true,
}) {
if (options.addTypename) {
document = (0, apollo_language_server_1.withTypenameFieldAddedWhereNeeded)(document);
}
const compiler = new Compiler(schema, options);
const operations = Object.create(null);
const fragments = Object.create(null);
for (const definition of document.definitions) {
switch (definition.kind) {
case graphql_1.Kind.OPERATION_DEFINITION:
const operation = compiler.compileOperation(definition);
operations[operation.operationName] = operation;
break;
case graphql_1.Kind.FRAGMENT_DEFINITION:
const fragment = compiler.compileFragment(definition);
fragments[fragment.fragmentName] = fragment;
break;
}
}
for (const fragmentSpread of compiler.unresolvedFragmentSpreads) {
const fragment = fragments[fragmentSpread.fragmentName];
if (!fragment) {
throw new Error(`Cannot find fragment "${fragmentSpread.fragmentName}"`);
}
const possibleTypes = fragment.selectionSet.possibleTypes.filter((type) => fragmentSpread.selectionSet.possibleTypes.includes(type));
fragmentSpread.isConditional = fragment.selectionSet.possibleTypes.some((type) => !fragmentSpread.selectionSet.possibleTypes.includes(type));
fragmentSpread.selectionSet = {
possibleTypes,
selections: fragment.selectionSet.selections,
};
}
const typesUsed = compiler.typesUsed;
const unionTypes = compiler.unionTypes;
const interfaceTypes = compiler.interfaceTypes;
return {
schema,
typesUsed,
operations,
fragments,
options,
unionTypes,
interfaceTypes,
};
}
exports.compileToIR = compileToIR;
class Compiler {
constructor(schema, options) {
this.unresolvedFragmentSpreads = [];
this.schema = schema;
this.options = options;
this.typesUsedSet = new Set();
this.unionTypesSet = new Set();
this.interfaceTypesMap = new Map();
}
addTypeUsed(type) {
if (this.typesUsedSet.has(type))
return;
if ((0, graphql_1.isEnumType)(type) ||
(0, graphql_1.isInputObjectType)(type) ||
((0, graphql_1.isScalarType)(type) && !(0, graphql_1.isSpecifiedScalarType)(type))) {
this.typesUsedSet.add(type);
}
if ((0, graphql_1.isInputObjectType)(type)) {
for (const field of Object.values(type.getFields())) {
this.addTypeUsed((0, graphql_1.getNamedType)(field.type));
}
}
}
get typesUsed() {
return Array.from(this.typesUsedSet);
}
addUnionType(type) {
if ((0, graphql_1.isUnionType)(type)) {
if (this.unionTypesSet.has(type))
return;
this.unionTypesSet.add(type);
}
}
get unionTypes() {
return Array.from(this.unionTypesSet);
}
addInterfaceType(type) {
if ((0, graphql_1.isInterfaceType)(type)) {
if (this.interfaceTypesMap.has(type))
return;
this.interfaceTypesMap.set(type, this.possibleTypesForType(type));
}
}
get interfaceTypes() {
return this.interfaceTypesMap;
}
compileOperation(operationDefinition) {
if (!operationDefinition.name) {
throw new Error("Operations should be named");
}
const filePath = (0, graphql_2.filePathForNode)(operationDefinition);
const operationName = operationDefinition.name.value;
const operationType = operationDefinition.operation;
const variables = (operationDefinition.variableDefinitions || []).map((node) => {
const name = node.variable.name.value;
const type = (0, graphql_1.typeFromAST)(this.schema, node.type);
this.addTypeUsed((0, graphql_1.getNamedType)(type));
const typeNode = this.options.exposeTypeNodes && type
? stripProp("loc", (0, graphql_1.parseType)(type.toString()))
: undefined;
return {
name,
type: type,
typeNode,
};
});
const source = (0, graphql_1.print)(operationDefinition);
const rootType = (0, graphql_2.getOperationRootType)(this.schema, operationDefinition);
return {
filePath,
operationName,
operationType,
variables,
source,
rootType,
selectionSet: this.compileSelectionSet(operationDefinition.selectionSet, rootType),
};
}
compileFragment(fragmentDefinition) {
const fragmentName = fragmentDefinition.name.value;
const filePath = (0, graphql_2.filePathForNode)(fragmentDefinition);
const source = (0, graphql_1.print)(fragmentDefinition);
const type = (0, graphql_1.typeFromAST)(this.schema, fragmentDefinition.typeCondition);
const typeNode = this.options.exposeTypeNodes
? stripProp("loc", (0, graphql_1.parseType)(type.toString()))
: undefined;
return {
fragmentName,
filePath,
source,
type,
selectionSet: this.compileSelectionSet(fragmentDefinition.selectionSet, type),
typeNode,
};
}
compileSelectionSet(selectionSetNode, parentType, possibleTypes = this.possibleTypesForType(parentType), visitedFragments = new Set()) {
return {
possibleTypes,
selections: selectionSetNode.selections
.map((selectionNode) => wrapInBooleanConditionsIfNeeded(this.compileSelection(selectionNode, parentType, possibleTypes, visitedFragments), selectionNode, possibleTypes))
.filter((x) => x),
};
}
compileSelection(selectionNode, parentType, possibleTypes, visitedFragments) {
switch (selectionNode.kind) {
case graphql_1.Kind.FIELD: {
const name = selectionNode.name.value;
const alias = selectionNode.alias
? selectionNode.alias.value
: undefined;
const fieldDef = (0, graphql_2.getFieldDef)(this.schema, parentType, selectionNode);
if (!fieldDef) {
throw new graphql_1.GraphQLError(`Cannot query field "${name}" on type "${String(parentType)}"`, [selectionNode]);
}
const fieldType = fieldDef.type;
const typeNode = this.options.exposeTypeNodes
? stripProp("loc", (0, graphql_1.parseType)(fieldType.toString()))
: undefined;
const unmodifiedFieldType = (0, graphql_1.getNamedType)(fieldType);
this.addTypeUsed(unmodifiedFieldType);
this.addUnionType(unmodifiedFieldType);
this.addInterfaceType(unmodifiedFieldType);
const { description, isDeprecated, deprecationReason } = fieldDef;
const responseKey = alias || name;
const args = selectionNode.arguments && selectionNode.arguments.length > 0
? selectionNode.arguments.map((arg) => {
const name = arg.name.value;
const argDef = fieldDef.args.find((argDef) => argDef.name === arg.name.value);
const argDefType = (argDef && argDef.type) || undefined;
const argDeftypeNode = this.options.exposeTypeNodes && argDefType
? stripProp("loc", (0, graphql_1.parseType)(argDefType.toString()))
: undefined;
return {
name,
value: (0, graphql_2.valueFromValueNode)(arg.value),
type: argDefType,
typeNode: argDeftypeNode,
};
})
: undefined;
let field = {
kind: "Field",
responseKey,
name,
alias,
args,
type: fieldType,
typeNode,
description: !(0, graphql_2.isMetaFieldName)(name) && description ? description : undefined,
isDeprecated,
deprecationReason: deprecationReason || undefined,
};
if ((0, graphql_1.isCompositeType)(unmodifiedFieldType)) {
const selectionSetNode = selectionNode.selectionSet;
if (!selectionSetNode) {
throw new graphql_1.GraphQLError(`Composite field "${name}" on type "${String(parentType)}" requires selection set`, [selectionNode]);
}
field.selectionSet = this.compileSelectionSet(selectionNode.selectionSet, unmodifiedFieldType);
}
return field;
}
case graphql_1.Kind.INLINE_FRAGMENT: {
const typeNode = selectionNode.typeCondition;
const type = typeNode
? (0, graphql_1.typeFromAST)(this.schema, typeNode)
: parentType;
const possibleTypesForTypeCondition = this.possibleTypesForType(type).filter((type) => possibleTypes.includes(type));
const typeConditiontypeNode = this.options.exposeTypeNodes
? stripProp("loc", (0, graphql_1.parseType)(type.toString()))
: undefined;
return {
kind: "TypeCondition",
type,
typeNode: typeConditiontypeNode,
selectionSet: this.compileSelectionSet(selectionNode.selectionSet, type, possibleTypesForTypeCondition),
};
}
case graphql_1.Kind.FRAGMENT_SPREAD: {
const fragmentName = selectionNode.name.value;
if (visitedFragments.has(fragmentName))
return null;
visitedFragments.add(fragmentName);
const fragmentSpread = {
kind: "FragmentSpread",
fragmentName,
selectionSet: {
possibleTypes,
selections: [],
},
};
this.unresolvedFragmentSpreads.push(fragmentSpread);
return fragmentSpread;
}
}
}
possibleTypesForType(type) {
if ((0, graphql_1.isAbstractType)(type)) {
return Array.from(this.schema.getPossibleTypes(type)) || [];
}
else {
return [type];
}
}
}
function wrapInBooleanConditionsIfNeeded(selection, selectionNode, possibleTypes) {
if (!selection)
return null;
if (!selectionNode.directives)
return selection;
for (const directive of selectionNode.directives) {
const directiveName = directive.name.value;
if (directiveName === "skip" || directiveName === "include") {
if (!directive.arguments)
continue;
const value = directive.arguments[0].value;
switch (value.kind) {
case "BooleanValue":
if (directiveName === "skip") {
return value.value ? null : selection;
}
else {
return value.value ? selection : null;
}
break;
case "Variable":
selection = {
kind: "BooleanCondition",
variableName: value.name.value,
inverted: directiveName === "skip",
selectionSet: {
possibleTypes,
selections: [selection],
},
};
break;
}
}
}
return selection;
}
//# sourceMappingURL=index.js.map