Note: this is an in-house item already being handled by the maintainer - not open for contribution. Filed for tracking only.
better-sqlite3 is our only native-compiled dependency. Node now ships SQLite as a core module, so we can stop shipping a compiled copy of one.
Why
The native build is the cause of every install complaint we get. It is what produces the blocked-install-script warnings under npm 12, the prebuild-install@7.1.3 deprecation notice, and the recurring EBADENGINE friction whenever a new Node major lands before better-sqlite3 declares support for it.
Worth being precise about where the noise comes from: cve-lite-cli itself declares no install-time hooks at all, no preinstall, install, postinstall or prepare. The only install script in the tree is better-sqlite3's prebuild-install || node-gyp rebuild --release. Reported as a symptom in #1223.
Why this is cheap
Measured rather than assumed:
better-sqlite3 is imported in exactly one file, src/advisory/local-db.ts.
node:sqlite opens our existing 498MB database unmodified, with the same SQL. No migration, no re-sync.
- Both query shapes work: the
(ecosystem, package_name) lookup returns in about 1ms, and the advisory_id point lookup returns the blob intact.
The port
| today |
becomes |
new Database(p, { readonly: true, fileMustExist: true }) |
new DatabaseSync(p, { readOnly: true }); a missing file already throws ERR_SQLITE_ERROR, so fileMustExist has no equivalent and needs none |
db.pragma("foreign_keys = ON") |
delete; node:sqlite enables foreign keys by default |
db.transaction(fn), 2 call sites |
a small helper over exec("BEGIN"/"COMMIT"/"ROLLBACK"); no equivalent exists |
prepare / run / get / all / exec / close |
identical |
named parameters @id with bare object keys |
identical |
Two things that need care
The experimental warning. node:sqlite prints ExperimentalWarning: SQLite is an experimental feature on load. The plan is to suppress that one warning and nothing else, since a user cannot act on it and it concerns which SQLite binding we use rather than their security.
A static import cannot be suppressed: builtins initialise during module linking, before any user code runs. Tested and confirmed. The working approach is a side-effecting filter module imported first, plus loading node:sqlite through a lazy createRequire at first use, which stays synchronous. When the module stabilises, both go away and a plain static import returns.
The Node floor moves from >=20 to >=22. Node 20 passed end of life in April 2026, our CI currently tests only Node 24 so the >=20 claim was never verified, and the published Action defaults adopters to 24. CI gains a Node 22 job as part of this, so the floor we declare is the floor we test.
Accepted risk
This makes us depend on an experimental core API. Mitigated by testing both supported majors in CI, recording the risk in the CHANGELOG and docs rather than in every user's terminal, and by the fact that reverting is a one-file change since better-sqlite3 reads the same file with the same SQL.
Outcome
Runtime dependencies drop from 5 to 4, and to zero native. No install scripts anywhere in the tree.
better-sqlite3is our only native-compiled dependency. Node now ships SQLite as a core module, so we can stop shipping a compiled copy of one.Why
The native build is the cause of every install complaint we get. It is what produces the blocked-install-script warnings under npm 12, the
prebuild-install@7.1.3deprecation notice, and the recurringEBADENGINEfriction whenever a new Node major lands beforebetter-sqlite3declares support for it.Worth being precise about where the noise comes from:
cve-lite-cliitself declares no install-time hooks at all, nopreinstall,install,postinstallorprepare. The only install script in the tree isbetter-sqlite3'sprebuild-install || node-gyp rebuild --release. Reported as a symptom in #1223.Why this is cheap
Measured rather than assumed:
better-sqlite3is imported in exactly one file,src/advisory/local-db.ts.node:sqliteopens our existing 498MB database unmodified, with the same SQL. No migration, no re-sync.(ecosystem, package_name)lookup returns in about 1ms, and theadvisory_idpoint lookup returns the blob intact.The port
new Database(p, { readonly: true, fileMustExist: true })new DatabaseSync(p, { readOnly: true }); a missing file already throwsERR_SQLITE_ERROR, sofileMustExisthas no equivalent and needs nonedb.pragma("foreign_keys = ON")node:sqliteenables foreign keys by defaultdb.transaction(fn), 2 call sitesexec("BEGIN"/"COMMIT"/"ROLLBACK"); no equivalent existsprepare/run/get/all/exec/close@idwith bare object keysTwo things that need care
The experimental warning.
node:sqliteprintsExperimentalWarning: SQLite is an experimental featureon load. The plan is to suppress that one warning and nothing else, since a user cannot act on it and it concerns which SQLite binding we use rather than their security.A static import cannot be suppressed: builtins initialise during module linking, before any user code runs. Tested and confirmed. The working approach is a side-effecting filter module imported first, plus loading
node:sqlitethrough a lazycreateRequireat first use, which stays synchronous. When the module stabilises, both go away and a plain static import returns.The Node floor moves from
>=20to>=22. Node 20 passed end of life in April 2026, our CI currently tests only Node 24 so the>=20claim was never verified, and the published Action defaults adopters to 24. CI gains a Node 22 job as part of this, so the floor we declare is the floor we test.Accepted risk
This makes us depend on an experimental core API. Mitigated by testing both supported majors in CI, recording the risk in the CHANGELOG and docs rather than in every user's terminal, and by the fact that reverting is a one-file change since
better-sqlite3reads the same file with the same SQL.Outcome
Runtime dependencies drop from 5 to 4, and to zero native. No install scripts anywhere in the tree.