1import { memoryAdapter, type MemoryDB } from "better-auth/adapters/memory";
2import { getSchema } from "better-auth/db";
3import * as Effect from "effect/Effect";
4import * as Layer from "effect/Layer";
5import { 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 */
34export 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 );