pub struct Grit { /* private fields */ }Expand description
Handle to one grit database: a writer actor plus a read connection.
All methods take &self; Grit is Send + Sync. Writes are serialized
through the single writer thread (SQLite has one writer — embrace it);
reads run on a separate WAL connection and never block behind writes.
Implementations§
Source§impl Grit
impl Grit
Sourcepub fn export_jsonl<W: Write>(&self, out: W) -> Result<()>
pub fn export_jsonl<W: Write>(&self, out: W) -> Result<()>
Stream the entire database (graph tables + oplog + embedding metadata,
not the recomputable vectors) as JSONL. The inverse of
import_jsonl.
§Example
let mut out = Vec::new();
g.export_jsonl(&mut out)?;Source§impl Grit
impl Grit
Sourcepub fn stats(&self) -> Result<Stats>
pub fn stats(&self) -> Result<Stats>
Row counts across the whole file, from one consistent snapshot.
Sourcepub fn node(&self, id: Uuid) -> Result<Option<Node>>
pub fn node(&self, id: Uuid) -> Result<Option<Node>>
Fetch one node by id (regardless of validity — point lookups see everything; filtering is for traversal/search).
Sourcepub fn nodes_in_group(&self, group_id: &str) -> Result<Vec<Node>>
pub fn nodes_in_group(&self, group_id: &str) -> Result<Vec<Node>>
Every node in a group, id-ordered (deterministic). Includes expired
and merged-away rows — callers filter on Node::expired_at /
Node::merged_into when they want only live entities.
§Example
let live: Vec<_> = g
.nodes_in_group("algebra")?
.into_iter()
.filter(|n| n.expired_at.is_none())
.collect();Sourcepub fn edges_in_group(&self, group_id: &str) -> Result<Vec<Edge>>
pub fn edges_in_group(&self, group_id: &str) -> Result<Vec<Edge>>
Every edge in a group, id-ordered (deterministic). Includes
invalidated and expired rows — the full bi-temporal record, same view
as Grit::export_jsonl.
§Example
let facts = g.edges_in_group("algebra")?.len();Sourcepub fn episodes_in_group(&self, group_id: &str) -> Result<Vec<Episode>>
pub fn episodes_in_group(&self, group_id: &str) -> Result<Vec<Episode>>
Every episode in a group, ordered by event time then id (deterministic, chronological — the natural order for building previous-episode context windows).
§Example
let history = g.episodes_in_group("chat")?;
let latest = history.last();Sourcepub fn get_node_embedding(&self, id: Uuid) -> Result<Option<Vec<f32>>>
pub fn get_node_embedding(&self, id: Uuid) -> Result<Option<Vec<f32>>>
The embedding stored for a node via Grit::set_node_embedding, or
None when the node has no vector (or no model is registered).
§Example
if g.get_node_embedding(id)?.is_none() {
// embed and g.set_node_embedding(id, vector)?
}Sourcepub fn get_edge_embedding(&self, id: Uuid) -> Result<Option<Vec<f32>>>
pub fn get_edge_embedding(&self, id: Uuid) -> Result<Option<Vec<f32>>>
The embedding stored for an edge via Grit::set_edge_embedding, or
None when the edge has no vector (or no model is registered).
Sourcepub fn mentions_of(&self, target: Uuid) -> Result<Vec<Uuid>>
pub fn mentions_of(&self, target: Uuid) -> Result<Vec<Uuid>>
Episode ids that mention the given node or edge (provenance lookup).
Mention rows keep their original target ids, so this resolves target
to its canonical node and gathers mentions across all merged aliases.
Sourcepub fn traverse(&self, from: &[Uuid], spec: &Traversal) -> Result<Subgraph>
pub fn traverse(&self, from: &[Uuid], spec: &Traversal) -> Result<Subgraph>
Expand from from along edges valid at the requested instant, up to
spec.depth hops, and return the induced subgraph. Time-travel is the
public API here (Design Invariant 4): pass as_at for “what did I
believe at t?”, as_of for “what was true at t?”.
§Example
let believed_in_march = g.traverse(&[node_id], &Traversal::default().as_at(march))?;Sourcepub fn node_history(&self, id: Uuid) -> Result<NodeHistory>
pub fn node_history(&self, id: Uuid) -> Result<NodeHistory>
Full bi-temporal audit for a node: the node row plus every incident edge ever believed, invalidated and expired included — one snapshot.
Sourcepub fn find_merge_candidates(
&self,
id: Uuid,
min_score: f64,
) -> Result<Vec<MergeCandidate>>
pub fn find_merge_candidates( &self, id: Uuid, min_score: f64, ) -> Result<Vec<MergeCandidate>>
Score possible duplicates of id within its group: Jaro-Winkler on
names, cosine over embeddings when present. Returns candidates with
score >= min_score, best first. Grit never acts on these — executing
a crate::GraphOp::MergeNodes is Layer 2’s decision.
Source§impl Grit
impl Grit
Sourcepub fn open(path: impl AsRef<Path>, opts: Options) -> Result<Self>
pub fn open(path: impl AsRef<Path>, opts: Options) -> Result<Self>
Open (creating or migrating as needed) the database at path.
The file plus its WAL sidecars is the entire truth (Design Invariant 2). In-memory databases are not supported: grit uses two connections (writer + reader) that must see the same file.
§Example
use grit_core::{Grit, Options};
let g = Grit::open("memory.db", Options::new("laptop"))?;Sourcepub fn new_id(&self) -> Uuid
pub fn new_id(&self) -> Uuid
Mint a UUIDv7 from the injected clock. Use for the id fields of
GraphOps.
Sourcepub fn apply(&self, op: GraphOp) -> Result<OplogEntry>
pub fn apply(&self, op: GraphOp) -> Result<OplogEntry>
Apply a local write: mint an OplogEntry (op id + HLC), append it to
the oplog and update the graph tables in one transaction. Returns the
entry — the unit a sync mechanism would ship to other devices.
§Example
let entry = g.apply(GraphOp::AddNode {
id: g.new_id(),
kind: "person".into(),
name: "Ada Lovelace".into(),
summary: String::new(),
attrs: serde_json::json!({}),
group_id: String::new(),
})?;
assert_eq!(entry.device_id, "laptop");Sourcepub fn apply_remote(&self, entry: OplogEntry) -> Result<bool>
pub fn apply_remote(&self, entry: OplogEntry) -> Result<bool>
Apply an op that originated on another device (future sync ingest, and
the test seam for the oplog merge laws). Idempotent: returns false
if this op id was already applied. Folds the remote HLC into the local
generator so subsequent local ops sort after everything observed.
Unlike Grit::apply, structurally invalid ops (e.g. a self-merge)
are recorded and applied as no-ops rather than erroring — one
malformed entry from a buggy device must not wedge a sync loop. The
one exception is a negative HLC wall time, which is rejected: it
cannot be ordered, so nothing downstream of it can converge.
Sourcepub fn oplog_since(&self, after_seq: i64) -> Result<Vec<(i64, OplogEntry)>>
pub fn oplog_since(&self, after_seq: i64) -> Result<Vec<(i64, OplogEntry)>>
Read the oplog from local sequence after_seq (exclusive; 0 = start).
Returns (seq, entry) pairs in local apply order.
Sourcepub fn register_embedding_model(
&self,
model_id: impl Into<String>,
dim: usize,
model_version: impl Into<String>,
) -> Result<()>
pub fn register_embedding_model( &self, model_id: impl Into<String>, dim: usize, model_version: impl Into<String>, ) -> Result<()>
Register the embedding model whose vectors this file stores. Creates
the vec_nodes/vec_edges tables sized to dim if they are missing
(also after an import — vectors are never exported, the tables are
rebuilt empty and rows re-embed). Vectors are partitioned by their
row’s group_id, so vector search filters by group inside the KNN
scan. Embeddings are recomputable local state — not part of the oplog
(Design Invariant 5).
Sourcepub fn set_node_embedding(&self, node_id: Uuid, vector: Vec<f32>) -> Result<()>
pub fn set_node_embedding(&self, node_id: Uuid, vector: Vec<f32>) -> Result<()>
Store (or replace) the embedding for a node. The caller computed it;
grit only stores it, in the node’s group partition. The node row must
already exist (Error::NotFound otherwise) — apply the
GraphOp::AddNode first, then embed. Embedding a purged id is a
silent no-op (right to forget).
Sourcepub fn set_edge_embedding(&self, edge_id: Uuid, vector: Vec<f32>) -> Result<()>
pub fn set_edge_embedding(&self, edge_id: Uuid, vector: Vec<f32>) -> Result<()>
Store (or replace) the embedding for an edge (its fact sentence), in
the edge’s group partition. The edge row must already exist
(Error::NotFound otherwise); embedding a purged id is a no-op.