UNPKG

apollo-codegen

Version:

Generate API code or type annotations based on a GraphQL schema and query documents

321 lines 16.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const graphql_1 = require("graphql"); const graphql_2 = require("./utilities/graphql"); const printing_1 = require("./utilities/printing"); const lodash_1 = require("lodash"); const sjcl = require("sjcl"); function compileToIR(schema, document, options = { mergeInFieldsFromFragmentSpreads: true }) { if (options.addTypename) { document = graphql_2.withTypenameFieldAddedWhereNeeded(schema, document); } const compiler = new Compiler(schema, document, options); const operations = Object.create(null); compiler.operations.forEach(operation => { operations[operation.name.value] = compiler.compileOperation(operation); }); const fragments = Object.create(null); compiler.fragments.forEach(fragment => { fragments[fragment.name.value] = compiler.compileFragment(fragment); }); Object.values(operations).forEach(operation => { augmentCompiledOperationWithFragments(operation, fragments); }); const typesUsed = compiler.typesUsed; return { schema, operations, fragments, typesUsed }; } exports.compileToIR = compileToIR; class Compiler { constructor(schema, document, options) { this.schema = schema; this.options = options; this.typesUsedSet = new Set(); this.fragmentMap = Object.create(null); this.operations = []; for (const definition of document.definitions) { switch (definition.kind) { case graphql_1.Kind.OPERATION_DEFINITION: this.operations.push(definition); break; case graphql_1.Kind.FRAGMENT_DEFINITION: this.fragmentMap[definition.name.value] = definition; break; } } this.compiledFragmentMap = Object.create(null); } addTypeUsed(type) { if (this.typesUsedSet.has(type)) return; if (type instanceof graphql_1.GraphQLEnumType || type instanceof graphql_1.GraphQLInputObjectType || (type instanceof graphql_1.GraphQLScalarType && !graphql_2.isBuiltInScalarType(type))) { this.typesUsedSet.add(type); } if (type instanceof graphql_1.GraphQLInputObjectType) { for (const field of Object.values(type.getFields())) { this.addTypeUsed(graphql_1.getNamedType(field.type)); } } } get typesUsed() { return Array.from(this.typesUsedSet); } fragmentNamed(fragmentName) { return this.fragmentMap[fragmentName]; } get fragments() { return Object.values(this.fragmentMap); } compileOperation(operationDefinition) { const filePath = 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 = graphql_1.typeFromAST(this.schema, node.type); this.addTypeUsed(graphql_1.getNamedType(type)); return { name, type }; }); const source = graphql_1.print(operationDefinition); const rootType = graphql_2.getOperationRootType(this.schema, operationDefinition); const groupedVisitedFragmentSet = new Map(); const groupedFieldSet = this.collectFields(rootType, operationDefinition.selectionSet, undefined, groupedVisitedFragmentSet); const fragmentsReferencedSet = Object.create(null); const { fields } = this.resolveFields(rootType, groupedFieldSet, groupedVisitedFragmentSet, fragmentsReferencedSet); const fragmentsReferenced = Object.keys(fragmentsReferencedSet); return { filePath, operationName, operationType, rootType, variables, source, fields, fragmentsReferenced }; } compileFragment(fragmentDefinition) { const filePath = graphql_2.filePathForNode(fragmentDefinition); const fragmentName = fragmentDefinition.name.value; const source = graphql_1.print(fragmentDefinition); const typeCondition = graphql_1.typeFromAST(this.schema, fragmentDefinition.typeCondition); const possibleTypes = this.possibleTypesForType(typeCondition); const groupedVisitedFragmentSet = new Map(); const groupedFieldSet = this.collectFields(typeCondition, fragmentDefinition.selectionSet, undefined, groupedVisitedFragmentSet); const fragmentsReferencedSet = Object.create(null); const { fields, fragmentSpreads, inlineFragments } = this.resolveFields(typeCondition, groupedFieldSet, groupedVisitedFragmentSet, fragmentsReferencedSet); const fragmentsReferenced = Object.keys(fragmentsReferencedSet); return { filePath, fragmentName, source, typeCondition, possibleTypes, fields, fragmentSpreads, inlineFragments, fragmentsReferenced }; } collectFields(parentType, selectionSet, groupedFieldSet = Object.create(null), groupedVisitedFragmentSet = new Map()) { if (!graphql_1.isCompositeType(parentType)) { throw new Error(`parentType should be a composite type, but is "${String(parentType)}"`); } for (const selection of selectionSet.selections) { switch (selection.kind) { case graphql_1.Kind.FIELD: { const fieldName = selection.name.value; const responseName = selection.alias ? selection.alias.value : fieldName; const field = graphql_2.getFieldDef(this.schema, parentType, selection); if (!field) { throw new graphql_1.GraphQLError(`Cannot query field "${fieldName}" on type "${String(parentType)}"`, [selection]); } if (groupedFieldSet) { if (!groupedFieldSet[responseName]) { groupedFieldSet[responseName] = []; } groupedFieldSet[responseName].push([parentType, { responseName, fieldName, args: argumentsFromAST(selection.arguments), type: field.type, directives: selection.directives, selectionSet: selection.selectionSet }]); } break; } case graphql_1.Kind.INLINE_FRAGMENT: { const typeCondition = selection.typeCondition; const inlineFragmentType = typeCondition ? graphql_1.typeFromAST(this.schema, typeCondition) : parentType; if (!graphql_1.doTypesOverlap(this.schema, inlineFragmentType, parentType)) continue; const effectiveType = parentType instanceof graphql_1.GraphQLObjectType ? parentType : inlineFragmentType; this.collectFields(effectiveType, selection.selectionSet, groupedFieldSet, groupedVisitedFragmentSet); break; } case graphql_1.Kind.FRAGMENT_SPREAD: { const fragmentName = selection.name.value; const fragment = this.fragmentNamed(fragmentName); if (!fragment) throw new graphql_1.GraphQLError(`Cannot find fragment "${fragmentName}"`); const typeCondition = fragment.typeCondition; const fragmentType = graphql_1.typeFromAST(this.schema, typeCondition); if (groupedVisitedFragmentSet) { let visitedFragmentSet = groupedVisitedFragmentSet.get(parentType); if (!visitedFragmentSet) { visitedFragmentSet = {}; groupedVisitedFragmentSet.set(parentType, visitedFragmentSet); } if (visitedFragmentSet[fragmentName]) continue; visitedFragmentSet[fragmentName] = true; } if (!graphql_1.doTypesOverlap(this.schema, fragmentType, parentType)) continue; const effectiveType = parentType instanceof graphql_1.GraphQLObjectType ? parentType : fragmentType; this.collectFields(effectiveType, fragment.selectionSet, this.options.mergeInFieldsFromFragmentSpreads ? groupedFieldSet : null, groupedVisitedFragmentSet); break; } } } return groupedFieldSet; } possibleTypesForType(type) { if (graphql_1.isAbstractType(type)) { return this.schema.getPossibleTypes(type); } else { return [type]; } } mergeSelectionSets(parentType, fieldSet, groupedVisitedFragmentSet) { const groupedFieldSet = Object.create(null); for (const [, field] of fieldSet) { const selectionSet = field.selectionSet; if (selectionSet) { this.collectFields(parentType, selectionSet, groupedFieldSet, groupedVisitedFragmentSet); } } return groupedFieldSet; } resolveFields(parentType, groupedFieldSet, groupedVisitedFragmentSet, fragmentsReferencedSet) { const fields = []; for (let [responseName, fieldSet] of Object.entries(groupedFieldSet)) { fieldSet = fieldSet.filter(([typeCondition,]) => graphql_1.isTypeSubTypeOf(this.schema, parentType, typeCondition)); if (fieldSet.length < 1) continue; const [, firstField] = fieldSet[0]; const fieldName = firstField.fieldName; const args = firstField.args; const type = firstField.type; let field = { responseName, fieldName, type }; if (args && args.length > 0) { field.args = args; } const isConditional = fieldSet.some(([, field]) => { return field.directives && field.directives.some(directive => { const directiveName = directive.name.value; return directiveName == 'skip' || directiveName == 'include'; }); }); if (isConditional) { field.isConditional = true; } if (parentType instanceof graphql_1.GraphQLObjectType || parentType instanceof graphql_1.GraphQLInterfaceType) { const fieldDef = parentType.getFields()[fieldName]; if (fieldDef) { const description = fieldDef.description; if (description) { field.description = description; } Object.assign(field, { isDeprecated: fieldDef.isDeprecated, deprecationReason: fieldDef.deprecationReason, }); } } const bareType = graphql_1.getNamedType(type); this.addTypeUsed(bareType); if (graphql_1.isCompositeType(bareType)) { const subSelectionGroupedVisitedFragmentSet = new Map(); const subSelectionGroupedFieldSet = this.mergeSelectionSets(bareType, fieldSet, subSelectionGroupedVisitedFragmentSet); const { fields, fragmentSpreads, inlineFragments } = this.resolveFields(bareType, subSelectionGroupedFieldSet, subSelectionGroupedVisitedFragmentSet, fragmentsReferencedSet); Object.assign(field, { fields, fragmentSpreads, inlineFragments }); } fields.push(field); } const fragmentSpreads = this.fragmentSpreadsForParentType(parentType, groupedVisitedFragmentSet); const inlineFragments = this.resolveInlineFragments(parentType, groupedFieldSet, groupedVisitedFragmentSet, fragmentsReferencedSet); if (fragmentsReferencedSet) { Object.assign(fragmentsReferencedSet, ...groupedVisitedFragmentSet.values()); for (let fragmentName of fragmentSpreads) { const fragment = this.fragmentNamed(fragmentName); if (!fragment) throw new graphql_1.GraphQLError(`Cannot find fragment "${fragmentName}"`); const { fragmentsReferenced: fragmentsReferencedFromFragment } = this.compileFragment(fragment); for (let fragmentReferenced of fragmentsReferencedFromFragment) { fragmentsReferencedSet[fragmentReferenced] = true; } } } return { fields, fragmentSpreads, inlineFragments }; } resolveInlineFragments(parentType, groupedFieldSet, groupedVisitedFragmentSet, fragmentsReferencedSet) { return this.collectPossibleTypes(parentType, groupedFieldSet, groupedVisitedFragmentSet).map(typeCondition => { const { fields, fragmentSpreads } = this.resolveFields(typeCondition, groupedFieldSet, groupedVisitedFragmentSet, fragmentsReferencedSet); const possibleTypes = this.possibleTypesForType(typeCondition); return { typeCondition, possibleTypes, fields, fragmentSpreads }; }); } collectPossibleTypes(parentType, groupedFieldSet, groupedVisitedFragmentSet) { if (!graphql_1.isAbstractType(parentType)) return []; const possibleTypes = new Set(); for (const fieldSet of Object.values(groupedFieldSet)) { for (const [typeCondition,] of fieldSet) { if (this.schema.isPossibleType(parentType, typeCondition)) { possibleTypes.add(typeCondition); } } } if (groupedVisitedFragmentSet) { for (const effectiveType of groupedVisitedFragmentSet.keys()) { if (this.schema.isPossibleType(parentType, effectiveType)) { possibleTypes.add(effectiveType); } } } return Array.from(possibleTypes); } fragmentSpreadsForParentType(parentType, groupedVisitedFragmentSet) { if (!groupedVisitedFragmentSet) return []; let fragmentSpreads = new Set(); for (const [effectiveType, visitedFragmentSet] of groupedVisitedFragmentSet) { if (!graphql_2.isTypeProperSuperTypeOf(this.schema, effectiveType, parentType)) continue; for (const fragmentName of Object.keys(visitedFragmentSet)) { fragmentSpreads.add(fragmentName); } } return Array.from(fragmentSpreads); } } exports.Compiler = Compiler; function augmentCompiledOperationWithFragments(compiledOperation, compiledFragments) { const operationAndFragments = operationAndRelatedFragments(compiledOperation, compiledFragments); compiledOperation.sourceWithFragments = operationAndFragments.map(operationOrFragment => { return operationOrFragment.source; }).join('\n'); const idBits = sjcl.hash.sha256.hash(compiledOperation.sourceWithFragments); compiledOperation.operationId = sjcl.codec.hex.fromBits(idBits); } function operationAndRelatedFragments(compiledOperationOrFragment, allCompiledFragments) { let result = lodash_1.flatMap(compiledOperationOrFragment.fragmentsReferenced, (fragmentName) => { return operationAndRelatedFragments(allCompiledFragments[fragmentName], allCompiledFragments); }); result.unshift(compiledOperationOrFragment); result = lodash_1.uniqBy(result, (compiledOperationOrFragment) => { return compiledOperationOrFragment.fragmentName; }); result = result.sort((a, b) => { return a.fragmentName > b.fragmentName; }); return result; } function argumentsFromAST(args) { return args && args.map(arg => { return { name: arg.name.value, value: graphql_2.valueFromValueNode(arg.value) }; }); } function printIR({ fields, inlineFragments, fragmentSpreads }) { return fields && printing_1.wrap('<', printing_1.join(fragmentSpreads, ', '), '> ') + printing_1.block(fields.map(field => `${field.name}: ${String(field.type)}` + printing_1.wrap(' ', printIR(field))).concat(inlineFragments && inlineFragments.map(inlineFragment => `${String(inlineFragment.typeCondition)}` + printing_1.wrap(' ', printIR(inlineFragment))))); } exports.printIR = printIR; //# sourceMappingURL=compilation.js.map