mod r#impl;
pub use r#impl::PulseDBSubstrate;
use std::pin::Pin;
use async_trait::async_trait;
use futures_core::Stream;
use crate::activity::Activity;
use crate::collective::Collective;
use crate::error::PulseDBError;
use crate::experience::{Experience, NewExperience};
use crate::insight::{DerivedInsight, NewDerivedInsight};
use crate::relation::{ExperienceRelation, NewExperienceRelation};
use crate::search::{ContextCandidates, ContextRequest};
use crate::types::{CollectiveId, ExperienceId, InsightId, RelationId};
use crate::watch::WatchEvent;
#[async_trait]
pub trait SubstrateProvider: Send + Sync {
async fn store_experience(&self, exp: NewExperience) -> Result<ExperienceId, PulseDBError>;
async fn get_experience(&self, id: ExperienceId) -> Result<Option<Experience>, PulseDBError>;
async fn reinforce_experience(&self, _id: ExperienceId) -> Result<u32, PulseDBError> {
Err(PulseDBError::internal(
"SubstrateProvider::reinforce_experience is not supported by this implementation",
))
}
async fn energy(&self, _id: ExperienceId) -> Result<f32, PulseDBError> {
Err(PulseDBError::internal(
"SubstrateProvider::energy is not supported by this implementation",
))
}
async fn search_similar(
&self,
collective: CollectiveId,
embedding: &[f32],
k: usize,
) -> Result<Vec<(Experience, f32)>, PulseDBError>;
async fn get_recent(
&self,
collective: CollectiveId,
limit: usize,
) -> Result<Vec<Experience>, PulseDBError>;
async fn store_relation(&self, rel: NewExperienceRelation) -> Result<RelationId, PulseDBError>;
async fn get_related(
&self,
exp_id: ExperienceId,
) -> Result<Vec<(Experience, ExperienceRelation)>, PulseDBError>;
async fn store_insight(&self, insight: NewDerivedInsight) -> Result<InsightId, PulseDBError>;
async fn get_insights(
&self,
collective: CollectiveId,
embedding: &[f32],
k: usize,
) -> Result<Vec<(DerivedInsight, f32)>, PulseDBError>;
async fn get_activities(&self, collective: CollectiveId)
-> Result<Vec<Activity>, PulseDBError>;
async fn get_context_candidates(
&self,
request: ContextRequest,
) -> Result<ContextCandidates, PulseDBError>;
async fn watch(
&self,
collective: CollectiveId,
) -> Result<Pin<Box<dyn Stream<Item = WatchEvent> + Send>>, PulseDBError>;
async fn create_collective(&self, name: &str) -> Result<CollectiveId, PulseDBError>;
async fn get_or_create_collective(&self, name: &str) -> Result<CollectiveId, PulseDBError>;
async fn list_collectives(&self) -> Result<Vec<Collective>, PulseDBError>;
async fn list_experiences(
&self,
_collective: CollectiveId,
_limit: usize,
_offset: usize,
) -> Result<Vec<Experience>, PulseDBError> {
Ok(vec![])
}
async fn list_relations(
&self,
_collective: CollectiveId,
_limit: usize,
_offset: usize,
) -> Result<Vec<ExperienceRelation>, PulseDBError> {
Ok(vec![])
}
async fn list_insights(
&self,
_collective: CollectiveId,
_limit: usize,
_offset: usize,
) -> Result<Vec<DerivedInsight>, PulseDBError> {
Ok(vec![])
}
async fn list_cold_experiences(
&self,
_collective: CollectiveId,
_below: f32,
_limit: usize,
) -> Result<Vec<(ExperienceId, f32)>, PulseDBError> {
Err(PulseDBError::internal(
"SubstrateProvider::list_cold_experiences is not supported by this implementation",
))
}
}
#[cfg(test)]
mod tests {
use super::*;
struct StubProvider;
#[async_trait]
impl SubstrateProvider for StubProvider {
async fn store_experience(
&self,
_exp: NewExperience,
) -> Result<ExperienceId, PulseDBError> {
unimplemented!("stub")
}
async fn get_experience(
&self,
_id: ExperienceId,
) -> Result<Option<Experience>, PulseDBError> {
unimplemented!("stub")
}
async fn search_similar(
&self,
_collective: CollectiveId,
_embedding: &[f32],
_k: usize,
) -> Result<Vec<(Experience, f32)>, PulseDBError> {
unimplemented!("stub")
}
async fn get_recent(
&self,
_collective: CollectiveId,
_limit: usize,
) -> Result<Vec<Experience>, PulseDBError> {
unimplemented!("stub")
}
async fn store_relation(
&self,
_rel: NewExperienceRelation,
) -> Result<RelationId, PulseDBError> {
unimplemented!("stub")
}
async fn get_related(
&self,
_exp_id: ExperienceId,
) -> Result<Vec<(Experience, ExperienceRelation)>, PulseDBError> {
unimplemented!("stub")
}
async fn store_insight(
&self,
_insight: NewDerivedInsight,
) -> Result<InsightId, PulseDBError> {
unimplemented!("stub")
}
async fn get_insights(
&self,
_collective: CollectiveId,
_embedding: &[f32],
_k: usize,
) -> Result<Vec<(DerivedInsight, f32)>, PulseDBError> {
unimplemented!("stub")
}
async fn get_activities(
&self,
_collective: CollectiveId,
) -> Result<Vec<Activity>, PulseDBError> {
unimplemented!("stub")
}
async fn get_context_candidates(
&self,
_request: ContextRequest,
) -> Result<ContextCandidates, PulseDBError> {
unimplemented!("stub")
}
async fn watch(
&self,
_collective: CollectiveId,
) -> Result<Pin<Box<dyn Stream<Item = WatchEvent> + Send>>, PulseDBError> {
unimplemented!("stub")
}
async fn create_collective(&self, _name: &str) -> Result<CollectiveId, PulseDBError> {
unimplemented!("stub")
}
async fn get_or_create_collective(
&self,
_name: &str,
) -> Result<CollectiveId, PulseDBError> {
unimplemented!("stub")
}
async fn list_collectives(&self) -> Result<Vec<Collective>, PulseDBError> {
unimplemented!("stub")
}
}
#[tokio::test]
async fn default_list_cold_experiences_is_unsupported() {
let provider = StubProvider;
let result = provider
.list_cold_experiences(CollectiveId::nil(), 0.5, 10)
.await;
let err = result.expect_err("default list_cold_experiences must return an Err");
assert!(
err.to_string()
.contains("not supported by this implementation"),
"expected unsupported-operation error, got: {err}"
);
}
#[tokio::test]
async fn cold_list_default_is_object_safe() {
let provider: Box<dyn SubstrateProvider> = Box::new(StubProvider);
let result = provider
.list_cold_experiences(CollectiveId::nil(), 0.25, 5)
.await;
assert!(result.is_err(), "default via trait object must be Err");
}
}