Giter Club home page Giter Club logo

prisma-generator-drizzle's Introduction

prisma-generator-drizzle

Test

prisma-generator-drizzle is a Prisma generator that lets you generate a Drizzle schema. It is specifically designed for (existing) projects that are using Prisma and want to migrate to Drizzle, or for projects that want to use both Prisma and Drizzle together.

In this version, the focus is still on the query and mutation capabilities of the generated Drizzle schema.

Features

CleanShot.2023-12-25.at.01.11.37.mp4
  • ๐Ÿค 1:1 Prisma to Drizzle schema generation
  • โœจ Compatible with all *scalars, enums, and *constraints
  • ๐Ÿ“ฆ Supports drizzle relational query
  • ๐Ÿš€ Generates drizzle-specific features like the .$type<..>() method

*Only supports default scalar for now and more constraints will be added in future

This project is still considered as experimental, but you can safely use it for production. Follow the progress on v1.

Get started

Installation

1. Install the package

npm install -D prisma-generator-drizzle
npm install drizzle-orm

2. Add to your Prisma schema

generator drizzle {
  provider = "prisma-generator-drizzle"

  // Specify the output directory
  // output = "./lib/drizzle/models"
}

See configuration for more options.

3. Run the generator

prisma generate

Usages

Note: This generator will use the default Prisma field mapping, meaning any @db.* modifiers will be ignored for now.

prisma-generator-drizzle aims for 1:1 compatibility with Prisma, this means that you can use the generated Drizzle schema as a complete and familiar drop-in replacement for the Prisma client.

In addition to the Prisma features, you can also generate Drizzle-specific features:

Directive syntax is still experimental, feel free to suggest a better approach.

  1. Generate .$defaultFn<..>()
  2. Generate .$type<..>()

Configuration

Key Description Default Example
output Change the output "./drizzle" "../models"
Generate single output file "drizzle.ts"
formatter Run prettier after generation - "prettier"
relationalQuery Flag to generate relational query true false
moduleResolution Specify the module resolution that will affect the import style *auto nodenext
verbose Flag to enable verbose logging - true
abortOnFailedFormatting Flag to throw exception when formatting fails true false
**dateMode Change the generated mode for date "date"

* It will find the closest tsconfig from the current working directory. Note that extends is not supported

**Does not work with sqlite

Setting up relational query

import { drizzle } from 'drizzle-orm/node-postgres'

// `schema` contains all table and relation definitions
import { schema } from 'prisma/drizzle/schema'

const client = ... // database client
const db = drizzle(client, { schema })

Setting up drizzle-kit

Use the glob pattern (see example 3) to reference the generated table definitions.

import { defineConfig } from 'drizzle-kit'
export default defineConfig({
  // Using the default output path
  schema: './prisma/drizzle/*',
})

Generate .$defaultFn() Custom Default Initializer

โš ๏ธ DEPRECATED , will be replace by drizzle.custom directive

Add /// drizzle.default <module>::<named-function-import> directive above the field definition to generate a custom default initializer.

NOTE: This will override any @default(...) attribute from the schema.

model User {
  /// drizzle.default @paralleldrive/cuid2::createId
  id     String @id
  ...
}

This will result to:

import { createId } from '@paralleldrive/cuid2'
...

export const users = pgTable('User', {
  id: text('id')
    .$defaultFn(() => createId())
    .primaryKey(),
  ...
})

Or with a custom code

model User {
  /// drizzle.default crypto::randomBytes `() => randomBytes(16).toString('hex')`
  salt      String?
  ...
}
import { randomBytes } from 'node:crypto'
...

export const users = pgTable('User', {
  salt: text('salt')
    .$defaultFn(() => randomBytes(16).toString('hex'))
    .notNull(),
  ...
})

Generate .$type<..>() Type Customization

โš ๏ธ DEPRECATED , will be replace by drizzle.custom directive

Add /// drizzle.type <module>::<named-import> directive above the field definition.

model Wallet {
  /// drizzle.type viem::Address
  address     String?
  ...
}

This will result to:

import { Wallet } from 'viem'
...

export const wallets = pgTable('Wallet', {
  address: text('address').$type<Address>(),
  ...
})

Or with a relative import

model User {
  /// drizzle.type ../my-type::Email
  email     String?
  ...
}
import { Email } from '../my-type'
...

export const users = pgTable('User', {
  email: text('email').$type<Email>(),
  ...
})

Example

  1. with-drizzle-prisma: using drizzle's prisma extension

Gotchas

Relative import paths need explicit file extensions in ECMAScript imports when '--moduleResolution' is 'node16' or 'nodenext'.

By default, the generator will try to find the closest tsconfig from the current working directory to determine the import style, whether to add .js or not. When there's no config found, it will use the common import (e.g. import { users } from './users').

You can explicitly set the moduleResolution option in the generator configuration.

Check also the discussion

SqliteError: NOT NULL constraint failed: <table-name>.id

Currently having @default(autoincrement()) only work for postgres and mysql.

prisma-generator-drizzle's People

Contributors

daniel-nagy avatar fdarian avatar jansedlon avatar scalahansolo avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

prisma-generator-drizzle's Issues

Re-export all the models in the schema file

I am trying to create an internal package in my codebase that needs access to everything in my schema. Right now all the models are imported to build the schema, but I need access to those types outside of that one file and I don't want to have to seek into the individual files.

Maybe the generated directory should include an index.ts which exports out the schema as well as all the individual models?

Option to suppress generator output?

First off, Im so thankful for this lib. Not yet using it in production, but it's allowing me to properly test Drizzle with my prod schemas and will make migrating off Prisma so much easier. Onto my idea...

My Prisma schema is pretty massive... I would love to have an option in the generator to suppress the output of the generator. I know where the files are going and it just ends up making a lot of noise and I run prisma generate

Respect default values in timestamps/dates

Given a simple Prisma model:

model Bar {
  foo       String
  createdAT DateTime @default(now())
}

It outputs something like:

import { pgTable, text, timestamp } from 'drizzle-orm/pg-core'

export const sites = pgTable('Bar', {
  foo: text('foo').notNull(),
  createdAT: timestamp('createdAT', { mode: 'date', precision: 3 }).notNull(),
})

Should it have a default value?

import { pgTable, text, timestamp } from 'drizzle-orm/pg-core'

export const sites = pgTable('Bar', {
  foo: text('foo').notNull(),
  createdAt: timestamp('createdAt', { mode: 'date', precision: 3 })
    .defaultNow()
    .notNull(),
})

Or default(sql`CURRENT_TIMESTAMP`). There seems to be some issues with it. Is it intentionally ambiguous?

In one to one relations, the side without foreign key shouldn't have relation opts

TL;DR

This is correct

export const oneToOneBsRelations = relations(oneToOneBs, (helpers) => ({
    a: helpers.one(oneToOneAs)
);

export const oneToOneAsRelations = relations(oneToOneAs, (helpers) => ({
    b: helpers.one(oneToOneBs, {
        relationName: 'OneToOne_AToOneToOne_B',
        fields: [ oneToOneAs.bId ],
        references: [ oneToOneBs.id ] })
    })
);

This is not

export const oneToOneBsRelations = relations(oneToOneBs, (helpers) => ({
    a: helpers.one(oneToOneAs, {
        relationName: 'OneToOne_AToOneToOne_B',
        fields: [oneToOneBs.id],
        references: [oneToOneAs.bId] })
    })
  // This whole opts object shouldn't be there
);

export const oneToOneAsRelations = relations(oneToOneAs, (helpers) => ({
    b: helpers.one(oneToOneBs, {
        relationName: 'OneToOne_AToOneToOne_B',
        fields: [ oneToOneAs.bId ],
        references: [ oneToOneBs.id ] })
    })
);

Hi, if you have one to one relation (A, B) and the foreign key is in table A, the one relation on the other side shouldn't have any opts.

If it does, drizzle query with with does not infer it as an optional relation.

The best showcase is if you open this repo, hover your mouse over result in packages/usage/tests/shared/testOneToOne.ts
image

You can see that the a property is not nullable even though it should be.

From my understanding, if you pass any opts into one relation function, it's then inferred as required when using drizzle query.

https://orm.drizzle.team/docs/rqb#one-to-one

I found that the issue is here
https://github.com/farreldarian/prisma-generator-drizzle/blob/ed849c2e304a4bdf9b0daaa66d4c4e01e46542c2/packages/generator/src/lib/adapter/declarations/generateTableRelationsDeclaration.ts#L126

In this case the whole object shouldn't be passed there. I don't really understand the black magic of that code, but in this case, it shouldn't be there :D

Foreign key is not correctly generated for implicit M:N relations

Hey, sorry for another issue :) My colleague found your package so i'm trying to integrate it.

We have a an implicit M:N relation

model Country {
  id String @id @default(cuid())
  // ...
  currencies Currency[]
}
 
model Currency {
  code String @id @unique
  countries Country[]
  // ...
}

The main thing here is that Currency's id is code.

This is the relations that this package generates. See the comment

export const countriesToCurrenciesRelations = relations(
  countriesToCurrencies,
  (helpers) => ({
    country: helpers.one(countries, {
      fields: [countriesToCurrencies.A],
      references: [countries.id],
    }),
    currency: helpers.one(currencies, {
      fields: [countriesToCurrencies.B],
      references: [currencies.id], // Property 'id' does not exist on type. It should be `code`
    }),
  }),
);

Provide default export for all schema

Using schema.ts for drizzle-kit doesn't expose any schema.

export default defineConfig({
  schema: './drizzle/schema/schema.ts',
})

It can't find any tables from the schema.ts, but glob works fine

export default defineConfig({
  schema: './drizzle/schema/*',
})

Originally posted by @hilja in #18 (comment)

Check tsconfig and add .ts to imports

Screenshot 2024-01-12 at 17 16 01

Not a big problem, but if youโ€™ve got something like this in tsonfig you need to import with .ts extension. Could also be a setting.

{
  "compilerOptions": {
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "target": "ES2022",
    "allowImportingTsExtensions": true,
  }
}

Custom date modes?

Default output is:

  latestMessageCreatedAt: timestamp('latestMessageCreatedAt', { mode: 'date', precision: 3 })
    .defaultNow()
    .notNull(),

How do I change it to:

  latestMessageCreatedAt: timestamp('latestMessageCreatedAt', { mode: 'string', precision: 3 })
    .defaultNow()
    .notNull(),

or modify the options?

`prisma generate` throw Error: `Expected property name or '}' in JSON at position 29`

I followed the instructions in the README and executed the relevant commands.

However, when I reached the following command, an error occurred.

I searched extensively on search engines but couldn't find a solution.

image

generator client {
  provider = "prisma-generator-drizzle"
}

datasource db {
  provider = "sqlite"
  url      = "file:../prisma.db"
}

model User {
  id    Int     @id @default(autoincrement())
  email String  @unique
  name  String?
}
prisma:engines binaries to download libquery-engine, schema-engine +156ms
prisma:get-platform Found distro info:
{
  "targetDistro": "debian",
  "familyDistro": "debian",
  "originalDistro": "ubuntu"
} +4ms
prisma:get-platform Trying platform-specific paths for "debian" (and "ubuntu") +0ms
prisma:get-platform Found libssl.so file using platform-specific paths: libssl.so.3 +0ms
prisma:get-platform The parsed libssl version is: 3.0.x +0ms
prisma:loadEnv project root found at /home/work/package.json +246ms
prisma:tryLoadEnv Environment variables loaded from /home/work/.env +5ms
Environment variables loaded from .env
Prisma schema loaded from prisma/schema.prisma
prisma:getConfig Using getConfig Wasm +3ms
prisma:getConfig config data retrieved without errors in getConfig Wasm +7ms
prisma:get-platform Found distro info:
{
  "targetDistro": "debian",
  "familyDistro": "debian",
  "originalDistro": "ubuntu"
} +1ms
prisma:get-platform Trying platform-specific paths for "debian" (and "ubuntu") +0ms
prisma:get-platform Found libssl.so file using platform-specific paths: libssl.so.3 +1ms
prisma:get-platform The parsed libssl version is: 3.0.x +0ms
prisma:getConfig Using getConfig Wasm +34ms
prisma:getConfig config data retrieved without errors in getConfig Wasm +1ms
prisma:getDMMF Using getDmmf Wasm +0ms
prisma:getDMMF Using given datamodel +0ms
prisma:getDMMF dmmf data retrieved without errors in getDmmf Wasm +36ms
prisma:getGenerators neededVersions {} +294ms
Error: Error: 
Expected property name or '}' in JSON at position 29


    at Mf.parse (/home/work/node_modules/.pnpm/[email protected]/node_modules/prisma/build/index.js:1413:71)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async Rgt (/home/work/node_modules/.pnpm/[email protected]/node_modules/prisma/build/index.js:1892:280)

Cockroachdb provider support (same as postgres)

I saw that in your V1 notes, you didn't call out CockroachDB support. For the sake of this lib, I think it would just be simple enough to generate CockroachDB models in the same way you do Postgres. CockroachDB is designed and meant to be Postgres compatible.

datasource db {
  provider = "cockroachdb"
  url = env("DATABASE_URL")
}

Biome is not recognized as a formatter

Hey, when i want to use biome as a formatter, it throws this error

@flixydev/prisma:db:generate: [prisma-generator-drizzle] Invalid Config:
@flixydev/prisma:db:generate: 
@flixydev/prisma:db:generate: - formatter: Invalid type: Expected "prettier" but received "biome"

v1

๐Ÿ’ก This post will be updated over time

Planned Features

These are the essential features necessary for querying and mutating purposes, providing a 1:1 feature mapping from Prisma so you can start write in Drizzle right away. Kindly point out if there are anything missing from list:

Models

  • Field types
    • Scalar
      • Unsupported
    • List
    • Relations (e.g. []) for Drizzle relational query
    • Enum
  • Constraints
    • ID
    • primaryKey
    • notNull (when no ? modifier)
  • Custom @db. field
  • Multi database schema

Helpers

Config

  • Multi .prisma file
  • Support for all providers
    • postgres
    • mysql
    • sqlite

Unplanned

V1 will focus exclusively on query and mutation operations. Although you can still utilize most of the drizzle-kit features, it is advisable to stick with Prisma for those. The following parts are currently not under consideration:

Error: There is not enough information to infer relation...

I gotta say first I am beyond stoked on this library. My prisma schema is ~600 lines and the drizzle schema generation happened without error. However, I do get the following error when trying to load Drizzle Studio.

Error: There is not enough information to infer relation "__public__.catalogProducts.salesChannels"

CatalogProduct:

import {
  mysqlTable,
  text,
  datetime,
  boolean,
  int,
} from "drizzle-orm/mysql-core";
import { catalogProductTypeEnum } from "./catalog-product-type-enum";
import { catalogProductCategoryEnum } from "./catalog-product-category-enum";
import { relations } from "drizzle-orm";
import { variants } from "./variants";
import { externalProducts } from "./external-products";
import { salesChannels } from "./sales-channels";
import { externalUsers } from "./external-users";

export const catalogProducts = mysqlTable("CatalogProduct", {
  id: text("id").primaryKey(),
  createdAt: datetime("createdAt", { mode: "date", fsp: 3 }).notNull(),
  updatedAt: datetime("updatedAt", { mode: "date", fsp: 3 }).notNull(),
  title: text("title").notNull(),
  description: text("description"),
  productType: catalogProductTypeEnum("productType").notNull(),
  productCategory: catalogProductCategoryEnum("productCategory").notNull(),
  isActive: boolean("isActive").notNull(),
  retailPrice: int("retailPrice").notNull(),
  imageUrl: text("imageUrl"),
  isCustom: boolean("isCustom").notNull(),
  customExternalUserId: text("customExternalUserId"),
  customSkuIdentifier: text("customSkuIdentifier"),
  dielineTemplateUrl: text("dielineTemplateUrl"),
});

export const catalogProductsRelations = relations(
  catalogProducts,
  (helpers) => {
    return {
      variants: helpers.many(variants, {
        relationName: "CatalogProductToVariant",
      }),
      ExternalProduct: helpers.many(externalProducts, {
        relationName: "CatalogProductToExternalProduct",
      }),
      salesChannels: helpers.many(salesChannels, {
        relationName: "CatalogProductToSalesChannel",
      }),
      customExternalUser: helpers.one(externalUsers, {
        relationName: "CatalogProductToExternalUser",
        fields: [catalogProducts.customExternalUserId],
        references: [externalUsers.id],
      }),
    };
  },
);

SalesChannel Drizzle:

import { mysqlTable, text } from "drizzle-orm/mysql-core";
import { availableSalesChannelEnum } from "./available-sales-channel-enum";
import { relations } from "drizzle-orm";
import { catalogProducts } from "./catalog-products";

export const salesChannels = mysqlTable("SalesChannel", {
  id: text("id").primaryKey(),
  salesChannels: availableSalesChannelEnum("salesChannels").notNull(),
});

export const salesChannelsRelations = relations(salesChannels, (helpers) => {
  return {
    CatalogProduct: helpers.many(catalogProducts, {
      relationName: "CatalogProductToSalesChannel",
    }),
  };
});

Prisma:

model SalesChannel {
    id             String                @id @default(cuid())
    salesChannels  AvailableSalesChannel
    CatalogProduct CatalogProduct[]
}

model CatalogProduct {
    id                   String                 @id @default(cuid())
    createdAt            DateTime               @default(now())
    updatedAt            DateTime               @updatedAt
    title                String                 @db.Text
    description          String?                @db.Text
    productType          CatalogProductType
    productCategory      CatalogProductCategory
    isActive             Boolean                @default(false)
    retailPrice          Int
    imageUrl             String?
    variants             Variant[]
    ExternalProduct      ExternalProduct[]
    salesChannels        SalesChannel[]
    isCustom             Boolean                @default(false)
    customExternalUser   ExternalUser?          @relation(fields: [customExternalUserId], references: [id])
    customExternalUserId String?
    customSkuIdentifier  String?
    dielineTemplateUrl   String?

    @@index([customExternalUserId])
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.