Files

63 lines
2.8 KiB
JavaScript

/**
* SQLite 어댑터. www 사이트(server/db.mjs)와 같은 모양(prepare/bind/first/all/run/batch)을 유지해
* 두 저장소의 코드를 서로 옮겨 쓸 수 있게 합니다. node:sqlite 는 Node 22.13+ 에서 플래그 없이 동작합니다.
*/
import { DatabaseSync } from 'node:sqlite';
import { readFileSync, readdirSync, mkdirSync } from 'node:fs';
import { dirname, join } from 'node:path';
class Stmt {
constructor(db, sql, params = []) { this.db = db; this.sql = sql; this.params = params; }
bind(...params) { return new Stmt(this.db, this.sql, this.params.length ? this.params : params); }
#prep() { return this.db.prepare(this.sql); }
async first() { const row = this.#prep().get(...this.params); return row === undefined ? null : row; }
async all() { return { results: this.#prep().all(...this.params) }; }
async run() { const r = this.#prep().run(...this.params); return { success: true, meta: { changes: Number(r.changes) } }; }
}
export function openDb(file, { readOnly = false } = {}) {
if (file !== ':memory:' && !readOnly) mkdirSync(dirname(file), { recursive: true });
const db = new DatabaseSync(file, readOnly ? { readOnly: true } : {});
if (!readOnly) {
db.exec('PRAGMA foreign_keys = ON;');
if (file !== ':memory:') { db.exec('PRAGMA journal_mode = WAL;'); db.exec('PRAGMA busy_timeout = 5000;'); }
} else if (file !== ':memory:') db.exec('PRAGMA busy_timeout = 5000;');
return db;
}
export function d1(db) {
let queue = Promise.resolve();
return {
prepare: (sql) => new Stmt(db, sql),
batch: (stmts) => {
const execute = async () => {
db.exec('BEGIN');
try { const out = []; for (const s of stmts) out.push(await s.run()); db.exec('COMMIT'); return out; }
catch (e) { db.exec('ROLLBACK'); throw e; }
};
const result = queue.then(execute, execute);
queue = result.catch(() => undefined);
return result;
},
_raw: db,
};
}
export function migrate(db, dir, { quiet = false } = {}) {
db.exec('CREATE TABLE IF NOT EXISTS schema_migrations (name TEXT PRIMARY KEY, applied_at TEXT NOT NULL)');
const done = new Set(db.prepare('SELECT name FROM schema_migrations').all().map((r) => r.name));
let applied = 0;
for (const f of readdirSync(dir).filter((x) => x.endsWith('.sql')).sort()) {
if (done.has(f)) continue;
db.exec('BEGIN');
try {
db.exec(readFileSync(join(dir, f), 'utf8'));
db.prepare('INSERT INTO schema_migrations (name, applied_at) VALUES (?, ?)').run(f, new Date().toISOString());
db.exec('COMMIT'); applied++;
if (!quiet) console.log(` 적용: ${f}`);
} catch (e) { db.exec('ROLLBACK'); throw new Error(`마이그레이션 실패 ${f}: ${e.message}`); }
}
if (!quiet && !applied) console.log(' 적용할 마이그레이션 없음');
return applied;
}