Files
joke-app/apps/server/src/db/index.ts
EJClaw 6ee8da18f1 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>
2026-10-04 04:18:29 +09:00

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());
})();
}
}