Workers / WebAssembly
Browser WebAssembly retrieval guide
Run retrieval and local MiniLM embedding in dedicated browser Workers with packaged WebAssembly tiers.
On this page
Install
Browser retrieval and browser embedding are independent Worker packages. They do not import or fall back to the native Node.js packages.
npm install @gungorbasa/retrievalkit-browser@0.1.0
npm install @gungorbasa/retrievalkit-browser-embedding@0.1.0
Retrieval combines flat, graph-only, and graph-scoped databases in one package. Embedding uses a separate Worker and a pinned FP32 MiniLM model.
Create the retrieval Worker
The Worker selects the SIMD128 build when supported and otherwise loads the portable WebAssembly tier.
// retrievalkit.worker.ts
import { installRetrievalKitWorker }
from "@gungorbasa/retrievalkit-browser/worker";
import { createAdaptiveGeneratedWasmAdapter }
from "@gungorbasa/retrievalkit-browser/adapter";
installRetrievalKitWorker(createAdaptiveGeneratedWasmAdapter({
portable: async () => {
const wasm = await import(
"@gungorbasa/retrievalkit-browser/wasm/portable"
);
await wasm.default();
return wasm;
},
simd128: async () => {
const wasm = await import(
"@gungorbasa/retrievalkit-browser/wasm/simd128"
);
await wasm.default();
return wasm;
},
}));
Create the embedding Worker
// embedding.worker.ts
import { installBrowserEmbeddingWorker }
from "@gungorbasa/retrievalkit-browser-embedding/worker";
installBrowserEmbeddingWorker();
The embedding Worker owns model acquisition, verification, tokenization, ONNX
session creation, warmup, and inference. execution: "auto" prefers WebGPU and
falls back to deterministic WASM.
Create the clients
import { RetrievalKitBrowser }
from "@gungorbasa/retrievalkit-browser";
import { BrowserEmbedder }
from "@gungorbasa/retrievalkit-browser-embedding";
const kit = await RetrievalKitBrowser.create({
worker: () => new Worker(
new URL("./retrievalkit.worker.js", import.meta.url),
{ type: "module" },
),
});
const embedder = await BrowserEmbedder.load({
worker: () => new Worker(
new URL("./embedding.worker.js", import.meta.url),
{ type: "module" },
),
execution: "auto",
});
Model acquisition happens during load() or prefetch(). Calls to embed()
and embedBatch() never download model artifacts.
Index and search
const builder = kit.retrievalDatabase({
corpusId: "notes",
metric: "cosine",
encoding: "f32",
});
const documentEmbedding = await embedder.embed("A local-first search note");
await builder.add([{
id: "note-1",
text: "A local-first search note",
embedding: documentEmbedding,
}]);
const database = await builder.build();
const queryEmbedding = await embedder.embed("local search");
const hits = await database.search({
mode: "hybrid",
text: "local search",
embedding: queryEmbedding,
alpha: 0.6,
});
Use kit.graphDatabase() for graph-only work or
kit.graphRetrievalDatabase() when graph selection should scope the same
ranker.
Close and deploy
await database.close();
await embedder.close();
kit.close();
Browser databases are Worker-owned and in memory. Persistence and threaded WebAssembly are not claimed in v0.1.0. Bundle both Worker entries as static module assets and permit Worker creation and WebAssembly compilation in the application content security policy.
The complete Apollo 11 demo has run successfully in Chromium, Firefox, and Safari on desktop. WebAssembly support alone is not enough: local answer generation also requires WebGPU and sufficient GPU memory. These successful runs are not a promise for every browser, operating system, or device. Physical mobile browsers remain unqualified.
Choose the database boundary
Three query paths
Compare the same job in TypeScript. These are separate APIs, not modes hidden behind one query.
Hybrid vector + BM25
RetrievalDatabase Worker clientRank the whole in-memory corpus with exact vector similarity and BM25.
const hits = await retrievalDatabase.search({
mode: "hybrid",
text: "What did we decide about offline sync?",
embedding: queryEmbedding,
alpha: 0.6,
limit: 5
});Graph query alone
GraphDatabase Worker clientSelect records by relationship. No embedding or retrieval ranker is involved.
const selection = await graphDatabase.graph.query({
seed: {
kind: "nodes",
nodes: [{
kind: "record",
nodeType: "Notebook",
recordId: "product-research"
}]
},
traverse: [{ relationship: "references" }]
});Graph + vector + BM25
GraphRetrievalDatabase Worker clientSelect candidates with the graph, then rank only that scope with vector + BM25.
const selection = await graphRetrievalDatabase.graph.query({
seed: {
kind: "nodes",
nodes: [{
kind: "record",
nodeType: "Project",
recordId: "mobile-app"
}]
},
traverse: [{ relationship: "contains" }]
});
const hits = await graphRetrievalDatabase.retrieval.search({
mode: "hybrid",
text: "Why is cold start slow on Android?",
embedding: queryEmbedding,
alpha: 0.6,
within: selection,
limit: 5
});