graphql#visitWithTypeInfo TypeScript Examples

The following examples show how to use graphql#visitWithTypeInfo. 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: no-unused-fields.ts    From graphql-eslint with MIT License 6 votes vote down vote up
function getUsedFields(schema: GraphQLSchema, operations: SiblingOperations): UsedFields {
  // We don't want cache usedFields on test environment
  // Otherwise usedFields will be same for all tests
  if (process.env.NODE_ENV !== 'test' && usedFieldsCache) {
    return usedFieldsCache;
  }
  const usedFields: UsedFields = Object.create(null);
  const typeInfo = new TypeInfo(schema);

  const visitor = visitWithTypeInfo(typeInfo, {
    Field(node): false | void {
      const fieldDef = typeInfo.getFieldDef();
      if (!fieldDef) {
        // skip visiting this node if field is not defined in schema
        return false;
      }
      const parentTypeName = typeInfo.getParentType().name;
      const fieldName = node.name.value;

      usedFields[parentTypeName] ??= new Set();
      usedFields[parentTypeName].add(fieldName);
    },
  });

  const allDocuments = [...operations.getOperations(), ...operations.getFragments()];
  for (const { document } of allDocuments) {
    visit(document, visitor);
  }
  usedFieldsCache = usedFields;
  return usedFieldsCache;
}
Example #2
Source File: Variables.ts    From tql with MIT License 6 votes vote down vote up
buildVariableDefinitions = <T extends SelectionSet<any>>(
  schema: GraphQLSchema,
  root: OperationTypeNode,
  selectionSet: T
): Array<VariableDefinition<any, any>> => {
  const variableDefinitions: VariableDefinition<any, any>[] = [];
  const typeInfo = new TypeInfo(schema);

  // @note need to wrap selectionset in an operation (root) for TypeInfo to track correctly
  const operationDefinition = operation(
    root,
    "",
    selectionSet,
    variableDefinitions
  );

  const visitor = visitWithTypeInfo(typeInfo, {
    [Kind.ARGUMENT]: (node) => {
      const type = typeInfo.getArgument()?.astNode?.type!;

      if (node.value.kind === "Variable") {
        // define the `VariableDefinition`
        variableDefinitions.push(variableDefinition(node.value, type));
      }
    },
  });

  // @todo return from here
  visit(operationDefinition, visitor);

  return variableDefinitions;
}
Example #3
Source File: converter.ts    From graphql-eslint with MIT License 5 votes vote down vote up
export function convertToESTree<T extends DocumentNode>(node: T, schema?: GraphQLSchema) {
  const typeInfo = schema ? new TypeInfo(schema) : null;

  const visitor: ASTVisitor = {
    leave(node, key, parent) {
      const leadingComments: Comment[] =
        'description' in node && node.description
          ? [
              {
                type: node.description.block ? 'Block' : 'Line',
                value: node.description.value,
              },
            ]
          : [];

      const calculatedTypeInfo: TypeInformation | Record<string, never> = typeInfo
        ? {
            argument: typeInfo.getArgument(),
            defaultValue: typeInfo.getDefaultValue(),
            directive: typeInfo.getDirective(),
            enumValue: typeInfo.getEnumValue(),
            fieldDef: typeInfo.getFieldDef(),
            inputType: typeInfo.getInputType(),
            parentInputType: typeInfo.getParentInputType(),
            parentType: typeInfo.getParentType(),
            gqlType: typeInfo.getType(),
          }
        : {};

      const rawNode = () => {
        if (parent && key !== undefined) {
          return parent[key];
        }
        return node.kind === Kind.DOCUMENT
          ? <DocumentNode>{
              ...node,
              definitions: node.definitions.map(definition =>
                (definition as unknown as GraphQLESTreeNode<DefinitionNode>).rawNode()
              ),
            }
          : node;
      };

      const commonFields: Omit<GraphQLESTreeNode<typeof node>, 'parent'> = {
        ...node,
        type: node.kind,
        loc: convertLocation(node.loc),
        range: [node.loc.start, node.loc.end],
        leadingComments,
        // Use function to prevent RangeError: Maximum call stack size exceeded
        typeInfo: () => calculatedTypeInfo as any, // Don't know if can fix error
        rawNode,
      };

      return 'type' in node
        ? {
            ...commonFields,
            gqlType: node.type,
          }
        : commonFields;
    },
  };

  return visit(node, typeInfo ? visitWithTypeInfo(typeInfo, visitor) : visitor) as GraphQLESTreeNode<T>;
}
Example #4
Source File: index.ts    From graphql-mesh with MIT License 5 votes vote down vote up
export default function useMeshLiveQuery(options: MeshPluginOptions<YamlConfig.LiveQueryConfig>): Plugin {
  const liveQueryInvalidationFactoryMap = new Map<string, ResolverDataBasedFactory<string>[]>();
  options.logger.debug(`Creating Live Query Store`);
  const liveQueryStore = new InMemoryLiveQueryStore({
    includeIdentifierExtension: true,
  });
  options.liveQueryInvalidations?.forEach(liveQueryInvalidation => {
    const rawInvalidationPaths = liveQueryInvalidation.invalidate;
    const factories = rawInvalidationPaths.map(rawInvalidationPath =>
      getInterpolatedStringFactory(rawInvalidationPath)
    );
    liveQueryInvalidationFactoryMap.set(liveQueryInvalidation.field, factories);
  });
  return useEnvelop(
    envelop({
      plugins: [
        useLiveQuery({ liveQueryStore }),
        {
          onExecute(onExecuteParams) {
            if (!onExecuteParams.args.schema.getDirective('live')) {
              options.logger.warn(`You have to add @live directive to additionalTypeDefs to enable Live Queries
See more at https://www.graphql-mesh.com/docs/recipes/live-queries`);
            }
            return {
              onExecuteDone({ args: executionArgs, result }) {
                queueMicrotask(() => {
                  const { schema, document, operationName, rootValue, contextValue } = executionArgs;
                  const operationAST = getOperationAST(document, operationName);
                  if (!operationAST) {
                    throw new Error(`Operation couldn't be found`);
                  }
                  const typeInfo = new TypeInfo(schema);
                  visit(
                    operationAST,
                    visitWithTypeInfo(typeInfo, {
                      Field: () => {
                        const parentType = typeInfo.getParentType();
                        const fieldDef = typeInfo.getFieldDef();
                        const path = `${parentType.name}.${fieldDef.name}`;
                        if (liveQueryInvalidationFactoryMap.has(path)) {
                          const invalidationPathFactories = liveQueryInvalidationFactoryMap.get(path);
                          const invalidationPaths = invalidationPathFactories.map(invalidationPathFactory =>
                            invalidationPathFactory({
                              root: rootValue,
                              context: contextValue,
                              env: process.env,
                              result,
                            })
                          );
                          liveQueryStore
                            .invalidate(invalidationPaths)
                            .catch((e: Error) => options.logger.warn(`Invalidation failed for ${path}: ${e.message}`));
                        }
                      },
                    })
                  );
                });
              },
            };
          },
        },
      ],
    })
  );
}
Example #5
Source File: index.ts    From graphql-mesh with MIT License 5 votes vote down vote up
transformRequest(executionRequest: ExecutionRequest, delegationContext: DelegationContext): ExecutionRequest {
    const { transformedSchema, rootValue, args, context, info } = delegationContext;
    if (transformedSchema) {
      const errors: GraphQLError[] = [];
      const resolverData: ResolverData = {
        env: process.env,
        root: rootValue,
        args,
        context,
        info,
      };
      const typeInfo = new TypeInfo(transformedSchema);
      let remainingFields = 0;
      const newDocument = visit(
        executionRequest.document,
        visitWithTypeInfo(typeInfo, {
          Field: () => {
            const parentType = typeInfo.getParentType();
            const fieldDef = typeInfo.getFieldDef();
            const path = `${parentType.name}.${fieldDef.name}`;
            const rateLimitConfig = this.pathRateLimitDef.get(path);
            if (rateLimitConfig) {
              const identifier = stringInterpolator.parse(rateLimitConfig.identifier, resolverData);
              const mapKey = `${identifier}-${path}`;
              let remainingTokens = this.tokenMap.get(mapKey);

              if (remainingTokens == null) {
                remainingTokens = rateLimitConfig.max;
                const timeout = setTimeout(() => {
                  this.tokenMap.delete(mapKey);
                  this.timeouts.delete(timeout);
                }, rateLimitConfig.ttl);
                this.timeouts.add(timeout);
              }

              if (remainingTokens === 0) {
                errors.push(new GraphQLError(`Rate limit of "${path}" exceeded for "${identifier}"`));
                // Remove this field from the selection set
                return null;
              } else {
                this.tokenMap.set(mapKey, remainingTokens - 1);
              }
            }
            remainingFields++;
            return false;
          },
        })
      );
      if (remainingFields === 0) {
        if (errors.length === 1) {
          throw errors[0];
        } else if (errors.length > 0) {
          throw new AggregateError(errors);
        }
      }
      this.errors.set(delegationContext, errors);
      return {
        ...executionRequest,
        document: newDocument,
      };
    }
    return executionRequest;
  }
Example #6
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);
        }
      },
    };
  },
}