Skip to main content

engram/
graph.rs

1//! `GraphStore` trait — the storage interface for the entity-relationship graph.
2//!
3//! Implementations (SQLite, Postgres, in-memory) must implement this trait.
4//! The trait is `async_trait`-annotated and requires `Send + Sync` so it can
5//! be used behind an `Arc<dyn GraphStore>` across async task boundaries.
6
7use crate::fact::{Entity, EntityId, Relationship, RelationshipId, SubGraph};
8use crate::scope::Scope;
9use crate::store::MemoryError;
10use async_trait::async_trait;
11use chrono::{DateTime, Utc};
12
13/// Storage interface for the entity-relationship knowledge graph.
14///
15/// All mutation methods are fallible and return `Result<_, MemoryError>`.
16/// Temporal queries use RFC 3339 `DateTime<Utc>` values for point-in-time
17/// filtering consistent with `FactStore` semantics.
18#[async_trait]
19pub trait GraphStore: Send + Sync {
20    /// Insert or update an entity.
21    ///
22    /// If an entity with `entity.id` already exists, its `name`, `entity_type`,
23    /// `attributes`, and `updated_at` fields are overwritten. Scope and
24    /// `created_at` are preserved.
25    async fn upsert_entity(&self, entity: &Entity) -> Result<(), MemoryError>;
26
27    /// Insert or update a relationship.
28    ///
29    /// If a relationship with `rel.id` already exists, its `relation` and
30    /// `invalid_at` fields are overwritten.
31    async fn upsert_relationship(&self, rel: &Relationship) -> Result<(), MemoryError>;
32
33    /// Mark a relationship as invalid as of `invalid_at`.
34    ///
35    /// Does not delete the record; historical graph queries still traverse it.
36    async fn invalidate_relationship(
37        &self,
38        id: RelationshipId,
39        invalid_at: DateTime<Utc>,
40    ) -> Result<(), MemoryError>;
41
42    /// Retrieve a single entity by id. Returns `None` if not found.
43    async fn get_entity(&self, id: EntityId) -> Result<Option<Entity>, MemoryError>;
44
45    /// BFS neighbourhood query starting from `id`.
46    ///
47    /// Expands up to `depth` hops along currently-valid relationships. When
48    /// `as_of` is `Some(t)`, a relationship is considered valid if
49    /// `valid_from <= t AND (invalid_at IS NULL OR invalid_at > t)`. When
50    /// `as_of` is `None`, only relationships with `invalid_at IS NULL` are
51    /// traversed.
52    ///
53    /// Returns a `SubGraph` containing all discovered entities (excluding the
54    /// start node) and the relationships that connect them.
55    async fn neighbors(
56        &self,
57        id: EntityId,
58        depth: u8,
59        as_of: Option<DateTime<Utc>>,
60    ) -> Result<SubGraph, MemoryError>;
61
62    /// Full-text search over entity names.
63    ///
64    /// Returns up to `top_k` entities whose `name` contains `query`
65    /// (case-insensitive substring match).
66    async fn search_entities(&self, query: &str, top_k: usize) -> Result<Vec<Entity>, MemoryError>;
67
68    /// Hard-delete all entities and relationships belonging to `scope`.
69    ///
70    /// Relationships are deleted first to avoid foreign-key-style dangling
71    /// references. Returns the total number of rows deleted (entities +
72    /// relationships combined).
73    async fn delete_by_scope(&self, scope: &Scope) -> Result<u64, MemoryError>;
74}