Replace rkyv/bincode caching with redb indices
Remove three-tier loading (rkyv snapshot, bincode cache, capnp replay)
in favor of direct capnp log replay + redb for indexed access.
- Remove all rkyv derives from types (Node, Relation, enums, etc.)
- Remove Snapshot struct, RKYV_MAGIC, CACHE_MAGIC constants
- Remove load_snapshot_mmap(), save(), save_snapshot()
- Remove MmapView, AnyView from view.rs (keep StoreView trait)
- Simplify Store::load() to just replay capnp logs
- Add db.rs with redb schema: nodes, uuid_to_key, visits, transcript_progress
- Simplify cmd_fsck to just check capnp integrity + graph health
capnp logs remain source of truth; redb indices will be rebuilt on demand.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-04-13 18:30:58 -04:00
|
|
|
// Read-only access abstraction for the memory store
|
2026-03-03 12:56:15 -05:00
|
|
|
|
2026-04-13 19:31:28 -04:00
|
|
|
use super::{capnp, index, types::*};
|
2026-04-13 19:17:31 -04:00
|
|
|
use super::Store;
|
2026-03-03 12:56:15 -05:00
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// StoreView: read-only access trait for search and graph code.
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
pub trait StoreView {
|
|
|
|
|
/// Iterate all nodes. Callback receives (key, content, weight).
|
|
|
|
|
fn for_each_node<F: FnMut(&str, &str, f32)>(&self, f: F);
|
|
|
|
|
|
2026-03-14 02:40:00 -04:00
|
|
|
/// Iterate all nodes with metadata. Callback receives (key, node_type, timestamp).
|
|
|
|
|
fn for_each_node_meta<F: FnMut(&str, NodeType, i64)>(&self, f: F);
|
|
|
|
|
|
2026-03-03 12:56:15 -05:00
|
|
|
/// Iterate all relations. Callback receives (source_key, target_key, strength, rel_type).
|
|
|
|
|
fn for_each_relation<F: FnMut(&str, &str, f32, RelationType)>(&self, f: F);
|
|
|
|
|
|
|
|
|
|
/// Node weight by key, or the default weight if missing.
|
|
|
|
|
fn node_weight(&self, key: &str) -> f64;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl StoreView for Store {
|
|
|
|
|
fn for_each_node<F: FnMut(&str, &str, f32)>(&self, mut f: F) {
|
2026-04-13 19:31:28 -04:00
|
|
|
let db = match self.db.as_ref() {
|
|
|
|
|
Some(db) => db,
|
|
|
|
|
None => return,
|
|
|
|
|
};
|
|
|
|
|
let keys = match index::all_keys(db) {
|
|
|
|
|
Ok(keys) => keys,
|
|
|
|
|
Err(_) => return,
|
|
|
|
|
};
|
|
|
|
|
for key in keys {
|
|
|
|
|
if let Ok(Some(offset)) = index::get_offset(db, &key) {
|
|
|
|
|
if let Ok(node) = capnp::read_node_at_offset(offset) {
|
|
|
|
|
f(&key, &node.content, node.weight);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-03-03 12:56:15 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-14 02:40:00 -04:00
|
|
|
fn for_each_node_meta<F: FnMut(&str, NodeType, i64)>(&self, mut f: F) {
|
2026-04-13 19:31:28 -04:00
|
|
|
let db = match self.db.as_ref() {
|
|
|
|
|
Some(db) => db,
|
|
|
|
|
None => return,
|
|
|
|
|
};
|
|
|
|
|
let keys = match index::all_keys(db) {
|
|
|
|
|
Ok(keys) => keys,
|
|
|
|
|
Err(_) => return,
|
|
|
|
|
};
|
|
|
|
|
for key in keys {
|
|
|
|
|
if let Ok(Some(offset)) = index::get_offset(db, &key) {
|
|
|
|
|
if let Ok(node) = capnp::read_node_at_offset(offset) {
|
|
|
|
|
f(&key, node.node_type, node.timestamp);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-03-14 02:40:00 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-03 12:56:15 -05:00
|
|
|
fn for_each_relation<F: FnMut(&str, &str, f32, RelationType)>(&self, mut f: F) {
|
2026-04-13 21:12:47 -04:00
|
|
|
let db = match self.db.as_ref() {
|
|
|
|
|
Some(db) => db,
|
|
|
|
|
None => return,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Build uuid → key map by iterating all nodes once
|
|
|
|
|
let mut uuid_to_key: std::collections::HashMap<[u8; 16], String> = std::collections::HashMap::new();
|
|
|
|
|
let keys = match index::all_keys(db) {
|
|
|
|
|
Ok(keys) => keys,
|
|
|
|
|
Err(_) => return,
|
|
|
|
|
};
|
|
|
|
|
for key in &keys {
|
|
|
|
|
if let Ok(Some(uuid)) = index::get_uuid_for_key(db, key) {
|
|
|
|
|
uuid_to_key.insert(uuid, key.clone());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Iterate edges: only process outgoing to avoid duplicates
|
|
|
|
|
for key in &keys {
|
|
|
|
|
let uuid = match index::get_uuid_for_key(db, key) {
|
|
|
|
|
Ok(Some(u)) => u,
|
|
|
|
|
_ => continue,
|
|
|
|
|
};
|
|
|
|
|
let edges = match index::edges_for_node(db, &uuid) {
|
|
|
|
|
Ok(e) => e,
|
|
|
|
|
Err(_) => continue,
|
|
|
|
|
};
|
|
|
|
|
for (other_uuid, strength, rel_type_byte, is_outgoing) in edges {
|
|
|
|
|
if !is_outgoing { continue; } // only process outgoing
|
|
|
|
|
let target_key = match uuid_to_key.get(&other_uuid) {
|
|
|
|
|
Some(k) => k,
|
|
|
|
|
None => continue, // orphan edge
|
|
|
|
|
};
|
|
|
|
|
let rel_type = RelationType::from_u8(rel_type_byte);
|
|
|
|
|
f(key, target_key, strength, rel_type);
|
|
|
|
|
}
|
2026-03-03 12:56:15 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn node_weight(&self, key: &str) -> f64 {
|
2026-04-13 18:50:21 -04:00
|
|
|
let cfg = crate::config::get();
|
2026-04-13 19:31:28 -04:00
|
|
|
self.get_node(key)
|
|
|
|
|
.ok()
|
|
|
|
|
.flatten()
|
|
|
|
|
.map(|n| n.weight as f64)
|
|
|
|
|
.unwrap_or(cfg.default_node_weight)
|
2026-03-03 12:56:15 -05:00
|
|
|
}
|
|
|
|
|
}
|