@auth/drizzle-adapter 0.8.2 → 1.0.0

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/src/lib/sqlite.ts CHANGED
@@ -1,211 +1,253 @@
1
1
  import { eq, and } from "drizzle-orm"
2
2
  import {
3
3
  integer,
4
- sqliteTable as defaultSqliteTableFn,
5
4
  text,
6
5
  primaryKey,
7
6
  BaseSQLiteDatabase,
8
- SQLiteTableFn,
7
+ sqliteTable,
8
+ index,
9
+ TableConfig,
10
+ SQLiteTableWithColumns,
9
11
  } from "drizzle-orm/sqlite-core"
10
- import { stripUndefined } from "./utils.js"
11
12
 
12
- import type { Adapter, AdapterAccount } from "@auth/core/adapters"
13
+ import type {
14
+ Adapter,
15
+ AdapterAccount,
16
+ AdapterUser,
17
+ AdapterSession,
18
+ VerificationToken,
19
+ } from "@auth/core/adapters"
20
+ import { randomUUID } from "crypto"
13
21
 
14
- export function createTables(sqliteTable: SQLiteTableFn) {
15
- const users = sqliteTable("user", {
16
- id: text("id").notNull().primaryKey(),
17
- name: text("name"),
18
- email: text("email").notNull(),
19
- emailVerified: integer("emailVerified", { mode: "timestamp_ms" }),
20
- image: text("image"),
21
- })
22
+ export const sqliteUsersTable = sqliteTable("user" as string, {
23
+ id: text("id")
24
+ .primaryKey()
25
+ .$defaultFn(() => randomUUID()),
26
+ name: text("name"),
27
+ email: text("email").notNull().unique(),
28
+ emailVerified: integer("emailVerified", { mode: "timestamp_ms" }),
29
+ image: text("image"),
30
+ })
22
31
 
23
- const accounts = sqliteTable(
24
- "account",
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
- )
32
+ export const sqliteAccountsTable = sqliteTable(
33
+ "account" as string,
34
+ {
35
+ userId: text("userId")
36
+ .notNull()
37
+ .references(() => sqliteUsersTable.id, { onDelete: "cascade" }),
38
+ type: text("type").notNull(),
39
+ provider: text("provider").notNull(),
40
+ providerAccountId: text("providerAccountId").notNull(),
41
+ refresh_token: text("refresh_token"),
42
+ access_token: text("access_token"),
43
+ expires_at: integer("expires_at"),
44
+ token_type: text("token_type"),
45
+ scope: text("scope"),
46
+ id_token: text("id_token"),
47
+ session_state: text("session_state"),
48
+ },
49
+ (account) => ({
50
+ userIdIdx: index("Account_userId_index").on(account.userId),
51
+ compositePk: primaryKey({
52
+ columns: [account.provider, account.providerAccountId],
53
+ }),
54
+ })
55
+ )
44
56
 
45
- const sessions = sqliteTable("session", {
46
- sessionToken: text("sessionToken").notNull().primaryKey(),
57
+ export const sqliteSessionsTable = sqliteTable(
58
+ "session" as string,
59
+ {
60
+ id: text("id")
61
+ .primaryKey()
62
+ .$defaultFn(() => randomUUID()),
63
+ sessionToken: text("sessionToken").notNull().unique(),
47
64
  userId: text("userId")
48
65
  .notNull()
49
- .references(() => users.id, { onDelete: "cascade" }),
66
+ .references(() => sqliteUsersTable.id, { onDelete: "cascade" }),
50
67
  expires: integer("expires", { mode: "timestamp_ms" }).notNull(),
68
+ },
69
+ (table) => ({
70
+ userIdIdx: index("Session_userId_index").on(table.userId),
51
71
  })
72
+ )
52
73
 
53
- const verificationTokens = sqliteTable(
54
- "verificationToken",
55
- {
56
- identifier: text("identifier").notNull(),
57
- token: text("token").notNull(),
58
- expires: integer("expires", { mode: "timestamp_ms" }).notNull(),
59
- },
60
- (vt) => ({
61
- compoundKey: primaryKey(vt.identifier, vt.token),
62
- })
63
- )
64
-
65
- return { users, accounts, sessions, verificationTokens }
66
- }
67
-
68
- export type DefaultSchema = ReturnType<typeof createTables>
74
+ export const sqliteVerificationTokensTable = sqliteTable(
75
+ "verificationToken" as string,
76
+ {
77
+ identifier: text("identifier").notNull(),
78
+ token: text("token").notNull().unique(),
79
+ expires: integer("expires", { mode: "timestamp_ms" }).notNull(),
80
+ },
81
+ (vt) => ({
82
+ compositePk: primaryKey({ columns: [vt.identifier, vt.token] }),
83
+ })
84
+ )
69
85
 
70
86
  export function SQLiteDrizzleAdapter(
71
- client: InstanceType<typeof BaseSQLiteDatabase>,
72
- tableFn = defaultSqliteTableFn
87
+ client: BaseSQLiteDatabase<"sync" | "async", any, any>,
88
+ schema: DefaultSQLiteSchema = {
89
+ usersTable: sqliteUsersTable,
90
+ accountsTable: sqliteAccountsTable,
91
+ sessionsTable: sqliteSessionsTable,
92
+ verificationTokensTable: sqliteVerificationTokensTable,
93
+ }
73
94
  ): Adapter {
74
- const { users, accounts, sessions, verificationTokens } =
75
- createTables(tableFn)
95
+ const { usersTable, accountsTable, sessionsTable, verificationTokensTable } =
96
+ schema
76
97
 
77
98
  return {
78
- async createUser(data) {
79
- return await client
80
- .insert(users)
81
- .values({ ...data, id: crypto.randomUUID() })
82
- .returning()
83
- .get()
99
+ async createUser(data: Omit<AdapterUser, "id">) {
100
+ return client.insert(usersTable).values(data).returning().get()
84
101
  },
85
- async getUser(data) {
102
+ async getUser(userId: string) {
86
103
  const result = await client
87
104
  .select()
88
- .from(users)
89
- .where(eq(users.id, data))
105
+ .from(usersTable)
106
+ .where(eq(usersTable.id, userId))
90
107
  .get()
108
+
91
109
  return result ?? null
92
110
  },
93
- async getUserByEmail(data) {
111
+ async getUserByEmail(email: string) {
94
112
  const result = await client
95
113
  .select()
96
- .from(users)
97
- .where(eq(users.email, data))
114
+ .from(usersTable)
115
+ .where(eq(usersTable.email, email))
98
116
  .get()
117
+
99
118
  return result ?? null
100
119
  },
101
- createSession(data) {
102
- return client.insert(sessions).values(data).returning().get()
120
+ async createSession(data: {
121
+ sessionToken: string
122
+ userId: string
123
+ expires: Date
124
+ }) {
125
+ return await client.insert(sessionsTable).values(data).returning().get()
103
126
  },
104
- async getSessionAndUser(data) {
127
+ async getSessionAndUser(sessionToken: string) {
105
128
  const result = await client
106
- .select({ session: sessions, user: users })
107
- .from(sessions)
108
- .where(eq(sessions.sessionToken, data))
109
- .innerJoin(users, eq(users.id, sessions.userId))
129
+ .select({
130
+ session: sessionsTable,
131
+ user: usersTable,
132
+ })
133
+ .from(sessionsTable)
134
+ .where(eq(sessionsTable.sessionToken, sessionToken))
135
+ .innerJoin(usersTable, eq(usersTable.id, sessionsTable.userId))
110
136
  .get()
137
+
111
138
  return result ?? null
112
139
  },
113
- async updateUser(data) {
140
+ async updateUser(data: Partial<AdapterUser> & Pick<AdapterUser, "id">) {
114
141
  if (!data.id) {
115
142
  throw new Error("No user id.")
116
143
  }
117
144
 
118
145
  const result = await client
119
- .update(users)
146
+ .update(usersTable)
120
147
  .set(data)
121
- .where(eq(users.id, data.id))
148
+ .where(eq(usersTable.id, data.id))
122
149
  .returning()
123
150
  .get()
124
- return result ?? null
151
+
152
+ if (!result) {
153
+ throw new Error("User not found.")
154
+ }
155
+
156
+ return result
125
157
  },
126
- async updateSession(data) {
158
+ async updateSession(
159
+ data: Partial<AdapterSession> & Pick<AdapterSession, "sessionToken">
160
+ ) {
127
161
  const result = await client
128
- .update(sessions)
162
+ .update(sessionsTable)
129
163
  .set(data)
130
- .where(eq(sessions.sessionToken, data.sessionToken))
164
+ .where(eq(sessionsTable.sessionToken, data.sessionToken))
131
165
  .returning()
132
166
  .get()
167
+
133
168
  return result ?? null
134
169
  },
135
- async linkAccount(rawAccount) {
136
- return stripUndefined(
137
- await client.insert(accounts).values(rawAccount).returning().get()
138
- )
170
+ async linkAccount(data: AdapterAccount) {
171
+ await client.insert(accountsTable).values(data).run()
139
172
  },
140
- async getUserByAccount(account) {
141
- const results = await client
142
- .select()
143
- .from(accounts)
144
- .leftJoin(users, eq(users.id, accounts.userId))
173
+ async getUserByAccount(
174
+ account: Pick<AdapterAccount, "provider" | "providerAccountId">
175
+ ) {
176
+ const result = await client
177
+ .select({
178
+ account: accountsTable,
179
+ user: usersTable,
180
+ })
181
+ .from(accountsTable)
182
+ .innerJoin(usersTable, eq(accountsTable.userId, usersTable.id))
145
183
  .where(
146
184
  and(
147
- eq(accounts.provider, account.provider),
148
- eq(accounts.providerAccountId, account.providerAccountId)
185
+ eq(accountsTable.provider, account.provider),
186
+ eq(accountsTable.providerAccountId, account.providerAccountId)
149
187
  )
150
188
  )
151
189
  .get()
152
190
 
153
- if (!results) {
154
- return null
155
- }
156
- return Promise.resolve(results).then((results) => results.user)
191
+ return result?.user ?? null
157
192
  },
158
- async deleteSession(sessionToken) {
159
- const result = await client
160
- .delete(sessions)
161
- .where(eq(sessions.sessionToken, sessionToken))
162
- .returning()
163
- .get()
164
- return result ?? null
193
+ async deleteSession(sessionToken: string) {
194
+ await client
195
+ .delete(sessionsTable)
196
+ .where(eq(sessionsTable.sessionToken, sessionToken))
197
+ .run()
165
198
  },
166
- async createVerificationToken(token) {
167
- const result = await client
168
- .insert(verificationTokens)
169
- .values(token)
199
+ async createVerificationToken(data: VerificationToken) {
200
+ return await client
201
+ .insert(verificationTokensTable)
202
+ .values(data)
170
203
  .returning()
171
204
  .get()
172
- return result ?? null
173
205
  },
174
- async useVerificationToken(token) {
175
- try {
176
- const result = await client
177
- .delete(verificationTokens)
178
- .where(
179
- and(
180
- eq(verificationTokens.identifier, token.identifier),
181
- eq(verificationTokens.token, token.token)
182
- )
183
- )
184
- .returning()
185
- .get()
186
- return result ?? null
187
- } catch (err) {
188
- throw new Error("No verification token found.")
189
- }
190
- },
191
- async deleteUser(id) {
206
+ async useVerificationToken(params: { identifier: string; token: string }) {
192
207
  const result = await client
193
- .delete(users)
194
- .where(eq(users.id, id))
208
+ .delete(verificationTokensTable)
209
+ .where(
210
+ and(
211
+ eq(verificationTokensTable.identifier, params.identifier),
212
+ eq(verificationTokensTable.token, params.token)
213
+ )
214
+ )
195
215
  .returning()
196
216
  .get()
217
+
197
218
  return result ?? null
198
219
  },
199
- async unlinkAccount(account) {
220
+ async deleteUser(id: string) {
221
+ await client.delete(usersTable).where(eq(usersTable.id, id)).run()
222
+ },
223
+ async unlinkAccount(
224
+ params: Pick<AdapterAccount, "provider" | "providerAccountId">
225
+ ) {
200
226
  await client
201
- .delete(accounts)
227
+ .delete(accountsTable)
202
228
  .where(
203
229
  and(
204
- eq(accounts.providerAccountId, account.providerAccountId),
205
- eq(accounts.provider, account.provider)
230
+ eq(accountsTable.provider, params.provider),
231
+ eq(accountsTable.providerAccountId, params.providerAccountId)
206
232
  )
207
233
  )
208
234
  .run()
209
235
  },
210
236
  }
211
237
  }
238
+
239
+ export type SQLiteTableFn<T extends TableConfig> = SQLiteTableWithColumns<{
240
+ name: T["name"]
241
+ columns: T["columns"]
242
+ dialect: T["dialect"]
243
+ schema: string | undefined
244
+ }>
245
+
246
+ export type DefaultSQLiteSchema = {
247
+ usersTable: SQLiteTableFn<(typeof sqliteUsersTable)["_"]["config"]>
248
+ accountsTable: SQLiteTableFn<(typeof sqliteAccountsTable)["_"]["config"]>
249
+ sessionsTable: SQLiteTableFn<(typeof sqliteSessionsTable)["_"]["config"]>
250
+ verificationTokensTable: SQLiteTableFn<
251
+ (typeof sqliteVerificationTokensTable)["_"]["config"]
252
+ >
253
+ }
package/src/lib/utils.ts CHANGED
@@ -1,51 +1,32 @@
1
1
  import { MySqlDatabase } from "drizzle-orm/mysql-core"
2
2
  import { PgDatabase } from "drizzle-orm/pg-core"
3
3
  import { BaseSQLiteDatabase } from "drizzle-orm/sqlite-core"
4
-
5
- import type { AnyMySqlTable, MySqlTableFn } from "drizzle-orm/mysql-core"
6
- import type { AnyPgTable, PgTableFn } from "drizzle-orm/pg-core"
7
- import type { AnySQLiteTable, SQLiteTableFn } from "drizzle-orm/sqlite-core"
8
- import type { DefaultSchema as PgSchema } from "./pg.js"
9
- import type { DefaultSchema as MySqlSchema } from "./mysql.js"
10
- import type { DefaultSchema as SQLiteSchema } from "./sqlite.js"
11
-
12
- export type AnyMySqlDatabase = MySqlDatabase<any, any>
13
- export type AnyPgDatabase = PgDatabase<any, any, any>
14
- export type AnySQLiteDatabase = BaseSQLiteDatabase<any, any, any, any>
15
-
16
- export interface MinimumSchema {
17
- mysql: MySqlSchema & Record<string, AnyMySqlTable>
18
- pg: PgSchema & Record<string, AnyPgTable>
19
- sqlite: SQLiteSchema & Record<string, AnySQLiteTable>
20
- }
4
+ import type {
5
+ QueryResultHKT as MySQLQueryResultHKT,
6
+ PreparedQueryHKTBase,
7
+ } from "drizzle-orm/mysql-core"
8
+ import type { QueryResultHKT as PostgresQueryResultHKT } from "drizzle-orm/pg-core"
9
+ import { DefaultSQLiteSchema } from "./sqlite"
10
+ import { DefaultPostgresSchema } from "./pg"
11
+ import { DefaultMySqlSchema } from "./mysql"
12
+
13
+ type AnyPostgresDatabase = PgDatabase<PostgresQueryResultHKT, any>
14
+ type AnyMySqlDatabase = MySqlDatabase<
15
+ MySQLQueryResultHKT,
16
+ PreparedQueryHKTBase,
17
+ any
18
+ >
19
+ type AnySQLiteDatabase = BaseSQLiteDatabase<"sync" | "async", any, any>
21
20
 
22
21
  export type SqlFlavorOptions =
22
+ | AnyPostgresDatabase
23
23
  | AnyMySqlDatabase
24
- | AnyPgDatabase
25
24
  | AnySQLiteDatabase
26
25
 
27
- export type ClientFlavors<Flavor> = Flavor extends AnyMySqlDatabase
28
- ? MinimumSchema["mysql"]
29
- : Flavor extends AnyPgDatabase
30
- ? MinimumSchema["pg"]
26
+ export type DefaultSchema<Flavor> = Flavor extends AnyMySqlDatabase
27
+ ? DefaultMySqlSchema
28
+ : Flavor extends AnyPostgresDatabase
29
+ ? DefaultPostgresSchema
31
30
  : Flavor extends AnySQLiteDatabase
32
- ? MinimumSchema["sqlite"]
31
+ ? DefaultSQLiteSchema
33
32
  : never
34
-
35
- export type TableFn<Flavor> = Flavor extends AnyMySqlDatabase
36
- ? MySqlTableFn
37
- : Flavor extends AnyPgDatabase
38
- ? PgTableFn
39
- : Flavor extends AnySQLiteDatabase
40
- ? SQLiteTableFn
41
- : AnySQLiteTable
42
-
43
- type NonNullableProps<T> = {
44
- [P in keyof T]: null extends T[P] ? never : P
45
- }[keyof T]
46
-
47
- export function stripUndefined<T>(obj: T): Pick<T, NonNullableProps<T>> {
48
- const result = {} as T
49
- for (const key in obj) if (obj[key] !== undefined) result[key] = obj[key]
50
- return result
51
- }