Getting started
Prerequisites
- Bun v1.4.0+ (required for
bun:ffi; tested on 1.4.0 — Bun 1.3.x is not supported because of FFI cleanup crashes). - Linux x86_64 only — the FFI layer loads a native shared library and refuses to import anywhere else (
process.platform === "linux"andprocess.arch === "x64", enforced at load time). - glibc 2.38+ — the native binaries are dynamically linked against glibc. Older distros (Debian 11 "bullseye", Ubuntu 20.04 "focal", RHEL 8, and older) fail at
dlopen()time withlibnostrdb.so: cannot open shared object file: GLIBC_2.38 not found(__isoc23_strtolis the highest required symbol). - The native library, built from the pinned nostrdb submodule:
Compatibility matrix
| Component | Minimum | Tested | Notes |
|---|---|---|---|
| OS | Linux x86_64 | Linux x86_64 | Enforced at load time. macOS/Windows are out of scope. |
| glibc | 2.38 | 2.43 | libnostrdb.so requires GLIBC_2.38. Older distros fail at dlopen() time. |
| Bun | 1.4.0 | 1.4.0 | Required for bun:ffi. Bun 1.3.x is not supported because of FFI cleanup crashes. |
Source builds additionally need a C toolchain (build-essential or equivalent), GNU Make (not BSD Make), and GNU coreutils (file, grep, find). npm consumers skip this — bun add bunostrdb ships prebuilt Linux x86_64 shared objects.
bash
bun run build:nativeThis produces packages/bunostrdb/native/libnostrdb.so (+ the struct-by-value shim). The loader verifies the submodule commit at import time and throws if the .so was built from anything else.
Install
bash
bun add bunostrdbYour first database
ts
// @ts-nocheck
import { buildDraftNote, BunNdb, FilterBuilder } from "bunostrdb";
// A missing directory is created for you — first run just works.
{
using ndb = new BunNdb("./data");
// Signed sample event — drafts are real events, so they ingest cleanly.
const { event } = buildDraftNote("hello local world");
// Ingest: queue one event (JSON string) for the background parser,
// then wait until it is committed before reading it back.
ndb.processEvent(JSON.stringify(["EVENT", "sub1", event]));
await ndb.waitForNoteById(event.id);
// Query: build a filter, read matches — `using` destroys the filter.
using filter = new FilterBuilder().kinds([1]).limit(10).build();
for (const note of ndb.query([filter], 10)) {
console.log(note.id, note.content);
}
// Search: ranked full-text over everything ingested so far.
console.log(ndb.textSearch("hello", { limit: 10 }).length);
} // ndb destroyed here, even on throwOwnership rules
The FFI layer has no garbage-collector backstop for native memory. Three rules keep you sound:
ndb.destroy()is mandatory — or useusing. Nothing frees the database handle for you.using ndb = new BunNdb(...)destroys it at scope exit; otherwise wrapdestroy()intry/finally. With a subscription callback, preferawait using(drains posted callbacks viadestroyAsync()). See Resource management.- Built filters must be destroyed.
builder.build()transfers ownership to theFilter;subscribe()builds and destroys builders internally, but aFilteryou hold needsfilter.destroy(). - Ingestion is asynchronous.
processEvent()queues work for the background parser — it does not mean "stored". To observe Committed data,subscribe()+await waitForNotes(subId), or poll withpollForNotes(). The CLI's ingest path waits viawaitForNoteById()(default 10s,--timeout <ms>) for the same reason.
Violating lifecycle rules throws NdbError with a Lifecycle code instead of corrupting memory — but the throw is the backstop, not the plan.
Troubleshooting
mdb_env_open failed— ancient history for missing directories (now auto-created). If you still see it, the path exists as a file or permissions deny creation.NdbError: initat import — the.sodoesn't match the pinned nostrdb commit. Rebuild withbun run build:native.GLIBC_2.38 not foundatdlopen()— glibc too old (Debian 11, Ubuntu 20.04, RHEL 8). Upgrade the host; there is no static-glibc build.- Import errors on other platforms/architectures — expected: Linux x86_64
- Bun only, enforced at import time. macOS/Windows are out of scope.
- FFI crashes on Bun 1.3.x — upgrade to Bun 1.4.0+.
Next steps
- Integrations — relays, scripts, apps.
- Resource management —
using/await usingfor every handle. - Architecture — native build, loader, threading, LMDB locking.
- Personal draft DB (CLI) — the CLI from zero to search.
