Skip to content

refactor: abstract SQLite behind runtime-conditional #db import#18316

Merged
thdxr merged 1 commit intodevfrom
refactor/sqlite-abstraction
Mar 20, 2026
Merged

refactor: abstract SQLite behind runtime-conditional #db import#18316
thdxr merged 1 commit intodevfrom
refactor/sqlite-abstraction

Conversation

@thdxr
Copy link
Member

@thdxr thdxr commented Mar 20, 2026

Replace direct bun:sqlite usage with a #db package import that resolves to either db.bun.ts (bun:sqlite) or db.node.ts (node:sqlite) depending on the runtime, enabling the database layer to work under both Bun and Node.js.

@thdxr thdxr force-pushed the refactor/sqlite-abstraction branch from 260be87 to c2ce3b1 Compare March 20, 2026 01:05
@greptile-apps
Copy link

greptile-apps bot commented Mar 20, 2026

Greptile Summary

This PR introduces a runtime-conditional #db package import in package.json to route database initialization to either db.bun.ts (bun:sqlite) or db.node.ts (node:sqlite) based on the JavaScript runtime, and refactors db.ts to use init() from that abstraction instead of instantiating bun:sqlite directly. The mechanism itself (package.json imports map) is correctly implemented, but the abstraction is incomplete:

  • Migrator not abstracted: migrate is still imported directly from drizzle-orm/bun-sqlite/migrator, so schema migrations will fail or behave incorrectly under Node.js where the client is a drizzle-orm/node-sqlite instance. The migrate export (or a separate #migrator import) needs to be moved into the #db abstraction as well.
  • Bun-specific type persists: type Client = SQLiteBunDatabase (from drizzle-orm/bun-sqlite) remains the internal type alias for the database client, misrepresenting the Node.js path's return type.
  • close() safety regression: The pre-existing guard against calling close() on an uninitialized database was removed; the new implementation will inadvertently open the database if close() is called before any queries have been made.

Confidence Score: 2/5

  • Not safe to merge — schema migrations will fail at runtime under Node.js due to incomplete abstraction of the Bun-specific migrator.
  • The package.json conditional import mechanism is correctly wired, but db.ts still pulls migrate and the SQLiteBunDatabase type directly from drizzle-orm/bun-sqlite — the Bun-specific paths that the PR was meant to eliminate. This makes the Node.js path non-functional for database migrations.
  • packages/opencode/src/storage/db.ts requires the most attention — both the migrate import and the Client type alias need to be moved into the #db abstraction.

Important Files Changed

Filename Overview
packages/opencode/src/storage/db.ts Core database module refactored to use #db conditional import for init(), but still imports migrate and SQLiteBunDatabase directly from Bun-specific drizzle paths — incomplete abstraction that will break under Node.js. The close() guard against uninitialized state was also removed.
packages/opencode/src/storage/db.bun.ts New Bun-specific adapter: wraps bun:sqlite + drizzle-orm/bun-sqlite into a single init(path) function. Clean and minimal. Missing a migrate re-export that db.ts still needs from this adapter.
packages/opencode/src/storage/db.node.ts New Node.js-specific adapter: uses node:sqlite DatabaseSync with drizzle-orm/node-sqlite. Mirrors db.bun.ts correctly, but does not enable WAL mode or any of the PRAGMAs applied in db.ts (those are handled there via db.run()). Also missing a migrate re-export.
packages/opencode/package.json Adds imports map with runtime-conditional #db resolution: bundb.bun.ts, nodedb.node.ts, defaultdb.bun.ts. Correct use of Node.js package imports spec; the default fallback to the Bun adapter is reasonable.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[db.ts - Database.Client lazy init] --> B{Runtime}
    B -->|bun| C[db.bun.ts\ninit - bun:sqlite + drizzle bun-sqlite]
    B -->|node| D[db.node.ts\ninit - node:sqlite + drizzle node-sqlite]
    B -->|default| C

    A --> E[migrate - drizzle-orm/bun-sqlite/migrator]
    E -->|⚠️ NOT abstracted| F{passes Bun migrator\nto Node.js client on Node}

    C --> G[Returns SQLiteBunDatabase]
    D --> H[Returns node-sqlite DB]

    G --> I[db.run PRAGMA calls]
    H --> I

    I --> J[migrate db entries]
    E --> J

    style E fill:#ff6b6b,color:#fff
    style F fill:#ff6b6b,color:#fff
Loading

Last reviewed commit: "refactor: abstract S..."

import { Database as BunDatabase } from "bun:sqlite"
import { drizzle, type SQLiteBunDatabase } from "drizzle-orm/bun-sqlite"
import { type SQLiteBunDatabase } from "drizzle-orm/bun-sqlite"
import { migrate } from "drizzle-orm/bun-sqlite/migrator"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Bun-specific migrator not abstracted behind #db

The whole point of this PR is to abstract the runtime-specific database layer, but migrate is still imported directly from drizzle-orm/bun-sqlite/migrator. On line 104, migrate(db, entries) is called passing whatever init() returned — which, on Node.js, will be a drizzle-orm/node-sqlite client — into a Bun-specific migrator. The two adapters are typed differently and this is likely to fail at runtime under Node.js.

The migrator should either be exported from #db alongside init, or a separate #migrator conditional import should be added to package.json.

Suggested change
import { migrate } from "drizzle-orm/bun-sqlite/migrator"
import { migrate } from "#db/migrator"

And correspondingly, db.bun.ts should re-export from drizzle-orm/bun-sqlite/migrator and db.node.ts from drizzle-orm/node-sqlite/migrator.

@@ -1,5 +1,4 @@
import { Database as BunDatabase } from "bun:sqlite"
import { drizzle, type SQLiteBunDatabase } from "drizzle-orm/bun-sqlite"
import { type SQLiteBunDatabase } from "drizzle-orm/bun-sqlite"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Bun-specific type alias persists for Client

SQLiteBunDatabase is imported and used as the Client type alias (line 40: type Client = SQLiteBunDatabase). Under Node.js, init() from db.node.ts returns a BetterSQLite3Database<Record<string, never>> (or equivalent drizzle node-sqlite type), not a SQLiteBunDatabase. This mismatch means all Client-typed values, including the $client access in close(), are typed against the wrong runtime object.

The fix would be to either export a common DB type from #db or use ReturnType<typeof init> to infer the correct type.

Comment on lines 110 to 113
export function close() {
const sqlite = state.sqlite
if (!sqlite) return
sqlite.close()
state.sqlite = undefined
Client().$client.close()
Client.reset()
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 close() is no longer a safe no-op when DB is uninitialized

The original implementation guarded against uninitialized state with if (!sqlite) return. The new implementation calls Client(), which lazily initializes the database if it hasn't been opened yet. This means:

  1. If close() is called before the database has ever been accessed, it will now open the database, then immediately close it — a behavioral regression.
  2. If close() is called a second time (after Client.reset()), it will re-open the database again before closing it, rather than being a no-op.

A simple guard can restore the original safety:

Suggested change
export function close() {
const sqlite = state.sqlite
if (!sqlite) return
sqlite.close()
state.sqlite = undefined
Client().$client.close()
Client.reset()
}
export function close() {
if (!Client.loaded) return
Client().$client.close()
Client.reset()
}

(Requires exposing a loaded flag on the lazy wrapper, or checking via a separate flag.)

Replace direct bun:sqlite usage with a #db package import that resolves
to either db.bun.ts (bun:sqlite) or db.node.ts (node:sqlite) depending
on the runtime, enabling the database layer to work under both Bun and
Node.js.
@thdxr thdxr force-pushed the refactor/sqlite-abstraction branch from c2ce3b1 to cca7440 Compare March 20, 2026 01:08
@thdxr thdxr merged commit 37b8662 into dev Mar 20, 2026
7 of 9 checks passed
@thdxr thdxr deleted the refactor/sqlite-abstraction branch March 20, 2026 01:15
SamirMoustafa pushed a commit to SamirMoustafa/opencode that referenced this pull request Mar 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant