Rebasely Beta: data may be purged

examples/storefront Public

TypeScript · 74 lines · 2.5 KiB · at 38a9ec4

// A D1 stand-in over node:sqlite with every file in migrations/ applied, so tests run the// storefront's real SQL. It covers what the code calls: prepare, bind, first, all, run and batch.// Foreign keys are on, as they are in D1.import { readdirSync, readFileSync } from "node:fs";import { dirname, join } from "node:path";import { DatabaseSync, type SQLInputValue } from "node:sqlite";import { fileURLToPath } from "node:url";const MIGRATIONS = join(dirname(fileURLToPath(import.meta.url)), "../../migrations");class Statement {  readonly db: DatabaseSync;  readonly sql: string;  readonly args: SQLInputValue[];  constructor(db: DatabaseSync, sql: string, args: SQLInputValue[] = []) {    this.db = db;    this.sql = sql;    this.args = args;  }  bind(...args: unknown[]): Statement {    return new Statement(this.db, this.sql, args as SQLInputValue[]);  }  async first<T>(column?: string): Promise<T | null> {    const row = this.db.prepare(this.sql).get(...this.args) as Record<string, unknown> | undefined;    if (!row) return null;    return (column ? row[column] : { ...row }) as T;  }  async all<T>(): Promise<{ results: T[]; success: true; meta: Record<string, unknown> }> {    const rows = this.db.prepare(this.sql).all(...this.args) as Record<string, unknown>[];    return { results: rows.map((r) => ({ ...r }) as T), success: true, meta: {} };  }  async run(): Promise<{ results: []; success: true; meta: { changes: number; last_row_id: number } }> {    const r = this.db.prepare(this.sql).run(...this.args);    return { results: [], success: true, meta: { changes: Number(r.changes), last_row_id: Number(r.lastInsertRowid) } };  }}export class TestD1 {  readonly sqlite = new DatabaseSync(":memory:");  constructor() {    this.sqlite.exec("PRAGMA foreign_keys = ON");    const files = readdirSync(MIGRATIONS).filter((f) => f.endsWith(".sql")).sort();    for (const f of files) this.sqlite.exec(readFileSync(join(MIGRATIONS, f), "utf8"));  }  prepare(sql: string): Statement {    return new Statement(this.sqlite, sql);  }  // Like D1, a batch is one transaction: a statement that fails rolls the others back.  async batch(statements: Statement[]) {    this.sqlite.exec("BEGIN");    try {      const out = [];      for (const s of statements) out.push(await s.run());      this.sqlite.exec("COMMIT");      return out;    } catch (e) {      this.sqlite.exec("ROLLBACK");      throw e;    }  }}export function testDb(): D1Database {  return new TestD1() as unknown as D1Database;}