@delali/sirannon-db 0.2.3-next.38 → 0.3.0

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 (41) hide show
  1. package/dist/backup/index.d.ts +5 -5
  2. package/dist/backup/index.mjs +4 -4
  3. package/dist/backup-scheduler/index.d.ts +58 -8
  4. package/dist/backup-scheduler/index.mjs +3 -2
  5. package/dist/{change-tracker-BLKRX8ic.d.ts → change-tracker-uG9gWvsY.d.ts} +1 -1
  6. package/dist/chunk-2QCPVE67.mjs +143 -0
  7. package/dist/{chunk-P53I2G6J.mjs → chunk-63VBFSCS.mjs} +2 -2
  8. package/dist/{chunk-CR6HTXSC.mjs → chunk-7SC2TZAS.mjs} +1 -1
  9. package/dist/chunk-DVS4QSIZ.mjs +124 -0
  10. package/dist/{chunk-IPDHHIG2.mjs → chunk-DXXCLTFF.mjs} +205 -45
  11. package/dist/{chunk-G7AXGAEA.mjs → chunk-OJQHM4MT.mjs} +202 -100
  12. package/dist/{chunk-KSYFFYAP.mjs → chunk-U445A3C4.mjs} +1 -70
  13. package/dist/{chunk-GJ6SX53Z.mjs → chunk-W77EEVOT.mjs} +4 -120
  14. package/dist/client/index.d.ts +8 -8
  15. package/dist/client/topology.d.ts +4 -4
  16. package/dist/{client-base-DldBgJWS.d.ts → client-base-C-B8u5Lm.d.ts} +2 -2
  17. package/dist/core/index.d.ts +9 -9
  18. package/dist/core/index.mjs +14 -5
  19. package/dist/{database-DSbJl-k5.d.ts → database-BlhUdJAO.d.ts} +7 -6
  20. package/dist/{database-backup-CliAhaEm.d.ts → database-backup-C090lMHc.d.ts} +2 -2
  21. package/dist/driver/better-sqlite3.d.ts +1 -1
  22. package/dist/driver/better-sqlite3.mjs +7 -6
  23. package/dist/driver/bun.d.ts +1 -1
  24. package/dist/driver/expo.d.ts +1 -1
  25. package/dist/driver/node.d.ts +1 -1
  26. package/dist/driver/node.mjs +7 -6
  27. package/dist/driver/wa-sqlite.d.ts +1 -1
  28. package/dist/file-migrations/index.d.ts +1 -1
  29. package/dist/{protocol-BK0Yxu5P.d.ts → protocol-QekTGoKn.d.ts} +1 -1
  30. package/dist/replication/index.d.ts +7 -7
  31. package/dist/{restore-options-O5gYlr3n.d.ts → restore-options-C9MW9gpm.d.ts} +1 -1
  32. package/dist/server/index.d.ts +9 -9
  33. package/dist/server/index.mjs +5 -4
  34. package/dist/{server-options-CFgBQDLz.d.ts → server-options-DeK6ZG8u.d.ts} +1 -1
  35. package/dist/{sirannon-Bo0PX2Cj.d.ts → sirannon-C9cTgFrw.d.ts} +2 -2
  36. package/dist/transport/grpc.d.ts +3 -3
  37. package/dist/transport/memory.d.ts +3 -3
  38. package/dist/{types-CL8uxXIV.d.ts → types-C1yBGTdx.d.ts} +2 -2
  39. package/dist/{types-CopTgHG3.d.ts → types-DApb7dvw.d.ts} +151 -5
  40. package/package.json +8 -7
  41. package/dist/chunk-GBM4AU24.mjs +0 -42
@@ -1,8 +1,8 @@
1
- import { B as BackupDestination, f as BackupRunReport } from '../types-CopTgHG3.js';
2
- export { g as BackupChain, h as BackupChainBase, i as BackupChainChange, j as BackupChainPosition, k as BackupChainRecord, l as BackupCycle, m as BackupCycleError, n as BackupCycleOptions, o as BackupCycleStatus, p as BackupGroupMembership, q as BackupGroupSource, r as BackupNodePreference, s as BackupPiece, t as BackupProgress, u as BackupSkip, v as BackupSkipReason, w as BackupToDestinationOptions, x as BackupVerifyResult, y as DEFAULT_CHAIN_NAME, z as chainLogName, C as readBackupChains, E as verifyBackupRecord } from '../types-CopTgHG3.js';
3
- export { B as BackupCapabilities, a as BackupChainLocation, b as BackupRestorePlan, c as BackupSafeToDeleteOptions, d as backupPiecesSafeToDelete, p as planBackupRestore } from '../database-backup-CliAhaEm.js';
4
- import { B as BackupRestoreOptions, a as BackupRestoreReport } from '../restore-options-O5gYlr3n.js';
5
- export { b as BackupRestoreProgress } from '../restore-options-O5gYlr3n.js';
1
+ import { B as BackupDestination, f as BackupRunReport } from '../types-DApb7dvw.js';
2
+ export { g as BackupChain, h as BackupChainBase, i as BackupChainChange, j as BackupChainPosition, k as BackupChainRecord, l as BackupCycle, m as BackupCycleError, n as BackupCycleOptions, o as BackupCycleStatus, p as BackupFileReport, q as BackupGroupMembership, r as BackupGroupSource, s as BackupLogPosition, t as BackupNodePreference, u as BackupPiece, v as BackupProgress, w as BackupSkip, x as BackupSkipReason, y as BackupToDestinationOptions, z as BackupVerifyResult, C as DEFAULT_CHAIN_NAME, E as chainLogName, F as readBackupChains, G as verifyBackupRecord } from '../types-DApb7dvw.js';
3
+ export { B as BackupCapabilities, a as BackupChainLocation, b as BackupRestorePlan, c as BackupSafeToDeleteOptions, d as backupPiecesSafeToDelete, p as planBackupRestore } from '../database-backup-C090lMHc.js';
4
+ import { B as BackupRestoreOptions, a as BackupRestoreReport } from '../restore-options-C9MW9gpm.js';
5
+ export { b as BackupRestoreProgress } from '../restore-options-C9MW9gpm.js';
6
6
  import '../query-types-Cv8N7pXj.js';
7
7
 
8
8
  /** What one assembled file took to build.
@@ -1,8 +1,8 @@
1
- export { assembleFromDestination, restoreBackup } from '../chunk-P53I2G6J.mjs';
1
+ export { assembleFromDestination, restoreBackup } from '../chunk-63VBFSCS.mjs';
2
2
  export { backupPiecesSafeToDelete, planBackupRestore } from '../chunk-YNTDOFTN.mjs';
3
- export { verifyBackupRecord } from '../chunk-CR6HTXSC.mjs';
4
- import '../chunk-KSYFFYAP.mjs';
5
- import '../chunk-GBM4AU24.mjs';
3
+ export { verifyBackupRecord } from '../chunk-7SC2TZAS.mjs';
4
+ import '../chunk-U445A3C4.mjs';
5
+ import '../chunk-DVS4QSIZ.mjs';
6
6
  export { DEFAULT_CHAIN_NAME, chainLogName, readBackupChains } from '../chunk-YVF2ZJTE.mjs';
7
7
  import '../chunk-V5FUJEYY.mjs';
8
8
  import '../chunk-PBRXXISQ.mjs';
@@ -1,4 +1,5 @@
1
- import { G as SQLiteConnection, H as BackupRunRequest, f as BackupRunReport, I as BackupScheduleOptions } from '../types-CopTgHG3.js';
1
+ import { I as SQLiteConnection, J as BackupFileCopy, K as BackupRunRequest, f as BackupRunReport, L as BackupScheduleRequest } from '../types-DApb7dvw.js';
2
+ export { p as BackupFileReport, O as BackupScheduleOptions, R as RunExclusive } from '../types-DApb7dvw.js';
2
3
  import '../query-types-Cv8N7pXj.js';
3
4
 
4
5
  /** What one runtime needs before a copy can reach a destination without a local file.
@@ -14,15 +15,44 @@ interface BackupStreamingSupport {
14
15
  declare class BackupManager {
15
16
  private readonly streaming?;
16
17
  constructor(streaming?: BackupStreamingSupport | undefined);
17
- backup(conn: SQLiteConnection, destPath: string, onFirstStep?: () => void): Promise<void>;
18
+ /**
19
+ * Copies the database behind a connection to a file, while that database
20
+ * stays open for reads and writes.
21
+ *
22
+ * @param conn - Connection the copy runs on, which must be the connection that writes.
23
+ * @param destPath - Path to write the copy to. A file already there stops the copy.
24
+ * @param onFirstStep - Called once the copy's first step is done, so the caller can hand the writer back.
25
+ * @returns What the copy moved, how long it took, and how often it restarted.
26
+ */
27
+ backup(conn: SQLiteConnection, destPath: string, onFirstStep?: () => void): Promise<BackupFileCopy>;
28
+ /**
29
+ * Reads the size of the file a copy wrote.
30
+ *
31
+ * @param resolved - Absolute path of that file.
32
+ * @param destPath - Path the caller named, which the error states.
33
+ * @returns Bytes it holds.
34
+ */
35
+ private fileBytes;
36
+ /**
37
+ * Runs the copy, and removes the file it was writing where that copy fails, so
38
+ * that a database missing its later pages never stays on disk as though the
39
+ * copy had finished. Sirannon would otherwise count that half-written file
40
+ * among the copies it keeps, and it would evict a whole copy to make room for
41
+ * one nothing can restore.
42
+ *
43
+ * Sirannon stops waiting on a copy once the stall deadline passes, and SQLite
44
+ * keeps writing that copy to the file. A removal that raced a live copy would
45
+ * leave a truncated file in place, and on Windows it would fail outright.
46
+ * Sirannon therefore removes the file only once the copy stops, while the
47
+ * caller receives the failure at once and waits for none of that.
48
+ */
49
+ private copyOrClearUp;
18
50
  copyToDestination(conn: SQLiteConnection, request: BackupRunRequest): Promise<BackupRunReport>;
19
51
  streamsToDestination(): boolean;
20
52
  generateFilename(): string;
21
53
  rotate(dir: string, maxFiles: number): void;
22
54
  }
23
55
 
24
- /** Runs a backup while the caller's writer lock is held, so it never shares the writer connection with another write. */
25
- type RunExclusive = (op: () => Promise<void>) => Promise<void>;
26
56
  /**
27
57
  * @public
28
58
  *
@@ -35,12 +65,32 @@ declare class BackupScheduler {
35
65
  * Starts repeating backups and returns a function that stops them.
36
66
  *
37
67
  * @param conn - Connection to the database being copied.
38
- * @param options - Cron expression, destination directory, retention, time zone, and failure callback.
39
- * @param runExclusive - Runs each copy with the database's writer held so that no write commits mid-copy.
68
+ * @param request - Cron expression, destination directory, retention, and time zone, along with the callbacks and the database the copies come from.
40
69
  * @returns A function that stops the schedule.
41
70
  */
42
- schedule(conn: SQLiteConnection, options: BackupScheduleOptions, runExclusive?: RunExclusive): () => void;
71
+ schedule(conn: SQLiteConnection, request: BackupScheduleRequest): () => void;
72
+ /**
73
+ * Works out the database and the file every report names. A caller may supply
74
+ * either of them, and Sirannon reads the file SQLite has open where the caller
75
+ * named none. Sirannon puts that question to the connection that writes, so it
76
+ * asks with nothing else holding the writer.
77
+ *
78
+ * @param conn - Connection the copies come from.
79
+ * @param run - The schedule, which holds whichever of the two the caller named.
80
+ * @param runExclusive - Runs the question with nothing else holding the writer.
81
+ * @returns The database identifier and the path of the file the copies come from.
82
+ * @throws A `BACKUP_ERROR` where the caller named no source file and SQLite has none open.
83
+ */
84
+ private namesFor;
85
+ /**
86
+ * Passes one finished copy to the caller's completion callback, and stops
87
+ * waiting on that callback once its deadline passes.
88
+ *
89
+ * @param report - What the copy produced.
90
+ * @param run - The schedule, which holds the callback and its deadline.
91
+ */
92
+ private handToCaller;
43
93
  private run;
44
94
  }
45
95
 
46
- export { BackupScheduleOptions, BackupScheduler };
96
+ export { BackupScheduleRequest, BackupScheduler };
@@ -1,5 +1,6 @@
1
- export { BackupScheduler } from '../chunk-IPDHHIG2.mjs';
1
+ export { BackupScheduler } from '../chunk-DXXCLTFF.mjs';
2
2
  import '../chunk-W67A4H2E.mjs';
3
- import '../chunk-GBM4AU24.mjs';
3
+ import '../chunk-2QCPVE67.mjs';
4
+ import '../chunk-DVS4QSIZ.mjs';
4
5
  import '../chunk-V5FUJEYY.mjs';
5
6
  import '../chunk-PBRXXISQ.mjs';
@@ -1,4 +1,4 @@
1
- import { G as SQLiteConnection } from './types-CopTgHG3.js';
1
+ import { I as SQLiteConnection } from './types-DApb7dvw.js';
2
2
  import { C as ChangeEvent } from './query-types-Cv8N7pXj.js';
3
3
 
4
4
  /**
@@ -0,0 +1,143 @@
1
+ import { LOG_HEADER_BYTES, readLogHeader, logFrameOffset, readValidLogFrame } from './chunk-DVS4QSIZ.mjs';
2
+ import { SirannonError } from './chunk-PBRXXISQ.mjs';
3
+ import { open } from 'fs/promises';
4
+
5
+ var READ_CHUNK_BYTES = 4 * 1024 * 1024;
6
+ async function openForReading(path) {
7
+ try {
8
+ return await open(path, "r");
9
+ } catch (err) {
10
+ if (err.code === "ENOENT") return void 0;
11
+ throw err;
12
+ }
13
+ }
14
+ async function readLogFileHeader(logPath) {
15
+ const file = await openForReading(logPath);
16
+ if (!file) return void 0;
17
+ try {
18
+ const buffer = Buffer.allocUnsafe(LOG_HEADER_BYTES);
19
+ const { bytesRead } = await file.read(buffer, 0, LOG_HEADER_BYTES, 0);
20
+ if (bytesRead < LOG_HEADER_BYTES) return void 0;
21
+ return readLogHeader(buffer);
22
+ } finally {
23
+ await file.close();
24
+ }
25
+ }
26
+ async function scanLogFrames(logPath, header, from) {
27
+ const stopped = {
28
+ lastCommitFrame: from.frame,
29
+ endOffset: logFrameOffset(from.frame + 1, header.frameBytes),
30
+ checksum: from.checksum
31
+ };
32
+ const file = await openForReading(logPath);
33
+ if (!file) return stopped;
34
+ try {
35
+ const { size } = await file.stat();
36
+ const framesInFile = Math.max(0, Math.floor((size - LOG_HEADER_BYTES) / header.frameBytes));
37
+ const framesPerChunk = Math.max(1, Math.floor(READ_CHUNK_BYTES / header.frameBytes));
38
+ const buffer = Buffer.allocUnsafe(framesPerChunk * header.frameBytes);
39
+ const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
40
+ let running = from.checksum;
41
+ let found = stopped;
42
+ let frame = from.frame + 1;
43
+ while (frame <= framesInFile) {
44
+ const frames = Math.min(framesPerChunk, framesInFile - frame + 1);
45
+ const wanted = frames * header.frameBytes;
46
+ let filled = 0;
47
+ while (filled < wanted) {
48
+ const { bytesRead } = await file.read(
49
+ buffer,
50
+ filled,
51
+ wanted - filled,
52
+ logFrameOffset(frame, header.frameBytes) + filled
53
+ );
54
+ if (bytesRead === 0) break;
55
+ filled += bytesRead;
56
+ }
57
+ const readable = Math.floor(filled / header.frameBytes);
58
+ if (readable === 0) break;
59
+ for (let inChunk = 0; inChunk < readable; inChunk++) {
60
+ const read = readValidLogFrame(view, inChunk * header.frameBytes, header, running);
61
+ if (!read) return found;
62
+ running = read.checksum;
63
+ if (read.frame.databasePages !== 0) {
64
+ found = {
65
+ lastCommitFrame: frame + inChunk,
66
+ endOffset: logFrameOffset(frame + inChunk + 1, header.frameBytes),
67
+ checksum: running
68
+ };
69
+ }
70
+ }
71
+ frame += readable;
72
+ }
73
+ return found;
74
+ } finally {
75
+ await file.close();
76
+ }
77
+ }
78
+ async function copyLogRange(logPath, startOffset, endOffset, destPath) {
79
+ const source = await open(logPath, "r");
80
+ try {
81
+ const dest = await open(destPath, "w");
82
+ try {
83
+ const buffer = Buffer.allocUnsafe(Math.min(READ_CHUNK_BYTES, Math.max(endOffset - startOffset, 1)));
84
+ let at = startOffset;
85
+ let written = 0;
86
+ while (at < endOffset) {
87
+ const wanted = Math.min(buffer.byteLength, endOffset - at);
88
+ const { bytesRead } = await source.read(buffer, 0, wanted, at);
89
+ if (bytesRead === 0) {
90
+ throw new SirannonError(
91
+ `The write-ahead log '${logPath}' ends at byte ${at} while the frames captured from it run to byte ${endOffset}, so it lost frames while Sirannon was reading it. Another connection checkpointed the log. Route every write and every checkpoint through Sirannon, and take a fresh full copy so a new chain starts from a known state.`,
92
+ "BACKUP_LOG_REWOUND"
93
+ );
94
+ }
95
+ let put = 0;
96
+ while (put < bytesRead) {
97
+ const { bytesWritten } = await dest.write(buffer, put, bytesRead - put);
98
+ if (bytesWritten === 0) {
99
+ throw new SirannonError(
100
+ `The capture of '${logPath}' could write no more than ${written + put} of its ${endOffset - startOffset} bytes into '${destPath}'. Check the free space and the permissions on that directory.`,
101
+ "BACKUP_ERROR"
102
+ );
103
+ }
104
+ put += bytesWritten;
105
+ }
106
+ at += bytesRead;
107
+ written += bytesRead;
108
+ }
109
+ return written;
110
+ } finally {
111
+ await dest.close();
112
+ }
113
+ } finally {
114
+ await source.close();
115
+ }
116
+ }
117
+ function logPathFor(sourcePath) {
118
+ return `${sourcePath}-wal`;
119
+ }
120
+ async function readLogPosition(sourcePath) {
121
+ const logPath = logPathFor(sourcePath);
122
+ try {
123
+ const header = await readLogFileHeader(logPath);
124
+ if (!header) return void 0;
125
+ const scan = await scanLogFrames(logPath, header, { frame: 0, checksum: header.checksum });
126
+ return {
127
+ logSequence: header.logSequence,
128
+ salt1: header.salt1,
129
+ salt2: header.salt2,
130
+ lastFrame: scan.lastCommitFrame
131
+ };
132
+ } catch {
133
+ return void 0;
134
+ }
135
+ }
136
+ function cursorChecksum(cursor) {
137
+ return { first: cursor.checksum1, second: cursor.checksum2 };
138
+ }
139
+ function sameLog(header, cursor) {
140
+ return header.salt1 === cursor.salt1 && header.salt2 === cursor.salt2;
141
+ }
142
+
143
+ export { copyLogRange, cursorChecksum, logPathFor, readLogFileHeader, readLogPosition, sameLog, scanLogFrames };
@@ -1,6 +1,6 @@
1
1
  import { planBackupRestore } from './chunk-YNTDOFTN.mjs';
2
- import { listStoredFilePieces, fetchStoredFile, checkpointLog, LOG_HEADER_BYTES, LOG_FRAME_HEADER_BYTES, readLogHeader, foldLogChecksum, MAGIC_WITH_LITTLE_ENDIAN_CONTENT, LOG_FORMAT_VERSION } from './chunk-KSYFFYAP.mjs';
3
- import { destinationWithDeadline, DEFAULT_DESTINATION_TIMEOUT_MS } from './chunk-GBM4AU24.mjs';
2
+ import { listStoredFilePieces, fetchStoredFile, checkpointLog } from './chunk-U445A3C4.mjs';
3
+ import { destinationWithDeadline, DEFAULT_DESTINATION_TIMEOUT_MS, LOG_HEADER_BYTES, LOG_FRAME_HEADER_BYTES, readLogHeader, foldLogChecksum, MAGIC_WITH_LITTLE_ENDIAN_CONTENT, LOG_FORMAT_VERSION } from './chunk-DVS4QSIZ.mjs';
4
4
  import { readBackupChains, DEFAULT_CHAIN_NAME } from './chunk-YVF2ZJTE.mjs';
5
5
  import { randomHex } from './chunk-V5FUJEYY.mjs';
6
6
  import { SirannonError } from './chunk-PBRXXISQ.mjs';
@@ -1,4 +1,4 @@
1
- import { listStoredFilePieces, fetchStoredFile } from './chunk-KSYFFYAP.mjs';
1
+ import { listStoredFilePieces, fetchStoredFile } from './chunk-U445A3C4.mjs';
2
2
  import { SirannonError } from './chunk-PBRXXISQ.mjs';
3
3
 
4
4
  // src/core/backup/verify.ts
@@ -0,0 +1,124 @@
1
+ import { SirannonError } from './chunk-PBRXXISQ.mjs';
2
+
3
+ // src/core/backup/destination-deadline.ts
4
+ var DEFAULT_DESTINATION_TIMEOUT_MS = 6e5;
5
+ var LONGEST_DEADLINE_MS = 2147483647;
6
+ function assertDeadline(timeoutMs, subject) {
7
+ if (!Number.isFinite(timeoutMs) || timeoutMs < 0) {
8
+ throw new SirannonError(
9
+ `${subject} must be a number of milliseconds that is zero or above, and it was ${timeoutMs}`,
10
+ "BACKUP_ERROR"
11
+ );
12
+ }
13
+ if (timeoutMs > LONGEST_DEADLINE_MS) {
14
+ throw new SirannonError(
15
+ `${subject} must be no longer than ${LONGEST_DEADLINE_MS}ms, which is the longest a timer holds, and it was ${timeoutMs}`,
16
+ "BACKUP_ERROR"
17
+ );
18
+ }
19
+ }
20
+ async function withinDeadline(operation, timeoutMs, timedOut) {
21
+ let timer = null;
22
+ const deadline = new Promise((_, reject) => {
23
+ timer = setTimeout(() => reject(timedOut()), timeoutMs);
24
+ });
25
+ try {
26
+ return await Promise.race([operation, deadline]);
27
+ } finally {
28
+ if (timer) clearTimeout(timer);
29
+ }
30
+ }
31
+ function destinationWithin(operation, action, timeoutMs) {
32
+ return withinDeadline(
33
+ operation,
34
+ timeoutMs,
35
+ () => new SirannonError(
36
+ `The destination did not ${action} within ${timeoutMs}ms, so the run stopped`,
37
+ "BACKUP_DESTINATION_ERROR"
38
+ )
39
+ );
40
+ }
41
+ function destinationWithDeadline(destination, timeoutMs) {
42
+ assertDeadline(timeoutMs, "The destination deadline");
43
+ if (timeoutMs === 0) return destination;
44
+ const claim = destination.writePieceIfAbsent?.bind(destination);
45
+ return {
46
+ writePiece: (name, index, bytes) => destinationWithin(destination.writePiece(name, index, bytes), `store piece ${index} of '${name}'`, timeoutMs),
47
+ ...claim ? {
48
+ writePieceIfAbsent: (name, index, bytes) => destinationWithin(claim(name, index, bytes), `claim piece ${index} of '${name}'`, timeoutMs)
49
+ } : {},
50
+ readPiece: (name, index) => destinationWithin(destination.readPiece(name, index), `return piece ${index} of '${name}'`, timeoutMs),
51
+ listPieces: (name) => destinationWithin(destination.listPieces(name), `list the pieces of '${name}'`, timeoutMs)
52
+ };
53
+ }
54
+
55
+ // src/core/backup/wal-format.ts
56
+ var MAGIC_WITH_LITTLE_ENDIAN_CONTENT = 931071618;
57
+ var MAGIC_WITH_BIG_ENDIAN_CONTENT = 931071619;
58
+ var LOG_FORMAT_VERSION = 3007e3;
59
+ var SMALLEST_PAGE_BYTES = 512;
60
+ var LARGEST_PAGE_BYTES = 65536;
61
+ var LOG_HEADER_BYTES = 32;
62
+ var LOG_FRAME_HEADER_BYTES = 24;
63
+ function foldLogChecksum(view, offset, byteLength, bigEndianContent, seed) {
64
+ const littleEndian = !bigEndianContent;
65
+ const end = offset + byteLength;
66
+ let first = seed.first;
67
+ let second = seed.second;
68
+ for (let at = offset; at < end; at += 8) {
69
+ first = first + view.getUint32(at, littleEndian) + second >>> 0;
70
+ second = second + view.getUint32(at + 4, littleEndian) + first >>> 0;
71
+ }
72
+ return { first, second };
73
+ }
74
+ function readLogHeader(bytes) {
75
+ if (bytes.byteLength < LOG_HEADER_BYTES) return void 0;
76
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
77
+ const magic = view.getUint32(0, false);
78
+ if (magic !== MAGIC_WITH_LITTLE_ENDIAN_CONTENT && magic !== MAGIC_WITH_BIG_ENDIAN_CONTENT) return void 0;
79
+ const bigEndianContent = magic === MAGIC_WITH_BIG_ENDIAN_CONTENT;
80
+ const pageSize = view.getUint32(8, false);
81
+ if (pageSize < SMALLEST_PAGE_BYTES || pageSize > LARGEST_PAGE_BYTES || (pageSize & pageSize - 1) !== 0) {
82
+ return void 0;
83
+ }
84
+ const stored = { first: view.getUint32(24, false), second: view.getUint32(28, false) };
85
+ const computed = foldLogChecksum(view, 0, 24, bigEndianContent, { first: 0, second: 0 });
86
+ if (computed.first !== stored.first || computed.second !== stored.second) return void 0;
87
+ return {
88
+ pageSize,
89
+ frameBytes: LOG_FRAME_HEADER_BYTES + pageSize,
90
+ logSequence: view.getUint32(12, false),
91
+ salt1: view.getUint32(16, false),
92
+ salt2: view.getUint32(20, false),
93
+ checksum: stored,
94
+ bigEndianContent
95
+ };
96
+ }
97
+ function readLogFrameHeader(view, offset) {
98
+ return {
99
+ pageNumber: view.getUint32(offset, false),
100
+ databasePages: view.getUint32(offset + 4, false),
101
+ salt1: view.getUint32(offset + 8, false),
102
+ salt2: view.getUint32(offset + 12, false),
103
+ checksum: { first: view.getUint32(offset + 16, false), second: view.getUint32(offset + 20, false) }
104
+ };
105
+ }
106
+ function readValidLogFrame(view, offset, header, seed) {
107
+ const frame = readLogFrameHeader(view, offset);
108
+ if (frame.salt1 !== header.salt1 || frame.salt2 !== header.salt2) return void 0;
109
+ const overHeader = foldLogChecksum(view, offset, 8, header.bigEndianContent, seed);
110
+ const checksum = foldLogChecksum(
111
+ view,
112
+ offset + LOG_FRAME_HEADER_BYTES,
113
+ header.pageSize,
114
+ header.bigEndianContent,
115
+ overHeader
116
+ );
117
+ if (checksum.first !== frame.checksum.first || checksum.second !== frame.checksum.second) return void 0;
118
+ return { frame, checksum };
119
+ }
120
+ function logFrameOffset(frameNumber, frameBytes) {
121
+ return LOG_HEADER_BYTES + (frameNumber - 1) * frameBytes;
122
+ }
123
+
124
+ export { DEFAULT_DESTINATION_TIMEOUT_MS, LOG_FORMAT_VERSION, LOG_FRAME_HEADER_BYTES, LOG_HEADER_BYTES, MAGIC_WITH_LITTLE_ENDIAN_CONTENT, assertDeadline, destinationWithDeadline, foldLogChecksum, logFrameOffset, readLogHeader, readValidLogFrame, withinDeadline };