Skip to content

TypeScript SDK

@lumbung/client has no runtime dependencies and only needs fetch, so it runs in Node, Bun, Deno, Workers and the browser.

import { createClient } from "@lumbung/client"
const db = createClient({
url: "http://127.0.0.1:4870",
token: process.env.LUMBUNG_TOKEN!,
database: "app",
})
const { rows } = await db.query("SELECT id, email FROM users WHERE id = ?", [1])
Option Type Default What it does
url string required The engine’s base URL.
token string required A Lumbung token.
database string Database name or id for query and batch.
branch string the default branch Branch name or id.
intMode "number", "bigint", "string" "number" How integers come back. "number" throws a RangeError for an integer past 2^53 instead of rounding it.
fetch typeof fetch globalThis.fetch A fetch to use, for tests or custom agents.
headers object Extra headers on every request.
retries number 2 Retries for safe reads only (see below).

client.branch(name) and client.database(name, branch?) return a client bound to another branch or database, sharing the same options.

// Positional parameters
await db.query("INSERT INTO users (email) VALUES (?)", ["budi@example.com"])
// Named parameters (:name, @name or $name)
await db.query("SELECT * FROM users WHERE email = :email", { email: "budi@example.com" })
// One transaction: all or nothing
await db.batch([
{ sql: "INSERT INTO users (email) VALUES (?)", args: ["cici@example.com"] },
"UPDATE users SET name = 'Cici' WHERE email = 'cici@example.com'",
])
// A read-only connection, even with a write token. Also makes it safe to retry.
await db.query("SELECT count(*) AS n FROM users", [], { readOnly: true })
// A schema migration: foreign keys are checked once at the end, so a table
// rebuild doesn't fire ON DELETE CASCADE.
await db.batch(["ALTER TABLE users ADD COLUMN bio TEXT"], { migration: true })

A Result has:

Field Type
columns string[] Column names in order. Repeated names appear more than once.
columnTypes string[] Declared types, "" for expressions.
rows Record<string, Value>[] Rows as objects. With repeated names the last column wins; use values.
values Value[][] Rows as arrays in column order.
rowsAffected number Rows changed by an INSERT, UPDATE or DELETE.
lastInsertRowid bigint The last inserted rowid, or 0n.
SQLite You get You can send
NULL null null, undefined
INTEGER number (or bigint / string per intMode) number (safe integers), bigint, boolean (1/0)
REAL number number with a fraction, -0, Infinity, or whole numbers too big for 64-bit integers (1e21)
TEXT string string, Date (as an ISO string)
BLOB Uint8Array Uint8Array, ArrayBuffer

Integers travel as decimal strings, so a bigint keeps every bit. A whole JS number between 2^53 and 2^63 is refused with a RangeError: as a number it may already have been rounded, so pass a bigint for an exact integer. See Value encoding.

Every failure is a LumbungError with a stable code (also a success response whose body isn’t valid JSON, as invalid_response) from the error codes, the HTTP status, SQLite’s message in detail, and the requestId to find it in the server log. Network failures have code network_error and status 0.

import { LumbungError } from "@lumbung/client"
try {
await db.query("INSERT INTO users (email) VALUES (?)", ["budi@example.com"])
} catch (err) {
if (err instanceof LumbungError && err.code === "constraint_violation") {
// already there
} else throw err
}

Only requests that can’t change data are retried: GET requests, and queries or batches with readOnly: true. They retry on busy, rate_limited and gateway errors, with a short backoff. Writes are never retried by the SDK; use an idempotencyKey on creates if you retry them yourself.

import { createClient } from "@lumbung/client"
// No database option: management calls name the database themselves.
const admin = createClient({ url: "http://127.0.0.1:4870", token: process.env.LUMBUNG_TOKEN! })
await admin.databases.create("shop")
await admin.branches.create("shop", "pr-42", { expiresInDays: 7 })
const diff = await admin.branches.diff("shop", "pr-42", "main")
const snap = await admin.snapshots.create("shop", "main", { label: "before deploy" })
await admin.snapshots.restore("shop", snap.id, "restored")
const ci = await admin.tokens.create({ name: "ci", database: "shop", access: "write" })
console.log(diff.changes.length, ci.token.slice(0, 4))
Group Methods
databases list, trash, get, create, rename, delete, restore
branches list, get, create, update, delete, reset, promote, diff, schema, stats
snapshots list, create, restore, delete, schedule.get, schedule.set, schedule.delete
tokens list, create, revoke
audit list

Lists return { data, nextCursor }; pass { cursor: nextCursor } for the next page. import(db, branch, data, { format, mode }) and download(db, branch, format) move whole databases. The request and response types are generated from the REST API’s OpenAPI document and exported from the package.