diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8713426..8f4fa9c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,7 +34,8 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: "20" + # 22+: the CLI reflects a database via the built-in node:sqlite, added in 22.5. + node-version: "22" - name: Install deps run: make js-setup - name: Run the TS gate diff --git a/packages/js/src/cli/cli.ts b/packages/js/src/cli/cli.ts index cb3e17c..920e3df 100644 --- a/packages/js/src/cli/cli.ts +++ b/packages/js/src/cli/cli.ts @@ -1,4 +1,5 @@ -import { DatabaseSync } from 'node:sqlite'; +import { createRequire } from 'node:module'; +import type { DatabaseSync } from 'node:sqlite'; import type { Model } from '../model.js'; import type { BoundCapability } from '../capability.js'; import { Agent } from '../loop.js'; @@ -15,13 +16,38 @@ import { fromSqlite } from './sqlite.js'; /** A sink for one line of output. */ export type Writer = (line: string) => void; +type SqliteCtor = new (path: string) => DatabaseSync; +let cachedCtor: SqliteCtor | undefined; + +/** + * Load node:sqlite lazily, so the CLI module still imports on Node < 22 (where the + * built-in is absent) and only errors — with a clear hint — when a database command + * actually runs. node:sqlite was added in Node 22.5. + */ +function loadSqlite(): SqliteCtor { + if (cachedCtor) { + return cachedCtor; + } + try { + const req = createRequire(import.meta.url); + cachedCtor = (req('node:sqlite') as { DatabaseSync: SqliteCtor }).DatabaseSync; + return cachedCtor; + } catch { + throw new ReinsError( + 'the reins CLI needs Node 22+ (its built-in node:sqlite is unavailable)', + 'upgrade Node to 22 or newer, then retry', + ); + } +} + /** * Open a SQLite database from a URL: a bare path, `:memory:`, a `sqlite://` URL * (scheme stripped), or a `file:` URI (passed through). */ export function openDb(dbUrl: string): DatabaseSync { + const Ctor = loadSqlite(); // throws a clear Node-22 hint if node:sqlite is missing try { - return new DatabaseSync(sqlitePath(dbUrl)); + return new Ctor(sqlitePath(dbUrl)); } catch (e) { throw new ReinsError( `cannot open database: ${message(e)}`,