Skip to main content

repolith_core/
cache.rs

1//! Cache trait — abstract storage for build events.
2//!
3//! Defines the contract that any backend (sqlite, in-memory, redis…) must
4//! satisfy. Concrete implementations live in dependent crates (e.g.
5//! `repolith-cache::SqliteCache`). The trait lives here in `repolith-core`
6//! so consumers like [`crate::plan::Plan`] can depend on the abstraction
7//! without pulling in any specific backend.
8
9use crate::types::{ActionId, BuildEvent};
10use async_trait::async_trait;
11use thiserror::Error;
12
13/// Errors that can occur during cache operations.
14#[derive(Debug, Error)]
15pub enum CacheError {
16    /// An I/O error occurred while accessing the cache.
17    #[error("io: {0}")]
18    Io(#[from] std::io::Error),
19    /// A backend-specific error occurred (driver-level, schema, lock, …).
20    #[error("backend: {0}")]
21    Backend(String),
22}
23
24/// A specialized `Result` type for cache operations.
25pub type Result<T> = std::result::Result<T, CacheError>;
26
27/// Persistent store for `BuildEvent`s, keyed by [`ActionId`].
28///
29/// The cache lets the [planner](crate::plan::Plan) compare current input
30/// hashes against the last recorded build, and lets the orchestrator persist
31/// new events as they fire.
32#[async_trait]
33pub trait Cache: Send + Sync {
34    /// Last recorded event for `id`, or `None` if the action has never run.
35    async fn last_build(&self, id: &ActionId) -> Option<BuildEvent>;
36
37    /// Persist `event`. After success, [`Self::last_build`] for the event's
38    /// id must return this event.
39    async fn record(&mut self, event: BuildEvent) -> Result<()>;
40
41    /// Persist `events` atomically — either every event lands or none.
42    ///
43    /// The orchestrator calls this at the end of each layer so a crash
44    /// mid-batch doesn't leave half the layer marked Success and the
45    /// other half un-persisted (which would re-run those actions on
46    /// the next sync, contradicting the cached Success).
47    ///
48    /// The default implementation is a non-atomic loop over [`Self::record`]
49    /// that suffices for in-memory backends; persistent backends
50    /// (`SQLite`, etc.) should override with a transaction.
51    async fn record_batch(&mut self, events: Vec<BuildEvent>) -> Result<()> {
52        for event in events {
53            self.record(event).await?;
54        }
55        Ok(())
56    }
57}