Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Schema type inference & Prisma types differer when using optional fields #90

Open
franky47 opened this issue Jan 20, 2022 · 2 comments
Open

Comments

@franky47
Copy link
Contributor

The 0.5.x releases and the change from .nullable() to .nullish() for optional fields have revealed a potential flaw in my application logic. Allow me to elaborate (this is not an issue per-se, but it can help others in a similar situation).

Optional fields in Prisma behave differently when reading and writing.

Let's use the following example model:

model User {
  id   String  @id @default(uuid())
  name String?
}

When writing data to the database (using create/update/upsert model methods), the following input types are accepted:

Value type on create on update/upsert
string insert value replace value
null keep field empty make field empty
undefined keep field empty no-op

This is because the UserCreateInput type in Prisma looks like this:

export type UserCreateInput = {
  id?: string 
  name?: string | null
}

On the other hand, when querying data, there are only two data types returned:

  • a string for existing field values
  • null otherwise

Note that there is no undefined on queried data.

This is because the User type in Prisma looks like this:

export type User = {
  id: string
  name: string | null
}

I agree that the generated schemas should represent what Prisma will accept as input, in order to do pre-write validation.

However, I use z.TypeOf<typeof aSubsetOfASchema> to generate partial types from the schemas, and have this single source of truth be used for:

  • Generating a Fastify response schema (using zod-to-json-schema)
  • Strong-typing the Fastify response object
  • Exposing the zod schema to the client to parse the API call response (eg: when there are Dates involved that need to be converted from an ISO-8601 representation in JSON), or just the type if it's all JSON-serialisable and I feel lazy about validating external input.

Example:

// On the server:

export const userNameReply = userSchema.pick({
  name: true
})

export type UserNameReply = z.TypeOf<typeof userNameReply>

fastify.get<{ Reply: UserNameReply }>(
  '/user/name', 
  {
    schema: {
      reply: {
        200: zodToJsonSchema(userNameReply)
      }
    }
  },
  async (req, res) => {
    const user = await req.server.prisma.user.findUnique({ where: {...} })
    // The JSON schema on the reply guarantees only the `name` property is returned
    return user
  }
)

// In the client:

const res = await axios.get('/user/name')
const userName = userNameReply.parse(res.data)

// or

const res = await axios.get<UserNameReply>('/user/name')
const userName = res.data

Because the userNameReply schema has .nullish() optional fields, it adds incorrect undefined constraints to the response data type.

As I said, this is not really an issue with zod-prisma and more with the way I use it, but I thought it could open a discussion on this duality of input/output types in Prisma queries.

Note: I know of the z.input and z.output type inferring helpers, but those probably won't help here as they are internal to the schema structure.

@franky47 franky47 changed the title Input vs Output schemas for nullable fields Schema type inference & Prisma types differer when using optional fields Jan 20, 2022
@zomars
Copy link
Contributor

zomars commented Jan 25, 2022

Maybe this could be solved in zod by adding a way to remove null or undefined values from a schema.

@CarterGrimmeisen
Copy link
Owner

CarterGrimmeisen commented Jan 26, 2022

I agree that this is potentially something to address and that the nullish change made things a little bit unclear.

For me, the use case was always as a form of pre-validation when creating new records. For updating you can always just take the schema and run '.partial()' on it to selectively make fields optional.

One idea I had is to make different schemas for the different Prisma operations. (i.e. one for create, update, and one for querying). I don't think this is all that difficult to do given the information we get from the Prisma DMMF but it would certainly require some work.

Let me know what you think or if you have any additional ideas.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants