Refactor: Remove legacy data access objects and logging utilities

- Deleted MemoryStore, OverviewStore, SessionStore, StreamingSessionStore, and TranscriptEventStore classes to streamline database interactions.
- Removed logger and rolling log utilities to simplify logging mechanisms.
- Updated index file to reflect the removal of stores and logging functionalities.
This commit is contained in:
Alex Newman
2025-10-15 20:02:15 -04:00
parent 047298a183
commit 7307563cfe
24 changed files with 165 additions and 5111 deletions
-229
View File
@@ -1,229 +0,0 @@
import { Database } from 'bun:sqlite';
import { getDatabase } from './Database.js';
import { DiagnosticRow, DiagnosticInput, normalizeTimestamp } from './types.js';
/**
* Data Access Object for diagnostic records
*/
export class DiagnosticsStore {
private db: Database.Database;
constructor(db?: Database.Database) {
this.db = db || getDatabase();
}
/**
* Create a new diagnostic record
*/
create(input: DiagnosticInput): DiagnosticRow {
const { isoString, epoch } = normalizeTimestamp(input.created_at);
const stmt = this.db.query(`
INSERT INTO diagnostics (
session_id, message, severity, created_at, created_at_epoch, project, origin
) VALUES (?, ?, ?, ?, ?, ?, ?)
`);
const info = stmt.run(
input.session_id || null,
input.message,
input.severity || 'warn',
isoString,
epoch,
input.project,
input.origin || 'compressor'
);
return this.getById(info.lastInsertRowid as number)!;
}
/**
* Get diagnostic by primary key
*/
getById(id: number): DiagnosticRow | null {
const stmt = this.db.query('SELECT * FROM diagnostics WHERE id = ?');
return stmt.get(id) as DiagnosticRow || null;
}
/**
* Get diagnostics for a specific session
*/
getBySessionId(sessionId: string): DiagnosticRow[] {
const stmt = this.db.query(`
SELECT * FROM diagnostics
WHERE session_id = ?
ORDER BY created_at_epoch DESC
`);
return stmt.all(sessionId) as DiagnosticRow[];
}
/**
* Get recent diagnostics for a project
*/
getRecentForProject(project: string, limit = 10): DiagnosticRow[] {
const stmt = this.db.query(`
SELECT * FROM diagnostics
WHERE project = ?
ORDER BY created_at_epoch DESC
LIMIT ?
`);
return stmt.all(project, limit) as DiagnosticRow[];
}
/**
* Get recent diagnostics across all projects
*/
getRecent(limit = 10): DiagnosticRow[] {
const stmt = this.db.query(`
SELECT * FROM diagnostics
ORDER BY created_at_epoch DESC
LIMIT ?
`);
return stmt.all(limit) as DiagnosticRow[];
}
/**
* Get diagnostics by severity level
*/
getBySeverity(severity: 'info' | 'warn' | 'error', limit?: number): DiagnosticRow[] {
const query = limit
? '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.query(query);
const params = limit ? [severity, limit] : [severity];
return stmt.all(...params) as DiagnosticRow[];
}
/**
* Get diagnostics by origin
*/
getByOrigin(origin: string, limit?: number): DiagnosticRow[] {
const query = limit
? '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.query(query);
const params = limit ? [origin, limit] : [origin];
return stmt.all(...params) as DiagnosticRow[];
}
/**
* Search diagnostics by message content
*/
searchByMessage(query: string, project?: string, limit = 20): DiagnosticRow[] {
let sql = 'SELECT * FROM diagnostics WHERE message LIKE ?';
const params: any[] = [`%${query}%`];
if (project) {
sql += ' AND project = ?';
params.push(project);
}
sql += ' ORDER BY created_at_epoch DESC LIMIT ?';
params.push(limit);
const stmt = this.db.query(sql);
return stmt.all(...params) as DiagnosticRow[];
}
/**
* Count total diagnostics
*/
count(): number {
const stmt = this.db.query('SELECT COUNT(*) as count FROM diagnostics');
const result = stmt.get() as { count: number };
return result.count;
}
/**
* Count diagnostics by project
*/
countByProject(project: string): number {
const stmt = this.db.query('SELECT COUNT(*) as count FROM diagnostics WHERE project = ?');
const result = stmt.get(project) as { count: number };
return result.count;
}
/**
* Count diagnostics by severity
*/
countBySeverity(severity: 'info' | 'warn' | 'error'): number {
const stmt = this.db.query('SELECT COUNT(*) as count FROM diagnostics WHERE severity = ?');
const result = stmt.get(severity) as { count: number };
return result.count;
}
/**
* Update a diagnostic record
*/
update(id: number, input: Partial<DiagnosticInput>): DiagnosticRow {
const existing = this.getById(id);
if (!existing) {
throw new Error(`Diagnostic with id ${id} not found`);
}
const { isoString, epoch } = normalizeTimestamp(input.created_at || existing.created_at);
const stmt = this.db.query(`
UPDATE diagnostics SET
message = ?, severity = ?, created_at = ?, created_at_epoch = ?, project = ?, origin = ?
WHERE id = ?
`);
stmt.run(
input.message || existing.message,
input.severity || existing.severity,
isoString,
epoch,
input.project || existing.project,
input.origin || existing.origin,
id
);
return this.getById(id)!;
}
/**
* Delete a diagnostic by ID
*/
deleteById(id: number): boolean {
const stmt = this.db.query('DELETE FROM diagnostics WHERE id = ?');
const info = stmt.run(id);
return info.changes > 0;
}
/**
* Delete diagnostics by session_id
*/
deleteBySessionId(sessionId: string): number {
const stmt = this.db.query('DELETE FROM diagnostics WHERE session_id = ?');
const info = stmt.run(sessionId);
return info.changes;
}
/**
* Get unique projects from diagnostics
*/
getUniqueProjects(): string[] {
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);
}
/**
* Get diagnostic summary stats
*/
getStats(): { total: number; info: number; warn: number; error: number } {
const stmt = this.db.query(`
SELECT
COUNT(*) as total,
COUNT(CASE WHEN severity = 'info' THEN 1 END) as info,
COUNT(CASE WHEN severity = 'warn' THEN 1 END) as warn,
COUNT(CASE WHEN severity = 'error' THEN 1 END) as error
FROM diagnostics
`);
return stmt.get() as { total: number; info: number; warn: number; error: number };
}
}
-287
View File
@@ -1,287 +0,0 @@
import { Database } from 'bun:sqlite';
import { getDatabase } from './Database.js';
import { MemoryRow, MemoryInput, normalizeTimestamp } from './types.js';
/**
* Data Access Object for memory records
*/
export class MemoryStore {
private db: Database.Database;
constructor(db?: Database.Database) {
this.db = db || getDatabase();
}
/**
* Create a new memory record
*/
create(input: MemoryInput): MemoryRow {
const { isoString, epoch } = normalizeTimestamp(input.created_at);
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
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`);
const info = stmt.run(
input.session_id,
input.text,
input.document_id || null,
input.keywords || null,
isoString,
epoch,
input.project,
input.archive_basename || null,
input.origin || 'transcript',
input.title || null,
input.subtitle || null,
input.facts || null,
input.concepts || null,
input.files_touched || null
);
return this.getById(info.lastInsertRowid as number)!;
}
/**
* Create multiple memory records in a transaction
*/
createMany(inputs: MemoryInput[]): MemoryRow[] {
const transaction = this.db.transaction((memories: MemoryInput[]) => {
const results: MemoryRow[] = [];
for (const memory of memories) {
results.push(this.create(memory));
}
return results;
});
return transaction(inputs);
}
/**
* Get memory by primary key
*/
getById(id: number): MemoryRow | null {
const stmt = this.db.query('SELECT * FROM memories WHERE id = ?');
return stmt.get(id) as MemoryRow || null;
}
/**
* Get memory by document_id
*/
getByDocumentId(documentId: string): MemoryRow | null {
const stmt = this.db.query('SELECT * FROM memories WHERE document_id = ?');
return stmt.get(documentId) as MemoryRow || null;
}
/**
* Check if a document_id already exists
*/
hasDocumentId(documentId: string): boolean {
const stmt = this.db.query('SELECT 1 FROM memories WHERE document_id = ? LIMIT 1');
return Boolean(stmt.get(documentId));
}
/**
* Get memories for a specific session
*/
getBySessionId(sessionId: string): MemoryRow[] {
const stmt = this.db.query(`
SELECT * FROM memories
WHERE session_id = ?
ORDER BY created_at_epoch DESC
`);
return stmt.all(sessionId) as MemoryRow[];
}
/**
* Get recent memories for a project
*/
getRecentForProject(project: string, limit = 10): MemoryRow[] {
const stmt = this.db.query(`
SELECT * FROM memories
WHERE project = ?
ORDER BY created_at_epoch DESC
LIMIT ?
`);
return stmt.all(project, limit) as MemoryRow[];
}
/**
* Get recent memories across all projects
*/
getRecent(limit = 10): MemoryRow[] {
const stmt = this.db.query(`
SELECT * FROM memories
ORDER BY created_at_epoch DESC
LIMIT ?
`);
return stmt.all(limit) as MemoryRow[];
}
/**
* Search memories by text content
*/
searchByText(query: string, project?: string, limit = 20): MemoryRow[] {
let sql = 'SELECT * FROM memories WHERE text LIKE ?';
const params: any[] = [`%${query}%`];
if (project) {
sql += ' AND project = ?';
params.push(project);
}
sql += ' ORDER BY created_at_epoch DESC LIMIT ?';
params.push(limit);
const stmt = this.db.query(sql);
return stmt.all(...params) as MemoryRow[];
}
/**
* Search memories by keywords
*/
searchByKeywords(keywords: string, project?: string, limit = 20): MemoryRow[] {
let sql = 'SELECT * FROM memories WHERE keywords LIKE ?';
const params: any[] = [`%${keywords}%`];
if (project) {
sql += ' AND project = ?';
params.push(project);
}
sql += ' ORDER BY created_at_epoch DESC LIMIT ?';
params.push(limit);
const stmt = this.db.query(sql);
return stmt.all(...params) as MemoryRow[];
}
/**
* Get memories by origin type
*/
getByOrigin(origin: string, limit?: number): MemoryRow[] {
const query = limit
? '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.query(query);
const params = limit ? [origin, limit] : [origin];
return stmt.all(...params) as MemoryRow[];
}
/**
* Get recent memories for a project filtered by origin
*/
getRecentForProjectByOrigin(project: string, origin: string, limit = 10): MemoryRow[] {
const stmt = this.db.query(`
SELECT * FROM memories
WHERE project = ? AND origin = ?
ORDER BY created_at_epoch DESC
LIMIT ?
`);
return stmt.all(project, origin, limit) as MemoryRow[];
}
/**
* Get last N memories for a project, sorted oldest to newest
*/
getLastNForProject(project: string, limit = 10): MemoryRow[] {
const stmt = this.db.query(`
SELECT * FROM (
SELECT * FROM memories
WHERE project = ?
ORDER BY created_at_epoch DESC
LIMIT ?
)
ORDER BY created_at_epoch ASC
`);
return stmt.all(project, limit) as MemoryRow[];
}
/**
* Count total memories
*/
count(): number {
const stmt = this.db.query('SELECT COUNT(*) as count FROM memories');
const result = stmt.get() as { count: number };
return result.count;
}
/**
* Count memories by project
*/
countByProject(project: string): number {
const stmt = this.db.query('SELECT COUNT(*) as count FROM memories WHERE project = ?');
const result = stmt.get(project) as { count: number };
return result.count;
}
/**
* Update a memory record
*/
update(id: number, input: Partial<MemoryInput>): MemoryRow {
const existing = this.getById(id);
if (!existing) {
throw new Error(`Memory with id ${id} not found`);
}
const { isoString, epoch } = normalizeTimestamp(input.created_at || existing.created_at);
const stmt = this.db.query(`
UPDATE memories SET
text = ?, document_id = ?, keywords = ?, created_at = ?, created_at_epoch = ?,
project = ?, archive_basename = ?, origin = ?, title = ?, subtitle = ?, facts = ?,
concepts = ?, files_touched = ?
WHERE id = ?
`);
stmt.run(
input.text || existing.text,
input.document_id !== undefined ? input.document_id : existing.document_id,
input.keywords !== undefined ? input.keywords : existing.keywords,
isoString,
epoch,
input.project || existing.project,
input.archive_basename !== undefined ? input.archive_basename : existing.archive_basename,
input.origin || existing.origin,
input.title !== undefined ? input.title : existing.title,
input.subtitle !== undefined ? input.subtitle : existing.subtitle,
input.facts !== undefined ? input.facts : existing.facts,
input.concepts !== undefined ? input.concepts : existing.concepts,
input.files_touched !== undefined ? input.files_touched : existing.files_touched,
id
);
return this.getById(id)!;
}
/**
* Delete a memory by ID
*/
deleteById(id: number): boolean {
const stmt = this.db.query('DELETE FROM memories WHERE id = ?');
const info = stmt.run(id);
return info.changes > 0;
}
/**
* Delete memories by session_id
*/
deleteBySessionId(sessionId: string): number {
const stmt = this.db.query('DELETE FROM memories WHERE session_id = ?');
const info = stmt.run(sessionId);
return info.changes;
}
/**
* Get unique projects from memories
*/
getUniqueProjects(): string[] {
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);
}
}
-241
View File
@@ -1,241 +0,0 @@
import { Database } from 'bun:sqlite';
import { getDatabase } from './Database.js';
import { OverviewRow, OverviewInput, normalizeTimestamp } from './types.js';
/**
* Data Access Object for overview records
*/
export class OverviewStore {
private db: Database.Database;
constructor(db?: Database.Database) {
this.db = db || getDatabase();
}
/**
* Create a new overview record
*/
create(input: OverviewInput): OverviewRow {
const { isoString, epoch } = normalizeTimestamp(input.created_at);
const stmt = this.db.query(`
INSERT INTO overviews (
session_id, content, created_at, created_at_epoch, project, origin
) VALUES (?, ?, ?, ?, ?, ?)
`);
const info = stmt.run(
input.session_id,
input.content,
isoString,
epoch,
input.project,
input.origin || 'claude'
);
return this.getById(info.lastInsertRowid as number)!;
}
/**
* Create or replace an overview for a session (since one session should have one overview)
*/
upsert(input: OverviewInput): OverviewRow {
const existing = this.getBySessionId(input.session_id);
if (existing) {
return this.update(existing.id, input);
}
return this.create(input);
}
/**
* Get overview by primary key
*/
getById(id: number): OverviewRow | null {
const stmt = this.db.query('SELECT * FROM overviews WHERE id = ?');
return stmt.get(id) as OverviewRow || null;
}
/**
* Get overview by session_id
*/
getBySessionId(sessionId: string): OverviewRow | null {
const stmt = this.db.query('SELECT * FROM overviews WHERE session_id = ?');
return stmt.get(sessionId) as OverviewRow || null;
}
/**
* Get recent overviews for a project
*/
getRecentForProject(project: string, limit = 5): OverviewRow[] {
const stmt = this.db.query(`
SELECT * FROM overviews
WHERE project = ?
ORDER BY created_at_epoch DESC
LIMIT ?
`);
return stmt.all(project, limit) as OverviewRow[];
}
/**
* Get all overviews for a project (oldest to newest)
*/
getAllForProject(project: string): OverviewRow[] {
const stmt = this.db.query(`
SELECT * FROM overviews
WHERE project = ?
ORDER BY created_at_epoch ASC
`);
return stmt.all(project) as OverviewRow[];
}
/**
* Get recent overviews across all projects
*/
getRecent(limit = 5): OverviewRow[] {
const stmt = this.db.query(`
SELECT * FROM overviews
ORDER BY created_at_epoch DESC
LIMIT ?
`);
return stmt.all(limit) as OverviewRow[];
}
/**
* Search overviews by content
*/
searchByContent(query: string, project?: string, limit = 10): OverviewRow[] {
let sql = 'SELECT * FROM overviews WHERE content LIKE ?';
const params: any[] = [`%${query}%`];
if (project) {
sql += ' AND project = ?';
params.push(project);
}
sql += ' ORDER BY created_at_epoch DESC LIMIT ?';
params.push(limit);
const stmt = this.db.query(sql);
return stmt.all(...params) as OverviewRow[];
}
/**
* Get overviews by origin type
*/
getByOrigin(origin: string, limit?: number): OverviewRow[] {
const query = limit
? '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.query(query);
const params = limit ? [origin, limit] : [origin];
return stmt.all(...params) as OverviewRow[];
}
/**
* Count total overviews
*/
count(): number {
const stmt = this.db.query('SELECT COUNT(*) as count FROM overviews');
const result = stmt.get() as { count: number };
return result.count;
}
/**
* Count overviews by project
*/
countByProject(project: string): number {
const stmt = this.db.query('SELECT COUNT(*) as count FROM overviews WHERE project = ?');
const result = stmt.get(project) as { count: number };
return result.count;
}
/**
* Update an overview record
*/
update(id: number, input: Partial<OverviewInput>): OverviewRow {
const existing = this.getById(id);
if (!existing) {
throw new Error(`Overview with id ${id} not found`);
}
const { isoString, epoch } = normalizeTimestamp(input.created_at || existing.created_at);
const stmt = this.db.query(`
UPDATE overviews SET
content = ?, created_at = ?, created_at_epoch = ?, project = ?, origin = ?
WHERE id = ?
`);
stmt.run(
input.content || existing.content,
isoString,
epoch,
input.project || existing.project,
input.origin || existing.origin,
id
);
return this.getById(id)!;
}
/**
* Delete an overview by ID
*/
deleteById(id: number): boolean {
const stmt = this.db.query('DELETE FROM overviews WHERE id = ?');
const info = stmt.run(id);
return info.changes > 0;
}
/**
* Delete overview by session_id
*/
deleteBySessionId(sessionId: string): boolean {
const stmt = this.db.query('DELETE FROM overviews WHERE session_id = ?');
const info = stmt.run(sessionId);
return info.changes > 0;
}
/**
* Get unique projects from overviews
*/
getUniqueProjects(): string[] {
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);
}
/**
* Get most recent overview for a specific project
*/
getByProject(project: string): OverviewRow | null {
const stmt = this.db.query(`
SELECT * FROM overviews
WHERE project = ?
ORDER BY created_at_epoch DESC
LIMIT 1
`);
return stmt.get(project) as OverviewRow || null;
}
/**
* Create or update overview for a project (keeps only most recent)
*/
upsertByProject(input: OverviewInput): OverviewRow {
const existing = this.getByProject(input.project);
if (existing) {
return this.update(existing.id, input);
}
return this.create(input);
}
/**
* Delete overview by project name
*/
deleteByProject(project: string): boolean {
const stmt = this.db.query('DELETE FROM overviews WHERE project = ?');
const info = stmt.run(project);
return info.changes > 0;
}
}
-195
View File
@@ -1,195 +0,0 @@
import { Database } from 'bun:sqlite';
import { getDatabase } from './Database.js';
import { SessionRow, SessionInput, normalizeTimestamp } from './types.js';
/**
* Data Access Object for session records
*/
export class SessionStore {
private db: Database.Database;
constructor(db?: Database.Database) {
this.db = db || getDatabase();
}
/**
* Create a new session record
*/
create(input: SessionInput): SessionRow {
const { isoString, epoch } = normalizeTimestamp(input.created_at);
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
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`);
const info = stmt.run(
input.session_id,
input.project,
isoString,
epoch,
input.source || 'compress',
input.archive_path || null,
input.archive_bytes || null,
input.archive_checksum || null,
input.archived_at || null,
input.metadata_json || null
);
return this.getById(info.lastInsertRowid as number)!;
}
/**
* Upsert a session record (insert or update if session_id exists)
*/
upsert(input: SessionInput): SessionRow {
const existing = this.getBySessionId(input.session_id);
if (existing) {
return this.update(existing.id, input);
}
return this.create(input);
}
/**
* Update an existing session record
*/
update(id: number, input: Partial<SessionInput>): SessionRow {
const existing = this.getById(id);
if (!existing) {
throw new Error(`Session with id ${id} not found`);
}
const { isoString, epoch } = normalizeTimestamp(input.created_at || existing.created_at);
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 = ?
WHERE id = ?
`);
stmt.run(
input.project || existing.project,
isoString,
epoch,
input.source || existing.source,
input.archive_path !== undefined ? input.archive_path : existing.archive_path,
input.archive_bytes !== undefined ? input.archive_bytes : existing.archive_bytes,
input.archive_checksum !== undefined ? input.archive_checksum : existing.archive_checksum,
input.archived_at !== undefined ? input.archived_at : existing.archived_at,
input.metadata_json !== undefined ? input.metadata_json : existing.metadata_json,
id
);
return this.getById(id)!;
}
/**
* Get session by primary key
*/
getById(id: number): SessionRow | null {
const stmt = this.db.query('SELECT * FROM sessions WHERE id = ?');
return stmt.get(id) as SessionRow || null;
}
/**
* Get session by session_id
*/
getBySessionId(sessionId: string): SessionRow | null {
const stmt = this.db.query('SELECT * FROM sessions WHERE session_id = ?');
return stmt.get(sessionId) as SessionRow || null;
}
/**
* Check if a session exists by session_id
*/
has(sessionId: string): boolean {
const stmt = this.db.query('SELECT 1 FROM sessions WHERE session_id = ? LIMIT 1');
return Boolean(stmt.get(sessionId));
}
/**
* Get all session_ids as a Set (useful for import-history)
*/
getAllSessionIds(): Set<string> {
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));
}
/**
* Get recent sessions for a project
*/
getRecentForProject(project: string, limit = 5): SessionRow[] {
const stmt = this.db.query(`
SELECT * FROM sessions
WHERE project = ?
ORDER BY created_at_epoch DESC
LIMIT ?
`);
return stmt.all(project, limit) as SessionRow[];
}
/**
* Get recent sessions across all projects
*/
getRecent(limit = 5): SessionRow[] {
const stmt = this.db.query(`
SELECT * FROM sessions
ORDER BY created_at_epoch DESC
LIMIT ?
`);
return stmt.all(limit) as SessionRow[];
}
/**
* Get sessions by source type
*/
getBySource(source: 'compress' | 'save' | 'legacy-jsonl', limit?: number): SessionRow[] {
const query = limit
? '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.query(query);
const params = limit ? [source, limit] : [source];
return stmt.all(...params) as SessionRow[];
}
/**
* Count total sessions
*/
count(): number {
const stmt = this.db.query('SELECT COUNT(*) as count FROM sessions');
const result = stmt.get() as { count: number };
return result.count;
}
/**
* Count sessions by project
*/
countByProject(project: string): number {
const stmt = this.db.query('SELECT COUNT(*) as count FROM sessions WHERE project = ?');
const result = stmt.get(project) as { count: number };
return result.count;
}
/**
* Delete a session by ID (cascades to related records)
*/
deleteById(id: number): boolean {
const stmt = this.db.query('DELETE FROM sessions WHERE id = ?');
const info = stmt.run(id);
return info.changes > 0;
}
/**
* Delete a session by session_id (cascades to related records)
*/
deleteBySessionId(sessionId: string): boolean {
const stmt = this.db.query('DELETE FROM sessions WHERE session_id = ?');
const info = stmt.run(sessionId);
return info.changes > 0;
}
}
@@ -1,266 +0,0 @@
import { Database } from 'bun:sqlite';
import { getDatabase } from './Database.js';
import { normalizeTimestamp } from './types.js';
/**
* Represents a streaming session row in the database
*/
export interface StreamingSessionRow {
id: number;
claude_session_id: string;
sdk_session_id?: string;
project: string;
title?: string;
subtitle?: string;
user_prompt?: string;
started_at: string;
started_at_epoch: number;
updated_at?: string;
updated_at_epoch?: number;
completed_at?: string;
completed_at_epoch?: number;
status: 'active' | 'completed' | 'failed';
}
/**
* Input type for creating a new streaming session
*/
export interface StreamingSessionInput {
claude_session_id: string;
project: string;
user_prompt?: string;
started_at?: string | Date | number;
}
/**
* Input type for updating a streaming session
*/
export interface StreamingSessionUpdate {
sdk_session_id?: string;
title?: string;
subtitle?: string;
status?: 'active' | 'completed' | 'failed';
completed_at?: string | Date | number;
}
/**
* Data Access Object for streaming session records
* Handles real-time session tracking during SDK compression
*/
export class StreamingSessionStore {
private db: Database.Database;
constructor(db?: Database.Database) {
this.db = db || getDatabase();
}
/**
* Create a new streaming session record
* This should be called immediately when the hook receives a user prompt
*/
create(input: StreamingSessionInput): StreamingSessionRow {
const { isoString, epoch } = normalizeTimestamp(input.started_at);
const stmt = this.db.query(`
INSERT INTO streaming_sessions (
claude_session_id, project, user_prompt, started_at, started_at_epoch, status
) VALUES (?, ?, ?, ?, ?, 'active')
`);
const info = stmt.run(
input.claude_session_id,
input.project,
input.user_prompt || null,
isoString,
epoch
);
return this.getById(info.lastInsertRowid as number)!;
}
/**
* Update a streaming session by internal ID
* Uses atomic transaction to prevent race conditions
*/
update(id: number, updates: StreamingSessionUpdate): StreamingSessionRow {
const { isoString: updatedAt, epoch: updatedEpoch } = normalizeTimestamp(new Date());
const existing = this.getById(id);
if (!existing) {
throw new Error(`Streaming session with id ${id} not found`);
}
const parts: string[] = [];
const values: any[] = [];
if (updates.sdk_session_id !== undefined) {
parts.push('sdk_session_id = ?');
values.push(updates.sdk_session_id);
}
if (updates.title !== undefined) {
parts.push('title = ?');
values.push(updates.title);
}
if (updates.subtitle !== undefined) {
parts.push('subtitle = ?');
values.push(updates.subtitle);
}
if (updates.status !== undefined) {
parts.push('status = ?');
values.push(updates.status);
}
if (updates.completed_at !== undefined) {
const { isoString, epoch } = normalizeTimestamp(updates.completed_at);
parts.push('completed_at = ?', 'completed_at_epoch = ?');
values.push(isoString, epoch);
}
// Always update the updated_at timestamp
parts.push('updated_at = ?', 'updated_at_epoch = ?');
values.push(updatedAt, updatedEpoch);
values.push(id);
const stmt = this.db.query(`
UPDATE streaming_sessions
SET ${parts.join(', ')}
WHERE id = ?
`);
stmt.run(...values);
return this.getById(id)!;
}
/**
* Update a streaming session by Claude session ID
* Convenience method for hooks that only have the Claude session ID
*/
updateByClaudeSessionId(claudeSessionId: string, updates: StreamingSessionUpdate): StreamingSessionRow | null {
const session = this.getByClaudeSessionId(claudeSessionId);
if (!session) {
return null;
}
return this.update(session.id, updates);
}
/**
* Get streaming session by internal ID
*/
getById(id: number): StreamingSessionRow | null {
const stmt = this.db.query('SELECT * FROM streaming_sessions WHERE id = ?');
return stmt.get(id) as StreamingSessionRow || null;
}
/**
* Get streaming session by Claude session ID
*/
getByClaudeSessionId(claudeSessionId: string): StreamingSessionRow | null {
const stmt = this.db.query('SELECT * FROM streaming_sessions WHERE claude_session_id = ?');
return stmt.get(claudeSessionId) as StreamingSessionRow || null;
}
/**
* Get streaming session by SDK session ID
*/
getBySdkSessionId(sdkSessionId: string): StreamingSessionRow | null {
const stmt = this.db.query('SELECT * FROM streaming_sessions WHERE sdk_session_id = ?');
return stmt.get(sdkSessionId) as StreamingSessionRow || null;
}
/**
* Check if a streaming session exists by Claude session ID
*/
has(claudeSessionId: string): boolean {
const stmt = this.db.query('SELECT 1 FROM streaming_sessions WHERE claude_session_id = ? LIMIT 1');
return Boolean(stmt.get(claudeSessionId));
}
/**
* Get active streaming sessions for a project
*/
getActiveForProject(project: string): StreamingSessionRow[] {
const stmt = this.db.query(`
SELECT * FROM streaming_sessions
WHERE project = ? AND status = 'active'
ORDER BY started_at_epoch DESC
`);
return stmt.all(project) as StreamingSessionRow[];
}
/**
* Get all active streaming sessions
*/
getAllActive(): StreamingSessionRow[] {
const stmt = this.db.query(`
SELECT * FROM streaming_sessions
WHERE status = 'active'
ORDER BY started_at_epoch DESC
`);
return stmt.all() as StreamingSessionRow[];
}
/**
* Get recent streaming sessions (completed or failed)
*/
getRecent(limit = 10): StreamingSessionRow[] {
const stmt = this.db.query(`
SELECT * FROM streaming_sessions
ORDER BY started_at_epoch DESC
LIMIT ?
`);
return stmt.all(limit) as StreamingSessionRow[];
}
/**
* Mark a session as completed
*/
markCompleted(id: number): StreamingSessionRow {
return this.update(id, {
status: 'completed',
completed_at: new Date()
});
}
/**
* Mark a session as failed
*/
markFailed(id: number): StreamingSessionRow {
return this.update(id, {
status: 'failed',
completed_at: new Date()
});
}
/**
* Delete a streaming session by ID
*/
deleteById(id: number): boolean {
const stmt = this.db.query('DELETE FROM streaming_sessions WHERE id = ?');
const info = stmt.run(id);
return info.changes > 0;
}
/**
* Delete a streaming session by Claude session ID
*/
deleteByClaudeSessionId(claudeSessionId: string): boolean {
const stmt = this.db.query('DELETE FROM streaming_sessions WHERE claude_session_id = ?');
const info = stmt.run(claudeSessionId);
return info.changes > 0;
}
/**
* Clean up old completed/failed sessions (older than N days)
*/
cleanupOldSessions(daysOld = 30): number {
const cutoffEpoch = Date.now() - (daysOld * 24 * 60 * 60 * 1000);
const stmt = this.db.query(`
DELETE FROM streaming_sessions
WHERE status IN ('completed', 'failed')
AND completed_at_epoch < ?
`);
const info = stmt.run(cutoffEpoch);
return info.changes;
}
}
-107
View File
@@ -1,107 +0,0 @@
import { Database } from 'bun:sqlite';
import { getDatabase } from './Database.js';
import {
TranscriptEventInput,
TranscriptEventRow,
normalizeTimestamp
} from './types.js';
/**
* Data access for transcript_events table
*/
export class TranscriptEventStore {
private db: Database.Database;
constructor(db?: Database.Database) {
this.db = db || getDatabase();
}
/**
* Insert or update a transcript event
*/
upsert(event: TranscriptEventInput): TranscriptEventRow {
const { isoString, epoch } = normalizeTimestamp(event.captured_at);
const stmt = this.db.query(`
INSERT INTO transcript_events (
session_id,
project,
event_index,
event_type,
raw_json,
captured_at,
captured_at_epoch
) VALUES (?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(session_id, event_index) DO UPDATE SET
project = excluded.project,
event_type = excluded.event_type,
raw_json = excluded.raw_json,
captured_at = excluded.captured_at,
captured_at_epoch = excluded.captured_at_epoch
`);
stmt.run(
event.session_id,
event.project || null,
event.event_index,
event.event_type || null,
event.raw_json,
isoString,
epoch
);
return this.getBySessionAndIndex(event.session_id, event.event_index)!;
}
/**
* Bulk upsert events in a single transaction
*/
upsertMany(events: TranscriptEventInput[]): TranscriptEventRow[] {
const transaction = this.db.transaction((rows: TranscriptEventInput[]) => {
const results: TranscriptEventRow[] = [];
for (const row of rows) {
results.push(this.upsert(row));
}
return results;
});
return transaction(events);
}
/**
* Get event by session and index
*/
getBySessionAndIndex(sessionId: string, eventIndex: number): TranscriptEventRow | null {
const stmt = this.db.query(`
SELECT * FROM transcript_events
WHERE session_id = ? AND event_index = ?
`);
return stmt.get(sessionId, eventIndex) as TranscriptEventRow | null;
}
/**
* Get highest event_index stored for a session
*/
getMaxEventIndex(sessionId: string): number {
const stmt = this.db.query(`
SELECT MAX(event_index) as max_event_index
FROM transcript_events
WHERE session_id = ?
`);
const row = stmt.get(sessionId) as { max_event_index: number | null } | undefined;
return row?.max_event_index ?? -1;
}
/**
* List recent events for a session
*/
listBySession(sessionId: string, limit = 200, offset = 0): TranscriptEventRow[] {
const stmt = this.db.query(`
SELECT * FROM transcript_events
WHERE session_id = ?
ORDER BY event_index ASC
LIMIT ? OFFSET ?
`);
return stmt.all(sessionId, limit, offset) as TranscriptEventRow[];
}
}
-38
View File
@@ -1,14 +1,6 @@
// Export main components
export { DatabaseManager, getDatabase, initializeDatabase } from './Database.js';
// Export store classes
export { SessionStore } from './SessionStore.js';
export { MemoryStore } from './MemoryStore.js';
export { OverviewStore } from './OverviewStore.js';
export { DiagnosticsStore } from './DiagnosticsStore.js';
export { TranscriptEventStore } from './TranscriptEventStore.js';
export { StreamingSessionStore } from './StreamingSessionStore.js';
// Export hooks database
export { HooksDatabase } from './HooksDatabase.js';
@@ -17,33 +9,3 @@ export * from './types.js';
// Export migrations
export { migrations } from './migrations.js';
// Convenience function to get all stores
export async function createStores() {
const { DatabaseManager } = await import('./Database.js');
const { migrations } = await import('./migrations.js');
// Register migrations before initialization
const manager = DatabaseManager.getInstance();
for (const migration of migrations) {
manager.registerMigration(migration);
}
const db = await manager.initialize();
const { SessionStore } = await import('./SessionStore.js');
const { MemoryStore } = await import('./MemoryStore.js');
const { OverviewStore } = await import('./OverviewStore.js');
const { DiagnosticsStore } = await import('./DiagnosticsStore.js');
const { TranscriptEventStore } = await import('./TranscriptEventStore.js');
const { StreamingSessionStore } = await import('./StreamingSessionStore.js');
return {
sessions: new SessionStore(db),
memories: new MemoryStore(db),
overviews: new OverviewStore(db),
diagnostics: new DiagnosticsStore(db),
transcriptEvents: new TranscriptEventStore(db),
streamingSessions: new StreamingSessionStore(db)
};
}