58 lines · 2.2 KB
import { Database } from "bun:sqlite";
import { ACCOUNT_SCHEMA, REPO_SCHEMA } from "./schema.gen";
import { ACCOUNT_SCHEMA_EXTRA, REPO_SCHEMA_EXTRA } from "./schema-extra";
import type { ScopedDb, ScopedStatement, ScopedValue } from "./types";

/**
 * Test double for `~/server/scoped-db`: every repo and account gets its own in-memory bun:sqlite with the real
 * scoped schema. Use with `mock.module("<path>/scoped-db", () => memoryScopes().module)`.
 */
export function memoryScopes() {
  const databases = new Map<string, Database>();
  const open = (key: string, schema: readonly string[]) => {
    let db = databases.get(key);
    if (!db) {
      db = new Database(":memory:");
      for (const statement of schema) db.run(statement);
      databases.set(key, db);
    }
    return db;
  };
  const make = (scopeId: string, db: Database): ScopedDb => {
    const meta = (result: { changes: number; lastInsertRowid: number | bigint }) => ({
      meta: { changes: result.changes, last_row_id: Number(result.lastInsertRowid) },
    });
    return {
      scopeId,
      async first<T>(sql: string, ...params: ScopedValue[]) {
        return (db.query(sql).get(...params) as T | null) ?? null;
      },
      async all<T>(sql: string, ...params: ScopedValue[]) {
        return db.query(sql).all(...params) as T[];
      },
      async run(sql: string, ...params: ScopedValue[]) {
        return meta(db.query(sql).run(...params));
      },
      async batch(statements: ScopedStatement[]) {
        return db.transaction(() =>
          statements.map((statement) => meta(db.query(statement.sql).run(...statement.params))),
        )();
      },