|
import { Kysely, SqliteAdapter } from 'kysely' |
|
|
|
export function KyselyAdapter(db: Kysely<any>): any { |
|
const { adapter } = db.getExecutor() |
|
const { supportsReturning } = adapter |
|
const isSqlite = adapter instanceof SqliteAdapter |
|
/** If the database is SQLite, turn dates into an ISO string */ |
|
const to = isSqlite ? format.to : <T>(x: T) => x as T |
|
/** If the database is SQLite, turn ISO strings into dates */ |
|
const from = isSqlite ? format.from : <T>(x: T) => x as T |
|
return { |
|
async createUser(data) { |
|
const user = { ...data, id: crypto.randomUUID() } |
|
const userData = to(user) |
|
|
|
await db |
|
.insertInto('users') |
|
.values({ |
|
id: userData.id, |
|
name: userData.name, |
|
email: userData.email, |
|
image: userData.image, |
|
email_verified: userData.emailVerified, |
|
}) |
|
.executeTakeFirstOrThrow() |
|
return user |
|
}, |
|
|
|
async getUser(id) { |
|
const result = await db |
|
.selectFrom('users') |
|
.selectAll() |
|
.where('id', '=', id) |
|
.executeTakeFirst() |
|
if (!result) return null |
|
return from(result) |
|
}, |
|
|
|
async getUserByEmail(email) { |
|
const result = await db |
|
.selectFrom('users') |
|
.selectAll() |
|
.where('email', '=', email) |
|
.executeTakeFirst() |
|
if (!result) return null |
|
return from(result) |
|
}, |
|
|
|
async getUserByAccount({ providerAccountId, provider }) { |
|
const result = await db |
|
.selectFrom('users') |
|
.innerJoin('accounts', 'users.id', 'accounts.user_id') |
|
.selectAll('users') |
|
.where('accounts.provider_account_id', '=', providerAccountId) |
|
.where('accounts.provider', '=', provider) |
|
.executeTakeFirst() |
|
if (!result) return null |
|
return from(result) |
|
}, |
|
|
|
async updateUser({ id, ...user }) { |
|
const userData = to(user) as any |
|
const query = db.updateTable('users').set(userData).where('id', '=', id) |
|
const result = supportsReturning |
|
? query.returningAll().executeTakeFirstOrThrow() |
|
: query |
|
.executeTakeFirstOrThrow() |
|
.then(() => |
|
db |
|
.selectFrom('users') |
|
.selectAll() |
|
.where('id', '=', id) |
|
.executeTakeFirstOrThrow(), |
|
) |
|
return from(await result) |
|
}, |
|
|
|
async deleteUser(userId) { |
|
await db |
|
.deleteFrom('users') |
|
.where('users.id', '=', userId) |
|
.executeTakeFirst() |
|
}, |
|
|
|
async linkAccount(account) { |
|
const accountData = to(account) |
|
|
|
await db |
|
.insertInto('accounts') |
|
.values({ |
|
user_id: accountData.userId, |
|
type: accountData.type, |
|
provider: accountData.provider, |
|
provider_account_id: accountData.providerAccountId, |
|
access_token: accountData.access_token, |
|
expires_at: accountData.expires_at, |
|
token_type: accountData.token_type, |
|
scope: accountData.scope, |
|
id_token: accountData.id_token, |
|
}) |
|
.executeTakeFirstOrThrow() |
|
|
|
return account |
|
}, |
|
|
|
async unlinkAccount({ providerAccountId, provider }) { |
|
await db |
|
.deleteFrom('accounts') |
|
.where('accounts.provider_account_id', '=', providerAccountId) |
|
.where('accounts.provider', '=', provider) |
|
.executeTakeFirstOrThrow() |
|
}, |
|
|
|
async createSession(session) { |
|
const sessionData = to(session) |
|
|
|
await db |
|
.insertInto('sessions') |
|
.values({ |
|
user_id: sessionData.userId, |
|
session_token: sessionData.sessionToken, |
|
expires: sessionData.expires, |
|
}) |
|
.execute() |
|
|
|
return session |
|
}, |
|
|
|
async getSessionAndUser(session_token) { |
|
const result = await db |
|
.selectFrom('sessions') |
|
.innerJoin('users', 'users.id', 'sessions.user_id') |
|
.selectAll('users') |
|
.select(['sessions.expires', 'sessions.user_id']) |
|
.where('sessions.session_token', '=', session_token) |
|
.executeTakeFirst() |
|
|
|
if (!result) return null |
|
|
|
const { user_id, expires, ...user } = result |
|
const session = { session_token, user_id, expires } |
|
|
|
return { user: from(user), session: from(session) } |
|
}, |
|
|
|
async updateSession(session) { |
|
const sessionData = to(session) |
|
|
|
const query = db |
|
.updateTable('sessions') |
|
.set({ |
|
user_id: sessionData.userId, |
|
session_token: sessionData.sessionToken, |
|
expires: sessionData.expires, |
|
}) |
|
.where('sessions.session_token', '=', session.sessionToken) |
|
|
|
const result = supportsReturning |
|
? await query.returningAll().executeTakeFirstOrThrow() |
|
: await query.executeTakeFirstOrThrow().then(async () => { |
|
return await db |
|
.selectFrom('sessions') |
|
.selectAll() |
|
.where('sessions.session_token', '=', sessionData.sessionToken) |
|
.executeTakeFirstOrThrow() |
|
}) |
|
|
|
return from(result) |
|
}, |
|
|
|
async deleteSession(sessionToken) { |
|
await db |
|
.deleteFrom('sessions') |
|
.where('sessions.session_token', '=', sessionToken) |
|
.executeTakeFirstOrThrow() |
|
}, |
|
|
|
async createVerificationToken(data) { |
|
await db.insertInto('verification_tokens').values(to(data)).execute() |
|
return data |
|
}, |
|
|
|
async useVerificationToken({ identifier, token }) { |
|
const query = db |
|
.deleteFrom('verification_tokens') |
|
.where('verification_tokens.token', '=', token) |
|
.where('verification_tokens.identifier', '=', identifier) |
|
|
|
const result = supportsReturning |
|
? await query.returningAll().executeTakeFirst() |
|
: await db |
|
.selectFrom('verification_tokens') |
|
.selectAll() |
|
.where('token', '=', token) |
|
.executeTakeFirst() |
|
.then(async res => { |
|
await query.executeTakeFirst() |
|
return res |
|
}) |
|
|
|
if (!result) return null |
|
|
|
return from(result) |
|
}, |
|
} |
|
} |
|
|
|
// import { Users } from '../db/schema/public/Users' |
|
// import { Accounts } from '../db/schema/public/Accounts' |
|
// import { Sessions } from '../db/schema/public/Sessions' |
|
// import { VerificationTokens } from '../db/schema/public/VerificationTokens' |
|
|
|
// export interface Database { |
|
// users: Users |
|
// accounts: Accounts |
|
// sessions: Sessions |
|
// verification_tokens: VerificationTokens |
|
// } |
|
|
|
// https://github.com/honeinc/is-iso-date/blob/8831e79b5b5ee615920dcb350a355ffc5cbf7aed/index.js#L5 |
|
const isoDateRE = |
|
/(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))/ |
|
|
|
const isDate = (val: any): val is ConstructorParameters<typeof Date>[0] => |
|
!!(val && isoDateRE.test(val) && !isNaN(Date.parse(val))) |
|
|
|
export const format = { |
|
from<T>(object?: Record<string, any>): T { |
|
const newObject: Record<string, unknown> = {} |
|
for (const key in object) { |
|
const value = object[key] |
|
if (isDate(value)) newObject[key] = new Date(value) |
|
else newObject[key] = value |
|
} |
|
return newObject as T |
|
}, |
|
to<T>(object: Record<string, any>): T { |
|
const newObject: Record<string, unknown> = {} |
|
for (const [key, value] of Object.entries(object)) |
|
newObject[key] = value instanceof Date ? value.toISOString() : value |
|
return newObject as T |
|
}, |
|
} |