rs-histver 0.2.2

A CLI tool and library for querying Rust historical release versions with local redb cache
Documentation
//! # rs-histver
//!
//! A library for querying Rust historical release versions with local redb cache.
//!
//! ## As a CLI tool
//!
//! ```sh
//! rs-histver sync --channel stable
//! rs-histver sync --data-dir /app/data --db-file myapp.redb --table-prefix myapp_histver
//! rs-histver list --limit 20
//! rs-histver search 1.75
//! ```
//!
//! ## As a library (standalone)
//!
//! ```ignore
//! use rs_histver::{HistVer, Config};
//!
//! // Default: database at <exe_dir>/data/rs-histver.redb
//! let hv = HistVer::new(Config::new())?;
//!
//! // Or: custom database path
//! let hv = HistVer::new(Config::with_db_path("/path/to/custom.redb"))?;
//! ```
//!
//! ## As a library (embedded in host project)
//!
//! When embedded in a host project (e.g. Tauri app), the database path
//! should be determined by the host. Use `ConfigBuilder` to pass
//! `data_dir`, `db_file`, and `table_prefix` programmatically.
//!
//! ### Standalone mode (default)
//!
//! Creates a dedicated `rs-histver.redb` file in `data_dir`.
//!
//! ```ignore
//! use rs_histver::{HistVer, ConfigBuilder};
//!
//! let hv = HistVer::new(
//!     ConfigBuilder::new()
//!         .data_dir(app_data_dir)          // creates <data_dir>/rs-histver.redb
//!         .build()?
//! )?;
//! ```
//!
//! ### Shared mode (host uses redb)
//!
//! When the host project also uses redb, you can share the same database file.
//! Set `db_file` to point to the host's redb file, and `table_prefix` to avoid
//! table name collisions.
//!
//! ```ignore
//! use rs_histver::{HistVer, ConfigBuilder};
//!
//! // Shared mode
//! let hv = HistVer::new(
//!     ConfigBuilder::new()
//!         .data_dir(app_data_dir)
//!         .db_file("myapp.redb")          // shared mode
//!         .table_prefix("myapp_histver")
//!         .build()?
//! )?;
//! ```
//!
//! If `db_file` points to a non-redb file, it automatically falls back to
//! standalone mode (creates `rs-histver.redb` in the same directory).
//!
//! ### Priority order
//!
//! ```text
//! Programmatic override (highest) → hardcoded defaults (lowest)
//! ```
//!
//! - `db_path()` overrides everything (complete file path, always standalone mode)
//! - `db_file()` enables shared mode, combined with `data_dir()`
//! - `data_dir()` alone → standalone mode: `<data_dir>/rs-histver.redb`
//! - `table_prefix()` / `timeout()` / `max_concurrency()` override defaults
//!
//! ## Core API
//!
//! ```ignore
//! use rs_histver::HistVer;
//!
//! // Fetch and cache releases (async)
//! let releases = hv.fetch_releases("stable", false, 30).await?;
//! hv.store_releases(&releases)?;
//!
//! // Query local cache (sync)
//! let all = hv.list_releases(None)?;
//! let results = hv.search_releases("1.75", None)?;
//! let count = hv.count_releases(None)?;
//! ```

#[doc(hidden)]
pub mod app;
#[doc(hidden)]
pub mod cli;
mod domain;
pub(crate) mod infra;

// ---- Public API re-exports ----

pub use domain::RustRelease;
pub use infra::fetcher::{create_fetcher, ReleaseFetcher};
pub use infra::Config;
pub use infra::ConfigBuilder;
pub use infra::Db;

use anyhow::Result;

/// Main entry point for library usage.
///
/// Wraps `Config` and `Db`, providing a high-level API for fetching,
/// storing, and querying Rust release data.
pub struct HistVer {
    config: Config,
    db: Db,
}

impl HistVer {
    /// Create a new instance with the given config.
    ///
    /// Opens or creates the database at the path specified in config.
    /// Default database path: `<exe_dir>/data/rs-histver.redb`
    ///
    /// # Errors
    ///
    /// Returns an error if the database cannot be opened or created.
    pub fn new(config: Config) -> Result<Self> {
        let db = Db::open(&config)?;
        Ok(Self { config, db })
    }

    /// Fetch release data from remote source.
    ///
    /// - `channel`: "stable", "beta", or "nightly"
    /// - `full`: use RELEASES.md as data source (stable only)
    /// - `days`: how many days of history to probe (beta/nightly only)
    ///
    /// # Errors
    ///
    /// Returns an error if the channel is unknown or the remote request fails.
    pub async fn fetch_releases(
        &self,
        channel: &str,
        full: bool,
        days: u32,
    ) -> Result<Vec<RustRelease>> {
        let fetcher = infra::fetcher::create_fetcher(channel, full, days)?;
        fetcher.fetch(&self.config).await
    }

    /// Store release records into the local database (upsert).
    ///
    /// Returns the number of records written.
    ///
    /// # Errors
    ///
    /// Returns an error if the database write transaction fails.
    pub fn store_releases(&self, releases: &[RustRelease]) -> Result<u64> {
        self.db.upsert_all(releases)
    }

    /// List all cached releases, optionally filtered by channel.
    ///
    /// # Errors
    ///
    /// Returns an error if the database read fails.
    pub fn list_releases(&self, channel: Option<&str>) -> Result<Vec<RustRelease>> {
        self.db.list_all(channel)
    }

    /// Search cached releases by keyword (matches version or date).
    ///
    /// # Errors
    ///
    /// Returns an error if the database read fails.
    pub fn search_releases(
        &self,
        keyword: &str,
        channel: Option<&str>,
    ) -> Result<Vec<RustRelease>> {
        self.db.search(keyword, channel)
    }

    /// Count cached releases, optionally filtered by channel.
    ///
    /// # Errors
    ///
    /// Returns an error if the database read fails.
    pub fn count_releases(&self, channel: Option<&str>) -> Result<u64> {
        self.db.count(channel)
    }
}