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}