graphql-nexus / nexus-prisma

Prisma plugin for Nexus
https://graphql-nexus.github.io/nexus-prisma
MIT License
564 stars 45 forks source link

Nexus ignores Prisma middleware #1130

Open habovh opened 4 days ago

habovh commented 4 days ago

Description

I am using olivierwilkinson/prisma-soft-delete-middleware to keep important models in the DB even after they are deleted. However, when used in conjunction with Nexus the middleware is ignored, which leads to runtime errors due to inconsistencies withe the gql schema.

Repro Steps/Link

db.ts:

export const db = new PrismaClient()

db.$use(
  createSoftDeleteMiddleware({
    models: {
      Category: true,
    },
    defaultConfig: {
      field: 'deletedAt',
      createValue: (deleted) => {
        if (deleted) return new Date()
        return null
      },
    },
  }),
)

schema.prisma:

model Post {
  categoryId String?
  category   Category? @relation
}

model Category {
  deletedAt DateTime?
  createdBy User
  name String
}

Post.ts:

import {
  Post as PrismaPost,
} from 'nexus-prisma'

const Post = objectType({
  name: PrismaPost.$name,
  definition(t) {
    t.field(PrismaPost.category)
  },
})

Category.ts:

import {
  Category as Prisma Category,
} from 'nexus-prisma'

const Category = objectType({
  name: PrismaCategory.$name,
  definition(t) {
    t.field(PrismaCategory.createdBy)
  },
})

When querying a Post with a soft-deleted category, the category will be returned. This in itself would not cause issues other than the data being accessible when it shouldn't.

query post {
  post(id: 1) {
    category {
      name
    }
  }
}

However, errors can arise when querying non-scalar fields:

query post {
  post(id: 1) {
    category {
      name
      createdBy {
        firstname
      }
    }
  }
}

For some reason the nested access to the Category.createdBy field will return null because of the Category soft delete, but the category itself will not be null. In this specific case, this triggers a GraphQL error because my schema sets the createdBy as a non-null field on the Category type.

I'm not sure what's causing this partial support, but it's giving me headaches.

Current workaround is to implement a custom resolver in the object type declaration that defines a field that points to a soft-deletable record.

For example:

const Post = objectType({
  name: PrismaPost.$name,
  definition(t) {
    t.field(PrismaPost.category.name, {
      ...PrismaPost.category,
      resolve(root, args, ctx) {
        if (!root.categoryId) return null
        // Explicit use of `findUnique` will get handled by the middleware.
        return ctx.db.category.findUnique({ where: { id: root.categoryId } })
      },
    })
  },
})

Any insights would be appreciated here!