Giter Club home page Giter Club logo

graphql-query-complexity's Introduction

npm version CircleCI Twitter Follow

Slicknode is an all-in-one GraphQL backend, gateway and Headless CMS for creating custom backends and digital products with content management. It consist of 3 main components:

  • GraphQL Gateway: Compose builtin modules, custom data models and 3rd party APIs in a unified GraphQL API in minutes.
  • Content/Data Modeling: Declaratively model your content and data using the GraphQL schema definition language. Slicknode generates flexible CRUD queries and mutations with filters, pagination, authorization, etc. Automatic database migrations based on the schema definition enable full version control (review, branching, merge, etc.), CI-Pipeline integration, offline development.
  • Admin UI: Slicknode provides an automatically generated Admin-UI based on your data model with advanced content workflows (Draft → Review → Published), Localization, Version-History, etc.

Deploy to the managed Slicknode Cloud with a single command (with auto scaling, global GraphQL CDN, serverless infrastructure), or self-host in any data center with any cloud provider.

Headless GraphQL CMS

Links:

Why Slicknode?

Most content management systems are isolated systems. Combining them with your own or 3rd party APIs usually requires extensive knowledge about the CMS architecture and is time consuming. You can do it in the frontend using a headless CMS, but that adds complexity, is limited and has to be done by hand. Exending core functionality, for example as "simple" as adding a stripeCustomerId to the user object, is often hard or impossible. If you need to add custom business logic or need advanced access control, you usually need to write a custom backend, which is time consuming.

On the other hand, there are solutions to create backends with business logic, but they often lack content managment features like localization, non destructive editing of live content (Draft → Review → Published), Admin UI, version history etc.

Slicknode combines both by providing reusable building blocks, a modular architecture and a declarative programming model. An open-source framework to create custom backends and content management infrastructure with minimal code and minimal knowledge, while at the same time providing functionality to extend and customize everything.

Here are some of the problems that Slicknode solves:

  • Git-Workflows: The entire application structure and content model is stored in the local codebase, which can then be managed with version control like git and enable essential workflows like code review, reversing changes, feature branches, cloning, CI/CD integration etc.
  • Modular Architecture: It is often hard to reuse functionality or data models across projects. If you are building similar solutions, you often have to recreate the same thing over and over. Slicknode has a modular architecture at its core. This allows you to reuse, share and organize complex projects.
  • Flexibility: With a design around the open source GraphQL standard, you can combine Slicknode with any frontend technology, API, database, or existing IT infrastructure. Merge multiple GraphQL APIs into a unified data graph and bring it to the global edge network with Slicknode Cloud for low latencies.
  • Scalability: Slicknode was designed with a cloud native architecture from the ground up. The Slicknode Cloud is powered by serverless computing and a globally distributed CDN, it scales instantly to any traffic spikes without managing infrastructure. It has no single point of failure and a self-healing infrastructure that seamlessly handles outages of entire availability zones.

Features:

  • Instant GraphQL API on managed cloud infrastructure (or self-hosted)
  • Modular architecture
  • Custom publishing workflows (e.g.: Draft > Review > Translation > Published)
  • Strong data consistency with referential integrity and automatic database migrations
  • Internationalization
  • Declarative permission model (for multi tenant SaaS, customer facing apps, enterprise etc.)
  • Powerful content modeling features with relations, union types, interfaces, enum types etc.
  • Multi-Stage development workflow
  • Works with any frontend technology (React, Angular, Vue, Javascript, iOS, Android etc.)
  • Extend existing GraphQL APIs
  • Apollo Federation
  • Extensible with custom code (Javascript, TypeScript, Flow etc.)

Quickstart

This is a quickstart tutorial to create a Slicknode project from scratch. If you would rather start with a fullstack application, check out our NextJS blog starter

To get started with Slicknode, you need a Slicknode Cloud account. You can sign up for free here (No credit card required)

Check out this 10-minute tutorial which walks you through everything you need to get started:

Slicknode Getting Started

Installation

The Slicknode CLI can be installed via the terminal using npm. (How to get npm?)

npm install -g slicknode@latest

Usage

Initialize

To create a new Slicknode project, navigate to the folder where you want to create your new project and run:

slicknode init quickstart-project

# Change into the newly created project directory
cd ./quickstart-project

This will ask for your Slicknode login information when run for the first time. Enter the login information that you used when you signed up.

Adding Modules

Modules are the top level building blocks that let you organize your project in a modular way. They allow you to reuse functionality across multiple projects or to share them publicly with the community.

Now, add some builtin modules for content management and image handling to your project:

slicknode module add image content

Then deploy the changes:

slicknode deploy

Creating New Modules

Your own types will be added in your own modules. To create a blog for example, run:

slicknode module create blog

It will suggest a namespace and the label that will be displayed in the data browser. Just hit enter to use the suggested values for now.

This will create the following file structure in your project folder:

modules/
    blog/
        slicknode.yml
        schema.graphql
slicknode.yml

Model your schema

You can model your schema using the GraphQL SDL.

In your favorite editor, open the file modules/blog/schema.graphql and enter your schema, for example:

"""
A blog article
"""
type Blog_Article implements Content & Node {
  id: ID!

  title: String!
  image: Image
  slug: String! @unique
  text: String @input(type: MARKDOWN)
  category: Blog_Category
  createdAt: DateTime!
  lastUpdatedAt: DateTime

  # Content interface fields to enable content management
  contentNode: ContentNode!
  locale: Locale!
  status: ContentStatus!
  publishedAt: DateTime
  publishedBy: User
  createdAt: DateTime!
  createdBy: User
  lastUpdatedAt: DateTime
  lastUpdatedBy: User
}

type Blog_Category implements Content & Node {
  id: ID!

  name: String
  slug: String! @unique

  # Content interface fields to enable content management
  contentNode: ContentNode!
  locale: Locale!
  status: ContentStatus!
  publishedAt: DateTime
  publishedBy: User
  createdAt: DateTime!
  createdBy: User
  lastUpdatedAt: DateTime
  lastUpdatedBy: User
}

Save the file and check if you have any errors in your project by printing the project status. Run the status command from the project folder:

slicknode status

Deploy

To deploy the changes to the cloud, simply run:

slicknode deploy

Now you have a production ready content HUB with GraphQL API.

Explore

To explore your newly created GraphQL API, open the playground:

slicknode playground

This will open the GraphiQL playground for your API. (It might ask you for your login credentials)

To open the CMS data browser of your project:

slicknode console

Show the GraphQL endpoint that you can use with your GraphQL clients:

slicknode endpoint

Next Steps

Explore the full potential of Slicknode. Here are a few topics that can get you started:

Join our Slack community!

  • Tutorial: Build an advanced blog application in a step by step tutorial.
  • Client Setup: Connect your frontend application with the Slicknode server
  • Writing Extensions: Write custom extensions to add any API and database to your application
  • Auth: Secure your application and write complex permission rules that span multiple tables.
  • Data Modeling: Learn how to model the data for your application

Follow us on Twitter and Facebook.

graphql-query-complexity's People

Contributors

beehivejava avatar dimatill avatar hayes avatar hnq90 avatar hongbo-miao avatar huang-xiao-jian avatar ivome avatar marcosansoni avatar martinstepanek avatar michallytek avatar noc7c9 avatar piefayth avatar robhogan avatar zcei avatar zimuliu avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

graphql-query-complexity's Issues

Using this in an Apollo Gateway without `schema`

Hi, I'm using this amazing package to control the queries in our federated ApolloGateway. The problem comes when, following the examples, I always see the schema object passed to the new ApolloServer constructor and, we don't have the schema available at Gateway level.

I would prefer to keep this cost analyser in the Gateway to avoid repeating the config in all the sub ApolloServers we have.

Is there any way to achieve this? Is there something super obvious I'm missing?

The code I'm using is:

export async function bootstrap() {
  const gateway = createApolloGateway();
  const apolloServer = new ApolloServer({
    gateway,
    plugins: [logErrorPlugin],
    introspection: (process.env.INTROSPECTION === 'true'),
    debug: (process.env.DEBUG === 'true'),
    formatError: (err) => {
    // Don't give the specific errors to the client.
    if (err.extensions.code.startsWith('DOWNSTREAM_SERVICE_ERROR')) {
      return new Error('Internal server error from subgraph ' + err.extensions.serviceName + '.  Logs are in datadog');
    }
    return err;
  },

    context: ({ req }) => {
      // Get the user token from the headers
      // Try to retrieve a user with the token
      // Add the user ID to the context
      return { req };
    },
  });
  await apolloServer.start();

  const app = express();

  configExpress(app);
  configApolloServer(apolloServer, app);

  const server = await start(app, config.port);
  attachGracefulHandlers(server);

  return { server, app };
}

Declaration merging

Since your library add new field to GraphQLFieldConfig, you should use the declaration and interfaces merging mechanism. This way users wouldn't be forced to replace it with ComplexityGraphQLField in their codebase, it will just work out of the box 🎉

// `graphql.d.ts` file in your sources and package on npm

import { Complexity } from "graphql-query-complexity";

declare module "graphql/type/definition" {
  export interface GraphQLFieldConfig<TSource, TContext, TArgs = { [argName: string]: any }> {
    complexity?: Complexity;
  }
}

You should also expose this simple type to avoid repetition:

export type Complexity = number | ComplexityResolver;

Cannot read property 'multipliers' of undefined

Schema / Definition:

directive @complexity(
	# The complexity value for the field
	value: Int!,

	# Optional multipliers
	multipliers: [String!]
) on FIELD_DEFINITION

myProperties ( limit: Int!, findOptions: JSON ):
   PropertiesConnection @auth @complexity(value: 5, multipliers:["limit"])
...

Added to validationRules: (ApolloServer 2)

validationRules: [ queryComplexity.default({
	estimators: [
		directiveEstimator({
			name: 'complexity',
		}),
	],
	maximumComplexity: 1000,
	variables: req.body.variables || {},
	// Optional function to create a custom error
	createError: (max, actual) => {
		return new GraphQLError(`Query is too complex: ${actual}. Maximum allowed complexity: ${max}`);
	},
	onComplete: (complexity) => {console.log('Query Complexity:', complexity);},
})]

the Query:

myProperties (
   limit: 5
  ) {
    nodes {
      title
    }
  }

and above code throws the following error:

{
  "statusCode": 500,
  "error": "Internal Server Error",
  "message": "Cannot read property 'multipliers' of undefined"
}

any clue, what am doing wrong? BTW simpleEstimator works fine.

Feature Request: Support adding cost on types

Hi. I was wondering if it would make sense to add support for adding cost on the graphql types like this:

type Customer @complexity(value: 5){
  name: String
}
type Employee @complexity(value: 10){
  job: String
}

So for example in the next query, we can specify different complexities based on the type:

query MyQuery {
  People() {
    entities(first: 10) {
      nodes {
        ... on Customer {
          name
        }
        ... on Employee {
          job
        }
      }
    }
  }
}

In the previous example, the cost would be the max complexity of both types, in this case 10.

But for example if I do the query for only Customer like this:

query MyQuery {
 People() {
   entities(first: 10) {
     nodes {
       ... on Customer {
         name
       }
     }
   }
 }
}

The complexity for this query would be 5 instead.

Similar implementation as in this library: graphql-cost-analysis.

I am happy to contribute with a PR, but I wanted to get first your opinion.

Thanks.

GraphQL Upload File not working with query complexity

Hello everyone!
I'm using graphql query complexity in my project, but I got an error trying to upload a file

[ValidationError: Argument "file" of required type "Upload!" was provided the variable "$file" which was not provided a runtime value.] { message: 'Argument "file" of required type "Upload!" was provided the variable "$file" which was not provided a runtime value.', locations: [ { line: 2, column: 16 } ], path: undefined, extensions: { code: 'GRAPHQL_VALIDATION_FAILED', exception: { stacktrace: [Array] } } } { stacktrace: [ 'GraphQLError: Argument "file" of required type "Upload!" was provided the variable "$file" which was not provided a runtime value.', ' at Object.getArgumentValues (project/node_modules/graphql/execution/values.js:176:17)', ' at project/node_modules/graphql-query-complexity/src/QueryComplexity.ts:236:24', ' at Array.reduce (<anonymous>)', ' at QueryComplexity.nodeComplexity (project/node_modules/graphql-query-complexity/src/QueryComplexity.ts:197:84)', ' at QueryComplexity.onOperationDefinitionEnter (project/node_modules/graphql-query-complexity/src/QueryComplexity.ts:146:33)', ' at Object.enter (project/node_modules/graphql/language/visitor.js:323:29)', ' at Object.enter (project/node_modules/graphql/utilities/TypeInfo.js:370:25)', ' at visit (project/node_modules/graphql/language/visitor.js:243:26)', ' at Object.validate (project/node_modules/graphql/validation/validate.js:69:24)', ' at validate (project/node_modules/apollo-server-core/src/requestPipeline.ts:510:14)' ] } POST /graphql 400 8.352 ms - 106

It's pretty simple what I'm trying to do as we can see here:
image

I've followed this tutorial + graphql-query-complexity

https://dev.to/kingmaker9841/apollo-server-express-file-upload-on-local-storage-and-on-aws-s3-1km8

Did you already get this error?

Counting type complexity only once

I have a query
{ inqAcctGeneralInfo { clientId acctId field1 field2 } }

for schema
Query { inqAcctGeneralInfo: AcctGeneralInfo } type AcctGeneralInfo { clientId: Int acctId: String field1: String field2: String }

and i would like to count the type and assign it a complexity of 5, and have it still count its subfields as 1. So my above query would have a total complexity of 9. I am seeing that the estimators are called per field and i am currently getting complexity of 5 (4 returned fields plus 1 for the query itself). is there any way to be able to count the type once in a custom estimator?

Calculate query comlexity outside validation context

I was wondering if it's possible to calculate complexity based on the current query, statically. In an ideal situation we'd be able to pass a string containing a GraphQL query or an info object which would return the complexity for that query.

This way we would be able to display the cost of a query to the user similar to how GitHub does: https://developer.github.com/v4/guides/resource-limitations/#returning-a-calls-rate-limit-status.

I've tried fiddling around by manually invoking the nodeComplexity function:

class SomeResolver {
    public async resolve(root: any, args: any, ctx: IAppContext, info: GraphQLResolveInfo): Promise<number> {
        const context = new ValidationContext(info.schema, ... /* I have no idea how to get this */ , new TypeInfo(info.schema));
        const complexity = this.getQueryComplexity(ctx)(context) as QueryComplexity;
        const result = complexity.nodeComplexity(..., ... /* I have no idea how to get these */ );

        return result;
    }

    private getQueryComplexity(ctx: IAppContext): Function {
        return queryComplexity({
            maximumComplexity: 1000,
            variables: ctx.request.query.variables,
            estimators: [
                fieldConfigEstimator(),
                simpleEstimator({
                    defaultComplexity: 1
                })
            ]
        });
    }
}

interface IAppContext {
    request: express.Request;
    response: express.Response;
}

But as you can see I have no idea how to manually get a DocumentNode for the ValidationContext and I have no idea how to get a FieldNode and typeDef for the nodeComplexity function.

That being said I don't even know if this would be the right way to do this, maybe I'm missing something?

API stability?

Just wondering about the API stability as the package is still published as major version 0. Are there plans to release version 1.0.0? Thanks

Invalid module name in augmentation...Module 'graphql/type/definition' resolves to an untyped module

node_modules/.registry.npmjs.org/graphql-query-complexity/0.2.2/[email protected]/node_modules/graphql-query-complexity/dist/QueryComplexity.d.ts:6:16 - error TS2665: Invalid module name in augmentation. Module 'graphql/type/definition' resolves to an untyped module at '/xxx/node_modules/.registry.npmjs.org/graphql/14.0.2/node_modules/graphql/type/definition.js', which cannot be augmented.

6 declare module 'graphql/type/definition' {
                 ~~~~~~~~~~~~~~~~~~~~~~~~~

Maybe this helps: https://stackoverflow.com/a/42522751/130910

Error Cannot use GraphQLSchema "[object GraphQLSchema]" from another module or realm.

I face this error. It seems due to the presence of extensions in import file names.
The library generating my schema uses ".mjs" files of graphql module, whereas graphql-query-complexity uses ".js" ones, and so graphql module believe that there are 2 different versions of it used.

For instance:

	import {
	  getArgumentValues,
	  getDirectiveValues,
	  getVariableValues,
	} from 'graphql/execution/values.js';

should be replaced with

	import {
	  getArgumentValues,
	  getDirectiveValues,
	  getVariableValues,
	} from 'graphql/execution/values';

Return the highest complexity of nested fragments when using a UnionType or Interfaces

When using UnionType or Interfaces, the current implementation is returning the sum of all nested fragments.

As the types of the conditional fragments are mutually exclusive, I would not expect the complexities to be added up.

I would suggest using the complexity of the highest nested fragment.

Example:

      query {
        union {
          ...on Item { // assume complexity 22
            scalar
            complexScalar
          }
          ...on SecondItem { // assume complexity 2
            name
            scalar
          }
         value // assume complexity 1
        }
      }
    `

I would expect the resulting complexity to be 23.

Feature request: adding path to estimators args(for calculating actual query complexity)

I want to have both the expected and actual query complexity/cost, like in the shopify api: https://shopify.dev/api/usage/rate-limits#requested-and-actual-cost.

The expected query cost is pretty easy with this library, an example using an ApolloServer plugin:

{
  requestDidStart: async () => ({
    async didResolveOperation({ request, document }) {
      const complexity = getComplexity({
        schema,
        operationName: request.operationName,
        query: document,
        variables: request.variables,
        estimators: [
          directiveEstimator({ name: 'complexity' }),
          simpleEstimator({ defaultComplexity: 1 }),
        ],
      });
      logger.info('Expected query complexity points:', { complexity });
    }
  })
}

However, I'm having trouble calculating the actual query cost, here is my attempt so far:

{
  requestDidStart: async () => ({
    async willSendResponse({ request, document, response }) {
      // uses response objects to determine the actual operation complexity
      const responseEstimator: ComplexityEstimator = (options) => {
        // there is no "options.path"
        // const object = _.get(response, options.path);
        // i can then use object.length(or something else) to get the number of returned items
        // return object.length ?? 1;
        return 1;
      };
      const complexity = getComplexity({
        schema,
        operationName: request.operationName,
        query: document!,
        variables: request.variables,
        estimators: [responseEstimator],
      });
      logger.info('Actual query complexity points:', { complexity });
    },
  })
}

It seems for this implementation to work I'd need access to the path of the field, like in https://github.com/graphql/graphql-js/blob/main/src/type/definition.ts#L896 or the fourth field on the resolvers(info.path), so I can map the field in which the complexity is being calculated to the actual response

Is there a way to assign complexities to mutations, objects and fields by default ?

Is there a way to set up this library such that I provide a configuration where I specify the default cost for mutations, objects and fields ? Something like this:
estimator({mutation: 100, object: 10, field: 1})

This would be very useful for me and I saw it on another library that is not maintained. I cannot add custom directives on my schema and i cannot add extensions as well, because I am using apollo gateway.

Feature request: Federation support for directives

I setup complexity calculations at the Gateway level using directive complexity and the default complexity was returned instead of using the values that are on the subgraphs. It looks like the simpleEstimator did work though as it returned a result that was relative to how much was going on in the query.

When I moved the directiveEstimator to the subgraph, it correctly returned the complexity however.

I can go into further detail if needed.

Update to GraphQL-JS v14

Hi @ivome!

I've tried to update graphql-query-complexity to support graphql-js v14 but I got this error while running the tests:

(...)
\graphql-query-complexity\node_modules\graphql\validation\validate.js:89
    throw new Error(errors.map(function (error) {
          ^
Error: Unknown directive "complexity".

Unknown directive "complexity".

Unknown directive "cost".

Unknown directive "complexity".

Unknown directive "complexity".

Unknown directive "complexity".
    at assertValidSDL 
(...)

Because v14 is making better validation of the schema/query:
https://github.com/graphql/graphql-js/releases/tag/v14.0.0

Can you take a look at your test suite and maybe update readme to add info about the demand of registering directives explicitly? 😕

Empty runtime variables with @include directive

Hej folks,

when using the validation rule in combination with a query that provides a variable with a default value, the @include (and potentially also the @skip) directive is causing troubles.

GraphQLError: Argument "if" of required type "Boolean!" was provided the variable "$shouldSkip" which was not provided a runtime value.

I was able to reproduce this with a test case, and I'm wondering what the expected behavior is here.

it('should respect @include(if: false) via default variable value', () => {
  const ast = parse(`
    query Foo ($shouldSkip: Boolean = false) {
      variableScalar(count: 10) @include(if: $shouldSkip)
    }
  `);

  const complexity = getComplexity({
    estimators: [
      simpleEstimator({defaultComplexity: 1})
    ],
    schema,
    query: ast
  });
  expect(complexity).to.equal(0);
});

I see that you're only using graphql-js's getDirectiveValues, which under the hood has a logic that throws the error here.

But to me it sounds like it should first check for the query document default variables.

I feel like we have two options here:

  1. obtain default variables from operation definition here and add these to this.options.variables (or a derivate object of it).
  2. try to convince graphql-js maintainers that there's a flaw in their flow of obtaining runtime values.

As the graphql-js library does not seem to have issues with @include directives, I'm leaning towards (1), wdyt?

How to implement with Apollo server v3

I spent several hours finding the best way to implement graphql-query-complexity with Apollo server v3 (server and serverless)
and I just wanted to post the answer in case anyone else needs it.

The trouble I ran into is that apollo server doesn't give its validationRules access to the request object so calculating query complexity based on request parameters is not possible. In apollo validationRules are not guaranteed to run on every request.

The solution is to use the plugin api's didResolveOperation hook to manually trigger a complexity calculation further along the request pipeline as explained here.

If you are using express (apollo-server-express or apollo-server-lambda or apollo-server-cloud-functions) you could probably do this as express middleware as well.

Feel free to close this Issue.

EDIT: You will need to modify the code example to work with the latest version of graphql-query-complexity (fieldConfigEstimator is deprecated). If you are using Apollo and building your schema using gql() you may also need to convert your schema into a GraphQLSchema. Graphql includes a utility to do this import { buildASTSchema } from 'graphql';.

Feature Request: Complexity estimator receives all args to be used as multiplier factor

Hi!
To have a more precise and reliable complexity estimation, I was wondering about the following feature.
I see your project already supports the multiplier feature. But from what I can see in your code, it's only possible to operate on arguments directly applied to the query node itself.
For our use case, it would be nice to have all the query arguments, all the way down to the estimated node. I'll try to explain to you why.

This is how we structured our queries when list and pagination are involved:

query userPosts {
  user {
    id
    name
    surname
    blogPosts (pagination: {page:1, pageSize:3}) {
      count
      items {
        id
        post {
          id
          title
          body
        }
      }
    }
  }
}

user, blogPosts, and post are resolved by different endpoints/services.

Our main goal is to apply the multiplier factor to the resolution of post. So the complexity of post's resolution should be multiplied by the pageSize parameter/argument. So far, that's not possible because the estimator does not receive all the query arguments, but only the node's ones.
It would be awesome to have all the arguments available and use them for complexity computation 👍

What do you think about this? I would be happy to contribute, too!

Weird error when running mutations with invalid inputs

Hey, I've encountered a bug when running mutations and providing an invalid input.

Given the following schema

type Query {
  hello: String
}

input AddFavoriteFoodInput {
  id: ID!
  comment: String!
  foodNames: [String!]!
}

type Mutation {
  addFavoriteFood(input: AddFavoriteFoodInput!): Boolean!
}

if you run the mutation

mutation AddFavoriteFood($input: AddFavoriteFoodInput!) {
  addFavoriteFood(input: $input)
}

with variables

{
  "input": {
    "id": "123",
    "comment": "Hello",
    "foodNames": [
      "Pizza",
      "Apple",
       null,
      "Pineapple"
    ]
  }
}

the expected error message is:

"Variable \"$input\" got invalid value null at \"input.foodNames[2]\";
Expected non-nullable type \"String!\" not to be null."

However with GraphQL Complexity enabled, this turns into

"Argument \"input\" of required type \"AddFavoriteFoodInput!\" was provided the
variable \"$input\" which was not provided a runtime value."

Here's a repo reproducing the issue: https://github.com/adamors/graphql-complexity-bug

Federation Gateway Schema undefined

Hi I am using a GraphQL Federation and would like to include the Complexity in the Gateway. If I pass the schema then unfortunately it doesn't work because the gateway can pass an undefined schema. Is it possible to fix this?

Babel issue in projects not using babel

I have never used Babel and this might be a very basic question.

I have a typescript project that doesn't use babel or webpack and I am using apollo-server. When I tried to use graphql-query-complexity I got the following error

Error: Cannot find module 'babel-runtime/helpers/typeof'
    at Function.Module._resolveFilename (module.js:547:15)
    at Function.Module._load (module.js:474:25)
    at Module.require (module.js:596:17)
    at require (internal/module.js:11:18)
    at Object.<anonymous> (C:\Users\garp\Source\Data Platform\teams-adp\node_modules\graphql-query-complexity\dist\QueryComplexity.js:7:16)
    at Module._compile (module.js:652:30)
    at Object.Module._extensions..js (module.js:663:10)
    at Module.load (module.js:565:32)
    at tryModuleLoad (module.js:505:12)
    at Function.Module._load (module.js:497:3)

I found that you are using babel in devDependencies. So I tried adding those to my project's devDependencies and the issue got resolved.

My questions are

  • Since devDependencies are not required at the run time, what is causing this error?
  • Is adding all those babel packages to my devDependencies is the only clean solution? Since this project is not using babel, I am bit hesitant with adding it to devDependencies now.

Feature request: Add directive estimation support for "initial complexity"

Imagine we have an external service that we're calling out to in order to populate the foo array field.

I can request any number of elements for that, but the issue is that asking for any number drastically increases complexity, whereas the relative complexity between requesting 1 and 2 entries is much lower.

It would be good to have an "initial" field on the complexity directive, e.g.

@complexity(value: 1, multipliers: :["limit"], initial: 10)

in this example, requesting with a limit of 1 would be a complexity of 11, and requesting 2 would be a complexity if 12, etc.

If we like this suggestion I'm happy to take a crack at implementation

`TypeError` from passing invalid arguments when there's more than one query present

A query such as:

query {
  requiredArgs(count: x) {
    scalar
    complexScalar
  }
  nonNullItem {
    scalar
    complexScalar
    variableScalar(count: 10)
  }
}

results in complexityMap, being undefined, and the error TypeError: Cannot convert undefined or null to object arising from this call.

Here's a reproduction https://github.com/dburles/graphql-query-complexity/blob/bug/src/__tests__/QueryComplexity-test.ts#L800-L821. Running this test will result in the error being thrown.

GraphQL should be reporting "Argument \"count\" has invalid value x.".

Is there an easy way to calculate complexity on just the operationName called?

Right now I've plugged in the basic queryComplexity validationRule but noticed that i'm getting many logs of complexity queries. It turns out that clients are sometimes sending me POST objects with entire schemas full of queries and only calling one via operationName. I am therefore getting many scores logging, eventually i get NaNs logged then eventually it's erroring out complaining that my variables are missing in my query. For example

"{\"errors\":[{\"message\":\"Argument \\\"site\\\" of required type \\\"Site!\\\" was provided the variable \\\"$site\\\" which was not provided a runtime value.

When I take out the extra querys in the incoming POST, I actually get a correct logged query complexity score and no error.

So one option I have working is to manually call getComplexity which takes a query- here i can filter out all queries except for the one matching operationName. However, this is pretty hard to do if there is a query with a ... fragment.

I wonder if this is a feature we can have built into the module from the get go? Only score what the user is actually calling based on operationName ? thanks.

how to calculate connection fields

Hi!

I've got a connection field Network.channels

type Network {
  channels(
    after: String
    before: String
    first: Int
    last: Int
  ): ChannelConnection!
}

type ChannelConnection {
  edges: [ChannelEdge!]!
  nodes: [Channel!]!
  pageInfo: PageInfo!
}

type ChannelEdge {
  cursor: String!
  node: Channel!
}

type Channel {
  id: String!
}

So Network.channels takes an argument first or last which tells us how many nodes or edges the ChannelConnection is going to have. I can't use the field based custom estimator because the args are on the parent field and I don't access to the args when I'm calculating in the ChannelConnection.nodes or ChannelConnection.edges.

Is there a way to work around this?

Using within typescript project

Thank you for this awesome package, which I'm sure it'll become an important part in every graphql server written in Javascript.

I have a graphql server based on Apollo Server and the whole project is written in TypeScript. For running the server I use ts-node.

The problem I have is when I make a query, I always get 'Query Complexity of 0'. Then I made a custom Typescript module to where I copied the graph-query-complexity code with small modifications regarding only the type definitions. With this module, the Query Complexity is calculated as it should.

I'm not sure if this is a problem of the package or the fact that I'm using Typescript with ts-node.

Any help would be more than welcome.

Feature Request: expose the node to estimators

Hiya, first off thanks for the amazing library.

I'm trying to implement a custom estimation that depends on which fields are selected under a type.

Here's an example:

query {
    a: someType { id } # Only id is selected, so calculation happens one way
    b: someType { id, some, fields } # More than id is selected so calculation happens another way
}

I don't believe that the exact behavior I want is achievable with childComplexity unfortunately, I need access to the selectionSet for the given node.

Hopefully that made some sense!

Pass Current Type to complexity()

Hey so I've been trying to figure out how to use your library in my project and I ran across a use case that which would require an extension to field.complexity(). It's a bit hard to explain given a lack of well defined terms, but perhaps I can demonstrate what I'm looking for in an example.

Let's say I have an Album type:

const Album = new GraphQLObject({
  name: `Album`,
  fields: () => ({
    id: {
      type: GraphQLID
    },
    totalPhotos: {
      type: GraphQLInt // Let's say we have 100 Photos in the Album
    },
    photos: {
      type: new GraphQLList(Photo),
      args: {
        count: { type: GraphQLInt } // And we pass a Count of 150 in our Query
      },
      // It would therefor follow that if we used just args.Count alone, our complexity would be inflated
      // The calculated complexity of this node should be capped at 100
      complexity: (args, childComplexity, parent) =>
        childComplexity * (args.count < parent.totalPhotos ? args.count : parent.totalPhotos),
      // Lets assume our resolver will return an array of Photos of size Count, or all the Photos in the Album
      resolve: (parent, args, info) => fetchAlbumPhotos(parent.id, args.count)
    }
  })
})

To make this possible a change would need to be made to field.complexity(), ie: the addition of a third argument parent, similar to resolve as used in the above example. parent would be an object containing all the current values of the Type.

I hope my code comments make clear how complexity calculation could be inaccurate in a case such as this. If we look at maximumComplexity as a budget for our queries, then we don't want to over-pay for the cost of execution.

Let me know if you need further clarification!

Support Node.js ESM

Node.js ESM is not supported; please support it.

In demo.mjs:

import {
  createComplexityRule,
  simpleEstimator,
} from 'graphql-query-complexity';

Then run node demo.mjs:

file://[redacted]/demo.mjs:3
  simpleEstimator,
  ^^^^^^^^^^^^^^^
SyntaxError: Named export 'simpleEstimator' not found. The requested module 'graphql-query-complexity' is a CommonJS module, which may not support all module.exports as named exports.
CommonJS modules can always be imported via the default export, for example using:

import pkg from 'graphql-query-complexity';

    at ModuleJob._instantiate (internal/modules/esm/module_job.js:97:21)
    at async ModuleJob.run (internal/modules/esm/module_job.js:142:20)
    at async Loader.import (internal/modules/esm/loader.js:182:24)
    at async Object.loadESM (internal/process/esm_loader.js:68:5)

Ideally don't publish CJS at all, to avoid the dual package hazard.

Please make sure that the modules can all be deep imported without having to go through the index; that way we can avoid the JavaScript runtime parsing and loading into memory stuff our program doesn't use. E.g:

import createComplexityRule from 'graphql-query-complexity/createComplexityRule.mjs';
import simpleEstimator from 'graphql-query-complexity/simpleEstimator.mjs';

Exception if the GraphQL query has validation errors

If the GraphQL query has validation errors such as variables not being used, or referencing unknown fragments, normally a GraphQL server can respond with a 400 status and a JSON response containing the GraphQL errors.

Unfortunately the GraphQL query complexity validator errors if the GraphQL query has validation issues, causing a 500 response that doesn't contain the detailed GraphQL errors. Ideally the complexity analysis would account for the possibility the query is invalid, and not cause an exception so that the normal GraphQL query validation errors can be reported.

Example correct 400 response for an invalid query:

{
  "errors": [
    {
      "message": "Unknown fragment \"galleryExhibitConnection\".",
      "locations": [{ "line": 1, "column": 1532 }]
    },
    {
      "message": "Variable \"$devicePixelRatio\" is never used.",
      "locations": [{ "line": 1, "column": 623 }]
    },
    {
      "message": "Variable \"$cardPaginationLimit\" is never used.",
      "locations": [{ "line": 1, "column": 648 }]
    },
    {
      "message": "Variable \"$cardExhibitImageWidth\" is never used.",
      "locations": [{ "line": 1, "column": 757 }]
    },
    {
      "message": "Variable \"$cardExhibitImageHeight\" is never used.",
      "locations": [{ "line": 1, "column": 785 }]
    },
    {
      "message": "Variable \"$galleryExhibitsPageCursor\" is never used.",
      "locations": [{ "line": 1, "column": 894 }]
    },
    {
      "message": "Fragment \"GalleryExhibits_GalleryExhibitConnection\" is never used.",
      "locations": [{ "line": 1, "column": 1 }]
    }
  ]
}

With query complexity setup, the actual response has a 500 status without the GraphQL errors:

{
  "errors": [{ "message": "Internal Server Error" }]
}

Here is the error logged in the server:

TypeError: Cannot read property 'typeCondition' of undefined
    at /[redacted]/node_modules/graphql-query-complexity/dist/QueryComplexity.js:155:118
    at Array.reduce (<anonymous>)
    at QueryComplexity.nodeComplexity (/[redacted]/node_modules/graphql-query-complexity/dist/QueryComplexity.js:88:75)
    at /[redacted]/node_modules/graphql-query-complexity/dist/QueryComplexity.js:129:52
    at Array.reduce (<anonymous>)
    at QueryComplexity.nodeComplexity (/[redacted]/node_modules/graphql-query-complexity/dist/QueryComplexity.js:88:75)
    at /[redacted]/node_modules/graphql-query-complexity/dist/QueryComplexity.js:172:53
    at Array.reduce (<anonymous>)
    at QueryComplexity.nodeComplexity (/[redacted]/node_modules/graphql-query-complexity/dist/QueryComplexity.js:88:75)
    at /[redacted]/node_modules/graphql-query-complexity/dist/QueryComplexity.js:129:52
    at Array.reduce (<anonymous>)
    at QueryComplexity.nodeComplexity (/[redacted]/node_modules/graphql-query-complexity/dist/QueryComplexity.js:88:75)
    at QueryComplexity.onOperationDefinitionEnter (/[redacted]/node_modules/graphql-query-complexity/dist/QueryComplexity.js:50:41)
    at Object.enter (/[redacted]/node_modules/graphql/language/visitor.js:323:29)
    at Object.enter (/[redacted]/node_modules/graphql/utilities/TypeInfo.js:370:25)
    at visit (/[redacted]/node_modules/graphql/language/visitor.js:243:26)

I'm using graphql-api-koa, but I don't think that's relevant as it's GraphQL.js based.

Support graphql v15

Hi, graphql-query-complexity currently does not include graphql 15.0 as a supported peer depenency. Even though graphql 15 came with some breaking changes, I don't think anything needs to be changed to support it.

QueryComplexity does not work with ES Modules (ES2020, Typescript)

Hi. I have my codebase in Typescript and had queryComplexity setup in my project some time ago. It worked well when my codebase was transpiled to commonjs and I was targetting ES2017

But recently I made the move to use ES2020 with Node.js modules (type:"module" in package.json`) and I get this error:

e1

This is how I am importing (as in the README):
import queryComplexity, { directiveEstimator, simpleEstimator } from 'graphql-query-complexity';

And my validationRules look like this (from the README):

validationRules: [
            queryComplexity({
              estimators: [
                directiveEstimator({
                  name: 'complexity'
                }),
                simpleEstimator({
                  defaultComplexity: 1
                })
              ],
              maximumComplexity: 500,
              variables: variables,
              onComplete: (complexity: number) => {
                console.log('Query Complexity:', complexity);
              },
              createError: (max: number, actual: number) => {
                return new GraphQLError(`Query is too complex: ${actual}. Maximum allowed complexity: ${max}`);
              }
            })
          ]

My old TSConfig (which worked):

{
  "compileOnSave": true,
  "compilerOptions": {
    "target": "ES2017",
    "module": "commonjs",
    "declaration": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "moduleResolution": "node",
    "esModuleInterop": true,
    "incremental": true,
    "tsBuildInfoFile": "./dist/tsbuildinfo",
    "alwaysStrict": true,
    "skipLibCheck": true,
    "noImplicitAny": false,
    "noUnusedLocals": true,
    "removeComments": true,
    "preserveConstEnums": true,
    "allowSyntheticDefaultImports": true,
    "outDir": "dist",
    "rootDir": "src",
    "declarationMap": true,
    "sourceMap": true,
    "strict": true,
    "noEmitOnError": true,
    "lib": ["ES2017"]
  },
  "include": ["./src/**/*"],
  "exclude": ["node_modules", "**/*.spec.ts", "**/*.d.ts"]
}

My new TSConfig which does not work:

{
	"compileOnSave": true,
	"compilerOptions": {
	  "target": "ES2020",
	  "module": "ESNext",
	  "declaration": true,
	  "experimentalDecorators": true,
	  "emitDecoratorMetadata": true,
	  "moduleResolution": "node",
	  "esModuleInterop": true,
	  "incremental": true,
	  "tsBuildInfoFile": "./dist/tsbuildinfo",
	  "alwaysStrict": true,
	  "skipLibCheck": true,
	  "noImplicitAny": false,
	  "noUnusedLocals": true,
	  "removeComments": true,
	  "preserveConstEnums": true,
	  "allowSyntheticDefaultImports": true,
	  "outDir": "dist",
	  "rootDir": "src",
	  "declarationMap": true,
	  "sourceMap": true,
	  "strict": true,
	  "noEmitOnError": true,
	  "lib": ["ES2020"]
	},
	"include": ["./src/**/*"],
	"exclude": ["node_modules", "**/*.spec.ts", "**/*.d.ts"]
  }

PS: All other libraries work except this since I have esModuleInterop set.

Any ideas what I might be doing wrong? Thanks.

Feature request: Exponential complexity calculation

There should be a possibility for an exponential complexity calculation. Our graphql data models have a lot of relations and most of these can return multiple entities. Therefore the depth of the query can exponentially increase the returned data size. This should be possible to be taken into account in the complexity calculation.

The complexity of a type which is fetched as part of a set should exponentially increase the complexity. This could be extended for the DirectiveEstimator:

type board {
posts: [Post] @Complexity(childMultiplier: 3) #complexity.value should be optional
}

This also could be extended on the options for the directive estimator:

directiveEstimator({ childComplexity: 1, childListMultiplier: 3 })

The childListMultiplier calculates the complexity of the child sub query and multiplies it with the defined value

Example

The following example shows the issue without exponential complexity calculation:

type Board { name: String
posts: [Post] @Complexity(value: 5)
}
type User { name: String
boards: [Board] @Complexity(value: 5)
}
type Post { text: String
viewers: [User] @Complexity(value: 5)
}
type Query {
boards(ids: [ID]): [Board] @Complexity(value: 5)
}

With the queries

query {
q1: boards(ids:["1"]) { n1: name n2: name n3: name n4: name }
q2: boards(ids:["1"] { posts { text } posts2: posts { viewers { name } } }
q3: boards(ids:["1"] { posts { viewers { boards { name } } } }
}

All queries have a simple complexity of around 5. Using the directive Estimator q2 has a complexity of 22 and q3 21, although the amount of returned entities is exponentially bigger in q3.

GraphQL schema language

Is it possible to use this module with GraphQL schema language? If so would it be possible to extend the docs o demonstrate such a usage? Thank you in advance :)

ApolloServer and Upload File

I'm having issues with integrating graphql-query-complexity to ApolloServer. Mainly because of multipart requests (sending images).

This is the error I encounter if I have createComplexityRule set and try to do a multipart:
"message": "Argument \"file\" of required type \"Upload!\" was provided the variable \"$file\" which was not provided a runtime value.",

image

And here is the request:
image
image

The code for the server:

const server = new ApolloServer({
    schema,
    validationRules: [
      depthLimit(4),
      createComplexityRule({
        maximumComplexity: 32,
        estimators: [simpleEstimator({ defaultComplexity: 1 })]
      }),
    ]
  });

How can I make multipart requests work with graphql-query-complexity + ApolloServer? Some of the closed issues seem to suggest using variables, but how can I do that with ApolloServer?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.