neomemx 0.1.2

A high-performance memory library for AI agents with semantic search
Documentation
//! Operation types and results

use crate::core::change::ChangeType;
use crate::core::fact::StoredFact;
use serde::{Deserialize, Serialize};

/// Result of storing facts
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IngestionOutcome {
    /// IDs of facts that were created
    pub created: Vec<String>,

    /// IDs of facts that were updated
    pub updated: Vec<String>,

    /// IDs of facts that were consolidated
    pub consolidated: Vec<String>,

    /// Details of each operation
    pub operations: Vec<FactOperation>,
}

impl IngestionOutcome {
    /// Create a new empty ingestion outcome
    pub fn new() -> Self {
        Self {
            created: Vec::new(),
            updated: Vec::new(),
            consolidated: Vec::new(),
            operations: Vec::new(),
        }
    }

    /// Add a created fact
    pub fn add_created(&mut self, fact_id: String, operation: FactOperation) {
        self.created.push(fact_id.clone());
        self.operations.push(operation);
    }

    /// Add an updated fact
    pub fn add_updated(&mut self, fact_id: String, operation: FactOperation) {
        self.updated.push(fact_id.clone());
        self.operations.push(operation);
    }

    /// Add a consolidated fact
    pub fn add_consolidated(&mut self, fact_id: String, operation: FactOperation) {
        self.consolidated.push(fact_id.clone());
        self.operations.push(operation);
    }

    /// Check if any operations occurred
    pub fn is_empty(&self) -> bool {
        self.created.is_empty() && self.updated.is_empty() && self.consolidated.is_empty()
    }
}

impl Default for IngestionOutcome {
    fn default() -> Self {
        Self::new()
    }
}

/// Result of a search/query operation
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RetrievalResult {
    /// Matching facts
    pub facts: Vec<StoredFact>,

    /// Total number of matches (may be more than facts.len() if limited)
    pub total_matches: usize,

    /// Related facts from graph (if graph expansion enabled)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub related: Option<Vec<StoredFact>>,
}

impl RetrievalResult {
    /// Create a new retrieval result
    pub fn new(facts: Vec<StoredFact>) -> Self {
        let total_matches = facts.len();
        Self {
            facts,
            total_matches,
            related: None,
        }
    }

    /// Add related facts from graph
    pub fn with_related(mut self, related: Vec<StoredFact>) -> Self {
        self.related = Some(related);
        self
    }

    /// Set total matches count
    pub fn with_total(mut self, total: usize) -> Self {
        self.total_matches = total;
        self
    }
}

impl IntoIterator for RetrievalResult {
    type Item = StoredFact;
    type IntoIter = std::vec::IntoIter<StoredFact>;

    fn into_iter(self) -> Self::IntoIter {
        self.facts.into_iter()
    }
}

/// Result of a fact modification operation
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModificationResult {
    /// ID of the modified fact
    pub fact_id: String,
    /// Previous content before modification
    pub previous_content: String,
    /// New content after modification
    pub new_content: String,
    /// Timestamp of the modification
    pub timestamp: chrono::DateTime<chrono::Utc>,
}

impl ModificationResult {
    /// Create a new modification result
    pub fn new(
        fact_id: impl Into<String>,
        previous_content: impl Into<String>,
        new_content: impl Into<String>,
    ) -> Self {
        Self {
            fact_id: fact_id.into(),
            previous_content: previous_content.into(),
            new_content: new_content.into(),
            timestamp: chrono::Utc::now(),
        }
    }
}

/// An individual fact operation record
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FactOperation {
    /// The ID of the fact involved in the operation
    pub fact_id: String,

    /// The content of the fact
    pub content: String,

    /// The type of change
    pub change_type: ChangeType,

    /// The previous content of the fact, if applicable
    #[serde(skip_serializing_if = "Option::is_none")]
    pub previous_content: Option<String>,

    /// The actor ID associated with the operation
    #[serde(skip_serializing_if = "Option::is_none")]
    pub actor_id: Option<String>,

    /// The role associated with the operation
    #[serde(skip_serializing_if = "Option::is_none")]
    pub role: Option<String>,
}

impl FactOperation {
    /// Create a new fact operation
    pub fn new(
        fact_id: impl Into<String>,
        content: impl Into<String>,
        change_type: ChangeType,
    ) -> Self {
        Self {
            fact_id: fact_id.into(),
            content: content.into(),
            change_type,
            previous_content: None,
            actor_id: None,
            role: None,
        }
    }

    /// Set previous content
    pub fn with_previous_content(mut self, previous: impl Into<String>) -> Self {
        self.previous_content = Some(previous.into());
        self
    }

    /// Set actor ID
    pub fn with_actor_id(mut self, actor_id: impl Into<String>) -> Self {
        self.actor_id = Some(actor_id.into());
        self
    }

    /// Set role
    pub fn with_role(mut self, role: impl Into<String>) -> Self {
        self.role = Some(role.into());
        self
    }
}