Quick Start
Install Lucerna as a library dependency:
pnpm add @upstart.gg/lucerna# ornpm install @upstart.gg/lucerna# orbun add @upstart.gg/lucernaThen index and search your project:
import { CodeIndexer } from '@upstart.gg/lucerna';
const indexer = new CodeIndexer({ projectRoot: '/path/to/your/project',});
await indexer.initialize(); // open DB, init tree-sitterawait indexer.indexProject(); // walk and chunk all matching files
// Hybrid search (semantic + BM25 fused via RRF)const results = await indexer.search('authentication middleware', { limit: 5 });
for (const r of results) { console.log(`${r.chunk.filePath}:${r.chunk.startLine} [${r.chunk.type}] ${r.chunk.name ?? ''}`); console.log(r.chunk.content.slice(0, 200));}
await indexer.close();Running under Bun on macOS
Section titled “Running under Bun on macOS”Lucerna’s SQLite backend uses the sqlite-vec extension. On macOS, Apple’s system SQLite is compiled without dynamic extension loading, which breaks sqlite-vec. Under Bun, Lucerna works around this by pointing bun:sqlite at a Homebrew-installed SQLite via Database.setCustomSQLite().
The catch: setCustomSQLite is a once-per-process switch that is sealed the moment any bun:sqlite Database is opened anywhere in the process. If your host program (or any dependency) opens a bun:sqlite Database before Lucerna initializes, Lucerna can’t win that race — and you’ll see:
error: This build of sqlite3 does not support dynamic extension loadingThis requires a Homebrew SQLite build on disk:
brew install sqliteThere are three ways to wire this up, in order of robustness:
1. bun --preload (recommended)
Section titled “1. bun --preload (recommended)”Create a preload script:
import { configureBunSqlite } from '@upstart.gg/lucerna';configureBunSqlite();Launch your app with --preload:
bun --preload ./preload.ts src/server.tsPreloads run to completion before your entry file’s imports resolve, so this is immune to import-ordering issues.
2. Side-effect subpath import
Section titled “2. Side-effect subpath import”// server.ts — must be the FIRST import, above every other importimport '@upstart.gg/lucerna/configure';import { Database } from 'bun:sqlite'; // safe now// …everything elseESM evaluates imports in source order and runs each module’s top-level code before moving on. Putting @upstart.gg/lucerna/configure first guarantees setCustomSQLite runs before any other module can open a bun:sqlite Database.
3. Manual call
Section titled “3. Manual call”import { configureBunSqlite } from '@upstart.gg/lucerna';configureBunSqlite();configureBunSqlite() is a no-op on Node and on non-macOS platforms, so it’s safe to call unconditionally. Set LUCERNA_SQLITE_LIB=/path/to/libsqlite3.dylib to override the default Homebrew paths.
Alternative: run Lucerna in a Node subprocess. better-sqlite3 (used under Node) bundles its own extension-capable SQLite, so none of this applies.