| 1 | import { memoryAdapter, type MemoryDB } from "better-auth/adapters/memory"; |
| 2 | import { getSchema } from "better-auth/db"; |
| 3 | import * as Effect from "effect/Effect"; |
| 4 | import * as Layer from "effect/Layer"; |
| 5 | import { Database, type DatabaseInput } from "./Database.ts"; |
| 6 | |
| 7 | /** |
| 8 | * In-memory database layer for Better Auth — tests and throwaway dev |
| 9 | * only. Data lives in the provided (or an internal) record for the |
| 10 | * lifetime of the process/isolate; nothing is persisted and there is no |
| 11 | * migration step (tables are seeded from the resolved auth schema). |
| 12 | * |
| 13 | * |
| 14 | * ### Testing |
| 15 | * **Example:** Unit-testing auth flows without a database |
| 16 | * ```typescript |
| 17 | * import { BetterAuth, Memory } from "@alchemy.run/better-auth"; |
| 18 | * |
| 19 | * Effect.gen(function* () { |
| 20 | * const auth = yield* BetterAuth({ |
| 21 | * emailAndPassword: { enabled: true }, |
| 22 | * secret: "test-secret", |
| 23 | * }); |
| 24 | * const signUp = yield* auth.api.signUpEmail({ |
| 25 | * body: { email: "a@b.co", password: "password1234", name: "A" }, |
| 26 | * }); |
| 27 | * }).pipe(Effect.provide(Memory())) |
| 28 | * ``` |
| 29 | * |
| 30 | * @layer |
| 31 | * @provides BetterAuth.Database |
| 32 | * @product Memory |
| 33 | */ |
| 34 | export const Memory = (db?: MemoryDB): Layer.Layer<Database> => |
| 35 | Layer.effect( |
| 36 | Database, |
| 37 | Effect.sync(() => { |
| 38 | // One backing store per layer instance — executions share it so data |
| 39 | // survives across events within the same isolate/process. The memory |
| 40 | // adapter throws "Model X not found" for absent tables (and its |
| 41 | // transaction support `structuredClone`s the store, so no Proxy |
| 42 | // tricks) — seed an empty array per model from the resolved schema |
| 43 | // inside the adapter factory, which receives the full options. |
| 44 | const store: MemoryDB = db ?? {}; |
| 45 | const adapter: DatabaseInput = (options) => { |
| 46 | for (const modelName of Object.keys(getSchema(options))) { |
| 47 | store[modelName] ??= []; |
| 48 | } |
| 49 | return memoryAdapter(store)(options); |
| 50 | }; |
| 51 | return { |
| 52 | provider: "sqlite", |
| 53 | runtime: Effect.succeed(adapter), |
| 54 | }; |
| 55 | }), |
| 56 | ); |