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

bunely

v0.0.7

Published

A lightweight Query Builder for bun:sqlite

Readme

Bunely

A modern SQLite wrapper for Bun with query builder, schema management, and transaction support.

Features

  • 🚀 Fluent Query Builder - Chainable API for SELECT, INSERT, UPDATE, DELETE
  • 🏗️ Schema Management - Create, modify, and introspect database schemas
  • 🔄 Transaction Support - Nested transactions with savepoints and retry logic
  • 🛡️ Type Safety - Full TypeScript support with comprehensive type definitions
  • Performance - Built on Bun's native SQLite bindings
  • 🔧 SQLite Compatible - Works with all SQLite features and constraints

Quick Start

import { createMemoryDatabase } from "bunely";

// Create a database
const db = createMemoryDatabase();

// Enable foreign keys
db.enableForeignKeys();

// Create tables using schema builder
await db.schema.createTable("users")
    .ifNotExists(true)
    .addColumn({
        name: "id",
        type: "INTEGER",
        primaryKey: true,
        autoIncrement: true
    })
    .addColumn({
        name: "username",
        type: "TEXT",
        notNull: true,
        unique: true
    })
    .addColumn({
        name: "email",
        type: "TEXT",
        notNull: true,
        unique: true
    })
    .execute();

// Insert data using query builder
const user = await db.insert("users")
    .values({
        username: "alice",
        email: "[email protected]"
    })
    .execute();

// Query data
const users = await db.select()
    .from("users")
    .where({ username: "alice" })
    .execute();

// Use transactions
await db.transaction(async (tx) => {
    await tx.insert("users").values({ username: "bob", email: "[email protected]" }).execute();
    await tx.insert("users").values({ username: "charlie", email: "[email protected]" }).execute();
});

Installation

bun add bunely

API Reference

Database Creation

import { createMemoryDatabase, createFileDatabase, createDatabase } from "bunely";
import { Database as SQLiteDatabase } from "bun:sqlite";

// In-memory database
const db = createMemoryDatabase();

// File-based database
const db = createFileDatabase("database.sqlite");

// From existing SQLite database
const sqliteDb = new SQLiteDatabase("database.sqlite");
const db = createDatabase(sqliteDb);

Query Builder

SELECT Queries

// Simple select
const users = await db.select()
    .from("users")
    .execute();

// Select with conditions (object syntax)
const alice = await db.select()
    .from("users")
    .where({ username: "alice" })
    .first();

// Select with explicit where conditions
const aliceExplicit = await db.select()
    .from("users")
    .where("username", "=", "alice")
    .first();

// Select with joins
const postsWithUsers = await db.select([
    "p.title",
    "u.username"
])
    .from("posts as p")
    .join("users as u", "p.user_id = u.id")
    .execute();

// Complex queries
const recentPosts = await db.select()
    .from("posts")
    .where({ status: "published" })
    .orderBy(["created_at DESC"])
    .limit(10)
    .execute();

// Explicit where conditions with operators
const adults = await db.select()
    .from("users")
    .where("age", ">", 18)
    .where("is_active", "=", true)
    .execute();

// LIKE operators
const aliceUsers = await db.select()
    .from("users")
    .where("username", "LIKE", "alice%")
    .execute();

// IN operators
const specificUsers = await db.select()
    .from("users")
    .where("id", "IN", [1, 2, 3])
    .execute();

// BETWEEN operators
const middleAged = await db.select()
    .from("users")
    .where("age", "BETWEEN", 25, 65)
    .execute();

// NULL operators
const usersWithEmail = await db.select()
    .from("users")
    .where("email", "IS NOT NULL")
    .execute();

Supported Where Operators

Bunely supports the following operators for explicit where conditions:

Comparison Operators:

  • = - Equal to
  • != or <> - Not equal to
  • < - Less than
  • > - Greater than
  • <= - Less than or equal to
  • >= - Greater than or equal to

Pattern Matching:

  • LIKE - Pattern matching (use % for wildcards)
  • NOT LIKE - Negated pattern matching

Set Operations:

  • IN - Value is in a list
  • NOT IN - Value is not in a list

Range Operations:

  • BETWEEN - Value is between two values (inclusive)
  • NOT BETWEEN - Value is not between two values

Null Operations:

  • IS NULL - Value is null
  • IS NOT NULL - Value is not null

Examples:

// Comparison
.where("age", ">", 18)
.where("price", "<=", 100)

// Pattern matching
.where("name", "LIKE", "John%")
.where("email", "NOT LIKE", "%@spam.com")

// Set operations
.where("status", "IN", ["active", "pending"])
.where("category", "NOT IN", ["deleted", "archived"])

// Range operations
.where("age", "BETWEEN", 18, 65)
.where("price", "NOT BETWEEN", 10, 50)

// Null operations
.where("description", "IS NULL")
.where("email", "IS NOT NULL")

INSERT Queries

// Single insert
const result = await db.insert("users")
    .values({
        username: "alice",
        email: "[email protected]"
    })
    .execute();

// Multiple inserts
const results = await db.insert("users")
    .values([
        { username: "bob", email: "[email protected]" },
        { username: "charlie", email: "[email protected]" }
    ])
    .execute();

// Insert with returning
const user = await db.insert("users")
    .values({ username: "david", email: "[email protected]" })
    .returning(["id", "username"])
    .execute();

UPDATE Queries

// Update with conditions (object syntax)
const result = await db.update("users")
    .set({ age: 30 })
    .where({ username: "alice" })
    .execute();

// Update with explicit where conditions
const resultExplicit = await db.update("users")
    .set({ age: 30 })
    .where("username", "=", "alice")
    .execute();

// Update multiple records
const result = await db.update("posts")
    .set({ status: "published" })
    .where("status", "=", "draft")
    .execute();

// Update with comparison operators
const adultUsers = await db.update("users")
    .set({ verified: true })
    .where("age", ">=", 18)
    .execute();

DELETE Queries

// Delete with conditions (object syntax)
const result = await db.delete("users")
    .where({ username: "alice" })
    .execute();

// Delete with explicit where conditions
const resultExplicit = await db.delete("users")
    .where("username", "=", "alice")
    .execute();

// Delete with comparison operators
const oldPosts = await db.delete("posts")
    .where("created_at", "<", "2023-01-01")
    .execute();

// Delete with returning
const deleted = await db.delete("posts")
    .where("status", "=", "draft")
    .returning(["id", "title"])
    .execute();

Schema Management

Creating Tables

await db.schema.createTable("users")
    .ifNotExists(true)
    .addColumn({
        name: "id",
        type: "INTEGER",
        primaryKey: true,
        autoIncrement: true
    })
    .addColumn({
        name: "username",
        type: "TEXT",
        notNull: true,
        unique: true
    })
    .addColumn({
        name: "email",
        type: "TEXT",
        notNull: true,
        unique: true
    })
    .addColumn({
        name: "created_at",
        type: "DATETIME",
        defaultValue: "CURRENT_TIMESTAMP"
    })
    .addForeignKey({
        column: "profile_id",
        references: {
            table: "profiles",
            column: "id"
        },
        onDelete: "CASCADE"
    })
    .execute();

Creating Indexes

await db.schema.createIndex("idx_users_email")
    .on("users")
    .columns(["email"])
    .unique(true)
    .ifNotExists(true)
    .execute();

await db.schema.createIndex("idx_posts_user_id")
    .on("posts")
    .columns(["user_id"])
    .ifNotExists(true)
    .execute();

Schema Introspection

// Check if table exists
const hasTable = await db.schema.hasTable("users");

// Get table structure
const columns = await db.schema.getTableInfo("users");

// Get foreign keys
const foreignKeys = await db.schema.getForeignKeys("posts");

// Get indexes
const indexes = await db.schema.getIndexes("users");

Transactions

// Simple transaction
await db.transaction(async (tx) => {
    await tx.insert("users").values({ username: "alice" }).execute();
    await tx.insert("posts").values({ user_id: 1, title: "My Post" }).execute();
});

// Transaction with options
await db.transaction(async (tx) => {
    // Your operations here
}, {
    tries: 3,
    backoffMs: 100
});

// Nested transactions
await db.transaction(async (tx) => {
    await tx.insert("users").values({ username: "alice" }).execute();
    
    await tx.transaction(async (innerTx) => {
        await innerTx.insert("posts").values({ user_id: 1, title: "Nested Post" }).execute();
    });
});

Convenience Methods

// Find records
const users = await db.find("users", { status: "active" });
const user = await db.findOne("users", { username: "alice" });

// Create record
const result = await db.create("users", { username: "alice", email: "[email protected]" });

// Update record
const result = await db.updateOne("users", { age: 30 }, { username: "alice" });

// Delete record
const result = await db.deleteOne("users", { username: "alice" });

Raw SQL

// Execute raw SQL
const result = await db.run("INSERT INTO users (username) VALUES (?)", ["alice"]);
const users = await db.all("SELECT * FROM users WHERE age > ?", [25]);
const user = await db.get("SELECT * FROM users WHERE id = ?", [1]);

Type Definitions

Bunely provides comprehensive TypeScript types for all operations:

import type { 
    ColumnDefinition, 
    ForeignKeyDefinition, 
    IndexDefinition,
    WhereClause,
    TransactionOptions 
} from "bunely";

const column: ColumnDefinition = {
    name: "id",
    type: "INTEGER",
    primaryKey: true,
    autoIncrement: true
};

Examples

Check out the examples/ directory for comprehensive examples:

  • query-builder.ts - Complete query builder example
  • basic-schema.ts - Basic schema operations
  • schema-example.ts - Schema utilities example
  • schema-management.ts - Advanced migration management

Architecture

The library is organized into several modules:

  • src/core/ - Core database class and mutex
  • src/query/ - Query builders (SELECT, INSERT, UPDATE, DELETE)
  • src/schema/ - Schema builders (CREATE TABLE, ALTER TABLE, etc.)
  • src/types/ - TypeScript type definitions

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

MIT