import { DocumentNode, OperationDefinitionNode, FragmentDefinitionNode, ValueNode, } from 'graphql'; import { assign } from './util/assign'; import { valueToObjectRepresentation, JsonValue } from './storeUtils'; export function getMutationDefinition( doc: DocumentNode, ): OperationDefinitionNode { checkDocument(doc); let mutationDef: OperationDefinitionNode | null = doc.definitions.filter( definition => definition.kind === 'OperationDefinition' && definition.operation === 'mutation', )[0] as OperationDefinitionNode; if (!mutationDef) { throw new Error('Must contain a mutation definition.'); } return mutationDef; } // Checks the document for errors and throws an exception if there is an error. export function checkDocument(doc: DocumentNode) { if (doc.kind !== 'Document') { throw new Error(`Expecting a parsed GraphQL document. Perhaps you need to wrap the query \ string in a "gql" tag? http://docs.apollostack.com/apollo-client/core.html#gql`); } const operations = doc.definitions .filter(d => d.kind !== 'FragmentDefinition') .map(definition => { if (definition.kind !== 'OperationDefinition') { throw new Error( `Schema type definitions not allowed in queries. Found: "${definition.kind}"`, ); } return definition; }); if (operations.length > 1) { throw new Error( `Ambiguous GraphQL document: contains ${operations.length} operations`, ); } } export function getOperationDefinition( doc: DocumentNode, ): OperationDefinitionNode | undefined { checkDocument(doc); return doc.definitions.filter( definition => definition.kind === 'OperationDefinition', )[0] as OperationDefinitionNode; } export function getOperationDefinitionOrDie( document: DocumentNode, ): OperationDefinitionNode { const def = getOperationDefinition(document); if (!def) { throw new Error(`GraphQL document is missing an operation`); } return def; } export function getOperationName(doc: DocumentNode): string | null { return ( doc.definitions .filter( definition => definition.kind === 'OperationDefinition' && definition.name, ) .map((x: OperationDefinitionNode) => x.name.value)[0] || null ); } // Returns the FragmentDefinitions from a particular document as an array export function getFragmentDefinitions( doc: DocumentNode, ): FragmentDefinitionNode[] { return doc.definitions.filter( definition => definition.kind === 'FragmentDefinition', ) as FragmentDefinitionNode[]; } export function getQueryDefinition(doc: DocumentNode): OperationDefinitionNode { const queryDef = getOperationDefinition(doc) as OperationDefinitionNode; if (!queryDef || queryDef.operation !== 'query') { throw new Error('Must contain a query definition.'); } return queryDef; } export function getFragmentDefinition( doc: DocumentNode, ): FragmentDefinitionNode { if (doc.kind !== 'Document') { throw new Error(`Expecting a parsed GraphQL document. Perhaps you need to wrap the query \ string in a "gql" tag? http://docs.apollostack.com/apollo-client/core.html#gql`); } if (doc.definitions.length > 1) { throw new Error('Fragment must have exactly one definition.'); } const fragmentDef = doc.definitions[0] as FragmentDefinitionNode; if (fragmentDef.kind !== 'FragmentDefinition') { throw new Error('Must be a fragment definition.'); } return fragmentDef as FragmentDefinitionNode; } /** * Returns the first operation definition found in this document. * If no operation definition is found, the first fragment definition will be returned. * If no definitions are found, an error will be thrown. */ export function getMainDefinition( queryDoc: DocumentNode, ): OperationDefinitionNode | FragmentDefinitionNode { checkDocument(queryDoc); let fragmentDefinition; for (let definition of queryDoc.definitions) { if (definition.kind === 'OperationDefinition') { const operation = (definition as OperationDefinitionNode).operation; if ( operation === 'query' || operation === 'mutation' || operation === 'subscription' ) { return definition as OperationDefinitionNode; } } if (definition.kind === 'FragmentDefinition' && !fragmentDefinition) { // we do this because we want to allow multiple fragment definitions // to precede an operation definition. fragmentDefinition = definition as FragmentDefinitionNode; } } if (fragmentDefinition) { return fragmentDefinition; } throw new Error( 'Expected a parsed GraphQL query with a query, mutation, subscription, or a fragment.', ); } /** * This is an interface that describes a map from fragment names to fragment definitions. */ export interface FragmentMap { [fragmentName: string]: FragmentDefinitionNode; } // Utility function that takes a list of fragment definitions and makes a hash out of them // that maps the name of the fragment to the fragment definition. export function createFragmentMap( fragments: FragmentDefinitionNode[] = [], ): FragmentMap { const symTable: FragmentMap = {}; fragments.forEach(fragment => { symTable[fragment.name.value] = fragment; }); return symTable; } export function getDefaultValues( definition: OperationDefinitionNode | undefined, ): { [key: string]: JsonValue } { if ( definition && definition.variableDefinitions && definition.variableDefinitions.length ) { const defaultValues = definition.variableDefinitions .filter(({ defaultValue }) => defaultValue) .map(({ variable, defaultValue }): { [key: string]: JsonValue } => { const defaultValueObj: { [key: string]: JsonValue } = {}; valueToObjectRepresentation( defaultValueObj, variable.name, defaultValue as ValueNode, ); return defaultValueObj; }); return assign({}, ...defaultValues); } return {}; } /** * Returns the names of all variables declared by the operation. */ export function variablesInOperation( operation: OperationDefinitionNode, ): Set { const names = new Set(); if (operation.variableDefinitions) { for (const definition of operation.variableDefinitions) { names.add(definition.variable.name.value); } } return names; }