ApolloClient v2 to v3 : queries's result doesn't match anymore with types - apollo-client

I came here as I stuck with typescript and ApolloClient !
For the use of ApolloClient v3, possibleTypes as required and so, I have upgraded codegen as the latest version and since, my app typescript is not matching anymore !!
Here one example on one of my queries generated by codegen before upgrade.
export type GetMemberListQuery = (
{ __typename?: 'Query' }
& { userList: Maybe<(
{ __typename?: 'UserList' }
& Pick<UserList, 'totalCount' | 'maxPage'>
& { users: Array<Maybe<(
{ __typename?: 'User' }
& Pick<User, 'firstName' | 'lastName' | 'description'>
& { teams: Maybe<Array<Maybe<(
{ __typename?: 'Team' }
& __TeamFragment
)>>> }
)>> }
)> }
);
and there is the same query after upgrade :
export type GetMemberListQuery = {
userList?: {
__typename?: 'UserList',
totalCount?: number | null,
maxPage?: number | null,
users: Array<{
__typename?: 'User',
firstName?: string | null,
lastName?: string | null,
description?: string | null,
id: string, status?: string | null,
email?: string | null,
displayName?: string | null,
employeeNumber?: string | null,
avatarUri?: string | null,
teams?: Array<{
__typename?: 'Team',
id: string,
status?: string | null,
name?: string | null,
createdAt?: any | null,
updatedAt?: any | null
} | null> | null,
} | null>
} | null
};
Using useQuery hooks like this below :
const { data, loading } = useQuery<GetMemberListQuery, GetMemberListQueryVariables)(GetMemberList, {...})
and then passing users={data.userList.users} to a sub component with this following prop :
users: Users[]
An typescript error occured telling me my users props doesn't match with User types.
I am really lost... I need to upgrade my apollo/client and so, codegen... but I need that the generated file's query stay, as much as possible, the same than now.
Thanks a lot for reading
Cheers

Related

Undefined type error. But it seems I am providing the type explicitly with Prisma generated types

I'm trying to setup the createUser mutation in graphql with Prisma. Here's the error I'm receiving when I try to run the mutation:
node_modules/#nestjs/graphql/dist/utils/reflection.utilts.js:15
throw new undefined_type_error_1.UndefinedTypeError((0, lodash_1.get)(prototype, >'constructor.name'), propertyKey, index);
^
Error: Undefined type error. Make sure you are providing an explicit type for the >"createUser" (parameter at index [0]) of the "UsersResolver" class.
It's strange that I do everything according to the video and the author manages to complete the mutation without any errors. 🤷‍♂️
Here's my mutation resolver:
#Mutation(returns => User)
async createUser(
#Args('userCreateInput') userCreateInput: Prisma.UserCreateInput,
) {
return this.userService.create(userCreateInput)
}
Here's my service:
async create(userCreateInput: Prisma.UserCreateInput) {
return this.prisma.user.create({
data: userCreateInput
});
}
Here's the automatically generated type from the prisma schema:
export type UserCreateInput = {
id?: string
ID?: number | null
email: string
passwordHash: string
phone?: string | null
firstName?: string | null
secondName?: string | null
lastName?: string | null
role?: Role
isAdmin?: boolean
products?: ProductCreateNestedManyWithoutAuthorInput
shops?: ShopCreateNestedManyWithoutOwnerInput
AddressFrom?: AddressFromCreateNestedManyWithoutUserInput
registerdAt?: Date | string
lastVist?: Date | string | null
image?: string | null
updatedAt?: Date | string | null
}
Here's the mutation operation I'm trying to do:
mutation Mutation($userCreateInput: UserCreateInput!) {
createUser(userCreateInput: $userCreateInput) {
id
email
passwordHash
}
}
Arguments for this mutation in the playground:
{
"createUserInput": {
"email": thisemail#gmail.com,
"passwordHash": qwerty123
}
Response from playground.
"errors": [
{
"message": "Variable \"$userCreateInput\" of required type \"UserCreateInput!\" was not provided.",
"locations": [
{
"line": 1,
"column": 19
}
],
"extensions": {
"code": "BAD_USER_INPUT",
"exception": {
"stacktrace": [
"GraphQLError: Variable \"$userCreateInput\" of required type \"UserCreateInput!\" was not provided."
Please tell me what could be the cause of the problem 🙏

How to achieve GraphQL Error Handling for Multiple Items?

I have been learning GraphQL and right now I am learning things related to Error Handling in GraphQL. I referred to this article for Error Handling in GraphQL. Now, from Article I understand that If I have a user query in GraphQL then I can make an Union which will contain all possible types including Error
union UserResult = User | DeletedUser | Error
Now, In that particular resolver if any error would occur then I will send an Error type Response and I'll be able to show appropriate error on Client Side. and Thats the Error Handling.
But what If I have a query users which will return an Array of Users. Suppose by following the above approach I will be doing something like...
type Query {
users: [UserResult!]!
}
union UserResult = User | DeletedUser | Error
Now, In users resolver if any error would occur then I wont be able to send back a single Error Response. Because, GraphQL does not allow such syntax...
type Query {
users: UsersResult!
}
union UserResult = User | DeletedUser | Error
union UsersResult = [UserResult!] | Error
So, How am I gonna achieve this functionality in GraphQL?
The Response I expect is...
If nothing goes wrong...
data: {
users: [
{ id: '1', username: 'user1', email: 'user1#demo.com' },
{ id: '2', username: 'user2', email: 'user2#demo.com' },
{ id: '3', username: 'user3', email: 'user3#demo.com' }
]
}
If something goes wrong while fetching data from database(for instance)...
data: {
error: {
code: 'ER101',
message: 'Something went wrong',
}
}
I would do something like this, to keep the benefits explained in the article:
union UserResult = User | DeletedUser | Error
type UserList {
list: [UserResult!]!
count: Int
}
union UsersResult = UserList | Error
type Query {
users: UsersResult!
}
You can then handle errors like this:
query users {
users {
...on UserList {
count
list {
...on User {
name
}
...on DeletedUser {
code
}
...on Error {
code
}
}
}
...on Error {
code
}
}
}
It also depends on your use cases and business logic.
Not sure if this will solve your case:
// User type:
user: {
id: string,
username: string,
email: string,
}
// Return type looks like this:
data: {
users: user[] | null, // nullable
error: {
code: string,
message: string,
} | null // nullable
}
// If nothing went wrong:
data: {
users: [
{ id: '1', username: 'user1', email: 'user1#demo.com' },
{ id: '2', username: 'user2', email: 'user2#demo.com' },
{ id: '3', username: 'user3', email: 'user3#demo.com' }
],
error: null,
}
// If something went wrong:
data: {
users: null,
error: {
code: 'ER101',
message: 'Something went wrong',
}
}
It's up to your use case, since your users is an array, and error only contains one message, I suppose errors can be concluded to one single error and inform client?

Preload with additional column

So I have a table of users where each user is associated with a main tool and tools in their backpacks. Each tool also have a "quality". But I have no idea of how to fetch this value so I appreciate any help on how to do this.
Databases
users
+----+------+--------------+
| id | name | main_tool_id |
+----+------+--------------+
| 1 | adam | 1 |
+----+------+--------------+
tools
+----+-------------+
| id | name |
+----+-------------+
| 1 | hammer |
| 2 | screwdriver |
+----+-------------+
user_tools
+----+---------+---------+---------+
| id | user_id | tool_id | quality |
+----+---------+---------+---------+
| 1 | 1 | 1 | LOW |
| 2 | 1 | 2 | HIGH |
+----+---------+---------+---------+
Models
type User struct {
Id int64 `json:"-"`
Name string `json:"name"`
MainToolId int64 `json:"-"`
MainTool Tool `json:"main_tool"`
ToolsInBackpack []Tool `json:"tools_in_backpack" gorm:"many2many:user_tools"`
}
type Tool struct {
Id int64 `json:"-"`
Name string `json:"name"`
Quality string `json:"quality"`
}
Code
var users []User
DB.Preload("MainTool").Preload("ToolsInBackpack").Find(&users) // <-- Modify this (I guess)
Real result
{
"name": "adam",
"main_tool": {
"name": "hammer",
"quality": ""
},
"tools_in_backpack": [
{
"name": "hammer",
"quality": ""
},
{
"name": "screwdriver",
"quality": ""
},
]
}
Desired result
{
"name": "adam",
"main_tool": {
"name": "hammer",
"quality": "LOW"
},
"tools_in_backpack": [
{
"name": "hammer",
"quality": "LOW"
},
{
"name": "screwdriver",
"quality": "HIGH"
},
]
}
Thanks a lot for your help!
If you do not require MainTool to return an object.
maybe you uses it
type User struct {
ID uint `json:"-"`
Name string
MainTool int64
ToolsInBackpack string
Tools []*Tool
}
type Tool struct {
ID uint`json:"-"`
Name string
UserID uint
}
User{MainTool: 1, ToolsInBackpack: "[1,2]", Tools: []*Tool{&t1, &t2}}
if you don't wana to use string at ToolsInBackpack string you can't use datatypes.JSON at gorm.io/datatypes
for example
ToolsInBackpack datatypes.JSON
This isn't supported in Gorm so there won't be an easy way to just Preload and it's done.
Here are a few techniques you could look at:
Use a Custom Join Table Model that contains the fields that you need
type UserTool struct {
UserID int64 `gorm:"primaryKey"`
ToolID int64 `gorm:"primaryKey"`
Quality string
}
err := db.SetupJoinTable(&User{}, "ToolsInBackpack", &UserTool{})
// handle error
(I left out the UserTools.ID field, Gorm doesn't need it, and you only need it if a user can have the same tool twice, in which case put it back on as part of the primaryKey. I don't know whether Gorm will be happy with it but try it out).
Then you can use this model to query the field as any other model:
userTools := []UserTool
err := db.Where("user_id = ?", user.ID).Find(&userTools).Error
// handle error
// now userTools[i].Quality is filled, you could use it to update the corresponding users[j].ToolsInBackpack[k].Quality
It's a pain because you need to match IDs as a post-processing step.
Use a Has-Many/Belongs-To hybrid relation to model the Many-To-Many Join Table:
Here the User Has Many UserTools and a Tool belongs to one or more UserTools. This effectively models the meaning of Many-to-Many (in ERD, a relationship like [User]>--<[Tool] can be decomposed into [User]--<[UserTool]>--[Tool]).
type User struct {
ID int64
Name string
MainToolId int64
MainTool Tool
ToolsInBackpack []UserTool
}
type UserTool struct {
UserID int64 `gorm:"primaryKey"`
ToolID int64 `gorm:"primaryKey"`
Tool Tool
Quality string
}
type Tool struct {
ID int64
Name string
}
Now you can Preload this association like this:
err := db.Model(&User{}).Preload("ToolsInBackpack.Tool").Find(&users).Error
// handle error
// now users[i].ToolsInBackpack[j].Quality and
// users[i].ToolsInBackpack[j].Tool.Name will be set correctly.
Only problem now is that you've got a weird shape in the model that you're then trying to marshal into JSON (most likely to be used in an API). My advice here is to split the DB model from the JSON API model, and have a mapping layer in your code. The DB model and the API Messages invariably diverge in important ways and trying to reuse one model for both soon leads to pain.

Fetch complete objects including children using Golang and Gorm

I'm using Gorm and Golang to fetch data from my database. Is it possible to make Gorm fetch also the objects children (foreign keys)?
Database tables
users
+----+---------+------------+
| id | name | country_id |
+----+---------+------------+
| 1 | Adam | 1 |
| 2 | Bertil | 1 |
| 3 | Charlie | 2 |
+----+---------+------------+
countries
+----+--------+
| id | name |
+----+--------+
| 1 | Sweden |
| 2 | Norway |
+----+--------+
Models
type User struct {
Id int64 `json:"-"`
Name string `json:"name"`
CountryId int64 `json:"-"`
Country Country `json:"country"`
}
type Country struct {
Id int64 `json:"-"`
Name string `json:"name"`
}
Code to fetch all users
var users []User
DB.Find(&users) // Question: How should this be modified to automatically fetch the Country?
Actual result
[
{
"name": "Adam",
"country" : {
"name": "",
}
},
...
]
Desired result
[
{
"name": "Adam",
"country" : {
"name": "Sweden",
}
},
...
]
Thanks a lot for you input!
/Klarre
Yes it is possible, it's called Preloading.
users := make([]User,0)
DB.Preload("Country").Find(&users)

How to define values specific to a connection of two nodes in GraphQL?

I have two types in my schema:
type Resident = { type Visitor = {
id id
name name
} }
In my database:
Residents and Visitors Tables:
+--------+-------+ +--------+---------+
| res_id | name | | vis_id | name |
+--------+-------+ +--------+---------+
| 1 | Alice | | 1 | Charlie |
| 2 | Bob | +--------+---------+
+--------+-------+
And then a table that shows which visitor belongs to which resident:
+--------+--------+--------------+
| res_id | vis_id | relationship |
+--------+--------+--------------+
| 1 | 1 | fam/fri |
| 2 | 1 | contractor |
+--------+--------+--------------+
Each visitor could either be a "fam/fri" or a "contractor" to a resident. So Charlie is Alice's visitor as her family or friend. However, Charlie is also a visitor to Bob, but instead as a contractor.
Question: How do I structure my schema so that when I query Alice, Charlie returns as a fam/fri, and when I query Bob, Charlie is returned as a contractor? I imagine this:
{
Resident(id: 1) { "Resident" {
name "Alice"
Visitor { "Visitor" {
id ===> "1"
name "Charlie"
relationship "fam/fri"
} }
} }
}
and also:
{
Resident(id: 2) { "Resident" {
name "Bob"
Visitor { "Visitor" {
id ===> "1"
name "Charlie"
relationship "contractor"
} }
} }
}
Something like:
type Query {
resident(id: Int): Resident
}
type Resident {
id: Int!
name: String!
visitors: [Visitor!]!
}
type Vistor {
id: Int!
name: String!
relationship: VisitorRelationship!
}
enum VisitorRelationship {
CONTRACTOR
FAMILY_OR_FRIEND
}
Note that by convention field names should be camelCase and type names should be in PascalCase. If the data returned from your data source (whether that's a database, API, or whatever) is not in the same shape as what you want to return from your GraphQL service, then you should transform the data before returning it inside your resolver, for example:
const relationshipMap = {
'fam/fri': 'FAMILY_OR_FRIEND',
'contractor': 'CONTRACTOR',
}
const resolvers = {
Query: {
resident: (root, args) => {
const resident = await Resident.findById(args.id)
// assuming resident has a property named joinTable that's
// an array and each relationship has a property named visitor
return {
...resident,
visitors: resident.joinTable.map(relationship => {
return {
...joinTable.visitor,
relationship: relationshipMap[joinTable.relationship],
}
})
}
},
},
}
You can also map enums to custom values this way.

Resources