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}