@coursebuilder/adapter-drizzle 0.0.1

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 ADDED
@@ -0,0 +1,198 @@
1
+ import type { Adapter, AdapterAccount, AdapterSession, AdapterUser } from '@auth/core/adapters'
2
+ import { and, eq } from 'drizzle-orm'
3
+ import {
4
+ pgTable as defaultPgTableFn,
5
+ integer,
6
+ PgDatabase,
7
+ PgTableFn,
8
+ primaryKey,
9
+ text,
10
+ timestamp,
11
+ } from 'drizzle-orm/pg-core'
12
+
13
+ import { stripUndefined } from './utils.js'
14
+
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
+ })
23
+
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
+ )
45
+
46
+ const sessions = pgTable('session', {
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
+ 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
+ }
68
+
69
+ export type DefaultSchema = ReturnType<typeof createTables>
70
+
71
+ export function pgDrizzleAdapter(client: InstanceType<typeof PgDatabase>, tableFn = defaultPgTableFn): Adapter {
72
+ const { users, accounts, sessions, verificationTokens } = createTables(tableFn)
73
+
74
+ return {
75
+ async createUser(data) {
76
+ return await client
77
+ .insert(users)
78
+ .values({ ...data, id: crypto.randomUUID() })
79
+ .returning()
80
+ .then((res) => (res[0] as AdapterUser) ?? null)
81
+ },
82
+ async getUser(data) {
83
+ return await client
84
+ .select()
85
+ .from(users)
86
+ .where(eq(users.id, data))
87
+ .then((res) => res[0] ?? null)
88
+ },
89
+ async getUserByEmail(data) {
90
+ return await client
91
+ .select()
92
+ .from(users)
93
+ .where(eq(users.email, data))
94
+ .then((res) => res[0] ?? null)
95
+ },
96
+ async createSession(data) {
97
+ return await client
98
+ .insert(sessions)
99
+ .values(data)
100
+ .returning()
101
+ .then((res) => res[0] as AdapterSession)
102
+ },
103
+ async getSessionAndUser(data) {
104
+ return await client
105
+ .select({
106
+ session: sessions,
107
+ user: users,
108
+ })
109
+ .from(sessions)
110
+ .where(eq(sessions.sessionToken, data))
111
+ .innerJoin(users, eq(users.id, sessions.userId))
112
+ .then((res) => res[0] ?? null)
113
+ },
114
+ async updateUser(data) {
115
+ if (!data.id) {
116
+ throw new Error('No user id.')
117
+ }
118
+
119
+ return await client
120
+ .update(users)
121
+ .set(data)
122
+ .where(eq(users.id, data.id))
123
+ .returning()
124
+ .then((res) => res[0] as AdapterUser)
125
+ },
126
+ async updateSession(data) {
127
+ return await client
128
+ .update(sessions)
129
+ .set(data)
130
+ .where(eq(sessions.sessionToken, data.sessionToken))
131
+ .returning()
132
+ .then((res) => res[0])
133
+ },
134
+ async linkAccount(rawAccount) {
135
+ await client
136
+ .insert(accounts)
137
+ .values(rawAccount)
138
+ .returning()
139
+ .then((res) => res[0])
140
+ },
141
+ async getUserByAccount(account) {
142
+ const dbAccount =
143
+ (await client
144
+ .select()
145
+ .from(accounts)
146
+ .where(
147
+ and(eq(accounts.providerAccountId, account.providerAccountId), eq(accounts.provider, account.provider)),
148
+ )
149
+ .leftJoin(users, eq(accounts.userId, users.id))
150
+ .then((res) => res[0])) ?? null
151
+
152
+ return dbAccount?.user ?? null
153
+ },
154
+ async deleteSession(sessionToken) {
155
+ const session = await client
156
+ .delete(sessions)
157
+ .where(eq(sessions.sessionToken, sessionToken))
158
+ .returning()
159
+ .then((res) => res[0] ?? null)
160
+
161
+ return session
162
+ },
163
+ async createVerificationToken(token) {
164
+ return await client
165
+ .insert(verificationTokens)
166
+ .values(token)
167
+ .returning()
168
+ .then((res) => res[0])
169
+ },
170
+ async useVerificationToken(token) {
171
+ try {
172
+ return await client
173
+ .delete(verificationTokens)
174
+ .where(and(eq(verificationTokens.identifier, token.identifier), eq(verificationTokens.token, token.token)))
175
+ .returning()
176
+ .then((res) => res[0] ?? null)
177
+ } catch (err) {
178
+ throw new Error('No verification token found.')
179
+ }
180
+ },
181
+ async deleteUser(id) {
182
+ await client
183
+ .delete(users)
184
+ .where(eq(users.id, id))
185
+ .returning()
186
+ .then((res) => res[0] ?? null)
187
+ },
188
+ async unlinkAccount(account) {
189
+ const { type, provider, providerAccountId, userId } = await client
190
+ .delete(accounts)
191
+ .where(and(eq(accounts.providerAccountId, account.providerAccountId), eq(accounts.provider, account.provider)))
192
+ .returning()
193
+ .then((res) => (res[0] as AdapterAccount) ?? null)
194
+
195
+ return { provider, type, providerAccountId, userId }
196
+ },
197
+ }
198
+ }
@@ -0,0 +1,168 @@
1
+ import type { Adapter, AdapterAccount } from '@auth/core/adapters'
2
+ import { and, eq } from 'drizzle-orm'
3
+ import {
4
+ BaseSQLiteDatabase,
5
+ sqliteTable as defaultSqliteTableFn,
6
+ integer,
7
+ primaryKey,
8
+ SQLiteTableFn,
9
+ text,
10
+ } from 'drizzle-orm/sqlite-core'
11
+
12
+ import { stripUndefined } from './utils.js'
13
+
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
+
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
+ )
44
+
45
+ const sessions = sqliteTable('session', {
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
+ 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>
69
+
70
+ export function SQLiteDrizzleAdapter(
71
+ client: InstanceType<typeof BaseSQLiteDatabase>,
72
+ tableFn = defaultSqliteTableFn,
73
+ ): Adapter {
74
+ const { users, accounts, sessions, verificationTokens } = createTables(tableFn)
75
+
76
+ return {
77
+ async createUser(data) {
78
+ return client
79
+ .insert(users)
80
+ .values({ ...data, id: crypto.randomUUID() })
81
+ .returning()
82
+ .get()
83
+ },
84
+ async getUser(data) {
85
+ const result = await client.select().from(users).where(eq(users.id, data)).get()
86
+ return result ?? null
87
+ },
88
+ async getUserByEmail(data) {
89
+ const result = await client.select().from(users).where(eq(users.email, data)).get()
90
+ return result ?? null
91
+ },
92
+ createSession(data) {
93
+ return client.insert(sessions).values(data).returning().get()
94
+ },
95
+ async getSessionAndUser(data) {
96
+ const result = await client
97
+ .select({ session: sessions, user: users })
98
+ .from(sessions)
99
+ .where(eq(sessions.sessionToken, data))
100
+ .innerJoin(users, eq(users.id, sessions.userId))
101
+ .get()
102
+ return result ?? null
103
+ },
104
+ async updateUser(data) {
105
+ if (!data.id) {
106
+ throw new Error('No user id.')
107
+ }
108
+
109
+ const result = await client.update(users).set(data).where(eq(users.id, data.id)).returning().get()
110
+ return result ?? null
111
+ },
112
+ async updateSession(data) {
113
+ const result = await client
114
+ .update(sessions)
115
+ .set(data)
116
+ .where(eq(sessions.sessionToken, data.sessionToken))
117
+ .returning()
118
+ .get()
119
+ return result ?? null
120
+ },
121
+ async linkAccount(rawAccount) {
122
+ return stripUndefined(await client.insert(accounts).values(rawAccount).returning().get())
123
+ },
124
+ async getUserByAccount(account) {
125
+ const results = await client
126
+ .select()
127
+ .from(accounts)
128
+ .leftJoin(users, eq(users.id, accounts.userId))
129
+ .where(and(eq(accounts.provider, account.provider), eq(accounts.providerAccountId, account.providerAccountId)))
130
+ .get()
131
+
132
+ if (!results) {
133
+ return null
134
+ }
135
+ return Promise.resolve(results).then((results) => results.user)
136
+ },
137
+ async deleteSession(sessionToken) {
138
+ const result = await client.delete(sessions).where(eq(sessions.sessionToken, sessionToken)).returning().get()
139
+ return result ?? null
140
+ },
141
+ async createVerificationToken(token) {
142
+ const result = await client.insert(verificationTokens).values(token).returning().get()
143
+ return result ?? null
144
+ },
145
+ async useVerificationToken(token) {
146
+ try {
147
+ const result = await client
148
+ .delete(verificationTokens)
149
+ .where(and(eq(verificationTokens.identifier, token.identifier), eq(verificationTokens.token, token.token)))
150
+ .returning()
151
+ .get()
152
+ return result ?? null
153
+ } catch (err) {
154
+ throw new Error('No verification token found.')
155
+ }
156
+ },
157
+ async deleteUser(id) {
158
+ const result = await client.delete(users).where(eq(users.id, id)).returning().get()
159
+ return result ?? null
160
+ },
161
+ async unlinkAccount(account) {
162
+ await client
163
+ .delete(accounts)
164
+ .where(and(eq(accounts.providerAccountId, account.providerAccountId), eq(accounts.provider, account.provider)))
165
+ .run()
166
+ },
167
+ }
168
+ }
@@ -0,0 +1,48 @@
1
+ import { MySqlDatabase } from 'drizzle-orm/mysql-core'
2
+ import type { AnyMySqlTable, MySqlTableFn } from 'drizzle-orm/mysql-core'
3
+ import { PgDatabase } from 'drizzle-orm/pg-core'
4
+ import type { AnyPgTable, PgTableFn } from 'drizzle-orm/pg-core'
5
+ import { BaseSQLiteDatabase } from 'drizzle-orm/sqlite-core'
6
+ import type { AnySQLiteTable, SQLiteTableFn } from 'drizzle-orm/sqlite-core'
7
+
8
+ import type { DefaultSchema as MySqlSchema } from './mysql.js'
9
+ import type { DefaultSchema as PgSchema } from './pg.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
+ }
21
+
22
+ export type SqlFlavorOptions = AnyMySqlDatabase | AnyPgDatabase | AnySQLiteDatabase
23
+
24
+ export type ClientFlavors<Flavor> = Flavor extends AnyMySqlDatabase
25
+ ? MinimumSchema['mysql']
26
+ : Flavor extends AnyPgDatabase
27
+ ? MinimumSchema['pg']
28
+ : Flavor extends AnySQLiteDatabase
29
+ ? MinimumSchema['sqlite']
30
+ : never
31
+
32
+ export type TableFn<Flavor> = Flavor extends AnyMySqlDatabase
33
+ ? MySqlTableFn
34
+ : Flavor extends AnyPgDatabase
35
+ ? PgTableFn
36
+ : Flavor extends AnySQLiteDatabase
37
+ ? SQLiteTableFn
38
+ : AnySQLiteTable
39
+
40
+ type NonNullableProps<T> = {
41
+ [P in keyof T]: null extends T[P] ? never : P
42
+ }[keyof T]
43
+
44
+ export function stripUndefined<T>(obj: T): Pick<T, NonNullableProps<T>> {
45
+ const result = {} as T
46
+ for (const key in obj) if (obj[key] !== undefined) result[key] = obj[key]
47
+ return result
48
+ }