#![expect(
clippy::manual_async_fn,
reason = "async trait impls return impl Future + Send to preserve public Send bounds"
)]
use std::future::Future;
use std::sync::Arc;
use crate::entity::Entity;
use crate::lock::{InMemoryLockManager, Lock, LockManager};
use crate::read_model::{
ReadModelAdapterCapabilities, ReadModelCommitOutcome, ReadModelError, ReadModelLoadGraph,
ReadModelLoadRequest, ReadModelQueryCapabilities, ReadModelWritePlan,
};
use crate::repository::{
CommitBatch, GetStream, InboxStore, ReadModelWritePlanStore, RelationalReadModelQueryStore,
RepositoryError, SnapshotStore, StreamIdentity, TransactionalCommit,
};
use crate::snapshot::SnapshotRecord;
#[derive(Debug, Clone, Copy)]
pub struct ReadOpts {
pub lock: bool,
}
impl Default for ReadOpts {
fn default() -> Self {
Self { lock: true }
}
}
impl ReadOpts {
pub fn no_lock() -> Self {
Self { lock: false }
}
}
pub struct QueuedRepository<R, L = InMemoryLockManager> {
inner: R,
lock_manager: Arc<L>,
}
impl<R: Clone, L> Clone for QueuedRepository<R, L> {
fn clone(&self) -> Self {
QueuedRepository {
inner: self.inner.clone(),
lock_manager: Arc::clone(&self.lock_manager),
}
}
}
impl<R> QueuedRepository<R> {
pub fn new(inner: R) -> Self {
QueuedRepository {
inner,
lock_manager: Arc::new(InMemoryLockManager::new()),
}
}
}
impl<R, L> QueuedRepository<R, L> {
pub fn inner(&self) -> &R {
&self.inner
}
pub fn lock_manager(&self) -> &L {
&self.lock_manager
}
}
impl<R, L: LockManager> QueuedRepository<R, L> {
pub fn with_lock_manager(inner: R, lock_manager: L) -> Self {
QueuedRepository {
inner,
lock_manager: Arc::new(lock_manager),
}
}
fn ensure_lock(&self, id: &str) -> Result<Arc<L::Lock>, RepositoryError> {
Ok(self.lock_manager.get_lock(id)?)
}
async fn lock_ids_in_order(&self, ids: &[&str]) -> Result<Vec<Arc<L::Lock>>, RepositoryError> {
let mut unique: Vec<&str> = ids.to_vec();
unique.sort_unstable();
unique.dedup();
let mut locks = Vec::with_capacity(unique.len());
for id in unique {
let lock = self.ensure_lock(id)?;
lock.lock().await?;
locks.push(lock);
}
Ok(locks)
}
}
impl<R, L> GetStream for QueuedRepository<R, L>
where
R: GetStream,
L: LockManager,
{
fn get_stream<'a>(
&'a self,
identity: &'a StreamIdentity,
) -> impl Future<Output = Result<Option<Entity>, RepositoryError>> + Send + 'a {
async move {
let lock = self.ensure_lock(&identity.storage_key())?;
lock.lock().await?;
self.inner.get_stream(identity).await
}
}
fn get_streams<'a>(
&'a self,
identities: &'a [StreamIdentity],
) -> impl Future<Output = Result<Vec<Entity>, RepositoryError>> + Send + 'a {
async move {
let keys: Vec<String> = identities.iter().map(StreamIdentity::storage_key).collect();
let key_refs: Vec<&str> = keys.iter().map(String::as_str).collect();
let _locks = self.lock_ids_in_order(&key_refs).await?;
self.inner.get_streams(identities).await
}
}
fn get_stream_tail<'a>(
&'a self,
identity: &'a StreamIdentity,
after_version: u64,
) -> impl Future<Output = Result<Option<Entity>, RepositoryError>> + Send + 'a {
async move {
let lock = self.ensure_lock(&identity.storage_key())?;
lock.lock().await?;
self.inner.get_stream_tail(identity, after_version).await
}
}
}
impl<R, L> TransactionalCommit for QueuedRepository<R, L>
where
R: TransactionalCommit,
L: LockManager,
{
fn commit_batch<'a>(
&'a self,
batch: CommitBatch<'a>,
) -> impl Future<Output = Result<(), RepositoryError>> + Send + 'a {
async move {
let mut locks = Vec::with_capacity(batch.streams.len());
for stream in &batch.streams {
locks.push(self.ensure_lock(&stream.identity.storage_key())?);
}
let result = self.inner.commit_batch(batch).await;
if result.is_ok() {
for lock in locks {
let _ = lock.unlock().await;
}
}
result
}
}
}
impl<R, L> SnapshotStore for QueuedRepository<R, L>
where
R: SnapshotStore,
L: LockManager,
{
fn get_snapshot<'a>(
&'a self,
identity: &'a StreamIdentity,
) -> impl Future<Output = Result<Option<SnapshotRecord>, RepositoryError>> + Send + 'a {
self.inner.get_snapshot(identity)
}
fn save_snapshot<'a>(
&'a self,
identity: &'a StreamIdentity,
record: SnapshotRecord,
) -> impl Future<Output = Result<(), RepositoryError>> + Send + 'a {
self.inner.save_snapshot(identity, record)
}
fn delete_snapshot<'a>(
&'a self,
identity: &'a StreamIdentity,
) -> impl Future<Output = Result<bool, RepositoryError>> + Send + 'a {
self.inner.delete_snapshot(identity)
}
}
impl<R, L> ReadModelWritePlanStore for QueuedRepository<R, L>
where
R: ReadModelWritePlanStore,
L: LockManager,
{
fn read_model_capabilities(&self) -> ReadModelAdapterCapabilities {
self.inner.read_model_capabilities()
}
fn commit_write_plan(
&self,
plan: ReadModelWritePlan,
) -> impl Future<Output = Result<ReadModelCommitOutcome, ReadModelError>> + Send + '_ {
self.inner.commit_write_plan(plan)
}
}
impl<R, L> RelationalReadModelQueryStore for QueuedRepository<R, L>
where
R: RelationalReadModelQueryStore,
L: LockManager,
{
fn read_model_query_capabilities(&self) -> ReadModelQueryCapabilities {
self.inner.read_model_query_capabilities()
}
fn load_graph(
&self,
request: ReadModelLoadRequest,
) -> impl Future<Output = Result<ReadModelLoadGraph, ReadModelError>> + Send + '_ {
self.inner.load_graph(request)
}
}
impl<R, L> InboxStore for QueuedRepository<R, L>
where
R: InboxStore,
L: LockManager,
{
fn inbox_contains<'a>(
&'a self,
consumer: &'a str,
message_id: &'a str,
) -> impl Future<Output = Result<bool, RepositoryError>> + Send + 'a {
self.inner.inbox_contains(consumer, message_id)
}
fn purge_inbox_older_than(
&self,
age: std::time::Duration,
) -> impl Future<Output = Result<u64, RepositoryError>> + Send {
self.inner.purge_inbox_older_than(age)
}
}
pub trait GetWithOpts {
fn get_stream_with<'a>(
&'a self,
identity: &'a StreamIdentity,
opts: ReadOpts,
) -> impl Future<Output = Result<Option<Entity>, RepositoryError>> + Send + 'a;
}
pub trait GetAllWithOpts {
fn get_streams_with<'a>(
&'a self,
identities: &'a [StreamIdentity],
opts: ReadOpts,
) -> impl Future<Output = Result<Vec<Entity>, RepositoryError>> + Send + 'a;
}
impl<R, L> GetWithOpts for QueuedRepository<R, L>
where
R: GetStream,
L: LockManager,
{
fn get_stream_with<'a>(
&'a self,
identity: &'a StreamIdentity,
opts: ReadOpts,
) -> impl Future<Output = Result<Option<Entity>, RepositoryError>> + Send + 'a {
async move {
if opts.lock {
let lock = self.ensure_lock(&identity.storage_key())?;
lock.lock().await?;
}
self.inner.get_stream(identity).await
}
}
}
impl<R, L> GetAllWithOpts for QueuedRepository<R, L>
where
R: GetStream,
L: LockManager,
{
fn get_streams_with<'a>(
&'a self,
identities: &'a [StreamIdentity],
opts: ReadOpts,
) -> impl Future<Output = Result<Vec<Entity>, RepositoryError>> + Send + 'a {
async move {
if opts.lock {
let keys: Vec<String> =
identities.iter().map(StreamIdentity::storage_key).collect();
let key_refs: Vec<&str> = keys.iter().map(String::as_str).collect();
let _locks = self.lock_ids_in_order(&key_refs).await?;
}
self.inner.get_streams(identities).await
}
}
}
pub trait UnlockableRepository: Send + Sync {
fn unlock<'a>(
&'a self,
identity: &'a StreamIdentity,
) -> impl Future<Output = Result<(), RepositoryError>> + Send + 'a;
fn abort<'a>(
&'a self,
identity: &'a StreamIdentity,
) -> impl Future<Output = Result<(), RepositoryError>> + Send + 'a {
self.unlock(identity)
}
}
impl<R, L: LockManager> UnlockableRepository for QueuedRepository<R, L>
where
R: Send + Sync,
{
fn unlock<'a>(
&'a self,
identity: &'a StreamIdentity,
) -> impl Future<Output = Result<(), RepositoryError>> + Send + 'a {
async move {
self.ensure_lock(&identity.storage_key())?.unlock().await?;
Ok(())
}
}
}
pub trait Queueable: Sized {
fn queued(self) -> QueuedRepository<Self, InMemoryLockManager> {
QueuedRepository::with_lock_manager(self, InMemoryLockManager::new())
}
fn queued_with<L: LockManager>(self, lock_manager: L) -> QueuedRepository<Self, L> {
QueuedRepository::with_lock_manager(self, lock_manager)
}
}
impl<T> Queueable for T {}