@delali/sirannon-db 0.3.6-next.77 → 0.3.6-next.78

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.
Files changed (72) hide show
  1. package/dist/backup/index.d.ts +5 -5
  2. package/dist/backup/index.mjs +5 -3
  3. package/dist/backup-scheduler/index.d.ts +2 -2
  4. package/dist/backup-scheduler/index.mjs +2 -2
  5. package/dist/chunk-22TYGH7J.mjs +55 -0
  6. package/dist/{chunk-SB3MIFLG.mjs → chunk-2ZQOZFX7.mjs} +29 -6
  7. package/dist/{chunk-UIDQGPJY.mjs → chunk-4O7WYZHN.mjs} +38 -3
  8. package/dist/{chunk-ZFFUVYYE.mjs → chunk-5DKWHT6K.mjs} +109 -1
  9. package/dist/chunk-5O4J6LQQ.mjs +35 -0
  10. package/dist/{chunk-CZL4D3BM.mjs → chunk-6CABU54O.mjs} +2 -2
  11. package/dist/{chunk-JAQ2I4OT.mjs → chunk-A5ULJVK7.mjs} +11 -11
  12. package/dist/{chunk-RLBKZZ24.mjs → chunk-AC7TKWNH.mjs} +1 -0
  13. package/dist/chunk-BKO4SCEC.mjs +99 -0
  14. package/dist/{chunk-KOTBP4AW.mjs → chunk-C7E4H6UN.mjs} +24 -4
  15. package/dist/{chunk-G4AG3ESO.mjs → chunk-FBA7P4ZA.mjs} +114 -149
  16. package/dist/{chunk-DKML5425.mjs → chunk-GBVB6WSC.mjs} +2 -2
  17. package/dist/chunk-I4JY5QPA.mjs +51 -0
  18. package/dist/{chunk-L6EX6K27.mjs → chunk-MO4APBYG.mjs} +54 -13
  19. package/dist/{chunk-RPWBYASV.mjs → chunk-MTOQ6QIS.mjs} +1 -1
  20. package/dist/chunk-MX77PGEN.mjs +60 -0
  21. package/dist/{chunk-BTUD6XQO.mjs → chunk-N3MHZV3B.mjs} +11 -29
  22. package/dist/{chunk-UHUDPQFI.mjs → chunk-OJXV4L3N.mjs} +1 -3
  23. package/dist/chunk-QF7WF7RJ.mjs +34 -0
  24. package/dist/chunk-RLBV4YTB.mjs +22 -0
  25. package/dist/{chunk-ISDT4RBD.mjs → chunk-ULUZUBON.mjs} +1 -1
  26. package/dist/{chunk-AP4367HK.mjs → chunk-VGRAAQX3.mjs} +4 -4
  27. package/dist/chunk-W74HFQFD.mjs +153 -0
  28. package/dist/chunk-Y4SP7CRF.mjs +9 -0
  29. package/dist/{chunk-JFVEGN7J.mjs → chunk-ZAUHAJ4G.mjs} +2 -1
  30. package/dist/{chunk-DDIHBUEN.mjs → chunk-ZWW53O5P.mjs} +19 -7
  31. package/dist/client/index.d.ts +8 -8
  32. package/dist/client/topology.d.ts +4 -4
  33. package/dist/{client-base-Doaqwrx3.d.ts → client-base-DJZbSY55.d.ts} +2 -2
  34. package/dist/{client-streams-MKPE4MXW.mjs → client-streams-F7IG3PTU.mjs} +13 -16
  35. package/dist/core/index.d.ts +48 -12
  36. package/dist/core/index.mjs +1812 -107
  37. package/dist/core/writer-worker.mjs +28 -4
  38. package/dist/{database-fz-8CJBP.d.ts → database-BZ96ZzAx.d.ts} +411 -184
  39. package/dist/{database-backup-BwTNiBm2.d.ts → database-backup-BhM8yJ1A.d.ts} +32 -2
  40. package/dist/driver/better-sqlite3.d.ts +11 -1
  41. package/dist/driver/better-sqlite3.mjs +212 -23
  42. package/dist/driver/bun.d.ts +1 -1
  43. package/dist/driver/bun.mjs +15 -22
  44. package/dist/driver/expo.d.ts +1 -1
  45. package/dist/driver/expo.mjs +185 -16
  46. package/dist/driver/node.d.ts +1 -1
  47. package/dist/driver/node.mjs +15 -62
  48. package/dist/driver/wa-sqlite.d.ts +1 -1
  49. package/dist/driver/wa-sqlite.mjs +14 -153
  50. package/dist/{change-tracker-Dpo99EI-.d.ts → errors-DLMAGQFb.d.ts} +200 -2
  51. package/dist/file-migrations/index.d.ts +1 -1
  52. package/dist/peer-refusal-RR37UOLV.mjs +1 -0
  53. package/dist/{protocol-DZSYSv0S.d.ts → protocol-DE10taas.d.ts} +1 -1
  54. package/dist/replication/coordinator/etcd.mjs +1 -1
  55. package/dist/replication/index.d.ts +7 -7
  56. package/dist/replication/index.mjs +66 -9
  57. package/dist/{restore-options-CcMukOLO.d.ts → restore-options-B6HV_5XT.d.ts} +6 -1
  58. package/dist/server/index.d.ts +10 -10
  59. package/dist/server/index.mjs +506 -350
  60. package/dist/{server-options-B9CsAZp5.d.ts → server-options-Bhdlvehe.d.ts} +14 -1
  61. package/dist/{server-streams-SBYCHWWM.mjs → server-streams-Q2AZAW3N.mjs} +6 -30
  62. package/dist/{sirannon-D0CjDULa.d.ts → sirannon-Ba1o2HUQ.d.ts} +5 -5
  63. package/dist/transport/grpc.d.ts +15 -4
  64. package/dist/transport/grpc.mjs +34 -37
  65. package/dist/transport/memory.d.ts +4 -3
  66. package/dist/transport/memory.mjs +8 -26
  67. package/dist/{types-DTmi7pXp.d.ts → types-BWyv_N18.d.ts} +189 -1
  68. package/dist/{types-BlZ9GG1a.d.ts → types-CDnlkVRK.d.ts} +8 -2
  69. package/package.json +17 -9
  70. package/dist/chunk-DBN57KVR.mjs +0 -171
  71. package/dist/chunk-W67A4H2E.mjs +0 -23
  72. package/dist/chunk-YW7IKQ7T.mjs +0 -62
@@ -1,4 +1,4 @@
1
- import { i as BackupChainBase, j as BackupChainChange, h as BackupChain, l as BackupChainRecord, B as BackupDestination, J as SQLiteConnection, Q as DriverCapabilities, U as BackupEngine, q as BackupFileReport, z as BackupToDestinationOptions, g as BackupRunReport, P as BackupScheduleOptions, o as BackupCycleOptions, p as BackupCycleStatus, C as BackupVerifyResult } from './types-DTmi7pXp.js';
1
+ import { i as BackupChainBase, j as BackupChainChange, h as BackupChain, l as BackupChainRecord, B as BackupDestination, L as SQLiteConnection, V as DriverCapabilities, W as BackupEngine, q as BackupFileReport, z as BackupToDestinationOptions, g as BackupRunReport, R as BackupScheduleOptions, o as BackupCycleOptions, p as BackupCycleStatus, C as BackupVerifyResult } from './types-BWyv_N18.js';
2
2
 
3
3
  /**
4
4
  * The backup operations that one runtime supports, so that a caller can check
@@ -110,11 +110,14 @@ declare class DatabaseBackupController {
110
110
  private readonly databaseId;
111
111
  private readonly sourcePath;
112
112
  private readonly engine?;
113
+ private readonly keyId;
113
114
  private readonly cancellers;
114
115
  private cycle;
115
116
  private cycleOptions;
116
117
  private cycleStarted;
117
- constructor(runExclusive: RunExclusive, acquireWriter: () => SQLiteConnection, driverCapabilities: DriverCapabilities, databaseId: string, sourcePath: string, engine?: BackupEngine | undefined);
118
+ private readonly copies;
119
+ private readonly copyWriter;
120
+ constructor(runExclusive: RunExclusive, acquireWriter: () => SQLiteConnection, driverCapabilities: DriverCapabilities, databaseId: string, sourcePath: string, engine?: BackupEngine | undefined, keyId?: () => string | undefined);
118
121
  private require;
119
122
  private requireCycle;
120
123
  capabilities(): BackupCapabilities;
@@ -137,6 +140,33 @@ declare class DatabaseBackupController {
137
140
  piecesSafeToDelete(options?: BackupSafeToDeleteOptions): Promise<BackupChainRecord[]>;
138
141
  /** Stops the backup cycle after one last capture, which sends the writes made since the previous turn to the destination. */
139
142
  stopCycle(): Promise<void>;
143
+ /**
144
+ * Stops the backup cycle after one last capture, ahead of a key change that replaces the file.
145
+ *
146
+ * @returns `true` when a cycle was running, which {@link DatabaseBackupController.resumeCycle} starts again.
147
+ */
148
+ pauseCycle(): Promise<boolean>;
149
+ /**
150
+ * Stops new backup copies from starting ahead of a key change that replaces the file, and waits until SQLite has
151
+ * stopped writing every copy that started before, so that no copy reads pages from both files.
152
+ */
153
+ pauseCopies(): Promise<void>;
154
+ /** Lets backup copies start again once a key change has replaced the file or given up. */
155
+ resumeCopies(): void;
156
+ /**
157
+ * Drops the chain that the paused backup cycle extended, so that the cycle starts a new chain with a full copy
158
+ * whenever it starts again, including in a process that starts after a crash during the key change.
159
+ */
160
+ forgetPausedChain(): Promise<void>;
161
+ /**
162
+ * Starts the backup cycle again after a key change, on a new chain whose first piece is a full copy when the file
163
+ * now holds ciphertext under another key.
164
+ *
165
+ * @param options - Whether the cycle starts a new chain.
166
+ */
167
+ resumeCycle(options: {
168
+ newChain: boolean;
169
+ }): Promise<void>;
140
170
  cancelAll(): void;
141
171
  }
142
172
 
@@ -1,4 +1,4 @@
1
- import { S as SQLiteDriver } from '../types-DTmi7pXp.js';
1
+ import { S as SQLiteDriver } from '../types-BWyv_N18.js';
2
2
  import '../query-types-5B02ixH0.js';
3
3
 
4
4
  /**
@@ -37,6 +37,16 @@ interface BetterSqlite3Options {
37
37
  * needs Sirannon's SQLite, such as `rowCounts`, is set.
38
38
  */
39
39
  sqliteBindingPath?: string;
40
+ /**
41
+ * Set it to `true` to open every database through `@delali/sirannon-sqlite`,
42
+ * which contains better-sqlite3 built against SQLite3 Multiple Ciphers, so
43
+ * that a database opened with the `encryption` option keeps every page as
44
+ * AEGIS-256 ciphertext and
45
+ * `changeEncryption` can encrypt, decrypt, or rekey a database. With it unset,
46
+ * the driver keeps better-sqlite3's own SQLite and fails an open with a key
47
+ * with `ENCRYPTION_UNSUPPORTED`.
48
+ */
49
+ encryption?: boolean;
40
50
  }
41
51
  /**
42
52
  * Returns a driver that opens SQLite databases on Node through the `better-sqlite3` native module.
@@ -1,24 +1,63 @@
1
+ import { reportEncryptedPageFailures } from '../chunk-BKO4SCEC.mjs';
1
2
  import { optionalDependencyError } from '../chunk-WOMADPM4.mjs';
2
- import { nodeResolveExtensionPath, asyncLocalWriterContext, nodeBackupEngine, nodeStreamingSupport, WriterWorker, createStatementCache, loadThroughRuntime, bindableParams, narrowSafeBigInt, narrowRowIntegers, narrowRowsIntegers } from '../chunk-G4AG3ESO.mjs';
3
+ import { nodeResolveExtensionPath, nodeBackupEngine, nodeStreamingSupport, WriterWorker, createStatementCache } from '../chunk-FBA7P4ZA.mjs';
3
4
  import '../chunk-DX4WGJL7.mjs';
4
- import '../chunk-BTUD6XQO.mjs';
5
- import { defineDriver } from '../chunk-JAQ2I4OT.mjs';
6
- import '../chunk-W67A4H2E.mjs';
5
+ import '../chunk-N3MHZV3B.mjs';
6
+ import { asyncLocalWriterContext, loadThroughRuntime } from '../chunk-RLBV4YTB.mjs';
7
+ import { bindableParams, narrowSafeBigInt, narrowRowIntegers, narrowRowsIntegers } from '../chunk-MX77PGEN.mjs';
8
+ import '../chunk-Y4SP7CRF.mjs';
9
+ import '../chunk-I4JY5QPA.mjs';
7
10
  import '../chunk-K43WJORM.mjs';
11
+ import { defineDriver } from '../chunk-A5ULJVK7.mjs';
8
12
  import '../chunk-75IBRE73.mjs';
9
13
  import { withRowCounts } from '../chunk-E2GRX4ZD.mjs';
10
- import '../chunk-UHUDPQFI.mjs';
14
+ import '../chunk-OJXV4L3N.mjs';
11
15
  import '../chunk-QGVRRKB2.mjs';
12
16
  import '../chunk-AHCMO4YL.mjs';
13
17
  import '../chunk-BUWJH3D3.mjs';
14
- import { synchronousPragmaValue } from '../chunk-YCMB6GR4.mjs';
15
- import '../chunk-CZL4D3BM.mjs';
18
+ import '../chunk-6CABU54O.mjs';
16
19
  import '../chunk-BGJ5JVYL.mjs';
17
20
  import '../chunk-HO2XTXVX.mjs';
21
+ import { synchronousPragmaValue } from '../chunk-YCMB6GR4.mjs';
22
+ import { EncryptionError, keyRejected } from '../chunk-5O4J6LQQ.mjs';
18
23
  import { SirannonError, BackupError } from '../chunk-KINDFBQP.mjs';
24
+ import { rename, rm, open } from 'fs/promises';
19
25
  import { createRequire } from 'module';
20
26
  import { resolve } from 'path';
21
27
 
28
+ var NO_SUCH_FILE_CODES = /* @__PURE__ */ new Set(["ENOENT", "ENAMETOOLONG"]);
29
+ function missingFile(err) {
30
+ return typeof err === "object" && err !== null && NO_SUCH_FILE_CODES.has(err.code);
31
+ }
32
+ var nodeDatabaseFiles = {
33
+ async readStart(path, length) {
34
+ let handle;
35
+ try {
36
+ handle = await open(path, "r");
37
+ } catch (err) {
38
+ if (missingFile(err)) return null;
39
+ throw err;
40
+ }
41
+ try {
42
+ const buffer = new Uint8Array(length);
43
+ const { bytesRead } = await handle.read(buffer, 0, length, 0);
44
+ return buffer.subarray(0, bytesRead);
45
+ } finally {
46
+ await handle.close();
47
+ }
48
+ },
49
+ async remove(path) {
50
+ try {
51
+ await rm(path, { force: true });
52
+ } catch (err) {
53
+ if (!missingFile(err)) throw err;
54
+ }
55
+ },
56
+ async rename(from, to) {
57
+ await rename(from, to);
58
+ }
59
+ };
60
+
22
61
  // src/drivers/better-sqlite3/copy.ts
23
62
  var OPEN_TRANSACTION_MESSAGE = "A stepped copy cannot start while a transaction is open on the same connection, because SQLite copies no pages and reports success";
24
63
  async function copyDatabaseWithBetterSqlite3(db, request) {
@@ -32,6 +71,107 @@ async function copyDatabaseWithBetterSqlite3(db, request) {
32
71
  return final;
33
72
  }
34
73
 
74
+ // src/drivers/better-sqlite3/encryption.ts
75
+ var AEGIS_CIPHER_NAME = "aegis";
76
+ var AEGIS_256_ALGORITHM_NAME = "aegis-256";
77
+ var SQLITE_HEADER = "SQLite format 3\0";
78
+ var SQLITE_HEADER_BYTES = SQLITE_HEADER.length;
79
+ var ENCRYPTION_FEATURE = {
80
+ option: "encryption",
81
+ code: "ENCRYPTION_UNSUPPORTED",
82
+ description: "encryption engine",
83
+ presentIn: (addon) => typeof addon.Database?.prototype?.key === "function"
84
+ };
85
+ async function classifyDatabaseFile(path) {
86
+ if (path === "" || path === ":memory:") return "memory";
87
+ if (path.startsWith("file:")) return "unknown";
88
+ const start = await nodeDatabaseFiles.readStart(path, SQLITE_HEADER_BYTES);
89
+ if (start === null || start.length === 0) return "new";
90
+ if (start.length === SQLITE_HEADER_BYTES && String.fromCharCode(...start) === SQLITE_HEADER) return "plaintext";
91
+ return "encrypted";
92
+ }
93
+ function assertKeyFitsFile(path, kind, keyed) {
94
+ if (keyed && kind === "memory") {
95
+ throw new EncryptionError("An in-memory database cannot be encrypted", "ENCRYPTION_UNSUPPORTED");
96
+ }
97
+ if (keyed && kind === "plaintext") {
98
+ throw new EncryptionError(
99
+ `The database at ${path} is plaintext, so it opens without a key. Encrypt it through changeEncryption`,
100
+ "DATABASE_NOT_ENCRYPTED"
101
+ );
102
+ }
103
+ if (!keyed && kind === "encrypted") {
104
+ throw new EncryptionError(
105
+ `The database at ${path} is encrypted, so it opens only with the encryption option and its key`,
106
+ "ENCRYPTION_KEY_MISSING"
107
+ );
108
+ }
109
+ }
110
+ function sqliteCode(err) {
111
+ if (typeof err !== "object" || err === null) return void 0;
112
+ const { code } = err;
113
+ return typeof code === "string" ? code : void 0;
114
+ }
115
+ function probeDatabaseFile(open2) {
116
+ let probe;
117
+ try {
118
+ probe = open2();
119
+ } catch (err) {
120
+ if (sqliteCode(err) === "SQLITE_CANTOPEN") return "new";
121
+ throw err;
122
+ }
123
+ try {
124
+ const main = probe.prepare("SELECT file FROM pragma_database_list WHERE name = 'main'").get();
125
+ if (main?.file === "") return "memory";
126
+ probe.prepare("SELECT count(*) FROM sqlite_schema").get();
127
+ return Number(probe.pragma("page_count", { simple: true })) === 0 ? "new" : "plaintext";
128
+ } catch (err) {
129
+ if (sqliteCode(err) === "SQLITE_NOTADB") return "encrypted";
130
+ throw err;
131
+ } finally {
132
+ probe.close();
133
+ }
134
+ }
135
+ function readSchema(db, path) {
136
+ try {
137
+ db.prepare("SELECT count(*) FROM sqlite_schema").get();
138
+ } catch (err) {
139
+ const code = sqliteCode(err);
140
+ if (code === "SQLITE_NOTADB") throw keyRejected(path, err);
141
+ if (code?.startsWith("SQLITE_CORRUPT")) {
142
+ throw new EncryptionError(
143
+ `A schema page of the encrypted database at ${path} failed its authentication check`,
144
+ "ENCRYPTED_PAGE_CORRUPT",
145
+ err
146
+ );
147
+ }
148
+ throw err;
149
+ }
150
+ }
151
+ function engineVersion(db) {
152
+ try {
153
+ const row = db.prepare("SELECT sqlite3mc_version() AS version").get();
154
+ return row?.version;
155
+ } catch {
156
+ return void 0;
157
+ }
158
+ }
159
+ function keyConnection(db, path, rawKey) {
160
+ db.key(rawKey);
161
+ db.pragma("temp_store = MEMORY");
162
+ readSchema(db, path);
163
+ const version = engineVersion(db);
164
+ if (typeof version !== "string" || version.length === 0) {
165
+ throw new EncryptionError(
166
+ "The SQLite build reports no encryption engine version, so it cannot encrypt",
167
+ "ENCRYPTION_UNSUPPORTED"
168
+ );
169
+ }
170
+ if (db.pragma("cipher", { simple: true }) !== AEGIS_CIPHER_NAME || db.pragma("algorithm", { simple: true }) !== AEGIS_256_ALGORITHM_NAME) {
171
+ throw new EncryptionError("The SQLite build encrypts with a cipher other than AEGIS-256", "ENCRYPTION_UNSUPPORTED");
172
+ }
173
+ }
174
+
35
175
  // src/drivers/better-sqlite3/row-counting.ts
36
176
  var ROW_COUNTS_FEATURE = {
37
177
  option: "rowCounts",
@@ -210,7 +350,7 @@ function plainStatement(stmt) {
210
350
  }
211
351
  };
212
352
  }
213
- function createConnection(db, taker) {
353
+ function createConnection(db, taker, encrypting) {
214
354
  const batchStatementFor = createStatementCache((sql) => db.prepare(sql));
215
355
  const conn = {
216
356
  async exec(sql) {
@@ -266,22 +406,58 @@ function createConnection(db, taker) {
266
406
  db.close();
267
407
  }
268
408
  };
409
+ if (encrypting) {
410
+ conn.startChangeCapture = async () => {
411
+ encrypting.startChangeCapture();
412
+ };
413
+ conn.takeCapturedChanges = async () => encrypting.takeCapturedChanges();
414
+ conn.stopChangeCapture = async () => {
415
+ encrypting.stopChangeCapture();
416
+ };
417
+ conn.applyCapturedChanges = async (changes) => {
418
+ encrypting.applyChanges(changes);
419
+ };
420
+ }
269
421
  return conn;
270
422
  }
271
- async function openDatabase(path, readonly, driverOptions) {
272
- if (driverOptions?.rowCounts !== true) {
423
+ function sirannonSqliteFeatures(driverOptions) {
424
+ const features = [];
425
+ if (driverOptions?.encryption === true) features.push(ENCRYPTION_FEATURE);
426
+ if (driverOptions?.rowCounts === true) features.push(ROW_COUNTS_FEATURE);
427
+ return features;
428
+ }
429
+ async function openDatabase(path, readonly, driverOptions, rawKey) {
430
+ const [first, ...rest] = sirannonSqliteFeatures(driverOptions);
431
+ if (first === void 0) {
273
432
  const Database = (await import('better-sqlite3')).default;
274
- return { db: new Database(path, { readonly }), taker: null };
433
+ return { db: new Database(path, { readonly }), taker: null, encrypting: null };
275
434
  }
276
- const sqlite = await loadSirannonSqlite([ROW_COUNTS_FEATURE], driverOptions.sqliteBindingPath);
277
- return { db: new sqlite.Database(path, { readonly, nativeBinding: sqlite.bindingPath }), taker: new RowCountTaker() };
435
+ const sqlite = await loadSirannonSqlite([first, ...rest], driverOptions?.sqliteBindingPath);
436
+ const openConnection = () => new sqlite.Database(path, { readonly, nativeBinding: sqlite.bindingPath });
437
+ if (driverOptions?.encryption === true) {
438
+ const kind = await classifyDatabaseFile(path);
439
+ const settled = kind === "unknown" ? probeDatabaseFile(() => openConnection()) : kind;
440
+ assertKeyFitsFile(path, settled, rawKey !== void 0);
441
+ }
442
+ const db = openConnection();
443
+ const encrypting = driverOptions?.encryption === true ? db : null;
444
+ try {
445
+ if (rawKey !== void 0 && encrypting) keyConnection(encrypting, path, rawKey);
446
+ else db.pragma("temp_store = FILE");
447
+ } catch (err) {
448
+ db.close();
449
+ throw err;
450
+ }
451
+ return { db, taker: driverOptions?.rowCounts === true ? new RowCountTaker() : null, encrypting };
278
452
  }
279
453
  function betterSqlite3(driverOptions) {
280
454
  const workerEntry = { specifier: import.meta.url, exportName: "betterSqlite3", config: driverOptions };
281
455
  const rowCounts = driverOptions?.rowCounts === true;
456
+ const encryption = driverOptions?.encryption === true;
282
457
  return defineDriver({
283
- capabilities: { multipleConnections: true, extensions: true, steppedCopy: true, rowCounts },
458
+ capabilities: { multipleConnections: true, extensions: true, steppedCopy: true, rowCounts, encryption },
284
459
  worker: workerEntry,
460
+ files: nodeDatabaseFiles,
285
461
  startWriterHost: async (path, options, hostOptions) => {
286
462
  const host = await WriterWorker.start(workerEntry, path, options, { ...hostOptions, countsRows: rowCounts });
287
463
  return host.connection;
@@ -296,16 +472,29 @@ function betterSqlite3(driverOptions) {
296
472
  ),
297
473
  resolveExtensionPath: nodeResolveExtensionPath,
298
474
  async open(path, options) {
299
- const { db, taker } = await openDatabase(path, options?.readonly ?? false, driverOptions);
300
- db.defaultSafeIntegers(true);
301
- if (options?.walMode !== false) db.pragma("journal_mode = WAL");
302
- db.pragma(`synchronous = ${synchronousPragmaValue(options?.synchronous)}`);
303
- db.pragma("foreign_keys = ON");
304
- db.pragma(`busy_timeout = ${driverOptions?.busyTimeout ?? 5e3}`);
305
- if (options?.walAutoCheckpoint !== void 0) {
306
- db.pragma(`wal_autocheckpoint = ${Math.trunc(options.walAutoCheckpoint)}`);
475
+ const rawKey = options?.encryption?.key;
476
+ if (rawKey !== void 0 && !encryption) {
477
+ throw new EncryptionError(
478
+ "The better-sqlite3 driver opens encrypted databases only when you create it with betterSqlite3({ encryption: true })",
479
+ "ENCRYPTION_UNSUPPORTED"
480
+ );
481
+ }
482
+ const { db, taker, encrypting } = await openDatabase(path, options?.readonly ?? false, driverOptions, rawKey);
483
+ try {
484
+ db.defaultSafeIntegers(true);
485
+ if (options?.walMode !== false) db.pragma("journal_mode = WAL");
486
+ db.pragma(`synchronous = ${synchronousPragmaValue(options?.synchronous)}`);
487
+ db.pragma("foreign_keys = ON");
488
+ db.pragma(`busy_timeout = ${driverOptions?.busyTimeout ?? 5e3}`);
489
+ if (options?.walAutoCheckpoint !== void 0) {
490
+ db.pragma(`wal_autocheckpoint = ${Math.trunc(options.walAutoCheckpoint)}`);
491
+ }
492
+ } catch (err) {
493
+ db.close();
494
+ throw err;
307
495
  }
308
- return createConnection(db, taker);
496
+ const connection = createConnection(db, taker, encrypting);
497
+ return rawKey === void 0 ? connection : reportEncryptedPageFailures(connection);
309
498
  }
310
499
  });
311
500
  }
@@ -1,4 +1,4 @@
1
- import { S as SQLiteDriver } from '../types-DTmi7pXp.js';
1
+ import { S as SQLiteDriver } from '../types-BWyv_N18.js';
2
2
  import '../query-types-5B02ixH0.js';
3
3
 
4
4
  /**
@@ -1,32 +1,25 @@
1
- import { defineDriver, synchronousPragmaValue, assertHoldsStatement, bindableParams, narrowSafeBigInt, narrowRowIntegers, narrowRowsIntegers, SirannonError, ExtensionError } from '../chunk-DBN57KVR.mjs';
1
+ import { assertHoldsStatement } from '../chunk-22TYGH7J.mjs';
2
+ import { asyncLocalWriterContext, loadThroughRuntime } from '../chunk-RLBV4YTB.mjs';
3
+ import { bindableParams, narrowSafeBigInt, narrowRowIntegers, narrowRowsIntegers } from '../chunk-MX77PGEN.mjs';
4
+ import { defineDriver } from '../chunk-A5ULJVK7.mjs';
5
+ import { synchronousPragmaValue } from '../chunk-YCMB6GR4.mjs';
6
+ import { refuseEncryptedOpen } from '../chunk-5O4J6LQQ.mjs';
7
+ import '../chunk-KINDFBQP.mjs';
2
8
  import { resolve } from 'path';
3
- import { AsyncLocalStorage } from 'async_hooks';
4
9
 
5
- // src/core/driver/extension.ts
6
- async function loadThroughRuntime(extensionPath, load) {
7
- try {
8
- load();
9
- } catch (err) {
10
- if (err instanceof SirannonError) throw err;
11
- throw new ExtensionError(extensionPath, err instanceof Error ? err.message : String(err));
12
- }
13
- }
14
- function asyncLocalWriterContext() {
15
- const held = new AsyncLocalStorage();
16
- return {
17
- run: (operation) => held.run(true, operation),
18
- isActive: () => held.getStore() === true,
19
- exit: (operation) => held.exit(operation)
20
- };
21
- }
22
-
23
- // src/drivers/bun/index.ts
24
10
  function bunSqlite(driverOptions) {
25
11
  return defineDriver({
26
- capabilities: { multipleConnections: true, extensions: true, steppedCopy: false, rowCounts: false },
12
+ capabilities: {
13
+ multipleConnections: true,
14
+ extensions: true,
15
+ steppedCopy: false,
16
+ rowCounts: false,
17
+ encryption: false
18
+ },
27
19
  resolveExtensionPath: (extensionPath) => resolve(extensionPath),
28
20
  createWriterContext: asyncLocalWriterContext,
29
21
  async open(path, options) {
22
+ refuseEncryptedOpen("bun:sqlite", options);
30
23
  const { Database } = await import('bun:sqlite');
31
24
  const db = new Database(path, { readonly: options?.readonly ?? false, safeIntegers: true, strict: true });
32
25
  if (options?.walMode !== false) {
@@ -1,4 +1,4 @@
1
- import { S as SQLiteDriver } from '../types-DTmi7pXp.js';
1
+ import { S as SQLiteDriver } from '../types-BWyv_N18.js';
2
2
  import '../query-types-5B02ixH0.js';
3
3
 
4
4
  /**
@@ -1,4 +1,11 @@
1
- import { defineDriver, synchronousPragmaValue, ExtensionError, assertHoldsStatement, isNamedParams } from '../chunk-DBN57KVR.mjs';
1
+ import { reportEncryptedPageFailures } from '../chunk-BKO4SCEC.mjs';
2
+ import { assertHoldsStatement } from '../chunk-22TYGH7J.mjs';
3
+ import { isNamedParams } from '../chunk-MX77PGEN.mjs';
4
+ import { defineDriver } from '../chunk-A5ULJVK7.mjs';
5
+ import '../chunk-E2GRX4ZD.mjs';
6
+ import { synchronousPragmaValue } from '../chunk-YCMB6GR4.mjs';
7
+ import { EncryptionError, keyRejected } from '../chunk-5O4J6LQQ.mjs';
8
+ import { ExtensionError } from '../chunk-KINDFBQP.mjs';
2
9
 
3
10
  // src/drivers/expo/bind.ts
4
11
  var LARGEST_SAFE_INTEGER = BigInt(Number.MAX_SAFE_INTEGER);
@@ -29,20 +36,179 @@ function expoBindParams(params) {
29
36
  return params.map(expoValue);
30
37
  }
31
38
 
39
+ // src/drivers/expo/encryption.ts
40
+ var NOT_A_DATABASE = /file is not a database/i;
41
+ var HEX_DIGITS = "0123456789abcdef";
42
+ var EXPO_SQLITE_ENCRYPTION_FLOOR = "15.0.0";
43
+ var SQLCIPHER_FLOOR = [4, 6, 0];
44
+ function meetsSqlcipherFloor(cipherVersion) {
45
+ const parts = /^(\d+)\.(\d+)\.(\d+)/.exec(cipherVersion);
46
+ if (parts === null) return false;
47
+ const found = [Number(parts[1]), Number(parts[2]), Number(parts[3])];
48
+ for (let at = 0; at < SQLCIPHER_FLOOR.length; at++) {
49
+ const floor = SQLCIPHER_FLOOR[at] ?? 0;
50
+ const value = found[at] ?? 0;
51
+ if (value !== floor) return value > floor;
52
+ }
53
+ return true;
54
+ }
55
+ function errorMessage(err) {
56
+ return err instanceof Error ? err.message : String(err);
57
+ }
58
+ function rawKeyLiteral(rawKey) {
59
+ let hex = "";
60
+ for (const byte of rawKey) hex += HEX_DIGITS[byte >> 4] + HEX_DIGITS[byte & 15];
61
+ return `"x'${hex}'"`;
62
+ }
63
+ async function readUnkeyedExpoSchema(handle, path) {
64
+ try {
65
+ await handle.getFirstAsync("SELECT count(*) FROM sqlite_schema");
66
+ } catch (err) {
67
+ if (!NOT_A_DATABASE.test(errorMessage(err))) throw err;
68
+ throw new EncryptionError(
69
+ `The database ${path} is encrypted, so it opens only with the encryption option and its key`,
70
+ "ENCRYPTION_KEY_MISSING",
71
+ err
72
+ );
73
+ }
74
+ }
75
+ async function probeExpoDatabase(open) {
76
+ const probe = await open();
77
+ try {
78
+ await probe.getFirstAsync("SELECT count(*) FROM sqlite_schema");
79
+ const pages = await probe.getFirstAsync("PRAGMA page_count");
80
+ return (pages?.page_count ?? 0) > 0 ? "plaintext" : "new";
81
+ } catch (err) {
82
+ if (NOT_A_DATABASE.test(errorMessage(err))) return "encrypted";
83
+ throw err;
84
+ } finally {
85
+ await probe.closeAsync();
86
+ }
87
+ }
88
+ async function keyExpoConnection(handle, path, rawKey) {
89
+ await handle.execAsync(`PRAGMA key = ${rawKeyLiteral(rawKey)}`);
90
+ await handle.execAsync("PRAGMA temp_store = MEMORY");
91
+ const version = await handle.getFirstAsync("PRAGMA cipher_version");
92
+ const cipherVersion = typeof version?.cipher_version === "string" ? version.cipher_version : "";
93
+ if (!meetsSqlcipherFloor(cipherVersion)) {
94
+ throw new EncryptionError(
95
+ `Encryption on the Expo driver needs expo-sqlite ${EXPO_SQLITE_ENCRYPTION_FLOOR} or later, built with the useSQLCipher option of its config plugin, which carries SQLCipher ${SQLCIPHER_FLOOR.join(".")} or later. This app reports ${cipherVersion === "" ? "no SQLCipher" : `SQLCipher ${cipherVersion}`}`,
96
+ "ENCRYPTION_UNSUPPORTED"
97
+ );
98
+ }
99
+ try {
100
+ await handle.getFirstAsync("SELECT count(*) FROM sqlite_schema");
101
+ } catch (err) {
102
+ if (NOT_A_DATABASE.test(errorMessage(err))) throw keyRejected(path, err);
103
+ throw err;
104
+ }
105
+ }
106
+
107
+ // src/drivers/expo/files.ts
108
+ var EXPO_FILE_SYSTEM_FLOOR = "19.0.0";
109
+ var BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
110
+ var BASE64_VALUES = new Map([...BASE64_ALPHABET].map((char, value) => [char, value]));
111
+ var modules = null;
112
+ async function loadLegacyFileSystem() {
113
+ try {
114
+ return await import('expo-file-system/legacy');
115
+ } catch {
116
+ return null;
117
+ }
118
+ }
119
+ function loadModules() {
120
+ modules ??= (async () => {
121
+ const [fs, sqlite] = await Promise.all([loadLegacyFileSystem(), import('expo-sqlite')]);
122
+ if (fs === null) {
123
+ throw new EncryptionError(
124
+ `A key change on the Expo driver needs expo-file-system ${EXPO_FILE_SYSTEM_FLOOR} or later, whose expo-file-system/legacy entry reads, deletes, and moves the database files`,
125
+ "ENCRYPTION_UNSUPPORTED"
126
+ );
127
+ }
128
+ return { fs, directory: String(sqlite.defaultDatabaseDirectory) };
129
+ })();
130
+ return modules;
131
+ }
132
+ function bytesOfBase64(text) {
133
+ const bytes = [];
134
+ let buffer = 0;
135
+ let bits = 0;
136
+ for (const char of text) {
137
+ const value = BASE64_VALUES.get(char);
138
+ if (value === void 0) continue;
139
+ buffer = buffer << 6 | value;
140
+ bits += 6;
141
+ if (bits >= 8) {
142
+ bits -= 8;
143
+ bytes.push(buffer >> bits & 255);
144
+ }
145
+ }
146
+ return Uint8Array.from(bytes);
147
+ }
148
+ function fileUri(directory, path) {
149
+ const absolute = `${directory.replace(/\/*$/, "")}/${path.replace(/^\/+/, "")}`;
150
+ return `file://${absolute.split("/").map(encodeURIComponent).join("/")}`;
151
+ }
152
+ var expoDatabaseFiles = {
153
+ async readStart(path, length) {
154
+ const { fs, directory } = await loadModules();
155
+ const uri = fileUri(directory, path);
156
+ const info = await fs.getInfoAsync(uri);
157
+ if (!info.exists) return null;
158
+ if (info.size === 0) return new Uint8Array(0);
159
+ const text = await fs.readAsStringAsync(uri, { encoding: fs.EncodingType.Base64, position: 0, length });
160
+ return bytesOfBase64(text);
161
+ },
162
+ async remove(path) {
163
+ const { fs, directory } = await loadModules();
164
+ await fs.deleteAsync(fileUri(directory, path), { idempotent: true });
165
+ },
166
+ async rename(from, to) {
167
+ const { fs, directory } = await loadModules();
168
+ await fs.moveAsync({ from: fileUri(directory, from), to: fileUri(directory, to) });
169
+ }
170
+ };
171
+
32
172
  // src/drivers/expo/index.ts
33
173
  function expoSqlite() {
34
174
  return defineDriver({
35
- capabilities: { multipleConnections: false, extensions: false, steppedCopy: false, rowCounts: false },
175
+ capabilities: {
176
+ multipleConnections: false,
177
+ extensions: false,
178
+ steppedCopy: false,
179
+ rowCounts: false,
180
+ encryption: true
181
+ },
182
+ files: expoDatabaseFiles,
36
183
  async open(path, options) {
37
184
  const SQLite = await import('expo-sqlite');
38
- const db = await SQLite.openDatabaseAsync(path, {
39
- readOnly: options?.readonly
185
+ const openHandle = () => SQLite.openDatabaseAsync(path, {
186
+ readOnly: options?.readonly,
187
+ useNewConnection: true
40
188
  });
41
- if (options?.walMode !== false) await db.execAsync("PRAGMA journal_mode = WAL");
42
- await db.execAsync(`PRAGMA synchronous = ${synchronousPragmaValue(options?.synchronous)}`);
43
- await db.execAsync("PRAGMA foreign_keys = ON");
44
- if (options?.walAutoCheckpoint !== void 0) {
45
- await db.execAsync(`PRAGMA wal_autocheckpoint = ${Math.trunc(options.walAutoCheckpoint)}`);
189
+ const rawKey = options?.encryption?.key;
190
+ if (rawKey !== void 0 && (path === "" || path === ":memory:")) {
191
+ throw new EncryptionError("An in-memory database cannot be encrypted", "ENCRYPTION_UNSUPPORTED");
192
+ }
193
+ if (rawKey !== void 0 && await probeExpoDatabase(openHandle) === "plaintext") {
194
+ throw new EncryptionError(
195
+ `The database ${path} is plaintext, so it opens without a key. Encrypt it through changeEncryption`,
196
+ "DATABASE_NOT_ENCRYPTED"
197
+ );
198
+ }
199
+ const db = await openHandle();
200
+ try {
201
+ if (rawKey !== void 0) await keyExpoConnection(db, path, rawKey);
202
+ else await readUnkeyedExpoSchema(db, path);
203
+ if (options?.walMode !== false) await db.execAsync("PRAGMA journal_mode = WAL");
204
+ await db.execAsync(`PRAGMA synchronous = ${synchronousPragmaValue(options?.synchronous)}`);
205
+ await db.execAsync("PRAGMA foreign_keys = ON");
206
+ if (options?.walAutoCheckpoint !== void 0) {
207
+ await db.execAsync(`PRAGMA wal_autocheckpoint = ${Math.trunc(options.walAutoCheckpoint)}`);
208
+ }
209
+ } catch (err) {
210
+ await db.closeAsync().catch(() => void 0);
211
+ throw err;
46
212
  }
47
213
  const buildConnectionFromHandle = (dbHandle) => ({
48
214
  async exec(sql) {
@@ -68,12 +234,15 @@ function expoSqlite() {
68
234
  };
69
235
  },
70
236
  async transaction(fn) {
71
- let result;
72
- await dbHandle.withExclusiveTransactionAsync(async (txDb) => {
73
- const txConn = buildConnectionFromHandle(txDb);
74
- result = await fn(txConn);
75
- });
76
- return result;
237
+ await dbHandle.execAsync("BEGIN");
238
+ try {
239
+ const result = await fn(conn);
240
+ await dbHandle.execAsync("COMMIT");
241
+ return result;
242
+ } catch (err) {
243
+ await dbHandle.execAsync("ROLLBACK").catch(() => void 0);
244
+ throw err;
245
+ }
77
246
  },
78
247
  async loadExtension(extensionPath) {
79
248
  throw new ExtensionError(
@@ -86,7 +255,7 @@ function expoSqlite() {
86
255
  }
87
256
  });
88
257
  const conn = buildConnectionFromHandle(db);
89
- return conn;
258
+ return rawKey === void 0 ? conn : reportEncryptedPageFailures(conn);
90
259
  }
91
260
  });
92
261
  }
@@ -1,4 +1,4 @@
1
- import { S as SQLiteDriver } from '../types-DTmi7pXp.js';
1
+ import { S as SQLiteDriver } from '../types-BWyv_N18.js';
2
2
  import '../query-types-5B02ixH0.js';
3
3
 
4
4
  /**