Integrations
bunostrdb is a storage engine, not a network client: there is no relay, websocket, or signer code inside. Every integration follows one pattern — events in as JSON, answers out as objects — over the library or the CLI, whichever fits the host.
Relay → database
Bring your own relay connection (any library, any runtime that can call into Bun). Forward each EVENT envelope to the background parser; batch backfills through processEvents().
// @ts-nocheck
import { buildDraftNote, BunNdb } from "bunostrdb";
{
using ndb = new BunNdb("./data.relay");
// Wire this to any relay library — one call per EVENT envelope.
function onRelayEvent(subId: string, event: unknown): void {
ndb.processEvent(JSON.stringify(["EVENT", subId, event]));
}
// Demo with a local signed event standing in for a relay message.
const { event } = buildDraftNote("hello from the relay");
onRelayEvent("sub1", event);
await ndb.waitForNoteById(event.id);
// Backfill batch as NDJSON (one envelope per line) in a single call.
const more = [buildDraftNote("backfill one"), buildDraftNote("backfill two")];
const ndjson = more.map((draft) => JSON.stringify(["EVENT", "sub1", draft.event])).join("\n");
ndb.processEvents(ndjson);
for (const draft of more) await ndb.waitForNoteById(draft.id);
console.log("relay events committed:", more.length + 1);
}Pair it with a subscription to know when forwarded events are committed:
// @ts-nocheck
import { buildDraftNote, BunNdb, FilterBuilder } from "bunostrdb";
{
using ndb = new BunNdb("./data.relay-live");
const subId = ndb.subscribe([new FilterBuilder().kinds([1])]);
const { event, id } = buildDraftNote("hello live world");
ndb.processEvent(JSON.stringify(["EVENT", "sub1", event]));
await ndb.waitForNotes(subId);
console.log(ndb.pollForNotes(subId, 128), id);
}CLI scripting
Every subcommand reads --db <dir> (default ./data, auto-created) and the JSON-accepting options take a literal document, a file path, or - for stdin — so the CLI composes with pipes and cron:
# Relay dump (or any producer) straight into the DB.
websocat wss://relay.damus.io < req.json | bun run cli -- ingest --db ./data --event -
# Filtered reads as JSON criteria from a file.
bun run cli -- query --db ./data --filter ./timeline.json --limit 50
# Single-note / profile lookups for scripts and hooks.
bun run cli -- get-note --db ./data --id <64-hex-id>
bun run cli -- get-profile --db ./data --pubkey <64-hex-pubkey>From Node
The published bunostrdb-cli package runs from Node too: its bunostrdb binary is a small Node shim that spawns Bun with the CLI entry (override the Bun binary with BUN_BIN=<path>), so npm scripts and npx can call the same subcommands:
npx bunostrdb ingest --db ./data --event event.jsonLocal-first apps
The drafts module is the app pattern for data that must never leave the device: mint an ephemeral identity per draft (or pin one via createDraftEvent({ secret })), sign locally, ingest, and search. See Personal draft DB (CLI) for the full walkthrough.
// @ts-nocheck
import { BunNdb, createDraftEvent, ingestDraft } from "bunostrdb";
{
using ndb = new BunNdb("./data.app");
// One stable identity across drafts instead of the default ephemeral one
// (`secret` is a 32-byte Buffer — store it wherever the app keeps secrets).
const secret = Buffer.from("11".repeat(32), "hex");
const draft = createDraftEvent({ secret, content: "my app draft" });
const id = await ingestDraft(ndb, draft.eventJson);
console.log("stored app draft", id);
}Full-text search in products
textSearch(term, { filter?, order?, limit? }) is ranked standalone search (limit capped at 128). Scope it with a native Filter for per-author / per-kind search boxes; use FilterBuilder.search() + query() when the search must compose with other predicates.
