threepp
A cross-platform C++20 3D library with Python bindings and the high-level API of three.js.
Documentation for @qualithm/rqlite-client
Translation pending
Developer toolsTypeScript · PythonGitHub @qualithmOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
Native rqlite client for JavaScript and TypeScript runtimes.
Taken from the repository README
Loading the full README.
Read the full README ↗Native rqlite client for JavaScript and TypeScript runtimes. Zero runtime
dependencies — uses native fetch.
/db/requestnone, weak, strongnone consistency reads/nodes; rotates through all peers on
failurehostsfetch injection for client certificatesResult<T, E> discriminated unions, no thrown exceptionsConnectionError, QueryError, AuthenticationError with isError() guardsfetchbun add @qualithm/rqlite-client
# or
npm install @qualithm/rqlite-client
import { createRqliteClient } from "@qualithm/rqlite-client"
const client = createRqliteClient({ host: "localhost:4001" })
// Execute a write
await client.execute("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)")
await client.execute("INSERT INTO users(name) VALUES(?)", ["Alice"])
// Query
const result = await client.query("SELECT * FROM users")
if (result.ok) {
console.log(result.value.columns) // ["id", "name"]
console.log(result.value.values) // [[1, "Alice"]]
}
| rqlite version | Client version | Status |
|---|---|---|
| 10.x | 0.x | Tested |
The integration test suite runs against the rqlite image pinned in docker-compose.test.yaml:
docker compose -f docker-compose.test.yaml up -d
Use serverVersion() at runtime to check the connected server:
const ver = await client.serverVersion()
if (ver.ok) console.log(ver.value) // "v10.5.0"
import { createRqliteClient } from "@qualithm/rqlite-client"
const client = createRqliteClient({
host: "localhost:4001",
tls: false, // use HTTPS
auth: {
// basic authentication
username: "admin",
password: "secret"
},
timeout: 10_000, // default request timeout (ms)
consistencyLevel: "weak", // default for queries
freshness: {
// for "none" consistency
freshness: "5s",
freshnessStrict: true
},
followRedirects: true, // follow leader redirects
maxRetries: 3, // retry attempts for transient failures
maxRedirects: 5, // redirect attempts during leader election
retryBaseDelay: 100, // backoff base delay (ms)
hosts: ["localhost:4003", "localhost:4005"], // additional seed nodes
clusterDiscovery: true, // discover peers via /nodes after first success
fetch: customFetch // custom fetch for mTLS (see examples/mtls.ts)
})
Supply a custom fetch function to enable mTLS or other advanced transport options. The custom
function receives the same arguments as the global fetch and must return a Promise<Response>.
// Node.js with undici
import { Agent, fetch as undiciFetch } from "undici"
const agent = new Agent({
connect: {
ca: readFileSync("certs/ca.pem"),
cert: readFileSync("certs/client-cert.pem"),
key: readFileSync("certs/client-key.pem")
}
})
const client = createRqliteClient({
host: "rqlite.example.com:4001",
tls: true,
fetch: (input, init) => undiciFetch(input, { ...init, dispatcher: agent })
})
See examples/mtls.ts for Bun and Deno examples.
When the rqlite password rotates, supply authProvider instead of auth so a new value is adopted
without reconstructing the client:
const client = createRqliteClient({
host: "localhost:4001",
authProvider: async () => ({
username: "admin",
password: await readFile("/var/run/secrets/rqlite/password", "utf8")
})
})
The resolved credential is cached and reused across requests. When the server answers 401, the
client discards it, calls the provider once more, and replays that request a single time — a second
rejection surfaces as an AuthenticationError rather than looping. Concurrent refreshes collapse
into one provider invocation, and 403 is never retried, since it means the credential is valid but
unauthorized. authProvider takes precedence over auth when both are set.
// Simple statement
const result = await client.execute("CREATE TABLE foo (id INTEGER PRIMARY KEY, name TEXT)")
// Parameterised statement
const insert = await client.execute("INSERT INTO foo(name) VALUES(?)", ["bar"])
if (insert.ok) {
console.log(insert.value.lastInsertId) // 1
console.log(insert.value.rowsAffected) // 1
}
// Batch execute
const batch = await client.executeBatch([
["INSERT INTO foo(name) VALUES(?)", "one"],
["INSERT INTO foo(name) VALUES(?)", "two"]
])
// Queue mode — returns immediately, write applied asynchronously
await client.execute("INSERT INTO foo(name) VALUES(?)", ["queued"], { queue: true })
// Wait mode — wait for queued write to be applied
await client.execute("INSERT INTO foo(name) VALUES(?)", ["waited"], { queue: true, wait: true })
// Simple query
const result = await client.query("SELECT * FROM foo")
if (result.ok) {
console.log(result.value.columns) // ["id", "name"]
console.log(result.value.types) // ["integer", "text"]
console.log(result.value.values) // [[1, "bar"], [2, "baz"]]
}
// Parameterised query
const row = await client.query("SELECT * FROM foo WHERE id = ?", [1])
// With consistency level
const strong = await client.query("SELECT * FROM foo", undefined, { level: "strong" })
// Freshness for stale reads
const fresh = await client.query("SELECT * FROM foo", undefined, {
level: "none",
freshness: { freshness: "1s", freshnessStrict: true }
})
// Batch query
const results = await client.queryBatch([
["SELECT * FROM foo WHERE id = ?", 1],
["SELECT COUNT(*) FROM foo"]
])
Query results use arrays by default (matching the rqlite wire format). Use toRows() to convert to
keyed objects when needed:
import { toRows } from "@qualithm/rqlite-client"
const result = await client.query("SELECT id, name FROM foo")
if (result.ok) {
const rows = toRows(result.value) // [{ id: 1, name: "bar" }, ...]
}
Use queryPaginated() to iterate over large result sets in bounded-memory pages. It automatically
appends LIMIT/OFFSET to your SQL and yields pages via an async generator:
import { toRowsPaginated } from "@qualithm/rqlite-client"
for await (const page of client.queryPaginated("SELECT * FROM large_table", [], {
pageSize: 100
})) {
console.log(page.rows.values.length, page.hasMore, page.offset)
// Or convert to keyed row objects:
const { rows } = toRowsPaginated(page)
// rows → [{ id: 1, name: "Alice" }, ...]
}
You can also start from a custom offset:
for await (const page of client.queryPaginated("SELECT * FROM large_table", [], {
pageSize: 50,
offset: 200
})) {
// starts from row 200
}
// All statements succeed or all fail
const transfer = await client.executeBatch(
[
["UPDATE accounts SET balance = balance - ? WHERE id = ?", 100, 1],
["UPDATE accounts SET balance = balance + ? WHERE id = ?", 100, 2]
],
{ transaction: true }
)
const results = await client.requestBatch([
["INSERT INTO foo(name) VALUES(?)", "new"],
["SELECT * FROM foo"]
])
if (results.ok) {
for (const r of results.value) {
if (r.type === "execute") console.log(r.rowsAffected)
if (r.type === "query") console.log(r.columns, r.values)
}
}
// Node readiness
const ready = await client.ready()
if (ready.ok && ready.value.ready) {
console.log("node is ready, leader:", ready.value.isLeader)
}
// Check readiness without requiring a leader (useful during elections)
await client.ready({ noleader: true })
// List cluster nodes
const nodes = await client.nodes()
if (nodes.ok) {
for (const node of nodes.value) {
console.log(node.id, node.leader ? "(leader)" : "", node.apiAddr)
}
}
// Full node status
const status = await client.status()
All operations return Result<T, RqliteError> — no exceptions are thrown in normal operation.
import { AuthenticationError, ConnectionError, QueryError } from "@qualithm/rqlite-client"
const result = await client.query("SELECT * FROM foo")
if (!result.ok) {
const error = result.error
// Type narrowing with static guards
if (ConnectionError.isError(error)) {
console.log("network issue:", error.message, error.url)
} else if (QueryError.isError(error)) {
console.log("SQL error:", error.message)
} else if (AuthenticationError.isError(error)) {
console.log("auth failed:", error.message)
}
}
Full API documentation is generated with TypeDoc:
bun run docs
# Output in docs/
See the examples/ directory for runnable examples:
| Example | Description |
|---|---|
basic-usage.ts | Connect, execute, and query |
batch-processing.ts | Batch insert, query, and mixed requests |
transactions.ts | Atomic multi-statement transactions |
authentication.ts | Basic auth and TLS |
mtls.ts | Custom fetch injection for mTLS |
cluster-failover.ts | Leader redirect, multi-host seeds, cluster discovery, health checks |
error-handling.ts | Result-based error handling and type narrowing |
pagination.ts | Paginated queries and toRows result conversion |
consistency-levels.ts | Read consistency and freshness options |
bun run examples/basic-usage.ts
bun install
bun run build
bun run test # unit tests
bun run test:integration # against a real rqlite instance
bun run test:coverage # with coverage report
bun run lint
bun run format
bun run typecheck
bun run bench
Apache-2.0
client-lifecycle.tsAbort signals, destroy(), and server version |