cortexkit_bus_trait/stream.rs
1use std::time::Duration;
2
3use async_trait::async_trait;
4
5use crate::{BusResult, ContentDigest, Headers, Message};
6
7/// The server's decision about a publish, including deduplicated retries.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9#[non_exhaustive]
10#[must_use = "check duplicate() to distinguish a stored message from an idempotent retry or an id collision"]
11pub struct PublishReceipt {
12 stream_sequence: u64,
13 duplicate: bool,
14}
15
16impl PublishReceipt {
17 pub fn new(stream_sequence: u64, duplicate: bool) -> Self {
18 Self {
19 stream_sequence,
20 duplicate,
21 }
22 }
23
24 /// The stored message's sequence, or the original sequence for a duplicate.
25 pub fn stream_sequence(&self) -> u64 {
26 self.stream_sequence
27 }
28
29 /// Whether this id was already stored within the stream's deduplication window.
30 pub fn duplicate(&self) -> bool {
31 self.duplicate
32 }
33}
34
35#[derive(Debug, Clone, PartialEq, Eq)]
36pub struct StreamDelivery {
37 pub stream_seq: u64,
38 /// How many times this message has been delivered to this durable,
39 /// counting this delivery: 1 on the first delivery, and one more after
40 /// every nak or ack-wait expiry. JetStream reports it as `num_delivered`.
41 ///
42 /// On a stream whose max-deliver is unlimited this is the only way a
43 /// consumer can tell a message that keeps failing from a new one, so it is
44 /// what a consumer uses to give up and [`StreamCursor::term`] it.
45 pub delivery_count: u64,
46 pub message: Message,
47}
48
49/// A cursor for an already-created durable, selected by its associated registry identity.
50///
51/// At most one delivery is in flight per cursor: `ack`, `nak` and `term` each
52/// settle it, after which `next` may be called again. Each of them, and
53/// `in_progress`, returns `Ok` only once the server has recorded it.
54#[async_trait]
55pub trait StreamCursor: Send {
56 async fn next(&mut self) -> BusResult<Option<StreamDelivery>>;
57 async fn ack(&mut self) -> BusResult<()>;
58 async fn nak(&mut self, delay: Duration) -> BusResult<()>;
59
60 /// Settles the current delivery terminally: it is not acknowledged as
61 /// processed and it is never redelivered (JetStream `+TERM`). `reason`
62 /// is recorded with the termination where the backend can carry one
63 /// (JetStream's `+TERM <reason>`), for example `body_absent`.
64 async fn term(&mut self, reason: Option<&str>) -> BusResult<()>;
65
66 /// Reports that the current delivery is still being worked on (JetStream
67 /// `+WPI`): the ack-wait clock restarts from now, so a handler slower than
68 /// one ack wait is not redelivered under it. The delivery stays in flight
69 /// and must still be settled with `ack`, `nak` or `term`.
70 async fn in_progress(&mut self) -> BusResult<()>;
71}
72
73/// Whose durable a cursor binds: an agent or a module, with its id. The
74/// backend derives the durable name from the kind and the id (`c_` or `m_`
75/// followed by the id), so a cursor can bind only an agent's or a module's
76/// durable and never an arbitrary consumer name.
77#[derive(Debug, Clone, Copy, PartialEq, Eq)]
78pub enum DurableOwner<'a> {
79 /// An agent's durable on an agent stream (WAKE, PEER, EFFECT), named
80 /// `c_{agent_id}`.
81 Agent(&'a str),
82 /// A module's durable on a non-agent stream (EVENT, or prefrontal-core's
83 /// one durable on ROOM), named `m_{module_id}`.
84 Module(&'a str),
85}
86
87#[async_trait]
88pub trait Stream: Send + Sync {
89 type Cursor: StreamCursor;
90
91 /// Publishes a message, returning whether it was stored or deduplicated.
92 ///
93 /// A duplicate means the stream already stored this id within its
94 /// deduplication window; the receipt carries that original message's sequence
95 /// and no new message was stored. Callers may accept it as an idempotent retry,
96 /// or treat it as an id collision when a new message was intended. Use distinct
97 /// ids for distinct messages, even across subjects in the same stream.
98 async fn publish(
99 &self,
100 subject: &str,
101 id: &str,
102 digest: ContentDigest,
103 headers: Headers,
104 ) -> BusResult<PublishReceipt>;
105
106 /// Attaches to the owner's durable, created separately; this client cannot
107 /// create workload consumers.
108 async fn consumer(&self, owner: DurableOwner<'_>) -> BusResult<Self::Cursor>;
109}