Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions MIGRATION_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -331,3 +331,90 @@ persistence_strategy = "MemFirst"
---

**Last Updated:** February 2026

---

## 🚨 For v0.2.3 Users: API Surface Changes in v0.2.4 (#326)

### What Changed

v0.2.4 removes internal implementation details that were accidentally exposed as `pub`.
All removed items were internal — they were never part of the documented public API.

### Breaking Changes

#### 1. `EmbeddedClient::node_id()` removed

This method returned `client_id` (not a node ID), which was semantically incorrect.

```rust
// Old (v0.2.3) — broken semantics, removed
let id = client.node_id();

// New (v0.2.4) — use EmbeddedEngine instead
let id = engine.node_id();
```

#### 2. `GrpcClient` convenience methods now require `ClientApi` trait in scope

`get_linearizable()`, `get_lease()`, and `get_eventual()` are now only available via
the `ClientApi` trait. The return type is unified to `Option<Bytes>` (was `Option<ClientResult>`).

```rust
// Old (v0.2.3)
let result = client.get_linearizable("key").await?;
let value = result.map(|r| r.value); // extra unwrap needed

// New (v0.2.4) — add trait import, get Bytes directly
use d_engine_client::ClientApi;
let value = client.get_linearizable("key").await?; // Option<Bytes>
```

#### 3. `Node::set_rpc_ready()`, `is_rpc_ready()`, `ready_notifier()` removed from public API

These were internal lifecycle methods. Use `EmbeddedEngine::wait_ready()` instead.

```rust
// Old (v0.2.3)
node.set_rpc_ready(true);
let ready = node.is_rpc_ready();

// New (v0.2.4) — use the engine-level API
engine.wait_ready(Duration::from_secs(5)).await?;
```

#### 4. `Node::node_config` field is no longer public

```rust
// Old (v0.2.3)
let node_id = node.node_config.cluster.node_id;

// New (v0.2.4)
let node_id = node.node_id();
```

#### 5. `NodeBuilder::init()` is no longer public

Use the documented constructors instead.

```rust
// Old (v0.2.3)
NodeBuilder::init(config, shutdown_rx)

// New (v0.2.4)
NodeBuilder::new(None, shutdown_rx).node_config(config)
// or
NodeBuilder::from_cluster_config(cluster_config, shutdown_rx)
```

#### 6. `QuorumStatus` removed

This type was defined but never used. Remove any references to it.

#### 7. `ClientInner` and `ConnectionPool` no longer public

These are internal connection pool types. Use `Client` and `ClientBuilder` instead.

---

**Last Updated:** April 2026
23 changes: 0 additions & 23 deletions d-engine-client/src/grpc_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,29 +40,6 @@ impl GrpcClient {
Self { client_inner }
}

// Convenience methods for explicit consistency levels
pub async fn get_linearizable(
&self,
key: impl AsRef<[u8]>,
) -> std::result::Result<Option<ClientResult>, ClientApiError> {
self.get_with_policy(key, Some(ReadConsistencyPolicy::LinearizableRead)).await
}

pub async fn get_lease(
&self,
key: impl AsRef<[u8]>,
) -> std::result::Result<Option<ClientResult>, ClientApiError> {
self.get_with_policy(key, Some(ReadConsistencyPolicy::LeaseRead)).await
}

pub async fn get_eventual(
&self,
key: impl AsRef<[u8]>,
) -> std::result::Result<Option<ClientResult>, ClientApiError> {
self.get_with_policy(key, Some(ReadConsistencyPolicy::EventualConsistency))
.await
}

/// Retrieves a single key's value with explicit consistency policy
///
/// Allows client to override server's default consistency policy for this specific request.
Expand Down
8 changes: 4 additions & 4 deletions d-engine-client/src/grpc_client_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -604,7 +604,7 @@ async fn test_get_linearizable_success() {

let result = client.get_linearizable(key).await;
assert!(result.is_ok());
assert_eq!(result.unwrap().as_ref().map(|r| &r.value), Some(&value));
assert_eq!(result.unwrap(), Some(value));
}

#[tokio::test]
Expand Down Expand Up @@ -642,7 +642,7 @@ async fn test_get_lease_success() {

let result = client.get_lease(key).await;
assert!(result.is_ok());
assert_eq!(result.unwrap().as_ref().map(|r| &r.value), Some(&value));
assert_eq!(result.unwrap(), Some(value));
}

#[tokio::test]
Expand Down Expand Up @@ -680,7 +680,7 @@ async fn test_get_eventual_success() {

let result = client.get_eventual(key).await;
assert!(result.is_ok());
assert_eq!(result.unwrap().as_ref().map(|r| &r.value), Some(&value));
assert_eq!(result.unwrap(), Some(value));
}

#[tokio::test]
Expand Down Expand Up @@ -843,7 +843,7 @@ async fn test_get_consistency_methods_failure() {
let key = "test_key".to_string().into_bytes();

// Test all convenience methods with network failure
let methods: [(&str, ClientApiResult<Option<ClientResult>>); 3] = [
let methods: [(&str, ClientApiResult<Option<Bytes>>); 3] = [
(
"get_linearizable",
client.get_linearizable(key.clone()).await,
Expand Down
13 changes: 6 additions & 7 deletions d-engine-client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,7 @@ pub use builder::*;
pub use config::*;
pub use d_engine_core::client::{ClientApi, ClientApiError, ClientApiResult};
pub use grpc_client::*;
pub use pool::*;
pub use utils::*;
pub(crate) use pool::*;

// ==================== Protocol Types (Essential for Public API) ====================

Expand Down Expand Up @@ -131,11 +130,11 @@ pub struct Client {
}

#[derive(Clone)]
pub struct ClientInner {
pool: ConnectionPool,
client_id: u32,
config: ClientConfig,
endpoints: Vec<String>,
pub(crate) struct ClientInner {
pub(crate) pool: ConnectionPool,
pub(crate) client_id: u32,
pub(crate) config: ClientConfig,
pub(crate) endpoints: Vec<String>,
}

impl std::ops::Deref for Client {
Expand Down
2 changes: 1 addition & 1 deletion d-engine-client/src/utils_test.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::*;
use crate::utils::address_str;

#[test]
fn test_address_str_no_scheme() {
Expand Down
11 changes: 11 additions & 0 deletions d-engine-core/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ pub enum StorageError {
NotServing(String),
}

#[doc(hidden)]
#[derive(Debug, thiserror::Error)]
pub enum IdAllocationError {
/// ID allocation overflow
Expand All @@ -279,6 +280,7 @@ pub enum IdAllocationError {
NoIdsAvailable,
}

#[doc(hidden)]
#[derive(Debug, thiserror::Error)]
pub enum FileError {
#[error("Path does not exist: {0}")]
Expand All @@ -304,6 +306,7 @@ pub enum FileError {
}

/// Error type for value conversion operations
#[doc(hidden)]
#[derive(Debug, thiserror::Error)]
pub enum ConvertError {
/// Invalid input length error
Expand All @@ -319,6 +322,7 @@ pub enum ConvertError {
ConversionFailure(String),
}

#[doc(hidden)]
#[derive(Debug, thiserror::Error)]
pub enum ReadSendError {
#[error("Network timeout")]
Expand All @@ -328,6 +332,7 @@ pub enum ReadSendError {
Connection(#[from] tonic::transport::Error),
}

#[doc(hidden)]
#[derive(Debug, thiserror::Error)]
pub enum WriteSendError {
#[error("Not cluster leader")]
Expand Down Expand Up @@ -374,13 +379,15 @@ pub enum SystemError {
}

// Serialization is classified separately (across protocol layers and system layers)
#[doc(hidden)]
#[derive(Debug, thiserror::Error)]
pub enum SerializationError {
#[error("Bincode serialization failed: {0}")]
Bincode(#[from] bincode::Error),
}

/// Wrapper for prost encoding/decoding errors
#[doc(hidden)]
#[derive(Debug, thiserror::Error)]
pub enum ProstError {
#[error("Encoding failed: {0}")]
Expand All @@ -390,6 +397,7 @@ pub enum ProstError {
Decode(#[from] prost::DecodeError),
}

#[doc(hidden)]
#[derive(Debug, thiserror::Error)]
pub enum ElectionError {
/// General election process failure
Expand Down Expand Up @@ -425,6 +433,7 @@ pub enum ElectionError {
NoVotingMemberFound { candidate_id: u32 },
}

#[doc(hidden)]
#[derive(Debug, thiserror::Error)]
pub enum ReplicationError {
/// Stale leader detected during AppendEntries RPC
Expand Down Expand Up @@ -465,6 +474,7 @@ pub enum ReplicationError {
}

/// Errors that can occur during ReadIndex batching for linearizable reads
#[doc(hidden)]
#[derive(Debug, thiserror::Error, Clone)]
pub enum ReadIndexError {
/// This node is no longer the leader
Expand Down Expand Up @@ -550,6 +560,7 @@ pub enum MembershipError {
ClusterMetadataNotInitialized,
}

#[doc(hidden)]
#[derive(Debug, thiserror::Error)]
pub enum SnapshotError {
#[error("Snapshot receiver lagging, dropping chunk")]
Expand Down
53 changes: 33 additions & 20 deletions d-engine-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,35 +77,55 @@ pub mod storage;
#[cfg(feature = "watch")]
pub mod watch;

// ── User-facing public API ──────────────────────────────────────────────────
pub use client::*;
pub use commit_handler::*;
pub use config::*;
pub use election::*;
pub use errors::*;
pub use storage::*;
#[cfg(feature = "watch")]
pub use watch::*;

// Stable extension points — types developers need when implementing custom
// storage engines, state machines, or transport layers.
pub use membership::Membership;
pub use network::Transport;
pub use purge::PurgeExecutor;
pub use raft::{LeaderInfo, Raft, SignalParams};
pub use state_machine_handler::{SnapshotPolicy, StateMachineHandler};
pub use type_config::TypeConfig;

// ── Internal implementation details (not part of public API) ───────────────
// These remain accessible to d-engine-server but are hidden from cargo doc.
// Do not depend on these from external crates.
#[doc(hidden)]
pub use commit_handler::*;
#[doc(hidden)]
pub use election::*;
#[doc(hidden)]
pub use event::*;
#[doc(hidden)]
pub use maybe_clone_oneshot::*;
#[doc(hidden)]
pub use membership::*;
#[doc(hidden)]
pub use network::*;
#[doc(hidden)]
pub use purge::*;
pub use raft::*;
#[doc(hidden)]
pub use raft_context::*;
pub use replication::*;
pub use state_machine_handler::*;
pub use storage::*;
#[cfg(feature = "watch")]
pub use watch::*;

#[cfg(test)]
mod raft_test;

#[doc(hidden)]
pub use raft_role::*;
pub(crate) use timer::*;
#[doc(hidden)]
pub use replication::*;
#[doc(hidden)]
pub use state_machine_handler::*;
#[doc(hidden)]
pub use type_config::*;
#[doc(hidden)]
pub use utils::*;

pub(crate) use timer::*;

#[cfg(test)]
mod maybe_clone_oneshot_test;

Expand Down Expand Up @@ -161,10 +181,3 @@ pub(crate) fn is_target_log_more_recent(
(target_last_log_term > my_last_log_term)
|| (target_last_log_term == my_last_log_term && target_last_log_index >= my_last_log_index)
}

#[derive(Debug, Clone, Copy)]
pub enum QuorumStatus {
Confirmed, // Confirmed by the majority of nodes
LostQuorum, // Unable to obtain majority
NetworkError, // Network problem (can be retried)
}
Loading
Loading