Examples
Production-oriented Vecal 1.0 patterns.
Examples
Product search with an embedding callback
Keep embedding policy outside Vecal and store enough JSON metadata to render results without another lookup.
import { VectorDB, type Metadata } from 'vecal';
interface ProductMetadata extends Metadata {
sku: string;
title: string;
category: string;
}
export async function createProductSearch(
embed: (text: string) => Promise<Float32Array>,
) {
const db = await VectorDB.open<ProductMetadata>({
name: 'catalog',
dimension: 384,
metric: 'cosine',
});
return {
async addProduct(product: {
sku: string;
title: string;
category: string;
description: string;
}) {
return db.upsert({
id: product.sku,
vector: await embed(`${product.title}\n${product.description}`),
metadata: {
sku: product.sku,
title: product.title,
category: product.category,
},
});
},
async search(text: string, category?: string) {
return db.search(await embed(text), {
k: 12,
strategy: 'auto',
efSearch: 100,
where: category ? { category: { $eq: category } } : undefined,
minScore: 0.25,
});
},
close: () => db.close(),
};
}Atomic initial import
await db.addMany(
documents.map((document) => ({
id: document.id,
vector: Float32Array.from(document.embedding),
metadata: {
title: document.title,
source: document.source,
publishedAt: document.publishedAt,
},
})),
);
await db.ensureIndex({
type: 'hnsw',
m: 16,
efConstruction: 200,
onProgress: ({ ratio }) => setProgress(ratio),
});Prefer one addMany() over a loop of add() calls when the whole import should either commit or roll back.
Structured filtering
const results = await db.search(queryVector, {
k: 10,
strategy: 'auto',
where: {
language: { $eq: 'en' },
section: { $in: ['guide', 'reference'] },
publishedAt: { $gte: '2026-01-01' },
},
});Shorthand equality and in-list conditions are also accepted:
where: {
language: 'en',
section: ['guide', 'reference'],
}Cancellation
const controller = new AbortController();
const promise = db.search(queryVector, {
k: 20,
strategy: 'exact',
signal: controller.signal,
});
controller.abort();
try {
await promise;
} catch (error) {
if (error instanceof OperationCancelledError) {
// Expected user cancellation.
}
}Builds use the same AbortSignal pattern.
Compare HNSW with Exact
function recall(expected: string[], actual: string[]) {
const actualIds = new Set(actual);
return expected.filter((id) => actualIds.has(id)).length / expected.length;
}
async function recallAt10(query: Float32Array) {
const exact = await db.search(query, { k: 10, strategy: 'exact' });
const hnsw = await db.search(query, {
k: 10,
strategy: 'hnsw',
efSearch: 100,
});
return recall(
exact.map(({ id }) => id),
hnsw.map(({ id }) => id),
);
}Tune against representative data rather than assuming one efSearch works for every distribution.
React cleanup
useEffect(() => {
let db: VectorDB | undefined;
let active = true;
void VectorDB.open({ name: 'notes', dimension: 384, metric: 'cosine' }).then(
(opened) => {
if (active) db = opened;
else void opened.close();
},
);
return () => {
active = false;
void db?.close();
};
}, []);