p2panda_store/lib.rs
1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3#![cfg_attr(doctest, doc=include_str!("../README.md"))]
4
5//! Interfaces and implementations of persistence layers for p2panda data types and application
6//! states.
7//!
8//! p2panda follows a strict separation of read- and write-only database interfaces to allow
9//! designing efficient and fail-safe [atomic
10//! transactions](https://youtu.be/5ZjhNTM8XU8?feature=shared&t=420) throughout the stack.
11//!
12//! ## Read queries
13//!
14//! `p2panda-store` currently offers all read-only trait interfaces for commonly used p2panda core
15//! data-types and flows (for example "get the latest operation for this log"). These persistence
16//! and query APIs are utilised by higher-level components of the p2panda stack, such as
17//! `p2panda-sync` and `p2panda-stream`.
18//!
19//! For detailed information concerning the `Operation` type, please consult the documentation for
20//! the `p2panda-core` crate.
21//!
22//! Logs in the context of `p2panda-store` are simply a collection of operations grouped under a
23//! common identifier. The precise type for which `LogId` is implemented is left up to the
24//! developer to decide according to their needs. With this in mind, the traits and implementations
25//! provided by `p2panda-store` do not perform any validation of log integrity. Developers using
26//! this crate must take steps to ensure their log design is fit for purpose and that all
27//! operations have been thoroughly validated before being persisted.
28//!
29//! Also note that the traits provided here are not intended to offer generic storage solutions for
30//! non-p2panda data types, nor are they intended to solve all application-layer storage concerns.
31//!
32//! ## Write transactions
33//!
34//! Multiple writes to a database should be grouped into one single, atomic transaction when they
35//! need to strictly _all_ occur or _none_ occur. This is crucial to guarantee a crash-resiliant
36//! p2p application, as any form of failure and disruption (user moving mobile app into the
37//! background, etc.) might otherwise result in invalid database state which is hard to recover
38//! from.
39//!
40//! `p2panda-store` offers `WritableStore`, `Transaction` and `WriteToStore` traits to accommodate
41//! for exactly such a system and all p2panda implementations strictly follow the same pattern.
42//!
43//! ```rust
44//! # use p2panda_store::{Transaction, WritableStore, WriteToStore};
45//! #
46//! # pub struct SqliteTransaction;
47//! #
48//! # impl Transaction for SqliteTransaction {
49//! # type Error = ();
50//! #
51//! # fn commit(self) -> impl Future<Output = Result<(), Self::Error>> {
52//! # async { todo!() }
53//! # }
54//! #
55//! # fn rollback(self) -> impl Future<Output = Result<(), Self::Error>> {
56//! # async { todo!() }
57//! # }
58//! # }
59//! #
60//! # pub struct Sqlite;
61//! #
62//! # impl Sqlite {
63//! # pub fn new() -> Self {
64//! # Self
65//! # }
66//! # }
67//! #
68//! # impl WritableStore for Sqlite {
69//! # type Error = ();
70//! #
71//! # type Transaction<'c> = SqliteTransaction;
72//! #
73//! # fn begin<'c>(
74//! # &mut self,
75//! # ) -> impl Future<Output = Result<Self::Transaction<'c>, Self::Error>> {
76//! # async { todo!() }
77//! # }
78//! # }
79//! #
80//! # #[derive(Clone)]
81//! # pub struct User(String);
82//! #
83//! # impl User {
84//! # pub fn new(name: &str) -> Self {
85//! # Self(name.to_string())
86//! # }
87//! # }
88//! #
89//! # impl WriteToStore<Sqlite> for User {
90//! # async fn write(
91//! # &self,
92//! # tx: &mut <Sqlite as WritableStore>::Transaction<'_>,
93//! # ) -> Result<(), ()> {
94//! # Ok(())
95//! # }
96//! # }
97//! #
98//! # pub struct Event {
99//! # title: String,
100//! # attendances: Vec<User>,
101//! # }
102//! #
103//! # impl Event {
104//! # pub fn new(title: &str) -> Self {
105//! # Self {
106//! # title: title.to_string(),
107//! # attendances: vec![],
108//! # }
109//! # }
110//! #
111//! # pub fn register_attendance(&mut self, user: &User) {
112//! # self.attendances.push(user.clone());
113//! # }
114//! # }
115//! #
116//! # impl WriteToStore<Sqlite> for Event {
117//! # async fn write(
118//! # &self,
119//! # tx: &mut <Sqlite as WritableStore>::Transaction<'_>,
120//! # ) -> Result<(), ()> {
121//! # Ok(())
122//! # }
123//! # }
124//! #
125//! # async fn run() -> Result<(), ()> {
126//! // Initialise a concrete store implementation, for example for SQLite. This implementation
127//! // needs to implement the `WritableStore` trait, providing it's native transaction interface.
128//!
129//! let mut store = Sqlite::new();
130//!
131//! // Establish state, do things with it. `User` and `Event` both implement `WriteToStore` for the
132//! // concrete store type `Sqlite`.
133//!
134//! let user = User::new("casey");
135//! let mut event = Event::new("Ants Research Meetup");
136//! event.register_attendance(&user);
137//!
138//! // Persist state in database in one single, atomic transaction.
139//!
140//! let mut tx = store.begin().await?;
141//!
142//! user.write(&mut tx).await?;
143//! event.write(&mut tx).await?;
144//!
145//! tx.commit().await?;
146//!
147//! # Ok(())
148//! # }
149//! ```
150//!
151//! It is recommended for application developers to re-use similar transaction patterns to leverage
152//! the same crash-resiliance guarantees for their application-layer state and persistance
153//! handling.
154//!
155//! ## Store implementations
156//!
157//! Read queries and atomic write transactions are implemented for all p2panda-stack related data
158//! types for concrete databases: In-Memory and SQLite.
159//!
160//! An in-memory storage solution is provided in the form of a `MemoryStore` which implements both
161//! `OperationStore` and `LogStore`. The store is gated by the `memory` feature flag and is enabled
162//! by default.
163//!
164//! A SQLite storage solution is provided in the form of a `SqliteStore` which implements both
165//! `OperationStore` and `LogStore`. The store is gated by the `sqlite` feature flag and is
166//! disabled by default.
167#[cfg(feature = "memory")]
168pub mod memory;
169pub mod operations;
170#[cfg(feature = "sqlite")]
171pub mod sqlite;
172mod transactions;
173
174#[cfg(feature = "memory")]
175pub use memory::MemoryStore;
176pub use operations::{LogId, LogStore, OperationStore};
177#[cfg(feature = "sqlite")]
178pub use sqlite::store::{SqliteStore, SqliteStoreError};
179pub use transactions::{Transaction, WritableStore, WriteToStore};