monlite
Stop spinning up Docker stacks. Documents, vectors, full-text search, cache, queue, and
cron — in one SQLite file, with a zero-dependency TypeScript core.
import { createDb } from "@monlite/core";
const db = createDb("./app.db");
That's it. No server. No migrations. No configuration. Everything below lives in app.db.
📖 Docs · 🎮 Live demo — monlite running in your browser · 💻 GitHub
Replace your entire local stack with one file
Most local apps, CLIs, and AI agents juggle the same set of services. monlite collapses all
of them into a single .db file:
| MongoDB / Mongoose | @monlite/core — document collections, typed queries, reactive watch() |
| Redis (cache) | @monlite/kv — synchronous cache, atomic locks, TTLs |
| BullMQ + Redis | @monlite/queue — durable job queue, retries, backoff, dedupe |
| Qdrant / Pinecone | @monlite/vector — vector search, findSimilar(), hybrid RAG |
| Elasticsearch / Typesense | @monlite/fts — full-text search, FTS5, search() |
| Cron server | @monlite/cron — persisted scheduled jobs |
| MongoDB Atlas sync | @monlite/sync — local-first replication to MongoDB / PostgreSQL / MySQL |
One npm install per feature. One .db file for all of them. Backup = cp app.db backup.db.
The AI agent backend — without Docker
A coding agent, RAG pipeline, or autonomous worker typically needs MongoDB for memory,
Redis for cache and locks, Qdrant for semantic search, and BullMQ for the task queue.
With monlite, that entire stack is one file:
import { createDb } from "@monlite/core";
import { kv } from "@monlite/kv";
import { createVectorStore } from "@monlite/vector";
import { createQueue } from "@monlite/queue";
import { createCron } from "@monlite/cron";
const db = createDb("./agent.db");
const memories = db.collection("memories");
await memories.create({ data: { agentId: "a1", content: "user prefers dark mode" } });
const store = createVectorStore(db);
store.ensureCollection("memory", { dimensions: 384, indexedFields: ["agentId"] });
const recall = store.search("memory", { vector: queryEmb, topK: 5, where: { agentId: "a1" } });
const claimed = await jobs.findOneAndUpdate({
where: { status: "pending", type: "summarize" },
data: { $set: { status: "active" }, $inc: { version: 1 } },
returnDocument: "after",
});
const lock = kv(db);
const acquired = lock.setNX("lock:job:42", 1, { ttl: 30_000 });
const queue = createQueue(db, { maxAttempts: 3 });
queue.process("embed", async (job) => await embed(job.payload.text), { concurrency: 4 });
const cron = createCron(db);
cron.schedule("nightly-cleanup", "0 3 * * *", () => queue.add("cleanup", {}));
No Docker. No .env files with connection strings. No Redis setup. One file, node serve.mjs.
Real-time reactivity — local Firebase
collection.watch() delivers a live result set that re-emits only when a relevant change lands.
Row-level matching, no spurious re-renders. Works with changes from @monlite/sync too.
const stop = users.watch(
{ where: { roles: { has: "admin" } } },
({ results, added, removed }) => renderAdminList(results),
);
Pair with @monlite/sync and your local database becomes a live replica of MongoDB or
PostgreSQL — fully offline capable, syncs when reconnected.
A proper query language — not a toy
monlite has a Mongo/Prisma-style query API. Typed collections get compile-time checked
where/orderBy and return types that narrow with select.
interface Order {
customerId: string;
items: { sku: string; qty: number }[];
status: "pending" | "shipped" | "returned";
total: number;
}
const orders = db.collection<Order>("orders");
await orders.findMany({
where: { items: { elemMatch: { sku: "WIDGET-A", qty: { gte: 5 } } } },
});
await orders.findMany({ where: { status: { regex: "^pend", mode: "insensitive" } } });
await orders.groupBy({
by: ["customerId"],
where: { status: "shipped" },
_sum: { total: true },
orderBy: { _sum: { total: "desc" } },
take: 10,
});
await db.transactionAsync(async (tx) => {
const account = await tx.findFirst({ where: { _id: "acc-1" } });
if (account.balance < 100) throw new Error("insufficient funds");
await tx.update({ where: { _id: "acc-1" }, data: { $inc: { balance: -100 } } });
await tx.update({ where: { _id: "acc-2" }, data: { $inc: { balance: +100 } } });
});
Hybrid search — keyword + semantic in one call
Get the best of both worlds: FTS5 keyword ranking fused with vector similarity via Reciprocal
Rank Fusion. One query, one ranked list.
import { fts } from "@monlite/fts";
import { vector, hybridSearch } from "@monlite/vector";
const db = createDb("./app.db", {
allowExtensions: true,
plugins: [
fts({ docs: ["title", "body"] }),
vector({ docs: { field: "embedding", dimensions: 384 } }),
],
});
const hits = await hybridSearch(db.collection("docs"), {
text: "machine learning fundamentals",
vector: await embed("machine learning fundamentals"),
topK: 10,
where: { published: true },
});
Python reads the same file
A monlite database is plain SQLite with documented conventions, so the Python port reads
and writes the same .db. Python ingests, Node serves — or any split you like.
from monlite import create_db, kv
db = create_db("app.db")
users = db.collection("users")
users.create({"name": "Ali", "age": 30, "tags": ["admin"]})
users.find_many(where={"tags": {"has": "admin"}})
kv(db).set("session:42", {"user": "ali"}, ttl=60_000)
One file. Two runtimes. Zero translation layer.
Works everywhere SQLite runs
| Node 22.5+ | @monlite/core — uses built-in node:sqlite, zero native build |
| Node 18/20 | @monlite/core + better-sqlite3 — auto-selected when present |
| Browser | @monlite/wasm — same API on SQLite-WASM (sql.js) |
| Electron | @monlite/electron — DB in main process, same API in renderer over IPC |
| Python | pip install monlite — same .db file, pure stdlib core |
Install
npm install @monlite/core
npm install @monlite/core better-sqlite3
Add packages as you need them:
npm install @monlite/vector
npm install @monlite/fts
npm install @monlite/kv
npm install @monlite/queue
npm install @monlite/cron
npm install @monlite/sync
npm install @monlite/wasm
Why not just use…
SQLite directly? You could — but you'd be writing the document layer, the query translator,
the FTS integration, the vector extension wiring, the change feed, the sync engine, and all the
TypeScript types yourself. monlite is that work, already done and tested.
MongoDB + Redis + Qdrant? For local / edge / desktop / single-machine work, you're paying
the operational cost of three separate services to solve one problem. monlite puts them all in
one file, with one API, and zero infrastructure.
Firebase / Supabase? Great for shared cloud state. Not so great when you need to work
offline, ship a CLI tool, build a desktop app, or keep data on-device. monlite is local-first;
@monlite/sync handles the cloud part when you need it.
Documentation
Full guide at the documentation site:
Runnable demos are in examples/.
Status
Production-ready and published. Current versions: @monlite/core 2.6.12, @monlite/sync
1.3.4, @monlite/vector 0.5.5, @monlite/fts 0.5.4, @monlite/kv 0.2.1,
@monlite/queue 0.3.4, @monlite/cron 0.1.1, @monlite/wasm 0.2.2. The 2.x API is frozen.
Vector and full-text indexing are linear at scale — verified ingesting 100K documents
in ~0.8s and 50K vectors in ~8s (no O(n²) re-index), so it comfortably backs 10K–100K-document
RAG. The core bundles cleanly for the browser (no Node built-ins in the import graph).
The live demo showcases every package — documents,
full-text (FTS5), vector/semantic search, cache, queue, and cron — running 100% in the
browser on SQLite-WASM, with semantic embeddings computed on-device via Transformers.js.
The Python port (pip install monlite) currently ships documents + kv, with the rest of the
package family in progress.
License
MIT