mire 0.2.4

A small, generic PostgreSQL event-sourcing library: append-only event streams, aggregates with optimistic concurrency, and subscription-based projections (requires tokio + sqlx)
Documentation
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize, de::DeserializeOwned};
use uuid::Uuid;

/// The serializable shape of a domain event. Every variant must report
/// a stable `event_type` string — this is what mire stores in the
/// `event_type` column and uses as the schema discriminator for the
/// event log.
///
/// **`event_type` strings are load-bearing.** Once an event has been
/// persisted under a given string, changing that string orphans the
/// historical event (it no longer deserializes into the new shape).
/// Treat them as part of your wire format.
///
/// ## Implementing via the derive macro (recommended)
///
/// Available behind the default-on `derive` feature. One line on the
/// enum sets the entity prefix; variants get CamelCase → kebab-case
/// translation automatically:
///
/// ```rust,ignore
/// use mire::EventData;
/// use serde::{Serialize, Deserialize};
///
/// #[derive(Debug, Clone, Serialize, Deserialize, EventData)]
/// #[serde(tag = "type")]
/// #[mire(entity = "account")]
/// enum AccountEvent {
///     Opened { owner: String },           // → "account.opened"
///     MoneyDeposited { amount: i64 },     // → "account.money-deposited"
///
///     #[mire(rename = "closed-v2")]       // pin a specific variant
///     Closed,                             // → "account.closed-v2"
/// }
/// ```
///
/// **Stability rule:** the `entity` attribute is fixed at the enum
/// level (no per-variant override). A variant rename in Rust *will*
/// change the persisted event_type unless you pin it with
/// `#[mire(rename = "...")]`. If you're renaming variants on a stream
/// that's already in production, pin first or you'll orphan history.
///
/// ## Implementing by hand
///
/// If you turn off the `derive` feature, hand-write the impl:
///
/// ```rust,ignore
/// impl EventData for AccountEvent {
///     fn event_type(&self) -> &'static str {
///         match self {
///             AccountEvent::Opened { .. } => "account.opened",
///             // ...
///         }
///     }
/// }
/// ```
pub trait EventData: Serialize + DeserializeOwned + Send + Sync + 'static {
    fn event_type(&self) -> &'static str;
}

/// An [`EventData`] type whose `event_type` discriminator string is known at
/// compile time. Enables references like `await_event::<E>()` that don't have
/// an instance to call `event_type(&self)` on.
///
/// Implemented automatically by `#[derive(EventData)]` for struct-shaped
/// events. Enum-shaped events (multi-variant) cannot have a single static
/// const and don't implement this trait.
///
/// ```rust,ignore
/// use mire::{EventData, EventTypeStatic};
/// use serde::{Serialize, Deserialize};
///
/// #[derive(Debug, Clone, Serialize, Deserialize, EventData)]
/// #[mire(event_type = "flight.held")]
/// struct FlightHeld { hold_id: String }
///
/// assert_eq!(<FlightHeld as EventTypeStatic>::EVENT_TYPE, "flight.held");
/// ```
pub trait EventTypeStatic: EventData {
    const EVENT_TYPE: &'static str;
}

// =====================================================================
// IntoResponse — what a saga request handler can return.
//
// Lives in `mire` (not mire-sagas) so the derive macro can emit
// per-event-type impls without depending on mire-sagas, AND so the
// `Result<Ok, Err>` impl can coexist (Rust's orphan rule requires the
// trait or the type to be local). There is no blanket `impl<T:
// EventTypeStatic> IntoResponse for T` — that would coherence-conflict
// with the `Result` impl. Per-type impls come from the derive.
// =====================================================================

/// A value that can be turned into a response event.
///
/// Implemented automatically by `#[derive(EventData)]` on struct events,
/// and by the framework for `Result<Ok, Err>` where both branches are
/// `EventTypeStatic` events (Err must also implement `Display` for the
/// reason string surfaced to compensation handlers).
pub trait IntoResponse: Send {
    fn into_response(self) -> ResponseValue;
}

/// What [`IntoResponse::into_response`] returns. The saga runner uses the
/// fields to deliver the response back to the awaiting step (or fail it
/// when `is_error` is true).
#[derive(Debug, Clone)]
pub struct ResponseValue {
    pub event_type: &'static str,
    pub payload: serde_json::Value,
    /// `true` if this is the Err branch of a `Result` return — the
    /// runner surfaces it as `StepError::Failed` on the awaiting step.
    pub is_error: bool,
    /// `Display` of the Err value, used as the failure reason.
    pub error_reason: Option<String>,
}

impl<Ok, Err> IntoResponse for Result<Ok, Err>
where
    Ok: EventTypeStatic + Serialize + Send,
    Err: EventTypeStatic + Serialize + Send + std::fmt::Display,
{
    fn into_response(self) -> ResponseValue {
        match self {
            Result::Ok(ok) => ResponseValue {
                event_type: <Ok as EventTypeStatic>::EVENT_TYPE,
                payload: serde_json::to_value(&ok).expect("ok serialises"),
                is_error: false,
                error_reason: None,
            },
            Result::Err(err) => {
                let reason = format!("{err}");
                ResponseValue {
                    event_type: <Err as EventTypeStatic>::EVENT_TYPE,
                    payload: serde_json::to_value(&err).expect("err serialises"),
                    is_error: true,
                    error_reason: Some(reason),
                }
            }
        }
    }
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct EventMetadata {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub correlation_id: Option<Uuid>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub causation_id: Option<Uuid>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub actor: Option<String>,
}

#[derive(Debug, Clone)]
pub struct Event<E: EventData> {
    pub event: E,
    pub metadata: serde_json::Value,
}

impl<E: EventData> Event<E> {
    pub fn new(event: E) -> Self {
        Self {
            event,
            metadata: serde_json::json!({}),
        }
    }

    pub fn with_metadata(mut self, metadata: serde_json::Value) -> Self {
        self.metadata = metadata;
        self
    }
}

#[derive(Debug, Clone)]
pub struct RecordedEvent {
    pub global_position: i64,
    pub stream_id: String,
    pub stream_version: i64,
    pub event_type: String,
    pub data: serde_json::Value,
    pub metadata: serde_json::Value,
    /// The Postgres transaction (xid8) that appended this event. Used together
    /// with `global_position` as the subscription cursor; see the schema notes.
    pub transaction_id: u64,
    pub created_at: DateTime<Utc>,
}