ferrovec
v0.3.3 live on crates.io + npm

Semantic memory for the browser, with a Rust engine inside

A persisted HNSW vector store that compiles Rust to WebAssembly, auto-embeds through transformers.js, and saves to disk — so any web app gets scalable, private, offline semantic search. You never write Rust. You never run a server.

// Live in npm 0.3.3 — the three-line, batteries-included API
import { Ferrovec } from 'ferrovec';
const db = await Ferrovec.open('notes');          // worker + model + OPFS
await db.insert('WASM handles the math, JS handles the DOM');
const hits = await db.query('what owns performance?', 5);

// Or drive the raw core directly (bring your own embeddings)
import { FerrovecCore } from 'ferrovec';
const index = new FerrovecCore(384);
index.insert('doc-1', embedding);          // Float32Array in, [{id,distance}] out

The vision

The winning WASM apps never asked anyone to switch languages. They put a Rust engine inside and a plain JavaScript API outside. ferrovec brings that pattern to local-first AI.

01 — Private by default

Your data never leaves the tab

Embeddings are computed on-device and stored in the browser's origin-private file system. No API keys, no network round-trips, no server logs — semantic search a privacy policy can actually promise.

02 — Engine layer, not UI layer

Rust does the math, JS does the DOM

Rust isn't going to replace React — but it wins decisively as a compute core. ferrovec is that core, a nearest-neighbor engine, hidden behind an API a JS developer already understands.

03 — Offline & instant

Works on a plane, restores in a blink

The index lives on disk via OPFS. Reload the page and your vectors are already there — no re-embedding, no rebuild. Built for apps that must work with no connection at all.

04 — Batteries included

One package, not five primitives

Today you stitch together an embedder, an index, and a persistence layer by hand. ferrovec welds them into a single dependency — the integrated piece the ecosystem is missing.

Our story

Why we built ferrovec

We started with a frustration, not a product.

For years the promise was that WebAssembly would let Rust reshape the frontend — and for years, it didn't happen. So we ran the numbers across a hundred sources, and the verdict surprised us. Rust wasn't losing on speed; on raw DOM benchmarks it was already beating React. It was losing on everything human — hiring, tooling, the daily developer loop, an ecosystem a fraction the size.

But buried in that research was a pattern we couldn't unsee. Every WebAssembly app that did win — Figma, Photoshop on the web, the spreadsheet engines, the video tools — shared one shape: a Rust or C++ engine on the inside, a plain JavaScript API on the outside. None of them ever asked a web developer to learn a new language. They made the language invisible.

The goal was never to replace React. It was to find the one job WebAssembly does ten times better — and hide it behind an API a JavaScript developer already knows.

We found that job in the sudden, explosive need for AI features in ordinary web apps. Embeddings. Semantic search. “Chat with your notes.” The kind of thing that today means shipping your users' private data to someone else's server. We asked what it would take to do it entirely on-device — no server, no API key, nothing leaving the tab — and hit a wall. The embedding models were there. But the piece that stores and searches those vectors, fast, at scale, and remembers them between sessions — that piece didn't exist. The libraries that tried were abandoned, or brute-force, or made you rebuild the entire index just to add a single item.

So we built it. ferrovec is the missing piece: a real nearest-neighbor engine in Rust, small enough to ship to a browser, that saves to disk and never forgets. Private by default, because the data never moves. Offline by default, because there's no server to call. Invisible by default — because you write three lines of JavaScript and never once think about the Rust underneath.

That's why we're here. Not to win a language war — but to give every web developer a piece of infrastructure that should have existed already, and to prove that the engine-inside, API-outside pattern is how Rust actually wins the web.

What it does

Everything you need for local semantic search

Feed it vectors (and soon, text). Under the hood it indexes, searches, and persists — you touch none of it.

HNSW

Approximate nearest-neighbor

A hand-rolled Hierarchical Navigable Small World graph — the algorithm behind Pinecone, Weaviate, and Qdrant — in a tiny WASM core.

INCREMENTAL

Insert one at a time

Add vectors as they arrive. No rebuild-the-whole-index penalty — the flaw that hobbles every other browser vector lib.

SIMD

SIMD-accelerated distance

The distance kernel uses WASM SIMD128 with a scalar reference fallback — a free speedup with zero deployment headers.

TINY

~33 KB gzipped

Dependency-light by design — serde + postcard only, no getrandom, no threads. Small enough to ship anywhere.

METRICS

Cosine, dot & L2

Normalized vectors get a fast dot path; raw vectors get cosine or squared-Euclidean. Smaller always means closer.

UPSERT

Update & delete by id

Re-insert an id to replace it; remove to tombstone. IDs are your own strings — map them to whatever your app stores.

SERDE

Portable byte format

Serialize the whole index to a versioned byte blob and reload it anywhere — the same core runs natively in Rust.

SAFE

No unsafe in the core

#![deny(unsafe_code)] crate-wide; unsafe is confined to the audited SIMD kernel alone.

How it works

Four layers, one Worker

The synchronous OPFS API only works inside a dedicated Web Worker — and HNSW belongs off the main thread anyway. Both constraints point at one clean design, shipped in the browser package.

Main thread · your app
Ferrovec — a thin async proxy. open / insert / query just message the worker and await a reply.
▲  postMessage  ▼
Dedicated Web Worker · the engine room
Owns the model, the WASM module, and the exclusive OPFS lock.
JS · embeddings
transformers.js — text → Float32Array, WebGPU with WASM fallback.
Rust → WASM · the core (shipped)
Hand-rolled HNSW — incremental insert, k-NN search, SIMD distance, byte serialization.
▲  read / write / flush  ▼
OPFS · durable, origin-private
/ferrovec/<name>/index.bin. Survives reloads; never leaves the device.

How it compares

The only one built to do all four

Persisted, incremental, HNSW-not-brute-force, and a compiled engine — no existing browser library combines them.

LibraryEngineIndexIncrementalOPFS persistStatus
ferrovecRust/WASMHNSWyesbuilt-in + cross-tab0.3.3 live
voyRust/WASMkd-treeno — rebuildapp-managedabandoned
OramaTypeScriptbrute-forceyesserializeactive
MeMemoTypeScriptHNSWbulkIndexedDBdormant
sqlite-vecC/WASMbrute-forceyesmanual VFSactive

Landscape as surveyed July 2026. ferrovec's row reflects the shipped browser package — built-in OPFS persistence and cross-tab leader election.

Why Rust + WebAssembly

A niche where it genuinely wins

Rust's frontend problem was never speed — it was adoption and ecosystem. So we don't fight React; we live where Rust is strongest: the compute core.

33 KB
gzipped WASM core, wasm-opt'd
2
dependencies in the whole crate
0.99
recall@1 on a 2,000-vector benchmark
0
servers, API keys, or network calls

Roadmap

The roadmap, shipped end to end

M1

Rust HNSW core 0.3.3

Hand-rolled graph: incremental insert, k-NN search, three metrics, byte serialization, full test suite. Native and wasm32.

M2

WASM boundary + SIMD 0.3.3

wasm-bindgen FerrovecCore class over Float32Array, SIMD128 distance kernel, ~33 KB gzip.

Compaction 0.3.3

compact() rebuilds the index in place to reclaim memory from tombstoned nodes; clear() resets it. On crates.io now.

M3

Worker + embeddings 0.3.3

Dedicated worker loads transformers.js + WASM; end-to-end insert(text) / query(text). Live on npm.

M4

OPFS persistence 0.3.3

Sync-access-handle read/write/flush, hydrate on open, survive reloads. The differentiator nobody else ships — verified with a real headless-browser round-trip.

M5

ferrovec on npm 0.3.3

The three-line, batteries-included browser API — embeddings + persistence — live on npm.

M6

Cross-tab leader election 0.3.3

Web Locks + BroadcastChannel single-writer election: one leader tab owns the store, followers proxy through it, and on close/crash a follower is promoted — verified with a real two-page headless failover test.

Get started

Two registries, one name

The Rust crate is live on crates.io; the full browser package — M3 through M6 — is live on npm.

ferroveccrates.io · live
# the HNSW engine — native + WASM
cargo add ferrovec

The pure-Rust HNSW index, usable server-side and as the WASM core. Read the docs →

ferrovecnpm · live
# the WASM core for the browser
npm install ferrovec

The full three-line open/insert/query API — transformers.js embeddings, OPFS persistence, and cross-tab leader election — is live in 0.3.3, plus the raw FerrovecCore class. View on npm →

★ Follow development on GitHub