npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

busybase

v1.0.0

Published

HTTP SQLite database that feels like Supabase — drop-in headless backend powered by Bun

Downloads

132

Readme

BusyBase

License: MIT Built with Bun LanceDB Supabase Compatible Releases

A minimal, drop-in Supabase alternative — self-hosted, no Docker, no Postgres, no config files.

Built on Bun + LanceDB. Single process. File-based storage. Native vector search. Ed25519 keypair auth (anonymous-first). Supabase JS v2 compatible API. Ships as a single binary.

Documentation · Website · Releases


Feature Table

| Feature | BusyBase | Supabase (self-hosted) | PocketBase | |---|:---:|:---:|:---:| | Supabase JS v2 compatible | ✅ | ✅ | ❌ | | Single binary deploy | ✅ | ❌ | ✅ | | No Docker required | ✅ | ❌ | ✅ | | Native vector search | ✅ | ⚠️ pgvector | ❌ | | Ed25519 keypair auth | ✅ | ❌ | ❌ | | Anonymous-first auth | ✅ | ⚠️ anon key | ❌ | | File-based storage | ✅ | ❌ | ✅ | | Pluggable hooks (18 hooks) | ✅ | ⚠️ Edge Functions | ⚠️ JS hooks | | Zero config startup | ✅ | ❌ | ✅ | | Built-in SMTP email | ✅ | ⚠️ external | ✅ | | Realtime subscriptions | ✅ | ✅ | ✅ |


Quick Start

# Start server (default: http://localhost:54321)
bunx busybase serve

# Or download a standalone binary — no Bun required
curl -L https://github.com/AnEntrypoint/busybase/releases/latest/download/busybase-linux-x64 -o busybase
chmod +x busybase && ./busybase serve
import BB from "busybase";

const db = BB("http://localhost:54321", "local");

// Anonymous-first auth — no email/password needed!
const { data: { user, session } } = await db.auth.keypair.signIn();

// Insert (auto-creates table)
await db.from("todos").insert({ title: "Buy milk", done: false });

// Query
const { data } = await db.from("todos").select("*").eq("done", false);

// Vector search
await db.from("docs").insert({ text: "Bun is fast", vector: [0.9, 0.1, 0.0] });
const { data: results } = await db.from("docs").select("*").vec([0.85, 0.1, 0.0], 5);
# Run the full SDK test suite against your live server
bunx busybase test

Installation

# Run without installing
bunx busybase serve

# Install globally
bun install -g busybase

# SDK only
bun add busybase
npm install busybase

Auth

BusyBase supports two auth flows — both return the standard { data, error } shape.

Keypair Auth (Ed25519 — anonymous-first, zero deps)

// Sign in instantly — no email, no password, no form
const { data } = await db.auth.keypair.signIn();
// data.user.id — persistent UUID, same every time
// data.session.access_token

// Backup your keypair (save these!)
const { privkey, pubkey } = db.auth.keypair.export();

// Restore on any device
await db.auth.keypair.restore(privkey, pubkey);
// Same user.id ✓

// Add email/password to a keypair account later
await db.auth.updateUser({ email: "[email protected]", password: "secret" });

Email / Password

await db.auth.signUp({ email, password, options: { data: { name: "Alice" } } });
const { data: { user, session } } = await db.auth.signInWithPassword({ email, password });
await db.auth.updateUser({ password: "newpassword", data: { plan: "pro" } });
await db.auth.signOut();

Database (CRUD)

Tables are created automatically on first insert. No schema definition required.

// Insert
const { data } = await db.from("todos").insert({ title: "Buy milk" });
await db.from("todos").insert([{ title: "A" }, { title: "B" }]); // batch

// Select
await db.from("todos").select("*");
await db.from("todos").select("id,title"); // specific columns

// Filters
await db.from("todos").select("*")
  .eq("done", false)
  .like("title", "Buy")
  .order("title", { ascending: true })
  .limit(20)
  .offset(0);

// Update
await db.from("todos").update({ done: true }).eq("id", "abc");

// Delete
await db.from("todos").delete().eq("done", true);

Filter operators

| Method | SQL | |---|---| | .eq(col, val) | col = val | | .neq(col, val) | col != val | | .gt(col, val) | col > val | | .gte(col, val) | col >= val | | .lt(col, val) | col < val | | .lte(col, val) | col <= val | | .like(col, val) | col LIKE '%val%' | | .ilike(col, val) | case-insensitive LIKE | | .is(col, null) | col IS NULL | | .in(col, [a,b,c]) | col IN (a, b, c) | | .not(col, "eq", val) | NOT col = val | | .or("a.eq.1,b.eq.2") | a=1 OR b=2 |

Modifiers

| Method | Description | |---|---| | .order(col, { ascending }) | Sort results | | .limit(n) | Max rows (default: 1000) | | .offset(n) | Skip N rows | | .range(from, to) | Rows from–to inclusive | | .count("exact") | Add count + Content-Range header | | .single() | Return object (error if 0 rows) | | .maybeSingle() | Return object or null (no error) | | .vec(embedding, limit) | Vector similarity search |


Vector Search

// Insert with vectors
await db.from("articles").insert([
  { title: "Cats article", vector: [0.9, 0.1, 0.0, 0.0] },
  { title: "Dogs article", vector: [0.1, 0.9, 0.0, 0.0] },
]);

// Search by similarity (returns _distance field)
const { data } = await db.from("articles").select("*").vec([0.85, 0.15, 0.0, 0.0], 5);
// data[0].title === "Cats article"
// data[0]._distance === 0.02...

// Combine with filters
await db.from("articles").select("*").vec([...], 10).eq("category", "pets");

Works with any embedding model — OpenAI, Ollama, Cohere, local models, etc.


Realtime

Subscribe to table changes via WebSocket — INSERT, UPDATE, and DELETE events are broadcast to all active subscribers.

WebSocket endpoint

ws://localhost:54321/realtime/v1/websocket

Subscribe to a table by sending a JSON message after connecting:

{ "type": "subscribe", "table": "todos" }

Unsubscribe:

{ "type": "unsubscribe", "table": "todos" }

Event shape

{
  "event": "INSERT",
  "eventType": "INSERT",
  "table": "todos",
  "new": { "id": "abc", "title": "Buy milk" },
  "old": null
}

eventType is one of INSERT, UPDATE, or DELETE. For UPDATE, both new and old are populated. For DELETE, new is null.

SDK — channel() (Supabase-compatible)

const db = BB("http://localhost:54321", "local");

const ch = db.channel("todos-changes")
  .on("postgres_changes", { event: "*", schema: "public", table: "todos" }, (payload) => {
    console.log(payload.eventType, payload.new, payload.old);
  })
  .subscribe((status) => console.log("status:", status));

// Later:
ch.unsubscribe();

// Tear down all channels:
db.removeAllChannels();

Filter by event type:

db.channel("inserts-only")
  .on("postgres_changes", { event: "INSERT", schema: "public", table: "todos" }, (payload) => {
    console.log("New row:", payload.new);
  })
  .subscribe();

Hooks

Point BUSYBASE_HOOKS to a TypeScript file:

BUSYBASE_HOOKS=./hooks.ts bunx busybase serve

All 18 hooks are optional. Return { error: "message" } from any hook to abort the operation.

// hooks.ts
export const canAccess = ({ user, table, method }) => {
  if (!user && method !== "GET") return { error: "Login required" };
};

export const beforeInsert = (table, rows) => {
  if (table === "comments") rows.forEach(r => r.created_at = new Date().toISOString());
};

export const onSignup = async (user) => {
  await sendWelcomeEmail(user.email);
};

export const sendEmail = async ({ to, subject, html }) => {
  // Override built-in SMTP — use Resend, SendGrid, etc.
  await resend.emails.send({ from: "[email protected]", to, subject, html });
};

All available hooks

| Hook | Category | Description | |---|---|---| | onSignup(user) | Auth | New user created | | onSignin(user) | Auth | User signed in | | onSignout(user) | Auth | User signed out | | onEmailChange(user, newEmail) | Auth | Email update requested | | onPasswordReset(email, token) | Auth | Password reset requested | | onUserUpdate(user, changes) | Auth | User metadata updated | | onRequest(req) | Middleware | Every HTTP request (return Response to short-circuit) | | canAccess({ user, table, method }) | Access | Row-level access control | | beforeInsert(table, rows) | Data | Before rows inserted | | afterInsert(table, rows) | Data | After rows inserted (pipe) | | beforeUpdate(table, rows, changes) | Data | Before rows updated | | afterUpdate(table, rows) | Data | After rows updated (pipe) | | beforeDelete(table, rows) | Data | Before rows deleted | | afterDelete(table, rows) | Data | After rows deleted | | beforeSelect(table, params) | Data | Before query executes (pipe) | | afterSelect(table, rows) | Data | After query executes (pipe) | | sendEmail({ to, subject, html }) | Email | Override all email sending | | onIssueSession(user) | Session | Customize session payload |


REST API

| Method | Path | Description | |---|---|---| | GET | /rest/v1/:table | Query rows | | POST | /rest/v1/:table | Insert rows | | PATCH | /rest/v1/:table?eq.col=val | Update rows | | DELETE | /rest/v1/:table?eq.col=val | Delete rows | | GET | /auth/v1/keypair | Get nonce (step 1 of keypair auth) | | POST | /auth/v1/keypair | Verify signature (step 2) | | POST | /auth/v1/signup | Register email/password user | | POST | /auth/v1/token | Sign in with email/password | | GET | /auth/v1/user | Get current user | | PATCH | /auth/v1/update | Update user | | POST | /auth/v1/logout | Sign out | | POST | /auth/v1/recover | Request password reset | | POST | /auth/v1/verify | Confirm reset token + new password |

Supports: Prefer: return=minimal (204 response), Prefer: count=exact + Content-Range header, CORS on all routes.


CLI

busybase serve                           # Start server
busybase test                            # Full SDK e2e test suite
busybase signup [email protected] pass    # Register user
busybase signin [email protected] pass    # Sign in (prints token)
busybase user                            # Get current user
busybase insert todos '{"title":"Buy milk"}'
busybase query  todos done=false
busybase update todos '{"done":"true"}' title=Buy\ milk
busybase delete todos done=true
busybase vec embeddings '[1,0,0,0]' 5    # Vector search

Configuration

| Variable | Default | Description | |---|---|---| | BUSYBASE_PORT | 54321 | HTTP port | | BUSYBASE_DIR | busybase_data | Data directory (LanceDB Arrow files) | | BUSYBASE_URL | http://localhost:54321 | Public URL (used in reset email links) | | BUSYBASE_HOOKS | — | Path to your hooks file | | BUSYBASE_SMTP_HOST | — | SMTP hostname | | BUSYBASE_SMTP_PORT | 587 | SMTP port | | BUSYBASE_SMTP_USER | — | SMTP username | | BUSYBASE_SMTP_PASS | — | SMTP password | | BUSYBASE_SMTP_FROM | SMTP_USER | From address |


Standalone Binaries

Every push to master builds self-contained executables — no Bun or Node.js required:

| Platform | File | |---|---| | Linux x64 | busybase-linux-x64 | | macOS ARM64 (Apple Silicon) | busybase-macos-arm64 | | Windows x64 | busybase-windows-x64.exe |

Download from Releases.


Architecture

  • Runtime: Bun — native TypeScript, sub-ms startup, single binary compilation
  • Storage: LanceDB — Apache Arrow columnar files, no server process, cp -r to backup
  • Auth: Ed25519 via WebCrypto (zero deps) + bcrypt via Bun.password
  • Sessions: UUID tokens, 7-day expiry, stored in _sessions LanceDB table
  • Vector search: LanceDB ANN — rows without vectors get a transparent sentinel [0]
  • CLI = SDK = Server — the CLI uses the real SDK, making busybase test a true e2e test runner

License

MIT — see LICENSE.