Skip to main content

Tegral PrismaKT

caution

Tegral PrismaKT is experimental and not ready for production use. Please follow this issue for more information.

PrismaKT is a tool that allows you to generate JetBrains Exposed boilerplate code that matches your Prisma schema.

It can:

  • ✅ Generate tables (SQL DSL API) from a Prisma schema
  • ✅ Generate entities (DAO API) from a Prisma schema
  • ✅ Generate accurate columns for most @db.Xyz fields
  • ✅ Manually parse Schema files using Tegral Niwen

It currently does not/cannot:

  • * Generate relations
  • * Generate multi-column @@id fields.
  • * Generate JSON fields
  • * Automatically generate Database.connect calls
  • * Have built-in integration with Tegral DI or Tegral Web

* These features are planned but are not currently available due to PrismaKT's experimental status. Follow this issue for details.

If you find more limitations (or other things you want to see), feel free to open an issue!

Setting up: with JBang

This is the easiest and fastest way of running Tegral PrismaKT, because it does not require changing many things in your Gradle configuration and does not require ping-ponging between Prisma and Gradle.

However, this is not the best way of using Tegral PrismaKT in a real appliaction, as using proper Gradle integrations will help make the development process less painful (i.e. re-running prisma generate when needing, ensuring models are generated before compiling, etc.).

Add the following to your build.gradle file:

// Use the generated Kotlin files when compiling
sourceSets {
main {
kotlin {
// Change this to wherever you'll put the generated files
srcDir layout.buildDirectory.dir("prismaGeneratedSrc")
}
}
}

And the following to your schema.prisma file:

generator prismakt {
provider = "jbang --quiet guru.zoroark.tegral:tegral-prismakt-generator:0.0.4-SNAPSHOT"
output = "../build/prismaGeneratedSrc" // or wherever you want
exposedTarget = "sql"
}

Setting up: with Gradle

caution

You'll need a bit of experience with Gradle to successfully set this up. We're working on making things easier for you, but don't hesitate to open an issue if you need help!

This part will focus on creating a two-way integration between Gradle and Prisma:

In a nutshell, we'll use Gradle to manage PrismaKT, and give Prisma a command to call said PrismaKT.

Gradle setup

Assuming the following project structure:

my-project
+-- app
| +-- prisma/
| | +-- schema.prisma
| +-- src/...
| +-- build.gradle
+-- settings.gradle

In your app/build.gradle file, add the following:

plugins {
// Plugin for integration of npx/npm/etc in Gradle
id "com.github.node-gradle.node" version "4.0.0"
}

configurations {
// A configuration that we'll use to depend on
generator {
// This is a configuration intended to be resolved (i.e. retrieve dependencies) and not consumed by others.
canBeConsumed false
canBeResolved true
// We want to get the full JAR, so we need to configure the attributes for this configuration to do so
attributes {
attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling, Bundling.SHADOWED))
}
}
}

dependencies {
// Put your Tegral PrismaKT dependency here, with Tegral Catalog
generator tegralLibs.prismakt.generator
// or without Tegral Catalog (replace VERSION with the version you want)
generator "guru.zoroark.tegral:tegral-prismakt-generator:VERSION"
}

// Set up for letting Gradle call 'prisma generate' and take its results into account
sourceSets {
main {
kotlin {
srcDir layout.buildDirectory.dir("prismaGeneratedSrc")
}
}
}

// Ensure `prisma generate` is called before compiling any Kotlin code that may rely on the generated code
tasks.compileKotlin.dependsOn prismaGenerate
tasks.compileTestKotlin.dependsOn prismaGenerate

tasks.register('prismaGenerate', NpxTask) {
command = "prisma"
args = ["generate"]

inputs.file("prisma/schema.prisma")
outputs.dir(project.layout.buildDirectory.dir("prismakt-generator"))

dependsOn configurations.generator

// Created via providers.provider for lazy evaluation, otherwise this would
// retrieve information from configurations before their resolution
environment = providers.provider {
def command = "java -jar " + configurations.generator.find { it.name.endsWith(".jar") }
return ["PRISMAKT_CMD": command]
}
}

Prisma setup

In your prisma/schema.prisma file, add (or replace the current generator with) the PrismaKT generator:

generator prismakt {
provider = env("PRISMAKT_CMD")
output = "../build/prismaGeneratedSrc"
exposedTarget = "sql"
}

If you want Tegral to generate DAO bindings, use exposedTarget = "dao", otherwise use exposedTarget = "sql"

Testing things out

You should now be able to run PrismaKT, either by running prisma generate (when using JBang) or gradle prismaGenerate (when using Gradle).

If everything goes well, you should see a prismaGeneratedSrc folder appear in your build folder, containing a prismakt.generated package with your models.

Using the code

Exposed dependencies

Note that, to use the generated code, you will need the corresponding Exposed dependencies, i.e.:

  • In all cases, org.jetbrains.exposed:exposed-core and org.jetbrains.exposed:exposed-jdbc
  • If you enabled DAO generation, org.jetbrains.exposed:exposed-dao
  • If you use DateTime fields, org.jetbrains.exposed:exposed-java-time
  • The JDBC driver for your database (see here)

Generation example

The code generated should be available in the prismakt.generated. Here's an example of such code:

package prismakt.generated

import kotlin.Int
import kotlin.String
import org.jetbrains.exposed.dao.Entity
import org.jetbrains.exposed.dao.EntityClass
import org.jetbrains.exposed.dao.id.EntityID
import org.jetbrains.exposed.dao.id.IdTable
import org.jetbrains.exposed.sql.Column

public object DaoUserTable : IdTable<Int>(name = "\"DaoUser\"") {
public val email: Column<String> = text(name = "email")

public val name: Column<String?> = text(name = "name").nullable()

public val identifier: Column<EntityID<Int>> = integer(name =
"identifier").autoIncrement().entityId()

public override val id: Column<EntityID<Int>>
get() = identifier
}

public class DaoUserEntity(
id: EntityID<Int>,
) : Entity<Int>(id) {
public val identifier: Int
get() = this.id.value

public var email: String by DaoUserTable.email

public var name: String? by DaoUserTable.name

public companion object : EntityClass<Int, DaoUserEntity>(DaoUserTable)
}

Internals overview

Internally, the generator works as follows: