use std::pin::Pin;
use nstreams_core::event::DeliveredEvent;
use nstreams_core::handler::NStreamsHandler;
use nstreams_core::publisher::EventPublisher;
use nstreams_core::worker::{QueueConsumer, StreamWorker};
use nstreams_core::namespace::StreamNamespace;
use nstreams_core::{DEFAULT_MAX_EVENT_BYTES, Result};
use nstreams_postgres::PostgresEventStore;
use nstreams_rabbit::{LapinWriteQueue, QueueConsumerConfig, RabbitReadStream};
use tracing::info;
#[derive(Clone)]
pub struct NStreams {
handler: NStreamsHandler<PostgresEventStore, RabbitReadStream>,
publisher: EventPublisher<LapinWriteQueue>,
write_queue: LapinWriteQueue,
store: PostgresEventStore,
read_stream: RabbitReadStream,
worker_config: WorkerConfig,
}
#[derive(Clone)]
struct WorkerConfig {
amqp_url: String,
prefetch: u16,
}
#[derive(Debug, Clone)]
pub struct NStreamsBuilder {
database_url: Option<String>,
amqp_url: Option<String>,
max_event_bytes: u64,
worker_prefetch: u16,
}
impl Default for NStreamsBuilder {
fn default() -> Self {
Self {
database_url: None,
amqp_url: None,
max_event_bytes: DEFAULT_MAX_EVENT_BYTES,
worker_prefetch: 100,
}
}
}
impl NStreamsBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn database_url(mut self, database_url: impl Into<String>) -> Self {
self.database_url = Some(database_url.into());
self
}
pub fn amqp_url(mut self, amqp_url: impl Into<String>) -> Self {
self.amqp_url = Some(amqp_url.into());
self
}
pub fn max_event_bytes(mut self, max_event_bytes: u64) -> Self {
self.max_event_bytes = max_event_bytes;
self
}
pub fn worker_prefetch(mut self, prefetch: u16) -> Self {
self.worker_prefetch = prefetch;
self
}
pub async fn build(self) -> Result<NStreams> {
let database_url = self
.database_url
.ok_or_else(|| nstreams_core::Error::Other("database_url is required".into()))?;
let amqp_url = self
.amqp_url
.ok_or_else(|| nstreams_core::Error::Other("amqp_url is required".into()))?;
let store = PostgresEventStore::connect(&database_url)
.await
.map_err(|error| nstreams_core::Error::Store(error.to_string()))?;
let write_queue = LapinWriteQueue::connect(&amqp_url)
.await
.map_err(|error| nstreams_core::Error::WriteQueue(error.to_string()))?;
let read_stream = RabbitReadStream::connect(&amqp_url)
.await
.map_err(|error| nstreams_core::Error::ReadStream(error.to_string()))?;
let handler = NStreamsHandler::with_max_event_bytes(
store.clone(),
read_stream.clone(),
self.max_event_bytes,
);
Ok(NStreams {
handler,
publisher: EventPublisher::new(write_queue.clone()),
write_queue,
store,
read_stream,
worker_config: WorkerConfig {
amqp_url,
prefetch: self.worker_prefetch,
},
})
}
}
impl NStreams {
pub fn builder() -> NStreamsBuilder {
NStreamsBuilder::new()
}
pub fn handler(&self) -> &NStreamsHandler<PostgresEventStore, RabbitReadStream> {
&self.handler
}
pub fn publisher(&self) -> &EventPublisher<LapinWriteQueue> {
&self.publisher
}
pub fn store(&self) -> &PostgresEventStore {
&self.store
}
pub fn read_stream(&self) -> &RabbitReadStream {
&self.read_stream
}
pub async fn spawn_worker(&self) -> Result<()> {
let worker = StreamWorker::new(
self.store.clone(),
self.write_queue.clone(),
self.read_stream.clone(),
self.handler.clone(),
);
let consumer = self
.write_queue
.create_consumer(QueueConsumerConfig {
amqp_url: self.worker_config.amqp_url.clone(),
prefetch: self.worker_config.prefetch,
})
.await
.map_err(|error| nstreams_core::Error::WriteQueue(error.to_string()))?;
tokio::spawn(async move {
if let Err(error) = worker.run(consumer).await {
tracing::error!(%error, "nstreams worker exited");
}
});
info!("nstreams worker spawned");
Ok(())
}
pub async fn run_worker<QC: QueueConsumer>(&self, consumer: QC) -> Result<()> {
let worker = StreamWorker::new(
self.store.clone(),
self.write_queue.clone(),
self.read_stream.clone(),
self.handler.clone(),
);
worker.run(consumer).await
}
pub async fn subscribe(
&self,
namespace: &StreamNamespace,
history: u64,
) -> Result<nstreams_core::Subscription> {
self.handler.subscribe(namespace, history).await
}
pub async fn subscribe_request(
&self,
request: nstreams_core::SubscribeRequest,
) -> Result<nstreams_core::Subscription> {
self.handler.subscribe_with_request(request).await
}
pub async fn publish(
&self,
namespace: &StreamNamespace,
payload: &[u8],
filter_value: Option<&str>,
) -> Result<()> {
self.publisher.publish(namespace, payload, filter_value).await
}
#[cfg(feature = "axum")]
pub fn axum_routes() -> axum::Router<Self> {
crate::axum::routes()
}
#[cfg(feature = "axum")]
pub fn axum_router(self) -> axum::Router<Self> {
crate::axum::router(self)
}
}
pub type EventStream =
Pin<Box<dyn futures::Stream<Item = Result<DeliveredEvent>> + Send>>;