netabase_store 0.0.8

A type-safe, multi-backend key-value storage library for Rust with support for native (Sled, Redb) and WASM (IndexedDB) environments.
Documentation
use std::str::FromStr;

use crate::{MaybeSend, MaybeSync};
use strum::IntoDiscriminant;

/// A consolidated trait for all discriminant bounds used throughout netabase.
///
/// This trait encapsulates all the common bounds required for discriminants,
/// allowing simpler trait bounds like `D::Discriminant: NetabaseDiscriminant`
/// instead of repeating all bounds everywhere.
pub trait NetabaseDiscriminant:
    AsRef<str>
    + Clone
    + Copy
    + std::fmt::Debug
    + std::fmt::Display
    + PartialEq
    + Eq
    + std::hash::Hash
    + strum::IntoEnumIterator
    + MaybeSend
    + MaybeSync
    + 'static
    + FromStr
    + bincode::Encode
    + bincode::Decode<()>
{
}

// Blanket implementation for any type that satisfies the bounds
impl<T> NetabaseDiscriminant for T where
    T: AsRef<str>
        + Clone
        + Copy
        + std::fmt::Debug
        + std::fmt::Display
        + PartialEq
        + Eq
        + std::hash::Hash
        + strum::IntoEnumIterator
        + MaybeSend
        + MaybeSync
        + 'static
        + FromStr
        + bincode::Encode
        + bincode::Decode<()>
{
}

/// A consolidated trait for key discriminant bounds.
///
/// Similar to `NetabaseDiscriminant` but with fewer bounds (no `AsRef<str>`, Display, or FromStr)
/// since key discriminants don't need string conversion capabilities.
pub trait NetabaseKeyDiscriminant:
    Clone
    + Copy
    + std::fmt::Debug
    + PartialEq
    + Eq
    + std::hash::Hash
    + MaybeSend
    + MaybeSync
    + 'static
    + bincode::Encode
    + bincode::Decode<()>
{
}

// Blanket implementation for any type that satisfies the bounds
impl<T> NetabaseKeyDiscriminant for T where
    T: Clone
        + Copy
        + std::fmt::Debug
        + PartialEq
        + Eq
        + std::hash::Hash
        + MaybeSend
        + MaybeSync
        + 'static
        + bincode::Encode
        + bincode::Decode<()>
{
}

/// Trait for the module-level definition enum that wraps all models.
///
/// This trait is automatically implemented by the `#[netabase_definition_module]` macro.
/// The definition enum is used as the primary type for encoding, decoding, and moving
/// model data around.
///
/// # Simplified Bounds
///
/// This trait now uses `NetabaseDiscriminant` and `NetabaseKeyDiscriminant` to consolidate
/// the many trait bounds previously repeated everywhere. This means you can simply write:
///
/// ```
/// # use netabase_store::traits::definition::NetabaseDefinitionTrait;
/// fn my_function<D: NetabaseDefinitionTrait>() { /* ... */ }
/// ```
///
/// instead of having to repeat all the discriminant bounds.
pub trait NetabaseDefinitionTrait:
    bincode::Encode
    + bincode::Decode<()>
    + Clone
    + std::fmt::Debug
    + MaybeSend
    + MaybeSync
    + 'static
    + IntoDiscriminant<Discriminant: NetabaseDiscriminant>
{
    type Keys: NetabaseDefinitionTraitKey<Discriminant: NetabaseKeyDiscriminant>;

    // Always present - uses unit-like struct when redb disabled
    type Tables: Clone + Copy;

    // Subscription manager type - present when streams are enabled
    type SubscriptionManager: Clone;

    /// Get the table definitions for this schema (redb only)
    ///
    /// Returns a struct containing all TableDefinitions for models in this schema.
    /// This method is generated by the `#[netabase_definition_module]` macro.
    /// When redb feature is disabled, this returns an empty struct.
    fn tables() -> Self::Tables;

    /// Get a new instance of the subscription manager for this schema
    ///
    /// Returns a subscription manager instance that can track changes to models
    /// in this schema across different subscription topics. This method is generated
    /// by the `#[netabase_definition_module]` macro when `#[streams(...)]` is present.
    fn subscription_manager() -> Self::SubscriptionManager;

    /// Get the discriminant name as a string (for tree names)
    fn discriminant_name(&self) -> String {
        self.discriminant().to_string()
    }

    /// Convert this definition to a libp2p kad::Record (native-only)
    #[cfg(all(feature = "libp2p", not(target_arch = "wasm32")))]
    fn to_record(&self) -> Result<libp2p::kad::Record, bincode::error::EncodeError> {
        let key_bytes = bincode::encode_to_vec(self, bincode::config::standard())?;
        let record_key = libp2p::kad::RecordKey::new(&key_bytes);
        let value_bytes = bincode::encode_to_vec(self, bincode::config::standard())?;

        Ok(libp2p::kad::Record {
            key: record_key,
            value: value_bytes,
            publisher: None,
            expires: None,
        })
    }
}

/// Extension trait for RecordStore helper methods
///
/// This trait provides helper methods that are generated by the macro for each Definition type.
/// The methods handle deserialization, dispatching, and storage of models.
///
/// # Design
///
/// Instead of using a generic `S` parameter (which would cause trait bound issues),
/// we use separate methods for each store backend. This allows each backend to have
/// its own specific OpenTree bounds without violating Rust's trait implementation rules.
/// Note: libp2p requires native networking (mio) and is not available on WASM.
#[cfg(all(feature = "libp2p", not(target_arch = "wasm32")))]
pub trait RecordStoreExt: NetabaseDefinitionTrait
where
    <Self as IntoDiscriminant>::Discriminant: NetabaseDiscriminant,
    <<Self as NetabaseDefinitionTrait>::Keys as IntoDiscriminant>::Discriminant:
        NetabaseKeyDiscriminant,
{
    /// Put this Definition into a Sled store
    #[cfg(feature = "sled")]
    fn handle_sled_put(
        &self,
        store: &crate::databases::sled_store::SledStore<Self>,
    ) -> libp2p::kad::store::Result<()>;

    /// Get a Definition from a Sled store using a RecordKey
    #[cfg(feature = "sled")]
    fn handle_sled_get(
        store: &crate::databases::sled_store::SledStore<Self>,
        key: &libp2p::kad::RecordKey,
    ) -> Option<(Self, libp2p::kad::Record)>;

    /// Remove from a Sled store using a RecordKey
    #[cfg(feature = "sled")]
    fn handle_sled_remove(
        store: &crate::databases::sled_store::SledStore<Self>,
        key: &libp2p::kad::RecordKey,
    );

    /// Get all records from a Sled store as an iterator
    #[cfg(feature = "sled")]
    fn handle_sled_records<'a>(
        store: &'a crate::databases::sled_store::SledStore<Self>,
    ) -> Box<dyn Iterator<Item = std::borrow::Cow<'a, libp2p::kad::Record>> + 'a>;

    /// Put this Definition into a Redb store
    #[cfg(feature = "redb")]
    fn handle_redb_put(
        &self,
        store: &crate::databases::redb_store::RedbStore<Self>,
    ) -> libp2p::kad::store::Result<()>;

    /// Get a Definition from a Redb store using a RecordKey
    #[cfg(feature = "redb")]
    fn handle_redb_get(
        store: &crate::databases::redb_store::RedbStore<Self>,
        key: &libp2p::kad::RecordKey,
    ) -> Option<(Self, libp2p::kad::Record)>;

    /// Remove from a Redb store using a RecordKey
    #[cfg(feature = "redb")]
    fn handle_redb_remove(
        store: &crate::databases::redb_store::RedbStore<Self>,
        key: &libp2p::kad::RecordKey,
    );

    /// Get all records from a Redb store as an iterator
    #[cfg(feature = "redb")]
    fn handle_redb_records<'a>(
        store: &'a crate::databases::redb_store::RedbStore<Self>,
    ) -> Box<dyn Iterator<Item = std::borrow::Cow<'a, libp2p::kad::Record>> + 'a>;

    /// Put this Definition into an IndexedDB store
    #[cfg(all(feature = "wasm", target_arch = "wasm32"))]
    fn handle_indexeddb_put(
        &self,
        store: &crate::databases::indexeddb_store::IndexedDBStore<Self>,
    ) -> libp2p::kad::store::Result<()>;

    /// Get a Definition from an IndexedDB store using a RecordKey
    #[cfg(all(feature = "wasm", target_arch = "wasm32"))]
    fn handle_indexeddb_get(
        store: &crate::databases::indexeddb_store::IndexedDBStore<Self>,
        key: &libp2p::kad::RecordKey,
    ) -> Option<(Self, libp2p::kad::Record)>;

    /// Remove from an IndexedDB store using a RecordKey
    #[cfg(all(feature = "wasm", target_arch = "wasm32"))]
    fn handle_indexeddb_remove(
        store: &crate::databases::indexeddb_store::IndexedDBStore<Self>,
        key: &libp2p::kad::RecordKey,
    );
}

/// Trait for the module-level keys enum that wraps all model keys.
///
/// This trait is automatically implemented by the `#[netabase_definition_module]` macro.
pub trait NetabaseDefinitionTraitKey:
    bincode::Encode
    + bincode::Decode<()>
    + Clone
    + std::fmt::Debug
    + PartialEq
    + Eq
    + std::hash::Hash
    + PartialOrd
    + Ord
    + MaybeSend
    + MaybeSync
    + 'static
    + strum::IntoDiscriminant<Discriminant: NetabaseKeyDiscriminant>
{
    /// Convert this key to a libp2p kad::RecordKey
    #[cfg(all(feature = "libp2p", not(target_arch = "wasm32")))]
    fn to_record_key(&self) -> Result<libp2p::kad::RecordKey, bincode::error::EncodeError> {
        let key_bytes = bincode::encode_to_vec(self, bincode::config::standard())?;
        Ok(libp2p::kad::RecordKey::new(&key_bytes))
    }
}