diff --git a/src/resources/extensions/sf/sf-db/sf-db-core.js b/src/resources/extensions/sf/sf-db/sf-db-core.js index 4c4decfe7..64fbb2359 100644 --- a/src/resources/extensions/sf/sf-db/sf-db-core.js +++ b/src/resources/extensions/sf/sf-db/sf-db-core.js @@ -23,6 +23,8 @@ import { mkdirSync, readdirSync, readFileSync, + renameSync, + rmSync, statSync, unlinkSync, writeFileSync, @@ -199,13 +201,25 @@ function createDatabaseSnapshot(rawDb, path) { if (latest > 0 && Date.now() - latest < DB_BACKUP_MIN_INTERVAL_MS) return; const stamp = new Date().toISOString().replace(/[:.]/g, "-"); const backupPath = join(dir, `sf.db.${stamp}`); - rawDb.exec(`VACUUM INTO ${sqliteStringLiteral(backupPath)}`); + const tmpPath = `${backupPath}.tmp`; + rmSync(tmpPath, { force: true }); + rawDb.exec(`VACUUM INTO ${sqliteStringLiteral(tmpPath)}`); + renameSync(tmpPath, backupPath); pruneDatabaseBackups(dir); } catch (err) { logWarning( "sf-db", `database snapshot failed: ${getErrorMessage(err)}`, ); + try { + for (const entry of readdirSync(dir)) { + if (entry.startsWith("sf.db.") && entry.endsWith(".tmp")) { + rmSync(join(dir, entry), { force: true }); + } + } + } catch { + // Best-effort cleanup; preserve the original snapshot warning. + } } } function performDatabaseMaintenance(rawDb, path) { diff --git a/src/resources/extensions/sf/tests/sf-db-migration.test.mjs b/src/resources/extensions/sf/tests/sf-db-migration.test.mjs index 0392a9a85..942b25692 100644 --- a/src/resources/extensions/sf/tests/sf-db-migration.test.mjs +++ b/src/resources/extensions/sf/tests/sf-db-migration.test.mjs @@ -11,6 +11,7 @@ import { mkdtempSync, readdirSync, rmSync, + statSync, } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; @@ -453,6 +454,8 @@ test("openDatabase_when_file_backed_creates_db_snapshot_and_maintenance_marker", name.startsWith("sf.db."), ); assert.equal(backups.length, 1); + assert.equal(backups[0].endsWith(".tmp"), false); + assert.equal(statSync(join(backupDir, backups[0])).size > 0, true); assert.equal(existsSync(join(backupDir, "maintenance.json")), true); });