use super::entities::*;
use async_trait::async_trait;
use std::sync::Arc;
use uuid::Uuid;
#[derive(Debug, thiserror::Error)]
pub enum PersistenceError {
#[error("Connection error: {0}")]
Connection(String),
#[error("Query error: {0}")]
Query(String),
#[error("Serialization error: {0}")]
Serialization(String),
#[error("Record not found: {0}")]
NotFound(String),
#[error("Constraint violation: {0}")]
Constraint(String),
#[error("Persistence error: {0}")]
Other(String),
}
pub type PersistenceResult<T> = Result<T, PersistenceError>;
#[async_trait]
pub trait MessageStore: Send + Sync {
async fn save_message(&self, message: &LLMMessage) -> PersistenceResult<()>;
async fn save_messages(&self, messages: &[LLMMessage]) -> PersistenceResult<()> {
for msg in messages {
self.save_message(msg).await?;
}
Ok(())
}
async fn get_message(&self, id: Uuid) -> PersistenceResult<Option<LLMMessage>>;
async fn get_session_messages(&self, session_id: Uuid) -> PersistenceResult<Vec<LLMMessage>>;
async fn get_session_messages_paginated(
&self,
session_id: Uuid,
offset: i64,
limit: i64,
) -> PersistenceResult<Vec<LLMMessage>>;
async fn delete_message(&self, id: Uuid) -> PersistenceResult<bool>;
async fn delete_session_messages(&self, session_id: Uuid) -> PersistenceResult<i64>;
async fn count_session_messages(&self, session_id: Uuid) -> PersistenceResult<i64>;
}
#[async_trait]
pub trait ApiCallStore: Send + Sync {
async fn save_api_call(&self, call: &LLMApiCall) -> PersistenceResult<()>;
async fn save_api_calls(&self, calls: &[LLMApiCall]) -> PersistenceResult<()> {
for call in calls {
self.save_api_call(call).await?;
}
Ok(())
}
async fn get_api_call(&self, id: Uuid) -> PersistenceResult<Option<LLMApiCall>>;
async fn query_api_calls(&self, filter: &QueryFilter) -> PersistenceResult<Vec<LLMApiCall>>;
async fn get_statistics(&self, filter: &QueryFilter) -> PersistenceResult<UsageStatistics>;
async fn delete_api_call(&self, id: Uuid) -> PersistenceResult<bool>;
async fn cleanup_old_records(
&self,
before: chrono::DateTime<chrono::Utc>,
) -> PersistenceResult<i64>;
}
#[async_trait]
pub trait SessionStore: Send + Sync {
async fn create_session(&self, session: &ChatSession) -> PersistenceResult<()>;
async fn get_session(&self, id: Uuid) -> PersistenceResult<Option<ChatSession>>;
async fn get_user_sessions(&self, user_id: Uuid) -> PersistenceResult<Vec<ChatSession>>;
async fn update_session(&self, session: &ChatSession) -> PersistenceResult<()>;
async fn delete_session(&self, id: Uuid) -> PersistenceResult<bool>;
}
#[async_trait]
pub trait ProviderStore: Send + Sync {
async fn get_provider(&self, id: Uuid) -> PersistenceResult<Option<super::entities::Provider>>;
async fn get_provider_by_name(
&self,
tenant_id: Uuid,
name: &str,
) -> PersistenceResult<Option<super::entities::Provider>>;
async fn list_providers(
&self,
tenant_id: Uuid,
) -> PersistenceResult<Vec<super::entities::Provider>>;
async fn get_enabled_providers(
&self,
tenant_id: Uuid,
) -> PersistenceResult<Vec<super::entities::Provider>>;
}
#[async_trait]
pub trait AgentStore: Send + Sync {
async fn get_agent(&self, id: Uuid) -> PersistenceResult<Option<super::entities::Agent>>;
async fn get_agent_by_code(
&self,
code: &str,
) -> PersistenceResult<Option<super::entities::Agent>>;
async fn get_agent_by_code_and_tenant(
&self,
tenant_id: Uuid,
code: &str,
) -> PersistenceResult<Option<super::entities::Agent>>;
async fn list_agents(&self, tenant_id: Uuid) -> PersistenceResult<Vec<super::entities::Agent>>;
async fn get_active_agents(
&self,
tenant_id: Uuid,
) -> PersistenceResult<Vec<super::entities::Agent>>;
async fn get_agent_with_provider(
&self,
id: Uuid,
) -> PersistenceResult<Option<super::entities::AgentConfig>>;
async fn get_agent_by_code_with_provider(
&self,
code: &str,
) -> PersistenceResult<Option<super::entities::AgentConfig>>;
async fn get_agent_by_code_and_tenant_with_provider(
&self,
tenant_id: Uuid,
code: &str,
) -> PersistenceResult<Option<super::entities::AgentConfig>>;
}
pub trait PersistenceStore:
MessageStore + ApiCallStore + SessionStore + ProviderStore + AgentStore
{
fn backend_name(&self) -> &str;
fn is_connected(&self) -> bool;
fn close(&self) -> impl std::future::Future<Output = PersistenceResult<()>> + Send;
}
#[async_trait]
pub trait StoreFactory: Send + Sync {
type Store: PersistenceStore;
async fn create(&self, config: &str) -> PersistenceResult<Self::Store>;
}
#[async_trait]
pub trait Transactional: Send + Sync {
type Transaction<'a>: Send + Sync
where
Self: 'a;
async fn begin_transaction(&self) -> PersistenceResult<Self::Transaction<'_>>;
async fn commit_transaction(&self, tx: Self::Transaction<'_>) -> PersistenceResult<()>;
async fn rollback_transaction(&self, tx: Self::Transaction<'_>) -> PersistenceResult<()>;
}
pub type SharedStore<S> = Arc<S>;
pub type DynMessageStore = Arc<dyn MessageStore>;
pub type DynApiCallStore = Arc<dyn ApiCallStore>;
pub type DynSessionStore = Arc<dyn SessionStore>;
pub struct CompositeStore<M, A, S> {
pub message_store: M,
pub api_call_store: A,
pub session_store: S,
}
impl<M, A, S> CompositeStore<M, A, S>
where
M: MessageStore,
A: ApiCallStore,
S: SessionStore,
{
pub fn new(message_store: M, api_call_store: A, session_store: S) -> Self {
Self {
message_store,
api_call_store,
session_store,
}
}
}
#[derive(Debug, Clone)]
pub enum StoreEvent {
MessageSaved { message_id: Uuid, session_id: Uuid },
ApiCallRecorded { call_id: Uuid, session_id: Uuid },
SessionCreated { session_id: Uuid },
SessionDeleted { session_id: Uuid },
}
#[async_trait]
pub trait StoreEventListener: Send + Sync {
async fn on_event(&self, event: StoreEvent);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_persistence_error_display() {
let err = PersistenceError::NotFound("user".to_string());
assert!(err.to_string().contains("not found"));
}
#[test]
fn test_query_filter_default() {
let filter = QueryFilter::default();
assert!(filter.user_id.is_none());
assert!(filter.limit.is_none());
}
}