alkstore 0.1.0

The alkstore contract crate: one reactive store trait surface — notify, streams, durable queues, TTL locks, outbox, scheduler — implemented by the alkstore-sqlite/-postgres/-mem engine crates; no driver dependencies
Documentation
//! The transactional seam's caller-held handle (ADR-007, ADR-008 §2,
//! ADR-014, ADR-015 §2, ADR-021 §1/§4).
//!
//! The `*_tx` methods make side effects commit-atomic with a business
//! write: every op lands in *the caller's transaction*. The trait is
//! core-owned — engines implement it for their concrete handle; no
//! downcast, no enum, no generic method (object safety, ADR-008 §2).

use serde_json::Value;

use crate::future::BoxedFuture;
use crate::opts::EnqueueOpts;
use crate::{Job, Result, StreamEvent};

/// The caller-held transaction handle.
///
/// Engine shape notes, not contract: the Postgres handle holds the
/// pooled client directly (straight `.await`s); the SQLite handle is a
/// writer-slot lease whose ops round-trip `spawn_blocking` (ADR-003,
/// ADR-004).
///
/// # Entry-point validation obligation
///
/// Every name-bearing method (`enqueue_tx`, `publish_tx`,
/// `publish_with_key_tx`, `notify_tx`, `save_offset_tx`,
/// `get_job_tx`/`get_offset_tx`/`read_since_tx`/
/// `read_from_consumer_tx`, `outbox_enqueue_tx`) validates its names
/// before any engine round trip — identically on the auto-commit and
/// tx paths: non-empty (else `InvalidName`), reserved-prefix rejected
/// for shared-namespace kinds (else `ReservedName`). Validation must
/// not be bypassable by the commit-atomic path (ADR-008 §4).
///
/// # Drop = rollback
///
/// A handle dropped without `commit` rolls back (ADR-021 §4): the
/// no-ghosts property holds through RAII paths, not only the explicit
/// ones. There is no `rollback` method — dropping *is* the rollback;
/// a caller wanting an explicit rollback drops the handle.
pub trait TxHandle: Send {
    /// Enqueue a job into `queue`, commit-atomic with the caller's
    /// business write. Returns the job id.
    ///
    /// No-handle-open shape: the row stamps the engine's derived
    /// default `QueueOpts` (300/3/5/none, ADR-020 §3) — a `TxHandle`
    /// carries no queue opts. Validates `queue` as a shared-namespace
    /// name (`InvalidName`/`ReservedName`) before any round trip.
    fn enqueue_tx<'a>(
        &'a mut self,
        queue: &str,
        opts: EnqueueOpts,
        payload: Value,
    ) -> BoxedFuture<'a, Result<i64>>;

    /// Publish an event to `stream`, commit-atomic. Returns the
    /// assigned offset. Validates `stream` as a shared-namespace name.
    fn publish_tx<'a>(&'a mut self, stream: &str, payload: Value) -> BoxedFuture<'a, Result<i64>>;

    /// Publish a keyed event to `stream` — [`TxHandle::publish_tx`]'s
    /// keyed twin; commit-atomic keyed publishes are the point
    /// (ADR-015 §2). The key is carried metadata; a present key must
    /// be non-empty (`InvalidName`, ADR-008 §5 annotated). Returns the
    /// assigned offset.
    fn publish_with_key_tx<'a>(
        &'a mut self,
        stream: &str,
        key: Option<String>,
        payload: Value,
    ) -> BoxedFuture<'a, Result<i64>>;

    /// Deliver a fire-and-forget notify, commit-atomic — delivered
    /// only at commit; rollback drops it (ADR-007). Validates
    /// `channel`; may fail with `PayloadTooLarge` pg-side
    /// (client-side check before the round trip, the contract-wide
    /// matchable engine asymmetry).
    fn notify_tx<'a>(&'a mut self, channel: &str, payload: Value) -> BoxedFuture<'a, Result<()>>;

    /// Save a stream consumer's checkpoint, commit-atomic. Saves are
    /// monotone — a save below the stored checkpoint is a silent
    /// no-op (ADR-019 §6); the `Err` arm carries `Database` only
    /// (ADR-021 §5). Validates `stream`; `consumer` is a
    /// consumer-local identifier (non-empty only).
    fn save_offset_tx<'a>(
        &'a mut self,
        stream: &str,
        consumer: &str,
        offset: i64,
    ) -> BoxedFuture<'a, Result<()>>;

    /// Read a job row inside the caller's transaction — reads the
    /// transaction's own writes (read-your-own-writes, ADR-021 §1).
    /// Sees dead rows; a pure read, no ops ride it. Validates `queue`.
    fn get_job_tx<'a>(
        &'a mut self,
        queue: &str,
        job_id: i64,
    ) -> BoxedFuture<'a, Result<Option<Job>>>;

    /// Read a consumer's stored checkpoint inside the caller's
    /// transaction; absent consumer = 0 (ADR-019 §1). Pure read.
    /// Validates `stream`; `consumer` is consumer-local (non-empty
    /// only).
    fn get_offset_tx<'a>(
        &'a mut self,
        stream: &str,
        consumer: &str,
    ) -> BoxedFuture<'a, Result<i64>>;

    /// Read stream events from `offset`, inside the caller's
    /// transaction (`offset ASC`, global FIFO — ADR-015 §4). Pure
    /// read. Validates `stream`. `limit` is an extent argument
    /// (ADR-023 §2): `limit <= 0` reads nothing — the empty `Vec`.
    fn read_since_tx<'a>(
        &'a mut self,
        stream: &str,
        offset: i64,
        limit: i64,
    ) -> BoxedFuture<'a, Result<Vec<StreamEvent>>>;

    /// Read stream events from a consumer's stored checkpoint, inside
    /// the caller's transaction. Pure read. Validates `stream`;
    /// `consumer` is consumer-local (non-empty only). `limit` is an
    /// extent argument (ADR-023 §2): `limit <= 0` reads nothing — the
    /// empty `Vec`.
    fn read_from_consumer_tx<'a>(
        &'a mut self,
        stream: &str,
        consumer: &str,
        limit: i64,
    ) -> BoxedFuture<'a, Result<Vec<StreamEvent>>>;

    /// Enqueue into an outbox's derived backing queue, commit-atomic
    /// (ADR-014). Takes the *outbox* name — validated like
    /// `store.outbox(name)` — and derives the queue engine-side; the
    /// derived name (reserved-prefixed) is unreachable by
    /// [`TxHandle::enqueue_tx`] by design, so this is the only
    /// transactional path into the backing queue. Stamps the outbox's
    /// derived `QueueOpts` set (60/5/5).
    fn outbox_enqueue_tx<'a>(
        &'a mut self,
        outbox: &str,
        opts: EnqueueOpts,
        payload: Value,
    ) -> BoxedFuture<'a, Result<i64>>;

    /// Commit the transaction, consuming the handle. The alternative
    /// disposition is drop = rollback. One-shot: callers re-`begin_tx`
    /// for the next transaction.
    fn commit(self: Box<Self>) -> BoxedFuture<'static, Result<()>>;
}