| .. | ||
| examples | ||
| src | ||
| test | ||
| .cursorrules | ||
| .gitignore | ||
| bun.lock | ||
| package.json | ||
| README.md | ||
| tsconfig.json | ||
?uery
?uery is a SQL library for Bun that helps glue your database and class objects together with little setup and minimal fuss.
The API is simple and fully typed.
Don't hide SQL - embrace it.
Currently supports both SQLite and PlanetScale. Best used for prototypes and all kinds of spikes.
SQL
You can use SQL, but data mapping (next section) is the best way to use this library.
import { openDB } from "query"
const db = await openDB({ sqlite: "users.db" })
await db.run(
"CREATE TABLE IF NOT EXISTS users (ID INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)"
)
await db.run("INSERT INTO users (name) VALUES ($name)", { name: "John" })
const user = await db.queryOne("SELECT * FROM users WHERE name = $name", {
name: "John",
})
console.log("Name:", user["name"])
Data Mapping
It's not an ORM. Think of it more like SQL queries as typed templates, automatically created based on your class objects.
import { openDB } from "query"
// Define your model - just needs an id property
class User {
id: number
name: string
}
const db = await openDB({ sqlite: "users.db" }).model(User, "users") // connect your model to a db table <-- this is where the magic happens
await db.run(
"CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)"
)
const user = await db.users.create({ name: "John" })
console.log(user.name) // "John"
const john = await db.users.find(1) // or await db.users.one({name: "John"})
const updated = await db.users.update(1, { name: "John Smith" })
const smiths = await db.users.all("name LIKE $name", { name: "Smith%" })
await db.users.delete(1)
// SQL is encouraged
const results = await db.query<User>(
"SELECT * FROM users WHERE name LIKE $search",
{ search: "J%" }
)
async API
Everything needs await. Everything.
Queries
The main way you'll query the DB is with db.table.one() and db.table.all().
Each takes either:
-
SQL String + Args
-
Query Builder
// SQL Args
const everyoneNamedBob = db.users.all("name = $name LIMIT 2", { name: "Bob" })
// Query Builder
const everyoneNamedJon = db.users.all({ name: "Jon", age: 20, limit: 2 })
The Query Builder has three special keys:
- limit
- offset
- order
Database Operations
openDB(config: { sqlite: string } | { planetscale: PlanetScaleConfig }): Opens a database connection.model(modelClass: Model, tableName: string): DB: Chain this withopenDB()to get yourdbobjects.db.query(sql: string, args?: {}, map?: (row: T) => U): U[]: Query multiple rowsdb.queryOne(sql: string, args?: {}, map?: (row: T) => U): U | null: Query single rowdb.run(sql: string, args?: {}): Execute SQL with no resultsdb.transaction(fn): Run multiple SQL statements together, rolling them all back if an error is thrown.db.close(): Close connection
Mapper API
db.table.find(id: string | number): Model | null: Find a record by IDdb.table.create(data: Partial<Model>): Model: Create a new recorddb.table.update(id: string | number, data: Partial<Model>): Model | null: Update a recorddb.table.delete(id: string | number): boolean: Delete a recorddb.table.one(where?: string, args?: Record<string, any>): Model | null: Get one record with optional where clausedb.table.all(where?: string | Record<string, any>, args?: Record<string, any>): Model[]: Get all records with optional where clause or object-based querydb.table.query(sql: string, args?: {}): Model[]: Query multiple rowsdb.table.queryOne(sql: string, args?: {}): Model | null: Query single row
Database Operations
// Basic CRUD
const user = await db.users.find(1)
const users = await db.users.all()
const filtered = await db.users.all("age > $age", { age: 18 })
const byName = await db.users.all({ name: "John" }) // Object-based query
const oneUser = await db.users.one("email = $email", {
email: "test@example.com",
})
// Transactions
await db.transaction(async (tx) => {
const user = await db.users.create({ name: "Alice" })
const post = await db.posts.create({ userId: user.id, title: "First Post" })
})