Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Cover image for Create Prisma Generator
Yassin Eldeeb 🦀
Yassin Eldeeb 🦀

Posted on • Edited on

     

Create Prisma Generator

This blog is hosted onthis github repo incontent.md file so feel free to correct me when I miss up by making a PR there.

What's a prisma generator? 🤔

Prisma has a concept called "Generator". A generator is an executable program, which takes the parsed Prisma schema as an input and has full freedom to output anything.

The most prominent generator is calledprisma-client-js. It's the ORM Client powering the main TypeScript and JavaScript usage of Prisma from Node.js.

Generators will always be called when you runprisma generate. However, only the generators mentioned in theschema.prisma file are being run.

Strongly recommend reading the full article, It's pretty damn good

From a community perspective when integrating prisma in different environments you'll often notice that there's a thing that you always go to change after modifying your prisma schema in your codebase, and that's when great developers realize that this thing should be automated to eliminate the problem of maintaining two or more different sources of the same definitions.

Getting Started

Now that you've a high level overview of what a prisma generator is, let's discuss the hello world prisma generator you'll get when using create-prisma-generator CLI 💪

I made it so that it requires the least amount of effort to start developing your own prisma generator.

Answer the prompt questions to setup your project, The project setup will be based on your answers.

Note: "? setup workspace for testing the generator" means to symlink the generator with another sample usage project so that when you runprisma generate from your terminal, It uses the local generator in the workspace which is very useful when developing, I strongly recommend it.

$npx create-prisma-generator
Enter fullscreen modeExit fullscreen mode

I'll go and answer Yes for everything to go with the full capabilities of this CLI but you can follow along with your setup too.

my-answers-to-questions

And once you see the success message in your terminal saying that your project is now ready, open the project in your favourite IDE and let's have some fun 😉

First let's open theschema.prisma which you can find it atpackages/usage/prisma/schema.prisma.

You'll notice your generator there symlinked with the generator code in the workspace

Note: the provider can differ from package manager to another, here I chosepnpm

generatorcustom_generator{provider="npx my-gen"output="../types"}
Enter fullscreen modeExit fullscreen mode

You'll also see some enums there, that's because the hello world generator that you get from runningcreate-prisma-generator is for generating Typescript Enums fromschema.prisma.

Now let's run theprisma generate command which should run all of the generators listed inschema.prisma:

$cdpackages/usage$npx prisma generate
Enter fullscreen modeExit fullscreen mode

Oh, WOW! the types directory wasn't there before, what the hell happened!

You can see that thetypes directory was generated after runningprisma generate which contains all of the different enums defined inschema.prisma organized by an enum per file.

So if you opened any of the files in thetypes directory, you'll see an enum that matches exactly with the name and values as defined inschema.prisma

enumLanguage{Typescript='Typescript',Javascript='Javascript',Rust='Rust',Go='Go',Python='Python',Cpp='Cpp',}
Enter fullscreen modeExit fullscreen mode

Noticed something? the output option in thecustom_generator block inschema.prisma tells the generator where to output the generated files with a path relative to the directory whereschema.prisma is located, try to change this option to something different like../src/types and runnpx prisma generate again.

generatorcustom_generator{provider="npx my-gen"output="../src/types"}
Enter fullscreen modeExit fullscreen mode

You'll see that it created all of the directories for the defined path and outputted the generated enums there.

Now after we've played around with the Hello World generator, Let's take a look at the code for it.

You can find the generator code located underpackages/generator directory.

Openpackages/generator/src/generator.(ts|js) and let's slowly discuss what's in there.

At the top you'll see we're importing some strange modules like@prisma/generator-helper,@prisma/sdk, what are those?

@prisma/generator-helper

The generator has to be an executable binary somewhere in the filesystem. This binary, for example./my-gen needs to implement a JSON RPC interface via stdio.

When@prisma/sdk spawns our generator, It usesRPCs to communicate with our generator to send it the parsed datamodel AST as an example.

Luckily for us, prisma has wrote a helper library called@prisma/generator-helper. It takes all the work of implementing the interface and gives us simple callbacks where we can implement our business logic.

And as you can see, It has a callback calledgeneratorHandler which takes two methods:

onManifest:

When running the prisma cli with the following commandprisma generate It gets our generator manifest that gets returned from theonManifest callback method which contains all of the information about our generator like It's name, version, default output, which binaries and which version the generator needs.

generatorHandler({onManifest(){return{...}},...})
Enter fullscreen modeExit fullscreen mode

onGenerate:

This is a callback method that run when@prisma/sdk calls it with the correct arguments that contains the parsed datamodel AST, generator options and other useful information.

generatorHandler({...onGenerate:async(options:GeneratorOptions)=>{...},})
Enter fullscreen modeExit fullscreen mode

@prisma/sdk

This is an internal API that has some very cool utilities that are often used when developing prisma generators which I've documented some parts about ithere.

Back to our Hello World generator

After we've dicussed a bit about@prisma/generator-helper and@prisma/sdk, Let's get back togenerator.(ts|js)

You'll first see that we're importing the generator's package.json and grabbing the version out if it to pass it as a part of the generator manifest,

then using theGENERATOR_NAME constant which is imported frompackages/generator/constants.ts to log an info message to let us know when our generator is registred then returning an object expressing our generator manifest.

version andprettyName are used by@prisma/sdk when It callsgetGeneratorSuccessMessage to generate a success message from our generator manifest like shown below.

generator-success-message

defaultOutput is a fallback for theoutput option if It wasn't provided in the generator block.

const{version}=require('../package.json')generatorHandler({onManifest(){logger.info(`${GENERATOR_NAME}:Registered`)return{version,defaultOutput:'../generated',prettyName:GENERATOR_NAME,}},...}
Enter fullscreen modeExit fullscreen mode

Let's get to theonGenerate callback where you'll receive the generator options which you can find the latest type definitionshere, this contains a lot of information for our generator to use like pure datamodel, dmmf, generator(config, name, output, provider), schemaPath, version and hell a lot more.

DMMF?? It's the Datamodel Meta Format. It is an AST (abstract syntax tree) of the datamodel in the form of JSON.

You can see that we're specifically making use ofoptions.dmmf.datamodel.enums which contains all of the parsed enums as AST that we can then have full freedom of outputting anything with this information.

We're using a helper function that can be found inpackages/generator/src/helpers/genEnum.(ts|js) that takes the enum information and gives us back a string containing a Typescript Enum.

generatorHandler({...onGenerate:async(options:GeneratorOptions)=>{options.dmmf.datamodel.enums.forEach(async(enumInfo)=>{consttsEnum=genEnum(enumInfo)constwriteLocation=path.join(options.generator.output?.value!,`${enumInfo.name}.ts`,)awaitwriteFileSafely(writeLocation,tsEnum)})},})
Enter fullscreen modeExit fullscreen mode

Nothing crazy to make a Typescript Enum from the enum info, you can take a look at the file, It's really really simple.

exportconstgenEnum=({name,values}:DMMF.DatamodelEnum)=>{constenumValues=values.map(({name})=>`${name}="${name}"`).join(',\n')return`enum${name} { \n${enumValues}\n }`}
Enter fullscreen modeExit fullscreen mode

Another thing you'll see is a utility function calledwriteFileSafely which takes the write location for the file and the content for that file then It creates all of the directories recursivly following the write location path and uses another utility function calledformatFile to format the content using prettier before writing the file to the specified path.

exportconstwriteFileSafely=async(writeLocation:string,content:any)=>{fs.mkdirSync(path.dirname(writeLocation),{recursive:true,})fs.writeFileSync(writeLocation,awaitformatFile(content))}
Enter fullscreen modeExit fullscreen mode

And that's it, that's our Hello World generator, hope It was a fun ride.

How do I develop within this workspace?

1- Open a new terminal and cd intopackages/generator and run

# You can use whatever package manager to run the dev script$pnpm dev
Enter fullscreen modeExit fullscreen mode

This will watch your changes and compile on save into a dist folder.

2- Open another terminal and cd intopackages/usage and here you'll have the latest build of your generator's code symlinked to this package so running:

$npx prisma generate
Enter fullscreen modeExit fullscreen mode

..will always use the latest code of your compiled generator.

And as you iterate over your generator's code, you can runnpx prisma generate to see the results.

Testing 🧪

Quality Software can't be shipped directly to the users and have to be well tested before It goes live.

That's why I've included jest in any project that gets bootstrapped bycreate-prisma-generator CLI.

If you don't know what jest is? It's a JavaScript Testing Frameworklearn more about it here

There's a very simple test located underpackages/generator/__tests__/ calledgenEnum.test.ts, If you opened this file you'll see a test written that compares the generated output of the genEnum() helper function we've talked about previously with the already taken snapshot of a working version of this function.

We can run that test by running the following command inpackages/generator directory:

# You can use whatever package manager to run the test script$pnpmtest
Enter fullscreen modeExit fullscreen mode

You'll see all of the tests are passing, that means our software is ready to be shipped! 🥳

generator-success-message

You can also see that we're not getting the DMMF from@prisma/sdk, mmm... that's strange but how are we getting the DMMF from aschema.prisma and where is even thatschema.prisma file?

Usually in production the DMMF gets sent through this cycle:

@prisma/cli -> @prisma/sdk -> Spawns Generators -> Send DMMF through RPCs
Enter fullscreen modeExit fullscreen mode

Which works perfectly fine but not the ideal when testing prisma generators, we can cut this cycle and just get the utility function in @prisma/sdk that's responsible for generating the DMMF from a prisma definitions string which calledgetDMMF.

So as you can see we're callinggetSampleDMMF() from the fixtures defined in the tests directory which then reads thesample.prisma located under__tests__/__fixtures__/ and parse it to an AST exactly like the one we get normally in a production environment.

And now It's up to you to write tests for your own generator.

I'm curios to see your creative solutions for testing your prisma generator 🤗.

Fancy Stuff ✨

Now let's get fancy with the full capabilities of this CLI and manage this project like an elite open source programmer 💪.

Auto Publishing 🚀

Remember the "automate publishing the generator with Github Actions" I've said yes to it at first.

That had setup a Github Actions workflow at.github/workflows/CI.yml which will run all of our generator tests then if they're all passing It will publish the package to npm using your Access Token.

To get an access token, you must first be logged in with your npm account orregister here

Then click on your profile picture and go to "Access Tokens" like shown in the screenshot below 👇

npm-profile-dropdown

Click on "Generate New Token" and select the token type to be "Automation" so that you don't require 2FA when running in a CI environment.

npm-profile-dropdown

Before start publishing your package to npm, you'll need to replace the placeholders inpackages/generator/package.json with actual information like: description, homepage, repository, author and keywords.
Check the docs to know what all of those fields meannpm package.json docs.

Now that you've your npm access token you can create a new github repository and add a new secret to your github actions secrets with this exact same nameNPM_TOKEN.

npm-profile-dropdown

Let's make a small change to this generator like changing the name of the generator as an example.

- export const GENERATOR_NAME = 'my-gen'+ export const GENERATOR_NAME = 'my-super-gen'
Enter fullscreen modeExit fullscreen mode

Then commit & push to your repository on themain branch

$git add.$git commit-m"fix: generator name"$git push-u origin main
Enter fullscreen modeExit fullscreen mode

After you push, go to your repository on github specifically on thaActions tab and you'll immediately see the tests running and after they finish, the package will be published to npm with the version specified in the generator's package.json using your access token which you can then find using the following urlhttps://www.npmjs.com/package/$your-generator-name 🥳.

github-actions.png

Automatic Semantic Versioning 🤖

Don't know what semantic versioning is?, Mahmoud Abdelwahab got you covered with a 1 minute video about itcheck it out

Now we've a workflow for testing and automatic publishing the package to npm but It's not very nice having to go and manually bump the version in thepackage.json everytime you change something and wanna publish it.

Usingsemantic-release, we can just focus on our commit messages and It'll do the rest of the work for us like: bumping the version, github release, git tag, generating a CHANGELOG and a lot more.

Remember the "(Github Actions) setup automatic semantic release" I've said yes to it at first.

That had setup semantic-release for me with the Github Actions workflow and added husky with commitlint to forceConventional Commit Messages which then semantic-release will recognize and decide the next version based on it and do all of the stuff for us.

But there's a very small configuration we still need to make for this to work as intended.

Remember when I said:

bumping the version, github release, git tag, generating a CHANGELOG and a lot more.

Well, semantic-release needs read/write access over public/private repos to achieve all of that.

Create a new github access tokenfrom this link providing a note for it so you can remember what it was for.

Now that you've your github access token you can add a new secret to your github actions secrets with this exact same name GH_TOKEN which semantic-release will look for to do all of the magic for us.

Let's make a smalll change to this generator like changing the name of the generator as an example and call it a minor release.

  generatorHandler({  onManifest() {-   logger.info(`${GENERATOR_NAME}:Registered`)+   logger.info(`${GENERATOR_NAME}:Hooked`)
Enter fullscreen modeExit fullscreen mode

Then commit & push to your repository on themain branch

$git add.$git commit-m"new register message"$git push-u origin main
Enter fullscreen modeExit fullscreen mode

Oh crab what the hell is this?
husky-with-commitlint

Remember when I told you that this CLI has setup husky with commitlint to validate your commit messages if it was conventional or not before commiting so that semantic-release can decide what the next version is based on your commit messages.

Now let's run a proper conventional commit message

$git add.$git commit-m"feat: new register message"$git push-u origin main
Enter fullscreen modeExit fullscreen mode

After you push, go to your repository on github specifically on tha Actions tab and you'll see the same running tests and after they finish, you'll notice something different, semantic-release has bumped the version to1.1.0 and modified the package.json version to sync it with npm, generated a CHANGELOG for you, created a new tag and published a github release for you 🤯

github-actions.png

semantic-release-generated-changelog

WOW! I had a 0.01% chance that someone can read through all of that till the very end. I'm very proud of you, please mention or DM me on twitter and let me know you're one of the 0.01% of people.

Top comments(2)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss
CollapseExpand
 
sabinthedev profile image
Sabin Adams
Senior Full Stack Developer. I've been a full-stack web developer for about 9 years!My passion is making fun things and sharing how I did it!Prisma is my jam.
  • Location
    Visalia, California
  • Work
    Senior Full Stack Developer
  • Joined

Wow this is great!! Definitely gunna be playing with this. Thanks 🔥🔥

CollapseExpand
 
yassineldeeb profile image
Yassin Eldeeb 🦀
...
  • Location
    Portugal
  • Joined

I'm curious to see your creative creations using this knowledge 🔥

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

...
  • Location
    Portugal
  • Joined

More fromYassin Eldeeb 🦀

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp