Skip to main content

luft_storage/
lib.rs

1//! # luft-storage
2//!
3//! **SQLite-backed structured persistence for Luft.**
4//!
5//! Replaces the JSONL `events.jsonl` + `checkpoint.json` pair with a queryable,
6//! relational store. Provides a UI-ready query API for listing runs, inspecting
7//! agent turns, and searching event spans.
8//!
9//! ## Module Layout
10//!
11//! | Module | Responsibility |
12//! |--------|---------------|
13//! | [`db`] | Connection pool (`DbPool`) + schema migration |
14//! | [`writer`] | `AgentEvent` → SQL write path ([`EventWriter`]) |
15//! | [`reader`] | UI-ready query API: [`get_run_overview`], [`get_agent_turns`], etc. |
16//! | [`error`] | Unified [`StorageError`] type |
17//!
18//! ## Usage
19//!
20//! ```no_run
21//! use luft_storage::{open_db, EventWriter};
22//! use std::path::Path;
23//!
24//! # tokio::runtime::Runtime::new().unwrap().block_on(async {
25//! let pool = open_db(Path::new("./.luft/runs/latest/luft.db")).await?;
26//! let writer = EventWriter::new(pool.clone());
27//! // writer.write_event(&event).await?;
28//! # Ok::<(), Box<dyn std::error::Error>>(())
29//! # });
30//! ```
31
32pub mod checkpoint;
33pub mod db;
34pub mod error;
35pub mod reader;
36pub mod writer;
37
38pub use checkpoint::SqliteCheckpointBackend;
39pub use db::{open_db, DbPool, DEFAULT_DB_PATH};
40pub use error::StorageError;
41pub use reader::{
42    get_agent_overview, get_agent_turns, get_run_agents, get_run_overview, get_run_spans,
43    list_runs, search_turns, AgentOverview, RunOverview, RunSummary, SpanRow,
44    TurnKindCount, TurnRow,
45};
46pub use writer::EventWriter;
47
48#[cfg(test)]
49mod tests {
50    use super::*;
51
52    #[test]
53    fn default_db_path_is_luft_db_filename() {
54        let p = DEFAULT_DB_PATH;
55        assert_eq!(p, "luft.db");
56        assert!(p.ends_with(".db"));
57    }
58
59    #[test]
60    fn storage_error_re_export_matches_crate_path() {
61        let _e: StorageError = StorageError::Invalid("ping".into());
62        let msg = _e.to_string();
63        assert!(msg.contains("invalid input"));
64    }
65
66    #[test]
67    fn submodules_are_publicly_accessible() {
68        // Compile-time check: each module path resolves through the crate root.
69        let _: db::__DbProbe = ();
70        let _: error::__ErrorProbe = ();
71        let _: reader::__ReaderProbe = ();
72        let _: writer::__WriterProbe = ();
73        // The above consts are injected by the macros below for compile checks.
74        // If this test compiles, the module surface is exposed.
75    }
76
77    // Each submodule declares a `pub const __Probe: () = ();` only under cfg(test)
78    // so we can reference them generically above. Implemented inline below.
79    mod db {
80        #[cfg(test)]
81        pub type __DbProbe = ();
82    }
83    mod error {
84        #[cfg(test)]
85        pub type __ErrorProbe = ();
86    }
87    mod reader {
88        #[cfg(test)]
89        pub type __ReaderProbe = ();
90    }
91    mod writer {
92        #[cfg(test)]
93        pub type __WriterProbe = ();
94    }
95}