1import { dotAlchemyDirectory } from "alchemy/AlchemyContext";
2import path from "pathe";
3import type { Database as BunDatabase } from "bun:sqlite";
4import * as Effect from "effect/Effect";
5import * as Layer from "effect/Layer";
6import type * as Scope from "effect/Scope";
7import { Database } from "./Database.ts";
8import { BetterAuthMigrationError } from "./Errors.ts";
9
10const open = (path: string): Effect.Effect<BunDatabase, never, Scope.Scope> =>
11 Effect.acquireRelease(
12 Effect.promise(async () => {
13 const { Database: BunSqlite } = await import("bun:sqlite");
14 return new BunSqlite(path, { create: true });
15 }),
16 (db) => Effect.sync(() => db.close()),
17 );
18
19/**
20 * Local `bun:sqlite` database layer for Better Auth — development and
21 * tests on the bun runtime only (a deployed Worker/Lambda cannot open a
22 * local SQLite file).
23 *
24 * Data persists in the file across runs; migrations run against the same
25 * file at deploy time.
26 *
27 *
28 * ### Local development
29 * **Example:** File-backed auth for `alchemy dev`
30 * ```typescript
31 * import { BetterAuth } from "@alchemy.run/better-auth";
32 * import { SQLite } from "@alchemy.run/better-auth/SQLite";
33 *
34 * Effect.gen(function* () {
35 * const auth = yield* BetterAuth({ emailAndPassword: { enabled: true } });
36 * // ...
37 * }).pipe(Effect.provide(SQLite(".alchemy/auth.sqlite")))
38 * ```
39 *
40 * @param filename SQLite file path (parent directory must exist). Defaults to
41 * `better-auth.sqlite` under the resolved `AlchemyContext.dotAlchemy` directory.
42 * @default ".alchemy/better-auth.sqlite"
43 *
44 * @layer
45 * @provides BetterAuth.Database
46 * @product SQLite
47 */
48export const SQLite = (filename?: string): Layer.Layer<Database> =>
49 Layer.effect(
50 Database,
51 Effect.gen(function* () {
52 const resolved =
53 filename ?? path.join(yield* dotAlchemyDirectory, "better-auth.sqlite");
54 return {
55 provider: "sqlite",
56 runtime: open(resolved),
57 migrate: {
58 identity: { path: resolved },
59 connect: Effect.succeed(
60 open(resolved).pipe(
61 Effect.catchDefect((cause: unknown) =>
62 Effect.fail(
63 new BetterAuthMigrationError({
64 message: `Failed to open SQLite database at ${resolved}`,
65 cause,
66 }),
67 ),
68 ),
69 ),
70 ),
71 },
72 };
73 }),
74 );