Merge commit 'd803bfe2b1fe7f5e219e50ac20d6801a0a58ac75' as 'vendor/ruvector'

This commit is contained in:
ruv
2026-02-28 14:39:40 -05:00
7854 changed files with 3522914 additions and 0 deletions

View 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()
}

View 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),
}
}
}