import initSqlJs, { Database as SqlJsDatabase } from "sql.js" import { readFileSync, writeFileSync, existsSync } from "fs" import { createRequire } from "module" import { getDb } from "@/db" const require = createRequire(import.meta.url) const sqlJsWasm = require.resolve("sql.js/dist/sql-wasm.wasm") const DB_PATH = process.env.LOCAL_DB_PATH || "local.db" let sqlJs: Awaited> | null = null let db: SqlJsDatabase | null = null async function getLocalDb(): Promise { if (!sqlJs) { sqlJs = await initSqlJs({ locateFile: (file: string) => { if (file.endsWith(".wasm")) { return sqlJsWasm } return file }, }) } if (!db) { if (existsSync(DB_PATH)) { const buffer = readFileSync(DB_PATH) db = new sqlJs.Database(buffer) } else { db = new sqlJs.Database() } } return db } function saveDb(db: SqlJsDatabase) { const data = db.export() const buffer = Buffer.from(data) writeFileSync(DB_PATH, buffer) } interface D1Result { results: T[] success: boolean meta: { duration: number changes: number last_row_id: number rows_read: number rows_written: number } } class LocalPreparedStatement { private db: SqlJsDatabase private query: string private boundValues: unknown[] = [] constructor(db: SqlJsDatabase, query: string) { this.db = db this.query = query } bind(...values: unknown[]): this { this.boundValues = values return this } async first(): Promise { const stmt = this.db.prepare(this.query) if (this.boundValues.length > 0) { stmt.bind(this.boundValues as number[]) } if (stmt.step()) { const row = stmt.getAsObject() as T stmt.free() return row } stmt.free() return null } async run(): Promise { this.db.run(this.query, this.boundValues as number[]) saveDb(this.db) return { results: [], success: true, meta: { duration: 0, changes: this.db.getRowsModified(), last_row_id: Number(this.db.exec("SELECT last_insert_rowid()")[0]?.values[0]?.[0] || 0), rows_read: 0, rows_written: this.db.getRowsModified(), }, } } async all(): Promise> { const stmt = this.db.prepare(this.query) if (this.boundValues.length > 0) { stmt.bind(this.boundValues as number[]) } const results: T[] = [] while (stmt.step()) { results.push(stmt.getAsObject() as T) } stmt.free() return { results, success: true, meta: { duration: 0, changes: 0, last_row_id: 0, rows_read: results.length, rows_written: 0, }, } } async raw(): Promise { const results = this.db.exec(this.query, this.boundValues as number[]) return results[0]?.values as T[] ?? [] } } class LocalD1Database { constructor(private db: SqlJsDatabase) {} prepare(query: string): LocalPreparedStatement { return new LocalPreparedStatement(this.db, query) } async batch( statements: LocalPreparedStatement[] ): Promise[]> { const results: D1Result[] = [] for (const stmt of statements) { results.push(await stmt.all()) } return results } async exec(query: string): Promise { this.db.run(query) saveDb(this.db) return { results: [], success: true, meta: { duration: 0, changes: this.db.getRowsModified(), last_row_id: 0, rows_read: 0, rows_written: this.db.getRowsModified(), }, } } async dump(): Promise { const data = this.db.export() return data.buffer as ArrayBuffer } } export async function getCloudflareContext(): Promise<{ env: { DB: D1Database [key: string]: unknown } ctx: { waitUntil: (promise: Promise) => void } cf: unknown }> { const localDb = await getLocalDb() const d1 = new LocalD1Database(localDb) as unknown as D1Database return { env: { DB: d1, }, ctx: { waitUntil: () => {}, }, cf: {}, } }