deepbase-sqlite 3.8.0 → 3.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -3
- package/package.json +2 -2
- package/src/SqliteDriver.js +3 -5
- package/src/index.d.ts +6 -6
- package/src/maintenance.js +22 -8
- package/test/test.js +34 -10
package/README.md
CHANGED
|
@@ -102,12 +102,16 @@ await driver.checkpoint('TRUNCATE');
|
|
|
102
102
|
|
|
103
103
|
| Method | Behaviour |
|
|
104
104
|
|--------|-----------|
|
|
105
|
-
| `checkIntegrity()` |
|
|
106
|
-
| `backup(destination)` |
|
|
105
|
+
| `checkIntegrity()` | Read-only `PRAGMA integrity_check`; returns its status verbatim. |
|
|
106
|
+
| `backup(destination)` | Read-only source, online copy, verified before it resolves. Returns `destination`. |
|
|
107
107
|
| `vacuum()` | Rebuilds the file to reclaim free pages. |
|
|
108
108
|
| `checkpoint(mode)` | `PASSIVE` (default), `FULL`, `RESTART` or `TRUNCATE`. Returns `{ busy, log, checkpointed }`. |
|
|
109
109
|
|
|
110
|
-
`
|
|
110
|
+
`checkIntegrity()` and `backup()` open a temporary read-only connection with
|
|
111
|
+
`fileMustExist: true`, then close it. They never create, migrate, or change
|
|
112
|
+
PRAGMAs on the source database; a missing or mistyped source path fails instead
|
|
113
|
+
of producing an empty database. `backup()` creates destination parent
|
|
114
|
+
directories only after opening the source, then reopens the result read-only and
|
|
111
115
|
verifies it with `integrity_check` — a backup nobody validated is not a backup.
|
|
112
116
|
It rejects with code `DEEPBASE_SQLITE_BACKUP_CORRUPT` when the copy fails to
|
|
113
117
|
verify, leaving the bad file on disk for inspection, so a restore routine must
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "deepbase-sqlite",
|
|
3
|
-
"version": "3.8.
|
|
3
|
+
"version": "3.8.1",
|
|
4
4
|
"description": "⚡ DeepBase SQLite - SQLite database driver",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.cjs",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"better-sqlite3": "^11.8.1"
|
|
18
18
|
},
|
|
19
19
|
"peerDependencies": {
|
|
20
|
-
"deepbase": "^3.8.
|
|
20
|
+
"deepbase": "^3.8.1"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"mocha": "^10.8.2"
|
package/src/SqliteDriver.js
CHANGED
|
@@ -4,7 +4,7 @@ import fs from 'fs';
|
|
|
4
4
|
import * as pathModule from 'path';
|
|
5
5
|
import { withBusyRetry } from './busy.js';
|
|
6
6
|
import { resolveSqliteConfig } from './config.js';
|
|
7
|
-
import {
|
|
7
|
+
import { backupFrom, checkIntegrityAt, checkpoint, vacuum } from './maintenance.js';
|
|
8
8
|
import { ensureSchema } from './schema.js';
|
|
9
9
|
|
|
10
10
|
export class SqliteDriver extends DeepBaseDriver {
|
|
@@ -124,13 +124,11 @@ export class SqliteDriver extends DeepBaseDriver {
|
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
async checkIntegrity() {
|
|
127
|
-
|
|
128
|
-
return checkIntegrity(this.db);
|
|
127
|
+
return checkIntegrityAt(this.fileName, this.busyTimeoutMs);
|
|
129
128
|
}
|
|
130
129
|
|
|
131
130
|
async backup(destination) {
|
|
132
|
-
|
|
133
|
-
return backupTo(this.db, destination);
|
|
131
|
+
return backupFrom(this.fileName, destination, this.busyTimeoutMs);
|
|
134
132
|
}
|
|
135
133
|
|
|
136
134
|
async vacuum() {
|
package/src/index.d.ts
CHANGED
|
@@ -35,15 +35,15 @@ export class SqliteDriver extends DeepBaseDriver {
|
|
|
35
35
|
busyTimeoutMs: number;
|
|
36
36
|
busyRetry: Required<SqliteBusyRetryOptions>;
|
|
37
37
|
|
|
38
|
-
/**
|
|
39
|
-
*
|
|
38
|
+
/** Opens the existing database read-only, runs `PRAGMA integrity_check`,
|
|
39
|
+
* then closes it. Does not connect or mutate the driver database. */
|
|
40
40
|
checkIntegrity(): Promise<string>;
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* `destination`, or rejects with code
|
|
46
|
-
* the copy does not verify.
|
|
43
|
+
* Opens the existing source database read-only, writes an online backup to
|
|
44
|
+
* `destination`, then verifies the copy with `integrity_check`. Resolves
|
|
45
|
+
* with `destination`, or rejects with code
|
|
46
|
+
* `DEEPBASE_SQLITE_BACKUP_CORRUPT` when the copy does not verify.
|
|
47
47
|
*/
|
|
48
48
|
backup(destination: string): Promise<string>;
|
|
49
49
|
|
package/src/maintenance.js
CHANGED
|
@@ -4,23 +4,37 @@ import * as pathModule from 'path';
|
|
|
4
4
|
|
|
5
5
|
const CHECKPOINT_MODES = ['PASSIVE', 'FULL', 'RESTART', 'TRUNCATE'];
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
function checkIntegrity(db) {
|
|
8
8
|
return db.pragma('integrity_check', { simple: true });
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
function openReadonly(fileName, timeout) {
|
|
12
|
+
return new Database(fileName, {
|
|
13
|
+
readonly: true,
|
|
14
|
+
fileMustExist: true,
|
|
15
|
+
timeout,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
13
18
|
|
|
14
|
-
|
|
19
|
+
export function checkIntegrityAt(fileName, timeout) {
|
|
20
|
+
const db = openReadonly(fileName, timeout);
|
|
21
|
+
try {
|
|
22
|
+
return checkIntegrity(db);
|
|
23
|
+
} finally {
|
|
24
|
+
db.close();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
15
27
|
|
|
16
|
-
|
|
17
|
-
|
|
28
|
+
export async function backupFrom(fileName, destination, timeout) {
|
|
29
|
+
const source = openReadonly(fileName, timeout);
|
|
18
30
|
try {
|
|
19
|
-
|
|
31
|
+
fs.mkdirSync(pathModule.dirname(pathModule.resolve(destination)), { recursive: true });
|
|
32
|
+
await source.backup(destination);
|
|
20
33
|
} finally {
|
|
21
|
-
|
|
34
|
+
source.close();
|
|
22
35
|
}
|
|
23
36
|
|
|
37
|
+
const status = checkIntegrityAt(destination, timeout);
|
|
24
38
|
if (status !== 'ok') {
|
|
25
39
|
const error = new Error(
|
|
26
40
|
`deepbase-sqlite: backup verification failed for ${destination}: ${status}. ` +
|
package/test/test.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import assert from 'assert';
|
|
2
|
+
import Database from 'better-sqlite3';
|
|
2
3
|
import fs from 'fs';
|
|
3
4
|
import path from 'path';
|
|
4
5
|
import { fileURLToPath } from 'url';
|
|
@@ -771,10 +772,29 @@ for (const pragma of PRAGMA_MODES) {
|
|
|
771
772
|
await copy.disconnect();
|
|
772
773
|
});
|
|
773
774
|
|
|
774
|
-
it('
|
|
775
|
-
|
|
776
|
-
|
|
775
|
+
it('uses a temporary readonly connection', async function () {
|
|
776
|
+
await db.set('key', 'value');
|
|
777
|
+
const driver = db.getDriver(0);
|
|
777
778
|
await driver.disconnect();
|
|
779
|
+
|
|
780
|
+
assert.strictEqual(await driver.checkIntegrity(), 'ok');
|
|
781
|
+
assert.strictEqual(driver._connected, false);
|
|
782
|
+
assert.strictEqual(driver.db, null);
|
|
783
|
+
});
|
|
784
|
+
|
|
785
|
+
it('does not create a missing source database or backup directory', async function () {
|
|
786
|
+
const driver = new SqliteDriver({
|
|
787
|
+
name: `missing-${pragma}`,
|
|
788
|
+
path: path.join(testDataPath, 'missing'),
|
|
789
|
+
pragma,
|
|
790
|
+
});
|
|
791
|
+
const destination = path.join(testDataPath, 'should-not-exist', 'backup.db');
|
|
792
|
+
|
|
793
|
+
await assert.rejects(driver.checkIntegrity());
|
|
794
|
+
await assert.rejects(driver.backup(destination));
|
|
795
|
+
|
|
796
|
+
assert.strictEqual(fs.existsSync(driver.fileName), false);
|
|
797
|
+
assert.strictEqual(fs.existsSync(path.dirname(destination)), false);
|
|
778
798
|
});
|
|
779
799
|
|
|
780
800
|
it('vacuums without losing data', async function () {
|
|
@@ -831,9 +851,9 @@ describe('SqliteDriver backup verification', function () {
|
|
|
831
851
|
}
|
|
832
852
|
|
|
833
853
|
const destination = path.join(corruptPath, 'backup.db');
|
|
834
|
-
const originalBackup =
|
|
835
|
-
|
|
836
|
-
const result = await originalBackup(dest);
|
|
854
|
+
const originalBackup = Database.prototype.backup;
|
|
855
|
+
Database.prototype.backup = async function (dest, ...args) {
|
|
856
|
+
const result = await originalBackup.call(this, dest, ...args);
|
|
837
857
|
// Scribble over every page but the header, so the file still opens as a
|
|
838
858
|
// database yet fails integrity_check.
|
|
839
859
|
const handle = fs.openSync(dest, 'r+');
|
|
@@ -843,10 +863,14 @@ describe('SqliteDriver backup verification', function () {
|
|
|
843
863
|
return result;
|
|
844
864
|
};
|
|
845
865
|
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
866
|
+
try {
|
|
867
|
+
await assert.rejects(
|
|
868
|
+
driver.backup(destination),
|
|
869
|
+
error => error.code === 'DEEPBASE_SQLITE_BACKUP_CORRUPT',
|
|
870
|
+
);
|
|
871
|
+
} finally {
|
|
872
|
+
Database.prototype.backup = originalBackup;
|
|
873
|
+
}
|
|
850
874
|
assert.ok(fs.existsSync(destination), 'corrupt copy is left in place for inspection');
|
|
851
875
|
|
|
852
876
|
await driver.disconnect();
|