Skip to main content

khive_storage/
note.rs

1//! Note storage capability — temporal-referential record CRUD.
2
3use async_trait::async_trait;
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6use uuid::Uuid;
7
8use crate::types::{BatchWriteSummary, DeleteMode, Page, PageRequest, StorageResult};
9
10/// A storage-level note record. Flat, SQL-friendly representation.
11#[derive(Clone, Debug, Serialize, Deserialize)]
12pub struct Note {
13    pub id: Uuid,
14    pub namespace: String,
15    pub kind: String,
16    pub name: Option<String>,
17    pub content: String,
18    pub salience: f64,
19    pub decay_factor: f64,
20    pub expires_at: Option<i64>,
21    pub properties: Option<Value>,
22    pub created_at: i64,
23    pub updated_at: i64,
24    pub deleted_at: Option<i64>,
25}
26
27impl Note {
28    pub fn new(
29        namespace: impl Into<String>,
30        kind: impl Into<String>,
31        content: impl Into<String>,
32    ) -> Self {
33        let now = chrono::Utc::now().timestamp_micros();
34        Self {
35            id: Uuid::new_v4(),
36            namespace: namespace.into(),
37            kind: kind.into(),
38            name: None,
39            content: content.into(),
40            salience: 0.5,
41            decay_factor: 0.0,
42            expires_at: None,
43            properties: None,
44            created_at: now,
45            updated_at: now,
46            deleted_at: None,
47        }
48    }
49
50    pub fn with_name(mut self, n: impl Into<String>) -> Self {
51        self.name = Some(n.into());
52        self
53    }
54
55    pub fn with_salience(mut self, s: f64) -> Self {
56        self.salience = s.clamp(0.0, 1.0);
57        self
58    }
59
60    pub fn with_decay(mut self, d: f64) -> Self {
61        self.decay_factor = d.max(0.0);
62        self
63    }
64
65    pub fn with_properties(mut self, p: Value) -> Self {
66        self.properties = Some(p);
67        self
68    }
69}
70
71#[async_trait]
72pub trait NoteStore: Send + Sync + 'static {
73    async fn upsert_note(&self, note: Note) -> StorageResult<()>;
74    async fn upsert_notes(&self, notes: Vec<Note>) -> StorageResult<BatchWriteSummary>;
75    async fn get_note(&self, id: Uuid) -> StorageResult<Option<Note>>;
76    async fn delete_note(&self, id: Uuid, mode: DeleteMode) -> StorageResult<bool>;
77    async fn query_notes(
78        &self,
79        namespace: &str,
80        kind: Option<&str>,
81        page: PageRequest,
82    ) -> StorageResult<Page<Note>>;
83    async fn count_notes(&self, namespace: &str, kind: Option<&str>) -> StorageResult<u64>;
84
85    async fn get_notes_batch(&self, ids: &[Uuid]) -> StorageResult<Vec<Note>> {
86        let mut out = Vec::with_capacity(ids.len());
87        for &id in ids {
88            if let Some(n) = self.get_note(id).await? {
89                out.push(n);
90            }
91        }
92        Ok(out)
93    }
94
95    async fn upsert_note_if_below_quota(&self, note: Note, max_notes: u64) -> StorageResult<bool> {
96        let count = self.count_notes(&note.namespace, None).await?;
97        if count >= max_notes {
98            return Ok(false);
99        }
100        self.upsert_note(note).await?;
101        Ok(true)
102    }
103}