@delali/sirannon-db 0.3.5 → 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.
- package/dist/backup/index.d.ts +5 -5
- package/dist/backup/index.mjs +5 -3
- package/dist/backup-scheduler/index.d.ts +2 -2
- package/dist/backup-scheduler/index.mjs +2 -2
- package/dist/chunk-22TYGH7J.mjs +55 -0
- package/dist/{chunk-SB3MIFLG.mjs → chunk-2ZQOZFX7.mjs} +29 -6
- package/dist/{chunk-UIDQGPJY.mjs → chunk-4O7WYZHN.mjs} +38 -3
- package/dist/{chunk-ZFFUVYYE.mjs → chunk-5DKWHT6K.mjs} +109 -1
- package/dist/chunk-5O4J6LQQ.mjs +35 -0
- package/dist/{chunk-CZL4D3BM.mjs → chunk-6CABU54O.mjs} +2 -2
- package/dist/{chunk-JAQ2I4OT.mjs → chunk-A5ULJVK7.mjs} +11 -11
- package/dist/{chunk-RLBKZZ24.mjs → chunk-AC7TKWNH.mjs} +1 -0
- package/dist/chunk-BKO4SCEC.mjs +99 -0
- package/dist/{chunk-KOTBP4AW.mjs → chunk-C7E4H6UN.mjs} +24 -4
- package/dist/{chunk-G4AG3ESO.mjs → chunk-FBA7P4ZA.mjs} +114 -149
- package/dist/{chunk-DKML5425.mjs → chunk-GBVB6WSC.mjs} +2 -2
- package/dist/chunk-I4JY5QPA.mjs +51 -0
- package/dist/{chunk-L6EX6K27.mjs → chunk-MO4APBYG.mjs} +54 -13
- package/dist/{chunk-RPWBYASV.mjs → chunk-MTOQ6QIS.mjs} +1 -1
- package/dist/chunk-MX77PGEN.mjs +60 -0
- package/dist/{chunk-BTUD6XQO.mjs → chunk-N3MHZV3B.mjs} +11 -29
- package/dist/{chunk-UHUDPQFI.mjs → chunk-OJXV4L3N.mjs} +1 -3
- package/dist/chunk-QF7WF7RJ.mjs +34 -0
- package/dist/chunk-RLBV4YTB.mjs +22 -0
- package/dist/{chunk-ISDT4RBD.mjs → chunk-ULUZUBON.mjs} +1 -1
- package/dist/{chunk-AP4367HK.mjs → chunk-VGRAAQX3.mjs} +4 -4
- package/dist/chunk-W74HFQFD.mjs +153 -0
- package/dist/chunk-Y4SP7CRF.mjs +9 -0
- package/dist/{chunk-JFVEGN7J.mjs → chunk-ZAUHAJ4G.mjs} +2 -1
- package/dist/{chunk-DDIHBUEN.mjs → chunk-ZWW53O5P.mjs} +19 -7
- package/dist/client/index.d.ts +8 -8
- package/dist/client/topology.d.ts +4 -4
- package/dist/{client-base-Doaqwrx3.d.ts → client-base-DJZbSY55.d.ts} +2 -2
- package/dist/{client-streams-MKPE4MXW.mjs → client-streams-F7IG3PTU.mjs} +13 -16
- package/dist/core/index.d.ts +48 -12
- package/dist/core/index.mjs +1812 -107
- package/dist/core/writer-worker.mjs +28 -4
- package/dist/{database-fz-8CJBP.d.ts → database-BZ96ZzAx.d.ts} +411 -184
- package/dist/{database-backup-BwTNiBm2.d.ts → database-backup-BhM8yJ1A.d.ts} +32 -2
- package/dist/driver/better-sqlite3.d.ts +11 -1
- package/dist/driver/better-sqlite3.mjs +212 -23
- package/dist/driver/bun.d.ts +1 -1
- package/dist/driver/bun.mjs +15 -22
- package/dist/driver/expo.d.ts +1 -1
- package/dist/driver/expo.mjs +185 -16
- package/dist/driver/node.d.ts +1 -1
- package/dist/driver/node.mjs +15 -62
- package/dist/driver/wa-sqlite.d.ts +1 -1
- package/dist/driver/wa-sqlite.mjs +14 -153
- package/dist/{change-tracker-Dpo99EI-.d.ts → errors-DLMAGQFb.d.ts} +200 -2
- package/dist/file-migrations/index.d.ts +1 -1
- package/dist/peer-refusal-RR37UOLV.mjs +1 -0
- package/dist/{protocol-DZSYSv0S.d.ts → protocol-DE10taas.d.ts} +1 -1
- package/dist/replication/coordinator/etcd.mjs +1 -1
- package/dist/replication/index.d.ts +7 -7
- package/dist/replication/index.mjs +66 -9
- package/dist/{restore-options-CcMukOLO.d.ts → restore-options-B6HV_5XT.d.ts} +6 -1
- package/dist/server/index.d.ts +10 -10
- package/dist/server/index.mjs +506 -350
- package/dist/{server-options-B9CsAZp5.d.ts → server-options-Bhdlvehe.d.ts} +14 -1
- package/dist/{server-streams-SBYCHWWM.mjs → server-streams-Q2AZAW3N.mjs} +6 -30
- package/dist/{sirannon-D0CjDULa.d.ts → sirannon-Ba1o2HUQ.d.ts} +5 -5
- package/dist/transport/grpc.d.ts +15 -4
- package/dist/transport/grpc.mjs +34 -37
- package/dist/transport/memory.d.ts +4 -3
- package/dist/transport/memory.mjs +8 -26
- package/dist/{types-DTmi7pXp.d.ts → types-BWyv_N18.d.ts} +189 -1
- package/dist/{types-BlZ9GG1a.d.ts → types-CDnlkVRK.d.ts} +8 -2
- package/package.json +17 -9
- package/dist/chunk-DBN57KVR.mjs +0 -171
- package/dist/chunk-W67A4H2E.mjs +0 -23
- 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,
|
|
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
|
-
|
|
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-
|
|
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,
|
|
3
|
+
import { nodeResolveExtensionPath, nodeBackupEngine, nodeStreamingSupport, WriterWorker, createStatementCache } from '../chunk-FBA7P4ZA.mjs';
|
|
3
4
|
import '../chunk-DX4WGJL7.mjs';
|
|
4
|
-
import '../chunk-
|
|
5
|
-
import {
|
|
6
|
-
import '../chunk-
|
|
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-
|
|
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
|
|
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
|
-
|
|
272
|
-
|
|
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([
|
|
277
|
-
|
|
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
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
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
|
-
|
|
496
|
+
const connection = createConnection(db, taker, encrypting);
|
|
497
|
+
return rawKey === void 0 ? connection : reportEncryptedPageFailures(connection);
|
|
309
498
|
}
|
|
310
499
|
});
|
|
311
500
|
}
|
package/dist/driver/bun.d.ts
CHANGED
package/dist/driver/bun.mjs
CHANGED
|
@@ -1,32 +1,25 @@
|
|
|
1
|
-
import {
|
|
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: {
|
|
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) {
|
package/dist/driver/expo.d.ts
CHANGED
package/dist/driver/expo.mjs
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import {
|
|
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: {
|
|
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
|
|
39
|
-
readOnly: options?.readonly
|
|
185
|
+
const openHandle = () => SQLite.openDatabaseAsync(path, {
|
|
186
|
+
readOnly: options?.readonly,
|
|
187
|
+
useNewConnection: true
|
|
40
188
|
});
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
}
|
package/dist/driver/node.d.ts
CHANGED