Skip to content

Repository files navigation

Lucentdocs

An AI-native note editor with semantic search that scales to 50,000+ pages. Self-hosted, local-first, and built so AI can work directly inside your documents instead of being an external fixture.

Lucentdocs demo

What it does

  • Semantic search over large text collections. Find concepts, not keywords, across books and notes. Benchmarked against Project Gutenberg up to 47,112 pages (~19M words): query p50 stays under 52 ms with Qdrant; SQLite degrades to ~1.3s.
  • Real-time collaboration via WebSockets.
  • AI-native editing: inline suggestions and transformations that see your document structure, not a chat box.
  • Zero-dependency default: runs out of the box on SQLite's vector extension; optional Qdrant for scale.
  • Local-first: your data stays in a standard SQLite file you own.

Benchmarks

We benchmarked vector retrieval latency with the same chunker (400-word pages) and embedding provider used by the app. All numbers are single-client latency; queries are awaited sequentially. Qdrant uses default configuration and is memory-resident. Each pass runs 32 queries, repeated 20 times after 3 warmup passes.

Pages Backend Upsert time Query p50 Query p95 Query p99
500 sqlite 3.85s 16.0ms 16.5ms 16.7ms
500 qdrant 1.74s 43.4ms 44.9ms 45.6ms
1,000 sqlite 8.26s 30.4ms 35.6ms 44.8ms
1,000 qdrant 2.36s 44.0ms 45.8ms 46.8ms
5,000 sqlite 40.31s 146.8ms 161.5ms 172.6ms
5,000 qdrant 10.43s 44.6ms 45.9ms 46.3ms
47,112 sqlite 6m 14.33s 1.33s 1.41s 1.45s
47,112 qdrant 1m 22.62s 51.9ms 83.4ms 93.5ms

Takeaway: SQLite works well for small libraries (< 1,000 pages). Above that, switch to Qdrant (VECTOR_STORAGE=qdrant).

Quick start

bun install
cp .env.example .env   # edit as needed
bun run dev            # builds @lucentdocs/core, then starts the API with watch

The web app is served by the API in development. See .env.example for configuration.

Vector backend

SQLite is the default (VECTOR_STORAGE=sqlite). To use Qdrant:

docker compose --profile qdrant up -d
# set VECTOR_STORAGE=qdrant in .env

Running the benchmark

docker compose --profile qdrant up -d  # required for the Qdrant half
bun run bench:vector                   # 500, 1000, 5000, full (skip targets >= loaded pages)
bun run bench:vector -- --size 500     # one size

Benchmark data and embeddings are cached under data/vector-bench/. It never writes to the app database (LUCENTDOCS_DATA_DIR/sqlite.db).

Development

Package layout
Package Role
apps/api Express + tRPC backend, Yjs, job workers
apps/web Vite + React frontend
packages/core Rust native module (@lucentdocs/core) — SQLite storage, embeddings prep, markdown
packages/shared Shared TypeScript types and config

Prerequisites

  • Bun
  • Rust toolchain (rustup)
  • Linux: libsqlite3-dev and pkg-config (for the default native build)

Native storage (packages/core)

All SQLite I/O runs in Rust and is exposed to Node/Bun via NAPI. TypeScript uses thin adapters in apps/api/src/infrastructure/rust/. Vector search uses sqlite-vec, compiled into the native module (no npm platform packages).

Build the native addon:

cd packages/core
npm run build              # links system SQLite (default)
npm run build:bundled      # embeds SQLite when pkg-config is unavailable

Cross-compiled targets (musl, aarch64 Linux, Android) use bundled SQLite; the nightly CI workflow passes --features sqlite-bundled for those matrix entries automatically. Musl cross-builds also need packages/core/musl_compat.h on the compiler include path (CI sets this via CFLAGS_<target>).

Testing

bun run test

Tests use isolated data under data-test/ (LUCENTDOCS_TEST_MODE=1). In-memory test databases write temp files to apps/api/tmp/ by default. If /tmp is a small tmpfs and fills up, set TMPDIR or LUCENTDOCS_MEM_DB_DIR to a path on disk.

Production

bun run build
bun run serve

Docker deployment

cp .env.example .env   # edit as needed
docker compose up -d

Pulls sandmor/lucentdocs:latest. The app is at http://localhost:5677. Data persists in the lucentdocs-data volume. Compose sets HOST, NODE_ENV, LUCENTDOCS_DATA_DIR, and QDRANT_URL for the container.

CI image publishing

The Docker workflow builds on pull requests and pushes to Docker Hub on pushes to master and version tags (v*).

Set these repository secrets:

Secret Value
DOCKER_USERNAME Docker Hub username
DOCKER_PASSWORD Docker Hub access token

On master, published tags include latest and sha-<short-sha>. Version tags (for example v1.2.3) also receive semver tags.

Other commands

bun run typecheck
bun run lint
bun run format
bun run bench:vector

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages