slave-framework 0.6.0

Runtime for slave apps (a Laravel-inspired Rust framework for MCP-first apps): server lifecycle, config, logging, Turso Cloud database and migrations, and OpenID Connect sign-in
Documentation
//! Connections to the application's [Turso Cloud](https://turso.tech) database.
//!
//! Only libSQL's remote client is compiled (HTTP, pure Rust): there is no
//! database engine in the application. Configure it with `TURSO_DATABASE_URL`
//! (e.g. `libsql://shop-acme.turso.io`) and `TURSO_AUTH_TOKEN`.

use std::sync::Arc;

use libsql::{Builder, Connection};

use crate::{Error, Result};

/// Environment variable holding the database URL.
pub const URL_ENV: &str = "TURSO_DATABASE_URL";
/// Environment variable holding the database auth token.
pub const TOKEN_ENV: &str = "TURSO_AUTH_TOKEN";

/// How to get a database URL and token, shown when they are missing.
pub const SETUP_HINT: &str = "Create a database with `turso db create <name>`, then set \
    TURSO_DATABASE_URL (`turso db show --url <name>`) and TURSO_AUTH_TOKEN \
    (`turso db tokens create <name>`) in .env.";

/// Where to connect: a database URL and its auth token.
#[derive(Clone)]
pub struct DatabaseConfig {
    pub url: String,
    pub auth_token: String,
}

impl std::fmt::Debug for DatabaseConfig {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("DatabaseConfig")
            .field("url", &self.url)
            .field("auth_token", &"<redacted>")
            .finish()
    }
}

impl DatabaseConfig {
    /// Reads `TURSO_DATABASE_URL` (required) and `TURSO_AUTH_TOKEN` (empty
    /// for servers without authentication, such as `turso dev`).
    pub fn from_env() -> Result<Self> {
        let url = std::env::var(URL_ENV)
            .ok()
            .filter(|url| !url.trim().is_empty())
            .ok_or(Error::MissingDatabaseUrl(URL_ENV))?;
        let auth_token = std::env::var(TOKEN_ENV).unwrap_or_default();
        Ok(Self { url, auth_token })
    }
}

/// A shareable handle to the database. Cheap to clone.
///
/// Open one per process and call [`connect`](Database::connect) per unit of
/// work (for example, per request).
#[derive(Clone)]
pub struct Database {
    inner: Arc<libsql::Database>,
    url: Arc<str>,
}

impl std::fmt::Debug for Database {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Database").field("url", &self.url).finish()
    }
}

impl Database {
    /// Prepares a client for the database at `config.url`. No request is
    /// made until the first query.
    pub async fn open(config: &DatabaseConfig) -> Result<Self> {
        let inner = Builder::new_remote(config.url.clone(), config.auth_token.clone())
            .build()
            .await?;
        Ok(Self {
            inner: Arc::new(inner),
            url: config.url.as_str().into(),
        })
    }

    /// [`open`](Self::open) with [`DatabaseConfig::from_env`].
    pub async fn from_env() -> Result<Self> {
        Self::open(&DatabaseConfig::from_env()?).await
    }

    /// The database URL (never the token), for logs and messages.
    pub fn url(&self) -> &str {
        &self.url
    }

    /// Opens a new connection with foreign-key enforcement enabled.
    pub async fn connect(&self) -> Result<Connection, libsql::Error> {
        let conn = self.inner.connect()?;
        conn.execute("PRAGMA foreign_keys = ON", ()).await?;
        Ok(conn)
    }
}