* fix: batch 1 — deadlock, AVX-512 gating, Windows case-collisions
Closes #437: VectorDb::delete in ruvector-router-core acquired the stats
RwLock twice in one statement. parking_lot::RwLock is non-reentrant, so
the second .write() deadlocked against the first guard's lifetime. Bind
the guard once.
Closes #438: Gate AVX-512 intrinsics behind a new `simd-avx512` Cargo
feature (default-on). Lets downstream consumers on stable Rust 1.77–1.88
(before avx512f stabilization in 1.89) opt out without forcing nightly:
cargo build --no-default-features --features simd,storage,hnsw,api-embeddings,parallel
Runtime dispatch falls back to AVX2 + FMA when the feature is disabled.
All 4 #[target_feature(enable = "avx512f")] sites + 4 dispatch branches
updated. Both feature configurations verified to compile cleanly; all
18 simd_intrinsics tests pass.
Closes #458: Rename two pairs of case-colliding research artifacts under
docs/research/claude-code-rvsource/versions/v2.1.x/tree/react_memo_cache_sentinel/
that broke `git clone` on Windows/NTFS:
tmux.js → tmux_lc.js (TMUX.js kept)
type.js → type_lc.js (Type.js kept)
modules-manifest.json updated to match.
Co-Authored-By: claude-flow <ruv@ruv.net>
* fix(brain): observable hydration + larger page-error budget (issue #464)
Bisect outcome: source diff between the 2026-04-14 working revision
(00203-brv → 22,005 memories) and current main (00204-92l → 10,227)
is whitespace-only (cargo fmt 2026-04-24 + clippy 2026-04-25). No
semantic change in store.rs, types.rs, or graph.rs. BrainMemory schema
is byte-identical. So the regression is environmental, surfacing
through a code path that has no observability today.
Two changes:
1. load_from_firestore() now emits per-collection counters so the next
deploy is diagnosable instead of a black box:
Hydrate brain_memories: considered=N accepted=M rejected_parse=K
First 5 parse errors are logged with the serde_json error so any
live schema drift surfaces immediately.
2. firestore_list MAX_PAGE_ERRORS raised 3 → 8. Hydration crosses ~75
pages of 300 docs each; 3 transient OAuth-refresh blips at the
wrong moment terminated the load at ~10K, consistent with the
reported 10,227 number. 8 still bounds runaway behaviour while
tolerating realistic blip rates.
The actual environmental cause is recoverable from one deploy with the
new logs in place. Until then, traffic stays on 00203-brv (which is
what the rollback already did).
Co-Authored-By: claude-flow <ruv@ruv.net>
* fix(router-core): HNSW result-heap inversion, prune drops oldest, k > ef_search (#430)
Three correctness bugs in crates/ruvector-router-core/src/index.rs that
together collapsed recall@1 at scale:
1. `Neighbor::Ord` is reversed so BinaryHeap acts as a min-heap. Correct
for `candidates` (pop closest unexplored first), but WRONG for the
`result` heap — peek returned the BEST candidate, so the eviction
path kept dropping the best item instead of the worst whenever the
set was full. Wrap result in `std::cmp::Reverse<Neighbor>` so
peek/pop return the furthest item (the actual eviction target). This
is the primary recall@1 fix.
2. Per-insert connection pruning used `truncate(m)`, which keeps the
OLDEST m connections — including dropping the just-pushed edge when
it landed past index m. Switch to `drain(0..len-m)` so the freshly
inserted edge always survives.
3. `search()` capped at `ef_search` regardless of caller's k. With
default ef_search=10 and k=25, results were silently 10. Raise ef
to `max(ef_search, k)` before invoking search_knn_internal.
New tests:
- `test_recall_at_1_with_biased_insertion_order`: 1024 vectors,
biased insertion order (the topology that historically exposed the
bug); asserts recall@1 ≥ 95% AND ≥ 80% distinct ids across queries.
- `test_k_exceeds_ef_search_default`: 50 vectors, default ef_search=10,
k=25; asserts 25 results returned.
All 19 router-core tests pass.
Co-Authored-By: claude-flow <ruv@ruv.net>
* fix(npm): publish pipeline — dist/ guaranteed + dual ESM/CJS pi-brain (#462/#415/#376/#372)
@ruvector/pi-brain 0.1.1 → 0.1.2 (closes #462, #372):
* Add `prepack` hook so dist/ is always built before publish — tarballs
on 0.1.0/0.1.1 shipped without dist/ because `tsc` never ran.
* Add a second tsconfig (tsconfig.cjs.json) that emits CommonJS to
dist/cjs/ alongside the ESM build in dist/. A generated
dist/cjs/package.json carries {"type":"commonjs"} so Node treats
that subtree as CJS regardless of the package-level "type":"module".
* Expand the exports map with import + require + default conditions
so ruvector@0.2.x's CJS MCP server (Node 20.x, no require(ESM)
until 22.12) can require() the package. Add subpath exports for
./mcp and ./client.
* Verified locally: dist/cjs/index.js loads via `require()` and
dist/index.js loads via dynamic `import()`.
@ruvector/rvf-wasm 0.1.5 → 0.1.6 (closes #415):
* pkg/rvf_wasm.js contains ESM syntax (`import.meta.url`,
`export default`). The old exports map pointed `require` at this
file, which fails on every CJS consumer. Mark the package
explicitly `"type": "module"`, drop the `require` condition (the
`.mjs` build is the canonical one), and add a `./wasm` subpath for
consumers that want the raw bytes.
ruvector npm 0.2.25 (extends #376 mitigation):
* Add `prepack` mirroring `prepublishOnly` so `npm pack` (and CI
smoke tests that run pack) regenerate dist/ + run verify-dist.
Without this, `npm pack` skips prepublishOnly, masking
missing-dist regressions until publish.
Co-Authored-By: claude-flow <ruv@ruv.net>
* fix(mcp): hooks_route_enhanced in-process — drop spawnSync (#463/#422)
The hooks_route_enhanced MCP tool shelled out via
execSync('npx ruvector hooks route-enhanced …', { timeout: 30000 })
which deterministically timed out: npx's package-resolution and
bin-launch overhead can spike past 30s on cold-cache machines, even
though the underlying work finishes in ~500ms. Callers got
deterministic `spawnSync /bin/sh ETIMEDOUT`.
The sibling hooks_route tool (reported as working in #463) uses
intel.route() directly. Mirror that pattern: call intel.route(), then
inline the same coverage-router + AST-parser signal enrichment the CLI
does. No subprocess, no timeout, no npx dependency.
Falls back gracefully when coverage-router or ast-parser aren't
installed (try/catch around each optional enhancement, same as the
CLI handler).
Co-Authored-By: claude-flow <ruv@ruv.net>
* ci: regression guard for 9 issues + fixes for 5 latent regressions it surfaced
New workflow .github/workflows/regression-guard.yml runs on every push +
PR. Each job pins one of these issue classes shut:
#437 reentrant-rwlock-double-write
Forbids `x.write()…x.(write|read)()` and `x.read()…x.write()` in
a single statement (parking_lot is non-reentrant). PCRE
backreference matches only same-lock cases.
#458 case-insensitive-collisions
Fails if `git ls-files` has any two paths that match after
lowercasing — Windows clones drop one of each silently.
#438 ruvector-core-no-avx512-builds-on-stable
cargo check ruvector-core with AND without the simd-avx512
feature so the AVX-512 gating doesn't regress.
#430 hnsw-recall-at-1
Runs the new recall@1 (biased insertion / 1024 vectors) test
and the k > ef_search test in release mode.
#462 / #376 npm-publish-pipeline
npm pack each shipped package and assert every entry referenced
by main/module/types/exports is actually inside the tarball.
#463 / #422 no-npx-execSync-in-mcp-server
Forbids execSync('npx ruvector …') anywhere in the MCP server.
#256 shell-injection-in-mcp-server
Flags any exec*/spawn* call that interpolates ${args.X} without
wrapping in sanitizeShellArg(...).
#267 no-systemtime-in-wasm-crates
Crates named *wasm* with ungated SystemTime::now / Instant::now
calls are rejected (the wasm32-unknown-unknown panic class).
#359 no-hardcoded-workspaces-paths
Devcontainer-only `/workspaces/ruvector` literals are banned
from .github/workflows, .claude/settings*, and scripts/publish/.
Adding the guard surfaced five real, already-present regressions of
these classes — fixed in this commit:
* crates/prime-radiant/src/coherence/engine.rs (3 sites):
self.stats.write().X = self.stats.read().X - 1 in the same
statement — exactly issue #437's shape on a different lock. Bind
the write guard once.
* crates/ruvector-wasm/src/lib.rs:465 (benchmark fn):
used std::time::Instant which panics on wasm32 (issue #267).
Switch to js_sys::Date::now().
* scripts/publish/publish-router-wasm.sh + check-and-publish-router-wasm.sh:
hardcoded /workspaces/ruvector paths (issue #359). Resolve REPO_ROOT
from BASH_SOURCE instead.
Co-Authored-By: claude-flow <ruv@ruv.net>
* ci: narrow scope of two guards to avoid pre-existing-debt false positives
After the first PR run two guards caught existing technical debt rather
than fresh regressions:
* no-npx-execSync-in-mcp-server flagged 10 other execSync('npx
ruvector …') sites (ast-analyze, coverage-route, graph-mincut,
security-scan, git-churn, …) which predate issue #463 and are a
distinct concern (some legitimately need subprocess). Narrow the
guard to the EXACT regression — execSync inside the
hooks_route_enhanced case body — using awk to extract that case's
body before grepping. Rename: no-npx-execSync-in-route-enhanced.
* npm-publish-pipeline failed at npm install (peer-dep ERESOLVE).
Add --legacy-peer-deps. The point of this guard is the tarball
content, not the install graph.
Co-Authored-By: claude-flow <ruv@ruv.net>
* style: cargo fmt --all (mechanical, pre-existing diffs on main + my new code)
Workspace had 11 files with rustfmt diffs predating this branch, plus
one new diff in store.rs from the hydration counters added in
|
||
|---|---|---|
| .. | ||
| .cargo | ||
| kernels | ||
| src | ||
| tests | ||
| Cargo.toml | ||
| INTEGRATION_STATUS.md | ||
| package.json | ||
| README.md | ||
Ruvector WASM
High-performance vector database running entirely in your browser via WebAssembly.
Bring sub-millisecond vector search to the edge with offline-first capabilities. Perfect for AI applications, semantic search, and recommendation engines that run completely client-side. Built by rUv with Rust and WebAssembly.
🌟 Why Ruvector WASM?
In the age of privacy-first, offline-capable web applications, running AI workloads entirely in the browser is no longer optional—it's essential.
Ruvector WASM brings enterprise-grade vector search to the browser:
- ⚡ Blazing Fast: <1ms query latency with HNSW indexing and SIMD acceleration
- 🔒 Privacy First: All data stays in the browser—zero server round-trips
- 📴 Offline Capable: Full functionality without internet via IndexedDB persistence
- 🌐 Edge Computing: Deploy to CDNs for ultra-low latency globally
- 💾 Persistent Storage: IndexedDB integration with automatic synchronization
- 🧵 Multi-threaded: Web Workers support for parallel processing
- 📦 Compact: <400KB gzipped with optimizations
- 🎯 Zero Dependencies: Pure Rust compiled to WebAssembly
🚀 Features
Core Capabilities
- Complete VectorDB API: Insert, search, delete, batch operations with familiar patterns
- HNSW Indexing: Hierarchical Navigable Small World for fast approximate nearest neighbor search
- Multiple Distance Metrics: Euclidean, Cosine, Dot Product, Manhattan
- SIMD Acceleration: 2-4x speedup on supported hardware with automatic detection
- Memory Efficient: Optimized memory layouts and zero-copy operations
- Type-Safe: Full TypeScript definitions included
Browser-Specific Features
- IndexedDB Persistence: Save/load database state with progressive loading
- Web Workers Integration: Parallel operations across multiple threads
- Worker Pool Management: Automatic load balancing across 4-8 workers
- Zero-Copy Transfers: Transferable objects for efficient data passing
- Browser Console Debugging: Enhanced error messages and stack traces
- Progressive Web Apps: Perfect for PWA offline scenarios
Performance Optimizations
- Batch Operations: Efficient bulk insert/search for large datasets
- LRU Caching: 1000-entry hot vector cache for frequently accessed data
- Lazy Loading: Progressive data loading with callbacks
- Compressed Storage: Optimized serialization for IndexedDB
- WASM Streaming: Compile WASM modules while downloading
📦 Installation
NPM
npm install @ruvector/wasm
Yarn
yarn add @ruvector/wasm
CDN (for quick prototyping)
<script type="module">
import init, { VectorDB } from 'https://unpkg.com/@ruvector/wasm/pkg/ruvector_wasm.js';
await init();
const db = new VectorDB(384, 'cosine', true);
</script>
⚡ Quick Start
Basic Usage
import init, { VectorDB } from '@ruvector/wasm';
// 1. Initialize WASM module (one-time setup)
await init();
// 2. Create database with 384-dimensional vectors
const db = new VectorDB(
384, // dimensions
'cosine', // distance metric
true // enable HNSW index
);
// 3. Insert vectors with metadata
const embedding = new Float32Array(384).map(() => Math.random());
const id = db.insert(
embedding,
'doc_1', // optional ID
{ title: 'My Document', type: 'article' } // optional metadata
);
// 4. Search for similar vectors
const query = new Float32Array(384).map(() => Math.random());
const results = db.search(query, 10); // top 10 results
// 5. Process results
results.forEach(result => {
console.log(`ID: ${result.id}`);
console.log(`Score: ${result.score}`);
console.log(`Metadata:`, result.metadata);
});
React Integration
import { useEffect, useState } from 'react';
import init, { VectorDB } from '@ruvector/wasm';
function SemanticSearch() {
const [db, setDb] = useState<VectorDB | null>(null);
const [results, setResults] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
// Initialize WASM and create database
init().then(() => {
const vectorDB = new VectorDB(384, 'cosine', true);
setDb(vectorDB);
setLoading(false);
});
}, []);
const handleSearch = async (queryEmbedding: Float32Array) => {
if (!db) return;
const searchResults = db.search(queryEmbedding, 10);
setResults(searchResults);
};
if (loading) return <div>Loading vector database...</div>;
return (
<div>
<h1>Semantic Search</h1>
{/* Your search UI */}
</div>
);
}
Vue.js Integration
<template>
<div>
<h1>Vector Search</h1>
<div v-if="!dbReady">Initializing...</div>
<div v-else>
<button @click="search">Search</button>
<ul>
<li v-for="result in results" :key="result.id">
{{ result.id }}: {{ result.score }}
</li>
</ul>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import init, { VectorDB } from '@ruvector/wasm';
const db = ref(null);
const dbReady = ref(false);
const results = ref([]);
onMounted(async () => {
await init();
db.value = new VectorDB(384, 'cosine', true);
dbReady.value = true;
});
const search = () => {
const query = new Float32Array(384).map(() => Math.random());
results.value = db.value.search(query, 10);
};
</script>
Svelte Integration
<script>
import { onMount } from 'svelte';
import init, { VectorDB } from '@ruvector/wasm';
let db = null;
let ready = false;
let results = [];
onMount(async () => {
await init();
db = new VectorDB(384, 'cosine', true);
ready = true;
});
function search() {
const query = new Float32Array(384).map(() => Math.random());
results = db.search(query, 10);
}
</script>
{#if !ready}
<p>Loading...</p>
{:else}
<button on:click={search}>Search</button>
{#each results as result}
<div>{result.id}: {result.score}</div>
{/each}
{/if}
🔥 Advanced Usage
Web Workers for Background Processing
Offload heavy vector operations to background threads for smooth UI performance:
// main.js
import { WorkerPool } from '@ruvector/wasm/worker-pool';
const pool = new WorkerPool(
'/worker.js',
'/pkg/ruvector_wasm.js',
{
poolSize: navigator.hardwareConcurrency || 4, // Auto-detect CPU cores
dimensions: 384,
metric: 'cosine',
useHnsw: true
}
);
// Initialize worker pool
await pool.init();
// Batch insert in parallel (non-blocking)
const vectors = generateVectors(10000, 384);
const ids = await pool.insertBatch(vectors);
// Parallel search across workers
const query = new Float32Array(384).map(() => Math.random());
const results = await pool.search(query, 100);
// Get pool statistics
const stats = pool.getStats();
console.log(`Workers: ${stats.busyWorkers}/${stats.poolSize} busy`);
console.log(`Queue: ${stats.queuedTasks} tasks waiting`);
// Cleanup when done
pool.terminate();
// worker.js - Web Worker implementation
importScripts('/pkg/ruvector_wasm.js');
const { VectorDB } = wasm_bindgen;
let db = null;
self.onmessage = async (e) => {
const { type, data } = e.data;
switch (type) {
case 'init':
await wasm_bindgen('/pkg/ruvector_wasm_bg.wasm');
db = new VectorDB(data.dimensions, data.metric, data.useHnsw);
self.postMessage({ type: 'ready' });
break;
case 'insert':
const id = db.insert(data.vector, data.id, data.metadata);
self.postMessage({ type: 'inserted', id });
break;
case 'search':
const results = db.search(data.query, data.k);
self.postMessage({ type: 'results', results });
break;
}
};
IndexedDB Persistence - Offline First
Keep your vector database synchronized across sessions:
import { IndexedDBPersistence } from '@ruvector/wasm/indexeddb';
import init, { VectorDB } from '@ruvector/wasm';
await init();
// Create persistence layer
const persistence = new IndexedDBPersistence('my_vector_db', {
version: 1,
cacheSize: 1000, // LRU cache for hot vectors
batchSize: 100 // Batch size for bulk operations
});
await persistence.open();
// Create or restore VectorDB
const db = new VectorDB(384, 'cosine', true);
// Load existing data from IndexedDB (with progress)
await persistence.loadAll(async (progress) => {
console.log(`Loading: ${progress.loaded}/${progress.total} vectors`);
console.log(`Progress: ${(progress.percent * 100).toFixed(1)}%`);
// Insert batch into VectorDB
if (progress.vectors.length > 0) {
const ids = db.insertBatch(progress.vectors);
console.log(`Inserted ${ids.length} vectors`);
}
if (progress.complete) {
console.log('Database fully loaded!');
}
});
// Insert new vectors and save to IndexedDB
const vector = new Float32Array(384).map(() => Math.random());
const id = db.insert(vector, 'vec_123', { category: 'new' });
await persistence.save({
id,
vector,
metadata: { category: 'new' }
});
// Batch save for better performance
const entries = [...]; // Your vector entries
await persistence.saveBatch(entries);
// Get storage statistics
const stats = await persistence.getStats();
console.log(`Total vectors: ${stats.totalVectors}`);
console.log(`Storage used: ${(stats.storageBytes / 1024 / 1024).toFixed(2)} MB`);
console.log(`Cache size: ${stats.cacheSize}`);
console.log(`Cache hit rate: ${(stats.cacheHitRate * 100).toFixed(2)}%`);
// Clear old data
await persistence.clear();
Batch Operations for Performance
Process large datasets efficiently:
import init, { VectorDB } from '@ruvector/wasm';
await init();
const db = new VectorDB(384, 'cosine', true);
// Batch insert (10x faster than individual inserts)
const entries = [];
for (let i = 0; i < 10000; i++) {
entries.push({
vector: new Float32Array(384).map(() => Math.random()),
id: `vec_${i}`,
metadata: { index: i, batch: Math.floor(i / 100) }
});
}
const ids = db.insertBatch(entries);
console.log(`Inserted ${ids.length} vectors in batch`);
// Multiple parallel searches
const queries = Array.from({ length: 100 }, () =>
new Float32Array(384).map(() => Math.random())
);
const allResults = queries.map(query => db.search(query, 10));
console.log(`Completed ${allResults.length} searches`);
Memory Management Best Practices
import init, { VectorDB } from '@ruvector/wasm';
await init();
// Reuse Float32Array buffers to reduce GC pressure
const buffer = new Float32Array(384);
// Insert with reused buffer
for (let i = 0; i < 1000; i++) {
// Fill buffer with new data
for (let j = 0; j < 384; j++) {
buffer[j] = Math.random();
}
db.insert(buffer, `vec_${i}`, { index: i });
// Buffer is copied internally, safe to reuse
}
// Check memory usage
const vectorCount = db.len();
const isEmpty = db.isEmpty();
const dimensions = db.dimensions;
console.log(`Vectors: ${vectorCount}, Dims: ${dimensions}`);
// Clean up when done
// JavaScript GC will handle WASM memory automatically
📊 Performance Benchmarks
Browser Performance (Chrome 120 on M1 MacBook Pro)
| Operation | Vectors | Dimensions | Standard | SIMD | Speedup |
|---|---|---|---|---|---|
| Insert (individual) | 10,000 | 384 | 3.2s | 1.1s | 2.9x |
| Insert (batch) | 10,000 | 384 | 1.2s | 0.4s | 3.0x |
| Search (k=10) | 100 queries | 384 | 0.5s | 0.2s | 2.5x |
| Search (k=100) | 100 queries | 384 | 1.8s | 0.7s | 2.6x |
| Delete | 1,000 | 384 | 0.2s | 0.1s | 2.0x |
Throughput Comparison
Operation Ruvector WASM Tensorflow.js ml5.js
─────────────────────────────────────────────────────────────────
Insert (ops/sec) 25,000 5,000 1,200
Search (queries/sec) 500 80 20
Memory (10K vectors) ~50MB ~200MB ~150MB
Bundle Size (gzipped) 380KB 800KB 450KB
Offline Support ✅ Partial ❌
SIMD Acceleration ✅ ❌ ❌
Real-World Application Performance
Semantic Search (10,000 documents, 384-dim embeddings)
- Cold start: ~800ms (WASM compile + data load)
- Warm query: <5ms (with HNSW index)
- IndexedDB load: ~2s (10,000 vectors)
- Memory footprint: ~60MB
Recommendation Engine (100,000 items, 128-dim embeddings)
- Initial load: ~8s from IndexedDB
- Query latency: <10ms (p50)
- Memory usage: ~180MB
- Bundle impact: +400KB gzipped
🌐 Browser Compatibility
Support Matrix
| Browser | Version | WASM | SIMD | Workers | IndexedDB | Status |
|---|---|---|---|---|---|---|
| Chrome | 91+ | ✅ | ✅ | ✅ | ✅ | Full Support |
| Firefox | 89+ | ✅ | ✅ | ✅ | ✅ | Full Support |
| Safari | 16.4+ | ✅ | Partial | ✅ | ✅ | Limited SIMD |
| Edge | 91+ | ✅ | ✅ | ✅ | ✅ | Full Support |
| Opera | 77+ | ✅ | ✅ | ✅ | ✅ | Full Support |
| Samsung Internet | 15+ | ✅ | ❌ | ✅ | ✅ | No SIMD |
SIMD Support Detection
import { detectSIMD } from '@ruvector/wasm';
if (detectSIMD()) {
console.log('SIMD acceleration available!');
// Load SIMD-optimized build
await import('@ruvector/wasm/pkg-simd/ruvector_wasm.js');
} else {
console.log('Standard build');
// Load standard build
await import('@ruvector/wasm');
}
Polyfills and Fallbacks
// Check for required features
const hasWASM = typeof WebAssembly !== 'undefined';
const hasWorkers = typeof Worker !== 'undefined';
const hasIndexedDB = typeof indexedDB !== 'undefined';
if (!hasWASM) {
console.error('WebAssembly not supported');
// Fallback to server-side processing
}
if (!hasWorkers) {
console.warn('Web Workers not available, using main thread');
// Use synchronous API
}
if (!hasIndexedDB) {
console.warn('IndexedDB not available, data will not persist');
// Use in-memory only
}
📦 Bundle Size
Production Build Sizes
Build Type Uncompressed Gzipped Brotli
──────────────────────────────────────────────────────────
Standard WASM 1.2 MB 450 KB 380 KB
SIMD WASM 1.3 MB 480 KB 410 KB
JavaScript Glue 45 KB 12 KB 9 KB
TypeScript Definitions 8 KB 2 KB 1.5 KB
──────────────────────────────────────────────────────────
Total (Standard) 1.25 MB 462 KB 390 KB
Total (SIMD) 1.35 MB 492 KB 420 KB
With Optimizations (wasm-opt)
npm run optimize
Optimized Build Uncompressed Gzipped Brotli
──────────────────────────────────────────────────────────
Standard WASM 900 KB 380 KB 320 KB
SIMD WASM 980 KB 410 KB 350 KB
Code Splitting Strategy
// Lazy load WASM module when needed
const loadVectorDB = async () => {
const { default: init, VectorDB } = await import('@ruvector/wasm');
await init();
return VectorDB;
};
// Use in your application
button.addEventListener('click', async () => {
const VectorDB = await loadVectorDB();
const db = new VectorDB(384, 'cosine', true);
// Use db...
});
🔨 Building from Source
Prerequisites
- Rust: 1.77 or higher
- wasm-pack: Latest version
- Node.js: 18.0 or higher
# Install wasm-pack
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
# Or via npm
npm install -g wasm-pack
Build Commands
# Clone repository
git clone https://github.com/ruvnet/ruvector.git
cd ruvector/crates/ruvector-wasm
# Install dependencies
npm install
# Build for web (ES modules)
npm run build:web
# Build with SIMD optimizations
npm run build:simd
# Build for Node.js
npm run build:node
# Build for bundlers (webpack, rollup, etc.)
npm run build:bundler
# Build all targets
npm run build:all
# Run tests in browser
npm test
# Run tests in Node.js
npm run test:node
# Check bundle size
npm run size
# Optimize with wasm-opt (requires binaryen)
npm run optimize
# Serve examples locally
npm run serve
Development Workflow
# Watch mode (requires custom setup)
wasm-pack build --dev --target web -- --features simd
# Run specific browser tests
npm run test:firefox
# Profile WASM performance
wasm-pack build --profiling --target web
# Generate documentation
cargo doc --no-deps --open
Custom Build Configuration
# .cargo/config.toml
[target.wasm32-unknown-unknown]
rustflags = [
"-C", "opt-level=z",
"-C", "lto=fat",
"-C", "codegen-units=1"
]
📚 API Reference
VectorDB Class
class VectorDB {
constructor(
dimensions: number,
metric?: 'euclidean' | 'cosine' | 'dotproduct' | 'manhattan',
useHnsw?: boolean
);
// Insert operations
insert(vector: Float32Array, id?: string, metadata?: object): string;
insertBatch(entries: VectorEntry[]): string[];
// Search operations
search(query: Float32Array, k: number, filter?: object): SearchResult[];
// Retrieval operations
get(id: string): VectorEntry | null;
len(): number;
isEmpty(): boolean;
// Delete operations
delete(id: string): boolean;
// Persistence (IndexedDB)
saveToIndexedDB(): Promise<void>;
static loadFromIndexedDB(dbName: string): Promise<VectorDB>;
// Properties
readonly dimensions: number;
}
Types
interface VectorEntry {
id?: string;
vector: Float32Array;
metadata?: Record<string, any>;
}
interface SearchResult {
id: string;
score: number;
vector?: Float32Array;
metadata?: Record<string, any>;
}
Utility Functions
// Detect SIMD support
function detectSIMD(): boolean;
// Get version
function version(): string;
// Array conversion
function arrayToFloat32Array(arr: number[]): Float32Array;
// Benchmarking
function benchmark(name: string, iterations: number, dimensions: number): number;
See WASM API Documentation for complete reference.
🎯 Example Applications
Semantic Search Engine
// Semantic search with OpenAI embeddings
import init, { VectorDB } from '@ruvector/wasm';
import { Configuration, OpenAIApi } from 'openai';
await init();
const openai = new OpenAIApi(new Configuration({
apiKey: process.env.OPENAI_API_KEY
}));
const db = new VectorDB(1536, 'cosine', true); // OpenAI ada-002 = 1536 dims
// Index documents
const documents = [
'The quick brown fox jumps over the lazy dog',
'Machine learning is a subset of artificial intelligence',
'WebAssembly enables high-performance web applications'
];
for (const [i, doc] of documents.entries()) {
const response = await openai.createEmbedding({
model: 'text-embedding-ada-002',
input: doc
});
const embedding = new Float32Array(response.data.data[0].embedding);
db.insert(embedding, `doc_${i}`, { text: doc });
}
// Search
const queryResponse = await openai.createEmbedding({
model: 'text-embedding-ada-002',
input: 'What is AI?'
});
const queryEmbedding = new Float32Array(queryResponse.data.data[0].embedding);
const results = db.search(queryEmbedding, 3);
results.forEach(result => {
console.log(`${result.score.toFixed(4)}: ${result.metadata.text}`);
});
Offline Recommendation Engine
// Product recommendations that work offline
import init, { VectorDB } from '@ruvector/wasm';
import { IndexedDBPersistence } from '@ruvector/wasm/indexeddb';
await init();
const db = new VectorDB(128, 'cosine', true);
const persistence = new IndexedDBPersistence('product_recommendations');
await persistence.open();
// Load cached recommendations
await persistence.loadAll(async (progress) => {
if (progress.vectors.length > 0) {
db.insertBatch(progress.vectors);
}
});
// Get recommendations based on user history
function getRecommendations(userHistory, k = 10) {
// Compute user preference vector (average of liked items)
const userVector = computeAverageEmbedding(userHistory);
const recommendations = db.search(userVector, k);
return recommendations.map(r => ({
productId: r.id,
score: r.score,
...r.metadata
}));
}
// Add new products (syncs to IndexedDB)
async function addProduct(productId, embedding, metadata) {
db.insert(embedding, productId, metadata);
await persistence.save({ id: productId, vector: embedding, metadata });
}
RAG (Retrieval-Augmented Generation)
// Browser-based RAG system
import init, { VectorDB } from '@ruvector/wasm';
await init();
const db = new VectorDB(768, 'cosine', true); // BERT embeddings
// Index knowledge base
const knowledgeBase = loadKnowledgeBase(); // Your documents
for (const doc of knowledgeBase) {
const embedding = await getBertEmbedding(doc.text);
db.insert(embedding, doc.id, { text: doc.text, source: doc.source });
}
// RAG query function
async function ragQuery(question, llm) {
// 1. Get question embedding
const questionEmbedding = await getBertEmbedding(question);
// 2. Retrieve relevant context
const context = db.search(questionEmbedding, 5);
// 3. Augment prompt with context
const prompt = `
Context:
${context.map(r => r.metadata.text).join('\n\n')}
Question: ${question}
Answer based on the context above:
`;
// 4. Generate response
const response = await llm.generate(prompt);
return {
answer: response,
sources: context.map(r => r.metadata.source)
};
}
🐛 Troubleshooting
Common Issues
1. WASM Module Not Loading
// Ensure correct MIME type
// Add to server config (nginx):
// types {
// application/wasm wasm;
// }
// Or use explicit fetch
const wasmUrl = new URL('./pkg/ruvector_wasm_bg.wasm', import.meta.url);
await init(await fetch(wasmUrl));
2. CORS Errors
// For local development
// package.json
{
"scripts": {
"serve": "python3 -m http.server 8080 --bind 127.0.0.1"
}
}
3. Memory Issues
// Monitor memory usage
const stats = db.len();
const estimatedMemory = stats * dimensions * 4; // bytes
if (estimatedMemory > 100_000_000) { // 100MB
console.warn('High memory usage, consider chunking');
}
// Use batch operations to reduce GC pressure
const BATCH_SIZE = 1000;
for (let i = 0; i < entries.length; i += BATCH_SIZE) {
const batch = entries.slice(i, i + BATCH_SIZE);
db.insertBatch(batch);
}
4. Web Worker Issues
// Ensure worker script URL is correct
const workerUrl = new URL('./worker.js', import.meta.url);
const worker = new Worker(workerUrl, { type: 'module' });
// Handle worker errors
worker.onerror = (error) => {
console.error('Worker error:', error);
};
See WASM Troubleshooting Guide for more solutions.
🔗 Links & Resources
Documentation
- Getting Started Guide - Complete setup and usage
- WASM API Reference - Full API documentation
- Performance Tuning - Optimization tips
- Main README - Project overview and features
Examples & Demos
- Vanilla JS Example - Basic implementation
- React Demo - React integration with hooks
- Live Demo - Try it in your browser
- CodeSandbox - Interactive playground
Community & Support
- GitHub: github.com/ruvnet/ruvector
- Discord: Join our community
- Twitter: @ruvnet
- Issues: Report bugs
📄 License
MIT License - see LICENSE for details.
Free to use for commercial and personal projects.
🙏 Acknowledgments
- Built with wasm-pack and wasm-bindgen
- HNSW algorithm implementation from hnsw_rs
- SIMD optimizations powered by Rust's excellent WebAssembly support
- The WebAssembly community for making this possible