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

[Feature Request] Update @prisma/client to use the generated types #223

Open
Stadly opened this issue Dec 16, 2023 · 3 comments
Open

[Feature Request] Update @prisma/client to use the generated types #223

Stadly opened this issue Dec 16, 2023 · 3 comments

Comments

@Stadly
Copy link

Stadly commented Dec 16, 2023

Have you considered updating @prisma/client with the generated types? Then we would automatically get correctly typed models out of the database and get type safety when inserting in the database. We would not get automatic checks that inserted objects adhere to the generated Zod schema, but we would get automatic checks that they adhere to the TypeScript type corresponding to the Zod schema.

For the normal validators, I don't think it would make any difference, but for custom validators I think it would be really useful.

Example

Consider the following schema.prisma, schema generated by zod-prisma-types, and generated @prisma/client:

// schema.prisma
model App {
  id       String @id @default(cuid())
  i18nName Json   /// @zod.custom.use(z.record(z.string(), z.string()))
  type     String /// @zod.custom.use(z.union([z.literal("foo"), z.literal("bar")]))
  details  Json?  /// @zod.custom.use(z.object({description: z.string().optional(), names: z.string().array()}))
}
// prisma/generated/zod/index.ts
export const AppSchema = z.object({
  id: z.string().cuid(),
  i18nName: z.record(z.string(), z.string()),
  type: z.union([z.literal("foo"), z.literal("bar")]),
  details: z.object({description: z.string().optional(), names: z.string().array()}).nullable(),
})

export type App = z.infer<typeof AppSchema>
// node_modules/.prisma/client/index.d.ts
export namespace Prisma {
  export type $AppPayload<ExtArgs extends $Extensions.InternalArgs = $Extensions.DefaultArgs> = {
    name: "App"
    objects: {}
    scalars: $Extensions.GetPayloadResult<{
      id: string
      /**
       * @zod.custom.use(z.record(z.string(), z.string()))
       */
      i18nName: Prisma.JsonValue
      /**
       * @zod.custom.use(z.union([z.literal("foo"), z.literal("bar")]))
       */
      type: string
      /**
       * @zod.custom.use(z.object({description: z.string().optional(), names: z.string().array()}))
       */
      details: Prisma.JsonValue | null
    }, ExtArgs["result"]["app"]>
    composites: {}
  }
}

I envision that node_modules/.prisma/client/index.d.ts was changed to something like this:

// node_modules/.prisma/client/index.d.ts
import { App } from "../../../prisma/generated/zod"
export namespace Prisma {
  export type $AppPayload<ExtArgs extends $Extensions.InternalArgs = $Extensions.DefaultArgs> = {
    name: "App"
    objects: {}
    scalars: $Extensions.GetPayloadResult<{
      id: App["id"]
      /**
       * @zod.custom.use(z.record(z.string(), z.string()))
       */
      i18nName: App["i18nName"]
      /**
       * @zod.custom.use(z.union([z.literal("foo"), z.literal("bar")]))
       */
      type: App["type"]
      /**
       * @zod.custom.use(z.object({description: z.string().optional(), names: z.string().array()}))
       */
      details: App["details"]
    }, ExtArgs["result"]["app"]>
    composites: {}
  }
}

This would give us type-safety out of the box:

// TypeScript checks that `newApp` is of type `App` (from `prisma/generated/zod/index.ts`)
await prisma.app.create({data: newApp})

// `myApp` is of type `App` (from `prisma/generated/zod/index.ts`)
const myApp = await prisma.app.findUnique({where: {id: myUuid}})

Prisma Json Types Generator uses this approach for typing JSON fields, so that might be a good source for implementation inspiration.

@coindegen
Copy link

coindegen commented Dec 22, 2023

I agree this would be useful.

The discrepancy between the Prisma types and the zod-prisma-types is proving to be annoying. I am also using custom validators, specifically to handle date serialization like this:

model MintData {
  id                  String      @id @default(cuid())
  ...
  date_last_sale      DateTime? ///@zod.custom.use(z.coerce.string().nullish())

  created_at DateTime @default(now()) ///@zod.custom.use(z.coerce.string())
  updated_at DateTime @updatedAt ///@zod.custom.use(z.coerce.string())
}

This raises all kinds of "cannot assign Date to string" errors everywhere I run prisma queries, because Prisma is still expecting Date objects.

As a result, I end up having to override the inferred types everywhere by using as unknown as ActualType:

import {prisma} from "#app/utils/db.server.ts"
import type { Collection, MintData } from "#types/generated/index.ts";

export type IFetchedCollection = Collection & { mint_data: MintData };

export async function fetchCollection(address: string) {
  const collection = (await prisma.collection.findFirst({
    where: { address: address },
    include: { mint_data: true },
  })) as unknown as IFetchedCollection;

  if (collectionBySlug) {
    return collectionBySlug;
  }

  throw new Error("Collection not found");
}

I also have a question in this regard. Do I have to create this custom include intersection type (IFetchedCollection), or is that already exported somewhere in my generated types?

I know zod-prisma-types exports my "Collection" type like this:

export type Collection = z.infer<typeof CollectionSchema>

However, is there also a "Collection" type with all possible "joins"? In other words, is there a type consisting of my model intersecting all its potential includes?

@chrishoermann
Copy link
Owner

@Stadly thanks for the suggestion, this seems to be a nice extension of the client. I need to look into it and see how much time this would take to implement and how complex it would get.

@coindegen you can try to create additional model types with the options createOptionalDefaultValuesTypes, createRelationValuesTypes, createPartialTypes and see if these generated schemas/types will suit your needs.

@coindegen
Copy link

@coindegen you can try to create additional model types with the options createOptionalDefaultValuesTypes, createRelationValuesTypes, createPartialTypes and see if these generated schemas/types will suit your needs.

that's really helpful, thanks. Just what I needed, there's so many options it's hard to keep track :)

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