Skip to main content

serviceless/
lib.rs

1//! # serviceless
2//!
3//! A small **async actor** library: each [`Service`] runs on a single mailbox, handlers are
4//! `async`, and callers interact through [`ServiceAddress`] (typed messages) and optional
5//! **topic** pub/sub ([`Topic`], [`TopicEndpoint`]). See the [`docs`] module for how to use the
6//! actor model, runtime expectations, and topic semantics.
7//!
8//! ## Features
9//!
10//! - **Async actors** — Each [`Service`] runs a mailbox loop; hooks and [`Handler`] methods are
11//!   `async` (via [`async_trait::async_trait`]).
12//! - **Typed messages** — Implement [`Message`] and [`Handler`] for compile-time dispatch instead
13//!   of manual routing for your own message types.
14//! - **`call` and `send`** — [`ServiceAddress::call`] awaits `M::Result`; [`ServiceAddress::send`]
15//!   enqueues work and drops the handler return value.
16//! - **Preferred call path** — Set [`Message::IS_PERFERRED`] to dispatch [`ServiceAddress::call`]
17//!   via [`Handler::handle_preferred`] and [`ReplyHandle`] when you need custom reply timing.
18//! - **Topics (pub/sub-style)** — [`Topic`], [`RoutedTopic`], and [`TopicEndpoint`] for one-shot
19//!   subscribe / publish flows still serialized through the actor mailbox.
20//! - **External envelope streams** — [`Context::with_stream`] merges another [`futures_core::Stream`]
21//!   of [`Envelope`]s with the internal mailbox on the same single-consumer path.
22//! - **Typed narrowing** — [`ServiceAddress::into_address`] builds an [`Address`] for one message
23//!   type plus a forwarding future you spawn alongside the main `run` future.
24//! - **Bring your own runtime** — The crate returns a `run` future; you spawn it on Tokio or any
25//!   other executor. There are **no optional Cargo `[features]`** on this crate today: the API
26//!   surface is always enabled.
27//!
28//! ## Quick start
29//!
30//! 1. Implement [`Service`] for your actor state (pick [`EmptyStream`] if you
31//!    do not merge an external envelope stream).
32//! 2. Implement [`Message`] + [`Handler`] for request/reply or fire-and-forget work.
33//! 3. Build a [`Context`], call [`Service::start_by_context`], then **spawn** the returned
34//!    future on your async runtime.
35//! 4. Use the returned [`ServiceAddress`] for [`ServiceAddress::call`], [`ServiceAddress::send`],
36//!    and optionally [`ServiceAddress::subscribe`] (topic key + `Result` of a one-shot future) for
37//!    topics.
38//!
39//! ```rust,no_run
40//! use async_trait::async_trait;
41//! use serviceless::{Context, EmptyStream, Handler, Message, Service};
42//!
43//! #[derive(Default)]
44//! struct Greeter;
45//!
46//! #[async_trait]
47//! impl Service for Greeter {
48//!     type Stream = EmptyStream<Self>;
49//! }
50//!
51//! struct Hello(pub String);
52//! impl Message for Hello {
53//!     type Result = String;
54//! }
55//!
56//! #[async_trait]
57//! impl Handler<Hello> for Greeter {
58//!     async fn handle(
59//!         &mut self,
60//!         msg: Hello,
61//!         _ctx: &mut Context<Self, Self::Stream>,
62//!     ) -> String {
63//!         format!("Hello, {}", msg.0)
64//!     }
65//! }
66//!
67//! #[tokio::main]
68//! async fn main() {
69//!     let (addr, run) = Greeter::default().start_by_context(Context::new());
70//!     tokio::spawn(run);
71//!     let reply = addr.call(Hello("Ada".into())).await.expect("reply");
72//!     assert_eq!(reply, "Hello, Ada");
73//!     addr.close_service();
74//! }
75//! ```
76//!
77//! Longer explanations, caveats, and pub/sub details live under [`docs`].
78
79mod service;
80pub use service::*;
81
82mod context;
83pub use context::*;
84
85mod handler;
86pub use handler::*;
87
88mod topic;
89pub use topic::*;
90
91mod envelop;
92pub use envelop::*;
93
94mod error;
95pub use error::*;
96
97mod address;
98pub use address::*;
99
100mod service_address;
101pub use service_address::*;
102
103pub mod docs;
104
105mod reply_handle;
106pub use reply_handle::*;