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}