# Agent 8: NAPI-RS Node.js Native Bindings ## Overview Create high-performance Node.js bindings for RuVector's GNN latent space attention mechanisms using NAPI-RS, enabling seamless integration with JavaScript/TypeScript applications. ## Project Structure ``` ruvector-node/ ├── Cargo.toml ├── build.rs ├── package.json ├── index.js ├── index.d.ts ├── src/ │ ├── lib.rs │ ├── attention/ │ │ ├── mod.rs │ │ ├── dot_product.rs │ │ ├── multi_head.rs │ │ ├── graph_attention.rs │ │ ├── temporal_attention.rs │ │ └── hierarchical_attention.rs │ ├── types.rs │ ├── error.rs │ └── utils.rs ├── __test__/ │ ├── attention.spec.ts │ ├── batch.spec.ts │ └── benchmark.spec.ts ├── examples/ │ ├── basic-usage.js │ ├── async-batch.js │ └── typescript-example.ts └── .github/ └── workflows/ └── build.yml ``` ## 1. Cargo.toml Configuration ```toml [package] name = "ruvector-node" version = "0.1.0" edition = "2021" authors = ["RuVector Team"] description = "Node.js bindings for RuVector GNN latent space attention mechanisms" license = "MIT" [lib] crate-type = ["cdylib"] [dependencies] # NAPI-RS core napi = { version = "2.16", features = ["async", "tokio_rt"] } napi-derive = "2.16" # Async runtime tokio = { version = "1.35", features = ["full"] } # Core dependencies ndarray = "0.15" rayon = "1.8" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" # Error handling thiserror = "1.0" anyhow = "1.0" # Performance parking_lot = "0.12" [build-dependencies] napi-build = "2.1" [profile.release] lto = true codegen-units = 1 opt-level = 3 strip = true # Platform-specific optimizations [target.'cfg(target_arch = "x86_64")'.dependencies] packed_simd = "0.3" [target.'cfg(target_arch = "aarch64")'.dependencies] packed_simd = "0.3" ``` ## 2. build.rs ```rust extern crate napi_build; fn main() { napi_build::setup(); // Platform-specific optimizations println!("cargo:rerun-if-changed=build.rs"); #[cfg(target_arch = "x86_64")] { println!("cargo:rustc-env=TARGET_ARCH=x86_64"); // Enable AVX2 if available if is_x86_feature_detected!("avx2") { println!("cargo:rustc-cfg=has_avx2"); } } #[cfg(target_arch = "aarch64")] { println!("cargo:rustc-env=TARGET_ARCH=aarch64"); println!("cargo:rustc-cfg=has_neon"); } } #[cfg(target_arch = "x86_64")] fn is_x86_feature_detected(feature: &str) -> bool { std::arch::is_x86_feature_detected(feature) } ``` ## 3. Rust NAPI Bindings ### src/lib.rs ```rust #![deny(clippy::all)] use napi::bindgen_prelude::*; use napi_derive::napi; mod attention; mod error; mod types; mod utils; pub use attention::*; pub use error::*; pub use types::*; /// Initialize the RuVector native module #[napi] pub fn init() -> Result { Ok("RuVector native module initialized".to_string()) } /// Get module version #[napi] pub fn get_version() -> String { env!("CARGO_PKG_VERSION").to_string() } /// Get available SIMD features #[napi] pub fn get_features() -> Vec { let mut features = Vec::new(); #[cfg(target_arch = "x86_64")] { if std::arch::is_x86_feature_detected!("avx2") { features.push("avx2".to_string()); } if std::arch::is_x86_feature_detected!("fma") { features.push("fma".to_string()); } } #[cfg(target_arch = "aarch64")] { features.push("neon".to_string()); } features } ``` ### src/types.rs ```rust use napi::bindgen_prelude::*; use napi_derive::napi; /// Attention configuration options #[napi(object)] #[derive(Debug, Clone)] pub struct AttentionConfig { /// Number of attention heads pub num_heads: Option, /// Dimension of each head pub head_dim: Option, /// Dropout rate pub dropout: Option, /// Whether to use bias in projections pub use_bias: Option, /// Attention scaling factor pub scale: Option, } impl Default for AttentionConfig { fn default() -> Self { Self { num_heads: Some(8), head_dim: Some(64), dropout: Some(0.1), use_bias: Some(true), scale: None, } } } /// Graph structure for attention #[napi(object)] #[derive(Debug, Clone)] pub struct GraphStructure { /// Edge list (source, target pairs) pub edges: Vec>, /// Number of nodes pub num_nodes: u32, /// Edge features (optional) pub edge_features: Option>>, } /// Attention output #[napi(object)] #[derive(Debug, Clone)] pub struct AttentionOutput { /// Attention values pub values: Vec>, /// Attention weights (optional) pub weights: Option>>, /// Metadata pub metadata: Option, } /// Batch processing configuration #[napi(object)] #[derive(Debug, Clone)] pub struct BatchConfig { /// Maximum batch size pub max_batch_size: u32, /// Number of parallel threads pub num_threads: Option, /// Enable progress callbacks pub enable_progress: Option, } ``` ### src/error.rs ```rust use napi::bindgen_prelude::*; use thiserror::Error; #[derive(Error, Debug)] pub enum RuVectorError { #[error("Invalid input: {0}")] InvalidInput(String), #[error("Dimension mismatch: expected {expected}, got {actual}")] DimensionMismatch { expected: usize, actual: usize }, #[error("Computation error: {0}")] ComputationError(String), #[error("Invalid configuration: {0}")] InvalidConfig(String), } impl From for Error { fn from(err: RuVectorError) -> Self { Error::new(Status::GenericFailure, err.to_string()) } } ``` ### src/attention/mod.rs ```rust pub mod dot_product; pub mod multi_head; pub mod graph_attention; pub mod temporal_attention; pub mod hierarchical_attention; pub use dot_product::*; pub use multi_head::*; pub use graph_attention::*; pub use temporal_attention::*; pub use hierarchical_attention::*; ``` ### src/attention/dot_product.rs ```rust use napi::bindgen_prelude::*; use napi_derive::napi; use ndarray::{Array2, ArrayView2}; use crate::{AttentionConfig, AttentionOutput, RuVectorError}; /// Dot-product attention mechanism #[napi] pub struct DotProductAttention { config: AttentionConfig, } #[napi] impl DotProductAttention { /// Create a new dot-product attention instance #[napi(constructor)] pub fn new(config: Option) -> Self { Self { config: config.unwrap_or_default(), } } /// Compute attention (synchronous) #[napi] pub fn compute( &self, query: Vec>, key: Vec>, value: Vec>, ) -> Result { self.compute_internal(query, key, value) .map_err(|e| e.into()) } /// Compute attention (asynchronous) #[napi] pub async fn compute_async( &self, query: Vec>, key: Vec>, value: Vec>, ) -> Result { let config = self.config.clone(); tokio::task::spawn_blocking(move || { Self::compute_internal_static(&config, query, key, value) }) .await .map_err(|e| Error::new(Status::GenericFailure, e.to_string()))? .map_err(|e| e.into()) } /// Batch compute attention #[napi] pub fn compute_batch( &self, queries: Vec>>, keys: Vec>>, values: Vec>>, ) -> Result> { if queries.len() != keys.len() || queries.len() != values.len() { return Err(Error::from(RuVectorError::InvalidInput( "Batch sizes must match".to_string() ))); } queries .into_iter() .zip(keys) .zip(values) .map(|((q, k), v)| self.compute_internal(q, k, v)) .collect::, _>>() .map_err(|e| e.into()) } /// Batch compute attention (asynchronous with parallelism) #[napi] pub async fn compute_batch_async( &self, queries: Vec>>, keys: Vec>>, values: Vec>>, ) -> Result> { if queries.len() != keys.len() || queries.len() != values.len() { return Err(Error::from(RuVectorError::InvalidInput( "Batch sizes must match".to_string() ))); } let config = self.config.clone(); let tasks: Vec<_> = queries .into_iter() .zip(keys) .zip(values) .map(|((q, k), v)| { let config = config.clone(); tokio::task::spawn_blocking(move || { Self::compute_internal_static(&config, q, k, v) }) }) .collect(); let mut results = Vec::new(); for task in tasks { let result = task .await .map_err(|e| Error::new(Status::GenericFailure, e.to_string()))? .map_err(|e: RuVectorError| Error::from(e))?; results.push(result); } Ok(results) } // Internal implementation fn compute_internal( &self, query: Vec>, key: Vec>, value: Vec>, ) -> Result { Self::compute_internal_static(&self.config, query, key, value) } fn compute_internal_static( config: &AttentionConfig, query: Vec>, key: Vec>, value: Vec>, ) -> Result { // Validate dimensions if query.is_empty() || key.is_empty() || value.is_empty() { return Err(RuVectorError::InvalidInput("Empty input".to_string())); } let q_dim = query[0].len(); let k_dim = key[0].len(); let v_dim = value[0].len(); if q_dim != k_dim { return Err(RuVectorError::DimensionMismatch { expected: q_dim, actual: k_dim, }); } // Convert to ndarray let q = Self::vec_to_array2(&query)?; let k = Self::vec_to_array2(&key)?; let v = Self::vec_to_array2(&value)?; // Compute scaled dot-product attention let scale = config.scale.unwrap_or_else(|| (k_dim as f32).sqrt()); // Q @ K^T let scores = q.dot(&k.t()) / scale; // Softmax let weights = Self::softmax(&scores); // Attention @ V let output = weights.dot(&v); // Convert back to Vec let values = Self::array2_to_vec(&output); let weights_vec = Some(Self::array2_to_vec(&weights)); Ok(AttentionOutput { values, weights: weights_vec, metadata: None, }) } fn vec_to_array2(vec: &[Vec]) -> Result, RuVectorError> { if vec.is_empty() { return Err(RuVectorError::InvalidInput("Empty vector".to_string())); } let rows = vec.len(); let cols = vec[0].len(); let flat: Vec = vec.iter().flat_map(|row| row.iter().copied()).collect(); Array2::from_shape_vec((rows, cols), flat) .map_err(|e| RuVectorError::ComputationError(e.to_string())) } fn array2_to_vec(arr: &Array2) -> Vec> { arr.outer_iter() .map(|row| row.to_vec()) .collect() } fn softmax(arr: &Array2) -> Array2 { let mut result = Array2::zeros(arr.raw_dim()); for (i, row) in arr.outer_iter().enumerate() { let max = row.iter().copied().fold(f32::NEG_INFINITY, f32::max); let exp_sum: f32 = row.iter().map(|&x| (x - max).exp()).sum(); for (j, &val) in row.iter().enumerate() { result[[i, j]] = ((val - max).exp()) / exp_sum; } } result } } ``` ### src/attention/multi_head.rs ```rust use napi::bindgen_prelude::*; use napi_derive::napi; use crate::{AttentionConfig, AttentionOutput, RuVectorError}; /// Multi-head attention mechanism #[napi] pub struct MultiHeadAttention { config: AttentionConfig, } #[napi] impl MultiHeadAttention { #[napi(constructor)] pub fn new(config: Option) -> Self { Self { config: config.unwrap_or_default(), } } #[napi] pub fn compute( &self, query: Vec>, key: Vec>, value: Vec>, ) -> Result { self.compute_internal(query, key, value) .map_err(|e| e.into()) } #[napi] pub async fn compute_async( &self, query: Vec>, key: Vec>, value: Vec>, ) -> Result { let config = self.config.clone(); tokio::task::spawn_blocking(move || { Self::compute_internal_static(&config, query, key, value) }) .await .map_err(|e| Error::new(Status::GenericFailure, e.to_string()))? .map_err(|e| e.into()) } #[napi] pub fn compute_batch( &self, queries: Vec>>, keys: Vec>>, values: Vec>>, ) -> Result> { queries .into_iter() .zip(keys) .zip(values) .map(|((q, k), v)| self.compute_internal(q, k, v)) .collect::, _>>() .map_err(|e| e.into()) } #[napi] pub async fn compute_batch_async( &self, queries: Vec>>, keys: Vec>>, values: Vec>>, ) -> Result> { let config = self.config.clone(); let tasks: Vec<_> = queries .into_iter() .zip(keys) .zip(values) .map(|((q, k), v)| { let config = config.clone(); tokio::task::spawn_blocking(move || { Self::compute_internal_static(&config, q, k, v) }) }) .collect(); let mut results = Vec::new(); for task in tasks { results.push( task.await .map_err(|e| Error::new(Status::GenericFailure, e.to_string()))? .map_err(|e: RuVectorError| Error::from(e))? ); } Ok(results) } fn compute_internal( &self, query: Vec>, key: Vec>, value: Vec>, ) -> Result { Self::compute_internal_static(&self.config, query, key, value) } fn compute_internal_static( config: &AttentionConfig, query: Vec>, key: Vec>, value: Vec>, ) -> Result { let num_heads = config.num_heads.unwrap_or(8) as usize; // Simplified multi-head implementation // In production, would split into heads, compute attention per head, concat let values = query.clone(); // Placeholder Ok(AttentionOutput { values, weights: None, metadata: Some(serde_json::json!({ "num_heads": num_heads, "attention_type": "multi_head" })), }) } } ``` ### src/attention/graph_attention.rs ```rust use napi::bindgen_prelude::*; use napi_derive::napi; use crate::{AttentionConfig, AttentionOutput, GraphStructure, RuVectorError}; /// Graph attention network (GAT) mechanism #[napi] pub struct GraphAttention { config: AttentionConfig, } #[napi] impl GraphAttention { #[napi(constructor)] pub fn new(config: Option) -> Self { Self { config: config.unwrap_or_default(), } } #[napi] pub fn compute( &self, node_features: Vec>, graph: GraphStructure, ) -> Result { self.compute_internal(node_features, graph) .map_err(|e| e.into()) } #[napi] pub async fn compute_async( &self, node_features: Vec>, graph: GraphStructure, ) -> Result { let config = self.config.clone(); tokio::task::spawn_blocking(move || { Self::compute_internal_static(&config, node_features, graph) }) .await .map_err(|e| Error::new(Status::GenericFailure, e.to_string()))? .map_err(|e| e.into()) } fn compute_internal( &self, node_features: Vec>, graph: GraphStructure, ) -> Result { Self::compute_internal_static(&self.config, node_features, graph) } fn compute_internal_static( config: &AttentionConfig, node_features: Vec>, graph: GraphStructure, ) -> Result { // Simplified GAT implementation let values = node_features.clone(); // Placeholder Ok(AttentionOutput { values, weights: None, metadata: Some(serde_json::json!({ "num_nodes": graph.num_nodes, "num_edges": graph.edges.len(), "attention_type": "graph" })), }) } } ``` ### src/attention/temporal_attention.rs ```rust use napi::bindgen_prelude::*; use napi_derive::napi; use crate::{AttentionConfig, AttentionOutput, RuVectorError}; /// Temporal attention for sequence data #[napi] pub struct TemporalAttention { config: AttentionConfig, } #[napi] impl TemporalAttention { #[napi(constructor)] pub fn new(config: Option) -> Self { Self { config: config.unwrap_or_default(), } } #[napi] pub fn compute( &self, sequence: Vec>, timestamps: Option>, ) -> Result { self.compute_internal(sequence, timestamps) .map_err(|e| e.into()) } #[napi] pub async fn compute_async( &self, sequence: Vec>, timestamps: Option>, ) -> Result { let config = self.config.clone(); tokio::task::spawn_blocking(move || { Self::compute_internal_static(&config, sequence, timestamps) }) .await .map_err(|e| Error::new(Status::GenericFailure, e.to_string()))? .map_err(|e| e.into()) } fn compute_internal( &self, sequence: Vec>, timestamps: Option>, ) -> Result { Self::compute_internal_static(&self.config, sequence, timestamps) } fn compute_internal_static( _config: &AttentionConfig, sequence: Vec>, timestamps: Option>, ) -> Result { let values = sequence.clone(); // Placeholder Ok(AttentionOutput { values, weights: None, metadata: Some(serde_json::json!({ "sequence_length": sequence.len(), "has_timestamps": timestamps.is_some(), "attention_type": "temporal" })), }) } } ``` ### src/attention/hierarchical_attention.rs ```rust use napi::bindgen_prelude::*; use napi_derive::napi; use crate::{AttentionConfig, AttentionOutput, RuVectorError}; /// Hierarchical attention for multi-level structures #[napi] pub struct HierarchicalAttention { config: AttentionConfig, } #[napi] impl HierarchicalAttention { #[napi(constructor)] pub fn new(config: Option) -> Self { Self { config: config.unwrap_or_default(), } } #[napi] pub fn compute( &self, hierarchical_features: Vec>>, level_weights: Option>, ) -> Result { self.compute_internal(hierarchical_features, level_weights) .map_err(|e| e.into()) } #[napi] pub async fn compute_async( &self, hierarchical_features: Vec>>, level_weights: Option>, ) -> Result { let config = self.config.clone(); tokio::task::spawn_blocking(move || { Self::compute_internal_static(&config, hierarchical_features, level_weights) }) .await .map_err(|e| Error::new(Status::GenericFailure, e.to_string()))? .map_err(|e| e.into()) } fn compute_internal( &self, hierarchical_features: Vec>>, level_weights: Option>, ) -> Result { Self::compute_internal_static(&self.config, hierarchical_features, level_weights) } fn compute_internal_static( _config: &AttentionConfig, hierarchical_features: Vec>>, level_weights: Option>, ) -> Result { if hierarchical_features.is_empty() { return Err(RuVectorError::InvalidInput("Empty hierarchy".to_string())); } // Simplified hierarchical attention let values = hierarchical_features[0].clone(); // Placeholder Ok(AttentionOutput { values, weights: None, metadata: Some(serde_json::json!({ "num_levels": hierarchical_features.len(), "has_level_weights": level_weights.is_some(), "attention_type": "hierarchical" })), }) } } ``` ## 4. TypeScript Definitions ### index.d.ts ```typescript /* tslint:disable */ /* eslint-disable */ /* auto-generated by NAPI-RS */ /** * Initialize the RuVector native module */ export function init(): string /** * Get module version */ export function getVersion(): string /** * Get available SIMD features */ export function getFeatures(): Array /** * Attention configuration options */ export interface AttentionConfig { /** Number of attention heads */ numHeads?: number /** Dimension of each head */ headDim?: number /** Dropout rate */ dropout?: number /** Whether to use bias in projections */ useBias?: boolean /** Attention scaling factor */ scale?: number } /** * Graph structure for attention */ export interface GraphStructure { /** Edge list (source, target pairs) */ edges: Array> /** Number of nodes */ numNodes: number /** Edge features (optional) */ edgeFeatures?: Array> } /** * Attention output */ export interface AttentionOutput { /** Attention values */ values: Array> /** Attention weights (optional) */ weights?: Array> /** Metadata */ metadata?: any } /** * Batch processing configuration */ export interface BatchConfig { /** Maximum batch size */ maxBatchSize: number /** Number of parallel threads */ numThreads?: number /** Enable progress callbacks */ enableProgress?: boolean } /** * Dot-product attention mechanism */ export class DotProductAttention { /** * Create a new dot-product attention instance * @param config Optional attention configuration */ constructor(config?: AttentionConfig) /** * Compute attention (synchronous) * @param query Query matrix [seq_len, dim] * @param key Key matrix [seq_len, dim] * @param value Value matrix [seq_len, dim] * @returns Attention output with values and weights */ compute( query: Array>, key: Array>, value: Array> ): AttentionOutput /** * Compute attention (asynchronous) * @param query Query matrix [seq_len, dim] * @param key Key matrix [seq_len, dim] * @param value Value matrix [seq_len, dim] * @returns Promise resolving to attention output */ computeAsync( query: Array>, key: Array>, value: Array> ): Promise /** * Batch compute attention (synchronous) * @param queries Array of query matrices * @param keys Array of key matrices * @param values Array of value matrices * @returns Array of attention outputs */ computeBatch( queries: Array>>, keys: Array>>, values: Array>> ): Array /** * Batch compute attention (asynchronous with parallelism) * @param queries Array of query matrices * @param keys Array of key matrices * @param values Array of value matrices * @returns Promise resolving to array of attention outputs */ computeBatchAsync( queries: Array>>, keys: Array>>, values: Array>> ): Promise> } /** * Multi-head attention mechanism */ export class MultiHeadAttention { constructor(config?: AttentionConfig) compute( query: Array>, key: Array>, value: Array> ): AttentionOutput computeAsync( query: Array>, key: Array>, value: Array> ): Promise computeBatch( queries: Array>>, keys: Array>>, values: Array>> ): Array computeBatchAsync( queries: Array>>, keys: Array>>, values: Array>> ): Promise> } /** * Graph attention network (GAT) mechanism */ export class GraphAttention { constructor(config?: AttentionConfig) /** * Compute graph attention * @param nodeFeatures Node feature matrix [num_nodes, feature_dim] * @param graph Graph structure with edges and optional edge features * @returns Attention output with updated node features */ compute( nodeFeatures: Array>, graph: GraphStructure ): AttentionOutput computeAsync( nodeFeatures: Array>, graph: GraphStructure ): Promise } /** * Temporal attention for sequence data */ export class TemporalAttention { constructor(config?: AttentionConfig) /** * Compute temporal attention * @param sequence Sequence of feature vectors [seq_len, feature_dim] * @param timestamps Optional timestamps for each sequence element * @returns Attention output with temporal features */ compute( sequence: Array>, timestamps?: Array ): AttentionOutput computeAsync( sequence: Array>, timestamps?: Array ): Promise } /** * Hierarchical attention for multi-level structures */ export class HierarchicalAttention { constructor(config?: AttentionConfig) /** * Compute hierarchical attention * @param hierarchicalFeatures Multi-level features [num_levels][num_items][feature_dim] * @param levelWeights Optional weights for each hierarchy level * @returns Attention output with aggregated features */ compute( hierarchicalFeatures: Array>>, levelWeights?: Array ): AttentionOutput computeAsync( hierarchicalFeatures: Array>>, levelWeights?: Array ): Promise } ``` ## 5. Package.json ```json { "name": "@ruvector/node", "version": "0.1.0", "description": "High-performance Node.js bindings for RuVector GNN latent space attention mechanisms", "main": "index.js", "types": "index.d.ts", "keywords": [ "rust", "napi", "attention", "gnn", "graph-neural-networks", "machine-learning", "vector-database", "native-addon" ], "author": "RuVector Team", "license": "MIT", "repository": { "type": "git", "url": "https://github.com/ruvnet/ruvector" }, "napi": { "name": "ruvector-node", "triples": { "defaults": true, "additional": [ "x86_64-unknown-linux-musl", "aarch64-unknown-linux-gnu", "aarch64-unknown-linux-musl", "aarch64-apple-darwin", "x86_64-apple-darwin", "x86_64-pc-windows-msvc", "aarch64-pc-windows-msvc" ] } }, "engines": { "node": ">= 16" }, "scripts": { "artifacts": "napi artifacts", "build": "napi build --platform --release", "build:debug": "napi build --platform", "prepublishOnly": "napi prepublish -t npm", "test": "vitest run", "test:watch": "vitest", "universal": "napi universal", "version": "napi version", "bench": "node benchmarks/run.js", "lint": "eslint . --ext .ts,.js", "format": "prettier --write .", "typecheck": "tsc --noEmit" }, "devDependencies": { "@napi-rs/cli": "^2.18.0", "@types/node": "^20.10.0", "@typescript-eslint/eslint-plugin": "^6.15.0", "@typescript-eslint/parser": "^6.15.0", "eslint": "^8.56.0", "prettier": "^3.1.1", "typescript": "^5.3.3", "vitest": "^1.0.4" }, "packageManager": "npm@10.2.5", "files": [ "index.js", "index.d.ts", "README.md", "LICENSE" ], "optionalDependencies": { "@ruvector/node-win32-x64-msvc": "0.1.0", "@ruvector/node-darwin-x64": "0.1.0", "@ruvector/node-darwin-arm64": "0.1.0", "@ruvector/node-linux-x64-gnu": "0.1.0", "@ruvector/node-linux-x64-musl": "0.1.0", "@ruvector/node-linux-arm64-gnu": "0.1.0", "@ruvector/node-linux-arm64-musl": "0.1.0" } } ``` ## 6. GitHub Actions Workflow ### .github/workflows/build.yml ```yaml name: Build and Release on: push: branches: [main] tags: - 'v*' pull_request: branches: [main] workflow_dispatch: env: DEBUG: napi:* APP_NAME: ruvector-node MACOSX_DEPLOYMENT_TARGET: '10.13' jobs: build: strategy: fail-fast: false matrix: settings: - host: macos-latest target: x86_64-apple-darwin build: | npm run build strip -x *.node - host: macos-latest target: aarch64-apple-darwin build: | sudo rm -Rf /Library/Developer/CommandLineTools/SDKs/*; export CC=$(xcrun -f clang); export CXX=$(xcrun -f clang++); SYSROOT=$(xcrun --sdk macosx --show-sdk-path); export CFLAGS="-isysroot $SYSROOT -isystem $SYSROOT"; npm run build -- --target aarch64-apple-darwin strip -x *.node - host: ubuntu-latest target: x86_64-unknown-linux-gnu docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian build: | set -e && npm run build -- --target x86_64-unknown-linux-gnu && strip *.node - host: ubuntu-latest target: x86_64-unknown-linux-musl docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine build: | set -e && npm run build && strip *.node - host: ubuntu-latest target: aarch64-unknown-linux-gnu docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian-aarch64 build: | set -e && npm run build -- --target aarch64-unknown-linux-gnu && aarch64-unknown-linux-gnu-strip *.node - host: ubuntu-latest target: aarch64-unknown-linux-musl docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine-aarch64 build: | set -e && rustup target add aarch64-unknown-linux-musl && npm run build -- --target aarch64-unknown-linux-musl && /aarch64-linux-musl-cross/bin/aarch64-linux-musl-strip *.node - host: windows-latest target: x86_64-pc-windows-msvc build: npm run build - host: windows-latest target: aarch64-pc-windows-msvc build: npm run build -- --target aarch64-pc-windows-msvc name: stable - ${{ matrix.settings.target }} - node@20 runs-on: ${{ matrix.settings.host }} steps: - uses: actions/checkout@v4 - name: Setup node uses: actions/setup-node@v4 with: node-version: 20 cache: npm - name: Install Rust uses: dtolnay/rust-toolchain@stable with: toolchain: stable targets: ${{ matrix.settings.target }} - name: Cache cargo uses: actions/cache@v3 with: path: | ~/.cargo/registry/index/ ~/.cargo/registry/cache/ ~/.cargo/git/db/ .cargo-cache target/ key: ${{ matrix.settings.target }}-cargo-${{ matrix.settings.host }} - name: Install dependencies run: npm ci - name: Build in docker uses: addnab/docker-run-action@v3 if: ${{ matrix.settings.docker }} with: image: ${{ matrix.settings.docker }} options: '--user 0:0 -v ${{ github.workspace }}/.cargo-cache/git/db:/usr/local/cargo/git/db -v ${{ github.workspace }}/.cargo/registry/cache:/usr/local/cargo/registry/cache -v ${{ github.workspace }}/.cargo/registry/index:/usr/local/cargo/registry/index -v ${{ github.workspace }}:/build -w /build' run: ${{ matrix.settings.build }} - name: Build run: ${{ matrix.settings.build }} if: ${{ !matrix.settings.docker }} shell: bash - name: Upload artifact uses: actions/upload-artifact@v3 with: name: bindings-${{ matrix.settings.target }} path: ${{ env.APP_NAME }}.*.node if-no-files-found: error test-macOS-windows-binding: name: Test bindings on ${{ matrix.settings.target }} - node@${{ matrix.node }} needs: - build strategy: fail-fast: false matrix: settings: - host: macos-latest target: x86_64-apple-darwin - host: windows-latest target: x86_64-pc-windows-msvc node: - '18' - '20' runs-on: ${{ matrix.settings.host }} steps: - uses: actions/checkout@v4 - name: Setup node uses: actions/setup-node@v4 with: node-version: ${{ matrix.node }} cache: npm - name: Install dependencies run: npm ci - name: Download artifacts uses: actions/download-artifact@v3 with: name: bindings-${{ matrix.settings.target }} path: . - name: List packages run: ls -R . shell: bash - name: Test bindings run: npm test test-linux-x64-gnu-binding: name: Test bindings on Linux-x64-gnu - node@${{ matrix.node }} needs: - build strategy: fail-fast: false matrix: node: - '18' - '20' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup node uses: actions/setup-node@v4 with: node-version: ${{ matrix.node }} cache: npm - name: Install dependencies run: npm ci - name: Download artifacts uses: actions/download-artifact@v3 with: name: bindings-x86_64-unknown-linux-gnu path: . - name: List packages run: ls -R . shell: bash - name: Test bindings run: docker run --rm -v $(pwd):/build -w /build node:${{ matrix.node }}-slim npm test publish: name: Publish runs-on: ubuntu-latest needs: - test-macOS-windows-binding - test-linux-x64-gnu-binding if: startsWith(github.ref, 'refs/tags/v') steps: - uses: actions/checkout@v4 - name: Setup node uses: actions/setup-node@v4 with: node-version: 20 cache: npm - name: Install dependencies run: npm ci - name: Download all artifacts uses: actions/download-artifact@v3 with: path: artifacts - name: Move artifacts run: npm run artifacts - name: List packages run: ls -R ./npm shell: bash - name: Publish run: | npm config set provenance true if git log -1 --pretty=%B | grep "^[0-9]\+\.[0-9]\+\.[0-9]\+$"; then echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc npm publish --access public elif git log -1 --pretty=%B | grep "^[0-9]\+\.[0-9]\+\.[0-9]\+"; then echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc npm publish --tag next --access public else echo "Not a release, skipping publish" fi env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} ``` ## 7. Testing Strategy ### __test__/attention.spec.ts ```typescript import { describe, it, expect } from 'vitest' import { DotProductAttention, MultiHeadAttention, GraphAttention, TemporalAttention, HierarchicalAttention, AttentionConfig, GraphStructure } from '../index' describe('DotProductAttention', () => { it('should compute attention synchronously', () => { const attention = new DotProductAttention() const query = [[1, 2], [3, 4]] const key = [[1, 2], [3, 4]] const value = [[5, 6], [7, 8]] const result = attention.compute(query, key, value) expect(result.values).toBeDefined() expect(result.values.length).toBe(2) expect(result.weights).toBeDefined() }) it('should compute attention asynchronously', async () => { const attention = new DotProductAttention() const query = [[1, 2], [3, 4]] const key = [[1, 2], [3, 4]] const value = [[5, 6], [7, 8]] const result = await attention.computeAsync(query, key, value) expect(result.values).toBeDefined() expect(result.values.length).toBe(2) }) it('should compute batch attention', () => { const attention = new DotProductAttention() const queries = [[[1, 2]], [[3, 4]]] const keys = [[[1, 2]], [[3, 4]]] const values = [[[5, 6]], [[7, 8]]] const results = attention.computeBatch(queries, keys, values) expect(results.length).toBe(2) expect(results[0].values).toBeDefined() }) it('should compute batch attention asynchronously', async () => { const attention = new DotProductAttention() const queries = [[[1, 2]], [[3, 4]]] const keys = [[[1, 2]], [[3, 4]]] const values = [[[5, 6]], [[7, 8]]] const results = await attention.computeBatchAsync(queries, keys, values) expect(results.length).toBe(2) }) it('should accept custom configuration', () => { const config: AttentionConfig = { numHeads: 4, headDim: 32, dropout: 0.2, scale: 0.5 } const attention = new DotProductAttention(config) expect(attention).toBeDefined() }) }) describe('GraphAttention', () => { it('should compute graph attention', () => { const attention = new GraphAttention() const nodeFeatures = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] const graph: GraphStructure = { edges: [[0, 1], [1, 2], [2, 0]], numNodes: 3 } const result = attention.compute(nodeFeatures, graph) expect(result.values).toBeDefined() expect(result.metadata).toBeDefined() expect(result.metadata.attention_type).toBe('graph') }) it('should compute graph attention asynchronously', async () => { const attention = new GraphAttention() const nodeFeatures = [[1, 2, 3], [4, 5, 6]] const graph: GraphStructure = { edges: [[0, 1]], numNodes: 2 } const result = await attention.computeAsync(nodeFeatures, graph) expect(result.values).toBeDefined() }) }) describe('TemporalAttention', () => { it('should compute temporal attention', () => { const attention = new TemporalAttention() const sequence = [[1, 2], [3, 4], [5, 6]] const result = attention.compute(sequence) expect(result.values).toBeDefined() expect(result.metadata.sequence_length).toBe(3) }) it('should handle timestamps', () => { const attention = new TemporalAttention() const sequence = [[1, 2], [3, 4]] const timestamps = [0.0, 1.0] const result = attention.compute(sequence, timestamps) expect(result.metadata.has_timestamps).toBe(true) }) }) describe('HierarchicalAttention', () => { it('should compute hierarchical attention', () => { const attention = new HierarchicalAttention() const hierarchicalFeatures = [ [[1, 2], [3, 4]], [[5, 6], [7, 8]] ] const result = attention.compute(hierarchicalFeatures) expect(result.values).toBeDefined() expect(result.metadata.num_levels).toBe(2) }) }) ``` ### __test__/benchmark.spec.ts ```typescript import { describe, it, expect } from 'vitest' import { DotProductAttention } from '../index' describe('Performance Benchmarks', () => { it('should handle large matrices efficiently', () => { const attention = new DotProductAttention() const size = 1000 const dim = 512 // Generate random matrices const query = Array.from({ length: size }, () => Array.from({ length: dim }, () => Math.random()) ) const key = Array.from({ length: size }, () => Array.from({ length: dim }, () => Math.random()) ) const value = Array.from({ length: size }, () => Array.from({ length: dim }, () => Math.random()) ) const start = Date.now() const result = attention.compute(query, key, value) const duration = Date.now() - start expect(result.values).toBeDefined() expect(duration).toBeLessThan(5000) // Should complete in < 5 seconds }) it('async should be faster for large batches', async () => { const attention = new DotProductAttention() const batchSize = 100 const queries = Array.from({ length: batchSize }, () => [[1, 2, 3], [4, 5, 6]] ) const keys = queries const values = queries const syncStart = Date.now() attention.computeBatch(queries, keys, values) const syncDuration = Date.now() - syncStart const asyncStart = Date.now() await attention.computeBatchAsync(queries, keys, values) const asyncDuration = Date.now() - asyncStart console.log(`Sync: ${syncDuration}ms, Async: ${asyncDuration}ms`) expect(asyncDuration).toBeLessThanOrEqual(syncDuration * 1.5) }) }) ``` ## 8. Usage Examples ### examples/basic-usage.js ```javascript const { init, getVersion, getFeatures, DotProductAttention } = require('@ruvector/node') // Initialize console.log(init()) console.log('Version:', getVersion()) console.log('Features:', getFeatures()) // Create attention instance const attention = new DotProductAttention({ numHeads: 8, headDim: 64, dropout: 0.1 }) // Compute attention const query = [[1, 2, 3], [4, 5, 6]] const key = [[1, 2, 3], [4, 5, 6]] const value = [[7, 8, 9], [10, 11, 12]] const result = attention.compute(query, key, value) console.log('Attention output:', result.values) console.log('Attention weights:', result.weights) ``` ### examples/async-batch.js ```javascript const { DotProductAttention } = require('@ruvector/node') async function main() { const attention = new DotProductAttention() // Prepare batch data const batchSize = 10 const queries = Array.from({ length: batchSize }, (_, i) => [[i, i+1], [i+2, i+3]] ) const keys = queries const values = queries console.log('Processing batch asynchronously...') const start = Date.now() const results = await attention.computeBatchAsync(queries, keys, values) const duration = Date.now() - start console.log(`Processed ${batchSize} items in ${duration}ms`) console.log('Results:', results.length) } main().catch(console.error) ``` ### examples/typescript-example.ts ```typescript import { DotProductAttention, GraphAttention, AttentionConfig, GraphStructure, AttentionOutput } from '@ruvector/node' // Type-safe configuration const config: AttentionConfig = { numHeads: 8, headDim: 64, dropout: 0.1, useBias: true } // Dot-product attention const dotAttention = new DotProductAttention(config) const output: AttentionOutput = dotAttention.compute( [[1, 2], [3, 4]], [[1, 2], [3, 4]], [[5, 6], [7, 8]] ) console.log('Attention values:', output.values) // Graph attention const graphAttention = new GraphAttention(config) const graph: GraphStructure = { edges: [[0, 1], [1, 2]], numNodes: 3, edgeFeatures: [[1.0], [2.0]] } const graphOutput = graphAttention.compute( [[1, 2, 3], [4, 5, 6], [7, 8, 9]], graph ) console.log('Graph attention:', graphOutput.metadata) // Async with proper typing async function processAsync(): Promise { const result = await dotAttention.computeAsync( [[1, 2]], [[1, 2]], [[3, 4]] ) console.log('Async result:', result) } processAsync() ``` ## 9. Documentation ### README.md ```markdown # @ruvector/node High-performance Node.js bindings for RuVector's GNN latent space attention mechanisms. ## Features - ⚡ **Blazing Fast**: Native Rust implementation with SIMD optimizations - 🔄 **Async Support**: Non-blocking async methods with Tokio runtime - 📦 **Batch Processing**: Efficient parallel batch operations - 🎯 **Multiple Attention Types**: Dot-product, multi-head, graph, temporal, hierarchical - 🔒 **Type Safe**: Full TypeScript definitions - 🌐 **Cross-Platform**: Pre-built binaries for all major platforms ## Installation ```bash npm install @ruvector/node ``` ## Quick Start ```javascript const { DotProductAttention } = require('@ruvector/node') const attention = new DotProductAttention() const result = attention.compute(query, key, value) ``` ## API Reference See [index.d.ts](./index.d.ts) for complete API documentation. ## Performance - **SIMD Optimized**: AVX2 on x86_64, NEON on ARM64 - **Parallel Processing**: Multi-threaded batch operations - **Zero-Copy**: Efficient memory handling ## License MIT ``` ## Implementation Checklist ### Phase 1: Core Setup - [ ] Initialize NAPI-RS project with Cargo.toml - [ ] Configure build.rs for platform detection - [ ] Set up basic project structure - [ ] Implement error handling types - [ ] Create TypeScript definitions ### Phase 2: Attention Mechanisms - [ ] Implement DotProductAttention - [ ] Implement MultiHeadAttention - [ ] Implement GraphAttention - [ ] Implement TemporalAttention - [ ] Implement HierarchicalAttention - [ ] Add sync/async variants for all ### Phase 3: Testing - [ ] Write unit tests for each attention type - [ ] Add integration tests - [ ] Create benchmark suite - [ ] Test all platforms ### Phase 4: Build & CI/CD - [ ] Set up GitHub Actions workflow - [ ] Configure multi-platform builds - [ ] Add automated testing - [ ] Set up npm publishing ### Phase 5: Documentation - [ ] Write comprehensive README - [ ] Add usage examples - [ ] Document all APIs - [ ] Create migration guide ## Performance Targets - **Dot-Product Attention**: < 10ms for 1000x512 matrices - **Batch Processing**: < 100ms for 100 items (async) - **Memory Efficiency**: < 2x input size overhead - **SIMD Speedup**: 2-4x over scalar implementation ## Platform Support - ✅ Linux x64 (GNU/MUSL) - ✅ Linux ARM64 (GNU/MUSL) - ✅ macOS x64 - ✅ macOS ARM64 (Apple Silicon) - ✅ Windows x64 - ✅ Windows ARM64 ## Dependencies - **napi-rs**: 2.16+ (Node.js bindings) - **tokio**: 1.35+ (Async runtime) - **ndarray**: 0.15+ (Linear algebra) - **rayon**: 1.8+ (Parallelism) ## Integration Points - **Agent 4**: Uses core Rust attention implementations - **Agent 6**: Integrates with Python bindings - **Agent 7**: Provides C++ FFI layer - **Agent 9**: TypeScript SDK consumer