# ?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.

```typescript
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.

```typescript
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:

1. SQL String + Args

2. Query Builder

```typescript
// 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 with `openDB()` to get your `db` objects.
- `db.query(sql: string, args?: {}, map?: (row: T) => U): U[]`: Query multiple rows
- `db.queryOne(sql: string, args?: {}, map?: (row: T) => U): U | null`: Query single row
- `db.run(sql: string, args?: {})`: Execute SQL with no results
- `db.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 ID
- `db.table.create(data: Partial<Model>): Model`: Create a new record
- `db.table.update(id: string | number, data: Partial<Model>): Model | null`: Update a record
- `db.table.delete(id: string | number): boolean`: Delete a record
- `db.table.one(where?: string, args?: Record<string, any>): Model | null`: Get one record with optional where clause
- `db.table.all(where?: string | Record<string, any>, args?: Record<string, any>): Model[]`: Get all records with optional where clause or object-based query
- `db.table.query(sql: string, args?: {}): Model[]`: Query multiple rows
- `db.table.queryOne(sql: string, args?: {}): Model | null`: Query single row

### Database Operations

```typescript
// 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" })
})
```
