1#[cfg(feature = "native")]
5pub mod authority;
6#[cfg(feature = "replication")]
7pub mod authority_admission;
8#[cfg(feature = "semantic-analysis")]
9pub mod behavior;
10#[cfg(feature = "replication")]
11pub mod boundary_acceptance;
12#[cfg(feature = "replication")]
13pub mod collaboration;
14pub mod content;
15#[cfg(feature = "replication")]
16pub mod creation;
17#[cfg(feature = "signing")]
18pub mod credentials;
19#[cfg(feature = "replication")]
20pub mod evidence;
21#[cfg(feature = "source-transfer")]
22pub mod fetch;
23pub mod hybrid;
24#[cfg(feature = "replication")]
25pub mod live_replication;
26pub mod observation;
27#[cfg(feature = "root-attachment")]
28pub mod pairing;
29#[cfg(any(feature = "native", feature = "replication", feature = "iroh"))]
30pub mod publication;
31mod reopen;
32#[cfg(feature = "replication")]
33pub mod replication;
34#[cfg(all(feature = "native", feature = "iroh"))]
35pub mod replication_rpc;
36#[cfg(feature = "signing")]
37pub mod request_proof;
38#[cfg(feature = "root-attachment")]
39pub mod root_attachment;
40#[cfg(feature = "replication")]
41pub mod thread_control;
42#[cfg(feature = "replication")]
43pub mod thread_ownership;
44pub mod transport;
45
46use api::v2::client::{Client, ClientError, RpcTransport};
47pub use api::{
48 heddle::api::v1alpha2 as contract,
49 v2::{client::Rpc, rpc},
50};
51use contract::{DescribeEndpointRequest, DescribeEndpointResponse, EndpointKind, ThreadRef};
52pub use reopen::is_reopen_retryable;
53use transport::Error;
54
55pub struct Remote<T: RpcTransport<Error = Error>> {
58 pub api: Client<T>,
59 pub description: DescribeEndpointResponse,
60}
61
62impl<T: RpcTransport<Error = Error>> Remote<T> {
63 pub async fn discover(
66 transport: T,
67 endpoint_key: [u8; 32],
68 kind: EndpointKind,
69 ) -> Result<Self, ClientError<Error>> {
70 let bytes = transport
71 .unary(
72 rpc::EndpointServiceDescribeEndpoint::METHOD,
73 prost::Message::encode_to_vec(&DescribeEndpointRequest {
74 understood_packages: vec!["heddle.api.v1alpha2".into()],
75 }),
76 )
77 .await
78 .map_err(ClientError::Transport)?;
79 let description: DescribeEndpointResponse = prost::Message::decode(bytes.as_slice())?;
80 Self::from_description(transport, endpoint_key, kind, description)
81 }
82
83 pub fn from_description(
87 transport: T,
88 endpoint_key: [u8; 32],
89 kind: EndpointKind,
90 description: DescribeEndpointResponse,
91 ) -> Result<Self, ClientError<Error>> {
92 if description
93 .endpoint
94 .as_ref()
95 .is_none_or(|source| source.public_key != endpoint_key || source.kind != kind as i32)
96 || !description
97 .supported_packages
98 .iter()
99 .any(|p| p == "heddle.api.v1alpha2")
100 {
101 return Err(ClientError::Transport(Error::Protocol(
102 "endpoint identity/package mismatch",
103 )));
104 }
105 let mut api = Client::new(transport, description.implemented_methods.clone());
106 if let Some(protocol) = &description.protocol {
107 api = api.with_protocol(protocol.clone());
108 }
109 Ok(Self { api, description })
110 }
111
112 pub async fn observe<M>(
115 &self,
116 mut request: M::Request,
117 resume: Option<observation::Resume>,
118 ) -> Result<observation::Observation<T::Reader, M::Response>, observation::Error>
119 where
120 M: api::v2::client::ServerStreamingRpc,
121 M::Request: observation::ObservationRequest,
122 M::Response: observation::ObservedEvent,
123 {
124 use observation::ObservationRequest as _;
125
126 use crate::reopen::ReopenRetryable as _;
127 let budget = observation::budget(&self.description)?;
128 let options = request.options_mut();
129 options.budget = Some(budget);
130 options.after_cursor.clear();
131 let mut query = b"heddle-observation-query-v2\0".to_vec();
132 query.extend_from_slice(M::METHOD.path.as_bytes());
133 query.push(0);
134 query.extend_from_slice(&prost::Message::encode_to_vec(&request));
135 observation::validate_resume(&resume, &self.description, &query)?;
136 if let Some(resume) = &resume {
137 request.options_mut().after_cursor = resume.cursor.clone();
138 }
139 let mut attempt = 0;
140 loop {
141 let messages = match self.api.observe::<M>(&request).await {
142 Ok(messages) => messages,
143 Err(error)
144 if reopen::client_error_is_reopen_retryable(&error)
145 && attempt + 1 < reopen::ATTEMPTS =>
146 {
147 attempt += 1;
148 reopen::backoff(attempt).await;
149 continue;
150 }
151 Err(error) => return Err(error.into()),
152 };
153 let mut observation = observation::Observation::new(
154 messages,
155 &self.description,
156 budget,
157 resume.clone(),
158 query.clone(),
159 )?;
160 match observation.consume_open().await {
161 Ok(()) => return Ok(observation),
162 Err(error) if error.is_reopen_retryable() && attempt + 1 < reopen::ATTEMPTS => {
163 attempt += 1;
164 reopen::backoff(attempt).await;
165 }
166 Err(error) => {
167 observation.prime_error(error);
168 return Ok(observation);
169 }
170 }
171 }
172 }
173
174 pub async fn observe_analysis(
177 &self,
178 request: contract::ObserveAnalysisRequest,
179 resume: Option<observation::Resume>,
180 ) -> Result<observation::AnalysisObservation<T::Reader>, observation::Error> {
181 self.observe::<rpc::AnalysisServiceObserveAnalysis>(request, resume)
182 .await
183 }
184
185 pub fn thread(&self, thread: ThreadRef) -> Thread<'_, T> {
188 Thread {
189 remote: self,
190 reference: thread,
191 }
192 }
193}
194
195pub struct Thread<'a, T: RpcTransport<Error = Error>> {
196 remote: &'a Remote<T>,
197 pub reference: ThreadRef,
198}
199
200impl<T: RpcTransport<Error = Error>> Thread<'_, T> {
201 #[cfg(feature = "replication")]
204 pub async fn revise_intent(
205 &self,
206 command: &thread_control::PreparedControl,
207 ) -> Result<contract::ThreadMutationResponse, ClientError<Error>> {
208 let request = command.revise_intent().map_err(ClientError::Transport)?;
209 if request.thread.as_ref() != Some(&self.reference) {
210 return Err(ClientError::Transport(Error::Protocol(
211 "prepared command belongs to another Thread",
212 )));
213 }
214 self.remote
215 .api
216 .call::<rpc::ThreadServiceReviseIntent>(&request)
217 .await
218 }
219
220 pub async fn observe(
221 &self,
222 sections: &[contract::ThreadSection],
223 mode: contract::ObservationMode,
224 resume: Option<observation::Resume>,
225 ) -> Result<observation::ThreadObservation<T::Reader>, observation::Error> {
226 self.remote
227 .observe::<rpc::ThreadServiceObserveThread>(
228 contract::ObserveThreadRequest {
229 thread: Some(self.reference.clone()),
230 sections: sections.iter().map(|section| *section as i32).collect(),
231 observe: Some(contract::ObserveOptions {
232 mode: mode as i32,
233 ..Default::default()
234 }),
235 ..Default::default()
236 },
237 resume,
238 )
239 .await
240 }
241}