V8. Types generation error: Cannot find package 'es-toolkit'
Closed this issue · 31 comments
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
And what is the usage for open-telemetry?
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".
For generating types using js, example in docs import { generate } from 'graffle/generator'
is not correct, generate
is not exported.
Can you also provide documentation on how to add types for custom scalars.
@ziimakc thanks a lot for this feedback, will fix ASAP. Regarding custom scalar documentation, yep it is high on my list.
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
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
Same for @opentelemetry/api
Fixed by #1219
@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.
@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
},
@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
?
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.
Thank you for the introspection details, I'll take another look today.
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}"}'
}
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.
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.
@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
}
]
}
]
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}"}'
}
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.
@jasonkuhrt no, it's a localhost :(
And not open source right? haha :)
@jasonkuhrt you can run this gql server with any simple schema to reproduce issue: https://async-graphql.github.io/async-graphql
Awesome thanks that will help.
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)
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)
The issue is the option: inputValueDeprecation
.
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.
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: