iota_grpc_api/client/
node_client.rs

1// Copyright (c) 2025 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4use std::sync::OnceLock;
5
6use tonic::transport::Channel;
7
8use super::{checkpoint::CheckpointClient, event::EventClient};
9
10/// gRPC client factory for IOTA node operations.
11pub struct NodeClient {
12    /// Shared gRPC channel for all service clients
13    channel: Channel,
14    /// Cached checkpoint client (singleton)
15    checkpoint_client: OnceLock<CheckpointClient>,
16    /// Cached event client (singleton)
17    event_client: OnceLock<EventClient>,
18}
19
20impl NodeClient {
21    /// Connect to a gRPC server and create a new NodeClient instance.
22    pub async fn connect(url: &str) -> anyhow::Result<Self> {
23        let channel = Channel::from_shared(url.to_string())?.connect().await?;
24
25        Ok(Self {
26            channel,
27            checkpoint_client: OnceLock::new(),
28            event_client: OnceLock::new(),
29        })
30    }
31
32    /// Get a reference to the underlying channel.
33    ///
34    /// This can be useful for creating additional service clients that aren't
35    /// yet integrated into NodeClient.
36    pub fn channel(&self) -> &Channel {
37        &self.channel
38    }
39
40    // ========================================
41    // Service Client Factories
42    // ========================================
43
44    /// Get a checkpoint service client.
45    ///
46    /// Returns `Some(CheckpointClient)` if the node supports checkpoint
47    /// operations, `None` otherwise. The client is created only once and
48    /// cached for subsequent calls.
49    pub fn checkpoint_client(&self) -> Option<CheckpointClient> {
50        // For now, always return Some since checkpoint service is always available
51        // In the future, this could check node capabilities first
52        Some(
53            self.checkpoint_client
54                .get_or_init(|| CheckpointClient::new(self.channel.clone()))
55                .clone(),
56        )
57    }
58
59    /// Get an event service client.
60    ///
61    /// Returns `Some(EventClient)` if the node supports event streaming
62    /// operations, `None` otherwise. The client is created only once and
63    /// cached for subsequent calls.
64    pub fn event_client(&self) -> Option<EventClient> {
65        // For now, always return Some since event service is always available
66        // In the future, this could check node capabilities first
67        Some(
68            self.event_client
69                .get_or_init(|| EventClient::new(self.channel.clone()))
70                .clone(),
71        )
72    }
73}