Files
opengroupware/lib/db/sqlite.ts
Ken Yasue d7c4cd5e58 feat(m2): DB foundation tests + multi-statement migration fix
- Add SqliteDatabase.exec() using better-sqlite3 exec() so the migrator
  can run multi-statement SQL files (prepare() rejected 001_initial.sql)
- Migrator now uses exec() for migration file content
- Fix tests/helpers/db.ts: replace CJS require() with ESM import
- Add unit tests for SqliteDatabase (query/get/execute/transaction/exec,
  WAL & foreign_keys pragmas) and Migrator (ordering, idempotency,
  rollback, real 001_initial.sql applies)
- Restore .gitignore steering exclusion; remove stale steering files
2026-06-25 00:21:26 +02:00

99 lines
2.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import Database from 'better-sqlite3';
export type SqlParams = Record<string, unknown> | unknown[];
export interface ExecuteResult {
changes: number;
lastInsertRowid: number | bigint;
}
/**
* SQLiteへのアクセスを一元管理する共通SQLラッパー。
* 各Repositoryは直接better-sqlite3を触らず、必ずこのクラスを通してSQLを実行する。
*/
export class SqliteDatabase {
private db: Database.Database;
constructor(dbPath: string) {
this.db = new Database(dbPath);
this.db.pragma('journal_mode = WAL');
this.db.pragma('foreign_keys = ON');
}
/**
* 複数行を取得するSELECT
*/
query<T>(sql: string, params?: SqlParams): T[] {
return this.db.prepare(sql).all(params ?? {}) as T[];
}
/**
* 単一行を取得するSELECT該当なしの場合はnull
*/
get<T>(sql: string, params?: SqlParams): T | null {
const row = this.db.prepare(sql).get(params ?? {}) as T | undefined;
return row ?? null;
}
/**
* INSERT / UPDATE / DELETEを実行する単一プリペアドステートメント
*/
execute(sql: string, params?: SqlParams): ExecuteResult {
const result = this.db.prepare(sql).run(params ?? {});
return {
changes: result.changes,
lastInsertRowid: result.lastInsertRowid,
};
}
/**
* 複数ステートメントを含むSQLを一括実行するMigration用・パラメータバインド不可
* better-sqlite3の exec() を利用し、セミコロン区切りの複数文・コメントを処理する。
*/
exec(sql: string): void {
this.db.exec(sql);
}
/**
* トランザクション内でコールバックを実行する
*/
transaction<T>(callback: () => T): T {
const tx = this.db.transaction(callback);
return tx();
}
/**
* DB接続を閉じる
*/
close(): void {
this.db.close();
}
}
let instance: SqliteDatabase | null = null;
/**
* シングルトンのDB接続を取得する。
* dbPathは process.env.SQLITE_PATH ?? "./data/app.db"
*/
export function getDb(): SqliteDatabase {
if (!instance) {
const dbPath = process.env.SQLITE_PATH ?? './data/app.db';
instance = new SqliteDatabase(dbPath);
}
return instance;
}
/**
* テスト用途: シングルトンインスタンスをリセットする
*/
export function resetDbInstance(): void {
if (instance) {
instance.close();
instance = null;
}
}