1use crate::{ArtifactRef, Event, EventId, ForkId, ForkSnapshot, GraphSnapshot, StateError};
2use async_trait::async_trait;
3use serde::{Deserialize, Serialize};
4use std::path::{Path, PathBuf};
5use std::sync::{Arc, Mutex};
6
7#[async_trait]
8pub trait EventStore: Send + Sync {
9 async fn append(&self, events: Vec<Event>) -> Result<Vec<EventId>, StateError>;
10 async fn scan(&self) -> Result<Vec<Event>, StateError>;
11 async fn scan_after(&self, event_id: Option<EventId>) -> Result<Vec<Event>, StateError>;
12}
13
14#[async_trait]
15pub trait SnapshotStore: Send + Sync {
16 async fn save_snapshot(&self, snapshot: GraphSnapshot) -> Result<(), StateError>;
17 async fn load_snapshot(&self) -> Result<Option<GraphSnapshot>, StateError>;
18}
19
20#[async_trait]
21pub trait ForkStore: Send + Sync {
22 async fn save_fork(&self, fork: ForkSnapshot) -> Result<(), StateError>;
23 async fn load_fork(&self, fork_id: ForkId) -> Result<Option<ForkSnapshot>, StateError>;
24}
25
26#[async_trait]
27pub trait IndexStore: Send + Sync {
28 async fn put_index(&self, key: String, value: serde_json::Value) -> Result<(), StateError>;
29 async fn get_index(&self, key: String) -> Result<Option<serde_json::Value>, StateError>;
30}
31
32#[async_trait]
33pub trait ArtifactStore: Send + Sync {
34 async fn put_artifact(
35 &self,
36 artifact: ArtifactRef,
37 bytes: Vec<u8>,
38 ) -> Result<ArtifactRef, StateError>;
39 async fn get_artifact(&self, artifact: ArtifactRef) -> Result<Option<Vec<u8>>, StateError>;
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
43struct IndexRecord {
44 key: String,
45 value: serde_json::Value,
46}
47
48#[derive(Debug, Clone, Default)]
49pub struct MemoryEventStore {
50 events: Arc<Mutex<Vec<Event>>>,
51}
52
53impl MemoryEventStore {
54 pub fn new() -> Self {
55 Self::default()
56 }
57}
58
59#[async_trait]
60impl EventStore for MemoryEventStore {
61 async fn append(&self, events: Vec<Event>) -> Result<Vec<EventId>, StateError> {
62 let ids = events.iter().map(|event| event.id.clone()).collect();
63 self.events
64 .lock()
65 .map_err(|err| StateError::Store(err.to_string()))?
66 .extend(events);
67 Ok(ids)
68 }
69
70 async fn scan(&self) -> Result<Vec<Event>, StateError> {
71 Ok(self
72 .events
73 .lock()
74 .map_err(|err| StateError::Store(err.to_string()))?
75 .clone())
76 }
77
78 async fn scan_after(&self, event_id: Option<EventId>) -> Result<Vec<Event>, StateError> {
79 scan_after_events(self.scan().await?, event_id)
80 }
81}
82
83#[derive(Debug, Clone)]
84pub struct JsonlEventStore {
85 path: PathBuf,
86 lock: Arc<tokio::sync::Mutex<()>>,
87}
88
89impl JsonlEventStore {
90 pub fn new(path: impl Into<PathBuf>) -> Self {
91 Self {
92 path: path.into(),
93 lock: Arc::new(tokio::sync::Mutex::new(())),
94 }
95 }
96
97 pub fn path(&self) -> &Path {
98 &self.path
99 }
100
101 fn snapshot_path(&self) -> PathBuf {
102 self.path.with_extension("snapshot.json")
103 }
104
105 fn index_path(&self) -> PathBuf {
106 self.path.with_extension("index.jsonl")
107 }
108
109 fn fork_path(&self, fork_id: &ForkId) -> PathBuf {
110 self.path
111 .parent()
112 .unwrap_or_else(|| Path::new("."))
113 .join("forks")
114 .join(format!("{}.json", fork_id.0))
115 }
116}
117
118#[async_trait]
119impl EventStore for JsonlEventStore {
120 async fn append(&self, events: Vec<Event>) -> Result<Vec<EventId>, StateError> {
121 let _guard = self.lock.lock().await;
122 if let Some(parent) = self.path.parent() {
123 tokio::fs::create_dir_all(parent).await?;
124 }
125
126 let mut existing = if self.path.exists() {
127 tokio::fs::read_to_string(&self.path).await?
128 } else {
129 String::new()
130 };
131 let ids = events.iter().map(|event| event.id.clone()).collect();
132 for event in events {
133 existing.push_str(&serde_json::to_string(&event)?);
134 existing.push('\n');
135 }
136 tokio::fs::write(&self.path, existing).await?;
137 Ok(ids)
138 }
139
140 async fn scan(&self) -> Result<Vec<Event>, StateError> {
141 if !self.path.exists() {
142 return Ok(Vec::new());
143 }
144
145 let raw = tokio::fs::read_to_string(&self.path).await?;
146 raw.lines()
147 .filter(|line| !line.trim().is_empty())
148 .map(|line| serde_json::from_str(line).map_err(StateError::from))
149 .collect()
150 }
151
152 async fn scan_after(&self, event_id: Option<EventId>) -> Result<Vec<Event>, StateError> {
153 scan_after_events(self.scan().await?, event_id)
154 }
155}
156
157#[async_trait]
158impl SnapshotStore for JsonlEventStore {
159 async fn save_snapshot(&self, snapshot: GraphSnapshot) -> Result<(), StateError> {
160 let path = self.snapshot_path();
161 if let Some(parent) = path.parent() {
162 tokio::fs::create_dir_all(parent).await?;
163 }
164 tokio::fs::write(path, serde_json::to_vec_pretty(&snapshot)?).await?;
165 Ok(())
166 }
167
168 async fn load_snapshot(&self) -> Result<Option<GraphSnapshot>, StateError> {
169 let path = self.snapshot_path();
170 if !path.exists() {
171 return Ok(None);
172 }
173 Ok(Some(serde_json::from_slice(&tokio::fs::read(path).await?)?))
174 }
175}
176
177#[async_trait]
178impl ForkStore for JsonlEventStore {
179 async fn save_fork(&self, fork: ForkSnapshot) -> Result<(), StateError> {
180 let path = self.fork_path(&fork.id);
181 if let Some(parent) = path.parent() {
182 tokio::fs::create_dir_all(parent).await?;
183 }
184 tokio::fs::write(path, serde_json::to_vec_pretty(&fork)?).await?;
185 Ok(())
186 }
187
188 async fn load_fork(&self, fork_id: ForkId) -> Result<Option<ForkSnapshot>, StateError> {
189 let path = self.fork_path(&fork_id);
190 if !path.exists() {
191 return Ok(None);
192 }
193 Ok(Some(serde_json::from_slice(&tokio::fs::read(path).await?)?))
194 }
195}
196
197#[async_trait]
198impl IndexStore for JsonlEventStore {
199 async fn put_index(&self, key: String, value: serde_json::Value) -> Result<(), StateError> {
200 let path = self.index_path();
201 if let Some(parent) = path.parent() {
202 tokio::fs::create_dir_all(parent).await?;
203 }
204 let mut existing = if path.exists() {
205 tokio::fs::read_to_string(&path).await?
206 } else {
207 String::new()
208 };
209 existing.push_str(&serde_json::to_string(&IndexRecord { key, value })?);
210 existing.push('\n');
211 tokio::fs::write(path, existing).await?;
212 Ok(())
213 }
214
215 async fn get_index(&self, key: String) -> Result<Option<serde_json::Value>, StateError> {
216 let path = self.index_path();
217 if !path.exists() {
218 return Ok(None);
219 }
220 let mut found = None;
221 for line in tokio::fs::read_to_string(path).await?.lines() {
222 if line.trim().is_empty() {
223 continue;
224 }
225 let record: IndexRecord = serde_json::from_str(line)?;
226 if record.key == key {
227 found = Some(record.value);
228 }
229 }
230 Ok(found)
231 }
232}
233
234pub(crate) fn scan_after_events(
235 events: Vec<Event>,
236 event_id: Option<EventId>,
237) -> Result<Vec<Event>, StateError> {
238 let Some(event_id) = event_id else {
239 return Ok(events);
240 };
241
242 let position = events
243 .iter()
244 .position(|event| event.id == event_id)
245 .ok_or(StateError::EventNotFound(event_id))?;
246 Ok(events.into_iter().skip(position + 1).collect())
247}