Add local CAS file roots

This commit is contained in:
Eric Wendland 2026-05-18 03:50:09 +02:00
commit e1f36ffafb
12 changed files with 581 additions and 13 deletions

View file

@ -124,6 +124,15 @@ impl Store {
state_json TEXT NOT NULL DEFAULT '{}',
updated_at_ms INTEGER NOT NULL DEFAULT 0
);
CREATE TABLE IF NOT EXISTS file_roots (
root_id TEXT PRIMARY KEY,
resource_id TEXT NOT NULL,
name TEXT NOT NULL UNIQUE,
path TEXT NOT NULL,
latest_tree_hash TEXT,
latest_tree_json TEXT,
updated_at_ms INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS peer_cards (
peer_id TEXT PRIMARY KEY,
card_json TEXT NOT NULL,
@ -382,6 +391,65 @@ impl Store {
}
}
pub fn upsert_file_root(&self, root: &StoredFileRoot) -> Result<(), StoreError> {
self.conn.execute(
r#"INSERT OR REPLACE INTO file_roots(
root_id, resource_id, name, path, latest_tree_hash, latest_tree_json, updated_at_ms
) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)"#,
params![
root.root_id,
root.resource_id,
root.name,
root.path,
root.latest_tree_hash,
root.latest_tree_json,
root.updated_at_ms
],
)?;
Ok(())
}
pub fn get_file_root_by_name(&self, name: &str) -> Result<Option<StoredFileRoot>, StoreError> {
let mut stmt = self.conn.prepare(
r#"SELECT root_id, resource_id, name, path, latest_tree_hash, latest_tree_json, updated_at_ms
FROM file_roots WHERE name = ?1"#,
)?;
let mut rows = stmt.query(params![name])?;
if let Some(row) = rows.next()? {
Ok(Some(StoredFileRoot {
root_id: row.get(0)?,
resource_id: row.get(1)?,
name: row.get(2)?,
path: row.get(3)?,
latest_tree_hash: row.get(4)?,
latest_tree_json: row.get(5)?,
updated_at_ms: row.get(6)?,
}))
} else {
Ok(None)
}
}
pub fn list_file_roots(&self) -> Result<Vec<StoredFileRoot>, StoreError> {
let mut stmt = self.conn.prepare(
r#"SELECT root_id, resource_id, name, path, latest_tree_hash, latest_tree_json, updated_at_ms
FROM file_roots ORDER BY name"#,
)?;
let rows = stmt.query_map([], |row| {
Ok(StoredFileRoot {
root_id: row.get(0)?,
resource_id: row.get(1)?,
name: row.get(2)?,
path: row.get(3)?,
latest_tree_hash: row.get(4)?,
latest_tree_json: row.get(5)?,
updated_at_ms: row.get(6)?,
})
})?;
rows.collect::<Result<Vec<_>, _>>()
.map_err(StoreError::from)
}
pub fn insert_resource_secret(&self, secret: &StoredResourceSecret) -> Result<(), StoreError> {
self.conn.execute(
r#"INSERT OR REPLACE INTO resource_secrets(
@ -829,6 +897,17 @@ pub struct StoredDocumentResource {
pub updated_at_ms: i64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct StoredFileRoot {
pub root_id: String,
pub resource_id: String,
pub name: String,
pub path: String,
pub latest_tree_hash: Option<String>,
pub latest_tree_json: Option<String>,
pub updated_at_ms: i64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct StoredResourceSecret {
pub secret_id: String,
@ -1101,6 +1180,30 @@ mod tests {
);
}
#[test]
fn file_roots_roundtrip_by_name() {
let store = Store::open_memory().expect("open");
let root = StoredFileRoot {
root_id: "file-root:notes".to_owned(),
resource_id: "resource:cas-tree:notes".to_owned(),
name: "notes".to_owned(),
path: "/tmp/notes".to_owned(),
latest_tree_hash: Some(
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef".to_owned(),
),
latest_tree_json: Some("{}".to_owned()),
updated_at_ms: 10,
};
store.upsert_file_root(&root).expect("insert root");
assert_eq!(
store.get_file_root_by_name("notes").expect("get file root"),
Some(root.clone())
);
assert_eq!(store.list_file_roots().expect("list roots"), vec![root]);
}
#[test]
fn kv_store_and_entries_roundtrip() {
let store = Store::open_memory().expect("open");