Skip to main content

hyperstack_sdk/
client.rs

1use crate::auth::{AuthConfig, AuthToken, TokenTransport};
2use crate::config::{ConnectionConfig, HyperStackConfig};
3use crate::connection::{ConnectionManager, ConnectionState};
4use crate::entity::Stack;
5use crate::error::{HyperStackError, SocketIssue};
6use crate::frame::Frame;
7use crate::store::{SharedStore, StoreConfig};
8use crate::view::Views;
9use std::future::Future;
10use std::marker::PhantomData;
11use std::sync::Arc;
12use std::time::Duration;
13use tokio::sync::{broadcast, mpsc};
14
15/// HyperStack client with typed views access.
16///
17/// ```ignore
18/// use hyperstack_sdk::prelude::*;
19/// use hyperstack_stacks::ore::OreStack;
20///
21/// let hs = HyperStack::<OreStack>::connect().await?;
22/// let rounds = hs.views.latest().get().await;
23/// ```
24pub struct HyperStack<S: Stack> {
25    connection: ConnectionManager,
26    store: SharedStore,
27    #[allow(dead_code)]
28    config: HyperStackConfig,
29    pub views: S::Views,
30    _stack: PhantomData<S>,
31}
32
33impl<S: Stack> HyperStack<S> {
34    /// Connect to the stack's default URL.
35    pub async fn connect() -> Result<Self, HyperStackError> {
36        Self::builder().connect().await
37    }
38
39    /// Connect with custom URL.
40    pub async fn connect_url(url: &str) -> Result<Self, HyperStackError> {
41        Self::builder().url(url).connect().await
42    }
43
44    /// Create a builder for custom configuration.
45    pub fn builder() -> HyperStackBuilder<S> {
46        HyperStackBuilder::new()
47    }
48
49    pub async fn connection_state(&self) -> ConnectionState {
50        self.connection.state().await
51    }
52
53    pub async fn last_error(&self) -> Option<Arc<HyperStackError>> {
54        self.connection.last_error().await
55    }
56
57    pub async fn last_socket_issue(&self) -> Option<SocketIssue> {
58        self.connection.last_socket_issue().await
59    }
60
61    pub fn subscribe_socket_issues(&self) -> broadcast::Receiver<SocketIssue> {
62        self.connection.subscribe_socket_issues()
63    }
64
65    pub async fn disconnect(&self) {
66        self.connection.disconnect().await;
67    }
68
69    pub fn store(&self) -> &SharedStore {
70        &self.store
71    }
72}
73
74/// Builder for HyperStack with custom configuration.
75pub struct HyperStackBuilder<S: Stack> {
76    url: String,
77    config: HyperStackConfig,
78    _stack: PhantomData<S>,
79}
80
81impl<S: Stack> HyperStackBuilder<S> {
82    fn new() -> Self {
83        Self {
84            url: S::url().to_string(),
85            config: HyperStackConfig::default(),
86            _stack: PhantomData,
87        }
88    }
89
90    pub fn url(mut self, url: &str) -> Self {
91        self.url = url.to_string();
92        self
93    }
94
95    pub fn auto_reconnect(mut self, enabled: bool) -> Self {
96        self.config.auto_reconnect = enabled;
97        self
98    }
99
100    pub fn reconnect_intervals(mut self, intervals: Vec<Duration>) -> Self {
101        self.config.reconnect_intervals = intervals;
102        self
103    }
104
105    pub fn max_reconnect_attempts(mut self, max: u32) -> Self {
106        self.config.max_reconnect_attempts = max;
107        self
108    }
109
110    pub fn ping_interval(mut self, interval: Duration) -> Self {
111        self.config.ping_interval = interval;
112        self
113    }
114
115    pub fn initial_data_timeout(mut self, timeout: Duration) -> Self {
116        self.config.initial_data_timeout = timeout;
117        self
118    }
119
120    pub fn max_entries_per_view(mut self, max: usize) -> Self {
121        self.config.max_entries_per_view = Some(max);
122        self
123    }
124
125    pub fn unlimited_entries(mut self) -> Self {
126        self.config.max_entries_per_view = None;
127        self
128    }
129
130    pub fn auth(mut self, auth: AuthConfig) -> Self {
131        self.config.auth = Some(auth);
132        self
133    }
134
135    pub fn auth_token(mut self, token: impl Into<String>) -> Self {
136        let auth = self
137            .config
138            .auth
139            .take()
140            .unwrap_or_default()
141            .with_token(token);
142        self.config.auth = Some(auth);
143        self
144    }
145
146    pub fn publishable_key(mut self, publishable_key: impl Into<String>) -> Self {
147        let auth = self
148            .config
149            .auth
150            .take()
151            .unwrap_or_default()
152            .with_publishable_key(publishable_key);
153        self.config.auth = Some(auth);
154        self
155    }
156
157    pub fn token_endpoint(mut self, token_endpoint: impl Into<String>) -> Self {
158        let auth = self
159            .config
160            .auth
161            .take()
162            .unwrap_or_default()
163            .with_token_endpoint(token_endpoint);
164        self.config.auth = Some(auth);
165        self
166    }
167
168    pub fn token_endpoint_header(
169        mut self,
170        key: impl Into<String>,
171        value: impl Into<String>,
172    ) -> Self {
173        let auth = self
174            .config
175            .auth
176            .take()
177            .unwrap_or_default()
178            .with_token_endpoint_header(key, value);
179        self.config.auth = Some(auth);
180        self
181    }
182
183    pub fn token_transport(mut self, transport: TokenTransport) -> Self {
184        let auth = self
185            .config
186            .auth
187            .take()
188            .unwrap_or_default()
189            .with_token_transport(transport);
190        self.config.auth = Some(auth);
191        self
192    }
193
194    pub fn get_token<F, Fut>(mut self, provider: F) -> Self
195    where
196        F: Fn() -> Fut + Send + Sync + 'static,
197        Fut: Future<Output = Result<AuthToken, HyperStackError>> + Send + 'static,
198    {
199        let auth = self
200            .config
201            .auth
202            .take()
203            .unwrap_or_default()
204            .with_token_provider(provider);
205        self.config.auth = Some(auth);
206        self
207    }
208
209    pub async fn connect(self) -> Result<HyperStack<S>, HyperStackError> {
210        let HyperStackBuilder {
211            url,
212            config,
213            _stack: _,
214        } = self;
215
216        let store_config = StoreConfig {
217            max_entries_per_view: config.max_entries_per_view,
218        };
219        let store = SharedStore::with_config(store_config);
220        let store_clone = store.clone();
221
222        let (frame_tx, mut frame_rx) = mpsc::channel::<Frame>(1000);
223
224        let connection_config: ConnectionConfig = config.clone().into();
225        let connection = ConnectionManager::new(url, connection_config, frame_tx).await?;
226
227        tokio::spawn(async move {
228            while let Some(frame) = frame_rx.recv().await {
229                store_clone.apply_frame(frame).await;
230            }
231        });
232
233        let view_builder = crate::view::ViewBuilder::new(
234            connection.clone(),
235            store.clone(),
236            config.initial_data_timeout,
237        );
238        let views = S::Views::from_builder(view_builder);
239
240        Ok(HyperStack {
241            connection,
242            store,
243            config,
244            views,
245            _stack: PhantomData,
246        })
247    }
248}