use crate::PersistenceResult;
use async_trait::async_trait;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PersistenceMetadata {
pub instance_id: Uuid,
pub saved_at: DateTime<Utc>,
}
impl PersistenceMetadata {
pub fn new(instance_id: Uuid) -> Self {
Self {
instance_id,
saved_at: Utc::now(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StoreSnapshot {
pub data: Vec<u8>,
pub metadata: PersistenceMetadata,
}
#[derive(Debug, Clone)]
pub enum PersistenceEvent {
Saved(PersistenceMetadata),
ExternalChangeDetected {
path: PathBuf,
saved_at: DateTime<Utc>,
},
ConflictDetected { reason: String },
Error(String),
}
#[async_trait]
pub trait PersistenceStore: Send + Sync {
async fn save(&self, snapshot: StoreSnapshot) -> PersistenceResult<PersistenceMetadata>;
async fn load(&self) -> PersistenceResult<(StoreSnapshot, PersistenceMetadata)>;
async fn exists(&self) -> bool;
fn path(&self) -> &Path;
fn instance_id(&self) -> uuid::Uuid;
async fn sync_command_log(
&self,
_batches: &[Vec<kanban_domain::commands::Command>],
_cursor: u64,
_baseline: Option<&[u8]>,
) -> PersistenceResult<()> {
Ok(())
}
#[allow(clippy::type_complexity)]
fn get_command_log(
&self,
) -> PersistenceResult<(
Vec<Vec<kanban_domain::commands::Command>>,
u64,
Option<Vec<u8>>,
)> {
Ok((vec![], 0, None))
}
#[allow(clippy::type_complexity)]
fn load_sync(&self) -> PersistenceResult<Option<(StoreSnapshot, PersistenceMetadata)>> {
Err(crate::PersistenceError::Unsupported(
"load_sync not supported by this backend".into(),
))
}
}
#[async_trait]
pub trait ChangeDetector: Send + Sync {
async fn start_watching(&self, path: PathBuf) -> PersistenceResult<()>;
async fn stop_watching(&self) -> PersistenceResult<()>;
fn subscribe(&self) -> tokio::sync::broadcast::Receiver<ChangeEvent>;
fn is_watching(&self) -> bool;
}
#[derive(Debug, Clone)]
pub struct ChangeEvent {
pub path: PathBuf,
pub detected_at: DateTime<Utc>,
}
pub trait Serializer<T: Send + Sync>: Send + Sync {
fn serialize(&self, data: &T) -> PersistenceResult<Vec<u8>>;
fn deserialize(&self, bytes: &[u8]) -> PersistenceResult<T>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum FormatVersion {
V1,
V2,
V3,
V4,
V5,
}
impl FormatVersion {
pub fn as_u32(self) -> u32 {
match self {
Self::V1 => 1,
Self::V2 => 2,
Self::V3 => 3,
Self::V4 => 4,
Self::V5 => 5,
}
}
pub fn from_u32(v: u32) -> Option<Self> {
match v {
1 => Some(Self::V1),
2 => Some(Self::V2),
3 => Some(Self::V3),
4 => Some(Self::V4),
5 => Some(Self::V5),
_ => None,
}
}
}
#[async_trait]
pub trait MigrationStrategy: Send + Sync {
async fn detect_version(&self, path: &Path) -> PersistenceResult<FormatVersion>;
async fn migrate(
&self,
from: FormatVersion,
to: FormatVersion,
path: &Path,
) -> PersistenceResult<PathBuf>;
}
pub trait ConflictResolver: Send + Sync {
fn should_use_external(
&self,
local_metadata: &PersistenceMetadata,
external_metadata: &PersistenceMetadata,
) -> bool;
fn explain_resolution(
&self,
local_metadata: &PersistenceMetadata,
external_metadata: &PersistenceMetadata,
) -> String;
}