@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.
- 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 { startCopyWithoutHoldingWriter } from './chunk-
|
|
1
|
+
import { readPageSize, readMainDatabasePath, defaultDestinationName, assertPieceBytes, DEFAULT_PIECE_BYTES, startCopyWithoutHoldingWriter } from './chunk-I4JY5QPA.mjs';
|
|
2
2
|
import { readLogPosition } from './chunk-AHCMO4YL.mjs';
|
|
3
3
|
import { reportQuietly } from './chunk-BUWJH3D3.mjs';
|
|
4
4
|
import { randomHex, destinationWithDeadline, DEFAULT_DESTINATION_TIMEOUT_MS, assertDeadline, withinDeadline } from './chunk-BGJ5JVYL.mjs';
|
|
@@ -10,30 +10,6 @@ import { open, mkdir, mkdtemp, rm } from 'fs/promises';
|
|
|
10
10
|
import { tmpdir } from 'os';
|
|
11
11
|
import { createHash } from 'crypto';
|
|
12
12
|
|
|
13
|
-
// src/core/backup/report.ts
|
|
14
|
-
var DEFAULT_PIECE_BYTES = 16 * 1024 * 1024;
|
|
15
|
-
function defaultDestinationName() {
|
|
16
|
-
return `backup-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.db`;
|
|
17
|
-
}
|
|
18
|
-
function assertPieceBytes(pieceBytes) {
|
|
19
|
-
if (!Number.isInteger(pieceBytes) || pieceBytes <= 0) {
|
|
20
|
-
throw new SirannonError(
|
|
21
|
-
`Piece size must be a positive whole number of bytes, and it was ${pieceBytes}`,
|
|
22
|
-
"BACKUP_ERROR"
|
|
23
|
-
);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
async function readPageSize(conn) {
|
|
27
|
-
const stmt = await conn.prepare("PRAGMA page_size");
|
|
28
|
-
const row = await stmt.get();
|
|
29
|
-
return row ? Number(row.page_size) : 0;
|
|
30
|
-
}
|
|
31
|
-
async function readMainDatabasePath(conn) {
|
|
32
|
-
const stmt = await conn.prepare("PRAGMA database_list");
|
|
33
|
-
const rows = await stmt.all();
|
|
34
|
-
const main = rows.find((row) => row.name === "main");
|
|
35
|
-
return main?.file ?? "";
|
|
36
|
-
}
|
|
37
13
|
function destinationPieceError(name, index, err) {
|
|
38
14
|
if (err instanceof SirannonError) return err;
|
|
39
15
|
return new SirannonError(
|
|
@@ -490,13 +466,18 @@ var BackupStreamHost = class _BackupStreamHost {
|
|
|
490
466
|
|
|
491
467
|
// src/core/backup/streamed-copy.ts
|
|
492
468
|
var VFS_NAME = "sirannon";
|
|
469
|
+
var ENCRYPTING_VFS_PREFIX = "multipleciphers-";
|
|
493
470
|
var PIECE_BLOCK_BYTES = 512;
|
|
494
471
|
var DRAIN_POLL_MS = 1;
|
|
495
472
|
var STILL_TAKING_REPORT_MS = 5;
|
|
496
473
|
var REPORTS_BEFORE_THE_EXTENSION_STOPS_WAITING = 6;
|
|
497
474
|
var MICROSECONDS_PER_MS = 1e3;
|
|
498
|
-
function
|
|
499
|
-
|
|
475
|
+
async function destinationVfs(conn) {
|
|
476
|
+
const engine = await (await conn.prepare("SELECT 1 AS present FROM pragma_function_list WHERE name = 'sqlite3mc_version'")).get();
|
|
477
|
+
return engine === void 0 ? VFS_NAME : `${ENCRYPTING_VFS_PREFIX}${VFS_NAME}`;
|
|
478
|
+
}
|
|
479
|
+
function destinationUri(streamId, vfs) {
|
|
480
|
+
return `file:sirannon-stream-${streamId}?vfs=${vfs}`;
|
|
500
481
|
}
|
|
501
482
|
function sizeTheSteps(requestedPagesPerStep, pieceBytes, pageSize) {
|
|
502
483
|
const pagesPerPiece = Math.max(1, Math.floor(pieceBytes / pageSize));
|
|
@@ -546,6 +527,7 @@ async function copyToDestinationStreamed(conn, request, support) {
|
|
|
546
527
|
pageSize
|
|
547
528
|
);
|
|
548
529
|
const copyRunsOffCallerThread = conn.copyRunsOffCallerThread === true;
|
|
530
|
+
const vfs = await destinationVfs(conn);
|
|
549
531
|
const host = await BackupStreamHost.start(support.openConnection, support.extensionPath);
|
|
550
532
|
let streamId;
|
|
551
533
|
try {
|
|
@@ -559,7 +541,7 @@ async function copyToDestinationStreamed(conn, request, support) {
|
|
|
559
541
|
await host.stop().catch(() => void 0);
|
|
560
542
|
throw err;
|
|
561
543
|
}
|
|
562
|
-
const uri = destinationUri(streamId);
|
|
544
|
+
const uri = destinationUri(streamId, vfs);
|
|
563
545
|
const stillTaking = copyRunsOffCallerThread ? setInterval(() => {
|
|
564
546
|
void host.reportStillTaking(streamId).catch(() => void 0);
|
|
565
547
|
}, STILL_TAKING_REPORT_MS) : null;
|
|
@@ -1324,4 +1306,4 @@ var BackupScheduler = class {
|
|
|
1324
1306
|
}
|
|
1325
1307
|
};
|
|
1326
1308
|
|
|
1327
|
-
export { BackupManager, BackupScheduler,
|
|
1309
|
+
export { BackupManager, BackupScheduler, sendFileInPieces };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { checkpointLog } from './chunk-QGVRRKB2.mjs';
|
|
2
2
|
import { readLogFileHeader, sameLog, cursorChecksum, scanLogFrames, copyLogRange } from './chunk-AHCMO4YL.mjs';
|
|
3
3
|
import { logFrameOffset } from './chunk-BUWJH3D3.mjs';
|
|
4
|
-
import { isBackupChainPosition } from './chunk-
|
|
4
|
+
import { isBackupChainPosition } from './chunk-6CABU54O.mjs';
|
|
5
5
|
import { randomHex } from './chunk-BGJ5JVYL.mjs';
|
|
6
6
|
import { SirannonError } from './chunk-KINDFBQP.mjs';
|
|
7
7
|
import { readFile, mkdir, writeFile, rename, rm, readdir } from 'fs/promises';
|
|
@@ -45,8 +45,6 @@ async function writeCycleState(stagingDir, state) {
|
|
|
45
45
|
async function removeCycleState(stagingDir) {
|
|
46
46
|
await rm(cycleStatePath(stagingDir), { force: true });
|
|
47
47
|
}
|
|
48
|
-
|
|
49
|
-
// src/core/backup/cycle-capture.ts
|
|
50
48
|
var STAGED_CAPTURE_PREFIX = "capture-";
|
|
51
49
|
var STAGED_CAPTURE_SUFFIX = ".wal";
|
|
52
50
|
function stagedCapturePath(stagingDir, sequence) {
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { status, Metadata } from '@grpc/grpc-js';
|
|
2
|
+
|
|
3
|
+
// src/transport/grpc/peer-refusal.ts
|
|
4
|
+
var ERROR_CODE_TRAILER = "sirannon-error-code";
|
|
5
|
+
function refusalStatus(error) {
|
|
6
|
+
const trailer = new Metadata();
|
|
7
|
+
trailer.set(ERROR_CODE_TRAILER, error.code);
|
|
8
|
+
return Object.assign(new Error(error.message), {
|
|
9
|
+
code: status.FAILED_PRECONDITION,
|
|
10
|
+
details: error.message,
|
|
11
|
+
metadata: trailer
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
function refusePeerStreams(t, peerId, error) {
|
|
15
|
+
const served = t.serverPeerStreams.get(peerId);
|
|
16
|
+
for (const stream of [served?.replicateStream, served?.syncStream]) {
|
|
17
|
+
if (stream?.writable === true) stream.emit("error", refusalStatus(error));
|
|
18
|
+
}
|
|
19
|
+
const dialled = t.clientPeerStreams.get(peerId);
|
|
20
|
+
for (const stream of [dialled?.replicateStream, dialled?.syncStream]) {
|
|
21
|
+
try {
|
|
22
|
+
stream?.cancel();
|
|
23
|
+
} catch {
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function refusalCodeOf(err) {
|
|
28
|
+
const serviceError = err;
|
|
29
|
+
if (serviceError?.code !== status.FAILED_PRECONDITION) return null;
|
|
30
|
+
const [code] = serviceError.metadata?.get(ERROR_CODE_TRAILER) ?? [];
|
|
31
|
+
return typeof code === "string" && code.length > 0 ? code : null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export { ERROR_CODE_TRAILER, refusalCodeOf, refusePeerStreams };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { SirannonError, ExtensionError } from './chunk-KINDFBQP.mjs';
|
|
2
|
+
import { AsyncLocalStorage } from 'async_hooks';
|
|
3
|
+
|
|
4
|
+
// src/core/driver/extension.ts
|
|
5
|
+
async function loadThroughRuntime(extensionPath, load) {
|
|
6
|
+
try {
|
|
7
|
+
load();
|
|
8
|
+
} catch (err) {
|
|
9
|
+
if (err instanceof SirannonError) throw err;
|
|
10
|
+
throw new ExtensionError(extensionPath, err instanceof Error ? err.message : String(err));
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function asyncLocalWriterContext() {
|
|
14
|
+
const held = new AsyncLocalStorage();
|
|
15
|
+
return {
|
|
16
|
+
run: (operation) => held.run(true, operation),
|
|
17
|
+
isActive: () => held.getStore() === true,
|
|
18
|
+
exit: (operation) => held.exit(operation)
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export { asyncLocalWriterContext, loadThroughRuntime };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { withoutRowCounts, runWriterTransaction, queryMetered, execute, executeBatch } from './chunk-ZWW53O5P.mjs';
|
|
2
|
+
import { HLC, ensureMetaTable, selectMetaValue, IDENTIFIER_RE, attributeChangeSeqs, validateDdlSafety, insertDdlChange, extractDroppedTable, selectMaxChangeSeq, updateChangeStampsAfterSeqSql, selectOutboundHistoryChangesSql, selectOutboundChangesSql, selectNodeChangesAfterSeqSql, prepareUpsertNewerColumnVersion, canonicaliseForChecksum, upsertMetaValue, selectMaxColumnVersionHlcForRow, selectMaxRowChangeHlc, validateIdentifier, prepareDeleteRowColumnVersions, prepareUpsertColumnVersion, selectTableExists, selectAppliedSourceSeqsInRange, selectStagedPushTail, selectStagedPushSeqsInRange, deleteStagedPushChanges, prepareInsertStagedPushChange, selectStagedPushChangesAfter, insertAppliedChangesFromStagedPush, prepareInsertAppliedChange } from './chunk-MTOQ6QIS.mjs';
|
|
3
|
+
import { record, string, unknown, object, optional, nullable, picklist, safeParse, summarize } from './chunk-5DKWHT6K.mjs';
|
|
4
4
|
import { BatchValidationError, ConflictError, ReplicationError } from './chunk-BLMRNSUG.mjs';
|
|
5
|
-
import { CHANGES_TABLE, isReservedIdentifier, decodeTaggedValues, encodeTaggedValues } from './chunk-
|
|
5
|
+
import { CHANGES_TABLE, isReservedIdentifier, decodeTaggedValues, encodeTaggedValues } from './chunk-ZAUHAJ4G.mjs';
|
|
6
6
|
import { sha256Hex } from './chunk-GVCNMPOS.mjs';
|
|
7
7
|
import { lateRowCountsOf } from './chunk-K43WJORM.mjs';
|
|
8
8
|
import { rowCountsOf, totalRowCounts, addRowCounts } from './chunk-E2GRX4ZD.mjs';
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { pipe, instance, check, union, object, optional, string, safeParse } from './chunk-5DKWHT6K.mjs';
|
|
2
|
+
import { EncryptionError } from './chunk-5O4J6LQQ.mjs';
|
|
3
|
+
import { SirannonError } from './chunk-KINDFBQP.mjs';
|
|
4
|
+
|
|
5
|
+
// src/core/backup/chain-queries.ts
|
|
6
|
+
function chainError(message) {
|
|
7
|
+
return new SirannonError(message, "BACKUP_CHAIN_BROKEN");
|
|
8
|
+
}
|
|
9
|
+
function firstMissingSequence(changes) {
|
|
10
|
+
for (let expected = 1; expected <= changes.length; expected++) {
|
|
11
|
+
if (changes[expected - 1]?.sequence !== expected) return expected;
|
|
12
|
+
}
|
|
13
|
+
return void 0;
|
|
14
|
+
}
|
|
15
|
+
function withBase(chains) {
|
|
16
|
+
return chains.filter((chain) => chain.base !== void 0);
|
|
17
|
+
}
|
|
18
|
+
function planBackupRestore(chains, moment) {
|
|
19
|
+
const candidates = withBase(chains).filter((chain2) => chain2.base.finishedAt <= moment).sort((left, right) => right.base.finishedAt - left.base.finishedAt);
|
|
20
|
+
const chain = candidates[0];
|
|
21
|
+
if (!chain) {
|
|
22
|
+
const earliest = withBase(chains).map((candidate) => candidate.base.finishedAt).sort((left, right) => left - right)[0];
|
|
23
|
+
throw chainError(
|
|
24
|
+
earliest === void 0 ? "The destination holds no full copy, so no moment can be restored" : `The destination holds no full copy finished at or before ${new Date(moment).toISOString()}; the earliest moment it can restore is ${new Date(earliest).toISOString()}`
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
const changes = chain.changes.filter((change) => change.capturedAt <= moment);
|
|
28
|
+
const missing = firstMissingSequence(changes);
|
|
29
|
+
if (missing !== void 0) {
|
|
30
|
+
throw chainError(
|
|
31
|
+
`Chain '${chain.chainId}' is missing change piece ${missing}, so it cannot be applied past piece ${missing - 1}`
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
const last = changes[changes.length - 1];
|
|
35
|
+
return {
|
|
36
|
+
chainId: chain.chainId,
|
|
37
|
+
base: chain.base,
|
|
38
|
+
changes,
|
|
39
|
+
restoresTo: last ? last.capturedAt : chain.base.finishedAt
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function backupPiecesSafeToDelete(chains, options) {
|
|
43
|
+
const restorableFrom = options?.restorableFrom;
|
|
44
|
+
const superseded = restorableFrom === void 0 ? void 0 : withBase(chains).filter((chain) => chain.base.finishedAt <= restorableFrom).sort((left, right) => right.base.finishedAt - left.base.finishedAt)[0];
|
|
45
|
+
const oldestFirst = [...chains].sort((left, right) => left.startedAt - right.startedAt);
|
|
46
|
+
const deletable = [];
|
|
47
|
+
for (const chain of oldestFirst) {
|
|
48
|
+
if (!chain.base) {
|
|
49
|
+
deletable.push(...chain.changes);
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (superseded && chain.base.finishedAt < superseded.base.finishedAt) {
|
|
53
|
+
deletable.push(chain.base, ...chain.changes);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
const missing = firstMissingSequence(chain.changes);
|
|
57
|
+
if (missing !== void 0) {
|
|
58
|
+
deletable.push(...chain.changes.filter((change) => change.sequence > missing));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return deletable;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// src/core/encryption/key.ts
|
|
65
|
+
var ENCRYPTION_KEY_BYTES = 32;
|
|
66
|
+
var RAW_KEY_SCHEMA = pipe(
|
|
67
|
+
instance(Uint8Array),
|
|
68
|
+
check((bytes) => bytes.length === ENCRYPTION_KEY_BYTES)
|
|
69
|
+
);
|
|
70
|
+
var KEY_SCHEMA = union([RAW_KEY_SCHEMA, object({ key: RAW_KEY_SCHEMA, id: optional(string()) })]);
|
|
71
|
+
async function resolveEncryptionKey(source) {
|
|
72
|
+
let returned;
|
|
73
|
+
try {
|
|
74
|
+
returned = await source();
|
|
75
|
+
} catch (err) {
|
|
76
|
+
throw new EncryptionError(
|
|
77
|
+
"The encryption key function threw or rejected, so Sirannon has no key to open the database with",
|
|
78
|
+
"ENCRYPTION_KEY_UNAVAILABLE",
|
|
79
|
+
err
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
const parsed = safeParse(KEY_SCHEMA, returned);
|
|
83
|
+
if (!parsed.success) {
|
|
84
|
+
throw new EncryptionError(
|
|
85
|
+
`The encryption key function returned no ${ENCRYPTION_KEY_BYTES}-byte key. Return ${ENCRYPTION_KEY_BYTES} random bytes as a Uint8Array, alone or as { key, id }; Sirannon accepts no password and no string`,
|
|
86
|
+
"INVALID_ENCRYPTION_KEY"
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
const value = parsed.output;
|
|
90
|
+
if (value instanceof Uint8Array) return { bytes: Uint8Array.from(value) };
|
|
91
|
+
return { bytes: Uint8Array.from(value.key), ...value.id === void 0 ? {} : { id: value.id } };
|
|
92
|
+
}
|
|
93
|
+
function wipeEncryptionKey(key) {
|
|
94
|
+
key?.bytes.fill(0);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// src/core/encryption/keyring.ts
|
|
98
|
+
var EncryptionKeyring = class {
|
|
99
|
+
constructor(source) {
|
|
100
|
+
this.source = source;
|
|
101
|
+
}
|
|
102
|
+
lastKeyId;
|
|
103
|
+
/** `true` when the database opens under a key. */
|
|
104
|
+
get encrypted() {
|
|
105
|
+
return this.source !== null;
|
|
106
|
+
}
|
|
107
|
+
/** The identifier that came with the key that the last open used, or `undefined` when it came with none. */
|
|
108
|
+
get keyId() {
|
|
109
|
+
return this.lastKeyId;
|
|
110
|
+
}
|
|
111
|
+
/** The key function that the database currently opens under, or `null` for a plaintext database. */
|
|
112
|
+
get keySource() {
|
|
113
|
+
return this.source;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Makes the database open every later connection under a new key function, or as plaintext.
|
|
117
|
+
*
|
|
118
|
+
* @param source - The key function of the key that the file now holds, or `null` for a plaintext file.
|
|
119
|
+
*/
|
|
120
|
+
replace(source) {
|
|
121
|
+
this.source = source;
|
|
122
|
+
this.lastKeyId = void 0;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Calls the key function once, passes the key to the opens, and wipes Sirannon's copy of the key once they settle.
|
|
126
|
+
*
|
|
127
|
+
* @param open - Opens the connections with the settings that carry the key.
|
|
128
|
+
* @returns What `open` returns.
|
|
129
|
+
*/
|
|
130
|
+
async openWith(open) {
|
|
131
|
+
if (this.source === null) return open({}, void 0);
|
|
132
|
+
const key = await resolveEncryptionKey(this.source);
|
|
133
|
+
this.lastKeyId = key.id;
|
|
134
|
+
try {
|
|
135
|
+
return await open({ encryption: { key: key.bytes } }, key);
|
|
136
|
+
} finally {
|
|
137
|
+
wipeEncryptionKey(key);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Calls the key function and returns a copy of the key bytes, which the caller hands to a writer worker and wipes.
|
|
142
|
+
*
|
|
143
|
+
* @returns The key bytes.
|
|
144
|
+
*/
|
|
145
|
+
async freshKeyBytes() {
|
|
146
|
+
if (this.source === null) throw new Error("A plaintext database has no key to hand to its writer worker");
|
|
147
|
+
const key = await resolveEncryptionKey(this.source);
|
|
148
|
+
this.lastKeyId = key.id;
|
|
149
|
+
return key.bytes;
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
export { EncryptionKeyring, backupPiecesSafeToDelete, planBackupRestore, resolveEncryptionKey, wipeEncryptionKey };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// src/core/backup/cycle-options.ts
|
|
2
|
+
var DEFAULT_CAPTURE_INTERVAL_MS = 6e4;
|
|
3
|
+
var DEFAULT_FULL_COPY_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
4
|
+
var DEFAULT_BACKUP_NAME_PREFIX = "sirannon-backup";
|
|
5
|
+
function defaultStagingDir(sourcePath) {
|
|
6
|
+
return `${sourcePath}-backup`;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export { DEFAULT_BACKUP_NAME_PREFIX, DEFAULT_CAPTURE_INTERVAL_MS, DEFAULT_FULL_COPY_INTERVAL_MS, defaultStagingDir };
|
|
@@ -15,6 +15,7 @@ var STAGED_PUSH_CHANGES_TABLE = "_sirannon_staged_push_changes";
|
|
|
15
15
|
var REPLACED_ROWS_TABLE = "_sirannon_replaced_rows";
|
|
16
16
|
var CDC_TRIGGER_PREFIX = "_sirannon_trg_";
|
|
17
17
|
var LIVE_PROBE_TABLE_PREFIX = "_sirannon_live_probe_";
|
|
18
|
+
var KEY_CHANGE_CAPTURE_PREFIX = "_sirannon_key_change_";
|
|
18
19
|
var SIRANNON_PREFIX = "_sirannon";
|
|
19
20
|
var SQLITE_PREFIX = "sqlite_";
|
|
20
21
|
var SIRANNON_MESSAGE = "Access to internal tables is not permitted: identifiers beginning with '_sirannon' are reserved";
|
|
@@ -301,4 +302,4 @@ function decodeHexBytes(hex) {
|
|
|
301
302
|
return bytes;
|
|
302
303
|
}
|
|
303
304
|
|
|
304
|
-
export { APPLIED_CHANGES_TABLE, CDC_TRIGGER_PREFIX, CHANGES_TABLE, COLUMN_VERSIONS_TABLE, DEVICE_CURSORS_TABLE, INTERNAL_TABLE_PREFIX, LIVE_PROBE_TABLE_PREFIX, META_TABLE, MIGRATIONS_TABLE, PEER_STATE_TABLE, REPLACED_ROWS_TABLE, SAFE_INT_BOUND_TEXT, STAGED_CHANGES_TABLE, STAGED_PUSH_CHANGES_TABLE, SYNC_STATE_TABLE, assertSqlAllowed, changedAtToEventTimestamp, decodeTaggedValues, encodeTaggedValues, encodeWireRowsInPlace, eventTimestampToChangedAt, isReservedIdentifier };
|
|
305
|
+
export { APPLIED_CHANGES_TABLE, CDC_TRIGGER_PREFIX, CHANGES_TABLE, COLUMN_VERSIONS_TABLE, DEVICE_CURSORS_TABLE, INTERNAL_TABLE_PREFIX, KEY_CHANGE_CAPTURE_PREFIX, LIVE_PROBE_TABLE_PREFIX, META_TABLE, MIGRATIONS_TABLE, PEER_STATE_TABLE, REPLACED_ROWS_TABLE, SAFE_INT_BOUND_TEXT, STAGED_CHANGES_TABLE, STAGED_PUSH_CHANGES_TABLE, SYNC_STATE_TABLE, assertSqlAllowed, changedAtToEventTimestamp, decodeTaggedValues, encodeTaggedValues, encodeWireRowsInPlace, eventTimestampToChangedAt, isReservedIdentifier };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { encodeWireRowsInPlace, assertSqlAllowed } from './chunk-
|
|
1
|
+
import { encodeWireRowsInPlace, assertSqlAllowed } from './chunk-ZAUHAJ4G.mjs';
|
|
2
2
|
import { withRowCounts, rowCountsOf, allMetered, addRowCounts } from './chunk-E2GRX4ZD.mjs';
|
|
3
3
|
import { SirannonError, QueryError, TransactionError } from './chunk-KINDFBQP.mjs';
|
|
4
4
|
|
|
@@ -74,23 +74,27 @@ function withoutRowCounts(result) {
|
|
|
74
74
|
function metered(value, counts) {
|
|
75
75
|
return counts === void 0 ? { value } : { value, rowsRead: counts.rowsRead, rowsWritten: counts.rowsWritten };
|
|
76
76
|
}
|
|
77
|
-
async function query(conn, sql, params) {
|
|
77
|
+
async function query(conn, sql, params, leasedFrom) {
|
|
78
78
|
try {
|
|
79
79
|
const handle = guardedStatement(conn, sql);
|
|
80
80
|
const stmt = handle instanceof Promise ? await handle : handle;
|
|
81
81
|
return await stmt.all(...bindParams(params));
|
|
82
82
|
} catch (err) {
|
|
83
83
|
throw asQueryError(err, sql);
|
|
84
|
+
} finally {
|
|
85
|
+
leasedFrom?.releaseReader();
|
|
84
86
|
}
|
|
85
87
|
}
|
|
86
|
-
async function queryMetered(conn, sql, params) {
|
|
88
|
+
async function queryMetered(conn, sql, params, leasedFrom) {
|
|
87
89
|
try {
|
|
88
90
|
return await allMetered(await guardedStatement(conn, sql), bindParams(params));
|
|
89
91
|
} catch (err) {
|
|
90
92
|
throw asQueryError(err, sql);
|
|
93
|
+
} finally {
|
|
94
|
+
leasedFrom?.releaseReader();
|
|
91
95
|
}
|
|
92
96
|
}
|
|
93
|
-
async function queryForWire(conn, sql, params) {
|
|
97
|
+
async function queryForWire(conn, sql, params, leasedFrom) {
|
|
94
98
|
try {
|
|
95
99
|
const stmt = await guardedStatement(conn, sql);
|
|
96
100
|
const bound = bindParams(params);
|
|
@@ -103,9 +107,11 @@ async function queryForWire(conn, sql, params) {
|
|
|
103
107
|
return { value: encodeWireRowsInPlace(rows) };
|
|
104
108
|
} catch (err) {
|
|
105
109
|
throw asQueryError(err, sql);
|
|
110
|
+
} finally {
|
|
111
|
+
leasedFrom?.releaseReader();
|
|
106
112
|
}
|
|
107
113
|
}
|
|
108
|
-
async function queryRowsForWire(conn, sql, params) {
|
|
114
|
+
async function queryRowsForWire(conn, sql, params, leasedFrom) {
|
|
109
115
|
try {
|
|
110
116
|
const handle = guardedStatement(conn, sql);
|
|
111
117
|
const stmt = handle instanceof Promise ? await handle : handle;
|
|
@@ -114,18 +120,22 @@ async function queryRowsForWire(conn, sql, params) {
|
|
|
114
120
|
return encodeWireRowsInPlace(rows);
|
|
115
121
|
} catch (err) {
|
|
116
122
|
throw asQueryError(err, sql);
|
|
123
|
+
} finally {
|
|
124
|
+
leasedFrom?.releaseReader();
|
|
117
125
|
}
|
|
118
126
|
}
|
|
119
|
-
async function queryOne(conn, sql, params) {
|
|
127
|
+
async function queryOne(conn, sql, params, leasedFrom) {
|
|
120
128
|
try {
|
|
121
129
|
const handle = guardedStatement(conn, sql);
|
|
122
130
|
const stmt = handle instanceof Promise ? await handle : handle;
|
|
123
131
|
return await stmt.get(...bindParams(params));
|
|
124
132
|
} catch (err) {
|
|
125
133
|
throw asQueryError(err, sql);
|
|
134
|
+
} finally {
|
|
135
|
+
leasedFrom?.releaseReader();
|
|
126
136
|
}
|
|
127
137
|
}
|
|
128
|
-
async function queryOneMetered(conn, sql, params) {
|
|
138
|
+
async function queryOneMetered(conn, sql, params, leasedFrom) {
|
|
129
139
|
try {
|
|
130
140
|
const stmt = await guardedStatement(conn, sql);
|
|
131
141
|
const bound = bindParams(params);
|
|
@@ -133,6 +143,8 @@ async function queryOneMetered(conn, sql, params) {
|
|
|
133
143
|
return { value: await stmt.get(...bound) };
|
|
134
144
|
} catch (err) {
|
|
135
145
|
throw asQueryError(err, sql);
|
|
146
|
+
} finally {
|
|
147
|
+
leasedFrom?.releaseReader();
|
|
136
148
|
}
|
|
137
149
|
}
|
|
138
150
|
async function execute(conn, sql, params, trusted = false) {
|
package/dist/client/index.d.ts
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
export { F as FieldMergeResolver, L as LWWResolver, P as PrimaryWinsResolver } from '../primary-wins-BnNAEjwB.js';
|
|
2
2
|
import { C as ConflictResolver, R as ReplicationBatch } from '../types-BG31TtrQ.js';
|
|
3
3
|
export { a as ConflictContext, b as ConflictResolution } from '../types-BG31TtrQ.js';
|
|
4
|
-
import { C as ClientOptions, B as BulkLoadDurability } from '../server-options-
|
|
5
|
-
import { D as DatabaseClient, T as Transport, L as LiveHandlers, R as RemoteSubscription, a as RemoteSubscriptionBuilder, S as SubscribeOptions, b as RegistryDigestSource } from '../client-base-
|
|
6
|
-
export { c as LoadAllOptions, d as RemoteDatabase, e as RemoteError, f as SQL_REFUSED_MESSAGE, g as ServerCapabilities, h as ServerCapabilityCheck } from '../client-base-
|
|
4
|
+
import { C as ClientOptions, B as BulkLoadDurability } from '../server-options-Bhdlvehe.js';
|
|
5
|
+
import { D as DatabaseClient, T as Transport, L as LiveHandlers, R as RemoteSubscription, a as RemoteSubscriptionBuilder, S as SubscribeOptions, b as RegistryDigestSource } from '../client-base-DJZbSY55.js';
|
|
6
|
+
export { c as LoadAllOptions, d as RemoteDatabase, e as RemoteError, f as SQL_REFUSED_MESSAGE, g as ServerCapabilities, h as ServerCapabilityCheck } from '../client-base-DJZbSY55.js';
|
|
7
7
|
import { a as LiveQuery, b as LiveQueryState, c as LiveUpdate } from '../types-CEJG0eFS.js';
|
|
8
|
-
import {
|
|
8
|
+
import { b as DeviceSyncPort, D as Database } from '../database-BZ96ZzAx.js';
|
|
9
9
|
import { C as ChangeEvent, P as Params, R as ReadConcern, W as WriteConcern } from '../query-types-5B02ixH0.js';
|
|
10
|
-
import { Q as QueryResponse, E as ExecuteResponse, T as TransactionResponse, B as BatchResponse, L as LoadResponse, A as AckResponse } from '../protocol-
|
|
10
|
+
import { Q as QueryResponse, E as ExecuteResponse, T as TransactionResponse, B as BatchResponse, L as LoadResponse, A as AckResponse } from '../protocol-DE10taas.js';
|
|
11
|
+
import '../types-BWyv_N18.js';
|
|
11
12
|
import '../operation-registry-CGhNT-Sn.js';
|
|
12
|
-
import '../
|
|
13
|
-
import '../
|
|
14
|
-
import '../database-backup-BwTNiBm2.js';
|
|
13
|
+
import '../errors-DLMAGQFb.js';
|
|
14
|
+
import '../database-backup-BhM8yJ1A.js';
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* Connects to one sirannon-db server and returns a {@link RemoteDatabase} for each database on it.
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { a as ReadConcernLevel } from '../query-types-5B02ixH0.js';
|
|
2
|
-
import { C as ClientOptions } from '../server-options-
|
|
3
|
-
import { T as Transport, D as DatabaseClient } from '../client-base-
|
|
4
|
-
import {
|
|
2
|
+
import { C as ClientOptions } from '../server-options-Bhdlvehe.js';
|
|
3
|
+
import { T as Transport, D as DatabaseClient } from '../client-base-DJZbSY55.js';
|
|
4
|
+
import { K as ClusterStatusInfo } from '../types-BWyv_N18.js';
|
|
5
5
|
import '../operation-registry-CGhNT-Sn.js';
|
|
6
6
|
import '../types-BG31TtrQ.js';
|
|
7
7
|
import '../types-CEJG0eFS.js';
|
|
8
|
-
import '../protocol-
|
|
8
|
+
import '../protocol-DE10taas.js';
|
|
9
9
|
|
|
10
10
|
interface TopologyRouting {
|
|
11
11
|
_getReadEndpoint(databaseId?: string, readConcern?: ReadConcernLevel): Promise<string>;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { B as BulkLoadDurability, a as BulkLoadResult, C as ClientOptions } from './server-options-
|
|
1
|
+
import { B as BulkLoadDurability, a as BulkLoadResult, C as ClientOptions } from './server-options-Bhdlvehe.js';
|
|
2
2
|
import { R as ResultOp, L as LiveQueryOptions, a as LiveQuery } from './types-CEJG0eFS.js';
|
|
3
3
|
import { O as OperationRef, b as OperationArguments } from './operation-registry-CGhNT-Sn.js';
|
|
4
4
|
import { P as Params, R as ReadConcern, W as WriteConcern, C as ChangeEvent, Q as QueryOptions } from './query-types-5B02ixH0.js';
|
|
5
|
-
import { Q as QueryResponse, E as ExecuteResponse, T as TransactionResponse, B as BatchResponse, L as LoadResponse } from './protocol-
|
|
5
|
+
import { Q as QueryResponse, E as ExecuteResponse, T as TransactionResponse, B as BatchResponse, L as LoadResponse } from './protocol-DE10taas.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* The message that the client throws when the server accepts no SQL over the network.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { buildChannelCreds, ReplicationClient, localHello, peerHelloFields } from './chunk-4O7WYZHN.mjs';
|
|
2
|
+
import { refusalCodeOf } from './chunk-QF7WF7RJ.mjs';
|
|
3
|
+
import { claimEndpoint, registerEndpointAbort, noteEndpointReachable, releaseEndpoint, scheduleEndpointRedial, noteEndpointRefused, noteEndpointServing } from './chunk-C7E4H6UN.mjs';
|
|
4
|
+
import { registerPeer, removePeer } from './chunk-AC7TKWNH.mjs';
|
|
4
5
|
import { fromBatchPayload, fromAckPayload, fromSyncRequestPayload, fromSyncBatchPayload, fromSyncCompletePayload, fromSyncAckPayload } from './chunk-3VTT3VU6.mjs';
|
|
5
6
|
import './chunk-KU32OT4H.mjs';
|
|
6
7
|
import './chunk-BLMRNSUG.mjs';
|
|
@@ -48,13 +49,11 @@ function connectToEndpoint(t, endpoint) {
|
|
|
48
49
|
});
|
|
49
50
|
};
|
|
50
51
|
registerEndpointAbort(t, endpoint, teardown);
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
groupId: t.localGroupId ?? "",
|
|
55
|
-
primaryTerm: t.localPrimaryTerm ?? 0n,
|
|
56
|
-
protocolVersion: t.localProtocolVersion ?? ""
|
|
52
|
+
const endOnError = (err) => {
|
|
53
|
+
if (!closed && refusalCodeOf(err) !== null) noteEndpointRefused(t, endpoint);
|
|
54
|
+
teardown();
|
|
57
55
|
};
|
|
56
|
+
const hello = localHello(t);
|
|
58
57
|
replicateStream.write({ hello });
|
|
59
58
|
syncStream.write({ hello });
|
|
60
59
|
replicateStream.on("data", (msg) => {
|
|
@@ -67,14 +66,11 @@ function connectToEndpoint(t, endpoint) {
|
|
|
67
66
|
const peerRole = msg.hello.role;
|
|
68
67
|
t.clientPeerStreams.set(replicatePeerId, entry);
|
|
69
68
|
noteEndpointReachable(t, endpoint);
|
|
70
|
-
registerPeer(t.connectedPeers, t.peerConnectedHandler, replicatePeerId, peerRole,
|
|
71
|
-
groupId: msg.hello.groupId || void 0,
|
|
72
|
-
primaryTerm: msg.hello.primaryTerm === 0n ? void 0 : msg.hello.primaryTerm,
|
|
73
|
-
protocolVersion: msg.hello.protocolVersion || void 0
|
|
74
|
-
});
|
|
69
|
+
registerPeer(t.connectedPeers, t.peerConnectedHandler, replicatePeerId, peerRole, peerHelloFields(msg.hello));
|
|
75
70
|
return;
|
|
76
71
|
}
|
|
77
72
|
if (msg.batch) {
|
|
73
|
+
noteEndpointServing(t, endpoint);
|
|
78
74
|
t.batchHandler?.(fromBatchPayload(msg.batch), replicatePeerId).catch(() => {
|
|
79
75
|
});
|
|
80
76
|
} else if (msg.ack) {
|
|
@@ -82,7 +78,7 @@ function connectToEndpoint(t, endpoint) {
|
|
|
82
78
|
}
|
|
83
79
|
});
|
|
84
80
|
replicateStream.on("end", teardown);
|
|
85
|
-
replicateStream.on("error",
|
|
81
|
+
replicateStream.on("error", endOnError);
|
|
86
82
|
syncStream.on("data", (msg) => {
|
|
87
83
|
if (syncPeerId === null) {
|
|
88
84
|
if (!msg.hello || !t.validateTlsIdentity(syncStream, msg.hello.nodeId)) {
|
|
@@ -92,6 +88,7 @@ function connectToEndpoint(t, endpoint) {
|
|
|
92
88
|
syncPeerId = msg.hello.nodeId;
|
|
93
89
|
return;
|
|
94
90
|
}
|
|
91
|
+
if (msg.syncBatch || msg.syncComplete) noteEndpointServing(t, endpoint);
|
|
95
92
|
if (msg.syncRequest) {
|
|
96
93
|
t.syncRequestHandler?.(fromSyncRequestPayload(msg.syncRequest), syncPeerId).catch(() => {
|
|
97
94
|
});
|
|
@@ -106,7 +103,7 @@ function connectToEndpoint(t, endpoint) {
|
|
|
106
103
|
}
|
|
107
104
|
});
|
|
108
105
|
syncStream.on("end", teardown);
|
|
109
|
-
syncStream.on("error",
|
|
106
|
+
syncStream.on("error", endOnError);
|
|
110
107
|
}
|
|
111
108
|
|
|
112
109
|
export { connectToEndpoint };
|