mirror of
https://github.com/AgentSeal/codeburn.git
synced 2026-05-14 07:48:34 +00:00
fix: drop better-sqlite3 to remove deprecated prebuild-install (#75)
npm was warning on every install that prebuild-install@7.1.3 is no longer maintained. prebuild-install ships as a transitive dependency of better-sqlite3 and upstream PR #1446 to replace it is still open, so we switch to Node's built-in node:sqlite module (stable in Node 24, experimental in Node 22/23) and remove the better-sqlite3 dep entirely. - src/sqlite.ts: uses DatabaseSync from node:sqlite. The one-shot ExperimentalWarning about SQLite on Node 22/23 is silenced for that specific warning; other warnings pass through unchanged. - package.json: engines.node bumped to >=22 (Node 20 EOL 2026-04-30), better-sqlite3 and @types/better-sqlite3 removed, @types/node added (it was coming in transitively via @types/better-sqlite3). - tests/providers/opencode.test.ts: fixture DB creation switched to node:sqlite (API parity for the CREATE TABLE + INSERT + prepare path we use). End-user install footprint shrinks from 167 to 40 packages and prints zero deprecation warnings. Credit: @primeminister for the report.
This commit is contained in:
parent
2b15256189
commit
7aefd674fc
5 changed files with 83 additions and 540 deletions
|
|
@ -1,6 +1,7 @@
|
|||
import { createRequire } from 'node:module'
|
||||
|
||||
const require = createRequire(import.meta.url)
|
||||
/// Thin SQLite read-only wrapper over Node's built-in `node:sqlite` module (stable in
|
||||
/// Node 24, experimental in Node 22 / 23). Replaces the earlier `better-sqlite3` binding
|
||||
/// so the dependency graph no longer pulls in the deprecated `prebuild-install` package
|
||||
/// (issue #75). Works across Cursor and OpenCode session DBs, both of which we only read.
|
||||
|
||||
type Row = Record<string, unknown>
|
||||
|
||||
|
|
@ -9,21 +10,67 @@ export type SqliteDatabase = {
|
|||
close(): void
|
||||
}
|
||||
|
||||
let BetterSqlite3: unknown = null
|
||||
type DatabaseSyncCtor = new (path: string, options?: { readOnly?: boolean }) => {
|
||||
prepare(sql: string): { all(...params: unknown[]): Row[] }
|
||||
close(): void
|
||||
}
|
||||
|
||||
let DatabaseSync: DatabaseSyncCtor | null = null
|
||||
let loadAttempted = false
|
||||
let loadError: string | null = null
|
||||
|
||||
/// Lazily imports `node:sqlite`. On Node 22/23 it emits an ExperimentalWarning the first
|
||||
/// time the module is loaded; we silence that specific warning once so dashboards aren't
|
||||
/// preceded by a scary stderr line every run. Any other warnings (including future
|
||||
/// non-SQLite ones) are left untouched.
|
||||
function loadDriver(): boolean {
|
||||
if (loadAttempted) return BetterSqlite3 !== null
|
||||
if (loadAttempted) return DatabaseSync !== null
|
||||
loadAttempted = true
|
||||
|
||||
const origEmit = process.emit.bind(process)
|
||||
let restored = false
|
||||
const restore = () => {
|
||||
if (restored) return
|
||||
restored = true
|
||||
process.emit = origEmit
|
||||
}
|
||||
|
||||
// Node's `process.emit` signature is overloaded; we intercept the 'warning' channel
|
||||
// only and proxy everything else through unchanged. The `any` cast avoids chasing the
|
||||
// overload union which isn't worth its verbosity for a single-purpose shim.
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
process.emit = function patchedEmit(this: NodeJS.Process, event: string, ...args: any[]): boolean {
|
||||
if (event === 'warning') {
|
||||
const warning = args[0] as { name?: string; message?: string } | undefined
|
||||
if (
|
||||
warning?.name === 'ExperimentalWarning' &&
|
||||
typeof warning.message === 'string' &&
|
||||
/SQLite/i.test(warning.message)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return (origEmit as any).call(this, event, ...args)
|
||||
} as typeof process.emit
|
||||
|
||||
try {
|
||||
BetterSqlite3 = require('better-sqlite3')
|
||||
// Dynamic require via createRequire avoids TypeScript chasing types we don't need at
|
||||
// build time (node:sqlite landed in @types/node much later than in Node itself).
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
const mod = eval('require')('node:sqlite') as { DatabaseSync: DatabaseSyncCtor }
|
||||
DatabaseSync = mod.DatabaseSync
|
||||
return true
|
||||
} catch {
|
||||
loadError = 'SQLite-based providers (Cursor, OpenCode) require the better-sqlite3 package.\n' +
|
||||
'Install it with: npm install -g better-sqlite3\n' +
|
||||
'Then run codeburn again.'
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err)
|
||||
loadError =
|
||||
'SQLite-based providers (Cursor, OpenCode) need Node 22+ with the node:sqlite module.\n' +
|
||||
`Current Node: ${process.version}.\n` +
|
||||
'Upgrade Node (https://nodejs.org) and run codeburn again.\n' +
|
||||
`(underlying error: ${message})`
|
||||
return false
|
||||
} finally {
|
||||
restore()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -36,16 +83,11 @@ export function getSqliteLoadError(): string {
|
|||
}
|
||||
|
||||
export function openDatabase(path: string): SqliteDatabase {
|
||||
if (!loadDriver()) {
|
||||
if (!loadDriver() || DatabaseSync === null) {
|
||||
throw new Error(getSqliteLoadError())
|
||||
}
|
||||
|
||||
const Database = BetterSqlite3 as new (path: string, options?: Record<string, unknown>) => {
|
||||
prepare(sql: string): { all(...params: unknown[]): Row[] }
|
||||
close(): void
|
||||
}
|
||||
|
||||
const db = new Database(path, { readonly: true, fileMustExist: true })
|
||||
const db = new DatabaseSync(path, { readOnly: true })
|
||||
|
||||
return {
|
||||
query<T extends Row = Row>(sql: string, params: unknown[] = []): T[] {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue