Skip to main content

klieo_bus_memory/
lib.rs

1#![deny(missing_docs)]
2#![deny(rust_2018_idioms)]
3#![deny(rustdoc::broken_intra_doc_links)]
4
5//! In-process implementations of `klieo-core`'s bus traits
6//! ([`Pubsub`], [`RequestReply`],
7//! [`KvStore`], [`JobQueue`]).
8//!
9//! Use this crate for tests, single-binary applications, and local dev.
10//! For production multi-process deployment use `klieo-bus-nats`.
11//!
12//! # Limitations
13//!
14//! - **No durability across process restart.** Pubsub messages and
15//!   queued jobs live in process memory.
16//! - **Pubsub `AckHandle` is a no-op.** In-process pubsub has no
17//!   redelivery semantics; the trait surface is preserved for
18//!   consistency but `ack` / `nak` / `term` succeed without effect.
19//! - **JobQueue lease-expiry is lazy** — checked on the next `claim()`
20//!   call, not via a background task. Sufficient for tests and
21//!   single-binary apps where claim cadence is high.
22//!
23//! # Quickstart
24//!
25//! ```no_run
26//! use klieo_bus_memory::MemoryBus;
27//! let bus = MemoryBus::new();
28//! // bus.pubsub, bus.kv, bus.request_reply, bus.jobs are
29//! // Arc<dyn …> ready to drop into AgentContext.
30//! # let _ = bus;
31//! ```
32
33mod jobs;
34mod kv;
35mod pubsub;
36mod request_reply;
37
38pub use jobs::MemoryJobQueue;
39pub use kv::MemoryKv;
40pub use pubsub::MemoryPubsub;
41pub use request_reply::MemoryRequestReply;
42
43use klieo_core::{BusHandles, JobQueue, KvStore, Pubsub, RequestReply};
44use std::sync::Arc;
45
46/// Wired-together in-process bus. Build once, share across agents.
47pub struct MemoryBus {
48    /// Pub/sub.
49    pub pubsub: Arc<dyn Pubsub>,
50    /// Synchronous request/reply, riding on `pubsub`.
51    pub request_reply: Arc<dyn RequestReply>,
52    /// KV store.
53    pub kv: Arc<dyn KvStore>,
54    /// Durable job queue with DLQ + dedup-CAS, riding on `pubsub` + `kv`.
55    pub jobs: Arc<dyn JobQueue>,
56}
57
58impl MemoryBus {
59    /// Build a fresh in-process bus.
60    pub fn new() -> Self {
61        let pubsub_concrete = Arc::new(MemoryPubsub::new());
62        let kv_concrete = Arc::new(MemoryKv::new());
63        let rr_concrete = Arc::new(MemoryRequestReply::new(pubsub_concrete.clone()));
64        let jobs_concrete = Arc::new(MemoryJobQueue::new(
65            pubsub_concrete.clone(),
66            kv_concrete.clone(),
67        ));
68        Self {
69            pubsub: pubsub_concrete,
70            request_reply: rr_concrete,
71            kv: kv_concrete,
72            jobs: jobs_concrete,
73        }
74    }
75
76    /// Capability-shaped default — build a fresh in-process bus and
77    /// wrap it in [`Arc`] for cheap cross-task sharing.
78    ///
79    /// Equivalent to `Arc::new(MemoryBus::new())`. Lead-with this
80    /// constructor when wiring agents that hold the bus behind an
81    /// `Arc<MemoryBus>`; reach for [`Self::new`] when you want owned
82    /// access to the four trait handles via field destructuring.
83    pub fn shared() -> Arc<Self> {
84        Arc::new(Self::new())
85    }
86}
87
88impl Default for MemoryBus {
89    fn default() -> Self {
90        Self::new()
91    }
92}
93
94impl From<MemoryBus> for BusHandles {
95    fn from(value: MemoryBus) -> Self {
96        BusHandles::new(value.pubsub, value.kv, value.request_reply, value.jobs)
97    }
98}
99
100#[cfg(test)]
101mod lib_tests {
102    use super::*;
103    use bytes::Bytes;
104    use klieo_core::bus::{Headers, Job};
105    use klieo_core::ids::DurableName;
106    use std::time::Duration;
107    use tokio_stream::StreamExt;
108
109    #[tokio::test]
110    async fn shared_returns_arc_wired_and_usable() {
111        let bus = MemoryBus::shared();
112        bus.kv
113            .put("b", "k", Bytes::from_static(b"shared"))
114            .await
115            .unwrap();
116        let entry = bus.kv.get("b", "k").await.unwrap().unwrap();
117        assert_eq!(entry.value, Bytes::from_static(b"shared"));
118        // Cheap clone — same underlying state.
119        let bus2 = Arc::clone(&bus);
120        let entry2 = bus2.kv.get("b", "k").await.unwrap().unwrap();
121        assert_eq!(entry2.value, Bytes::from_static(b"shared"));
122    }
123
124    #[tokio::test]
125    async fn memory_bus_wires_all_four() {
126        let bus = MemoryBus::new();
127        bus.kv
128            .put("b", "k", Bytes::from_static(b"v"))
129            .await
130            .unwrap();
131        let entry = bus.kv.get("b", "k").await.unwrap().unwrap();
132        assert_eq!(entry.value, Bytes::from_static(b"v"));
133        let mut sub = bus
134            .pubsub
135            .subscribe("s", DurableName::new("d"))
136            .await
137            .unwrap();
138        bus.pubsub
139            .publish("s", Bytes::from_static(b"hi"), Headers::new())
140            .await
141            .unwrap();
142        let msg = sub.next().await.unwrap().unwrap();
143        assert_eq!(msg.payload, Bytes::from_static(b"hi"));
144        let err = bus
145            .request_reply
146            .request("svc", Bytes::from_static(b"x"), Duration::from_millis(20))
147            .await
148            .unwrap_err();
149        assert!(matches!(err, klieo_core::BusError::Timeout));
150        let id = bus
151            .jobs
152            .enqueue("q", Job::new(Bytes::from_static(b"j")))
153            .await
154            .unwrap();
155        let claimed = bus
156            .jobs
157            .claim("q", "w", Duration::from_secs(1))
158            .await
159            .unwrap()
160            .unwrap();
161        assert_eq!(claimed.id, id);
162    }
163
164    #[tokio::test]
165    async fn bus_handles_from_memory_bus_preserves_handles() {
166        let bus = MemoryBus::new();
167        let ps = Arc::as_ptr(&bus.pubsub);
168        let kv = Arc::as_ptr(&bus.kv);
169        let rr = Arc::as_ptr(&bus.request_reply);
170        let jq = Arc::as_ptr(&bus.jobs);
171        let handles: BusHandles = bus.into();
172        assert!(std::ptr::addr_eq(Arc::as_ptr(&handles.pubsub), ps));
173        assert!(std::ptr::addr_eq(Arc::as_ptr(&handles.kv), kv));
174        assert!(std::ptr::addr_eq(Arc::as_ptr(&handles.request_reply), rr));
175        assert!(std::ptr::addr_eq(Arc::as_ptr(&handles.jobs), jq));
176    }
177}