feat(db): Drizzle DAL with withUser/asAdmin GUC wrapper (Phase 2)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
import 'server-only'
|
||||
import { sql } from 'drizzle-orm'
|
||||
import { userDb, adminDb, type UserDb, type AdminDb } from './index'
|
||||
|
||||
export type DrizzleTransaction = Parameters<Parameters<typeof userDb.transaction>[0]>[0]
|
||||
|
||||
/**
|
||||
* Runs fn inside a transaction with app.user_id SET LOCAL to userId.
|
||||
* All RLS policies read this GUC via app_current_user_id().
|
||||
* The GUC is LOCAL so it is automatically cleared when the transaction ends.
|
||||
*/
|
||||
export async function withUser<T>(
|
||||
userId: string,
|
||||
fn: (tx: DrizzleTransaction) => Promise<T>
|
||||
): Promise<T> {
|
||||
return userDb.transaction(async (tx) => {
|
||||
await tx.execute(sql`SELECT set_config('app.user_id', ${userId}, true)`)
|
||||
return fn(tx)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs fn against the admin (BYPASSRLS) pool.
|
||||
* Use only for: admin user CRUD, service operations that legitimately bypass RLS.
|
||||
* Never use for regular user data access.
|
||||
*/
|
||||
export async function asAdmin<T>(fn: (db: AdminDb) => Promise<T>): Promise<T> {
|
||||
return fn(adminDb)
|
||||
}
|
||||
Reference in New Issue
Block a user