graphql#SelectionSetNode TypeScript Examples

The following examples show how to use graphql#SelectionSetNode. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: gql.ts    From anchor-web-app with Apache License 2.0 6 votes vote down vote up
export function findSelectionSet(document: DocumentNode): SelectionSetNode {
  const query: DefinitionNode | undefined = document.definitions.find(
    (definition) =>
      definition.kind === 'OperationDefinition' &&
      definition.operation === 'query',
  );

  if (!query) {
    throw new Error(`Can't find "query" operation from query`);
  }

  return (query as OperationDefinitionNode).selectionSet;
}
Example #2
Source File: get-mesh.ts    From graphql-mesh with MIT License 6 votes vote down vote up
function normalizeSelectionSetParamOrFactory(
  selectionSetParamOrFactory: SelectionSetParamOrFactory
): (subtree: SelectionSetNode) => SelectionSetNode {
  return function getSelectionSet(subtree: SelectionSetNode) {
    if (typeof selectionSetParamOrFactory === 'function') {
      const selectionSetParam = selectionSetParamOrFactory(subtree);
      return normalizeSelectionSetParam(selectionSetParam);
    } else {
      return normalizeSelectionSetParam(selectionSetParamOrFactory);
    }
  };
}
Example #3
Source File: index.ts    From amplify-codegen with Apache License 2.0 6 votes vote down vote up
compileSelectionSet(
    selectionSetNode: SelectionSetNode,
    parentType: GraphQLCompositeType,
    possibleTypes: GraphQLObjectType[] = this.possibleTypesForType(parentType),
    visitedFragments: Set<string> = new Set(),
  ): SelectionSet {
    return {
      possibleTypes,
      selections: selectionSetNode.selections
        .map(selectionNode =>
          wrapInBooleanConditionsIfNeeded(
            this.compileSelection(selectionNode, parentType, possibleTypes, visitedFragments),
            selectionNode,
            possibleTypes,
          ),
        )
        .filter(x => x) as Selection[],
    };
  }
Example #4
Source File: graphql.ts    From amplify-codegen with Apache License 2.0 6 votes vote down vote up
export function withTypenameFieldAddedWhereNeeded(ast: ASTNode) {
  return visit(ast, {
    enter: {
      SelectionSet(node: SelectionSetNode) {
        return {
          ...node,
          selections: node.selections.filter(
            selection => !(selection.kind === 'Field' && (selection as FieldNode).name.value === '__typename')
          ),
        };
      },
    },
    leave(node: ASTNode) {
      if (!(node.kind === 'Field' || node.kind === 'FragmentDefinition')) return undefined;
      if (!node.selectionSet) return undefined;

      if (true) {
        return {
          ...node,
          selectionSet: {
            ...node.selectionSet,
            selections: [typenameField, ...node.selectionSet.selections],
          },
        };
      } else {
        return undefined;
      }
    },
  });
}
Example #5
Source File: gqlTypes.ts    From ra-data-prisma with MIT License 6 votes vote down vote up
operationDefinition = (
  operation: OperationTypeNode,
  selectionSet: SelectionSetNode,
  name: NameNode,
  variableDefinitions: VariableDefinitionNode[],
): OperationDefinitionNode => ({
  kind: Kind.OPERATION_DEFINITION,
  operation,
  selectionSet,
  name,
  variableDefinitions,
})
Example #6
Source File: gqlTypes.ts    From ra-data-prisma with MIT License 5 votes vote down vote up
selectionSet = (
  selections: SelectionNode[],
): SelectionSetNode => ({
  kind: Kind.SELECTION_SET,
  selections,
})
Example #7
Source File: alphabetize.ts    From graphql-eslint with MIT License 4 votes vote down vote up
rule: GraphQLESLintRule<[AlphabetizeConfig]> = {
  meta: {
    type: 'suggestion',
    fixable: 'code',
    docs: {
      category: ['Schema', 'Operations'],
      description:
        'Enforce arrange in alphabetical order for type fields, enum values, input object fields, operation selections and more.',
      url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID}.md`,
      examples: [
        {
          title: 'Incorrect',
          usage: [{ fields: [Kind.OBJECT_TYPE_DEFINITION] }],
          code: /* GraphQL */ `
            type User {
              password: String
              firstName: String! # should be before "password"
              age: Int # should be before "firstName"
              lastName: String!
            }
          `,
        },
        {
          title: 'Correct',
          usage: [{ fields: [Kind.OBJECT_TYPE_DEFINITION] }],
          code: /* GraphQL */ `
            type User {
              age: Int
              firstName: String!
              lastName: String!
              password: String
            }
          `,
        },
        {
          title: 'Incorrect',
          usage: [{ values: [Kind.ENUM_TYPE_DEFINITION] }],
          code: /* GraphQL */ `
            enum Role {
              SUPER_ADMIN
              ADMIN # should be before "SUPER_ADMIN"
              USER
              GOD # should be before "USER"
            }
          `,
        },
        {
          title: 'Correct',
          usage: [{ values: [Kind.ENUM_TYPE_DEFINITION] }],
          code: /* GraphQL */ `
            enum Role {
              ADMIN
              GOD
              SUPER_ADMIN
              USER
            }
          `,
        },
        {
          title: 'Incorrect',
          usage: [{ selections: [Kind.OPERATION_DEFINITION] }],
          code: /* GraphQL */ `
            query {
              me {
                firstName
                lastName
                email # should be before "lastName"
              }
            }
          `,
        },
        {
          title: 'Correct',
          usage: [{ selections: [Kind.OPERATION_DEFINITION] }],
          code: /* GraphQL */ `
            query {
              me {
                email
                firstName
                lastName
              }
            }
          `,
        },
      ],
      configOptions: {
        schema: [
          {
            fields: fieldsEnum,
            values: valuesEnum,
            arguments: argumentsEnum,
            // TODO: add in graphql-eslint v4
            // definitions: true,
          },
        ],
        operations: [
          {
            selections: selectionsEnum,
            variables: variablesEnum,
            arguments: [Kind.FIELD, Kind.DIRECTIVE],
          },
        ],
      },
    },
    messages: {
      [RULE_ID]: '`{{ currName }}` should be before {{ prevName }}.',
    },
    schema: {
      type: 'array',
      minItems: 1,
      maxItems: 1,
      items: {
        type: 'object',
        additionalProperties: false,
        minProperties: 1,
        properties: {
          fields: {
            ...ARRAY_DEFAULT_OPTIONS,
            items: {
              enum: fieldsEnum,
            },
            description: 'Fields of `type`, `interface`, and `input`.',
          },
          values: {
            ...ARRAY_DEFAULT_OPTIONS,
            items: {
              enum: valuesEnum,
            },
            description: 'Values of `enum`.',
          },
          selections: {
            ...ARRAY_DEFAULT_OPTIONS,
            items: {
              enum: selectionsEnum,
            },
            description: 'Selections of `fragment` and operations `query`, `mutation` and `subscription`.',
          },
          variables: {
            ...ARRAY_DEFAULT_OPTIONS,
            items: {
              enum: variablesEnum,
            },
            description: 'Variables of operations `query`, `mutation` and `subscription`.',
          },
          arguments: {
            ...ARRAY_DEFAULT_OPTIONS,
            items: {
              enum: argumentsEnum,
            },
            description: 'Arguments of fields and directives.',
          },
          definitions: {
            type: 'boolean',
            description: 'Definitions – `type`, `interface`, `enum`, `scalar`, `input`, `union` and `directive`.',
            default: false,
          },
        },
      },
    },
  },
  create(context) {
    const sourceCode = context.getSourceCode();

    function isNodeAndCommentOnSameLine(node: { loc: SourceLocation }, comment: Comment): boolean {
      return node.loc.end.line === comment.loc.start.line;
    }

    function getBeforeComments(node): Comment[] {
      const commentsBefore = sourceCode.getCommentsBefore(node);
      if (commentsBefore.length === 0) {
        return [];
      }
      const tokenBefore = sourceCode.getTokenBefore(node);
      if (tokenBefore) {
        return commentsBefore.filter(comment => !isNodeAndCommentOnSameLine(tokenBefore, comment));
      }
      const filteredComments = [];
      const nodeLine = node.loc.start.line;
      // Break on comment that not attached to node
      for (let i = commentsBefore.length - 1; i >= 0; i -= 1) {
        const comment = commentsBefore[i];
        if (nodeLine - comment.loc.start.line - filteredComments.length > 1) {
          break;
        }
        filteredComments.unshift(comment);
      }
      return filteredComments;
    }

    function getRangeWithComments(node): AST.Range {
      if (node.kind === Kind.VARIABLE) {
        node = node.parent;
      }
      const [firstBeforeComment] = getBeforeComments(node);
      const [firstAfterComment] = sourceCode.getCommentsAfter(node);
      const from = firstBeforeComment || node;
      const to = firstAfterComment && isNodeAndCommentOnSameLine(node, firstAfterComment) ? firstAfterComment : node;
      return [from.range[0], to.range[1]];
    }

    function checkNodes(nodes: GraphQLESTreeNode<ASTNode>[]) {
      // Starts from 1, ignore nodes.length <= 1
      for (let i = 1; i < nodes.length; i += 1) {
        const currNode = nodes[i];
        const currName = 'name' in currNode && currNode.name?.value;
        if (!currName) {
          // we don't move unnamed current nodes
          continue;
        }

        const prevNode = nodes[i - 1];
        const prevName = 'name' in prevNode && prevNode.name?.value;
        if (prevName) {
          // Compare with lexicographic order
          const compareResult = prevName.localeCompare(currName);
          const shouldSort = compareResult === 1;
          if (!shouldSort) {
            const isSameName = compareResult === 0;
            if (!isSameName || !prevNode.kind.endsWith('Extension') || currNode.kind.endsWith('Extension')) {
              continue;
            }
          }
        }

        context.report({
          node: currNode.name,
          messageId: RULE_ID,
          data: {
            currName,
            prevName: prevName ? `\`${prevName}\`` : lowerCase(prevNode.kind),
          },
          *fix(fixer) {
            const prevRange = getRangeWithComments(prevNode);
            const currRange = getRangeWithComments(currNode);
            yield fixer.replaceTextRange(prevRange, sourceCode.getText({ range: currRange } as any));
            yield fixer.replaceTextRange(currRange, sourceCode.getText({ range: prevRange } as any));
          },
        });
      }
    }

    const opts = context.options[0];
    const fields = new Set(opts.fields ?? []);
    const listeners: GraphQLESLintRuleListener = {};

    const kinds = [
      fields.has(Kind.OBJECT_TYPE_DEFINITION) && [Kind.OBJECT_TYPE_DEFINITION, Kind.OBJECT_TYPE_EXTENSION],
      fields.has(Kind.INTERFACE_TYPE_DEFINITION) && [Kind.INTERFACE_TYPE_DEFINITION, Kind.INTERFACE_TYPE_EXTENSION],
      fields.has(Kind.INPUT_OBJECT_TYPE_DEFINITION) && [
        Kind.INPUT_OBJECT_TYPE_DEFINITION,
        Kind.INPUT_OBJECT_TYPE_EXTENSION,
      ],
    ]
      .filter(Boolean)
      .flat();

    const fieldsSelector = kinds.join(',');

    const hasEnumValues = opts.values?.[0] === Kind.ENUM_TYPE_DEFINITION;
    const selectionsSelector = opts.selections?.join(',');
    const hasVariables = opts.variables?.[0] === Kind.OPERATION_DEFINITION;
    const argumentsSelector = opts.arguments?.join(',');

    if (fieldsSelector) {
      listeners[fieldsSelector] = (
        node: GraphQLESTreeNode<
          | ObjectTypeDefinitionNode
          | ObjectTypeExtensionNode
          | InterfaceTypeDefinitionNode
          | InterfaceTypeExtensionNode
          | InputObjectTypeDefinitionNode
          | InputObjectTypeExtensionNode
        >
      ) => {
        checkNodes(node.fields);
      };
    }

    if (hasEnumValues) {
      const enumValuesSelector = [Kind.ENUM_TYPE_DEFINITION, Kind.ENUM_TYPE_EXTENSION].join(',');
      listeners[enumValuesSelector] = (node: GraphQLESTreeNode<EnumTypeDefinitionNode | EnumTypeExtensionNode>) => {
        checkNodes(node.values);
      };
    }

    if (selectionsSelector) {
      listeners[`:matches(${selectionsSelector}) SelectionSet`] = (node: GraphQLESTreeNode<SelectionSetNode>) => {
        checkNodes(
          node.selections.map(selection =>
            // sort by alias is field is renamed
            'alias' in selection && selection.alias ? ({ name: selection.alias } as any) : selection
          )
        );
      };
    }

    if (hasVariables) {
      listeners.OperationDefinition = (node: GraphQLESTreeNode<OperationDefinitionNode>) => {
        checkNodes(node.variableDefinitions.map(varDef => varDef.variable));
      };
    }

    if (argumentsSelector) {
      listeners[argumentsSelector] = (
        node: GraphQLESTreeNode<FieldDefinitionNode | FieldNode | DirectiveDefinitionNode | DirectiveNode>
      ) => {
        checkNodes(node.arguments);
      };
    }

    if (opts.definitions) {
      listeners.Document = node => {
        checkNodes(node.definitions);
      };
    }

    return listeners;
  },
}
Example #8
Source File: require-id-when-available.ts    From graphql-eslint with MIT License 4 votes vote down vote up
rule: GraphQLESLintRule<[RequireIdWhenAvailableRuleConfig], true> = {
  meta: {
    type: 'suggestion',
    // eslint-disable-next-line eslint-plugin/require-meta-has-suggestions
    hasSuggestions: true,
    docs: {
      category: 'Operations',
      description: 'Enforce selecting specific fields when they are available on the GraphQL type.',
      url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID}.md`,
      requiresSchema: true,
      requiresSiblings: true,
      examples: [
        {
          title: 'Incorrect',
          code: /* GraphQL */ `
            # In your schema
            type User {
              id: ID!
              name: String!
            }

            # Query
            query {
              user {
                name
              }
            }
          `,
        },
        {
          title: 'Correct',
          code: /* GraphQL */ `
            # In your schema
            type User {
              id: ID!
              name: String!
            }

            # Query
            query {
              user {
                id
                name
              }
            }

            # Selecting \`id\` with an alias is also valid
            query {
              user {
                id: name
              }
            }
          `,
        },
      ],
      recommended: true,
    },
    messages: {
      [RULE_ID]:
        "Field{{ pluralSuffix }} {{ fieldName }} must be selected when it's available on a type.\nInclude it in your selection set{{ addition }}.",
    },
    schema: {
      definitions: {
        asString: {
          type: 'string',
        },
        asArray: ARRAY_DEFAULT_OPTIONS,
      },
      type: 'array',
      maxItems: 1,
      items: {
        type: 'object',
        additionalProperties: false,
        properties: {
          fieldName: {
            oneOf: [{ $ref: '#/definitions/asString' }, { $ref: '#/definitions/asArray' }],
            default: DEFAULT_ID_FIELD_NAME,
          },
        },
      },
    },
  },
  create(context) {
    const schema = requireGraphQLSchemaFromContext(RULE_ID, context);
    const siblings = requireSiblingsOperations(RULE_ID, context);
    const { fieldName = DEFAULT_ID_FIELD_NAME } = context.options[0] || {};
    const idNames = asArray(fieldName);

    // Check selections only in OperationDefinition,
    // skip selections of OperationDefinition and InlineFragment
    const selector = 'OperationDefinition SelectionSet[parent.kind!=/(^OperationDefinition|InlineFragment)$/]';
    const typeInfo = new TypeInfo(schema);

    function checkFragments(node: GraphQLESTreeNode<SelectionSetNode>): void {
      for (const selection of node.selections) {
        if (selection.kind !== Kind.FRAGMENT_SPREAD) {
          continue;
        }

        const [foundSpread] = siblings.getFragment(selection.name.value);
        if (!foundSpread) {
          continue;
        }

        const checkedFragmentSpreads = new Set<string>();
        const visitor = visitWithTypeInfo(typeInfo, {
          SelectionSet(node, key, parent: ASTNode) {
            if (parent.kind === Kind.FRAGMENT_DEFINITION) {
              checkedFragmentSpreads.add(parent.name.value);
            } else if (parent.kind !== Kind.INLINE_FRAGMENT) {
              checkSelections(node, typeInfo.getType(), selection.loc.start, parent, checkedFragmentSpreads);
            }
          },
        });

        visit(foundSpread.document, visitor);
      }
    }

    function checkSelections(
      node: OmitRecursively<SelectionSetNode, 'loc'>,
      type: GraphQLOutputType,
      // Fragment can be placed in separate file
      // Provide actual fragment spread location instead of location in fragment
      loc: ESTree.Position,
      // Can't access to node.parent in GraphQL AST.Node, so pass as argument
      parent: any,
      checkedFragmentSpreads = new Set<string>()
    ): void {
      const rawType = getBaseType(type);
      const isObjectType = rawType instanceof GraphQLObjectType;
      const isInterfaceType = rawType instanceof GraphQLInterfaceType;

      if (!isObjectType && !isInterfaceType) {
        return;
      }
      const fields = rawType.getFields();
      const hasIdFieldInType = idNames.some(name => fields[name]);

      if (!hasIdFieldInType) {
        return;
      }

      function hasIdField({ selections }: typeof node): boolean {
        return selections.some(selection => {
          if (selection.kind === Kind.FIELD) {
            if (selection.alias && idNames.includes(selection.alias.value)) {
              return true;
            }

            return idNames.includes(selection.name.value);
          }

          if (selection.kind === Kind.INLINE_FRAGMENT) {
            return hasIdField(selection.selectionSet);
          }

          if (selection.kind === Kind.FRAGMENT_SPREAD) {
            const [foundSpread] = siblings.getFragment(selection.name.value);
            if (foundSpread) {
              const fragmentSpread = foundSpread.document;
              checkedFragmentSpreads.add(fragmentSpread.name.value);
              return hasIdField(fragmentSpread.selectionSet);
            }
          }
          return false;
        });
      }

      const hasId = hasIdField(node);

      checkFragments(node as GraphQLESTreeNode<SelectionSetNode>);

      if (hasId) {
        return;
      }

      const pluralSuffix = idNames.length > 1 ? 's' : '';
      const fieldName = englishJoinWords(idNames.map(name => `\`${(parent.alias || parent.name).value}.${name}\``));

      const addition =
        checkedFragmentSpreads.size === 0
          ? ''
          : ` or add to used fragment${checkedFragmentSpreads.size > 1 ? 's' : ''} ${englishJoinWords(
              [...checkedFragmentSpreads].map(name => `\`${name}\``)
            )}`;

      const problem: ReportDescriptor = {
        loc,
        messageId: RULE_ID,
        data: {
          pluralSuffix,
          fieldName,
          addition,
        },
      };

      // Don't provide suggestions for selections in fragments as fragment can be in a separate file
      if ('type' in node) {
        problem.suggest = idNames.map(idName => ({
          desc: `Add \`${idName}\` selection`,
          fix: fixer => fixer.insertTextBefore((node as any).selections[0], `${idName} `),
        }));
      }
      context.report(problem);
    }

    return {
      [selector](node: GraphQLESTreeNode<SelectionSetNode, true>) {
        const typeInfo = node.typeInfo();
        if (typeInfo.gqlType) {
          checkSelections(node, typeInfo.gqlType, node.loc.start, node.parent);
        }
      },
    };
  },
}
Example #9
Source File: sibling-operations.ts    From graphql-eslint with MIT License 4 votes vote down vote up
export function getSiblingOperations(projectForFile: GraphQLProjectConfig): SiblingOperations {
  const siblings = getSiblings(projectForFile);

  if (siblings.length === 0) {
    let printed = false;

    const noopWarn = () => {
      if (!printed) {
        logger.warn(
          'getSiblingOperations was called without any operations. Make sure to set "parserOptions.operations" to make this feature available!'
        );
        printed = true;
      }
      return [];
    };

    return {
      available: false,
      getFragment: noopWarn,
      getFragments: noopWarn,
      getFragmentByType: noopWarn,
      getFragmentsInUse: noopWarn,
      getOperation: noopWarn,
      getOperations: noopWarn,
      getOperationByType: noopWarn,
    };
  }

  // Since the siblings array is cached, we can use it as cache key.
  // We should get the same array reference each time we get
  // to this point for the same graphql project
  if (siblingOperationsCache.has(siblings)) {
    return siblingOperationsCache.get(siblings);
  }

  let fragmentsCache: FragmentSource[] | null = null;

  const getFragments = (): FragmentSource[] => {
    if (fragmentsCache === null) {
      const result: FragmentSource[] = [];

      for (const source of siblings) {
        for (const definition of source.document.definitions) {
          if (definition.kind === Kind.FRAGMENT_DEFINITION) {
            result.push({
              filePath: source.location,
              document: definition,
            });
          }
        }
      }
      fragmentsCache = result;
    }
    return fragmentsCache;
  };

  let cachedOperations: OperationSource[] | null = null;

  const getOperations = (): OperationSource[] => {
    if (cachedOperations === null) {
      const result: OperationSource[] = [];

      for (const source of siblings) {
        for (const definition of source.document.definitions) {
          if (definition.kind === Kind.OPERATION_DEFINITION) {
            result.push({
              filePath: source.location,
              document: definition,
            });
          }
        }
      }
      cachedOperations = result;
    }
    return cachedOperations;
  };

  const getFragment = (name: string) => getFragments().filter(f => f.document.name?.value === name);

  const collectFragments = (
    selectable: SelectionSetNode | OperationDefinitionNode | FragmentDefinitionNode,
    recursive,
    collected = new Map<string, FragmentDefinitionNode>()
  ) => {
    visit(selectable, {
      FragmentSpread(spread) {
        const fragmentName = spread.name.value;
        const [fragment] = getFragment(fragmentName);

        if (!fragment) {
          logger.warn(
            `Unable to locate fragment named "${fragmentName}", please make sure it's loaded using "parserOptions.operations"`
          );
          return;
        }
        if (!collected.has(fragmentName)) {
          collected.set(fragmentName, fragment.document);
          if (recursive) {
            collectFragments(fragment.document, recursive, collected);
          }
        }
      },
    });
    return collected;
  };

  const siblingOperations: SiblingOperations = {
    available: true,
    getFragment,
    getFragments,
    getFragmentByType: typeName => getFragments().filter(f => f.document.typeCondition?.name?.value === typeName),
    getFragmentsInUse: (selectable, recursive = true) => Array.from(collectFragments(selectable, recursive).values()),
    getOperation: name => getOperations().filter(o => o.document.name?.value === name),
    getOperations,
    getOperationByType: type => getOperations().filter(o => o.document.operation === type),
  };

  siblingOperationsCache.set(siblings, siblingOperations);
  return siblingOperations;
}
Example #10
Source File: generate-query.ts    From graphql-query-generator with MIT License 4 votes vote down vote up
function getSelectionSetAndVars(
  schema: GraphQLSchema,
  node: DefinitionNode,
  config: InternalConfiguration,
  depth: number = 0
): {
  selectionSet: SelectionSetNode
  variableDefinitionsMap: {
    [variableName: string]: VariableDefinitionNode
  }
  variableValues: {
    [variableName: string]: any
  }
} {
  let selections: SelectionNode[] = []
  let variableDefinitionsMap: {
    [variableName: string]: VariableDefinitionNode
  } = {}
  let variableValues: { [variableName: string]: any } = {}

  // Abort at leaf nodes:
  if (depth === config.maxDepth) {
    return {
      selectionSet: undefined,
      variableDefinitionsMap,
      variableValues
    }
  }

  if (node.kind === Kind.OBJECT_TYPE_DEFINITION) {
    let fields = getRandomFields(node.fields, config, schema, depth)

    fields.forEach((field) => {
      // Recurse, if field has children:
      const nextNode = schema.getType(getTypeName(field.type)).astNode
      let selectionSet: SelectionSetNode = undefined
      if (typeof nextNode !== 'undefined') {
        const res = getSelectionSetAndVars(schema, nextNode, config, depth + 1)

        // Update counts and nodeFactor:
        config.resolveCount += config.nodeFactor
        config.nodeFactor *= getNextNodefactor(res.variableValues)
        config.typeCount += config.nodeFactor

        selectionSet = res.selectionSet
        variableDefinitionsMap = {
          ...variableDefinitionsMap,
          ...res.variableDefinitionsMap
        }
        variableValues = { ...variableValues, ...res.variableValues }
      }

      const avs = getArgsAndVars(
        field,
        node.name.value,
        config,
        schema,
        variableValues
      )
      variableDefinitionsMap = {
        ...variableDefinitionsMap,
        ...avs.variableDefinitionsMap
      }
      variableValues = { ...variableValues, ...avs.variableValues }

      selections.push({
        kind: Kind.FIELD,
        name: getName(field.name.value),
        selectionSet,
        arguments: avs.args
      })
    })
  } else if (node.kind === Kind.INTERFACE_TYPE_DEFINITION) {
    let fields = getRandomFields(node.fields, config, schema, depth)

    fields.forEach((field) => {
      // Recurse, if field has children:
      const nextNode = schema.getType(getTypeName(field.type)).astNode
      let selectionSet: SelectionSetNode = undefined
      if (typeof nextNode !== 'undefined') {
        const res = getSelectionSetAndVars(schema, nextNode, config, depth + 1)

        // Update counts and nodeFactor:
        config.resolveCount += config.nodeFactor
        config.nodeFactor *= getNextNodefactor(res.variableValues)
        config.typeCount += config.nodeFactor

        selectionSet = res.selectionSet
        variableDefinitionsMap = {
          ...variableDefinitionsMap,
          ...res.variableDefinitionsMap
        }
        variableValues = { ...variableValues, ...res.variableValues }
      }

      const avs = getArgsAndVars(
        field,
        node.name.value,
        config,
        schema,
        variableValues
      )
      variableDefinitionsMap = {
        ...variableDefinitionsMap,
        ...avs.variableDefinitionsMap
      }
      variableValues = { ...variableValues, ...avs.variableValues }

      selections.push({
        kind: Kind.FIELD,
        name: getName(field.name.value),
        selectionSet,
        arguments: avs.args
      })
    })

    // Get all objects that implement an interface
    let objectsImplementingInterface = Object.values(
      schema.getTypeMap()
    ).filter((namedType) => {
      if (
        namedType.astNode &&
        namedType.astNode.kind === 'ObjectTypeDefinition'
      ) {
        let interfaceNames = namedType.astNode.interfaces.map(
          (interfaceNamedType) => {
            return interfaceNamedType.name.value
          }
        )

        if (interfaceNames.includes(node.name.value)) {
          return true
        }
      }

      return false
    })

    // Randomly select named types from the union
    let pickObjectsImplementingInterface = objectsImplementingInterface.filter(
      () => {
        if (typeof config.breadthProbability === 'number') {
          return random(config) <= config.breadthProbability
        } else {
          return random(config) <= config.breadthProbability(depth)
        }
      }
    )

    // If no named types are selected, select any one
    if (pickObjectsImplementingInterface.length === 0) {
      const forcedCleanIndex = Math.floor(
        random(config) * objectsImplementingInterface.length
      )
      pickObjectsImplementingInterface.push(
        objectsImplementingInterface[forcedCleanIndex]
      )
    }

    pickObjectsImplementingInterface.forEach((namedType) => {
      if (namedType.astNode) {
        let type = namedType.astNode

        // Unions can only contain objects
        if (type.kind === Kind.OBJECT_TYPE_DEFINITION) {
          // Get selections
          let selectionSet: SelectionSetNode = undefined
          const res = getSelectionSetAndVars(schema, type, config, depth)
          selectionSet = res.selectionSet
          variableDefinitionsMap = {
            ...variableDefinitionsMap,
            ...res.variableDefinitionsMap
          }
          variableValues = { ...variableValues, ...res.variableValues }

          let fragment: InlineFragmentNode = {
            kind: Kind.INLINE_FRAGMENT,
            typeCondition: {
              kind: Kind.NAMED_TYPE,
              name: {
                kind: Kind.NAME,
                value: type.name.value
              }
            },
            selectionSet: selectionSet
          }

          selections.push(fragment)
        } else {
          throw Error(
            `There should only be object types ` +
              `in the selectionSet but found: ` +
              `"${JSON.stringify(type, null, 2)}"`
          )
        }
      } else {
        selections.push({
          kind: Kind.FIELD,
          name: {
            kind: Kind.NAME,
            value: namedType.name
          }
        })
      }
    })
  } else if (node.kind === Kind.UNION_TYPE_DEFINITION) {
    // Get the named types in the union
    let unionNamedTypes = node.types.map((namedTypeNode) => {
      return schema.getType(namedTypeNode.name.value)
    })

    // Randomly select named types from the union
    let pickUnionNamedTypes = unionNamedTypes.filter(() => {
      if (typeof config.breadthProbability === 'number') {
        return random(config) <= config.breadthProbability
      } else {
        return random(config) <= config.breadthProbability(depth)
      }
    })

    // If no named types are selected, select any one
    if (pickUnionNamedTypes.length === 0) {
      const forcedCleanIndex = Math.floor(
        random(config) * unionNamedTypes.length
      )
      pickUnionNamedTypes.push(unionNamedTypes[forcedCleanIndex])
    }

    pickUnionNamedTypes.forEach((namedType) => {
      if (namedType.astNode) {
        let type = namedType.astNode

        // Unions can only contain objects
        if (type.kind === Kind.OBJECT_TYPE_DEFINITION) {
          // Get selections
          let selectionSet: SelectionSetNode = undefined
          const res = getSelectionSetAndVars(schema, type, config, depth)
          selectionSet = res.selectionSet
          variableDefinitionsMap = {
            ...variableDefinitionsMap,
            ...res.variableDefinitionsMap
          }
          variableValues = { ...variableValues, ...res.variableValues }

          let fragment: InlineFragmentNode = {
            kind: Kind.INLINE_FRAGMENT,
            typeCondition: {
              kind: Kind.NAMED_TYPE,
              name: {
                kind: Kind.NAME,
                value: type.name.value
              }
            },
            selectionSet: selectionSet
          }

          selections.push(fragment)
        } else {
          throw Error(
            `There should only be object types ` +
              `in the selectionSet but found: ` +
              `"${JSON.stringify(type, null, 2)}"`
          )
        }
      } else {
        selections.push({
          kind: Kind.FIELD,
          name: {
            kind: Kind.NAME,
            value: namedType.name
          }
        })
      }
    })
  }

  let aliasIndexes: { [fieldName: string]: number } = {}
  let cleanselections: SelectionNode[] = []

  // Ensure unique field names/aliases
  selections.forEach((selectionNode) => {
    if (selectionNode.kind === Kind.FIELD) {
      let fieldName = selectionNode.name.value
      if (fieldName in aliasIndexes) {
        cleanselections.push({
          ...selectionNode,
          ...{
            alias: {
              kind: Kind.NAME,
              value: `${fieldName}${aliasIndexes[fieldName]++}`
            }
          }
        })
      } else {
        aliasIndexes[fieldName] = 2
        cleanselections.push(selectionNode)
      }
    } else if (selectionNode.kind === Kind.INLINE_FRAGMENT) {
      let cleanFragmentSelections: SelectionNode[] = []
      selectionNode.selectionSet.selections.forEach((fragmentSelectionNode) => {
        if (fragmentSelectionNode.kind === Kind.FIELD) {
          let fieldName = fragmentSelectionNode.name.value
          if (fieldName in aliasIndexes) {
            cleanFragmentSelections.push({
              ...fragmentSelectionNode,
              ...{
                alias: {
                  kind: Kind.NAME,
                  value: `${fieldName}${aliasIndexes[fieldName]++}`
                }
              }
            })
          } else {
            aliasIndexes[fieldName] = 2
            cleanFragmentSelections.push(fragmentSelectionNode)
          }
        }
      })

      selectionNode.selectionSet.selections = cleanFragmentSelections
      cleanselections.push(selectionNode)
    } else {
      throw Error(
        `There should not be any fragment spreads in the selectionNode "${JSON.stringify(
          selectionNode,
          null,
          2
        )}"`
      )
    }
  })

  return {
    selectionSet:
      cleanselections.length > 0
        ? {
            kind: Kind.SELECTION_SET,
            selections: cleanselections
          }
        : undefined,
    variableDefinitionsMap,
    variableValues
  }
}
Example #11
Source File: resolve-additional-resolvers.ts    From graphql-mesh with MIT License 4 votes vote down vote up
function generateSelectionSetFactory(
  schema: GraphQLSchema,
  additionalResolver: YamlConfig.AdditionalStitchingBatchResolverObject | YamlConfig.AdditionalStitchingResolverObject
) {
  if (additionalResolver.sourceSelectionSet) {
    return () => parseSelectionSet(additionalResolver.sourceSelectionSet);
    // If result path provided without a selectionSet
  } else if (additionalResolver.result) {
    const resultPath = toPath(additionalResolver.result);
    let abstractResultTypeName: string;

    const sourceType = schema.getType(additionalResolver.sourceTypeName) as GraphQLObjectType;
    const sourceTypeFields = sourceType.getFields();
    const sourceField = sourceTypeFields[additionalResolver.sourceFieldName];
    const resultFieldType = getTypeByPath(sourceField.type, resultPath);

    if (isAbstractType(resultFieldType)) {
      if (additionalResolver.resultType) {
        abstractResultTypeName = additionalResolver.resultType;
      } else {
        const targetType = schema.getType(additionalResolver.targetTypeName) as GraphQLObjectType;
        const targetTypeFields = targetType.getFields();
        const targetField = targetTypeFields[additionalResolver.targetFieldName];
        const targetFieldType = getNamedType(targetField.type);
        abstractResultTypeName = targetFieldType?.name;
      }
      if (abstractResultTypeName !== resultFieldType.name) {
        const abstractResultType = schema.getType(abstractResultTypeName);
        if (
          (isInterfaceType(abstractResultType) || isObjectType(abstractResultType)) &&
          !schema.isSubType(resultFieldType, abstractResultType)
        ) {
          throw new Error(
            `${additionalResolver.sourceTypeName}.${additionalResolver.sourceFieldName}.${resultPath.join(
              '.'
            )} doesn't implement ${abstractResultTypeName}.}`
          );
        }
      }
    }

    return (subtree: SelectionSetNode) => {
      let finalSelectionSet = subtree;
      let isLastResult = true;
      const resultPathReversed = [...resultPath].reverse();
      for (const pathElem of resultPathReversed) {
        // Ensure the path elem is not array index
        if (Number.isNaN(parseInt(pathElem))) {
          if (isLastResult && abstractResultTypeName && abstractResultTypeName !== resultFieldType.name) {
            finalSelectionSet = {
              kind: Kind.SELECTION_SET,
              selections: [
                {
                  kind: Kind.INLINE_FRAGMENT,
                  typeCondition: {
                    kind: Kind.NAMED_TYPE,
                    name: {
                      kind: Kind.NAME,
                      value: abstractResultTypeName,
                    },
                  },
                  selectionSet: finalSelectionSet,
                },
              ],
            };
          }
          finalSelectionSet = {
            kind: Kind.SELECTION_SET,
            selections: [
              {
                // we create a wrapping AST Field
                kind: Kind.FIELD,
                name: {
                  kind: Kind.NAME,
                  value: pathElem,
                },
                // Inside the field selection
                selectionSet: finalSelectionSet,
              },
            ],
          };
          isLastResult = false;
        }
      }
      return finalSelectionSet;
    };
  }
  return undefined;
}
Example #12
Source File: index.ts    From amplify-codegen with Apache License 2.0 4 votes vote down vote up
compileSelection(
    selectionNode: SelectionNode,
    parentType: GraphQLCompositeType,
    possibleTypes: GraphQLObjectType[],
    visitedFragments: Set<string>,
  ): Selection | null {
    switch (selectionNode.kind) {
      case Kind.FIELD: {
        const name = selectionNode.name.value;
        const alias = selectionNode.alias ? selectionNode.alias.value : undefined;

        const fieldDef = getFieldDef(this.schema, parentType, selectionNode);
        if (!fieldDef) {
          throw new GraphQLError(`Cannot query field "${name}" on type "${String(parentType)}"`, [selectionNode]);
        }

        const fieldType = fieldDef.type;
        const unmodifiedFieldType = getNamedType(fieldType);

        this.addTypeUsed(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);
                return {
                  name,
                  value: valueFromValueNode(arg.value),
                  type: (argDef && argDef.type) || undefined,
                };
              })
            : undefined;

        let field: Field = {
          kind: 'Field',
          responseKey,
          name,
          alias,
          args,
          type: fieldType,
          description: !isMetaFieldName(name) && description ? description : undefined,
          isDeprecated,
          deprecationReason: deprecationReason || undefined,
        };

        if (isCompositeType(unmodifiedFieldType)) {
          const selectionSetNode = selectionNode.selectionSet;
          if (!selectionSetNode) {
            throw new GraphQLError(`Composite field "${name}" on type "${String(parentType)}" requires selection set`, [selectionNode]);
          }

          field.selectionSet = this.compileSelectionSet(selectionNode.selectionSet as SelectionSetNode, unmodifiedFieldType);
        }
        return field;
      }
      case Kind.INLINE_FRAGMENT: {
        const typeNode = selectionNode.typeCondition;
        const type = typeNode ? (typeFromAST(this.schema, typeNode) as GraphQLCompositeType) : parentType;
        const possibleTypesForTypeCondition = this.possibleTypesForType(type).filter(type => possibleTypes.includes(type));
        return {
          kind: 'TypeCondition',
          type,
          selectionSet: this.compileSelectionSet(selectionNode.selectionSet, type, possibleTypesForTypeCondition),
        };
      }
      case Kind.FRAGMENT_SPREAD: {
        const fragmentName = selectionNode.name.value;
        if (visitedFragments.has(fragmentName)) return null;
        visitedFragments.add(fragmentName);

        const fragmentSpread: FragmentSpread = {
          kind: 'FragmentSpread',
          fragmentName,
          selectionSet: {
            possibleTypes,
            selections: [],
          },
        };
        this.unresolvedFragmentSpreads.push(fragmentSpread);
        return fragmentSpread;
      }
    }
  }