dovecote-sqlx-postgres 0.2.2

PostgreSQL SQLx adapter for Dovecote
Documentation

dovecote

A holder. A recipient.

— Ursula K. Le Guin, “The Carrier Bag Theory of Fiction” (1986)

dovecote is a transactional outbox for Rust applications. It writes a validated CloudEvents-compatible event in the same database transaction as application state, then keeps its delivery state for an application-owned worker. In schema version 2, durable event identity is scoped to the tenant handle as (tenant_id, source, id).

Claims are leased, and only the matching claim token can change a delivery. Delivery is at least once. For one tenant, consumers can deduplicate on (source, id); a shared destination must include the tenant routing domain. Your application runs the worker, chooses the transport, and applies migrations. Dovecote promises neither FIFO nor exactly-once delivery.

[!WARNING] Dovecote is pre-release (0.2.x). Expect the Rust API, durable schema, and migration tooling to change before v1. Backend support is version-specific; see the support matrix. Existing Keepsake deployments on MariaDB use the documented maintenance-window migration route.

A transaction

Build the event, then enqueue it in the transaction that owns the application change:

use dovecote::{ContentType, EventData, EventId, EventSource, EventType, NewEvent, StreamName, TenantId};
use dovecote_sqlx_postgres::PostgresDovecote;
use sqlx::PgPool;

async fn record(pool: PgPool) -> Result<(), Box<dyn std::error::Error>> {
    let event = NewEvent::builder(
        StreamName::new("audit")?,
        EventId::new("evt-123")?,
        EventSource::new("https://example.test/audit")?,
        EventType::new("com.example.audit.recorded")?,
    )
    .datacontenttype(ContentType::new("application/json")?)
    .data(EventData::json(br#"{"ok":true}"#.to_vec())?)
    .build()?;

    let adapter = PostgresDovecote::new(pool.clone())
        .for_tenant(TenantId::new("tenant-a")?);
    let mut transaction = pool.begin().await?;
    sqlx::query("INSERT INTO application_audit_log (event_id) VALUES ($1)")
        .bind("evt-123")
        .execute(&mut *transaction)
        .await?;
    adapter.enqueue(&mut transaction, event).await?;
    transaction.commit().await?;
    Ok(())
}

The commit makes the application change and event visible together. Publication happens later, through a worker owned by the application.

The core dovecote crate is synchronous and has no runtime or SQLx dependency. Its SQLx adapters support PostgreSQL, MySQL/MariaDB, and SQLite without hiding their different transaction, locking, clock, or migration behaviour.

Documentation

Development

The project uses the tools pinned in .mise.toml:

mise install
mise run check

Licensed under MIT OR Apache-2.0.