M1 기반: 모노레포, 공통 엔진, 공용 프로토콜, 오목 엔진, 서버 골격
- 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>
This commit is contained in:
32
apps/server/src/db/index.ts
Normal file
32
apps/server/src/db/index.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
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());
|
||||
})();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user