Skip to main content

Module state

Module state 

Source
Expand description

Revisioned control-plane state and owned transactions.

Open a supplied crate::StateStore from config::StateStoreConfig, import crate::StateStoreBackend for reads and transaction creation, and import crate::StateTransactionBackend to stage and finalize mutations. record contains validated identities, write inputs, observations, and pagination. Transactions remain unique, lifetime-free values and are consumed by commit or rollback.

Read conditional-write preconditions inside the committing transaction. Ordinary reads and separate listing pages do not establish a snapshot. A lost commit reply may require durable reconciliation before retrying.

§Preconditions, revisions and pagination

This runs on embedded Lance without a service. Conditional writes validate their precondition inside the same transaction. Rollback consumes the transaction and discards its staged revision. Do not automatically retry an ambiguous commit.

use storage_kit::{StateStore, StateStoreBackend, StateTransactionBackend};
use storage_kit::state::config::{LanceStateStoreConfig, StateStoreConfig};
use storage_kit::state::record::{StateKey, StateListRequest, StateRecord};
let store = StateStore::connect(StateStoreConfig::lance(LanceStateStoreConfig::memory())).await?;
let key = StateKey::new("jobs", "a")?;
let mut create = store.begin_transaction().await?;
assert!(create.load(key.clone()).await?.is_none());
create.put(StateRecord::new(key.clone(), serde_json::json!({"status":"ready"}))).await?;
create.put(StateRecord::new(StateKey::new("jobs", "b")?, serde_json::json!({"status":"ready"}))).await?;
create.commit().await?;

let mut update = store.begin_transaction().await?;
let before = update.load(key.clone()).await?.expect("created job");
assert_eq!(before.revision.get(), 1);
assert_eq!(before.record.value["status"], "ready");
update.put(StateRecord::new(key.clone(), serde_json::json!({"status":"running"}))).await?;
assert_eq!(update.load(key.clone()).await?.unwrap().revision.get(), 2);
update.rollback().await?;
assert_eq!(store.load(key.clone()).await?.unwrap().revision.get(), 1);

let first = store.list(StateListRequest::new("jobs")?.with_limit(1)?).await?;
assert_eq!(first.records.len(), 1);
let cursor = first.next_start_after.expect("one remaining job");
let second = store.list(StateListRequest::new("jobs")?.with_limit(1)?.start_after(cursor)?).await?;
assert_eq!(second.records.len(), 1);
assert!(second.next_start_after.is_none());
// Separate pages observe committed state independently, not one frozen snapshot.
let mut replace = store.begin_transaction().await?;
replace.delete(key.clone()).await?;
assert!(replace.load(key.clone()).await?.is_none());
replace.put(StateRecord::new(key.clone(), serde_json::json!({"status":"new"}))).await?;
replace.commit().await?;
assert_eq!(store.load(key).await?.unwrap().revision.get(), 3);
drop(store);

A caller detecting a stale application precondition should roll back instead of writing. Native serialization conflicts use crate::StateStoreError::TransactionConflict; a timeout or lost commit reply does not establish that the write was rolled back.

§Directory-backed reopen

Memory belongs to one connected owner and its clones. Directory connections reopen persisted records. This example owns its temporary directory, consumes all transactions and drops both store handles before removing that directory. StateStore has no public shutdown operation; final-handle cleanup belongs to the selected implementation.

use storage_kit::{StateStore, StateStoreBackend, StateTransactionBackend};
use storage_kit::state::config::{LanceStateStoreConfig, StateStoreConfig};
use storage_kit::state::record::{StateKey, StateRecord};
let directory = std::env::temp_dir().join(format!("ai-kit-state-doc-{}", uuid::Uuid::new_v4()));
std::fs::create_dir(&directory)?;
let result = tokio::runtime::Runtime::new()?.block_on(async {
    let config = StateStoreConfig::lance(LanceStateStoreConfig::directory(&directory));
    let store = StateStore::connect(config.clone()).await?;
    let key = StateKey::new("settings", "theme")?;
    let mut transaction = store.begin_transaction().await?;
    transaction.put(StateRecord::new(key.clone(), serde_json::json!("dark"))).await?;
    transaction.commit().await?;
    drop(store);
    let reopened = StateStore::connect(config).await?;
    let observed = reopened.load(key).await?.expect("durable record");
    assert_eq!(observed.record.value.as_ref(), &serde_json::json!("dark"));
    assert_eq!(observed.revision.get(), 1);
    drop(reopened);
    Ok::<(), storage_kit::StateStoreError>(())
});
let cleanup = std::fs::remove_dir_all(&directory);
result?; // Preserve the operation error if cleanup also failed.
cleanup?;

Custom stores retain their own concrete associated transaction. The complete composition example delegates native commit and rollback instead of inventing transaction guarantees.

Modules§

config
Built-in state-store construction data.
record
Revisioned records, keys, and pagination. Revisioned state records, validated keys, and pagination.
transaction
The unique owned transaction handle.