Newer
Older
TelosDB / src / backend / db.js
import Database from "better-sqlite3";
import knex from "knex";
import * as sqlite_vec from "sqlite-vec";
import { CONFIG } from "./config.js";
import { Logger } from "./logger.js";

let db = null;
let knexDb = null;

/**
 * SQLite Vector を拡張ライブラリとして読み込む
 * @throws {Error} sqlite-vec の読み込みに失敗した場合
 */
function loadVectorExtension() {
  try {
    sqlite_vec.load(db);
    Logger.debug('sqlite-vec extension loaded');
  } catch (error) {
    Logger.error('Failed to load sqlite-vec extension', error);
    throw error;
  }
}

/**
 * テーブルスキーマを初期化
 * @throws {Error} スキーマの初期化に失敗した場合
 */
function initializeSchema() {
  try {
    const sql = `
      CREATE TABLE IF NOT EXISTS items (
        id INTEGER PRIMARY KEY,
        content TEXT NOT NULL,
        path TEXT,
        created_at TEXT DEFAULT (datetime('now', 'localtime')),
        updated_at TEXT DEFAULT (datetime('now', 'localtime'))
      );
      CREATE VIRTUAL TABLE IF NOT EXISTS vec_items USING vec0(
        id INTEGER PRIMARY KEY,
        embedding FLOAT[${CONFIG.database.embeddingDim}]
      );
      CREATE INDEX IF NOT EXISTS idx_items_created_at ON items(created_at);
      CREATE INDEX IF NOT EXISTS idx_items_path ON items(path);
    `;
    db.exec(sql);
    Logger.debug('Database schema initialized', { embeddingDim: CONFIG.database.embeddingDim });
  } catch (error) {
    Logger.error('Failed to initialize database schema', error);
    throw error;
  }
}

/**
 * データベースを初期化
 * @throws {Error} データベースの初期化に失敗した場合
 */
export function initDb() {
  try {
    Logger.info('Initializing database');

    // SQLite データベース接続
    db = new Database(CONFIG.database.filename);
    db.pragma('journal_mode = WAL');
    Logger.debug('SQLite database connected', { filename: CONFIG.database.filename });

    // sqlite-vec 拡張を読み込む
    loadVectorExtension();

    // スキーマを初期化
    initializeSchema();

    // knex 接続を作成
    knexDb = knex({
      client: "better-sqlite3",
      connection: {
        filename: CONFIG.database.filename,
      },
      useNullAsDefault: true,
    });

    Logger.info('Database initialization completed');
  } catch (error) {
    Logger.error('Failed to initialize database', error);
    throw error;
  }
}

/**
 * エンベディング次元数を取得
 * @returns {number} エンベディング次元数
 */
export function getEmbeddingDim() {
  return CONFIG.database.embeddingDim;
}

/**
 * データベース接続を取得(better-sqlite3)
 * @returns {Database} SQLiteデータベース接続
 * @throws {Error} データベースが未初期化の場合
 */
export function getDb() {
  if (!db) {
    throw new Error('Database not initialized. Call initDb() first.');
  }
  return db;
}

/**
 * Knex クエリビルダを取得
 * @returns {Object} Knex クエリビルダ
 * @throws {Error} データベースが未初期化の場合
 */
export function getKnexDb() {
  if (!knexDb) {
    throw new Error('Knex database not initialized. Call initDb() first.');
  }
  return knexDb;
}

// エクスポート(後方互換性)
export { db as default };