Skip to content

Quick Start

Install Lucerna as a library dependency:

Terminal window
pnpm add @upstart.gg/lucerna
# or
npm install @upstart.gg/lucerna
# or
bun add @upstart.gg/lucerna

Then 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-sitter
await 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();

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 loading

This requires a Homebrew SQLite build on disk:

Terminal window
brew install sqlite

There are three ways to wire this up, in order of robustness:

Create a preload script:

preload.ts
import { configureBunSqlite } from '@upstart.gg/lucerna';
configureBunSqlite();

Launch your app with --preload:

Terminal window
bun --preload ./preload.ts src/server.ts

Preloads run to completion before your entry file’s imports resolve, so this is immune to import-ordering issues.

// server.ts — must be the FIRST import, above every other import
import '@upstart.gg/lucerna/configure';
import { Database } from 'bun:sqlite'; // safe now
// …everything else

ESM 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.

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.