Skip to content

Instantly share code, notes, and snippets.

@marklawlor
Last active August 3, 2023 19:29
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save marklawlor/b1c26eefba43539c6611a508e67ee02f to your computer and use it in GitHub Desktop.
Save marklawlor/b1c26eefba43539c6611a508e67ee02f to your computer and use it in GitHub Desktop.
Prisma/Kysely database
/* eslint-disable @typescript-eslint/ban-types */
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { Prisma, PrismaClient, PrismaPromise } from "@prisma/client";
import type { CamelCase, Merge, SnakeCase, PascalCase } from "type-fest";
import {
ColumnType,
DummyDriver,
Generated,
Kysely,
MysqlAdapter,
MysqlIntrospector,
MysqlQueryCompiler,
} from "kysely";
type KyselyOverrides = {
[Model in Prisma.ModelName]?: {
[P in keyof Table<Model>]?: ColumnType<any>;
};
};
type NamingConvention = "snake_case" | "camelCase" | "PascalCase";
type ChangeCase<Value, T extends NamingConvention> = T extends "PascalCase"
? PascalCase<Value>
: T extends "camelCase"
? CamelCase<Value>
: T extends "snake_case"
? SnakeCase<Value>
: Value;
// A deep version of ChangeCaseProperties that respects Prisma scalar types
type ChangeCasePropertiesDeep<
Value,
Convention extends NamingConvention
> = Value extends Prisma.Decimal
? Value
: Value extends Buffer
? Value
: Value extends Function | Date | RegExp
? Value
: Value extends Array<infer U>
? Array<ChangeCasePropertiesDeep<U, Convention>>
: Value extends Set<infer U>
? Set<ChangeCasePropertiesDeep<U, Convention>>
: {
[K in keyof Value as ChangeCase<K, Convention>]: ChangeCasePropertiesDeep<
Value[K],
Convention
>;
};
// Hack to get the raw table types. We extract the ReturnType from a "findFirst" query
type Table<TableName extends Prisma.ModelName> = ReturnType<
PrismaClient[CamelCase<TableName>]["findFirst"]
> extends PrismaPromise<infer T>
? NonNullable<T>
: never;
type Database<
Overrides extends KyselyOverrides = Record<string, never>,
TableConvention extends NamingConvention = "PascalCase",
ColumnConvention extends NamingConvention = "camelCase"
> = {
[P in Prisma.ModelName as ChangeCase<
P,
TableConvention
>]: Overrides extends Record<string, never>
? ChangeCasePropertiesDeep<Table<P>, ColumnConvention>
: Merge<
ChangeCasePropertiesDeep<Table<P>, ColumnConvention>,
ChangeCase<Overrides[P], ColumnConvention>
>;
};
export type MyDatabase = Database<
// You can provide object to specify your Generated keys
{
User: { id: Generated<number> };
Post: { id: Generated<number> };
},
"snake_case", // Change the case of Table names
"snake_case" // Change the case of columns
>;
/*
MyDatabase is typed as:
type MyDatabase = {
user: {
email: string;
name: string | null;
profile_views: number | null;
role: string | null;
id: Generated<number>;
};
post: {
title: string;
content: string | null;
likes: number | null;
published: boolean;
author_id: number;
id: Generated<...>;
};
time_period: {
year: number;
quarter: number;
total: Prisma.Decimal;
};
}
*/
export const database = new Kysely<MyDatabase>({
dialect: {
createAdapter() {
return new MysqlAdapter();
},
createDriver() {
return new DummyDriver();
},
// eslint-disable-next-line @cspell/spellchecker
createIntrospector(database: Kysely<MyDatabase>) {
return new MysqlIntrospector(database);
},
createQueryCompiler() {
return new MysqlQueryCompiler();
},
},
});
// This is correctly typed
export const a = await database
.selectFrom("user")
.innerJoin("post", "post.author_id", "user.id");
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
profileViews Int?
role String?
posts Post[]
@@map("users")
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
likes Int?
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId Int @map("author_id")
@@map("posts")
}
model TimePeriod {
year Int
quarter Int
total Decimal
@@id([year, quarter])
@@unique(fields: [year, quarter], name: "timePeriodId")
}
@hanssonduck
Copy link

Hi there 👋
It's not possible to import PrismaPromise anymore from @prisma/client using the latest version.

@marklawlor
Copy link
Author

@essarn I think this approach was always a bit of a hack. I think a better method is used here https://github.com/lawrencecchen/prisma-generator-kysely

@hanssonduck
Copy link

@marklawlor Used this instead https://github.com/RobinBlomberg/kysely-codegen but will also check out that one. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment