velesdb-memory 0.14.1

VelesDB-memory: local-first MCP memory server for AI agents (remember/recall/relate/forget/why + deterministic context compiler).
Documentation
use velesdb_core::{AnyCollection, GraphEdge, Point};

use super::NativeStore;
use crate::migration::RawFact;
use crate::MemoryError;

impl NativeStore {
    pub(crate) fn migration_list(
        &self,
        cursor: Option<u64>,
        limit: usize,
    ) -> Result<(Vec<RawFact>, Option<u64>), MemoryError> {
        crate::migration::scroll_page(&self.db, self.collection_name(), cursor, limit)
    }

    pub(crate) fn migration_payload(
        &self,
        id: u64,
    ) -> Result<Option<crate::Metadata>, MemoryError> {
        self.memory
            .semantic()
            .get_metadata(id)
            .map_err(MemoryError::from)
    }

    pub(crate) fn migration_contains(&self, id: u64) -> Result<bool, MemoryError> {
        Ok(self
            .migration_collection()?
            .get(&[id])
            .into_iter()
            .next()
            .flatten()
            .is_some())
    }

    pub(crate) fn migration_upsert(&self, points: Vec<Point>) -> Result<(), MemoryError> {
        if points.is_empty() {
            return Ok(());
        }
        self.migration_collection()?.upsert(points)?;
        Ok(())
    }

    pub(crate) fn migration_delete(&self, id: u64) -> Result<(), MemoryError> {
        let collection = self.migration_collection()?;
        let AnyCollection::Vector(collection) = collection else {
            return Err(capture("semantic memory is not a vector collection"));
        };
        collection.delete(&[id])?;
        Ok(())
    }

    pub(crate) fn migration_live_edges(
        &self,
        from: u64,
        cap: usize,
    ) -> Result<Vec<GraphEdge>, MemoryError> {
        let bounded = self.memory.semantic().relations_bounded(from, cap)?;
        if bounded.truncated {
            return Err(degree_error(from, cap));
        }
        validate_edges(from, &bounded.edges)?;
        Ok(bounded.edges)
    }

    pub(crate) fn migration_replace_edges(
        &self,
        from: u64,
        edges: &[GraphEdge],
        cap: usize,
    ) -> Result<(), MemoryError> {
        validate_edges(from, edges)?;
        let current = self.current_edges_bounded(from, cap)?;
        self.remove_edges(current)?;
        self.add_edges(edges)
    }

    fn remove_edges(&self, edges: Vec<GraphEdge>) -> Result<(), MemoryError> {
        for edge in edges {
            if !self.memory.semantic().unrelate(edge.id())? {
                return Err(capture(format!(
                    "could not durably remove edge {}",
                    edge.id()
                )));
            }
        }
        Ok(())
    }

    fn add_edges(&self, edges: &[GraphEdge]) -> Result<(), MemoryError> {
        let collection = self.migration_collection()?;
        for edge in edges {
            collection.add_edge(edge.clone())?;
        }
        Ok(())
    }

    fn current_edges_bounded(&self, from: u64, cap: usize) -> Result<Vec<GraphEdge>, MemoryError> {
        let bounded = self.memory.semantic().relations_bounded(from, cap)?;
        if bounded.truncated {
            return Err(degree_error(from, cap));
        }
        Ok(self.migration_collection()?.get_outgoing_edges(from))
    }

    fn migration_collection(&self) -> Result<AnyCollection, MemoryError> {
        self.db
            .get_any_collection(self.collection_name())
            .ok_or_else(|| capture("semantic memory collection is absent"))
    }

    fn collection_name(&self) -> &str {
        self.memory.semantic().collection_name()
    }
}

fn validate_edges(from: u64, edges: &[GraphEdge]) -> Result<(), MemoryError> {
    for edge in edges {
        let derived = velesdb_core::hash_edge_id(edge.source(), edge.target(), edge.label());
        if edge.source() != from || edge.id() != derived {
            return Err(capture(format!(
                "edge {} is not a derived outgoing edge of {from}",
                edge.id()
            )));
        }
    }
    Ok(())
}

fn degree_error(from: u64, cap: usize) -> MemoryError {
    capture(format!(
        "outgoing degree of {from} exceeds migration cap {cap}"
    ))
}

fn capture(message: impl Into<String>) -> MemoryError {
    MemoryError::MigrationCapture(message.into())
}