Summary
setConfig() has no effect on the model query layer. After setConfig({ dialect: 'postgres', database: {...} }), model queries (Model.where(), Model.create()) still execute against the hardcoded default connection postgres://postgres:postgres@localhost:5432/test_db. When that connection fails, getBunSql() silently falls back to in-memory SQLite, so every model query then throws SQLiteError: no such table: <table> — even though Postgres is up, reachable, and migrated.
The raw query builder (createQueryBuilder().selectFrom(...)) honors setConfig() correctly; only the model layer (createModel / defineModel → getExecutor) does not.
Reproduced on 0.1.21 and 0.1.24 (Bun 1.3.14, linux x64).
Root cause
The bundled dist/src/index.js contains two separate copies of the config module (a duplicate-inlining issue). They use different module-scoped variables:
setConfig()writes toconfig3:function setConfig(userConfig) { if (config3 == null || config3.dialect === undefined) { config3 = defaultConfig3 ? { ...defaultConfig3 } : {} } Object.assign(config3, userConfig); if (userConfig.database) { config3.database = { ...config3.database, ...userConfig.database }; } ... }- but the model executor reads
config5:function getBunSql() { const dialect = config5.dialect; const connectionString = createConnectionString(dialect, config5.database); try { ... const sql = new SQL(connectionString); // postgres://postgres:postgres@localhost:5432/test_db return sql; } catch (error) { ... return createSQLiteSQL(":memory:"); // <-- silent fallback } }config5is initialized fromdefaultConfig4({ dialect:'postgres', database:{ database:'test_db', username:'postgres', password:'postgres', host:'localhost', port:5432 } }) and is never updated bysetConfig.
So config3 (what callers configure) and config5 (what model queries use) are different objects. setConfig updates the former; the executor reads the latter.
Evidence (live, instrumented)
Logging the connection string inside getBunSql() in a running app whose env, .env.production, and setConfig() call all specify postgres + database paweldregan:
[DIAG] dialect= postgres connstr= "postgres://postgres:postgres@localhost:5432/test_db"i.e. none of the configured values reached config5.database.
Reproduction
- Have a real Postgres running (db
app, userapp), migrated with a tablewidgets. import { setConfig, defineModel, createQueryBuilder } from 'bun-query-builder' setConfig({ dialect: 'postgres', database: { database: 'app', username: 'app', password: '', host: '127.0.0.1', port: 5432 } }) // raw builder — WORKS (reads config3) console.log(await createQueryBuilder().selectFrom('widgets').limit(1).get()) // model — FAILS with SQLiteError: no such table: widgets (reads config5) const Widget = defineModel({ name: 'Widget', table: 'widgets', attributes: { id: {}, name: {} } }) console.log(await Widget.where('name', 'x').first())
Impact
Any app using the model layer with a non-default Postgres/MySQL connection cannot connect — it silently runs against an in-memory SQLite and every query 500s with "no such table". The silent fallback masks the real connection failure entirely (the console.warn is gated behind config5.verbose). This blocks deploying a Stacks app's API to Postgres via the production server entry (storage/framework/core/server/src/start.ts); it only "works" under buddy dev because that runtime path configures the model layer differently.
Suggested fixes
- Dedupe the config module so
setConfigand the model executor share oneconfigobject (the core bug — likely a bundler/__esminlining issue producingconfig3vsconfig5). - Make
getBunSql()'s fallback non-silent — at minimumconsole.errorthe real connection error unconditionally (not gated byverbose) before falling back, and ideally do not fall back to in-memory SQLite whendialect !== 'sqlite'(fail loudly instead). - Have the model executor read the same config that
setConfigwrites.
Environment
- bun-query-builder 0.1.21 and 0.1.24
- Bun 1.3.14, Amazon Linux 2023 (x64)
- Used via
@stacksjs/ormdefineModelin a Stacks app deployed to EC2 with co-hosted Postgres 16