Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Please provide the following information:
- **LibreDB Studio Version**: (e.g., 0.5.4)
- **Browser**: (e.g., Chrome 120, Firefox 121, Safari 17)
- **OS**: (e.g., macOS 14, Windows 11, Ubuntu 22.04)
- **Node.js/Bun Version**: (e.g., Bun 1.3.14, Node.js 24.16.0)
- **Node.js/Bun Version**: (e.g., Bun 1.4.0, Node.js 24.16.0)
- **Database Type**: (e.g., PostgreSQL 15, MySQL 8.0, SQLite 3.42, MongoDB 7.0)
- **Database Version**: (e.g., PostgreSQL 15.3)

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/agent-eval.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: "1.3.14"
bun-version: "1.4.0"

- name: Install dependencies
uses: ./.github/actions/bun-install
Expand Down
12 changes: 6 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: "1.3.14"
bun-version: "1.4.0"

- name: Install dependencies
uses: ./.github/actions/bun-install
Expand Down Expand Up @@ -125,7 +125,7 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: "1.3.14"
bun-version: "1.4.0"

- name: Install dependencies
uses: ./.github/actions/bun-install
Expand Down Expand Up @@ -215,7 +215,7 @@ jobs:
# here. npm resolves bun through platform tarballs instead, so it needs
# no unzip. Keep the version in step with the setup-bun pins elsewhere.
- name: Setup Bun
run: npm install -g bun@1.3.14
run: npm install -g bun@1.4.0

- name: Install dependencies
uses: ./.github/actions/bun-install
Expand Down Expand Up @@ -277,7 +277,7 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: "1.3.14"
bun-version: "1.4.0"

- name: Install dependencies
uses: ./.github/actions/bun-install
Expand Down Expand Up @@ -330,7 +330,7 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: "1.3.14"
bun-version: "1.4.0"

- name: Install dependencies
uses: ./.github/actions/bun-install
Expand Down Expand Up @@ -409,7 +409,7 @@ jobs:

# npm rather than oven-sh/setup-bun - see the e2e job's note on unzip.
- name: Setup Bun
run: npm install -g bun@1.3.14
run: npm install -g bun@1.4.0

- name: Install dependencies
uses: ./.github/actions/bun-install
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/distribution-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: "1.3.14"
bun-version: "1.4.0"

- name: Install dependencies
uses: ./.github/actions/bun-install
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/docker-build-push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: "1.3.14"
bun-version: "1.4.0"

- name: Install dependencies
uses: ./.github/actions/bun-install
Expand Down Expand Up @@ -291,7 +291,7 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: "1.3.14"
bun-version: "1.4.0"

- name: Install dependencies
uses: ./.github/actions/bun-install
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/flatpak-smoke.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: "1.3.14"
bun-version: "1.4.0"

- name: Install dependencies
uses: ./.github/actions/bun-install
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/integration-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: "1.3.14"
bun-version: "1.4.0"

- name: Install dependencies
uses: ./.github/actions/bun-install
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/npm-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: "1.3.14"
bun-version: "1.4.0"

- name: Install dependencies
uses: ./.github/actions/bun-install
Expand Down Expand Up @@ -93,7 +93,7 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: "1.3.14"
bun-version: "1.4.0"

- name: Setup Node.js
uses: actions/setup-node@v7
Expand Down
14 changes: 7 additions & 7 deletions .github/workflows/release-artifacts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: "1.3.14"
bun-version: "1.4.0"

- name: Install dependencies
uses: ./.github/actions/bun-install
Expand Down Expand Up @@ -214,7 +214,7 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: "1.3.14"
bun-version: "1.4.0"

- name: Install dependencies
uses: ./.github/actions/bun-install
Expand Down Expand Up @@ -266,7 +266,7 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: "1.3.14"
bun-version: "1.4.0"

- name: Setup Go (launcher toolchain)
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
Expand Down Expand Up @@ -521,7 +521,7 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: "1.3.14"
bun-version: "1.4.0"

- name: Install dependencies
uses: ./.github/actions/bun-install
Expand Down Expand Up @@ -803,7 +803,7 @@ jobs:
if: matrix.arch == 'amd64'
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: "1.3.14"
bun-version: "1.4.0"

- name: Install dependencies (channel E2E)
if: matrix.arch == 'amd64'
Expand Down Expand Up @@ -900,7 +900,7 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: "1.3.14"
bun-version: "1.4.0"

- name: Setup Rust toolchain
# The runner images ship rustup; the Tauri 2.11 dependency tree needs
Expand Down Expand Up @@ -1050,7 +1050,7 @@ jobs:
if: steps.snapstore.outputs.enabled == 'true' && matrix.arch == 'amd64'
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: "1.3.14"
bun-version: "1.4.0"

- name: Install dependencies (channel E2E)
if: steps.snapstore.outputs.enabled == 'true' && matrix.arch == 'amd64'
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/security-scan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: "1.3.14"
bun-version: "1.4.0"

- name: Install dependencies
uses: ./.github/actions/bun-install
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# Bun for fast dependency installation, Node.js for build
# Bun's JIT compiler segfaults under QEMU emulation (ARM64 cross-build),
# so we use Node.js for the Next.js build step.
FROM oven/bun:1.3.14 AS deps
FROM oven/bun:1.4.0 AS deps
WORKDIR /usr/src/app
RUN apt-get update && apt-get install -y python3 make g++ --no-install-recommends && rm -rf /var/lib/apt/lists/*
COPY package.json bun.lock ./
Expand Down
11 changes: 11 additions & 0 deletions bin/studio.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,17 @@
* ESM in a .js file: bin/package.json sets "type": "module" for this
* directory only - the root package.json must stay typeless because the
* library dist ships CJS .js files consumed via require().
*
* The `#!/usr/bin/env node` shebang above is load-bearing, not decoration.
* `bunx` honours it and spawns a real node process, which is why `bunx
* @libredb/studio` works; the server below is then started with
* `process.execPath`, so it inherits that node. Drop the shebang and a bunx
* user gets Bun instead, where `better-sqlite3` - the STORAGE_PROVIDER=sqlite
* backend - segfaults rather than failing cleanly (oven-sh/bun#4290, open
* since 2023: Bun implements N-API but not the V8 C++ API these NAN addons
* link against). `assessNodeRuntime` cannot catch it either, because Bun
* reports a `process.versions.node` well above the floor - 26.3.0 on Bun
* 1.4.0. Only `bunx --bun` reaches that path, and that is the caller asking.
*/
import { spawn, spawnSync } from "node:child_process";
import * as fs from "node:fs";
Expand Down
51 changes: 40 additions & 11 deletions docs/providers/sqlite.md
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ Minimal by nature — SQLite keeps almost no server-style runtime statistics.
| `getPerformanceMetrics()` | — | **no cache-hit ratio, no QPS, no buffer-pool usage** — all three are omitted, so both monitoring tabs show "N/A / Not measured" for them ([§7.1](#71-there-is-no-cache-hit-ratio-and-there-cannot-be)); only `deadlocks: 0` is reported, which is a fact about the engine |
| `getSlowQueries()` | — | always `[]` (SQLite has no query stats) |
| `getActiveSessions()` | — | the single current process session |
| `getTableStats()` | `COUNT(*)` per table, `dbstat` for the bytes | size is **measured page bytes under `node:sqlite` and absent under `bun:sqlite`** — see [§7.2](#72-per-table-size-depends-on-which-driver-you-run) |
| `getTableStats()` | `COUNT(*)` per table, `dbstat` for the bytes | size is **measured page bytes wherever `dbstat` is compiled in, and absent where it is not** — see [§7.2](#72-per-table-size-depends-on-the-sqlite-build-behind-the-driver) |
| `getIndexStats()` | `PRAGMA index_list`/`index_info` | `scans` always `0` (no usage counter); `indexSize` is `N/A` and `indexSizeBytes` is **omitted** — SQLite publishes no per-index size, and a `0` was summed by the Storage tab as an empty index |
| `getStorageStats()` | `fs.statSync` on the DB / `-wal` / `-shm` files | per-file sizes (on disk only) |

Expand All @@ -359,15 +359,15 @@ Nothing SQL-reachable stands in either. On both drivers:
| `PRAGMA cache_size` | `-2000` — the *configured* page budget (negative = KiB), not a hit count |
| `PRAGMA cache_hit`, `PRAGMA cache_miss` | `[]` — these are not pragmas; SQLite answers an unknown pragma with zero rows rather than an error, so they *look* like empty readings |
| `PRAGMA stats` | `[]` |
| `SELECT * FROM dbstat` | `no such table: dbstat` under `bun:sqlite`; available under `node:sqlite` (`ENABLE_DBSTAT_VTAB`), but it reports page layout, not cache hits — which is what [§7.2](#72-per-table-size-depends-on-which-driver-you-run) reads it for |
| `SELECT * FROM dbstat` | Behind `ENABLE_DBSTAT_VTAB`, so it depends on the build: always available under `node:sqlite`, `no such table: dbstat` under `bun:sqlite` through Bun 1.3.14. Either way it reports page layout, not cache hits — which is what [§7.2](#72-per-table-size-depends-on-the-sqlite-build-behind-the-driver) reads it for |

So the field is **omitted permanently**, not pending a better query. Through 0.13.1 this provider
reported `95` whenever `PRAGMA cache_size` came back truthy — which it always does — and `99`
otherwise, and the Performance panel rated that invented figure "Excellent". A missing panel is
honest; a populated wrong one is not: the number was this provider's, not SQLite's. `getHealth()`
says the same thing in its own string field: `cacheHitRatio` is `N/A`.

### 7.2 Per-table size depends on which driver you run
### 7.2 Per-table size depends on the SQLite build behind the driver

SQLite has no catalog column for a table's size. The only source is `dbstat`, a virtual table that
reports one row per b-tree page group, and it is behind the compile-time
Expand All @@ -380,24 +380,53 @@ the same seeded database (200 rows of 4 KB text in `big` with an index on it, 20
| `bun:sqlite` (Bun 1.3.14, SQLite 3.53.0) | `no such table: dbstat` |
| `node:sqlite` (Node 24.14.0, SQLite 3.51.2) | `big 823296`, `idx_big 929792`, `small 4096` |

`LIBREDB_SQLITE_DRIVER` is what a user changes to move between them ([§2](#runtime--driver-selection)),
so both answers ship, and the same connection reports different things depending on it — verbatim
from `getTableStats()`:
**The divergence closed on Bun 1.4.0 — on the builds that carry Bun's own SQLite.** That
Bun bundles SQLite 3.53.2 with `SQLITE_ENABLE_DBSTAT_VTAB` compiled in, so `bun:sqlite`
answers where 1.3.14 raised `no such table: dbstat`, and the pinned runtime moved to it on
2026-08-31. Re-measured that day **on Linux x86_64** on a freshly seeded file (200 rows of a
4096-character payload in `big` with an index on `payload`, 200 short rows in `small`), the
two drivers returned **byte-identical** `getTableStats()` output under the same Bun 1.4.0:

```
# both LIBREDB_SQLITE_DRIVER unset (bun:sqlite) and LIBREDB_SQLITE_DRIVER=node
{"tableName":"big","rowCount":200,"tableSize":"904 KB","tableSizeBytes":925696,
"indexSize":"908 KB","indexSizeBytes":929792,"totalSize":"1.77 MB","totalSizeBytes":1855488}
{"tableName":"small","rowCount":200,"tableSize":"4 KB","tableSizeBytes":4096,
"indexSize":"0 B","indexSizeBytes":0,"totalSize":"4 KB","totalSizeBytes":4096}
```

The bytes differ from the 2026-08-24 row above because the seed is not the same file, not
because the drivers disagree — that comparison is a separate measurement, kept as the
record of what Bun 1.3.14 did. The 1.3.14 row is not history: an install pinned to an
older image still behaves that way, which is why the absent-field arm below stays.

**macOS is not covered by the row above.** There `bun:sqlite` dlopens Apple's
`/usr/lib/libsqlite3.dylib` instead of the amalgamation Bun links on Linux and Windows —
[oven-sh/bun#16717](https://github.com/oven-sh/bun/issues/16717), open, reproduced by Bun's own
triage bot, and the reason `sqlite_version()` still reads `3.43.2` there on 1.4.0. So what
`dbstat` does under `bun:sqlite` on macOS is Apple's build's answer, and nothing in this
document measures it. Read the empty arm below as live on any build without `dbstat`, macOS
included until someone measures it.

The absent arm is therefore still shipped, and `LIBREDB_SQLITE_DRIVER`
([§2](#runtime--driver-selection)) is what moves a connection between the two drivers. When the
build behind `bun:sqlite` has no `dbstat`, the same connection reports different things depending
on that variable — verbatim from `getTableStats()`, captured on Bun 1.3.14:

```
# LIBREDB_SQLITE_DRIVER=node
{"tableName":"big","rowCount":200,"tableSize":"804 KB","tableSizeBytes":823296,
"indexSize":"908 KB","indexSizeBytes":929792,"totalSize":"1.67 MB","totalSizeBytes":1753088}

# bun:sqlite (the default under Bun)
# bun:sqlite (the default under Bun), where dbstat is absent
{"tableName":"big","rowCount":200,"totalSize":"N/A","totalSizeBytes":0}
```

Under `node:sqlite` an index's pages are added to **its table's** `indexSizeBytes`, implicit
`sqlite_autoindex_*` ones included, because the Storage tab builds its index total from the
per-table figure. Under `bun:sqlite` `tableSize` and `tableSizeBytes` are **omitted** — the Storage
tab shows "N/A" for the Tables/Indexes cards and the breakdown, and "-" for each table's share,
rather than a figure. `dbstat` is read once per `getTableStats()` call, since it scans the whole
per-table figure. Where `dbstat` is missing, `tableSize` and `tableSizeBytes` are **omitted** — the
Storage tab shows "N/A" for the Tables/Indexes cards and the breakdown, and "-" for each table's
share, rather than a figure. `dbstat` is read once per `getTableStats()` call, since it scans the whole
database file.

Through 0.13.3 this was `rowCount * 100` — "Assume 100 bytes average per row" — and the Storage tab
Expand Down Expand Up @@ -763,7 +792,7 @@ not apply to SQLite ([§3.4](#34-no-transactions-api-no-cancellation-no-pool)).
`getIndexStats()`'s per-index size is `N/A`; slow queries are unavailable.
- **Per-table size only under `node:sqlite`.** `dbstat` is compiled into that driver and out of
`bun:sqlite`, so under Bun the byte fields are omitted rather than estimated
([§7.2](#72-per-table-size-depends-on-which-driver-you-run)). `getIndexStats()` still reports
([§7.2](#72-per-table-size-depends-on-the-sqlite-build-behind-the-driver)). `getIndexStats()` still reports
`indexSize: "N/A"` per index even where `dbstat` exists — the per-table index bytes it feeds the
Storage tab are measured, the per-index rows are not yet.
- **`:memory:` is ephemeral** — data is lost on disconnect; intended for trials/tests.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@
"engines": {
"node": ">=24.0.0"
},
"packageManager": "bun@1.3.14",
"packageManager": "bun@1.4.0",
"//trustedDependencies": "Explicit allowlist of the ONLY dependencies permitted to execute code at install time. Without this field bun applies its own default allowlist, which synthesizes a `node-gyp rebuild` for any package shipping a binding.gyp - including better-sqlite3 13, whose `gypfile: false` says not to (it is N-API and ships prebuilts, so npm honours it and bun does not). These three are exactly the packages that ran scripts before the field existed; cpu-features (optional, ssh2) and unrs-resolver stay blocked as they already were.",
"trustedDependencies": [
"esbuild",
Expand Down
28 changes: 22 additions & 6 deletions src/lib/db/providers/sql/sqlite-driver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,8 @@ async function loadBunDriver(): Promise<SQLiteConstructor> {
* - `run()` reports `changes` as `number | bigint`; normalize to `number`.
*
* Exported (with the injectable ctor) so the adapter semantics are unit-testable
* in-process under Bun, where node:sqlite itself cannot be imported.
* in-process against a stand-in, on any runtime, rather than only where node:sqlite
* happens to resolve - Bun could not import it before 1.4.0.
*/
export function createNodeSQLiteDriver(DatabaseSyncCtor: NodeSQLiteModule["DatabaseSync"]): SQLiteConstructor {
class NodeSQLiteDatabase implements SQLiteDatabase {
Expand Down Expand Up @@ -143,9 +144,11 @@ async function importNodeSQLite(): Promise<NodeSQLiteModule> {
}

/**
* Load the node:sqlite-backed driver. The module import is injectable so the
* success path is unit-testable under Bun (which lacks node:sqlite); callers
* outside tests use the default importer.
* Load the node:sqlite-backed driver. The module import is injectable for deterministic
* test isolation: the success path is then driven the same way on every runtime instead
* of only where node:sqlite resolves. Bun gained it in 1.4.0, so the real module is
* exercised too - both arms are asserted rather than whichever one the toolchain allows.
* Callers outside tests use the default importer.
*/
export async function loadNodeSQLiteDriver(
importModule: () => Promise<NodeSQLiteModule> = importNodeSQLite,
Expand All @@ -154,10 +157,23 @@ export async function loadNodeSQLiteDriver(
return createNodeSQLiteDriver(sqlite.DatabaseSync);
}

/** The real loader for a driver name — the default behind the seam below. */
async function importDriverForName(name: SQLiteDriverName): Promise<SQLiteConstructor> {
return name === "bun" ? loadBunDriver() : loadNodeSQLiteDriver();
}

/**
* Load the runtime-appropriate SQLite driver (lazily, cached per driver).
*
* The loader is injectable for the same reason `loadNodeSQLiteDriver`'s importer is:
* the failure arm is otherwise reachable only on a runtime that lacks the module, so a
* test asserting it is really asserting a property of the installed Bun. It was written
* that way once and went quietly unexercised the day Bun 1.4.0 shipped `node:sqlite`.
Comment on lines +168 to +171
* Callers outside tests pass nothing.
*/
export async function loadSQLiteDriver(): Promise<SQLiteConstructor> {
export async function loadSQLiteDriver(
loadDriver: (name: SQLiteDriverName) => Promise<SQLiteConstructor> = importDriverForName,
): Promise<SQLiteConstructor> {
const name = resolveSQLiteDriverName();

const cached = loadedDrivers.get(name);
Expand All @@ -170,7 +186,7 @@ export async function loadSQLiteDriver(): Promise<SQLiteConstructor> {
}

try {
const driver = name === "bun" ? await loadBunDriver() : await loadNodeSQLiteDriver();
const driver = await loadDriver(name);
loadedDrivers.set(name, driver);
return driver;
} catch (error) {
Expand Down
Loading
Loading