kanban_persistence/
traits.rs1use crate::PersistenceResult;
2use async_trait::async_trait;
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use std::path::{Path, PathBuf};
6use uuid::Uuid;
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct PersistenceMetadata {
11 pub instance_id: Uuid,
13 pub saved_at: DateTime<Utc>,
15}
16
17impl PersistenceMetadata {
18 pub fn new(instance_id: Uuid) -> Self {
19 Self {
20 instance_id,
21 saved_at: Utc::now(),
22 }
23 }
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct StoreSnapshot {
29 pub data: Vec<u8>,
31 pub metadata: PersistenceMetadata,
33}
34
35#[derive(Debug, Clone)]
37pub enum PersistenceEvent {
38 Saved(PersistenceMetadata),
40 ExternalChangeDetected {
42 path: PathBuf,
43 saved_at: DateTime<Utc>,
44 },
45 ConflictDetected { reason: String },
47 Error(String),
49}
50
51#[async_trait]
54pub trait PersistenceStore: Send + Sync {
55 async fn save(&self, snapshot: StoreSnapshot) -> PersistenceResult<PersistenceMetadata>;
57
58 async fn load(&self) -> PersistenceResult<(StoreSnapshot, PersistenceMetadata)>;
60
61 async fn exists(&self) -> bool;
63
64 fn path(&self) -> &Path;
66
67 fn instance_id(&self) -> uuid::Uuid;
69
70 async fn sync_command_log(
73 &self,
74 _batches: &[Vec<kanban_domain::commands::Command>],
75 _cursor: u64,
76 _baseline: Option<&[u8]>,
77 ) -> PersistenceResult<()> {
78 Ok(())
79 }
80
81 #[allow(clippy::type_complexity)]
84 fn get_command_log(
85 &self,
86 ) -> PersistenceResult<(
87 Vec<Vec<kanban_domain::commands::Command>>,
88 u64,
89 Option<Vec<u8>>,
90 )> {
91 Ok((vec![], 0, None))
92 }
93
94 #[allow(clippy::type_complexity)]
100 fn load_sync(&self) -> PersistenceResult<Option<(StoreSnapshot, PersistenceMetadata)>> {
101 Err(crate::PersistenceError::Unsupported(
102 "load_sync not supported by this backend".into(),
103 ))
104 }
105}
106
107#[async_trait]
110pub trait ChangeDetector: Send + Sync {
111 async fn start_watching(&self, path: PathBuf) -> PersistenceResult<()>;
113
114 async fn stop_watching(&self) -> PersistenceResult<()>;
116
117 fn subscribe(&self) -> tokio::sync::broadcast::Receiver<ChangeEvent>;
120
121 fn is_watching(&self) -> bool;
123}
124
125#[derive(Debug, Clone)]
127pub struct ChangeEvent {
128 pub path: PathBuf,
130 pub detected_at: DateTime<Utc>,
132}
133
134pub trait Serializer<T: Send + Sync>: Send + Sync {
137 fn serialize(&self, data: &T) -> PersistenceResult<Vec<u8>>;
139
140 fn deserialize(&self, bytes: &[u8]) -> PersistenceResult<T>;
142}
143
144#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
146pub enum FormatVersion {
147 V1,
148 V2,
149 V3,
150 V4,
151 V5,
152}
153
154impl FormatVersion {
155 pub fn as_u32(self) -> u32 {
156 match self {
157 Self::V1 => 1,
158 Self::V2 => 2,
159 Self::V3 => 3,
160 Self::V4 => 4,
161 Self::V5 => 5,
162 }
163 }
164
165 pub fn from_u32(v: u32) -> Option<Self> {
166 match v {
167 1 => Some(Self::V1),
168 2 => Some(Self::V2),
169 3 => Some(Self::V3),
170 4 => Some(Self::V4),
171 5 => Some(Self::V5),
172 _ => None,
173 }
174 }
175}
176
177#[async_trait]
179pub trait MigrationStrategy: Send + Sync {
180 async fn detect_version(&self, path: &Path) -> PersistenceResult<FormatVersion>;
182
183 async fn migrate(
186 &self,
187 from: FormatVersion,
188 to: FormatVersion,
189 path: &Path,
190 ) -> PersistenceResult<PathBuf>;
191}
192
193pub trait ConflictResolver: Send + Sync {
195 fn should_use_external(
198 &self,
199 local_metadata: &PersistenceMetadata,
200 external_metadata: &PersistenceMetadata,
201 ) -> bool;
202
203 fn explain_resolution(
205 &self,
206 local_metadata: &PersistenceMetadata,
207 external_metadata: &PersistenceMetadata,
208 ) -> String;
209}