@evumi/better-auth-berth
v0.1.2
Published
Run better-auth on Berth: libSQL / bun:sqlite databases, Berth KV secondary storage, Worker handlers and S3.
Maintainers
Readme
@evumi/better-auth-berth
在 Berth 上运行 better-auth:数据库用环境绑定的 libSQL 或 Worker 本地 SQLite,secondaryStorage 用项目 KV(Valkey),再提供 Worker 路由挂载和对象存储客户端。
不重新实现数据库 adapter:better-auth 自带的 SQLite(Kysely)和 Drizzle adapter 已经够用,这个包只负责把 Berth 注入的变量和能力接上去。
bun add @evumi/better-auth-berth better-auth
# 按需:
bun add drizzle-orm @libsql/client # Drizzle(模板默认)
bun add @libsql/client @libsql/kysely-libsql # 不用 ORM,用 better-auth 内置的 Kysely| 入口 | 内容 | 运行时 |
|---|---|---|
| @evumi/better-auth-berth | withBerth、berthKVStorage、toWorkerHandler、betterAuthInterceptor、resolveBerthDatabase | Node ≥ 20 / Bun(KV 需要 Bun 或自带客户端) |
| /drizzle | createBerthDrizzle({ schema }) | Node / Bun |
| /libsql | berthLibsqlDatabase()、createBerthLibsqlClient() | Node / Bun |
| /bun | openBerthSqlite()(bun:sqlite) | Bun |
| /s3 | berthS3Config()、createBerthS3()(Bun.S3Client) | 配置任意;客户端需 Bun |
用法
// src/lib/auth.ts
import { betterAuth } from 'better-auth';
import { drizzleAdapter } from 'better-auth/adapters/drizzle';
import { withBerth } from '@evumi/better-auth-berth';
import { createBerthDrizzle } from '@evumi/better-auth-berth/drizzle';
import * as schema from '@/db/schema';
export const db = createBerthDrizzle({ schema });
export const auth = betterAuth(withBerth({
database: drizzleAdapter(db, { provider: 'sqlite' }),
emailAndPassword: { enabled: true },
}));withBerth 只补充 Berth 相关的配置,options 里已经写了的值优先,session、rateLimit、advanced 合并一层:
| 字段 | 值 |
|---|---|
| baseURL | BETTER_AUTH_URL,否则 BERTH_PUBLIC_URL |
| trustedOrigins | baseURL 的 origin |
| secondaryStorage | 项目开了 kv 时用 Berth KV(BERTH_KV_URL、BERTH_KV_PREFIX);没开就不设 |
| session.storeSessionInDatabase | 有 KV 时为 true。项目 KV 默认 64 MB 且 noeviction,写满会报错;session 同时存数据库,KV 满了或被清空都不会让用户掉线 |
| rateLimit.storage | 有 KV 时 secondary-storage,否则 database |
| advanced.ipAddress.trustedProxies | 传了 trustedProxies 才设置 |
第二个参数:{ env?, kv?: false | { url, prefix, namespace, client }, trustedProxies? }。KV 键是 BERTH_KV_PREFIX + namespace + key,namespace 默认 auth:。
数据库怎么选
resolveBerthDatabase() 的顺序:
- 有
LIBSQL_URL:libSQL(LIBSQL_AUTH_TOKEN可选)。URL 原样使用:Berth 托管的 sqld 注入的是http://;Turso 之类的libsql://需要 TLS,不能改写。 BERTH_WORKER_DATABASE=libsql但没有 URL:报错,不会悄悄退回本地文件。- 否则是本地 SQLite:
BERTH_SQLITE_PATH(Worker 是卷上的/data/worker.sqlite),再否则./berth.sqlite。
三种接法:
// Drizzle:业务表和 auth 表共用一个客户端,迁移用 drizzle-kit
drizzleAdapter(createBerthDrizzle({ schema }), { provider: 'sqlite' })
// Kysely:可以用 `npx @better-auth/cli migrate` 或 getMigrations()
import { berthLibsqlDatabase } from '@evumi/better-auth-berth/libsql';
betterAuth(withBerth({ database: berthLibsqlDatabase() }))
// inline Worker 的本地 SQLite(WAL、synchronous=FULL、busy_timeout 5s)
import { openBerthSqlite } from '@evumi/better-auth-berth/bun';
betterAuth(withBerth({ database: openBerthSqlite() }))libSQL 的 interactive transaction 每次占一条 Hrana stream,事务里不要调用外部 API。
挂载
TanStack Start(Worker 全栈模式或普通服务):
// src/routes/api/auth/$.ts
import { createFileRoute } from '@tanstack/react-router';
import { auth } from '@/lib/auth';
export const Route = createFileRoute('/api/auth/$')({
server: { handlers: { GET: ({ request }) => auth.handler(request), POST: ({ request }) => auth.handler(request) } },
});inline Worker:文件路由目前没有 catch-all,用拦截器挂载。所有请求(包括 404)都会经过它:
// __interceptor__.ts
import { betterAuthInterceptor } from '@evumi/better-auth-berth';
import { auth } from '@/lib/auth';
export default betterAuthInterceptor(auth, { basePath: '/api/auth' });已经有拦截器时,在自己的函数里先调用它:return authInterceptor(ctx, next)。平台以后支持 catch-all 路由时,也可以写 export const GET = toWorkerHandler(auth)。
KV
berthKVStorage() 用 Bun 内置的 RedisClient,连接只在第一次使用时建立。不在 Bun 上时,传入任何带 send(command, args) 的客户端:
import Redis from 'ioredis';
const redis = new Redis(process.env.BERTH_KV_URL!);
berthKVStorage({ client: { send: (cmd, args) => redis.call(cmd, ...args) } });| 方法 | 命令 |
|---|---|
| get / delete | GET / DEL |
| set(k, v, ttl) | SET k v EX ttl |
| getAndDelete | GETDEL |
| increment(k, ttl) | SET k 0 EX ttl NX → INCR → EXPIRE k ttl NX |
increment 不用 MULTI:Bun 的客户端在一条连接上 pipeline 所有请求,事务可能和其他请求的命令交错。三条命令各自原子;TTL 只在创建时设置(better-auth 要求的固定窗口),EXPIRE NX 补上「键在两条命令之间过期」的情况。CI 在 Berth 的环境 ACL 下实测这些命令。
Worker 关闭时释放连接:
export async function onStop() { (auth.options.secondaryStorage as BerthKVStorage | undefined)?.close(); }客户端 IP
better-auth 默认只信任单值的 x-forwarded-for。如果前面有多层代理,用 trustedProxies 写上代理的地址,better-auth 会从右往左跳过它们取客户端 IP。不要填覆盖客户端的大网段。
对象存储
import { createBerthS3 } from '@evumi/better-auth-berth/s3';
const s3 = createBerthS3(); // BERTH_S3_*,path-style
await s3.write('avatars/1.png', bytes, { type: 'image/png' });
const url = s3.presign('avatars/1.png', { expiresIn: 3600 });其他 SDK 用 berthS3Config() 拿配置。这些都是服务端凭据,浏览器只拿预签名 URL。
开发
bun install
bun run typecheck
bun test # 单元测试 + 本地 SQLite / libSQL 文件的端到端测试
bun run buildBERTH_TEST_KV_URL(加 BERTH_TEST_KV_PREFIX)和 BERTH_TEST_LIBSQL_URL 设置后,会额外跑真实 Valkey 和 sqld 的测试。CI 用的是 Berth 部署的同版本镜像,见 .github/workflows/ci.yml。
发布
改 package.json 的 version,合并后打同名 tag:
git tag v0.1.0 && git push origin v0.1.0release.yml 校验 tag、跑测试并 npm publish。版本号带 -(如 0.2.0-beta.1)时发到 next dist-tag。需要仓库 secret NPM_TOKEN。
License
MIT
