Skip to content

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
// @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
// @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:

bash
# 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:

bash
npx bunostrdb ingest --db ./data --event event.json

Local-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
// @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.

Released under the BSD-3.0 License.