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

More granual control over what gets generated, excludeable fields and multiple exported schemas #49

Open
callum-gander opened this issue Nov 30, 2022 · 2 comments
Labels
enhancement New feature or request

Comments

@callum-gander
Copy link

Problem

This is a three part feature request

This is a great package and I really want it to succeed but there are some really irritating issues that need features to resolve them.

1: More granular control over what gets generated

I posted this issue on your prisma tRPC generator but it seems more relevant here and I'm going to add a bit more detail here on why it's needed and a more detailed solution. A major feature that's lacking from this package is the lack of granularity with what gets generated. For example, our app has about 8 different routes. Only 4 need full CRUD functionality, the rest are mainly for reading, and creation, deletion and update are manually handled by employees using prisma studio. The number of schemas and objects generated by this generator is ludicrous, most simply won't be needed for most use cases. This also makes integrating the codegen process into the development process slower, as you need to manually remove the unnecessary files or manually select all of the useful while leaving the bloat, both of which require with developer time or code to maintain. You should just be able to get what you actually need to use in as close to the way you need to use it as possible. Having more granular control is important to increase the adoption of this package as it will mean people can use it more flexible and generate only what they need for their specific use case

2: Excludeable fields

On this note, some fields are not relevant to the Prisma client and don't need to be type checked. For example, we may have an optional field on the backend for administrative purposes that is manually inputted and checked by our employees and is never going to be submitted by Prisma client, so it just gets in the way being there. This isn't that much of an issue for the prisma client but it is for using tRPC with Zod, and this is one of the biggest issues. The generated schema becomes practically useless as it can't be used to automatically validate incoming requests to tRPC because it contains fields that are never going to be submitted by the client. You have to either then manually edit and cut fields from the schema, which detracts from the usefulness of the codegen in the first place, or add a bunch of omits to the object in the tRPC procedure input, which again isn't ideal, especially if the model has a relation and hence is a z.ZodSchema not a z.object so you can't use omit.

Having control over what fields get generated is crucial for allowing the generation of useful Zod schema. Having complete Prisma schema for every Prisma model and client action is relatively useless if you can't then use those to validate the input your client will actually be sending to your tRPC endpoint. Just one more example here as to why it's necessary, for example, our user may send information like their name, email, address, etc. but will not send things like their user type, which is conditionally added at the API level. However, the generated schema will just fail the request as not being valid because it doesn't contain the user type, which can't be conditional as it is needed in later steps. This leads nicely onto my next point

3: Multiple exported schemas per Model

Given that the creator of this package also manages various tRPC, Zod and Prisma related packages, it seems worthwhile to point out that the main schemas generated by this package won't be that useful for most client's inputs coming into tRPC. Not everyone will be sending their data exactly as it will be stored in the database in their request from the client, so as I stated above, these schemas are not hugely useful or need heavy editing. One potential remedy for this is adding the option to generate two schemas for specific Prisma models, one for Prisma client validation and one for tRPC input validation. I'll explain more below

Suggested solution

Adding a config file

I'm not particularly well versed on the Prisma schema syntax and how it interacts with generators. In an ideal world something like the below would be great

{
      modelName: {
             createOne: true,
             createMany: false,
             findUnique: true,
             ....
      }
}

However, just at a glance I doubt prisma's schema will allow json objects. I'm not sure if you can even do nesting,
something like a YAML like syntax could work if that's possible like

modelName:
      createdOne: true
      createMany: false
      findUnique: true

But, these likely both won't work. Perhaps, one way around this might be to have a separate config file that that is assigned in the prisma.schema and passed to the actual generator file

generator zod {
  provider          = "prisma-zod-generator"
  output            = "./generated-zod-schemas"
  config             = "./zod-generator.json" // zod generator config path goes here
  isGenerateSelect  = true
  isGenerateInclude = true
}

I think this is likely the root of solving all three issues. You pass in your input as a json object, or maybe as yaml, and this is used to conditional transform your code into the correct output. For example,

{
    "models": {
        "User": {
            "generate": true, // potentially redundant
            "prisma": {
                "removeFields": ["createdAt"] // those included are removed
                "includeMethods": ["createdOne"] // only those included are generated
            },
            "trpc": {
                "removeFields": ["createdAt", "userType"] // those included are removed
            }
        }
        "Account": {
            "generate": false // prevents schema model from being generated
        }
        ...moreModels
    },
    ...additionalOptions
}

This is by no means perfect and there's a lot of room to improve the syntax. One potential compliant is that this separates the logic from the prisma.schema, which personally I prefer, as after using zod-prisma, all the comments clog up the file pretty fast. This obviously doesn't include anything about how this would actually be done by the generator, which I imagine is the actual hard part but hopefully this is a helpful start

Additional context

If you can provide some guidance, I'd be happy to assist with some of this

@omar-dulaimi omar-dulaimi added the enhancement New feature or request label Dec 2, 2022
@omar-dulaimi
Copy link
Owner

Hey @callum-gander
Thank you for the detailed featured request(here and on the other project). I do agree that this package tends to over-generate; as there's currently no way built to effectively control the outputs(comments can only hide entire models).

I like your suggested solution, and it does seem like a good start. But I must ask you about the resulting files in the end. Since you want to generate two copies of each schema, this means sometimes other schemas will get duplicated as well; most important ones to mention are those that reside inside the objects folder. Also, this would mean solwer generation times; especially for bigger projects. I understand this will be behind an option flag. Maybe there's a better way to do this aside from generating twice.

It could help to see the shared schemas count vs the duplicated schemas count after the includes the removed arrays above.

How will you use the schemas in the end?
Since you might start to conditionally specify which schema to use in your projects.

By the way, this feature will be next since I finished working on the other ones.

@callum-gander
Copy link
Author

Thanks for giving this feature request attention!

After some more thought and some more experiments in general with using this generator, Zod and tRPC, I think I might have some better ideas about the generating twice as many schemas issue. First, replying to your question as to how I'd use the schemas in the end, I generally use one schema for validating the input from the client to the tRPC endpoint and another schema for validating the input from the server to the database. For my specific usecase, the user will give us a bunch of data and our endpoint procedure will then use this to generate some additional properties, check what type of user is sending the request, etc. and then remove some of those properties and add other ones.

I think I'd partially got a solution. I think just generating duplicate schema's doesn't seem like the right way to go, even with an option flag, and that potentially a better way would be to simply provide options via the config and potentially additional files to tell the generator how to make a modified model using .omit and .extend on the regular generated model to create one suitable for the client. This would be relatively easy (I think) when we're just omitting fields from the model. See the below example

export const OrderModel = z.object({
  id: z.string(),
  orderId: z.number().int(),
  orderReference: z.string(),
  type: z.string(),
  ...
})

// Generate this object from the "removeFields" section of the object providered in the initial feature request
const omitFromtRPCOrderModel = {
  type: true,
  ...
}

export const tRPCOrderModel = OrderModel.omit(omitFromtRPCOrderModel)

What makes it difficult is if users want additional properties that aren't going to be actually on the Prisma model but do still need to be validated. You could obviously just not provide this functionality but I think a solution could be to allow the user for any given Model to point to a file that'll contain a single additional Zod object that is exported and passed into .extend(). This would allow users to arbitrarily add additional Zod validation for data that's passed from the client to the server but won't be present when the data is passed to the database. So an example of this might look like this for the actual file, maybe we could store it under a specific folder? Like "../prisma/zod/extras" or something, not sure about the naming

export const additionaltRPCOrderModelFields = z.object({
    modelType: z.string(),
    timeUserSubmitted: z.string().datetime()
})

While the actual config would look something like this

{
    "models": {
        "User": {
            "generate": true, // potentially redundant
            "prisma": {
                "removeFields": ["createdAt"] // those included are removed
                "includeMethods": ["createdOne"] // only those included are generated
            },
            "trpc": {
                "removeFields": ["createdAt", "userType"] // those included are removed
                // !!!!!
                "addFields": "/file-path"
            }
        }
        "Account": {
            "generate": false // prevents schema model from being generated
        }
        ...moreModels
    },
    ...additionalOptions
}

I'm not 100% on this, so it'd be interesting to get your thoughts. As a sidenote to this, one issue that I seem to get when using omit and extend is that intellisense doesn't also accurately infer my models, e.g. it'll show the model as just being the fields that are extended or omitted rather than the full model with all the extended + omitted fields. Hope this helps a bit

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

No branches or pull requests

2 participants