Closed outerlook closed 3 years ago
Didn't notice this issue, but if true this will be the main thing stopping me from trying gqless in production right now.
This is definitely an interesting issue, and something that is implemented in many GQL APIs. The nature of gqless
currently works incredibly well when the shape of your GQL schema mimics that of ordinary JS objects. GQL interfaces present a divergence from the way ordinary JS objects work.
{
node(id: "[ID]") {
... on Customer {
id
name
}
}
}
A query like the one above ends up with an object that looks like this:
{
"id": "...",
"name": "..."
}
Since gqless
works based on ES6 proxy it would have to find a unique way of supporting ... on Customer
. This would end up as maybe some sort of nested property on the object, and gqless
would have the task of translating the non-nested GQL response into a nested object. Take this example from above:
resolved(() => {
const customer = query.node({ id: customerId }) as Customer
const { __typename, id, name } = customer
return { __typename, id, name}
})
This wouldn't really work because you can have multiple GQL interfaces with similar property names, and though gqless
works with TS typings, at runtime it isn't aware of as Customer
. So you may end up with something like this:
resolved(() => {
const customer = query.node({ id: customerId })
const { __typename, onCustomer: { id, name } } = customer
return { __typename, id, name}
})
The above code might work, but would require your GQL API to not publish an onCustomer
key, and it would require some convention in gqless
to support it.
Definitely.
What others API's could be used?
resolved(() => {
const customer = query.node({ id: customerId })
const { __typename, on: { Customer: {id, name} } } = customer
return { __typename, id, name}
})
Could be another one that would reduce the "reserved word" count and become predictable.
Customer type would become Customer & {__typename: string}
.
If there were multiple "on", it would have to Unionize the types based on the present on
s
from example
query AllCharacters {
all_characters {
... on Character {
name
}
... on Jedi {
side
}
... on Droid {
model
}
}
}
would become
resolved(() => {
const characters = query.all_characters
const { on: { Character: {name}, Jedi: {side}, Droid: {model} } = characters[0]
return characters
})
To add another API example, here is a possible query for a Headless WordPress site that uses WPGraphQL:
{
nodeByUri(uri: "/my-post-or-page") {
id
uri
... on ContentNode {
slug
... on Post {
title
author {
node {
firstName
}
}
}
}
... on Post {
title
}
... on Page {
title
}
}
}
In this example title
is redundant on Post
but is used to show how the API works. So you would have to support nested interfaces as well as conflicting property names (e.g. title
as described above. Using the interface @outerlook suggests above would look something like this:
resolved(() => {
const postOrPage = query.nodeByUri({ uri: "/my-post-or-page" });
const {
id,
uri,
on: {
ContentNode: {
slug,
on: {
Post: {
title,
author {
node: {
firstName
}
},
}
}
},
Post: {
title
},
Page: {
title
}
}
} = postOrPage;
return postOrPage;
});
$on: { ... }
requires zero reserved words.
the $on syntax makes sense 👍 this weekend I'm hoping to be able to start working on this and #178
$on: { ... }
requires zero reserved words.
Wow, the real solution is so simple. Call me an impostor!
This could also be done as $on('NodeType')...
. I'm not sure what is easier/better to support.
@wjohnsto @vicary @outerlook Can you test gqty@2.0.0 with the new "$on" syntax?
It's pretty much the same syntax as @wjohnsto mentioned, but with the property $on
resolved(() => { const postOrPage = query.nodeByUri({ uri: "/my-post-or-page" }); const { id, uri, $on: { ContentNode: { slug, $on: { Post: { title, author { node: { firstName } }, } } }, Post: { title }, Page: { title } } } = postOrPage; return postOrPage; });
I will be able to look at this before the end of the week 👍
The new $on
functionality works well! With one caveat to the syntax:
In the above syntax, destructing is occurring with multiple instances of the same variable name. As in, the title
is being destructed from both Post
and Page
. This will throw an error in TypeScript "Cannot redeclare block-scoped variable 'title'" and a runtime error.
This doesn't change the functionality, but will change how the docs will describe the usage I assume.
As an example, take the following code that destructures and renames the fields needed:
const postOrPage = useQuery().nodeByUri({ uri: "/my-post-or-page" });
const {
$on: {
Post: { title: postTitle, content: postContent },
Page: { title: pageTitle, content: pageContent },
},
} = postOrPage;
const title = postTitle || pageTitle;
const content = postContent || pageContent;
Take an example schema:
This kind of schema creates some possibilities for fetching independent Nodes. We don't have to create queries
product(id)
orcustomer(id)
if this ID is uniquely indentified accross all nodes,
node(id)
can find any object that implements node's and return it.for example:
serves for this purpose. it create's possibilities of using relay's pagination schema with connections, etc which is highly scalable.
right now, when using it on gqless like this:
the produced query is
so it doesn't support fetching objects fields that implements Node.
You can read more complete examples of this usage on Documentation for relay's graphql server specs