♻️ (seaorm): Adapta al arranque async

- Elimina `run_now` y convierte `initialize()` en `async fn`.
- `DBCONN` pasa de `LazyLock` a `OnceLock`; `MigratorBase` adopta
  `#[pagetop::async_trait]` con métodos async nativos.
This commit is contained in:
Manuel Cillero 2026-07-06 19:40:54 +02:00
parent 1139df6210
commit 9b2d430d8b
8 changed files with 94 additions and 100 deletions

View file

@ -20,7 +20,6 @@ postgres = ["sea-orm/sqlx-postgres"]
sqlite = ["sea-orm/sqlx-sqlite"]
[dependencies]
async-trait.workspace = true
pagetop.workspace = true
sea-orm.workspace = true
sea-schema.workspace = true

View file

@ -70,6 +70,7 @@ mod migration;
struct MyApp;
#[async_trait]
impl Extension for MyApp {
fn dependencies(&self) -> Vec<ExtensionRef> {
vec![
@ -77,14 +78,14 @@ impl Extension for MyApp {
]
}
fn initialize(&self) {
async fn initialize(&self) {
install_migrations!(m20240101_000001_create_users);
}
}
#[pagetop::main]
async fn main() -> std::io::Result<()> {
Application::prepare(&MyApp).run()?.await
Application::prepare(&MyApp).await.run().await
}
```
@ -96,7 +97,7 @@ use pagetop_seaorm::migration::*;
pub struct Migration;
#[async_trait::async_trait]
#[pagetop::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager

View file

@ -143,10 +143,6 @@ pub use sea_orm::{
ActiveValue, DatabaseTransaction, ExecResult, QueryOrder, QuerySelect, TransactionTrait,
};
/// Permite implementar *traits* con métodos `async`:
#[doc(inline)]
pub use async_trait;
/// Re-exporta el crate `sea_orm` íntegro como puerta de acceso a su API completa.
///
/// Útil para tipos o utilidades que no están expuestos directamente en [`db::*`](self). La inmensa
@ -176,7 +172,13 @@ pub use sea_orm::sea_query as query;
/// ```
#[inline]
pub fn dbconn() -> &'static DatabaseConnection {
&super::DBCONN
super::DBCONN
// La inicialización requiere async (`Database::connect().await`). OnceLock asigna el valor
// con `.set()` en `Extension::initialize()` (contra LazyLock que sólo admite uso síncrono).
.get()
// Este texto no se multiplica al hacer `#[inline]`, es un `&'static str` que el compilador
// almacena una vez. Sólo se duplica una comprobación de nulo y un salto condicional.
.expect("Database not initialized: SeaORM extension must be listed as a dependency")
}
/// Ejecuta una sentencia SQL en crudo y devuelve su resultado.

View file

@ -71,6 +71,7 @@ mod migration;
struct MyApp;
#[async_trait]
impl Extension for MyApp {
fn dependencies(&self) -> Vec<ExtensionRef> {
vec![
@ -78,14 +79,14 @@ impl Extension for MyApp {
]
}
fn initialize(&self) {
async fn initialize(&self) {
install_migrations!(m20240101_000001_create_users);
}
}
#[pagetop::main]
async fn main() -> std::io::Result<()> {
Application::prepare(&MyApp).run()?.await
Application::prepare(&MyApp).await.run().await
}
```
@ -97,7 +98,7 @@ use pagetop_seaorm::migration::*;
pub struct Migration;
#[async_trait::async_trait]
#[pagetop::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
@ -176,7 +177,7 @@ use pagetop::prelude::*;
use sea_orm::{ConnectOptions, Database, DatabaseConnection};
use url::Url;
use std::sync::LazyLock;
use std::sync::OnceLock;
include_locales!(LOCALES_SEAORM);
@ -186,20 +187,22 @@ pub mod db;
pub mod migration;
// Ejecuta un *future* de forma síncrona dentro del runtime de Tokio.
//
// Usa [`tokio::task::block_in_place`] para ceder el hilo actual al código bloqueante sin detener el
// *pool* de trabajo de Tokio, y a continuación ejecuta el *future* con el *handle* del *runtime*
// activo. Requiere el *runtime* multi-hilo (predeterminado con `#[pagetop::main]`).
//
// En tests, `#[pagetop::test]` aplica `multi_thread` por defecto. Si se utiliza `#[tokio::test]`
// directamente, habría que añadir `(flavor = "multi_thread")` si el test invoca código que llame a
// esta función.
pub(crate) fn run_now<F: std::future::Future>(future: F) -> F::Output {
tokio::task::block_in_place(|| tokio::runtime::Handle::current().block_on(future))
static DBCONN: OnceLock<DatabaseConnection> = OnceLock::new();
/// Implementa la extensión.
pub struct SeaORM;
#[async_trait]
impl Extension for SeaORM {
fn name(&self) -> L10n {
L10n::t("extension_name", &LOCALES_SEAORM)
}
pub(crate) static DBCONN: LazyLock<DatabaseConnection> = LazyLock::new(|| {
fn description(&self) -> L10n {
L10n::t("extension_description", &LOCALES_SEAORM)
}
async fn initialize(&self) {
trace::info!(
"Connecting to database \"{}\" using a pool of {} connections",
&config::SETTINGS.database.db_name,
@ -208,7 +211,7 @@ pub(crate) static DBCONN: LazyLock<DatabaseConnection> = LazyLock::new(|| {
let db_uri: String = match config::SETTINGS.database.db_type {
config::DbType::Unset => panic!(
"database.db_type is not configured: set it to \"mysql\", \"postgres\" or \"sqlite\""
"database.db_type is not configured: use \"mysql\", \"postgres\" or \"sqlite\""
),
config::DbType::Mysql | config::DbType::Postgres => {
let scheme = if matches!(config::SETTINGS.database.db_type, config::DbType::Mysql) {
@ -226,8 +229,8 @@ pub(crate) static DBCONN: LazyLock<DatabaseConnection> = LazyLock::new(|| {
tmp_uri
.set_username(config::SETTINGS.database.db_user.as_str())
.expect("Failed to set db_user in connection URL");
// https://github.com/launchbadge/sqlx/issues/1624
tmp_uri
// https://github.com/launchbadge/sqlx/issues/1624
.set_password(Some(config::SETTINGS.database.db_pass.as_str()))
.expect("Failed to set db_pass in connection URL");
if let Some(port) = config::SETTINGS.database.db_port {
@ -242,27 +245,14 @@ pub(crate) static DBCONN: LazyLock<DatabaseConnection> = LazyLock::new(|| {
}
};
run_now(Database::connect::<ConnectOptions>({
let conn = Database::connect::<ConnectOptions>({
let mut db_opt = ConnectOptions::new(db_uri);
db_opt.max_connections(config::SETTINGS.database.max_pool_size);
db_opt
}))
.expect("Failed to connect to database")
});
})
.await
.expect("Failed to connect to database");
/// Implementa la extensión.
pub struct SeaORM;
impl Extension for SeaORM {
fn name(&self) -> L10n {
L10n::t("extension_name", &LOCALES_SEAORM)
}
fn description(&self) -> L10n {
L10n::t("extension_description", &LOCALES_SEAORM)
}
fn initialize(&self) {
std::sync::LazyLock::force(&DBCONN);
DBCONN.set(conn).expect("DBCONN already initialized");
}
}

View file

@ -34,7 +34,7 @@
//!
//! pub struct Migration;
//!
//! #[async_trait::async_trait]
//! #[pagetop::async_trait]
//! impl MigrationTrait for Migration {
//! async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
//! manager
@ -127,9 +127,7 @@ pub use connection::*;
pub use manager::*;
//pub use migrator::*;
/// Permite implementar *traits* con métodos `async`:
#[doc(inline)]
pub use async_trait;
//pub use async_trait;
//pub use sea_orm;
//pub use sea_orm::sea_query;
pub use sea_orm::DbErr;
@ -139,7 +137,7 @@ pub trait MigrationName {
}
/// The migration definition
#[async_trait::async_trait]
#[pagetop::async_trait]
pub trait MigrationTrait: MigrationName + Send + Sync {
/// Define actions to perform when applying the migration
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr>;
@ -173,35 +171,37 @@ impl<M: MigrationTrait> MigrationName for M {
/// Elemento de migración listo para incluir en la lista de un [`MigratorTrait`].
pub type MigrationItem = Box<dyn MigrationTrait>;
/// Interfaz síncrona para ejecutar migraciones desde código no asíncrono.
/// Interfaz asíncrona para ejecutar migraciones.
///
/// Todo tipo que implemente [`MigratorTrait`] obtiene esta interfaz automáticamente, incluidos los
/// tipos generados por los macros [`install_migrations!`](crate::install_migrations) y
/// [`uninstall_migrations!`](crate::uninstall_migrations).
#[pagetop::async_trait]
pub trait MigratorBase {
/// Ejecuta las migraciones pendientes en orden ascendente.
///
/// Provoca un `panic!` si alguna migración falla, evitando que la aplicación arranque con un
/// esquema de base de datos inconsistente.
fn run_up();
async fn run_up();
/// Revierte todas las migraciones en orden descendente.
///
/// Provoca un `panic!` si alguna reversión falla.
fn run_down();
async fn run_down();
}
#[pagetop::async_trait]
impl<M: MigratorTrait> MigratorBase for M {
fn run_up() {
let conn = SchemaManagerConnection::Connection(&super::DBCONN);
if let Err(e) = super::run_now(Self::up(conn, None)) {
async fn run_up() {
let conn = SchemaManagerConnection::Connection(super::db::dbconn());
if let Err(e) = Self::up(conn, None).await {
panic!("Migration upgrade failed: {e}");
}
}
fn run_down() {
let conn = SchemaManagerConnection::Connection(&super::DBCONN);
if let Err(e) = super::run_now(Self::down(conn, None)) {
async fn run_down() {
let conn = SchemaManagerConnection::Connection(super::db::dbconn());
if let Err(e) = Self::down(conn, None).await {
panic!("Migration downgrade failed: {e}");
}
}
@ -228,8 +228,9 @@ impl<M: MigratorTrait> MigratorBase for M {
/// ```rust,ignore
/// mod migration;
///
/// #[async_trait]
/// impl Extension for MyExt {
/// fn initialize(&self) {
/// async fn initialize(&self) {
/// install_migrations!(
/// m20240101_000001_create_users,
/// m20240115_000002_add_email_index,
@ -252,7 +253,7 @@ macro_rules! install_migrations {
m
}
}
Migrator::run_up();
Migrator::run_up().await;
}};
}
@ -269,8 +270,9 @@ macro_rules! install_migrations {
///
/// En `src/lib.rs`:
/// ```rust,ignore
/// #[async_trait]
/// impl Extension for MyExt {
/// fn uninitialize(&self) {
/// async fn uninitialize(&self) {
/// uninstall_migrations!(
/// m20240101_000001_create_users,
/// m20240115_000002_add_email_index,
@ -293,6 +295,6 @@ macro_rules! uninstall_migrations {
m
}
}
Migrator::run_down();
Migrator::run_down().await;
}};
}

View file

@ -10,7 +10,7 @@ pub enum SchemaManagerConnection<'c> {
Transaction(&'c DatabaseTransaction),
}
#[async_trait::async_trait]
#[pagetop::async_trait]
impl ConnectionTrait for SchemaManagerConnection<'_> {
fn get_database_backend(&self) -> DbBackend {
match self {
@ -55,7 +55,7 @@ impl ConnectionTrait for SchemaManagerConnection<'_> {
}
}
#[async_trait::async_trait]
#[pagetop::async_trait]
impl TransactionTrait for SchemaManagerConnection<'_> {
async fn begin(&self) -> Result<DatabaseTransaction, DbErr> {
match self {

View file

@ -57,7 +57,7 @@ impl Migration {
}
/// Performing migrations on a database
#[async_trait::async_trait]
#[pagetop::async_trait]
pub trait MigratorTrait: Send {
/// Vector of migrations in time sequence
fn migrations() -> Vec<Box<dyn MigrationTrait>>;

View file

@ -15,7 +15,7 @@
//!
//! pub struct Migration;
//!
//! #[async_trait::async_trait]
//! #[pagetop::async_trait]
//! impl MigrationTrait for Migration {
//! async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
//! let table = table_auto(Users::Table)