urql-graphql / urql-exchange-graphcache

A normalized and configurable cache exchange for urql
88 stars 13 forks source link

Pass custom variables to `update` and `optimistic` #146

Closed ed-zm closed 4 years ago

ed-zm commented 4 years ago

I am dealing with querying from store a query that has different variables(and can vary) than the mutation performed. I am migrating fro apollo where we can use optimistic and update as second parameter when executing a mutation so we can pass different variables that are available in the same context. That doesn't seem to be an option in urql, so in that way we have to recreate all the updates and optimistic when setting up the client, which only receives 3 parameters, the current variables of the mutation, the cache and info object. let's get some example:

this is the query:

import gql from "graphql-tag";

export const CalendarQuery = gql`
  query calendar($sessionId: String, $from: Long!, $to: Long!, $timezone: String!, $focusedGroup: String) {
    calendar(sessionId: $sessionId, from: $from, to: $to, timezone: $timezone, focusedGroup: $focusedGroup) {
      practices {
        id
        durationMinutes
        label
        location
        teamName
        rgb
        start
        attendance {
          athleteName
          athleteFirstName
          athleteLastName
          _id: athleteGuid
          attendance
        }
      }
      events {
        id
        durationMinutes
        label
        location
        teamName
        rgb
        start
        attendance {
          _id: athleteGuid
          attendance
        }
      }
      games {
        id
        durationMinutes
        label
        location
        teamName
        rgb
        start
        attendance {
          _id: athleteGuid
          attendance
        }
      }
      workouts {
        id
        durationMinutes
        label
        location
        teamName
        rgb
        start
      }
    }
  }
`;

notice that to and from can vary as user wants to fetch sessions in a certain timespan.

now, I want to update or delete a session:

const UPDATE_SESSION = gql`
  mutation($sessionId: ID!, $timezone: String!, $language: String, $input: SessionInput!) {
    updateSession(sessionId: $sessionId, timezone: $timezone, language: $language, input: $input) {
      ... on Practice {
        id
        start
        durationMinutes
      }
      ... on Event {
        id
        start
        durationMinutes
      }
      ... on Game {
        id
        start
        durationMinutes
      }
      ... on Workout {
        id
        start
        durationMinutes
      }
    }
  }
`;

I execute my mutation:

await updateSession({
              sessionId: user.sessionId,
              timezone: "GMT",
              language: "en",
              input: { id: session.id, start: date.valueOf() }
            })

now I am going to set up my optimistic response callback:

updateSession: ({ input, ...variables }, cache, info) => {
      cache.updateQuery({ query: CalendarQuery, variables }, (data: any) => {

First Issue: I am not able to query the calendar from store as I dont have the correct variables.

Second Issue: I couldnt find any simple way to pass variables to that callback. I tried adding some extra variables in the execute mutation:

await updateSession({
              sessionId: user.sessionId,
              timezone: "GMT",
              language: "en",
              calendar,
              __typename: session.__typename,
              input: { id: session.id, start: date.valueOf() }
            })
updateSession: ({ input, calendar, __typename, ...variables }, cache, info) => {
      return { ...input, __typename };
    }

(see calendar as a custom variable which is not used in the mutation itself)

Seems also urql cleans the variables object and only pass the variables that are actually used in the mutation/query.

So, I am just looking for a solution, workaround or a method where I can pass variables to the update/optimistic callbacks so it can work for queries that has dinamic variables(as above) and not fixed ones that can be hardcoded.

Thanks in advance.

kitten commented 4 years ago

Hiya 👋 Excellent question! It did occur to me that we’ll need this eventually, and we just weren’t sure of how to make this work cleanly.

Having variables be defined on the query means that they’re type-cast and safe to pass. GraphQL code generators would also generate types for them, which means that they can be seamlessly defined and passed from the JS-side.

The question is, how do we pass extra data? We could either clean the variables after they’ve gone through Graphcache. That would mean however that those variables wouldn’t be documented, sanitised, or typed, in any case. The same goes for passing them to a directive.

We could also make it easier to pass extra context data into urql, but that would complicate our API and would still not be type-safe.

So basically we’re just trying to figure out what an elegant solution could look like that would:

Hope that makes sense 🙌 I’ll try to keep this issue updated with some more ideas

kitten commented 4 years ago

One idea that may work and comes to mind is defining local variables with a directive on the mutation, e.g.

mutation ($id: ID!, $extra: String!) @cache(filter: [“extra”]) {
  addItem(id: $id) {
    id
  }
}

The directive would be called @cache to make it more generic sounding, so it can be reused for future features.

The filter argument tells us which variables aren’t to be sent to the server.

The disadvantage here is that it’s easy but in Graphcache we’d have to alter the query to remove the filtered variables from the definition (that’s kind of annoying) and also filter them from the variables object itself.

The resolver would then be able to access the data like so however:

updates: {
  addItem({ id }, cache, { variables: { extra } }) {
    // ...
  }
}

Which is rather nice!

Optimally we’d somehow attach the extra data to the arguments of the field though, so it’s easier to type them, but that wouldn’t play nicely with the field’s definition 🤔