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