Project releases

RuVector WASM 0.2.0 makes browser HNSW search real instead of silently flat

The browser build of RuVector accepted a request for an HNSW index but quietly used exact flat search, because the core library disables its native HNSW in WebAssembly. Pull request #1035, merged September 27, adds a portable in-browser graph index and reports which backend is actually running. It shipped as @ruvector/wasm 0.2.0.

GitHub activity: · Published:

What this is about

RuVector is a vector database: it stores lists of numbers that represent meaning and finds the nearest ones fast. Its WebAssembly package lets that run inside a web page. HNSW is the graph index that makes search fast on large collections, at the cost of occasionally missing an exact match.

The browser package took a flag asking for HNSW, but the core library it builds on turns native HNSW off in WebAssembly. So every request quietly got exact flat search, which is correct but scans everything. Nothing told the caller.

What changed

Pull request #1035, merged September 27 as commit 83bcaed0, changes that:

  • A portable in-memory graph index is built inside the WASM crate, with no file mapping or threads. Parameters: M=16, efConstruction=128, efSearch=64.
  • hnswAvailable() and a per-instance indexType report the backend actually in use, instead of echoing what the caller asked for.
  • Exact flat mode is kept: pass useHnsw false. Filtered queries use exact search so results are complete. Deletes and vector-changing updates rebuild the graph.
  • Input checks: vectors and dimensions are validated before any change, metadata serialisation is fixed, and result handles are released.
  • Version: @ruvector/wasm moves from 0.1.31 to 0.2.0, because the default now returns approximate results where it used to return exact ones.

The pull request reports 20 of 20 adapter and HNSW tests passing, and one synthetic benchmark: 10,000 vectors of 32 dimensions, 100 held-out queries, top 10, Node 24 on Linux. Flat search had recall 1.000 at 0.304 ms median; the optimised HNSW had recall 0.966 at 0.051 ms median with a 978 ms build. The author states these are single-process synthetic numbers, not claims about production embeddings or mobile browsers. They are quoted here as reported, not re-measured.

Get started

Prerequisites: a JavaScript project with a bundler or Node.js.

npm install @ruvector/wasm

The README recommends the adapter, which gives a higher-is-better similarity and reports the index type:

import { RuvectorWasmAdapter } from '@ruvector/wasm/adapter';

const index = await RuvectorWasmAdapter.create({ dimensions: 384, metric: 'cosine' });
index.insert({ id: 'doc_1', vector: embedding, metadata: { title: 'My Document' } });
const results = index.search({ vector: query, k: 10 });
console.log(index.indexType); // 'hnsw' ('flat' with useHnsw: false)

Expected result: indexType prints hnsw on 0.2.0. Note that on the raw VectorDB binding, result.score is a distance where lower is better; the adapter's similarity is higher-is-better.

MCP: this package is a browser library, not an MCP server. The RuVector repository documents a separate Node MCP server (claude mcp add ruvector -- npx ruvector mcp), covered in an earlier story. No npx skills package is documented for the WASM build.

Commands here were read from the release notes and repository documentation at the pinned reference; they were not executed as part of writing this article.

Use it today

Practical case: a documentation site embeds a few thousand passages and searches them in the browser, with no server. Input is the user's query embedding. The workflow is an adapter search with k=10. Output is ranked passages with similarity scores. On 0.2.0 that search uses a graph index; before, it scanned every passage.

Reader acceptance test: create two indexes over the same data, one default and one with useHnsw false. indexType should read hnsw and flat respectively, and top-10 overlap should be high but not always perfect. If both report flat, you are on an older version.

Push it further

Experimental commentary. The more lasting change is honesty in reporting: the binary now says what it is doing. That makes it possible to run the same retrieval in a browser and on a server and compare like with like.

Limitation: the graph lives in memory and is rebuilt on deletes and vector changes, so heavy-churn collections pay a rebuild cost. Depends on the default being approximate, which some callers may not want. Falsifiable test: on your own embeddings, measure recall@10 against flat search. If it falls well below the reported 0.966, the synthetic benchmark does not transfer to your data.

Read the original on GitHub pull request

PR #1035 — Fix WASM HNSW fallback with portable graph index (merge commit 83bcaed0)

RuVector repository

Back to the newsroom