exo_node/lib.rs
1//! Node.js bindings for EXO-AI cognitive substrate via NAPI-RS
2//!
3//! High-performance Rust-based cognitive substrate with async/await support,
4//! hypergraph queries, and temporal memory.
5
6#![deny(clippy::all)]
7#![warn(clippy::pedantic)]
8
9use napi::bindgen_prelude::*;
10use napi_derive::napi;
11
12use exo_backend_classical::ClassicalBackend;
13use exo_core::{Pattern, SubstrateBackend};
14use std::sync::Arc;
15
16mod types;
17use types::*;
18
19/// EXO-AI cognitive substrate for Node.js
20///
21/// Provides vector similarity search, hypergraph queries, and temporal memory
22/// backed by the high-performance ruvector database.
23#[napi]
24pub struct ExoSubstrateNode {
25 backend: Arc<ClassicalBackend>,
26}
27
28#[napi]
29impl ExoSubstrateNode {
30 /// Create a new substrate instance
31 ///
32 /// # Example
33 /// ```javascript
34 /// const substrate = new ExoSubstrateNode({
35 /// dimensions: 384,
36 /// distanceMetric: 'Cosine'
37 /// });
38 /// ```
39 #[napi(constructor)]
40 pub fn new(dimensions: u32) -> Result<Self> {
41 let backend = ClassicalBackend::with_dimensions(dimensions as usize)
42 .map_err(|e| Error::from_reason(format!("Failed to create backend: {}", e)))?;
43
44 Ok(Self {
45 backend: Arc::new(backend),
46 })
47 }
48
49 /// Create a substrate with default configuration (768 dimensions)
50 ///
51 /// # Example
52 /// ```javascript
53 /// const substrate = ExoSubstrateNode.withDimensions(384);
54 /// ```
55 #[napi(factory)]
56 pub fn with_dimensions(dimensions: u32) -> Result<Self> {
57 Self::new(dimensions)
58 }
59
60 /// Store a pattern in the substrate
61 ///
62 /// Returns the ID of the stored pattern
63 ///
64 /// # Example
65 /// ```javascript
66 /// const id = await substrate.store({
67 /// embedding: new Float32Array([1.0, 2.0, 3.0, ...]),
68 /// metadata: '{"text": "example", "category": "demo"}',
69 /// salience: 1.0
70 /// });
71 /// ```
72 #[napi]
73 pub fn store(&self, pattern: JsPattern) -> Result<String> {
74 let core_pattern: Pattern = pattern.try_into()?;
75 let pattern_id = core_pattern.id;
76
77 self.backend
78 .manifold_deform(&core_pattern, 0.0)
79 .map_err(|e| Error::from_reason(format!("Failed to store pattern: {}", e)))?;
80
81 Ok(pattern_id.to_string())
82 }
83
84 /// Search for similar patterns
85 ///
86 /// Returns an array of search results sorted by similarity
87 ///
88 /// # Example
89 /// ```javascript
90 /// const results = await substrate.search(
91 /// new Float32Array([1.0, 2.0, 3.0, ...]),
92 /// 10 // top-k
93 /// );
94 /// ```
95 #[napi]
96 pub fn search(&self, embedding: Float32Array, k: u32) -> Result<Vec<JsSearchResult>> {
97 let results = self
98 .backend
99 .similarity_search(&embedding.to_vec(), k as usize, None)
100 .map_err(|e| Error::from_reason(format!("Failed to search: {}", e)))?;
101
102 Ok(results.into_iter().map(Into::into).collect())
103 }
104
105 /// Query hypergraph topology
106 ///
107 /// Performs topological data analysis queries on the substrate
108 /// Note: This feature is not yet fully implemented in the classical backend
109 ///
110 /// # Example
111 /// ```javascript
112 /// const result = await substrate.hypergraphQuery('{"BettiNumbers":{"max_dimension":3}}');
113 /// ```
114 #[napi]
115 pub fn hypergraph_query(&self, _query: String) -> Result<String> {
116 // Hypergraph queries are not supported in the classical backend yet
117 // Return a NotSupported response
118 Ok(r#"{"NotSupported":null}"#.to_string())
119 }
120
121 /// Get substrate dimensions
122 ///
123 /// # Example
124 /// ```javascript
125 /// const dims = substrate.dimensions();
126 /// console.log(`Dimensions: ${dims}`);
127 /// ```
128 #[napi]
129 pub fn dimensions(&self) -> u32 {
130 self.backend.dimension() as u32
131 }
132}
133
134/// Get the version of the EXO-AI library
135#[napi]
136pub fn version() -> String {
137 env!("CARGO_PKG_VERSION").to_string()
138}
139
140/// Test function to verify the bindings are working
141#[napi]
142pub fn hello() -> String {
143 "Hello from EXO-AI cognitive substrate!".to_string()
144}