Migwell
Migwell

Reputation: 20107

One-to-many relationship in gatsby

Using Gatsby.js, I want to transform a number of static files into a hierarchy. One aspect of this hierarchy is that one "executable" has many files produced by that executable. My GraphQL schema for an executable is:

exports.createSchemaCustomization = ({ actions: {createTypes}, schema }) => {
  createTypes([
    schema.buildObjectType({
      name: "CondaExecutable",
      fields: {
        wrappers: "[File]",
        name: "String!",
        path: "String!",
        publicURL: "String!",
      },  
      interfaces: ["Node"],
    }), 
  ])  
}

Then, I want to add multiple files to the wrapper field of my new object, I try to do that in createPages, with reference to the foreign key section in the Gatsby docs:

exports.createPages = async ({ graphql, actions, getNode, createContentDigest, createNodeId}) => {
  const { createNode, createNodeField, createPage, createParentChildLink } = actions
  const result = await graphql(`
        {
          allFile(filter: {sourceInstanceName: {in: ["Wrappers", "Definitions"]}}) {
            edges {
              node {
                id
                relativePath
                extension
                publicURL
              }
            }
          }
        }
  `)

  await Promise.all(result.data.allFile.edges.map(async ({ node }) => {
    // Now create the node for the single file within that package
    const exeId = createNodeId(...);
    let exe = getNode(exeId);
    if (!exe) {
      exe = {
        id: exeId,
        name: stem,
        path: node.relativePath.split('.')[0],
        publicURL: exeUrl,
        parent: versionId,
        wrappers: [],
        internal: {
          type: "CondaExecutable",
          contentDigest: node.relativePath
        }
      };
      await createNode(exe);
    }

    // Link the executable to the wrapper
    const wrappers = exe.wrappers || [];
    wrappers.push(node.id)
    createNodeField({node: exe, name: 'wrappers___NODE', value: wrappers});
  }));
}

Unfortunately this code doesn't work. I get the error Cannot return null for non-nullable field File.id. In any case, I'm not surprised this is wrong, because I don't really know what I'm doing here.

How can I make a relationship between my own custom type, and many Files?

Upvotes: 1

Views: 812

Answers (1)

Migwell
Migwell

Reputation: 20107

As explained much better in this section of the documentation, the way you implement a foreign key relationship differs depending on how you defined your type. Only if you're using automatic type inference do you use the ___NODE syntax. If you have a custom type defined in GraphQL instead of using createTypes(), you can use the @link directive for this purpose.

If you're using the third option, createTypes(), to define your custom types as I am, you need to instead implement resolve() for your foreign keys. In my one-to-many case that means:

schema.buildObjectType({
  name: "CondaExecutable",
  fields: {
    wrappers: {
      type: "[File]",
      resolve(source, args, context, info){
        return context.nodeModel.getNodesByIds({
          ids: source.wrappers, // This matches the name of the field we're currently inside
          type: 'File' // This matches the type of the wrappers field
        })
      }
    },
    name: "String!",
    path: "String!",
    publicURL: "String!",
  },
  interfaces: ["Node"],
})

If you instead have a one-to-one relationship, your resolve function would look like:

resolve(source, args, context, info){
  return context.nodeModel.getNodeById({
    id: source.wrapper,
    type: 'File'
  })
}

Then, to link the nodes in createPages, you don't have to use the ___NODE syntax, but you do have to use createNodeField with an updated array each time you add a new child:

const wrappers = exe.wrappers || [];
wrappers.push(node.id)
createNodeField({node: exe, name: 'wrappers', value: wrappers});

Upvotes: 1

Related Questions