cortexkit_bus_trait/stream.rs
1use std::time::Duration;
2
3use async_trait::async_trait;
4
5use crate::{BusResult, ContentDigest, Headers, Message};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub struct PublishAck {
9 pub stream_seq: u64,
10}
11
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct StreamDelivery {
14 pub stream_seq: u64,
15 /// How many times this message has been delivered to this durable,
16 /// counting this delivery: 1 on the first delivery, and one more after
17 /// every nak or ack-wait expiry. JetStream reports it as `num_delivered`.
18 ///
19 /// On a stream whose max-deliver is unlimited this is the only way a
20 /// consumer can tell a message that keeps failing from a new one, so it is
21 /// what a consumer uses to give up and [`StreamCursor::term`] it.
22 pub delivery_count: u64,
23 pub message: Message,
24}
25
26/// A cursor for an already-created durable, selected by its associated registry identity.
27///
28/// At most one delivery is in flight per cursor: `ack`, `nak` and `term` each
29/// settle it, after which `next` may be called again. Each of them, and
30/// `in_progress`, returns `Ok` only once the server has recorded it.
31#[async_trait]
32pub trait StreamCursor: Send {
33 async fn next(&mut self) -> BusResult<Option<StreamDelivery>>;
34 async fn ack(&mut self) -> BusResult<()>;
35 async fn nak(&mut self, delay: Duration) -> BusResult<()>;
36
37 /// Settles the current delivery terminally: it is not acknowledged as
38 /// processed and it is never redelivered (JetStream `+TERM`). `reason`
39 /// is recorded with the termination where the backend can carry one
40 /// (JetStream's `+TERM <reason>`), for example `body_absent`.
41 async fn term(&mut self, reason: Option<&str>) -> BusResult<()>;
42
43 /// Reports that the current delivery is still being worked on (JetStream
44 /// `+WPI`): the ack-wait clock restarts from now, so a handler slower than
45 /// one ack wait is not redelivered under it. The delivery stays in flight
46 /// and must still be settled with `ack`, `nak` or `term`.
47 async fn in_progress(&mut self) -> BusResult<()>;
48}
49
50/// Whose durable a cursor binds: an agent or a module, with its id. The
51/// backend derives the durable name from the kind and the id (`c_` or `m_`
52/// followed by the id), so a cursor can bind only an agent's or a module's
53/// durable and never an arbitrary consumer name.
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
55pub enum DurableOwner<'a> {
56 /// An agent's durable on an agent stream (WAKE, PEER, EFFECT), named
57 /// `c_{agent_id}`.
58 Agent(&'a str),
59 /// A module's durable on a non-agent stream (EVENT, or prefrontal-core's
60 /// one durable on ROOM), named `m_{module_id}`.
61 Module(&'a str),
62}
63
64#[async_trait]
65pub trait Stream: Send + Sync {
66 type Cursor: StreamCursor;
67
68 async fn publish(
69 &self,
70 subject: &str,
71 id: &str,
72 digest: ContentDigest,
73 headers: Headers,
74 ) -> BusResult<PublishAck>;
75
76 /// Attaches to the owner's durable, created separately; this client cannot
77 /// create workload consumers.
78 async fn consumer(&self, owner: DurableOwner<'_>) -> BusResult<Self::Cursor>;
79}