feat: Implement Worker Service v2 with improved architecture

- Complete rewrite of the Worker Service following object-oriented principles.
- Introduced a single long-lived database connection to reduce overhead.
- Implemented event-driven queues to eliminate polling.
- Added DRY utilities for pagination and settings management.
- Reduced code size significantly from 1173 lines to approximately 600-700 lines.
- Created various composed services including DatabaseManager, SessionManager, and SDKAgent.
- Enhanced SSE broadcasting capabilities for real-time client updates.
- Established a structured approach for session lifecycle management and event handling.
- Introduced type safety with shared TypeScript interfaces for better maintainability.
This commit is contained in:
Alex Newman
2025-11-06 23:56:25 -05:00
parent 9eddc51979
commit 980151a50e
12 changed files with 3721 additions and 2 deletions
+115
View File
@@ -0,0 +1,115 @@
/**
* DatabaseManager: Single long-lived database connection
*
* Responsibility:
* - Manage single database connection for worker lifetime
* - Provide centralized access to SessionStore and SessionSearch
* - High-level database operations
* - ChromaSync integration
*/
import { SessionStore } from '../sqlite/SessionStore.js';
import { SessionSearch } from '../sqlite/SessionSearch.js';
import { ChromaSync } from '../sync/ChromaSync.js';
import { logger } from '../../utils/logger.js';
import type { DBSession } from '../worker-types.js';
export class DatabaseManager {
private sessionStore: SessionStore | null = null;
private sessionSearch: SessionSearch | null = null;
private chromaSync: ChromaSync | null = null;
/**
* Initialize database connection (once, stays open)
*/
async initialize(): Promise<void> {
// Open database connection (ONCE)
this.sessionStore = new SessionStore();
this.sessionSearch = new SessionSearch();
// Initialize ChromaSync
this.chromaSync = new ChromaSync('claude-mem');
// Start background backfill (fire-and-forget)
this.chromaSync.ensureBackfilled().catch(() => {});
logger.info('DB', 'Database initialized');
}
/**
* Close database connection
*/
async close(): Promise<void> {
if (this.sessionStore) {
this.sessionStore.close();
this.sessionStore = null;
}
if (this.sessionSearch) {
this.sessionSearch.close();
this.sessionSearch = null;
}
logger.info('DB', 'Database closed');
}
/**
* Get SessionStore instance (throws if not initialized)
*/
getSessionStore(): SessionStore {
if (!this.sessionStore) {
throw new Error('Database not initialized');
}
return this.sessionStore;
}
/**
* Get SessionSearch instance (throws if not initialized)
*/
getSessionSearch(): SessionSearch {
if (!this.sessionSearch) {
throw new Error('Database not initialized');
}
return this.sessionSearch;
}
/**
* Get ChromaSync instance (throws if not initialized)
*/
getChromaSync(): ChromaSync {
if (!this.chromaSync) {
throw new Error('ChromaSync not initialized');
}
return this.chromaSync;
}
/**
* Cleanup orphaned sessions from previous runs
* @returns Number of sessions cleaned
*/
cleanupOrphanedSessions(): number {
return this.getSessionStore().cleanupOrphanedSessions();
}
/**
* Get session by ID (throws if not found)
*/
getSessionById(sessionDbId: number): {
id: number;
claude_session_id: string;
sdk_session_id: string | null;
project: string;
user_prompt: string;
} {
const session = this.getSessionStore().getSessionById(sessionDbId);
if (!session) {
throw new Error(`Session ${sessionDbId} not found`);
}
return session;
}
/**
* Mark session as completed
*/
markSessionComplete(sessionDbId: number): void {
this.getSessionStore().markSessionCompleted(sessionDbId);
}
}