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,49 @@
[package]
name = "exo-node"
version = "0.1.0"
edition = "2021"
rust-version = "1.77"
license = "MIT OR Apache-2.0"
authors = ["rUv <ruv@ruv.io>"]
repository = "https://github.com/ruvnet/ruvector"
homepage = "https://ruv.io"
documentation = "https://docs.rs/exo-node"
description = "Node.js bindings for EXO-AI cognitive substrate via NAPI-RS"
keywords = ["nodejs", "napi", "bindings", "cognitive", "ai"]
categories = ["api-bindings", "science", "wasm"]
readme = "README.md"
[lib]
crate-type = ["cdylib"]
[dependencies]
# EXO-AI core
exo-core = "0.1"
exo-backend-classical = "0.1"
# Node.js bindings
napi = { version = "2.16", features = ["napi9", "async", "tokio_rt"] }
napi-derive = "2.16"
# Async runtime
tokio = { version = "1.41", features = ["rt-multi-thread"] }
# Serialization
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
# UUID for pattern IDs
uuid = { version = "1.10", features = ["v4", "serde"] }
# Error handling
thiserror = "2.0"
anyhow = "1.0"
[build-dependencies]
napi-build = "2.1"
[profile.release]
lto = true
strip = true
codegen-units = 1
opt-level = 3

View File

@@ -0,0 +1,43 @@
# exo-node
Node.js bindings for EXO-AI cognitive substrate via NAPI-RS.
[![Crates.io](https://img.shields.io/crates/v/exo-node.svg)](https://crates.io/crates/exo-node)
[![Documentation](https://docs.rs/exo-node/badge.svg)](https://docs.rs/exo-node)
[![License](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue.svg)](LICENSE)
## Overview
`exo-node` provides native Node.js bindings:
- **NAPI-RS Bindings**: High-performance native module
- **Async Support**: Full async/await support via Tokio
- **TypeScript Types**: Complete TypeScript definitions
- **Native Performance**: Direct Rust execution
## Installation
```bash
npm install exo-node
```
## Usage
```javascript
const exo = require('exo-node');
// Create consciousness substrate
const substrate = new exo.ConsciousnessSubstrate();
substrate.addPattern(pattern);
const phi = substrate.computePhi();
```
## Links
- [GitHub](https://github.com/ruvnet/ruvector)
- [Website](https://ruv.io)
- [EXO-AI Documentation](https://github.com/ruvnet/ruvector/tree/main/examples/exo-ai-2025)
## License
MIT OR Apache-2.0

View File

@@ -0,0 +1,5 @@
extern crate napi_build;
fn main() {
napi_build::setup();
}

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