@auth/drizzle-adapter 0.0.0-manual.2501f898

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2022-2023, Balázs Orbán
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,28 @@
1
+ <p align="center">
2
+ <br/>
3
+ <a href="https://authjs.dev" target="_blank">
4
+ <img height="64px" src="https://authjs.dev/img/logo/logo-sm.png" />
5
+ </a>
6
+ <a href="https://github.com/drizzle-team/drizzle-orm" target="_blank">
7
+ <img height="64px" src="https://pbs.twimg.com/profile_images/1598308842391179266/CtXrfLnk_400x400.jpg"/>
8
+ </a>
9
+ <h3 align="center"><b>Drizzle ORM Adapter</b> - NextAuth.js / Auth.js</a></h3>
10
+ <p align="center" style="align: center;">
11
+ <a href="https://npm.im/@auth/drizzle-adapter">
12
+ <img src="https://img.shields.io/badge/TypeScript-blue?style=flat-square" alt="TypeScript" />
13
+ </a>
14
+ <a href="https://npm.im/@auth/drizzle-adapter">
15
+ <img alt="npm" src="https://img.shields.io/npm/v/@auth/drizzle-adapter?color=green&label=@auth/drizzle-adapter&style=flat-square">
16
+ </a>
17
+ <a href="https://www.npmtrends.com/@auth/drizzle-adapter">
18
+ <img src="https://img.shields.io/npm/dm/@auth/drizzle-adapter?label=%20downloads&style=flat-square" alt="Downloads" />
19
+ </a>
20
+ <a href="https://github.com/nextauthjs/next-auth/stargazers">
21
+ <img src="https://img.shields.io/github/stars/nextauthjs/next-auth?style=flat-square" alt="Github Stars" />
22
+ </a>
23
+ </p>
24
+ </p>
25
+
26
+ ---
27
+
28
+ Check out the documentation at [authjs.dev](https://authjs.dev/reference/adapter/drizzle).
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@auth/drizzle-adapter",
3
+ "version": "0.0.0-manual.2501f898",
4
+ "description": "Drizzle adapter for Auth.js.",
5
+ "homepage": "https://authjs.dev",
6
+ "repository": "https://github.com/nextauthjs/next-auth",
7
+ "bugs": {
8
+ "url": "https://github.com/nextauthjs/next-auth/issues"
9
+ },
10
+ "author": "Anthony Shew",
11
+ "type": "module",
12
+ "types": "./index.d.ts",
13
+ "files": [
14
+ "*.js",
15
+ "*.d.ts*",
16
+ "lib",
17
+ "src"
18
+ ],
19
+ "exports": {
20
+ ".": {
21
+ "types": "./index.d.ts",
22
+ "import": "./index.js"
23
+ }
24
+ },
25
+ "license": "ISC",
26
+ "keywords": [
27
+ "next-auth",
28
+ "@auth",
29
+ "Auth.js",
30
+ "next.js",
31
+ "oauth",
32
+ "drizzle"
33
+ ],
34
+ "private": false,
35
+ "publishConfig": {
36
+ "access": "public"
37
+ },
38
+ "dependencies": {
39
+ "@auth/core": "0.10.0"
40
+ },
41
+ "devDependencies": {
42
+ "@types/better-sqlite3": "^7.6.4",
43
+ "@types/uuid": "^8.3.3",
44
+ "better-sqlite3": "^8.4.0",
45
+ "drizzle-kit": "^0.19.5",
46
+ "drizzle-orm": "^0.27.0",
47
+ "jest": "^27.4.3",
48
+ "mysql2": "^3.2.0",
49
+ "postgres": "^3.3.4",
50
+ "@next-auth/tsconfig": "0.0.0",
51
+ "@next-auth/adapter-test": "0.0.0"
52
+ },
53
+ "jest": {
54
+ "preset": "@next-auth/adapter-test/jest"
55
+ },
56
+ "scripts": {
57
+ "clean": "find . -type d -name \".drizzle\" | xargs rm -rf",
58
+ "test": "pnpm test:mysql && pnpm test:sqlite && pnpm test:pg",
59
+ "test:mysql": "pnpm clean && ./tests/mysql/test.sh",
60
+ "test:sqlite": "pnpm clean && ./tests/sqlite/test.sh",
61
+ "test:pg": "pnpm clean && ./tests/pg/test.sh",
62
+ "build": "tsc",
63
+ "dev": "drizzle-kit generate:mysql --schema=src/schema.ts --out=.drizzle && tsc -w"
64
+ }
65
+ }
package/src/index.ts ADDED
@@ -0,0 +1,265 @@
1
+ /**
2
+ * <div style={{display: "flex", justifyContent: "space-between", alignItems: "center", padding: 16}}>
3
+ * <p style={{fontWeight: "normal"}}>Official <a href="https://orm.drizzle.team">Drizzle ORM</a> adapter for Auth.js / NextAuth.js.</p>
4
+ * <a href="https://orm.drizzle.team">
5
+ * <img style={{display: "block"}} src="/img/adapters/drizzle-orm.png" width="38" />
6
+ * </a>
7
+ * </div>
8
+ *
9
+ * ## Installation
10
+ *
11
+ * ```bash npm2yarn2pnpm
12
+ * npm install drizzle-orm @auth/drizzle-adapter
13
+ * npm install drizzle-kit --save-dev
14
+ * ```
15
+ *
16
+ * @module @auth/drizzle-adapter
17
+ */
18
+ import {
19
+ SqlFlavorOptions,
20
+ isMySqlDatabase,
21
+ isPgDatabase,
22
+ isSQLiteDatabase,
23
+ } from "./utils"
24
+ import type { Adapter } from "@auth/core/adapters"
25
+ import { mySqlDrizzleAdapter } from "./mysql"
26
+ import { pgDrizzleAdapter } from "./pg"
27
+ import { SQLiteDrizzleAdapter } from "./sqlite"
28
+ /**
29
+ * Add the adapter to your `app/api/[...nextauth]/route.js` next-auth configuration object.
30
+ *
31
+ * ```ts title="pages/api/auth/[...nextauth].ts"
32
+ * import NextAuth from "next-auth"
33
+ * import GoogleProvider from "next-auth/providers/google"
34
+ * import { DrizzleAdapter } from "@auth/drizzle-adapter"
35
+ * import { db } from "./schema"
36
+ *
37
+ * export default NextAuth({
38
+ * adapter: DrizzleAdapter(db),
39
+ * providers: [
40
+ * GoogleProvider({
41
+ * clientId: process.env.GOOGLE_CLIENT_ID,
42
+ * clientSecret: process.env.GOOGLE_CLIENT_SECRET,
43
+ * }),
44
+ * ],
45
+ * })
46
+ * ```
47
+ *
48
+ * ## Setup
49
+ *
50
+ * First, create a schema that includes [the minimum requirements for a `next-auth` adapter](/reference/adapters#models). You can select your favorite SQL flavor below and copy it.
51
+ * Additionally, you may extend the schema from the minimum requirements to suit your needs.
52
+ *
53
+ * - [Postgres](#postgres)
54
+ * - [MySQL](#mysql)
55
+ * - [SQLite](#sqlite)
56
+ *
57
+ * ### Postgres
58
+
59
+ * ```ts title="schema.ts"
60
+ * import {
61
+ * timestamp,
62
+ * pgTable,
63
+ * text,
64
+ * primaryKey,
65
+ * integer
66
+ * } from "drizzle-orm/pg-core"
67
+ * import type { AdapterAccount } from '@auth/core/adapters'
68
+ *
69
+ * export const users = pgTable("users", {
70
+ * id: text("id").notNull().primaryKey(),
71
+ * name: text("name"),
72
+ * email: text("email").notNull(),
73
+ * emailVerified: timestamp("emailVerified", { mode: "date" }),
74
+ * image: text("image"),
75
+ * })
76
+ *
77
+ * export const accounts = pgTable(
78
+ * "accounts",
79
+ * {
80
+ * userId: text("userId")
81
+ * .notNull()
82
+ * .references(() => users.id, { onDelete: "cascade" }),
83
+ * type: text("type").$type<AdapterAccount["type"]>().notNull(),
84
+ * provider: text("provider").notNull(),
85
+ * providerAccountId: text("providerAccountId").notNull(),
86
+ * refresh_token: text("refresh_token"),
87
+ * access_token: text("access_token"),
88
+ * expires_at: integer("expires_at"),
89
+ * token_type: text("token_type"),
90
+ * scope: text("scope"),
91
+ * id_token: text("id_token"),
92
+ * session_state: text("session_state"),
93
+ * },
94
+ * (account) => ({
95
+ * compoundKey: primaryKey(account.provider, account.providerAccountId),
96
+ * })
97
+ * )
98
+ *
99
+ * export const sessions = pgTable("sessions", {
100
+ * sessionToken: text("sessionToken").notNull().primaryKey(),
101
+ * userId: text("userId")
102
+ * .notNull()
103
+ * .references(() => users.id, { onDelete: "cascade" }),
104
+ * expires: timestamp("expires", { mode: "date" }).notNull(),
105
+ * })
106
+ *
107
+ * export const verificationTokens = pgTable(
108
+ * "verificationToken",
109
+ * {
110
+ * identifier: text("identifier").notNull(),
111
+ * token: text("token").notNull(),
112
+ * expires: timestamp("expires", { mode: "date" }).notNull(),
113
+ * },
114
+ * (vt) => ({
115
+ * compoundKey: primaryKey(vt.identifier, vt.token),
116
+ * })
117
+ * )
118
+ * ```
119
+ *
120
+ * ### MySQL
121
+ *
122
+ * ```ts title="schema.ts"
123
+ * import {
124
+ * int,
125
+ * timestamp,
126
+ * mysqlTable,
127
+ * primaryKey,
128
+ * varchar,
129
+ * } from "drizzle-orm/mysql-core"
130
+ * import type { AdapterAccount } from "@auth/core/adapters"
131
+ *
132
+ * export const users = mysqlTable("users", {
133
+ * id: varchar("id", { length: 255 }).notNull().primaryKey(),
134
+ * name: varchar("name", { length: 255 }),
135
+ * email: varchar("email", { length: 255 }).notNull(),
136
+ * emailVerified: timestamp("emailVerified", { mode: "date", fsp: 3 }).defaultNow(),
137
+ * image: varchar("image", { length: 255 }),
138
+ * })
139
+ *
140
+ * export const accounts = mysqlTable(
141
+ * "accounts",
142
+ * {
143
+ * userId: varchar("userId", { length: 255 })
144
+ * .notNull()
145
+ * .references(() => users.id, { onDelete: "cascade" }),
146
+ * type: varchar("type", { length: 255 }).$type<AdapterAccount["type"]>().notNull(),
147
+ * provider: varchar("provider", { length: 255 }).notNull(),
148
+ * providerAccountId: varchar("providerAccountId", { length: 255 }).notNull(),
149
+ * refresh_token: varchar("refresh_token", { length: 255 }),
150
+ * access_token: varchar("access_token", { length: 255 }),
151
+ * expires_at: int("expires_at"),
152
+ * token_type: varchar("token_type", { length: 255 }),
153
+ * scope: varchar("scope", { length: 255 }),
154
+ * id_token: varchar("id_token", { length: 255 }),
155
+ * session_state: varchar("session_state", { length: 255 }),
156
+ * },
157
+ * (account) => ({
158
+ * compoundKey: primaryKey(account.provider, account.providerAccountId),
159
+ * })
160
+ * )
161
+ *
162
+ * export const sessions = mysqlTable("sessions", {
163
+ * sessionToken: varchar("sessionToken", { length: 255 }).notNull().primaryKey(),
164
+ * userId: varchar("userId", { length: 255 })
165
+ * .notNull()
166
+ * .references(() => users.id, { onDelete: "cascade" }),
167
+ * expires: timestamp("expires", { mode: "date" }).notNull(),
168
+ * })
169
+ *
170
+ * export const verificationTokens = mysqlTable(
171
+ * "verificationToken",
172
+ * {
173
+ * identifier: varchar("identifier", { length: 255 }).notNull(),
174
+ * token: varchar("token", { length: 255 }).notNull(),
175
+ * expires: timestamp("expires", { mode: "date" }).notNull(),
176
+ * },
177
+ * (vt) => ({
178
+ * compoundKey: primaryKey(vt.identifier, vt.token),
179
+ * })
180
+ * )
181
+ * ```
182
+ *
183
+ * ### SQLite
184
+ *
185
+ * ```ts title="schema.ts"
186
+ * import { integer, sqliteTable, text, primaryKey } from "drizzle-orm/sqlite-core"
187
+ * import type { AdapterAccount } from "@auth/core/adapters"
188
+ *
189
+ * export const users = sqliteTable("users", {
190
+ * id: text("id").notNull().primaryKey(),
191
+ * name: text("name"),
192
+ * email: text("email").notNull(),
193
+ * emailVerified: integer("emailVerified", { mode: "timestamp_ms" }),
194
+ * image: text("image"),
195
+ * })
196
+ *
197
+ * export const accounts = sqliteTable(
198
+ * "accounts",
199
+ * {
200
+ * userId: text("userId")
201
+ * .notNull()
202
+ * .references(() => users.id, { onDelete: "cascade" }),
203
+ * type: text("type").$type<AdapterAccount["type"]>().notNull(),
204
+ * provider: text("provider").notNull(),
205
+ * providerAccountId: text("providerAccountId").notNull(),
206
+ * refresh_token: text("refresh_token"),
207
+ * access_token: text("access_token"),
208
+ * expires_at: integer("expires_at"),
209
+ * token_type: text("token_type"),
210
+ * scope: text("scope"),
211
+ * id_token: text("id_token"),
212
+ * session_state: text("session_state"),
213
+ * },
214
+ * (account) => ({
215
+ * compoundKey: primaryKey(account.provider, account.providerAccountId),
216
+ * })
217
+ * )
218
+ *
219
+ * export const sessions = sqliteTable("sessions", {
220
+ * sessionToken: text("sessionToken").notNull().primaryKey(),
221
+ * userId: text("userId")
222
+ * .notNull()
223
+ * .references(() => users.id, { onDelete: "cascade" }),
224
+ * expires: integer("expires", { mode: "timestamp_ms" }).notNull(),
225
+ * })
226
+ *
227
+ * export const verificationTokens = sqliteTable(
228
+ * "verificationToken",
229
+ * {
230
+ * identifier: text("identifier").notNull(),
231
+ * token: text("token").notNull(),
232
+ * expires: integer("expires", { mode: "timestamp_ms" }).notNull(),
233
+ * },
234
+ * (vt) => ({
235
+ * compoundKey: primaryKey(vt.identifier, vt.token),
236
+ * })
237
+ * )
238
+ * ```
239
+ *
240
+ * ## Migrating your database
241
+ * With your schema now described in your code, you'll need to migrate your database to your schema.
242
+ *
243
+ * For full documentation on how to run migrations with Drizzle, [visit the Drizzle documentation](https://orm.drizzle.team/kit-docs/overview#running-migrations).
244
+ *
245
+ * ---
246
+ *
247
+ **/
248
+ export function DrizzleAdapter<SqlFlavor extends SqlFlavorOptions>(
249
+ db: SqlFlavor
250
+ ): Adapter {
251
+ if (isMySqlDatabase(db)) {
252
+ // We need to cast to unknown since the type overlaps (PScale is MySQL based)
253
+ return mySqlDrizzleAdapter(db)
254
+ }
255
+
256
+ if (isPgDatabase(db)) {
257
+ return pgDrizzleAdapter(db)
258
+ }
259
+
260
+ if (isSQLiteDatabase(db)) {
261
+ return SQLiteDrizzleAdapter(db)
262
+ }
263
+
264
+ throw new Error("Unsupported database type in Auth.js Drizzle adapter.")
265
+ }
@@ -0,0 +1,261 @@
1
+ import { and, eq } from "drizzle-orm"
2
+ import crypto from "node:crypto"
3
+ import {
4
+ int,
5
+ timestamp,
6
+ mysqlTable,
7
+ primaryKey,
8
+ varchar,
9
+ } from "drizzle-orm/mysql-core"
10
+ import type { Adapter, AdapterAccount } from "@auth/core/adapters"
11
+ import { MySql2Database } from "drizzle-orm/mysql2"
12
+
13
+ /** @internal */
14
+ export const users = mysqlTable("users", {
15
+ id: varchar("id", { length: 255 }).notNull().primaryKey(),
16
+ name: varchar("name", { length: 255 }),
17
+ email: varchar("email", { length: 255 }).notNull(),
18
+ emailVerified: timestamp("emailVerified", {
19
+ mode: "date",
20
+ fsp: 3,
21
+ }).defaultNow(),
22
+ image: varchar("image", { length: 255 }),
23
+ })
24
+
25
+ /** @internal */
26
+ export const accounts = mysqlTable(
27
+ "accounts",
28
+ {
29
+ userId: varchar("userId", { length: 255 })
30
+ .notNull()
31
+ .references(() => users.id, { onDelete: "cascade" }),
32
+ type: varchar("type", { length: 255 })
33
+ .$type<AdapterAccount["type"]>()
34
+ .notNull(),
35
+ provider: varchar("provider", { length: 255 }).notNull(),
36
+ providerAccountId: varchar("providerAccountId", { length: 255 }).notNull(),
37
+ refresh_token: varchar("refresh_token", { length: 255 }),
38
+ access_token: varchar("access_token", { length: 255 }),
39
+ expires_at: int("expires_at"),
40
+ token_type: varchar("token_type", { length: 255 }),
41
+ scope: varchar("scope", { length: 255 }),
42
+ id_token: varchar("id_token", { length: 255 }),
43
+ session_state: varchar("session_state", { length: 255 }),
44
+ },
45
+ (account) => ({
46
+ compoundKey: primaryKey(account.provider, account.providerAccountId),
47
+ })
48
+ )
49
+
50
+ /** @internal */
51
+ export const sessions = mysqlTable("sessions", {
52
+ sessionToken: varchar("sessionToken", { length: 255 }).notNull().primaryKey(),
53
+ userId: varchar("userId", { length: 255 })
54
+ .notNull()
55
+ .references(() => users.id, { onDelete: "cascade" }),
56
+ expires: timestamp("expires", { mode: "date" }).notNull(),
57
+ })
58
+
59
+ /** @internal */
60
+ export const verificationTokens = mysqlTable(
61
+ "verificationToken",
62
+ {
63
+ identifier: varchar("identifier", { length: 255 }).notNull(),
64
+ token: varchar("token", { length: 255 }).notNull(),
65
+ expires: timestamp("expires", { mode: "date" }).notNull(),
66
+ },
67
+ (vt) => ({
68
+ compoundKey: primaryKey(vt.identifier, vt.token),
69
+ })
70
+ )
71
+
72
+ /** @internal */
73
+ export const schema = { users, accounts, sessions, verificationTokens }
74
+ export type DefaultSchema = typeof schema
75
+
76
+ /** @internal */
77
+ export function mySqlDrizzleAdapter(
78
+ client: MySql2Database<Record<string, never>>
79
+ ): Adapter {
80
+ return {
81
+ createUser: async (data) => {
82
+ const id = crypto.randomUUID()
83
+
84
+ await client.insert(users).values({ ...data, id })
85
+
86
+ return await client
87
+ .select()
88
+ .from(users)
89
+ .where(eq(users.id, id))
90
+ .then((res) => res[0])
91
+ },
92
+ getUser: async (data) => {
93
+ const thing =
94
+ (await client
95
+ .select()
96
+ .from(users)
97
+ .where(eq(users.id, data))
98
+ .then((res) => res[0])) ?? null
99
+
100
+ return thing
101
+ },
102
+ getUserByEmail: async (data) => {
103
+ const user =
104
+ (await client
105
+ .select()
106
+ .from(users)
107
+ .where(eq(users.email, data))
108
+ .then((res) => res[0])) ?? null
109
+
110
+ return user
111
+ },
112
+ createSession: async (data) => {
113
+ await client.insert(sessions).values(data)
114
+
115
+ return await client
116
+ .select()
117
+ .from(sessions)
118
+ .where(eq(sessions.sessionToken, data.sessionToken))
119
+ .then((res) => res[0])
120
+ },
121
+ getSessionAndUser: async (data) => {
122
+ const sessionAndUser =
123
+ (await client
124
+ .select({
125
+ session: sessions,
126
+ user: users,
127
+ })
128
+ .from(sessions)
129
+ .where(eq(sessions.sessionToken, data))
130
+ .innerJoin(users, eq(users.id, sessions.userId))
131
+ .then((res) => res[0])) ?? null
132
+
133
+ return sessionAndUser
134
+ },
135
+ updateUser: async (data) => {
136
+ if (!data.id) {
137
+ throw new Error("No user id.")
138
+ }
139
+
140
+ await client.update(users).set(data).where(eq(users.id, data.id))
141
+
142
+ return await client
143
+ .select()
144
+ .from(users)
145
+ .where(eq(users.id, data.id))
146
+ .then((res) => res[0])
147
+ },
148
+ updateSession: async (data) => {
149
+ await client
150
+ .update(sessions)
151
+ .set(data)
152
+ .where(eq(sessions.sessionToken, data.sessionToken))
153
+
154
+ return await client
155
+ .select()
156
+ .from(sessions)
157
+ .where(eq(sessions.sessionToken, data.sessionToken))
158
+ .then((res) => res[0])
159
+ },
160
+ linkAccount: async (rawAccount) => {
161
+ await client
162
+ .insert(accounts)
163
+ .values(rawAccount)
164
+ .then((res) => res[0])
165
+ },
166
+ getUserByAccount: async (account) => {
167
+ const dbAccount =
168
+ (await client
169
+ .select()
170
+ .from(accounts)
171
+ .where(
172
+ and(
173
+ eq(accounts.providerAccountId, account.providerAccountId),
174
+ eq(accounts.provider, account.provider)
175
+ )
176
+ )
177
+ .leftJoin(users, eq(accounts.userId, users.id))
178
+ .then((res) => res[0])) ?? null
179
+
180
+ if (!dbAccount) {
181
+ return null
182
+ }
183
+
184
+ return dbAccount.users
185
+ },
186
+ deleteSession: async (sessionToken) => {
187
+ const session =
188
+ (await client
189
+ .select()
190
+ .from(sessions)
191
+ .where(eq(sessions.sessionToken, sessionToken))
192
+ .then((res) => res[0])) ?? null
193
+
194
+ await client
195
+ .delete(sessions)
196
+ .where(eq(sessions.sessionToken, sessionToken))
197
+
198
+ return session
199
+ },
200
+ createVerificationToken: async (token) => {
201
+ await client.insert(verificationTokens).values(token)
202
+
203
+ return await client
204
+ .select()
205
+ .from(verificationTokens)
206
+ .where(eq(verificationTokens.identifier, token.identifier))
207
+ .then((res) => res[0])
208
+ },
209
+ useVerificationToken: async (token) => {
210
+ try {
211
+ const deletedToken =
212
+ (await client
213
+ .select()
214
+ .from(verificationTokens)
215
+ .where(
216
+ and(
217
+ eq(verificationTokens.identifier, token.identifier),
218
+ eq(verificationTokens.token, token.token)
219
+ )
220
+ )
221
+ .then((res) => res[0])) ?? null
222
+
223
+ await client
224
+ .delete(verificationTokens)
225
+ .where(
226
+ and(
227
+ eq(verificationTokens.identifier, token.identifier),
228
+ eq(verificationTokens.token, token.token)
229
+ )
230
+ )
231
+
232
+ return deletedToken
233
+ } catch (err) {
234
+ throw new Error("No verification token found.")
235
+ }
236
+ },
237
+ deleteUser: async (id) => {
238
+ const user = await client
239
+ .select()
240
+ .from(users)
241
+ .where(eq(users.id, id))
242
+ .then((res) => res[0] ?? null)
243
+
244
+ await client.delete(users).where(eq(users.id, id))
245
+
246
+ return user
247
+ },
248
+ unlinkAccount: async (account) => {
249
+ await client
250
+ .delete(accounts)
251
+ .where(
252
+ and(
253
+ eq(accounts.providerAccountId, account.providerAccountId),
254
+ eq(accounts.provider, account.provider)
255
+ )
256
+ )
257
+
258
+ return undefined
259
+ },
260
+ }
261
+ }
@@ -0,0 +1,231 @@
1
+ import {
2
+ timestamp,
3
+ pgTable,
4
+ text,
5
+ primaryKey,
6
+ integer,
7
+ } from "drizzle-orm/pg-core"
8
+ import { PostgresJsDatabase } from "drizzle-orm/postgres-js"
9
+ import { Adapter, AdapterAccount } from "@auth/core/adapters"
10
+ import { and, eq } from "drizzle-orm"
11
+ import crypto from "node:crypto"
12
+
13
+ /** @internal */
14
+ export const users = pgTable("users", {
15
+ id: text("id").notNull().primaryKey(),
16
+ name: text("name"),
17
+ email: text("email").notNull(),
18
+ emailVerified: timestamp("emailVerified", { mode: "date" }),
19
+ image: text("image"),
20
+ })
21
+
22
+ /** @internal */
23
+ export const accounts = pgTable(
24
+ "accounts",
25
+ {
26
+ userId: text("userId")
27
+ .notNull()
28
+ .references(() => users.id, { onDelete: "cascade" }),
29
+ type: text("type").$type<AdapterAccount["type"]>().notNull(),
30
+ provider: text("provider").notNull(),
31
+ providerAccountId: text("providerAccountId").notNull(),
32
+ refresh_token: text("refresh_token"),
33
+ access_token: text("access_token"),
34
+ expires_at: integer("expires_at"),
35
+ token_type: text("token_type"),
36
+ scope: text("scope"),
37
+ id_token: text("id_token"),
38
+ session_state: text("session_state"),
39
+ },
40
+ (account) => ({
41
+ compoundKey: primaryKey(account.provider, account.providerAccountId),
42
+ })
43
+ )
44
+
45
+ /** @internal */
46
+ export const sessions = pgTable("sessions", {
47
+ sessionToken: text("sessionToken").notNull().primaryKey(),
48
+ userId: text("userId")
49
+ .notNull()
50
+ .references(() => users.id, { onDelete: "cascade" }),
51
+ expires: timestamp("expires", { mode: "date" }).notNull(),
52
+ })
53
+
54
+ /** @internal */
55
+ export const verificationTokens = pgTable(
56
+ "verificationToken",
57
+ {
58
+ identifier: text("identifier").notNull(),
59
+ token: text("token").notNull(),
60
+ expires: timestamp("expires", { mode: "date" }).notNull(),
61
+ },
62
+ (vt) => ({
63
+ compoundKey: primaryKey(vt.identifier, vt.token),
64
+ })
65
+ )
66
+
67
+ /** @internal */
68
+ export const schema = { users, accounts, sessions, verificationTokens }
69
+ export type DefaultSchema = typeof schema
70
+
71
+ /** @internal */
72
+ export function pgDrizzleAdapter(
73
+ client: PostgresJsDatabase<Record<string, never>>
74
+ ): Adapter {
75
+ return {
76
+ createUser: async (data) => {
77
+ return await client
78
+ .insert(users)
79
+ .values({ ...data, id: crypto.randomUUID() })
80
+ .returning()
81
+ .then((res) => res[0] ?? null)
82
+ },
83
+ getUser: async (data) => {
84
+ return await client
85
+ .select()
86
+ .from(users)
87
+ .where(eq(users.id, data))
88
+ .then((res) => res[0] ?? null)
89
+ },
90
+ getUserByEmail: async (data) => {
91
+ return await client
92
+ .select()
93
+ .from(users)
94
+ .where(eq(users.email, data))
95
+ .then((res) => res[0] ?? null)
96
+ },
97
+ createSession: async (data) => {
98
+ return await client
99
+ .insert(sessions)
100
+ .values(data)
101
+ .returning()
102
+ .then((res) => res[0])
103
+ },
104
+ getSessionAndUser: async (data) => {
105
+ return await client
106
+ .select({
107
+ session: sessions,
108
+ user: users,
109
+ })
110
+ .from(sessions)
111
+ .where(eq(sessions.sessionToken, data))
112
+ .innerJoin(users, eq(users.id, sessions.userId))
113
+ .then((res) => res[0] ?? null)
114
+ },
115
+ updateUser: async (data) => {
116
+ if (!data.id) {
117
+ throw new Error("No user id.")
118
+ }
119
+
120
+ return await client
121
+ .update(users)
122
+ .set(data)
123
+ .where(eq(users.id, data.id))
124
+ .returning()
125
+ .then((res) => res[0])
126
+ },
127
+ updateSession: async (data) => {
128
+ return await client
129
+ .update(sessions)
130
+ .set(data)
131
+ .where(eq(sessions.sessionToken, data.sessionToken))
132
+ .returning()
133
+ .then((res) => res[0])
134
+ },
135
+ linkAccount: async (rawAccount) => {
136
+ const updatedAccount = await client
137
+ .insert(accounts)
138
+ .values(rawAccount)
139
+ .returning()
140
+ .then((res) => res[0])
141
+
142
+ // Drizzle will return `null` for fields that are not defined.
143
+ // However, the return type is expecting `undefined`.
144
+ const account = {
145
+ ...updatedAccount,
146
+ access_token: updatedAccount.access_token ?? undefined,
147
+ token_type: updatedAccount.token_type ?? undefined,
148
+ id_token: updatedAccount.id_token ?? undefined,
149
+ refresh_token: updatedAccount.refresh_token ?? undefined,
150
+ scope: updatedAccount.scope ?? undefined,
151
+ expires_at: updatedAccount.expires_at ?? undefined,
152
+ session_state: updatedAccount.session_state ?? undefined,
153
+ }
154
+
155
+ return account
156
+ },
157
+ getUserByAccount: async (account) => {
158
+ const dbAccount =
159
+ (await client
160
+ .select()
161
+ .from(accounts)
162
+ .where(
163
+ and(
164
+ eq(accounts.providerAccountId, account.providerAccountId),
165
+ eq(accounts.provider, account.provider)
166
+ )
167
+ )
168
+ .leftJoin(users, eq(accounts.userId, users.id))
169
+ .then((res) => res[0])) ?? null
170
+
171
+ if (!dbAccount) {
172
+ return null
173
+ }
174
+
175
+ return dbAccount.users
176
+ },
177
+ deleteSession: async (sessionToken) => {
178
+ const session = await client
179
+ .delete(sessions)
180
+ .where(eq(sessions.sessionToken, sessionToken))
181
+ .returning()
182
+ .then((res) => res[0] ?? null)
183
+
184
+ return session
185
+ },
186
+ createVerificationToken: async (token) => {
187
+ return await client
188
+ .insert(verificationTokens)
189
+ .values(token)
190
+ .returning()
191
+ .then((res) => res[0])
192
+ },
193
+ useVerificationToken: async (token) => {
194
+ try {
195
+ return await client
196
+ .delete(verificationTokens)
197
+ .where(
198
+ and(
199
+ eq(verificationTokens.identifier, token.identifier),
200
+ eq(verificationTokens.token, token.token)
201
+ )
202
+ )
203
+ .returning()
204
+ .then((res) => res[0] ?? null)
205
+ } catch (err) {
206
+ throw new Error("No verification token found.")
207
+ }
208
+ },
209
+ deleteUser: async (id) => {
210
+ await client
211
+ .delete(users)
212
+ .where(eq(users.id, id))
213
+ .returning()
214
+ .then((res) => res[0] ?? null)
215
+ },
216
+ unlinkAccount: async (account) => {
217
+ const { type, provider, providerAccountId, userId } = await client
218
+ .delete(accounts)
219
+ .where(
220
+ and(
221
+ eq(accounts.providerAccountId, account.providerAccountId),
222
+ eq(accounts.provider, account.provider)
223
+ )
224
+ )
225
+ .returning()
226
+ .then((res) => res[0] ?? null)
227
+
228
+ return { provider, type, providerAccountId, userId }
229
+ },
230
+ }
231
+ }
@@ -0,0 +1,209 @@
1
+ import {
2
+ integer,
3
+ sqliteTable,
4
+ text,
5
+ primaryKey,
6
+ BaseSQLiteDatabase,
7
+ } from "drizzle-orm/sqlite-core"
8
+ import crypto from "node:crypto"
9
+ import { Adapter, AdapterAccount } from "@auth/core/adapters"
10
+ import { eq, and } from "drizzle-orm"
11
+
12
+ /** @internal */
13
+ export const users = sqliteTable("users", {
14
+ id: text("id").notNull().primaryKey(),
15
+ name: text("name"),
16
+ email: text("email").notNull(),
17
+ emailVerified: integer("emailVerified", { mode: "timestamp_ms" }),
18
+ image: text("image"),
19
+ })
20
+
21
+ /** @internal */
22
+ export const accounts = sqliteTable(
23
+ "accounts",
24
+ {
25
+ userId: text("userId")
26
+ .notNull()
27
+ .references(() => users.id, { onDelete: "cascade" }),
28
+ type: text("type").$type<AdapterAccount["type"]>().notNull(),
29
+ provider: text("provider").notNull(),
30
+ providerAccountId: text("providerAccountId").notNull(),
31
+ refresh_token: text("refresh_token"),
32
+ access_token: text("access_token"),
33
+ expires_at: integer("expires_at"),
34
+ token_type: text("token_type"),
35
+ scope: text("scope"),
36
+ id_token: text("id_token"),
37
+ session_state: text("session_state"),
38
+ },
39
+ (account) => ({
40
+ compoundKey: primaryKey(account.provider, account.providerAccountId),
41
+ })
42
+ )
43
+
44
+ /** @internal */
45
+ export const sessions = sqliteTable("sessions", {
46
+ sessionToken: text("sessionToken").notNull().primaryKey(),
47
+ userId: text("userId")
48
+ .notNull()
49
+ .references(() => users.id, { onDelete: "cascade" }),
50
+ expires: integer("expires", { mode: "timestamp_ms" }).notNull(),
51
+ })
52
+
53
+ /** @internal */
54
+ export const verificationTokens = sqliteTable(
55
+ "verificationToken",
56
+ {
57
+ identifier: text("identifier").notNull(),
58
+ token: text("token").notNull(),
59
+ expires: integer("expires", { mode: "timestamp_ms" }).notNull(),
60
+ },
61
+ (vt) => ({
62
+ compoundKey: primaryKey(vt.identifier, vt.token),
63
+ })
64
+ )
65
+
66
+ /** @internal */
67
+ export const schema = { users, accounts, sessions, verificationTokens }
68
+ export type DefaultSchema = typeof schema
69
+
70
+ /** @internal */
71
+ export function SQLiteDrizzleAdapter(
72
+ client: BaseSQLiteDatabase<any, any>
73
+ ): Adapter {
74
+ return {
75
+ createUser: (data) => {
76
+ return client
77
+ .insert(users)
78
+ .values({ ...data, id: crypto.randomUUID() })
79
+ .returning()
80
+ .get()
81
+ },
82
+ getUser: (data) => {
83
+ return client.select().from(users).where(eq(users.id, data)).get() ?? null
84
+ },
85
+ getUserByEmail: (data) => {
86
+ return (
87
+ client.select().from(users).where(eq(users.email, data)).get() ?? null
88
+ )
89
+ },
90
+ createSession: (data) => {
91
+ return client.insert(sessions).values(data).returning().get()
92
+ },
93
+ getSessionAndUser: (data) => {
94
+ return (
95
+ client
96
+ .select({
97
+ session: sessions,
98
+ user: users,
99
+ })
100
+ .from(sessions)
101
+ .where(eq(sessions.sessionToken, data))
102
+ .innerJoin(users, eq(users.id, sessions.userId))
103
+ .get() ?? null
104
+ )
105
+ },
106
+ updateUser: (data) => {
107
+ if (!data.id) {
108
+ throw new Error("No user id.")
109
+ }
110
+
111
+ return client
112
+ .update(users)
113
+ .set(data)
114
+ .where(eq(users.id, data.id))
115
+ .returning()
116
+ .get()
117
+ },
118
+ updateSession: (data) => {
119
+ return client
120
+ .update(sessions)
121
+ .set(data)
122
+ .where(eq(sessions.sessionToken, data.sessionToken))
123
+ .returning()
124
+ .get()
125
+ },
126
+ linkAccount: (rawAccount) => {
127
+ const updatedAccount = client
128
+ .insert(accounts)
129
+ .values(rawAccount)
130
+ .returning()
131
+ .get()
132
+
133
+ const account: AdapterAccount = {
134
+ ...updatedAccount,
135
+ type: updatedAccount.type,
136
+ access_token: updatedAccount.access_token ?? undefined,
137
+ token_type: updatedAccount.token_type ?? undefined,
138
+ id_token: updatedAccount.id_token ?? undefined,
139
+ refresh_token: updatedAccount.refresh_token ?? undefined,
140
+ scope: updatedAccount.scope ?? undefined,
141
+ expires_at: updatedAccount.expires_at ?? undefined,
142
+ session_state: updatedAccount.session_state ?? undefined,
143
+ }
144
+
145
+ return account
146
+ },
147
+ getUserByAccount: (account) => {
148
+ const results = client
149
+ .select()
150
+ .from(accounts)
151
+ .leftJoin(users, eq(users.id, accounts.userId))
152
+ .where(
153
+ and(
154
+ eq(accounts.provider, account.provider),
155
+ eq(accounts.providerAccountId, account.providerAccountId)
156
+ )
157
+ )
158
+ .get()
159
+
160
+ return results?.users ?? null
161
+ },
162
+ deleteSession: (sessionToken) => {
163
+ return (
164
+ client
165
+ .delete(sessions)
166
+ .where(eq(sessions.sessionToken, sessionToken))
167
+ .returning()
168
+ .get() ?? null
169
+ )
170
+ },
171
+ createVerificationToken: (token) => {
172
+ return client.insert(verificationTokens).values(token).returning().get()
173
+ },
174
+ useVerificationToken: (token) => {
175
+ try {
176
+ return (
177
+ client
178
+ .delete(verificationTokens)
179
+ .where(
180
+ and(
181
+ eq(verificationTokens.identifier, token.identifier),
182
+ eq(verificationTokens.token, token.token)
183
+ )
184
+ )
185
+ .returning()
186
+ .get() ?? null
187
+ )
188
+ } catch (err) {
189
+ throw new Error("No verification token found.")
190
+ }
191
+ },
192
+ deleteUser: (id) => {
193
+ return client.delete(users).where(eq(users.id, id)).returning().get()
194
+ },
195
+ unlinkAccount: (account) => {
196
+ client
197
+ .delete(accounts)
198
+ .where(
199
+ and(
200
+ eq(accounts.providerAccountId, account.providerAccountId),
201
+ eq(accounts.provider, account.provider)
202
+ )
203
+ )
204
+ .run()
205
+
206
+ return undefined
207
+ },
208
+ }
209
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,43 @@
1
+ import { AnyMySqlTable, MySqlDatabase } from "drizzle-orm/mysql-core"
2
+ import { AnyPgTable, PgDatabase } from "drizzle-orm/pg-core"
3
+ import { AnySQLiteTable, BaseSQLiteDatabase } from "drizzle-orm/sqlite-core"
4
+ import { DefaultSchema as PgSchema } from "./pg"
5
+ import { DefaultSchema as MySqlSchema } from "./mysql"
6
+ import { DefaultSchema as SQLiteSchema } from "./sqlite"
7
+
8
+ export type AnyMySqlDatabase = MySqlDatabase<any, any>
9
+ export type AnyPgDatabase = PgDatabase<any, any, any>
10
+ export type AnySQLiteDatabase = BaseSQLiteDatabase<any, any, any, any>
11
+
12
+ export interface MinimumSchema {
13
+ mysql: MySqlSchema & Record<string, AnyMySqlTable>
14
+ pg: PgSchema & Record<string, AnyPgTable>
15
+ sqlite: SQLiteSchema & Record<string, AnySQLiteTable>
16
+ }
17
+
18
+ export type SqlFlavorOptions =
19
+ | AnyMySqlDatabase
20
+ | AnyPgDatabase
21
+ | AnySQLiteDatabase
22
+
23
+ export type ClientFlavors<Flavor> = Flavor extends AnyMySqlDatabase
24
+ ? MinimumSchema["mysql"]
25
+ : Flavor extends AnyPgDatabase
26
+ ? MinimumSchema["pg"]
27
+ : Flavor extends AnySQLiteDatabase
28
+ ? MinimumSchema["sqlite"]
29
+ : never
30
+
31
+ export function isMySqlDatabase(
32
+ db: any
33
+ ): db is MySqlDatabase<any, any, any, any> {
34
+ return db instanceof MySqlDatabase
35
+ }
36
+
37
+ export function isPgDatabase(db: any): db is PgDatabase<any, any, any> {
38
+ return db instanceof PgDatabase
39
+ }
40
+
41
+ export function isSQLiteDatabase(db: any): db is AnySQLiteDatabase {
42
+ return db instanceof BaseSQLiteDatabase
43
+ }