Cannot update multiple nodes at once

context: Updating multiple nodes at once with their own separate patches.

I can generate a mutation with multiple mutation statements one for each node, but my IDE frowns upon dynamic queries/mutations in this regards.

I can’t do this with the generated updateType mutation as it stands because it only takes one filter and patches all nodes that match the filter with the same patch.

I thought I could do this using a deep mutation, but deep mutations do not allow you to patch child nodes.

type Contact {
  id: ID!
  name: String!
  settings: [Setting] @hasInverse(field: forContact)
}

type Setting {
  id: ID!
  type: SettingType!
  forContact: Contact!
  value: String!
}

type SettingType {
  id: ID!
  name: String! @search(by: [hash,term,fulltext])
  type: String!
  hasValues: [Setting] @hasInverse(field: type)
}

Data to update:

{
  "contact": {
    "id": "0xa", // existing Contact
    "settings": [
      {
        "id": "0x42",
        "value": "foo", // updated value
        "type": {
          "id:" "0xc" // existing SettingType
        }
      },
      {
        "id": "0x43",
        "value": "bar", // update value
        "type": {
          "id": "0xb" // existing SettingType
        }
      },
      {
        "value": "baz", // new value
        "type": {
          "id": "0xd" // existing SettingType
        }
      }
    ]
  }
}

So I can’t do updateContact because no patch on children. I can’t do updateSettingType because no patch on children. I can’t do updateSetting because one of the settings is a new setting and because it can only update one node at a time.

“Hack” Solution:

Is there any other way to push this data in a single request besides breaking it down and doing something dynamic like:

fragment SettingFrag on Setting {
  id
  value
}
mutation myUpdate {
  setting1: updateSetting(input: {
    filter: { id: ["0x42"] }
    set: { value: "foo" }
  }) {
    setting {
      ...SettingFrag
    }
  }
  setting2: updateSetting(input: {
    filter: { id: ["0x43"] }
    set: { value: "bar" }
  }) {
    setting {
      ...SettingFrag
    }
  }
  # update Contact to do a deep mutation adding new settings
  updateContact(input: {
    filter: { id: ["0xa"] }
    set: {
      settings: [{
        type: { id: "0xd" }
        value: "baz"
      }]
    }
  }) {
    contact {
      id
      settings {
        ...SettingFrag
      }
    }
  }
}

Mimum Solution:

If updateType mutations accepted an array of UpdateTypeInput such as:

updateType(input: [UpdateTypeInput!]!) UpdatePayload

then I could at least make my mutation above a static mutation with variables even though I have to separate patched settings vs. new settings.

With the recent changes converting objects to arrays when necessary to conform to spec, this would be a backwards compatible change still :wink:

Best Solution:

What would be even better and a perfect solution would be to allow to patch children in an update mutation. :smiley:

3 Likes

Does your IDE give you an error? I am assuming that you are talking about something like the following.

mutation update {
  updateSetting(...)
  
  updateSetting(...)

}

The minimum solution that you have proposed here is something that we could look into. We don’t want to allow to patch children in an update mutation because then the API’s can have unintended consequences. For example, my update state API might end up updating the country linked to the state which is not ideal.

Well, sort of. The error is not from the server, but rather an error from the IDE/extensions. See Anyone know how to quiet a GraphQL Syntax Error in VS Code with Apollo GraphQL extension?

Almost, but the mutations have to be aliased with a unique name. Also these mutations are created dynamically and not with a static mutation with variables. That is the need of the OP in the first place. Here is some pseudo code:

const updateData = [{id: 1, name: "New Name 1"}, {id: 2, name: "New Name 2"}]
let mutations = ""
updateData.forEach(node => {
  mutations += `
    updateNode(input: {
      filter: { id: [\"${node.id}\"] }
      set: { name: \"${node.name}\" }
    }) { 
      numUids
      node {
        id
        name
      }
    }
  `
})
const [doUpdate] = useMutation(gql`mutation { ${mutations} }`)

One of the main problems here is that because we are using multiple mutations, variables are harder to pass in and when including the variables in a template string as above, it could open up injection in the query. Hello Little Bobby Tables GraphQL style.

Yeah, not exactly sure how differret API caches would handle this. With Apollo client, all it needs is the id (which uses id or _id by default but can be configured to use a custom id for specific types) and then it updates that item in the cache and it updates everywhere in the app that uses that item. This happens even for children items automatically as long as the id and updated data is returned. If I update Parent with Children, then I need to return the Parent and nested Children to update the cache.

Any idea if this could be a simple solution, or would this be more complicated?

I have another use case where I have a one way relationship. In order to change the relationship I have to write a weird looking mutation instead of a simple one:

mutation MOVE_FILTER_CATEGORY($oldCat: ID!, $newCat: ID!, $filter: ID!) {
  remove: updateFilterCategory(input: {
    filter: { id: [$oldCat] }
    remove: { hasFilters: [{ id: $filter }] }
  }) { numUids }
  set: updateFilterCategory(input: {
    filter: { id: [$newCat] }
    set: { hasFilters: [{ id: $filter }] }
  }) { numUids }
}

vs.

mutation MOVE_FILTER_CATEGORY($oldCat: ID!, $newCat: ID!, $filter: ID!) {
  updateFilterCategory(input: [
    {
      filter: { id: [$oldCat] }
      remove: { hasFilters: [{ id: $filter }] }
    },
    {
      filter: { id: [$newCat] }
      set: { hasFilters: [{ id: $filter }] }
    }
  ]) { numUids }
}

My query returned in this example is just the numUids, but if I had a long query, I would have to also either duplicate code or use a fragment, making a somewhat simple mutation even more complex.

1 Like

@minhaj do you know if this might be a easy fix to give a better API with possibly little effort?

2 Likes