storage_kit/lib.rs
1//! Portable database and transactional state-store abstractions.
2//!
3//! Built-in databases are opened with [`Database::connect`], while built-in
4//! state stores are opened with [`StateStore::connect`]. Caller-defined roots
5//! implement [`DatabaseBackend`] or [`StateStoreBackend`] directly. Associated
6//! state transactions remain owned by their parent backend.
7//!
8//! Supporting request, response, and configuration types live in the
9//! functional [`database`] and [`state`] modules.
10//!
11//! [`Database`] owns application data and retrieval. [`StateStore`] owns
12//! revisioned control-plane records, with a unique transaction for atomic writes.
13//! A StateStore transaction cannot include Database effects. Bring the relevant
14//! backend traits into scope to invoke behavior on the supplied handles.
15//!
16//! # Choose a workflow
17//!
18//! - Application builders: [collection writes, filtered/vector retrieval and
19//! index cleanup](database), then [transactions and durable reopen](state).
20//! - Custom implementers: [concrete Database composition](DatabaseBackend) and
21//! [associated StateStore/Transaction composition](StateStoreBackend).
22//! These examples delegate real storage semantics without an erased ingress.
23//!
24//! # Transactional state
25//!
26//! This example runs against process-local Lance and requires no service:
27//!
28//! ```
29//! use storage_kit::{StateStore, StateStoreBackend, StateTransactionBackend};
30//! use storage_kit::state::{
31//! config::{LanceStateStoreConfig, StateStoreConfig},
32//! record::{StateKey, StateRecord},
33//! };
34//! # tokio::runtime::Runtime::new()?.block_on(async {
35//! let store = StateStore::connect(StateStoreConfig::lance(LanceStateStoreConfig::memory())).await?;
36//! let key = StateKey::new("workflow", "run/1")?;
37//! let mut transaction = store.begin_transaction().await?;
38//! transaction.put(StateRecord::new(key.clone(), serde_json::json!({"status": "ready"}))).await?;
39//! transaction.commit().await?;
40//! let observed = store.load(key).await?.expect("committed record");
41//! assert_eq!(observed.revision.get(), 1);
42//! # Ok::<(), storage_kit::StateStoreError>(())
43//! # })?;
44//! # Ok::<(), Box<dyn std::error::Error>>(())
45//! ```
46//!
47//! Conditional writes must read their precondition through the same transaction
48//! that commits them. Separate reads or listing pages do not establish a fence
49//! or historical snapshot. After an uncertain commit result, reconcile durable
50//! application state before retrying; losing a reply does not establish rollback.
51//!
52//! # Application data
53//!
54//! This example opens an in-memory LanceDB instance and performs a catalog read:
55//!
56//! ```
57//! use storage_kit::{Database, DatabaseBackend};
58//! use storage_kit::database::{
59//! config::{DatabaseConfig, LanceConfig},
60//! catalog::{CatalogRequest, CatalogResponse, ListCollectionsRequest},
61//! };
62//! # tokio::runtime::Runtime::new()?.block_on(async {
63//! let database = Database::connect(DatabaseConfig::lance(LanceConfig::embedded_path("memory://"))).await?;
64//! let response = database.catalog(CatalogRequest::ListCollections(ListCollectionsRequest {
65//! namespace: Vec::new(), limit: Some(10), start_after: None,
66//! })).await?;
67//! assert!(matches!(response, CatalogResponse::Collections(_)));
68//! # Ok::<(), storage_kit::StorageError>(())
69//! # })?;
70//! # Ok::<(), Box<dyn std::error::Error>>(())
71//! ```
72//!
73//! Use [`database::catalog`] for collections and schemas, [`database::data`] for
74//! records and writes, [`database::query`] for retrieval, and [`database::index`]
75//! for index lifecycle. Optional capabilities describe supported families;
76//! operation results remain authoritative for each request.
77//!
78//! # Features
79//!
80//! LanceDB Database and the embedded Lance StateStore are always compiled as
81//! distinct products. Cargo defaults are empty (`default = []`). Alternative backends
82//! require explicit `redb`, `postgres`, `sqlite`, or `surrealdb`
83//! features. Default and no-default builds both provide Lance alone.
84//!
85//! # Static composition
86//!
87//! Advanced DTOs are intentionally absent from the crate root:
88//!
89//! ```compile_fail
90//! use storage_kit::DataRecord;
91//! ```
92//!
93//! Each [`StateStoreBackend`] returns its own associated transaction type.
94//! The built-in [`StateTransaction`] remains a unique owner:
95//!
96//! ```compile_fail
97//! let _ = storage_kit::StateTransaction::from_backend;
98//! ```
99//!
100//! ```compile_fail
101//! fn require_clone<T: Clone>() {}
102//! require_clone::<storage_kit::StateTransaction>();
103//! ```
104//!
105//! RPITIT backend traits are implemented directly rather than exposed as
106//! public trait objects:
107//!
108//! ```compile_fail
109//! fn accept_erased(_: &dyn storage_kit::DatabaseBackend) {}
110//! ```
111#![doc(html_logo_url = "../assets/ai-kit-logo.png")]
112#![forbid(unsafe_code)]
113
114mod core;
115mod credentials;
116pub mod database;
117mod database_handle;
118mod databases;
119pub mod state;
120mod state_handle;
121mod state_stores;
122
123pub use crate::core::StorageError;
124pub use crate::database_handle::{Database, DatabaseBackend};
125pub use crate::state_handle::{
126 StateStore, StateStoreBackend, StateStoreError, StateTransaction, StateTransactionBackend,
127};
128
129pub(crate) use crate::core::*;
130pub(crate) use crate::credentials::Password;
131pub(crate) use crate::databases::config::*;
132#[cfg(feature = "redb")]
133pub(crate) use crate::state::record::StateNamespace;
134pub(crate) use crate::state::record::{
135 StateKey, StateListRequest, StateListResponse, StateRecord, StateRecordSnapshot, StateRevision,
136};