How context-mode Handles Node.js Version Differences for SQLite Backend Selection

context-mode uses a lazy-loader in src/db-base.ts that detects the JavaScript runtime at module initialization, automatically selecting bun:sqlite for Bun, node:sqlite for Linux Node.js ≥22.5, and falling back to better-sqlite3 for older versions and other platforms.

The mksglu/context-mode repository abstracts SQLite drivers behind a unified interface to eliminate version-specific dependencies. By implementing runtime detection logic in the database base layer, context-mode ensures optimal backend selection without manual configuration, seamlessly handling the availability of Node.js's built-in node:sqlite module while avoiding platform-specific crashes historically associated with native addons on Linux.

Runtime-Aware SQLite Backend Selection

The library determines which SQLite implementation to load based on three distinct environment checks performed inside the loadDatabase() function.

Bun Runtime Detection

When (globalThis as any).Bun evaluates to truthy, context-mode immediately routes to Bun's native SQLite binding. The loader imports from bun:sqlite and wraps the Database constructor with BunSQLiteAdapter, providing a better-sqlite3-compatible API surface.

Linux Node.js Version Handling

For Linux platforms (process.platform === "linux"), the loader attempts to use Node.js's built-in node:sqlite module first. This module is only available in Node.js version 22.5 and higher. The implementation wraps DatabaseSync with NodeSQLiteAdapter to normalize the API.

If require(["node", "sqlite"].join(":")) throws—indicating an older Node.js version without the built-in module—the loader catches the exception and automatically falls back to better-sqlite3. This fallback is avoids a known segfault issue (nodejs/node#62515) that affects Linux builds of the native addon.

Cross-Platform Fallback Strategy

For all other environments—including macOS, Windows, and non-Linux Unix systems—the loader defaults to better-sqlite3. These platforms do not have the built-in node:sqlite module, and the native addon operates stably outside of the Linux-specific crash scenario.

The loadDatabase Implementation

The lazy-loader is implemented in src/db-base.ts (lines 85–103) and executes only once per module lifecycle. The following TypeScript demonstrates the complete detection logic:

export function loadDatabase(): typeof DatabaseConstructor {
  if (!_Database) {
    const require = createRequire(import.meta.url);

    if ((globalThis as any).Bun) {
      // Bun → bun:sqlite
      const BunDB = require(["bun", "sqlite"].join(":")).Database;
      _Database = (path, opts) => new BunSQLiteAdapter(new BunDB(path, { readonly: opts?.readonly, create: true }));
    } else if (process.platform === "linux") {
      // Linux Node → try node:sqlite first
      try {
        const { DatabaseSync } = require(["node", "sqlite"].join(":"));
        _Database = (path, opts) => new NodeSQLiteAdapter(new DatabaseSync(path, { readOnly: opts?.readonly ?? false }));
      } catch {
        // node:sqlite not present (older Node) → fallback
        _Database = require("better-sqlite3");
      }
    } else {
      // All other environments → better-sqlite3
      _Database = require("better-sqlite3");
    }
  }
  return _Database!;
}

This pattern ensures that version awareness is handled at runtime. When running on Node.js versions prior to 22.5, the require call for node:sqlite throws, and the loader automatically falls back to the native addon without crashing the application.

Adapter Pattern for API Compatibility

Both BunSQLiteAdapter and NodeSQLiteAdapter expose an identical surface to the rest of the codebase. According to the source in src/store.ts, the consuming code interacts with methods like pragma, exec, prepare, transaction, and close regardless of the underlying driver.

This abstraction guarantees that src/store.ts and other consumers remain agnostic to whether they are using Bun's binding, Node.js's native module, or the third-party addon. The adapter layer normalizes differences in constructor signatures and method behaviors, ensuring that context-mode delivers consistent performance across all supported platforms.

Practical Usage Examples

The following examples demonstrate how the runtime-agnostic API works in practice.

Opening a Database Connection

The same import and initialization code functions identically across Bun, Node.js 22.5+, and older Node.js versions:

import { loadDatabase } from "./db-base.js";

const Database = loadDatabase();          // selects appropriate driver automatically
const db = new Database("/tmp/my.db", { timeout: 30_000 });

Executing Queries Across Backends

All adapters support the unified interface for SQL execution:

// Works identically on Bun, Linux Node ≥22.5, and other platforms
db.exec(`
  CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, text TEXT);
  INSERT INTO notes (text) VALUES ('Hello world');
`);

const row = db.prepare("SELECT * FROM notes WHERE id = ?").get(1);
console.log(row); // { id: 1, text: 'Hello world' }

Inspecting the Active Backend

For debugging purposes, you can determine which driver was selected by examining the constructor name:

import { loadDatabase } from "./db-base.js";

const Database = loadDatabase();
console.log("Backend:", Database.name); // "BunSQLiteAdapter" | "NodeSQLiteAdapter" | "Database"

Summary

  • src/db-base.ts implements a lazy-loader (loadDatabase()) that detects Bun via globalThis.Bun and Linux Node.js via process.platform.
  • Node.js ≥22.5 on Linux uses the built-in node:sqlite module to avoid native addon segfaults; older versions fall back to better-sqlite3.
  • Bun environments automatically use bun:sqlite wrapped in BunSQLiteAdapter for native performance.
  • Adapter classes normalize the API across all backends, allowing src/store.ts to remain driver-agnostic.
  • Test coverage in tests/core/cli.test.ts and tests/session-db.test.ts validates correct backend selection across simulated runtimes.

Frequently Asked Questions

Which Node.js versions support the built-in SQLite backend?

Node.js version 22.5 and higher includes the built-in node:sqlite module. In context-mode, this is only utilized on Linux platforms to avoid stability issues. On older Node.js versions, the library automatically falls back to better-sqlite3 without requiring code changes.

Why does context-mode avoid better-sqlite3 on Linux?

The native better-sqlite3 addon has a documented history of segfaults on Linux builds (nodejs/node#62515). By preferring the built-in node:sqlite module on Linux when available, context-mode eliminates the risk of process crashes associated with native addon bindings on this platform.

How do I verify which SQLite backend is currently active?

After calling loadDatabase(), inspect the name property of the returned constructor. Values will be "BunSQLiteAdapter" when running on Bun, "NodeSQLiteAdapter" when using Node.js's built-in module on Linux, or "Database" when using the standard better-sqlite3 fallback.

Is Bun officially supported by context-mode?

Yes. The source code in src/db-base.ts explicitly checks for the Bun runtime using (globalThis as any).Bun and routes to the bun:sqlite module. This is a first-class supported platform, with BunSQLiteAdapter providing full API compatibility with the other backends.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →