feat(domain): package read queries with SQL-aggregated stats
Summary
Implements CQRS read-side queries for Package aggregates with SQL-level aggregation (COUNT, SUM, AVG) of member downloads. Unblocks the Package UI (task 29). Fully tested: 16 new integration tests, all acceptance criteria met.
Why
Packages need pre-computed statistics (download count, total size, progress %) without N+1 query overhead. CQRS separation: PackageReadRepository returns flattened DTOs from a single GROUP BY query, never exposes mutation. Matches established pattern from Accounts queries.
Changes
- Add
PackageViewandPackageFilterdomain models (domain/model/views.rs) - Add
PackageReadRepositoryport trait (domain/ports/driven/package_read_repository.rs) with three methods:find_packages(filter)— lists with optional source_type and name_q (fuzzy) filtersfind_package_by_id(id)— returns single package or Nonefind_package_downloads(id)— returns member downloads ordered by queue_position
- Implement
SqlitePackageReadRepoadapter (adapters/driven/sqlite/package_read_repo.rs):PACKAGE_AGG_SELECTconstant: singleLEFT JOIN ... GROUP BYquery with stats aggregationrow_to_view()for SQL→domain mapping (16 integration tests verify correctness)- Handles NULL aggregates correctly (COALESCE for SUM/COUNT edge cases)
- Add three Query handlers:
ListPackagesQuery,GetPackageQuery,ListPackageDownloadsQuery - Add
PackageViewDtoread model with#[serde(rename_all = "camelCase")]for frontend - Wire in QueryBus and expose three Tauri IPC commands:
package_list,package_get,package_list_downloads - Update CHANGELOG.md
Testing
All acceptance criteria verified:
- N+1 prevention: aggregation at SQL level via
LEFT JOIN ... GROUP BY p.id, not N+1 application queries. Tested bytest_find_packages_aggregates_member_downloads. - Fuzzy name filter:
LOWER(p.name) LIKE ?is case-insensitive substring match. Verified bytest_find_packages_filter_by_name_q_is_case_insensitive_substring. - SQLite integration: 16 tests via
setup_test_db()+ fixtures, no mocks. All pass locally and in CI.
cargo test package_read_repo -- --nocapture
cargo test --workspace
Full test output: 1299 cargo tests pass, 605 vitest pass, clippy clean, cargo fmt clean, oxlint clean.
Related Issues
- Closes task 28 (Vortex sprint roadmap)
- Unblocks task 29 (Vue Packages React, all dependencies now satisfied)
Checklist
- Tests added/updated and passing locally
- Docs updated (CHANGELOG.md)
- No secrets, debug prints, or commented-out code
- Self-reviewed the diff
- CI green
Summary by cubic
Add read-side package queries with SQL-aggregated stats to avoid N+1 and unblock the Packages UI (task 28). Also clamps progress, scales large member lookups, suppresses ETAs on Completed, and fixes legacy created_at fallback in package member views.
-
New Features
- New
PackageReadRepositoryandSqlitePackageReadRepocomputedownloads_count,total_bytes,downloaded_bytes,progress_percent, andall_completedvia oneLEFT JOIN ... GROUP BY. - Query handlers
ListPackagesQuery,GetPackageQuery,ListPackageDownloadsQuerywired intoQueryBus; DTOPackageViewDto(camelCase, no password). IPC:package_list,package_get,package_list_downloads; list results ordered by(created_at, id)and member downloads byqueue_position. - Filtering:
source_typeexact match;name_qis a Unicode-aware, case-insensitive substring in Rust; both AND together; blankname_qignored.
- New
-
Bug Fixes
- Aggregate credits
Completedmembers at fulltotal_bytes(fallback todownloaded_byteswhen total is NULL) symmetrically in numerator and denominator. - Clamp
progress_percentto 100% whendownloaded_bytes > total_bytesfor non-completed rows (aggregate and per-download views). - Chunk member ID and segment lookups in
find_package_downloadsto stay under SQLite’s variable limit for large packages. - Suppress
eta_secondsforCompleteddownloads. - Resolve legacy member
created_atwith a fallback chain (id-inferred →updated_at→ sentinel) viaresolve_download_created_at, used in bothpackage_read_repoanddownload_read_repoto avoid “1970” dates and keep ordering consistent.
- Aggregate credits
Summary by CodeRabbit
- New Features
- Package browsing: list packages (filter by source type and name substring), deterministic ordering, package details with aggregated download stats/progress, and list package downloads via new IPC endpoints.
- Accounts: quota-driven account rotation with selector exclusions and related domain/events; account UI and persistence added.
- Tests
- Extensive tests for package aggregation, progress clamping, filtering, ordering, DTO mapping, and query handlers.
- Documentation
- CHANGELOG updated to document these additions.