Merge commit 'd803bfe2b1fe7f5e219e50ac20d6801a0a58ac75' as 'vendor/ruvector'
This commit is contained in:
144
vendor/ruvector/examples/exo-ai-2025/crates/exo-node/src/lib.rs
vendored
Normal file
144
vendor/ruvector/examples/exo-ai-2025/crates/exo-node/src/lib.rs
vendored
Normal file
@@ -0,0 +1,144 @@
|
||||
//! Node.js bindings for EXO-AI cognitive substrate via NAPI-RS
|
||||
//!
|
||||
//! High-performance Rust-based cognitive substrate with async/await support,
|
||||
//! hypergraph queries, and temporal memory.
|
||||
|
||||
#![deny(clippy::all)]
|
||||
#![warn(clippy::pedantic)]
|
||||
|
||||
use napi::bindgen_prelude::*;
|
||||
use napi_derive::napi;
|
||||
|
||||
use exo_backend_classical::ClassicalBackend;
|
||||
use exo_core::{Pattern, SubstrateBackend};
|
||||
use std::sync::Arc;
|
||||
|
||||
mod types;
|
||||
use types::*;
|
||||
|
||||
/// EXO-AI cognitive substrate for Node.js
|
||||
///
|
||||
/// Provides vector similarity search, hypergraph queries, and temporal memory
|
||||
/// backed by the high-performance ruvector database.
|
||||
#[napi]
|
||||
pub struct ExoSubstrateNode {
|
||||
backend: Arc<ClassicalBackend>,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl ExoSubstrateNode {
|
||||
/// Create a new substrate instance
|
||||
///
|
||||
/// # Example
|
||||
/// ```javascript
|
||||
/// const substrate = new ExoSubstrateNode({
|
||||
/// dimensions: 384,
|
||||
/// distanceMetric: 'Cosine'
|
||||
/// });
|
||||
/// ```
|
||||
#[napi(constructor)]
|
||||
pub fn new(dimensions: u32) -> Result<Self> {
|
||||
let backend = ClassicalBackend::with_dimensions(dimensions as usize)
|
||||
.map_err(|e| Error::from_reason(format!("Failed to create backend: {}", e)))?;
|
||||
|
||||
Ok(Self {
|
||||
backend: Arc::new(backend),
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a substrate with default configuration (768 dimensions)
|
||||
///
|
||||
/// # Example
|
||||
/// ```javascript
|
||||
/// const substrate = ExoSubstrateNode.withDimensions(384);
|
||||
/// ```
|
||||
#[napi(factory)]
|
||||
pub fn with_dimensions(dimensions: u32) -> Result<Self> {
|
||||
Self::new(dimensions)
|
||||
}
|
||||
|
||||
/// Store a pattern in the substrate
|
||||
///
|
||||
/// Returns the ID of the stored pattern
|
||||
///
|
||||
/// # Example
|
||||
/// ```javascript
|
||||
/// const id = await substrate.store({
|
||||
/// embedding: new Float32Array([1.0, 2.0, 3.0, ...]),
|
||||
/// metadata: '{"text": "example", "category": "demo"}',
|
||||
/// salience: 1.0
|
||||
/// });
|
||||
/// ```
|
||||
#[napi]
|
||||
pub fn store(&self, pattern: JsPattern) -> Result<String> {
|
||||
let core_pattern: Pattern = pattern.try_into()?;
|
||||
let pattern_id = core_pattern.id;
|
||||
|
||||
self.backend
|
||||
.manifold_deform(&core_pattern, 0.0)
|
||||
.map_err(|e| Error::from_reason(format!("Failed to store pattern: {}", e)))?;
|
||||
|
||||
Ok(pattern_id.to_string())
|
||||
}
|
||||
|
||||
/// Search for similar patterns
|
||||
///
|
||||
/// Returns an array of search results sorted by similarity
|
||||
///
|
||||
/// # Example
|
||||
/// ```javascript
|
||||
/// const results = await substrate.search(
|
||||
/// new Float32Array([1.0, 2.0, 3.0, ...]),
|
||||
/// 10 // top-k
|
||||
/// );
|
||||
/// ```
|
||||
#[napi]
|
||||
pub fn search(&self, embedding: Float32Array, k: u32) -> Result<Vec<JsSearchResult>> {
|
||||
let results = self
|
||||
.backend
|
||||
.similarity_search(&embedding.to_vec(), k as usize, None)
|
||||
.map_err(|e| Error::from_reason(format!("Failed to search: {}", e)))?;
|
||||
|
||||
Ok(results.into_iter().map(Into::into).collect())
|
||||
}
|
||||
|
||||
/// Query hypergraph topology
|
||||
///
|
||||
/// Performs topological data analysis queries on the substrate
|
||||
/// Note: This feature is not yet fully implemented in the classical backend
|
||||
///
|
||||
/// # Example
|
||||
/// ```javascript
|
||||
/// const result = await substrate.hypergraphQuery('{"BettiNumbers":{"max_dimension":3}}');
|
||||
/// ```
|
||||
#[napi]
|
||||
pub fn hypergraph_query(&self, _query: String) -> Result<String> {
|
||||
// Hypergraph queries are not supported in the classical backend yet
|
||||
// Return a NotSupported response
|
||||
Ok(r#"{"NotSupported":null}"#.to_string())
|
||||
}
|
||||
|
||||
/// Get substrate dimensions
|
||||
///
|
||||
/// # Example
|
||||
/// ```javascript
|
||||
/// const dims = substrate.dimensions();
|
||||
/// console.log(`Dimensions: ${dims}`);
|
||||
/// ```
|
||||
#[napi]
|
||||
pub fn dimensions(&self) -> u32 {
|
||||
self.backend.dimension() as u32
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the version of the EXO-AI library
|
||||
#[napi]
|
||||
pub fn version() -> String {
|
||||
env!("CARGO_PKG_VERSION").to_string()
|
||||
}
|
||||
|
||||
/// Test function to verify the bindings are working
|
||||
#[napi]
|
||||
pub fn hello() -> String {
|
||||
"Hello from EXO-AI cognitive substrate!".to_string()
|
||||
}
|
||||
86
vendor/ruvector/examples/exo-ai-2025/crates/exo-node/src/types.rs
vendored
Normal file
86
vendor/ruvector/examples/exo-ai-2025/crates/exo-node/src/types.rs
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
//! Node.js-compatible type definitions
|
||||
|
||||
use exo_core::{Metadata, MetadataValue, Pattern, PatternId, SearchResult, SubstrateTime};
|
||||
use napi::bindgen_prelude::*;
|
||||
use napi_derive::napi;
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Pattern for Node.js
|
||||
#[napi(object)]
|
||||
#[derive(Clone)]
|
||||
pub struct JsPattern {
|
||||
/// Vector embedding as Float32Array
|
||||
pub embedding: Float32Array,
|
||||
/// Metadata as JSON string
|
||||
pub metadata: Option<String>,
|
||||
/// Causal antecedents (pattern IDs as strings)
|
||||
pub antecedents: Option<Vec<String>>,
|
||||
/// Salience score (importance, default 1.0)
|
||||
pub salience: Option<f64>,
|
||||
}
|
||||
|
||||
impl TryFrom<JsPattern> for Pattern {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(pattern: JsPattern) -> Result<Self> {
|
||||
let metadata = if let Some(meta_str) = pattern.metadata {
|
||||
let fields: HashMap<String, serde_json::Value> = serde_json::from_str(&meta_str)
|
||||
.map_err(|e| Error::from_reason(format!("Invalid metadata JSON: {}", e)))?;
|
||||
|
||||
let mut meta = Metadata::default();
|
||||
for (key, value) in fields {
|
||||
let meta_value = match value {
|
||||
serde_json::Value::String(s) => MetadataValue::String(s),
|
||||
serde_json::Value::Number(n) => {
|
||||
MetadataValue::Number(n.as_f64().unwrap_or(0.0))
|
||||
}
|
||||
serde_json::Value::Bool(b) => MetadataValue::Boolean(b),
|
||||
_ => continue,
|
||||
};
|
||||
meta.fields.insert(key, meta_value);
|
||||
}
|
||||
meta
|
||||
} else {
|
||||
Metadata::default()
|
||||
};
|
||||
|
||||
// Parse antecedents from UUID strings
|
||||
let antecedents = pattern
|
||||
.antecedents
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.filter_map(|s| uuid::Uuid::parse_str(&s).ok().map(|uuid| PatternId(uuid)))
|
||||
.collect();
|
||||
|
||||
Ok(Pattern {
|
||||
id: PatternId::new(),
|
||||
embedding: pattern.embedding.to_vec(),
|
||||
metadata,
|
||||
timestamp: SubstrateTime::now(),
|
||||
antecedents,
|
||||
salience: pattern.salience.unwrap_or(1.0) as f32,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Search result for Node.js
|
||||
#[napi(object)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct JsSearchResult {
|
||||
/// Pattern ID as string
|
||||
pub id: String,
|
||||
/// Similarity score (lower is better for distance metrics)
|
||||
pub score: f64,
|
||||
/// Distance value
|
||||
pub distance: f64,
|
||||
}
|
||||
|
||||
impl From<SearchResult> for JsSearchResult {
|
||||
fn from(result: SearchResult) -> Self {
|
||||
JsSearchResult {
|
||||
id: result.pattern.id.to_string(),
|
||||
score: f64::from(result.score),
|
||||
distance: f64::from(result.distance),
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user