Summary
ModelQueryBuilderImpl and the related model-instance methods in packages/bun-query-builder/src/orm.ts call getDatabase(), which unconditionally constructs an in-memory bun:sqlite Database, regardless of the dialect/connection configured via setConfig(). The result is that any project on MySQL or Postgres has its Model.where().first() / Model.create() calls silently route to a fresh, empty SQLite database — every query returns "no such table".
The direct query path (db.selectFrom(...) → getOrCreateBunSql()) honours the configured dialect correctly. The bug is limited to the model API.
Reproduction
DB_CONNECTION=mysql DB_DATABASE=mydb bun -e "
import { setConfig, createModel, createQueryBuilder } from 'bun-query-builder'
setConfig({
dialect: 'mysql',
database: { database: 'mydb', host: '127.0.0.1', username: 'root', password: '', port: 3306 },
})
const qb = createQueryBuilder()
console.log('direct:', await qb.selectFrom('users').selectAll().limit(1).execute()) // ✓ hits MySQL
const User = createModel({ name: 'User', table: 'users', primaryKey: 'id', autoIncrement: true, attributes: {} })
console.log('model:', await User.where('id', 1).first()) // ✗ SQLiteError: no such table: users
"Output:
direct: []
model: SQLiteError: no such table: users
at ... pantry/bun-query-builder/dist/src/index.js:25121:26 (ModelQueryBuilderImpl.get)Root cause
packages/bun-query-builder/src/orm.ts:312–316:
export function getDatabase(): Database {
if (!globalDb) {
globalDb = new Database(':memory:', { create: true }) // bun:sqlite, always
}
return globalDb
}Every model method below routes through this — confirmed via grep -n 'getDatabase()' packages/bun-query-builder/src/orm.ts (30+ call sites). Examples:
ModelQueryBuilderImpl.get()(line ~1149) —db.query(sql).all(...params)ModelQueryBuilderImpl.first()— calls.get()ModelQueryBuilderImpl.count()(line ~1161)findOrFail/ instance.save()/.delete()/belongsToManypivot reads, etc.
configureOrm({ database }) lets a caller pass a bun:sqlite Database instance OR a sqlite filename, but no path through it accepts a non-sqlite connection — the API surface itself assumes sqlite.
Why the direct path works but the model path doesn't
Two separate code paths in the same package:
| API | Connection acquired via | Honours setConfig({ dialect: 'mysql', … })? |
|---|---|---|
createQueryBuilder().selectFrom(...) | getOrCreateBunSql() → getBunSql() → new SQL(connectionString) | ✅ |
createModel(...).where(...).first() | getDatabase() → new Database(':memory:') | ❌ |
This is what makes the bug so surprising in downstream projects: the same setConfig call configures one half of the surface and silently no-ops on the other.
Impact
Any framework that builds its model layer on top of createModel (notably stacksjs/stacks's @stacksjs/orm) is fundamentally MySQL/Postgres-incompatible at runtime today, regardless of how carefully the consuming project sets DB_CONNECTION. Migrations land on the real database; queries land on the in-memory sqlite; both succeed silently from their respective sides.
Proposed fix (shape, not a PR yet)
- Make
globalDba discriminated union —{ kind: 'sqlite', conn: Database } | { kind: 'driver', sql: BunSQL }. getDatabase()consults the configured dialect (via the existinggetConfig()/config5). If dialect ismysql/postgres, return thedrivervariant routed throughgetOrCreateBunSql()(which already supports all dialects via Bun'sSQL). Otherwise keep the current sqlite path.- Introduce small
execAll(sql, params)/execGet(sql, params)/execRun(sql, params)helpers that branch on the union. Replace everydb.query(sql).all(...params)/.get(...)/.run(...)site (~30) with the helper. - Methods that are currently sync (
get,first,count, …) becomeasync— Bun's SQL driver is async. This is a breaking change for callers using these withoutawait, but those callers are already buggy (they receive an unresolved Promise wrapped in aModelInstanceproxy). - SQL emitted by
buildSelectQuery/buildInsertQueryneeds dialect-aware placeholders (?for sqlite/mysql,$1, $2, …for postgres). The dialect is already in scope viaconfig5.dialect. - Schema-introspection queries (
SELECT * FROM sqlite_master …) need MySQL (information_schema.tables) + Postgres (information_schema.tables/pg_catalog) variants. Estimated 3–5 sites.
Net change: ~300–500 LOC in orm.ts + corresponding test additions. The existing ORM test suite is sqlite-only and would need MySQL + Postgres parameterisation.
Workarounds
For affected downstream projects, until this is fixed:
- Bypass the model API where MySQL/Postgres is needed: use
db.selectFrom('users').where(...).execute()directly. Loses model traits (uuid/timestamps/hooks) but the connection routes correctly. - Stay on SQLite for the model surface.
Happy to send a PR implementing the proposed fix if the approach is acceptable — flagging it as an issue first because the async-cascade in step 4 is a breaking-change call that should be the maintainer's, not mine.