examples/storefront Public
// 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;}