Refactor database integration to use bun:sqlite instead of better-sqlite3

- Updated import statements across multiple files to use bun:sqlite.
- Changed database query methods from `prepare` to `query` for consistency.
- Removed dependency on better-sqlite3 from package.json and adjusted package.json to specify bun as the engine.
- Modified hook installation process to eliminate unnecessary dependency installation.
- Updated migration scripts to align with bun:sqlite's API.
This commit is contained in:
Alex Newman
2025-10-15 15:03:43 -04:00
parent b5bfc029c3
commit 2663121d9f
14 changed files with 351 additions and 364 deletions
+2 -3
View File
@@ -136,12 +136,11 @@ Perfect for developers who want their AI assistant to remember project context,
<!-- Prerequisites -->
### :bangbang: Prerequisites
This project requires Node.js and works best with Claude Code
This project requires Bun runtime and works best with Claude Code
- Node.js >= 18.0.0
- Bun >= 1.0.0 (required for SQLite support)
- Claude Code with MCP support
- macOS/Linux (POSIX-compliant system)
- Bun >= 1.0.0 (optional, for development)
<!-- Installation -->
### :gear: Installation
+9 -5
View File
@@ -43,10 +43,11 @@ async function build() {
const buildCommand = [
'bun build',
'src/bin/cli.ts',
'--target=node',
'--target=bun',
'--outfile=dist/claude-mem.min.js',
'--minify',
'--external @anthropic-ai/claude-agent-sdk',
'--external bun:sqlite',
`--define __DEFAULT_PACKAGE_VERSION__='"${version}"'`
].join(' ');
@@ -58,10 +59,13 @@ async function build() {
// Add shebang to output
console.log('\n📝 Adding shebang...');
const distFile = 'dist/claude-mem.min.js';
const content = fs.readFileSync(distFile, 'utf-8');
if (!content.startsWith('#!/usr/bin/env node')) {
fs.writeFileSync(distFile, `#!/usr/bin/env node\n${content}`);
}
let content = fs.readFileSync(distFile, 'utf-8');
// Remove any existing shebangs
content = content.replace(/^#!.*\n/gm, '');
// Add the bun shebang
fs.writeFileSync(distFile, `#!/usr/bin/env bun\n${content}`);
console.log('✓ Shebang added');
// Make executable
+193 -194
View File
File diff suppressed because one or more lines are too long
+11 -11
View File
@@ -10,7 +10,7 @@
import { spawn } from 'child_process';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
import Database from 'better-sqlite3';
import { Database } from 'bun:sqlite';
import os from 'os';
import fs from 'fs';
@@ -320,7 +320,7 @@ export function createStreamingSession(db, { claude_session_id, project, user_pr
const timestamp = started_at || new Date().toISOString();
const epoch = new Date(timestamp).getTime();
const stmt = db.prepare(`
const stmt = db.query(`
INSERT INTO streaming_sessions (
claude_session_id, project, user_prompt, started_at, started_at_epoch, status
) VALUES (?, ?, ?, ?, ?, 'active')
@@ -328,7 +328,7 @@ export function createStreamingSession(db, { claude_session_id, project, user_pr
const info = stmt.run(claude_session_id, project, user_prompt || null, timestamp, epoch);
return db.prepare('SELECT * FROM streaming_sessions WHERE id = ?').get(info.lastInsertRowid);
return db.query('SELECT * FROM streaming_sessions WHERE id = ?').get(info.lastInsertRowid);
}
/**
@@ -372,7 +372,7 @@ export function updateStreamingSession(db, id, updates) {
values.push(id);
const stmt = db.prepare(`
const stmt = db.query(`
UPDATE streaming_sessions
SET ${parts.join(', ')}
WHERE id = ?
@@ -380,7 +380,7 @@ export function updateStreamingSession(db, id, updates) {
stmt.run(...values);
return db.prepare('SELECT * FROM streaming_sessions WHERE id = ?').get(id);
return db.query('SELECT * FROM streaming_sessions WHERE id = ?').get(id);
}
/**
@@ -389,7 +389,7 @@ export function updateStreamingSession(db, id, updates) {
export function getActiveStreamingSessionsForProject(db, project) {
ensureStreamingSessionsTable(db);
const stmt = db.prepare(`
const stmt = db.query(`
SELECT * FROM streaming_sessions
WHERE project = ? AND status = 'active'
ORDER BY started_at_epoch DESC
@@ -405,7 +405,7 @@ export function markStreamingSessionCompleted(db, id) {
const timestamp = new Date().toISOString();
const epoch = Date.now();
const stmt = db.prepare(`
const stmt = db.query(`
UPDATE streaming_sessions
SET status = ?,
completed_at = ?,
@@ -417,7 +417,7 @@ export function markStreamingSessionCompleted(db, id) {
stmt.run('completed', timestamp, epoch, timestamp, epoch, id);
return db.prepare('SELECT * FROM streaming_sessions WHERE id = ?').get(id);
return db.query('SELECT * FROM streaming_sessions WHERE id = ?').get(id);
}
/**
@@ -459,7 +459,7 @@ export function acquireSessionLock(db, sdkSessionId, lockOwner) {
const timestamp = new Date().toISOString();
const epoch = Date.now();
const stmt = db.prepare(`
const stmt = db.query(`
INSERT INTO session_locks (sdk_session_id, locked_by, locked_at, locked_at_epoch)
VALUES (?, ?, ?, ?)
`);
@@ -478,7 +478,7 @@ export function acquireSessionLock(db, sdkSessionId, lockOwner) {
export function releaseSessionLock(db, sdkSessionId) {
ensureSessionLocksTable(db);
const stmt = db.prepare(`
const stmt = db.query(`
DELETE FROM session_locks
WHERE sdk_session_id = ?
`);
@@ -494,7 +494,7 @@ export function cleanupStaleLocks(db) {
const fiveMinutesAgo = Date.now() - (5 * 60 * 1000);
const stmt = db.prepare(`
const stmt = db.query(`
DELETE FROM session_locks
WHERE locked_at_epoch < ?
`);
+1 -2
View File
@@ -30,7 +30,7 @@
},
"type": "module",
"engines": {
"node": ">=18.0.0"
"bun": ">=1.0.0"
},
"bin": {
"claude-mem": "./dist/claude-mem.min.js"
@@ -44,7 +44,6 @@
"dependencies": {
"@anthropic-ai/claude-agent-sdk": "^0.1.0",
"@clack/prompts": "^0.11.0",
"better-sqlite3": "^11.8.1",
"boxen": "^8.0.1",
"chalk": "^5.6.0",
"commander": "^14.0.0",
+2 -15
View File
@@ -262,25 +262,12 @@ function writeHookFiles(timeout: number = 180000): void {
};
writeFileSync(join(runtimeHooksDir, 'config.json'), JSON.stringify(hookConfig, null, 2));
// Create package.json and install dependencies in hooks directory
// Create package.json in hooks directory (no dependencies needed with bun:sqlite)
const hookPackageJson = {
name: "claude-mem-hooks",
type: "module",
dependencies: {
"better-sqlite3": "^11.8.0"
}
type: "module"
};
writeFileSync(join(runtimeHooksDir, 'package.json'), JSON.stringify(hookPackageJson, null, 2));
// Install dependencies
try {
execSync('npm install --silent', {
cwd: runtimeHooksDir,
stdio: 'pipe'
});
} catch (error) {
// Silent fail - hooks might still work if better-sqlite3 is globally available
}
}
+31 -32
View File
@@ -1,22 +1,25 @@
import Database from 'better-sqlite3';
import { Database as BunDatabase } from 'bun:sqlite';
import path from 'path';
import fs from 'fs';
import { PathDiscovery } from '../path-discovery.js';
// Type alias for better-sqlite3 compatibility
type Database = BunDatabase;
export interface Migration {
version: number;
up: (db: Database.Database) => void;
down?: (db: Database.Database) => void;
up: (db: Database) => void;
down?: (db: Database) => void;
}
let dbInstance: Database.Database | null = null;
let dbInstance: Database | null = null;
/**
* SQLite Database singleton with migration support and optimized settings
*/
export class DatabaseManager {
private static instance: DatabaseManager;
private db: Database.Database | null = null;
private db: Database | null = null;
private migrations: Migration[] = [];
static getInstance(): DatabaseManager {
@@ -38,7 +41,7 @@ export class DatabaseManager {
/**
* Initialize database connection with optimized settings
*/
async initialize(): Promise<Database.Database> {
async initialize(): Promise<Database> {
if (this.db) {
return this.db;
}
@@ -48,15 +51,15 @@ export class DatabaseManager {
fs.mkdirSync(dataDir, { recursive: true });
const dbPath = path.join(dataDir, 'claude-mem.db');
this.db = new Database(dbPath);
this.db = new BunDatabase(dbPath, { create: true, readwrite: true });
// Apply optimized SQLite settings
this.db.pragma('journal_mode = WAL');
this.db.pragma('synchronous = NORMAL');
this.db.pragma('foreign_keys = ON');
this.db.pragma('temp_store = memory');
this.db.pragma('mmap_size = 268435456'); // 256MB
this.db.pragma('cache_size = 10000');
this.db.run('PRAGMA journal_mode = WAL');
this.db.run('PRAGMA synchronous = NORMAL');
this.db.run('PRAGMA foreign_keys = ON');
this.db.run('PRAGMA temp_store = memory');
this.db.run('PRAGMA mmap_size = 268435456'); // 256MB
this.db.run('PRAGMA cache_size = 10000');
// Initialize schema_versions table
this.initializeSchemaVersions();
@@ -71,7 +74,7 @@ export class DatabaseManager {
/**
* Get the current database connection
*/
getConnection(): Database.Database {
getConnection(): Database {
if (!this.db) {
throw new Error('Database not initialized. Call initialize() first.');
}
@@ -81,7 +84,7 @@ export class DatabaseManager {
/**
* Execute a function within a transaction
*/
withTransaction<T>(fn: (db: Database.Database) => T): T {
withTransaction<T>(fn: (db: Database) => T): T {
const db = this.getConnection();
const transaction = db.transaction(fn);
return transaction(db);
@@ -104,7 +107,7 @@ export class DatabaseManager {
private initializeSchemaVersions(): void {
if (!this.db) return;
this.db.exec(`
this.db.run(`
CREATE TABLE IF NOT EXISTS schema_versions (
id INTEGER PRIMARY KEY,
version INTEGER UNIQUE NOT NULL,
@@ -119,23 +122,20 @@ export class DatabaseManager {
private async runMigrations(): Promise<void> {
if (!this.db) return;
const appliedVersions = this.db
.prepare('SELECT version FROM schema_versions ORDER BY version')
.all()
.map((row: any) => row.version);
const query = this.db.query('SELECT version FROM schema_versions ORDER BY version');
const appliedVersions = query.all().map((row: any) => row.version);
const maxApplied = appliedVersions.length > 0 ? Math.max(...appliedVersions) : 0;
for (const migration of this.migrations) {
if (migration.version > maxApplied) {
console.log(`Applying migration ${migration.version}...`);
const transaction = this.db.transaction(() => {
migration.up(this.db!);
this.db!
.prepare('INSERT INTO schema_versions (version, applied_at) VALUES (?, ?)')
.run(migration.version, new Date().toISOString());
const insertQuery = this.db!.query('INSERT INTO schema_versions (version, applied_at) VALUES (?, ?)');
insertQuery.run(migration.version, new Date().toISOString());
});
transaction();
@@ -150,9 +150,8 @@ export class DatabaseManager {
getCurrentVersion(): number {
if (!this.db) return 0;
const result = this.db
.prepare('SELECT MAX(version) as version FROM schema_versions')
.get() as { version: number } | undefined;
const query = this.db.query('SELECT MAX(version) as version FROM schema_versions');
const result = query.get() as { version: number } | undefined;
return result?.version || 0;
}
@@ -161,7 +160,7 @@ export class DatabaseManager {
/**
* Get the global database instance (for compatibility)
*/
export function getDatabase(): Database.Database {
export function getDatabase(): Database {
if (!dbInstance) {
throw new Error('Database not initialized. Call DatabaseManager.getInstance().initialize() first.');
}
@@ -171,9 +170,9 @@ export function getDatabase(): Database.Database {
/**
* Initialize and get database manager
*/
export async function initializeDatabase(): Promise<Database.Database> {
export async function initializeDatabase(): Promise<Database> {
const manager = DatabaseManager.getInstance();
return await manager.initialize();
}
export { Database };
export { BunDatabase as Database };
+17 -17
View File
@@ -1,4 +1,4 @@
import { Database } from 'better-sqlite3';
import { Database } from 'bun:sqlite';
import { getDatabase } from './Database.js';
import { DiagnosticRow, DiagnosticInput, normalizeTimestamp } from './types.js';
@@ -18,7 +18,7 @@ export class DiagnosticsStore {
create(input: DiagnosticInput): DiagnosticRow {
const { isoString, epoch } = normalizeTimestamp(input.created_at);
const stmt = this.db.prepare(`
const stmt = this.db.query(`
INSERT INTO diagnostics (
session_id, message, severity, created_at, created_at_epoch, project, origin
) VALUES (?, ?, ?, ?, ?, ?, ?)
@@ -41,7 +41,7 @@ export class DiagnosticsStore {
* Get diagnostic by primary key
*/
getById(id: number): DiagnosticRow | null {
const stmt = this.db.prepare('SELECT * FROM diagnostics WHERE id = ?');
const stmt = this.db.query('SELECT * FROM diagnostics WHERE id = ?');
return stmt.get(id) as DiagnosticRow || null;
}
@@ -49,7 +49,7 @@ export class DiagnosticsStore {
* Get diagnostics for a specific session
*/
getBySessionId(sessionId: string): DiagnosticRow[] {
const stmt = this.db.prepare(`
const stmt = this.db.query(`
SELECT * FROM diagnostics
WHERE session_id = ?
ORDER BY created_at_epoch DESC
@@ -61,7 +61,7 @@ export class DiagnosticsStore {
* Get recent diagnostics for a project
*/
getRecentForProject(project: string, limit = 10): DiagnosticRow[] {
const stmt = this.db.prepare(`
const stmt = this.db.query(`
SELECT * FROM diagnostics
WHERE project = ?
ORDER BY created_at_epoch DESC
@@ -74,7 +74,7 @@ export class DiagnosticsStore {
* Get recent diagnostics across all projects
*/
getRecent(limit = 10): DiagnosticRow[] {
const stmt = this.db.prepare(`
const stmt = this.db.query(`
SELECT * FROM diagnostics
ORDER BY created_at_epoch DESC
LIMIT ?
@@ -90,7 +90,7 @@ export class DiagnosticsStore {
? 'SELECT * FROM diagnostics WHERE severity = ? ORDER BY created_at_epoch DESC LIMIT ?'
: 'SELECT * FROM diagnostics WHERE severity = ? ORDER BY created_at_epoch DESC';
const stmt = this.db.prepare(query);
const stmt = this.db.query(query);
const params = limit ? [severity, limit] : [severity];
return stmt.all(...params) as DiagnosticRow[];
}
@@ -103,7 +103,7 @@ export class DiagnosticsStore {
? 'SELECT * FROM diagnostics WHERE origin = ? ORDER BY created_at_epoch DESC LIMIT ?'
: 'SELECT * FROM diagnostics WHERE origin = ? ORDER BY created_at_epoch DESC';
const stmt = this.db.prepare(query);
const stmt = this.db.query(query);
const params = limit ? [origin, limit] : [origin];
return stmt.all(...params) as DiagnosticRow[];
}
@@ -123,7 +123,7 @@ export class DiagnosticsStore {
sql += ' ORDER BY created_at_epoch DESC LIMIT ?';
params.push(limit);
const stmt = this.db.prepare(sql);
const stmt = this.db.query(sql);
return stmt.all(...params) as DiagnosticRow[];
}
@@ -131,7 +131,7 @@ export class DiagnosticsStore {
* Count total diagnostics
*/
count(): number {
const stmt = this.db.prepare('SELECT COUNT(*) as count FROM diagnostics');
const stmt = this.db.query('SELECT COUNT(*) as count FROM diagnostics');
const result = stmt.get() as { count: number };
return result.count;
}
@@ -140,7 +140,7 @@ export class DiagnosticsStore {
* Count diagnostics by project
*/
countByProject(project: string): number {
const stmt = this.db.prepare('SELECT COUNT(*) as count FROM diagnostics WHERE project = ?');
const stmt = this.db.query('SELECT COUNT(*) as count FROM diagnostics WHERE project = ?');
const result = stmt.get(project) as { count: number };
return result.count;
}
@@ -149,7 +149,7 @@ export class DiagnosticsStore {
* Count diagnostics by severity
*/
countBySeverity(severity: 'info' | 'warn' | 'error'): number {
const stmt = this.db.prepare('SELECT COUNT(*) as count FROM diagnostics WHERE severity = ?');
const stmt = this.db.query('SELECT COUNT(*) as count FROM diagnostics WHERE severity = ?');
const result = stmt.get(severity) as { count: number };
return result.count;
}
@@ -165,7 +165,7 @@ export class DiagnosticsStore {
const { isoString, epoch } = normalizeTimestamp(input.created_at || existing.created_at);
const stmt = this.db.prepare(`
const stmt = this.db.query(`
UPDATE diagnostics SET
message = ?, severity = ?, created_at = ?, created_at_epoch = ?, project = ?, origin = ?
WHERE id = ?
@@ -188,7 +188,7 @@ export class DiagnosticsStore {
* Delete a diagnostic by ID
*/
deleteById(id: number): boolean {
const stmt = this.db.prepare('DELETE FROM diagnostics WHERE id = ?');
const stmt = this.db.query('DELETE FROM diagnostics WHERE id = ?');
const info = stmt.run(id);
return info.changes > 0;
}
@@ -197,7 +197,7 @@ export class DiagnosticsStore {
* Delete diagnostics by session_id
*/
deleteBySessionId(sessionId: string): number {
const stmt = this.db.prepare('DELETE FROM diagnostics WHERE session_id = ?');
const stmt = this.db.query('DELETE FROM diagnostics WHERE session_id = ?');
const info = stmt.run(sessionId);
return info.changes;
}
@@ -206,7 +206,7 @@ export class DiagnosticsStore {
* Get unique projects from diagnostics
*/
getUniqueProjects(): string[] {
const stmt = this.db.prepare('SELECT DISTINCT project FROM diagnostics ORDER BY project');
const stmt = this.db.query('SELECT DISTINCT project FROM diagnostics ORDER BY project');
const rows = stmt.all() as { project: string }[];
return rows.map(row => row.project);
}
@@ -215,7 +215,7 @@ export class DiagnosticsStore {
* Get diagnostic summary stats
*/
getStats(): { total: number; info: number; warn: number; error: number } {
const stmt = this.db.prepare(`
const stmt = this.db.query(`
SELECT
COUNT(*) as total,
COUNT(CASE WHEN severity = 'info' THEN 1 END) as info,
+19 -19
View File
@@ -1,4 +1,4 @@
import { Database } from 'better-sqlite3';
import { Database } from 'bun:sqlite';
import { getDatabase } from './Database.js';
import { MemoryRow, MemoryInput, normalizeTimestamp } from './types.js';
@@ -18,7 +18,7 @@ export class MemoryStore {
create(input: MemoryInput): MemoryRow {
const { isoString, epoch } = normalizeTimestamp(input.created_at);
const stmt = this.db.prepare(`
const stmt = this.db.query(`
INSERT INTO memories (
session_id, text, document_id, keywords, created_at, created_at_epoch,
project, archive_basename, origin, title, subtitle, facts, concepts, files_touched
@@ -64,7 +64,7 @@ export class MemoryStore {
* Get memory by primary key
*/
getById(id: number): MemoryRow | null {
const stmt = this.db.prepare('SELECT * FROM memories WHERE id = ?');
const stmt = this.db.query('SELECT * FROM memories WHERE id = ?');
return stmt.get(id) as MemoryRow || null;
}
@@ -72,7 +72,7 @@ export class MemoryStore {
* Get memory by document_id
*/
getByDocumentId(documentId: string): MemoryRow | null {
const stmt = this.db.prepare('SELECT * FROM memories WHERE document_id = ?');
const stmt = this.db.query('SELECT * FROM memories WHERE document_id = ?');
return stmt.get(documentId) as MemoryRow || null;
}
@@ -80,7 +80,7 @@ export class MemoryStore {
* Check if a document_id already exists
*/
hasDocumentId(documentId: string): boolean {
const stmt = this.db.prepare('SELECT 1 FROM memories WHERE document_id = ? LIMIT 1');
const stmt = this.db.query('SELECT 1 FROM memories WHERE document_id = ? LIMIT 1');
return Boolean(stmt.get(documentId));
}
@@ -88,7 +88,7 @@ export class MemoryStore {
* Get memories for a specific session
*/
getBySessionId(sessionId: string): MemoryRow[] {
const stmt = this.db.prepare(`
const stmt = this.db.query(`
SELECT * FROM memories
WHERE session_id = ?
ORDER BY created_at_epoch DESC
@@ -100,7 +100,7 @@ export class MemoryStore {
* Get recent memories for a project
*/
getRecentForProject(project: string, limit = 10): MemoryRow[] {
const stmt = this.db.prepare(`
const stmt = this.db.query(`
SELECT * FROM memories
WHERE project = ?
ORDER BY created_at_epoch DESC
@@ -113,7 +113,7 @@ export class MemoryStore {
* Get recent memories across all projects
*/
getRecent(limit = 10): MemoryRow[] {
const stmt = this.db.prepare(`
const stmt = this.db.query(`
SELECT * FROM memories
ORDER BY created_at_epoch DESC
LIMIT ?
@@ -136,7 +136,7 @@ export class MemoryStore {
sql += ' ORDER BY created_at_epoch DESC LIMIT ?';
params.push(limit);
const stmt = this.db.prepare(sql);
const stmt = this.db.query(sql);
return stmt.all(...params) as MemoryRow[];
}
@@ -155,7 +155,7 @@ export class MemoryStore {
sql += ' ORDER BY created_at_epoch DESC LIMIT ?';
params.push(limit);
const stmt = this.db.prepare(sql);
const stmt = this.db.query(sql);
return stmt.all(...params) as MemoryRow[];
}
@@ -167,7 +167,7 @@ export class MemoryStore {
? 'SELECT * FROM memories WHERE origin = ? ORDER BY created_at_epoch DESC LIMIT ?'
: 'SELECT * FROM memories WHERE origin = ? ORDER BY created_at_epoch DESC';
const stmt = this.db.prepare(query);
const stmt = this.db.query(query);
const params = limit ? [origin, limit] : [origin];
return stmt.all(...params) as MemoryRow[];
}
@@ -176,7 +176,7 @@ export class MemoryStore {
* Get recent memories for a project filtered by origin
*/
getRecentForProjectByOrigin(project: string, origin: string, limit = 10): MemoryRow[] {
const stmt = this.db.prepare(`
const stmt = this.db.query(`
SELECT * FROM memories
WHERE project = ? AND origin = ?
ORDER BY created_at_epoch DESC
@@ -189,7 +189,7 @@ export class MemoryStore {
* Get last N memories for a project, sorted oldest to newest
*/
getLastNForProject(project: string, limit = 10): MemoryRow[] {
const stmt = this.db.prepare(`
const stmt = this.db.query(`
SELECT * FROM (
SELECT * FROM memories
WHERE project = ?
@@ -205,7 +205,7 @@ export class MemoryStore {
* Count total memories
*/
count(): number {
const stmt = this.db.prepare('SELECT COUNT(*) as count FROM memories');
const stmt = this.db.query('SELECT COUNT(*) as count FROM memories');
const result = stmt.get() as { count: number };
return result.count;
}
@@ -214,7 +214,7 @@ export class MemoryStore {
* Count memories by project
*/
countByProject(project: string): number {
const stmt = this.db.prepare('SELECT COUNT(*) as count FROM memories WHERE project = ?');
const stmt = this.db.query('SELECT COUNT(*) as count FROM memories WHERE project = ?');
const result = stmt.get(project) as { count: number };
return result.count;
}
@@ -230,7 +230,7 @@ export class MemoryStore {
const { isoString, epoch } = normalizeTimestamp(input.created_at || existing.created_at);
const stmt = this.db.prepare(`
const stmt = this.db.query(`
UPDATE memories SET
text = ?, document_id = ?, keywords = ?, created_at = ?, created_at_epoch = ?,
project = ?, archive_basename = ?, origin = ?, title = ?, subtitle = ?, facts = ?,
@@ -262,7 +262,7 @@ export class MemoryStore {
* Delete a memory by ID
*/
deleteById(id: number): boolean {
const stmt = this.db.prepare('DELETE FROM memories WHERE id = ?');
const stmt = this.db.query('DELETE FROM memories WHERE id = ?');
const info = stmt.run(id);
return info.changes > 0;
}
@@ -271,7 +271,7 @@ export class MemoryStore {
* Delete memories by session_id
*/
deleteBySessionId(sessionId: string): number {
const stmt = this.db.prepare('DELETE FROM memories WHERE session_id = ?');
const stmt = this.db.query('DELETE FROM memories WHERE session_id = ?');
const info = stmt.run(sessionId);
return info.changes;
}
@@ -280,7 +280,7 @@ export class MemoryStore {
* Get unique projects from memories
*/
getUniqueProjects(): string[] {
const stmt = this.db.prepare('SELECT DISTINCT project FROM memories ORDER BY project');
const stmt = this.db.query('SELECT DISTINCT project FROM memories ORDER BY project');
const rows = stmt.all() as { project: string }[];
return rows.map(row => row.project);
}
+17 -17
View File
@@ -1,4 +1,4 @@
import { Database } from 'better-sqlite3';
import { Database } from 'bun:sqlite';
import { getDatabase } from './Database.js';
import { OverviewRow, OverviewInput, normalizeTimestamp } from './types.js';
@@ -18,7 +18,7 @@ export class OverviewStore {
create(input: OverviewInput): OverviewRow {
const { isoString, epoch } = normalizeTimestamp(input.created_at);
const stmt = this.db.prepare(`
const stmt = this.db.query(`
INSERT INTO overviews (
session_id, content, created_at, created_at_epoch, project, origin
) VALUES (?, ?, ?, ?, ?, ?)
@@ -51,7 +51,7 @@ export class OverviewStore {
* Get overview by primary key
*/
getById(id: number): OverviewRow | null {
const stmt = this.db.prepare('SELECT * FROM overviews WHERE id = ?');
const stmt = this.db.query('SELECT * FROM overviews WHERE id = ?');
return stmt.get(id) as OverviewRow || null;
}
@@ -59,7 +59,7 @@ export class OverviewStore {
* Get overview by session_id
*/
getBySessionId(sessionId: string): OverviewRow | null {
const stmt = this.db.prepare('SELECT * FROM overviews WHERE session_id = ?');
const stmt = this.db.query('SELECT * FROM overviews WHERE session_id = ?');
return stmt.get(sessionId) as OverviewRow || null;
}
@@ -67,7 +67,7 @@ export class OverviewStore {
* Get recent overviews for a project
*/
getRecentForProject(project: string, limit = 5): OverviewRow[] {
const stmt = this.db.prepare(`
const stmt = this.db.query(`
SELECT * FROM overviews
WHERE project = ?
ORDER BY created_at_epoch DESC
@@ -80,7 +80,7 @@ export class OverviewStore {
* Get all overviews for a project (oldest to newest)
*/
getAllForProject(project: string): OverviewRow[] {
const stmt = this.db.prepare(`
const stmt = this.db.query(`
SELECT * FROM overviews
WHERE project = ?
ORDER BY created_at_epoch ASC
@@ -92,7 +92,7 @@ export class OverviewStore {
* Get recent overviews across all projects
*/
getRecent(limit = 5): OverviewRow[] {
const stmt = this.db.prepare(`
const stmt = this.db.query(`
SELECT * FROM overviews
ORDER BY created_at_epoch DESC
LIMIT ?
@@ -115,7 +115,7 @@ export class OverviewStore {
sql += ' ORDER BY created_at_epoch DESC LIMIT ?';
params.push(limit);
const stmt = this.db.prepare(sql);
const stmt = this.db.query(sql);
return stmt.all(...params) as OverviewRow[];
}
@@ -127,7 +127,7 @@ export class OverviewStore {
? 'SELECT * FROM overviews WHERE origin = ? ORDER BY created_at_epoch DESC LIMIT ?'
: 'SELECT * FROM overviews WHERE origin = ? ORDER BY created_at_epoch DESC';
const stmt = this.db.prepare(query);
const stmt = this.db.query(query);
const params = limit ? [origin, limit] : [origin];
return stmt.all(...params) as OverviewRow[];
}
@@ -136,7 +136,7 @@ export class OverviewStore {
* Count total overviews
*/
count(): number {
const stmt = this.db.prepare('SELECT COUNT(*) as count FROM overviews');
const stmt = this.db.query('SELECT COUNT(*) as count FROM overviews');
const result = stmt.get() as { count: number };
return result.count;
}
@@ -145,7 +145,7 @@ export class OverviewStore {
* Count overviews by project
*/
countByProject(project: string): number {
const stmt = this.db.prepare('SELECT COUNT(*) as count FROM overviews WHERE project = ?');
const stmt = this.db.query('SELECT COUNT(*) as count FROM overviews WHERE project = ?');
const result = stmt.get(project) as { count: number };
return result.count;
}
@@ -161,7 +161,7 @@ export class OverviewStore {
const { isoString, epoch } = normalizeTimestamp(input.created_at || existing.created_at);
const stmt = this.db.prepare(`
const stmt = this.db.query(`
UPDATE overviews SET
content = ?, created_at = ?, created_at_epoch = ?, project = ?, origin = ?
WHERE id = ?
@@ -183,7 +183,7 @@ export class OverviewStore {
* Delete an overview by ID
*/
deleteById(id: number): boolean {
const stmt = this.db.prepare('DELETE FROM overviews WHERE id = ?');
const stmt = this.db.query('DELETE FROM overviews WHERE id = ?');
const info = stmt.run(id);
return info.changes > 0;
}
@@ -192,7 +192,7 @@ export class OverviewStore {
* Delete overview by session_id
*/
deleteBySessionId(sessionId: string): boolean {
const stmt = this.db.prepare('DELETE FROM overviews WHERE session_id = ?');
const stmt = this.db.query('DELETE FROM overviews WHERE session_id = ?');
const info = stmt.run(sessionId);
return info.changes > 0;
}
@@ -201,7 +201,7 @@ export class OverviewStore {
* Get unique projects from overviews
*/
getUniqueProjects(): string[] {
const stmt = this.db.prepare('SELECT DISTINCT project FROM overviews ORDER BY project');
const stmt = this.db.query('SELECT DISTINCT project FROM overviews ORDER BY project');
const rows = stmt.all() as { project: string }[];
return rows.map(row => row.project);
}
@@ -210,7 +210,7 @@ export class OverviewStore {
* Get most recent overview for a specific project
*/
getByProject(project: string): OverviewRow | null {
const stmt = this.db.prepare(`
const stmt = this.db.query(`
SELECT * FROM overviews
WHERE project = ?
ORDER BY created_at_epoch DESC
@@ -234,7 +234,7 @@ export class OverviewStore {
* Delete overview by project name
*/
deleteByProject(project: string): boolean {
const stmt = this.db.prepare('DELETE FROM overviews WHERE project = ?');
const stmt = this.db.query('DELETE FROM overviews WHERE project = ?');
const info = stmt.run(project);
return info.changes > 0;
}
+14 -14
View File
@@ -1,4 +1,4 @@
import { Database } from 'better-sqlite3';
import { Database } from 'bun:sqlite';
import { getDatabase } from './Database.js';
import { SessionRow, SessionInput, normalizeTimestamp } from './types.js';
@@ -18,7 +18,7 @@ export class SessionStore {
create(input: SessionInput): SessionRow {
const { isoString, epoch } = normalizeTimestamp(input.created_at);
const stmt = this.db.prepare(`
const stmt = this.db.query(`
INSERT INTO sessions (
session_id, project, created_at, created_at_epoch, source,
archive_path, archive_bytes, archive_checksum, archived_at, metadata_json
@@ -63,7 +63,7 @@ export class SessionStore {
const { isoString, epoch } = normalizeTimestamp(input.created_at || existing.created_at);
const stmt = this.db.prepare(`
const stmt = this.db.query(`
UPDATE sessions SET
project = ?, created_at = ?, created_at_epoch = ?, source = ?,
archive_path = ?, archive_bytes = ?, archive_checksum = ?, archived_at = ?, metadata_json = ?
@@ -90,7 +90,7 @@ export class SessionStore {
* Get session by primary key
*/
getById(id: number): SessionRow | null {
const stmt = this.db.prepare('SELECT * FROM sessions WHERE id = ?');
const stmt = this.db.query('SELECT * FROM sessions WHERE id = ?');
return stmt.get(id) as SessionRow || null;
}
@@ -98,7 +98,7 @@ export class SessionStore {
* Get session by session_id
*/
getBySessionId(sessionId: string): SessionRow | null {
const stmt = this.db.prepare('SELECT * FROM sessions WHERE session_id = ?');
const stmt = this.db.query('SELECT * FROM sessions WHERE session_id = ?');
return stmt.get(sessionId) as SessionRow || null;
}
@@ -106,7 +106,7 @@ export class SessionStore {
* Check if a session exists by session_id
*/
has(sessionId: string): boolean {
const stmt = this.db.prepare('SELECT 1 FROM sessions WHERE session_id = ? LIMIT 1');
const stmt = this.db.query('SELECT 1 FROM sessions WHERE session_id = ? LIMIT 1');
return Boolean(stmt.get(sessionId));
}
@@ -114,7 +114,7 @@ export class SessionStore {
* Get all session_ids as a Set (useful for import-history)
*/
getAllSessionIds(): Set<string> {
const stmt = this.db.prepare('SELECT session_id FROM sessions');
const stmt = this.db.query('SELECT session_id FROM sessions');
const rows = stmt.all() as { session_id: string }[];
return new Set(rows.map(row => row.session_id));
}
@@ -123,7 +123,7 @@ export class SessionStore {
* Get recent sessions for a project
*/
getRecentForProject(project: string, limit = 5): SessionRow[] {
const stmt = this.db.prepare(`
const stmt = this.db.query(`
SELECT * FROM sessions
WHERE project = ?
ORDER BY created_at_epoch DESC
@@ -136,7 +136,7 @@ export class SessionStore {
* Get recent sessions across all projects
*/
getRecent(limit = 5): SessionRow[] {
const stmt = this.db.prepare(`
const stmt = this.db.query(`
SELECT * FROM sessions
ORDER BY created_at_epoch DESC
LIMIT ?
@@ -152,7 +152,7 @@ export class SessionStore {
? 'SELECT * FROM sessions WHERE source = ? ORDER BY created_at_epoch DESC LIMIT ?'
: 'SELECT * FROM sessions WHERE source = ? ORDER BY created_at_epoch DESC';
const stmt = this.db.prepare(query);
const stmt = this.db.query(query);
const params = limit ? [source, limit] : [source];
return stmt.all(...params) as SessionRow[];
}
@@ -161,7 +161,7 @@ export class SessionStore {
* Count total sessions
*/
count(): number {
const stmt = this.db.prepare('SELECT COUNT(*) as count FROM sessions');
const stmt = this.db.query('SELECT COUNT(*) as count FROM sessions');
const result = stmt.get() as { count: number };
return result.count;
}
@@ -170,7 +170,7 @@ export class SessionStore {
* Count sessions by project
*/
countByProject(project: string): number {
const stmt = this.db.prepare('SELECT COUNT(*) as count FROM sessions WHERE project = ?');
const stmt = this.db.query('SELECT COUNT(*) as count FROM sessions WHERE project = ?');
const result = stmt.get(project) as { count: number };
return result.count;
}
@@ -179,7 +179,7 @@ export class SessionStore {
* Delete a session by ID (cascades to related records)
*/
deleteById(id: number): boolean {
const stmt = this.db.prepare('DELETE FROM sessions WHERE id = ?');
const stmt = this.db.query('DELETE FROM sessions WHERE id = ?');
const info = stmt.run(id);
return info.changes > 0;
}
@@ -188,7 +188,7 @@ export class SessionStore {
* Delete a session by session_id (cascades to related records)
*/
deleteBySessionId(sessionId: string): boolean {
const stmt = this.db.prepare('DELETE FROM sessions WHERE session_id = ?');
const stmt = this.db.query('DELETE FROM sessions WHERE session_id = ?');
const info = stmt.run(sessionId);
return info.changes > 0;
}
+13 -13
View File
@@ -1,4 +1,4 @@
import { Database } from 'better-sqlite3';
import { Database } from 'bun:sqlite';
import { getDatabase } from './Database.js';
import { normalizeTimestamp } from './types.js';
@@ -61,7 +61,7 @@ export class StreamingSessionStore {
create(input: StreamingSessionInput): StreamingSessionRow {
const { isoString, epoch } = normalizeTimestamp(input.started_at);
const stmt = this.db.prepare(`
const stmt = this.db.query(`
INSERT INTO streaming_sessions (
claude_session_id, project, user_prompt, started_at, started_at_epoch, status
) VALUES (?, ?, ?, ?, ?, 'active')
@@ -121,7 +121,7 @@ export class StreamingSessionStore {
values.push(id);
const stmt = this.db.prepare(`
const stmt = this.db.query(`
UPDATE streaming_sessions
SET ${parts.join(', ')}
WHERE id = ?
@@ -148,7 +148,7 @@ export class StreamingSessionStore {
* Get streaming session by internal ID
*/
getById(id: number): StreamingSessionRow | null {
const stmt = this.db.prepare('SELECT * FROM streaming_sessions WHERE id = ?');
const stmt = this.db.query('SELECT * FROM streaming_sessions WHERE id = ?');
return stmt.get(id) as StreamingSessionRow || null;
}
@@ -156,7 +156,7 @@ export class StreamingSessionStore {
* Get streaming session by Claude session ID
*/
getByClaudeSessionId(claudeSessionId: string): StreamingSessionRow | null {
const stmt = this.db.prepare('SELECT * FROM streaming_sessions WHERE claude_session_id = ?');
const stmt = this.db.query('SELECT * FROM streaming_sessions WHERE claude_session_id = ?');
return stmt.get(claudeSessionId) as StreamingSessionRow || null;
}
@@ -164,7 +164,7 @@ export class StreamingSessionStore {
* Get streaming session by SDK session ID
*/
getBySdkSessionId(sdkSessionId: string): StreamingSessionRow | null {
const stmt = this.db.prepare('SELECT * FROM streaming_sessions WHERE sdk_session_id = ?');
const stmt = this.db.query('SELECT * FROM streaming_sessions WHERE sdk_session_id = ?');
return stmt.get(sdkSessionId) as StreamingSessionRow || null;
}
@@ -172,7 +172,7 @@ export class StreamingSessionStore {
* Check if a streaming session exists by Claude session ID
*/
has(claudeSessionId: string): boolean {
const stmt = this.db.prepare('SELECT 1 FROM streaming_sessions WHERE claude_session_id = ? LIMIT 1');
const stmt = this.db.query('SELECT 1 FROM streaming_sessions WHERE claude_session_id = ? LIMIT 1');
return Boolean(stmt.get(claudeSessionId));
}
@@ -180,7 +180,7 @@ export class StreamingSessionStore {
* Get active streaming sessions for a project
*/
getActiveForProject(project: string): StreamingSessionRow[] {
const stmt = this.db.prepare(`
const stmt = this.db.query(`
SELECT * FROM streaming_sessions
WHERE project = ? AND status = 'active'
ORDER BY started_at_epoch DESC
@@ -192,7 +192,7 @@ export class StreamingSessionStore {
* Get all active streaming sessions
*/
getAllActive(): StreamingSessionRow[] {
const stmt = this.db.prepare(`
const stmt = this.db.query(`
SELECT * FROM streaming_sessions
WHERE status = 'active'
ORDER BY started_at_epoch DESC
@@ -204,7 +204,7 @@ export class StreamingSessionStore {
* Get recent streaming sessions (completed or failed)
*/
getRecent(limit = 10): StreamingSessionRow[] {
const stmt = this.db.prepare(`
const stmt = this.db.query(`
SELECT * FROM streaming_sessions
ORDER BY started_at_epoch DESC
LIMIT ?
@@ -236,7 +236,7 @@ export class StreamingSessionStore {
* Delete a streaming session by ID
*/
deleteById(id: number): boolean {
const stmt = this.db.prepare('DELETE FROM streaming_sessions WHERE id = ?');
const stmt = this.db.query('DELETE FROM streaming_sessions WHERE id = ?');
const info = stmt.run(id);
return info.changes > 0;
}
@@ -245,7 +245,7 @@ export class StreamingSessionStore {
* Delete a streaming session by Claude session ID
*/
deleteByClaudeSessionId(claudeSessionId: string): boolean {
const stmt = this.db.prepare('DELETE FROM streaming_sessions WHERE claude_session_id = ?');
const stmt = this.db.query('DELETE FROM streaming_sessions WHERE claude_session_id = ?');
const info = stmt.run(claudeSessionId);
return info.changes > 0;
}
@@ -255,7 +255,7 @@ export class StreamingSessionStore {
*/
cleanupOldSessions(daysOld = 30): number {
const cutoffEpoch = Date.now() - (daysOld * 24 * 60 * 60 * 1000);
const stmt = this.db.prepare(`
const stmt = this.db.query(`
DELETE FROM streaming_sessions
WHERE status IN ('completed', 'failed')
AND completed_at_epoch < ?
+5 -5
View File
@@ -1,4 +1,4 @@
import { Database } from 'better-sqlite3';
import { Database } from 'bun:sqlite';
import { getDatabase } from './Database.js';
import {
TranscriptEventInput,
@@ -22,7 +22,7 @@ export class TranscriptEventStore {
upsert(event: TranscriptEventInput): TranscriptEventRow {
const { isoString, epoch } = normalizeTimestamp(event.captured_at);
const stmt = this.db.prepare(`
const stmt = this.db.query(`
INSERT INTO transcript_events (
session_id,
project,
@@ -72,7 +72,7 @@ export class TranscriptEventStore {
* Get event by session and index
*/
getBySessionAndIndex(sessionId: string, eventIndex: number): TranscriptEventRow | null {
const stmt = this.db.prepare(`
const stmt = this.db.query(`
SELECT * FROM transcript_events
WHERE session_id = ? AND event_index = ?
`);
@@ -83,7 +83,7 @@ export class TranscriptEventStore {
* Get highest event_index stored for a session
*/
getMaxEventIndex(sessionId: string): number {
const stmt = this.db.prepare(`
const stmt = this.db.query(`
SELECT MAX(event_index) as max_event_index
FROM transcript_events
WHERE session_id = ?
@@ -96,7 +96,7 @@ export class TranscriptEventStore {
* List recent events for a session
*/
listBySession(sessionId: string, limit = 200, offset = 0): TranscriptEventRow[] {
const stmt = this.db.prepare(`
const stmt = this.db.query(`
SELECT * FROM transcript_events
WHERE session_id = ?
ORDER BY event_index ASC
+17 -17
View File
@@ -1,4 +1,4 @@
import { Database } from 'better-sqlite3';
import { Database } from 'bun:sqlite';
import { Migration } from './Database.js';
/**
@@ -6,9 +6,9 @@ import { Migration } from './Database.js';
*/
export const migration001: Migration = {
version: 1,
up: (db: Database.Database) => {
up: (db: Database) => {
// Sessions table - core session tracking
db.exec(`
db.run(`
CREATE TABLE IF NOT EXISTS sessions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
session_id TEXT UNIQUE NOT NULL,
@@ -29,7 +29,7 @@ export const migration001: Migration = {
`);
// Memories table - compressed memory chunks
db.exec(`
db.run(`
CREATE TABLE IF NOT EXISTS memories (
id INTEGER PRIMARY KEY AUTOINCREMENT,
session_id TEXT NOT NULL,
@@ -53,7 +53,7 @@ export const migration001: Migration = {
`);
// Overviews table - session summaries (one per project)
db.exec(`
db.run(`
CREATE TABLE IF NOT EXISTS overviews (
id INTEGER PRIMARY KEY AUTOINCREMENT,
session_id TEXT NOT NULL,
@@ -73,7 +73,7 @@ export const migration001: Migration = {
`);
// Diagnostics table - system health and debug info
db.exec(`
db.run(`
CREATE TABLE IF NOT EXISTS diagnostics (
id INTEGER PRIMARY KEY AUTOINCREMENT,
session_id TEXT,
@@ -93,7 +93,7 @@ export const migration001: Migration = {
`);
// Transcript events table - raw conversation events
db.exec(`
db.run(`
CREATE TABLE IF NOT EXISTS transcript_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
session_id TEXT NOT NULL,
@@ -116,8 +116,8 @@ export const migration001: Migration = {
console.log('✅ Created all database tables successfully');
},
down: (db: Database.Database) => {
db.exec(`
down: (db: Database) => {
db.run(`
DROP TABLE IF EXISTS transcript_events;
DROP TABLE IF EXISTS diagnostics;
DROP TABLE IF EXISTS overviews;
@@ -132,9 +132,9 @@ export const migration001: Migration = {
*/
export const migration002: Migration = {
version: 2,
up: (db: Database.Database) => {
up: (db: Database) => {
// Add new columns for hierarchical memory structure
db.exec(`
db.run(`
ALTER TABLE memories ADD COLUMN title TEXT;
ALTER TABLE memories ADD COLUMN subtitle TEXT;
ALTER TABLE memories ADD COLUMN facts TEXT;
@@ -143,7 +143,7 @@ export const migration002: Migration = {
`);
// Create indexes for the new fields to improve search performance
db.exec(`
db.run(`
CREATE INDEX IF NOT EXISTS idx_memories_title ON memories(title);
CREATE INDEX IF NOT EXISTS idx_memories_concepts ON memories(concepts);
`);
@@ -151,7 +151,7 @@ export const migration002: Migration = {
console.log('✅ Added hierarchical memory fields to memories table');
},
down: (db: Database.Database) => {
down: (db: Database) => {
// Note: SQLite doesn't support DROP COLUMN in all versions
// In production, we'd need to recreate the table without these columns
// For now, we'll just log a warning
@@ -165,9 +165,9 @@ export const migration002: Migration = {
*/
export const migration003: Migration = {
version: 3,
up: (db: Database.Database) => {
up: (db: Database) => {
// Streaming sessions table - tracks active SDK compression sessions
db.exec(`
db.run(`
CREATE TABLE IF NOT EXISTS streaming_sessions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
claude_session_id TEXT UNIQUE NOT NULL,
@@ -195,8 +195,8 @@ export const migration003: Migration = {
console.log('✅ Created streaming_sessions table for real-time session tracking');
},
down: (db: Database.Database) => {
db.exec(`
down: (db: Database) => {
db.run(`
DROP TABLE IF EXISTS streaming_sessions;
`);
}