refactor: decompose stats-db into focused modules#252
Conversation
Break the 1,870-line monolithic stats-db.ts into 13 focused modules under src/main/stats/, following the same pattern used in the agents module refactor. Key improvements: - StatementCache for prepared statement reuse across CRUD operations - DB guard accessor (get database()) replaces 18 repeated null checks - Transaction-wrapped clearOldData for atomic multi-table cleanup - _meta table replaces external file for vacuum timestamp storage - Row mapper functions centralize snake_case-to-camelCase conversion - runStatements() helper eliminates repeated split-by-semicolon patterns - Migration functions accept db param instead of relying on class context - CSV export adds isRemote column and RFC 4180 compliant escaping - Statement caches properly cleared on database close Split the 6,400-line monolithic test file into 8 focused test files under src/__tests__/main/stats/ matching the source module structure.
PR Review: Refactor stats-db into focused modulesThis is an excellent refactoring effort that significantly improves the codebase maintainability and follows established patterns. The decomposition is well-executed with comprehensive test coverage. ✅ Strengths
🔍 Areas for Consideration1. Statement Cache Lifecycle (Minor)In const stmtCache = new StatementCache();While Suggestion: Add a JSDoc comment to 2. Migration Function Signature (Minor)The migration functions in export function migrateV1(db: Database.Database): void {
// ...
}This would allow testing individual migrations without running the full migration system. 3. Error Handling in Row Mappers (Low Priority)The row mappers in export function mapQueryEventRow(row: QueryEventRow): QueryEvent {
if (!row.id || !row.session_id || !row.agent_type) {
throw new Error('Invalid query event row: missing required fields');
}
// ...
}4. SQL Injection Protection (Security - Low Risk)In // Future-proofing example (not needed now)
const allowedSortColumns = ['start_time', 'duration', 'agent_type'];
if (sortColumn && !allowedSortColumns.includes(sortColumn)) {
throw new Error('Invalid sort column');
}Current code is safe - this is just a preventive note for future enhancements. 5. Performance Metrics Threshold (Minor)In 6. CSV Export Memory Usage (Performance)
const events = getQueryEvents(db, range);For very large datasets (years of data), this could cause memory issues. Consider implementing streaming CSV export if this becomes a concern: // Future enhancement
export function* exportToCsvStream(db: Database.Database, range: StatsTimeRange) {
yield headers.join(',') + '\n';
// Stream rows in batches
}Not critical for current usage, but worth noting for future scalability. 🧪 Testing
Recommendation: Consider adding a smoke test that verifies the old monolithic API surface is fully preserved by the new modular structure (e.g., importing both and comparing exported symbols). 📝 Documentation & Conventions
🎯 Overall AssessmentThis is a high-quality refactoring that:
The concerns raised are minor and mostly preventive. The PR is ready to merge as-is, with the suggestions being optional enhancements for future consideration. Verdict: ✅ Approved - Excellent work! 📊 Metrics
Great job maintaining comprehensive test coverage while improving code organization! 🎉 |
Decompose
stats-dbinto focused modulesSummary
src/main/stats-db.tsinto 13 focused modules undersrc/main/stats/, following the same barrel-export pattern established in the agents module refactorsrc/__tests__/main/stats/matching the source module structureModule Breakdown
types.tsIntegrityCheckResult,BackupResult,Migration, etc.)utils.tsLOG_CONTEXT,generateId(),getTimeRangeStart(),normalizePath(),StatementCacheschema.tsCREATE_*_SQLconstants,runStatements()helperrow-mappers.tsmigrations.tsmigrateV1–migrateV3query-events.tsinsertQueryEvent(),getQueryEvents()auto-run.tssession-lifecycle.tsrecordSessionCreated(),recordSessionClosed(),getSessionLifecycleEvents()aggregations.tsgetAggregatedStats()decomposed into 10 sub-query functionsdata-management.tsclearOldData(),exportToCsv(),csvEscape()stats-db.tsStatsDBclass — lifecycle, vacuum, integrity, thin CRUD wrapperssingleton.tsgetStatsDB(),initializeStatsDB(),closeStatsDB(), performance metrics APIindex.tsImprovements Applied
StatementCache— Reuses prepared statements across repeated CRUD calls instead of re-preparing each timeget database()accessor — Single guard-and-throw replaces 18 repeatedif (!this.db) throwchecksMigration.up(db)signature — Migration functions receivedbas a parameter, enabling standalone testing without class contextrunStatements()helper — Eliminates repeatedsplit(';').filter(s => s.trim())loops for multi-statement SQLisRemotecolumn and RFC 4180 compliant escaping viacsvEscape()clearOldData()— All four DELETE operations wrapped indb.transaction()for atomic cleanupclose()now callsclear*Cache()for each CRUD module to prevent stale references_metatable for vacuum timestamp — Replaces externalstats-vacuum-timestampfile with self-contained DB storageCREATE_META_TABLE_SQL— Schema constant for the_metakey-value table used by vacuum schedulingTest File Split
types.test.tsstats-db.test.tsquery-events.test.tsauto-run.test.tsaggregations.test.tspaths.test.tsintegration.test.tsdata-management.test.tsVerification
Test plan
npm run lint— TypeScript type checking passesnpm run lint:eslint— No new ESLint errorsnpm run test— All 16,303 tests passnpm run build— Production build succeeds