- packages/engine: 시드 RNG(sfc32), GameDefinition 타입, 무작위 대국·정보 유출 테스트 도구 - packages/shared: WS 프로토콜 스키마, 닉네임·방 코드 규칙, 게임 카탈로그 - packages/games/omok: 한국식·렌주·자유룰, 재귀 금수 판정, 무르기·무승부·시계 (테스트 27개) - apps/server: SQLite 스키마, 게스트·디스코드 로그인, 세션, 방 관리(저장·복구·타이머·재접속), WS 게이트웨이, HTTP API Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
33 lines
1.2 KiB
TypeScript
33 lines
1.2 KiB
TypeScript
import { Database } from 'bun:sqlite';
|
|
import { mkdirSync, readdirSync, readFileSync } from 'node:fs';
|
|
import { dirname, join } from 'node:path';
|
|
|
|
const MIGRATIONS_DIR = join(import.meta.dir, 'migrations');
|
|
|
|
export function openDb(path: string): Database {
|
|
if (path !== ':memory:') mkdirSync(dirname(path), { recursive: true });
|
|
const db = new Database(path, { create: true, strict: true });
|
|
db.exec('PRAGMA journal_mode = WAL;');
|
|
db.exec('PRAGMA synchronous = NORMAL;');
|
|
db.exec('PRAGMA foreign_keys = ON;');
|
|
db.exec('PRAGMA busy_timeout = 5000;');
|
|
migrate(db);
|
|
return db;
|
|
}
|
|
|
|
export function migrate(db: Database): void {
|
|
db.exec('CREATE TABLE IF NOT EXISTS schema_migrations (name TEXT PRIMARY KEY, applied_at INTEGER NOT NULL)');
|
|
const applied = new Set(
|
|
db.query<{ name: string }, []>('SELECT name FROM schema_migrations').all().map((r) => r.name),
|
|
);
|
|
const files = readdirSync(MIGRATIONS_DIR).filter((f) => f.endsWith('.sql')).sort();
|
|
for (const file of files) {
|
|
if (applied.has(file)) continue;
|
|
const sql = readFileSync(join(MIGRATIONS_DIR, file), 'utf8');
|
|
db.transaction(() => {
|
|
db.exec(sql);
|
|
db.query('INSERT INTO schema_migrations (name, applied_at) VALUES (?, ?)').run(file, Date.now());
|
|
})();
|
|
}
|
|
}
|