Skip to content

Commit

Permalink
fix(drizzle): update schema type & fix issue with default id (#10750)
Browse files Browse the repository at this point in the history
Co-authored-by: Nico Domino <yo@ndo.dev>
Co-authored-by: Balázs Orbán <info@balazsorban.com>
Co-authored-by: Noam Honig <noam.honig@gmail.com>
  • Loading branch information
4 people committed May 8, 2024
1 parent ddd8209 commit aecc221
Show file tree
Hide file tree
Showing 17 changed files with 338 additions and 311 deletions.
15 changes: 8 additions & 7 deletions docs/pages/getting-started/adapters/drizzle.mdx
Expand Up @@ -35,7 +35,8 @@ To use this adapter, you must have setup Drizzle ORM and Drizzle Kit in your pro
2. Install a supported database driver to your project, like `@libsql/client`, `mysql2` or `postgres`.
3. Create a `drizzle.config.ts` [file](https://orm.drizzle.team/kit-docs/conf).
4. Generate the initial migration from your schema file with a command like, `drizzle-kit generate:pg`.
5. Push that migration to your configured database with a command like, `drizzle-kit push:pg`.
5. Apply migrations by using `migrate()` function or push changes directly to your database with a command like, `drizzle-kit push:pg`.
6. If your schemas differ from the default ones, pass them as the second parameter to the adapter.

#### Schemas

Expand All @@ -53,7 +54,7 @@ import {
} from "drizzle-orm/pg-core"
import postgres from "postgres"
import { drizzle } from "drizzle-orm/postgres-js"
import type { AdapterAccount } from "next-auth/adapters"
import type { AdapterAccountType } from "@auth/core/adapters"

const connectionString = "postgres://postgres:postgres@localhost:5432/drizzle"
const pool = postgres(connectionString, { max: 1 })
Expand All @@ -76,7 +77,7 @@ export const accounts = pgTable(
userId: text("userId")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
type: text("type").$type<AdapterAccount["type"]>().notNull(),
type: text("type").$type<AdapterAccountType>().notNull(),
provider: text("provider").notNull(),
providerAccountId: text("providerAccountId").notNull(),
refresh_token: text("refresh_token"),
Expand Down Expand Up @@ -129,7 +130,7 @@ import {
} from "drizzle-orm/mysql-core"
import mysql from "mysql2/promise"
import { drizzle } from "drizzle-orm/mysql2"
import type { AdapterAccount } from "next-auth/adapters"
import type { AdapterAccountType } from "@auth/core/adapters"

export const connection = await mysql.createConnection({
host: "host",
Expand Down Expand Up @@ -160,7 +161,7 @@ export const accounts = mysqlTable(
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
type: varchar("type", { length: 255 })
.$type<AdapterAccount["type"]>()
.$type<AdapterAccountType>()
.notNull(),
provider: varchar("provider", { length: 255 }).notNull(),
providerAccountId: varchar("providerAccountId", { length: 255 }).notNull(),
Expand Down Expand Up @@ -208,7 +209,7 @@ If you want to modify the schema or add additional fields, you can use the follo
import { integer, sqliteTable, text, primaryKey } from "drizzle-orm/sqlite-core"
import { createClient } from "@libsql/client"
import { drizzle } from "drizzle-orm/libsql"
import type { AdapterAccount } from "next-auth/adapters"
import type { AdapterAccountType } from "@auth/core/adapters"

const client = createClient({
url: "DATABASE_URL",
Expand All @@ -232,7 +233,7 @@ export const accounts = sqliteTable(
userId: text("userId")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
type: text("type").$type<AdapterAccount["type"]>().notNull(),
type: text("type").$type<AdapterAccountType>().notNull(),
provider: text("provider").notNull(),
providerAccountId: text("providerAccountId").notNull(),
refresh_token: text("refresh_token"),
Expand Down
2 changes: 1 addition & 1 deletion packages/adapter-drizzle/package.json
Expand Up @@ -53,7 +53,7 @@
"@types/uuid": "^8.3.3",
"better-sqlite3": "^9.4.0",
"drizzle-kit": "^0.20.17",
"drizzle-orm": "^0.30.8",
"drizzle-orm": "^0.30.9",
"mysql2": "^3.9.7",
"postgres": "^3.4.3",
"tsx": "^4.7.0"
Expand Down
19 changes: 9 additions & 10 deletions packages/adapter-drizzle/src/index.ts
Expand Up @@ -83,11 +83,10 @@ import type { Adapter } from "@auth/core/adapters"
* primaryKey,
* integer
* } from "drizzle-orm/pg-core"
* import type { AdapterAccount } from '@auth/core/adapters'
* import { randomUUID } from "crypto"
* import type { AdapterAccountType } from '@auth/core/adapters'
*
* export const users = pgTable("user", {
* id: text("id").primaryKey().$defaultFn(() => randomUUID()),
* id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
* name: text("name"),
* email: text("email").notNull(),
* emailVerified: timestamp("emailVerified", { mode: "date" }),
Expand All @@ -100,7 +99,7 @@ import type { Adapter } from "@auth/core/adapters"
* userId: text("userId")
* .notNull()
* .references(() => users.id, { onDelete: "cascade" }),
* type: text("type").notNull(),
* type: text("type").$type<AdapterAccountType>().notNull(),
* provider: text("provider").notNull(),
* providerAccountId: text("providerAccountId").notNull(),
* refresh_token: text("refresh_token"),
Expand Down Expand Up @@ -149,10 +148,10 @@ import type { Adapter } from "@auth/core/adapters"
* primaryKey,
* varchar,
* } from "drizzle-orm/mysql-core"
* import type { AdapterAccount } from "@auth/core/adapters"
* import type { AdapterAccountType } from "@auth/core/adapters"
*
* export const users = mysqlTable("user", {
* id: varchar("id", { length: 255 }).primaryKey().$defaultFn(() => randomUUID()),
* id: varchar("id", { length: 255 }).primaryKey().$defaultFn(() => crypto.randomUUID()),
* name: varchar("name", { length: 255 }),
* email: varchar("email", { length: 255 }).notNull(),
* emailVerified: timestamp("emailVerified", { mode: "date", fsp: 3 }),
Expand All @@ -165,7 +164,7 @@ import type { Adapter } from "@auth/core/adapters"
* userId: varchar("userId", { length: 255 })
* .notNull()
* .references(() => users.id, { onDelete: "cascade" }),
* type: varchar("type", { length: 255 }).notNull(),
* type: varchar("type", { length: 255 }).$type<AdapterAccountType>().notNull(),
* provider: varchar("provider", { length: 255 }).notNull(),
* providerAccountId: varchar("providerAccountId", { length: 255 }).notNull(),
* refresh_token: varchar("refresh_token", { length: 255 }),
Expand Down Expand Up @@ -208,10 +207,10 @@ import type { Adapter } from "@auth/core/adapters"
*
* ```ts title="schema.ts"
* import { integer, sqliteTable, text, primaryKey } from "drizzle-orm/sqlite-core"
* import type { AdapterAccount } from "@auth/core/adapters"
* import type { AdapterAccountType } from "@auth/core/adapters"
*
* export const users = sqliteTable("user", {
* id: text("id").primaryKey().$defaultFn(() => randomUUID()),
* id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
* name: text("name"),
* email: text("email").notNull(),
* emailVerified: integer("emailVerified", { mode: "timestamp_ms" }),
Expand All @@ -224,7 +223,7 @@ import type { Adapter } from "@auth/core/adapters"
* userId: text("userId")
* .notNull()
* .references(() => users.id, { onDelete: "cascade" }),
* type: text("type").notNull(),
* type: text("type").$type<AdapterAccountType>().notNull(),
* provider: text("provider").notNull(),
* providerAccountId: text("providerAccountId").notNull(),
* refresh_token: text("refresh_token"),
Expand Down
142 changes: 80 additions & 62 deletions packages/adapter-drizzle/src/lib/mysql.ts
Expand Up @@ -15,86 +15,104 @@ import {
import type {
Adapter,
AdapterAccount,
AdapterAccountType,
AdapterSession,
AdapterUser,
VerificationToken,
} from "@auth/core/adapters"

export const mysqlUsersTable = mysqlTable("user", {
id: varchar("id", { length: 255 })
.primaryKey()
.$defaultFn(() => crypto.randomUUID()),
name: varchar("name", { length: 255 }),
email: varchar("email", { length: 255 }).notNull(),
emailVerified: timestamp("emailVerified", { mode: "date", fsp: 3 }),
image: varchar("image", { length: 255 }),
}) satisfies DefaultMySqlUsersTable
export function defineTables(
schema: Partial<DefaultMySqlSchema> = {}
): Required<DefaultMySqlSchema> {
const usersTable =
schema.usersTable ??
(mysqlTable("user", {
id: varchar("id", { length: 255 })
.primaryKey()
.$defaultFn(() => crypto.randomUUID()),
name: varchar("name", { length: 255 }),
email: varchar("email", { length: 255 }).notNull(),
emailVerified: timestamp("emailVerified", { mode: "date", fsp: 3 }),
image: varchar("image", { length: 255 }),
}) satisfies DefaultMySqlUsersTable)

export const mysqlAccountsTable = mysqlTable(
"account",
{
userId: varchar("userId", { length: 255 })
.notNull()
.references(() => mysqlUsersTable.id, { onDelete: "cascade" }),
type: varchar("type", { length: 255 })
.$type<AdapterAccount["type"]>()
.notNull(),
provider: varchar("provider", { length: 255 }).notNull(),
providerAccountId: varchar("providerAccountId", { length: 255 }).notNull(),
refresh_token: varchar("refresh_token", { length: 255 }),
access_token: varchar("access_token", { length: 255 }),
expires_at: int("expires_at"),
token_type: varchar("token_type", { length: 255 }),
scope: varchar("scope", { length: 255 }),
id_token: varchar("id_token", { length: 2048 }),
session_state: varchar("session_state", { length: 255 }),
},
(account) => ({
compositePk: primaryKey({
columns: [account.provider, account.providerAccountId],
}),
})
) satisfies DefaultMySqlAccountsTable
const accountsTable =
schema.accountsTable ??
(mysqlTable(
"account",
{
userId: varchar("userId", { length: 255 })
.notNull()
.references(() => usersTable.id, { onDelete: "cascade" }),
type: varchar("type", { length: 255 })
.$type<AdapterAccountType>()
.notNull(),
provider: varchar("provider", { length: 255 }).notNull(),
providerAccountId: varchar("providerAccountId", {
length: 255,
}).notNull(),
refresh_token: varchar("refresh_token", { length: 255 }),
access_token: varchar("access_token", { length: 255 }),
expires_at: int("expires_at"),
token_type: varchar("token_type", { length: 255 }),
scope: varchar("scope", { length: 255 }),
id_token: varchar("id_token", { length: 2048 }),
session_state: varchar("session_state", { length: 255 }),
},
(account) => ({
compositePk: primaryKey({
columns: [account.provider, account.providerAccountId],
}),
})
) satisfies DefaultMySqlAccountsTable)

export const mysqlSessionsTable = mysqlTable("session", {
sessionToken: varchar("sessionToken", { length: 255 }).primaryKey(),
userId: varchar("userId", { length: 255 })
.notNull()
.references(() => mysqlUsersTable.id, { onDelete: "cascade" }),
expires: timestamp("expires", { mode: "date" }).notNull(),
}) satisfies DefaultMySqlSessionsTable
const sessionsTable =
schema.sessionsTable ??
(mysqlTable("session", {
sessionToken: varchar("sessionToken", { length: 255 }).primaryKey(),
userId: varchar("userId", { length: 255 })
.notNull()
.references(() => usersTable.id, { onDelete: "cascade" }),
expires: timestamp("expires", { mode: "date" }).notNull(),
}) satisfies DefaultMySqlSessionsTable)

export const mysqlVerificationTokensTable = mysqlTable(
"verificationToken",
{
identifier: varchar("identifier", { length: 255 }).notNull(),
token: varchar("token", { length: 255 }).notNull(),
expires: timestamp("expires", { mode: "date" }).notNull(),
},
(vt) => ({
compositePk: primaryKey({ columns: [vt.identifier, vt.token] }),
})
) satisfies DefaultMySqlVerificationTokenTable
const verificationTokensTable =
schema.verificationTokensTable ??
(mysqlTable(
"verificationToken",
{
identifier: varchar("identifier", { length: 255 }).notNull(),
token: varchar("token", { length: 255 }).notNull(),
expires: timestamp("expires", { mode: "date" }).notNull(),
},
(vt) => ({
compositePk: primaryKey({ columns: [vt.identifier, vt.token] }),
})
) satisfies DefaultMySqlVerificationTokenTable)

return {
usersTable,
accountsTable,
sessionsTable,
verificationTokensTable,
}
}

export function MySqlDrizzleAdapter(
client: MySqlDatabase<QueryResultHKT, PreparedQueryHKTBase, any>,
schema: DefaultMySqlSchema = {
usersTable: mysqlUsersTable,
accountsTable: mysqlAccountsTable,
sessionsTable: mysqlSessionsTable,
verificationTokensTable: mysqlVerificationTokensTable,
}
schema?: DefaultMySqlSchema
): Adapter {
const { usersTable, accountsTable, sessionsTable, verificationTokensTable } =
schema
defineTables(schema)

return {
async createUser(data: AdapterUser) {
const { id, ...insertData } = data
const hasDefaultId = getTableColumns(usersTable)["id"]["hasDefault"]

await client
.insert(usersTable)
.values(hasDefaultId ? data : { ...data, id: crypto.randomUUID() })
.values(hasDefaultId ? insertData : { ...insertData, id })

return client
.select()
Expand Down Expand Up @@ -442,6 +460,6 @@ export type DefaultMySqlVerificationTokenTable = MySqlTableWithColumns<{
export type DefaultMySqlSchema = {
usersTable: DefaultMySqlUsersTable
accountsTable: DefaultMySqlAccountsTable
sessionsTable: DefaultMySqlSessionsTable
verificationTokensTable: DefaultMySqlVerificationTokenTable
sessionsTable?: DefaultMySqlSessionsTable
verificationTokensTable?: DefaultMySqlVerificationTokenTable
}

0 comments on commit aecc221

Please sign in to comment.