GraphQL ancestor data context - graphql

I need to be able to create a catalog for a given entity and then somewhere in the grandchildren I want to use the catalog IDs and resolve them.
think of this (very simplified) data model
type Entity {
id: ID
componentCatalog: [Component]
child: Child
}
type Child {
grandChildren: [GrandChild]
}
type GrandChild {
components: [Component]
}
in the NoSQL db I would store this as:
{
id: 'abc',
componentCatalog: [ { id: 1, title: 'a' }, { id: 2, title: 'b' }],
child: {
grandChildren: [
{
componentIds: [1]
},
{
componentIds: [1,2]
}
]
}
}
and I would like to resolve the IDs to the components that are stored in the catalog of the Entity
however how do I get to the data from the grandchildren? Parent is just a child, do I have to save the catalog into the GQL context? If so then how? If there are multiple entities in the query how do I know which Grandchild belongs to which entity?
Thanks a lot in advance

Related

hotchocolate (GraphQL) filter by extended schema (stitching)

Is it possible to 'filter' (where) by an extended schema in stitching file in graphQL?
For example:
{
contractSKUCost(where: { contractSKU: { products: { productId: 1}} }) {
items {
id
contractSKU {
id
products {
productId
}
}
}
}
}
In this case, products is in a different database, that is why we need to define it in stitching file:
extend type ContractSKU {
products():[Product]
#delegate(
schema: "products",
path: "productsBySkuId(skuId: $fields:id)"
)
Essentially, what I want to do is "GET All ContractSKUCost that have ProductId = 1". But as you see, ContractSKUCost does not have direct relationship with Product, only its parent ContractSKU.
Edit:
Code above does not work; because ContractSkuCost schema technically does not know the 'product' extension yet.

Graphql: How can I solve the N + N problem?

After having implemented dataloader in the respective resolvers to solve the N+1 problem, I also need to be able to solve the N+N problem.
I need a decently efficient data loading mechanism to get a relation like this:
{
persons (active: true) {
id,
given_name,
projects (active: true) {
id,
title,
}
}
}
I've created a naive implementation for this, returning
{
persons: [
{
id: 1,
given_name: 'Mike'
projects: [
{
id: 1,
title: 'API'
},
{
id: 2,
title: 'Frontend'
}
]
}
{
id: 2,
given_name: 'Eddie'
projects: [
{
id: 2,
title: 'Frontend'
},
{
id: 3,
title: 'Testing'
}
]
}
]
}
In SQL the underlying structure would be represented by a many many to many relationship.
Is there a similiar tool like dataloader for solving this or can this maybe even be solved with dataloader itself?
The expectation with GraphQL is that the trip to the database is generally the fastest thing you can do, so you just add a resolver to Person.projects that makes a call to the database. You can still use dataLoaders for that.
const resolvers = {
Query: {
persons(parent, args, context) {
// 1st call to database
return someUsersService.list()
},
},
Person: {
projects(parent, args, context) {
// this should be a dataLoader behind the scenes.
// Makes second call to database
return projectsService.loadByUserId(parent.id)
}
}
}
Just remember that now your dataLoader is expecting to return an Array of objects in each slot instead of a single object.

How to return complex object as scalar type in GraphQL?

Let's imagine we have GraphQL API that can return an object Entity with Id and Name properties and I requested Name only:
query {
entities {
name
}
}
And it returns
{
"data": {
"entities": [
{
"name": "Name1"
},
{
"name": "Name2"
}
]
}
}
But what if I want to have only the name of entities as a scalar type? In other words, I want to have something like:
{
"data": {
"entities": [
"Name1",
"Name2"
]
}
}
Is it possible to have such result without changes on the GraphQL API side? Aliases, Fragments, etc. GraphQL has a lot of built-in query capabilities, but none of the known me can return complex objects as scalar type.
what you're asking for is almost impossible if you don't want to change the type definition for Entities.
This: 👇🏽
Entity: id: int! name: String
entities(): [Entity]
returns an array of objects with keys name and id.
To achieve what you're asking you either change Entity to be just a string or have your client reduce that object to an array of just Entity names when they receive it.
They could do something like this:
const data = {
entities: [
{
name: 'Name1',
},
{
name: 'Name2',
},
],
};
const entityNames = data.entities.reduce(
(acc, curr) => [...acc, curr.name],
[]
);
console.log(entityNames);

GraphQL - When to use a resolver or an argument with recursive and normalized data?

I'm working with a very large normalized and recursive object. I want to get the list of all recursive items. Should I use an argument or a custom resolver?
My object looks like:
{
products: [{
product_id: "car",
bundle_id: 5
},{
product_id: "door"
bundle_id: 6
},
{ product_id: "wheel" },
{ product_id: "metal" },
{ product_id: "glass" }],
bundles: [{
bundle_id: 5,
options: [{product_id: "door"},{product_id: "wheel"}]
},
{
bundle_id: 6,
options: [{product_id: "metal"},{product_id: "glass"}]
}]
}
You might notice that "car" is a bundle that has a door and a wheel. "door" is also a bundle that has metal and glass. This structure could recurse indefinitely. That is, a bundle could have infinitely more bundle products underneath it.
I want to get a list of all products for a bundle (example: "car"). What is the best approach?
I see two options.
First Option - use a custom resolver, for example child_products that would recurse and resolve to a flat array of all children:
products(product_id: "car") {
product_id
bundle {
options {
product_id
}
}
child_products {
product_id
bundle {
options {
product_id
}
}
}
}
Second Option - use an argument that specifies including all children:
products(product_id: "car", include_children: true) {
product_id
bundle {
options {
product_id
}
}
}
I'm going to build a JS library that can take the array of products and options and build the nested structure. Please let me know what you think is the right way. Thanks!
You should not need an argument like include_children because a client's query will be sufficient to determine whether to include the nodes or not -- if a client doesn't need the nodes, it can simply omit the appropriate field.
Based on the provided JSON object, I would expect a schema that looks something like this:
type Query {
product(id: ID!): Product
}
type Product {
id: ID!
bundle: Bundle
}
type Bundle {
id: ID!
options: [Product!]!
}
which would let you make a query like:
query {
product(id: "car") {
id
bundle {
options {
id
bundle {
id
# and so on...
}
}
}
}
}
The actual depth of this query would be left up to the client's needs. Recursive type definitions like this do present a possible attack vector and so you should also look into using a library like graphql-depth-limit or graphql-query-complexity.

How to adapt query to API?

I'm trying to wrap my head around GraphQL.
Right now I'm just playing with the public API of Artsy (an art website, playground at https://metaphysics-production.artsy.net). What I want to achieve is following:
I want to get all node types entities without declaring them by hand (is there a shortcut for this)?
I want every node with a field type from which I can read the type, without parsing through imageUrl etc. to fint that out.
What I constructed as of right now is this:
{
search(query: "Berlin", first: 100, page: 1, entities: [ARTIST, ARTWORK, ARTICLE]) {
edges {
node {
displayLabel
imageUrl
href
}
}
}}
Very primitive I guess. Can you guys help me?
TL;DR:
1) There is no shortcut, it's not something GraphQL offers out of the box. Nor is it something I was able to find via their Schema.
2) Their returned node of type Searchable does not contain a property for type that you're looking for. But you can access it via the ... on SearchableItem (union) syntax.
Explanation:
For question 1):
Looking at their schema, you can see that their search query has the following type details:
search(
query: String!
entities: [SearchEntity]
mode: SearchMode
aggregations: [SearchAggregation]
page: Int
after: String
first: Int
before: String
last: Int
): SearchableConnection
The query accepts an entities property of type SearchEntity which looks like this:
enum SearchEntity {
ARTIST
ARTWORK
ARTICLE
CITY
COLLECTION
FAIR
FEATURE
GALLERY
GENE
INSTITUTION
PROFILE
SALE
SHOW
TAG
}
Depending on what your usecase is, if you're constructing this query via some code, then you can find out which SearchEntity values they have:
{
__type(name: "SearchEntity") {
name
enumValues {
name
}
}
}
Which returns:
{
"data": {
"__type": {
"name": "SearchEntity",
"enumValues": [
{
"name": "ARTIST"
},
{
"name": "ARTWORK"
},
...
}
}
}
then store them in an array, omit the quotation marks from the enum and pass the array back to the original query directly as an argument.
Something along the lines of this:
query search($entities: [SearchEntity]) {
search(query: "Berlin", first: 100, page: 1, entities: $entities) {
edges {
node {
displayLabel
imageUrl
href
}
}
}
}
and in your query variables section, you just need to add:
{
"entities": [ARTIST, ARTWORK, ...]
}
As for question 2)
The query itself returns a SearchableConnection object.
type SearchableConnection {
pageInfo: PageInfo!
edges: [SearchableEdge]
pageCursors: PageCursors
totalCount: Int
aggregations: [SearchAggregationResults]
}
Digging deeper, we can see that they have edges, of type SearchableEdge - which is what you're querying.
type SearchableEdge {
node: Searchable
cursor: String!
}
and finally, node of type Searchable which contains the data you're trying to access.
Now, the type Searchable doesn't contain type:
type Searchable {
displayLabel: String
imageUrl: String
href: String
}
But, if you look at where that Searchable type is implemented, you can see SearchableItem - which contains the property of displayType - which doesn't actually exist in Searchable.
You can access the property of SearchableItem and get the displayType, like so:
{
search(query: "Berlin", first: 100, page: 1, entities: [ARTIST, ARTWORK, ARTICLE]) {
edges {
node {
displayLabel
imageUrl
href
... on SearchableItem {
displayType
}
}
}
}
}
and your result will look like this:
{
"data": {
"search": {
"edges": [
{
"node": {
"displayLabel": "Boris Berlin",
"imageUrl": "https://d32dm0rphc51dk.cloudfront.net/CRxSPNyhHKDIonwLKIVmIA/square.jpg",
"href": "/artist/boris-berlin",
"displayType": "Artist"
}
},
...

Resources