Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

V8. Types generation error: Cannot find package 'es-toolkit' #1214

Closed
ziimakc opened this issue Oct 24, 2024 · 31 comments · Fixed by #1226
Closed

V8. Types generation error: Cannot find package 'es-toolkit' #1214

ziimakc opened this issue Oct 24, 2024 · 31 comments · Fixed by #1226

Comments

@ziimakc
Copy link

ziimakc commented Oct 24, 2024

Description

When running pnpm graffle --schema my_url following error is thrown:

Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'es-toolkit' imported from /node_modules/.pnpm/graffle@8.0.0-next.78_graphql@16.9.0/node_modules/graffle/build/generator/generators/MethodsSelect.js

Same for @opentelemetry/api

Version: 8.0.0-next.78

@ziimakc
Copy link
Author

ziimakc commented Oct 24, 2024

And what is the usage for open-telemetry?

@ziimakc
Copy link
Author

ziimakc commented Oct 24, 2024

If I install manually, I get following errors:

GraphQLError: Unknown field "isDeprecated" on type "__InputValue".
GraphQLError: Unknown field "deprecationReason" on type "__InputValue"
GraphQLError: Unknown argument "includeDeprecated" on field "args" of type "__Field".

@ziimakc
Copy link
Author

ziimakc commented Oct 24, 2024

For generating types using js, example in docs import { generate } from 'graffle/generator' is not correct, generate is not exported.

@ziimakc
Copy link
Author

ziimakc commented Oct 24, 2024

Can you also provide documentation on how to add types for custom scalars.

@jasonkuhrt
Copy link
Member

@ziimakc thanks a lot for this feedback, will fix ASAP. Regarding custom scalar documentation, yep it is high on my list.

@jasonkuhrt
Copy link
Member

Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'es-toolkit' imported from /node_modules/.pnpm/graffle@8.0.0-next.78_graphql@16.9.0/node_modules/graffle/build/generator/generators/MethodsSelect.js

This is fixed by #1217

@jasonkuhrt
Copy link
Member

jasonkuhrt commented Oct 25, 2024

Same for @opentelemetry/api

I'm confused by that. It's only imported in the extension. Which is only exported from the /extensions entrypoint. I am not seeing this issue in my e2e tests. Can you share more context?

And what is the usage for open-telemetry?

https://graffle.js.org/extensions/opentelemetry

If I install manually, I get following errors:

Interesting, that appears to be coming from the introspection query? Can you share more context about the error you are seeing?

Can you also provide documentation on how to add types for custom scalars.

For now please refer to the working example https://graffle.js.org/examples/custom-scalar/custom-scalar. I will be adding a guide about custom scalars in the coming days or weeks. If you have specific questions meanwhile feel free to ask.

For generating types using js, example in docs import { generate } from 'graffle/generator' is not correct, generate is not exported.

Fixed by #1218

@jasonkuhrt
Copy link
Member

Nevermind I was able to reproduce. Investigating!

CleanShot 2024-10-24 at 22 29 03@2x

@jasonkuhrt
Copy link
Member

Same for @opentelemetry/api

Fixed by #1219

@jasonkuhrt
Copy link
Member

@ziimakc the only outstanding issue here now is #1214 (comment) I think.

If you encounter more problems, let's track them in new issues.

I think the issue you're having is particular to the GraphQL server you're trying to introspect. Maybe there is something we can do to make it work though, like, exposing some configuration in the generator of how the introspection query will be sent etc.

Internally, we re-use the introspection extension: https://graffle.js.org/extensions/introspection. It definitely can work, so that's why I think there's something more particular to the schema you're working with. Curious to learn more about it.

@ziimakc
Copy link
Author

ziimakc commented Oct 25, 2024

@jasonkuhrt regarding deprecated I assume it's build-in directive and should be supported by default. I can generate schema using api schema file, but not using url.

Error message v82:

   ContextualError: Unknown field "isDeprecated" on type "__InputValue".
        at .../node_modules/.pnpm/graffle@8.0.0-next.82_@opentelemetry+api@1.9.0_graphql@16.9.0/node_modules/graffle/build/layers/6_client/handleOutput.js:34:20

 ContextualError: Unknown field "deprecationReason" on type "__InputValue".
        at .../.pnpm/graffle@8.0.0-next.82_@opentelemetry+api@1.9.0_graphql@16.9.0/node_modules/graffle/build/layers/6_client/handleOutput.js:34:20


ContextualError: Unknown argument "includeDeprecated" on field "args" of type "__Field".
        at .../node_modules/.pnpm/graffle@8.0.0-next.82_@opentelemetry+api@1.9.0_graphql@16.9.0/node_modules/graffle/build/layers/6_client/handleOutput.js:34:20

1 full error:

ContextualError: Unknown argument "includeDeprecated" on field "args" of type "__Field".
        at .../node_modules/.pnpm/graffle@8.0.0-next.82_@opentelemetry+api@1.9.0_graphql@16.9.0/node_modules/graffle/build/layers/6_client/handleOutput.js:35:20
        at Array.map (<anonymous>)
        at handleOutput (.../node_modules/.pnpm/graffle@8.0.0-next.82_@opentelemetry+api@1.9.0_graphql@16.9.0/node_modules/graffle/build/layers/6_client/handleOutput.js:30:124)
        at Object.send (.../node_modules/.pnpm/graffle@8.0.0-next.82_@opentelemetry+api@1.9.0_graphql@16.9.0/node_modules/graffle/build/layers/6_client/gql/gql.js:53:28)
        at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
        at async Proxy.<anonymous> (.../node_modules/.pnpm/graffle@8.0.0-next.82_@opentelemetry+api@1.9.0_graphql@16.9.0/node_modules/graffle/build/extensions/Introspection/Introspection.js:40:24)
        at async createConfigSchema (.../node_modules/.pnpm/graffle@8.0.0-next.82_@opentelemetry+api@1.9.0_graphql@16.9.0/node_modules/graffle/build/generator/config/config.js:147:22)
        at async createConfig (.../node_modules/.pnpm/graffle@8.0.0-next.82_@opentelemetry+api@1.9.0_graphql@16.9.0/node_modules/graffle/build/generator/config/config.js:30:20)
        at async Module.generate (.../node_modules/.pnpm/graffle@8.0.0-next.82_@opentelemetry+api@1.9.0_graphql@16.9.0/node_modules/graffle/build/generator/generator/generate.js:35:20)
        at async .../node_modules/.pnpm/graffle@8.0.0-next.82_@opentelemetry+api@1.9.0_graphql@16.9.0/node_modules/graffle/build/generator/cli/generate.js:63:1 {
      context: { locations: [ { line: 32, column: 14 } ] },
      cause: undefined
    },

Introspection looks like this:

                        {
                            "name": "enumValues",
                            "description": null,
                            "args": [
                                {
                                    "name": "includeDeprecated",
                                    "description": null,
                                    "type": {
                                        "kind": "NON_NULL",
                                        "name": null,
                                        "ofType": {
                                            "kind": "SCALAR",
                                            "name": "Boolean",
                                            "ofType": null
                                        }
                                    },
                                    "defaultValue": "false"
                                }
                            ],
                            "type": {
                                "kind": "LIST",
                                "name": null,
                                "ofType": {
                                    "kind": "NON_NULL",
                                    "name": null,
                                    "ofType": {
                                        "kind": "OBJECT",
                                        "name": "__EnumValue",
                                        "ofType": null
                                    }
                                }
                            },
                            "isDeprecated": false,
                            "deprecationReason": null
                        },
                    "kind": "OBJECT",
                    "name": "AuthTokens",
                    "description": null,
                    "fields": [
                        {
                            "name": "userId",
                            "description": null,
                            "args": [],
                            "type": {
                                "kind": "NON_NULL",
                                "name": null,
                                "ofType": {
                                    "kind": "SCALAR",
                                    "name": "UUID",
                                    "ofType": null
                                }
                            },
                            "isDeprecated": false,
                            "deprecationReason": null
                        },

@ziimakc
Copy link
Author

ziimakc commented Oct 25, 2024

@jasonkuhrt I would like to add it to gql types generator script, similar to gql-codegen:

#!/usr/bin/env zx

import { Generator } from "graffle/generator";
import { join } from "node:path";

await Generator.generate({
	schema: {
		type: "sdl",
		dirOrFilePath: join(
			import.meta.dirname,
			"../../api/generated/schema.gql",
		),
	},
	outputDirPath: join(import.meta.dirname, "../src/generated/gql"),
});

Maybe you can also explain a reason to reimplement GraphQL-Codegen instead of generating graffle types as a plugin for GraphQL-Codegen, I think it was like that for graphql-request?

@jasonkuhrt
Copy link
Member

graphql-request never had a generator.

Graffle is a self contained tool. I am not sure what the benefit to being a code-generator plugin would be, I'm not familiar with the tool. My concern was that it is not simple enough.

@jasonkuhrt
Copy link
Member

Thank you for the introspection details, I'll take another look today.

@jasonkuhrt
Copy link
Member

jasonkuhrt commented Oct 25, 2024

So regarding:

GraphQLError: Unknown field "isDeprecated" on type "__InputValue".
GraphQLError: Unknown field "deprecationReason" on type "__InputValue"
GraphQLError: Unknown argument "includeDeprecated" on field "args" of type "__Field".

It seems that for some reason your schema does not support the introspection document being sent.

Can you share with me an introspection query that works for your schema? You can log what introspect sends and then send it again manually with your own tweaks using graffle.gql.

const pokemon = Pokemon.create().use(Introspection()).anyware(async ({ exchange }) => {
  console.log(exchange.input.request)
  return await exchange()
})
{
  methodMode: 'post',
  headers: Headers {
    accept: 'application/graphql-response+json; charset=utf-8, application/json; charset=utf-8',
    'content-type': 'application/json'
  },
  signal: undefined,
  method: 'post',
  url: URL {
    href: 'http://localhost:3000/graphql',
    origin: 'http://localhost:3000',
    protocol: 'http:',
    username: '',
    password: '',
    host: 'localhost:3000',
    hostname: 'localhost',
    port: '3000',
    pathname: '/graphql',
    search: '',
    searchParams: URLSearchParams {},
    hash: ''
  },
  body: '{"query":"query IntrospectionQuery {\\n  __schema {\\n    queryType {\\n      name\\n    }\\n    mutationType {\\n      name\\n    }\\n    subscriptionType {\\n      name\\n    }\\n    types {\\n      ...FullType\\n    }\\n    directives {\\n      name\\n      description\\n      locations\\n      args {\\n        ...InputValue\\n      }\\n    }\\n  }\\n}\\n\\nfragment FullType on __Type {\\n  kind\\n  name\\n  description\\n  fields(includeDeprecated: true) {\\n    name\\n    description\\n    args {\\n      ...InputValue\\n    }\\n    type {\\n      ...TypeRef\\n    }\\n    isDeprecated\\n    deprecationReason\\n  }\\n  inputFields {\\n    ...InputValue\\n  }\\n  interfaces {\\n    ...TypeRef\\n  }\\n  enumValues(includeDeprecated: true) {\\n    name\\n    description\\n    isDeprecated\\n    deprecationReason\\n  }\\n  possibleTypes {\\n    ...TypeRef\\n  }\\n}\\n\\nfragment InputValue on __InputValue {\\n  name\\n  description\\n  type {\\n    ...TypeRef\\n  }\\n  defaultValue\\n}\\n\\nfragment TypeRef on __Type {\\n  kind\\n  name\\n  ofType {\\n    kind\\n    name\\n    ofType {\\n      kind\\n      name\\n      ofType {\\n        kind\\n        name\\n        ofType {\\n          kind\\n          name\\n          ofType {\\n            kind\\n            name\\n            ofType {\\n              kind\\n              name\\n              ofType {\\n                kind\\n                name\\n                ofType {\\n                  kind\\n                  name\\n                  ofType {\\n                    kind\\n                    name\\n                  }\\n                }\\n              }\\n            }\\n          }\\n        }\\n      }\\n    }\\n  }\\n}"}'
}

@ziimakc
Copy link
Author

ziimakc commented Oct 25, 2024

Will send you introspection later, it does work fine for requests made by graphiql web sandbox.

This is the plugin that I was using for getting types when using graphql-request.

@jasonkuhrt
Copy link
Member

jasonkuhrt commented Oct 25, 2024

This is the plugin that I was using for getting types when using graphql-request.

Not sure you know yet but Graffle has a document builder that I personally prefer. That interface needs its own generator. If you are not using the document builder you do not need to use the Graffle generator.

Let me know if this was not clear to you, as the documentation should achieve that.

I recently rewrote the getting started guide. Here is where document builder gets introduced: https://graffle.js.org/guides/getting-started#🧙-meet-document-builder.

@ziimakc
Copy link
Author

ziimakc commented Oct 25, 2024

@jasonkuhrt error is thrown when I run "graffle" "--schema" "http://localhost:3001/api/graphql". Here is query that works fine for altair:

{
  "query": "\n    query IntrospectionQuery {\n      __schema {\n        \n        queryType { name }\n        mutationType { name }\n        subscriptionType { name }\n        types {\n          ...FullType\n        }\n        directives {\n          name\n          description\n          \n          locations\n          args {\n            ...InputValue\n          }\n        }\n      }\n    }\n\n    fragment FullType on __Type {\n      kind\n      name\n      description\n      \n      fields(includeDeprecated: true) {\n        name\n        description\n        args {\n          ...InputValue\n        }\n        type {\n          ...TypeRef\n        }\n        isDeprecated\n        deprecationReason\n      }\n      inputFields {\n        ...InputValue\n      }\n      interfaces {\n        ...TypeRef\n      }\n      enumValues(includeDeprecated: true) {\n        name\n        description\n        isDeprecated\n        deprecationReason\n      }\n      possibleTypes {\n        ...TypeRef\n      }\n    }\n\n    fragment InputValue on __InputValue {\n      name\n      description\n      type { ...TypeRef }\n      defaultValue\n      \n      \n    }\n\n    fragment TypeRef on __Type {\n      kind\n      name\n      ofType {\n        kind\n        name\n        ofType {\n          kind\n          name\n          ofType {\n            kind\n            name\n            ofType {\n              kind\n              name\n              ofType {\n                kind\n                name\n                ofType {\n                  kind\n                  name\n                  ofType {\n                    kind\n                    name\n                  }\n                }\n              }\n            }\n          }\n        }\n      }\n    }\n  ",
  "variables": {},
  "operationName": "IntrospectionQuery"
}

Here is __InputValue type from error:

{
  "kind": "OBJECT",
  "name": "__InputValue",
  "description": "Arguments provided to Fields or Directives and the input fields of an\nInputObject are represented as Input Values which describe their type and\noptionally a default value.",
  "fields": [
    {
      "name": "name",
      "description": null,
      "args": [],
      "type": {
        "kind": "NON_NULL",
        "name": null,
        "ofType": {
          "kind": "SCALAR",
          "name": "String",
          "ofType": null
        }
      },
      "isDeprecated": false,
      "deprecationReason": null
    },
    {
      "name": "description",
      "description": null,
      "args": [],
      "type": {
        "kind": "SCALAR",
        "name": "String",
        "ofType": null
      },
      "isDeprecated": false,
      "deprecationReason": null
    },
    {
      "name": "type",
      "description": null,
      "args": [],
      "type": {
        "kind": "NON_NULL",
        "name": null,
        "ofType": {
          "kind": "OBJECT",
          "name": "__Type",
          "ofType": null
        }
      },
      "isDeprecated": false,
      "deprecationReason": null
    },
    {
      "name": "defaultValue",
      "description": null,
      "args": [],
      "type": {
        "kind": "SCALAR",
        "name": "String",
        "ofType": null
      },
      "isDeprecated": false,
      "deprecationReason": null
    }
  ],
  "inputFields": null,
  "interfaces": [],
  "enumValues": null,
  "possibleTypes": null
}

Here is directive response:

[
  {
    "name": "deprecated",
    "description": "Marks an element of a GraphQL schema as no longer supported.",
    "locations": [
      "FIELD_DEFINITION",
      "ARGUMENT_DEFINITION",
      "INPUT_FIELD_DEFINITION",
      "ENUM_VALUE"
    ],
    "args": [
      {
        "name": "reason",
        "description": "A reason for why it is deprecated, formatted using Markdown syntax",
        "type": {
          "kind": "SCALAR",
          "name": "String",
          "ofType": null
        },
        "defaultValue": "\"No longer supported\""
      }
    ]
  },
  {
    "name": "include",
    "description": "Directs the executor to include this field or fragment only when the `if` argument is true.",
    "locations": [
      "FIELD",
      "FRAGMENT_SPREAD",
      "INLINE_FRAGMENT"
    ],
    "args": [
      {
        "name": "if",
        "description": "Included when true.",
        "type": {
          "kind": "NON_NULL",
          "name": null,
          "ofType": {
            "kind": "SCALAR",
            "name": "Boolean",
            "ofType": null
          }
        },
        "defaultValue": null
      }
    ]
  },
  {
    "name": "oneOf",
    "description": "Indicates that an Input Object is a OneOf Input Object (and thus requires\n                        exactly one of its field be provided)",
    "locations": [
      "INPUT_OBJECT"
    ],
    "args": []
  },
  {
    "name": "skip",
    "description": "Directs the executor to skip this field or fragment when the `if` argument is true.",
    "locations": [
      "FIELD",
      "FRAGMENT_SPREAD",
      "INLINE_FRAGMENT"
    ],
    "args": [
      {
        "name": "if",
        "description": "Skipped when true.",
        "type": {
          "kind": "NON_NULL",
          "name": null,
          "ofType": {
            "kind": "SCALAR",
            "name": "Boolean",
            "ofType": null
          }
        },
        "defaultValue": null
      }
    ]
  },
  {
    "name": "specifiedBy",
    "description": "Provides a scalar specification URL for specifying the behavior of custom scalar types.",
    "locations": [
      "SCALAR"
    ],
    "args": [
      {
        "name": "url",
        "description": "URL that specifies the behavior of this scalar.",
        "type": {
          "kind": "NON_NULL",
          "name": null,
          "ofType": {
            "kind": "SCALAR",
            "name": "String",
            "ofType": null
          }
        },
        "defaultValue": null
      }
    ]
  }
]

@ziimakc
Copy link
Author

ziimakc commented Oct 25, 2024

Not sure what is supposed to do, query works fine as I generated types from file.

const pokemon = await XXX.create({
		transport: { methodMode: "post" },
		schema: new URL("http://localhost:3001/api/graphql"),
	})
		.use(Introspection())
		.anyware(async ({ exchange }) => {
			console.error(exchange.input.request);
			return await exchange();
		});

	const xx = await pokemon.introspect();
	console.error(xx);
{
  methodMode: 'post',
  headers: Headers {
    accept: 'application/graphql-response+json; charset=utf-8, application/json; charset=utf-8',
    'content-type': 'application/json'
  },
  signal: undefined,
  method: 'post',
  url: URL {
    href: 'http://localhost:3001/api/graphql',
    origin: 'http://localhost:3001',
    protocol: 'http:',
    username: '',
    password: '',
    host: 'localhost:3001',
    hostname: 'localhost',
    port: '3001',
    pathname: '/api/graphql',
    search: '',
    searchParams: URLSearchParams {},
    hash: ''
  },
  body: '{"query":"query IntrospectionQuery {\\n  __schema {\\n    queryType {\\n      name\\n    }\\n    mutationType {\\n      name\\n    }\\n    subscriptionType {\\n      name\\n    }\\n    types {\\n      ...FullType\\n    }\\n    directives {\\n      name\\n      description\\n      locations\\n      args {\\n        ...InputValue\\n      }\\n    }\\n  }\\n}\\n\\nfragment FullType on __Type {\\n  kind\\n  name\\n  description\\n  fields(includeDeprecated: true) {\\n    name\\n    description\\n    args {\\n      ...InputValue\\n    }\\n    type {\\n      ...TypeRef\\n    }\\n    isDeprecated\\n    deprecationReason\\n  }\\n  inputFields {\\n    ...InputValue\\n  }\\n  interfaces {\\n    ...TypeRef\\n  }\\n  enumValues(includeDeprecated: true) {\\n    name\\n    description\\n    isDeprecated\\n    deprecationReason\\n  }\\n  possibleTypes {\\n    ...TypeRef\\n  }\\n}\\n\\nfragment InputValue on __InputValue {\\n  name\\n  description\\n  type {\\n    ...TypeRef\\n  }\\n  defaultValue\\n}\\n\\nfragment TypeRef on __Type {\\n  kind\\n  name\\n  ofType {\\n    kind\\n    name\\n    ofType {\\n      kind\\n      name\\n      ofType {\\n        kind\\n        name\\n        ofType {\\n          kind\\n          name\\n          ofType {\\n            kind\\n            name\\n            ofType {\\n              kind\\n              name\\n              ofType {\\n                kind\\n                name\\n                ofType {\\n                  kind\\n                  name\\n                  ofType {\\n                    kind\\n                    name\\n                  }\\n                }\\n              }\\n            }\\n          }\\n        }\\n      }\\n    }\\n  }\\n}"}'
}

@jasonkuhrt
Copy link
Member

Thanks for the updates. Will look again. Is the URL public such that I could try introspecting against myself? That would allow me to iterate without going back and forth with you about it.

@ziimakc
Copy link
Author

ziimakc commented Oct 25, 2024

@jasonkuhrt no, it's a localhost :(

@jasonkuhrt
Copy link
Member

And not open source right? haha :)

@ziimakc
Copy link
Author

ziimakc commented Oct 25, 2024

@jasonkuhrt you can run this gql server with any simple schema to reproduce issue: https://async-graphql.github.io/async-graphql

@jasonkuhrt
Copy link
Member

Awesome thanks that will help.

@ziimakc
Copy link
Author

ziimakc commented Oct 25, 2024

@jasonkuhrt
Copy link
Member

jasonkuhrt commented Oct 26, 2024

So this works just fine:

import { Graffle } from './entrypoints/__Graffle.js'
import { Introspection } from './extensions/Introspection/Introspection.js'

const graffle = Graffle
  .create({
    schema: `http://localhost:8000`,
  })
  .use(Introspection())

const result = await graffle.introspect()

console.log(result)

It is only when the generator runs... (I can reproduce, thanks @ziimakc)

@jasonkuhrt
Copy link
Member

Ah this fails:

import { Graffle } from './entrypoints/__Graffle.js'
import { Introspection } from './extensions/Introspection/Introspection.js'

const graffle = Graffle
  .create({
    schema: `http://localhost:8000`,
  })
  .use(Introspection({
    options: {
      directiveIsRepeatable: true,
      schemaDescription: true,
      specifiedByUrl: true,
      inputValueDeprecation: true,
      // todo oneOf
    },
  }))

const result = await graffle.introspect()

console.log(result)

@jasonkuhrt
Copy link
Member

The issue is the option: inputValueDeprecation.

@jasonkuhrt
Copy link
Member

So it seems to be a missing feature (or bug) on the part of that GrahQL server: async-graphql/async-graphql#1621.

I will now think about how to make Graffle able to circumvent this issue. Probably what I will do is remove the option by default and let users add it via generator config.

@jasonkuhrt
Copy link
Member

Will be resolved by #1226. I discovered another issue: #1225. Will fix both things in that PR. Will ship next week.

@jasonkuhrt
Copy link
Member

jasonkuhrt commented Oct 27, 2024

Thanks for all the feedback @ziimakc, you helped me find a major flaw in Graffle (it assumed the names of the root types). There are things I still want to improve with introspection and feedback the user gets but in general the base functionality is now in place:

CleanShot 2024-10-27 at 09 54 46@2x

CleanShot 2024-10-27 at 09 54 33@2x

@jasonkuhrt jasonkuhrt unpinned this issue Oct 28, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants