Better Auth adapter for @effect/sql
npm install better-auth-effectA Better Auth database adapter for @effect/sql.
Use any Effect SQL provider (PostgreSQL, MySQL, SQLite) with Better Auth.
``bash`
npm install better-auth-effector
pnpm add better-auth-effect
`bash`
npm install better-auth effect @effect/sql
And one of the database drivers:
`bashPostgreSQL
npm install @effect/sql-pg
Usage
`typescript
import { ManagedRuntime } from "effect"
import { PgClient } from "@effect/sql-pg"
import { effectSqlAdapter } from "better-auth-effect"
import { betterAuth } from "better-auth"// 1. Create your database Layer
const SqlLive = PgClient.layer({
url: "postgresql://postgres:password@localhost:5432/myapp"
})
// Or with individual options:
// const SqlLive = PgClient.layer({
// host: "localhost",
// database: "myapp",
// username: "postgres",
// password: "password",
// })
// 2. Create a ManagedRuntime
const runtime = ManagedRuntime.make(SqlLive)
// 3. Use the adapter with Better Auth
export const auth = betterAuth({
database: effectSqlAdapter({
runtime,
dialect: "pg",
}),
// ... other Better Auth options
})
`Configuration
`typescript
interface EffectSqlAdapterConfig {
/**
* ManagedRuntime that provides SqlClient.
* Create with ManagedRuntime.make(YourSqlLayer)
*/
runtime: ManagedRuntime.ManagedRuntime /**
* Database dialect for SQL differences (RETURNING clause, etc.)
* - "pg": PostgreSQL
* - "mysql": MySQL
* - "sqlite": SQLite
*/
dialect: "pg" | "mysql" | "sqlite"
/**
* Enable debug logging for adapter operations.
* @default false
*/
debugLogs?: boolean
}
`Why ManagedRuntime?
Using
ManagedRuntime allows you to share the same connection pool between Better Auth and your Effect application code:`typescript
import { Effect, ManagedRuntime } from "effect"
import { SqlClient } from "@effect/sql"
import { PgClient } from "@effect/sql-pg"// Single Layer for your entire app
const SqlLive = PgClient.layer({ database: "myapp" })
// Single Runtime - shared connection pool
const runtime = ManagedRuntime.make(SqlLive)
// Better Auth uses the same pool
const auth = betterAuth({
database: effectSqlAdapter({ runtime, dialect: "pg" }),
})
// Your app code uses the same pool
const getUsers = Effect.gen(function* () {
const sql = yield* SqlClient.SqlClient
return yield sql
SELECT FROM users
})// Run with the same runtime
runtime.runPromise(getUsers)
`Database Support
| Database | Dialect | RETURNING Support |
|------------|------------|-------------------|
| PostgreSQL |
"pg" | Native |
| SQLite | "sqlite" | Native (3.35+) |
| MySQL | "mysql" | Emulated* |\* MySQL doesn't support
RETURNING. The adapter uses LAST_INSERT_ID() + SELECT as a fallback. Tables must have an id column as primary key. This is not a problem for Better Auth, which always uses id.Column Naming (snake_case / camelCase)
Better Auth uses camelCase field names (
emailVerified, accessToken). If your database uses snake_case columns (email_verified, access_token), configure transformation in PgClient.layer:`typescript
import { PgClient } from "@effect/sql-pg"const snakeToCamel = (str: string) =>
str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase())
const camelToSnake = (str: string) =>
str.replace(/[A-Z]/g, (letter) =>
_${letter.toLowerCase()})const SqlLive = PgClient.layer({
database: "myapp",
// Transform column names automatically
transformResultNames: snakeToCamel, // DB → JS (snake_case → camelCase)
transformQueryNames: camelToSnake, // JS → DB (camelCase → snake_case)
})
`This is configured once at the client level and applies to all queries automatically.
Supported Operations
All Better Auth adapter methods are implemented:
-
create - Insert with RETURNING
- findOne - SELECT with WHERE, LIMIT 1
- findMany - SELECT with WHERE, ORDER BY, LIMIT, OFFSET
- update - UPDATE with RETURNING
- updateMany - UPDATE multiple rows
- delete - DELETE single row
- deleteMany - DELETE multiple rows
- count - SELECT COUNT(*)Error Handling
The adapter maps SQL errors to typed errors:
`typescript
import {
AdapterError,
ConstraintViolationError,
ConnectionError
} from "better-auth-effect"
``- ConstraintViolationError - Unique/foreign key violations
- ConnectionError - Database connection issues
- AdapterError - Other SQL errors
MIT