Coder Social home page Coder Social logo

graphql-jit's Introduction

GraphQL JIT

npm codecov

Why?

GraphQL-JS is a very well written runtime implementation of the latest GraphQL spec. However, by compiling to JS, V8 is able to create optimized code which yields much better performance. graphql-jit leverages this behaviour of V8 optimization by compiling the queries into functions to significantly improve performance (See benchmarks below)

Benchmarks

GraphQL-JS 16 on Node 16.13.0

$ yarn benchmark skip-json
Starting introspection
graphql-js x 1,941 ops/sec ±2.50% (225 runs sampled)
graphql-jit x 6,158 ops/sec ±2.38% (222 runs sampled)
Starting fewResolvers
graphql-js x 26,620 ops/sec ±2.41% (225 runs sampled)
graphql-jit x 339,223 ops/sec ±2.94% (215 runs sampled)
Starting manyResolvers
graphql-js x 16,415 ops/sec ±2.36% (220 runs sampled)
graphql-jit x 178,331 ops/sec ±2.73% (221 runs sampled)
Starting nestedArrays
graphql-js x 127 ops/sec ±1.43% (220 runs sampled)
graphql-jit x 1,316 ops/sec ±2.58% (219 runs sampled)
Done in 141.25s.

Support for GraphQL spec

The goal is to support the June 2018 version of the GraphQL spec.

Differences to graphql-js

In order to achieve better performance, the graphql-jit compiler introduces some limitations. The primary limitation is that all computed properties must have a resolver and only these can return a Promise.

More details here - GraphQL-JS.md

Install

yarn add graphql-jit

Example

For complete working examples, check the examples/ directory

Create a schema

const typeDefs = `
type Query {
  hello: String
}
`;
const resolvers = {
  Query: {
    hello() {
      return new Promise((resolve) => setTimeout(() => resolve("World!"), 200));
    }
  }
};

const { makeExecutableSchema } = require("@graphql-tools/schema");
const schema = makeExecutableSchema({ typeDefs, resolvers });

Compile a Query

const query = `
{
  hello
}
`;
const { parse } = require("graphql");
const document = parse(query);

const { compileQuery, isCompiledQuery } = require("graphql-jit");
const compiledQuery = compileQuery(schema, document);
// check if the compilation is successful

if (!isCompiledQuery(compiledQuery)) {
  console.error(compiledQuery);
  throw new Error("Error compiling query");
}

Execute the Query

const executionResult = await compiledQuery.query(root, context, variables);
console.log(executionResult);

Subscribe to the Query

const result = await compiledQuery.subscribe(root, context, variables);
for await (const value of result) {
  console.log(value);
}

API

compiledQuery = compileQuery(schema, document, operationName, compilerOptions)

Compiles the document AST, using an optional operationName and compiler options.

  • schema {GraphQLSchema} - graphql schema object

  • document {DocumentNode} - document query AST ,can be obtained by parse from graphql

  • operationName {string} - optional operation name in case the document contains multiple operations(queries/mutations/subscription).

  • compilerOptions {Object} - Configurable options for the compiler

    • disableLeafSerialization {boolean, default: false} - disables leaf node serializers. The serializers validate the content of the field at runtime so this option should only be set to true if there are strong assurances that the values are valid.
    • customSerializers {Object as Map, default: {}} - Replace serializer functions for specific types. Can be used as a safer alternative for overly expensive serializers
    • customJSONSerializer {boolean, default: false} - Whether to produce also a JSON serializer function using fast-json-stringify. The default stringifier function is JSON.stringify

compiledQuery.query(root: any, context: any, variables: Maybe<{ [key: string]: any }>)

the compiled function that can be called with a root value, a context and the required variables.

compiledQuery.subscribe(root: any, context: any, variables: Maybe<{ [key: string]: any }>)

(available for GraphQL Subscription only) the compiled function that can be called with a root value, a context and the required variables to produce either an AsyncIterator (if successful) or an ExecutionResult (error).

compiledQuery.stringify(value: any)

the compiled function for producing a JSON string. It will be JSON.stringify unless compilerOptions.customJSONSerializer is true. The value argument should be the return of the compiled GraphQL function.

LICENSE

MIT

graphql-jit's People

Contributors

addityasingh avatar airhorns avatar ajordanow avatar aldis-ameriks avatar andrew0 avatar ardatan avatar bbnjmn avatar boopathi avatar dependabot[bot] avatar eomm avatar fjmoreno avatar frikille avatar hoangvvo avatar igrlk avatar ivangoncharov avatar karihe avatar markrzen avatar mikkeloscar avatar nabeelbukhari avatar oporkka avatar perploug avatar ponimas avatar ruiaraujo avatar simenb avatar snyk-bot avatar topicus avatar trim21 avatar trkohler avatar vigneshshanmugam avatar wojtkiewicz 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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

graphql-jit's Issues

Input type defaults not supported?

Our stack is using a combination of apollo-server-express (to be removed), graphql-envelop, graphql-modules, and now recently we were adding graphql-jit.

While the stack works alright otherwise, it appears that out input type defaults do not get applied in mutations. A sample:

input CustomerInput {
  id: ID
  type: CustomerType = WORK_SITE
}

Before digging further, I'd like to get your confirmation whether input type defaults should be working, or is this something new? Could this be related to using ENUMs or something else? Is there a working sample/test/whatever that I could try and extend?

GraphQL v15 support

Hello,

GraphQL v15 was released recently and I'm curious if there is a plan to enable this lib with the new version?

Name new Function to identify queries

Functions created in the executor are not named which makes hard to debug problematic queries.

  • Add persisted query as functionName if possible
  • Otherwise use the operationName

Error metadata from custom directives

I'm looking into using custom directives for validation using graphql-tools based on the docs here.
https://www.graphql-tools.com/docs/schema-directives/#enforcing-value-restrictions

The validation seems to be working and the error message in the compiled query result matches the one which is thrown from the validation directive. However, I cannot seem to get the originalError, including extensions from the error that's thrown from the validation directive.

I saw this issue #2 which might be similar, but I'm not actually throwing the error from within the resolver, so it may be a different case.

I've created a sandbox with the example code to illustrate what I'm trying to achieve and how.
https://codesandbox.io/s/long-grass-hlul0?file=/src/index.js

Is this type of case supported and/or is there some way to get the error metadata?
Thanks!

Empty object when use fragment inside @skip and @include

Hello,
Im trying to integrate graphql-jit into an existing Apollo Server Service, we detected some queries sended by clients that are not working correctly when we change to graphql-jit, i tracked it down to this edge case (ill use very simplified queries):
Steps to reproduce
Start example/blog-apollo-server and make a this query:

query TEST(
    $includeOnly: Boolean!
){
    post(id: "post:1") {
        title
        Author1: author @skip(if: $includeOnly) {
            ...authorFragment
        }
        Author2: author @include(if: $includeOnly) {
            ...authorFragment
        }
    }
}
fragment authorFragment on User {
    name
}

Along with this variables:

{
    "includeOnly": true
}

and you will get an empty Author object:

{
  "data": {
    "post": {
      "title": "Introduction to GraphQL!",
      "Author2": {}
    }
  }
}

it seems to be working if i remove the fragment and use props directly

Output compiled queries at buildtime

Is it possible to compile queries and have the function output in some way?

The rationale is that I don't want to wait for queries to come in to build up the cache of compiled queries at runtime. This isn't ideal when the GraphQL API is running in a cluster environment, as there's no guarantee the containers will have any or all compiled versions of the query.

Original errors in resolvers are swallowed

The actual version of graphql-js adds the original error into the originalError property for each error in the errors array returned by the execute function.
With graphql-jit the original error is swallowed (only the message/locations/path tuple is returned).

graphql-jit rejecting nullable parameter with default values in @include directive

Copied from n1ru4l/envelop#1615

Issue workflow progress

Progress of the issue based on the Contributor Workflow

  • 1. The issue provides a minimal reproduction available on Stackblitz.
    • Please install the latest @envelop/* packages that you are using.
    • Please make sure the reproduction is as small as possible.
  • 2. A failing test has been provided
  • 3. A local solution has been provided
  • 4. A pull request is pending review

Describe the bug

Nullable arguments to a query with a default values are considered non-nullable. This, however, is not the case for variables used in directives (specifically tested with @include(if: ):

query myQuery($inc: Boolean = true) {
  myField(id: 5) @include(if: $inc)
}

This yields the following error:

Variable 'inc' of type 'Boolean' used in position expecting type 'Boolean!'

To Reproduce

See above.

Expected behavior

The error should not occur, and the directive should be evaluated according to the variable's default value.

Environment:

    "@envelop/core": "^3.0.4",
    "@envelop/graphql-jit": "^5.0.4",
    "@envelop/newrelic": "5.0.4",
    "@envelop/sentry": "^4.0.4",
  • OS: Ubuntu 22.04.1 LTS (WSL 2)
  • NodeJS: v18.12.1

Additional context

n/a

doesn't handle resolveType that returns a promise

For abstract types, graphql-js handles resolveType if it's a promise https://github.com/graphql/graphql-js/blob/e590dd2b6f1d05085fbb057fe357be957378bcfb/src/execution/execute.js#L967

graphql-jit currently does not https://github.com/zalando-incubator/graphql-jit/blob/master/src/execution.ts#L896

this normally wouldn't cause a problem because most people don't return a promise from resolveType, but type-graphql converts all resolveTypes to return promises so I'm running into this.

not working with root resolvers

I'm currently trying to adjust https://github.com/mcollina/fastify-gql to use this.

However, I'm getting stuck on this problem:

{
  "data": {
    "add": null
  },
  "errors": [
    {
      "message": "Int cannot represent non-integer value: [function add]",
      "locations": [
        {
          "line": 1,
          "column": 2
        }
      ],
      "path": [
        "add"
      ]
    }
  ]
}

This is our source code:

'use strict'

const Fastify = require('fastify')
const GQL = require('.')

const app = Fastify()

const schema = `
  type Query {
    add(x: Int, y: Int): Int
  }
`

const resolvers = {
  add: async ({ x, y }) => x + y
}

app.register(GQL, {
  schema,
  resolvers,
  graphiql: true
})

app.get('/', async function (req, reply) {
  const query = '{ add(x: 2, y: 2) }'
  return reply.graphql(query)
})

app.listen(3000)

Note that attaching add to the query object works.

'use strict'

const Fastify = require('fastify')
const GQL = require('.')

const app = Fastify()

const schema = `
  type Query {
    add(x: Int, y: Int): Int
  }
`

const resolvers = {
  Query: {
    add: async (_, obj) => {
      const { x, y } = obj
      return x + y
    }
  }
}

app.register(GQL, {
  schema,
  resolvers,
  graphiql: true
})

app.get('/', async function (req, reply) {
  const query = '{ add(x: 2, y: 2) }'
  return reply.graphql(query)
})

app.listen(3000)

Our integration lives here: https://github.com/mcollina/fastify-gql/tree/jit.

GraphQL 17 support?

Hey everyone ✋. I'm currently working on adding @defer directive support to Mercurius GraphQL server here.
And it looks like some of the tests in Mercurius where it interacts with graphql-jit, and most of the tests in this repo are failing against the new major version of GraphQL (version 17, which is in alpha yet).

Are there any plans on adding support for graphql@17? Also, how open are you to the PRs from the community to add support for graphql@17, in case someone will want to contribute?

Looking forward to hearing from you. Thanks! 👍

Unexpected token 'import'

We would like to switch from express-graphql to express-gql (which uses graphql-jit) and are having trouble with one of our queries: import(id: Int!): Import

The function compileQuery(...) returns the following error:

{"errors":[{"message":"Unexpected token 'import'"}]}

As far as I understand, graphql-jit compiles the query into a JS function and import is a reserved keyword in JavaScript but not in GraphQL.

Is there any way to modify the function generation so that those keywords are okay to use?

Directives are ignored by mutations

@skip, @include work for queries, but they are ignored by mutations.

Code to reproduce:

mutation {
  doThing @include(if: false) {
    success
  }
}

Expected: {"data": {}}
Actual: {"data": {"doThing": {"success": true}}}

Related: #96

misbehaving spreading fragments

Given Schema:

type Query {
  detailContent: [Content!]
}


type Post implements Content {
  id: ID!
  title: String!
  type: String!
  related: [Content]
}

type Article implements Content {
  id: ID!
  title: String!
  type: String!
}

interface Content {
  id: ID!
  title: String!
  type: String!
}

and query:

query TEST(
    $includeOnly: Boolean!
){
  detailContent{
    ...articleFragment
    ...on Post {
      ...postFragment 
      related{
          id
  	  ...articleFragment @include(if:$includeOnly)
    	}
    }
  }
}

fragment postFragment on Post {
  id
  title
}

fragment articleFragment on Article {
title
  type
}

Expect:

{
  "data": {
    "detailContent": [
      {
        "id": "post:1",
        "title": "Introduction to GraphQL!",
        "related": [
          {
            "id": "article:1",
          }
        ]
      },
      {
        "id": "post:2",
        "title": "GraphQL-Jit a fast engine for GraphQL",
        "related": [
          {
            "id": "article:2",
          }
        ]
      },
      {
        "title": "article Introduction to GraphQL!",
        "type": "article"
      },
      {
        "title": "article GraphQL-Jit a fast engine for GraphQL",
        "type": "article"
      }
    ]
  }
}

But receive:

{
  "data": {
    "detailContent": [
      {
        "id": "post:1",
        "title": "Introduction to GraphQL!",
        "related": [
          {}
        ]
      },
      {
        "id": "post:2",
        "title": "GraphQL-Jit a fast engine for GraphQL",
        "related": [
          {}
        ]
      },
      {},
      {}
    ]
  }
}

README.md not clear on consequences of limitations

At https://github.com/zalando-incubator/graphql-jit/#differences-to-graphql-js, the README.md mentions that

The primary limitation is that all computed properties must have a resolver and only these can return a Promise.

However, it doesn't mention what the consequences are of failing to meet these criteria. How can they be violated? Will there be an error if they are violated, or will execution fall back to non-jitted? Could you please elaborate on the consequences of the limitations?

[RangeError: invalid String length]

Hello,

Im getting an error when use skip/inlude in a deep node, this error comes from this function:

graphql-jit/src/ast.ts

Lines 301 to 331 in 15ec15f

function joinShouldIncludeCompilations(...compilations: string[]) {
// remove empty strings
let filteredCompilations = compilations.filter(it => it);
// FIXME(boopathi): Maybe, find a better fix?
//
// remove "true" since we are joining with '&&' as `true && X` = `X`
// This prevents an explosion of `&& true` which could break
// V8's internal size limit for string.
//
// Note: the `true` appears if a field does not have a skip/include directive
// So, the more nested the query is, the more of unnecessary `&& true`
// we get.
//
// Failing to do this results in [RangeError: invalid array length]
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Invalid_array_length
filteredCompilations = filteredCompilations.filter(
it => it.trim() !== "true"
);
/**
* If we have filtered out everything in the list, it means, we
* called this with a join(true, true, etc...) - returning empty string
* is wrong, so we return a single true
*/
if (filteredCompilations.length < 1) {
return "true";
}
return filteredCompilations.join(" && ");
}

More specifically from at .join on line 330, the reason of that is that this function deduplicate multiples trues but it fails to deduplicate of the conditions comes from a variables, repeating multiple times same condition on the __internalShouldInclude,

for example, if you have a nested skip/include with a var:

....
@skip(if: $includeOnly)
....

you can get this __internalShouldInclude:

(__context.variables["includeOnly"] === false) && (__context.variables["includeOnly"] === false) &&  (__context.variables["includeOnly"] === false) && (__context.variables["includeOnly"] === false) && (__context.variables["includeOnly"] === false) && (__context.variables["includeOnly"] === false) &&  (__context.variables["includeOnly"] === false) && (__context.variables["includeOnly"] === false)
.......................................

Must deduplicate it to ensure not exceed v8 max string length

Request variables with circular dependency

Hi,

I encountered this issue when making a create request for entity A that has a circular reference with B. Schema definition is implemented with type-graphql roughly like this.

@ObjectType()
@InputType('FooInput')
class Foo {
    @Field(() => Number)
    id: number;
    @Field(() => Bar, {nullable: true})
    bar: Bar;
}

@ObjectType()
@InputType('BarInput')
class Bar {
    @Field(() => Number)
    id: number;
    @Field(() => Foo, {nullable: true})
    foo: Foo;
}

@Resolver(Foo)
class FooResolver {
    @Mutation()
    addFoo(@Arg('foo') foo: Foo): string {
        return 'ok';
    }
}

Now if I try to send a query

mutation AddFoo($foo: FooInput!){
    addFoo(foo: $foo)
}

with variables

{
  "foo": {
    "id": 1,
    "bar": null
  }
}

the query execution gets stuck in a recursive loop during compileQuery call or more specifically in generateInput function. Apparently when going through the request variables in if (isInputType(varType)) block generating another call to itself and this goes on until out of memory error.

I'm using apollo-server with custom executor like described in the examples. Similar issues do not occur with default apollo-server config or by running the query with with graphql-js execute.

new ApolloServer({
    executor: ({ source }) => {
      const compiled = compileQuery(schema, parse(source)); // <-- never returns from here
      // ...
    }
});

graphql-jit and TypeGraphQL union issues.

I'm using TypeGraphQL for defining my schema and resolvers. When enabling graphql-jit and fetching a union type I got the following error:
Abstract type Notifiable must resolve to an Object type at runtime for field Notification.notifiable. Either the Notifiable type should provide a \"resolveType\" function or each possible types should provide an \"isTypeOf\" function

It works fine without graphql-jit.

TypeGraphQL union types docs

Union type definition:

const NotifiableUnion = createUnionType({
    name: 'Notifiable',
    types: () => [A,B,] as const,
    resolveType: value => {
        if ('title' in value) {
            return A;
        }

        return B;
    },
});

subscribe function doesn't pass correct arguments when hardcoded to the query

when passing arguments via variables the subscription resolver receives args correctly. If the variables aren't used and hardcoded to the query the subscription resolver gets an empty object.

This modification to src/__tests__/subscription.test.ts will demonstrate

Changed the query from

    subscription ($priority: Int = 0) {
      importantEmail(priority: $priority) {
        email {
          from
          subject
        }
        inbox {
          unread
          total
        }
      }
    }

to

     subscription {
      importantEmail(priority: 0) {
        email {
          from
          subject
        }
        inbox {
          unread
          total
        }
      }
    }

and added expect(args).toEqual({"priority": 0})

function createSubscription(pubsub: SimplePubSub<Email>) {
  const document = parse(`
    subscription {
      importantEmail(priority: 0) {
        email {
          from
          subject
        }
        inbox {
          unread
          total
        }
      }
    }
  `);

  const emails = [
    {
      from: "[email protected]",
      subject: "Hello",
      message: "Hello World",
      unread: false
    }
  ];

  const inbox = { emails };

  const QueryType = new GraphQLObjectType({
    name: "Query",
    fields: {
      inbox: { type: InboxType, resolve: (...args) => {
        return emails
      } }
    }
  });

  const emailSchema = new GraphQLSchema({
    query: QueryType,
    subscription: new GraphQLObjectType({
      name: "Subscription",
      fields: {
        importantEmail: {
          type: EmailEventType,
          args: {
            priority: { type: GraphQLInt }
          },
          // FIXME: we shouldn't use mapAsyncIterator here since it makes tests way more complex
          subscribe(root,args) {
            return pubsub.getSubscriber((newEmail) => {
              expect(args).toEqual({"priority": 0})
              emails.push(newEmail);

              return {
                importantEmail: {
                  email: newEmail,
                  inbox
                }
              };
            });
          }
        }
      }
    })
  });

variables should default to undefined

We've switched to fastify+graphql-jit and had mostly a really easy/seamless transition. Thanks for the project!

One bump we hit was that it seems like when graphql-jit does variable interpolation for clients, it starts out with null default values, i.e. for a client mutation that looks like:

mutation SaveTaskStatus(
  $complete: Boolean	
  $durationInDays: Int	
  $internalUserId: ID	
  $internalNote: String	
  $baselineMode: Boolean!	
) {	
  saveTask(	
    input: {	
      id: $taskId	
      complete: $complete	
      durationInDays: $durationInDays	
      internalUserId: $internalUserId	
      internalNote: $internalNote	
      baselineMode: $baselineMode	
    }	
  ) {

Here is a chunk of our graphql-jit-generated code:

  let MutationsaveTaskResolverValidArgs = true;
  const MutationsaveTaskResolverArgs = {"input":{"id":null,"durationInDays":null,"complete":null,"internalNote":null,"internalUserId":null,"baselineMode":null}};
  if (Object.prototype.hasOwnProperty.call(__context.variables, "taskId")) {
    MutationsaveTaskResolverArgs["input"]["id"] = __context.variables['taskId'];
    }if (Object.prototype.hasOwnProperty.call(__context.variables, "durationInDays")) {
    MutationsaveTaskResolverArgs["input"]["durationInDays"] = __context.variables['durationInDays'];

Where it starts out with the input.id/durationInDays/etc. fields as null and then sets them to the __context.variables value only if the variable is present.

Which means that with graphql-jit, if a client did not set the (say) internalNote variable, it shows up in our resolver as args.input.internalNote === null, which for us means "unset / clear".

For us, this was a behavior change from apollo-server, where if a client did not set the internalNote variable, it would show up in our resolver as args.input.internalNote === undefined, which for us is "do not change".

So this caused a fairly bad bug where our clients were "wiping" these fields like internalNote, internalUserId, etc., b/c they didn't set the variables. (...I believe I also tried to have the clients set the variables as undefined, but that meant apollo-client didn't send them over the wire, just as if we had not set them at all.)

So, again, disclaimer I don't technically if apollo-server or graphql-jit is "more correct", and so I'm kind of lazily filing the issue with the guess that apollo-sever is correct here, but I'm not 100% sure.

Non-primitive custom scalar argument gets coerced to a string

I tried adding graphql-jit to a project of mine and ran into some type errors with resolvers that have custom Date scalars as arguments. I made a failing test demonstrating the problem here: andrew0@abf1add.

it("deserializes non-primitive scalars properly", async () => {
  const request = `
    {
      scalar(arg: "2022-01-01")
    }
  `;

  const schema = new GraphQLSchema({
    query: new GraphQLObjectType({
      name: "Query",
      fields: {
        scalar: {
          type: GraphQLString,
          args: {
            arg: {
              type: new GraphQLScalarType({
                name: "Date",
                serialize: (value: any) => value.toISOString().slice(0, 10),
                parseValue: (value: any) => new Date(value),
                parseLiteral: (ast) =>
                  ast.kind === Kind.STRING ? new Date(ast.value) : null
              })
            }
          },
          resolve: (_parent, { arg }) => Object.prototype.toString.call(arg)
        }
      }
    })
  });

  const result = await executeQuery(schema, parse(request));
  expect(result).toEqual({
    data: {
      scalar: "[object Date]"
    }
  });
});

With the regular graphql-js engine, this test passes, but with graphql-jit, arg gets converted to the string 2022-01-01T00:00:00.000Z (i.e. the result of calling toJSON() on a Date object).

Support custom scalars in json serialiser

Currently only Int, Float, String, Boolean and ID scalars are supported while generating the json-schema for fast-json-stringify. Any unknown scalar triggers "Got unexpected PRIMITIVES type: XXX" error to be thrown.

https://github.com/zalando-incubator/graphql-jit/blame/9b2ef2036c820473044d8d9cef36a58f81ff9800/src/json.ts#L177

Prior to 0.8.2 the default type was {} (meaning AnySchema) instead of exception.

It would be nice the have a better support for custom scalars or at least restore the previous behaviour as in https://github.com/zalando-incubator/graphql-jit/blame/2df0bbfd6a6626d9f8230a30b5068ec1efaedbe0/src/json.ts#L165

Support createSourceEventStream?

Wonderful package, lots to learn from, thank you for open sourcing it!

Any plans to support createSourceEventStream for subscriptions? Any tips on how I'd go about PRing it?

Support skip and include

I know there is already a thread open about this but its almost a year old.
We really want to use this lib but the lack of this feature is why we cannot use it.

Any plans on implementing it? I am taking a look at the code to see if I would be able to send a PR but it looks a bit complicated for someone with no context to help, but I can spend more time on this probably.

Anyway, for the creators of this lib, implementing it would be much quicker and since @skip and @include are so widely used, is there anyway we can move forward with this feature?

Thanks and congrats on the great lib

Node configuration recommendations

Hi,
I'm running graphql-jit in production (as custom executor for Apollo GraphQL) and its performing worse than regular graphql-js implementation (higher cpu usage with similar throughput). I can see a difference in favor of graphql-jit when running benchmark (similar to schema-many-resolvers.benchmark.ts) but can't reproduce this in production setting. Results of compileQuery are cached properly between consecutive calls.

Do you have any recommendations for custom node config?
Do you have to tweak inlining options for v8 (like max-inlined-bytecode-size) for graphql-jit to work properly?

Indicate errors in User Input

We are currently having problem that we get errors from invalid variables passed to the queries, and we tag them as server side errors instead of user errors, and this causes unnecessary errors in the monitoring / tracing and eventually even paging of the on-call duty.

Examples

    type Query{
      parameterField(param: String!): String
    }

    type Mutation {
      inputMutation(input: TestInput!): String
    }

    input TestInput { id: ID! }

Example 1

Calling

query paramQuery($param: String!) { parameterField(param: $param) } 

without variable results in:

Variable "$param" of required type "String!" was not provided.

Example 2

Calling

mutation M($input: TestInput!) { inputMutation(input: $input) } 

with

{ "input": {} }

causes

Variable "$input" got invalid value {}; Field value.id of required type ID! was not provided.

Workaround

We can currently only parse this kind of errors from the GraphQL error message. It would be helpful to have indication for user error type.

Possible solution

The thrown errors have a field or other clear mechanism to indicate that the error is a user error.

Field Expansion for Union and Interfaces misbehaving

Description
When Union or interfaces are sharing the same types too as in the bellow schema, too is expanded for all types(Foo and Bar), We expect shared fields are expand only for fields used in query

Given Schema

        type Query {
          uBaz: Baz
        }
        union Baz = Foo | Bar
        type Foo {
          foo: String
          too: String
        }
        type Bar {
          bar: Int
          too: String
        }
      `

and Query

 `
            query {
              uBaz {
                ... on Foo {
                  foo
                  too
                }
                ... on Bar {
                  bar
                }
              }
            }
          `

Expected info.fieldExpansion is

                  Object {
                    "Bar": Object {
                      "bar": Object {
                        Symbol(LeafFieldSymbol): true,
                      },
                    },
                    "Foo": Object {
                      "foo": Object {
                        Symbol(LeafFieldSymbol): true,
                      },
                      "too": Object {
                        Symbol(LeafFieldSymbol): true,
                      },
                    },
                  }

But Actual info.fieldExpansion is

                 Object {
                    "Bar": Object {
                      "bar": Object {
                        Symbol(LeafFieldSymbol): true,
                      },
                      "too": Object {     <--------- `too` is not expected as its not in the query   
                        Symbol(LeafFieldSymbol): true,
                      },
                    },
                    "Foo": Object {
                      "foo": Object {
                        Symbol(LeafFieldSymbol): true,
                      },
                      "too": Object {
                        Symbol(LeafFieldSymbol): true,
                      },
                    },
                  }
           

Empty object when use inline fragment inside a named fragment affected by skip/include directives

Hello,
it's an edge case of: #111,
when using an inline fragment inside a named fragment affected by skip/include directives, it returns empty objects

Steps to reproduce
Start example/blog-apollo-server and make a this query:

query TEST(
    $includeOnly: Boolean!
){
    post(id: "post:1") {
        title
        Author1: author @skip(if: $includeOnly) {
            ...authorFragment
        }
        Author2: author @include(if: $includeOnly) {
            ...authorFragment
        }
        
    }
}
fragment authorFragment on User {
  ... on User{
    name
  }
}

Along with this variable:

{
  "includeOnly": true
}

and you will get an empty Author object:

{
  "data": {
    "post": {
      "title": "Introduction to GraphQL!",
      "Author2": {}
    }
  }
}

integration with graphql-sequelize

I'm having trouble using graphql-jit in an app that also uses graphql-sequelize, it seems because graphql-sequelize defines a custom type SequelizeJSON which graphql-jit is not recognizing. In queries that use a where criteria that takes a SequelizeJSON input, I get the following error from compileQuery:

 GraphQLError: Variable "$where" expected value of type "SequelizeJSON" which cannot be used as an input type.

Any idea how to avoid this?

Filters not resolving properly?

Hi,

I'm having a bit of an issue with filters right now, which is confusing me.

I have a query which looks something like this:

query Foo($bar: String!) {
  system() {
    component(filter: { someKey: [$bar] }) {
     ...
  }
}

When I check the filter in the args passed to the resolvers, it's an empty object. If I change the filter to

    component(filter: { someKey: $bar }) {

I see the correct value in the filter.

Am I doing something wrong, or is there potentially a bug here?

Thanks.

How to use the lib with the Apollo server v4?

Current examples for integration with apollo-server are provided for Apollo server v3.
For integration with the v3 the executor constructor option is used.

However in Apollo v4, there is no longer an executor constructor option.

The docs suggests passing the executor via the gateway constructor option, however this option is supposed to be used for federated solutions, which is not always the case.

Is there any recommended/tested way to integrate graphql-jit with the Apollo server v4? Probably via some Apollo plugin?

When having CacheControl directive this executor context has side effect

It actually calls the internal cache plugin of apollo twice when having this executor operational. Maybe its wise for the plugin to have compatibility check with frameworks? So people can see what works and doesn't :)

The cache setup is triggered by 'requestDidStart'

so when i add just like this and enable cacheControl in Apollo it creates the side effect that apollo does mGet (a Redis get) twice when having a custom executor. So the combi cacheControl and jit is not a good one, maybe thats why it never gets an official Apollo plugin ? Shame because it DID optimize the amount of executions :(

Issue when returning Union (Abstract type)

So, I am using type-graphql with express-graphql and graphql-jit.

Here is how I use graphql-jit within express-graphql :

function resolveGraphqlHTTPOptions(schema: GraphQLSchema): Options {
  const cache: any = {};
  return (req: Request, res: unknown, params: any) => {
    const { query } = params;
    if (query && !(query in cache)) {
      const document = parse(query);
      cache[query] = compileQuery(schema, document);
    }
    return {
      schema,
      graphiql: !isProduction,
      customFormatErrorFn,
      customExecuteFn: (args: ExecutionArgs) => {
        const { rootValue, contextValue, variableValues } = args;
        return cache[query].query(rootValue, contextValue, variableValues);
      },
    };
  };
}

At first everything works fine before I add a query that returns a Union type but the moment I use a query that returns a Union, I get the error below:

Abstract type <Name>Union must resolve to an Object type at runtime for field Mutation.<Mutation_Name>. Either the <Name>Union type should provide a \"resolveType\" function or each possible types should provide an \"isTypeOf\" function.

This error only manifests when a mutation/query returns a Union. Other than that every mutation/query works just fine. when I disable graphql-jit (by commenting the customExecuteFn), everything works fine.

BTW, thanks for this awesome lib ❤️

can't install graphql-jit via GitHub url

I tried to install the latest version of graphql-jit via npm install 'https://github.com/zalando-incubator/graphql-jit.git#main', which succeeded. But it is not possible to import the package, as there is not dist folder in the node_modules. The node_modules folder looks as follows:

$ ls -l node_modules/graphql-jit
-rwxr-xr-x  1 felix  staff  1263 Mar 14 15:33 LICENSE
-rw-r--r--  1 felix  staff  4805 Mar 14 15:33 README.md
-rw-r--r--  1 felix  staff  2841 Mar 14 15:33 package.json

Is it possible that something is missing in the package json, that skips the build process when installing from GitHub? Same behavior when installing with yarn

Caching compiled queries

In the context of a serverless architecture, would it be possible to cache the compiled queries (e.g. using ElastiCache w. Redis). Is it even worth caching the queries? If so, do you have any specific recommendations?

Make fast-json-stringify optional

Thanks for the great project.

Is your feature request related to a problem? Please describe.

I'm experimenting with using graphql-jit in the browser. Currently graphql-jit is bundled with fast-json-stringify, which makes its size extremely big to be used in browser.

https://bundlephobia.com/result?p=graphql-jit

Removing fast-json-stringify would cut the size in half.

Describe the solution you'd like

Currently, we have the option compilerOptions.customJSONSerializer, which accepts a boolean value. However the option does not provide much use because it only switches between JSON.stringify and fastJSON, not really 'custom'.

My proposal is that we replace it with a user-supplied function that accepts CompilerContext and returns the stringify function.

TypeError: Cannot set properties of null (setting 'fieldName')

Hi everyone and thanks for open-sourcing this project 🙂

Issue

We’ve been facing flaky issues on some queries that sometimes leads to a TypeError: Cannot set properties of null (setting 'fieldName'). After some investigations it seems this depends on the order in which fields are resolved, that’s why we don't get the issue all the time.

How to reproduce ?

Here is an example of a query that could sometimes trigger the issue :

{
  rootField {
    nestedField {
      nonNullableFieldThatMightResolveAfterTheOthers
    }
    nonNullableField
  }
}

For the examples we'll assume that nonNullableFieldThatMightResolveAfterTheOthers and nonNullableField are both returning null.

If fields resolution happens in that order :

  1. nonNullableFieldThatMightResolveAfterTheOthers
  2. nonNullableField

We get the expected response

{
  "data": {
    "rootField": null
  }
}

If fields resolution happens in that order:

  1. nonNullableField
  2. nonNullableFieldThatMightResolveAfterTheOthers

It triggers a TypeError: Cannot set properties of null (setting 'nestedField').

Query works without but fails with graphql-jit throwing RangeError

Hi, I have a GraphQL API auto generated from Prisma schema using typegraphql-prisma. The following query for example working as expected with Yoga as the GraphQL server:

query GetNodes($where: NodeWhereInput) {
    nodes(where: $where) {
        id
    }
}

Variables:

{
  "where": {
    "ownerId": {
      "equals": 1
    }
  }
}

Result:

{
  "data": {
    "nodes": [
      {
        "id": "6820393e-3117-460b-b22a-443dabd5a5d0"
      },
      {
        "id": "d5f7639b-5280-409d-9139-12813bc035f7"
      }
    ]
  }
}

But when I enable graphql-jit, I got this error from the same query:

{
  errors: [
    RangeError: Maximum call stack size exceeded
        at generateInput (/root/dev/baas/web/backend/node_modules/.pnpm/[email protected][email protected]/node_modules/graphql-jit/src/variables.ts:153:5)
        at generateInput (/root/dev/baas/web/backend/node_modules/.pnpm/[email protected][email protected]/node_modules/graphql-jit/src/variables.ts:332:13)
        at generateInput (/root/dev/baas/web/backend/node_modules/.pnpm/[email protected][email protected]/node_modules/graphql-jit/src/variables.ts:376:11)
        at generateInput (/root/dev/baas/web/backend/node_modules/.pnpm/[email protected][email protected]/node_modules/graphql-jit/src/variables.ts:332:13)
        at generateInput (/root/dev/baas/web/backend/node_modules/.pnpm/[email protected][email protected]/node_modules/graphql-jit/src/variables.ts:376:11)
        at generateInput (/root/dev/baas/web/backend/node_modules/.pnpm/[email protected][email protected]/node_modules/graphql-jit/src/variables.ts:332:13)
        at generateInput (/root/dev/baas/web/backend/node_modules/.pnpm/[email protected][email protected]/node_modules/graphql-jit/src/variables.ts:376:11)
        at generateInput (/root/dev/baas/web/backend/node_modules/.pnpm/[email protected][email protected]/node_modules/graphql-jit/src/variables.ts:332:13)
        at generateInput (/root/dev/baas/web/backend/node_modules/.pnpm/[email protected][email protected]/node_modules/graphql-jit/src/variables.ts:376:11)
        at generateInput (/root/dev/baas/web/backend/node_modules/.pnpm/[email protected][email protected]/node_modules/graphql-jit/src/variables.ts:332:13)
  ]
}

It works fine in both cases without the query parameters though:

query GetNodes {
    nodes {
        region
    }
}

Benchmark Results

The project is really interesting. The idea is brilliant!

The project objective is to provide better performance than the graphql-js's execution engine in query execution. It would be great to see some numbers showing how faster it could be. Also, the benchmark code for generating those numbers can also serve as a starting point for anyone who is interested in adopting the project for evaluation :)

Support include and skip

Include and skip directives in queries are not supported by the runtime / compiled code that's generated.

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.