63 lines
2.8 KiB
JavaScript
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;
|
||
|
|
}
|