refactor: migrate from better-sqlite3 to bun:sqlite

- Updated build-hooks.js to remove better-sqlite3 dependency and use bun:sqlite.
- Modified smart-install.js to eliminate checks and installations related to better-sqlite3.
- Refactored Database.ts, SessionSearch.ts, SessionStore.ts, and migrations.ts to import and utilize bun:sqlite.
- Replaced exec and pragma calls with appropriate run methods for bun:sqlite compatibility.
- Removed unnecessary native module verification and installation logic for better-sqlite3.
This commit is contained in:
Alex Newman
2025-12-10 22:11:55 -05:00
parent b39cf84730
commit e4bd0ae461
12 changed files with 145 additions and 218 deletions
+1 -1
View File
@@ -13,7 +13,7 @@ module.exports = {
apps: [ apps: [
{ {
name: 'claude-mem-worker', name: 'claude-mem-worker',
script: './plugin/scripts/worker-service.cjs', script: 'bun ./plugin/scripts/worker-service.cjs',
// Windows: prevent visible console windows // Windows: prevent visible console windows
windowsHide: true, windowsHide: true,
// INTENTIONAL: Watch mode enables auto-restart on plugin updates // INTENTIONAL: Watch mode enables auto-restart on plugin updates
-2
View File
@@ -50,7 +50,6 @@
"@anthropic-ai/claude-agent-sdk": "^0.1.62", "@anthropic-ai/claude-agent-sdk": "^0.1.62",
"@modelcontextprotocol/sdk": "^1.20.1", "@modelcontextprotocol/sdk": "^1.20.1",
"ansi-to-html": "^0.7.2", "ansi-to-html": "^0.7.2",
"better-sqlite3": "^12.5.0",
"express": "^4.18.2", "express": "^4.18.2",
"glob": "^11.0.3", "glob": "^11.0.3",
"handlebars": "^4.7.8", "handlebars": "^4.7.8",
@@ -60,7 +59,6 @@
"zod-to-json-schema": "^3.24.6" "zod-to-json-schema": "^3.24.6"
}, },
"devDependencies": { "devDependencies": {
"@types/better-sqlite3": "^7.6.8",
"@types/cors": "^2.8.19", "@types/cors": "^2.8.19",
"@types/express": "^4.17.21", "@types/express": "^4.17.21",
"@types/node": "^20.0.0", "@types/node": "^20.0.0",
+4 -5
View File
@@ -1,13 +1,12 @@
{ {
"name": "claude-mem-plugin", "name": "claude-mem-plugin",
"version": "7.0.9", "version": "7.0.10",
"private": true, "private": true,
"description": "Runtime dependencies for claude-mem bundled hooks", "description": "Runtime dependencies for claude-mem bundled hooks",
"type": "module", "type": "module",
"dependencies": { "dependencies": {},
"better-sqlite3": "^12.5.0"
},
"engines": { "engines": {
"node": ">=18.0.0" "node": ">=18.0.0",
"bun": ">=1.0.0"
} }
} }
File diff suppressed because one or more lines are too long
-11
View File
@@ -287,17 +287,6 @@ async function runNpmInstall() {
encoding: 'utf-8', encoding: 'utf-8',
}); });
// Verify better-sqlite3 was installed
if (!existsSync(BETTER_SQLITE3_PATH)) {
throw new Error('better-sqlite3 installation verification failed');
}
// Verify native modules actually work
const nativeModulesWork = await verifyNativeModules();
if (!nativeModulesWork) {
throw new Error('Native modules failed to load after install');
}
const packageVersion = getPackageVersion(); const packageVersion = getPackageVersion();
const nodeVersion = getNodeVersion(); const nodeVersion = getNodeVersion();
setInstalledVersion(packageVersion, nodeVersion); setInstalledVersion(packageVersion, nodeVersion);
File diff suppressed because one or more lines are too long
+8 -10
View File
@@ -59,8 +59,7 @@ async function buildHooks() {
console.log('✓ Output directories ready'); console.log('✓ Output directories ready');
// Generate plugin/package.json for cache directory dependency installation // Generate plugin/package.json for cache directory dependency installation
// The bundled hooks use `external: ['better-sqlite3']` so dependencies must be // Note: bun:sqlite is a Bun built-in, no external dependencies needed for SQLite
// installed at runtime. This package.json enables npm install in the cache directory.
console.log('\n📦 Generating plugin package.json...'); console.log('\n📦 Generating plugin package.json...');
const pluginPackageJson = { const pluginPackageJson = {
name: 'claude-mem-plugin', name: 'claude-mem-plugin',
@@ -68,11 +67,10 @@ async function buildHooks() {
private: true, private: true,
description: 'Runtime dependencies for claude-mem bundled hooks', description: 'Runtime dependencies for claude-mem bundled hooks',
type: 'module', type: 'module',
dependencies: { dependencies: {},
'better-sqlite3': packageJson.dependencies['better-sqlite3']
},
engines: { engines: {
node: '>=18.0.0' node: '>=18.0.0',
bun: '>=1.0.0'
} }
}; };
fs.writeFileSync('plugin/package.json', JSON.stringify(pluginPackageJson, null, 2) + '\n'); fs.writeFileSync('plugin/package.json', JSON.stringify(pluginPackageJson, null, 2) + '\n');
@@ -103,7 +101,7 @@ async function buildHooks() {
outfile: `${hooksDir}/${WORKER_SERVICE.name}.cjs`, outfile: `${hooksDir}/${WORKER_SERVICE.name}.cjs`,
minify: true, minify: true,
logLevel: 'error', // Suppress warnings (import.meta warning is benign) logLevel: 'error', // Suppress warnings (import.meta warning is benign)
external: ['better-sqlite3'], external: ['bun:sqlite'],
define: { define: {
'__DEFAULT_PACKAGE_VERSION__': `"${version}"` '__DEFAULT_PACKAGE_VERSION__': `"${version}"`
}, },
@@ -128,7 +126,7 @@ async function buildHooks() {
outfile: `${hooksDir}/${MCP_SERVER.name}.cjs`, outfile: `${hooksDir}/${MCP_SERVER.name}.cjs`,
minify: true, minify: true,
logLevel: 'error', logLevel: 'error',
external: ['better-sqlite3'], external: ['bun:sqlite'],
define: { define: {
'__DEFAULT_PACKAGE_VERSION__': `"${version}"` '__DEFAULT_PACKAGE_VERSION__': `"${version}"`
}, },
@@ -153,7 +151,7 @@ async function buildHooks() {
outfile: `${hooksDir}/${CONTEXT_GENERATOR.name}.cjs`, outfile: `${hooksDir}/${CONTEXT_GENERATOR.name}.cjs`,
minify: true, minify: true,
logLevel: 'error', logLevel: 'error',
external: ['better-sqlite3'], external: ['bun:sqlite'],
define: { define: {
'__DEFAULT_PACKAGE_VERSION__': `"${version}"` '__DEFAULT_PACKAGE_VERSION__': `"${version}"`
} }
@@ -176,7 +174,7 @@ async function buildHooks() {
format: 'esm', format: 'esm',
outfile, outfile,
minify: true, minify: true,
external: ['better-sqlite3'], external: ['bun:sqlite'],
define: { define: {
'__DEFAULT_PACKAGE_VERSION__': `"${version}"` '__DEFAULT_PACKAGE_VERSION__': `"${version}"`
}, },
+2 -59
View File
@@ -25,7 +25,6 @@ const MARKETPLACE_ROOT = join(homedir(), '.claude', 'plugins', 'marketplaces', '
const PACKAGE_JSON_PATH = join(MARKETPLACE_ROOT, 'package.json'); const PACKAGE_JSON_PATH = join(MARKETPLACE_ROOT, 'package.json');
const VERSION_MARKER_PATH = join(MARKETPLACE_ROOT, '.install-version'); const VERSION_MARKER_PATH = join(MARKETPLACE_ROOT, '.install-version');
const NODE_MODULES_PATH = join(MARKETPLACE_ROOT, 'node_modules'); const NODE_MODULES_PATH = join(MARKETPLACE_ROOT, 'node_modules');
const BETTER_SQLITE3_PATH = join(NODE_MODULES_PATH, 'better-sqlite3');
// Colors for output // Colors for output
const colors = { const colors = {
@@ -104,11 +103,6 @@ function needsInstall() {
return true; return true;
} }
// Check if better-sqlite3 is installed
if (!existsSync(BETTER_SQLITE3_PATH)) {
log('📦 better-sqlite3 missing - reinstalling', colors.cyan);
return true;
}
// Check version marker // Check version marker
const currentPackageVersion = getPackageVersion(); const currentPackageVersion = getPackageVersion();
@@ -143,46 +137,6 @@ function needsInstall() {
return false; return false;
} }
/**
* Verify that better-sqlite3 native module loads correctly
* This catches ABI mismatches and corrupted builds
*/
async function verifyNativeModules() {
try {
log('🔍 Verifying native modules...', colors.dim);
// CRITICAL: Use createRequire() to resolve from MARKETPLACE_ROOT
// This script may run from cache but must load modules from marketplace's node_modules
const require = createRequire(join(MARKETPLACE_ROOT, 'package.json'));
const Database = require('better-sqlite3');
// Try to create a test in-memory database
const db = new Database(':memory:');
// Run a simple query to ensure it works
const result = db.prepare('SELECT 1 + 1 as result').get();
// Clean up
db.close();
if (result.result !== 2) {
throw new Error('SQLite math check failed');
}
log('✓ Native modules verified', colors.dim);
return true;
} catch (error) {
if (error.code === 'ERR_DLOPEN_FAILED') {
log('⚠️ Native module ABI mismatch detected', colors.yellow);
return false;
}
// Other errors are unexpected - log and fail
log(`❌ Native module verification failed: ${error.message}`, colors.red);
return false;
}
}
function getWindowsErrorHelp(errorOutput) { function getWindowsErrorHelp(errorOutput) {
// Detect Python version at runtime // Detect Python version at runtime
@@ -203,7 +157,7 @@ function getWindowsErrorHelp(errorOutput) {
'║ Windows Installation Help ║', '║ Windows Installation Help ║',
'╚══════════════════════════════════════════════════════════════════════╝', '╚══════════════════════════════════════════════════════════════════════╝',
'', '',
'📋 better-sqlite3 requires build tools to compile native modules.', '',
'', '',
'🔧 Option 1: Install Visual Studio Build Tools (Recommended)', '🔧 Option 1: Install Visual Studio Build Tools (Recommended)',
' 1. Download: https://visualstudio.microsoft.com/downloads/#build-tools-for-visual-studio-2022', ' 1. Download: https://visualstudio.microsoft.com/downloads/#build-tools-for-visual-studio-2022',
@@ -232,9 +186,6 @@ function getWindowsErrorHelp(errorOutput) {
help.push('❌ Permission denied - try running as Administrator'); help.push('❌ Permission denied - try running as Administrator');
} }
help.push('');
help.push('📖 Full documentation: https://github.com/WiseLibs/better-sqlite3/blob/master/docs/troubleshooting.md');
help.push('');
return help.join('\n'); return help.join('\n');
} }
@@ -266,10 +217,7 @@ async function runNpmInstall() {
windowsHide: true, windowsHide: true,
}); });
// Verify better-sqlite3 was installed
if (!existsSync(BETTER_SQLITE3_PATH)) {
throw new Error('better-sqlite3 installation verification failed');
}
// NEW: Verify native modules actually work // NEW: Verify native modules actually work
const nativeModulesWork = await verifyNativeModules(); const nativeModulesWork = await verifyNativeModules();
@@ -300,11 +248,6 @@ async function runNpmInstall() {
log('❌ Installation failed after retrying!', colors.bright); log('❌ Installation failed after retrying!', colors.bright);
log('', colors.reset); log('', colors.reset);
// Provide Windows-specific help
if (isWindows && lastError && lastError.message && lastError.message.includes('better-sqlite3')) {
log(getWindowsErrorHelp(lastError.message), colors.yellow);
}
// Show generic error info with troubleshooting steps // Show generic error info with troubleshooting steps
if (lastError) { if (lastError) {
if (lastError.stderr) { if (lastError.stderr) {
+1 -1
View File
@@ -1,4 +1,4 @@
import { Database } from 'better-sqlite3'; import { Database } from 'bun:sqlite';
import { DATA_DIR, DB_PATH, ensureDir } from '../../shared/paths.js'; import { DATA_DIR, DB_PATH, ensureDir } from '../../shared/paths.js';
export interface Migration { export interface Migration {
+9 -9
View File
@@ -1,4 +1,4 @@
import Database from 'better-sqlite3'; import { Database } from 'bun:sqlite';
import { TableNameRow } from '../../types/database.js'; import { TableNameRow } from '../../types/database.js';
import { DATA_DIR, DB_PATH, ensureDir } from '../../shared/paths.js'; import { DATA_DIR, DB_PATH, ensureDir } from '../../shared/paths.js';
import { import {
@@ -18,7 +18,7 @@ import {
* Vector search is handled by ChromaDB - this class only supports filtering without query text * Vector search is handled by ChromaDB - this class only supports filtering without query text
*/ */
export class SessionSearch { export class SessionSearch {
private db: Database.Database; private db: Database;
constructor(dbPath?: string) { constructor(dbPath?: string) {
if (!dbPath) { if (!dbPath) {
@@ -26,7 +26,7 @@ export class SessionSearch {
dbPath = DB_PATH; dbPath = DB_PATH;
} }
this.db = new Database(dbPath); this.db = new Database(dbPath);
this.db.pragma('journal_mode = WAL'); this.db.run('PRAGMA journal_mode = WAL');
// Ensure FTS tables exist // Ensure FTS tables exist
this.ensureFTSTables(); this.ensureFTSTables();
@@ -60,7 +60,7 @@ export class SessionSearch {
console.error('[SessionSearch] Creating FTS5 tables...'); console.error('[SessionSearch] Creating FTS5 tables...');
// Create observations_fts virtual table // Create observations_fts virtual table
this.db.exec(` this.db.run(`
CREATE VIRTUAL TABLE IF NOT EXISTS observations_fts USING fts5( CREATE VIRTUAL TABLE IF NOT EXISTS observations_fts USING fts5(
title, title,
subtitle, subtitle,
@@ -74,14 +74,14 @@ export class SessionSearch {
`); `);
// Populate with existing data // Populate with existing data
this.db.exec(` this.db.run(`
INSERT INTO observations_fts(rowid, title, subtitle, narrative, text, facts, concepts) INSERT INTO observations_fts(rowid, title, subtitle, narrative, text, facts, concepts)
SELECT id, title, subtitle, narrative, text, facts, concepts SELECT id, title, subtitle, narrative, text, facts, concepts
FROM observations; FROM observations;
`); `);
// Create triggers for observations // Create triggers for observations
this.db.exec(` this.db.run(`
CREATE TRIGGER IF NOT EXISTS observations_ai AFTER INSERT ON observations BEGIN CREATE TRIGGER IF NOT EXISTS observations_ai AFTER INSERT ON observations BEGIN
INSERT INTO observations_fts(rowid, title, subtitle, narrative, text, facts, concepts) INSERT INTO observations_fts(rowid, title, subtitle, narrative, text, facts, concepts)
VALUES (new.id, new.title, new.subtitle, new.narrative, new.text, new.facts, new.concepts); VALUES (new.id, new.title, new.subtitle, new.narrative, new.text, new.facts, new.concepts);
@@ -101,7 +101,7 @@ export class SessionSearch {
`); `);
// Create session_summaries_fts virtual table // Create session_summaries_fts virtual table
this.db.exec(` this.db.run(`
CREATE VIRTUAL TABLE IF NOT EXISTS session_summaries_fts USING fts5( CREATE VIRTUAL TABLE IF NOT EXISTS session_summaries_fts USING fts5(
request, request,
investigated, investigated,
@@ -115,14 +115,14 @@ export class SessionSearch {
`); `);
// Populate with existing data // Populate with existing data
this.db.exec(` this.db.run(`
INSERT INTO session_summaries_fts(rowid, request, investigated, learned, completed, next_steps, notes) INSERT INTO session_summaries_fts(rowid, request, investigated, learned, completed, next_steps, notes)
SELECT id, request, investigated, learned, completed, next_steps, notes SELECT id, request, investigated, learned, completed, next_steps, notes
FROM session_summaries; FROM session_summaries;
`); `);
// Create triggers for session_summaries // Create triggers for session_summaries
this.db.exec(` this.db.run(`
CREATE TRIGGER IF NOT EXISTS session_summaries_ai AFTER INSERT ON session_summaries BEGIN CREATE TRIGGER IF NOT EXISTS session_summaries_ai AFTER INSERT ON session_summaries BEGIN
INSERT INTO session_summaries_fts(rowid, request, investigated, learned, completed, next_steps, notes) INSERT INTO session_summaries_fts(rowid, request, investigated, learned, completed, next_steps, notes)
VALUES (new.id, new.request, new.investigated, new.learned, new.completed, new.next_steps, new.notes); VALUES (new.id, new.request, new.investigated, new.learned, new.completed, new.next_steps, new.notes);
+46 -46
View File
@@ -1,4 +1,4 @@
import Database from 'better-sqlite3'; import { Database } from 'bun:sqlite';
import { DATA_DIR, DB_PATH, ensureDir } from '../../shared/paths.js'; import { DATA_DIR, DB_PATH, ensureDir } from '../../shared/paths.js';
import { logger } from '../../utils/logger.js'; import { logger } from '../../utils/logger.js';
import { import {
@@ -18,16 +18,16 @@ import {
* Provides simple, synchronous CRUD operations for session-based memory * Provides simple, synchronous CRUD operations for session-based memory
*/ */
export class SessionStore { export class SessionStore {
public db: Database.Database; public db: Database;
constructor() { constructor() {
ensureDir(DATA_DIR); ensureDir(DATA_DIR);
this.db = new Database(DB_PATH); this.db = new Database(DB_PATH);
// Ensure optimized settings // Ensure optimized settings
this.db.pragma('journal_mode = WAL'); this.db.run('PRAGMA journal_mode = WAL');
this.db.pragma('synchronous = NORMAL'); this.db.run('PRAGMA synchronous = NORMAL');
this.db.pragma('foreign_keys = ON'); this.db.run('PRAGMA foreign_keys = ON');
// Initialize schema if needed (fresh database) // Initialize schema if needed (fresh database)
this.initializeSchema(); this.initializeSchema();
@@ -49,7 +49,7 @@ export class SessionStore {
private initializeSchema(): void { private initializeSchema(): void {
try { try {
// Create schema_versions table if it doesn't exist // Create schema_versions table if it doesn't exist
this.db.exec(` this.db.run(`
CREATE TABLE IF NOT EXISTS schema_versions ( CREATE TABLE IF NOT EXISTS schema_versions (
id INTEGER PRIMARY KEY, id INTEGER PRIMARY KEY,
version INTEGER UNIQUE NOT NULL, version INTEGER UNIQUE NOT NULL,
@@ -67,7 +67,7 @@ export class SessionStore {
console.error('[SessionStore] Initializing fresh database with migration004...'); console.error('[SessionStore] Initializing fresh database with migration004...');
// Migration004: SDK agent architecture tables // Migration004: SDK agent architecture tables
this.db.exec(` this.db.run(`
CREATE TABLE IF NOT EXISTS sdk_sessions ( CREATE TABLE IF NOT EXISTS sdk_sessions (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
claude_session_id TEXT UNIQUE NOT NULL, claude_session_id TEXT UNIQUE NOT NULL,
@@ -146,11 +146,11 @@ export class SessionStore {
if (applied) return; if (applied) return;
// Check if column exists // Check if column exists
const tableInfo = this.db.pragma('table_info(sdk_sessions)') as TableColumnInfo[]; const tableInfo = this.db.query('PRAGMA table_info(sdk_sessions)').all() as TableColumnInfo[];
const hasWorkerPort = tableInfo.some(col => col.name === 'worker_port'); const hasWorkerPort = tableInfo.some(col => col.name === 'worker_port');
if (!hasWorkerPort) { if (!hasWorkerPort) {
this.db.exec('ALTER TABLE sdk_sessions ADD COLUMN worker_port INTEGER'); this.db.run('ALTER TABLE sdk_sessions ADD COLUMN worker_port INTEGER');
console.error('[SessionStore] Added worker_port column to sdk_sessions table'); console.error('[SessionStore] Added worker_port column to sdk_sessions table');
} }
@@ -171,29 +171,29 @@ export class SessionStore {
if (applied) return; if (applied) return;
// Check sdk_sessions for prompt_counter // Check sdk_sessions for prompt_counter
const sessionsInfo = this.db.pragma('table_info(sdk_sessions)') as TableColumnInfo[]; const sessionsInfo = this.db.query('PRAGMA table_info(sdk_sessions)').all() as TableColumnInfo[];
const hasPromptCounter = sessionsInfo.some(col => col.name === 'prompt_counter'); const hasPromptCounter = sessionsInfo.some(col => col.name === 'prompt_counter');
if (!hasPromptCounter) { if (!hasPromptCounter) {
this.db.exec('ALTER TABLE sdk_sessions ADD COLUMN prompt_counter INTEGER DEFAULT 0'); this.db.run('ALTER TABLE sdk_sessions ADD COLUMN prompt_counter INTEGER DEFAULT 0');
console.error('[SessionStore] Added prompt_counter column to sdk_sessions table'); console.error('[SessionStore] Added prompt_counter column to sdk_sessions table');
} }
// Check observations for prompt_number // Check observations for prompt_number
const observationsInfo = this.db.pragma('table_info(observations)') as TableColumnInfo[]; const observationsInfo = this.db.query('PRAGMA table_info(observations)').all() as TableColumnInfo[];
const obsHasPromptNumber = observationsInfo.some(col => col.name === 'prompt_number'); const obsHasPromptNumber = observationsInfo.some(col => col.name === 'prompt_number');
if (!obsHasPromptNumber) { if (!obsHasPromptNumber) {
this.db.exec('ALTER TABLE observations ADD COLUMN prompt_number INTEGER'); this.db.run('ALTER TABLE observations ADD COLUMN prompt_number INTEGER');
console.error('[SessionStore] Added prompt_number column to observations table'); console.error('[SessionStore] Added prompt_number column to observations table');
} }
// Check session_summaries for prompt_number // Check session_summaries for prompt_number
const summariesInfo = this.db.pragma('table_info(session_summaries)') as TableColumnInfo[]; const summariesInfo = this.db.query('PRAGMA table_info(session_summaries)').all() as TableColumnInfo[];
const sumHasPromptNumber = summariesInfo.some(col => col.name === 'prompt_number'); const sumHasPromptNumber = summariesInfo.some(col => col.name === 'prompt_number');
if (!sumHasPromptNumber) { if (!sumHasPromptNumber) {
this.db.exec('ALTER TABLE session_summaries ADD COLUMN prompt_number INTEGER'); this.db.run('ALTER TABLE session_summaries ADD COLUMN prompt_number INTEGER');
console.error('[SessionStore] Added prompt_number column to session_summaries table'); console.error('[SessionStore] Added prompt_number column to session_summaries table');
} }
@@ -214,7 +214,7 @@ export class SessionStore {
if (applied) return; if (applied) return;
// Check if UNIQUE constraint exists // Check if UNIQUE constraint exists
const summariesIndexes = this.db.pragma('index_list(session_summaries)') as IndexInfo[]; const summariesIndexes = this.db.query('PRAGMA index_list(session_summaries)').all() as IndexInfo[];
const hasUniqueConstraint = summariesIndexes.some(idx => idx.unique === 1); const hasUniqueConstraint = summariesIndexes.some(idx => idx.unique === 1);
if (!hasUniqueConstraint) { if (!hasUniqueConstraint) {
@@ -226,11 +226,11 @@ export class SessionStore {
console.error('[SessionStore] Removing UNIQUE constraint from session_summaries.sdk_session_id...'); console.error('[SessionStore] Removing UNIQUE constraint from session_summaries.sdk_session_id...');
// Begin transaction // Begin transaction
this.db.exec('BEGIN TRANSACTION'); this.db.run('BEGIN TRANSACTION');
try { try {
// Create new table without UNIQUE constraint // Create new table without UNIQUE constraint
this.db.exec(` this.db.run(`
CREATE TABLE session_summaries_new ( CREATE TABLE session_summaries_new (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
sdk_session_id TEXT NOT NULL, sdk_session_id TEXT NOT NULL,
@@ -251,7 +251,7 @@ export class SessionStore {
`); `);
// Copy data from old table // Copy data from old table
this.db.exec(` this.db.run(`
INSERT INTO session_summaries_new INSERT INTO session_summaries_new
SELECT id, sdk_session_id, project, request, investigated, learned, SELECT id, sdk_session_id, project, request, investigated, learned,
completed, next_steps, files_read, files_edited, notes, completed, next_steps, files_read, files_edited, notes,
@@ -260,20 +260,20 @@ export class SessionStore {
`); `);
// Drop old table // Drop old table
this.db.exec('DROP TABLE session_summaries'); this.db.run('DROP TABLE session_summaries');
// Rename new table // Rename new table
this.db.exec('ALTER TABLE session_summaries_new RENAME TO session_summaries'); this.db.run('ALTER TABLE session_summaries_new RENAME TO session_summaries');
// Recreate indexes // Recreate indexes
this.db.exec(` this.db.run(`
CREATE INDEX idx_session_summaries_sdk_session ON session_summaries(sdk_session_id); CREATE INDEX idx_session_summaries_sdk_session ON session_summaries(sdk_session_id);
CREATE INDEX idx_session_summaries_project ON session_summaries(project); CREATE INDEX idx_session_summaries_project ON session_summaries(project);
CREATE INDEX idx_session_summaries_created ON session_summaries(created_at_epoch DESC); CREATE INDEX idx_session_summaries_created ON session_summaries(created_at_epoch DESC);
`); `);
// Commit transaction // Commit transaction
this.db.exec('COMMIT'); this.db.run('COMMIT');
// Record migration // Record migration
this.db.prepare('INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)').run(7, new Date().toISOString()); this.db.prepare('INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)').run(7, new Date().toISOString());
@@ -281,7 +281,7 @@ export class SessionStore {
console.error('[SessionStore] Successfully removed UNIQUE constraint from session_summaries.sdk_session_id'); console.error('[SessionStore] Successfully removed UNIQUE constraint from session_summaries.sdk_session_id');
} catch (error: any) { } catch (error: any) {
// Rollback on error // Rollback on error
this.db.exec('ROLLBACK'); this.db.run('ROLLBACK');
throw error; throw error;
} }
} catch (error: any) { } catch (error: any) {
@@ -299,7 +299,7 @@ export class SessionStore {
if (applied) return; if (applied) return;
// Check if new fields already exist // Check if new fields already exist
const tableInfo = this.db.pragma('table_info(observations)') as TableColumnInfo[]; const tableInfo = this.db.query('PRAGMA table_info(observations)').all() as TableColumnInfo[];
const hasTitle = tableInfo.some(col => col.name === 'title'); const hasTitle = tableInfo.some(col => col.name === 'title');
if (hasTitle) { if (hasTitle) {
@@ -311,7 +311,7 @@ export class SessionStore {
console.error('[SessionStore] Adding hierarchical fields to observations table...'); console.error('[SessionStore] Adding hierarchical fields to observations table...');
// Add new columns // Add new columns
this.db.exec(` this.db.run(`
ALTER TABLE observations ADD COLUMN title TEXT; ALTER TABLE observations ADD COLUMN title TEXT;
ALTER TABLE observations ADD COLUMN subtitle TEXT; ALTER TABLE observations ADD COLUMN subtitle TEXT;
ALTER TABLE observations ADD COLUMN facts TEXT; ALTER TABLE observations ADD COLUMN facts TEXT;
@@ -341,7 +341,7 @@ export class SessionStore {
if (applied) return; if (applied) return;
// Check if text column is already nullable // Check if text column is already nullable
const tableInfo = this.db.pragma('table_info(observations)') as TableColumnInfo[]; const tableInfo = this.db.query('PRAGMA table_info(observations)').all() as TableColumnInfo[];
const textColumn = tableInfo.find(col => col.name === 'text'); const textColumn = tableInfo.find(col => col.name === 'text');
if (!textColumn || textColumn.notnull === 0) { if (!textColumn || textColumn.notnull === 0) {
@@ -353,11 +353,11 @@ export class SessionStore {
console.error('[SessionStore] Making observations.text nullable...'); console.error('[SessionStore] Making observations.text nullable...');
// Begin transaction // Begin transaction
this.db.exec('BEGIN TRANSACTION'); this.db.run('BEGIN TRANSACTION');
try { try {
// Create new table with text as nullable // Create new table with text as nullable
this.db.exec(` this.db.run(`
CREATE TABLE observations_new ( CREATE TABLE observations_new (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
sdk_session_id TEXT NOT NULL, sdk_session_id TEXT NOT NULL,
@@ -379,7 +379,7 @@ export class SessionStore {
`); `);
// Copy data from old table (all existing columns) // Copy data from old table (all existing columns)
this.db.exec(` this.db.run(`
INSERT INTO observations_new INSERT INTO observations_new
SELECT id, sdk_session_id, project, text, type, title, subtitle, facts, SELECT id, sdk_session_id, project, text, type, title, subtitle, facts,
narrative, concepts, files_read, files_modified, prompt_number, narrative, concepts, files_read, files_modified, prompt_number,
@@ -388,13 +388,13 @@ export class SessionStore {
`); `);
// Drop old table // Drop old table
this.db.exec('DROP TABLE observations'); this.db.run('DROP TABLE observations');
// Rename new table // Rename new table
this.db.exec('ALTER TABLE observations_new RENAME TO observations'); this.db.run('ALTER TABLE observations_new RENAME TO observations');
// Recreate indexes // Recreate indexes
this.db.exec(` this.db.run(`
CREATE INDEX idx_observations_sdk_session ON observations(sdk_session_id); CREATE INDEX idx_observations_sdk_session ON observations(sdk_session_id);
CREATE INDEX idx_observations_project ON observations(project); CREATE INDEX idx_observations_project ON observations(project);
CREATE INDEX idx_observations_type ON observations(type); CREATE INDEX idx_observations_type ON observations(type);
@@ -402,7 +402,7 @@ export class SessionStore {
`); `);
// Commit transaction // Commit transaction
this.db.exec('COMMIT'); this.db.run('COMMIT');
// Record migration // Record migration
this.db.prepare('INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)').run(9, new Date().toISOString()); this.db.prepare('INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)').run(9, new Date().toISOString());
@@ -410,7 +410,7 @@ export class SessionStore {
console.error('[SessionStore] Successfully made observations.text nullable'); console.error('[SessionStore] Successfully made observations.text nullable');
} catch (error: any) { } catch (error: any) {
// Rollback on error // Rollback on error
this.db.exec('ROLLBACK'); this.db.run('ROLLBACK');
throw error; throw error;
} }
} catch (error: any) { } catch (error: any) {
@@ -428,7 +428,7 @@ export class SessionStore {
if (applied) return; if (applied) return;
// Check if table already exists // Check if table already exists
const tableInfo = this.db.pragma('table_info(user_prompts)') as TableColumnInfo[]; const tableInfo = this.db.query('PRAGMA table_info(user_prompts)').all() as TableColumnInfo[];
if (tableInfo.length > 0) { if (tableInfo.length > 0) {
// Already migrated // Already migrated
this.db.prepare('INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)').run(10, new Date().toISOString()); this.db.prepare('INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)').run(10, new Date().toISOString());
@@ -438,11 +438,11 @@ export class SessionStore {
console.error('[SessionStore] Creating user_prompts table with FTS5 support...'); console.error('[SessionStore] Creating user_prompts table with FTS5 support...');
// Begin transaction // Begin transaction
this.db.exec('BEGIN TRANSACTION'); this.db.run('BEGIN TRANSACTION');
try { try {
// Create main table (using claude_session_id since sdk_session_id is set asynchronously by worker) // Create main table (using claude_session_id since sdk_session_id is set asynchronously by worker)
this.db.exec(` this.db.run(`
CREATE TABLE user_prompts ( CREATE TABLE user_prompts (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
claude_session_id TEXT NOT NULL, claude_session_id TEXT NOT NULL,
@@ -460,7 +460,7 @@ export class SessionStore {
`); `);
// Create FTS5 virtual table // Create FTS5 virtual table
this.db.exec(` this.db.run(`
CREATE VIRTUAL TABLE user_prompts_fts USING fts5( CREATE VIRTUAL TABLE user_prompts_fts USING fts5(
prompt_text, prompt_text,
content='user_prompts', content='user_prompts',
@@ -469,7 +469,7 @@ export class SessionStore {
`); `);
// Create triggers to sync FTS5 // Create triggers to sync FTS5
this.db.exec(` this.db.run(`
CREATE TRIGGER user_prompts_ai AFTER INSERT ON user_prompts BEGIN CREATE TRIGGER user_prompts_ai AFTER INSERT ON user_prompts BEGIN
INSERT INTO user_prompts_fts(rowid, prompt_text) INSERT INTO user_prompts_fts(rowid, prompt_text)
VALUES (new.id, new.prompt_text); VALUES (new.id, new.prompt_text);
@@ -489,7 +489,7 @@ export class SessionStore {
`); `);
// Commit transaction // Commit transaction
this.db.exec('COMMIT'); this.db.run('COMMIT');
// Record migration // Record migration
this.db.prepare('INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)').run(10, new Date().toISOString()); this.db.prepare('INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)').run(10, new Date().toISOString());
@@ -497,7 +497,7 @@ export class SessionStore {
console.error('[SessionStore] Successfully created user_prompts table with FTS5 support'); console.error('[SessionStore] Successfully created user_prompts table with FTS5 support');
} catch (error: any) { } catch (error: any) {
// Rollback on error // Rollback on error
this.db.exec('ROLLBACK'); this.db.run('ROLLBACK');
throw error; throw error;
} }
} catch (error: any) { } catch (error: any) {
@@ -517,20 +517,20 @@ export class SessionStore {
if (applied) return; if (applied) return;
// Check if discovery_tokens column exists in observations table // Check if discovery_tokens column exists in observations table
const observationsInfo = this.db.pragma('table_info(observations)') as TableColumnInfo[]; const observationsInfo = this.db.query('PRAGMA table_info(observations)').all() as TableColumnInfo[];
const obsHasDiscoveryTokens = observationsInfo.some(col => col.name === 'discovery_tokens'); const obsHasDiscoveryTokens = observationsInfo.some(col => col.name === 'discovery_tokens');
if (!obsHasDiscoveryTokens) { if (!obsHasDiscoveryTokens) {
this.db.exec('ALTER TABLE observations ADD COLUMN discovery_tokens INTEGER DEFAULT 0'); this.db.run('ALTER TABLE observations ADD COLUMN discovery_tokens INTEGER DEFAULT 0');
console.error('[SessionStore] Added discovery_tokens column to observations table'); console.error('[SessionStore] Added discovery_tokens column to observations table');
} }
// Check if discovery_tokens column exists in session_summaries table // Check if discovery_tokens column exists in session_summaries table
const summariesInfo = this.db.pragma('table_info(session_summaries)') as TableColumnInfo[]; const summariesInfo = this.db.query('PRAGMA table_info(session_summaries)').all() as TableColumnInfo[];
const sumHasDiscoveryTokens = summariesInfo.some(col => col.name === 'discovery_tokens'); const sumHasDiscoveryTokens = summariesInfo.some(col => col.name === 'discovery_tokens');
if (!sumHasDiscoveryTokens) { if (!sumHasDiscoveryTokens) {
this.db.exec('ALTER TABLE session_summaries ADD COLUMN discovery_tokens INTEGER DEFAULT 0'); this.db.run('ALTER TABLE session_summaries ADD COLUMN discovery_tokens INTEGER DEFAULT 0');
console.error('[SessionStore] Added discovery_tokens column to session_summaries table'); console.error('[SessionStore] Added discovery_tokens column to session_summaries table');
} }
+1 -1
View File
@@ -1,4 +1,4 @@
import { Database } from 'better-sqlite3'; import { Database } from 'bun:sqlite';
import { Migration } from './Database.js'; import { Migration } from './Database.js';
/** /**