Rado GitHub

Drivers

Rado doesn't talk to your database directly. It wraps a client you provide. Each supported client has a thin driver under rado/driver/* exporting a connect function. You hand it a client instance, it hands you a typed database.

DatabasePackageImportSync/async
PostgreSQLpgrado/driver/pgasync
PostgreSQL@electric-sql/pgliterado/driver/pgliteasync
PostgreSQL@neondatabase/serverlessrado/driver/pgasync
PostgreSQL@vercel/postgresrado/driver/pgasync
SQLitebetter-sqlite3rado/driver/better-sqlite3sync
SQLitebun:sqlite (built into Bun)rado/driver/bun-sqlitesync
SQLitesql.jsrado/driver/sql.jssync
SQLite@libsql/clientrado/driver/libsqlasync
SQLiteCloudflare D1rado/driver/d1async
MySQLmysql2rado/driver/mysql2async

Sync vs async databases

Drivers that operate synchronously (better-sqlite3, bun:sqlite, sql.js) produce a SyncDatabase: query results are available immediately, no event loop round-trips.

// Synchronous driver: results, right now
const users = db.select().from(User).all()
const first = db.select().from(User).get()

// ...but awaiting also works, queries are thenable
const same = await db.select().from(User)

Async drivers produce an AsyncDatabase, where queries must be awaited. The type system keeps track for you. db.transaction callbacks, .all(), .get() and friends are typed sync or async to match the driver.

PostgreSQL

pg

import {Pool} from 'pg'
import {connect} from 'rado/driver/pg'

const db = connect(new Pool({connectionString: process.env.DATABASE_URL}))

connect accepts a Client, Pool or PoolClient.

PGlite

Postgres compiled to WASM runs in Node.js and the browser. It works well for tests and local-first apps.

import {PGlite} from '@electric-sql/pglite'
import {connect} from 'rado/driver/pglite'

const db = connect(new PGlite()) // in-memory, or pass a data directory

Neon / Vercel Postgres

Both expose a pg-compatible pool, so they use the pg driver:

import {Pool} from '@neondatabase/serverless'
import {connect} from 'rado/driver/pg'

const db = connect(new Pool({connectionString: process.env.DATABASE_URL}))
import {createPool} from '@vercel/postgres'
import {connect} from 'rado/driver/pg'

const db = connect(createPool())

SQLite

better-sqlite3 (Node.js, sync)

import Database from 'better-sqlite3'
import {connect} from 'rado/driver/better-sqlite3'

const db = connect(new Database('app.db'))

bun:sqlite (Bun, sync)

import {Database} from 'bun:sqlite'
import {connect} from 'rado/driver/bun-sqlite'

const db = connect(new Database('app.db'))

sql.js (anywhere WASM runs, sync)

import initSqlJs from 'sql.js'
import {connect} from 'rado/driver/sql.js'

const {Database} = await initSqlJs()
const db = connect(new Database())

libSQL / Turso (async)

import {createClient} from '@libsql/client'
import {connect} from 'rado/driver/libsql'

const db = connect(
  createClient({url: 'libsql://your-db.turso.io', authToken: '...'})
)

Cloudflare D1 (async)

import {connect} from 'rado/driver/d1'

export default {
  async fetch(request: Request, env: Env) {
    const db = connect(env.DB)
    // ...
  }
}

Note: D1 does not support interactive transactions, so db.transaction is unavailable. Use db.batch instead, which D1 executes atomically.

MySQL

mysql2 (async)

import {createConnection} from 'mysql2'
import {connect} from 'rado/driver/mysql2'

const db = connect(createConnection({uri: process.env.DATABASE_URL}))

Both callback-style and promise-style mysql2 connections are accepted.

One import to rule them all

If you prefer, rado/driver re-exports every connect function keyed by package name:

import {connect} from 'rado/driver'

const db = connect['better-sqlite3'](new Database('app.db'))

Handy when the driver choice itself is dynamic.

What the driver determines

  • Dialect: which SQL gets emitted (quoting, parameter style, type names)
  • Sync or async: whether results need await
  • JSON handling: drivers that parse JSON natively (pg, pglite, mysql2) are handled transparently; SQLite drivers receive JSON as text and rado parses it for you
  • Available features: e.g. returning exists on PostgreSQL and SQLite but not MySQL; the types only offer what your dialect supports