//! Toasty models and transactional libSQL snapshots for Console control data. //! //! Component bytes and WIT packages remain filesystem artifacts. This module //! persists only service manifests, metrics, and the bounded event history. use std::path::Path; use toasty::Db; use toasty_driver_turso::Turso; use tokio::sync::Mutex; #[derive(Clone, Debug, toasty::Model)] pub(crate) struct StoredService { #[key] pub service_key: String, pub manifest_toml: String, pub updated_at_ms: u64, pub calls: u64, pub errors: u64, pub last_latency_ms: Option, } #[derive(Clone, Debug, toasty::Model)] pub(crate) struct StoredEvent { #[key] pub id: u64, pub timestamp_ms: u64, pub kind: String, pub service_id: Option, pub revision: Option, pub message: String, } /// Serialized access to the Toasty database connection. pub(crate) struct Persistence { db: Mutex, } impl Persistence { /// Opens the database and installs the schema for a new file. pub async fn open(path: &Path) -> toasty::Result { let is_new_database = !path.exists(); let db = Db::builder() .models(toasty::models!(StoredService, StoredEvent)) .build(Turso::file(path)) .await?; if is_new_database { db.push_schema().await?; } Ok(Self { db: Mutex::new(db) }) } /// Loads the complete service set and retained event history. pub async fn load(&self) -> toasty::Result<(Vec, Vec)> { let mut db = self.db.lock().await; let services = StoredService::all().exec(&mut *db).await?; let events = StoredEvent::all().exec(&mut *db).await?; Ok((services, events)) } /// Atomically upserts a Console snapshot and prunes expired events. pub async fn sync( &self, services: Vec, events: Vec, ) -> toasty::Result<()> { let mut db = self.db.lock().await; let mut tx = db.transaction().await?; for service in services { StoredService::upsert_by_service_key(service.service_key) .manifest_toml(service.manifest_toml) .updated_at_ms(service.updated_at_ms) .calls(service.calls) .errors(service.errors) .last_latency_ms(service.last_latency_ms) .exec(&mut tx) .await?; } if let Some(oldest_event) = events.last() { StoredEvent::filter(StoredEvent::fields().id().lt(oldest_event.id)) .delete() .exec(&mut tx) .await?; } for event in events { StoredEvent::upsert_by_id(event.id) .timestamp_ms(event.timestamp_ms) .kind(event.kind) .service_id(event.service_id) .revision(event.revision) .message(event.message) .exec(&mut tx) .await?; } tx.commit().await } }