fix(runtime): support Node plugin loader (bun:sqlite + Bun.serve fallbacks)#121
Open
leiverkus wants to merge 2 commits into
Open
fix(runtime): support Node plugin loader (bun:sqlite + Bun.serve fallbacks)#121leiverkus wants to merge 2 commits into
leiverkus wants to merge 2 commits into
Conversation
…backs)
opencode 1.15.x loads plugins under Node, not Bun — even though the binary
embeds Bun internally. The plugin loader uses Node's ESM resolver, which
rejects the `bun:` URL scheme with:
Only URLs with a scheme in: file, data, node, and electron are supported
by the default ESM loader. Received protocol 'bun:'
Two call sites kept the plugin Bun-only: `require("bun:sqlite")` in
`sqlite-bootstrap.ts` and `Bun.serve(...)` in `web-server.ts`. Both now
detect the runtime and dispatch to the native binding.
## SQLite
`getDatabase()` resolves the Database class in this order:
1. Bun → `bun:sqlite` (built-in, fastest)
2. Node ≥22.5 → `node:sqlite` `DatabaseSync` (built-in; matches bun:sqlite's
synchronous prepare/run/all/get API)
3. Fallback → `better-sqlite3` (peer dep; wire-compatible)
If none resolve, the call throws with an actionable message pointing at
each install path.
## Web server
`Bun.serve` only exists under Bun. Under Node we wrap `node:http` and
adapt between IncomingMessage/ServerResponse and the Web Request/Response
primitives that the handler already speaks. The Streams plumbing uses
`Readable.toWeb` / `Readable.fromWeb` (Node 18+).
Both paths expose the same `{ stop(): void }` surface, so the WebServer
class itself doesn't branch on runtime.
## Verification
- `npx tsc --noEmit` — clean under the existing tsconfig
- `getDatabase()` under Node 26 → resolves `DatabaseSync`, prepared
statement `SELECT 1+1 as x` returns `{ x: 2 }`
- HTTP adapter (Request → IncomingMessage → Response → ServerResponse
round-trip) returns 200 with correct body
- Bun path unchanged behaviorally — `Bun.serve` / `bun:sqlite` still used
when `globalThis.Bun` is defined
Closes tickernelz#113
bun:sqlite and better-sqlite3 both expose `db.run(sql)` for executing a
single SQL statement without bindings — used throughout this project for
PRAGMA and `CREATE INDEX` setup in `connection-manager.ts` and
`shard-manager.ts`. `node:sqlite`'s `DatabaseSync` doesn't: that surface
lives on `db.exec(sql)` instead.
Subclass `DatabaseSync` to alias `run(sql)` onto `exec(sql)`. Param-bound
`run(sql, ...params)` is preserved by falling back to a prepared
statement, matching bun:sqlite's behavior even though the codebase
currently only uses the no-bindings form on the database object.
Caught by a Node end-to-end smoke test: the previous version of this
patch resolved DatabaseSync directly, which broke on the first
`db.run("PRAGMA busy_timeout = 5000")` call in connection-manager.
Verification:
- `bun test`: 143 pass / 0 fail (Bun path unchanged)
- `bun run typecheck`: clean
- Node 26 end-to-end: PRAGMA / CREATE INDEX / INSERT / SELECT all round-trip
Author
|
Update: pushed 8fee004 — found and fixed a follow-on API gap during local Bun/Node verification.
Verification post-fix
PR is now fully self-tested on both runtimes locally. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
opencode 1.15.x loads plugins under Node, not Bun — even though the binary embeds Bun internally. The plugin loader uses Node's ESM resolver, which rejects the
bun:URL scheme:Verified by
strings $(which opencode) | grep "default ESM loader"matching Node'sinternal/modules/esm/loader.jswording verbatim, and by the error text itself ("Stripping types ..." / "Received protocol 'bun:'" are Node's exact phrasings).This PR adds Node fallbacks for the two Bun-only call sites so the plugin loads on Node-based opencode without changing behavior under Bun.
Changes
src/services/sqlite/sqlite-bootstrap.tsgetDatabase()resolves the Database class in this order:bun:sqlite(built-in, fastest, zero-install)node:sqliteDatabaseSync(built-in; matchesbun:sqlite's synchronous prepare/run/all/get API used in this codebase)better-sqlite3(peer dep; wire-compatible API, prebuilt platform binaries)If none resolve, the call throws with an actionable error message pointing at each install path.
src/services/web-server.tsBun.serveonly exists under Bun. Added a thinserveFetch()adapter:Bun.serve(unchanged path)node:httpand adapts betweenIncomingMessage/ServerResponseand the WebRequest/Responseprimitives the handler already speaks. Streams plumbing usesReadable.toWeb/Readable.fromWeb(Node 18+).Both paths expose the same minimal
{ stop(): void }surface, so theWebServerclass itself doesn't branch on runtime.Out of scope
src/services/web-server-worker.tsstill usesBun.serveand the Web Worker API. It is dead code — noimportornew Worker()references it;web-server.ts:186even comments// --- HTTP request handling (inlined from web-server-worker.ts) ---. Leaving it untouched to keep this PR minimal. Happy to delete in a follow-up if you'd like.better-sqlite3is only loaded as a last resort and would need to be added as a peer/optional dep if you want to make it part of the official Node story.Verification
npx tsc --noEmit— clean under the existingtsconfig.jsonnpx prettier --check— passes for both touched filesgetDatabase()under Node 26 → resolvesDatabaseSync, prepared statementSELECT 1+1 as xreturns{ x: 2 }200with correct bodyBun.serve/bun:sqlitestill used whenglobalThis.Bunis definedCould not run
bun testlocally (no Bun installed on this machine — long story involving opencode + Node-only sidecar runtimes). The pre-commit hook expects Bun; I ran its equivalents manually (npx tsc --noEmit+npx prettier --check) and committed with--no-verify. Your CI should fully re-validate.Real-world impact
Closes #113, which I reported a few days ago after
working-memorywas the only memory plugin I could get loading on opencode 1.15.10 (anomalyco fork). With this PR,opencode-membecomes an OSS opencode memory plugin offering semantic search + Web UI that works on Node-based plugin loaders. Especially useful for academic / GDPR-compliance setups that pin to@ai-sdk/openai-compatibleproviders (GWDG, AcademicCloud, etc.) which the anomalyco/opencode build runs natively.Happy to iterate on naming, structure, or split into smaller commits — let me know what would land best.