rightkit_memory_client/
store.rs1use crate::screening::{self, RejectReason, ScreeningVerdict};
12use serde::{Deserialize, Serialize};
13
14#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
16pub struct MemoryRecord {
17 pub id: String,
18 pub scope: String,
19 pub content: String,
20 pub digest: String,
21 pub subject_key: String,
22 pub created_at: String,
23 pub access_count: u32,
24 pub pinned: bool,
28}
29
30#[derive(Debug, Clone)]
34pub struct NewMemory {
35 pub id: String,
36 pub scope: String,
37 pub content: String,
38 pub digest: String,
39 pub subject_key: String,
40 pub created_at: String,
41}
42
43#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
45#[serde(rename_all = "snake_case", tag = "reason")]
46pub enum WriteRejection {
47 Screened { detail: RejectReason },
50 DuplicateId,
54}
55
56impl From<RejectReason> for WriteRejection {
57 fn from(detail: RejectReason) -> Self {
58 WriteRejection::Screened { detail }
59 }
60}
61
62#[derive(Debug, Clone, Default, Serialize, Deserialize)]
64pub struct MemoryStore {
65 records: std::collections::BTreeMap<String, MemoryRecord>,
66}
67
68impl MemoryStore {
69 pub fn new() -> Self {
70 Self::default()
71 }
72
73 pub fn write(&mut self, candidate: NewMemory) -> Result<&MemoryRecord, WriteRejection> {
77 if self.records.contains_key(&candidate.id) {
78 return Err(WriteRejection::DuplicateId);
79 }
80 match screening::screen(&candidate.content) {
81 ScreeningVerdict::Reject { reason } => Err(WriteRejection::from(reason)),
82 ScreeningVerdict::Admit => {
83 let record = MemoryRecord {
84 id: candidate.id.clone(),
85 scope: candidate.scope,
86 content: candidate.content,
87 digest: candidate.digest,
88 subject_key: candidate.subject_key,
89 created_at: candidate.created_at,
90 access_count: 0,
91 pinned: false,
92 };
93 self.records.insert(candidate.id.clone(), record);
94 Ok(self.records.get(&candidate.id).expect("just inserted"))
95 }
96 }
97 }
98
99 pub fn delete(&mut self, id: &str) -> Option<MemoryRecord> {
103 self.records.remove(id)
104 }
105
106 pub fn get(&self, id: &str) -> Option<&MemoryRecord> {
107 self.records.get(id)
108 }
109
110 pub fn set_pinned(&mut self, id: &str, pinned: bool) -> bool {
111 if let Some(record) = self.records.get_mut(id) {
112 record.pinned = pinned;
113 true
114 } else {
115 false
116 }
117 }
118
119 pub fn record_access(&mut self, id: &str) {
120 if let Some(record) = self.records.get_mut(id) {
121 record.access_count = record.access_count.saturating_add(1);
122 }
123 }
124
125 pub fn list(&self, scope: Option<&str>) -> Vec<&MemoryRecord> {
128 self.records
129 .values()
130 .filter(|record| match scope {
131 Some(s) => record.scope == s,
132 None => true,
133 })
134 .collect()
135 }
136
137 pub fn len(&self) -> usize {
138 self.records.len()
139 }
140
141 pub fn is_empty(&self) -> bool {
142 self.records.is_empty()
143 }
144}