Fixing Inverse Relationships

I was once again needing a way to fix some relationships where the imported data using live import, was not setup correctly with inverse edges.

I wrote a JavaScript snippet to quickly set my outbound and inbound type and edges and then it fixes the @hasInverse relationships correctly. I used this for a many:1 relationship, but it should also work for many:many and 1:1 relationship types without any modification needed.

@gja, Do you think something like this could be added to the Slash Management Console to help users quickly fix data relationship integrity?

Schema Snippet
type Event {
  id: ID
  at: Contact @hasInverse(field: "hasEvents")
}
type Contact {
  id: ID
  hasEvents: [Event]
}
Fix Inverse Script
// TODO: Change this to your Slash endpoint without the `/graphql`
const ENDPOINT = 'https://your-endpoint.location.provider.cloud.dgraph.io'
// TODO: Change this to your client API key
const xAuthToken = '.../v4='

// TODO: Change to true when ready to run and persist changes
const commitNow = true

// TODO: Set the following using type dotted predicate formats:
// NOTE: These are not parsed and made safe from code injection, so use wisely or add injection checks if these values are coming from function parameters and could be open to injection.
const outboundType = 'Event'
const outboundEdge = 'Event.at'
const inboundType = 'Contact'
const inboundEdge = 'Contact.hasEvents'

const { default: fetch } = require('node-fetch')
const dqlContentType = 'application/graphql+-'
const rdfContentType = 'application/rdf'

const QUERY = `
  {
    outbound(func: type(${outboundType})) @cascade {
      uid
      ${outboundEdge} {
        uid
      }
    }
    inbound(func: type(${inboundType})) @cascade {
      uid
      ${inboundEdge} {
        uid
      }
    }
  }
`

return (
  fetch(`${ENDPOINT}/query`, {
    method: 'POST',
    headers: { 'Content-Type': dqlContentType, 'x-auth-token': xAuthToken },
    body: QUERY,
  })
    .then((res) => res.json())
    .then((res) => {
      let rdf = ''
      res.data.outbound.forEach((node) => {
        const children = Array.isArray(node[outboundEdge]) ? node[outboundEdge] : [node[outboundEdge]]
        children.forEach((child) => {
          rdf += `<${child.uid}> <${inboundEdge}> <${node.uid}> .\n`
        })
      })
      res.data.inbound.forEach((node) => {
        const children = Array.isArray(node[inboundEdge]) ? node[inboundEdge] : [node[inboundEdge]]
        children.forEach((child) => {
          rdf += `<${child.uid}> <${outboundEdge}> <${node.uid}> .\n`
        })
      })
      return rdf
    })
    .then(async (rdf) => {
      if (rdf === '') return null
      console.log(rdf)
      const res = await fetch(`${ENDPOINT}/mutate${commitNow ? '?commitNow=true' : ''}`, {
        method: 'POST',
        headers: { 'Content-Type': rdfContentType, 'x-auth-token': xAuthToken },
        body: `{
        set {
          ${rdf}
        }
      }`,
      })
      const resJSON = await res.json()
      return console.log(resJSON)
    })
)
5 Likes