vecal is a browser vector database built on top of IndexedDB.
- CRUD operations for vectors
- Similarity search using multiple distance metrics
- Optional ANN indexes: LSH, IVFFlat, and HNSW
- In-memory index lifecycle (indexes are rebuilt after refresh)
npm install vecal
# or
yarn add vecalimport { VectorDB } from 'vecal';
const db = new VectorDB({
dbName: 'products-db',
dimension: 3,
distanceType: 'cosine',
});
await db.add(new Float32Array([0.9, 0.1, 0.1]), { label: 'Apple' });
await db.add(new Float32Array([0.1, 0.9, 0.1]), { label: 'Banana' });
await db.add(new Float32Array([0.1, 0.1, 0.9]), { label: 'Cherry' });
const results = await db.search(new Float32Array([0.85, 0.2, 0.15]), 2);
console.log(results);import { VectorDB } from 'vecal';
const db = new VectorDB({ dbName: 'example-crud', dimension: 3 });
const id = await db.add(new Float32Array([0.9, 0.1, 0.1]), { label: 'Apple' });
const entry = await db.get(id);
console.log(entry?.metadata?.label); // Apple
await db.update(id, {
metadata: { label: 'Green Apple' },
});
await db.delete(id);const db = new VectorDB({
dbName: 'example-distance',
dimension: 3,
distanceType: 'cosine',
});
await db.add(new Float32Array([0.9, 0.1, 0.1]), { label: 'Apple' });
await db.add(new Float32Array([0.1, 0.9, 0.1]), { label: 'Banana' });
const query = new Float32Array([0.85, 0.2, 0.15]);
const cosineTop = await db.search(query, 1, 'cosine');
const l2Top = await db.search(query, 1, 'l2');
const dotTop = await db.search(query, 1, 'dot');
console.log(cosineTop[0], l2Top[0], dotTop[0]);const db = new VectorDB({ dbName: 'example-lsh', dimension: 3 });
await db.add(new Float32Array([0.9, 0.1, 0.1]), { label: 'Apple' });
await db.add(new Float32Array([0.1, 0.9, 0.1]), { label: 'Banana' });
await db.add(new Float32Array([0.1, 0.1, 0.9]), { label: 'Cherry' });
await db.buildIndex(8);
const results = await db.annSearch(new Float32Array([0.85, 0.2, 0.15]), 2, 1);
console.log(results);const db = new VectorDB({ dbName: 'example-ivf', dimension: 3 });
await db.add(new Float32Array([0.9, 0.1, 0.1]), { label: 'Apple' });
await db.add(new Float32Array([0.1, 0.9, 0.1]), { label: 'Banana' });
await db.add(new Float32Array([0.1, 0.1, 0.9]), { label: 'Cherry' });
await db.buildIVFFlatIndex(64, 8);
const results = await db.ivfSearch(new Float32Array([0.85, 0.2, 0.15]), 2);
console.log(results);const db = new VectorDB({ dbName: 'example-hnsw', dimension: 3 });
await db.add(new Float32Array([0.9, 0.1, 0.1]), { label: 'Apple' });
await db.add(new Float32Array([0.1, 0.9, 0.1]), { label: 'Banana' });
await db.add(new Float32Array([0.1, 0.1, 0.9]), { label: 'Cherry' });
await db.buildHNSWIndex(16, 200);
// Higher efSearch usually gives better recall but costs more compute.
const fastResults = await db.hnswSearch(new Float32Array([0.85, 0.2, 0.15]), 2, 32);
const highRecallResults = await db.hnswSearch(new Float32Array([0.85, 0.2, 0.15]), 2, 128);
console.log(fastResults, highRecallResults);const db = new VectorDB({ dbName: 'example-close', dimension: 3 });
await db.close();
try {
await db.search(new Float32Array([0.1, 0.2, 0.3]), 1);
} catch (error) {
console.error(error); // Error: Database is closed
}Creates a database instance. config fields:
dbName– name of the IndexedDB database.dimension– length of the stored vectors.storeName– optional object store name (defaults to"vectors").distanceType– optional default distance metric.minkowskiP– power parameter when using Minkowski distance (default3).
Add a vector with optional metadata. Returns the generated id.
Retrieve a stored entry.
Partially update an entry.
Remove an entry from the database.
Build an LSH index from all entries. numHashes controls the number of hyperplanes (default 10).
Build an IVFFlat index from all entries. nlist is the number of clusters (default 256) and nprobe is the number of probed clusters at query time (default 8).
Build a graph-based HNSW index from all entries. If Web Workers are available, building is attempted in a worker with a synchronous fallback path.
Exact similarity search. distanceType can be "cosine", "l2", "l1", "dot", "hamming", or "minkowski".
Approximate nearest neighbour search using the LSH index. The index is built lazily when first needed. distanceType uses the same options as search.
Approximate nearest neighbour search using the IVFFlat index. The index is built lazily when first needed.
Approximate nearest neighbour search using the HNSW index. The index is built lazily when first needed. efSearch controls the search candidate queue size (default 64), where larger values generally improve recall with higher query cost.
Close the underlying IndexedDB connection.
After calling close(), any further operation will throw Database is closed.
ANN indexes (LSH, IVFFlat, HNSW) are in-memory structures. Stored vectors remain in IndexedDB, but indexes are rebuilt on demand after page refresh or new session start.
- Use
searchfor exact results on small or medium datasets. - Use
annSearch(LSH) for very lightweight approximate search. - Use
ivfSearchfor centroid-based approximate search. - Use
hnswSearchfor higher recall ANN and tuneefSearchfor speed/quality trade-off.
VectorDBConfigVectorEntrySearchResultDistanceType
The src/main.ts file in this repository demonstrates how to build a small Hacker News search tool. The high level steps are:
- obtain an OpenAI API key;
- fetch items to index;
- convert each title to an embedding using the API;
- create a
VectorDBwith the embedding dimension and store each vector; - run searches with
db.searchordb.annSearch.
Refer to the code for a full example.