Skip to main content

turnframe_store/
lib.rs

1//! `turnframe-store`: the persistence contract of Turnframe, plus a
2//! deterministic in-memory implementation and an executable conformance suite.
3//!
4//! It defines *what* must be durable and *with which rules*, as seven
5//! object-safe traits, and nothing about *where*: no driver, no SQL. The
6//! PostgreSQL adapter is one implementation and is optional. The rules an
7//! implementation must honour — total account scoping, a closed error surface,
8//! compare-and-swap instead of blind overwrite, immutability, specified
9//! ordering — and the all-or-nothing commit model are in
10//! [`docs/persistence.md`](https://github.com/turnframe-rs/turnframe/blob/main/docs/persistence.md).
11//! [`conformance::run_all`] proves an implementation right without reading its
12//! code, and [`MemoryStores`] is the reference one.
13//!
14//! Six traits are split into a `…Reader` and a `…Writer` half, aggregated by
15//! the trait carrying the historical name, so a caller that must not write can
16//! be handed [`ReadOnlyStores`] and the compiler keeps it that way.
17//! [`Stores`] bundles one implementation of each.
18//!
19//! | Trait | Module | What it owns |
20//! |---|---|---|
21//! | [`ConversationStore`](conversation::ConversationStore) | [`conversation`] | conversations, user turns, assistant turns *as returned*, the crash-recovery phase marker |
22//! | [`InteractionStore`](interaction::InteractionStore) | [`interaction`] | persisted cards: payloads, the one-blocking-per-case slot, compare-and-swap resolution, expiry |
23//! | [`CommandJournal`](journal::CommandJournal) | [`journal`] | idempotency admission and the persisted outcome of every command |
24//! | [`EventJournal`](events::EventJournal) | [`events`] | the append-only claim ledger, paged exactly once, erasable only by redaction |
25//! | [`OutboxStore`](outbox::OutboxStore) | [`outbox`] | external side effects awaiting dispatch, with claim/reschedule semantics |
26//! | [`ReplayStore`](replay::ReplayStore) | [`replay`] | one replay record per turn, upserted as the turn advances |
27//! | [`CommitStore`](commit::CommitStore) | [`commit`] | the all-or-nothing write of everything one commit produces |
28//!
29//! # Example
30//!
31//! ```rust
32//! use turnframe_core::ids::{AccountId, ConversationId};
33//! use turnframe_store::prelude::*;
34//!
35//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
36//! # tokio::runtime::Runtime::new()?.block_on(async {
37//! let stores = Stores::in_memory();
38//! let account = AccountId::from("aurora");
39//! let conversation = ConversationId::new();
40//! let now = chrono::Utc::now();
41//!
42//! stores
43//!     .conversations()
44//!     .create_conversation(ConversationRecord::new(
45//!         conversation,
46//!         account.clone(),
47//!         now,
48//!     ))
49//!     .await?;
50//!
51//! let loaded = stores
52//!     .conversations()
53//!     .load_conversation(&account, &conversation)
54//!     .await?;
55//! assert_eq!(loaded.account_id, account);
56//!
57//! // Another tenant cannot tell it apart from one that never existed.
58//! let other = AccountId::from("other");
59//! assert_eq!(
60//!     stores
61//!         .conversations()
62//!         .load_conversation(&other, &conversation)
63//!         .await,
64//!     Err(StoreError::NotFound)
65//! );
66//! # Ok::<(), StoreError>(())
67//! # })?;
68//! # Ok(())
69//! # }
70//! ```
71#![forbid(unsafe_code)]
72#![cfg_attr(test, allow(clippy::unwrap_used, clippy::expect_used, clippy::panic))]
73
74/// The crate README, compiled as a doc-test so its examples cannot rot.
75#[cfg(doctest)]
76#[doc = include_str!("../README.md")]
77mod readme {}
78
79pub mod commit;
80pub mod conformance;
81pub mod conversation;
82pub mod error;
83pub mod events;
84pub mod interaction;
85pub mod journal;
86pub mod memory;
87pub mod outbox;
88pub mod replay;
89pub mod stores;
90
91// The handful of names an application says out loud, at the crate root, so a
92// caller writes `turnframe_store::Stores` rather than `stores::Stores` through
93// a module whose name repeats the type's. The modules stay public for
94// everything else.
95pub use crate::error::{StoreError, StoreResult};
96pub use crate::memory::{Clock, ManualClock, MemoryStores, SystemClock};
97pub use crate::stores::{ReadOnlyStores, ReadOnlyStoresBuilder, Stores, StoresBuilder};
98
99/// The items an application needs to hold and use a set of stores.
100///
101/// Adapter authors additionally want the record and outcome types from the
102/// individual modules; this prelude carries the traits, the aggregate, the
103/// error surface and the in-memory implementation.
104pub mod prelude {
105    pub use crate::commit::{CommitBundle, CommitReceipt, CommitStore};
106    pub use crate::conversation::{
107        ConversationReader, ConversationRecord, ConversationStore, ConversationWriter,
108        RecoveryScope, StoredTurn, StoredUserTurn, TurnPhaseMarker,
109    };
110    pub use crate::error::{StoreError, StoreResult};
111    pub use crate::events::{
112        EventBatch, EventCursor, EventJournal, EventJournalReader, EventJournalWriter, EventPage,
113        LedgerReceiptGroup, StoredEvent, group_for_receipts,
114    };
115    pub use crate::interaction::{
116        InteractionReader, InteractionRecord, InteractionStore, InteractionWriter,
117        InvalidationReason, ResolutionOutcome,
118    };
119    pub use crate::journal::{
120        CommandJournal, CommandJournalEntry, CommandJournalReader, CommandJournalStatus,
121        CommandJournalWriter, JournalAdmission, JournalOutcome,
122    };
123    pub use crate::memory::{Clock, ManualClock, MemoryStores, SystemClock};
124    pub use crate::outbox::{OutboxReader, OutboxRecord, OutboxStore, OutboxWriter};
125    pub use crate::replay::{ReplayReader, ReplayStore, ReplayWriter};
126    pub use crate::stores::{ReadOnlyStores, Stores};
127}