From 7dcc3472b7897dde2a45b77a17a7f9641f17f2cc Mon Sep 17 00:00:00 2001 From: Luke Zehrung Date: Mon, 22 Jun 2026 11:50:48 -0400 Subject: [PATCH 1/3] Avoid SQLite warning for CLI version output --- src/sqlite-driver.ts | 42 +++++++++++++++++++++++++---------- tests/cli-regressions.test.ts | 5 ++++- 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/src/sqlite-driver.ts b/src/sqlite-driver.ts index 38c1496c..f7499a56 100644 --- a/src/sqlite-driver.ts +++ b/src/sqlite-driver.ts @@ -1,5 +1,7 @@ -import { constants, DatabaseSync, type StatementColumnMetadata, type StatementResultingChanges } from "node:sqlite"; +import { createRequire } from "node:module"; import type { PathLike } from "node:fs"; +import type * as NodeSqlite from "node:sqlite"; +import type { DatabaseSync, StatementColumnMetadata, StatementResultingChanges, StatementSync } from "node:sqlite"; export type SqliteValue = null | number | bigint | string | NodeJS.ArrayBufferView; export type SqliteRow = Record; @@ -7,17 +9,31 @@ export type SqliteRawRow = unknown[]; export type SqliteRunResult = StatementResultingChanges; export type SqliteColumn = StatementColumnMetadata; +type NodeSqliteModule = typeof NodeSqlite; +type SqliteConstants = NodeSqliteModule["constants"]; type SqliteParameterInput = SqliteValue | readonly SqliteValue[]; -const isSqliteValueArray = (value: SqliteParameterInput): value is readonly SqliteValue[] => Array.isArray(value); +const requireNodeModule = createRequire(import.meta.url); +let sqliteModule: NodeSqliteModule | undefined; + +function loadNodeSqlite(): NodeSqliteModule { + if (sqliteModule) return sqliteModule; + const loaded = requireNodeModule("node:sqlite") as NodeSqliteModule; + sqliteModule = loaded; + return loaded; +} + +function isReadOnlyAllowedAction(actionCode: number, constants: SqliteConstants): boolean { + return ( + actionCode === constants.SQLITE_FUNCTION || + actionCode === constants.SQLITE_PRAGMA || + actionCode === constants.SQLITE_READ || + actionCode === constants.SQLITE_SELECT || + actionCode === constants.SQLITE_TRANSACTION + ); +} -const readOnlyAllowedActions = new Set([ - constants.SQLITE_FUNCTION, - constants.SQLITE_PRAGMA, - constants.SQLITE_READ, - constants.SQLITE_SELECT, - constants.SQLITE_TRANSACTION, -]); +const isSqliteValueArray = (value: SqliteParameterInput): value is readonly SqliteValue[] => Array.isArray(value); const normalizeParams = (params: readonly SqliteParameterInput[]): SqliteValue[] => { if (params.length === 1) { @@ -36,7 +52,7 @@ const normalizeParams = (params: readonly SqliteParameterInput[]): SqliteValue[] export class SqliteStatement { constructor( - private readonly statement: ReturnType, + private readonly statement: StatementSync, private readonly returnArrays = false, ) { this.statement.setReturnArrays(returnArrays); @@ -71,13 +87,15 @@ export class SqliteDatabase { private readonly db: DatabaseSync; constructor(filePath: PathLike, options?: { readonly?: boolean }) { - this.db = new DatabaseSync(filePath, { + const sqlite = loadNodeSqlite(); + this.db = new sqlite.DatabaseSync(filePath, { readOnly: options?.readonly, timeout: 5000, }); if (options?.readonly) { + const { constants } = sqlite; this.db.setAuthorizer((actionCode) => - readOnlyAllowedActions.has(actionCode) ? constants.SQLITE_OK : constants.SQLITE_DENY, + isReadOnlyAllowedAction(actionCode, constants) ? constants.SQLITE_OK : constants.SQLITE_DENY, ); } } diff --git a/tests/cli-regressions.test.ts b/tests/cli-regressions.test.ts index eff64906..b86a1a06 100644 --- a/tests/cli-regressions.test.ts +++ b/tests/cli-regressions.test.ts @@ -128,13 +128,16 @@ describe("CLI regressions", () => { expect(stdout.trim()).toBe(packageJson.version); }); - it("does not statically load SQLite-backed command modules for version output", async () => { + it("does not statically load SQLite for version output", async () => { const source = await fsp.readFile(sourceCliPath, "utf8"); + const sqliteDriverSource = await fsp.readFile(path.resolve(process.cwd(), "src", "sqlite-driver.ts"), "utf8"); expect(source).not.toContain('from "./cli/artifact.js"'); expect(source).not.toContain('from "./cli/graph.js"'); expect(source).not.toContain('from "./cli/mcp.js"'); expect(source).not.toContain('from "./cli/sql.js"'); + expect(sqliteDriverSource).not.toContain('import { constants, DatabaseSync'); + expect(sqliteDriverSource).toContain('requireNodeModule("node:sqlite")'); }); it("importing cli.ts as a module does not execute the entrypoint", async () => { From 5423e4e16554f2cb73d04e8535f4c41e4bb9dac0 Mon Sep 17 00:00:00 2001 From: Luke Zehrung Date: Mon, 22 Jun 2026 13:32:51 -0400 Subject: [PATCH 2/3] Address SQLite lazy-load type imports --- src/sqlite-driver.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/sqlite-driver.ts b/src/sqlite-driver.ts index f7499a56..a4064106 100644 --- a/src/sqlite-driver.ts +++ b/src/sqlite-driver.ts @@ -1,7 +1,12 @@ import { createRequire } from "node:module"; import type { PathLike } from "node:fs"; -import type * as NodeSqlite from "node:sqlite"; -import type { DatabaseSync, StatementColumnMetadata, StatementResultingChanges, StatementSync } from "node:sqlite"; +import type { + constants as sqliteConstants, + DatabaseSync, + StatementColumnMetadata, + StatementResultingChanges, + StatementSync, +} from "node:sqlite"; export type SqliteValue = null | number | bigint | string | NodeJS.ArrayBufferView; export type SqliteRow = Record; @@ -9,8 +14,11 @@ export type SqliteRawRow = unknown[]; export type SqliteRunResult = StatementResultingChanges; export type SqliteColumn = StatementColumnMetadata; -type NodeSqliteModule = typeof NodeSqlite; -type SqliteConstants = NodeSqliteModule["constants"]; +type SqliteConstants = typeof sqliteConstants; +type NodeSqliteModule = { + DatabaseSync: typeof DatabaseSync; + constants: SqliteConstants; +}; type SqliteParameterInput = SqliteValue | readonly SqliteValue[]; const requireNodeModule = createRequire(import.meta.url); From 2c598de8f2ac9ab3708bc20a3188025af8230085 Mon Sep 17 00:00:00 2001 From: Luke Zehrung Date: Mon, 22 Jun 2026 14:15:36 -0400 Subject: [PATCH 3/3] Harden SQLite lazy-load regression --- src/sqlite-driver.ts | 24 +++++++++++++++--------- tests/cli-regressions.test.ts | 4 ++-- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/sqlite-driver.ts b/src/sqlite-driver.ts index a4064106..a20133bc 100644 --- a/src/sqlite-driver.ts +++ b/src/sqlite-driver.ts @@ -1,12 +1,6 @@ import { createRequire } from "node:module"; import type { PathLike } from "node:fs"; -import type { - constants as sqliteConstants, - DatabaseSync, - StatementColumnMetadata, - StatementResultingChanges, - StatementSync, -} from "node:sqlite"; +import type { DatabaseSync, StatementColumnMetadata, StatementResultingChanges, StatementSync } from "node:sqlite"; export type SqliteValue = null | number | bigint | string | NodeJS.ArrayBufferView; export type SqliteRow = Record; @@ -14,9 +8,21 @@ export type SqliteRawRow = unknown[]; export type SqliteRunResult = StatementResultingChanges; export type SqliteColumn = StatementColumnMetadata; -type SqliteConstants = typeof sqliteConstants; +type SqliteConstants = { + SQLITE_DENY: number; + SQLITE_FUNCTION: number; + SQLITE_OK: number; + SQLITE_PRAGMA: number; + SQLITE_READ: number; + SQLITE_SELECT: number; + SQLITE_TRANSACTION: number; +}; +type SqliteDatabaseConstructor = new ( + filePath: PathLike, + options?: { readOnly?: boolean | undefined; timeout?: number }, +) => DatabaseSync; type NodeSqliteModule = { - DatabaseSync: typeof DatabaseSync; + DatabaseSync: SqliteDatabaseConstructor; constants: SqliteConstants; }; type SqliteParameterInput = SqliteValue | readonly SqliteValue[]; diff --git a/tests/cli-regressions.test.ts b/tests/cli-regressions.test.ts index b86a1a06..e7a85d9e 100644 --- a/tests/cli-regressions.test.ts +++ b/tests/cli-regressions.test.ts @@ -136,8 +136,8 @@ describe("CLI regressions", () => { expect(source).not.toContain('from "./cli/graph.js"'); expect(source).not.toContain('from "./cli/mcp.js"'); expect(source).not.toContain('from "./cli/sql.js"'); - expect(sqliteDriverSource).not.toContain('import { constants, DatabaseSync'); - expect(sqliteDriverSource).toContain('requireNodeModule("node:sqlite")'); + expect(sqliteDriverSource).not.toMatch(/import\s*\{[^}]*\b(?:constants|DatabaseSync)\b[^}]*\}\s*from\s*[\"']node:sqlite[\"']/s); + expect(sqliteDriverSource).toMatch(/requireNodeModule\(\s*[\"']node:sqlite[\"']\s*\)/); }); it("importing cli.ts as a module does not execute the entrypoint", async () => {