ReviewOS

stacks/bun-query-builder

setConfig() has no effect on the model layer: queries hit hardcoded default 'test_db' then silently fall back to in-memory SQLite (config3 vs config5 duplicate-bundle)

#1022
Closed glennmichael123 opened this 22 days ago · 0 comments
22 days ago

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 / defineModelgetExecutor) 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 to config3:
    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
      }
    }
    config5 is initialized from defaultConfig4 ({ dialect:'postgres', database:{ database:'test_db', username:'postgres', password:'postgres', host:'localhost', port:5432 } }) and is never updated by setConfig.

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

  1. Have a real Postgres running (db app, user app), migrated with a table widgets.
  2. 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

  1. Dedupe the config module so setConfig and the model executor share one config object (the core bug — likely a bundler/__esm inlining issue producing config3 vs config5).
  2. Make getBunSql()'s fallback non-silent — at minimum console.error the real connection error unconditionally (not gated by verbose) before falling back, and ideally do not fall back to in-memory SQLite when dialect !== 'sqlite' (fail loudly instead).
  3. Have the model executor read the same config that setConfig writes.

Environment

  • bun-query-builder 0.1.21 and 0.1.24
  • Bun 1.3.14, Amazon Linux 2023 (x64)
  • Used via @stacksjs/orm defineModel in a Stacks app deployed to EC2 with co-hosted Postgres 16

Sign in to comment on this issue.