ruvector_graph_node/
lib.rs1#![deny(clippy::all)]
7#![warn(clippy::pedantic)]
8
9use napi::bindgen_prelude::*;
10use napi_derive::napi;
11use ruvector_core::advanced::hypergraph::{
12 CausalMemory as CoreCausalMemory, Hyperedge as CoreHyperedge,
13 HypergraphIndex as CoreHypergraphIndex, HypergraphStats as CoreHypergraphStats,
14 TemporalGranularity as CoreTemporalGranularity, TemporalHyperedge as CoreTemporalHyperedge,
15};
16use ruvector_core::DistanceMetric;
17use std::collections::HashMap;
18use std::sync::{Arc, RwLock};
19
20mod streaming;
21mod transactions;
22mod types;
23
24pub use streaming::*;
25pub use transactions::*;
26pub use types::*;
27
28#[napi]
30pub struct GraphDatabase {
31 hypergraph: Arc<RwLock<CoreHypergraphIndex>>,
32 causal_memory: Arc<RwLock<CoreCausalMemory>>,
33 transaction_manager: Arc<RwLock<transactions::TransactionManager>>,
34}
35
36#[napi]
37impl GraphDatabase {
38 #[napi(constructor)]
48 pub fn new(options: Option<JsGraphOptions>) -> Result<Self> {
49 let opts = options.unwrap_or_default();
50 let metric = opts.distance_metric.unwrap_or(JsDistanceMetric::Cosine);
51 let core_metric: DistanceMetric = metric.into();
52
53 Ok(Self {
54 hypergraph: Arc::new(RwLock::new(CoreHypergraphIndex::new(core_metric))),
55 causal_memory: Arc::new(RwLock::new(CoreCausalMemory::new(core_metric))),
56 transaction_manager: Arc::new(RwLock::new(transactions::TransactionManager::new())),
57 })
58 }
59
60 #[napi]
71 pub async fn create_node(&self, node: JsNode) -> Result<String> {
72 let hypergraph = self.hypergraph.clone();
73 let id = node.id.clone();
74 let embedding = node.embedding.to_vec();
75
76 tokio::task::spawn_blocking(move || {
77 let mut hg = hypergraph.write().expect("RwLock poisoned");
78 hg.add_entity(id.clone(), embedding);
79 Ok::<String, Error>(id)
80 })
81 .await
82 .map_err(|e| Error::from_reason(format!("Task failed: {}", e)))?
83 }
84
85 #[napi]
98 pub async fn create_edge(&self, edge: JsEdge) -> Result<String> {
99 let hypergraph = self.hypergraph.clone();
100 let nodes = vec![edge.from.clone(), edge.to.clone()];
101 let description = edge.description.clone();
102 let embedding = edge.embedding.to_vec();
103 let confidence = edge.confidence.unwrap_or(1.0) as f32;
104
105 tokio::task::spawn_blocking(move || {
106 let core_edge = CoreHyperedge::new(nodes, description, embedding, confidence);
107 let edge_id = core_edge.id.clone();
108 let mut hg = hypergraph.write().expect("RwLock poisoned");
109 hg.add_hyperedge(core_edge)
110 .map_err(|e| Error::from_reason(format!("Failed to create edge: {}", e)))?;
111 Ok(edge_id)
112 })
113 .await
114 .map_err(|e| Error::from_reason(format!("Task failed: {}", e)))?
115 }
116
117 #[napi]
130 pub async fn create_hyperedge(&self, hyperedge: JsHyperedge) -> Result<String> {
131 let hypergraph = self.hypergraph.clone();
132 let nodes = hyperedge.nodes.clone();
133 let description = hyperedge.description.clone();
134 let embedding = hyperedge.embedding.to_vec();
135 let confidence = hyperedge.confidence.unwrap_or(1.0) as f32;
136
137 tokio::task::spawn_blocking(move || {
138 let core_edge = CoreHyperedge::new(nodes, description, embedding, confidence);
139 let edge_id = core_edge.id.clone();
140 let mut hg = hypergraph.write().expect("RwLock poisoned");
141 hg.add_hyperedge(core_edge)
142 .map_err(|e| Error::from_reason(format!("Failed to create hyperedge: {}", e)))?;
143 Ok(edge_id)
144 })
145 .await
146 .map_err(|e| Error::from_reason(format!("Task failed: {}", e)))?
147 }
148
149 #[napi]
156 pub async fn query(&self, cypher: String) -> Result<JsQueryResult> {
157 let hypergraph = self.hypergraph.clone();
159
160 tokio::task::spawn_blocking(move || {
161 let hg = hypergraph.read().expect("RwLock poisoned");
162 let stats = hg.stats();
163
164 Ok::<JsQueryResult, Error>(JsQueryResult {
166 nodes: vec![],
167 edges: vec![],
168 stats: Some(JsGraphStats {
169 total_nodes: stats.total_entities as u32,
170 total_edges: stats.total_hyperedges as u32,
171 avg_degree: stats.avg_entity_degree as f64,
172 }),
173 })
174 })
175 .await
176 .map_err(|e| Error::from_reason(format!("Task failed: {}", e)))?
177 }
178
179 #[napi]
186 pub fn query_sync(&self, cypher: String) -> Result<JsQueryResult> {
187 let hg = self.hypergraph.read().expect("RwLock poisoned");
188 let stats = hg.stats();
189
190 Ok(JsQueryResult {
192 nodes: vec![],
193 edges: vec![],
194 stats: Some(JsGraphStats {
195 total_nodes: stats.total_entities as u32,
196 total_edges: stats.total_hyperedges as u32,
197 avg_degree: stats.avg_entity_degree as f64,
198 }),
199 })
200 }
201
202 #[napi]
212 pub async fn search_hyperedges(
213 &self,
214 query: JsHyperedgeQuery,
215 ) -> Result<Vec<JsHyperedgeResult>> {
216 let hypergraph = self.hypergraph.clone();
217 let embedding = query.embedding.to_vec();
218 let k = query.k as usize;
219
220 tokio::task::spawn_blocking(move || {
221 let hg = hypergraph.read().expect("RwLock poisoned");
222 let results = hg.search_hyperedges(&embedding, k);
223
224 Ok::<Vec<JsHyperedgeResult>, Error>(
225 results
226 .into_iter()
227 .map(|(id, score)| JsHyperedgeResult {
228 id,
229 score: f64::from(score),
230 })
231 .collect(),
232 )
233 })
234 .await
235 .map_err(|e| Error::from_reason(format!("Task failed: {}", e)))?
236 }
237
238 #[napi]
245 pub async fn k_hop_neighbors(&self, start_node: String, k: u32) -> Result<Vec<String>> {
246 let hypergraph = self.hypergraph.clone();
247 let hops = k as usize;
248
249 tokio::task::spawn_blocking(move || {
250 let hg = hypergraph.read().expect("RwLock poisoned");
251 let neighbors = hg.k_hop_neighbors(start_node, hops);
252 Ok::<Vec<String>, Error>(neighbors.into_iter().collect())
253 })
254 .await
255 .map_err(|e| Error::from_reason(format!("Task failed: {}", e)))?
256 }
257
258 #[napi]
265 pub async fn begin(&self) -> Result<String> {
266 let tm = self.transaction_manager.clone();
267
268 tokio::task::spawn_blocking(move || {
269 let mut manager = tm.write().expect("RwLock poisoned");
270 Ok::<String, Error>(manager.begin())
271 })
272 .await
273 .map_err(|e| Error::from_reason(format!("Task failed: {}", e)))?
274 }
275
276 #[napi]
283 pub async fn commit(&self, tx_id: String) -> Result<()> {
284 let tm = self.transaction_manager.clone();
285
286 tokio::task::spawn_blocking(move || {
287 let mut manager = tm.write().expect("RwLock poisoned");
288 manager
289 .commit(&tx_id)
290 .map_err(|e| Error::from_reason(format!("Failed to commit: {}", e)))
291 })
292 .await
293 .map_err(|e| Error::from_reason(format!("Task failed: {}", e)))?
294 }
295
296 #[napi]
303 pub async fn rollback(&self, tx_id: String) -> Result<()> {
304 let tm = self.transaction_manager.clone();
305
306 tokio::task::spawn_blocking(move || {
307 let mut manager = tm.write().expect("RwLock poisoned");
308 manager
309 .rollback(&tx_id)
310 .map_err(|e| Error::from_reason(format!("Failed to rollback: {}", e)))
311 })
312 .await
313 .map_err(|e| Error::from_reason(format!("Task failed: {}", e)))?
314 }
315
316 #[napi]
326 pub async fn batch_insert(&self, batch: JsBatchInsert) -> Result<JsBatchResult> {
327 let hypergraph = self.hypergraph.clone();
328 let nodes = batch.nodes;
329 let edges = batch.edges;
330
331 tokio::task::spawn_blocking(move || {
332 let mut hg = hypergraph.write().expect("RwLock poisoned");
333 let mut node_ids = Vec::new();
334 let mut edge_ids = Vec::new();
335
336 for node in nodes {
338 hg.add_entity(node.id.clone(), node.embedding.to_vec());
339 node_ids.push(node.id);
340 }
341
342 for edge in edges {
344 let nodes = vec![edge.from.clone(), edge.to.clone()];
345 let embedding = edge.embedding.to_vec();
346 let confidence = edge.confidence.unwrap_or(1.0) as f32;
347 let core_edge = CoreHyperedge::new(nodes, edge.description, embedding, confidence);
348 let edge_id = core_edge.id.clone();
349 hg.add_hyperedge(core_edge)
350 .map_err(|e| Error::from_reason(format!("Failed to insert edge: {}", e)))?;
351 edge_ids.push(edge_id);
352 }
353
354 Ok::<JsBatchResult, Error>(JsBatchResult { node_ids, edge_ids })
355 })
356 .await
357 .map_err(|e| Error::from_reason(format!("Task failed: {}", e)))?
358 }
359
360 #[napi]
369 pub fn subscribe(&self, callback: JsFunction) -> Result<()> {
370 Ok(())
373 }
374
375 #[napi]
383 pub async fn stats(&self) -> Result<JsGraphStats> {
384 let hypergraph = self.hypergraph.clone();
385
386 tokio::task::spawn_blocking(move || {
387 let hg = hypergraph.read().expect("RwLock poisoned");
388 let stats = hg.stats();
389
390 Ok::<JsGraphStats, Error>(JsGraphStats {
391 total_nodes: stats.total_entities as u32,
392 total_edges: stats.total_hyperedges as u32,
393 avg_degree: stats.avg_entity_degree as f64,
394 })
395 })
396 .await
397 .map_err(|e| Error::from_reason(format!("Task failed: {}", e)))?
398 }
399}
400
401#[napi]
403pub fn version() -> String {
404 env!("CARGO_PKG_VERSION").to_string()
405}
406
407#[napi]
409pub fn hello() -> String {
410 "Hello from RuVector Graph Node.js bindings!".to_string()
411}