Use Node SQLite libraries for backup verification, if available
Some checks failed
CI/CD Pipeline / Test on Node.js 16.x and ubuntu-latest (release) Successful in 1m37s
CI/CD Pipeline / Test on Node.js 18.x and ubuntu-latest (release) Successful in 43s
CI/CD Pipeline / Test on Node.js 20.x and ubuntu-latest (release) Successful in 45s
CI/CD Pipeline / Test on Node.js 22.x and ubuntu-latest (release) Successful in 50s
CI/CD Pipeline / Lint and Code Quality (release) Failing after 11s
CI/CD Pipeline / Security Scan (release) Failing after 11s
CI/CD Pipeline / Test on Node.js 16.x and windows-latest (release) Has been cancelled
CI/CD Pipeline / Test on Node.js 18.x and macos-latest (release) Has been cancelled
CI/CD Pipeline / Test on Node.js 18.x and windows-latest (release) Has been cancelled
CI/CD Pipeline / Test on Node.js 20.x and macos-latest (release) Has been cancelled
CI/CD Pipeline / Test on Node.js 20.x and windows-latest (release) Has been cancelled
CI/CD Pipeline / Test on Node.js 22.x and macos-latest (release) Has been cancelled
CI/CD Pipeline / Test on Node.js 22.x and windows-latest (release) Has been cancelled
CI/CD Pipeline / Publish to npm (release) Has been cancelled
CI/CD Pipeline / Auto-increment version on main (release) Has been cancelled

This commit is contained in:
Skylar Ittner 2026-01-23 22:33:09 -07:00 committed by GitHub
parent b34e4b5d83
commit c2b4a8eca3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -1,12 +1,12 @@
const path = require('path'); const path = require('path');
const fs = require('fs'); const fs = require('fs');
const { spawn, exec } = require('child_process'); const {spawn, exec} = require('child_process');
const { promisify } = require('util'); const {promisify} = require('util');
const execAsync = promisify(exec); const execAsync = promisify(exec);
/** /**
* SQLite Backup Library * SQLite Backup Library
* *
* A standalone library for creating, managing, and verifying SQLite database backups. * A standalone library for creating, managing, and verifying SQLite database backups.
*/ */
class SQLiteBackup { class SQLiteBackup {
@ -24,8 +24,8 @@ class SQLiteBackup {
this.databasePath = path.resolve(options.databasePath); this.databasePath = path.resolve(options.databasePath);
this.backupDirectory = options.backupDirectory ? this.backupDirectory = options.backupDirectory ?
path.resolve(options.backupDirectory) : path.resolve(options.backupDirectory) :
path.join(path.dirname(this.databasePath), 'backups'); path.join(path.dirname(this.databasePath), 'backups');
this.createBackupDir = options.createBackupDir !== false; this.createBackupDir = options.createBackupDir !== false;
// Validate database file exists // Validate database file exists
@ -35,8 +35,8 @@ class SQLiteBackup {
// Create backup directory if needed // Create backup directory if needed
if (this.createBackupDir && !fs.existsSync(this.backupDirectory)) { if (this.createBackupDir && !fs.existsSync(this.backupDirectory)) {
fs.mkdirSync(this.backupDirectory, { recursive: true }); fs.mkdirSync(this.backupDirectory, {recursive: true});
} }
} }
/** /**
@ -99,7 +99,7 @@ class SQLiteBackup {
error: error.message, error: error.message,
timestamp: new Date().toISOString() timestamp: new Date().toISOString()
}; };
} }
} }
/** /**
@ -114,9 +114,27 @@ class SQLiteBackup {
return false; return false;
} }
const command = `sqlite3 "${backupPath}" "PRAGMA integrity_check;"`; const dbDriver = this._getDatabaseDriver();
const { stdout } = await execAsync(command);
return stdout.trim() === 'ok'; var result = "";
switch (dbDriver.type) {
case "cli":
const command = `sqlite3 "${backupPath}" "PRAGMA integrity_check;"`;
const {stdout} = await execAsync(command);
result = stdout.trim();
break;
case "better-sqlite3":
const db = dbDriver.driver(backupPath);
result = await db.pragma('integrity_check', {simple: true});
break;
case "sqlite3":
result = await this._validateUsingSQLite3Package(dbDriver.driver, backupPath);
console.log(result);
break;
}
return result === 'ok';
} catch (error) { } catch (error) {
return false; return false;
} }
@ -182,7 +200,7 @@ class SQLiteBackup {
removed: 0, removed: 0,
errors: [error.message] errors: [error.message]
}; };
} }
} }
/** /**
@ -212,7 +230,6 @@ class SQLiteBackup {
isValid: null, isValid: null,
checksum: null checksum: null
}; };
if (includeChecksums) { if (includeChecksums) {
backup.checksum = await this._calculateChecksum(file.path); backup.checksum = await this._calculateChecksum(file.path);
backup.isValid = await this.verifyBackup(file.path); backup.isValid = await this.verifyBackup(file.path);
@ -228,7 +245,7 @@ class SQLiteBackup {
} catch (error) { } catch (error) {
throw new Error(`Failed to list backups: ${error.message}`); throw new Error(`Failed to list backups: ${error.message}`);
} }
} }
/** /**
@ -295,7 +312,7 @@ class SQLiteBackup {
error: error.message, error: error.message,
timestamp: new Date().toISOString() timestamp: new Date().toISOString()
}; };
} }
} }
// Private methods // Private methods
@ -307,7 +324,7 @@ class SQLiteBackup {
const baseName = path.basename(this.databasePath, '.db'); const baseName = path.basename(this.databasePath, '.db');
const timestamp = includeTimestamp ? const timestamp = includeTimestamp ?
`-${new Date().toISOString().replace(/[:.]/g, '-')}` : ''; `-${new Date().toISOString().replace(/[:.]/g, '-')}` : '';
return `${baseName}-backup${timestamp}.db`; return `${baseName}-backup${timestamp}.db`;
} }
@ -390,6 +407,25 @@ class SQLiteBackup {
}); });
} }
async _validateUsingSQLite3Package(driver, databasePath) {
return new Promise((resolve, reject) => {
const db = new driver.Database(databasePath, driver.OPEN_READONLY, err => {
if (err) {
return resolve("fail");
}
});
db.get("PRAGMA integrity_check", function (err, res) {
console.log(res);
if (typeof res == 'undefined') {
resolve("fail");
} else {
resolve(res.integrity_check ?? "fail");
}
});
});
}
async _backupUsingCopy(backupPath) { async _backupUsingCopy(backupPath) {
fs.copyFileSync(this.databasePath, backupPath); fs.copyFileSync(this.databasePath, backupPath);
} }
@ -401,7 +437,7 @@ class SQLiteBackup {
async _calculateChecksum(filePath) { async _calculateChecksum(filePath) {
try { try {
const { stdout } = await execAsync(`shasum -a 256 "${filePath}"`); const {stdout} = await execAsync(`shasum -a 256 "${filePath}"`);
return stdout.split(' ')[0]; return stdout.split(' ')[0];
} catch (error) { } catch (error) {
return null; return null;
@ -434,7 +470,8 @@ class SQLiteBackup {
_matchesPattern(filename, pattern) { _matchesPattern(filename, pattern) {
// Simple pattern matching - could be enhanced with a proper glob library // Simple pattern matching - could be enhanced with a proper glob library
if (pattern === '*' || pattern === '*.*') return true; if (pattern === '*' || pattern === '*.*')
return true;
if (pattern.startsWith('*.')) { if (pattern.startsWith('*.')) {
const extension = pattern.slice(2); const extension = pattern.slice(2);
return filename.endsWith('.' + extension); return filename.endsWith('.' + extension);
@ -454,7 +491,8 @@ class BackupUtils {
*/ */
static formatSize(bytes) { static formatSize(bytes) {
const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
if (bytes === 0) return '0 B'; if (bytes === 0)
return '0 B';
const i = Math.floor(Math.log(bytes) / Math.log(1024)); const i = Math.floor(Math.log(bytes) / Math.log(1024));
return `${(bytes / Math.pow(1024, i)).toFixed(2)} ${sizes[i]}`; return `${(bytes / Math.pow(1024, i)).toFixed(2)} ${sizes[i]}`;
} }
@ -465,8 +503,10 @@ class BackupUtils {
* @returns {string} Formatted duration string * @returns {string} Formatted duration string
*/ */
static formatDuration(milliseconds) { static formatDuration(milliseconds) {
if (milliseconds < 1000) return `${milliseconds}ms`; if (milliseconds < 1000)
if (milliseconds < 60000) return `${(milliseconds / 1000).toFixed(2)}s`; return `${milliseconds}ms`;
if (milliseconds < 60000)
return `${(milliseconds / 1000).toFixed(2)}s`;
return `${(milliseconds / 60000).toFixed(2)}m`; return `${(milliseconds / 60000).toFixed(2)}m`;
} }
@ -483,7 +523,7 @@ class BackupUtils {
} }
const command = `sqlite3 "${databasePath}" "PRAGMA integrity_check;"`; const command = `sqlite3 "${databasePath}" "PRAGMA integrity_check;"`;
const { stdout } = await execAsync(command); const {stdout} = await execAsync(command);
return stdout.trim() === 'ok'; return stdout.trim() === 'ok';
} catch (error) { } catch (error) {
return false; return false;