From ad6344d16ed11b899fd8ca983e33649568296d14 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Mar 2026 14:14:09 +0000 Subject: [PATCH 1/3] Initial plan From cbcc11aecc546d48aea39ba929587d8d3f161c24 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Mar 2026 14:18:33 +0000 Subject: [PATCH 2/3] =?UTF-8?q?=F0=9F=94=A7=20update=20(runtime):=20avoid?= =?UTF-8?q?=20static=20bun=20sqlite=20loads?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: warengonzaga <15052701+warengonzaga@users.noreply.github.com> --- README.md | 3 +++ src/runtime.ts | 16 +++++++++---- tests/runtime.test.ts | 54 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 4 deletions(-) create mode 100644 tests/runtime.test.ts diff --git a/README.md b/README.md index 7683cc5..a36177a 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,9 @@ For Node.js, also install the SQLite driver: npm install @wgtechlabs/config-engine better-sqlite3 ``` +The Bun SQLite backend is loaded only at runtime on Bun, so Node-targeted +bundles can safely consume the package without pulling in a static `bun:*` import. + ## Quick Start ```typescript diff --git a/src/runtime.ts b/src/runtime.ts index 8f1ba8d..8b8d379 100644 --- a/src/runtime.ts +++ b/src/runtime.ts @@ -4,8 +4,11 @@ * Uses `bun:sqlite` on Bun, `better-sqlite3` on Node.js. */ +import { createRequire } from "node:module"; import type { DatabaseAdapter, StatementAdapter } from "./types.js"; +const runtimeRequire = createRequire(import.meta.url); + /** Returns `true` when running under Bun. */ export function isBun(): boolean { return typeof globalThis.Bun !== "undefined"; @@ -28,9 +31,10 @@ export function openDatabase(filepath: string): DatabaseAdapter { // --------------------------------------------------------------------------- function openBunDatabase(filepath: string): DatabaseAdapter { - // Using dynamic import syntax to avoid static analysis issues on Node + // Construct the module specifier at runtime so Node-targeted bundlers + // don't preserve a static bun:* import in published CLI artifacts. // biome-ignore lint/suspicious/noExplicitAny: bun:sqlite types vary - const { Database } = require("bun:sqlite") as any; + const { Database } = runtimeRequire(getBunSqliteSpecifier()) as any; const db = new Database(filepath); // Enable WAL for concurrent read performance @@ -72,8 +76,8 @@ function openNodeDatabase(filepath: string): DatabaseAdapter { // better-sqlite3 is a peer dep — error if missing let BetterSqlite3: typeof import("better-sqlite3"); try { - // biome-ignore lint/suspicious/noExplicitAny: dynamic require - BetterSqlite3 = require("better-sqlite3") as any; + // biome-ignore lint/suspicious/noExplicitAny: runtime require for ESM output + BetterSqlite3 = runtimeRequire("better-sqlite3") as any; } catch { throw new Error( 'config-engine requires "better-sqlite3" as a peer dependency when running on Node.js. ' + @@ -113,3 +117,7 @@ function openNodeDatabase(filepath: string): DatabaseAdapter { }, }; } + +function getBunSqliteSpecifier(): string { + return String.fromCharCode(98, 117, 110, 58, 115, 113, 108, 105, 116, 101); +} diff --git a/tests/runtime.test.ts b/tests/runtime.test.ts new file mode 100644 index 0000000..b02dae1 --- /dev/null +++ b/tests/runtime.test.ts @@ -0,0 +1,54 @@ +/** + * Tests for the runtime SQLite adapter selection. + */ + +import { describe, expect, test } from "bun:test"; +import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { fileURLToPath } from "node:url"; + +describe("runtime bundling", () => { + test("does not emit a static bun:sqlite import in Node bundles", async () => { + const workDir = mkdtempSync(join(tmpdir(), "config-engine-runtime-")); + const entryPath = join(workDir, "entry.ts"); + const outDir = join(workDir, "dist"); + const runtimePath = fileURLToPath(new URL("../src/runtime.ts", import.meta.url)); + + try { + mkdirSync(outDir, { recursive: true }); + writeFileSync( + entryPath, + `import { isBun } from ${JSON.stringify(runtimePath)};\nconsole.log(isBun());\n`, + ); + + const result = await Bun.build({ + entrypoints: [entryPath], + outdir: outDir, + target: "node", + format: "esm", + splitting: false, + external: ["better-sqlite3"], + }); + + expect(result.success).toBe(true); + + const outputPath = join(outDir, "entry.js"); + const bundled = readFileSync(outputPath, "utf8"); + expect(bundled).not.toContain('from "bun:sqlite"'); + expect(bundled).not.toContain("from 'bun:sqlite'"); + expect(bundled).not.toContain('import "bun:sqlite"'); + expect(bundled).not.toContain("import 'bun:sqlite'"); + + const run = Bun.spawnSync(["node", outputPath], { + stdout: "pipe", + stderr: "pipe", + }); + + expect(run.exitCode).toBe(0); + expect(new TextDecoder().decode(run.stdout).trim()).toBe("false"); + } finally { + rmSync(workDir, { recursive: true, force: true }); + } + }); +}); From eb5ed9c2508ac4b27f54ffdcb68785e5517afb06 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Mar 2026 14:20:13 +0000 Subject: [PATCH 3/3] =?UTF-8?q?=F0=9F=A7=AA=20test:=20tighten=20runtime=20?= =?UTF-8?q?bundle=20regression=20check?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: warengonzaga <15052701+warengonzaga@users.noreply.github.com> --- src/runtime.ts | 1 + tests/runtime.test.ts | 5 +---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/runtime.ts b/src/runtime.ts index 8b8d379..4e6b810 100644 --- a/src/runtime.ts +++ b/src/runtime.ts @@ -119,5 +119,6 @@ function openNodeDatabase(filepath: string): DatabaseAdapter { } function getBunSqliteSpecifier(): string { + // Spells "bun:sqlite" without exposing a static bun:* import to bundlers. return String.fromCharCode(98, 117, 110, 58, 115, 113, 108, 105, 116, 101); } diff --git a/tests/runtime.test.ts b/tests/runtime.test.ts index b02dae1..1c28439 100644 --- a/tests/runtime.test.ts +++ b/tests/runtime.test.ts @@ -35,10 +35,7 @@ describe("runtime bundling", () => { const outputPath = join(outDir, "entry.js"); const bundled = readFileSync(outputPath, "utf8"); - expect(bundled).not.toContain('from "bun:sqlite"'); - expect(bundled).not.toContain("from 'bun:sqlite'"); - expect(bundled).not.toContain('import "bun:sqlite"'); - expect(bundled).not.toContain("import 'bun:sqlite'"); + expect(bundled).not.toMatch(/(?:from|import)\s+["']bun:sqlite["']/); const run = Bun.spawnSync(["node", outputPath], { stdout: "pipe",