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

create prisma utils plugin #348

Merged
merged 8 commits into from
Aug 20, 2022
Merged

create prisma utils plugin #348

merged 8 commits into from
Aug 20, 2022

Conversation

hayes
Copy link
Owner

@hayes hayes commented Mar 19, 2022

No description provided.

@vercel
Copy link

vercel bot commented Mar 19, 2022

This pull request is being automatically deployed with Vercel (learn more).
To see the status of your deployment, click below or on the icon next to each commit.

🔍 Inspect: https://vercel.com/mhayes/pothos/HesXVKxajV2RqBTEcNseHPAWZQfF
✅ Preview: https://pothos-git-mh-prisma-crud-spike-mhayes.vercel.app

@changeset-bot
Copy link

changeset-bot bot commented Mar 19, 2022

⚠️ No Changeset found

Latest commit: ad16d7c

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@fivethreeo
Copy link

Any progress on this? If you need ideas on crud look at the typegraphql-prisma module which uses ts decorators. Pothos is much nicer imo, since decorators in ts is terrible.

@hayes
Copy link
Owner Author

hayes commented May 2, 2022

Not yet. I've been working on the new tracing plugin, and haven't had a lot of extra time to build this out. The API for this is pretty complex, and something I specifically want to do differently than some of existing options for building crud schemas using Prisma. Its still something I want to build, but it will take some time before I have anything that is usable.

@vercel
Copy link

vercel bot commented May 10, 2022

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Updated
pothos ✅ Ready (Inspect) Visit Preview Aug 20, 2022 at 6:44AM (UTC)

@omarragi
Copy link

Great talk in Prisma day
I hope you elevate the priority of the crud plugin please

@Cauen
Copy link
Contributor

Cauen commented Jul 4, 2022

@hayes

I think that Pothos + Prisma + Crud will be the next better way to build Graphql APIs.

Recently (27 Jan 2021), Nexus integration with prisma was suddenly discontinued due to an "better alternative" that never cames.
Inside discussions (graphql-nexus/nexus-plugin-prisma#1039), Pothos is heavly recommended.
I tried and, IMO, that library is a upgrade of Nexus (in terms of developer experience, becase of the Type Infer and no runtime generation).
But, differently from nexus-plugin-prisma, the there is no way to fastly create complex operations from API to Prisma, like where, create .

I read your comments inside #428 and...
I think this pull request, is all that we "old nexus adopters" need.


Said that, all we need is: generated Input Objects

With this, we will reach this to Pothos Prisma Plugin:

  • Easy way to fetch relations from prisma (plugin already has). ✅
    • ie: get all posts from a user, inside findFirstUser resolver
  • Easy way to filter relations ❌
    • ie: get last 10 posts from a user, inside findFirstUser resolver
  • Easy way to create crud resolvers ❌
    • ie: complex mutations like: create users with posts inside.
    • ie: complex queries like: get all posts with status "DRAFT"

By only adding this input type as args of the t.relation and t.prismaField.


A second goal to this, is a plugin to auto-generate crud based on the Prisma Schema (like you commented too at #428)

I think a good API is:

I'm assuming that the main objective would be to generate everything, and give the possibility to customize

// Models be like: User, Post, Like, Follow, ...
// Generating crud in 3 scenarios

// 1. ALL: Generating all crud for all Models
// src/schema/index.ts
builder.prismaCrud()

// 2. SPECIFIC: Generating only models, queries or mutations
// src/schema/index.ts
builder.queryFields((t) => t.prismaCrud() // findCount, findFirst, findMany, findUnique, aggregate for all models
builder.mutationFields((t) => t.prismaCrud() // createOne, createMany, deleteMany, deleteOne, updateOne, updateMany, upsertOne for all models
builder.prismaCrudObjects() // create all objects, with all fields
// Auto generate for specific Models
builder.queryFields((t) => t.prismaCrud({ include: ["Post"] }) // operations for User only.
builder.mutationFields((t) => t.prismaCrud({ exclude: ["User"] }) // operations for all but User.
builder.prismaCrudObjects({ exclude: ["User"] }) // create all objects but User, with all fields

// 3. CUSTOMIZE EVERYTING
// src/schema/user/objects.ts
builder.prismaCrudObject('User', {
  fields: (t) => {
    const { password, ...publicFields } = t.prismaCrudFields;
    console.log(`This ${password} is never sent to user`)

    return {
      ...publicFields,
      // custom field
      openedWelcomeEmail: t.boolean({
        resolve: (user) => mailService.openedWelcomeEmail(user.email),
      }),
    }
  },
});
// src/schema/user/queries.ts
builder.queryFields((t) => ({
  findManyUser: t.prismaCrudField(), // default find one (with all options)
  findOneUser: t.prismaCrudField(t => ({
    resolve: (query, root, args, ctx) =>
      db.post.findUnique({
        ...query,
        where: { id: ctx.user.id },
      }),
  })),
}))
// src/schema/user/mutations.ts
builder.mutationFields((t) => ({
  createManyUser: t.prismaCrudField(), // default find one (with all options)
  createOneUser: t.prismaCrudField(t => ({
    args: {
      ...t.prismaCrudArgs,
      sendEmail: t.arg({ type: "Boolean", defaultValue: true }), // custom args for crud
    },
    resolve: (query, root, args, ctx) => {
      if (args.sendEmail) mailService.sendWelcomeEmail(args.data);
      return db.post.findUnique({
        ...query,
        where: { id: ctx.user.id },
      })
    },
  })),
}))

Questions:

  1. Is this implementable for Pothos?
  2. Can you imagine something like this being done by you?

If possible I would like to help with development, I feel that this functionality would fill a very big hole in the Graphql/Prisma ecosystem.

@hayes
Copy link
Owner Author

hayes commented Jul 7, 2022

I know this is a popular feature that a lot of people are excited about, just want to temper expectations a bit, I haven't had a lot of time to make significant progress on this yet.

I also want to be clear that this will NOT be an attempt to replicate the same behavior that the old nexus prisma plugin provided.

I have discussed this in a few places, but automatically generating crud operations form your database is not in line with the core principles behind Pothos, and is a non-goal for the change. The goal here will be to simplify and streamline the processes of building curd APIs by providing building blocks, which eventually could be put together to generate crud APIs, but will require some helpers defined at the application level to reach parity with what you might have been able to do in the nexus plugin.

Things I want this plugin to help with:

  • Defining input filters for various scalar and enum types
  • Defining inputs for sorting
  • Defining input types that can be passed directly to prisma for creates and updates
  • Include recipes for doing more automatic generator by just passing in a type name.

Things that this plugin won't do

  • Create create/update inputs without explicitly defining the fields/types included in those types
  • Defining crud operations (despite the title of this PR). The actual queries/mutations will still need to be manually defined

I should probably document more about why these limitations are important, but I think the end result should result in a better experience for most use cases. The simplest apps will have slightly more overhead in the inital set up, but this should be relatively easy to overcome.

@hayes
Copy link
Owner Author

hayes commented Jul 7, 2022

@Cauen let me address some of your comments more directly, there is a lot there to unpack:

But, differently from nexus-plugin-prisma, the there is no way to fastly create complex operations from API to Prisma, like where, create .

This is definitely the thing that's people are asking for. I see this as a asking for faster horses instead of cars situation. There are some good reasons that prisma tried to go in a new direction with their second version of a nexus plugin.

Said that, all we need is: generated Input Objects

This is likely what the initial version of this plugin will contain

Easy way to fetch relations from prisma (plugin already has). ✅
ie: get all posts from a user, inside findFirstUser resolver
Easy way to filter relations ❌
ie: get last 10 posts from a user, inside findFirstUser resolver
Easy way to create crud resolvers ❌
ie: complex mutations like: create users with posts inside.
ie: complex queries like: get all posts with status "DRAFT"

Easy way to filter relations

The basics for this already exists:

t.relation('posts', {
  args: {
   first: t.int()
  },
  query: (args) => ({ limit: args.first ?? 10 })
})

The above mentioned input type helpers will help create types to use in args that can automatically be passed into the query part of a relation, or used in a prisma field, which will help with more complex filters (includes, negations, startsWith, etc).

Easy way to create crud resolvers

This is a little more complicated. There are a LOT of options when it comes to relations when crating/deleting/mutation a record. Automatically supporting connecting/creating/cascading deletes on every operation is a bad idea. Schemas should be constrained to the operations you actually want to allow, and I think a lot of apps are going to want to make different choices on how they want to support things like nested creates or connecting to existing entities.

This can't be generated in a way that I think is good, so the solution here will be improved APIs for manually defining input types for creates/updates related to specific prisma types, and examples of how to create your own utilities that use those methods to generate things for you.

I'm assuming that the main objective would be to generate everything, and give the possibility to customize

This is very specifically NOT a goal

I think the code examples are probably fairly far off from the API I am imagining, but an example that shows how to build a similar API yourself using the provided helpers would probably be reasonable, and hopefully not too complex.

Questions:

Is this implementable for Pothos?
Can you imagine something like this being done by you?

I hope the above comments have some indication on what I think makes sense in pothos vs what belongs somewhere else.
Yes, this is something that I plan on working on, but it is VERY complicated, and will take time to get right. It will require some longer stretches of uninterrupted time than the couple of hours every few days I am spending now. So I expect this will be a while before I have this ready to use.

If possible I would like to help with development, I feel that this functionality would fill a very big hole in the Graphql/Prisma ecosystem.

Yes, I would absolutely be open to help working on this. This is a very important API to get right, and will need to be highly composable, and will probably take a lot if iteration. My general approach to development for pothos is to build out the entire API in the type-system first without any implementation, because that tends to be that hardest piece.

If you are serious about wanting to work on this, I'd be happy to help you get familiar how to get set up working in the repo and answer questions. I am usually pretty active in the Pothos discord channel, so that might be a good place for a more real time conversation about this.

@Cauen
Copy link
Contributor

Cauen commented Jul 9, 2022

Hi @hayes, thanks for providing better specs for your expectations for the plugin.

Automatically supporting connecting/creating/cascading deletes on every operation is a bad idea.
I should probably document more about why these limitations are important

I'm quite interested in hearing your thoughts on this.

In my experience, keeping a "super power" resolver with restrictions based on role/access is the most productive way to work (Especially if fullstack).

This was why I liked the Nexus integration so much. I believe that was not the reason for the "new direction with their second version" but because according to them: "accumulated a lot of technical debt".
The "crud" functionality was on the radar in the roadmap of the second version.


If you are serious about wanting to work on this, I'd be happy to help you get familiar how to get set up working in the repo and answer questions.

Really thanks for your willingness to help.
Spend a few hours studying some Pothos plugins code, and I believe it's currently a little beyond my capabilities.

With that in mind, I put into practice the development of a Prisma Generator codegen for Pothos.

The roadmap:

  • Generator code for all input fields.
  • Generator for all Objects, Queries and Mutations. Something like prisma-tools do with Prisma and Nexus.

The first version was published at prisma-generator-pothos-codegen - npm

It's my first open source project, so any contribution is more than welcome.

---- Edit 2022-07-13 ----

Published version 0.2.0 with generator for all Objects, Queries and Mutations.
With a lot of new options:

Click to see configs options
{
  inputs?: {
    prismaImporter?: string // default: import { Prisma } from ".prisma/client"
    builderImporter?: string // default: import { builder } from "./builder"
    excludeInputs?: string[] // default: undefined
    excludeScalars?: string[] // default: undefined
    outputFilePath?: string // path to generate file, from project root
    replacer?: (generated: string, position: ReplacerPosition) => string // a function to replace generated source
  },
  crud?: {
    disabled?: boolean // disable generaton of crud. default: false
    includeResolversExact?: string[] // generate only resolvers with name in the list. default: undefined. ie: ['createOneUser']
    includeResolversContain?: string[] // generate only resolvers with name included in the list. default: undefined. ie: ['User'].
    excludeResolversExact?: string[] // default: undefined. ie: ['createOneComment']
    excludeResolversContain?: string[] // default: undefined. ie: ['createOne']
    resolversImports?: string // default: what to import inside resolver
    dbCaller?: string // how to call prisma. default: context.db
    inputsImporter?: string // default: import * as Inputs from "@/generated/inputs";
    builderImporter?: string // default: import { builder } from "./builder"
    outputFolderPath?: string // path to generate files, from project root. default: ./generated
    replacer?: (generated: string, position: ReplacerPosition) => string // a function to replace generated source
  },
  global?: {
    replacer?: (generated: string, position: ReplacerPosition) => string // a function to replace generated source
  }
}

See example: click here

@hayes
Copy link
Owner Author

hayes commented Jul 9, 2022

@Cauen that's great! I think that's exactly the right direction. There will be a lot of people who want something like what you are describing. What I want to build are building blocks to make that generated code simpler.

In my opinion, there are too many choices and tradeoffs to make to design something that solves everyones use case, which is why I am starting with the building blocks that enable some of these higher level APIs.

@izziaraffaele
Copy link

The roadmap:

  • Generator code for all input fields.
  • Generator for all Objects, Queries and Mutations. Something like prisma-tools do with Prisma and Nexus.

@Cauen Have you considered to build a pal.js generator class for Pothos before starting prisma-generator-pothos-codegen?

@Cauen
Copy link
Contributor

Cauen commented Aug 5, 2022

@Cauen Have you considered to build a pal.js generator class for Pothos before starting prisma-generator-pothos-codegen?

@izziaraffaele No, i wanted to build my own specific alternative. That's because I saw that his code revolves around the Nexus.

@litewarp
Copy link

Hi all. Just wanted to chime in and say thanks for all the work and to offer a suggestion that may make a path forward easier--

Would it make sense to either (i) move the filter / orderBy generator to its own package (@pothos/plugin-prisma-inputs?) or (ii) move them into the prisma-plug-in itself?

At the very least I feel like there's a disconnect between the label "crud" and what I'm particularly looking for -- just creation of Filter and OrderBy args.

Crud implies more of what I think you're trying to avoid -- generating resolvers (cf., graphql-compose-mongoose)

@hayes
Copy link
Owner Author

hayes commented Aug 18, 2022

Yes, it will be renamed before release. I created the initial branch and empty plugin without thinking through it much. It will be its own package (at least for early versions) so it can be versined independently.

@hayes hayes changed the title wip - create prisma-crud plugin package create prisma utils plugin Aug 19, 2022
@hayes hayes marked this pull request as ready for review August 20, 2022 06:42
@hayes hayes merged commit dc5704a into main Aug 20, 2022
@hayes hayes deleted the mh--prisma-crud-spike branch August 20, 2022 07:05
@hayes
Copy link
Owner Author

hayes commented Aug 20, 2022

I know several of you have been waiting for movement on this PR for a long time. The scope of what I want to do with this had become a bit of a blocker for getting started, and I have been focusing on smaller iterative changes in other parts of Pothos. Instead of trying to tackle implementing full crud support in a single PR, I built out some of the first building blocks needed. This is still an early iteration, but I'm hoping that what I have so far can serve as a way to prove out the concept. I've merged this PR and published an initial version that includes a few features:

  • Defining input objects for ordering
  • Defining filter types for scalars, enums and lists with relevant prisma filters
  • Defining filters for prisma objects, with support for filtering by scalar fields, and using nested filters for relations.

These utilities generate input types that are automatically normalized so they can be passed to prisma without having to coerce nulls to undefined (the args themselves currently can still be null, but anything nested in the input object will be a real value or undefined).

Definitions for these types are still completely explicit. For each filter type, you provide the list of filter operations. Same thing applies to order by and where objects, the set of fields and relations you can order or filter by is explicitly defined for each object. That means this isnt the auto-generation that some people have been asking for, but this makes writing tools to automatically generate these filters a lot easier

Last thing to note is filters for scalars are based on your graphql types not prisma types. The basic idea is that there are frequently more db types than there are graphql types, and your user doesn't care how something is stored in your db. All input types are built based on typescript compatibility. This also means that different db variants with different primatives can all be supported automatically, as long as you can define a graphql scalar that has a compatible typescript type.

Docs still need to be written, and I'll open a discussion or something for feedback soon.

@hayes
Copy link
Owner Author

hayes commented Aug 20, 2022

I've created a discussion thread here as a place to collect feedback. If this feature is something you would like to use, I would really appreciate you checking this out, and giving any feedback there. I also added some (very basic) docs to the repo (linked in the discussion thread).

#544

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

Successfully merging this pull request may close these issues.

6 participants