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 CHANGED
@@ -102,12 +102,16 @@ await driver.checkpoint('TRUNCATE');
102
102
 
103
103
  | Method | Behaviour |
104
104
  |--------|-----------|
105
- | `checkIntegrity()` | Runs `PRAGMA integrity_check` and returns its status verbatim. |
106
- | `backup(destination)` | Online copy, verified before it resolves. Returns `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
- `backup()` creates parent directories as needed, then reopens the result and
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.0",
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.0"
20
+ "deepbase": "^3.8.1"
21
21
  },
22
22
  "devDependencies": {
23
23
  "mocha": "^10.8.2"
@@ -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 { backupTo, checkIntegrity, checkpoint, vacuum } from './maintenance.js';
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
- await this.connect();
128
- return checkIntegrity(this.db);
127
+ return checkIntegrityAt(this.fileName, this.busyTimeoutMs);
129
128
  }
130
129
 
131
130
  async backup(destination) {
132
- await this.connect();
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
- /** Runs `PRAGMA integrity_check` and returns its status: `'ok'` when the
39
- * database is sound, otherwise SQLite's description of the damage. */
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
- * Writes an online backup to `destination`, creating parent directories as
44
- * needed, then verifies the copy with `integrity_check`. Resolves with
45
- * `destination`, or rejects with code `DEEPBASE_SQLITE_BACKUP_CORRUPT` when
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
 
@@ -4,23 +4,37 @@ import * as pathModule from 'path';
4
4
 
5
5
  const CHECKPOINT_MODES = ['PASSIVE', 'FULL', 'RESTART', 'TRUNCATE'];
6
6
 
7
- export function checkIntegrity(db) {
7
+ function checkIntegrity(db) {
8
8
  return db.pragma('integrity_check', { simple: true });
9
9
  }
10
10
 
11
- export async function backupTo(db, destination) {
12
- fs.mkdirSync(pathModule.dirname(pathModule.resolve(destination)), { recursive: true });
11
+ function openReadonly(fileName, timeout) {
12
+ return new Database(fileName, {
13
+ readonly: true,
14
+ fileMustExist: true,
15
+ timeout,
16
+ });
17
+ }
13
18
 
14
- await db.backup(destination);
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
- const copy = new Database(destination, { readonly: true });
17
- let status;
28
+ export async function backupFrom(fileName, destination, timeout) {
29
+ const source = openReadonly(fileName, timeout);
18
30
  try {
19
- status = checkIntegrity(copy);
31
+ fs.mkdirSync(pathModule.dirname(pathModule.resolve(destination)), { recursive: true });
32
+ await source.backup(destination);
20
33
  } finally {
21
- copy.close();
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('opens the connection on demand', async function () {
775
- const driver = new SqliteDriver({ name: `lazy-${pragma}`, path: testDataPath, pragma });
776
- assert.strictEqual(await driver.checkIntegrity(), 'ok');
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 = driver.db.backup.bind(driver.db);
835
- driver.db.backup = async dest => {
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
- await assert.rejects(
847
- driver.backup(destination),
848
- error => error.code === 'DEEPBASE_SQLITE_BACKUP_CORRUPT',
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();