slave-framework 0.1.0

Runtime for slave apps: Axum server lifecycle, config, logging, Turso database, migrations and password/token auth
//! Schema migrations for the local Turso database.
//!
//! A migration is a type implementing [`Migration`]: `up` returns the SQL that
//! applies it and `down` optionally returns SQL that reverts it. Migrations are
//! listed with the [`migrations!`](crate::migrations) macro and executed by a
//! [`Migrator`], which records each applied migration with its batch number in
//! the [`TABLE`] tracking table.
//!
//! Every migration runs inside its own transaction together with its tracking
//! row, so a failing migration leaves neither schema changes nor a record
//! behind. Turso, like SQLite, supports transactional DDL; statements that
//! cannot run in a transaction (such as `VACUUM`) must opt out through
//! [`Migration::transactional`].

pub mod console;
mod migrator;

pub use migrator::{Failure, Migrator, Outcome, RollbackReport, RunReport, State, Status, TABLE};

/// An ordered list of SQL statements produced by a migration.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Schema {
    statements: Vec<String>,
}

impl Schema {
    pub fn new() -> Self {
        Self::default()
    }

    /// Appends a SQL statement. A string may contain several `;`-separated statements.
    #[must_use]
    pub fn execute(mut self, sql: impl Into<String>) -> Self {
        self.statements.push(sql.into());
        self
    }

    pub fn statements(&self) -> &[String] {
        &self.statements
    }
}

/// A single schema change.
pub trait Migration: Send + Sync {
    /// Statements that apply the migration.
    fn up(&self) -> Schema;

    /// Statements that revert [`up`](Migration::up), or `None` when the change
    /// cannot be reversed safely (for example, dropping a table loses its data).
    ///
    /// A batch containing an irreversible migration is never rolled back.
    fn down(&self) -> Option<Schema> {
        None
    }

    /// Whether to wrap the migration in a transaction. Defaults to `true`.
    ///
    /// Opt out only for statements that cannot run inside a transaction; if a
    /// non-transactional migration fails, earlier statements may remain applied.
    fn transactional(&self) -> bool {
        true
    }
}

/// A migration paired with its unique name, which also determines run order.
#[derive(Clone, Copy)]
pub struct Registered {
    name: &'static str,
    migration: &'static dyn Migration,
}

impl Registered {
    pub fn new(name: &'static str, migration: &'static dyn Migration) -> Self {
        Self { name, migration }
    }

    /// Registers a migration from a module named `m<name>`, as produced by
    /// `slave make:migration` (`m20261016160000_create_users_table`).
    pub fn from_module(module: &'static str, migration: &'static dyn Migration) -> Self {
        Self::new(module.strip_prefix('m').unwrap_or(module), migration)
    }

    pub fn name(&self) -> &'static str {
        self.name
    }

    pub fn migration(&self) -> &'static dyn Migration {
        self.migration
    }
}

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

/// Lists migration modules, each exposing `pub const MIGRATION: &dyn Migration`.
///
/// ```ignore
/// slave_framework::migrations![m20261016160000_create_users_table]
/// ```
#[macro_export]
macro_rules! migrations {
    ($($module:ident),* $(,)?) => {
        vec![$(
            $crate::migration::Registered::from_module(stringify!($module), $module::MIGRATION)
        ),*]
    };
}