feat(console): persist Host KV in libSQL
Store KV entries through Toasty with a composite service ID and key primary key, including an additive table migration for existing Console databases. Bridge synchronous Wasmtime Host calls to async persistence with a bounded worker queue and response timeout capped by the service deadline. Verify same-service revision sharing, cross-service isolation, deletion, exact capability reporting, and persistence after reopening Console.
This commit is contained in:
@@ -1,10 +1,14 @@
|
||||
//! Toasty models and transactional libSQL snapshots for Console control data.
|
||||
//!
|
||||
//! Component bytes and WIT packages remain filesystem artifacts. This module
|
||||
//! persists only service manifests, deployments, metrics, and the bounded
|
||||
//! event history.
|
||||
//! persists service manifests, deployments, metrics, the bounded event
|
||||
//! history, and service-scoped Host KV entries.
|
||||
|
||||
use std::path::Path;
|
||||
use std::{
|
||||
path::Path,
|
||||
sync::Arc,
|
||||
time::{SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
use toasty::Db;
|
||||
use toasty_driver_turso::Turso;
|
||||
@@ -40,9 +44,19 @@ pub(crate) struct StoredDeployment {
|
||||
pub updated_at_ms: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, toasty::Model)]
|
||||
#[key(service_id, entry_key)]
|
||||
pub(crate) struct StoredKvEntry {
|
||||
pub service_id: String,
|
||||
pub entry_key: String,
|
||||
pub value: Vec<u8>,
|
||||
pub updated_at_ms: u64,
|
||||
}
|
||||
|
||||
/// Serialized access to the Toasty database connection.
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct Persistence {
|
||||
db: Mutex<Db>,
|
||||
db: Arc<Mutex<Db>>,
|
||||
}
|
||||
|
||||
impl Persistence {
|
||||
@@ -53,7 +67,8 @@ impl Persistence {
|
||||
.models(toasty::models!(
|
||||
StoredService,
|
||||
StoredEvent,
|
||||
StoredDeployment
|
||||
StoredDeployment,
|
||||
StoredKvEntry
|
||||
))
|
||||
.build(Turso::file(path))
|
||||
.await?;
|
||||
@@ -74,9 +89,24 @@ impl Persistence {
|
||||
)
|
||||
.exec(&mut db)
|
||||
.await?;
|
||||
return Ok(Self { db: Mutex::new(db) });
|
||||
toasty::sql::statement(
|
||||
r#"CREATE TABLE IF NOT EXISTS "stored_kv_entries" (
|
||||
"service_id" TEXT NOT NULL,
|
||||
"entry_key" TEXT NOT NULL,
|
||||
"value" BLOB NOT NULL,
|
||||
"updated_at_ms" INTEGER NOT NULL,
|
||||
PRIMARY KEY ("service_id", "entry_key")
|
||||
)"#,
|
||||
)
|
||||
.exec(&mut db)
|
||||
.await?;
|
||||
return Ok(Self {
|
||||
db: Arc::new(Mutex::new(db)),
|
||||
});
|
||||
}
|
||||
Ok(Self { db: Mutex::new(db) })
|
||||
Ok(Self {
|
||||
db: Arc::new(Mutex::new(db)),
|
||||
})
|
||||
}
|
||||
|
||||
/// Loads the complete service and deployment sets plus retained events.
|
||||
@@ -139,4 +169,46 @@ impl Persistence {
|
||||
|
||||
tx.commit().await
|
||||
}
|
||||
|
||||
pub(crate) async fn kv_get(
|
||||
&self,
|
||||
service_id: &str,
|
||||
entry_key: &str,
|
||||
) -> toasty::Result<Option<Vec<u8>>> {
|
||||
let mut db = self.db.lock().await;
|
||||
Ok(
|
||||
StoredKvEntry::filter_by_service_id_and_entry_key(service_id, entry_key)
|
||||
.first()
|
||||
.exec(&mut *db)
|
||||
.await?
|
||||
.map(|entry| entry.value),
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) async fn kv_set(
|
||||
&self,
|
||||
service_id: String,
|
||||
entry_key: String,
|
||||
value: Vec<u8>,
|
||||
) -> toasty::Result<()> {
|
||||
let mut db = self.db.lock().await;
|
||||
StoredKvEntry::upsert_by_service_id_and_entry_key(service_id, entry_key)
|
||||
.value(value)
|
||||
.updated_at_ms(unix_time_ms())
|
||||
.exec(&mut *db)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn kv_delete(&self, service_id: &str, entry_key: &str) -> toasty::Result<()> {
|
||||
let mut db = self.db.lock().await;
|
||||
StoredKvEntry::delete_by_service_id_and_entry_key(&mut *db, service_id, entry_key).await
|
||||
}
|
||||
}
|
||||
|
||||
fn unix_time_ms() -> u64 {
|
||||
SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.map(|duration| u64::try_from(duration.as_millis()).unwrap_or(u64::MAX))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user