From c436a44c894e5b47788a2455c4543b6a3e787b61 Mon Sep 17 00:00:00 2001 From: screentinker Date: Thu, 13 Aug 2026 12:18:47 -0500 Subject: [PATCH] Pin better-sqlite3 to 12.9.0 (was ^9.4.3) (#264) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prepares for the Node 22 move by decoupling it from the database driver, so the two upgrades land as independently reversible steps rather than one flag day. 9.6.0 cannot work on Node 22. It uses the raw V8 API (220 v8:: references, zero napi_), so it is ABI-locked per Node major, and its GitHub release assets carry prebuilds for ABI 108/115/120 only — nothing for Node 22's 127. Its install script is `prebuild-install || node-gyp rebuild --release`, so on Node 22 it silently falls through to compiling raw-V8 code against Node 22 headers. That is not just slow: lib/preflight-deps.js rebuilds synchronously before the server listens, and prod's systemd unit is TimeoutStartSec=90 with Restart=always, so a slow or failing compile is an unbootable loop rather than the intended self-heal. 12.9.0 ships prebuilds for BOTH Node 20 (ABI 115) and Node 22 (127), so neither the current runtime nor the target has to compile anything. THE PIN IS EXACT ON PURPOSE — ^12.9.0 would defeat it. 12.10.0 dropped the Node 20 prebuild while still advertising "20.x" in engines, so a caret resolves to 12.11.x and reintroduces the from-source compile on today's runtime. Verified against the release assets per version: 12.0.0 / 12.2.0 / 12.4.5 / 12.6.2 / 12.9.0 ABIs 115,127,... 12.10.0 / 12.10.1 / 12.11.1 ABIs 127,137,141,147 — no 115 The reasoning is recorded in preflight-deps.js, which is where anyone hitting the matching failure will already be reading. 13.x was considered and rejected FOR NOW: it is the first N-API release, which ends the per-major ABI problem for good (8 prebuilds keyed by platform, not ABI) and is where we should eventually land — but engines is ">=22", so it cannot be adopted while prod, alpha and CI all run Node 20. It is also three weeks old with three patch releases, which is young for the one component that owns all the data. No API changes to absorb: every major from 10 to 13 bumped only for dropping EOL Node/Electron versions, so the ~1486 .prepare(), 46 .transaction() and 59 .pragma() call sites are untouched. Verified on Node 20: installed from a PREBUILT binary (no obj.target, so no compilation), opens a real database, and the WAL path the #149 checkpointer depends on still works — journal_mode=WAL engages on a file DB, pragma(...,{simple:false}) returns the expected shape, and a second connection from another handle reads and runs wal_checkpoint(TRUNCATE). 1649/1649 tests pass. --- server/lib/preflight-deps.js | 12 ++++++++++ server/package-lock.json | 44 +++++++++++++++++++++++++++++++----- server/package.json | 2 +- 3 files changed, 51 insertions(+), 7 deletions(-) diff --git a/server/lib/preflight-deps.js b/server/lib/preflight-deps.js index 9be7249..e65cf26 100644 --- a/server/lib/preflight-deps.js +++ b/server/lib/preflight-deps.js @@ -14,6 +14,18 @@ * NODE UPGRADE better-sqlite3 is a native module compiled against one ABI. Upgrading Node makes * every boot fail with NODE_MODULE_VERSION mismatch, which reads like database * corruption and is not. + * + * ⚠️ better-sqlite3 is pinned to EXACTLY 12.9.0, not a caret range, and the reason + * is invisible from package.json: 12.10.0 DROPPED the prebuilt binary for Node 20 + * (ABI 115) while still advertising `"node": "20.x || ..."` in engines. So a caret + * resolves to 12.11.x, finds no prebuild on Node 20, and silently falls through to + * `node-gyp rebuild` — a from-source compile during install, and during the repair + * below. That matters here: this file rebuilds synchronously BEFORE the server + * listens, and prod's systemd unit has TimeoutStartSec=90 with Restart=always, so a + * slow or failing compile is a boot loop rather than a self-heal. 12.9.0 is the last + * version shipping prebuilds for BOTH Node 20 (115) and Node 22 (127), which is what + * lets the runtime move without the module having to compile at all. + * Re-check the release assets before widening the pin. * HAND EDITS a `git checkout`, a partly-copied tree, an interrupted install. * * All three present as a server that will not start, with an error that names a file rather than diff --git a/server/package-lock.json b/server/package-lock.json index ff95a92..e2c9469 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -13,7 +13,7 @@ "@jsquash/webp": "^1.5.0", "archiver": "^7.0.1", "bcryptjs": "^3.0.3", - "better-sqlite3": "^9.4.3", + "better-sqlite3": "12.9.0", "cors": "^2.8.5", "express": "^4.18.2", "express-rate-limit": "^8.3.1", @@ -24,7 +24,6 @@ "nodemailer": "^6.9.16", "otplib": "^12.0.1", "qrcode": "^1.5.4", - "sharp": "^0.35.3", "socket.io": "^4.7.2", "stripe": "^20.4.1", "unzipper": "^0.12.3", @@ -33,6 +32,7 @@ "devDependencies": { "js-yaml": "^4.2.0", "puppeteer-core": "^24.43.1", + "sharp": "^0.35.3", "socket.io-client": "^4.8.3" } }, @@ -72,6 +72,7 @@ "version": "1.11.3", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz", "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -82,6 +83,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -94,6 +96,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -116,6 +119,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -135,6 +139,7 @@ "version": "0.35.3", "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.3.tgz", "integrity": "sha512-lUxcqWIj2wMQ9BrwNjngcr1gWUr5xgaGThBRqPPalIC2n67Cqj1uPh8NnA/ZhAg8hUbKl+kVHKwgUIwe6ZYPrg==", + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -157,6 +162,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -173,6 +179,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -189,6 +196,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -205,6 +213,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -221,6 +230,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -237,6 +247,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -253,6 +264,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -269,6 +281,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -285,6 +298,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -301,6 +315,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -317,6 +332,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -339,6 +355,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -361,6 +378,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -383,6 +401,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -405,6 +424,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -427,6 +447,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -449,6 +470,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -471,6 +493,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -490,6 +513,7 @@ "version": "0.35.3", "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.3.tgz", "integrity": "sha512-cZ0XkcYGpHZkqW6iCkqTcmUC0CD9DhD5d/qeZlZkfRBn6GnHniZXLUo5+9xw8Iv76YE6LQFN9YNBlKREcCG76w==", + "dev": true, "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", "optional": true, "dependencies": { @@ -509,6 +533,7 @@ "cpu": [ "wasm32" ], + "dev": true, "license": "Apache-2.0", "optional": true, "dependencies": { @@ -528,6 +553,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ @@ -547,6 +573,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ @@ -566,6 +593,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ @@ -1829,14 +1857,17 @@ } }, "node_modules/better-sqlite3": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-9.6.0.tgz", - "integrity": "sha512-yR5HATnqeYNVnkaUTf4bOP2dJSnyhP4puJN/QPRyx4YkBEEUxib422n2XzPqDEHjQQqazoYoADdAm5vE15+dAQ==", + "version": "12.9.0", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.9.0.tgz", + "integrity": "sha512-wqUv4Gm3toFpHDQmaKD4QhZm3g1DjUBI0yzS4UBl6lElUmXFYdTQmmEDpAFa5o8FiFiymURypEnfVHzILKaxqQ==", "hasInstallScript": true, "license": "MIT", "dependencies": { "bindings": "^1.5.0", "prebuild-install": "^7.1.1" + }, + "engines": { + "node": "20.x || 22.x || 23.x || 24.x || 25.x" } }, "node_modules/bindings": { @@ -4591,6 +4622,7 @@ "version": "0.35.3", "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.3.tgz", "integrity": "sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@img/colour": "^1.1.0", @@ -5309,7 +5341,7 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "devOptional": true, + "dev": true, "license": "0BSD" }, "node_modules/tunnel-agent": { diff --git a/server/package.json b/server/package.json index 3d96a68..2a5cfc5 100644 --- a/server/package.json +++ b/server/package.json @@ -15,7 +15,7 @@ "@jsquash/webp": "^1.5.0", "archiver": "^7.0.1", "bcryptjs": "^3.0.3", - "better-sqlite3": "^9.4.3", + "better-sqlite3": "12.9.0", "cors": "^2.8.5", "express": "^4.18.2", "express-rate-limit": "^8.3.1",