Vexi is a local-first RAG database you drive from TypeScript.
- Define tables in
schema.tswith a small Zod-like DSL. - Run
vexi syncto apply additive migrations and register the schema. - Use a fully type-safe client (
insert,update,search) while the Rust API handles storage (LanceDB) and embeddings (Gemini).
Highlights:
- Type-safe
db.<table>.insert/update/searchderived from your schema. - Server-side validation (schema registry is the contract; no inference on write).
- Automatic embeddings on write and query (Gemini v1).
- Additive-only migrations + explicit
reindexfor embedding changes. - Chunking strategy support (
recursive-markdown) for long-form text.
Why this exists:
- RAG apps often accumulate glue code: schema drift, ad-hoc migrations, hand-rolled embedding pipelines.
- Vexi makes schema + validation + embeddings a single contract you can
syncand then rely on.
This repo is a monorepo:
sdk/TypeScript SDK +vexiCLIapi/Rust HTTP API (Axum) + LanceDBexample-app/minimal consumer
flowchart LR
schema["schema.ts"] -->|vexi sync| api["API: POST /sync"]
api --> registry["_vexi_schema_registry"]
app["Node app"] -->|insert / update / search| api
api --> lancedb["LanceDB (.lancedb)"]
api -->|embed| gemini["Gemini embeddings"]
lancedb --> api --> app
- Schema registry is the contract (server validates writes; no schema inference).
- Migrations are additive-only.
- Embeddings provider is Gemini only.
- No backward compatibility (v1-only endpoints + CLI).
Prereqs: Node.js 18+ and Rust.
Terminal A (API):
cd sdk
npm ci
npm run build
cd ../api
cp .env.example .env
# set GEMINI_API_KEY in api/.env
cargo runTerminal B (client + schema sync):
cd example-app
npm ci
npm run sync
npm run startNotes:
GEMINI_API_KEYis required for embeddings/search/reindex (the API can start without it, but search will fail).- If you see a vector dimension error, set
VEXI_VECTOR_DIM(default768).
schema.ts
import { createTable, v } from "vexi";
export const users = createTable({
name: v.string().embed(),
bio: v.optional(v.string().embed({ strategy: "recursive-markdown" })),
isActive: v.boolean(),
});main.ts (NodeNext/ESM: note the .js extension on local imports)
import { createClient } from "vexi";
import { users } from "./schema.js";
const db = createClient({
schema: { users },
config: { baseUrl: "http://localhost:3000" },
});
const inserted = await db.users.insert({ name: "Alice", isActive: true });
await db.users.update(inserted.id, { isActive: false });
const results = await db.users.search("Alice", { topK: 5 });
console.log(results);Sync schema (one-shot):
npx vexi sync --schema ./schema.ts --url http://localhost:3000Reindex (backfill vectors after changing embedding config/model/strategy):
npx vexi reindex users --url http://localhost:3000PROJECT.md(product + API philosophy)STATUS.md(what v1 does today)
GET /healthPOST /syncPOST /tables/{name}/insertPATCH /tables/{name}/{id}POST /tables/{name}/searchPOST /tables/{name}/reindex
Error shape (all non-2xx):
{ "error": { "code": "...", "message": "...", "details": {} } }Insert response shape:
{ "ok": true, "rows": [{ "id": "...", "...": "..." }] }Search response shape:
{ "ok": true, "results": [{ "score": 0.123, "item": { "id": "..." } }] }API env vars:
GEMINI_API_KEY(required for embeddings/search/reindex)VEXI_VECTOR_DIM(default768)LANCEDB_URI(default.lancedb)VEXI_DEBUG=1(enablesGET /registry)
- No auth / multi-tenant model.
- No destructive migrations (type changes, column removal).
- Local-first (LanceDB on disk).
cd sdk && npm run lint && npm run build
cd api && cargo fmt && cargo clippy -- -D warnings