Input type for array of objects as mutation input messes with optimistic response - graphql

On the server I built a schema where I used and input type to obtain the ability to pass an array of objects. I've done this in a couple of places but this is by far the most simple one:
export default gql`
input SkillInput{
skill: String!
}
extend type Mutation {
createSkill(input: [SkillInput]): [Skill]!
}
type Skill {
id: ID!
skill: String!
created_at: DateTime!
}
`;
On the frontend, I'm able to execute the mutation with said array of objects just fine. The issue comes when I try to incorporate optimistic response.
This isthe mutation in question:
this.$apollo
.mutate({
mutation: CREATE_SKILL_MUTATION,
variables: { input: skillArrOfObj },
optimisticResponse: {
__typename: "Mutation",
createSkill: skillArrOfObj.map(entry => ({
__typename: "Skill",
id: -1,
skill: entry.skill
}))
},
update: (store, { data: { createSkill } }) => {
const data = store.readQuery({
query: SKILLS_QUERY
});
console.log(createSkill);
data.skills.push(...createSkill);
store.writeQuery({
query: SKILLS_QUERY,
data
});
}
})
I've tried to add to each entry of skillArrOfObj the __typename and id, however the mutation fails.
Another thing to mention is that update runs twice and the log on createSkill yields two different results on update:
First run
{__typename: "Skill", id: -1, skillObj: Array(2)}
id: -1
skillObj: (2) [{…}, {…}]
__typename: "Skill"
Second run shows an array of just the id and the __typename with no skill attribute
Is there a special __typename needed for arrays? Or something I need to do before running the mutation?

Related

how can I fetch data from graphql in my resolver

Within my resolver I seem to be unable to fetch connected data
this works in the graphql playground (prisma) but I am unsure of the syntax about how to form a resolver in apollo server
// my typedef for activity is
type Activity {
id: ID! #id
ActivityType: ActivityType!
title: String!
date: DateTime
user: User!
distance: Float!
distance_unit: Unit!
duration: Float!
elevation: Float
elevation_unit: Unit
createdAt: DateTime! #createdAt
updatedAt: DateTime! #updatedAt
// and my resolver currently looks like this
async activity(parent, args, ctx, info) {
const foundActivity = await ctx.db.query.activity({
where: {
id: args.id
}
});
// todo fetch user data from query
console.log(foundActivity);
}
// where db has been placed onto the ctx (context)
// the CL gives all of the data for Activity apart from the user
// in the playground I would do something like this
query activity {
activity(where: {
id: "cjxxow7c8si4o0b5314f9ibek"
}){
title
user {
id
name
}
}
}
// but I do not know how to specify what is returned in my resolver.
console.log(foundActivity) gives:
{ id: 'cjxxpuh1bsq750b53psd2c77d',
ActivityType: 'CYCLING',
title: 'Test Activity',
date: '2019-07-10T20:21:27.681Z',
distance: 13.4,
distance_unit: 'KM',
duration: 90030,
elevation: 930,
elevation_unit: 'METERS',
createdAt: '2019-07-10T20:48:50.879Z',
updatedAt: '2019-07-10T20:48:50.879Z' }
Prisma is the DB ORM and then I have an Apollo-Server 2 server running on top of that. Unfortunately, stack overflow also thinks that there is too much code on this post so I will have to waffle on about inconsequential gibberish due to the fact that their system can't handle it.
You will have to implement a resolver for Activity.user. Unfortunately your entity does not seem to contain a reference to the user. First, add the user connection to your Prisma data model. Then implement a resolver for Activity.user. I am not very familiar with Prisma 1 but this naive implementation should already do what you want:
let resolvers = {
Query: {
// ...
},
Activity: {
user(parent, args, ctx) {
return ctx.db.query.activity({ id: parent.id }).user();
}
}
}
Find out more about resolving relations in Prisma here
So the answer was incredibly simple:
I just add a second argument to the query (after the "where" with a gql tag of the data shape to be returned so my code now looks like:
const foundActivity = await ctx.db.query.activity(
{
where: {
id: args.id
}
},
`{id title user { id name }}`
);

How to handle Union or Interface in graphQL mutations?

I got the following schema :
type Vehicle {
id: ID!
name: String!
color: String!
}
input AddVehicle {
name: String!
color: String!
}
input UpdateVehicle {
id: ID!
name: String!
}
Now I would like to add some properties to my vehicles, depending of the Vehicle model, like
type CarProperties {
wheelSize: Int!
doors: Int!
}
type BoatProperties {
length: Int!
}
union VehicleProperties = CarProperties | BoatProperties
type Vehicle {
[ ... ]
properties: vehicleProperties!
}
So it's quite straightforward to write the queries, but I'm struggling when it comes to make mutations...
AFAIK graphQL inputs does not implement unions or interface (There is a related thread here https://github.com/graphql/graphql-spec/issues/488)
So the workaround I see here is to duplicate my inputs, like :
input addBoatVehicle {
name: String!
color: String!
properties: BoatProperties!
}
and so on with updateBoatVehicle, addCarVehicle, updateCarVehicle.
But if I get a lot of vehicle models, or maybe a third or a fourth mutation, I'm afraid it becomes cumbersome quickly.
Is there any recommended way to manage this case ?
Creating separate mutations is the proper solution. You can alleviate some of the pain by making your mutations incredibly lightweight and refactor out processing of those items to a separate function.
function addVehicle(input) {
// disambiguate the input type
}
function updateVehicle(input) {
// dismabiguate the input type, preferably in its own refactor function so
// it can be used above too!
}
const resolvers = {
Mutation: {
addBoat: (parent, boatInput) => { return addVehicle(boatInput) },
addCar: (parent, carInput) => { return addVehicle(carInput) },
updateBoat: (parent, boatInput) => { return updateVehicle(boatInput) },
updateCar: (parent, carInput) => { return updateVehicle(carInput) },
}
}

Prisma append custom field to info argument when querying data

I am trying to append extra fields to the info object when querying data from prisma database. I have seen this post but I can't get fragments working.
I have below migrations:
type User {
id: ID! #unique
name: String
}
type Video {
id: ID! #unique
name: String
likes: [Like]
}
type Like {
id: ID! #unique
user: User
createdAt: DateTime!
}
Now, I would like to query all videos and check if current user has already liked a video. If so, the likes object should read the createdAt value, otherwise it would be empty.
I am doing this in playground via following query:
query {
videos() {
id
name
hasVoted: likes(where: {user: {id: "cjr7r85jy00rc0892dfwpu96u"}){
createdAt
}
}
}
this works but I would like to add the hasVoted to my query resolver to automatically append it to all queries. How can I do this ?
I tried solving it via fragments but without luck:
videos: {
fragment: `fragment hasVoted on Video {
hasVoted: likes (where: {user: {id: "cjr7r85jy00rc0892dfwpu96u"}}){
createdAt
}
}`,
resolve: async (_, args, ctx, info) => {
return await ctx.prisma.query.videos({},info);
}
}
Anyone has some ideas how I can do this? thx!
You can use addFragmentToInfo from graphql-bindings
https://oss.prisma.io/content/graphql-binding/02-api-reference#addfragmenttoinfo

Create mutation between related types in GraphQL

I'm using GraphQL to try to create a record that has a relation to another type. The types are Task and Day
datamodel.graphql:
type Task {
id: ID! #unique
content: String!
completed: Boolean!
dateToDo: Day!
}
type Day {
id: ID! #unique
content: String!
tasks: [Task]
}
I want to create a task so that it has a reference of the date it should be completed (from the Day type)
schema.graphql
type Mutation {
createTask(content: String!, completed: Boolean!, dateToDo: ID! ): Task!
}
my mutation resolver looks like this:
const Mutations = {
async createTask(parent, args, ctx, info) {
const task = await ctx.db.mutation.createTask(
{
data: {
dateToDo: {
connect: {
id: args.dateToDo
}
},
...args
}
},
info
);
return task;
},
when I run this mutation to create the task:
mutation CREATE_ONE_TASK {
createTask(
content: "a new task",
completed: false,
dateToDo: "cjqycv9dtjklr09179y9zfntq")
{
id
}
}
I get this error:
"message": "Variable \"$_v0_data\" got invalid value
{\"dateToDo\":\"cjqycv9dtjklr09179y9zfntq\",\"content\":\"a new
task\",\"completed\":false}; Expected type
DayCreateOneWithoutTasksInput to be an object at value.dateToDo.",
My questions are: Am I using connect correctly in the mutation resolver? And what the heck is DayCreateOneWithoutTasksInput (I see its been automagically added in prisma.graphql) and how do I use it to create a Task that has a relation to a Day's ID?
The mutation to create the task has the following shape:
mutation b {
createTask(
data: {
content: "Task1"
completed: false
dateToDo: { connect: { id: "cjqzjvk6w000e0999a75mzwpx" } }
}
) {
id
}
}
The type DayCreateOneWithoutTasksInput Prisma is asking for is autogenerated and is the one expected for the field dataToDo. The name means that Prisma would accept a type that creates one Day node but does not have the field tasks or a type that specifies a connection. The WithoutTasksInput part states is there because the type can only be used nested in a mutation where you start from a task, Prisma therefore already has the value to fill in for the tasks field on the nested Day node and you do not need to specify it if you create the day instead of connecting an existing one.
If you use the Playground you can explore the schema that contains all the types on the right side.
schema explorer in the playground
Hope that helps!

Cascade delete related nodes using GraphQL and Prisma

I'm trying to figure out cascade deletion in GraphQL.
I'm attempting to delete a node of type Question, but type QuestionVote has a required relation to Question. I'm looking for a way to delete a Question and all its votes at once.
Mutation for deleting a Question:
type Mutation {
deleteQuestion(where: QuestionWhereUniqueInput!): Question!
}
And its resolver (I'm using Prisma):
function deleteQuestion(parent, args, context, info) {
const userId = getUserId(context)
return context.db.mutation.deleteQuestion(
{
where: {id: args.id}
},
info,
)
}
How can I modify that mutation to also delete related QuestionVote nodes? Or should I add a separate mutation that deletes one or multiple instances of QuestionVote?
In case it's important, here are the mutations that create Question and QuestionVote:
function createQuestion(parent, args, context, info) {
const userId = getUserId(context)
return context.db.mutation.createQuestion(
{
data: {
content: args.content,
postedBy: { connect: { id: userId } },
},
},
info,
)
}
async function voteOnQuestion(parent, args, context, info) {
const userId = getUserId(context)
const questionExists = await context.db.exists.QuestionVote({
user: { id: userId },
question: { id: args.questionId },
})
if (questionExists) {
throw new Error(`Already voted for question: ${args.questionId}`)
}
return context.db.mutation.createQuestionVote(
{
data: {
user: { connect: { id: userId } },
question: { connect: { id: args.questionId } },
},
},
info,
)
}
Thanks!
You can set up cascade deletion by modifying your datamodel.
Given your question, I assume your datamodel looks somewhat like this:
type Question {
id: ID! #unique
votes: [QuestionVote!]! #relation(name: "QuestionVotes")
text: String!
}
type QuestionVote {
id: ID! #unique
question: Question #relation(name: "QuestionVotes")
isUpvote: Boolean!
}
Then you have to add the onCascade: DELETE field to the #relation directive like so:
type Question {
id: ID! #unique
votes: [QuestionVote!]! #relation(name: "QuestionVotes" onDelete: CASCADE)
text: String!
}
type QuestionVote {
id: ID! #unique
question: Question #relation(name: "QuestionVotes")
isUpvote: Boolean!
}
Now, every time a Question node is deleted, all related QuestionVote nodes are also deleted.
Note: If omitting onDelete, the value is automatically set to onDelete: SET_NULL by default. This means that deleting a node results in setting the other side of the relation to null.
You can read more about cascading deletes in Prisma in the documentation.

Resources